@cedarjs/cli 6.0.0-rc.189 → 6.0.0-rc.221

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.
@@ -114,7 +114,7 @@ const handler = async ({
114
114
  },
115
115
  task: (_ctx, task) => {
116
116
  task.output = [
117
- "Please add the following to your 'redwoodFastifyGraphQLServer' plugin options to enable OTel for your graphql",
117
+ "Please add the following to your 'cedarFastifyGraphQLServer' plugin options to enable OTel for your graphql",
118
118
  "openTelemetryOptions: {",
119
119
  " resolvers: true,",
120
120
  " result: true,",
@@ -42,6 +42,7 @@ import { files as serviceFiles } from "../service/serviceHandler.js";
42
42
  import { customOrDefaultTemplatePath } from "../yargsHandlerHelpers.js";
43
43
  const SKIPPABLE_ASSETS = ["scaffold.css"];
44
44
  const PACKAGE_SET = "Set";
45
+ const PACKAGE_PRIVATE_SET = "PrivateSet";
45
46
  const getIdType = (model) => {
46
47
  return model.fields.find((field) => field.isId)?.type;
47
48
  };
@@ -512,6 +513,65 @@ const addHelperPackages = async (task) => {
512
513
  await removeWorkspacePackages("web", ["humanize-string"]);
513
514
  });
514
515
  };
516
+ const isAuthSetup = () => {
517
+ const extensions = ["ts", "js", "tsx", "jsx"];
518
+ return extensions.some(
519
+ (ext) => fs.existsSync(path.join(getPaths().web.src, "auth." + ext))
520
+ );
521
+ };
522
+ const ROUTE_TAG_RE = /<Route\s+([^>]*?)\/?>/g;
523
+ const extractRouteAttr = (tagAttrs, attrName) => tagAttrs.match(new RegExp(`\\b${attrName}=["']([^"']+)["']`))?.[1];
524
+ const getRoutesFileContent = () => {
525
+ const routesPath = getPaths().web.routes;
526
+ if (!fs.existsSync(routesPath)) {
527
+ return void 0;
528
+ }
529
+ return readFile(routesPath).toString();
530
+ };
531
+ const hasLoginRoute = () => {
532
+ const content = getRoutesFileContent();
533
+ if (!content) {
534
+ return false;
535
+ }
536
+ return Array.from(content.matchAll(ROUTE_TAG_RE)).some(
537
+ ([, attrs]) => extractRouteAttr(attrs, "name") === "login"
538
+ );
539
+ };
540
+ const findUnprotectedLandingPageRouteName = () => {
541
+ const content = getRoutesFileContent();
542
+ if (!content) {
543
+ return void 0;
544
+ }
545
+ const privateSetRanges = Array.from(
546
+ content.matchAll(/<PrivateSet\b[^>]*>([\s\S]*?)<\/PrivateSet>/g)
547
+ ).map((match) => ({
548
+ start: match.index ?? 0,
549
+ end: (match.index ?? 0) + match[0].length
550
+ }));
551
+ for (const match of content.matchAll(ROUTE_TAG_RE)) {
552
+ const [, attrs] = match;
553
+ if (extractRouteAttr(attrs, "path") !== "/") {
554
+ continue;
555
+ }
556
+ const tagStart = match.index ?? 0;
557
+ const isProtected = privateSetRanges.some(
558
+ (range) => tagStart >= range.start && tagStart < range.end
559
+ );
560
+ if (!isProtected) {
561
+ return extractRouteAttr(attrs, "name");
562
+ }
563
+ }
564
+ return void 0;
565
+ };
566
+ const getUnauthenticatedRedirectRoute = () => {
567
+ if (!isAuthSetup()) {
568
+ return void 0;
569
+ }
570
+ if (hasLoginRoute()) {
571
+ return "login";
572
+ }
573
+ return findUnprotectedLandingPageRouteName();
574
+ };
515
575
  const addSetImport = (task) => {
516
576
  const routesPath = getPaths().web.routes;
517
577
  const routesContent = readFile(routesPath).toString();
@@ -525,15 +585,19 @@ const addSetImport = (task) => {
525
585
  return void 0;
526
586
  }
527
587
  const routerImports = importContent.replace(/\s/g, "").split(",");
528
- if (routerImports.includes(PACKAGE_SET)) {
588
+ const namesToImport = [
589
+ PACKAGE_SET,
590
+ ...getUnauthenticatedRedirectRoute() ? [PACKAGE_PRIVATE_SET] : []
591
+ ].filter((name) => !routerImports.includes(name));
592
+ if (!namesToImport.length) {
529
593
  return "Skipping Set import";
530
594
  }
531
595
  const newRoutesContent = routesContent.replace(
532
596
  cedarRouterImport,
533
- importStart + spacing + PACKAGE_SET + `,` + spacing + importContent + importEnd
597
+ importStart + spacing + namesToImport.join("," + spacing) + `,` + spacing + importContent + importEnd
534
598
  );
535
599
  writeFile(routesPath, newRoutesContent, { overwriteExisting: true });
536
- return "Added Set import to Routes.{jsx,tsx}";
600
+ return `Added ${namesToImport.join(", ")} import to Routes.{jsx,tsx}`;
537
601
  };
538
602
  const addScaffoldSetToRouter = async (model, scaffoldPath) => {
539
603
  const templateNames = getTemplateStrings(model, scaffoldPath);
@@ -542,10 +606,12 @@ const addScaffoldSetToRouter = async (model, scaffoldPath) => {
542
606
  const titleTo = templateNames.pluralRouteName;
543
607
  const buttonLabel = `New ${nameVars.singularPascalName}`;
544
608
  const buttonTo = templateNames.newRouteName;
609
+ const unauthenticatedRoute = getUnauthenticatedRedirectRoute();
545
610
  return addRoutesToRouterTask(
546
611
  await routes({ model, path: scaffoldPath }),
547
612
  "ScaffoldLayout",
548
- { title, titleTo, buttonLabel, buttonTo }
613
+ { title, titleTo, buttonLabel, buttonTo },
614
+ unauthenticatedRoute ? { unauthenticated: unauthenticatedRoute } : void 0
549
615
  );
550
616
  };
551
617
  const tasks = ({
@@ -664,7 +730,10 @@ const splitPathAndModel = (pathSlashModel) => {
664
730
  };
665
731
  export {
666
732
  files,
733
+ getUnauthenticatedRedirectRoute,
667
734
  handler,
735
+ hasLoginRoute,
736
+ isAuthSetup,
668
737
  routes,
669
738
  shouldUseTailwindCSS,
670
739
  splitPathAndModel,
@@ -10,6 +10,14 @@ import * as webServerCLIConfig from "@cedarjs/web-server";
10
10
  import { getPaths, getConfig } from "../lib/index.js";
11
11
  import { serverFileExists } from "../lib/project.js";
12
12
  import { webSsrServerHandler } from "./serveWebHandler.js";
13
+ function refuseServerFileUnderUD() {
14
+ console.error(
15
+ c.error(
16
+ "\n api/src/server.ts was detected, but a custom server file is not supported with --ud. It is a Fastify concept \u2014 anything registered there (Realtime, custom plugins, custom middleware) would silently be skipped if serving continued.\n"
17
+ )
18
+ );
19
+ process.exit(1);
20
+ }
13
21
  function resolveUDEntryPath() {
14
22
  const base = path.join(getPaths().api.dist, "ud", "index");
15
23
  for (const ext of [".mjs", ".js"]) {
@@ -90,17 +98,13 @@ const builder = async (yargs) => {
90
98
  process.exit(1);
91
99
  }
92
100
  if (serverFileExists()) {
93
- console.warn(
94
- c.warning(
95
- "\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"
96
- )
97
- );
101
+ refuseServerFileUnderUD();
98
102
  }
99
103
  const { getAPIHost, getAPIPort, getWebHost, getWebPort } = await import("@cedarjs/api-server/cliHelpers");
100
104
  const apiPort = argv.apiPort ?? getAPIPort();
101
105
  const apiHost = argv.apiHost ?? getAPIHost();
102
- const webPort = argv.webPort ?? getWebPort();
103
- const webHost = argv.webHost ?? getWebHost();
106
+ const webPort = argv.webPort ?? getWebPort({ isPublicSide: true });
107
+ const webHost = argv.webHost ?? getWebHost({ isPublicSide: true });
104
108
  const apiRootPath = argv.apiRootPath ?? "/";
105
109
  const apiTarget = `http://${apiHost.includes(":") ? `[${apiHost}]` : apiHost}:${apiPort}`;
106
110
  const { serveStatic } = await import("srvx/static");
@@ -187,7 +191,15 @@ const builder = async (yargs) => {
187
191
  socket: argv.socket,
188
192
  apiRootPath: argv.apiRootPath
189
193
  });
194
+ const { getAPIHost, getAPIPort } = await import("@cedarjs/api-server/cliHelpers");
195
+ const apiPort = argv.port ?? getAPIPort({ isPublicSide: true });
196
+ const apiHost = argv.host ?? getAPIHost({ isPublicSide: true });
197
+ argv.port = apiPort;
198
+ argv.host = apiHost;
190
199
  if (argv.ud) {
200
+ if (serverFileExists()) {
201
+ refuseServerFileUnderUD();
202
+ }
191
203
  const udEntryPath = resolveUDEntryPath();
192
204
  if (!udEntryPath) {
193
205
  console.error(
@@ -197,8 +209,6 @@ const builder = async (yargs) => {
197
209
  );
198
210
  process.exit(1);
199
211
  }
200
- const apiPort = argv.port ?? parseInt(process.env.PORT ?? "8911", 10);
201
- const apiHost = argv.host ?? process.env.HOST ?? "localhost";
202
212
  process.stdout.write(
203
213
  `API server starting at http://${apiHost}:${apiPort}...`
204
214
  );
@@ -27,8 +27,8 @@ const bothServerFileHandler = async (argv) => {
27
27
  } else {
28
28
  argv.apiPort ??= getAPIPort();
29
29
  argv.apiHost ??= getAPIHost();
30
- argv.webPort ??= getWebPort();
31
- argv.webHost ??= getWebHost();
30
+ argv.webPort ??= getWebPort({ isPublicSide: true });
31
+ argv.webHost ??= getWebHost({ isPublicSide: true });
32
32
  const apiRootPath = argv.apiRootPath ?? getAPIRootPath();
33
33
  const apiProxyTarget = [
34
34
  "http://",
@@ -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
+ };
@@ -5,13 +5,7 @@ import { recordTelemetryAttributes, colors as c } from "@cedarjs/cli-helpers";
5
5
  import { getPaths, getPrismaSchemas } from "@cedarjs/project-config";
6
6
  import { errorTelemetry } from "@cedarjs/telemetry";
7
7
  import { writeFilesTask, printSetupNotes } from "../../../../lib/index.js";
8
- import { addFilesTask } from "../helpers/index.js";
9
- import {
10
- POSTGRES_YAML,
11
- RENDER_HEALTH_CHECK,
12
- RENDER_YAML,
13
- SQLITE_YAML
14
- } from "../templates/render.js";
8
+ import { POSTGRES_YAML, RENDER_YAML, SQLITE_YAML } from "../templates/render.js";
15
9
  const { getConfig } = prismaInternals;
16
10
  const getRenderYamlContent = async (database) => {
17
11
  if (database === "none") {
@@ -54,14 +48,9 @@ const getRenderYamlContent = async (database) => {
54
48
  const notes = [
55
49
  "You are ready to deploy to Render!\n",
56
50
  "Go to https://dashboard.render.com/iacs to create your account and deploy to Render",
57
- "Check out the deployment docs at https://render.com/docs/deploy-redwood for detailed instructions",
58
- "Note: After first deployment to Render update the rewrite rule destination in `./render.yaml`"
59
- ];
60
- const additionalFiles = [
61
- {
62
- path: path.join(getPaths().base, "api/src/functions/healthz.js"),
63
- content: RENDER_HEALTH_CHECK
64
- }
51
+ "Check out the deployment docs at https://cedarjs.com/docs/deploy/render for detailed instructions",
52
+ "Note: After first deployment to Render update the rewrite rule destination in `./render.yaml`",
53
+ "Note: The api service now health checks `/graphql/health`. If a previous setup left an unused `api/src/functions/healthz.js` behind, you can delete it"
65
54
  ];
66
55
  const handler = async ({
67
56
  force,
@@ -83,11 +72,6 @@ const handler = async ({
83
72
  return writeFilesTask(files, { overwriteExisting: force });
84
73
  }
85
74
  },
86
- // Add health check api function
87
- addFilesTask({
88
- files: additionalFiles,
89
- force
90
- }),
91
75
  printSetupNotes(notes)
92
76
  ],
93
77
  { rendererOptions: { collapseSubtasks: false } }
@@ -5,13 +5,15 @@ const PROJECT_NAME = path.basename(getPaths().base);
5
5
  const RENDER_YAML = (database) => {
6
6
  const apiUrl = getUserApiUrl().replace(/\/$/, "");
7
7
  return `# Quick links to the docs:
8
- # - Redwood on Render: https://render.com/docs/deploy-redwood
9
- # - Render's Blueprint spec: https://render.com/docs/yaml-spec
8
+ # - Deploying Cedar: https://cedarjs.com/docs/deploy/render
9
+ # - Render's own walkthrough (uses \`yarn rw\`, but just swap in \`yarn cedar\`):
10
+ # https://render.com/docs/deploy-redwood
11
+ # - Render's Blueprint spec: https://render.com/docs/blueprint-spec
10
12
 
11
13
  services:
12
14
  - name: ${PROJECT_NAME}-web
13
15
  type: web
14
- env: static
16
+ runtime: static
15
17
  buildCommand: npm install --global corepack && yarn install && yarn cedar deploy render web
16
18
  staticPublishPath: ./web/dist
17
19
 
@@ -22,11 +24,16 @@ services:
22
24
  routes:
23
25
  - type: rewrite
24
26
  source: ${apiUrl}/*
25
- # Replace \`destination\` here after your first deploy:
27
+ # Replace \`destination\` after your first deploy, with the api service's
28
+ # URL from the Render dashboard:
26
29
  #
27
30
  # \`\`\`
28
- # destination: https://my-redwood-project-api.onrender.com/*
31
+ # destination: https://${PROJECT_NAME}-api.onrender.com/*
29
32
  # \`\`\`
33
+ #
34
+ # This can't be filled in automatically \u2014 Render's \`fromService\` only
35
+ # resolves service hosts into \`envVars\`, not into a static site's route
36
+ # destination.
30
37
  destination: replace_with_api_url/*
31
38
  - type: rewrite
32
39
  source: /*
@@ -35,11 +42,22 @@ services:
35
42
  - name: ${PROJECT_NAME}-api
36
43
  type: web
37
44
  plan: free
38
- env: node
45
+ runtime: node
39
46
  region: oregon
40
47
  buildCommand: npm install --global corepack && yarn install && yarn cedar build api
41
48
  startCommand: yarn cedar deploy render api
42
49
 
50
+ # Proves the GraphQL server is actually serving, not just that the process
51
+ # is alive. Returns 200 with an \`x-yoga-id\` response header.
52
+ #
53
+ # The route is \`<apiRootPath><graphiQLEndpoint>/health\`, and the value below
54
+ # assumes both defaults (\`/\` and \`/graphql\`). Update it to match if you
55
+ # customize either \u2014 by setting \`CEDAR_API_ROOT_PATH\` in the envVars below,
56
+ # by passing \`apiRootPath\` to \`createServer\` in \`api/src/server.ts\`, or by
57
+ # setting \`graphiQLEndpoint\` in \`api/src/functions/graphql.ts\`. A mismatch
58
+ # here 404s, and Render will not promote the deploy.
59
+ healthCheckPath: /graphql/health
60
+
43
61
  envVars:
44
62
  ${database}
45
63
  `;
@@ -58,17 +76,9 @@ const SQLITE_YAML = ` - key: DATABASE_URL
58
76
  name: sqlite-data
59
77
  mountPath: /opt/render/project/src/api/db/data
60
78
  sizeGB: 1`;
61
- const RENDER_HEALTH_CHECK = `// render-health-check
62
- export const handler = async () => {
63
- return {
64
- statusCode: 200,
65
- }
66
- }
67
- `;
68
79
  export {
69
80
  POSTGRES_YAML,
70
81
  PROJECT_NAME,
71
- RENDER_HEALTH_CHECK,
72
82
  RENDER_YAML,
73
83
  SQLITE_YAML
74
84
  };
@@ -94,11 +94,8 @@ ENV NODE_ENV=production
94
94
 
95
95
  # default api serve command
96
96
  # ---------
97
- # If you are using a custom server file, you must use the following
98
- # command to launch your server instead of the default api-server below.
99
- # This is important if you intend to configure GraphQL to use Realtime.
100
- #
101
- # CMD [ "./api/dist/server.js" ]
97
+ # See https://cedarjs.com/docs/server-file for customizing the api server
98
+ # (Realtime, custom Fastify plugins, etc).
102
99
  CMD ["node_modules/.bin/cedarjs-server", "api"]
103
100
 
104
101
  # web serve
@@ -18,7 +18,7 @@ services:
18
18
  - SESSION_SECRET=super_secret_session_key_change_me_in_production_please
19
19
  - CI=
20
20
  - NODE_ENV=development
21
- - REDWOOD_API_HOST=0.0.0.0
21
+ - CEDAR_API_HOST=0.0.0.0
22
22
 
23
23
  db:
24
24
  image: postgres:18-bookworm
@@ -4,11 +4,8 @@ services:
4
4
  context: .
5
5
  dockerfile: ./Dockerfile
6
6
  target: api_serve
7
- # Without a command specified, the Dockerfile's api_serve CMD will be used.
8
- # If you are using a custom server file, you should either use the following
9
- # command to launch your server or update the Dockerfile to do so.
10
- # This is important if you intend to configure GraphQL to use Realtime.
11
- # command: "./api/dist/server.js"
7
+ # See https://cedarjs.com/docs/server-file for customizing the api
8
+ # server (Realtime, custom Fastify plugins, etc).
12
9
  ports:
13
10
  - '8911:8911'
14
11
  depends_on:
@@ -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 { addWorkspacePackages } from "@cedarjs/cli-helpers/packageManager/packages";
6
+ import { prettyPrintCedarCommand } from "@cedarjs/cli-helpers/packageManager";
7
7
  import { errorTelemetry } from "@cedarjs/telemetry";
8
- const cedarPaths = getPaths();
8
+ import {
9
+ checkProjectShape,
10
+ getSqliteToPostgresTasks
11
+ } from "../database/postgresHandler.js";
9
12
  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
- );
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
- 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;
24
+ let hasExistingDatabaseUrl = false;
29
25
  if (fs.existsSync(envPath)) {
30
- hasDirectDatabaseUrl = /^DATABASE_URL=/m.test(
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: "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"'
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 updated = configContent.replace(
187
- /env\(["']DATABASE_URL["']\)/,
188
- "env('DIRECT_DATABASE_URL')"
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: (ctx) => {
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: "Writing database connection to .env",
107
+ title: "Running Prisma migrations",
244
108
  skip: (ctx) => {
245
- if (ctx.unsupportedProvider) {
109
+ if (skipProvisioning) {
246
110
  return true;
247
111
  }
248
- if (hasDirectDatabaseUrl && !force) {
249
- return true;
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 (ctx.unsupportedProvider) {
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
- exitOnError: false
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
  };
@@ -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/dist/lib/index.js CHANGED
@@ -305,20 +305,35 @@ const cleanupEmptyDirsTask = (files) => {
305
305
  })
306
306
  );
307
307
  };
308
- function wrapWithSet(routesContent, layout, routes, newLineAndIndent, props = {}) {
308
+ function wrapWithSet(routesContent, layout, routes, newLineAndIndent, props = {}, privateSetProps) {
309
309
  const [_, indentOne, indentTwo] = routesContent.match(
310
310
  /([ \t]*)<Router.*?>[^<]*[\r\n]+([ \t]+)/
311
311
  ) || ["", "", ""];
312
312
  const oneLevelIndent = indentTwo.slice(0, indentTwo.length - indentOne.length);
313
- const newRoutesWithExtraIndent = routes.map((route) => oneLevelIndent + route);
314
313
  const propsString = Object.entries(props).map((values) => `${values[0]}="${values[1]}"`).join(" ");
314
+ if (!privateSetProps) {
315
+ const newRoutesWithExtraIndent2 = routes.map(
316
+ (route) => oneLevelIndent + route
317
+ );
318
+ return [
319
+ `<Set wrap={${layout}}${propsString && " " + propsString}>`,
320
+ ...newRoutesWithExtraIndent2,
321
+ `</Set>`
322
+ ].join(newLineAndIndent);
323
+ }
324
+ const privateSetPropsString = Object.entries(privateSetProps).map((values) => `${values[0]}="${values[1]}"`).join(" ");
325
+ const newRoutesWithExtraIndent = routes.map(
326
+ (route) => oneLevelIndent + oneLevelIndent + route
327
+ );
315
328
  return [
316
- `<Set wrap={${layout}}${propsString && " " + propsString}>`,
329
+ `<PrivateSet${privateSetPropsString && " " + privateSetPropsString}>`,
330
+ `${oneLevelIndent}<Set wrap={${layout}}${propsString && " " + propsString}>`,
317
331
  ...newRoutesWithExtraIndent,
318
- `</Set>`
332
+ `${oneLevelIndent}</Set>`,
333
+ `</PrivateSet>`
319
334
  ].join(newLineAndIndent);
320
335
  }
321
- function addRoutesToRouterTask(routes, layout, setProps = {}) {
336
+ function addRoutesToRouterTask(routes, layout, setProps = {}, privateSetProps) {
322
337
  const cedarPaths = getPaths();
323
338
  const routesContent = readFile(cedarPaths.web.routes).toString();
324
339
  let newRoutes = routes.filter((route) => !routesContent.match(route));
@@ -342,7 +357,8 @@ ${route}`);
342
357
  layout,
343
358
  newRoutes,
344
359
  newLineAndIndent,
345
- setProps
360
+ setProps,
361
+ privateSetProps
346
362
  ) : newRoutes.join(newLineAndIndent);
347
363
  const newRoutesContent = routesContent.replace(
348
364
  routerStart,
@@ -7,13 +7,10 @@ import system from "systeminformation";
7
7
  import { v4 as uuidv4, validate as validateUUID } from "uuid";
8
8
  import { getPaths, getRawConfig } from "@cedarjs/project-config";
9
9
  import { RWProject } from "@cedarjs/structure/dist/model/RWProject";
10
- import {
11
- name as _packageName,
12
- version as _packageVersion
13
- } from "../../package.js";
14
- const packageName = _packageName;
15
- const packageVersion = _packageVersion;
16
10
  async function getResources() {
11
+ const packageJson = await import("../../package.json", { with: { type: "json" } });
12
+ const packageName = packageJson.default["name"];
13
+ const packageVersion = packageJson.default["version"];
17
14
  let UID = uuidv4();
18
15
  try {
19
16
  const telemetryFile = path.join(getPaths().generated.base, "telemetry.txt");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cedarjs/cli",
3
- "version": "6.0.0-rc.189",
3
+ "version": "6.0.0-rc.221",
4
4
  "description": "The CedarJS Command Line",
5
5
  "repository": {
6
6
  "type": "git",
@@ -31,19 +31,22 @@
31
31
  "test:watch": "vitest watch"
32
32
  },
33
33
  "dependencies": {
34
+ "@babel/core": "^7.26.10",
34
35
  "@babel/parser": "7.29.7",
35
36
  "@babel/preset-typescript": "7.29.7",
36
- "@cedarjs/api-server": "6.0.0-rc.189",
37
- "@cedarjs/cli-helpers": "6.0.0-rc.189",
38
- "@cedarjs/fastify-web": "6.0.0-rc.189",
39
- "@cedarjs/internal": "6.0.0-rc.189",
40
- "@cedarjs/prerender": "6.0.0-rc.189",
41
- "@cedarjs/project-config": "6.0.0-rc.189",
42
- "@cedarjs/structure": "6.0.0-rc.189",
43
- "@cedarjs/telemetry": "6.0.0-rc.189",
44
- "@cedarjs/utils": "6.0.0-rc.189",
45
- "@cedarjs/vite": "6.0.0-rc.189",
46
- "@cedarjs/web-server": "6.0.0-rc.189",
37
+ "@babel/traverse": "7.29.7",
38
+ "@babel/types": "7.29.7",
39
+ "@cedarjs/api-server": "6.0.0-rc.221",
40
+ "@cedarjs/babel-config": "6.0.0-rc.221",
41
+ "@cedarjs/cli-helpers": "6.0.0-rc.221",
42
+ "@cedarjs/internal": "6.0.0-rc.221",
43
+ "@cedarjs/prerender": "6.0.0-rc.221",
44
+ "@cedarjs/project-config": "6.0.0-rc.221",
45
+ "@cedarjs/structure": "6.0.0-rc.221",
46
+ "@cedarjs/telemetry": "6.0.0-rc.221",
47
+ "@cedarjs/utils": "6.0.0-rc.221",
48
+ "@cedarjs/vite": "6.0.0-rc.221",
49
+ "@cedarjs/web-server": "6.0.0-rc.221",
47
50
  "@listr2/prompt-adapter-enquirer": "4.3.0",
48
51
  "@opentelemetry/api": "1.9.1",
49
52
  "@opentelemetry/core": "1.30.1",
@@ -61,7 +64,6 @@
61
64
  "ci-info": "4.4.0",
62
65
  "concurrently": "9.2.4",
63
66
  "configstore": "7.1.0",
64
- "cross-env": "7.0.3",
65
67
  "decamelize": "6.0.1",
66
68
  "dotenv-defaults": "5.0.2",
67
69
  "enquirer": "2.4.1",
@@ -80,7 +82,6 @@
80
82
  "prettier": "3.8.4",
81
83
  "prisma": "7.8.0",
82
84
  "prompts": "2.4.2",
83
- "rimraf": "6.1.3",
84
85
  "semver": "7.7.4",
85
86
  "smol-toml": "1.6.1",
86
87
  "srvx": "0.11.16",
@@ -93,8 +94,7 @@
93
94
  "yargs": "17.7.3"
94
95
  },
95
96
  "devDependencies": {
96
- "@babel/cli": "7.29.7",
97
- "@babel/core": "^7.26.10",
97
+ "@cedarjs/framework-tools": "6.0.0-rc.221",
98
98
  "@prisma/dmmf": "7.8.0",
99
99
  "@types/archiver": "^7.0.0",
100
100
  "memfs": "4.64.0",