@cedarjs/cli 4.0.0-canary.13869 → 4.0.0-canary.13870

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.
@@ -20,6 +20,7 @@ import { loadAndValidateSdls } from "@cedarjs/internal/dist/validateSchema";
20
20
  import { detectPrerenderRoutes } from "@cedarjs/prerender/detection";
21
21
  import {} from "@cedarjs/project-config";
22
22
  import { timedTelemetry } from "@cedarjs/telemetry";
23
+ import { buildUDApiServer } from "@cedarjs/vite/buildUDApiServer";
23
24
  import { generatePrismaCommand } from "../../lib/generatePrismaClient.js";
24
25
  import { getPaths, getConfig } from "../../lib/index.js";
25
26
  import { buildPackagesTask } from "./buildPackagesTask.js";
@@ -76,7 +77,8 @@ const handler = async ({
76
77
  workspace = ["api", "web", "packages/*"],
77
78
  verbose = false,
78
79
  prisma = true,
79
- prerender = true
80
+ prerender = true,
81
+ ud = false
80
82
  }) => {
81
83
  recordTelemetryAttributes({
82
84
  command: "build",
@@ -161,6 +163,11 @@ Run ` + c.info(formatCedarCommand(["build"])) + " (without specifying a workspac
161
163
  title: "Verifying graphql schema...",
162
164
  task: loadAndValidateSdls
163
165
  },
166
+ // The API build has two sequential steps:
167
+ // 1. esbuild compiles api/src/** → api/dist/ (functions, services, etc.)
168
+ // 2. Vite wraps api/dist/functions/ into a self-contained UD Node server
169
+ // entry at api/dist/ud/index.js for `cedar serve api`
170
+ // Step 2 depends on step 1 having completed.
164
171
  workspace.includes("api") && {
165
172
  title: "Building API...",
166
173
  task: async () => {
@@ -168,6 +175,12 @@ Run ` + c.info(formatCedarCommand(["build"])) + " (without specifying a workspac
168
175
  await buildApiWithVite();
169
176
  }
170
177
  },
178
+ ud && workspace.includes("api") && {
179
+ title: "Bundling API server entry (Universal Deploy)...",
180
+ task: async () => {
181
+ await buildUDApiServer({ verbose });
182
+ }
183
+ },
171
184
  workspace.includes("web") && {
172
185
  title: "Building Web...",
173
186
  task: async () => {
@@ -25,6 +25,10 @@ const builder = (yargs) => {
25
25
  alias: "db",
26
26
  default: true,
27
27
  description: "Generate the Prisma client"
28
+ }).option("ud", {
29
+ type: "boolean",
30
+ default: false,
31
+ description: "Build the Universal Deploy server entry (api/dist/ud/index.js)."
28
32
  }).middleware(() => {
29
33
  const check = checkNodeVersion();
30
34
  if (check.ok) {
@@ -18,7 +18,8 @@ const handler = async ({
18
18
  workspace = ["api", "web", "packages/*"],
19
19
  forward = "",
20
20
  generate = true,
21
- apiDebugPort
21
+ apiDebugPort,
22
+ ud = false
22
23
  }) => {
23
24
  recordTelemetryAttributes({
24
25
  command: "dev",
@@ -28,22 +29,26 @@ const handler = async ({
28
29
  const cedarPaths = getPaths();
29
30
  const serverFile = serverFileExists();
30
31
  const apiPreferredPort = parseInt(String(getConfig().api.port));
32
+ let apiAvailablePort;
33
+ let apiPortChangeNeeded = false;
34
+ if (workspace.includes("api")) {
35
+ if (!serverFile) {
36
+ apiAvailablePort = await getFreePort(apiPreferredPort);
37
+ if (apiAvailablePort === -1) {
38
+ exitWithError(void 0, {
39
+ message: `Could not determine a free port for the api server`
40
+ });
41
+ }
42
+ apiPortChangeNeeded = apiAvailablePort !== apiPreferredPort;
43
+ } else {
44
+ apiAvailablePort = apiPreferredPort;
45
+ }
46
+ }
31
47
  let webPreferredPort = parseInt(
32
48
  String(getConfig().web.port)
33
49
  );
34
- let apiAvailablePort = apiPreferredPort;
35
- let apiPortChangeNeeded = false;
36
50
  let webAvailablePort = webPreferredPort;
37
51
  let webPortChangeNeeded = false;
38
- if (workspace.includes("api") && !serverFile) {
39
- apiAvailablePort = await getFreePort(apiPreferredPort);
40
- if (apiAvailablePort === -1) {
41
- exitWithError(void 0, {
42
- message: `Could not determine a free port for the api server`
43
- });
44
- }
45
- apiPortChangeNeeded = apiAvailablePort !== apiPreferredPort;
46
- }
47
52
  if (workspace.includes("web")) {
48
53
  const forwardedPortMatches = [
49
54
  ...forward.matchAll(/\-\-port(\=|\s)(?<port>[^\s]*)/g)
@@ -52,10 +57,10 @@ const handler = async ({
52
57
  const port = forwardedPortMatches.pop()?.groups?.port;
53
58
  webPreferredPort = port ? parseInt(port, 10) : void 0;
54
59
  }
55
- webAvailablePort = await getFreePort(webPreferredPort, [
56
- apiPreferredPort,
57
- apiAvailablePort
58
- ]);
60
+ webAvailablePort = await getFreePort(
61
+ webPreferredPort,
62
+ apiAvailablePort !== void 0 ? [apiPreferredPort, apiAvailablePort] : [apiPreferredPort]
63
+ );
59
64
  if (webAvailablePort === -1) {
60
65
  exitWithError(void 0, {
61
66
  message: `Could not determine a free port for the web server`
@@ -63,21 +68,17 @@ const handler = async ({
63
68
  }
64
69
  webPortChangeNeeded = webAvailablePort !== webPreferredPort;
65
70
  }
66
- if (apiPortChangeNeeded || webPortChangeNeeded) {
71
+ if (webPortChangeNeeded) {
67
72
  const message = [
68
- "The currently configured ports for the development server are",
69
- "unavailable. Suggested changes to your ports, which can be changed in",
70
- "cedar.toml (or redwood.toml), are:\n",
71
- apiPortChangeNeeded && ` - API to use port ${apiAvailablePort} instead`,
72
- apiPortChangeNeeded && "of your currently configured",
73
- apiPortChangeNeeded && `${apiPreferredPort}
73
+ "The currently configured port for the development server is",
74
+ "unavailable. Suggested change to your port, which can be changed in",
75
+ "cedar.toml (or redwood.toml):\n",
76
+ ` - Web to use port ${webAvailablePort} instead`,
77
+ "of your currently configured",
78
+ `${webPreferredPort}
74
79
  `,
75
- webPortChangeNeeded && ` - Web to use port ${webAvailablePort} instead`,
76
- webPortChangeNeeded && "of your currently configured",
77
- webPortChangeNeeded && `${webPreferredPort}
78
- `,
79
- "\nCannot run the development server until your configured ports are",
80
- "changed or become available."
80
+ "\nCannot run the development server until your configured port is",
81
+ "changed or becomes available."
81
82
  ].filter(Boolean).join(" ");
82
83
  exitWithError(void 0, { message });
83
84
  }
@@ -89,19 +90,8 @@ const handler = async ({
89
90
  errorTelemetry(process.argv, `Error generating prisma client: ${message}`);
90
91
  console.error(c.error(message));
91
92
  }
92
- if (!serverFile) {
93
- try {
94
- await shutdownPort(apiAvailablePort);
95
- } catch (e) {
96
- const message = getErrorMessage(e);
97
- errorTelemetry(process.argv, `Error shutting down "api": ${message}`);
98
- console.error(
99
- `Error whilst shutting down "api" port: ${c.error(message)}`
100
- );
101
- }
102
- }
103
93
  }
104
- if (workspace.includes("web")) {
94
+ if (workspace.includes("web") && webAvailablePort !== void 0) {
105
95
  try {
106
96
  await shutdownPort(webAvailablePort);
107
97
  } catch (e) {
@@ -127,6 +117,9 @@ const handler = async ({
127
117
  fs.readFileSync(rootPackageJsonPath, "utf8")
128
118
  );
129
119
  const buildUnifiedDevCommand = () => {
120
+ if (!ud) {
121
+ return null;
122
+ }
130
123
  if (streamingSsrEnabled) {
131
124
  return null;
132
125
  }
@@ -149,6 +142,20 @@ const handler = async ({
149
142
  ].join(" ").replace(/\s+/g, " ").trim();
150
143
  };
151
144
  const unifiedDevCommand = buildUnifiedDevCommand();
145
+ if (!unifiedDevCommand && apiPortChangeNeeded) {
146
+ const message = [
147
+ "The currently configured port for the development server is",
148
+ "unavailable. Suggested change to your port, which can be changed in",
149
+ "cedar.toml (or redwood.toml):\n",
150
+ ` - API to use port ${apiAvailablePort} instead`,
151
+ "of your currently configured",
152
+ `${apiPreferredPort}
153
+ `,
154
+ "\nCannot run the development server until your configured port is",
155
+ "changed or becomes available."
156
+ ].filter(Boolean).join(" ");
157
+ exitWithError(void 0, { message });
158
+ }
152
159
  const jobs = [];
153
160
  if (unifiedDevCommand) {
154
161
  jobs.push({
@@ -24,6 +24,10 @@ const builder = (yargs) => {
24
24
  }).option("apiDebugPort", {
25
25
  type: "number",
26
26
  description: "Port on which to expose API server debugger. If you supply the flag with no value it defaults to 1 prepended to the api port (e.g. api port 8913 -> debug port 18913)."
27
+ }).option("ud", {
28
+ type: "boolean",
29
+ default: false,
30
+ description: "Use the unified Vite dev server that handles both web and API in a single process (experimental)."
27
31
  }).middleware(() => {
28
32
  const check = checkNodeVersion();
29
33
  if (check.ok) {
@@ -1,5 +1,6 @@
1
+ import { fork } from "node:child_process";
1
2
  import fs from "node:fs";
2
- import path from "path";
3
+ import path from "node:path";
3
4
  import { terminalLink } from "termi-link";
4
5
  import * as apiServerCLIConfig from "@cedarjs/api-server/apiCliConfig";
5
6
  import * as bothServerCLIConfig from "@cedarjs/api-server/bothCliConfig";
@@ -43,7 +44,18 @@ const builder = async (yargs) => {
43
44
  }).command({
44
45
  command: "api",
45
46
  description: apiServerCLIConfig.description,
46
- builder: apiServerCLIConfig.builder,
47
+ builder: (yargs2) => {
48
+ if (typeof apiServerCLIConfig.builder === "function") {
49
+ apiServerCLIConfig.builder(yargs2);
50
+ }
51
+ return yargs2.option("ud", {
52
+ // UD serving is opt-in. Pass --ud to use the new srvx server instead
53
+ // of the legacy Fastify server.
54
+ description: "Use the Universal Deploy server (srvx). Pass --ud to opt in; the default is Fastify.",
55
+ type: "boolean",
56
+ default: false
57
+ });
58
+ },
47
59
  handler: async (argv) => {
48
60
  recordTelemetryAttributes({
49
61
  command: "serve",
@@ -52,6 +64,47 @@ const builder = async (yargs) => {
52
64
  socket: argv.socket,
53
65
  apiRootPath: argv.apiRootPath
54
66
  });
67
+ if (argv.ud) {
68
+ const udEntryPath = path.join(getPaths().api.dist, "ud", "index.js");
69
+ if (!fs.existsSync(udEntryPath)) {
70
+ console.error(
71
+ c.error(
72
+ `
73
+ Universal Deploy server entry not found at ${udEntryPath}.
74
+ Please run \`yarn cedar build api\` before serving.
75
+ `
76
+ )
77
+ );
78
+ process.exit(1);
79
+ }
80
+ const udArgs = [];
81
+ if (argv.port) {
82
+ udArgs.push("--port", String(argv.port));
83
+ }
84
+ if (argv.host) {
85
+ udArgs.push("--host", argv.host);
86
+ }
87
+ await new Promise((resolve, reject) => {
88
+ const child = fork(udEntryPath, udArgs, {
89
+ execArgv: process.execArgv,
90
+ env: {
91
+ ...process.env,
92
+ NODE_ENV: process.env.NODE_ENV ?? "production",
93
+ PORT: argv.port ? String(argv.port) : process.env.PORT,
94
+ HOST: argv.host ?? process.env.HOST
95
+ }
96
+ });
97
+ child.on("error", reject);
98
+ child.on("exit", (code) => {
99
+ if (code !== 0) {
100
+ reject(new Error(`UD server exited with code ${code}`));
101
+ } else {
102
+ resolve();
103
+ }
104
+ });
105
+ });
106
+ return;
107
+ }
55
108
  if (serverFileExists()) {
56
109
  const { apiServerFileHandler } = await import("./serveApiHandler.js");
57
110
  await apiServerFileHandler(argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cedarjs/cli",
3
- "version": "4.0.0-canary.13869+572dd23657",
3
+ "version": "4.0.0-canary.13870+b45da21869",
4
4
  "description": "The CedarJS Command Line",
5
5
  "repository": {
6
6
  "type": "git",
@@ -33,16 +33,16 @@
33
33
  "dependencies": {
34
34
  "@babel/parser": "7.29.2",
35
35
  "@babel/preset-typescript": "7.28.5",
36
- "@cedarjs/api-server": "4.0.0-canary.13869",
37
- "@cedarjs/cli-helpers": "4.0.0-canary.13869",
38
- "@cedarjs/fastify-web": "4.0.0-canary.13869",
39
- "@cedarjs/internal": "4.0.0-canary.13869",
40
- "@cedarjs/prerender": "4.0.0-canary.13869",
41
- "@cedarjs/project-config": "4.0.0-canary.13869",
42
- "@cedarjs/structure": "4.0.0-canary.13869",
43
- "@cedarjs/telemetry": "4.0.0-canary.13869",
44
- "@cedarjs/utils": "4.0.0-canary.13869",
45
- "@cedarjs/web-server": "4.0.0-canary.13869",
36
+ "@cedarjs/api-server": "4.0.0-canary.13870",
37
+ "@cedarjs/cli-helpers": "4.0.0-canary.13870",
38
+ "@cedarjs/fastify-web": "4.0.0-canary.13870",
39
+ "@cedarjs/internal": "4.0.0-canary.13870",
40
+ "@cedarjs/prerender": "4.0.0-canary.13870",
41
+ "@cedarjs/project-config": "4.0.0-canary.13870",
42
+ "@cedarjs/structure": "4.0.0-canary.13870",
43
+ "@cedarjs/telemetry": "4.0.0-canary.13870",
44
+ "@cedarjs/utils": "4.0.0-canary.13870",
45
+ "@cedarjs/web-server": "4.0.0-canary.13870",
46
46
  "@listr2/prompt-adapter-enquirer": "4.2.1",
47
47
  "@opentelemetry/api": "1.9.0",
48
48
  "@opentelemetry/core": "1.30.1",
@@ -108,5 +108,5 @@
108
108
  "publishConfig": {
109
109
  "access": "public"
110
110
  },
111
- "gitHead": "572dd236578efed144ab85d042496cd75950bd5c"
111
+ "gitHead": "b45da21869cec79bedc40b47868d54fd598786ac"
112
112
  }