@cedarjs/cli 8.0.0-canary.3275 → 8.0.0-canary.3286

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.
@@ -14,6 +14,8 @@ const CONFIG_FILENAME = "deploy.toml";
14
14
  const SYMLINK_FLAGS = "-nsf";
15
15
  const CURRENT_RELEASE_SYMLINK_NAME = "current";
16
16
  const LIFECYCLE_HOOKS = ["before", "after"];
17
+ const RELEASE_DIR_PATTERN = "^[0-9]{14}$";
18
+ const releaseDirRegExp = new RegExp(RELEASE_DIR_PATTERN);
17
19
  const DEFAULT_SERVER_CONFIG = {
18
20
  port: 22,
19
21
  branch: "main",
@@ -141,8 +143,13 @@ const rollbackTasks = (count, ssh, serverConfig) => {
141
143
  title: `Rolling back ${rollbackCount} release(s)...`,
142
144
  task: async () => {
143
145
  const currentLink = (await ssh.exec(serverConfig.path, "readlink", ["-f", "current"])).stdout.split("/").pop();
144
- const dirs = (await ssh.exec(serverConfig.path, "ls", ["-t"])).stdout.split("\n").filter((dirs2) => !dirs2.match(/current/));
146
+ const dirs = (await ssh.exec(serverConfig.path, "ls", ["-t"])).stdout.split("\n").filter((dir) => releaseDirRegExp.test(dir));
145
147
  const deployedIndex = dirs.indexOf(currentLink ?? "");
148
+ if (deployedIndex === -1) {
149
+ throw new Error(
150
+ `Cannot rollback: \`current\` points to "${currentLink}", which is not a timestamp-named release directory`
151
+ );
152
+ }
146
153
  const rollbackIndex = deployedIndex + rollbackCount;
147
154
  if (dirs[rollbackIndex]) {
148
155
  console.info("Setting symlink");
@@ -153,7 +160,7 @@ const rollbackTasks = (count, ssh, serverConfig) => {
153
160
  );
154
161
  } else {
155
162
  throw new Error(
156
- `Cannot rollback ${rollbackCount} release(s): ${dirs.length - dirs.indexOf(currentLink ?? "") - 1} previous release(s) available`
163
+ `Cannot rollback ${rollbackCount} release(s): ${dirs.length - deployedIndex - 1} previous release(s) available`
157
164
  );
158
165
  }
159
166
  }
@@ -425,10 +432,10 @@ const deployTasks = (yargs, ssh, serverConfig, serverLifecycle) => {
425
432
  command: {
426
433
  title: `Cleaning up old deploys...`,
427
434
  task: async () => {
428
- const fileStartIndex = serverConfig.keepReleases + 2;
435
+ const fileStartIndex = serverConfig.keepReleases + 1;
429
436
  await ssh.exec(
430
437
  serverConfig.path,
431
- `ls -t | tail -n +${fileStartIndex} | xargs rm -rf`
438
+ `ls -t | grep -E '${RELEASE_DIR_PATTERN}' | tail -n +${fileStartIndex} | xargs -r rm -rf`
432
439
  );
433
440
  }
434
441
  }
@@ -587,6 +594,7 @@ const handler = async (yargs) => {
587
594
  };
588
595
  export {
589
596
  DEFAULT_SERVER_CONFIG,
597
+ RELEASE_DIR_PATTERN,
590
598
  commandWithLifecycleEvents,
591
599
  commands,
592
600
  deployTasks,
@@ -1,9 +1,13 @@
1
1
  import { terminalLink } from "termi-link";
2
2
  import { getYargsDefaults, createHandler } from "../yargsCommandHelpers.js";
3
3
  const command = "dbAuth";
4
- const description = "Generate Login, Signup and Forgot Password pages for dbAuth";
4
+ const description = "Generate Login, Signup, Forgot Password, Reset Password and Change Password pages for dbAuth";
5
5
  const builder = (yargs) => {
6
- yargs.option("skip-forgot", {
6
+ yargs.option("skip-change", {
7
+ description: "Skip generating the Change Password page",
8
+ type: "boolean",
9
+ default: false
10
+ }).option("skip-forgot", {
7
11
  description: "Skip generating the Forgot Password page",
8
12
  type: "boolean",
9
13
  default: false
@@ -18,12 +18,25 @@ import {
18
18
  } from "../../../lib/index.js";
19
19
  import { prepareForRollback } from "../../../lib/rollback.js";
20
20
  import { templateForComponentFile } from "../yargsHandlerHelpers.js";
21
- const ROUTES = [
22
- `<Route path="/login" page={LoginPage} name="login" />`,
23
- `<Route path="/signup" page={SignupPage} name="signup" />`,
24
- `<Route path="/forgot-password" page={ForgotPasswordPage} name="forgotPassword" />`,
25
- `<Route path="/reset-password" page={ResetPasswordPage} name="resetPassword" />`
26
- ];
21
+ function routesFor({
22
+ skipChange,
23
+ skipForgot,
24
+ skipLogin,
25
+ skipReset,
26
+ skipSignup
27
+ }) {
28
+ return [
29
+ !skipLogin && `<Route path="/login" page={LoginPage} name="login" />`,
30
+ !skipSignup && `<Route path="/signup" page={SignupPage} name="signup" />`,
31
+ !skipForgot && `<Route path="/forgot-password" page={ForgotPasswordPage} name="forgotPassword" />`,
32
+ !skipReset && `<Route path="/reset-password" page={ResetPasswordPage} name="resetPassword" />`,
33
+ !skipChange && `<Route path="/change-password" page={ChangePasswordPage} name="changePassword" />`
34
+ ].filter((route) => typeof route === "string");
35
+ }
36
+ function pascalCase(str) {
37
+ const camelCased = camelCase(str);
38
+ return camelCased.charAt(0).toUpperCase() + camelCased.slice(1);
39
+ }
27
40
  function getPostInstallMessage(isDbAuthSetup2) {
28
41
  return [
29
42
  ` ${c.warning("Pages created! But you're not done yet:")}
@@ -35,9 +48,11 @@ function getPostInstallMessage(isDbAuthSetup2) {
35
48
  " navigate(routes.home())",
36
49
  " }\n",
37
50
  " and change the route to where you want them to go if the user is already",
38
- " logged in. Also take a look in the onSubmit() functions in ForgotPasswordPage",
39
- " and ResetPasswordPage to change where the user redirects to after submitting",
40
- " those forms.\n",
51
+ " logged in. Also take a look in the onSubmit() functions in ForgotPasswordPage,",
52
+ " ResetPasswordPage and ChangePasswordPage to change where the user redirects",
53
+ " to after submitting those forms.\n",
54
+ " ChangePasswordPage is for users who are already logged in. Link to it from",
55
+ " wherever your app lets users manage their account.\n",
41
56
  !isDbAuthSetup2 && ` Oh, and if you haven't already, add the necessary dbAuth functions and
42
57
  app setup by running:
43
58
 
@@ -58,9 +73,11 @@ function getPostInstallWebauthnMessage(isDbAuthSetup2) {
58
73
  " navigate(routes.home())",
59
74
  " }\n",
60
75
  " and change the route to where you want them to go if the user is already",
61
- " logged in. Also take a look in the onSubmit() functions in ForgotPasswordPage",
62
- " and ResetPasswordPage to change where the user redirects to after submitting",
63
- " those forms.\n",
76
+ " logged in. Also take a look in the onSubmit() functions in ForgotPasswordPage,",
77
+ " ResetPasswordPage and ChangePasswordPage to change where the user redirects",
78
+ " to after submitting those forms.\n",
79
+ " ChangePasswordPage is for users who are already logged in. Link to it from",
80
+ " wherever your app lets users manage their account.\n",
64
81
  !isDbAuthSetup2 && ` Oh, and if you haven't already, add the necessary dbAuth functions and
65
82
  app setup by running:
66
83
 
@@ -71,6 +88,7 @@ function getPostInstallWebauthnMessage(isDbAuthSetup2) {
71
88
  }
72
89
  const files = async ({
73
90
  typescript,
91
+ skipChange,
74
92
  skipForgot,
75
93
  skipLogin,
76
94
  skipReset,
@@ -90,7 +108,8 @@ const files = async ({
90
108
  usernameTitleCase: titleCase(usernameLabel),
91
109
  passwordLowerCase: passwordLabel.toLowerCase(),
92
110
  passwordCamelCase: camelCase(passwordLabel),
93
- passwordTitleCase: titleCase(passwordLabel)
111
+ passwordTitleCase: titleCase(passwordLabel),
112
+ passwordPascalCase: pascalCase(passwordLabel)
94
113
  };
95
114
  if (!skipForgot) {
96
115
  filesList.push(
@@ -131,6 +150,19 @@ const files = async ({
131
150
  })
132
151
  );
133
152
  }
153
+ if (!skipChange) {
154
+ filesList.push(
155
+ await templateForComponentFile({
156
+ name: "ChangePassword",
157
+ suffix: "Page",
158
+ extension: typescript ? ".tsx" : ".jsx",
159
+ webPathSection: "pages",
160
+ generator: "dbAuth",
161
+ templatePath: "changePassword.tsx.template",
162
+ templateVars
163
+ })
164
+ );
165
+ }
134
166
  if (!skipSignup) {
135
167
  filesList.push(
136
168
  await templateForComponentFile({
@@ -179,6 +211,7 @@ const tasks = ({
179
211
  listr2,
180
212
  force,
181
213
  typescript,
214
+ skipChange,
182
215
  skipForgot,
183
216
  skipLogin,
184
217
  skipReset,
@@ -289,6 +322,7 @@ const tasks = ({
289
322
  task: async () => {
290
323
  const filesObj = await files({
291
324
  typescript,
325
+ skipChange,
292
326
  skipForgot,
293
327
  skipLogin,
294
328
  skipReset,
@@ -305,7 +339,15 @@ const tasks = ({
305
339
  {
306
340
  title: "Adding routes...",
307
341
  task: async () => {
308
- addRoutesToRouterTask(ROUTES);
342
+ addRoutesToRouterTask(
343
+ routesFor({
344
+ skipChange,
345
+ skipForgot,
346
+ skipLogin,
347
+ skipReset,
348
+ skipSignup
349
+ })
350
+ );
309
351
  }
310
352
  },
311
353
  {
@@ -335,6 +377,7 @@ const tasks = ({
335
377
  const handler = async (yargs) => {
336
378
  recordTelemetryAttributes({
337
379
  command: "generate dbAuth",
380
+ skipChange: yargs.skipChange,
338
381
  skipForgot: yargs.skipForgot,
339
382
  skipLogin: yargs.skipLogin,
340
383
  skipReset: yargs.skipReset,
@@ -0,0 +1,131 @@
1
+ import { useEffect, useRef } from 'react'
2
+
3
+ import {
4
+ Form,
5
+ Label,
6
+ PasswordField,
7
+ Submit,
8
+ FieldError,
9
+ } from '@cedarjs/forms'
10
+ import { navigate, routes } from '@cedarjs/router'
11
+ import { Metadata } from '@cedarjs/web'
12
+ import { toast, Toaster } from '@cedarjs/web/toast'
13
+
14
+ import { useAuth } from 'src/auth'
15
+
16
+ const ChangePasswordPage = () => {
17
+ const { loading, isAuthenticated, reauthenticate, changePassword } =
18
+ useAuth()
19
+
20
+ // Only a logged in user can change their password
21
+ useEffect(() => {
22
+ if (!loading && !isAuthenticated) {
23
+ navigate(routes.login())
24
+ }
25
+ }, [loading, isAuthenticated])
26
+
27
+ const current${passwordPascalCase}Ref = useRef<HTMLInputElement>(null)
28
+ useEffect(() => {
29
+ current${passwordPascalCase}Ref.current?.focus()
30
+ }, [])
31
+
32
+ const onSubmit = async (data: Record<string, string>) => {
33
+ const response = await changePassword({
34
+ currentPassword: data.current${passwordPascalCase},
35
+ newPassword: data.new${passwordPascalCase},
36
+ })
37
+
38
+ if (response.error) {
39
+ toast.error(response.error)
40
+ } else {
41
+ // The function `changePassword.handler` in api/src/functions/auth.js
42
+ // decides if the user stays logged in. `reauthenticate()` picks up
43
+ // whichever it was
44
+ toast.success('${passwordTitleCase} changed!')
45
+ await reauthenticate()
46
+ navigate(routes.home())
47
+ }
48
+ }
49
+
50
+ return (
51
+ <>
52
+ <Metadata title="Change ${passwordTitleCase}" />
53
+
54
+ <main className="rw-main">
55
+ <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} />
56
+ <div className="rw-scaffold rw-login-container">
57
+ <div className="rw-segment">
58
+ <header className="rw-segment-header">
59
+ <h2 className="rw-heading rw-heading-secondary">
60
+ Change ${passwordTitleCase}
61
+ </h2>
62
+ </header>
63
+
64
+ <div className="rw-segment-main">
65
+ <div className="rw-form-wrapper">
66
+ <Form onSubmit={onSubmit} className="rw-form-wrapper">
67
+ <Label
68
+ name="current${passwordPascalCase}"
69
+ className="rw-label"
70
+ errorClassName="rw-label rw-label-error"
71
+ >
72
+ Current ${passwordTitleCase}
73
+ </Label>
74
+ <PasswordField
75
+ name="current${passwordPascalCase}"
76
+ autoComplete="current-password"
77
+ className="rw-input"
78
+ errorClassName="rw-input rw-input-error"
79
+ ref={current${passwordPascalCase}Ref}
80
+ validation={{
81
+ required: {
82
+ value: true,
83
+ message: 'Current ${passwordTitleCase} is required',
84
+ },
85
+ }}
86
+ />
87
+
88
+ <FieldError
89
+ name="current${passwordPascalCase}"
90
+ className="rw-field-error"
91
+ />
92
+
93
+ <Label
94
+ name="new${passwordPascalCase}"
95
+ className="rw-label"
96
+ errorClassName="rw-label rw-label-error"
97
+ >
98
+ New ${passwordTitleCase}
99
+ </Label>
100
+ <PasswordField
101
+ name="new${passwordPascalCase}"
102
+ autoComplete="new-password"
103
+ className="rw-input"
104
+ errorClassName="rw-input rw-input-error"
105
+ validation={{
106
+ required: {
107
+ value: true,
108
+ message: 'New ${passwordTitleCase} is required',
109
+ },
110
+ }}
111
+ />
112
+
113
+ <FieldError
114
+ name="new${passwordPascalCase}"
115
+ className="rw-field-error"
116
+ />
117
+
118
+ <div className="rw-button-group">
119
+ <Submit className="rw-button rw-button-blue">Submit</Submit>
120
+ </div>
121
+ </Form>
122
+ </div>
123
+ </div>
124
+ </div>
125
+ </div>
126
+ </main>
127
+ </>
128
+ )
129
+ }
130
+
131
+ export default ChangePasswordPage
@@ -1,11 +1,24 @@
1
1
  import {
2
2
  createBuilder,
3
3
  createCommand,
4
- createHandler
4
+ createHandler,
5
+ getYargsDefaults
5
6
  } from "../yargsCommandHelpers.js";
6
7
  const command = createCommand("directive");
7
8
  const description = "Generate a new GraphQL directive";
8
- const builder = createBuilder({ componentName: "directive" });
9
+ const builder = createBuilder({
10
+ componentName: "directive",
11
+ optionsObj: () => ({
12
+ ...getYargsDefaults(),
13
+ // No default value, so the handler prompts for a type when the option is
14
+ // omitted
15
+ type: {
16
+ description: "Directive type. If omitted, the generator prompts for one",
17
+ type: "string",
18
+ choices: ["validator", "transformer"]
19
+ }
20
+ })
21
+ });
9
22
  const handler = createHandler("directive");
10
23
  export {
11
24
  builder,
@@ -1,3 +1,4 @@
1
+ import { isBuiltin } from "node:module";
1
2
  const START_LINE = /^([ \t]*)await server\.start\(\)/m;
2
3
  const FACTORY_IMPORT = /import\s*\{[^}]*\bcreateAuthDecoder\b[^}]*\}\s*from\s*['"](@cedarjs\/auth-[a-z0-9-]+-api)['"]/;
3
4
  const DECODER_IMPORT = /import\s*\{[^}]*\bauthDecoder\b[^}]*\}\s*from\s*['"](@cedarjs\/auth-[a-z0-9-]+-api)['"]/;
@@ -48,21 +49,20 @@ function hasBinding(source, name) {
48
49
  return importedBindings(source).has(name) || declared.test(source);
49
50
  }
50
51
  function uploadsServerImports(source, auth) {
51
- const packageSpecs = [];
52
- const srcSpecs = [];
52
+ const specs = [];
53
53
  if (auth) {
54
54
  if (auth.usesFactory) {
55
55
  if (!hasBinding(source, "authDecoder")) {
56
- packageSpecs.push({
56
+ specs.push({
57
57
  names: ["createAuthDecoder"],
58
58
  from: auth.decoderPackage
59
59
  });
60
60
  }
61
61
  } else {
62
- packageSpecs.push({ names: ["authDecoder"], from: auth.decoderPackage });
62
+ specs.push({ names: ["authDecoder"], from: auth.decoderPackage });
63
63
  }
64
64
  }
65
- packageSpecs.push({
65
+ specs.push({
66
66
  names: auth ? ["cedarUploadsPlugin", "createUploadAuthenticator"] : ["cedarUploadsPlugin"],
67
67
  from: "@cedarjs/uploads"
68
68
  });
@@ -71,20 +71,97 @@ function uploadsServerImports(source, auth) {
71
71
  if (auth.usesFactory && !hasBinding(source, "authDecoder")) {
72
72
  authNames.unshift("cookieName");
73
73
  }
74
- srcSpecs.push({ names: authNames, from: "src/lib/auth" });
74
+ specs.push({ names: authNames, from: "src/lib/auth" });
75
75
  }
76
- srcSpecs.push({ names: ["db"], from: "src/lib/db" });
77
- srcSpecs.push({ names: ["targets"], from: "src/lib/uploads" });
78
- const render = (specs) => specs.map((spec) => ({
76
+ specs.push({ names: ["db"], from: "src/lib/db" });
77
+ specs.push({ names: ["targets"], from: "src/lib/uploads" });
78
+ return specs.map((spec) => ({
79
79
  ...spec,
80
80
  names: spec.names.filter((name) => !hasBinding(source, name))
81
- })).filter((spec) => spec.names.length > 0).map((spec) => `import { ${spec.names.join(", ")} } from '${spec.from}'`);
82
- const packageLines = render(packageSpecs);
83
- const srcLines = render(srcSpecs);
84
- if (packageLines.length > 0 && srcLines.length > 0) {
85
- return [...packageLines, "", ...srcLines];
81
+ })).filter((spec) => spec.names.length > 0).map((spec) => ({
82
+ line: `import { ${spec.names.join(", ")} } from '${spec.from}'`,
83
+ from: spec.from
84
+ }));
85
+ }
86
+ function findImports(lines) {
87
+ const imports = [];
88
+ for (let start = 0; start < lines.length; start++) {
89
+ if (!/^import\b/.test(lines[start])) {
90
+ continue;
91
+ }
92
+ const sideEffect = /^import\s*['"]([^'"]+)['"]/.exec(lines[start]);
93
+ if (sideEffect) {
94
+ imports.push({ start, end: start, from: sideEffect[1] });
95
+ continue;
96
+ }
97
+ for (let end = start; end < lines.length; end++) {
98
+ const from = /\bfrom\s*['"]([^'"]+)['"]/.exec(lines[end]);
99
+ if (from) {
100
+ imports.push({ start, end, from: from[1] });
101
+ start = end;
102
+ break;
103
+ }
104
+ }
105
+ }
106
+ return imports;
107
+ }
108
+ function importGroup(from) {
109
+ if (isBuiltin(from)) {
110
+ return 0;
111
+ }
112
+ if (from === "react") {
113
+ return 1;
114
+ }
115
+ if (from.startsWith("@cedarjs/")) {
116
+ return 3;
117
+ }
118
+ if (from.startsWith("src/")) {
119
+ return /^src\/[^/]+\/.+\.(?:sdl\.)?(?:js|ts)$/.test(from) ? 4 : 5;
86
120
  }
87
- return [...packageLines, ...srcLines];
121
+ if (from === ".." || from.startsWith("../")) {
122
+ return 6;
123
+ }
124
+ if (/^\.\/?(?:index(?:\.[jt]sx?)?)?$/.test(from)) {
125
+ return 8;
126
+ }
127
+ if (from.startsWith("./")) {
128
+ return 7;
129
+ }
130
+ return 2;
131
+ }
132
+ function compareSpecifiers(a, b) {
133
+ const segmentsA = a.toLowerCase().split("/");
134
+ const segmentsB = b.toLowerCase().split("/");
135
+ for (let i = 0; i < Math.min(segmentsA.length, segmentsB.length); i++) {
136
+ if (segmentsA[i] !== segmentsB[i]) {
137
+ return segmentsA[i] < segmentsB[i] ? -1 : 1;
138
+ }
139
+ }
140
+ return segmentsA.length - segmentsB.length;
141
+ }
142
+ function insertImport(lines, serverImport) {
143
+ const imports = findImports(lines);
144
+ const group = importGroup(serverImport.from);
145
+ const sameGroup = imports.filter((i) => importGroup(i.from) === group);
146
+ if (sameGroup.length > 0) {
147
+ const next = sameGroup.find(
148
+ (i) => compareSpecifiers(i.from, serverImport.from) > 0
149
+ );
150
+ const at2 = next ? next.start : sameGroup[sameGroup.length - 1].end + 1;
151
+ return [...lines.slice(0, at2), serverImport.line, ...lines.slice(at2)];
152
+ }
153
+ const earlier = imports.filter((i) => importGroup(i.from) < group);
154
+ if (earlier.length > 0) {
155
+ const at2 = Math.max(...earlier.map((i) => i.end)) + 1;
156
+ const inserted = ["", serverImport.line];
157
+ if (at2 < lines.length && lines[at2].trim() !== "") {
158
+ inserted.push("");
159
+ }
160
+ return [...lines.slice(0, at2), ...inserted, ...lines.slice(at2)];
161
+ }
162
+ const later = imports.find((i) => importGroup(i.from) > group);
163
+ const at = later ? later.start : 0;
164
+ return [...lines.slice(0, at), serverImport.line, "", ...lines.slice(at)];
88
165
  }
89
166
  function uploadsServerRegistration(source, auth) {
90
167
  if (!auth) {
@@ -122,18 +199,7 @@ function addUploadsPlugin(source, { auth = null } = {}) {
122
199
  if (!start) {
123
200
  throw new Error("CEDAR_UPLOADS_ERR_NO_START");
124
201
  }
125
- const lines = source.split("\n");
126
- let lastImport = -1;
127
- for (const [index, line] of lines.entries()) {
128
- if (/^import\s/.test(line)) {
129
- lastImport = index;
130
- }
131
- }
132
- const withImports = [
133
- ...lines.slice(0, lastImport + 1),
134
- ...uploadsServerImports(source, auth),
135
- ...lines.slice(lastImport + 1)
136
- ].join("\n");
202
+ const withImports = uploadsServerImports(source, auth).reduce(insertImport, source.split("\n")).join("\n");
137
203
  return withImports.replace(
138
204
  START_LINE,
139
205
  (line) => `${uploadsServerRegistration(source, auth)}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cedarjs/cli",
3
- "version": "8.0.0-canary.3275",
3
+ "version": "8.0.0-canary.3286",
4
4
  "description": "The CedarJS Command Line",
5
5
  "repository": {
6
6
  "type": "git",
@@ -36,17 +36,17 @@
36
36
  "@babel/preset-typescript": "7.29.7",
37
37
  "@babel/traverse": "7.29.8",
38
38
  "@babel/types": "7.29.8",
39
- "@cedarjs/api-server": "8.0.0-canary.3275",
40
- "@cedarjs/babel-config": "8.0.0-canary.3275",
41
- "@cedarjs/cli-helpers": "8.0.0-canary.3275",
42
- "@cedarjs/internal": "8.0.0-canary.3275",
43
- "@cedarjs/prerender": "8.0.0-canary.3275",
44
- "@cedarjs/project-config": "8.0.0-canary.3275",
45
- "@cedarjs/structure": "8.0.0-canary.3275",
46
- "@cedarjs/telemetry": "8.0.0-canary.3275",
47
- "@cedarjs/utils": "8.0.0-canary.3275",
48
- "@cedarjs/vite": "8.0.0-canary.3275",
49
- "@cedarjs/web-server": "8.0.0-canary.3275",
39
+ "@cedarjs/api-server": "8.0.0-canary.3286",
40
+ "@cedarjs/babel-config": "8.0.0-canary.3286",
41
+ "@cedarjs/cli-helpers": "8.0.0-canary.3286",
42
+ "@cedarjs/internal": "8.0.0-canary.3286",
43
+ "@cedarjs/prerender": "8.0.0-canary.3286",
44
+ "@cedarjs/project-config": "8.0.0-canary.3286",
45
+ "@cedarjs/structure": "8.0.0-canary.3286",
46
+ "@cedarjs/telemetry": "8.0.0-canary.3286",
47
+ "@cedarjs/utils": "8.0.0-canary.3286",
48
+ "@cedarjs/vite": "8.0.0-canary.3286",
49
+ "@cedarjs/web-server": "8.0.0-canary.3286",
50
50
  "@listr2/prompt-adapter-enquirer": "4.3.2",
51
51
  "@opentelemetry/api": "1.9.1",
52
52
  "@opentelemetry/core": "2.11.0",
@@ -94,7 +94,7 @@
94
94
  "yargs": "17.7.3"
95
95
  },
96
96
  "devDependencies": {
97
- "@cedarjs/framework-tools": "8.0.0-canary.3275",
97
+ "@cedarjs/framework-tools": "8.0.0-canary.3286",
98
98
  "@prisma/dmmf": "7.10.0",
99
99
  "@types/archiver": "^7.0.0",
100
100
  "memfs": "4.79.0",