@cedarjs/cli 6.0.0-canary.2841 → 6.0.0-canary.2843
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.
- package/dist/commands/setup/database/database.js +15 -0
- package/dist/commands/setup/database/postgres.js +19 -0
- package/dist/commands/setup/database/postgresHandler.js +194 -0
- package/dist/commands/setup/neon/neonHandler.js +96 -254
- package/dist/commands/setup.js +2 -1
- package/package.json +13 -13
- /package/dist/commands/setup/{neon → database}/templates/db.ts.template +0 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { terminalLink } from "termi-link";
|
|
2
|
+
import * as setupDatabasePostgres from "./postgres.js";
|
|
3
|
+
const command = "database <command>";
|
|
4
|
+
const description = "Switch your project's database";
|
|
5
|
+
const builder = (yargs) => yargs.command(setupDatabasePostgres).demandCommand().epilogue(
|
|
6
|
+
`Also see the ${terminalLink(
|
|
7
|
+
"CedarJS CLI Reference",
|
|
8
|
+
"https://cedarjs.com/docs/cli-commands#setup"
|
|
9
|
+
)}`
|
|
10
|
+
);
|
|
11
|
+
export {
|
|
12
|
+
builder,
|
|
13
|
+
command,
|
|
14
|
+
description
|
|
15
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { recordTelemetryAttributes } from "@cedarjs/cli-helpers";
|
|
2
|
+
const command = "postgres";
|
|
3
|
+
const description = "Switch your project from SQLite to PostgreSQL (schema, dependencies, and database adapter)";
|
|
4
|
+
function builder(yargs) {
|
|
5
|
+
return yargs;
|
|
6
|
+
}
|
|
7
|
+
async function handler() {
|
|
8
|
+
recordTelemetryAttributes({
|
|
9
|
+
command: "setup database postgres"
|
|
10
|
+
});
|
|
11
|
+
const { handler: handler2 } = await import("./postgresHandler.js");
|
|
12
|
+
return handler2();
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
builder,
|
|
16
|
+
command,
|
|
17
|
+
description,
|
|
18
|
+
handler
|
|
19
|
+
};
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import execa from "execa";
|
|
4
|
+
import { Listr } from "listr2";
|
|
5
|
+
import { colors, getPaths, installPackages } from "@cedarjs/cli-helpers";
|
|
6
|
+
import { prettyPrintCedarCommand } from "@cedarjs/cli-helpers/packageManager";
|
|
7
|
+
import { addWorkspacePackages } from "@cedarjs/cli-helpers/packageManager/packages";
|
|
8
|
+
import { resolveFile } from "@cedarjs/project-config";
|
|
9
|
+
import { errorTelemetry } from "@cedarjs/telemetry";
|
|
10
|
+
function checkProjectShape(cedarPaths) {
|
|
11
|
+
const schemaPath = path.join(cedarPaths.api.base, "db", "schema.prisma");
|
|
12
|
+
const dbPath = resolveFile(path.join(cedarPaths.api.lib, "db"));
|
|
13
|
+
if (!fs.existsSync(schemaPath)) {
|
|
14
|
+
return blocked(`Could not find ${schemaPath}.`);
|
|
15
|
+
}
|
|
16
|
+
if (!dbPath) {
|
|
17
|
+
return blocked(`No ${path.join(cedarPaths.api.lib, "db")} file found`);
|
|
18
|
+
}
|
|
19
|
+
const schemaContent = fs.readFileSync(schemaPath, "utf-8");
|
|
20
|
+
const hasPgAdapter = fs.readFileSync(dbPath, "utf-8").includes("PrismaPg");
|
|
21
|
+
if (schemaContent.includes('provider = "postgresql"') && hasPgAdapter) {
|
|
22
|
+
return {
|
|
23
|
+
ok: false,
|
|
24
|
+
alreadyConverted: true,
|
|
25
|
+
message: "This project is already configured for PostgreSQL."
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
if (!schemaContent.includes('provider = "sqlite"') || hasPgAdapter) {
|
|
29
|
+
return blocked(
|
|
30
|
+
"This command only converts a project that is still on SQLite, with the default adapter in api/src/lib/db.ts (or db.js) untouched. This project doesn't match that shape (a different provider, or a partial previous conversion). Please switch it over to PostgreSQL manually."
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
return { ok: true, dbPath };
|
|
34
|
+
}
|
|
35
|
+
function blocked(message) {
|
|
36
|
+
return { ok: false, alreadyConverted: false, message };
|
|
37
|
+
}
|
|
38
|
+
function getSqliteToPostgresTasks({
|
|
39
|
+
dbPath
|
|
40
|
+
}) {
|
|
41
|
+
const cedarPaths = getPaths();
|
|
42
|
+
const schemaPath = path.join(cedarPaths.api.base, "db", "schema.prisma");
|
|
43
|
+
const rootPkgPath = path.join(cedarPaths.base, "package.json");
|
|
44
|
+
const apiPkgPath = path.join(cedarPaths.api.base, "package.json");
|
|
45
|
+
const dbTsTemplatePath = path.join(
|
|
46
|
+
import.meta.dirname,
|
|
47
|
+
"templates",
|
|
48
|
+
"db.ts.template"
|
|
49
|
+
);
|
|
50
|
+
return [
|
|
51
|
+
{
|
|
52
|
+
title: "Removing SQLite dependencies from api/package.json",
|
|
53
|
+
task: () => {
|
|
54
|
+
const pkg = JSON.parse(fs.readFileSync(apiPkgPath, "utf-8"));
|
|
55
|
+
if (pkg.dependencies) {
|
|
56
|
+
delete pkg.dependencies["better-sqlite3"];
|
|
57
|
+
delete pkg.dependencies["@prisma/adapter-better-sqlite3"];
|
|
58
|
+
}
|
|
59
|
+
fs.writeFileSync(apiPkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
title: "Removing better-sqlite3 dependenciesMeta",
|
|
64
|
+
task: () => {
|
|
65
|
+
if (!fs.existsSync(rootPkgPath)) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const pkg = JSON.parse(fs.readFileSync(rootPkgPath, "utf-8"));
|
|
69
|
+
if (pkg.dependenciesMeta?.["better-sqlite3"]) {
|
|
70
|
+
delete pkg.dependenciesMeta["better-sqlite3"];
|
|
71
|
+
if (Object.keys(pkg.dependenciesMeta).length === 0) {
|
|
72
|
+
delete pkg.dependenciesMeta;
|
|
73
|
+
}
|
|
74
|
+
fs.writeFileSync(rootPkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
title: "Switching Prisma schema to PostgreSQL",
|
|
80
|
+
task: () => {
|
|
81
|
+
const schemaContent = fs.readFileSync(schemaPath, "utf-8");
|
|
82
|
+
fs.writeFileSync(
|
|
83
|
+
schemaPath,
|
|
84
|
+
schemaContent.replace(
|
|
85
|
+
'provider = "sqlite"',
|
|
86
|
+
'provider = "postgresql"'
|
|
87
|
+
)
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
title: "Updating database adapter",
|
|
93
|
+
task: () => {
|
|
94
|
+
const pgDbTs = fs.readFileSync(dbTsTemplatePath, "utf-8");
|
|
95
|
+
fs.writeFileSync(dbPath, pgDbTs);
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
title: "Adding required api packages...",
|
|
100
|
+
task: async () => {
|
|
101
|
+
await addWorkspacePackages("api", ["@prisma/adapter-pg@7.8.0"], {
|
|
102
|
+
cwd: cedarPaths.api.base
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
];
|
|
107
|
+
}
|
|
108
|
+
function readEnvVar(envContent, name) {
|
|
109
|
+
return envContent.match(new RegExp(`^${name}=(.*)$`, "m"))?.[1] || void 0;
|
|
110
|
+
}
|
|
111
|
+
async function handler() {
|
|
112
|
+
const cedarPaths = getPaths();
|
|
113
|
+
const shape = checkProjectShape(cedarPaths);
|
|
114
|
+
if (!shape.ok) {
|
|
115
|
+
if (shape.alreadyConverted) {
|
|
116
|
+
console.log(colors.note(shape.message));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
console.error(colors.error(shape.message));
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
const envPath = path.join(cedarPaths.base, ".env");
|
|
123
|
+
const envContent = fs.existsSync(envPath) ? fs.readFileSync(envPath, "utf-8") : "";
|
|
124
|
+
const databaseUrl = readEnvVar(envContent, "DATABASE_URL");
|
|
125
|
+
const tasks = new Listr(
|
|
126
|
+
[
|
|
127
|
+
...getSqliteToPostgresTasks({ dbPath: shape.dbPath }),
|
|
128
|
+
installPackages,
|
|
129
|
+
{
|
|
130
|
+
title: "Running Prisma migrations",
|
|
131
|
+
skip: () => {
|
|
132
|
+
if (!databaseUrl) {
|
|
133
|
+
return `No DATABASE_URL found in \`.env\`. Set it to your PostgreSQL connection string, then run \`${prettyPrintCedarCommand(["prisma", "migrate", "dev"])}\``;
|
|
134
|
+
}
|
|
135
|
+
return false;
|
|
136
|
+
},
|
|
137
|
+
task: () => {
|
|
138
|
+
const result = execa.commandSync(
|
|
139
|
+
"yarn cedar prisma migrate dev --name init-postgres",
|
|
140
|
+
{
|
|
141
|
+
cwd: cedarPaths.base,
|
|
142
|
+
stdio: ["inherit", "inherit", "pipe"],
|
|
143
|
+
reject: false
|
|
144
|
+
}
|
|
145
|
+
);
|
|
146
|
+
if (result.exitCode !== 0) {
|
|
147
|
+
throw new Error(
|
|
148
|
+
"Prisma migration failed:\n\n" + result.stderr + `
|
|
149
|
+
|
|
150
|
+
You can try running it manually:
|
|
151
|
+
${prettyPrintCedarCommand(["prisma", "migrate", "dev", "--name", "init-postgres"])}`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
],
|
|
157
|
+
{
|
|
158
|
+
exitOnError: false,
|
|
159
|
+
collectErrors: "minimal"
|
|
160
|
+
}
|
|
161
|
+
);
|
|
162
|
+
try {
|
|
163
|
+
await tasks.run();
|
|
164
|
+
if (tasks.errors.length > 0) {
|
|
165
|
+
for (const error of tasks.errors) {
|
|
166
|
+
if (isErrorWithMessage(error)) {
|
|
167
|
+
errorTelemetry(process.argv, error.message);
|
|
168
|
+
console.error(colors.error(error.message));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
} catch (e) {
|
|
174
|
+
if (isErrorWithMessage(e)) {
|
|
175
|
+
errorTelemetry(process.argv, e.message);
|
|
176
|
+
console.error(colors.error(e.message));
|
|
177
|
+
}
|
|
178
|
+
if (isErrorWithExitCode(e)) {
|
|
179
|
+
process.exit(e.exitCode);
|
|
180
|
+
}
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function isErrorWithMessage(e) {
|
|
185
|
+
return !!e && typeof e === "object" && "message" in e;
|
|
186
|
+
}
|
|
187
|
+
function isErrorWithExitCode(e) {
|
|
188
|
+
return !!e && typeof e === "object" && "exitCode" in e && typeof e.exitCode === "number";
|
|
189
|
+
}
|
|
190
|
+
export {
|
|
191
|
+
checkProjectShape,
|
|
192
|
+
getSqliteToPostgresTasks,
|
|
193
|
+
handler
|
|
194
|
+
};
|
|
@@ -3,213 +3,76 @@ import path from "node:path";
|
|
|
3
3
|
import execa from "execa";
|
|
4
4
|
import { Listr } from "listr2";
|
|
5
5
|
import { colors, getPaths, installPackages } from "@cedarjs/cli-helpers";
|
|
6
|
-
import {
|
|
6
|
+
import { prettyPrintCedarCommand } from "@cedarjs/cli-helpers/packageManager";
|
|
7
7
|
import { errorTelemetry } from "@cedarjs/telemetry";
|
|
8
|
-
|
|
8
|
+
import {
|
|
9
|
+
checkProjectShape,
|
|
10
|
+
getSqliteToPostgresTasks
|
|
11
|
+
} from "../database/postgresHandler.js";
|
|
9
12
|
async function handler({ force }) {
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
const cedarPaths = getPaths();
|
|
14
|
+
const shape = checkProjectShape(cedarPaths);
|
|
15
|
+
if (!shape.ok) {
|
|
16
|
+
if (shape.alreadyConverted) {
|
|
17
|
+
console.log(colors.note(shape.message));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
console.error(colors.error(shape.message));
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
20
23
|
const envPath = path.join(cedarPaths.base, ".env");
|
|
21
|
-
|
|
22
|
-
const apiPkgPath = path.join(cedarPaths.api.base, "package.json");
|
|
23
|
-
const dbTsTemplatePath = path.join(
|
|
24
|
-
import.meta.dirname,
|
|
25
|
-
"templates",
|
|
26
|
-
"db.ts.template"
|
|
27
|
-
);
|
|
28
|
-
let hasDirectDatabaseUrl = false;
|
|
24
|
+
let hasExistingDatabaseUrl = false;
|
|
29
25
|
if (fs.existsSync(envPath)) {
|
|
30
|
-
|
|
26
|
+
hasExistingDatabaseUrl = /^DATABASE_URL=/m.test(
|
|
31
27
|
fs.readFileSync(envPath, "utf-8")
|
|
32
28
|
);
|
|
33
29
|
}
|
|
30
|
+
const skipProvisioning = hasExistingDatabaseUrl && !force;
|
|
34
31
|
const notes = [];
|
|
32
|
+
if (skipProvisioning) {
|
|
33
|
+
notes.push(
|
|
34
|
+
colors.note(
|
|
35
|
+
"DATABASE_URL is already set in .env. Use --force to overwrite."
|
|
36
|
+
)
|
|
37
|
+
);
|
|
38
|
+
}
|
|
35
39
|
const tasks = new Listr(
|
|
36
40
|
[
|
|
41
|
+
...getSqliteToPostgresTasks({ dbPath: shape.dbPath }),
|
|
37
42
|
{
|
|
38
|
-
title: "
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
ctx.isNeon = false;
|
|
49
|
-
}
|
|
50
|
-
if (!ctx.isSqlite && !ctx.isPostgres) {
|
|
51
|
-
ctx.unsupportedProvider = true;
|
|
52
|
-
notes.push(
|
|
53
|
-
colors.note(
|
|
54
|
-
"setup neon only supports migrating from SQLite to PostgreSQL. Your project uses a different database provider."
|
|
55
|
-
)
|
|
56
|
-
);
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
if (!ctx.isPostgres) {
|
|
60
|
-
ctx.hasSqliteUsageOutsideDb = hasSqliteUsageOutsideDb(
|
|
61
|
-
cedarPaths.api.src,
|
|
62
|
-
dbTsPath
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
if (hasDirectDatabaseUrl && !force) {
|
|
66
|
-
ctx.skipWithNote = true;
|
|
67
|
-
notes.push(
|
|
68
|
-
colors.note(
|
|
69
|
-
"DATABASE_URL is already set in .env. Use --force to overwrite."
|
|
70
|
-
)
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
title: "Removing SQLite dependencies from api/package.json",
|
|
77
|
-
skip: (ctx) => {
|
|
78
|
-
if (ctx.unsupportedProvider) {
|
|
79
|
-
return "Unsupported database provider";
|
|
80
|
-
}
|
|
81
|
-
if (ctx.isPostgres) {
|
|
82
|
-
return "Already configured for PostgreSQL";
|
|
83
|
-
}
|
|
84
|
-
if (ctx.hasSqliteUsageOutsideDb) {
|
|
85
|
-
return "SQLite is in use outside db.ts \u2014 keeping packages";
|
|
86
|
-
}
|
|
87
|
-
return false;
|
|
88
|
-
},
|
|
89
|
-
task: () => {
|
|
90
|
-
const pkg = JSON.parse(fs.readFileSync(apiPkgPath, "utf-8"));
|
|
91
|
-
if (pkg.dependencies) {
|
|
92
|
-
delete pkg.dependencies["better-sqlite3"];
|
|
93
|
-
delete pkg.dependencies["@prisma/adapter-better-sqlite3"];
|
|
94
|
-
}
|
|
95
|
-
fs.writeFileSync(apiPkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
96
|
-
}
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
title: "Removing better-sqlite3 dependenciesMeta",
|
|
100
|
-
skip: (ctx) => {
|
|
101
|
-
if (ctx.unsupportedProvider) {
|
|
102
|
-
return "Unsupported database provider";
|
|
103
|
-
}
|
|
104
|
-
if (ctx.isPostgres) {
|
|
105
|
-
return "Already configured for PostgreSQL";
|
|
106
|
-
}
|
|
107
|
-
if (ctx.hasSqliteUsageOutsideDb) {
|
|
108
|
-
return "SQLite is in use outside db.ts so we're keeping it installed";
|
|
109
|
-
}
|
|
110
|
-
return false;
|
|
111
|
-
},
|
|
112
|
-
task: () => {
|
|
113
|
-
if (!fs.existsSync(rootPkgPath)) {
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
const pkg = JSON.parse(fs.readFileSync(rootPkgPath, "utf-8"));
|
|
117
|
-
if (pkg.dependenciesMeta?.["better-sqlite3"]) {
|
|
118
|
-
delete pkg.dependenciesMeta["better-sqlite3"];
|
|
119
|
-
if (Object.keys(pkg.dependenciesMeta).length === 0) {
|
|
120
|
-
delete pkg.dependenciesMeta;
|
|
121
|
-
}
|
|
122
|
-
fs.writeFileSync(rootPkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
title: "Switching Prisma schema to PostgreSQL",
|
|
128
|
-
skip: (ctx) => {
|
|
129
|
-
if (ctx.unsupportedProvider) {
|
|
130
|
-
return "Unsupported database provider";
|
|
131
|
-
}
|
|
132
|
-
if (ctx.isPostgres) {
|
|
133
|
-
return "Schema is already configured for PostgreSQL";
|
|
134
|
-
}
|
|
135
|
-
return false;
|
|
136
|
-
},
|
|
137
|
-
task: (ctx) => {
|
|
138
|
-
const updated = ctx.schemaContent.replace(
|
|
139
|
-
'provider = "sqlite"',
|
|
140
|
-
'provider = "postgresql"'
|
|
43
|
+
title: "Setting DIRECT_DATABASE_URL in Prisma config",
|
|
44
|
+
skip: () => skipProvisioning,
|
|
45
|
+
task: (ctx, task) => {
|
|
46
|
+
const prismaConfigPathCjs = path.join(
|
|
47
|
+
cedarPaths.api.base,
|
|
48
|
+
"prisma.config.cjs"
|
|
49
|
+
);
|
|
50
|
+
const prismaConfigPathMts = path.join(
|
|
51
|
+
cedarPaths.api.base,
|
|
52
|
+
"prisma.config.mts"
|
|
141
53
|
);
|
|
142
|
-
fs.writeFileSync(schemaPath, updated);
|
|
143
|
-
}
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
title: "Updating database adapter",
|
|
147
|
-
skip: (ctx) => {
|
|
148
|
-
if (ctx.unsupportedProvider) {
|
|
149
|
-
return "Unsupported database provider";
|
|
150
|
-
}
|
|
151
|
-
if (ctx.isNeon) {
|
|
152
|
-
return "Database adapter is already configured for Neon (PrismaPg)";
|
|
153
|
-
}
|
|
154
|
-
if (ctx.skipWithNote) {
|
|
155
|
-
return "DATABASE_URL already configured \u2014 skipping adapter update";
|
|
156
|
-
}
|
|
157
|
-
return false;
|
|
158
|
-
},
|
|
159
|
-
task: () => {
|
|
160
|
-
const neonDbTs = fs.readFileSync(dbTsTemplatePath, "utf-8");
|
|
161
|
-
fs.writeFileSync(dbTsPath, neonDbTs);
|
|
162
|
-
}
|
|
163
|
-
},
|
|
164
|
-
{
|
|
165
|
-
title: "Updating Prisma config",
|
|
166
|
-
skip: (ctx) => {
|
|
167
|
-
if (ctx.unsupportedProvider) {
|
|
168
|
-
return "Unsupported database provider";
|
|
169
|
-
}
|
|
170
|
-
if (ctx.isNeon) {
|
|
171
|
-
return "Prisma config is already configured for Neon";
|
|
172
|
-
}
|
|
173
|
-
if (ctx.skipWithNote) {
|
|
174
|
-
return "DATABASE_URL already configured \u2014 skipping config update";
|
|
175
|
-
}
|
|
176
|
-
return false;
|
|
177
|
-
},
|
|
178
|
-
task: () => {
|
|
179
|
-
if (!fs.existsSync(prismaConfigPathCjs) && !fs.existsSync(prismaConfigPathMts)) {
|
|
180
|
-
throw new Error(
|
|
181
|
-
"No Prisma config file found. Expected prisma.config.cjs or prisma.config.mts in the api directory."
|
|
182
|
-
);
|
|
183
|
-
}
|
|
184
54
|
const configPath = fs.existsSync(prismaConfigPathCjs) ? prismaConfigPathCjs : prismaConfigPathMts;
|
|
185
55
|
const configContent = fs.readFileSync(configPath, "utf-8");
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
56
|
+
const datasourceUrlRegex = /(\burl\s*:\s*)env\(["'][^"']*?["']\)/;
|
|
57
|
+
if (!datasourceUrlRegex.test(configContent)) {
|
|
58
|
+
ctx.directDatabaseUrlNotSet = true;
|
|
59
|
+
task.skip(
|
|
60
|
+
"Could not set DIRECT_DATABASE_URL. Please manually set datasource.url in " + configPath
|
|
61
|
+
);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
fs.writeFileSync(
|
|
65
|
+
configPath,
|
|
66
|
+
configContent.replace(
|
|
67
|
+
datasourceUrlRegex,
|
|
68
|
+
"$1env('DIRECT_DATABASE_URL')"
|
|
69
|
+
)
|
|
189
70
|
);
|
|
190
|
-
fs.writeFileSync(configPath, updated);
|
|
191
|
-
}
|
|
192
|
-
},
|
|
193
|
-
{
|
|
194
|
-
title: "Adding required api packages...",
|
|
195
|
-
skip: (ctx) => ctx.unsupportedProvider,
|
|
196
|
-
task: async () => {
|
|
197
|
-
await addWorkspacePackages("api", ["@prisma/adapter-pg@7.8.0"], {
|
|
198
|
-
cwd: cedarPaths.api.base
|
|
199
|
-
});
|
|
200
71
|
}
|
|
201
72
|
},
|
|
202
73
|
{
|
|
203
74
|
title: "Provisioning Neon database",
|
|
204
|
-
skip: (
|
|
205
|
-
if (ctx.unsupportedProvider) {
|
|
206
|
-
return true;
|
|
207
|
-
}
|
|
208
|
-
if (hasDirectDatabaseUrl && !force) {
|
|
209
|
-
return true;
|
|
210
|
-
}
|
|
211
|
-
return false;
|
|
212
|
-
},
|
|
75
|
+
skip: () => skipProvisioning,
|
|
213
76
|
task: async (ctx) => {
|
|
214
77
|
const res = await fetch("https://neon.new/api/v1/database", {
|
|
215
78
|
method: "POST",
|
|
@@ -239,20 +102,52 @@ async function handler({ force }) {
|
|
|
239
102
|
ctx.neonClaimExpiry = new Date(data.expires_at).toUTCString();
|
|
240
103
|
}
|
|
241
104
|
},
|
|
105
|
+
installPackages,
|
|
242
106
|
{
|
|
243
|
-
title: "
|
|
107
|
+
title: "Running Prisma migrations",
|
|
244
108
|
skip: (ctx) => {
|
|
245
|
-
if (
|
|
109
|
+
if (skipProvisioning) {
|
|
246
110
|
return true;
|
|
247
111
|
}
|
|
248
|
-
if (
|
|
249
|
-
return
|
|
250
|
-
}
|
|
251
|
-
if (!ctx.databaseUrl) {
|
|
252
|
-
return "No database URL to write (Neon provisioning skipped)";
|
|
112
|
+
if (ctx.directDatabaseUrlNotSet) {
|
|
113
|
+
return `Skipping migrations \u2014 could not confirm prisma.config is reading DIRECT_DATABASE_URL, so migrations could target the wrong database. Fix datasource.url, then run \`${prettyPrintCedarCommand(["prisma", "migrate", "dev"])}\` manually.`;
|
|
253
114
|
}
|
|
254
115
|
return false;
|
|
255
116
|
},
|
|
117
|
+
task: (ctx) => {
|
|
118
|
+
const result = execa.commandSync(
|
|
119
|
+
"yarn cedar prisma migrate dev --name init-neon",
|
|
120
|
+
{
|
|
121
|
+
cwd: cedarPaths.base,
|
|
122
|
+
stdio: ["inherit", "inherit", "pipe"],
|
|
123
|
+
reject: false,
|
|
124
|
+
env: {
|
|
125
|
+
...process.env,
|
|
126
|
+
DIRECT_DATABASE_URL: ctx.databaseUrlDirect
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
);
|
|
130
|
+
if (result.exitCode !== 0) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
"Prisma migration failed:\n\n" + result.stderr + `
|
|
133
|
+
|
|
134
|
+
You can try running it manually:
|
|
135
|
+
${prettyPrintCedarCommand(["prisma", "migrate", "dev", "--name", "init-neon"])}`
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
title: "Writing database connection to .env",
|
|
142
|
+
// Deliberately runs after migrations, not before — with
|
|
143
|
+
// `exitOnError: true`, a migration failure stops the list here and
|
|
144
|
+
// this task never runs. That means a project whose migrations
|
|
145
|
+
// failed never has DATABASE_URL/DIRECT_DATABASE_URL written to
|
|
146
|
+
// .env, so it's never left pointing at a Neon database it doesn't
|
|
147
|
+
// know it needs to claim. Provisioning that database anyway (and
|
|
148
|
+
// letting it expire unclaimed) is fine — it's exactly as if the
|
|
149
|
+
// command were re-run from scratch, which is safe.
|
|
150
|
+
skip: () => skipProvisioning,
|
|
256
151
|
task: (ctx) => {
|
|
257
152
|
let envContent = "";
|
|
258
153
|
if (fs.existsSync(envPath)) {
|
|
@@ -275,49 +170,10 @@ async function handler({ force }) {
|
|
|
275
170
|
fs.writeFileSync(envPath, envContent);
|
|
276
171
|
}
|
|
277
172
|
},
|
|
278
|
-
installPackages,
|
|
279
|
-
{
|
|
280
|
-
title: "Running Prisma migrations",
|
|
281
|
-
skip: (ctx) => {
|
|
282
|
-
if (ctx.unsupportedProvider) {
|
|
283
|
-
return true;
|
|
284
|
-
}
|
|
285
|
-
if (ctx.skipWithNote) {
|
|
286
|
-
return "DATABASE_URL already configured \u2014 skipping migration";
|
|
287
|
-
}
|
|
288
|
-
if (!ctx.databaseUrl) {
|
|
289
|
-
return "No database provisioned \u2014 skipping migration";
|
|
290
|
-
}
|
|
291
|
-
return false;
|
|
292
|
-
},
|
|
293
|
-
task: (ctx) => {
|
|
294
|
-
const result = execa.commandSync(
|
|
295
|
-
"yarn cedar prisma migrate dev --name init-neon",
|
|
296
|
-
{
|
|
297
|
-
cwd: cedarPaths.base,
|
|
298
|
-
stdio: ["inherit", "inherit", "pipe"],
|
|
299
|
-
reject: false,
|
|
300
|
-
env: {
|
|
301
|
-
...process.env,
|
|
302
|
-
DIRECT_DATABASE_URL: ctx.databaseUrlDirect
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
);
|
|
306
|
-
if (result.exitCode !== 0) {
|
|
307
|
-
throw new Error(
|
|
308
|
-
"Prisma migration failed:\n\n" + result.stderr + "\n\nYou can try running it manually:\n yarn cedar prisma migrate dev --name init-neon"
|
|
309
|
-
);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
},
|
|
313
173
|
{
|
|
314
174
|
title: "One more thing...",
|
|
315
175
|
task: (ctx, task) => {
|
|
316
|
-
if (
|
|
317
|
-
task.output = "Skipped \u2014 unsupported database provider";
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
|
-
if (ctx.skipWithNote) {
|
|
176
|
+
if (skipProvisioning) {
|
|
321
177
|
task.output = "Skipped \u2014 DATABASE_URL already configured";
|
|
322
178
|
return;
|
|
323
179
|
}
|
|
@@ -336,7 +192,11 @@ async function handler({ force }) {
|
|
|
336
192
|
}
|
|
337
193
|
],
|
|
338
194
|
{
|
|
339
|
-
|
|
195
|
+
// Migrations run before .env is written (see above) specifically so
|
|
196
|
+
// that a failure here — the one step that shouldn't be allowed to
|
|
197
|
+
// continue — stops the whole list via the default exitOnError
|
|
198
|
+
// behavior, rather than needing every later task to know to skip.
|
|
199
|
+
exitOnError: true
|
|
340
200
|
}
|
|
341
201
|
);
|
|
342
202
|
try {
|
|
@@ -362,24 +222,6 @@ function isErrorWithMessage(e) {
|
|
|
362
222
|
function isErrorWithExitCode(e) {
|
|
363
223
|
return !!e && typeof e === "object" && "exitCode" in e && typeof e.exitCode === "number";
|
|
364
224
|
}
|
|
365
|
-
function hasSqliteUsageOutsideDb(srcPath, dbTsPath) {
|
|
366
|
-
const sqlitePattern = /better-sqlite3|@prisma\/adapter-better-sqlite3/;
|
|
367
|
-
const files = fs.globSync("**/*.{ts,tsx,js,jsx}", { cwd: srcPath });
|
|
368
|
-
for (const file of files) {
|
|
369
|
-
const fullPath = path.join(srcPath, file);
|
|
370
|
-
if (fullPath === dbTsPath) {
|
|
371
|
-
continue;
|
|
372
|
-
}
|
|
373
|
-
try {
|
|
374
|
-
const content = fs.readFileSync(fullPath, "utf-8");
|
|
375
|
-
if (sqlitePattern.test(content)) {
|
|
376
|
-
return true;
|
|
377
|
-
}
|
|
378
|
-
} catch {
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
return false;
|
|
382
|
-
}
|
|
383
225
|
export {
|
|
384
226
|
handler
|
|
385
227
|
};
|
package/dist/commands/setup.js
CHANGED
|
@@ -2,6 +2,7 @@ import { terminalLink } from "termi-link";
|
|
|
2
2
|
import { detectCedarVersion } from "../middleware/detectProjectCedarVersion.js";
|
|
3
3
|
import * as setupAuth from "./setup/auth/auth.js";
|
|
4
4
|
import * as setupCache from "./setup/cache/cache.js";
|
|
5
|
+
import * as setupDatabase from "./setup/database/database.js";
|
|
5
6
|
import * as setupDeploy from "./setup/deploy/deploy.js";
|
|
6
7
|
import * as setupDocker from "./setup/docker/docker.js";
|
|
7
8
|
import * as setupGenerator from "./setup/generator/generator.js";
|
|
@@ -20,7 +21,7 @@ import * as setupUi from "./setup/ui/ui.js";
|
|
|
20
21
|
import * as setupUploads from "./setup/uploads/uploads.js";
|
|
21
22
|
const command = "setup <command>";
|
|
22
23
|
const description = "Initialize project config and install packages";
|
|
23
|
-
const builder = (yargs) => yargs.command(setupAuth).command(setupCache).command(setupDeploy).command(setupDocker).command(setupGenerator).command(setupGraphql).command(setupI18n).command(setupJobs).command(setupMailer).command(setupMiddleware).command(setupMonitoring).command(setupNeon).command(setupPackage).command(setupRealtime).command(setupServerFile).command(setupTsconfig).command(setupUi).command(setupUploads).demandCommand().middleware(detectCedarVersion).epilogue(
|
|
24
|
+
const builder = (yargs) => yargs.command(setupAuth).command(setupCache).command(setupDatabase).command(setupDeploy).command(setupDocker).command(setupGenerator).command(setupGraphql).command(setupI18n).command(setupJobs).command(setupMailer).command(setupMiddleware).command(setupMonitoring).command(setupNeon).command(setupPackage).command(setupRealtime).command(setupServerFile).command(setupTsconfig).command(setupUi).command(setupUploads).demandCommand().middleware(detectCedarVersion).epilogue(
|
|
24
25
|
`Also see the ${terminalLink(
|
|
25
26
|
"CedarJS CLI Reference",
|
|
26
27
|
"https://cedarjs.com/docs/cli-commands#setup"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cedarjs/cli",
|
|
3
|
-
"version": "6.0.0-canary.
|
|
3
|
+
"version": "6.0.0-canary.2843",
|
|
4
4
|
"description": "The CedarJS Command Line",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -36,17 +36,17 @@
|
|
|
36
36
|
"@babel/preset-typescript": "7.29.7",
|
|
37
37
|
"@babel/traverse": "7.29.7",
|
|
38
38
|
"@babel/types": "7.29.7",
|
|
39
|
-
"@cedarjs/api-server": "6.0.0-canary.
|
|
40
|
-
"@cedarjs/babel-config": "6.0.0-canary.
|
|
41
|
-
"@cedarjs/cli-helpers": "6.0.0-canary.
|
|
42
|
-
"@cedarjs/internal": "6.0.0-canary.
|
|
43
|
-
"@cedarjs/prerender": "6.0.0-canary.
|
|
44
|
-
"@cedarjs/project-config": "6.0.0-canary.
|
|
45
|
-
"@cedarjs/structure": "6.0.0-canary.
|
|
46
|
-
"@cedarjs/telemetry": "6.0.0-canary.
|
|
47
|
-
"@cedarjs/utils": "6.0.0-canary.
|
|
48
|
-
"@cedarjs/vite": "6.0.0-canary.
|
|
49
|
-
"@cedarjs/web-server": "6.0.0-canary.
|
|
39
|
+
"@cedarjs/api-server": "6.0.0-canary.2843",
|
|
40
|
+
"@cedarjs/babel-config": "6.0.0-canary.2843",
|
|
41
|
+
"@cedarjs/cli-helpers": "6.0.0-canary.2843",
|
|
42
|
+
"@cedarjs/internal": "6.0.0-canary.2843",
|
|
43
|
+
"@cedarjs/prerender": "6.0.0-canary.2843",
|
|
44
|
+
"@cedarjs/project-config": "6.0.0-canary.2843",
|
|
45
|
+
"@cedarjs/structure": "6.0.0-canary.2843",
|
|
46
|
+
"@cedarjs/telemetry": "6.0.0-canary.2843",
|
|
47
|
+
"@cedarjs/utils": "6.0.0-canary.2843",
|
|
48
|
+
"@cedarjs/vite": "6.0.0-canary.2843",
|
|
49
|
+
"@cedarjs/web-server": "6.0.0-canary.2843",
|
|
50
50
|
"@listr2/prompt-adapter-enquirer": "4.3.0",
|
|
51
51
|
"@opentelemetry/api": "1.9.1",
|
|
52
52
|
"@opentelemetry/core": "1.30.1",
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"yargs": "17.7.3"
|
|
95
95
|
},
|
|
96
96
|
"devDependencies": {
|
|
97
|
-
"@cedarjs/framework-tools": "6.0.0-canary.
|
|
97
|
+
"@cedarjs/framework-tools": "6.0.0-canary.2843",
|
|
98
98
|
"@prisma/dmmf": "7.8.0",
|
|
99
99
|
"@types/archiver": "^7.0.0",
|
|
100
100
|
"memfs": "4.64.0",
|
|
File without changes
|