@cedarjs/codemods 3.0.0-canary.13607 → 3.0.0-canary.13610

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,72 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { getSchemaPath, getPaths } from "@cedarjs/project-config";
4
+ import runTransform from "../../../lib/runTransform.js";
5
+ import rewriteRemainingImports from "./rewriteRemainingImports.js";
6
+ import { updateApiPackageJson } from "./updateApiPackageJson.js";
7
+ import { checkDotEnv, updateEnvDefaults } from "./updateEnvDefaults.js";
8
+ import { updateGitignore } from "./updateGitignore.js";
9
+ import { updatePrismaConfig } from "./updatePrismaConfig.js";
10
+ import runUpdateSchemaFile from "./updateSchemaFile.js";
11
+ import { updateTsConfigs } from "./updateTsConfigs.js";
12
+ function detectProvider(schemaPath) {
13
+ if (!fs.existsSync(schemaPath)) {
14
+ return "sqlite";
15
+ }
16
+ const source = fs.readFileSync(schemaPath, "utf-8");
17
+ const match = source.match(
18
+ /datasource\s+\w+\s*\{[^}]*provider\s*=\s*["']([^"']+)["']/
19
+ );
20
+ return match ? match[1].toLowerCase() : "sqlite";
21
+ }
22
+ async function getPrismaV7Context() {
23
+ const paths = getPaths();
24
+ const schemaPath = await getSchemaPath(paths.api.prismaConfig);
25
+ const provider = detectProvider(schemaPath);
26
+ const isSqlite = provider === "sqlite";
27
+ const dbPathTs = path.join(paths.api.lib, "db.ts");
28
+ const dbPathJs = path.join(paths.api.lib, "db.js");
29
+ let dbFilePath = null;
30
+ if (fs.existsSync(dbPathTs)) {
31
+ dbFilePath = dbPathTs;
32
+ } else if (fs.existsSync(dbPathJs)) {
33
+ dbFilePath = dbPathJs;
34
+ }
35
+ return { isSqlite, paths, dbFilePath };
36
+ }
37
+ async function prismaV7() {
38
+ const context = await getPrismaV7Context();
39
+ const { paths, isSqlite } = context;
40
+ await runUpdateSchemaFile();
41
+ await updatePrismaConfig(paths.api.prismaConfig);
42
+ if (context.dbFilePath) {
43
+ await runTransform({
44
+ transformPath: path.join(import.meta.dirname, "updateDbFile.js"),
45
+ targetPaths: [context.dbFilePath],
46
+ parser: "ts",
47
+ options: {
48
+ isSqlite
49
+ }
50
+ });
51
+ }
52
+ await rewriteRemainingImports();
53
+ if (isSqlite) {
54
+ await updateApiPackageJson(path.join(paths.api.base, "package.json"));
55
+ }
56
+ await updateTsConfigs({
57
+ apiTsConfig: path.join(paths.api.base, "tsconfig.json"),
58
+ scriptsTsConfig: path.join(paths.base, "scripts", "tsconfig.json"),
59
+ webTsConfig: path.join(paths.web.base, "tsconfig.json")
60
+ });
61
+ await updateGitignore(path.join(paths.base, ".gitignore"));
62
+ await updateEnvDefaults(path.join(paths.base, ".env.defaults"));
63
+ const dotEnvWarning = checkDotEnv(path.join(paths.base, ".env"));
64
+ if (dotEnvWarning) {
65
+ console.warn(`
66
+ Warning: ${dotEnvWarning}`);
67
+ }
68
+ }
69
+ export {
70
+ prismaV7 as default,
71
+ getPrismaV7Context
72
+ };
@@ -0,0 +1,164 @@
1
+ import path from "node:path";
2
+ import task from "tasuku";
3
+ import runTransform from "../../../lib/runTransform.js";
4
+ import { getPrismaV7Context } from "./prismaV7.js";
5
+ import rewriteRemainingImports from "./rewriteRemainingImports.js";
6
+ import { updateApiPackageJson } from "./updateApiPackageJson.js";
7
+ import { checkDotEnv, updateEnvDefaults } from "./updateEnvDefaults.js";
8
+ import { updateGitignore } from "./updateGitignore.js";
9
+ import { updatePrismaConfig } from "./updatePrismaConfig.js";
10
+ import runUpdateSchemaFile from "./updateSchemaFile.js";
11
+ import { updateTsConfigs } from "./updateTsConfigs.js";
12
+ const command = "prisma-v7";
13
+ const description = "(v3.x) Upgrades your Cedar app to use Prisma v7 \u2014 updates schema.prisma, db.ts, prisma.config.cjs, and related config files";
14
+ const handler = async () => {
15
+ const context = await getPrismaV7Context();
16
+ const { paths, isSqlite, dbFilePath } = context;
17
+ await task("Prisma v7 Migration", async ({ task: task2 }) => {
18
+ await task2.group(
19
+ (task3) => [
20
+ task3("Update schema.prisma", async ({ setOutput }) => {
21
+ const { results } = await runUpdateSchemaFile();
22
+ for (const result of results) {
23
+ if (result.status === "skipped") {
24
+ setOutput(`Skipped \u2014 ${result.path} not found`);
25
+ } else if (result.status === "unmodified") {
26
+ setOutput("No changes needed (already migrated)");
27
+ } else {
28
+ setOutput(`Updated ${result.path}`);
29
+ }
30
+ for (const warning of result.warnings) {
31
+ console.warn(`
32
+ \u26A0\uFE0F ${warning}`);
33
+ }
34
+ }
35
+ if (results.length === 0) {
36
+ setOutput("Skipped \u2014 no schema.prisma found");
37
+ }
38
+ }),
39
+ task3("Update prisma.config.cjs", async ({ setOutput }) => {
40
+ const result = await updatePrismaConfig(paths.api.prismaConfig);
41
+ if (result === "skipped") {
42
+ setOutput("Skipped \u2014 prisma.config.cjs not found");
43
+ } else if (result === "unmodified") {
44
+ setOutput("No changes needed (already has datasource block)");
45
+ } else {
46
+ setOutput(`Updated ${paths.api.prismaConfig}`);
47
+ }
48
+ }),
49
+ task3("Update api/src/lib/db.{ts,js}", async ({ setOutput }) => {
50
+ if (!dbFilePath) {
51
+ setOutput(
52
+ "Skipped \u2014 no api/src/lib/db.ts or api/src/lib/db.js found"
53
+ );
54
+ return;
55
+ }
56
+ await runTransform({
57
+ transformPath: path.join(import.meta.dirname, "updateDbFile.js"),
58
+ targetPaths: [dbFilePath],
59
+ parser: "ts",
60
+ options: {
61
+ isSqlite
62
+ }
63
+ });
64
+ setOutput(`Updated ${dbFilePath}`);
65
+ if (!isSqlite) {
66
+ console.log(
67
+ "\n\u2139\uFE0F Non-SQLite database detected. The import paths in db.ts have\n been updated, but no driver adapter was added. If you want to\n use a Prisma driver adapter (recommended), add one manually.\n See: https://www.prisma.io/docs/orm/overview/databases/database-drivers"
68
+ );
69
+ }
70
+ })
71
+ ],
72
+ { concurrency: 1 }
73
+ );
74
+ await task2.group(
75
+ (task3) => [
76
+ task3(
77
+ "Rewrite remaining @prisma/client imports",
78
+ async ({ setOutput }) => {
79
+ await rewriteRemainingImports();
80
+ setOutput("Done");
81
+ }
82
+ ),
83
+ task3("Update api/package.json", async ({ setOutput }) => {
84
+ if (!isSqlite) {
85
+ setOutput(
86
+ "Skipped \u2014 non-SQLite project. Add your own driver adapter package."
87
+ );
88
+ return;
89
+ }
90
+ const pkgPath = path.join(paths.api.base, "package.json");
91
+ const result = await updateApiPackageJson(pkgPath);
92
+ if (result === "skipped") {
93
+ setOutput("Skipped \u2014 api/package.json not found");
94
+ } else if (result === "unmodified") {
95
+ setOutput("No changes needed (adapter already installed)");
96
+ } else {
97
+ setOutput(`Updated ${pkgPath}`);
98
+ }
99
+ }),
100
+ task3("Update tsconfig.json files", async ({ setOutput }) => {
101
+ const results = await updateTsConfigs({
102
+ apiTsConfig: path.join(paths.api.base, "tsconfig.json"),
103
+ scriptsTsConfig: path.join(paths.base, "scripts", "tsconfig.json"),
104
+ webTsConfig: path.join(paths.web.base, "tsconfig.json")
105
+ });
106
+ const updated = Object.entries(results).filter(([, status]) => status === "updated").map(([name]) => name);
107
+ if (updated.length === 0) {
108
+ setOutput("No changes needed");
109
+ } else {
110
+ setOutput(`Updated: ${updated.join(", ")}`);
111
+ }
112
+ }),
113
+ task3("Update .gitignore", async ({ setOutput }) => {
114
+ const gitignorePath = path.join(paths.base, ".gitignore");
115
+ const result = await updateGitignore(gitignorePath);
116
+ if (result === "skipped") {
117
+ setOutput("Skipped \u2014 .gitignore not found");
118
+ } else if (result === "unmodified") {
119
+ setOutput("No changes needed");
120
+ } else {
121
+ setOutput(`Updated ${gitignorePath}`);
122
+ }
123
+ }),
124
+ task3("Update .env.defaults", async ({ setOutput }) => {
125
+ const envDefaultsPath = path.join(paths.base, ".env.defaults");
126
+ const result = await updateEnvDefaults(envDefaultsPath);
127
+ if (result === "skipped") {
128
+ setOutput("Skipped \u2014 .env.defaults not found");
129
+ } else if (result === "unmodified") {
130
+ setOutput("No changes needed");
131
+ } else {
132
+ setOutput(`Updated ${envDefaultsPath}`);
133
+ }
134
+ const dotEnvWarning = checkDotEnv(path.join(paths.base, ".env"));
135
+ if (dotEnvWarning) {
136
+ console.warn(`
137
+ \u26A0\uFE0F ${dotEnvWarning}`);
138
+ }
139
+ })
140
+ ],
141
+ { concurrency: Infinity }
142
+ );
143
+ await task2("Next steps", async ({ setOutput }) => {
144
+ const steps = [
145
+ " 1. Run `yarn install` to install new dependencies",
146
+ " 2. Run `yarn cedar prisma generate` to generate the new Prisma client",
147
+ " 3. Run `yarn cedar prisma migrate dev` to verify migrations work",
148
+ " 4. Run `yarn cedar lint --fix` to fix any import ordering issues"
149
+ ];
150
+ if (!isSqlite) {
151
+ steps.push(
152
+ " 5. Add a Prisma driver adapter for your database to api/src/lib/db.ts",
153
+ " See: https://www.prisma.io/docs/orm/overview/databases/database-drivers"
154
+ );
155
+ }
156
+ setOutput("\n\n" + steps.join("\n"));
157
+ });
158
+ });
159
+ };
160
+ export {
161
+ command,
162
+ description,
163
+ handler
164
+ };
@@ -0,0 +1,61 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import {
4
+ ensurePosixPath,
5
+ getDataMigrationsPath,
6
+ getPaths
7
+ } from "@cedarjs/project-config";
8
+ const CODE_FILE_GLOB = "**/*.{ts,tsx,cts,mts,js,jsx,cjs,mjs}";
9
+ async function collectCodeFiles(dir) {
10
+ try {
11
+ return await Array.fromAsync(fs.promises.glob(CODE_FILE_GLOB, { cwd: dir }));
12
+ } catch (error) {
13
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
14
+ return [];
15
+ }
16
+ throw error;
17
+ }
18
+ }
19
+ async function rewritePrismaImportsInDirectory(dir, dbFilePath) {
20
+ const scriptsDir = ensurePosixPath(getPaths().scripts);
21
+ const normalizedDbFilePath = dbFilePath ? ensurePosixPath(dbFilePath) : null;
22
+ const fileMatches = await collectCodeFiles(dir);
23
+ const files = fileMatches.map((relativePath) => path.join(dir, relativePath)).filter((filePath) => ensurePosixPath(filePath) !== normalizedDbFilePath);
24
+ if (files.length === 0) {
25
+ return "skipped";
26
+ }
27
+ let anyUpdated = false;
28
+ for (const filePath of files) {
29
+ const source = await fs.promises.readFile(filePath, "utf-8");
30
+ const isScriptFile = ensurePosixPath(filePath).startsWith(scriptsDir);
31
+ const importPath = isScriptFile ? "api/src/lib/db" : "src/lib/db";
32
+ const importPattern = /(['"])@prisma\/client\1/g;
33
+ const transformed = source.replace(importPattern, `$1${importPath}$1`);
34
+ if (transformed !== source) {
35
+ await fs.promises.writeFile(filePath, transformed);
36
+ anyUpdated = true;
37
+ }
38
+ }
39
+ return anyUpdated ? "updated" : "skipped";
40
+ }
41
+ async function rewriteRemainingImports() {
42
+ const paths = getPaths();
43
+ const prismaConfigPath = paths.api.prismaConfig;
44
+ const dataMigrationsPath = await getDataMigrationsPath(prismaConfigPath);
45
+ const dbPathTs = path.join(paths.api.lib, "db.ts");
46
+ const dbPathJs = path.join(paths.api.lib, "db.js");
47
+ let dbFilePath = null;
48
+ if (fs.existsSync(dbPathTs)) {
49
+ dbFilePath = dbPathTs;
50
+ } else if (fs.existsSync(dbPathJs)) {
51
+ dbFilePath = dbPathJs;
52
+ }
53
+ const dirsToTransform = [paths.api.src, dataMigrationsPath, paths.scripts];
54
+ for (const dir of dirsToTransform) {
55
+ await rewritePrismaImportsInDirectory(dir, dbFilePath);
56
+ }
57
+ }
58
+ export {
59
+ rewriteRemainingImports as default,
60
+ rewritePrismaImportsInDirectory
61
+ };
@@ -0,0 +1,47 @@
1
+ import fs from "node:fs";
2
+ const ADAPTER_PACKAGE = "@prisma/adapter-better-sqlite3";
3
+ const SQLITE_PACKAGE = "better-sqlite3";
4
+ const ADAPTER_VERSION = "^7.0.0";
5
+ const SQLITE_VERSION = "^12.0.0";
6
+ function transformApiPackageJson(source, adapterVersion = ADAPTER_VERSION, sqliteVersion = SQLITE_VERSION) {
7
+ const pkg = JSON.parse(source);
8
+ if (pkg.dependencies?.[ADAPTER_PACKAGE]) {
9
+ return source;
10
+ }
11
+ pkg.dependencies = {
12
+ ...pkg.dependencies ?? {},
13
+ [ADAPTER_PACKAGE]: adapterVersion,
14
+ [SQLITE_PACKAGE]: sqliteVersion
15
+ };
16
+ const indentMatch = source.match(/^(\s+)"/m);
17
+ const indent = indentMatch ? indentMatch[1] : " ";
18
+ return JSON.stringify(pkg, null, indent.length) + "\n";
19
+ }
20
+ async function updateApiPackageJson(packageJsonPath, adapterVersion = ADAPTER_VERSION, sqliteVersion = SQLITE_VERSION) {
21
+ if (!fs.existsSync(packageJsonPath)) {
22
+ return "skipped";
23
+ }
24
+ const source = fs.readFileSync(packageJsonPath, "utf-8");
25
+ const pkg = JSON.parse(source);
26
+ if (pkg.dependencies?.[ADAPTER_PACKAGE]) {
27
+ return "unmodified";
28
+ }
29
+ const transformed = transformApiPackageJson(
30
+ source,
31
+ adapterVersion,
32
+ sqliteVersion
33
+ );
34
+ if (transformed === source) {
35
+ return "unmodified";
36
+ }
37
+ fs.writeFileSync(packageJsonPath, transformed, "utf-8");
38
+ return "updated";
39
+ }
40
+ export {
41
+ ADAPTER_PACKAGE,
42
+ ADAPTER_VERSION,
43
+ SQLITE_PACKAGE,
44
+ SQLITE_VERSION,
45
+ transformApiPackageJson,
46
+ updateApiPackageJson
47
+ };
@@ -0,0 +1,164 @@
1
+ const NEW_CLIENT_PATH = "api/db/generated/prisma/client.mts";
2
+ const OLD_PRISMA_CLIENT = "@prisma/client";
3
+ function transform(file, api, options = {}) {
4
+ const j = api.jscodeshift;
5
+ const root = j(file.source);
6
+ const alreadyMigrated = root.find(j.ImportDeclaration, { source: { value: NEW_CLIENT_PATH } }).length > 0;
7
+ if (alreadyMigrated) {
8
+ return file.source;
9
+ }
10
+ const isSqlite = options["isSqlite"] !== false;
11
+ let didTransform = false;
12
+ root.find(j.ImportDeclaration, { source: { value: OLD_PRISMA_CLIENT } }).forEach((nodePath) => {
13
+ nodePath.node.source = j.stringLiteral(NEW_CLIENT_PATH);
14
+ didTransform = true;
15
+ });
16
+ root.find(j.ExportAllDeclaration).filter((nodePath) => {
17
+ const src = nodePath.node.source?.value;
18
+ return src === OLD_PRISMA_CLIENT || src === "src/lib/db" || src === "api/src/lib/db";
19
+ }).forEach((nodePath) => {
20
+ nodePath.node.source = j.stringLiteral(NEW_CLIENT_PATH);
21
+ didTransform = true;
22
+ });
23
+ if (!didTransform) {
24
+ return file.source;
25
+ }
26
+ if (!isSqlite) {
27
+ return root.toSource({ quote: "single" });
28
+ }
29
+ const hasPathImport = root.find(j.ImportDeclaration, { source: { value: "node:path" } }).length > 0;
30
+ const hasAdapterImport = root.find(j.ImportDeclaration, {
31
+ source: { value: "@prisma/adapter-better-sqlite3" }
32
+ }).length > 0;
33
+ const hasGetPathsImport = root.find(j.ImportDeclaration, {
34
+ source: { value: "@cedarjs/project-config" }
35
+ }).length > 0;
36
+ const allImports = root.find(j.ImportDeclaration);
37
+ const firstImport = allImports.at(0);
38
+ if (!hasPathImport) {
39
+ const pathImport = j.importDeclaration(
40
+ [j.importDefaultSpecifier(j.identifier("path"))],
41
+ j.stringLiteral("node:path")
42
+ );
43
+ firstImport.insertBefore(pathImport);
44
+ }
45
+ const clientImport = root.find(j.ImportDeclaration, {
46
+ source: { value: NEW_CLIENT_PATH }
47
+ });
48
+ if (!hasAdapterImport) {
49
+ const adapterImport = j.importDeclaration(
50
+ [
51
+ j.importSpecifier(
52
+ j.identifier("PrismaBetterSqlite3"),
53
+ j.identifier("PrismaBetterSqlite3")
54
+ )
55
+ ],
56
+ j.stringLiteral("@prisma/adapter-better-sqlite3")
57
+ );
58
+ clientImport.insertBefore(adapterImport);
59
+ }
60
+ if (!hasGetPathsImport) {
61
+ const cedarjsImports = root.find(
62
+ j.ImportDeclaration,
63
+ (node) => String(node.source.value).startsWith("@cedarjs/")
64
+ );
65
+ const getPathsImport = j.importDeclaration(
66
+ [j.importSpecifier(j.identifier("getPaths"), j.identifier("getPaths"))],
67
+ j.stringLiteral("@cedarjs/project-config")
68
+ );
69
+ if (cedarjsImports.length > 0) {
70
+ cedarjsImports.at(-1).insertAfter(getPathsImport);
71
+ } else {
72
+ clientImport.insertAfter(getPathsImport);
73
+ }
74
+ }
75
+ const hasResolveSqliteUrl = root.find(j.VariableDeclarator, {
76
+ id: { type: "Identifier", name: "resolveSqliteUrl" }
77
+ }).length > 0;
78
+ const hasAdapter = root.find(j.VariableDeclarator, {
79
+ id: { type: "Identifier", name: "adapter" }
80
+ }).length > 0;
81
+ const prismaClientNewExpr = root.find(j.NewExpression, {
82
+ callee: { type: "Identifier", name: "PrismaClient" }
83
+ });
84
+ if (prismaClientNewExpr.length > 0 && (!hasResolveSqliteUrl || !hasAdapter)) {
85
+ const prismaClientDeclaration = prismaClientNewExpr.closest(
86
+ j.VariableDeclaration
87
+ );
88
+ if (!hasResolveSqliteUrl) {
89
+ const resolveSqliteUrlSource = `
90
+ const resolveSqliteUrl = (url = 'file:./db/dev.db') => {
91
+ if (!url.startsWith('file:.')) {
92
+ return url
93
+ }
94
+
95
+ return \`file:\${path.resolve(getPaths().api.base, url.slice('file:'.length))}\`
96
+ }
97
+ `;
98
+ const resolveSqliteUrlDecl = j(resolveSqliteUrlSource).find(j.VariableDeclaration).get().node;
99
+ prismaClientDeclaration.insertBefore(resolveSqliteUrlDecl);
100
+ }
101
+ if (!hasAdapter) {
102
+ const adapterDecl = j.variableDeclaration("const", [
103
+ j.variableDeclarator(
104
+ j.identifier("adapter"),
105
+ j.newExpression(j.identifier("PrismaBetterSqlite3"), [
106
+ j.objectExpression([
107
+ j.objectProperty(
108
+ j.identifier("url"),
109
+ j.callExpression(j.identifier("resolveSqliteUrl"), [
110
+ j.memberExpression(
111
+ j.memberExpression(
112
+ j.identifier("process"),
113
+ j.identifier("env")
114
+ ),
115
+ j.identifier("DATABASE_URL")
116
+ )
117
+ ])
118
+ )
119
+ ])
120
+ ])
121
+ )
122
+ ]);
123
+ prismaClientDeclaration.insertBefore(adapterDecl);
124
+ }
125
+ }
126
+ root.find(j.NewExpression, {
127
+ callee: { type: "Identifier", name: "PrismaClient" }
128
+ }).forEach((nodePath) => {
129
+ const args = nodePath.node.arguments;
130
+ if (args.length === 0) {
131
+ nodePath.node.arguments = [
132
+ j.objectExpression([
133
+ Object.assign(
134
+ j.objectProperty(
135
+ j.identifier("adapter"),
136
+ j.identifier("adapter")
137
+ ),
138
+ { shorthand: true }
139
+ )
140
+ ])
141
+ ];
142
+ return;
143
+ }
144
+ const firstArg = args[0];
145
+ if (firstArg.type !== "ObjectExpression") {
146
+ return;
147
+ }
148
+ const hasAdapterProp = firstArg.properties.some(
149
+ (prop) => prop.type === "ObjectProperty" && prop.key.type === "Identifier" && prop.key.name === "adapter"
150
+ );
151
+ if (!hasAdapterProp) {
152
+ firstArg.properties.push(
153
+ Object.assign(
154
+ j.objectProperty(j.identifier("adapter"), j.identifier("adapter")),
155
+ { shorthand: true }
156
+ )
157
+ );
158
+ }
159
+ });
160
+ return root.toSource({ quote: "single" });
161
+ }
162
+ export {
163
+ transform as default
164
+ };
@@ -0,0 +1,39 @@
1
+ import fs from "node:fs";
2
+ const OLD_SQLITE_URL = "file:./dev.db";
3
+ const NEW_SQLITE_URL = "file:./db/dev.db";
4
+ function transformEnvDefaults(source) {
5
+ return source.replace(
6
+ /^(DATABASE_URL=)file:\.\/dev\.db([ \t]*)$/m,
7
+ `$1${NEW_SQLITE_URL}$2`
8
+ );
9
+ }
10
+ async function updateEnvDefaults(envDefaultsPath) {
11
+ if (!fs.existsSync(envDefaultsPath)) {
12
+ return "skipped";
13
+ }
14
+ const source = fs.readFileSync(envDefaultsPath, "utf-8");
15
+ if (!source.includes(`DATABASE_URL=${OLD_SQLITE_URL}`)) {
16
+ return "unmodified";
17
+ }
18
+ const transformed = transformEnvDefaults(source);
19
+ if (transformed === source) {
20
+ return "unmodified";
21
+ }
22
+ fs.writeFileSync(envDefaultsPath, transformed, "utf-8");
23
+ return "updated";
24
+ }
25
+ function checkDotEnv(envPath) {
26
+ if (!fs.existsSync(envPath)) {
27
+ return null;
28
+ }
29
+ const source = fs.readFileSync(envPath, "utf-8");
30
+ if (source.includes(`DATABASE_URL=${OLD_SQLITE_URL}`)) {
31
+ return `Your .env file still contains DATABASE_URL=${OLD_SQLITE_URL}. You should manually update it to DATABASE_URL=${NEW_SQLITE_URL}.`;
32
+ }
33
+ return null;
34
+ }
35
+ export {
36
+ checkDotEnv,
37
+ transformEnvDefaults,
38
+ updateEnvDefaults
39
+ };
@@ -0,0 +1,32 @@
1
+ import fs from "node:fs";
2
+ const GENERATED_PRISMA_ENTRY = "api/db/generated/prisma";
3
+ function transformGitignore(source) {
4
+ if (source.includes(GENERATED_PRISMA_ENTRY)) {
5
+ return source;
6
+ }
7
+ if (/^dev\.db\*/m.test(source)) {
8
+ return source.replace(/^(dev\.db\*)$/m, `$1
9
+ ${GENERATED_PRISMA_ENTRY}`);
10
+ }
11
+ const withTrailingNewline = source.endsWith("\n") ? source : source + "\n";
12
+ return withTrailingNewline + GENERATED_PRISMA_ENTRY + "\n";
13
+ }
14
+ async function updateGitignore(gitignorePath) {
15
+ if (!fs.existsSync(gitignorePath)) {
16
+ return "skipped";
17
+ }
18
+ const source = fs.readFileSync(gitignorePath, "utf-8");
19
+ if (source.includes(GENERATED_PRISMA_ENTRY)) {
20
+ return "unmodified";
21
+ }
22
+ const transformed = transformGitignore(source);
23
+ if (transformed === source) {
24
+ return "unmodified";
25
+ }
26
+ fs.writeFileSync(gitignorePath, transformed, "utf-8");
27
+ return "updated";
28
+ }
29
+ export {
30
+ transformGitignore,
31
+ updateGitignore
32
+ };
@@ -0,0 +1,48 @@
1
+ import fs from "node:fs";
2
+ function transformPrismaConfig(source) {
3
+ if (/datasource\s*:/.test(source)) {
4
+ return source;
5
+ }
6
+ let result = source;
7
+ if (!/\benv\b/.test(result)) {
8
+ result = result.replace(
9
+ /const\s*\{([^}]*)\}\s*=\s*require\(['"]prisma\/config['"]\)/,
10
+ (match, destructured) => {
11
+ const trimmed = destructured.trim();
12
+ return `const { ${trimmed}, env } = require('prisma/config')`;
13
+ }
14
+ );
15
+ }
16
+ result = result.replace(
17
+ /(\n[ \t]*\}[ \t]*,?\s*\n)([ \t]*\}\))/,
18
+ (match, lastPropClose, closingParen) => {
19
+ const indent = lastPropClose.match(/^(\n([ \t]*))/)?.[2] ?? " ";
20
+ const propIndent = indent;
21
+ const innerIndent = indent + " ";
22
+ return lastPropClose + `${propIndent}datasource: {
23
+ ${innerIndent}url: env('DATABASE_URL'),
24
+ ${propIndent}},
25
+ ` + closingParen;
26
+ }
27
+ );
28
+ return result;
29
+ }
30
+ async function updatePrismaConfig(configPath) {
31
+ if (!fs.existsSync(configPath)) {
32
+ return "skipped";
33
+ }
34
+ const source = fs.readFileSync(configPath, "utf-8");
35
+ if (/datasource\s*:/.test(source)) {
36
+ return "unmodified";
37
+ }
38
+ const transformed = transformPrismaConfig(source);
39
+ if (transformed === source) {
40
+ return "unmodified";
41
+ }
42
+ fs.writeFileSync(configPath, transformed, "utf-8");
43
+ return "updated";
44
+ }
45
+ export {
46
+ transformPrismaConfig,
47
+ updatePrismaConfig
48
+ };
@@ -0,0 +1,98 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { getSchemaPath, getPaths } from "@cedarjs/project-config";
4
+ const NEW_GENERATOR_BLOCK = `generator client {
5
+ provider = "prisma-client"
6
+ output = "./generated/prisma"
7
+ moduleFormat = "cjs"
8
+ generatedFileExtension = "mts"
9
+ importFileExtension = "mts"
10
+ }`;
11
+ function isAlreadyMigrated(source) {
12
+ return /generator\s+client\s*\{[^}]*provider\s*=\s*["']prisma-client["'][^}]*\}/.test(
13
+ source
14
+ );
15
+ }
16
+ function transformSchema(source) {
17
+ let directUrlEnvVar = null;
18
+ let customBinaryTargets = null;
19
+ const result = source.replace(
20
+ /(datasource\s+\w+\s*\{)([^}]*?)(\})/g,
21
+ (_fullMatch, open, body, close) => {
22
+ const directUrlMatch = body.match(
23
+ /directUrl\s*=\s*env\(["']([^"']+)["']\)/
24
+ );
25
+ if (directUrlMatch) {
26
+ directUrlEnvVar = directUrlMatch[1];
27
+ }
28
+ const cleaned = body.replace(/^[ \t]*url\s*=\s*.+\n?/gm, "").replace(/^[ \t]*directUrl\s*=\s*.+\n?/gm, "").replace(/\n{3,}/g, "\n\n");
29
+ return `${open}${cleaned}${close}`;
30
+ }
31
+ ).replace(
32
+ /generator\s+client\s*\{([^}]*)\}/g,
33
+ (_fullMatch, body) => {
34
+ const binaryMatch = body.match(
35
+ /binaryTargets\s*=\s*(\[[^\]]*\]|"[^"]*"|'[^']*')/
36
+ );
37
+ if (binaryMatch) {
38
+ const val = binaryMatch[1];
39
+ if (!/^\s*["']native["']\s*$/.test(val)) {
40
+ customBinaryTargets = val;
41
+ }
42
+ }
43
+ return NEW_GENERATOR_BLOCK;
44
+ }
45
+ );
46
+ return { result, directUrlEnvVar, customBinaryTargets };
47
+ }
48
+ async function updateSchemaFile(schemaPath) {
49
+ if (!fs.existsSync(schemaPath)) {
50
+ return { status: "skipped", warnings: [] };
51
+ }
52
+ const source = fs.readFileSync(schemaPath, "utf-8");
53
+ const warnings = [];
54
+ if (isAlreadyMigrated(source)) {
55
+ return { status: "unmodified", warnings };
56
+ }
57
+ const { result, directUrlEnvVar, customBinaryTargets } = transformSchema(source);
58
+ if (directUrlEnvVar) {
59
+ warnings.push(
60
+ `A directUrl was found in your schema.prisma and has been removed. In Prisma v7 the \`url\` field in prisma.config.cjs is used by the CLI for migrations (replacing directUrl). Update api/prisma.config.cjs to use your direct URL value there:
61
+ datasource: {
62
+ url: env('${directUrlEnvVar}'),
63
+ },`
64
+ );
65
+ }
66
+ if (customBinaryTargets) {
67
+ warnings.push(
68
+ `binaryTargets (${customBinaryTargets}) has been removed from your schema.prisma. binaryTargets is no longer needed in Prisma v7 as the new driver is written in TypeScript.`
69
+ );
70
+ }
71
+ if (result === source) {
72
+ return { status: "unmodified", warnings };
73
+ }
74
+ fs.writeFileSync(schemaPath, result, "utf-8");
75
+ return { status: "updated", warnings };
76
+ }
77
+ async function runUpdateSchemaFile() {
78
+ const paths = getPaths();
79
+ const schemaPath = await getSchemaPath(paths.api.prismaConfig);
80
+ const results = [];
81
+ if (schemaPath) {
82
+ const schemaDir = path.dirname(schemaPath);
83
+ const siblings = fs.existsSync(schemaDir) ? fs.readdirSync(schemaDir).filter((f) => f.endsWith(".prisma")).map((f) => path.join(schemaDir, f)) : [];
84
+ const filesToProcess = [
85
+ schemaPath,
86
+ ...siblings.filter((f) => f !== schemaPath)
87
+ ];
88
+ for (const filePath of filesToProcess) {
89
+ const result = await updateSchemaFile(filePath);
90
+ results.push({ path: filePath, ...result });
91
+ }
92
+ }
93
+ return { results };
94
+ }
95
+ export {
96
+ runUpdateSchemaFile as default,
97
+ updateSchemaFile
98
+ };
@@ -0,0 +1,73 @@
1
+ import fs from "node:fs";
2
+ function transformTsConfig(source) {
3
+ if (/["']allowImportingTsExtensions["']/.test(source)) {
4
+ return source;
5
+ }
6
+ const afterModuleResolution = source.replace(
7
+ /("moduleResolution"\s*:\s*"[^"]*")(,?)(\s*\n)/,
8
+ (match, prop, comma, ws) => {
9
+ const lineStart = source.lastIndexOf("\n", source.indexOf(match)) + 1;
10
+ const indentMatch = source.slice(lineStart).match(/^([ \t]+)/);
11
+ const indent = indentMatch ? indentMatch[1] : " ";
12
+ const trailingComma = comma || ",";
13
+ return `${prop}${trailingComma}${ws}${indent}"allowImportingTsExtensions": true,
14
+ `;
15
+ }
16
+ );
17
+ if (afterModuleResolution !== source) {
18
+ return afterModuleResolution;
19
+ }
20
+ const afterModule = source.replace(
21
+ /("module"\s*:\s*"[^"]*")(,?)(\s*\n)/,
22
+ (match, prop, comma, ws) => {
23
+ const lineStart = source.lastIndexOf("\n", source.indexOf(match)) + 1;
24
+ const indentMatch = source.slice(lineStart).match(/^([ \t]+)/);
25
+ const indent = indentMatch ? indentMatch[1] : " ";
26
+ const trailingComma = comma || ",";
27
+ return `${prop}${trailingComma}${ws}${indent}"allowImportingTsExtensions": true,
28
+ `;
29
+ }
30
+ );
31
+ if (afterModule !== source) {
32
+ return afterModule;
33
+ }
34
+ return source.replace(
35
+ /("compilerOptions"\s*:\s*\{)(\s*\n)/,
36
+ (match, open, ws) => {
37
+ const lineStart = source.lastIndexOf("\n", source.indexOf(match)) + 1;
38
+ const indentMatch = source.slice(lineStart).match(/^([ \t]+)/);
39
+ const outerIndent = indentMatch ? indentMatch[1] : " ";
40
+ const innerIndent = outerIndent + " ";
41
+ return `${open}${ws}${innerIndent}"allowImportingTsExtensions": true,
42
+ `;
43
+ }
44
+ );
45
+ }
46
+ async function updateTsConfig(tsConfigPath) {
47
+ if (!fs.existsSync(tsConfigPath)) {
48
+ return "skipped";
49
+ }
50
+ const source = fs.readFileSync(tsConfigPath, "utf-8");
51
+ if (/["']allowImportingTsExtensions["']/.test(source)) {
52
+ return "unmodified";
53
+ }
54
+ const transformed = transformTsConfig(source);
55
+ if (transformed === source) {
56
+ return "unmodified";
57
+ }
58
+ fs.writeFileSync(tsConfigPath, transformed, "utf-8");
59
+ return "updated";
60
+ }
61
+ async function updateTsConfigs(paths) {
62
+ const [api, scripts, web] = await Promise.all([
63
+ updateTsConfig(paths.apiTsConfig),
64
+ updateTsConfig(paths.scriptsTsConfig),
65
+ updateTsConfig(paths.webTsConfig)
66
+ ]);
67
+ return { api, scripts, web };
68
+ }
69
+ export {
70
+ transformTsConfig,
71
+ updateTsConfig,
72
+ updateTsConfigs
73
+ };
package/dist/codemods.js CHANGED
@@ -22,7 +22,8 @@ import * as v6ThemeConfig from "./codemods/redwood/v6.x.x/updateThemeConfig/upda
22
22
  import * as v7Gql from "./codemods/redwood/v7.x.x/updateGraphQLConfig/updateGraphqlConfig.yargs.js";
23
23
  import * as v2MoveGeneratorTemplates from "./codemods/v2.3.x/moveGeneratorTemplates/moveGeneratorTemplates.yargs.js";
24
24
  import * as v2PrismaV7Prep from "./codemods/v2.7.x/prismaV7Prep/prismaV7Prep.yargs.js";
25
- yargs(hideBin(process.argv)).scriptName("").command(v2MoveGeneratorTemplates).command(v2PrismaV7Prep).command("redwood", "List or run Redwood codemods", (yargs2) => {
25
+ import * as v3PrismaV7 from "./codemods/v3.x/prismaV7/prismaV7.yargs.js";
26
+ yargs(hideBin(process.argv)).scriptName("").command(v2MoveGeneratorTemplates).command(v2PrismaV7Prep).command(v3PrismaV7).command("redwood", "List or run Redwood codemods", (yargs2) => {
26
27
  return yargs2.command(v2TsconfigForRouteHooks).command(v2ConfigureFastify).command(v2UpdateResolverTypes).command(v4UpdateClerkGetCurrentUser).command(v4UseArmor).command(v5CellQueryResult).command(v5DetectEmptyCells).command(v5RenameValidateWith).command(v5UpdateAuth0ToV2).command(v5UpdateNodeEngineTo18).command(v5UpgradeToReact18).command(v6GlobalThis).command(v6Jsx).command(v6EntryClient).command(v6EnvDot).command(v6Svgs).command(v6DevFatalErrorPage).command(v6ThemeConfig).command(v7Gql).demandCommand().strict();
27
28
  }).demandCommand().epilog(
28
29
  [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cedarjs/codemods",
3
- "version": "3.0.0-canary.13607+f190aacd5",
3
+ "version": "3.0.0-canary.13610+e4c07ee8e",
4
4
  "description": "Codemods to ease upgrading a CedarJS Project",
5
5
  "repository": {
6
6
  "type": "git",
@@ -30,7 +30,7 @@
30
30
  "@babel/plugin-transform-typescript": "^7.26.8",
31
31
  "@babel/runtime-corejs3": "7.29.0",
32
32
  "@babel/traverse": "7.29.0",
33
- "@cedarjs/project-config": "3.0.0-canary.13607",
33
+ "@cedarjs/project-config": "3.0.0-canary.13610",
34
34
  "@svgr/core": "8.1.0",
35
35
  "@svgr/plugin-jsx": "8.1.0",
36
36
  "@vscode/ripgrep": "1.17.1",
@@ -49,7 +49,7 @@
49
49
  "yargs": "17.7.2"
50
50
  },
51
51
  "devDependencies": {
52
- "@cedarjs/framework-tools": "3.0.0-canary.13607",
52
+ "@cedarjs/framework-tools": "3.0.0-canary.13610",
53
53
  "@types/babel__core": "7.20.5",
54
54
  "@types/jscodeshift": "17.3.0",
55
55
  "@types/yargs": "17.0.35",
@@ -62,5 +62,5 @@
62
62
  "publishConfig": {
63
63
  "access": "public"
64
64
  },
65
- "gitHead": "f190aacd5629d47e68cbdc21188ee855d855bab9"
65
+ "gitHead": "e4c07ee8e478f9a417c260eb8bf5366a869a4d49"
66
66
  }