@cedarjs/cli 2.6.1-next.0 → 2.7.0-rc.107

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 (38) hide show
  1. package/dist/commands/build/buildHandler.js +67 -0
  2. package/dist/commands/build/buildPackagesTask.js +20 -19
  3. package/dist/commands/consoleHandler.js +8 -7
  4. package/dist/commands/deploy/render.js +1 -1
  5. package/dist/commands/execHandler.js +22 -19
  6. package/dist/commands/experimental.js +2 -2
  7. package/dist/commands/generate/dataMigration/dataMigration.js +11 -3
  8. package/dist/commands/generate/dataMigration/templates/dataMigration.js.template +1 -1
  9. package/dist/commands/generate/dataMigration/templates/dataMigration.ts.template +1 -1
  10. package/dist/commands/generate/script/scriptHandler.js +8 -4
  11. package/dist/commands/generate/service/serviceHandler.js +10 -1
  12. package/dist/commands/generate/service/templates/scenarios.ts.template +4 -2
  13. package/dist/commands/generate/service/templates/test.ts.template +1 -1
  14. package/dist/commands/info.js +1 -1
  15. package/dist/commands/lint.js +15 -5
  16. package/dist/commands/record/init.js +1 -1
  17. package/dist/commands/record.js +4 -3
  18. package/dist/commands/serve.js +18 -8
  19. package/dist/commands/serveApiHandler.js +3 -2
  20. package/dist/commands/serveBothHandler.js +8 -2
  21. package/dist/commands/serveWebHandler.js +1 -0
  22. package/dist/commands/setup/live-queries/liveQueries.js +31 -0
  23. package/dist/commands/setup/live-queries/liveQueriesHandler.js +282 -0
  24. package/dist/commands/setup/live-queries/templates/liveQueriesListener.ts.template +137 -0
  25. package/dist/commands/setup/live-queries/templates/migration.sql.template +98 -0
  26. package/dist/commands/setup/monitoring/sentry/sentryHandler.js +1 -1
  27. package/dist/commands/setup/realtime/realtimeHandler.js +2 -2
  28. package/dist/commands/setup/realtime/templates/realtime.ts.template +1 -1
  29. package/dist/commands/setup.js +3 -2
  30. package/dist/commands/studioHandler.js +2 -2
  31. package/dist/commands/test.js +2 -1
  32. package/dist/commands/testEsm.js +2 -1
  33. package/dist/commands/type-check.js +2 -1
  34. package/dist/lib/generatePrismaClient.js +3 -6
  35. package/dist/lib/test.js +1 -0
  36. package/dist/lib/updateCheck.js +6 -7
  37. package/dist/middleware/{detectProjectRxVersion.js → detectProjectCedarVersion.js} +2 -2
  38. package/package.json +13 -13
@@ -10,9 +10,57 @@ import { generate } from "@cedarjs/internal/dist/generate/generate";
10
10
  import { loadAndValidateSdls } from "@cedarjs/internal/dist/validateSchema";
11
11
  import { detectPrerenderRoutes } from "@cedarjs/prerender/detection";
12
12
  import { timedTelemetry } from "@cedarjs/telemetry";
13
+ import c from "../../lib/colors.js";
13
14
  import { generatePrismaCommand } from "../../lib/generatePrismaClient.js";
14
15
  import { getPaths, getConfig } from "../../lib/index.js";
15
16
  import { buildPackagesTask } from "./buildPackagesTask.js";
17
+ function checkWorkspacePackageEntryPoints(cedarPaths) {
18
+ const packagesDir = cedarPaths.packages;
19
+ if (!packagesDir || !fs.existsSync(packagesDir)) {
20
+ return [];
21
+ }
22
+ const problems = [];
23
+ const packageDirs = fs.readdirSync(packagesDir, { withFileTypes: true });
24
+ for (const entry of packageDirs) {
25
+ if (!entry.isDirectory()) {
26
+ continue;
27
+ }
28
+ const pkgJsonPath = path.join(packagesDir, entry.name, "package.json");
29
+ if (!fs.existsSync(pkgJsonPath)) {
30
+ continue;
31
+ }
32
+ const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8"));
33
+ const pkgName = pkgJson.name || entry.name;
34
+ const pkgDir = path.join(packagesDir, entry.name);
35
+ const entryFiles = /* @__PURE__ */ new Set();
36
+ if (pkgJson.main) {
37
+ entryFiles.add(path.normalize(pkgJson.main));
38
+ }
39
+ if (pkgJson.exports) {
40
+ const collectPaths = (obj) => {
41
+ if (typeof obj === "string") {
42
+ if (!obj.endsWith(".d.ts")) {
43
+ entryFiles.add(path.normalize(obj));
44
+ }
45
+ } else if (obj && typeof obj === "object") {
46
+ for (const [key, value] of Object.entries(obj)) {
47
+ if (key !== "types") {
48
+ collectPaths(value);
49
+ }
50
+ }
51
+ }
52
+ };
53
+ collectPaths(pkgJson.exports);
54
+ }
55
+ for (const entryFile of entryFiles) {
56
+ const resolvedPath = path.resolve(pkgDir, entryFile);
57
+ if (!fs.existsSync(resolvedPath)) {
58
+ problems.push({ pkgName, entryFile, pkgDir });
59
+ }
60
+ }
61
+ }
62
+ return problems;
63
+ }
16
64
  const handler = async ({
17
65
  workspace = ["api", "web", "packages/*"],
18
66
  verbose = false,
@@ -56,6 +104,25 @@ const handler = async ({
56
104
  title: "Building Packages...",
57
105
  task: (_ctx, task) => buildPackagesTask(task, nonApiWebWorkspaces)
58
106
  },
107
+ (workspace.includes("web") || workspace.includes("api")) && {
108
+ title: "Checking workspace packages...",
109
+ task: () => {
110
+ const problems = checkWorkspacePackageEntryPoints(cedarPaths);
111
+ if (problems.length === 0) {
112
+ return;
113
+ }
114
+ const details = problems.map(
115
+ ({ pkgName, entryFile, pkgDir }) => ` \u2022 ${c.error(pkgName)}: missing "${entryFile}" (in ${pkgDir})`
116
+ ).join("\n");
117
+ throw new Error(
118
+ `The following workspace package entry points are missing:
119
+ ${details}
120
+
121
+ This usually means the package has not been built yet.
122
+ Run ` + c.info("yarn cedar build") + " (without specifying a workspace) to build everything,\nor build the package manually first, e.g. " + c.info(`yarn workspace ${problems[0].pkgName} build`)
123
+ );
124
+ }
125
+ },
59
126
  // If using GraphQL Fragments or Trusted Documents, then we need to use
60
127
  // codegen to generate the types needed for possible types and the trusted
61
128
  // document store hashes
@@ -1,9 +1,8 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import concurrently from "concurrently";
3
+ import execa from "execa";
4
4
  import { importStatementPath } from "@cedarjs/project-config";
5
5
  import { errorTelemetry } from "@cedarjs/telemetry";
6
- import { exitWithError } from "../../lib/exit.js";
7
6
  import { getPaths } from "../../lib/index.js";
8
7
  async function buildPackagesTask(task, nonApiWebWorkspaces) {
9
8
  const cedarPaths = getPaths();
@@ -22,28 +21,30 @@ async function buildPackagesTask(task, nonApiWebWorkspaces) {
22
21
  task.skip("No packages to build at " + nonApiWebWorkspaces.join(", "));
23
22
  return;
24
23
  }
25
- const { result } = concurrently(
24
+ return task.newListr(
26
25
  workspacePaths.map((workspacePath) => {
26
+ const name = workspacePath.split("/").at(-1);
27
27
  return {
28
- command: `yarn build`,
29
- name: workspacePath.split("/").at(-1),
30
- cwd: workspacePath
28
+ title: name,
29
+ task: async () => {
30
+ try {
31
+ await execa("yarn", ["build"], { cwd: workspacePath });
32
+ } catch (e) {
33
+ errorTelemetry(
34
+ process.argv,
35
+ `Error building package "${name}": ${e.message}`
36
+ );
37
+ throw new Error(
38
+ `Building "${name}" failed
39
+
40
+ ${e.stderr || e.message}`
41
+ );
42
+ }
43
+ }
31
44
  };
32
45
  }),
33
- {
34
- prefix: "{name} |",
35
- timestampFormat: "HH:mm:ss"
36
- }
46
+ { concurrent: true, rendererOptions: { collapseSubtasks: false } }
37
47
  );
38
- await result.catch((e) => {
39
- if (e?.message) {
40
- errorTelemetry(
41
- process.argv,
42
- `Error concurrently building sides: ${e.message}`
43
- );
44
- exitWithError(e);
45
- }
46
- });
47
48
  }
48
49
  export {
49
50
  buildPackagesTask
@@ -24,10 +24,10 @@ const loadConsoleHistory = async (r) => {
24
24
  try {
25
25
  const history = await fs.promises.readFile(consoleHistoryFile, "utf8");
26
26
  history.split("\n").reverse().map((line) => r.history.push(line));
27
- } catch (e) {
27
+ } catch {
28
28
  }
29
29
  };
30
- const handler = () => {
30
+ const handler = (_options) => {
31
31
  recordTelemetryAttributes({
32
32
  command: "console"
33
33
  });
@@ -46,19 +46,20 @@ const handler = () => {
46
46
  });
47
47
  const r = repl.start();
48
48
  const defaultEval = r.eval;
49
- r.eval = (cmd, context, filename, callback) => {
50
- defaultEval(cmd, context, filename, async (err, result) => {
49
+ const asyncEval = (cmd, context, filename, callback) => {
50
+ defaultEval.call(r, cmd, context, filename, async (err, result) => {
51
51
  if (err) {
52
- callback(err);
52
+ callback(err, null);
53
53
  } else {
54
54
  try {
55
55
  callback(null, await Promise.resolve(result));
56
- } catch (err2) {
57
- callback(err2);
56
+ } catch (error) {
57
+ callback(error, null);
58
58
  }
59
59
  }
60
60
  });
61
61
  };
62
+ r.eval = asyncEval;
62
63
  loadConsoleHistory(r);
63
64
  r.addListener("close", () => persistConsoleHistory(r));
64
65
  loadPrismaClient(r.context);
@@ -1,6 +1,6 @@
1
1
  import { terminalLink } from "termi-link";
2
2
  if (process.argv.slice(2).includes("api")) {
3
- process.env.REDWOOD_DISABLE_TELEMETRY = 1;
3
+ process.env.REDWOOD_DISABLE_TELEMETRY = "1";
4
4
  }
5
5
  const command = "render <side>";
6
6
  const description = "Build, migrate, and serve command for Render deploy";
@@ -10,18 +10,21 @@ import { runScriptFunction } from "../lib/exec.js";
10
10
  import { generatePrismaClient } from "../lib/generatePrismaClient.js";
11
11
  import { getPaths } from "../lib/index.js";
12
12
  const printAvailableScriptsToConsole = () => {
13
- const scripts = findScripts(getPaths().scripts).reduce((acc, scriptPath) => {
14
- const relativePath = path.relative(getPaths().scripts, scriptPath);
15
- const ext = path.parse(relativePath).ext;
16
- const pathNoExt = relativePath.slice(0, -ext.length);
17
- acc[pathNoExt] ||= [];
18
- acc[pathNoExt].push(relativePath);
19
- return acc;
20
- }, {});
13
+ const scripts = findScripts(getPaths().scripts).reduce(
14
+ (acc, scriptPath) => {
15
+ const relativePath = path.relative(getPaths().scripts, scriptPath);
16
+ const ext = path.parse(relativePath).ext;
17
+ const pathNoExt = relativePath.slice(0, -ext.length);
18
+ acc[pathNoExt] ||= [];
19
+ acc[pathNoExt].push(relativePath);
20
+ return acc;
21
+ },
22
+ {}
23
+ );
21
24
  console.log("Available scripts:");
22
- Object.entries(scripts).forEach(([name, paths]) => {
23
- if (paths.length > 1) {
24
- paths.forEach((scriptPath) => {
25
+ Object.entries(scripts).forEach(([name, scriptPaths]) => {
26
+ if (scriptPaths.length > 1) {
27
+ scriptPaths.forEach((scriptPath) => {
25
28
  console.log(c.info(`- ${scriptPath}`));
26
29
  });
27
30
  } else {
@@ -54,7 +57,7 @@ No script called \`${name}\` in the ./scripts folder.
54
57
  const scriptTasks = [
55
58
  {
56
59
  title: "Generating Prisma client",
57
- enabled: () => prisma,
60
+ enabled: () => Boolean(prisma),
58
61
  task: () => generatePrismaClient({
59
62
  force: false,
60
63
  verbose: !args.silent,
@@ -70,15 +73,15 @@ No script called \`${name}\` in the ./scripts folder.
70
73
  functionName: "default",
71
74
  args: { args: scriptArgs }
72
75
  });
73
- } catch (e) {
74
- console.error(c.error(`Error in script: ${e.message}`));
75
- throw e;
76
+ } catch (error) {
77
+ const message = error instanceof Error ? error.message : String(error);
78
+ console.error(c.error(`Error in script: ${message}`));
79
+ throw error;
76
80
  }
77
81
  }
78
82
  }
79
83
  ];
80
84
  const tasks = new Listr(scriptTasks, {
81
- rendererOptions: { collapseSubtasks: false },
82
85
  renderer: args.silent ? "silent" : "verbose"
83
86
  });
84
87
  await context.with(suppressTracing(context.active()), async () => {
@@ -93,9 +96,9 @@ function resolveScriptPath(name) {
93
96
  const extensions = [".js", ".jsx", ".ts", ".tsx"];
94
97
  const matches = [];
95
98
  for (const extension of extensions) {
96
- const p = scriptPath + extension;
97
- if (fs.existsSync(p)) {
98
- matches.push(p);
99
+ const candidate = scriptPath + extension;
100
+ if (fs.existsSync(candidate)) {
101
+ matches.push(candidate);
99
102
  }
100
103
  }
101
104
  if (matches.length === 1) {
@@ -1,5 +1,5 @@
1
1
  import { terminalLink } from "termi-link";
2
- import detectRxVersion from "../middleware/detectProjectRxVersion.js";
2
+ import { detectCedarVersion } from "../middleware/detectProjectCedarVersion.js";
3
3
  import * as experimentalInngest from "./experimental/setupInngest.js";
4
4
  import * as experimentalOpenTelemetry from "./experimental/setupOpentelemetry.js";
5
5
  import * as experimentalReactCompiler from "./experimental/setupReactCompiler.js";
@@ -8,7 +8,7 @@ import * as experimentalStreamingSsr from "./experimental/setupStreamingSsr.js";
8
8
  const command = "experimental <command>";
9
9
  const aliases = ["exp"];
10
10
  const description = "Run or setup experimental features";
11
- const builder = (yargs) => yargs.command(experimentalInngest).command(experimentalOpenTelemetry).command(experimentalReactCompiler).command(experimentalRsc).command(experimentalStreamingSsr).demandCommand().middleware(detectRxVersion).epilogue(
11
+ const builder = (yargs) => yargs.command(experimentalInngest).command(experimentalOpenTelemetry).command(experimentalReactCompiler).command(experimentalRsc).command(experimentalStreamingSsr).demandCommand().middleware(detectCedarVersion).epilogue(
12
12
  `Also see the ${terminalLink(
13
13
  "CedarJS CLI Reference",
14
14
  "https://cedarjs.com/docs/cli-commands#experimental"
@@ -1,12 +1,16 @@
1
- import fs from "node:fs";
2
1
  import path from "node:path";
3
2
  import { paramCase } from "change-case";
4
3
  import { Listr } from "listr2";
5
4
  import { terminalLink } from "termi-link";
6
5
  import { recordTelemetryAttributes } from "@cedarjs/cli-helpers";
6
+ import { dbReexportsPrismaClient } from "@cedarjs/internal/dist/project";
7
7
  import { getDataMigrationsPath } from "@cedarjs/project-config";
8
8
  import c from "../../../lib/colors.js";
9
- import { getPaths, writeFilesTask } from "../../../lib/index.js";
9
+ import {
10
+ generateTemplate,
11
+ getPaths,
12
+ writeFilesTask
13
+ } from "../../../lib/index.js";
10
14
  import { prepareForRollback } from "../../../lib/rollback.js";
11
15
  import { validateName } from "../helpers.js";
12
16
  import { getYargsDefaults } from "../yargsCommandHelpers.js";
@@ -40,8 +44,12 @@ const files = async ({ name, typescript }) => {
40
44
  getPaths().api.prismaConfig
41
45
  );
42
46
  const outputPath = path.join(dataMigrationsPath, outputFilename);
47
+ const prismaImportSource = dbReexportsPrismaClient() ? "src/lib/db" : "@prisma/client";
43
48
  return {
44
- [outputPath]: fs.readFileSync(TEMPLATE_PATHS[extension]).toString()
49
+ [outputPath]: await generateTemplate(TEMPLATE_PATHS[extension], {
50
+ name,
51
+ prismaImportSource
52
+ })
45
53
  };
46
54
  };
47
55
  const command = "data-migration <name>";
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @typedef { import("@prisma/client").PrismaClient } PrismaClient
2
+ * @typedef { import("${prismaImportSource}").PrismaClient } PrismaClient
3
3
  * @param {{db: PrismaClient}} db
4
4
  */
5
5
  export default async ({ db }) => {
@@ -1,4 +1,4 @@
1
- import type { PrismaClient } from '@prisma/client'
1
+ import type { PrismaClient } from '${prismaImportSource}'
2
2
 
3
3
  export default async ({ db }: { db: PrismaClient }) => {
4
4
  // Migration here...
@@ -35,7 +35,10 @@ const files = async ({ name, typescript = false }) => {
35
35
  }
36
36
  };
37
37
  };
38
- const handler = async ({ force, ...args }) => {
38
+ const handler = async ({
39
+ force,
40
+ ...args
41
+ }) => {
39
42
  recordTelemetryAttributes({
40
43
  command: "generate script",
41
44
  force,
@@ -74,9 +77,10 @@ const handler = async ({ force, ...args }) => {
74
77
  prepareForRollback(tasks);
75
78
  }
76
79
  await tasks.run();
77
- } catch (e) {
78
- errorTelemetry(process.argv, e.message);
79
- console.log(c.error(e.message));
80
+ } catch (error) {
81
+ const message = error instanceof Error ? error.message : String(error);
82
+ errorTelemetry(process.argv, message);
83
+ console.log(c.error(message));
80
84
  process.exit(1);
81
85
  }
82
86
  };
@@ -1,5 +1,6 @@
1
1
  import path from "node:path";
2
2
  import camelcase from "camelcase";
3
+ import { dbReexportsPrismaClient } from "@cedarjs/internal/dist/project";
3
4
  import { pluralize, singularize } from "../../../lib/cedarPluralize.js";
4
5
  import { transformTSToJS } from "../../../lib/index.js";
5
6
  import { getSchema, verifyModelName } from "../../../lib/schemaHelpers.js";
@@ -222,6 +223,7 @@ const files = async ({
222
223
  const componentName = camelcase(pluralize(name));
223
224
  const model = name;
224
225
  const idName = await getIdName(model);
226
+ const prismaImportSource = dbReexportsPrismaClient() ? "src/lib/db" : "@prisma/client";
225
227
  const modelRelations = relations || relationsForModel(await getSchema(model));
226
228
  const serviceFile = await templateForFile({
227
229
  name,
@@ -230,7 +232,12 @@ const files = async ({
230
232
  generator: "service",
231
233
  outputPath: path.join(componentName, componentName + ".ts"),
232
234
  templatePath: "service.ts.template",
233
- templateVars: { relations: modelRelations, idName, ...rest }
235
+ templateVars: {
236
+ relations: modelRelations,
237
+ idName,
238
+ prismaImportSource,
239
+ ...rest
240
+ }
234
241
  });
235
242
  const testFile = await templateForFile({
236
243
  name,
@@ -249,6 +256,7 @@ const files = async ({
249
256
  ),
250
257
  prismaModel: model,
251
258
  idName,
259
+ prismaImportSource,
252
260
  ...rest
253
261
  }
254
262
  });
@@ -265,6 +273,7 @@ const files = async ({
265
273
  prismaModel: model,
266
274
  idName,
267
275
  relations: modelRelations,
276
+ prismaImportSource,
268
277
  ...rest
269
278
  }
270
279
  });
@@ -1,6 +1,8 @@
1
- import type { Prisma, ${prismaModel} } from '@prisma/client'
1
+ <% if (prismaImportSource === 'src/lib/db') { %>import type { ScenarioData } from '@cedarjs/testing/api'
2
2
 
3
- import type { ScenarioData } from '@cedarjs/testing/api'
3
+ import type { Prisma, ${prismaModel} } from '${prismaImportSource}'<% } else { %>import type { Prisma, ${prismaModel} } from '${prismaImportSource}'
4
+
5
+ import type { ScenarioData } from '@cedarjs/testing/api'<% } %>
4
6
 
5
7
  export const standard = defineScenario<Prisma.${prismaModel}CreateArgs>(${stringifiedScenario})
6
8
 
@@ -31,7 +31,7 @@
31
31
  // Not all values can be represented as JSON, like function invocations
32
32
  return jsonString.replace(/"new Uint8Array\(([^)]+)\)"/g, 'new Uint8Array($1)')
33
33
  } %>
34
- <% if (prismaImport) { %>import { Prisma, ${prismaModel} } from '@prisma/client'<% } else { %>import type { ${prismaModel} } from '@prisma/client'<% } %>
34
+ <% if (prismaImport) { %>import { Prisma, ${prismaModel} } from '${prismaImportSource}'<% } else { %>import type { ${prismaModel} } from '${prismaImportSource}'<% } %>
35
35
 
36
36
  import { ${pluralCamelName}<% if (crud) { %>,${singularCamelName}, create${singularPascalName}, update${singularPascalName}, delete${singularPascalName}<% } %> } from './${pluralCamelName}.js'
37
37
  import type { StandardScenario } from './${pluralCamelName}.scenarios.js'
@@ -28,7 +28,7 @@ const handler = async () => {
28
28
  const tomlContent = fs.readFileSync(configTomlPath, "utf8");
29
29
  console.log(
30
30
  output + ` ${path.basename(configTomlPath)}:
31
- ` + tomlContent.split("\n").filter((line) => line.trim().length > 0).filter((line) => !/^#/.test(line)).map((line) => ` ${line}`).join("\n")
31
+ ` + tomlContent.split("\n").filter((line) => line.trim().length > 0).filter((line) => !line.startsWith("#")).map((line) => ` ${line}`).join("\n")
32
32
  );
33
33
  };
34
34
  export {
@@ -29,7 +29,7 @@ function detectLegacyEslintConfig() {
29
29
  if (packageJson.eslint) {
30
30
  foundLegacyFiles.push("package.json (eslint field)");
31
31
  }
32
- } catch (error) {
32
+ } catch {
33
33
  }
34
34
  }
35
35
  return foundLegacyFiles;
@@ -63,7 +63,8 @@ const description = "Lint your files";
63
63
  const builder = (yargs) => {
64
64
  yargs.positional("paths", {
65
65
  description: "Specify file(s) or directory(ies) to lint relative to project root",
66
- type: "array"
66
+ type: "string",
67
+ array: true
67
68
  }).option("fix", {
68
69
  default: false,
69
70
  description: "Try to fix errors",
@@ -79,7 +80,11 @@ const builder = (yargs) => {
79
80
  )}`
80
81
  );
81
82
  };
82
- const handler = async ({ paths, fix, format }) => {
83
+ const handler = async ({
84
+ paths = [],
85
+ fix = false,
86
+ format = "stylish"
87
+ }) => {
83
88
  recordTelemetryAttributes({ command: "lint", fix, format });
84
89
  const config = getConfig();
85
90
  const legacyConfigFiles = detectLegacyEslintConfig();
@@ -98,13 +103,18 @@ const handler = async ({ paths, fix, format }) => {
98
103
  fs.existsSync(getPaths().api.src) && "api/src"
99
104
  );
100
105
  }
101
- const result = await execa("yarn", args.filter(Boolean), {
106
+ const filteredArgs = args.filter((arg) => Boolean(arg));
107
+ const result = await execa("yarn", filteredArgs, {
102
108
  cwd: getPaths().base,
103
109
  stdio: "inherit"
104
110
  });
105
111
  process.exitCode = result.exitCode;
106
112
  } catch (error) {
107
- process.exitCode = error.exitCode ?? 1;
113
+ if (error && typeof error === "object" && "exitCode" in error) {
114
+ process.exitCode = typeof error.exitCode === "number" ? error.exitCode : 1;
115
+ return;
116
+ }
117
+ process.exitCode = 1;
108
118
  }
109
119
  };
110
120
  export {
@@ -1,5 +1,5 @@
1
1
  import { recordTelemetryAttributes } from "@cedarjs/cli-helpers";
2
- const handler = async () => {
2
+ const handler = async (_argv) => {
3
3
  recordTelemetryAttributes({
4
4
  command: "record"
5
5
  });
@@ -1,7 +1,8 @@
1
+ import { terminalLink } from "termi-link";
1
2
  const command = "record <command>";
2
3
  const description = "Setup RedwoodRecord for your project. Caches a JSON version of your data model and adds api/src/models/index.js with some config.";
3
- import { terminalLink } from "termi-link";
4
- const builder = (yargs) => yargs.command({ command, description, handler }).demandCommand().epilogue(
4
+ const builder = (yargs) => yargs.command(command, description, () => {
5
+ }, handler).demandCommand().epilogue(
5
6
  `Also see the ${terminalLink(
6
7
  "RedwoodRecord Docs",
7
8
  "https://cedarjs.com/docs/redwoodrecord"
@@ -10,7 +11,7 @@ const builder = (yargs) => yargs.command({ command, description, handler }).dema
10
11
  );
11
12
  async function handler(argv) {
12
13
  const recordInit = await import("./record/init.js");
13
- recordInit(argv);
14
+ return recordInit.handler(argv);
14
15
  }
15
16
  export {
16
17
  builder,
@@ -27,11 +27,11 @@ const builder = async (yargs) => {
27
27
  socket: argv.socket
28
28
  });
29
29
  if (serverFileExists()) {
30
- const { bothServerFileHandler } = await import("./serveBothHandler.js");
31
- await bothServerFileHandler(argv);
30
+ const serveBothHandlers = await import("./serveBothHandler.js");
31
+ await serveBothHandlers.bothServerFileHandler(argv);
32
32
  } else if (rscEnabled || streamingEnabled) {
33
- const { bothSsrRscServerHandler } = await import("./serveBothHandler.js");
34
- await bothSsrRscServerHandler(argv, rscEnabled);
33
+ const serveBothHandlers = await import("./serveBothHandler.js");
34
+ await serveBothHandlers.bothSsrRscServerHandler(argv, rscEnabled);
35
35
  } else {
36
36
  if (!projectIsEsm()) {
37
37
  const { handler } = await import("@cedarjs/api-server/cjs/bothCliConfigHandler");
@@ -88,7 +88,7 @@ const builder = async (yargs) => {
88
88
  command: "serve"
89
89
  });
90
90
  const positionalArgs = argv._;
91
- if (positionalArgs.includes("web") && !fs.existsSync(path.join(getPaths().web.dist), "index.html")) {
91
+ if (positionalArgs.includes("web") && !webSideIsBuilt(streamingEnabled || rscEnabled)) {
92
92
  console.error(
93
93
  c.error(
94
94
  "\n Please run `yarn cedar build web` before trying to serve web. \n"
@@ -119,15 +119,16 @@ const builder = async (yargs) => {
119
119
  if (!apiSideExists && !rscEnabled) {
120
120
  console.error(
121
121
  c.error(
122
- "\n Unable to serve the both sides as no `api` folder exists. Please use `yarn cedar serve web` instead. \n"
122
+ "\nUnable to serve web and api as no `api` folder exists. Please use `yarn cedar serve web` instead. \n"
123
123
  )
124
124
  );
125
125
  process.exit(1);
126
126
  }
127
- if (fs.existsSync(path.join(getPaths().api.base)) && !fs.existsSync(path.join(getPaths().api.dist)) || !fs.existsSync(path.join(getPaths().web.dist), "index.html")) {
127
+ const apiExistsButIsNotBuilt = apiSideExists && !fs.existsSync(getPaths().api.dist);
128
+ if (apiExistsButIsNotBuilt || !webSideIsBuilt(streamingEnabled || rscEnabled)) {
128
129
  console.error(
129
130
  c.error(
130
- "\n Please run `yarn cedar build` before trying to serve your redwood app. \n"
131
+ "\nPlease run `yarn cedar build` before trying to serve your Cedar app.\n"
131
132
  )
132
133
  );
133
134
  process.exit(1);
@@ -143,6 +144,15 @@ const builder = async (yargs) => {
143
144
  )}`
144
145
  );
145
146
  };
147
+ function webSideIsBuilt(isStreamingOrRSC) {
148
+ if (isStreamingOrRSC) {
149
+ return fs.existsSync(
150
+ path.join(getPaths().web.distBrowser, "client-build-manifest.json")
151
+ );
152
+ } else {
153
+ return fs.existsSync(path.join(getPaths().web.dist, "index.html"));
154
+ }
155
+ }
146
156
  export {
147
157
  builder,
148
158
  command,
@@ -3,9 +3,10 @@ import { getPaths } from "@cedarjs/project-config";
3
3
  const apiServerFileHandler = async (argv) => {
4
4
  const args = ["node", "server.js", "--apiRootPath", argv.apiRootPath];
5
5
  if (argv.port) {
6
- args.push("--apiPort", argv.port);
6
+ args.push("--apiPort", String(argv.port));
7
7
  }
8
- await execa("yarn", args, {
8
+ const filteredArgs = args.filter((arg) => Boolean(arg));
9
+ await execa("yarn", filteredArgs, {
9
10
  cwd: getPaths().api.dist,
10
11
  stdio: "inherit"
11
12
  });
@@ -11,6 +11,10 @@ import {
11
11
  import { getConfig, getPaths } from "@cedarjs/project-config";
12
12
  import { errorTelemetry } from "@cedarjs/telemetry";
13
13
  import { exitWithError } from "../lib/exit.js";
14
+ const hasStringMessage = (error) => {
15
+ const message = typeof error === "object" && error !== null ? Reflect.get(error, "message") : void 0;
16
+ return typeof error === "object" && error !== null && "message" in error && typeof message === "string";
17
+ };
14
18
  const bothServerFileHandler = async (argv) => {
15
19
  if (getConfig().experimental?.rsc?.enabled || getConfig().experimental?.streamingSsr?.enabled) {
16
20
  logSkippingFastifyWebServer();
@@ -54,10 +58,11 @@ const bothServerFileHandler = async (argv) => {
54
58
  try {
55
59
  await result;
56
60
  } catch (error) {
57
- if (typeof error?.message !== "undefined") {
61
+ const message = hasStringMessage(error) ? error.message : void 0;
62
+ if (typeof message !== "undefined") {
58
63
  errorTelemetry(
59
64
  process.argv,
60
- `Error concurrently starting sides: ${error.message}`
65
+ `Error concurrently starting sides: ${message}`
61
66
  );
62
67
  exitWithError(error);
63
68
  }
@@ -74,6 +79,7 @@ const bothSsrRscServerHandler = async (argv, rscEnabled) => {
74
79
  cwd: getPaths().web.base,
75
80
  stdio: "inherit",
76
81
  env: rscEnabled ? {
82
+ ...process.env,
77
83
  // TODO (RSC): Is this how we want to do it? If so, we need to find a way
78
84
  // to merge this with users' NODE_OPTIONS
79
85
  NODE_OPTIONS: "--conditions react-server"
@@ -5,6 +5,7 @@ const webSsrServerHandler = async (rscEnabled) => {
5
5
  cwd: getPaths().web.base,
6
6
  stdio: "inherit",
7
7
  env: rscEnabled ? {
8
+ ...process.env,
8
9
  // TODO (RSC): Is this how we want to do it? If so, we need to find a way
9
10
  // to merge this with users' NODE_OPTIONS
10
11
  NODE_OPTIONS: "--conditions react-server"