@cedarjs/cli 7.0.1-next.333 → 7.0.1-next.466

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 (54) hide show
  1. package/dist/commands/deploy/baremetal/baremetalHandler.js +363 -72
  2. package/dist/commands/deploy/baremetal/monitors.js +66 -0
  3. package/dist/commands/deploy/baremetal.js +6 -0
  4. package/dist/commands/destroy/scaffold/scaffoldHandler.js +63 -25
  5. package/dist/commands/dev/devHandler.js +13 -1
  6. package/dist/commands/generate/dbAuth/templates/login.webAuthn.tsx.template +20 -12
  7. package/dist/commands/generate/helpers.js +26 -0
  8. package/dist/commands/generate/ogImage/templates/ogImage.og.tsx.template +1 -1
  9. package/dist/commands/generate/package/templates/tsconfig.json.template +1 -0
  10. package/dist/commands/generate/realtime/templates/liveQueries/blank/blank.service.ts.template +12 -4
  11. package/dist/commands/generate/realtime/templates/subscriptions/blank/blank.service.ts.template +2 -2
  12. package/dist/commands/generate/realtime/templates/subscriptions/blank/blank.ts.template +3 -3
  13. package/dist/commands/generate/scaffold/scaffold.js +1 -1
  14. package/dist/commands/generate/scaffold/scaffoldHandler.js +83 -44
  15. package/dist/commands/generate/scaffold/templates/assets/scaffold.tailwind.css.template +12 -7
  16. package/dist/commands/generate/scaffold/templates/components/EditNameCell.tsx.template +7 -7
  17. package/dist/commands/generate/scaffold/templates/components/NameForm.tsx.template +7 -6
  18. package/dist/commands/generate/scaffold/templates/layouts/NamesScaffoldLayout.tsx.template +18 -0
  19. package/dist/commands/generate/scaffold/templates/lib/formatters.tsx.template +3 -3
  20. package/dist/commands/generate/script/templates/tsconfig.json.template +1 -0
  21. package/dist/commands/generate/sdl/sdlHandler.js +8 -3
  22. package/dist/commands/generate/service/serviceHandler.js +32 -11
  23. package/dist/commands/generate/service/templates/service.ts.template +21 -8
  24. package/dist/commands/generate/service/templates/test.ts.template +7 -5
  25. package/dist/commands/jobsHandler.js +5 -3
  26. package/dist/commands/setup/cache/templates/memcached.ts.template +14 -17
  27. package/dist/commands/setup/cache/templates/redis.ts.template +12 -20
  28. package/dist/commands/setup/deploy/helpers/index.js +3 -1
  29. package/dist/commands/setup/deploy/providers/baremetal.js +7 -0
  30. package/dist/commands/setup/deploy/providers/baremetalHandler.js +128 -20
  31. package/dist/commands/setup/deploy/providers/netlifyHandler.js +25 -3
  32. package/dist/commands/setup/deploy/templates/baremetal.js +119 -9
  33. package/dist/commands/setup/deploy/templates/netlifyUD.js +3 -0
  34. package/dist/commands/setup/jobs/jobsHandler.js +2 -0
  35. package/dist/commands/setup/monitoring/sentry/sentryHandler.js +5 -2
  36. package/dist/commands/setup/monitoring/sentry/templates/sentryApi.ts.template +1 -4
  37. package/dist/commands/setup/monitoring/sentry/templates/sentryWeb.ts.template +7 -4
  38. package/dist/commands/setup/realtime/templates/defer/fastAndSlowFields/fastAndSlowFields.ts.template +4 -1
  39. package/dist/commands/setup/realtime/templates/liveQueries/auctions/auctions.ts.template +12 -4
  40. package/dist/commands/setup/realtime/templates/subscriptions/newMessage/newMessage.ts.template +1 -1
  41. package/dist/commands/setup/tenancy/signupCodemod.js +2 -1
  42. package/dist/commands/setup/tenancy/templates/InvitePage.tsx.template +10 -1
  43. package/dist/commands/setup/tenancy/templates/organizations.scenarios.ts.template +0 -2
  44. package/dist/commands/setup/tenancy/templates/organizations.test.ts.template +0 -8
  45. package/dist/commands/setup/tenancy/templates/organizations.ts.template +1 -1
  46. package/dist/commands/setup/tenancy/templates/requireMembership.test.ts.template +1 -1
  47. package/dist/commands/setup/tenancy/tenancyHandler.js +1 -1
  48. package/dist/commands/setup/ui/libraries/tailwindcssHandler.js +61 -81
  49. package/dist/commands/setup/uploads/gitignore.js +17 -0
  50. package/dist/commands/setup/uploads/templates/uploads.ts.template +14 -5
  51. package/dist/commands/setup/uploads/uploadsHandler.js +22 -1
  52. package/dist/lib/index.js +10 -6
  53. package/package.json +24 -24
  54. package/dist/commands/setup/ui/templates/postcss.config.cjs.template +0 -9
@@ -10,38 +10,136 @@ import {
10
10
  getPaths,
11
11
  printSetupNotes
12
12
  } from "../../../../lib/index.js";
13
+ import { MONITORS, isMonitor } from "../../../deploy/baremetal/monitors.js";
13
14
  import { addFilesTask } from "../helpers/index.js";
14
- import { DEPLOY, ECOSYSTEM, MAINTENANCE } from "../templates/baremetal.js";
15
+ import {
16
+ MAINTENANCE,
17
+ SYSTEMD_DIR,
18
+ deployToml,
19
+ ecosystemConfig,
20
+ systemdJobsUnit,
21
+ systemdServiceUnit
22
+ } from "../templates/baremetal.js";
15
23
  const configFilename = "deploy.toml";
16
- const files = [
24
+ const MONITOR_CHOICES = [
17
25
  {
18
- path: path.join(getPaths().base, configFilename),
19
- content: DEPLOY
26
+ title: "pm2",
27
+ description: "Node process manager, configured in ecosystem.config.js",
28
+ value: "pm2"
20
29
  },
21
30
  {
22
- path: path.join(getPaths().base, "ecosystem.config.js"),
23
- content: ECOSYSTEM
31
+ title: "systemd (user units)",
32
+ description: "Units in ~/.config/systemd/user, managed by the deploy user without sudo",
33
+ value: "systemd-user"
24
34
  },
25
35
  {
26
- path: path.join(getPaths().web.src, "maintenance.html"),
27
- content: MAINTENANCE
36
+ title: "systemd (system units)",
37
+ description: "Units in /etc/systemd/system, managed with sudo systemctl",
38
+ value: "systemd-system"
28
39
  }
29
40
  ];
30
- const notes = [
31
- "You are almost ready to go BAREMETAL!",
32
- "",
33
- "See https://cedarjs.com/docs/deploy/baremetal for the remaining",
34
- "config and setup required before you can perform your first deploy."
35
- ];
36
- const handler = async ({ force }) => {
41
+ const appNameFromPath = (projectPath) => {
42
+ const name = path.basename(projectPath).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
43
+ return name || "cedar";
44
+ };
45
+ const setupFiles = ({
46
+ monitor,
47
+ jobs,
48
+ appName,
49
+ basePath,
50
+ webSrcPath
51
+ }) => {
52
+ const files = [];
53
+ let processNames;
54
+ if (monitor === "pm2") {
55
+ processNames = jobs ? ["serve", "jobs"] : ["serve"];
56
+ files.push({
57
+ path: path.join(basePath, "ecosystem.config.js"),
58
+ content: ecosystemConfig({ jobs })
59
+ });
60
+ } else {
61
+ const jobsUnit = `${appName}-jobs`;
62
+ processNames = jobs ? [appName, `${jobsUnit}@0`] : [appName];
63
+ files.push({
64
+ path: path.join(basePath, SYSTEMD_DIR, `${appName}.service`),
65
+ content: systemdServiceUnit({ appName, monitor })
66
+ });
67
+ if (jobs) {
68
+ files.push({
69
+ path: path.join(basePath, SYSTEMD_DIR, `${jobsUnit}@.service`),
70
+ content: systemdJobsUnit({ appName, monitor })
71
+ });
72
+ }
73
+ }
74
+ files.unshift({
75
+ path: path.join(basePath, configFilename),
76
+ content: deployToml({ monitor, processNames })
77
+ });
78
+ files.push({
79
+ path: path.join(webSrcPath, "maintenance.html"),
80
+ content: MAINTENANCE
81
+ });
82
+ return { files, processNames };
83
+ };
84
+ const notesFor = (monitor, jobs, processNames) => {
85
+ const notes = ["You are almost ready to go BAREMETAL!", ""];
86
+ if (monitor !== "pm2") {
87
+ notes.push(
88
+ `Copy the unit files in ${SYSTEMD_DIR}/ to the server before your first`,
89
+ "deploy. The comments at the top of each file say where they go.",
90
+ ""
91
+ );
92
+ }
93
+ if (jobs) {
94
+ notes.push(
95
+ `Background jobs are set up, so deploy.toml lists a jobs worker process`,
96
+ `(${processNames[processNames.length - 1]}). Add one per entry in the`,
97
+ "`workers` array of api/src/lib/jobs.ts.",
98
+ ""
99
+ );
100
+ }
101
+ notes.push(
102
+ "See https://cedarjs.com/docs/deploy/baremetal for the remaining",
103
+ "config and setup required before you can perform your first deploy."
104
+ );
105
+ return notes;
106
+ };
107
+ const promptForMonitor = async () => {
108
+ const { monitor } = await prompts({
109
+ type: "select",
110
+ name: "monitor",
111
+ message: "Which process monitor will run your app on the server?",
112
+ choices: MONITOR_CHOICES
113
+ });
114
+ return isMonitor(monitor) ? monitor : void 0;
115
+ };
116
+ const handler = async ({
117
+ force,
118
+ monitor: monitorOption
119
+ }) => {
37
120
  recordTelemetryAttributes({
38
121
  command: "setup deploy baremetal",
39
- force
122
+ force,
123
+ monitor: monitorOption
40
124
  });
41
- if (fs.existsSync(path.join(getPaths().base, ".pnp.cjs"))) {
125
+ if (monitorOption !== void 0 && !isMonitor(monitorOption)) {
126
+ console.error(
127
+ c.error(
128
+ `Unknown monitor "${monitorOption}". Use one of ${MONITORS.join(", ")}.`
129
+ )
130
+ );
131
+ process.exit(1);
132
+ }
133
+ const monitor = monitorOption ?? await promptForMonitor();
134
+ if (!monitor) {
135
+ console.log("Aborting baremetal setup.");
136
+ return;
137
+ }
138
+ const paths = getPaths();
139
+ if (fs.existsSync(path.join(paths.base, ".pnp.cjs"))) {
42
140
  console.warn(
43
141
  c.warning(
44
- "Your project uses Yarn PnP (Plug'n'Play), which is not officially supported for Baremetal deployments. The generated ecosystem.config.js file uses node_modules/.bin/cedar as the PM2 script path, which will most likely not work under PnP.\n\nYou will need to manually configure the server. See also the packageManagerCommand field in deploy.toml."
142
+ "Your project uses Yarn PnP (Plug'n'Play), which is not officially supported for Baremetal deployments. The generated process config starts the app with node_modules/.bin/cedar, which will most likely not work under PnP.\n\nYou will need to manually configure the server. See also the packageManagerCommand field in deploy.toml."
45
143
  )
46
144
  );
47
145
  console.log();
@@ -56,6 +154,14 @@ const handler = async ({ force }) => {
56
154
  }
57
155
  console.log();
58
156
  }
157
+ const jobs = Boolean(paths.api.jobsConfig);
158
+ const { files, processNames } = setupFiles({
159
+ monitor,
160
+ jobs,
161
+ appName: appNameFromPath(paths.base),
162
+ basePath: paths.base,
163
+ webSrcPath: paths.web.src
164
+ });
59
165
  const tasks = new Listr(
60
166
  [
61
167
  await addPackagesTask({
@@ -66,7 +172,7 @@ const handler = async ({ force }) => {
66
172
  files,
67
173
  force
68
174
  }),
69
- printSetupNotes(notes)
175
+ printSetupNotes(notesFor(monitor, jobs, processNames))
70
176
  ],
71
177
  { rendererOptions: { collapseSubtasks: false } }
72
178
  );
@@ -81,6 +187,8 @@ const handler = async ({ force }) => {
81
187
  }
82
188
  };
83
189
  export {
190
+ appNameFromPath,
84
191
  configFilename,
85
- handler
192
+ handler,
193
+ setupFiles
86
194
  };
@@ -56,11 +56,17 @@ function addNetlifyPluginsToViteConfigTask() {
56
56
  const hasNetlifyCompat = content.includes(
57
57
  "@universal-deploy/netlify/vite"
58
58
  );
59
- if (hasNetlifyPlugin && hasNetlifyCompat && content.includes("netlifyCompat(")) {
59
+ const hasSkewProtectionImport = content.includes(
60
+ "cedarNetlifySkewProtectionPlugin"
61
+ );
62
+ const hasSkewProtectionCall = content.includes(
63
+ "cedarNetlifySkewProtectionPlugin("
64
+ );
65
+ if (hasNetlifyPlugin && hasNetlifyCompat && hasSkewProtectionImport && hasSkewProtectionCall && content.includes("netlifyCompat(")) {
60
66
  task.skip("Netlify plugins are already configured.");
61
67
  return;
62
68
  }
63
- if (!hasNetlifyPlugin || !hasNetlifyCompat) {
69
+ if (!hasNetlifyPlugin || !hasNetlifyCompat || !hasSkewProtectionImport) {
64
70
  const newContent = content.replace(
65
71
  /(import\s+\{[^}]*\}\s+from\s+['"]vite['"];?)/,
66
72
  (match) => {
@@ -71,6 +77,10 @@ ${result}`;
71
77
  }
72
78
  if (!hasNetlifyCompat) {
73
79
  result = `import netlifyCompat from '@universal-deploy/netlify/vite'
80
+ ${result}`;
81
+ }
82
+ if (!hasSkewProtectionImport) {
83
+ result = `import { cedarNetlifySkewProtectionPlugin } from '@cedarjs/vite'
74
84
  ${result}`;
75
85
  }
76
86
  return result;
@@ -84,6 +94,9 @@ ${result}`;
84
94
  if (!hasNetlifyCompat) {
85
95
  prepend += "import netlifyCompat from '@universal-deploy/netlify/vite'\n";
86
96
  }
97
+ if (!hasSkewProtectionImport) {
98
+ prepend += "import { cedarNetlifySkewProtectionPlugin } from '@cedarjs/vite'\n";
99
+ }
87
100
  content = prepend + content;
88
101
  } else {
89
102
  content = newContent;
@@ -94,12 +107,21 @@ ${result}`;
94
107
  content,
95
108
  pluginCodes: [
96
109
  "netlify({ build: { enabled: true } })",
97
- "netlifyCompat()"
110
+ "netlifyCompat()",
111
+ "cedarNetlifySkewProtectionPlugin()"
98
112
  ]
99
113
  });
100
114
  if (result) {
101
115
  content = result;
102
116
  }
117
+ } else if (!hasSkewProtectionCall) {
118
+ const result = insertPluginsBeforeCedar({
119
+ content,
120
+ pluginCodes: ["cedarNetlifySkewProtectionPlugin()"]
121
+ });
122
+ if (result) {
123
+ content = result;
124
+ }
103
125
  }
104
126
  fs.writeFileSync(viteConfigPath, content);
105
127
  }
@@ -1,5 +1,26 @@
1
1
  import { getPackageManager } from "@cedarjs/project-config/packageManager";
2
- const ECOSYSTEM = `module.exports = {
2
+ const SYSTEMD_DIR = "systemd";
3
+ const JOBS_WORKER_BIN = "api/node_modules/.bin/cedar-jobs-worker";
4
+ const WORKER_STOP_TIMEOUT_SECONDS = 600;
5
+ const JOBS_WORKER_COMMENT = `# Runs one background jobs worker. \`--index\` picks the entry of the \`workers\`
6
+ # array in api/src/lib/jobs.ts and \`--id\` numbers the worker within that
7
+ # entry, so an entry with \`count: 2\` needs a second worker with \`--id=1\`.`;
8
+ const ecosystemConfig = ({ jobs }) => {
9
+ const jobsApp = jobs ? `
10
+ {
11
+ // Runs one background jobs worker. \`--index\` picks the entry of the
12
+ // \`workers\` array in api/src/lib/jobs.ts and \`--id\` numbers the worker
13
+ // within that entry, so an entry with \`count: 2\` needs a second app
14
+ // with \`--id=1\`.
15
+ name: 'jobs',
16
+ cwd: 'current',
17
+ script: '${JOBS_WORKER_BIN}',
18
+ args: '--index=0 --id=0',
19
+ // pm2 stops the worker with SIGINT, which lets it finish the job it is
20
+ // running. This is how long pm2 waits for that (in ms) before killing it.
21
+ kill_timeout: ${WORKER_STOP_TIMEOUT_SECONDS * 1e3},
22
+ },` : "";
23
+ return `module.exports = {
3
24
  apps: [
4
25
  {
5
26
  name: 'serve',
@@ -10,10 +31,79 @@ const ECOSYSTEM = `module.exports = {
10
31
  exec_mode: 'cluster',
11
32
  wait_ready: true,
12
33
  listen_timeout: 10000,
13
- },
34
+ },${jobsApp}
14
35
  ],
15
- }`;
16
- const DEPLOY = `# This file contains config for a baremetal deployment
36
+ }
37
+ `;
38
+ };
39
+ const unitHeader = (appName, monitor) => {
40
+ const install = monitor === "systemd-system" ? `# sudo cp ${SYSTEMD_DIR}/*.service /etc/systemd/system/` : `# mkdir -p ~/.config/systemd/user && cp ${SYSTEMD_DIR}/*.service ~/.config/systemd/user/`;
41
+ const linger = monitor === "systemd-system" ? "" : `#
42
+ # User units only run while the user has a session, so let them run after a
43
+ # reboot too:
44
+ # sudo loginctl enable-linger $(whoami)
45
+ `;
46
+ return `# systemd unit for the ${appName} Cedar app, generated by \`cedar setup deploy baremetal\`.
47
+ #
48
+ # Copy the unit files to the server once, before the first deploy:
49
+ ${install}
50
+ ${linger}#
51
+ # Then deploy with \`--first-run\`, which reloads the unit files and enables and
52
+ # starts every process listed in deploy.toml.
53
+ #
54
+ # WorkingDirectory must be the \`path\` from deploy.toml followed by \`/current\`.`;
55
+ };
56
+ const unitService = (monitor, execStart, extraDirectives = "") => {
57
+ const user = monitor === "systemd-system" ? "\nUser=deploy" : "";
58
+ return `[Service]
59
+ WorkingDirectory=/var/www/app/current
60
+ # A login shell puts node and ${getPackageManager()} from a version manager such as nvm on the PATH
61
+ ExecStart=/bin/bash -lc '${execStart}'
62
+ Restart=always
63
+ RestartSec=5
64
+ Environment=NODE_ENV=production${user}${extraDirectives}`;
65
+ };
66
+ const JOBS_STOP_DIRECTIVES = `
67
+ # Stopping sends SIGINT so the worker finishes the job it is running before it
68
+ # exits. systemd waits this long for that before killing the worker.
69
+ KillSignal=SIGINT
70
+ TimeoutStopSec=${WORKER_STOP_TIMEOUT_SECONDS}`;
71
+ const unitInstall = (monitor) => `[Install]
72
+ WantedBy=${monitor === "systemd-system" ? "multi-user.target" : "default.target"}`;
73
+ const systemdServiceUnit = ({
74
+ appName,
75
+ monitor
76
+ }) => `${unitHeader(appName, monitor)}
77
+
78
+ [Unit]
79
+ Description=${appName} (Cedar)
80
+ After=network.target
81
+
82
+ ${unitService(monitor, "exec node_modules/.bin/cedar serve")}
83
+
84
+ ${unitInstall(monitor)}
85
+ `;
86
+ const systemdJobsUnit = ({
87
+ appName,
88
+ monitor
89
+ }) => `${unitHeader(appName, monitor)}
90
+ #
91
+ ${JOBS_WORKER_COMMENT}
92
+ # Start one instance per \`workers\` entry: \`${appName}-jobs@0\`, \`${appName}-jobs@1\`
93
+ # and so on, and list each one in \`processNames\` in deploy.toml.
94
+
95
+ [Unit]
96
+ Description=${appName} background jobs worker %i (Cedar)
97
+ After=network.target
98
+
99
+ ${unitService(monitor, `exec ${JOBS_WORKER_BIN} --index=%i --id=0`, JOBS_STOP_DIRECTIVES)}
100
+
101
+ ${unitInstall(monitor)}
102
+ `;
103
+ const deployToml = ({
104
+ monitor,
105
+ processNames
106
+ }) => `# This file contains config for a baremetal deployment
17
107
  #
18
108
  # SSH connection options include:
19
109
  #
@@ -26,6 +116,22 @@ const DEPLOY = `# This file contains config for a baremetal deployment
26
116
  # * passphrase - used if your private key has a passphrase
27
117
  # * agentForward - set to \`true\` to forward the client machine's ssh credentials
28
118
  #
119
+ # Process monitor options:
120
+ #
121
+ # * monitor - "pm2", "systemd-user" or "systemd-system". Decides how processes
122
+ # are started for the first time, restarted and stopped
123
+ # * monitorCommand - overrides the command the monitor is invoked with. Defaults
124
+ # to \`pm2\`, \`systemctl --user\` or \`sudo systemctl\` depending on \`monitor\`,
125
+ # for example \`doppler run -- pm2\`
126
+ # * processNames - the pm2 app names or systemd unit names to (re)start
127
+ #
128
+ # After restarting the processes, the deploy requests the api side's health
129
+ # check endpoint on the server and fails if it doesn't respond in time:
130
+ #
131
+ # * healthCheckUrl - defaults to http://localhost:<[api].port>/graphql/health
132
+ # on servers that host the api side. Set to \`false\` to skip the check
133
+ # * healthCheckTimeout - seconds to keep retrying, defaults to 30
134
+ #
29
135
  # See https://cedarjs.com/docs/deploy/baremetal for more info
30
136
 
31
137
  [[production.servers]]
@@ -34,13 +140,14 @@ username = "user"
34
140
  agentForward = true
35
141
  sides = ["api","web"]
36
142
  packageManagerCommand = "${getPackageManager()}"
37
- monitorCommand = "pm2"
143
+ monitor = "${monitor}"
38
144
  path = "/var/www/app"
39
- processNames = ["serve"]
145
+ processNames = [${processNames.map((name) => `"${name}"`).join(", ")}]
40
146
  repo = "git@github.com:myorg/myapp.git"
41
147
  branch = "main"
42
148
  keepReleases = 5
43
149
  freeSpaceRequired = 2048
150
+ healthCheckTimeout = 30
44
151
 
45
152
  # If you have separate api and web servers:
46
153
  #
@@ -123,7 +230,10 @@ And take it back down with:
123
230
  </html>
124
231
  `;
125
232
  export {
126
- DEPLOY,
127
- ECOSYSTEM,
128
- MAINTENANCE
233
+ MAINTENANCE,
234
+ SYSTEMD_DIR,
235
+ deployToml,
236
+ ecosystemConfig,
237
+ systemdJobsUnit,
238
+ systemdServiceUnit
129
239
  };
@@ -10,6 +10,9 @@ NODE_VERSION = "24"
10
10
 
11
11
  [functions]
12
12
  directory = "api/dist/ud"
13
+ # Sandboxed child scripts (\`@cedarjs/api/sandbox\`) and the packages they
14
+ # import. Nothing imports them, so Netlify's file tracing can't find them
15
+ included_files = ["api/dist/ud/sandbox-children/**"]
13
16
 
14
17
  # To use Netlify Dev, install Netlify's CLI (\`netlify-cli\`) from NPM and use
15
18
  # \`netlify link\` to connect your local project to a site on Netlify. Then run
@@ -23,6 +23,8 @@ model BackgroundJob {
23
23
  lockedBy String?
24
24
  lastError String?
25
25
  failedAt DateTime?
26
+ key String? @unique
27
+ pausedAt DateTime?
26
28
  createdAt DateTime @default(now())
27
29
  updatedAt DateTime @updatedAt
28
30
  }
@@ -76,7 +76,6 @@ const handler = async ({ force }) => {
76
76
  "export const handler = createGraphQLHandler({",
77
77
  "extraPlugins: [useSentry({",
78
78
  " includeRawResult: true,",
79
- " includeResolverArgs: true,",
80
79
  " includeExecuteVariables: true,",
81
80
  "})],"
82
81
  );
@@ -112,7 +111,11 @@ const handler = async ({ force }) => {
112
111
  contentLines.splice(
113
112
  boundaryOpenIndex,
114
113
  1,
115
- "<Sentry.ErrorBoundary fallback={FatalErrorPage}>"
114
+ "<Sentry.ErrorBoundary",
115
+ " fallback={({ error }) => (",
116
+ " <FatalErrorPage error={error instanceof Error ? error : undefined} />",
117
+ " )}",
118
+ ">"
116
119
  );
117
120
  const boundaryCloseIndex = contentLines.findLastIndex(
118
121
  (line) => line.includes("</FatalErrorBoundary>")
@@ -3,10 +3,7 @@ import * as Sentry from '@sentry/node'
3
3
  Sentry.init({
4
4
  dsn: process.env.SENTRY_DSN,
5
5
  environment: process.env.NODE_ENV,
6
- integrations: [
7
- Sentry.prismaIntegration(),
8
- Sentry.httpIntegration(),
9
- ],
6
+ integrations: [Sentry.prismaIntegration(), Sentry.httpIntegration()],
10
7
  tracesSampleRate: 1.0,
11
8
  })
12
9
 
@@ -1,9 +1,12 @@
1
1
  import * as Sentry from '@sentry/react'
2
2
 
3
+ const sentryDsn =
4
+ typeof process === 'undefined' ? undefined : process.env?.SENTRY_DSN
5
+
3
6
  let dsn = ''
4
7
  let environment = 'development'
5
8
 
6
- if (typeof process === 'undefined' || !process.env?.SENTRY_DSN) {
9
+ if (!sentryDsn) {
7
10
  console.error(
8
11
  'Missing SENTRY_DSN environment variable. Did you forget to add it to ' +
9
12
  'your cedar.toml (or redwood.toml) in `includeEnvironmentVariables`?'
@@ -17,14 +20,14 @@ if (typeof process === 'undefined' || !process.env?.SENTRY_DSN) {
17
20
  `)
18
21
  console.error('Sentry is disabled for now')
19
22
  } else {
20
- dsn = process.env.SENTRY_DSN
21
- environment = process.env.NODE_ENV
23
+ dsn = sentryDsn
24
+ environment = process.env.NODE_ENV ?? 'development'
22
25
  }
23
26
 
24
27
  Sentry.init({
25
28
  dsn,
26
29
  environment,
27
- integrations: [ Sentry.browserTracingIntegration() ],
30
+ integrations: [Sentry.browserTracingIntegration()],
28
31
  tracesSampleRate: 1.0,
29
32
  })
30
33
 
@@ -7,7 +7,10 @@ export const fastField = async () => {
7
7
  return 'I am fast'
8
8
  }
9
9
 
10
- export const slowField = async (_, { waitFor = 5000 }) => {
10
+ export const slowField = async (
11
+ _: unknown,
12
+ { waitFor = 5000 }: { waitFor?: number }
13
+ ) => {
11
14
  logger.debug('waiting on slowField')
12
15
  await wait(waitFor)
13
16
  return 'I am slow'
@@ -1,8 +1,16 @@
1
+ import type { BidInput } from 'types/graphql'
2
+
1
3
  import type { LiveQueryStorageMechanism } from '@cedarjs/realtime'
2
4
 
3
5
  import { logger } from 'src/lib/logger'
4
6
 
5
- const auctions = [
7
+ interface AuctionData {
8
+ id: string
9
+ title: string
10
+ bids: { amount: number }[]
11
+ }
12
+
13
+ const auctions: AuctionData[] = [
6
14
  { id: '1', title: 'RedwoodJS Logo Shirt', bids: [{ amount: 20 }] },
7
15
  { id: '2', title: 'RedwoodJS Lapel Pin', bids: [{ amount: 5 }] },
8
16
  { id: '3', title: 'RedwoodJS Beanie', bids: [{ amount: 15 }] },
@@ -34,14 +42,14 @@ const auctions = [
34
42
  * }
35
43
  * }
36
44
  */
37
- export const auction = async ({ id }) => {
45
+ export const auction = async ({ id }: { id: string }) => {
38
46
  const foundAuction = auctions.find((a) => a.id === id)
39
47
  logger.debug({ id, auction: foundAuction }, 'auction')
40
48
  return foundAuction
41
49
  }
42
50
 
43
51
  export const bid = async (
44
- { input },
52
+ { input }: { input: BidInput },
45
53
  { context }: { context: { liveQueryStore: LiveQueryStorageMechanism } }
46
54
  ) => {
47
55
  const { auctionId, amount } = input
@@ -62,7 +70,7 @@ export const bid = async (
62
70
  }
63
71
 
64
72
  export const Auction = {
65
- highestBid: (obj, { root }) => {
73
+ highestBid: (obj: unknown, { root }: { root: AuctionData }) => {
66
74
  const [max] = root.bids.sort((a, b) => b.amount - a.amount)
67
75
 
68
76
  logger.debug({ obj, root }, 'highestBid')
@@ -48,7 +48,7 @@ const newMessage = {
48
48
 
49
49
  return pubSub.subscribe('newMessage', roomId)
50
50
  },
51
- resolve: (payload) => {
51
+ resolve: (payload: NewMessageChannel['newMessage'][1]) => {
52
52
  logger.debug({ payload }, 'newMessage subscription resolve')
53
53
 
54
54
  return payload
@@ -52,7 +52,8 @@ function wrapCreateCall(fn) {
52
52
  ),
53
53
  j.objectProperty(
54
54
  j.identifier("invitationToken"),
55
- j.memberExpression(
55
+ // `userAttributes` is optional in dbAuth's signup handler options
56
+ j.optionalMemberExpression(
56
57
  j.identifier("userAttributes"),
57
58
  j.identifier("invitationToken")
58
59
  )
@@ -1,11 +1,20 @@
1
1
  import { useEffect, useState } from 'react'
2
2
 
3
+ import type {
4
+ AcceptInvitationMutation,
5
+ AcceptInvitationMutationVariables,
6
+ } from 'types/graphql'
7
+
3
8
  import { navigate, routes, useParams } from '@cedarjs/router'
4
9
  import { Metadata, useMutation } from '@cedarjs/web'
10
+ import type { TypedDocumentNode } from '@cedarjs/web'
5
11
 
6
12
  import { useAuth } from 'src/auth'
7
13
 
8
- const ACCEPT_INVITATION_MUTATION = gql`
14
+ const ACCEPT_INVITATION_MUTATION: TypedDocumentNode<
15
+ AcceptInvitationMutation,
16
+ AcceptInvitationMutationVariables
17
+ > = gql`
9
18
  mutation AcceptInvitationMutation($token: String!) {
10
19
  acceptInvitation(token: $token) {
11
20
  id
@@ -26,7 +26,6 @@ export const standard = defineScenario<
26
26
  email: 'owner@example.com',
27
27
  hashedPassword: 'x',
28
28
  salt: 'x',
29
- fullName: 'Org Owner',
30
29
  },
31
30
  },
32
31
  },
@@ -40,7 +39,6 @@ export const standard = defineScenario<
40
39
  email: 'admin@example.com',
41
40
  hashedPassword: 'x',
42
41
  salt: 'x',
43
- fullName: 'Org Admin',
44
42
  },
45
43
  },
46
44
  },
@@ -199,7 +199,6 @@ describe('organizations service', () => {
199
199
  email: 'second-owner@example.com',
200
200
  hashedPassword: 'x',
201
201
  salt: 'x',
202
- fullName: 'Second Owner',
203
202
  },
204
203
  })
205
204
  await db.membership.create({
@@ -233,7 +232,6 @@ describe('organizations service', () => {
233
232
  email: 'invitee@example.com',
234
233
  hashedPassword: 'x',
235
234
  salt: 'x',
236
- fullName: 'Invitee',
237
235
  },
238
236
  })
239
237
 
@@ -255,7 +253,6 @@ describe('organizations service', () => {
255
253
  email: 'invitee@example.com',
256
254
  hashedPassword: 'x',
257
255
  salt: 'x',
258
- fullName: 'Invitee',
259
256
  },
260
257
  })
261
258
 
@@ -278,7 +275,6 @@ describe('organizations service', () => {
278
275
  email: 'first@example.com',
279
276
  hashedPassword: 'x',
280
277
  salt: 'x',
281
- fullName: 'First User',
282
278
  },
283
279
  })
284
280
  const secondUser = await db.user.create({
@@ -286,7 +282,6 @@ describe('organizations service', () => {
286
282
  email: 'second@example.com',
287
283
  hashedPassword: 'x',
288
284
  salt: 'x',
289
- fullName: 'Second User',
290
285
  },
291
286
  })
292
287
 
@@ -308,7 +303,6 @@ describe('organizations service', () => {
308
303
  email: 'invitee@example.com',
309
304
  hashedPassword: 'x',
310
305
  salt: 'x',
311
- fullName: 'Invitee',
312
306
  },
313
307
  })
314
308
 
@@ -355,7 +349,6 @@ describe('organizations service', () => {
355
349
  email: 'invitee@example.com',
356
350
  hashedPassword: 'x',
357
351
  salt: 'x',
358
- fullName: 'Invitee',
359
352
  },
360
353
  })
361
354
 
@@ -375,7 +368,6 @@ describe('organizations service', () => {
375
368
  email: 'brand-new@example.com',
376
369
  hashedPassword: 'x',
377
370
  salt: 'x',
378
- fullName: 'Brand New',
379
371
  },
380
372
  })
381
373