@cedarjs/cli 5.0.7-next.338 → 5.0.7

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.
Files changed (58) hide show
  1. package/README.md +10 -7
  2. package/dist/cfw.js +1 -3
  3. package/dist/commands/build/buildHandler.js +6 -14
  4. package/dist/commands/console.js +3 -5
  5. package/dist/commands/consoleHandler.js +75 -0
  6. package/dist/commands/dev/devHandler.js +4 -71
  7. package/dist/commands/dev.js +4 -7
  8. package/dist/commands/execHandler.js +0 -12
  9. package/dist/commands/experimental/setupOpentelemetryHandler.js +1 -1
  10. package/dist/commands/generate/helpers.js +0 -16
  11. package/dist/commands/generate/job/jobHandler.js +7 -5
  12. package/dist/commands/generate/package/filesTask.js +0 -12
  13. package/dist/commands/generate/package/packageHandler.js +11 -18
  14. package/dist/commands/generate/package/templates/test.ts.template +2 -2
  15. package/dist/commands/generate/scaffold/scaffoldHandler.js +19 -138
  16. package/dist/commands/generate/script/templates/script.ts.template +4 -9
  17. package/dist/commands/generate/sdl/sdlHandler.js +44 -113
  18. package/dist/commands/generate/sdl/templates/sdl.js.template +1 -2
  19. package/dist/commands/generate/sdl/templates/sdl.ts.template +1 -2
  20. package/dist/commands/generate/service/serviceHandler.js +6 -17
  21. package/dist/commands/generate/yargsHandlerHelpers.js +6 -0
  22. package/dist/commands/lint.js +61 -1
  23. package/dist/commands/prismaHandler.js +7 -4
  24. package/dist/commands/serve.js +24 -23
  25. package/dist/commands/serveBothHandler.js +4 -4
  26. package/dist/commands/setup/auth/auth.js +1 -1
  27. package/dist/commands/setup/deploy/helpers/index.js +39 -9
  28. package/dist/commands/setup/deploy/providers/flightcontrolHandler.js +5 -17
  29. package/dist/commands/setup/deploy/providers/renderHandler.js +21 -27
  30. package/dist/commands/setup/deploy/templates/render.js +16 -29
  31. package/dist/commands/setup/docker/templates/Dockerfile.yarn +5 -2
  32. package/dist/commands/setup/docker/templates/docker-compose.dev.yml +1 -1
  33. package/dist/commands/setup/docker/templates/docker-compose.prod.yml +5 -2
  34. package/dist/commands/setup/graphql/features/fragments/appGqlConfigTransform.js +3 -6
  35. package/dist/commands/setup/monitoring/sentry/sentryHandler.js +4 -9
  36. package/dist/commands/setup/neon/neon.js +3 -13
  37. package/dist/commands/setup/neon/neonHandler.js +258 -141
  38. package/dist/commands/setup/ui/libraries/chakra-uiHandler.js +1 -3
  39. package/dist/commands/setup/ui/libraries/mantineHandler.js +1 -3
  40. package/dist/commands/setup/ui/libraries/tailwindcssHandler.js +1 -0
  41. package/dist/commands/setup/uploads/uploadsHandler.js +5 -6
  42. package/dist/commands/setup.js +1 -2
  43. package/dist/commands/test/testHandlerEsm.js +1 -1
  44. package/dist/commands/upgrade/preUpgradeScripts.js +9 -9
  45. package/dist/commands/upgrade/upgradeHandler.js +32 -42
  46. package/dist/lib/background.js +1 -20
  47. package/dist/lib/exec.js +8 -5
  48. package/dist/lib/extendFile.js +1 -1
  49. package/dist/lib/index.js +6 -22
  50. package/dist/lib/updateCheck.js +6 -34
  51. package/dist/telemetry/resource.js +8 -3
  52. package/package.json +27 -26
  53. package/dist/commands/generate/package/templates/vitest.config.ts.template +0 -9
  54. package/dist/commands/generate/sdl/stubFiles.js +0 -132
  55. package/dist/commands/setup/database/database.js +0 -15
  56. package/dist/commands/setup/database/postgres.js +0 -19
  57. package/dist/commands/setup/database/postgresHandler.js +0 -194
  58. /package/dist/commands/setup/{database → neon}/templates/db.ts.template +0 -0
@@ -1,8 +1,63 @@
1
1
  import fs from "node:fs";
2
+ import path from "node:path";
2
3
  import { terminalLink } from "termi-link";
3
4
  import { recordTelemetryAttributes } from "@cedarjs/cli-helpers";
4
5
  import { runBin } from "@cedarjs/cli-helpers/packageManager/exec";
5
- import { getPaths } from "@cedarjs/project-config";
6
+ import { getPaths, getConfig } from "@cedarjs/project-config";
7
+ function detectLegacyEslintConfig() {
8
+ const projectRoot = getPaths().base;
9
+ const legacyConfigFiles = [
10
+ ".eslintrc.js",
11
+ ".eslintrc.cjs",
12
+ ".eslintrc.json",
13
+ ".eslintrc.yaml",
14
+ ".eslintrc.yml"
15
+ ];
16
+ const foundLegacyFiles = [];
17
+ for (const configFile of legacyConfigFiles) {
18
+ if (fs.existsSync(path.join(projectRoot, configFile))) {
19
+ foundLegacyFiles.push(configFile);
20
+ }
21
+ }
22
+ const packageJsonPath = path.join(projectRoot, "package.json");
23
+ if (fs.existsSync(packageJsonPath)) {
24
+ try {
25
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
26
+ if (packageJson.eslintConfig) {
27
+ foundLegacyFiles.push("package.json (eslintConfig field)");
28
+ }
29
+ if (packageJson.eslint) {
30
+ foundLegacyFiles.push("package.json (eslint field)");
31
+ }
32
+ } catch {
33
+ }
34
+ }
35
+ return foundLegacyFiles;
36
+ }
37
+ function showLegacyEslintDeprecationWarning(legacyFiles) {
38
+ console.warn("");
39
+ console.warn("\u26A0\uFE0F DEPRECATION WARNING: Legacy ESLint Configuration Detected");
40
+ console.warn("");
41
+ console.warn(" The following legacy ESLint configuration files were found:");
42
+ legacyFiles.forEach((file) => {
43
+ console.warn(` - ${file}`);
44
+ });
45
+ console.warn("");
46
+ console.warn(
47
+ " Cedar has migrated to ESLint flat config format. Legacy configurations"
48
+ );
49
+ console.warn(
50
+ " still work but are deprecated and will be removed in a future version."
51
+ );
52
+ console.warn("");
53
+ console.warn(" To migrate to the new format:");
54
+ console.warn(" 1. Remove the legacy config file(s) listed above");
55
+ console.warn(" 2. Create an eslint.config.mjs");
56
+ console.warn(" 3. Use the flat config format with @cedarjs/eslint-config");
57
+ console.warn("");
58
+ console.warn(" See more here: https://github.com/cedarjs/cedar/pull/629");
59
+ console.warn("");
60
+ }
6
61
  const command = "lint [paths..]";
7
62
  const description = "Lint your files";
8
63
  const builder = (yargs) => {
@@ -31,6 +86,11 @@ const handler = async ({
31
86
  format = "stylish"
32
87
  }) => {
33
88
  recordTelemetryAttributes({ command: "lint", fix, format });
89
+ const config = getConfig();
90
+ const legacyConfigFiles = detectLegacyEslintConfig();
91
+ if (legacyConfigFiles.length > 0 && config instanceof Object && "eslintLegacyConfigWarning" in config && config.eslintLegacyConfigWarning) {
92
+ showLegacyEslintDeprecationWarning(legacyConfigFiles);
93
+ }
34
94
  try {
35
95
  const sbPath = getPaths().web.storybook;
36
96
  const eslintArgs = [
@@ -3,8 +3,7 @@ import boxen from "boxen";
3
3
  import { recordTelemetryAttributes, colors as c } from "@cedarjs/cli-helpers";
4
4
  import {
5
5
  formatCedarCommand,
6
- formatRunBinCommand,
7
- formatRunTransitiveBinCommand
6
+ formatRunBinCommand
8
7
  } from "@cedarjs/cli-helpers/packageManager/display";
9
8
  import { runTransitiveBinSync } from "@cedarjs/cli-helpers/packageManager/exec";
10
9
  import { errorTelemetry } from "@cedarjs/telemetry";
@@ -50,14 +49,18 @@ const handler = async ({
50
49
  for (const [name, value] of Object.entries(options)) {
51
50
  args.push(name.length > 1 ? `--${name}` : `-${name}`);
52
51
  if (typeof value === "string") {
53
- args.push(value);
52
+ if (value.split(" ").length > 1) {
53
+ args.push(`"${value}"`);
54
+ } else {
55
+ args.push(value);
56
+ }
54
57
  } else if (typeof value === "number") {
55
58
  args.push(String(value));
56
59
  }
57
60
  }
58
61
  console.log();
59
62
  console.log(c.note("Running Prisma CLI..."));
60
- console.log(c.underline(`$ ${formatRunTransitiveBinCommand("prisma", args)}`));
63
+ console.log(c.underline(`$ <pm exec> prisma ${args.join(" ")}`));
61
64
  console.log();
62
65
  try {
63
66
  runTransitiveBinSync("prisma", args, {
@@ -6,18 +6,11 @@ import { terminalLink } from "termi-link";
6
6
  import * as apiServerCLIConfig from "@cedarjs/api-server/apiCliConfig";
7
7
  import * as bothServerCLIConfig from "@cedarjs/api-server/bothCliConfig";
8
8
  import { recordTelemetryAttributes, colors as c } from "@cedarjs/cli-helpers";
9
+ import { projectIsEsm } from "@cedarjs/project-config";
9
10
  import * as webServerCLIConfig from "@cedarjs/web-server";
10
11
  import { getPaths, getConfig } from "../lib/index.js";
11
12
  import { serverFileExists } from "../lib/project.js";
12
13
  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
- }
21
14
  function resolveUDEntryPath() {
22
15
  const base = path.join(getPaths().api.dist, "ud", "index");
23
16
  for (const ext of [".mjs", ".js"]) {
@@ -98,16 +91,20 @@ const builder = async (yargs) => {
98
91
  process.exit(1);
99
92
  }
100
93
  if (serverFileExists()) {
101
- refuseServerFileUnderUD();
94
+ console.warn(
95
+ c.warning(
96
+ "\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"
97
+ )
98
+ );
102
99
  }
103
100
  const { getAPIHost, getAPIPort, getWebHost, getWebPort } = await import("@cedarjs/api-server/cliHelpers");
104
101
  const apiPort = argv.apiPort ?? getAPIPort();
105
102
  const apiHost = argv.apiHost ?? getAPIHost();
106
- const webPort = argv.webPort ?? getWebPort({ isPublicSide: true });
107
- const webHost = argv.webHost ?? getWebHost({ isPublicSide: true });
103
+ const webPort = argv.webPort ?? getWebPort();
104
+ const webHost = argv.webHost ?? getWebHost();
108
105
  const apiRootPath = argv.apiRootPath ?? "/";
109
106
  const apiTarget = `http://${apiHost.includes(":") ? `[${apiHost}]` : apiHost}:${apiPort}`;
110
- const { staticMiddleware } = await import("srvx/static");
107
+ const { serveStatic } = await import("srvx/static");
111
108
  const apiUrl = getConfig().web.apiUrl;
112
109
  const webDist = getPaths().web.dist;
113
110
  const prerenderIndexPath = path.join(webDist, "200.html");
@@ -117,7 +114,7 @@ const builder = async (yargs) => {
117
114
  // Dummy fetch handler. All requests are handled by middleware
118
115
  fetch: async () => new Response("Not Found", { status: 404 }),
119
116
  middleware: [
120
- staticMiddleware({ dir: webDist }),
117
+ serveStatic({ dir: webDist }),
121
118
  async (req, next) => {
122
119
  const url = new URL(req.url, "http://localhost");
123
120
  if (!url.pathname.startsWith(apiUrl)) {
@@ -165,7 +162,12 @@ const builder = async (yargs) => {
165
162
  const serveBothHandlers = await import("./serveBothHandler.js");
166
163
  await serveBothHandlers.bothSsrRscServerHandler(argv, rscEnabled);
167
164
  } else {
168
- await bothServerCLIConfig.handler(argv);
165
+ if (!projectIsEsm()) {
166
+ const { handler } = await import("@cedarjs/api-server/cjs/bothCliConfigHandler");
167
+ await handler(argv);
168
+ } else {
169
+ await bothServerCLIConfig.handler(argv);
170
+ }
169
171
  }
170
172
  }
171
173
  }).command({
@@ -191,15 +193,7 @@ const builder = async (yargs) => {
191
193
  socket: argv.socket,
192
194
  apiRootPath: argv.apiRootPath
193
195
  });
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;
199
196
  if (argv.ud) {
200
- if (serverFileExists()) {
201
- refuseServerFileUnderUD();
202
- }
203
197
  const udEntryPath = resolveUDEntryPath();
204
198
  if (!udEntryPath) {
205
199
  console.error(
@@ -209,6 +203,8 @@ const builder = async (yargs) => {
209
203
  );
210
204
  process.exit(1);
211
205
  }
206
+ const apiPort = argv.port ?? parseInt(process.env.PORT ?? "8911", 10);
207
+ const apiHost = argv.host ?? process.env.HOST ?? "localhost";
212
208
  process.stdout.write(
213
209
  `API server starting at http://${apiHost}:${apiPort}...`
214
210
  );
@@ -223,7 +219,12 @@ const builder = async (yargs) => {
223
219
  const { apiServerFileHandler } = await import("./serveApiHandler.js");
224
220
  await apiServerFileHandler(argv);
225
221
  } else {
226
- await apiServerCLIConfig.handler(argv);
222
+ if (!projectIsEsm()) {
223
+ const { handler } = await import("@cedarjs/api-server/cjs/apiCliConfigHandler");
224
+ await handler(argv);
225
+ } else {
226
+ await apiServerCLIConfig.handler(argv);
227
+ }
227
228
  }
228
229
  }
229
230
  }).command({
@@ -1,13 +1,13 @@
1
1
  import path from "path";
2
2
  import concurrently from "concurrently";
3
- import { handler as apiServerHandler } from "@cedarjs/api-server/apiCliConfigHandler";
3
+ import { handler as apiServerHandler } from "@cedarjs/api-server/cjs/apiCliConfigHandler";
4
4
  import {
5
5
  getAPIHost,
6
6
  getAPIPort,
7
7
  getAPIRootPath,
8
8
  getWebHost,
9
9
  getWebPort
10
- } from "@cedarjs/api-server/cliHelpers";
10
+ } from "@cedarjs/api-server/cjs/cliHelpers";
11
11
  import { formatRunBinCommand } from "@cedarjs/cli-helpers/packageManager/display";
12
12
  import { runBin } from "@cedarjs/cli-helpers/packageManager/exec";
13
13
  import { getConfig, getPaths } from "@cedarjs/project-config";
@@ -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({ isPublicSide: true });
31
- argv.webHost ??= getWebHost({ isPublicSide: true });
30
+ argv.webPort ??= getWebPort();
31
+ argv.webHost ??= getWebHost();
32
32
  const apiRootPath = argv.apiRootPath ?? getAPIRootPath();
33
33
  const apiProxyTarget = [
34
34
  "http://",
@@ -237,7 +237,7 @@ async function getAuthSetupHandler(module) {
237
237
  });
238
238
  }
239
239
  const setupModule = await import(module);
240
- return setupModule.handler;
240
+ return setupModule.default.handler;
241
241
  }
242
242
  function isInstalled(module) {
243
243
  const { dependencies, devDependencies } = JSON.parse(
@@ -4,6 +4,7 @@ import * as parser from "@babel/parser";
4
4
  import * as t from "@babel/types";
5
5
  import execa from "execa";
6
6
  import { Listr } from "listr2";
7
+ import * as recast from "recast";
7
8
  import { getConfigPath, getConfig } from "@cedarjs/project-config";
8
9
  import { getPaths, writeFilesTask } from "../../../../lib/index.js";
9
10
  const updateApiURLTask = (apiUrl) => {
@@ -87,6 +88,14 @@ const verifyUDSetupTask = () => {
87
88
  }
88
89
  };
89
90
  };
91
+ function posToIndex(str, line, column) {
92
+ const lines = str.split("\n");
93
+ let index = 0;
94
+ for (let i = 0; i < line - 1; i++) {
95
+ index += lines[i].length + 1;
96
+ }
97
+ return index + column;
98
+ }
90
99
  function resolveConfigObject(arg) {
91
100
  if (t.isObjectExpression(arg)) {
92
101
  return arg;
@@ -118,9 +127,15 @@ function insertPluginsBeforeCedar({
118
127
  content,
119
128
  pluginCodes
120
129
  }) {
121
- const ast = parser.parse(content, {
122
- sourceType: "module",
123
- plugins: ["typescript", "jsx"]
130
+ const ast = recast.parse(content, {
131
+ parser: {
132
+ parse(source) {
133
+ return parser.parse(source, {
134
+ sourceType: "module",
135
+ plugins: ["typescript", "jsx"]
136
+ });
137
+ }
138
+ }
124
139
  });
125
140
  const defaultExport = ast.program.body.find(t.isExportDefaultDeclaration);
126
141
  if (!defaultExport) {
@@ -148,18 +163,33 @@ function insertPluginsBeforeCedar({
148
163
  return null;
149
164
  }
150
165
  const cedarNode = cedarElement;
151
- if (!cedarNode.loc || !arrayExpr.loc || !pluginsProp.loc || cedarNode.start == null || arrayExpr.start == null || arrayExpr.end == null) {
166
+ if (!cedarNode.loc || !arrayExpr.loc || !pluginsProp.loc) {
152
167
  return null;
153
168
  }
154
169
  const isInline = cedarNode.loc.start.line === arrayExpr.loc.start.line;
155
170
  if (isInline) {
156
- const precedingText = content.slice(0, arrayExpr.start);
157
- const followingText = content.slice(arrayExpr.end);
171
+ const startPos = posToIndex(
172
+ content,
173
+ arrayExpr.loc.start.line,
174
+ arrayExpr.loc.start.column
175
+ );
176
+ const endPos = posToIndex(
177
+ content,
178
+ arrayExpr.loc.end.line,
179
+ arrayExpr.loc.end.column
180
+ );
181
+ const precedingText = content.slice(0, startPos);
182
+ const followingText = content.slice(endPos);
158
183
  const existingCodes = elements.flatMap((el) => {
159
- if (el?.start == null || el.end == null) {
184
+ if (!el?.loc) {
160
185
  return [];
161
186
  }
162
- return [content.slice(el.start, el.end)];
187
+ return [
188
+ content.slice(
189
+ posToIndex(content, el.loc.start.line, el.loc.start.column),
190
+ posToIndex(content, el.loc.end.line, el.loc.end.column)
191
+ )
192
+ ];
163
193
  });
164
194
  const lines2 = content.split("\n");
165
195
  const pluginsLine = pluginsProp.loc.start.line;
@@ -175,7 +205,7 @@ function insertPluginsBeforeCedar({
175
205
  return precedingText + multiline + followingText;
176
206
  }
177
207
  const cedarLine = cedarNode.loc.start.line;
178
- const insertPos = cedarNode.start - cedarNode.loc.start.column;
208
+ const insertPos = posToIndex(content, cedarLine, 0);
179
209
  const lines = content.split("\n");
180
210
  const indent = (lines[cedarLine - 1].match(/^\s*/) ?? [""])[0];
181
211
  const insertion = pluginCodes.map((code) => `${indent}${code},
@@ -16,10 +16,6 @@ import {
16
16
  mysqlDatabaseService
17
17
  } from "../templates/flightcontrol.js";
18
18
  const { getConfig } = prismaInternals;
19
- const APOLLO_PROVIDER_COMPONENT_NAMES = [
20
- "CedarApolloProvider",
21
- "RedwoodApolloProvider"
22
- ];
23
19
  const getFlightcontrolJson = async (database) => {
24
20
  const flightcontrolConfig = getFlightcontrolConfig();
25
21
  if (database === "none") {
@@ -209,26 +205,18 @@ const updateApp = () => {
209
205
  appContent[authLineIndex] = ` <AuthProvider type="dbAuth" config={{ fetchConfig: { credentials: 'include' } }}>
210
206
  `;
211
207
  }
212
- let gqlLineIndex = -1;
213
- let apolloProviderComponentName = "";
214
- for (const componentName of APOLLO_PROVIDER_COMPONENT_NAMES) {
215
- gqlLineIndex = appContent.findIndex(
216
- (line) => line.includes(`<${componentName}`)
217
- );
218
- if (gqlLineIndex !== -1) {
219
- apolloProviderComponentName = componentName;
220
- break;
221
- }
222
- }
208
+ const gqlLineIndex = appContent.findIndex(
209
+ (line) => line.includes("<RedwoodApolloProvider")
210
+ );
223
211
  if (gqlLineIndex === -1) {
224
212
  console.log(`
225
- Couldn't find <CedarApolloProvider in web/src/App.js
213
+ Couldn't find <RedwoodApolloProvider in web/src/App.js
226
214
  If (and when) you use *dbAuth*, you'll have to add the following fetch config manually:
227
215
 
228
216
  graphQLClientConfig={{ httpLinkConfig: { credentials: 'include' }}}
229
217
  `);
230
218
  } else if (appContent.toString().match(/dbAuth/)) {
231
- appContent[gqlLineIndex] = ` <${apolloProviderComponentName} graphQLClientConfig={{ httpLinkConfig: { credentials: 'include' }}} >
219
+ appContent[gqlLineIndex] = ` <RedwoodApolloProvider graphQLClientConfig={{ httpLinkConfig: { credentials: 'include' }}} >
232
220
  `;
233
221
  }
234
222
  fs.writeFileSync(appPath, appContent.join(EOL));
@@ -1,14 +1,18 @@
1
1
  import path from "path";
2
2
  import prismaInternals from "@prisma/internals";
3
3
  import { Listr } from "listr2";
4
- import prompts from "prompts";
5
4
  import { recordTelemetryAttributes, colors as c } from "@cedarjs/cli-helpers";
6
5
  import { getPaths, getPrismaSchemas } from "@cedarjs/project-config";
7
6
  import { errorTelemetry } from "@cedarjs/telemetry";
8
7
  import { writeFilesTask, printSetupNotes } from "../../../../lib/index.js";
9
- import { POSTGRES_YAML, RENDER_YAML, SQLITE_YAML } from "../templates/render.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";
10
15
  const { getConfig } = prismaInternals;
11
- const SQLITE_API_PLAN = "starter";
12
16
  const getRenderYamlContent = async (database) => {
13
17
  if (database === "none") {
14
18
  return {
@@ -29,7 +33,7 @@ const getRenderYamlContent = async (database) => {
29
33
  case "sqlite":
30
34
  return {
31
35
  path: path.join(getPaths().base, "render.yaml"),
32
- content: RENDER_YAML(SQLITE_YAML, SQLITE_API_PLAN)
36
+ content: RENDER_YAML(SQLITE_YAML)
33
37
  };
34
38
  default:
35
39
  throw new Error(`
@@ -50,9 +54,14 @@ const getRenderYamlContent = async (database) => {
50
54
  const notes = [
51
55
  "You are ready to deploy to Render!\n",
52
56
  "Go to https://dashboard.render.com/iacs to create your account and deploy to Render",
53
- "Check out the deployment docs at https://cedarjs.com/docs/deploy/render for detailed instructions",
54
- "Note: After first deployment to Render update the rewrite rule destination in `./render.yaml`",
55
- "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"
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
+ }
56
65
  ];
57
66
  const handler = async ({
58
67
  force,
@@ -63,26 +72,6 @@ const handler = async ({
63
72
  force,
64
73
  database
65
74
  });
66
- if (database === "sqlite") {
67
- console.warn(
68
- c.warning(
69
- `Render's free plan doesn't support persistent disks, which the \`sqlite\` deploy option requires for its database file. The generated render.yaml will set the api service's plan to "${SQLITE_API_PLAN}" (a paid plan) instead of "free" so the disk can actually attach.
70
-
71
- If you want to stay on the free plan, rerun this command with \`--database postgresql\` (a managed database, not a disk) or \`--database none\`.`
72
- )
73
- );
74
- console.log();
75
- const { confirmed } = await prompts({
76
- type: "confirm",
77
- name: "confirmed",
78
- message: `Generate render.yaml with the api service on the "${SQLITE_API_PLAN}" plan?`
79
- });
80
- if (!confirmed) {
81
- console.log("Aborting render setup.");
82
- return;
83
- }
84
- console.log();
85
- }
86
75
  const tasks = new Listr(
87
76
  [
88
77
  {
@@ -94,6 +83,11 @@ If you want to stay on the free plan, rerun this command with \`--database postg
94
83
  return writeFilesTask(files, { overwriteExisting: force });
95
84
  }
96
85
  },
86
+ // Add health check api function
87
+ addFilesTask({
88
+ files: additionalFiles,
89
+ force
90
+ }),
97
91
  printSetupNotes(notes)
98
92
  ],
99
93
  { rendererOptions: { collapseSubtasks: false } }
@@ -2,18 +2,16 @@ import path from "path";
2
2
  import { getPaths } from "../../../../lib/index.js";
3
3
  import { getUserApiUrl } from "../helpers/index.js";
4
4
  const PROJECT_NAME = path.basename(getPaths().base);
5
- const RENDER_YAML = (database, plan = "free") => {
5
+ const RENDER_YAML = (database) => {
6
6
  const apiUrl = getUserApiUrl().replace(/\/$/, "");
7
7
  return `# Quick links to the docs:
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
8
+ # - Redwood on Render: https://render.com/docs/deploy-redwood
9
+ # - Render's Blueprint spec: https://render.com/docs/yaml-spec
12
10
 
13
11
  services:
14
12
  - name: ${PROJECT_NAME}-web
15
13
  type: web
16
- runtime: static
14
+ env: static
17
15
  buildCommand: npm install --global corepack && yarn install && yarn cedar deploy render web
18
16
  staticPublishPath: ./web/dist
19
17
 
@@ -24,16 +22,11 @@ services:
24
22
  routes:
25
23
  - type: rewrite
26
24
  source: ${apiUrl}/*
27
- # Replace \`destination\` after your first deploy, with the api service's
28
- # URL from the Render dashboard:
25
+ # Replace \`destination\` here after your first deploy:
29
26
  #
30
27
  # \`\`\`
31
- # destination: https://${PROJECT_NAME}-api.onrender.com/*
28
+ # destination: https://my-redwood-project-api.onrender.com/*
32
29
  # \`\`\`
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.
37
30
  destination: replace_with_api_url/*
38
31
  - type: rewrite
39
32
  source: /*
@@ -41,23 +34,12 @@ services:
41
34
 
42
35
  - name: ${PROJECT_NAME}-api
43
36
  type: web
44
- plan: ${plan}
45
- runtime: node
37
+ plan: free
38
+ env: node
46
39
  region: oregon
47
40
  buildCommand: npm install --global corepack && yarn install && yarn cedar build api
48
41
  startCommand: yarn cedar deploy render api
49
42
 
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
-
61
43
  envVars:
62
44
  ${database}
63
45
  `;
@@ -69,19 +51,24 @@ const POSTGRES_YAML = ` - key: DATABASE_URL
69
51
 
70
52
  databases:
71
53
  - name: ${PROJECT_NAME}-db
72
- plan: free
73
54
  region: oregon`;
74
55
  const SQLITE_YAML = ` - key: DATABASE_URL
75
56
  value: file:./data/sqlite.db
76
- # Persistent disks aren't available on Render's free plan, which is why
77
- # the api service above is on a paid plan when SQLite is selected.
78
57
  disk:
79
58
  name: sqlite-data
80
59
  mountPath: /opt/render/project/src/api/db/data
81
60
  sizeGB: 1`;
61
+ const RENDER_HEALTH_CHECK = `// render-health-check
62
+ export const handler = async () => {
63
+ return {
64
+ statusCode: 200,
65
+ }
66
+ }
67
+ `;
82
68
  export {
83
69
  POSTGRES_YAML,
84
70
  PROJECT_NAME,
71
+ RENDER_HEALTH_CHECK,
85
72
  RENDER_YAML,
86
73
  SQLITE_YAML
87
74
  };
@@ -94,8 +94,11 @@ ENV NODE_ENV=production
94
94
 
95
95
  # default api serve command
96
96
  # ---------
97
- # See https://cedarjs.com/docs/server-file for customizing the api server
98
- # (Realtime, custom Fastify plugins, etc).
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" ]
99
102
  CMD ["node_modules/.bin/cedarjs-server", "api"]
100
103
 
101
104
  # 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
- - CEDAR_API_HOST=0.0.0.0
21
+ - REDWOOD_API_HOST=0.0.0.0
22
22
 
23
23
  db:
24
24
  image: postgres:18-bookworm
@@ -4,8 +4,11 @@ services:
4
4
  context: .
5
5
  dockerfile: ./Dockerfile
6
6
  target: api_serve
7
- # See https://cedarjs.com/docs/server-file for customizing the api
8
- # server (Realtime, custom Fastify plugins, etc).
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"
9
12
  ports:
10
13
  - '8911:8911'
11
14
  depends_on:
@@ -16,11 +16,8 @@ function isPropertyWithName(node, name) {
16
16
  function transform(file, api) {
17
17
  const j = api.jscodeshift;
18
18
  const root = j(file.source);
19
- let apolloProviderElements = root.findJSXElements("CedarApolloProvider");
20
- if (apolloProviderElements.length === 0) {
21
- apolloProviderElements = root.findJSXElements("RedwoodApolloProvider");
22
- }
23
- const graphQLClientConfigCollection = apolloProviderElements.find(
19
+ const redwoodApolloProvider = root.findJSXElements("RedwoodApolloProvider");
20
+ const graphQLClientConfigCollection = redwoodApolloProvider.find(
24
21
  j.JSXAttribute,
25
22
  {
26
23
  name: { name: "graphQLClientConfig" }
@@ -107,7 +104,7 @@ function transform(file, api) {
107
104
  cacheConfigValue.properties.push(property);
108
105
  }
109
106
  graphQLClientConfigCollection.remove();
110
- apolloProviderElements.get(0).node.openingElement.attributes.push(
107
+ redwoodApolloProvider.get(0).node.openingElement.attributes.push(
111
108
  j.jsxAttribute(
112
109
  j.jsxIdentifier("graphQLClientConfig"),
113
110
  j.jsxExpressionContainer(j.identifier(graphQLClientConfigVariableName))
@@ -90,20 +90,15 @@ const handler = async ({ force }) => {
90
90
  title: "Replacing Redwood's Error boundary",
91
91
  task: async () => {
92
92
  const contentLines = fs.readFileSync(rwPaths.web.app).toString().split("\n");
93
- const webImportRe = /^import \{ FatalErrorBoundary, ((?:Cedar|Redwood)Provider) \} from '@cedarjs\/web'$/;
94
93
  const webImportIndex = contentLines.findLastIndex(
95
- (line) => webImportRe.test(line)
94
+ (line) => /^import { FatalErrorBoundary, RedwoodProvider } from '@cedarjs\/web'$/.test(
95
+ line
96
+ )
96
97
  );
97
- if (webImportIndex === -1) {
98
- throw new Error(
99
- `Could not find "import { FatalErrorBoundary, CedarProvider } from '@cedarjs/web'" in web/src/App`
100
- );
101
- }
102
- const providerName = contentLines[webImportIndex].match(webImportRe)?.[1];
103
98
  contentLines.splice(
104
99
  webImportIndex,
105
100
  1,
106
- `import { ${providerName} } from '@cedarjs/web'`
101
+ "import { RedwoodProvider } from '@cedarjs/web'"
107
102
  );
108
103
  const boundaryOpenIndex = contentLines.findLastIndex(
109
104
  (line) => line.includes("<FatalErrorBoundary page={FatalErrorPage}>")