@cedarjs/cli 4.1.1-next.14 → 4.1.1-next.64

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.
@@ -11,6 +11,7 @@ import {
11
11
  } from "@cedarjs/cli-helpers/packageManager/display";
12
12
  import { runBin } from "@cedarjs/cli-helpers/packageManager/exec";
13
13
  import {
14
+ buildApi,
14
15
  buildApiWithVite,
15
16
  cleanApiBuild
16
17
  } from "@cedarjs/internal/dist/build/api";
@@ -20,6 +21,7 @@ import { loadAndValidateSdls } from "@cedarjs/internal/dist/validateSchema";
20
21
  import { detectPrerenderRoutes } from "@cedarjs/prerender/detection";
21
22
  import {} from "@cedarjs/project-config";
22
23
  import { timedTelemetry } from "@cedarjs/telemetry";
24
+ import { buildCedarApp } from "@cedarjs/vite/build";
23
25
  import { buildUDApiServer } from "@cedarjs/vite/buildUDApiServer";
24
26
  import { generatePrismaCommand } from "../../lib/generatePrismaClient.js";
25
27
  import { getPaths, getConfig } from "../../lib/index.js";
@@ -163,25 +165,59 @@ Run ` + c.info(formatCedarCommand(["build"])) + " (without specifying a workspac
163
165
  title: "Verifying graphql schema...",
164
166
  task: loadAndValidateSdls
165
167
  },
166
- // The API build has two sequential steps:
167
- // 1. esbuild compiles api/src/** api/dist/ (functions, services, etc.)
168
- // 2. Vite wraps api/dist/functions/ into a self-contained UD Node server
169
- // entry at api/dist/ud/index.js for `cedar serve api`
170
- // Step 2 depends on step 1 having completed.
171
- workspace.includes("api") && {
168
+ // When streaming SSR is enabled, fall back to the legacy separate build
169
+ // paths because streaming SSR has its own complex build orchestration.
170
+ // Phase 7 (SSR/RSC rebuild) will address unifying this path.
171
+ workspace.includes("api") && getConfig().experimental?.streamingSsr?.enabled && {
172
172
  title: "Building API...",
173
173
  task: async () => {
174
174
  await cleanApiBuild();
175
175
  await buildApiWithVite();
176
176
  }
177
177
  },
178
- ud && workspace.includes("api") && {
179
- title: "Bundling API server entry (Universal Deploy)...",
178
+ workspace.includes("web") && getConfig().experimental?.streamingSsr?.enabled && {
179
+ title: "Building Web...",
180
180
  task: async () => {
181
- await buildUDApiServer({ verbose });
181
+ process.env.VITE_CJS_IGNORE_WARNING = "true";
182
+ const createdRequire = createRequire(import.meta.url);
183
+ const buildBinPath = createdRequire.resolve(
184
+ "@cedarjs/vite/bins/cedar-vite-build.mjs"
185
+ );
186
+ await execa(
187
+ `node ${buildBinPath} --webDir="${cedarPaths.web.base}" --verbose=${verbose}`,
188
+ {
189
+ stdio: verbose ? "inherit" : "pipe",
190
+ shell: true,
191
+ cwd: cedarPaths.web.base
192
+ }
193
+ );
194
+ if (!getConfig().experimental?.streamingSsr?.enabled) {
195
+ console.log("Creating 200.html...");
196
+ const indexHtmlPath = path.join(getPaths().web.dist, "index.html");
197
+ fs.copyFileSync(
198
+ indexHtmlPath,
199
+ path.join(getPaths().web.dist, "200.html")
200
+ );
201
+ }
202
+ }
203
+ },
204
+ // Legacy separate build path (default when not --ud, non-streaming-SSR)
205
+ workspace.includes("api") && !ud && !getConfig().experimental?.streamingSsr?.enabled && {
206
+ title: "Building API...",
207
+ task: async () => {
208
+ await cleanApiBuild();
209
+ const { errors, warnings } = await buildApi();
210
+ if (warnings.length) {
211
+ console.warn(warnings);
212
+ }
213
+ if (errors.length) {
214
+ throw new Error(
215
+ `API build failed with ${errors.length} error(s). See output above for details.`
216
+ );
217
+ }
182
218
  }
183
219
  },
184
- workspace.includes("web") && {
220
+ workspace.includes("web") && !ud && !getConfig().experimental?.streamingSsr?.enabled && {
185
221
  title: "Building Web...",
186
222
  task: async () => {
187
223
  process.env.VITE_CJS_IGNORE_WARNING = "true";
@@ -200,7 +236,30 @@ Run ` + c.info(formatCedarCommand(["build"])) + " (without specifying a workspac
200
236
  cwd: cedarPaths.web.base
201
237
  }
202
238
  );
203
- if (!getConfig().experimental?.streamingSsr?.enabled) {
239
+ console.log("Creating 200.html...");
240
+ const indexHtmlPath = path.join(getPaths().web.dist, "index.html");
241
+ fs.copyFileSync(
242
+ indexHtmlPath,
243
+ path.join(getPaths().web.dist, "200.html")
244
+ );
245
+ }
246
+ },
247
+ // Unified build path (experimental, non-streaming-SSR, --ud)
248
+ (workspace.includes("api") || workspace.includes("web")) && ud && !getConfig().experimental?.streamingSsr?.enabled && {
249
+ title: workspace.includes("api") && workspace.includes("web") ? "Building App..." : workspace.includes("api") ? "Building API..." : "Building Web...",
250
+ task: async () => {
251
+ process.env.VITE_CJS_IGNORE_WARNING = "true";
252
+ if (workspace.includes("api")) {
253
+ await cleanApiBuild();
254
+ }
255
+ const originalCwd = process.cwd();
256
+ process.chdir(cedarPaths.web.base);
257
+ try {
258
+ await buildCedarApp({ verbose, workspace });
259
+ } finally {
260
+ process.chdir(originalCwd);
261
+ }
262
+ if (workspace.includes("web")) {
204
263
  console.log("Creating 200.html...");
205
264
  const indexHtmlPath = path.join(getPaths().web.dist, "index.html");
206
265
  fs.copyFileSync(
@@ -209,6 +268,12 @@ Run ` + c.info(formatCedarCommand(["build"])) + " (without specifying a workspac
209
268
  );
210
269
  }
211
270
  }
271
+ },
272
+ ud && workspace.includes("api") && {
273
+ title: "Bundling API server entry (Universal Deploy)...",
274
+ task: async () => {
275
+ await buildUDApiServer({ verbose });
276
+ }
212
277
  }
213
278
  ].filter((t) => Boolean(t));
214
279
  const triggerPrerender = async () => {
@@ -68,27 +68,64 @@ const handler = async ({
68
68
  }
69
69
  webPortChangeNeeded = webAvailablePort !== webPreferredPort;
70
70
  }
71
- if (webPortChangeNeeded) {
72
- const message = [
73
- "The currently configured port for the development server is",
74
- "unavailable. Suggested change to your port, which can be changed in",
75
- "cedar.toml (or redwood.toml):\n",
76
- ` - Web to use port ${webAvailablePort} instead`,
77
- "of your currently configured",
78
- `${webPreferredPort}
71
+ if (ud) {
72
+ if (webPortChangeNeeded) {
73
+ const message = [
74
+ "The currently configured port for the development server is",
75
+ "unavailable. Suggested change to your port, which can be changed in",
76
+ "cedar.toml (or redwood.toml):\n",
77
+ ` - Web to use port ${webAvailablePort} instead`,
78
+ "of your currently configured",
79
+ `${webPreferredPort}
79
80
  `,
80
- "\nCannot run the development server until your configured port is",
81
- "changed or becomes available."
82
- ].filter(Boolean).join(" ");
83
- exitWithError(void 0, { message });
81
+ "\nCannot run the development server until your configured port is",
82
+ "changed or becomes available."
83
+ ].filter(Boolean).join(" ");
84
+ exitWithError(void 0, { message });
85
+ }
86
+ } else {
87
+ if (apiPortChangeNeeded || webPortChangeNeeded) {
88
+ const message = [
89
+ "The currently configured ports for the development server are",
90
+ "unavailable. Suggested changes to your ports, which can be changed in",
91
+ "cedar.toml (or redwood.toml), are:\n",
92
+ apiPortChangeNeeded && ` - API to use port ${apiAvailablePort} instead`,
93
+ apiPortChangeNeeded && "of your currently configured",
94
+ apiPortChangeNeeded && `${apiPreferredPort}
95
+ `,
96
+ webPortChangeNeeded && ` - Web to use port ${webAvailablePort} instead`,
97
+ webPortChangeNeeded && "of your currently configured",
98
+ webPortChangeNeeded && `${webPreferredPort}
99
+ `,
100
+ "\nCannot run the development server until your configured ports are",
101
+ "changed or become available."
102
+ ].filter(Boolean).join(" ");
103
+ exitWithError(void 0, { message });
104
+ }
84
105
  }
85
106
  if (workspace.includes("api")) {
86
- try {
87
- await generatePrismaClient({ verbose: false, force: false });
88
- } catch (e) {
89
- const message = getErrorMessage(e);
90
- errorTelemetry(process.argv, `Error generating prisma client: ${message}`);
91
- console.error(c.error(message));
107
+ if (generate) {
108
+ try {
109
+ await generatePrismaClient({ verbose: false });
110
+ } catch (e) {
111
+ const message = getErrorMessage(e);
112
+ errorTelemetry(
113
+ process.argv,
114
+ `Error generating prisma client: ${message}`
115
+ );
116
+ console.error(c.error(message));
117
+ }
118
+ }
119
+ if (!ud && !serverFile) {
120
+ try {
121
+ await shutdownPort(apiAvailablePort);
122
+ } catch (e) {
123
+ const message = getErrorMessage(e);
124
+ errorTelemetry(process.argv, `Error shutting down "api": ${message}`);
125
+ console.error(
126
+ `Error whilst shutting down "api" port: ${c.error(message)}`
127
+ );
128
+ }
92
129
  }
93
130
  }
94
131
  if (workspace.includes("web") && webAvailablePort !== void 0) {
@@ -65,7 +65,6 @@ No script called \`${name}\` in the ./scripts folder.
65
65
  title: "Generating Prisma client",
66
66
  enabled: () => !!prisma,
67
67
  task: () => generatePrismaClient({
68
- force: false,
69
68
  verbose: !args.silent,
70
69
  silent: !!args.silent
71
70
  })
@@ -1,5 +1,6 @@
1
1
  import { fork } from "node:child_process";
2
2
  import fs from "node:fs";
3
+ import net from "node:net";
3
4
  import path from "node:path";
4
5
  import { terminalLink } from "termi-link";
5
6
  import * as apiServerCLIConfig from "@cedarjs/api-server/apiCliConfig";
@@ -18,7 +19,14 @@ const builder = async (yargs) => {
18
19
  yargs.command({
19
20
  command: "$0",
20
21
  description: bothServerCLIConfig.description,
21
- builder: bothServerCLIConfig.builder,
22
+ builder: (yargs2) => {
23
+ bothServerCLIConfig.builder(yargs2);
24
+ return yargs2.option("ud", {
25
+ description: "Use the Universal Deploy server for the API side. The web side is served by the existing static file server. Pass --ud to opt in; the default is Fastify for both sides.",
26
+ type: "boolean",
27
+ default: false
28
+ });
29
+ },
22
30
  handler: async (argv) => {
23
31
  recordTelemetryAttributes({
24
32
  command: "serve",
@@ -26,6 +34,110 @@ const builder = async (yargs) => {
26
34
  host: argv.host,
27
35
  socket: argv.socket
28
36
  });
37
+ if (argv.ud) {
38
+ if (argv.port) {
39
+ console.error(
40
+ c.error(
41
+ "\n The --port flag is not supported with --ud. Use --web-port and --api-port instead.\n"
42
+ )
43
+ );
44
+ process.exit(1);
45
+ }
46
+ const udEntryPath = path.join(getPaths().api.dist, "ud", "index.js");
47
+ if (!fs.existsSync(udEntryPath)) {
48
+ console.error(
49
+ c.error(
50
+ `
51
+ Universal Deploy server entry not found at ${udEntryPath}.
52
+ Please run \`yarn cedar build --ud\` before serving.
53
+ `
54
+ )
55
+ );
56
+ process.exit(1);
57
+ }
58
+ const webDistIndexHtml = path.join(getPaths().web.dist, "index.html");
59
+ if (!fs.existsSync(webDistIndexHtml)) {
60
+ console.error(
61
+ c.error(
62
+ "\n Web build artifacts not found.\n Please run `yarn cedar build` before serving.\n"
63
+ )
64
+ );
65
+ process.exit(1);
66
+ }
67
+ if (serverFileExists()) {
68
+ console.warn(
69
+ c.warning(
70
+ "\n Note: api/src/server.ts was detected. This file is a Fastify concept and will be ignored when using --ud. You are testing the experimental UD support, so the behavior will not match your production Fastify setup.\n"
71
+ )
72
+ );
73
+ }
74
+ const { getAPIHost, getAPIPort, getWebHost, getWebPort } = await import("@cedarjs/api-server/cliHelpers");
75
+ const apiPort = argv.apiPort ?? getAPIPort();
76
+ const apiHost = argv.apiHost ?? getAPIHost();
77
+ const webPort = argv.webPort ?? getWebPort();
78
+ const webHost = argv.webHost ?? getWebHost();
79
+ const apiRootPath = argv.apiRootPath ?? "/";
80
+ const apiProxyTarget = [
81
+ "http://",
82
+ apiHost.includes(":") ? `[${apiHost}]` : apiHost,
83
+ ":",
84
+ apiPort,
85
+ apiRootPath
86
+ ].join("");
87
+ const { redwoodFastifyWeb } = await import("@cedarjs/fastify-web");
88
+ const { createFastifyInstance } = await import("@cedarjs/api-server/fastify");
89
+ const webFastify = await createFastifyInstance();
90
+ webFastify.register(redwoodFastifyWeb, {
91
+ redwood: {
92
+ apiProxyTarget
93
+ }
94
+ });
95
+ await webFastify.listen({
96
+ port: webPort,
97
+ host: webHost
98
+ });
99
+ const child = fork(
100
+ udEntryPath,
101
+ ["--port", String(apiPort), "--host", apiHost],
102
+ {
103
+ execArgv: process.execArgv,
104
+ env: {
105
+ ...process.env,
106
+ NODE_ENV: process.env.NODE_ENV ?? "production",
107
+ PORT: String(apiPort),
108
+ HOST: apiHost
109
+ }
110
+ }
111
+ );
112
+ child.on("error", (err) => {
113
+ console.error(
114
+ c.error(`
115
+ Failed to start UD API server: ${err.message}
116
+ `)
117
+ );
118
+ process.exit(1);
119
+ });
120
+ child.on("exit", (code) => {
121
+ if (code !== 0) {
122
+ console.error(
123
+ c.error(`
124
+ UD API server exited with code ${code}
125
+ `)
126
+ );
127
+ process.exit(1);
128
+ }
129
+ });
130
+ console.log(`Web server listening at http://${webHost}:${webPort}`);
131
+ process.stdout.write(
132
+ `API server starting at http://${apiHost}:${apiPort}...`
133
+ );
134
+ await waitForPort(apiHost, apiPort);
135
+ process.stdout.write(
136
+ `\rAPI server listening at http://${apiHost}:${apiPort}
137
+ `
138
+ );
139
+ return;
140
+ }
29
141
  if (serverFileExists()) {
30
142
  const serveBothHandlers = await import("./serveBothHandler.js");
31
143
  await serveBothHandlers.bothServerFileHandler(argv);
@@ -51,7 +163,7 @@ const builder = async (yargs) => {
51
163
  return yargs2.option("ud", {
52
164
  // UD serving is opt-in. Pass --ud to use the new srvx server instead
53
165
  // of the legacy Fastify server.
54
- description: "Use the Universal Deploy server (srvx). Pass --ud to opt in; the default is Fastify.",
166
+ description: "Use the Universal Deploy server. Pass --ud to opt in; the default is Fastify.",
55
167
  type: "boolean",
56
168
  default: false
57
169
  });
@@ -71,7 +183,7 @@ const builder = async (yargs) => {
71
183
  c.error(
72
184
  `
73
185
  Universal Deploy server entry not found at ${udEntryPath}.
74
- Please run \`yarn cedar build api\` before serving.
186
+ Please run \`yarn cedar build --ud\` before serving.
75
187
  `
76
188
  )
77
189
  );
@@ -84,17 +196,34 @@ const builder = async (yargs) => {
84
196
  if (argv.host) {
85
197
  udArgs.push("--host", argv.host);
86
198
  }
199
+ const child = fork(udEntryPath, udArgs, {
200
+ execArgv: process.execArgv,
201
+ env: {
202
+ ...process.env,
203
+ NODE_ENV: process.env.NODE_ENV ?? "production",
204
+ PORT: argv.port ? String(argv.port) : process.env.PORT,
205
+ HOST: argv.host ?? process.env.HOST
206
+ }
207
+ });
208
+ child.on("error", (err) => {
209
+ console.error(
210
+ c.error(`
211
+ Failed to start UD server: ${err.message}
212
+ `)
213
+ );
214
+ process.exit(1);
215
+ });
216
+ const apiPort = argv.port ?? parseInt(process.env.PORT ?? "8911", 10);
217
+ const apiHost = argv.host ?? process.env.HOST ?? "localhost";
218
+ process.stdout.write(
219
+ `API server starting at http://${apiHost}:${apiPort}...`
220
+ );
221
+ await waitForPort(apiHost, apiPort);
222
+ process.stdout.write(
223
+ `\rAPI server listening at http://${apiHost}:${apiPort}
224
+ `
225
+ );
87
226
  await new Promise((resolve, reject) => {
88
- const child = fork(udEntryPath, udArgs, {
89
- execArgv: process.execArgv,
90
- env: {
91
- ...process.env,
92
- NODE_ENV: process.env.NODE_ENV ?? "production",
93
- PORT: argv.port ? String(argv.port) : process.env.PORT,
94
- HOST: argv.host ?? process.env.HOST
95
- }
96
- });
97
- child.on("error", reject);
98
227
  child.on("exit", (code) => {
99
228
  if (code !== 0) {
100
229
  reject(new Error(`UD server exited with code ${code}`));
@@ -166,6 +295,20 @@ const builder = async (yargs) => {
166
295
  );
167
296
  process.exit(1);
168
297
  }
298
+ if (argv.ud) {
299
+ const udEntryPath = path.join(getPaths().api.dist, "ud", "index.js");
300
+ if (!fs.existsSync(udEntryPath)) {
301
+ console.error(
302
+ c.error(
303
+ `
304
+ Universal Deploy server entry not found at ${udEntryPath}.
305
+ Please run \`yarn cedar build --ud\` before serving.
306
+ `
307
+ )
308
+ );
309
+ process.exit(1);
310
+ }
311
+ }
169
312
  }
170
313
  if (positionalArgs.length === 1) {
171
314
  if (!apiSideExists && !rscEnabled) {
@@ -176,14 +319,38 @@ const builder = async (yargs) => {
176
319
  );
177
320
  process.exit(1);
178
321
  }
179
- const apiExistsButIsNotBuilt = apiSideExists && !fs.existsSync(getPaths().api.dist);
180
- if (apiExistsButIsNotBuilt || !webSideIsBuilt(streamingEnabled || rscEnabled)) {
181
- console.error(
182
- c.error(
183
- "\nPlease run `yarn cedar build` before trying to serve your Cedar app.\n"
184
- )
185
- );
186
- process.exit(1);
322
+ if (argv.ud) {
323
+ const udEntryPath = path.join(getPaths().api.dist, "ud", "index.js");
324
+ if (!fs.existsSync(udEntryPath)) {
325
+ console.error(
326
+ c.error(
327
+ `
328
+ Universal Deploy server entry not found at ${udEntryPath}.
329
+ Please run \`yarn cedar build --ud\` before serving.
330
+ `
331
+ )
332
+ );
333
+ process.exit(1);
334
+ }
335
+ const webDistIndexHtml = path.join(getPaths().web.dist, "index.html");
336
+ if (!fs.existsSync(webDistIndexHtml)) {
337
+ console.error(
338
+ c.error(
339
+ "\n Web build artifacts not found.\n Please run `yarn cedar build` before serving.\n"
340
+ )
341
+ );
342
+ process.exit(1);
343
+ }
344
+ } else {
345
+ const apiExistsButIsNotBuilt = apiSideExists && !fs.existsSync(getPaths().api.dist);
346
+ if (apiExistsButIsNotBuilt || !webSideIsBuilt(streamingEnabled || rscEnabled)) {
347
+ console.error(
348
+ c.error(
349
+ "\nPlease run `yarn cedar build` before trying to serve your Cedar app.\n"
350
+ )
351
+ );
352
+ process.exit(1);
353
+ }
187
354
  }
188
355
  }
189
356
  if (!process.env.NODE_ENV) {
@@ -205,6 +372,34 @@ function webSideIsBuilt(isStreamingOrRSC) {
205
372
  return fs.existsSync(path.join(getPaths().web.dist, "index.html"));
206
373
  }
207
374
  }
375
+ function waitForPort(host, port) {
376
+ const maxAttempts = 50;
377
+ const intervalMs = 200;
378
+ return new Promise((resolve, reject) => {
379
+ let attempts = 0;
380
+ const tryConnect = () => {
381
+ attempts++;
382
+ const socket = net.createConnection({ host, port });
383
+ socket.on("connect", () => {
384
+ socket.destroy();
385
+ resolve();
386
+ });
387
+ socket.on("error", () => {
388
+ socket.destroy();
389
+ if (attempts >= maxAttempts) {
390
+ reject(
391
+ new Error(
392
+ `API server did not become ready on port ${port} after ${maxAttempts * intervalMs}ms`
393
+ )
394
+ );
395
+ } else {
396
+ setTimeout(tryConnect, intervalMs);
397
+ }
398
+ });
399
+ };
400
+ tryConnect();
401
+ });
402
+ }
208
403
  export {
209
404
  builder,
210
405
  command,
@@ -0,0 +1,25 @@
1
+ import { recordTelemetryAttributes } from "@cedarjs/cli-helpers";
2
+ const command = "neon";
3
+ const description = "Provision a Neon Postgres database and configure your project";
4
+ function builder(yargs) {
5
+ return yargs.option("force", {
6
+ alias: "f",
7
+ default: false,
8
+ description: "Overwrite existing DATABASE_URL in .env",
9
+ type: "boolean"
10
+ });
11
+ }
12
+ async function handler({ force }) {
13
+ recordTelemetryAttributes({
14
+ command: "setup neon",
15
+ force
16
+ });
17
+ const { handler: handler2 } = await import("./neonHandler.js");
18
+ return handler2({ force });
19
+ }
20
+ export {
21
+ builder,
22
+ command,
23
+ description,
24
+ handler
25
+ };
@@ -0,0 +1,385 @@
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 { addWorkspacePackages } from "@cedarjs/cli-helpers/packageManager/packages";
7
+ import { errorTelemetry } from "@cedarjs/telemetry";
8
+ const cedarPaths = getPaths();
9
+ async function handler({ force }) {
10
+ const schemaPath = path.join(cedarPaths.api.base, "db", "schema.prisma");
11
+ const dbTsPath = path.join(cedarPaths.api.src, "lib", "db.ts");
12
+ const prismaConfigPathCjs = path.join(
13
+ cedarPaths.api.base,
14
+ "prisma.config.cjs"
15
+ );
16
+ const prismaConfigPathMts = path.join(
17
+ cedarPaths.api.base,
18
+ "prisma.config.mts"
19
+ );
20
+ const envPath = path.join(cedarPaths.base, ".env");
21
+ const rootPkgPath = path.join(cedarPaths.base, "package.json");
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;
29
+ if (fs.existsSync(envPath)) {
30
+ hasDirectDatabaseUrl = /^DATABASE_URL=/m.test(
31
+ fs.readFileSync(envPath, "utf-8")
32
+ );
33
+ }
34
+ const notes = [];
35
+ const tasks = new Listr(
36
+ [
37
+ {
38
+ title: "Checking current database configuration",
39
+ task: (ctx) => {
40
+ const schemaContent = fs.readFileSync(schemaPath, "utf-8");
41
+ ctx.schemaContent = schemaContent;
42
+ ctx.isSqlite = schemaContent.includes('provider = "sqlite"');
43
+ ctx.isPostgres = schemaContent.includes('provider = "postgresql"');
44
+ if (fs.existsSync(dbTsPath)) {
45
+ ctx.dbTsContent = fs.readFileSync(dbTsPath, "utf-8");
46
+ ctx.isNeon = ctx.dbTsContent.includes("PrismaPg");
47
+ } else {
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"'
141
+ );
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
+ const configPath = fs.existsSync(prismaConfigPathCjs) ? prismaConfigPathCjs : prismaConfigPathMts;
185
+ const configContent = fs.readFileSync(configPath, "utf-8");
186
+ const updated = configContent.replace(
187
+ /env\(["']DATABASE_URL["']\)/,
188
+ "env('DIRECT_DATABASE_URL')"
189
+ );
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
+ }
201
+ },
202
+ {
203
+ title: "Provisioning Neon database",
204
+ skip: (ctx) => {
205
+ if (ctx.unsupportedProvider) {
206
+ return true;
207
+ }
208
+ if (hasDirectDatabaseUrl && !force) {
209
+ return true;
210
+ }
211
+ return false;
212
+ },
213
+ task: async (ctx) => {
214
+ const res = await fetch("https://neon.new/api/v1/database", {
215
+ method: "POST",
216
+ headers: { "Content-Type": "application/json" },
217
+ body: JSON.stringify({ ref: "cedarjs" })
218
+ });
219
+ if (!res.ok) {
220
+ throw new Error(`Neon API returned ${res.status} ${res.statusText}`);
221
+ }
222
+ const data = await res.json();
223
+ if (!data.connection_string || !data.expires_at || !data.claim_url) {
224
+ throw new Error(
225
+ "Neon API returned an invalid response\n\n" + JSON.stringify(data, null, 2)
226
+ );
227
+ }
228
+ ctx.databaseUrl = data.connection_string;
229
+ ctx.databaseUrlDirect = data.connection_string.replace(
230
+ "-pooler.",
231
+ "."
232
+ );
233
+ if (ctx.databaseUrlDirect === ctx.databaseUrl) {
234
+ throw new Error(
235
+ 'Could not derive a direct (non-pooler) connection string from the Neon response. Expected the connection string to contain "-pooler." in the hostname.'
236
+ );
237
+ }
238
+ ctx.neonClaimUrl = data.claim_url;
239
+ ctx.neonClaimExpiry = new Date(data.expires_at).toUTCString();
240
+ }
241
+ },
242
+ {
243
+ title: "Writing database connection to .env",
244
+ skip: (ctx) => {
245
+ if (ctx.unsupportedProvider) {
246
+ return true;
247
+ }
248
+ if (hasDirectDatabaseUrl && !force) {
249
+ return true;
250
+ }
251
+ if (!ctx.databaseUrl) {
252
+ return "No database URL to write (Neon provisioning skipped)";
253
+ }
254
+ return false;
255
+ },
256
+ task: (ctx) => {
257
+ let envContent = "";
258
+ if (fs.existsSync(envPath)) {
259
+ envContent = fs.readFileSync(envPath, "utf-8");
260
+ if (force) {
261
+ const lines = envContent.split("\n");
262
+ const filtered = lines.filter(
263
+ (line) => !line.startsWith("DATABASE_URL=") && !line.startsWith("DIRECT_DATABASE_URL=")
264
+ );
265
+ envContent = filtered.join("\n").trimEnd();
266
+ }
267
+ if (envContent && !envContent.endsWith("\n")) {
268
+ envContent += "\n";
269
+ }
270
+ }
271
+ envContent += `DATABASE_URL=${ctx.databaseUrl}
272
+ `;
273
+ envContent += `DIRECT_DATABASE_URL=${ctx.databaseUrlDirect}
274
+ `;
275
+ fs.writeFileSync(envPath, envContent);
276
+ }
277
+ },
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
+ {
314
+ title: "One more thing...",
315
+ task: (ctx, task) => {
316
+ if (ctx.unsupportedProvider) {
317
+ task.output = "Skipped \u2014 unsupported database provider";
318
+ return;
319
+ }
320
+ if (ctx.skipWithNote) {
321
+ task.output = "Skipped \u2014 DATABASE_URL already configured";
322
+ return;
323
+ }
324
+ const claimMsg = [
325
+ colors.important(
326
+ "Your Neon database has been created and is ready to use!"
327
+ ),
328
+ "",
329
+ `Claim URL: ${colors.underline(ctx.neonClaimUrl || "N/A")}`,
330
+ `Expires: ${ctx.neonClaimExpiry || "N/A"}`,
331
+ "",
332
+ "Claim your database to keep it beyond the expiration date."
333
+ ];
334
+ notes.push(...claimMsg);
335
+ }
336
+ }
337
+ ],
338
+ {
339
+ exitOnError: false
340
+ }
341
+ );
342
+ try {
343
+ await tasks.run();
344
+ if (notes.length > 0) {
345
+ console.log();
346
+ console.log(notes.join("\n"));
347
+ }
348
+ } catch (e) {
349
+ if (isErrorWithMessage(e)) {
350
+ errorTelemetry(process.argv, e.message);
351
+ console.error(colors.error(e.message));
352
+ }
353
+ if (isErrorWithExitCode(e)) {
354
+ process.exit(e.exitCode);
355
+ }
356
+ process.exit(1);
357
+ }
358
+ }
359
+ function isErrorWithMessage(e) {
360
+ return !!e && typeof e === "object" && "message" in e;
361
+ }
362
+ function isErrorWithExitCode(e) {
363
+ return !!e && typeof e === "object" && "exitCode" in e && typeof e.exitCode === "number";
364
+ }
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
+ export {
384
+ handler
385
+ };
@@ -0,0 +1,32 @@
1
+ import { PrismaPg } from '@prisma/adapter-pg'
2
+ import { PrismaClient } from 'api/db/generated/prisma/client.mts'
3
+
4
+ import { emitLogLevels, handlePrismaLogging } from '@cedarjs/api/logger'
5
+
6
+ import { logger } from './logger.js'
7
+
8
+ export * from 'api/db/generated/prisma/client.mts'
9
+
10
+ if (!process.env.DATABASE_URL) {
11
+ throw new Error('DATABASE_URL environment variable is not set')
12
+ }
13
+
14
+ const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL })
15
+ const prismaClient = new PrismaClient({
16
+ log: emitLogLevels(['info', 'warn', 'error']),
17
+ adapter,
18
+ })
19
+
20
+ handlePrismaLogging({
21
+ db: prismaClient,
22
+ logger,
23
+ logLevels: ['info', 'warn', 'error'],
24
+ })
25
+
26
+ /**
27
+ * Global Prisma client extensions should be added here, as $extend
28
+ * returns a new instance.
29
+ * export const db = prismaClient.$extend(...)
30
+ * Add any .$on hooks before using $extend
31
+ */
32
+ export const db = prismaClient
@@ -11,6 +11,7 @@ import * as setupJobs from "./setup/jobs/jobs.js";
11
11
  import * as setupMailer from "./setup/mailer/mailer.js";
12
12
  import * as setupMiddleware from "./setup/middleware/middleware.js";
13
13
  import * as setupMonitoring from "./setup/monitoring/monitoring.js";
14
+ import * as setupNeon from "./setup/neon/neon.js";
14
15
  import * as setupPackage from "./setup/package/package.js";
15
16
  import * as setupRealtime from "./setup/realtime/realtime.js";
16
17
  import * as setupServerFile from "./setup/server-file/serverFile.js";
@@ -20,7 +21,7 @@ import * as setupUploads from "./setup/uploads/uploads.js";
20
21
  import * as setupVite from "./setup/vite/vite.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(setupPackage).command(setupRealtime).command(setupServerFile).command(setupTsconfig).command(setupUi).command(setupUploads).command(setupVite).demandCommand().middleware(detectCedarVersion).epilogue(
24
+ 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).command(setupVite).demandCommand().middleware(detectCedarVersion).epilogue(
24
25
  `Also see the ${terminalLink(
25
26
  "CedarJS CLI Reference",
26
27
  "https://cedarjs.com/docs/cli-commands#setup"
@@ -440,7 +440,7 @@ async function downloadYarnPatches(ctx, { dryRun, verbose }) {
440
440
  }
441
441
  async function refreshPrismaClient(task, { verbose }) {
442
442
  try {
443
- await generatePrismaClient({ verbose, force: false });
443
+ await generatePrismaClient({ verbose, force: true });
444
444
  } catch (e) {
445
445
  const message = e instanceof Error ? e.message : String(e);
446
446
  task.skip("Refreshing the Prisma client caused an Error.");
package/dist/lib/exec.js CHANGED
@@ -1,8 +1,6 @@
1
+ import { existsSync } from "node:fs";
1
2
  import path from "node:path";
2
- import { createServer, version as viteVersion } from "vite";
3
- import { ViteNodeRunner } from "vite-node/client";
4
- import { ViteNodeServer } from "vite-node/server";
5
- import { installSourcemapsSupport } from "vite-node/source-map";
3
+ import { createServer, isRunnableDevEnvironment } from "vite";
6
4
  import { getPaths, importStatementPath } from "@cedarjs/project-config";
7
5
  import {
8
6
  cedarCellTransform,
@@ -10,8 +8,21 @@ import {
10
8
  cedarjsJobPathInjectorPlugin,
11
9
  cedarSwapApolloProvider,
12
10
  cedarImportDirPlugin,
13
- cedarAutoImportsPlugin
11
+ cedarAutoImportsPlugin,
12
+ cedarCjsCompatPlugin
14
13
  } from "@cedarjs/vite";
14
+ function resolveExtension(id) {
15
+ if (existsSync(id)) {
16
+ return id;
17
+ }
18
+ const withoutExt = /\.jsx?$/.test(id) ? id.replace(/\.jsx?$/, "") : id;
19
+ for (const ext of [".ts", ".tsx", ".js", ".jsx"]) {
20
+ if (existsSync(withoutExt + ext)) {
21
+ return withoutExt + ext;
22
+ }
23
+ }
24
+ return id;
25
+ }
15
26
  async function runScriptFunction({
16
27
  path: scriptPath,
17
28
  functionName,
@@ -22,10 +33,16 @@ async function runScriptFunction({
22
33
  const server = await createServer({
23
34
  mode: "production",
24
35
  optimizeDeps: {
25
- // This is recommended in the vite-node readme
26
36
  noDiscovery: true,
27
37
  include: void 0
28
38
  },
39
+ server: {
40
+ hmr: false,
41
+ watch: null
42
+ },
43
+ environments: {
44
+ nodeRunnerEnv: {}
45
+ },
29
46
  resolve: {
30
47
  alias: [
31
48
  {
@@ -52,18 +69,12 @@ async function runScriptFunction({
52
69
  const webImportBase = importStatementPath(getPaths().web.base);
53
70
  if (importer.startsWith(apiImportBase)) {
54
71
  const apiImportSrc = importStatementPath(getPaths().api.src);
55
- let resolvedId = id.replace("src", apiImportSrc);
56
- if (importer.endsWith(".ts") || importer.endsWith(".tsx")) {
57
- resolvedId = resolvedId.replace(/\.jsx?$/, "");
58
- }
59
- return { id: resolvedId };
72
+ const resolvedId = id.replace("src", apiImportSrc);
73
+ return { id: resolveExtension(resolvedId) };
60
74
  } else if (importer.startsWith(webImportBase)) {
61
75
  const webImportSrc = importStatementPath(getPaths().web.src);
62
- let resolvedId = id.replace("src", webImportSrc);
63
- if (importer.endsWith(".ts") || importer.endsWith(".tsx")) {
64
- resolvedId = resolvedId.replace(/\.jsx?$/, "");
65
- }
66
- return { id: resolvedId };
76
+ const resolvedId = id.replace("src", webImportSrc);
77
+ return { id: resolveExtension(resolvedId) };
67
78
  }
68
79
  return null;
69
80
  }
@@ -71,6 +82,7 @@ async function runScriptFunction({
71
82
  ]
72
83
  },
73
84
  plugins: [
85
+ cedarCjsCompatPlugin(),
74
86
  cedarjsResolveCedarStyleImportsPlugin(),
75
87
  cedarCellTransform(),
76
88
  cedarjsJobPathInjectorPlugin(),
@@ -79,41 +91,21 @@ async function runScriptFunction({
79
91
  cedarAutoImportsPlugin()
80
92
  ]
81
93
  });
82
- if (Number(viteVersion.split(".")[0]) < 6) {
83
- await server.pluginContainer.buildStart({});
94
+ const env = server.environments.nodeRunnerEnv;
95
+ if (!env || !isRunnableDevEnvironment(env)) {
96
+ await server.close();
97
+ throw new Error("Vite environment is not runnable.");
84
98
  }
85
- const node = new ViteNodeServer(server, {
86
- transformMode: {
87
- ssr: [/.*/],
88
- web: [/\/web\//]
89
- },
90
- deps: {
91
- fallbackCJS: true
92
- }
93
- });
94
- installSourcemapsSupport({
95
- getSourceMap: (source) => node.getSourceMap(source)
96
- });
97
- const runner = new ViteNodeRunner({
98
- root: server.config.root,
99
- base: server.config.base,
100
- fetchModule(id) {
101
- return node.fetchModule(id);
102
- },
103
- resolveId(id, importer) {
104
- return node.resolveId(id, importer);
105
- }
106
- });
107
99
  let returnValue;
108
100
  let scriptError = null;
109
101
  try {
110
- const script = await runner.executeFile(scriptPath);
102
+ const script = await env.runner.import(scriptPath);
111
103
  returnValue = await script[functionName](args);
112
104
  } catch (error) {
113
105
  scriptError = error;
114
106
  }
115
107
  try {
116
- const { db } = await runner.executeFile(path.join(getPaths().api.lib, "db"));
108
+ const { db } = await env.runner.import(path.join(getPaths().api.lib, "db"));
117
109
  db.$disconnect();
118
110
  } catch (e) {
119
111
  }
@@ -1,8 +1,11 @@
1
+ import { createHash } from "node:crypto";
1
2
  import fs from "node:fs";
2
3
  import { createRequire } from "node:module";
3
- import { resolveGeneratedPrismaClient } from "@cedarjs/project-config";
4
+ import path from "node:path";
5
+ import { getConfig, getPrismaSchemas } from "@cedarjs/project-config";
4
6
  import { runCommandTask, getPaths } from "./index.js";
5
7
  const generatePrismaCommand = async () => {
8
+ const config = getConfig();
6
9
  const createdRequire = createRequire(import.meta.url);
7
10
  const prismaIndexPath = createdRequire.resolve("prisma/build/index.js");
8
11
  return {
@@ -10,19 +13,52 @@ const generatePrismaCommand = async () => {
10
13
  args: [
11
14
  prismaIndexPath,
12
15
  "generate",
13
- `--config=${getPaths().api.prismaConfig}`
16
+ `--config=${getPaths().api.prismaConfig}`,
17
+ ...config.api.prismaGenerateArgs
14
18
  ]
15
19
  };
16
20
  };
17
- const generatePrismaClient = async ({
21
+ async function computePrismaSchemaHash() {
22
+ try {
23
+ const hash = createHash("sha256");
24
+ const configPath = getPaths().api.prismaConfig;
25
+ if (fs.existsSync(configPath)) {
26
+ hash.update(fs.readFileSync(configPath));
27
+ }
28
+ const { schemas } = await getPrismaSchemas();
29
+ for (const schema of schemas) {
30
+ hash.update(schema[1]);
31
+ }
32
+ return hash.digest("hex");
33
+ } catch {
34
+ return null;
35
+ }
36
+ }
37
+ function getHashFilePath() {
38
+ const generatedBase = getPaths().generated.base;
39
+ return path.join(generatedBase, "prisma-schema-hash");
40
+ }
41
+ function getStoredSchemaHash() {
42
+ const hashFile = getHashFilePath();
43
+ if (fs.existsSync(hashFile)) {
44
+ return fs.readFileSync(hashFile, "utf-8").trim();
45
+ }
46
+ return null;
47
+ }
48
+ function storeSchemaHash(hash) {
49
+ const hashFile = getHashFilePath();
50
+ fs.mkdirSync(path.dirname(hashFile), { recursive: true });
51
+ fs.writeFileSync(hashFile, hash);
52
+ }
53
+ async function generatePrismaClient({
18
54
  verbose = true,
19
- force = true,
55
+ force = false,
20
56
  silent = false
21
- } = {}) => {
57
+ } = {}) {
58
+ const hash = await computePrismaSchemaHash();
22
59
  if (!force) {
23
- const { clientPath } = await resolveGeneratedPrismaClient();
24
- const prismaClientFile = clientPath && fs.existsSync(clientPath) ? fs.readFileSync(clientPath, "utf8") : "";
25
- if (!prismaClientFile.includes("@prisma/client did not initialize yet.") && prismaClientFile.includes("exports.Prisma.")) {
60
+ const storedHash = hash ? getStoredSchemaHash() : null;
61
+ if (hash !== null && hash === storedHash) {
26
62
  return;
27
63
  }
28
64
  }
@@ -38,7 +74,10 @@ const generatePrismaClient = async ({
38
74
  silent
39
75
  }
40
76
  );
41
- };
77
+ if (hash !== null) {
78
+ storeSchemaHash(hash);
79
+ }
80
+ }
42
81
  export {
43
82
  generatePrismaClient,
44
83
  generatePrismaCommand
@@ -32,11 +32,15 @@ function unsetLock(identifier) {
32
32
  }
33
33
  function isLockSet(identifier) {
34
34
  const lockfilePath = path.join(getPaths().generated.base, "locks", identifier);
35
- const exists = fs.existsSync(lockfilePath);
36
- if (!exists) {
37
- return false;
35
+ let createdAt;
36
+ try {
37
+ createdAt = fs.statSync(lockfilePath).birthtimeMs;
38
+ } catch (error) {
39
+ if (isErrorWithCode(error, "ENOENT")) {
40
+ return false;
41
+ }
42
+ throw error;
38
43
  }
39
- const createdAt = fs.statSync(lockfilePath).birthtimeMs;
40
44
  if (Date.now() - createdAt > 36e5) {
41
45
  unsetLock(identifier);
42
46
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cedarjs/cli",
3
- "version": "4.1.1-next.14+69388bd68a",
3
+ "version": "4.1.1-next.64",
4
4
  "description": "The CedarJS Command Line",
5
5
  "repository": {
6
6
  "type": "git",
@@ -31,18 +31,19 @@
31
31
  "test:watch": "vitest watch"
32
32
  },
33
33
  "dependencies": {
34
- "@babel/parser": "7.29.2",
34
+ "@babel/parser": "7.29.3",
35
35
  "@babel/preset-typescript": "7.28.5",
36
- "@cedarjs/api-server": "4.1.1-next.14+69388bd68a",
37
- "@cedarjs/cli-helpers": "4.1.1-next.14+69388bd68a",
38
- "@cedarjs/fastify-web": "4.1.1-next.14+69388bd68a",
39
- "@cedarjs/internal": "4.1.1-next.14+69388bd68a",
40
- "@cedarjs/prerender": "4.1.1-next.14+69388bd68a",
41
- "@cedarjs/project-config": "4.1.1-next.14+69388bd68a",
42
- "@cedarjs/structure": "4.1.1-next.14+69388bd68a",
43
- "@cedarjs/telemetry": "4.1.1-next.14+69388bd68a",
44
- "@cedarjs/utils": "4.1.1-next.14+69388bd68a",
45
- "@cedarjs/web-server": "4.1.1-next.14+69388bd68a",
36
+ "@cedarjs/api-server": "4.1.0",
37
+ "@cedarjs/cli-helpers": "4.1.0",
38
+ "@cedarjs/fastify-web": "4.1.0",
39
+ "@cedarjs/internal": "4.1.0",
40
+ "@cedarjs/prerender": "4.1.0",
41
+ "@cedarjs/project-config": "4.1.0",
42
+ "@cedarjs/structure": "4.1.0",
43
+ "@cedarjs/telemetry": "4.1.0",
44
+ "@cedarjs/utils": "4.1.0",
45
+ "@cedarjs/vite": "4.1.0",
46
+ "@cedarjs/web-server": "4.1.0",
46
47
  "@listr2/prompt-adapter-enquirer": "4.2.1",
47
48
  "@opentelemetry/api": "1.9.0",
48
49
  "@opentelemetry/core": "1.30.1",
@@ -88,7 +89,6 @@
88
89
  "title-case": "3.0.3",
89
90
  "unionfs": "4.6.0",
90
91
  "uuid": "11.1.0",
91
- "vite-node": "3.2.4",
92
92
  "yargs": "17.7.2"
93
93
  },
94
94
  "devDependencies": {
@@ -108,5 +108,5 @@
108
108
  "publishConfig": {
109
109
  "access": "public"
110
110
  },
111
- "gitHead": "69388bd68ad03ce3b1afd399167899369c2c08c1"
111
+ "gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1"
112
112
  }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 Cedar
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.