@codemation/cli 0.0.5 → 0.0.11

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 (40) hide show
  1. package/README.md +20 -26
  2. package/dist/{CliBin-C3ar49fj.js → CliBin-BAnFX1wL.js} +1105 -366
  3. package/dist/bin.js +1 -1
  4. package/dist/index.d.ts +655 -207
  5. package/dist/index.js +1 -1
  6. package/package.json +14 -6
  7. package/src/CliProgramFactory.ts +23 -8
  8. package/src/Program.ts +7 -3
  9. package/src/bootstrap/CodemationCliApplicationSession.ts +17 -19
  10. package/src/commands/DevCommand.ts +203 -171
  11. package/src/commands/ServeWebCommand.ts +26 -1
  12. package/src/commands/ServeWorkerCommand.ts +46 -30
  13. package/src/commands/devCommandLifecycle.types.ts +7 -11
  14. package/src/database/ConsumerDatabaseConnectionResolver.ts +55 -9
  15. package/src/database/DatabaseMigrationsApplyService.ts +2 -2
  16. package/src/dev/Builder.ts +1 -14
  17. package/src/dev/CliDevProxyServer.ts +457 -0
  18. package/src/dev/CliDevProxyServerFactory.ts +10 -0
  19. package/src/dev/DevApiRuntimeFactory.ts +44 -0
  20. package/src/dev/DevApiRuntimeHost.ts +130 -0
  21. package/src/dev/DevApiRuntimeServer.ts +107 -0
  22. package/src/dev/DevApiRuntimeTypes.ts +24 -0
  23. package/src/dev/DevAuthSettingsLoader.ts +9 -3
  24. package/src/dev/DevBootstrapSummaryFetcher.ts +1 -1
  25. package/src/dev/DevHttpProbe.ts +2 -2
  26. package/src/dev/DevNextHostEnvironmentBuilder.ts +35 -5
  27. package/src/dev/DevRebuildQueue.ts +54 -0
  28. package/src/dev/DevRebuildQueueFactory.ts +7 -0
  29. package/src/dev/DevSessionPortsResolver.ts +2 -2
  30. package/src/dev/DevSessionServices.ts +0 -4
  31. package/src/dev/DevSourceChangeClassifier.ts +33 -13
  32. package/src/dev/ListenPortConflictDescriber.ts +83 -0
  33. package/src/dev/WatchRootsResolver.ts +6 -4
  34. package/src/runtime/NextHostConsumerServerCommandFactory.ts +11 -2
  35. package/src/user/CliDatabaseUrlDescriptor.ts +2 -2
  36. package/src/user/UserAdminCliBootstrap.ts +9 -21
  37. package/codemation-cli-0.0.3.tgz +0 -0
  38. package/src/dev/DevSourceRestartCoordinator.ts +0 -48
  39. package/src/dev/DevelopmentGatewayNotifier.ts +0 -35
  40. package/src/dev/RuntimeToolEntrypointResolver.ts +0 -47
@@ -1,23 +1,28 @@
1
1
  import { createRequire } from "node:module";
2
2
  import process$1 from "node:process";
3
- import { CodemationConsumerConfigLoader, CodemationPluginDiscovery, WorkflowDiscoveryPathSegmentsComputer, WorkflowModulePathFinder } from "@codemation/host/server";
4
- import { ServerLoggerFactory, logLevelPolicyFactory } from "@codemation/host/next/server";
3
+ import { AppConfigLoader, CodemationConsumerConfigLoader, CodemationFrontendAuthSnapshotFactory, CodemationPluginDiscovery, FrontendAppConfigJsonCodec, WorkflowDiscoveryPathSegmentsComputer, WorkflowModulePathFinder } from "@codemation/host/server";
4
+ import { ApiPaths, AppContainerFactory, AppContainerLifecycle, ApplicationTokens, CodemationFrontendAuthSnapshotFactory as CodemationFrontendAuthSnapshotFactory$1, DatabaseMigrations, FrontendAppConfigJsonCodec as FrontendAppConfigJsonCodec$1, ListUserAccountsQuery, PrismaClient, UpsertLocalBootstrapUserCommand, WorkerRuntime } from "@codemation/host";
5
+ import { AppContainerFactory as AppContainerFactory$1, AppContainerLifecycle as AppContainerLifecycle$1, CodemationHonoApiApp, CodemationPluginListMerger, FrontendRuntime, ServerLoggerFactory, logLevelPolicyFactory } from "@codemation/host/next/server";
5
6
  import { randomUUID } from "node:crypto";
6
7
  import { access, copyFile, cp, mkdir, open, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
7
8
  import path from "node:path";
8
9
  import { fileURLToPath, pathToFileURL } from "node:url";
9
- import { spawn } from "node:child_process";
10
- import { ApiPaths, ApplicationTokens, CodemationApplication, CodemationBootstrapRequest, ListUserAccountsQuery, PrismaClient, UpsertLocalBootstrapUserCommand } from "@codemation/host";
10
+ import { execFile, spawn } from "node:child_process";
11
11
  import { existsSync, readFileSync } from "node:fs";
12
12
  import { config, parse } from "dotenv";
13
13
  import { watch } from "chokidar";
14
14
  import ts from "typescript";
15
- import { DatabasePersistenceResolver, PrismaMigrationDeployer } from "@codemation/host/persistence";
15
+ import { PrismaMigrationDeployer } from "@codemation/host/persistence";
16
16
  import boxen from "boxen";
17
17
  import chalk from "chalk";
18
18
  import figlet from "figlet";
19
+ import { createServer } from "node:http";
20
+ import httpProxy from "http-proxy";
21
+ import { WebSocket, WebSocketServer } from "ws";
22
+ import { serve } from "@hono/node-server";
23
+ import { Hono } from "hono";
19
24
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
20
- import { createServer } from "node:net";
25
+ import { createServer as createServer$1 } from "node:net";
21
26
  import { Command } from "commander";
22
27
 
23
28
  //#region src/build/ConsumerBuildArtifactsPublisher.ts
@@ -120,9 +125,8 @@ var DbMigrateCommand = class {
120
125
  //#region src/commands/DevCommand.ts
121
126
  var DevCommand = class {
122
127
  require = createRequire(import.meta.url);
123
- constructor(pathResolver, pluginDiscovery, tsRuntime, devLockFactory, devSourceWatcherFactory, cliLogger, session, databaseMigrationsApplyService, devBootstrapSummaryFetcher, devCliBannerRenderer, devConsumerPublishBootstrap, consumerEnvDotenvFilePredicate, devTrackedProcessTreeKiller, nextHostConsumerServerCommandFactory) {
128
+ constructor(pathResolver, tsRuntime, devLockFactory, devSourceWatcherFactory, cliLogger, session, databaseMigrationsApplyService, devBootstrapSummaryFetcher, devCliBannerRenderer, devConsumerPublishBootstrap, consumerEnvDotenvFilePredicate, devTrackedProcessTreeKiller, nextHostConsumerServerCommandFactory, devApiRuntimeFactory, cliDevProxyServerFactory, devRebuildQueueFactory) {
124
129
  this.pathResolver = pathResolver;
125
- this.pluginDiscovery = pluginDiscovery;
126
130
  this.tsRuntime = tsRuntime;
127
131
  this.devLockFactory = devLockFactory;
128
132
  this.devSourceWatcherFactory = devSourceWatcherFactory;
@@ -135,14 +139,17 @@ var DevCommand = class {
135
139
  this.consumerEnvDotenvFilePredicate = consumerEnvDotenvFilePredicate;
136
140
  this.devTrackedProcessTreeKiller = devTrackedProcessTreeKiller;
137
141
  this.nextHostConsumerServerCommandFactory = nextHostConsumerServerCommandFactory;
142
+ this.devApiRuntimeFactory = devApiRuntimeFactory;
143
+ this.cliDevProxyServerFactory = cliDevProxyServerFactory;
144
+ this.devRebuildQueueFactory = devRebuildQueueFactory;
138
145
  }
139
- async execute(consumerRoot) {
140
- const paths = await this.pathResolver.resolve(consumerRoot);
146
+ async execute(args) {
147
+ const paths = await this.pathResolver.resolve(args.consumerRoot);
141
148
  this.devCliBannerRenderer.renderBrandHeader();
142
149
  this.tsRuntime.configure(paths.repoRoot);
143
150
  await this.databaseMigrationsApplyService.applyForConsumer(paths.consumerRoot);
144
151
  await this.devConsumerPublishBootstrap.ensurePublished(paths);
145
- const devMode = this.resolveDevModeFromEnv();
152
+ const devMode = this.resolveDevMode(args);
146
153
  const { nextPort, gatewayPort } = await this.session.sessionPorts.resolve({
147
154
  devMode,
148
155
  portEnv: process$1.env.PORT,
@@ -151,70 +158,56 @@ var DevCommand = class {
151
158
  const devLock = this.devLockFactory.create();
152
159
  await devLock.acquire({
153
160
  consumerRoot: paths.consumerRoot,
154
- nextPort: devMode === "framework" ? nextPort : gatewayPort
161
+ nextPort: devMode === "watch-framework" ? nextPort : gatewayPort
155
162
  });
156
163
  const authSettings = await this.session.devAuthLoader.loadForConsumer(paths.consumerRoot);
157
164
  const watcher = this.devSourceWatcherFactory.create();
158
165
  const processState = this.createInitialProcessState();
166
+ let proxyServer = null;
159
167
  try {
160
168
  const prepared = await this.prepareDevRuntime(paths, devMode, nextPort, gatewayPort, authSettings);
161
169
  const stopPromise = this.wireStopPromise(processState);
162
- const uiProxyBase = await this.startConsumerUiProxyWhenNeeded(prepared, processState);
170
+ const uiProxyBase = await this.startPackagedUiWhenNeeded(prepared, processState);
171
+ proxyServer = await this.startProxyServer(prepared.gatewayPort, uiProxyBase);
163
172
  const gatewayBaseUrl = this.gatewayBaseHttpUrl(gatewayPort);
164
- await this.spawnGatewayChildAndWaitForHealth(prepared, processState, gatewayBaseUrl, uiProxyBase);
173
+ await this.bootInitialRuntime(prepared, processState, proxyServer);
165
174
  await this.session.devHttpProbe.waitUntilBootstrapSummaryReady(gatewayBaseUrl);
166
175
  const initialSummary = await this.devBootstrapSummaryFetcher.fetch(gatewayBaseUrl);
167
176
  if (initialSummary) this.devCliBannerRenderer.renderRuntimeSummary(initialSummary);
168
- this.bindShutdownSignalsToChildProcesses(processState);
169
- await this.spawnFrameworkNextHostWhenNeeded(prepared, processState, gatewayBaseUrl);
170
- await this.startWatcherForSourceRestart(prepared, processState, watcher, devMode, gatewayBaseUrl);
171
- this.logConsumerDevHintWhenNeeded(devMode, gatewayPort);
177
+ this.bindShutdownSignalsToChildProcesses(processState, proxyServer);
178
+ await this.spawnDevUiWhenNeeded(prepared, processState, gatewayBaseUrl);
179
+ await this.startWatcherForSourceRestart(prepared, processState, watcher, devMode, gatewayBaseUrl, proxyServer);
180
+ this.logPackagedUiDevHintWhenNeeded(devMode, gatewayPort);
172
181
  await stopPromise;
173
182
  } finally {
174
183
  processState.stopRequested = true;
175
- await this.stopLiveChildProcesses(processState);
184
+ await this.stopLiveProcesses(processState, proxyServer);
176
185
  await watcher.stop();
177
186
  await devLock.release();
178
187
  }
179
188
  }
180
- resolveDevModeFromEnv() {
181
- return process$1.env.CODEMATION_DEV_MODE === "framework" ? "framework" : "consumer";
189
+ resolveDevMode(args) {
190
+ if (args.watchFramework === true || process$1.env.CODEMATION_DEV_MODE === "framework") return "watch-framework";
191
+ return "packaged-ui";
182
192
  }
183
193
  async prepareDevRuntime(paths, devMode, nextPort, gatewayPort, authSettings) {
184
- const developmentServerToken = this.session.devAuthLoader.resolveDevelopmentServerToken(process$1.env.CODEMATION_DEV_SERVER_TOKEN);
185
- const gatewayEntrypoint = await this.session.runtimeEntrypointResolver.resolve({
186
- packageName: "@codemation/dev-gateway",
187
- repoRoot: paths.repoRoot,
188
- sourceEntrypoint: "packages/dev-gateway/src/bin.ts"
189
- });
190
- const runtimeEntrypoint = await this.session.runtimeEntrypointResolver.resolve({
191
- packageName: "@codemation/runtime-dev",
192
- repoRoot: paths.repoRoot,
193
- sourceEntrypoint: "packages/runtime-dev/src/bin.ts"
194
- });
195
- const runtimeWorkingDirectory = paths.repoRoot ?? paths.consumerRoot;
196
- const consumerEnv = this.session.consumerEnvLoader.load(paths.consumerRoot);
197
194
  return {
198
195
  paths,
199
196
  devMode,
200
197
  nextPort,
201
198
  gatewayPort,
202
199
  authSettings,
203
- developmentServerToken,
204
- gatewayEntrypoint,
205
- runtimeEntrypoint,
206
- runtimeWorkingDirectory,
207
- discoveredPluginPackagesJson: JSON.stringify(await this.pluginDiscovery.discover(paths.consumerRoot)),
208
- consumerEnv
200
+ developmentServerToken: this.session.devAuthLoader.resolveDevelopmentServerToken(process$1.env.CODEMATION_DEV_SERVER_TOKEN),
201
+ consumerEnv: this.session.consumerEnvLoader.load(paths.consumerRoot)
209
202
  };
210
203
  }
211
204
  createInitialProcessState() {
212
205
  return {
213
- currentGateway: null,
214
- currentNextHost: null,
215
- currentUiNext: null,
216
- currentUiProxyBaseUrl: null,
217
- isRestartingNextHost: false,
206
+ currentDevUi: null,
207
+ currentPackagedUi: null,
208
+ currentPackagedUiBaseUrl: null,
209
+ currentRuntime: null,
210
+ isRestartingUi: false,
218
211
  stopRequested: false,
219
212
  stopResolve: null,
220
213
  stopReject: null
@@ -229,19 +222,15 @@ var DevCommand = class {
229
222
  gatewayBaseHttpUrl(gatewayPort) {
230
223
  return `http://127.0.0.1:${gatewayPort}`;
231
224
  }
232
- /**
233
- * Consumer mode: run `next start` for the host UI and wait until it responds, so the gateway can proxy to it.
234
- * Framework mode: no separate UI child (Next runs in dev later).
235
- */
236
- async startConsumerUiProxyWhenNeeded(prepared, state) {
237
- if (prepared.devMode !== "consumer") return "";
225
+ async startPackagedUiWhenNeeded(prepared, state) {
226
+ if (prepared.devMode !== "packaged-ui") return "";
238
227
  const websocketPort = prepared.gatewayPort;
239
- const uiProxyBase = state.currentUiProxyBaseUrl ?? `http://127.0.0.1:${await this.session.loopbackPortAllocator.allocate()}`;
240
- state.currentUiProxyBaseUrl = uiProxyBase;
241
- await this.spawnConsumerUiProxy(prepared, state, prepared.authSettings, websocketPort, uiProxyBase);
228
+ const uiProxyBase = state.currentPackagedUiBaseUrl ?? `http://127.0.0.1:${await this.session.loopbackPortAllocator.allocate()}`;
229
+ state.currentPackagedUiBaseUrl = uiProxyBase;
230
+ await this.spawnPackagedUi(prepared, state, prepared.authSettings, websocketPort, uiProxyBase);
242
231
  return uiProxyBase;
243
232
  }
244
- async spawnConsumerUiProxy(prepared, state, authSettings, websocketPort, uiProxyBase) {
233
+ async spawnPackagedUi(prepared, state, authSettings, websocketPort, uiProxyBase) {
245
234
  const nextHostPackageJsonPath = this.require.resolve("@codemation/next-host/package.json");
246
235
  const nextHostRoot = path.dirname(nextHostPackageJsonPath);
247
236
  const nextHostCommand = await this.nextHostConsumerServerCommandFactory.create({ nextHostRoot });
@@ -259,60 +248,38 @@ var DevCommand = class {
259
248
  skipUiAuth: authSettings.skipUiAuth,
260
249
  websocketPort
261
250
  });
262
- state.currentUiNext = spawn(nextHostCommand.command, nextHostCommand.args, {
251
+ state.currentPackagedUi = spawn(nextHostCommand.command, nextHostCommand.args, {
263
252
  cwd: nextHostCommand.cwd,
264
253
  ...this.devDetachedChildSpawnOptions(),
265
254
  env: nextHostEnvironment
266
255
  });
267
- state.currentUiNext.on("error", (error) => {
268
- if (state.stopRequested || state.isRestartingNextHost) return;
256
+ state.currentPackagedUi.on("error", (error) => {
257
+ if (state.stopRequested || state.isRestartingUi) return;
269
258
  state.stopRequested = true;
270
259
  state.stopReject?.(error instanceof Error ? error : new Error(String(error)));
271
260
  });
272
- state.currentUiNext.on("exit", (code) => {
273
- if (state.stopRequested || state.isRestartingNextHost) return;
261
+ state.currentPackagedUi.on("exit", (code) => {
262
+ if (state.stopRequested || state.isRestartingUi) return;
274
263
  state.stopRequested = true;
275
- if (state.currentGateway?.exitCode === null) this.devTrackedProcessTreeKiller.killProcessTreeRootedAt(state.currentGateway);
276
- state.stopReject?.(/* @__PURE__ */ new Error(`next start (consumer UI) exited unexpectedly with code ${code ?? 0}.`));
264
+ state.stopReject?.(/* @__PURE__ */ new Error(`next start (packaged UI) exited unexpectedly with code ${code ?? 0}.`));
277
265
  });
278
266
  await this.session.devHttpProbe.waitUntilUrlRespondsOk(`${uiProxyBase}/`);
279
267
  }
280
- async spawnGatewayChildAndWaitForHealth(prepared, state, gatewayBaseUrl, uiProxyBase) {
281
- const gatewayProcessEnv = this.session.consumerEnvLoader.mergeIntoProcessEnvironment(process$1.env, prepared.consumerEnv);
282
- state.currentGateway = spawn(prepared.gatewayEntrypoint.command, prepared.gatewayEntrypoint.args, {
283
- cwd: prepared.runtimeWorkingDirectory,
284
- ...this.devDetachedChildSpawnOptions(),
285
- env: {
286
- ...gatewayProcessEnv,
287
- ...prepared.gatewayEntrypoint.env,
288
- CODEMATION_DEV_GATEWAY_HTTP_PORT: String(prepared.gatewayPort),
289
- CODEMATION_RUNTIME_CHILD_BIN: prepared.runtimeEntrypoint.command,
290
- CODEMATION_RUNTIME_CHILD_ARGS_JSON: JSON.stringify(prepared.runtimeEntrypoint.args),
291
- CODEMATION_RUNTIME_CHILD_ENV_JSON: JSON.stringify(prepared.runtimeEntrypoint.env),
292
- CODEMATION_RUNTIME_CHILD_CWD: prepared.runtimeWorkingDirectory,
293
- CODEMATION_CONSUMER_ROOT: prepared.paths.consumerRoot,
294
- CODEMATION_DISCOVERED_PLUGIN_PACKAGES_JSON: prepared.discoveredPluginPackagesJson,
295
- CODEMATION_PREFER_PLUGIN_SOURCE_ENTRY: "true",
296
- CODEMATION_DEV_SERVER_TOKEN: prepared.developmentServerToken,
297
- CODEMATION_SKIP_STARTUP_MIGRATIONS: "true",
298
- NODE_OPTIONS: this.session.sourceMapNodeOptions.appendToNodeOptions(process$1.env.NODE_OPTIONS),
299
- WS_NO_BUFFER_UTIL: "1",
300
- WS_NO_UTF_8_VALIDATE: "1",
301
- ...uiProxyBase.length > 0 ? { CODEMATION_DEV_UI_PROXY_TARGET: uiProxyBase } : {}
302
- }
303
- });
304
- state.currentGateway.on("error", (error) => {
305
- if (!state.stopRequested) {
306
- state.stopRequested = true;
307
- state.stopReject?.(error instanceof Error ? error : new Error(String(error)));
308
- }
309
- });
310
- state.currentGateway.on("exit", (code) => {
311
- if (state.stopRequested) return;
312
- state.stopRequested = true;
313
- state.stopReject?.(/* @__PURE__ */ new Error(`codemation dev-gateway exited unexpectedly with code ${code ?? 0}.`));
268
+ async startProxyServer(gatewayPort, uiProxyBase) {
269
+ const proxyServer = this.cliDevProxyServerFactory.create(gatewayPort);
270
+ proxyServer.setUiProxyTarget(uiProxyBase.length > 0 ? uiProxyBase : null);
271
+ await proxyServer.start();
272
+ await this.session.devHttpProbe.waitUntilGatewayHealthy(this.gatewayBaseHttpUrl(gatewayPort));
273
+ return proxyServer;
274
+ }
275
+ async bootInitialRuntime(prepared, state, proxyServer) {
276
+ const runtime = await this.createRuntime(prepared);
277
+ state.currentRuntime = runtime;
278
+ await proxyServer.activateRuntime({
279
+ httpPort: runtime.httpPort,
280
+ workflowWebSocketPort: runtime.workflowWebSocketPort
314
281
  });
315
- await this.session.devHttpProbe.waitUntilGatewayHealthy(gatewayBaseUrl);
282
+ proxyServer.setBuildStatus("idle");
316
283
  }
317
284
  devDetachedChildSpawnOptions() {
318
285
  return process$1.platform === "win32" ? {
@@ -324,20 +291,14 @@ var DevCommand = class {
324
291
  detached: true
325
292
  };
326
293
  }
327
- bindShutdownSignalsToChildProcesses(state) {
294
+ bindShutdownSignalsToChildProcesses(state, proxyServer) {
328
295
  let shutdownInProgress = false;
329
296
  const runShutdown = async () => {
330
297
  if (shutdownInProgress) return;
331
298
  shutdownInProgress = true;
332
299
  state.stopRequested = true;
333
300
  process$1.stdout.write("\n[codemation] Stopping..\n");
334
- const children = [];
335
- for (const child of [
336
- state.currentUiNext,
337
- state.currentNextHost,
338
- state.currentGateway
339
- ]) if (child && child.exitCode === null && child.signalCode === null) children.push(child);
340
- await Promise.all(children.map((child) => this.devTrackedProcessTreeKiller.killProcessTreeRootedAt(child)));
301
+ await this.stopLiveProcesses(state, proxyServer);
341
302
  process$1.stdout.write("[codemation] Stopped.\n");
342
303
  state.stopResolve?.();
343
304
  };
@@ -349,14 +310,11 @@ var DevCommand = class {
349
310
  runShutdown();
350
311
  });
351
312
  }
352
- /**
353
- * Framework mode: run `next dev` for the Next host with HMR, pointed at the dev gateway runtime URL.
354
- */
355
- async spawnFrameworkNextHostWhenNeeded(prepared, state, gatewayBaseUrl) {
356
- if (prepared.devMode !== "framework") return;
357
- await this.spawnFrameworkNextHost(prepared, state, gatewayBaseUrl, prepared.authSettings);
313
+ async spawnDevUiWhenNeeded(prepared, state, gatewayBaseUrl) {
314
+ if (prepared.devMode !== "watch-framework") return;
315
+ await this.spawnDevUi(prepared, state, gatewayBaseUrl, prepared.authSettings);
358
316
  }
359
- async spawnFrameworkNextHost(prepared, state, gatewayBaseUrl, authSettings) {
317
+ async spawnDevUi(prepared, state, gatewayBaseUrl, authSettings) {
360
318
  const websocketPort = prepared.gatewayPort;
361
319
  const nextHostPackageJsonPath = this.require.resolve("@codemation/next-host/package.json");
362
320
  const nextHostRoot = path.dirname(nextHostPackageJsonPath);
@@ -369,7 +327,7 @@ var DevCommand = class {
369
327
  websocketPort,
370
328
  runtimeDevUrl: gatewayBaseUrl
371
329
  });
372
- state.currentNextHost = spawn("pnpm", [
330
+ state.currentDevUi = spawn("pnpm", [
373
331
  "exec",
374
332
  "next",
375
333
  "dev"
@@ -378,26 +336,28 @@ var DevCommand = class {
378
336
  ...this.devDetachedChildSpawnOptions(),
379
337
  env: nextHostEnvironment
380
338
  });
381
- state.currentNextHost.on("exit", (code) => {
339
+ state.currentDevUi.on("exit", (code) => {
382
340
  const normalizedCode = code ?? 0;
383
- if (state.stopRequested || state.isRestartingNextHost) return;
341
+ if (state.stopRequested || state.isRestartingUi) return;
384
342
  if (normalizedCode === 0) {
385
343
  state.stopRequested = true;
386
- if (state.currentGateway?.exitCode === null) this.devTrackedProcessTreeKiller.killProcessTreeRootedAt(state.currentGateway);
387
344
  state.stopResolve?.();
388
345
  return;
389
346
  }
390
347
  state.stopRequested = true;
391
348
  state.stopReject?.(/* @__PURE__ */ new Error(`next host exited with code ${normalizedCode}.`));
392
349
  });
393
- state.currentNextHost.on("error", (error) => {
394
- if (state.stopRequested || state.isRestartingNextHost) return;
350
+ state.currentDevUi.on("error", (error) => {
351
+ if (state.stopRequested || state.isRestartingUi) return;
395
352
  state.stopRequested = true;
396
353
  state.stopReject?.(error instanceof Error ? error : new Error(String(error)));
397
354
  });
398
355
  await this.session.devHttpProbe.waitUntilUrlRespondsOk(`http://127.0.0.1:${prepared.nextPort}/`);
399
356
  }
400
- async startWatcherForSourceRestart(prepared, state, watcher, devMode, gatewayBaseUrl) {
357
+ async startWatcherForSourceRestart(prepared, state, watcher, devMode, gatewayBaseUrl, proxyServer) {
358
+ const rebuildQueue = this.devRebuildQueueFactory.create({ run: async (request) => {
359
+ await this.runQueuedRebuild(prepared, state, gatewayBaseUrl, proxyServer, request);
360
+ } });
401
361
  await watcher.start({
402
362
  roots: this.session.watchRootsResolver.resolve({
403
363
  consumerRoot: prepared.paths.consumerRoot,
@@ -406,7 +366,7 @@ var DevCommand = class {
406
366
  }),
407
367
  onChange: async ({ changedPaths }) => {
408
368
  if (changedPaths.length > 0 && changedPaths.every((p) => this.consumerEnvDotenvFilePredicate.matches(p))) {
409
- process$1.stdout.write("\n[codemation] Consumer environment file changed (e.g. .env). Restart the `codemation dev` process so the gateway and runtime pick up updated variables (host `process.env` does not hot-reload).\n");
369
+ process$1.stdout.write("\n[codemation] Consumer environment file changed (e.g. .env). Restart the `codemation dev` process so the runtime picks up updated variables (host `process.env` does not hot-reload).\n");
410
370
  return;
411
371
  }
412
372
  try {
@@ -414,69 +374,112 @@ var DevCommand = class {
414
374
  changedPaths,
415
375
  consumerRoot: prepared.paths.consumerRoot
416
376
  });
417
- const shouldRestartNextHost = this.session.sourceChangeClassifier.requiresNextHostRestart({
377
+ const shouldRestartUi = this.session.sourceChangeClassifier.requiresUiRestart({
418
378
  changedPaths,
419
379
  consumerRoot: prepared.paths.consumerRoot
420
380
  });
421
- process$1.stdout.write(shouldRestartNextHost ? "\n[codemation] Consumer config change detected — rebuilding consumer, restarting runtime, and restarting Next host…\n" : "\n[codemation] Source change detected — rebuilding consumer and restarting runtime…\n");
422
- if (shouldRepublishConsumerOutput) await this.devConsumerPublishBootstrap.ensurePublished(prepared.paths);
423
- await this.session.sourceRestartCoordinator.runHandshakeAfterSourceChange(gatewayBaseUrl, prepared.developmentServerToken);
424
- process$1.stdout.write("[codemation] Waiting for runtime to accept traffic…\n");
425
- await this.session.devHttpProbe.waitUntilBootstrapSummaryReady(gatewayBaseUrl);
426
- if (shouldRestartNextHost) await this.restartNextHostForConfigChange(prepared, state, gatewayBaseUrl);
427
- const json = await this.devBootstrapSummaryFetcher.fetch(gatewayBaseUrl);
428
- if (json) this.devCliBannerRenderer.renderCompact(json);
429
- process$1.stdout.write("[codemation] Runtime ready.\n");
381
+ process$1.stdout.write(shouldRestartUi ? "\n[codemation] Source change detected — rebuilding consumer, restarting the runtime, and restarting the UI…\n" : "\n[codemation] Source change detected — rebuilding consumer and restarting the runtime…\n");
382
+ await rebuildQueue.enqueue({
383
+ changedPaths,
384
+ shouldRepublishConsumerOutput,
385
+ shouldRestartUi
386
+ });
430
387
  } catch (error) {
431
- await this.failDevSessionAfterIrrecoverableSourceError(state, error);
388
+ await this.failDevSessionAfterIrrecoverableSourceError(state, proxyServer, error);
432
389
  }
433
390
  }
434
391
  });
435
392
  }
436
- async restartNextHostForConfigChange(prepared, state, gatewayBaseUrl) {
393
+ async runQueuedRebuild(prepared, state, gatewayBaseUrl, proxyServer, request) {
394
+ request.changedPaths;
395
+ proxyServer.setBuildStatus("building");
396
+ proxyServer.broadcastBuildStarted();
397
+ try {
398
+ if (request.shouldRepublishConsumerOutput) await this.devConsumerPublishBootstrap.ensurePublished(prepared.paths);
399
+ await this.stopCurrentRuntime(state, proxyServer);
400
+ process$1.stdout.write("[codemation] Waiting for runtime to accept traffic…\n");
401
+ const runtime = await this.createRuntime(prepared);
402
+ state.currentRuntime = runtime;
403
+ await proxyServer.activateRuntime({
404
+ httpPort: runtime.httpPort,
405
+ workflowWebSocketPort: runtime.workflowWebSocketPort
406
+ });
407
+ if (request.shouldRestartUi) await this.restartUiAfterSourceChange(prepared, state, gatewayBaseUrl);
408
+ await this.session.devHttpProbe.waitUntilBootstrapSummaryReady(gatewayBaseUrl);
409
+ const json = await this.devBootstrapSummaryFetcher.fetch(gatewayBaseUrl);
410
+ if (json) this.devCliBannerRenderer.renderCompact(json);
411
+ proxyServer.setBuildStatus("idle");
412
+ proxyServer.broadcastBuildCompleted(runtime.buildVersion);
413
+ process$1.stdout.write("[codemation] Runtime ready.\n");
414
+ } catch (error) {
415
+ proxyServer.setBuildStatus("idle");
416
+ proxyServer.broadcastBuildFailed(error instanceof Error ? error.message : String(error));
417
+ throw error;
418
+ }
419
+ }
420
+ async restartUiAfterSourceChange(prepared, state, gatewayBaseUrl) {
437
421
  const refreshedAuthSettings = await this.session.devAuthLoader.loadForConsumer(prepared.paths.consumerRoot);
438
- process$1.stdout.write("[codemation] Restarting Next host to apply auth/database/scheduler/branding changes…\n");
439
- state.isRestartingNextHost = true;
422
+ process$1.stdout.write("[codemation] Restarting the UI process to apply source changes…\n");
423
+ state.isRestartingUi = true;
440
424
  try {
441
- if (prepared.devMode === "consumer") {
442
- await this.restartConsumerUiProxy(prepared, state, refreshedAuthSettings);
425
+ if (prepared.devMode === "packaged-ui") {
426
+ await this.restartPackagedUi(prepared, state, refreshedAuthSettings);
443
427
  return;
444
428
  }
445
- await this.restartFrameworkNextHost(prepared, state, gatewayBaseUrl, refreshedAuthSettings);
429
+ await this.restartDevUi(prepared, state, gatewayBaseUrl, refreshedAuthSettings);
446
430
  } finally {
447
- state.isRestartingNextHost = false;
431
+ state.isRestartingUi = false;
448
432
  }
449
433
  }
450
- async restartConsumerUiProxy(prepared, state, authSettings) {
451
- if (state.currentUiNext && state.currentUiNext.exitCode === null && state.currentUiNext.signalCode === null) await this.devTrackedProcessTreeKiller.killProcessTreeRootedAt(state.currentUiNext);
452
- state.currentUiNext = null;
453
- const uiProxyBaseUrl = state.currentUiProxyBaseUrl;
454
- if (!uiProxyBaseUrl) throw new Error("Consumer UI proxy base URL is missing during Next host restart.");
455
- await this.spawnConsumerUiProxy(prepared, state, authSettings, prepared.gatewayPort, uiProxyBaseUrl);
434
+ async restartPackagedUi(prepared, state, authSettings) {
435
+ if (state.currentPackagedUi && state.currentPackagedUi.exitCode === null && state.currentPackagedUi.signalCode === null) await this.devTrackedProcessTreeKiller.killProcessTreeRootedAt(state.currentPackagedUi);
436
+ state.currentPackagedUi = null;
437
+ const uiProxyBaseUrl = state.currentPackagedUiBaseUrl;
438
+ if (!uiProxyBaseUrl) throw new Error("Packaged UI proxy base URL is missing during UI restart.");
439
+ await this.spawnPackagedUi(prepared, state, authSettings, prepared.gatewayPort, uiProxyBaseUrl);
456
440
  }
457
- async restartFrameworkNextHost(prepared, state, gatewayBaseUrl, authSettings) {
458
- if (state.currentNextHost && state.currentNextHost.exitCode === null && state.currentNextHost.signalCode === null) await this.devTrackedProcessTreeKiller.killProcessTreeRootedAt(state.currentNextHost);
459
- state.currentNextHost = null;
460
- await this.spawnFrameworkNextHost(prepared, state, gatewayBaseUrl, authSettings);
441
+ async restartDevUi(prepared, state, gatewayBaseUrl, authSettings) {
442
+ if (state.currentDevUi && state.currentDevUi.exitCode === null && state.currentDevUi.signalCode === null) await this.devTrackedProcessTreeKiller.killProcessTreeRootedAt(state.currentDevUi);
443
+ state.currentDevUi = null;
444
+ await this.spawnDevUi(prepared, state, gatewayBaseUrl, authSettings);
461
445
  }
462
- async failDevSessionAfterIrrecoverableSourceError(state, error) {
446
+ async failDevSessionAfterIrrecoverableSourceError(state, proxyServer, error) {
463
447
  const exception = error instanceof Error ? error : new Error(String(error));
464
448
  state.stopRequested = true;
465
- await this.stopLiveChildProcesses(state);
449
+ await this.stopLiveProcesses(state, proxyServer);
466
450
  state.stopReject?.(exception);
467
451
  }
468
- async stopLiveChildProcesses(state) {
452
+ async stopLiveProcesses(state, proxyServer) {
453
+ await this.stopCurrentRuntime(state, proxyServer);
469
454
  const children = [];
470
- for (const child of [
471
- state.currentUiNext,
472
- state.currentNextHost,
473
- state.currentGateway
474
- ]) if (child && child.exitCode === null && child.signalCode === null) children.push(child);
455
+ for (const child of [state.currentPackagedUi, state.currentDevUi]) if (child && child.exitCode === null && child.signalCode === null) children.push(child);
475
456
  await Promise.all(children.map((child) => this.devTrackedProcessTreeKiller.killProcessTreeRootedAt(child)));
457
+ if (proxyServer) await proxyServer.stop();
476
458
  }
477
- logConsumerDevHintWhenNeeded(devMode, gatewayPort) {
478
- if (devMode !== "consumer") return;
479
- this.cliLogger.info(`codemation dev (consumer): open http://127.0.0.1:${gatewayPort} — uses the packaged @codemation/next-host UI. For framework UI HMR, set CODEMATION_DEV_MODE=framework.`);
459
+ async stopCurrentRuntime(state, proxyServer) {
460
+ const runtime = state.currentRuntime;
461
+ state.currentRuntime = null;
462
+ if (proxyServer) await proxyServer.activateRuntime(null);
463
+ if (runtime) await runtime.stop();
464
+ }
465
+ async createRuntime(prepared) {
466
+ const runtimeEnvironment = this.session.consumerEnvLoader.mergeIntoProcessEnvironment(process$1.env, prepared.consumerEnv);
467
+ return await this.devApiRuntimeFactory.create({
468
+ consumerRoot: prepared.paths.consumerRoot,
469
+ runtimeWorkingDirectory: process$1.cwd(),
470
+ env: {
471
+ ...runtimeEnvironment,
472
+ CODEMATION_DEV_SERVER_TOKEN: prepared.developmentServerToken,
473
+ CODEMATION_SKIP_STARTUP_MIGRATIONS: "true",
474
+ NODE_OPTIONS: this.session.sourceMapNodeOptions.appendToNodeOptions(process$1.env.NODE_OPTIONS),
475
+ WS_NO_BUFFER_UTIL: "1",
476
+ WS_NO_UTF_8_VALIDATE: "1"
477
+ }
478
+ });
479
+ }
480
+ logPackagedUiDevHintWhenNeeded(devMode, gatewayPort) {
481
+ if (devMode !== "packaged-ui") return;
482
+ this.cliLogger.info(`codemation dev: open http://127.0.0.1:${gatewayPort} — this uses the packaged @codemation/next-host UI. Use \`codemation dev --watch-framework\` only when working on the framework UI itself.`);
480
483
  }
481
484
  };
482
485
 
@@ -484,8 +487,9 @@ var DevCommand = class {
484
487
  //#region src/commands/ServeWebCommand.ts
485
488
  var ServeWebCommand = class {
486
489
  require = createRequire(import.meta.url);
487
- constructor(pathResolver, pluginDiscovery, artifactsPublisher, tsRuntime, sourceMapNodeOptions, outputBuilderLoader, envLoader, listenPortResolver, nextHostConsumerServerCommandFactory) {
490
+ constructor(pathResolver, configLoader, pluginDiscovery, artifactsPublisher, tsRuntime, sourceMapNodeOptions, outputBuilderLoader, envLoader, listenPortResolver, nextHostConsumerServerCommandFactory, frontendAuthSnapshotFactory, frontendAppConfigJsonCodec) {
488
491
  this.pathResolver = pathResolver;
492
+ this.configLoader = configLoader;
489
493
  this.pluginDiscovery = pluginDiscovery;
490
494
  this.artifactsPublisher = artifactsPublisher;
491
495
  this.tsRuntime = tsRuntime;
@@ -494,6 +498,8 @@ var ServeWebCommand = class {
494
498
  this.envLoader = envLoader;
495
499
  this.listenPortResolver = listenPortResolver;
496
500
  this.nextHostConsumerServerCommandFactory = nextHostConsumerServerCommandFactory;
501
+ this.frontendAuthSnapshotFactory = frontendAuthSnapshotFactory;
502
+ this.frontendAppConfigJsonCodec = frontendAppConfigJsonCodec;
497
503
  }
498
504
  async execute(consumerRoot, buildOptions) {
499
505
  const paths = await this.pathResolver.resolve(consumerRoot);
@@ -504,6 +510,15 @@ var ServeWebCommand = class {
504
510
  const nextHostRoot = path.dirname(this.require.resolve("@codemation/next-host/package.json"));
505
511
  const nextHostCommand = await this.nextHostConsumerServerCommandFactory.create({ nextHostRoot });
506
512
  const consumerEnv = this.envLoader.load(paths.consumerRoot);
513
+ const configResolution = await this.configLoader.load({ consumerRoot: paths.consumerRoot });
514
+ const frontendAuthSnapshot = this.frontendAuthSnapshotFactory.createFromResolvedInputs({
515
+ authConfig: configResolution.config.auth,
516
+ env: {
517
+ ...process$1.env,
518
+ ...consumerEnv
519
+ },
520
+ uiAuthEnabled: !(consumerEnv.NODE_ENV !== "production" && configResolution.config.auth?.allowUnauthenticatedInDevelopment === true)
521
+ });
507
522
  const nextPort = this.listenPortResolver.resolvePrimaryApplicationPort(process$1.env.PORT);
508
523
  const websocketPort = this.listenPortResolver.resolveWebsocketPortRelativeToHttp({
509
524
  nextPort,
@@ -517,6 +532,11 @@ var ServeWebCommand = class {
517
532
  ...process$1.env,
518
533
  ...consumerEnv,
519
534
  PORT: String(nextPort),
535
+ CODEMATION_FRONTEND_APP_CONFIG_JSON: this.frontendAppConfigJsonCodec.serialize({
536
+ auth: frontendAuthSnapshot,
537
+ productName: "Codemation",
538
+ logoUrl: null
539
+ }),
520
540
  CODEMATION_CONSUMER_OUTPUT_MANIFEST_PATH: manifest.manifestPath,
521
541
  CODEMATION_CONSUMER_ROOT: paths.consumerRoot,
522
542
  CODEMATION_WS_PORT: String(websocketPort),
@@ -542,34 +562,46 @@ var ServeWebCommand = class {
542
562
  //#endregion
543
563
  //#region src/commands/ServeWorkerCommand.ts
544
564
  var ServeWorkerCommand = class {
545
- require = createRequire(import.meta.url);
546
- constructor(sourceMapNodeOptions) {
547
- this.sourceMapNodeOptions = sourceMapNodeOptions;
565
+ constructor(pathResolver, appConfigLoader, appContainerFactory) {
566
+ this.pathResolver = pathResolver;
567
+ this.appConfigLoader = appConfigLoader;
568
+ this.appContainerFactory = appContainerFactory;
548
569
  }
549
570
  async execute(consumerRoot, configPathOverride) {
550
- const workerPackageRoot = path.dirname(this.require.resolve("@codemation/worker-cli/package.json"));
551
- const args = [path.join(workerPackageRoot, "bin", "codemation-worker.js")];
552
- if (configPathOverride !== void 0 && configPathOverride.trim().length > 0) args.push("--config", path.resolve(process$1.cwd(), configPathOverride.trim()));
553
- args.push("--consumer-root", consumerRoot);
554
- const child = spawn(process$1.execPath, args, {
555
- cwd: consumerRoot,
556
- stdio: "inherit",
557
- env: {
558
- ...process$1.env,
559
- NODE_OPTIONS: this.sourceMapNodeOptions.appendToNodeOptions(process$1.env.NODE_OPTIONS)
560
- }
571
+ const paths = await this.pathResolver.resolve(consumerRoot);
572
+ const loadResult = await this.appConfigLoader.load({
573
+ consumerRoot,
574
+ repoRoot: paths.repoRoot,
575
+ env: process$1.env,
576
+ configPathOverride
561
577
  });
562
- await new Promise((resolve, reject) => {
563
- child.on("exit", (code) => {
564
- if ((code ?? 0) === 0) {
565
- resolve();
566
- return;
567
- }
568
- reject(/* @__PURE__ */ new Error(`codemation-worker exited with code ${code ?? 0}.`));
569
- });
570
- child.on("error", reject);
578
+ if (loadResult.appConfig.scheduler.kind !== "bullmq") throw new Error("Worker mode requires runtime.scheduler.kind = \"bullmq\".");
579
+ const container = await this.appContainerFactory.create({
580
+ appConfig: loadResult.appConfig,
581
+ sharedWorkflowWebsocketServer: null
582
+ });
583
+ const workerQueues = loadResult.appConfig.scheduler.workerQueues.length > 0 ? loadResult.appConfig.scheduler.workerQueues : ["default"];
584
+ const handle = await container.resolve(WorkerRuntime).start(workerQueues);
585
+ await new Promise((resolve) => {
586
+ this.bindSignals(handle.stop, resolve);
571
587
  });
572
588
  }
589
+ bindSignals(stop, resolve) {
590
+ let stopping = false;
591
+ const onSignal = async () => {
592
+ if (stopping) return;
593
+ stopping = true;
594
+ try {
595
+ await stop();
596
+ } finally {
597
+ resolve();
598
+ process$1.exit(0);
599
+ }
600
+ };
601
+ process$1.on("SIGINT", () => void onSignal());
602
+ process$1.on("SIGTERM", () => void onSignal());
603
+ process$1.on("SIGQUIT", () => void onSignal());
604
+ }
573
605
  };
574
606
 
575
607
  //#endregion
@@ -1270,13 +1302,36 @@ var ConsumerOutputBuilderLoader = class {
1270
1302
  * Resolves TCP PostgreSQL vs PGlite vs none from env + {@link CodemationConfig} (same rules as the host runtime).
1271
1303
  */
1272
1304
  var ConsumerDatabaseConnectionResolver = class {
1273
- resolver = new DatabasePersistenceResolver();
1274
1305
  resolve(processEnv, config$1, consumerRoot) {
1275
- return this.resolver.resolve({
1276
- runtimeConfig: config$1.runtime ?? {},
1277
- env: processEnv,
1278
- consumerRoot
1279
- });
1306
+ const database = config$1.runtime?.database;
1307
+ if (!database) return { kind: "none" };
1308
+ if (this.resolveDatabaseKind(database.kind, database.url, processEnv) === "postgresql") {
1309
+ const databaseUrl = database.url?.trim() ?? "";
1310
+ if (!databaseUrl) throw new Error("runtime.database.kind is \"postgresql\" but no database URL was set (runtime.database.url).");
1311
+ return {
1312
+ kind: "postgresql",
1313
+ databaseUrl
1314
+ };
1315
+ }
1316
+ return {
1317
+ kind: "pglite",
1318
+ dataDir: this.resolvePgliteDataDir(database.pgliteDataDir, processEnv, consumerRoot)
1319
+ };
1320
+ }
1321
+ resolveDatabaseKind(configuredKind, databaseUrl, env) {
1322
+ const kindFromEnv = env.CODEMATION_DATABASE_KIND?.trim();
1323
+ if (kindFromEnv === "postgresql" || kindFromEnv === "pglite") return kindFromEnv;
1324
+ if (configuredKind) return configuredKind;
1325
+ const trimmedUrl = databaseUrl?.trim();
1326
+ if (trimmedUrl && (trimmedUrl.startsWith("postgresql://") || trimmedUrl.startsWith("postgres://"))) return "postgresql";
1327
+ return "pglite";
1328
+ }
1329
+ resolvePgliteDataDir(configuredPath, env, consumerRoot) {
1330
+ const envPath = env.CODEMATION_PGLITE_DATA_DIR?.trim();
1331
+ if (envPath && envPath.length > 0) return path.isAbsolute(envPath) ? envPath : path.resolve(consumerRoot, envPath);
1332
+ const trimmedConfiguredPath = configuredPath?.trim();
1333
+ if (trimmedConfiguredPath && trimmedConfiguredPath.length > 0) return path.isAbsolute(trimmedConfiguredPath) ? trimmedConfiguredPath : path.resolve(consumerRoot, trimmedConfiguredPath);
1334
+ return path.resolve(consumerRoot, ".codemation", "pglite");
1280
1335
  }
1281
1336
  };
1282
1337
 
@@ -1352,7 +1407,7 @@ var HostPackageRootResolver = class {
1352
1407
  //#endregion
1353
1408
  //#region src/dev/DevBootstrapSummaryFetcher.ts
1354
1409
  /**
1355
- * Fetches {@link DevBootstrapSummaryJson} from the dev gateway (proxied to runtime-dev).
1410
+ * Fetches {@link DevBootstrapSummaryJson} from the stable CLI-owned dev endpoint.
1356
1411
  */
1357
1412
  var DevBootstrapSummaryFetcher = class {
1358
1413
  async fetch(gatewayBaseUrl) {
@@ -1502,6 +1557,776 @@ var DevConsumerPublishBootstrap = class {
1502
1557
  }
1503
1558
  };
1504
1559
 
1560
+ //#endregion
1561
+ //#region src/dev/CliDevProxyServer.ts
1562
+ var CliDevProxyServer = class {
1563
+ proxy = httpProxy.createProxyServer({
1564
+ ws: true,
1565
+ xfwd: true
1566
+ });
1567
+ devClients = /* @__PURE__ */ new Set();
1568
+ devWss = new WebSocketServer({ noServer: true });
1569
+ workflowClients = /* @__PURE__ */ new Set();
1570
+ workflowWss = new WebSocketServer({ noServer: true });
1571
+ roomIdsByWorkflowClient = /* @__PURE__ */ new Map();
1572
+ workflowClientCountByRoomId = /* @__PURE__ */ new Map();
1573
+ activeRuntime = null;
1574
+ activeBuildStatus = "idle";
1575
+ childWorkflowSocket = null;
1576
+ server = null;
1577
+ uiProxyTarget = null;
1578
+ constructor(listenPort, listenPortConflictDescriber) {
1579
+ this.listenPort = listenPort;
1580
+ this.listenPortConflictDescriber = listenPortConflictDescriber;
1581
+ }
1582
+ async start() {
1583
+ if (this.server) return;
1584
+ this.bindDevWebSocket();
1585
+ this.bindWorkflowWebSocket();
1586
+ this.proxy.on("error", (error, _req, res) => {
1587
+ if (res && "writeHead" in res && typeof res.writeHead === "function") {
1588
+ const serverResponse = res;
1589
+ if (!serverResponse.headersSent) {
1590
+ serverResponse.writeHead(502, { "content-type": "text/plain" });
1591
+ serverResponse.end(`Bad gateway: ${error.message}`);
1592
+ }
1593
+ }
1594
+ });
1595
+ const server = createServer((req, res) => {
1596
+ this.handleHttpRequest(req, res);
1597
+ });
1598
+ server.on("upgrade", (request, socket, head) => {
1599
+ this.handleUpgrade(request, socket, head);
1600
+ });
1601
+ await new Promise((resolve, reject) => {
1602
+ server.once("error", (error) => {
1603
+ this.rejectListenError(error, reject);
1604
+ });
1605
+ server.listen(this.listenPort, "127.0.0.1", () => {
1606
+ resolve();
1607
+ });
1608
+ });
1609
+ this.server = server;
1610
+ }
1611
+ async stop() {
1612
+ await this.disconnectChildWorkflowSocket();
1613
+ this.activeRuntime = null;
1614
+ const server = this.server;
1615
+ this.server = null;
1616
+ for (const client of this.devClients) client.terminate();
1617
+ this.devClients.clear();
1618
+ for (const client of this.workflowClients) client.terminate();
1619
+ this.workflowClients.clear();
1620
+ this.roomIdsByWorkflowClient.clear();
1621
+ this.workflowClientCountByRoomId.clear();
1622
+ if (!server) return;
1623
+ await new Promise((resolve, reject) => {
1624
+ server.close((error) => {
1625
+ if (error) {
1626
+ reject(error);
1627
+ return;
1628
+ }
1629
+ resolve();
1630
+ });
1631
+ });
1632
+ }
1633
+ setUiProxyTarget(target) {
1634
+ this.uiProxyTarget = target?.trim() ? target.trim() : null;
1635
+ }
1636
+ async activateRuntime(target) {
1637
+ this.activeRuntime = target;
1638
+ await this.connectChildWorkflowSocket();
1639
+ }
1640
+ setBuildStatus(status) {
1641
+ this.activeBuildStatus = status;
1642
+ }
1643
+ broadcastBuildStarted() {
1644
+ this.broadcastDev({ kind: "devBuildStarted" });
1645
+ this.broadcastWorkflowLifecycleToSubscribedRooms((roomId) => ({
1646
+ kind: "devBuildStarted",
1647
+ workflowId: roomId
1648
+ }));
1649
+ }
1650
+ broadcastBuildCompleted(buildVersion) {
1651
+ this.broadcastDev({
1652
+ kind: "devBuildCompleted",
1653
+ buildVersion
1654
+ });
1655
+ this.broadcastWorkflowLifecycleToSubscribedRooms((roomId) => ({
1656
+ kind: "devBuildCompleted",
1657
+ workflowId: roomId,
1658
+ buildVersion
1659
+ }));
1660
+ }
1661
+ broadcastBuildFailed(message) {
1662
+ this.broadcastDev({
1663
+ kind: "devBuildFailed",
1664
+ message
1665
+ });
1666
+ this.broadcastWorkflowLifecycleToSubscribedRooms((roomId) => ({
1667
+ kind: "devBuildFailed",
1668
+ workflowId: roomId,
1669
+ message
1670
+ }));
1671
+ }
1672
+ bindDevWebSocket() {
1673
+ this.devWss.on("connection", (socket) => {
1674
+ this.devClients.add(socket);
1675
+ socket.on("close", () => {
1676
+ this.devClients.delete(socket);
1677
+ });
1678
+ });
1679
+ }
1680
+ bindWorkflowWebSocket() {
1681
+ this.workflowWss.on("connection", (socket) => {
1682
+ this.connectWorkflowClient(socket);
1683
+ });
1684
+ }
1685
+ async handleHttpRequest(req, res) {
1686
+ const pathname = this.safePathname(req.url ?? "");
1687
+ const uiProxyTarget = this.uiProxyTarget;
1688
+ if (pathname === "/api/dev/health" && req.method === "GET") {
1689
+ res.writeHead(200, { "content-type": "application/json" });
1690
+ res.end(JSON.stringify({
1691
+ ok: true,
1692
+ runtime: { status: this.activeRuntime ? this.activeBuildStatus === "building" ? "building" : "ready" : "stopped" }
1693
+ }));
1694
+ return;
1695
+ }
1696
+ if (uiProxyTarget && pathname.startsWith("/api/auth/")) {
1697
+ this.proxy.web(req, res, { target: uiProxyTarget.replace(/\/$/, "") });
1698
+ return;
1699
+ }
1700
+ if (pathname.startsWith("/api/")) {
1701
+ if (this.activeBuildStatus === "building" || !this.activeRuntime) {
1702
+ res.writeHead(503, { "content-type": "text/plain" });
1703
+ res.end("Runtime is rebuilding.");
1704
+ return;
1705
+ }
1706
+ this.proxy.web(req, res, { target: `http://127.0.0.1:${this.activeRuntime.httpPort}` });
1707
+ return;
1708
+ }
1709
+ if (uiProxyTarget) {
1710
+ this.proxy.web(req, res, { target: uiProxyTarget.replace(/\/$/, "") });
1711
+ return;
1712
+ }
1713
+ res.writeHead(404, { "content-type": "text/plain" });
1714
+ res.end("Not found.");
1715
+ }
1716
+ handleUpgrade(request, socket, head) {
1717
+ const pathname = this.safePathname(request.url ?? "");
1718
+ if (pathname === ApiPaths.devGatewaySocket()) {
1719
+ this.devWss.handleUpgrade(request, socket, head, (ws) => {
1720
+ this.devWss.emit("connection", ws, request);
1721
+ });
1722
+ return;
1723
+ }
1724
+ if (pathname === ApiPaths.workflowWebsocket()) {
1725
+ this.workflowWss.handleUpgrade(request, socket, head, (ws) => {
1726
+ this.workflowWss.emit("connection", ws, request);
1727
+ });
1728
+ return;
1729
+ }
1730
+ const uiProxyTarget = this.uiProxyTarget;
1731
+ if (uiProxyTarget && !pathname.startsWith("/api/")) {
1732
+ this.proxy.ws(request, socket, head, { target: uiProxyTarget.replace(/\/$/, "") });
1733
+ return;
1734
+ }
1735
+ socket.destroy();
1736
+ }
1737
+ safePathname(url) {
1738
+ try {
1739
+ return new URL(url, "http://127.0.0.1").pathname;
1740
+ } catch {
1741
+ return url.split("?")[0] ?? url;
1742
+ }
1743
+ }
1744
+ async rejectListenError(error, reject) {
1745
+ if (error.code !== "EADDRINUSE") {
1746
+ reject(error);
1747
+ return;
1748
+ }
1749
+ const description = await this.listenPortConflictDescriber.describeLoopbackPort(this.listenPort);
1750
+ const baseMessage = `Dev gateway port ${this.listenPort} is already in use on 127.0.0.1.`;
1751
+ const suffix = description === null ? " Stop the process using that port or change the configured Codemation dev port." : ` Listener: ${description}. Stop that process or change the configured Codemation dev port.`;
1752
+ reject(new Error(`${baseMessage}${suffix}`, { cause: error instanceof Error ? error : void 0 }));
1753
+ }
1754
+ broadcastDev(message) {
1755
+ const text = JSON.stringify(message);
1756
+ for (const client of this.devClients) if (client.readyState === WebSocket.OPEN) client.send(text);
1757
+ }
1758
+ broadcastWorkflowLifecycleToSubscribedRooms(createMessage) {
1759
+ for (const roomId of this.workflowClientCountByRoomId.keys()) this.broadcastWorkflowTextToRoom(roomId, JSON.stringify(createMessage(roomId)));
1760
+ }
1761
+ async connectWorkflowClient(socket) {
1762
+ this.workflowClients.add(socket);
1763
+ this.roomIdsByWorkflowClient.set(socket, /* @__PURE__ */ new Set());
1764
+ socket.send(JSON.stringify({ kind: "ready" }));
1765
+ socket.on("message", (rawData) => {
1766
+ this.handleWorkflowClientMessage(socket, rawData);
1767
+ });
1768
+ socket.on("close", () => {
1769
+ this.disconnectWorkflowClient(socket);
1770
+ });
1771
+ socket.on("error", () => {
1772
+ this.disconnectWorkflowClient(socket);
1773
+ });
1774
+ }
1775
+ disconnectWorkflowClient(socket) {
1776
+ const roomIds = this.roomIdsByWorkflowClient.get(socket);
1777
+ if (roomIds) for (const roomId of roomIds) this.releaseWorkflowRoom(roomId);
1778
+ this.roomIdsByWorkflowClient.delete(socket);
1779
+ this.workflowClients.delete(socket);
1780
+ }
1781
+ async handleWorkflowClientMessage(socket, rawData) {
1782
+ try {
1783
+ const message = this.parseWorkflowClientMessage(rawData);
1784
+ if (message.kind === "subscribe") {
1785
+ const roomIds$1 = this.roomIdsByWorkflowClient.get(socket);
1786
+ if (!roomIds$1) return;
1787
+ if (!roomIds$1.has(message.roomId)) {
1788
+ roomIds$1.add(message.roomId);
1789
+ this.retainWorkflowRoom(message.roomId);
1790
+ }
1791
+ socket.send(JSON.stringify({
1792
+ kind: "subscribed",
1793
+ roomId: message.roomId
1794
+ }));
1795
+ return;
1796
+ }
1797
+ const roomIds = this.roomIdsByWorkflowClient.get(socket);
1798
+ if (!roomIds) return;
1799
+ if (roomIds.delete(message.roomId)) this.releaseWorkflowRoom(message.roomId);
1800
+ socket.send(JSON.stringify({
1801
+ kind: "unsubscribed",
1802
+ roomId: message.roomId
1803
+ }));
1804
+ } catch (error) {
1805
+ const exception = error instanceof Error ? error : new Error(String(error));
1806
+ if (socket.readyState === WebSocket.OPEN) socket.send(JSON.stringify({
1807
+ kind: "error",
1808
+ message: exception.message
1809
+ }));
1810
+ }
1811
+ }
1812
+ parseWorkflowClientMessage(rawData) {
1813
+ const value = typeof rawData === "string" ? rawData : Buffer.isBuffer(rawData) ? rawData.toString("utf8") : "";
1814
+ const message = JSON.parse(value);
1815
+ if (message.kind === "subscribe" && typeof message.roomId === "string") return {
1816
+ kind: "subscribe",
1817
+ roomId: message.roomId
1818
+ };
1819
+ if (message.kind === "unsubscribe" && typeof message.roomId === "string") return {
1820
+ kind: "unsubscribe",
1821
+ roomId: message.roomId
1822
+ };
1823
+ throw new Error("Unsupported websocket client message.");
1824
+ }
1825
+ retainWorkflowRoom(roomId) {
1826
+ const nextCount = (this.workflowClientCountByRoomId.get(roomId) ?? 0) + 1;
1827
+ this.workflowClientCountByRoomId.set(roomId, nextCount);
1828
+ if (nextCount === 1) this.sendToChildWorkflowSocket({
1829
+ kind: "subscribe",
1830
+ roomId
1831
+ });
1832
+ }
1833
+ releaseWorkflowRoom(roomId) {
1834
+ const currentCount = this.workflowClientCountByRoomId.get(roomId) ?? 0;
1835
+ if (currentCount <= 1) {
1836
+ this.workflowClientCountByRoomId.delete(roomId);
1837
+ this.sendToChildWorkflowSocket({
1838
+ kind: "unsubscribe",
1839
+ roomId
1840
+ });
1841
+ return;
1842
+ }
1843
+ this.workflowClientCountByRoomId.set(roomId, currentCount - 1);
1844
+ }
1845
+ sendToChildWorkflowSocket(message) {
1846
+ if (!this.childWorkflowSocket || this.childWorkflowSocket.readyState !== WebSocket.OPEN) return;
1847
+ this.childWorkflowSocket.send(JSON.stringify(message));
1848
+ }
1849
+ async connectChildWorkflowSocket() {
1850
+ await this.disconnectChildWorkflowSocket();
1851
+ if (!this.activeRuntime || this.activeBuildStatus === "building") return;
1852
+ const childWorkflowSocket = await this.openChildWorkflowSocket(this.activeRuntime.workflowWebSocketPort);
1853
+ this.childWorkflowSocket = childWorkflowSocket;
1854
+ childWorkflowSocket.on("message", (rawData) => {
1855
+ this.handleChildWorkflowSocketMessage(rawData);
1856
+ });
1857
+ childWorkflowSocket.on("close", () => {
1858
+ if (this.childWorkflowSocket === childWorkflowSocket) this.childWorkflowSocket = null;
1859
+ });
1860
+ childWorkflowSocket.on("error", () => {
1861
+ if (this.childWorkflowSocket === childWorkflowSocket) this.childWorkflowSocket = null;
1862
+ });
1863
+ for (const roomId of this.workflowClientCountByRoomId.keys()) this.sendToChildWorkflowSocket({
1864
+ kind: "subscribe",
1865
+ roomId
1866
+ });
1867
+ }
1868
+ openChildWorkflowSocket(workflowWebSocketPort) {
1869
+ return new Promise((resolve, reject) => {
1870
+ const socket = new WebSocket(`ws://127.0.0.1:${workflowWebSocketPort}${ApiPaths.workflowWebsocket()}`);
1871
+ socket.once("open", () => {
1872
+ resolve(socket);
1873
+ });
1874
+ socket.once("error", (error) => {
1875
+ socket.close();
1876
+ reject(error);
1877
+ });
1878
+ });
1879
+ }
1880
+ async disconnectChildWorkflowSocket() {
1881
+ if (!this.childWorkflowSocket) return;
1882
+ const socket = this.childWorkflowSocket;
1883
+ this.childWorkflowSocket = null;
1884
+ await new Promise((resolve) => {
1885
+ socket.once("close", () => {
1886
+ resolve();
1887
+ });
1888
+ socket.close();
1889
+ });
1890
+ }
1891
+ handleChildWorkflowSocketMessage(rawData) {
1892
+ const text = typeof rawData === "string" ? rawData : Buffer.isBuffer(rawData) ? rawData.toString("utf8") : "";
1893
+ if (text.trim().length === 0) return;
1894
+ try {
1895
+ const message = JSON.parse(text);
1896
+ if (message.kind === "event" && typeof message.event?.workflowId === "string") {
1897
+ this.broadcastWorkflowTextToRoom(message.event.workflowId, text);
1898
+ return;
1899
+ }
1900
+ if ((message.kind === "workflowChanged" || message.kind === "devBuildStarted" || message.kind === "devBuildCompleted" || message.kind === "devBuildFailed") && typeof message.workflowId === "string") {
1901
+ this.broadcastWorkflowTextToRoom(message.workflowId, text);
1902
+ return;
1903
+ }
1904
+ if (message.kind === "error" && typeof message.message === "string") this.broadcastWorkflowTextToAll(text);
1905
+ } catch {}
1906
+ }
1907
+ broadcastWorkflowTextToRoom(roomId, text) {
1908
+ for (const [client, roomIds] of this.roomIdsByWorkflowClient) {
1909
+ if (client.readyState !== WebSocket.OPEN || !roomIds.has(roomId)) continue;
1910
+ client.send(text);
1911
+ }
1912
+ }
1913
+ broadcastWorkflowTextToAll(text) {
1914
+ for (const client of this.workflowClients) if (client.readyState === WebSocket.OPEN) client.send(text);
1915
+ }
1916
+ };
1917
+
1918
+ //#endregion
1919
+ //#region src/dev/ListenPortConflictDescriber.ts
1920
+ var ListenPortConflictDescriber = class {
1921
+ constructor(platform = process$1.platform) {
1922
+ this.platform = platform;
1923
+ }
1924
+ async describeLoopbackPort(port) {
1925
+ if (!Number.isInteger(port) || port <= 0) return null;
1926
+ if (this.platform !== "linux" && this.platform !== "darwin") return null;
1927
+ const raw = await this.readLsofOutput(port);
1928
+ if (raw === null) return null;
1929
+ const occupants = this.parseLsofOutput(raw);
1930
+ if (occupants.length === 0) return null;
1931
+ return occupants.map((occupant) => `pid=${occupant.pid} command=${occupant.command} endpoint=${occupant.endpoint}`).join("; ");
1932
+ }
1933
+ async readLsofOutput(port) {
1934
+ try {
1935
+ return await new Promise((resolve, reject) => {
1936
+ execFile("lsof", [
1937
+ "-nP",
1938
+ `-iTCP:${port}`,
1939
+ "-sTCP:LISTEN",
1940
+ "-Fpcn"
1941
+ ], (error, stdout) => {
1942
+ if (error) {
1943
+ reject(error);
1944
+ return;
1945
+ }
1946
+ resolve(stdout);
1947
+ });
1948
+ });
1949
+ } catch {
1950
+ return null;
1951
+ }
1952
+ }
1953
+ parseLsofOutput(raw) {
1954
+ const occupants = [];
1955
+ let currentPid = null;
1956
+ let currentCommand = null;
1957
+ for (const line of raw.split("\n")) {
1958
+ if (line.length < 2) continue;
1959
+ const prefix = line[0];
1960
+ const value = line.slice(1).trim();
1961
+ if (prefix === "p") {
1962
+ currentPid = Number.parseInt(value, 10);
1963
+ currentCommand = null;
1964
+ continue;
1965
+ }
1966
+ if (prefix === "c") {
1967
+ currentCommand = value;
1968
+ continue;
1969
+ }
1970
+ if (prefix === "n" && currentPid !== null && currentCommand !== null) occupants.push({
1971
+ pid: currentPid,
1972
+ command: currentCommand,
1973
+ endpoint: value
1974
+ });
1975
+ }
1976
+ return occupants;
1977
+ }
1978
+ };
1979
+
1980
+ //#endregion
1981
+ //#region src/dev/CliDevProxyServerFactory.ts
1982
+ var CliDevProxyServerFactory = class {
1983
+ listenPortConflictDescriber = new ListenPortConflictDescriber();
1984
+ create(gatewayPort) {
1985
+ return new CliDevProxyServer(gatewayPort, this.listenPortConflictDescriber);
1986
+ }
1987
+ };
1988
+
1989
+ //#endregion
1990
+ //#region ../host/src/presentation/server/CodemationTsyringeParamInfoReader.ts
1991
+ var CodemationTsyringeParamInfoReader = class {
1992
+ static injectionTokenMetadataKey = "injectionTokens";
1993
+ static designParamTypesMetadataKey = "design:paramtypes";
1994
+ static read(target) {
1995
+ const designParamTypes = this.readDesignParamTypes(target);
1996
+ const injectionTokens = this.readInjectionTokens(target);
1997
+ Object.keys(injectionTokens).forEach((key) => {
1998
+ designParamTypes[Number(key)] = injectionTokens[key];
1999
+ });
2000
+ return designParamTypes;
2001
+ }
2002
+ static readDesignParamTypes(target) {
2003
+ const reflected = Reflect.getMetadata?.(this.designParamTypesMetadataKey, target);
2004
+ return Array.isArray(reflected) ? [...reflected] : [];
2005
+ }
2006
+ static readInjectionTokens(target) {
2007
+ const reflected = Reflect.getOwnMetadata?.(this.injectionTokenMetadataKey, target);
2008
+ if (!reflected || typeof reflected !== "object") return {};
2009
+ return reflected;
2010
+ }
2011
+ };
2012
+
2013
+ //#endregion
2014
+ //#region ../host/src/presentation/server/CodemationTsyringeTypeInfoRegistrar.ts
2015
+ var CodemationTsyringeTypeInfoRegistrar = class {
2016
+ visitedTokens = /* @__PURE__ */ new Set();
2017
+ visitedConfigObjects = /* @__PURE__ */ new Set();
2018
+ constructor(container) {
2019
+ this.container = container;
2020
+ }
2021
+ registerWorkflowDefinitions(workflows) {
2022
+ for (const workflow of workflows) for (const node of workflow.nodes) {
2023
+ this.registerTypeToken(node.type);
2024
+ this.registerConfigTokens(node.config);
2025
+ }
2026
+ }
2027
+ registerTypeToken(token) {
2028
+ if (typeof token !== "function" || this.visitedTokens.has(token)) return;
2029
+ this.visitedTokens.add(token);
2030
+ const paramInfo = CodemationTsyringeParamInfoReader.read(token);
2031
+ for (const dependency of paramInfo) this.registerDependency(dependency);
2032
+ this.registerFactoryProvider(token, paramInfo);
2033
+ }
2034
+ registerDependency(dependency) {
2035
+ const token = this.resolveDependencyToken(dependency);
2036
+ if (typeof token !== "function") return;
2037
+ if (!this.container.isRegistered(token, true)) return;
2038
+ this.registerTypeToken(token);
2039
+ }
2040
+ registerConfigTokens(value) {
2041
+ if (Array.isArray(value)) {
2042
+ value.forEach((entry) => this.registerConfigTokens(entry));
2043
+ return;
2044
+ }
2045
+ if (!value || typeof value !== "object") return;
2046
+ if (this.visitedConfigObjects.has(value)) return;
2047
+ this.visitedConfigObjects.add(value);
2048
+ if ("type" in value && typeof value.type === "function") this.registerTypeToken(value.type);
2049
+ Object.values(value).forEach((entry) => this.registerConfigTokens(entry));
2050
+ }
2051
+ registerFactoryProvider(token, paramInfo) {
2052
+ if (this.container.isRegistered(token, true)) return;
2053
+ const classToken = token;
2054
+ const constructorToken = token;
2055
+ this.container.register(classToken, { useFactory: (dependencyContainer) => {
2056
+ return new constructorToken(...paramInfo.map((dependency) => this.resolveFactoryDependency(dependencyContainer, dependency)));
2057
+ } });
2058
+ }
2059
+ resolveDependencyToken(dependency) {
2060
+ if (this.isInjectionDescriptor(dependency)) return dependency.token;
2061
+ return dependency;
2062
+ }
2063
+ resolveFactoryDependency(dependencyContainer, dependency) {
2064
+ const token = this.resolveDependencyToken(dependency);
2065
+ if (typeof token === "function") {
2066
+ if (dependencyContainer.isRegistered(token, true)) try {
2067
+ return dependencyContainer.resolve(token);
2068
+ } catch (error) {
2069
+ if (!this.isMissingTypeInfoError(error)) throw error;
2070
+ }
2071
+ this.registerTypeToken(token);
2072
+ return new token(...CodemationTsyringeParamInfoReader.read(token).map((entry) => this.resolveFactoryDependency(dependencyContainer, entry)));
2073
+ }
2074
+ return dependencyContainer.resolve(token);
2075
+ }
2076
+ isInjectionDescriptor(value) {
2077
+ return value !== null && typeof value === "object" && "token" in value;
2078
+ }
2079
+ isMissingTypeInfoError(error) {
2080
+ return error instanceof Error && error.message.includes("TypeInfo not known for");
2081
+ }
2082
+ };
2083
+
2084
+ //#endregion
2085
+ //#region src/dev/DevApiRuntimeHost.ts
2086
+ var DevApiRuntimeHost = class {
2087
+ pluginListMerger = new CodemationPluginListMerger();
2088
+ contextPromise = null;
2089
+ constructor(configLoader, pluginDiscovery, args) {
2090
+ this.configLoader = configLoader;
2091
+ this.pluginDiscovery = pluginDiscovery;
2092
+ this.args = args;
2093
+ }
2094
+ async prepare() {
2095
+ if (!this.contextPromise) this.contextPromise = this.createContext();
2096
+ return await this.contextPromise;
2097
+ }
2098
+ async stop() {
2099
+ const contextPromise = this.contextPromise;
2100
+ this.contextPromise = null;
2101
+ if (!contextPromise) return;
2102
+ await (await contextPromise).container.resolve(AppContainerLifecycle$1).stop();
2103
+ }
2104
+ async createContext() {
2105
+ const consumerRoot = path.resolve(this.args.consumerRoot);
2106
+ const repoRoot = await this.detectWorkspaceRoot(consumerRoot);
2107
+ const prismaCliOverride = await this.resolvePrismaCliOverride();
2108
+ const hostPackageRoot = path.resolve(repoRoot, "packages", "host");
2109
+ const env = { ...this.args.env };
2110
+ if (prismaCliOverride) env.CODEMATION_PRISMA_CLI_PATH = prismaCliOverride;
2111
+ env.CODEMATION_HOST_PACKAGE_ROOT = hostPackageRoot;
2112
+ env.CODEMATION_PRISMA_CONFIG_PATH = path.resolve(hostPackageRoot, "prisma.config.ts");
2113
+ env.CODEMATION_CONSUMER_ROOT = consumerRoot;
2114
+ const configResolution = await this.configLoader.load({
2115
+ consumerRoot,
2116
+ repoRoot,
2117
+ env
2118
+ });
2119
+ const discoveredPlugins = await this.loadDiscoveredPlugins(consumerRoot);
2120
+ const appConfig = {
2121
+ ...configResolution.appConfig,
2122
+ env,
2123
+ plugins: discoveredPlugins.length > 0 ? this.pluginListMerger.merge(configResolution.appConfig.plugins, discoveredPlugins) : configResolution.appConfig.plugins
2124
+ };
2125
+ const container = await new AppContainerFactory$1().create({
2126
+ appConfig,
2127
+ sharedWorkflowWebsocketServer: null
2128
+ });
2129
+ new CodemationTsyringeTypeInfoRegistrar(container).registerWorkflowDefinitions(appConfig.workflows ?? []);
2130
+ await container.resolve(FrontendRuntime).start();
2131
+ return {
2132
+ buildVersion: this.createBuildVersion(),
2133
+ container,
2134
+ consumerRoot,
2135
+ repoRoot,
2136
+ workflowIds: appConfig.workflows.map((workflow) => workflow.id),
2137
+ workflowSources: appConfig.workflowSources
2138
+ };
2139
+ }
2140
+ async loadDiscoveredPlugins(consumerRoot) {
2141
+ return (await this.pluginDiscovery.resolvePlugins(consumerRoot)).map((resolvedPackage) => resolvedPackage.plugin);
2142
+ }
2143
+ async detectWorkspaceRoot(startDirectory) {
2144
+ let currentDirectory = path.resolve(startDirectory);
2145
+ while (true) {
2146
+ if (await this.exists(path.resolve(currentDirectory, "pnpm-workspace.yaml"))) return currentDirectory;
2147
+ const parentDirectory = path.dirname(currentDirectory);
2148
+ if (parentDirectory === currentDirectory) return startDirectory;
2149
+ currentDirectory = parentDirectory;
2150
+ }
2151
+ }
2152
+ async resolvePrismaCliOverride() {
2153
+ const candidate = path.resolve(this.args.runtimeWorkingDirectory, "node_modules", "prisma", "build", "index.js");
2154
+ return await this.exists(candidate) ? candidate : null;
2155
+ }
2156
+ createBuildVersion() {
2157
+ return `${Date.now()}-${process$1.pid}`;
2158
+ }
2159
+ async exists(filePath) {
2160
+ try {
2161
+ await access(filePath);
2162
+ return true;
2163
+ } catch {
2164
+ return false;
2165
+ }
2166
+ }
2167
+ };
2168
+
2169
+ //#endregion
2170
+ //#region src/dev/DevApiRuntimeServer.ts
2171
+ var DevApiRuntimeServer = class {
2172
+ bootstrapLogger = new ServerLoggerFactory(logLevelPolicyFactory).create("codemation-cli.dev-runtime");
2173
+ server = null;
2174
+ constructor(httpPort, workflowWebSocketPort, host) {
2175
+ this.httpPort = httpPort;
2176
+ this.workflowWebSocketPort = workflowWebSocketPort;
2177
+ this.host = host;
2178
+ }
2179
+ async start() {
2180
+ const root = new Hono();
2181
+ root.get("/health", (c) => c.json({ ok: true }));
2182
+ root.all("*", async (c) => {
2183
+ return (await this.host.prepare()).container.resolve(CodemationHonoApiApp).fetch(c.req.raw);
2184
+ });
2185
+ await this.listen(root);
2186
+ try {
2187
+ return await this.host.prepare();
2188
+ } catch (error) {
2189
+ await this.stop();
2190
+ throw error;
2191
+ }
2192
+ }
2193
+ async stop() {
2194
+ const server = this.server;
2195
+ this.server = null;
2196
+ const failures = [];
2197
+ if (server) try {
2198
+ await this.closeServer(server);
2199
+ } catch (error) {
2200
+ failures.push(this.normalizeError(error));
2201
+ }
2202
+ try {
2203
+ await this.host.stop();
2204
+ } catch (error) {
2205
+ failures.push(this.normalizeError(error));
2206
+ }
2207
+ if (failures.length > 0) throw failures[0];
2208
+ }
2209
+ async listen(root) {
2210
+ if (this.server) return;
2211
+ await new Promise((resolve, reject) => {
2212
+ let resolved = false;
2213
+ const server = serve({
2214
+ fetch: root.fetch,
2215
+ port: this.httpPort,
2216
+ hostname: "127.0.0.1"
2217
+ }, () => {
2218
+ resolved = true;
2219
+ this.server = server;
2220
+ resolve();
2221
+ });
2222
+ server.on("error", (error) => {
2223
+ if (resolved) {
2224
+ this.bootstrapLogger.error("runtime HTTP server error", this.normalizeError(error));
2225
+ return;
2226
+ }
2227
+ reject(error);
2228
+ });
2229
+ });
2230
+ this.bootstrapLogger.debug(`runtime listening httpPort=${this.httpPort} workflowWebSocketPort=${this.workflowWebSocketPort}`);
2231
+ }
2232
+ closeServer(server) {
2233
+ return new Promise((resolve, reject) => {
2234
+ server.close((error) => {
2235
+ if (error) {
2236
+ reject(error);
2237
+ return;
2238
+ }
2239
+ resolve();
2240
+ });
2241
+ });
2242
+ }
2243
+ normalizeError(error) {
2244
+ return error instanceof Error ? error : new Error(String(error));
2245
+ }
2246
+ };
2247
+
2248
+ //#endregion
2249
+ //#region src/dev/DevApiRuntimeFactory.ts
2250
+ var DevApiRuntimeFactory = class {
2251
+ constructor(portAllocator, configLoader, pluginDiscovery) {
2252
+ this.portAllocator = portAllocator;
2253
+ this.configLoader = configLoader;
2254
+ this.pluginDiscovery = pluginDiscovery;
2255
+ }
2256
+ async create(args) {
2257
+ const httpPort = await this.portAllocator.allocate();
2258
+ const workflowWebSocketPort = await this.portAllocator.allocate();
2259
+ const runtime = new DevApiRuntimeServer(httpPort, workflowWebSocketPort, new DevApiRuntimeHost(this.configLoader, this.pluginDiscovery, {
2260
+ consumerRoot: args.consumerRoot,
2261
+ env: {
2262
+ ...args.env,
2263
+ CODEMATION_WS_PORT: String(workflowWebSocketPort),
2264
+ NEXT_PUBLIC_CODEMATION_WS_PORT: String(workflowWebSocketPort)
2265
+ },
2266
+ runtimeWorkingDirectory: args.runtimeWorkingDirectory
2267
+ }));
2268
+ const context = await runtime.start();
2269
+ return {
2270
+ buildVersion: context.buildVersion,
2271
+ httpPort,
2272
+ stop: async () => {
2273
+ await runtime.stop();
2274
+ },
2275
+ workflowIds: context.workflowIds,
2276
+ workflowWebSocketPort
2277
+ };
2278
+ }
2279
+ };
2280
+
2281
+ //#endregion
2282
+ //#region src/dev/DevRebuildQueue.ts
2283
+ var DevRebuildQueue = class {
2284
+ pendingRequest = null;
2285
+ drainPromise = null;
2286
+ constructor(handler) {
2287
+ this.handler = handler;
2288
+ }
2289
+ async enqueue(request) {
2290
+ this.pendingRequest = this.mergePendingRequest(this.pendingRequest, request);
2291
+ if (!this.drainPromise) this.drainPromise = this.drain();
2292
+ return await this.drainPromise;
2293
+ }
2294
+ async drain() {
2295
+ try {
2296
+ while (this.pendingRequest) {
2297
+ const nextRequest = this.pendingRequest;
2298
+ this.pendingRequest = null;
2299
+ await this.handler.run(nextRequest);
2300
+ }
2301
+ } finally {
2302
+ this.drainPromise = null;
2303
+ if (this.pendingRequest) {
2304
+ this.drainPromise = this.drain();
2305
+ await this.drainPromise;
2306
+ }
2307
+ }
2308
+ }
2309
+ mergePendingRequest(current, next) {
2310
+ if (!current) return {
2311
+ ...next,
2312
+ changedPaths: [...next.changedPaths]
2313
+ };
2314
+ return {
2315
+ changedPaths: [...new Set([...current.changedPaths, ...next.changedPaths])],
2316
+ shouldRepublishConsumerOutput: current.shouldRepublishConsumerOutput || next.shouldRepublishConsumerOutput,
2317
+ shouldRestartUi: current.shouldRestartUi || next.shouldRestartUi
2318
+ };
2319
+ }
2320
+ };
2321
+
2322
+ //#endregion
2323
+ //#region src/dev/DevRebuildQueueFactory.ts
2324
+ var DevRebuildQueueFactory = class {
2325
+ create(handler) {
2326
+ return new DevRebuildQueue(handler);
2327
+ }
2328
+ };
2329
+
1505
2330
  //#endregion
1506
2331
  //#region src/runtime/ListenPortResolver.ts
1507
2332
  /**
@@ -1536,36 +2361,13 @@ var SourceMapNodeOptions = class {
1536
2361
  }
1537
2362
  };
1538
2363
 
1539
- //#endregion
1540
- //#region src/dev/DevelopmentGatewayNotifier.ts
1541
- var DevelopmentGatewayNotifier = class {
1542
- constructor(cliLogger) {
1543
- this.cliLogger = cliLogger;
1544
- }
1545
- async notify(args) {
1546
- const targetUrl = `${args.gatewayBaseUrl.replace(/\/$/, "")}${ApiPaths.devGatewayNotify()}`;
1547
- try {
1548
- const response = await fetch(targetUrl, {
1549
- method: "POST",
1550
- headers: {
1551
- "content-type": "application/json",
1552
- "x-codemation-dev-token": args.developmentServerToken
1553
- },
1554
- body: JSON.stringify(args.payload)
1555
- });
1556
- if (!response.ok) this.cliLogger.warn(`failed to notify dev gateway status=${response.status}`);
1557
- } catch (error) {
1558
- this.cliLogger.warn(`failed to notify dev gateway: ${error instanceof Error ? error.message : String(error)}`);
1559
- }
1560
- }
1561
- };
1562
-
1563
2364
  //#endregion
1564
2365
  //#region src/dev/DevAuthSettingsLoader.ts
1565
2366
  var DevAuthSettingsLoader = class DevAuthSettingsLoader {
1566
2367
  static defaultDevelopmentAuthSecret = "codemation-dev-auth-secret-not-for-production";
1567
- constructor(configLoader) {
2368
+ constructor(configLoader, consumerEnvLoader) {
1568
2369
  this.configLoader = configLoader;
2370
+ this.consumerEnvLoader = consumerEnvLoader;
1569
2371
  }
1570
2372
  resolveDevelopmentServerToken(rawToken) {
1571
2373
  if (rawToken && rawToken.trim().length > 0) return rawToken;
@@ -1573,14 +2375,15 @@ var DevAuthSettingsLoader = class DevAuthSettingsLoader {
1573
2375
  }
1574
2376
  async loadForConsumer(consumerRoot) {
1575
2377
  const resolution = await this.configLoader.load({ consumerRoot });
2378
+ const envForAuthSecret = this.consumerEnvLoader.mergeConsumerRootIntoProcessEnvironment(consumerRoot, process.env);
1576
2379
  return {
1577
2380
  authConfigJson: JSON.stringify(resolution.config.auth ?? null),
1578
- authSecret: this.resolveDevelopmentAuthSecret(process.env),
2381
+ authSecret: this.resolveDevelopmentAuthSecret(envForAuthSecret),
1579
2382
  skipUiAuth: resolution.config.auth?.allowUnauthenticatedInDevelopment === true
1580
2383
  };
1581
2384
  }
1582
2385
  resolveDevelopmentAuthSecret(env) {
1583
- const configuredSecret = env.AUTH_SECRET ?? env.NEXTAUTH_SECRET;
2386
+ const configuredSecret = env.AUTH_SECRET;
1584
2387
  if (configuredSecret && configuredSecret.trim().length > 0) return configuredSecret;
1585
2388
  return DevAuthSettingsLoader.defaultDevelopmentAuthSecret;
1586
2389
  }
@@ -1607,10 +2410,10 @@ var DevHttpProbe = class {
1607
2410
  } catch {}
1608
2411
  await setTimeout$1(50);
1609
2412
  }
1610
- throw new Error("Timed out waiting for dev gateway HTTP health check.");
2413
+ throw new Error("Timed out waiting for the stable dev HTTP health check.");
1611
2414
  }
1612
2415
  /**
1613
- * Polls until the runtime child serves bootstrap summary (after gateway is up, the disposable runtime may still be wiring).
2416
+ * Polls until the active disposable runtime serves bootstrap summary through the stable CLI dev endpoint.
1614
2417
  */
1615
2418
  async waitUntilBootstrapSummaryReady(gatewayBaseUrl) {
1616
2419
  const url = `${gatewayBaseUrl.replace(/\/$/, "")}/api/dev/bootstrap-summary`;
@@ -1630,14 +2433,17 @@ var DevHttpProbe = class {
1630
2433
  //#endregion
1631
2434
  //#region src/dev/DevNextHostEnvironmentBuilder.ts
1632
2435
  var DevNextHostEnvironmentBuilder = class {
1633
- constructor(consumerEnvLoader, sourceMapNodeOptions) {
2436
+ constructor(consumerEnvLoader, sourceMapNodeOptions, frontendAuthSnapshotFactory = new CodemationFrontendAuthSnapshotFactory$1(), frontendAppConfigJsonCodec = new FrontendAppConfigJsonCodec$1()) {
1634
2437
  this.consumerEnvLoader = consumerEnvLoader;
1635
2438
  this.sourceMapNodeOptions = sourceMapNodeOptions;
2439
+ this.frontendAuthSnapshotFactory = frontendAuthSnapshotFactory;
2440
+ this.frontendAppConfigJsonCodec = frontendAppConfigJsonCodec;
1636
2441
  }
1637
2442
  buildConsumerUiProxy(args) {
1638
2443
  return {
1639
2444
  ...this.build({
1640
2445
  authConfigJson: args.authConfigJson,
2446
+ authSecret: args.authSecret,
1641
2447
  consumerRoot: args.consumerRoot,
1642
2448
  developmentServerToken: args.developmentServerToken,
1643
2449
  nextPort: args.nextPort,
@@ -1646,23 +2452,33 @@ var DevNextHostEnvironmentBuilder = class {
1646
2452
  websocketPort: args.websocketPort,
1647
2453
  consumerOutputManifestPath: args.consumerOutputManifestPath
1648
2454
  }),
2455
+ HOSTNAME: "127.0.0.1",
1649
2456
  AUTH_SECRET: args.authSecret,
1650
- AUTH_URL: args.publicBaseUrl,
1651
- NEXTAUTH_SECRET: args.authSecret,
1652
- NEXTAUTH_URL: args.publicBaseUrl
2457
+ AUTH_URL: args.publicBaseUrl
1653
2458
  };
1654
2459
  }
1655
2460
  build(args) {
1656
2461
  const merged = this.consumerEnvLoader.mergeConsumerRootIntoProcessEnvironment(args.consumerRoot, process$1.env);
1657
2462
  const manifestPath = args.consumerOutputManifestPath ?? path.resolve(args.consumerRoot, ".codemation", "output", "current.json");
2463
+ const authSecret = args.authSecret ?? merged.AUTH_SECRET;
2464
+ const authSnapshot = this.frontendAuthSnapshotFactory.createFromResolvedInputs({
2465
+ authConfig: this.parseAuthConfig(args.authConfigJson),
2466
+ env: {
2467
+ ...merged,
2468
+ ...typeof authSecret === "string" && authSecret.trim().length > 0 ? { AUTH_SECRET: authSecret } : {}
2469
+ },
2470
+ uiAuthEnabled: !args.skipUiAuth
2471
+ });
1658
2472
  return {
1659
2473
  ...merged,
1660
2474
  PORT: String(args.nextPort),
1661
- CODEMATION_AUTH_CONFIG_JSON: args.authConfigJson,
1662
2475
  CODEMATION_CONSUMER_ROOT: args.consumerRoot,
1663
2476
  CODEMATION_CONSUMER_OUTPUT_MANIFEST_PATH: manifestPath,
1664
- CODEMATION_SKIP_UI_AUTH: args.skipUiAuth ? "true" : "false",
1665
- NEXT_PUBLIC_CODEMATION_SKIP_UI_AUTH: args.skipUiAuth ? "true" : "false",
2477
+ CODEMATION_FRONTEND_APP_CONFIG_JSON: this.frontendAppConfigJsonCodec.serialize({
2478
+ auth: authSnapshot,
2479
+ productName: "Codemation",
2480
+ logoUrl: null
2481
+ }),
1666
2482
  CODEMATION_WS_PORT: String(args.websocketPort),
1667
2483
  NEXT_PUBLIC_CODEMATION_WS_PORT: String(args.websocketPort),
1668
2484
  CODEMATION_DEV_SERVER_TOKEN: args.developmentServerToken,
@@ -1673,6 +2489,10 @@ var DevNextHostEnvironmentBuilder = class {
1673
2489
  ...args.runtimeDevUrl !== void 0 && args.runtimeDevUrl.trim().length > 0 ? { CODEMATION_RUNTIME_DEV_URL: args.runtimeDevUrl.trim() } : {}
1674
2490
  };
1675
2491
  }
2492
+ parseAuthConfig(authConfigJson) {
2493
+ if (authConfigJson.trim().length === 0) return;
2494
+ return JSON.parse(authConfigJson) ?? void 0;
2495
+ }
1676
2496
  };
1677
2497
 
1678
2498
  //#endregion
@@ -1686,7 +2506,7 @@ var DevSessionPortsResolver = class {
1686
2506
  const nextPort = this.listenPorts.resolvePrimaryApplicationPort(args.portEnv);
1687
2507
  return {
1688
2508
  nextPort,
1689
- gatewayPort: this.listenPorts.parsePositiveInteger(args.gatewayPortEnv) ?? (args.devMode === "consumer" ? nextPort : await this.loopbackPorts.allocate())
2509
+ gatewayPort: this.listenPorts.parsePositiveInteger(args.gatewayPortEnv) ?? (args.devMode === "packaged-ui" ? nextPort : await this.loopbackPorts.allocate())
1690
2510
  };
1691
2511
  }
1692
2512
  };
@@ -1697,86 +2517,48 @@ var DevSessionPortsResolver = class {
1697
2517
  * Bundles dependencies for {@link DevCommand} so the command stays a thin orchestrator.
1698
2518
  */
1699
2519
  var DevSessionServices = class {
1700
- constructor(consumerEnvLoader, sourceMapNodeOptions, sessionPorts, loopbackPortAllocator, devHttpProbe, runtimeEntrypointResolver, devAuthLoader, nextHostEnvBuilder, watchRootsResolver, sourceChangeClassifier, sourceRestartCoordinator) {
2520
+ constructor(consumerEnvLoader, sourceMapNodeOptions, sessionPorts, loopbackPortAllocator, devHttpProbe, devAuthLoader, nextHostEnvBuilder, watchRootsResolver, sourceChangeClassifier) {
1701
2521
  this.consumerEnvLoader = consumerEnvLoader;
1702
2522
  this.sourceMapNodeOptions = sourceMapNodeOptions;
1703
2523
  this.sessionPorts = sessionPorts;
1704
2524
  this.loopbackPortAllocator = loopbackPortAllocator;
1705
2525
  this.devHttpProbe = devHttpProbe;
1706
- this.runtimeEntrypointResolver = runtimeEntrypointResolver;
1707
2526
  this.devAuthLoader = devAuthLoader;
1708
2527
  this.nextHostEnvBuilder = nextHostEnvBuilder;
1709
2528
  this.watchRootsResolver = watchRootsResolver;
1710
2529
  this.sourceChangeClassifier = sourceChangeClassifier;
1711
- this.sourceRestartCoordinator = sourceRestartCoordinator;
1712
2530
  }
1713
2531
  };
1714
2532
 
1715
2533
  //#endregion
1716
2534
  //#region src/dev/DevSourceChangeClassifier.ts
1717
- var DevSourceChangeClassifier = class {
2535
+ var DevSourceChangeClassifier = class DevSourceChangeClassifier {
2536
+ static configFileNames = new Set([
2537
+ "codemation.config.ts",
2538
+ "codemation.config.js",
2539
+ "codemation.config.mjs"
2540
+ ]);
1718
2541
  shouldRepublishConsumerOutput(args) {
1719
2542
  const resolvedConsumerRoot = path.resolve(args.consumerRoot);
1720
2543
  return args.changedPaths.some((changedPath) => this.isPathInsideDirectory(changedPath, resolvedConsumerRoot));
1721
2544
  }
1722
- requiresNextHostRestart(args) {
1723
- const configPaths = new Set(this.resolveConfigPaths(args.consumerRoot));
1724
- return args.changedPaths.some((changedPath) => configPaths.has(path.resolve(changedPath)));
1725
- }
1726
- resolveConfigPaths(consumerRoot) {
1727
- const resolvedConsumerRoot = path.resolve(consumerRoot);
1728
- return [
1729
- path.resolve(resolvedConsumerRoot, "codemation.config.ts"),
1730
- path.resolve(resolvedConsumerRoot, "codemation.config.js"),
1731
- path.resolve(resolvedConsumerRoot, "src", "codemation.config.ts"),
1732
- path.resolve(resolvedConsumerRoot, "src", "codemation.config.js")
1733
- ];
2545
+ requiresUiRestart(args) {
2546
+ const resolvedConsumerRoot = path.resolve(args.consumerRoot);
2547
+ return args.changedPaths.some((changedPath) => this.pathRequiresUiRestart(path.resolve(changedPath), resolvedConsumerRoot));
1734
2548
  }
1735
2549
  isPathInsideDirectory(filePath, directoryPath) {
1736
2550
  const resolvedFilePath = path.resolve(filePath);
1737
2551
  const relativePath = path.relative(directoryPath, resolvedFilePath);
1738
2552
  return relativePath.length === 0 || !relativePath.startsWith("..") && !path.isAbsolute(relativePath);
1739
2553
  }
1740
- };
1741
-
1742
- //#endregion
1743
- //#region src/dev/DevSourceRestartCoordinator.ts
1744
- var DevSourceRestartCoordinator = class {
1745
- constructor(gatewayNotifier, performanceDiagnosticsLogger, cliLogger) {
1746
- this.gatewayNotifier = gatewayNotifier;
1747
- this.performanceDiagnosticsLogger = performanceDiagnosticsLogger;
1748
- this.cliLogger = cliLogger;
1749
- }
1750
- async runHandshakeAfterSourceChange(gatewayBaseUrl, developmentServerToken) {
1751
- const restartStarted = performance.now();
1752
- try {
1753
- await this.gatewayNotifier.notify({
1754
- gatewayBaseUrl,
1755
- developmentServerToken,
1756
- payload: { kind: "buildStarted" }
1757
- });
1758
- await this.gatewayNotifier.notify({
1759
- gatewayBaseUrl,
1760
- developmentServerToken,
1761
- payload: {
1762
- kind: "buildCompleted",
1763
- buildVersion: `${Date.now()}-${process$1.pid}`
1764
- }
1765
- });
1766
- const totalMs = performance.now() - restartStarted;
1767
- this.performanceDiagnosticsLogger.info(`triggered source-based runtime restart timingMs={total:${totalMs.toFixed(1)}}`);
1768
- } catch (error) {
1769
- const exception = error instanceof Error ? error : new Error(String(error));
1770
- await this.gatewayNotifier.notify({
1771
- gatewayBaseUrl,
1772
- developmentServerToken,
1773
- payload: {
1774
- kind: "buildFailed",
1775
- message: exception.message
1776
- }
1777
- });
1778
- this.cliLogger.error("source-based runtime restart request failed", exception);
1779
- }
2554
+ pathRequiresUiRestart(resolvedPath, consumerRoot) {
2555
+ if (!this.isPathInsideDirectory(resolvedPath, consumerRoot)) return false;
2556
+ const relativePath = path.relative(consumerRoot, resolvedPath);
2557
+ if (DevSourceChangeClassifier.configFileNames.has(path.basename(relativePath))) return true;
2558
+ if (relativePath.startsWith(path.join("src", "workflows"))) return false;
2559
+ if (relativePath.startsWith(path.join("src", "plugins"))) return false;
2560
+ if (relativePath.includes("credential")) return true;
2561
+ return false;
1780
2562
  }
1781
2563
  };
1782
2564
 
@@ -1785,7 +2567,7 @@ var DevSourceRestartCoordinator = class {
1785
2567
  var LoopbackPortAllocator = class {
1786
2568
  async allocate() {
1787
2569
  return await new Promise((resolve, reject) => {
1788
- const server = createServer();
2570
+ const server = createServer$1();
1789
2571
  server.once("error", reject);
1790
2572
  server.listen(0, "127.0.0.1", () => {
1791
2573
  const address = server.address();
@@ -1801,52 +2583,20 @@ var LoopbackPortAllocator = class {
1801
2583
  }
1802
2584
  };
1803
2585
 
1804
- //#endregion
1805
- //#region src/dev/RuntimeToolEntrypointResolver.ts
1806
- var RuntimeToolEntrypointResolver = class {
1807
- require = createRequire(import.meta.url);
1808
- async resolve(args) {
1809
- const sourceEntrypointPath = path.resolve(args.repoRoot, args.sourceEntrypoint);
1810
- if (await this.exists(sourceEntrypointPath)) return {
1811
- command: process$1.execPath,
1812
- args: [
1813
- "--import",
1814
- "tsx",
1815
- sourceEntrypointPath
1816
- ],
1817
- env: { TSX_TSCONFIG_PATH: path.resolve(args.repoRoot, "tsconfig.codemation-tsx.json") }
1818
- };
1819
- return {
1820
- command: process$1.execPath,
1821
- args: [this.require.resolve(args.packageName)],
1822
- env: {}
1823
- };
1824
- }
1825
- async exists(filePath) {
1826
- try {
1827
- await access(filePath);
1828
- return true;
1829
- } catch {
1830
- return false;
1831
- }
1832
- }
1833
- };
1834
-
1835
2586
  //#endregion
1836
2587
  //#region src/dev/WatchRootsResolver.ts
1837
2588
  var WatchRootsResolver = class {
1838
2589
  resolve(args) {
1839
- if (args.devMode === "consumer") return [args.consumerRoot];
2590
+ if (args.devMode === "packaged-ui") return [args.consumerRoot];
1840
2591
  return [
1841
2592
  args.consumerRoot,
2593
+ path.resolve(args.repoRoot, "packages", "cli"),
1842
2594
  path.resolve(args.repoRoot, "packages", "core"),
1843
2595
  path.resolve(args.repoRoot, "packages", "core-nodes"),
1844
2596
  path.resolve(args.repoRoot, "packages", "core-nodes-gmail"),
1845
2597
  path.resolve(args.repoRoot, "packages", "eventbus-redis"),
1846
2598
  path.resolve(args.repoRoot, "packages", "host"),
1847
- path.resolve(args.repoRoot, "packages", "node-example"),
1848
- path.resolve(args.repoRoot, "packages", "queue-bullmq"),
1849
- path.resolve(args.repoRoot, "packages", "runtime-dev")
2599
+ path.resolve(args.repoRoot, "packages", "node-example")
1850
2600
  ];
1851
2601
  }
1852
2602
  };
@@ -1854,16 +2604,12 @@ var WatchRootsResolver = class {
1854
2604
  //#endregion
1855
2605
  //#region src/dev/Builder.ts
1856
2606
  var DevSessionServicesBuilder = class {
1857
- constructor(loggerFactory$1) {
1858
- this.loggerFactory = loggerFactory$1;
1859
- }
1860
2607
  build() {
1861
2608
  const consumerEnvLoader = new ConsumerEnvLoader();
1862
2609
  const sourceMapNodeOptions = new SourceMapNodeOptions();
1863
2610
  const listenPortResolver = new ListenPortResolver();
1864
2611
  const loopbackPortAllocator = new LoopbackPortAllocator();
1865
- const cliLogger = this.loggerFactory.create("codemation-cli");
1866
- return new DevSessionServices(consumerEnvLoader, sourceMapNodeOptions, new DevSessionPortsResolver(listenPortResolver, loopbackPortAllocator), loopbackPortAllocator, new DevHttpProbe(), new RuntimeToolEntrypointResolver(), new DevAuthSettingsLoader(new CodemationConsumerConfigLoader()), new DevNextHostEnvironmentBuilder(consumerEnvLoader, sourceMapNodeOptions), new WatchRootsResolver(), new DevSourceChangeClassifier(), new DevSourceRestartCoordinator(new DevelopmentGatewayNotifier(cliLogger), this.loggerFactory.createPerformanceDiagnostics("codemation-cli.performance"), cliLogger));
2612
+ return new DevSessionServices(consumerEnvLoader, sourceMapNodeOptions, new DevSessionPortsResolver(listenPortResolver, loopbackPortAllocator), loopbackPortAllocator, new DevHttpProbe(), new DevAuthSettingsLoader(new CodemationConsumerConfigLoader(), consumerEnvLoader), new DevNextHostEnvironmentBuilder(consumerEnvLoader, sourceMapNodeOptions), new WatchRootsResolver(), new DevSourceChangeClassifier());
1867
2613
  }
1868
2614
  };
1869
2615
 
@@ -2136,14 +2882,17 @@ var CliProgram = class {
2136
2882
  program.command("build").description("Build consumer workflows/plugins output and write the manifest.").option("--consumer-root <path>", "Path to the consumer project root (defaults to cwd)").option("--no-source-maps", "Disable .js.map files for emitted workflow modules (recommended for locked-down production bundles).").option("--target <es2020|es2022>", "ECMAScript language version for emitted workflow JavaScript (default: es2022).", "es2022").action(async (opts) => {
2137
2883
  await this.buildCommand.execute(resolveConsumerRoot(opts.consumerRoot), this.buildOptionsParser.parse(opts));
2138
2884
  });
2139
- program.command("dev", { isDefault: true }).description("Start the dev gateway and runtime child. Default consumer mode uses the packaged Codemation UI; use CODEMATION_DEV_MODE=framework for Next dev HMR when working on the host itself.").option("--consumer-root <path>", "Path to the consumer project root (defaults to cwd)").action(async (opts) => {
2140
- await this.devCommand.execute(resolveConsumerRoot(opts.consumerRoot));
2885
+ program.command("dev", { isDefault: true }).description("Start the stable dev endpoint and disposable API runtime. Uses the packaged Codemation UI by default.").option("--consumer-root <path>", "Path to the consumer project root (defaults to cwd)").option("--watch-framework", "Use Next dev HMR for framework UI work inside this repository.").action(async (opts) => {
2886
+ await this.devCommand.execute({
2887
+ consumerRoot: resolveConsumerRoot(opts.consumerRoot),
2888
+ watchFramework: opts.watchFramework === true
2889
+ });
2141
2890
  });
2142
- const serve = program.command("serve").description("Run production web or worker processes (no dev watchers).");
2143
- serve.command("web").description("Start the packaged Codemation web host.").option("--consumer-root <path>", "Path to the consumer project root (defaults to cwd)").option("--no-source-maps", "Disable .js.map files for emitted workflow modules when this command runs the consumer build step.").option("--target <es2020|es2022>", "ECMAScript language version for emitted workflow JavaScript when building consumer output (default: es2022).", "es2022").action(async (opts) => {
2891
+ const serve$1 = program.command("serve").description("Run production web or worker processes (no dev watchers).");
2892
+ serve$1.command("web").description("Start the packaged Codemation web host.").option("--consumer-root <path>", "Path to the consumer project root (defaults to cwd)").option("--no-source-maps", "Disable .js.map files for emitted workflow modules when this command runs the consumer build step.").option("--target <es2020|es2022>", "ECMAScript language version for emitted workflow JavaScript when building consumer output (default: es2022).", "es2022").action(async (opts) => {
2144
2893
  await this.serveWebCommand.execute(resolveConsumerRoot(opts.consumerRoot), this.buildOptionsParser.parse(opts));
2145
2894
  });
2146
- serve.command("worker").description("Start the Codemation worker process.").option("--consumer-root <path>", "Path to the consumer project root (defaults to cwd)").option("--config <path>", "Override path to codemation.config.ts / .js").action(async (opts) => {
2895
+ serve$1.command("worker").description("Start the Codemation worker process.").option("--consumer-root <path>", "Path to the consumer project root (defaults to cwd)").option("--config <path>", "Override path to codemation.config.ts / .js").action(async (opts) => {
2147
2896
  await this.serveWorkerCommand.execute(resolveConsumerRoot(opts.consumerRoot), opts.config);
2148
2897
  });
2149
2898
  program.command("db").description("Database utilities (PostgreSQL / Prisma).").command("migrate").description("Apply pending Prisma migrations using the consumer database URL (DATABASE_URL in `.env`, or CodemationConfig.runtime.database.url).").option("--consumer-root <path>", "Path to the consumer project root (defaults to cwd)").option("--config <path>", "Override path to codemation.config.ts / .js").action(async (opts) => {
@@ -2216,7 +2965,9 @@ var NextHostConsumerServerCommandFactory = class {
2216
2965
  args: [
2217
2966
  "exec",
2218
2967
  "next",
2219
- "start"
2968
+ "start",
2969
+ "-H",
2970
+ "127.0.0.1"
2220
2971
  ],
2221
2972
  cwd: args.nextHostRoot
2222
2973
  };
@@ -2288,22 +3039,20 @@ var CliDatabaseUrlDescriptor = class {
2288
3039
  //#endregion
2289
3040
  //#region src/bootstrap/CodemationCliApplicationSession.ts
2290
3041
  /**
2291
- * Opens a {@link CodemationApplication} with persistence + command/query buses (no HTTP/WebSocket servers),
3042
+ * Opens an app container with persistence + command/query buses (no HTTP/WebSocket servers),
2292
3043
  * for CLI tools that dispatch application commands or queries (e.g. user admin).
2293
3044
  */
2294
3045
  var CodemationCliApplicationSession = class CodemationCliApplicationSession {
2295
- constructor(application) {
2296
- this.application = application;
3046
+ constructor(container) {
3047
+ this.container = container;
2297
3048
  }
2298
3049
  static async open(args) {
2299
- const app = new CodemationApplication().useConfig(args.resolution.config);
2300
- await app.bootCli(new CodemationBootstrapRequest({
2301
- repoRoot: args.bootstrap.repoRoot,
2302
- consumerRoot: args.bootstrap.consumerRoot,
2303
- env: args.bootstrap.env,
2304
- workflowSources: args.resolution.workflowSources
2305
- }));
2306
- return new CodemationCliApplicationSession(app);
3050
+ const container = await new AppContainerFactory().create({
3051
+ appConfig: args.appConfig,
3052
+ sharedWorkflowWebsocketServer: null
3053
+ });
3054
+ if (args.appConfig.env.CODEMATION_SKIP_STARTUP_MIGRATIONS !== "true") await container.resolve(DatabaseMigrations).migrate();
3055
+ return new CodemationCliApplicationSession(container);
2307
3056
  }
2308
3057
  getPrismaClient() {
2309
3058
  const container = this.getContainer();
@@ -2317,10 +3066,10 @@ var CodemationCliApplicationSession = class CodemationCliApplicationSession {
2317
3066
  return this.getContainer().resolve(ApplicationTokens.QueryBus);
2318
3067
  }
2319
3068
  async close() {
2320
- await this.application.stop({ stopWebsocketServer: false });
3069
+ await this.container.resolve(AppContainerLifecycle).stop({ stopWebsocketServer: false });
2321
3070
  }
2322
3071
  getContainer() {
2323
- return this.application.getContainer();
3072
+ return this.container;
2324
3073
  }
2325
3074
  };
2326
3075
 
@@ -2330,37 +3079,26 @@ var CodemationCliApplicationSession = class CodemationCliApplicationSession {
2330
3079
  * Shared env/config/session wiring for `codemation user *` commands (local auth + database).
2331
3080
  */
2332
3081
  var UserAdminCliBootstrap = class {
2333
- constructor(configLoader, pathResolver, consumerDotenvLoader, tsconfigPreparation, databasePersistenceResolver) {
2334
- this.configLoader = configLoader;
3082
+ constructor(appConfigLoader, pathResolver, consumerDotenvLoader, tsconfigPreparation) {
3083
+ this.appConfigLoader = appConfigLoader;
2335
3084
  this.pathResolver = pathResolver;
2336
3085
  this.consumerDotenvLoader = consumerDotenvLoader;
2337
3086
  this.tsconfigPreparation = tsconfigPreparation;
2338
- this.databasePersistenceResolver = databasePersistenceResolver;
2339
3087
  }
2340
3088
  async withSession(options, fn) {
2341
3089
  const consumerRoot = options.consumerRoot ?? process.cwd();
2342
3090
  this.consumerDotenvLoader.load(consumerRoot);
2343
3091
  this.tsconfigPreparation.applyWorkspaceTsconfigForTsxIfPresent(consumerRoot);
2344
- const resolution = await this.configLoader.load({
3092
+ const paths = await this.pathResolver.resolve(consumerRoot);
3093
+ const loadResult = await this.appConfigLoader.load({
2345
3094
  consumerRoot,
2346
- configPathOverride: options.configPath
2347
- });
2348
- if (resolution.config.auth?.kind !== "local") throw new Error("Codemation user commands require CodemationConfig.auth.kind to be \"local\".");
2349
- if (this.databasePersistenceResolver.resolve({
2350
- runtimeConfig: resolution.config.runtime ?? {},
3095
+ repoRoot: paths.repoRoot,
2351
3096
  env: process.env,
2352
- consumerRoot
2353
- }).kind === "none") throw new Error("Database persistence is not configured. Set CodemationConfig.runtime.database (postgresql URL or PGlite).");
2354
- const paths = await this.pathResolver.resolve(consumerRoot);
2355
- const session = await CodemationCliApplicationSession.open({
2356
- resolution,
2357
- bootstrap: new CodemationBootstrapRequest({
2358
- repoRoot: paths.repoRoot,
2359
- consumerRoot,
2360
- env: process.env,
2361
- workflowSources: resolution.workflowSources
2362
- })
3097
+ configPathOverride: options.configPath
2363
3098
  });
3099
+ if (loadResult.appConfig.auth?.kind !== "local") throw new Error("Codemation user commands require CodemationConfig.auth.kind to be \"local\".");
3100
+ if (loadResult.appConfig.persistence.kind === "none") throw new Error("Database persistence is not configured. Set CodemationConfig.runtime.database (postgresql URL or PGlite).");
3101
+ const session = await CodemationCliApplicationSession.open({ appConfig: loadResult.appConfig });
2364
3102
  try {
2365
3103
  return await fn(session);
2366
3104
  } finally {
@@ -2414,6 +3152,7 @@ const loggerFactory = new ServerLoggerFactory(logLevelPolicyFactory);
2414
3152
  var CliProgramFactory = class {
2415
3153
  create() {
2416
3154
  const cliLogger = loggerFactory.create("codemation-cli");
3155
+ const appConfigLoader = new AppConfigLoader();
2417
3156
  const pathResolver = new CliPathResolver();
2418
3157
  const pluginDiscovery = new CodemationPluginDiscovery();
2419
3158
  const artifactsPublisher = new ConsumerBuildArtifactsPublisher();
@@ -2421,15 +3160,15 @@ var CliProgramFactory = class {
2421
3160
  const outputBuilderLoader = new ConsumerOutputBuilderLoader();
2422
3161
  const sourceMapNodeOptions = new SourceMapNodeOptions();
2423
3162
  const nextHostConsumerServerCommandFactory = new NextHostConsumerServerCommandFactory();
3163
+ const devSessionServices = new DevSessionServicesBuilder().build();
2424
3164
  const tsconfigPreparation = new ConsumerCliTsconfigPreparation();
2425
- const databasePersistenceResolver = new DatabasePersistenceResolver();
2426
- const userAdminBootstrap = new UserAdminCliBootstrap(new CodemationConsumerConfigLoader(), pathResolver, new UserAdminConsumerDotenvLoader(), tsconfigPreparation, databasePersistenceResolver);
3165
+ const userAdminBootstrap = new UserAdminCliBootstrap(appConfigLoader, pathResolver, new UserAdminConsumerDotenvLoader(), tsconfigPreparation);
2427
3166
  const hostPackageRoot = new HostPackageRootResolver().resolveHostPackageRoot();
2428
3167
  const userAdminCliOptionsParser = new UserAdminCliOptionsParser();
2429
3168
  const databaseMigrationsApplyService = new DatabaseMigrationsApplyService(cliLogger, new UserAdminConsumerDotenvLoader(), tsconfigPreparation, new CodemationConsumerConfigLoader(), new ConsumerDatabaseConnectionResolver(), new CliDatabaseUrlDescriptor(), hostPackageRoot, new PrismaMigrationDeployer());
2430
3169
  const buildOptionsParser = new ConsumerBuildOptionsParser();
2431
3170
  const devConsumerPublishBootstrap = new DevConsumerPublishBootstrap(cliLogger, pluginDiscovery, artifactsPublisher, outputBuilderLoader, buildOptionsParser);
2432
- return new CliProgram(buildOptionsParser, new BuildCommand(cliLogger, pathResolver, pluginDiscovery, artifactsPublisher, tsRuntime, outputBuilderLoader), new DevCommand(pathResolver, pluginDiscovery, tsRuntime, new DevLockFactory(), new DevSourceWatcherFactory(), cliLogger, new DevSessionServicesBuilder(loggerFactory).build(), databaseMigrationsApplyService, new DevBootstrapSummaryFetcher(), new DevCliBannerRenderer(), devConsumerPublishBootstrap, new ConsumerEnvDotenvFilePredicate(), new DevTrackedProcessTreeKiller(), nextHostConsumerServerCommandFactory), new ServeWebCommand(pathResolver, pluginDiscovery, artifactsPublisher, tsRuntime, sourceMapNodeOptions, outputBuilderLoader, new ConsumerEnvLoader(), new ListenPortResolver(), nextHostConsumerServerCommandFactory), new ServeWorkerCommand(sourceMapNodeOptions), new DbMigrateCommand(databaseMigrationsApplyService), new UserCreateCommand(new LocalUserCreator(userAdminBootstrap), userAdminCliOptionsParser), new UserListCommand(cliLogger, userAdminBootstrap, new CliDatabaseUrlDescriptor(), userAdminCliOptionsParser));
3171
+ return new CliProgram(buildOptionsParser, new BuildCommand(cliLogger, pathResolver, pluginDiscovery, artifactsPublisher, tsRuntime, outputBuilderLoader), new DevCommand(pathResolver, tsRuntime, new DevLockFactory(), new DevSourceWatcherFactory(), cliLogger, devSessionServices, databaseMigrationsApplyService, new DevBootstrapSummaryFetcher(), new DevCliBannerRenderer(), devConsumerPublishBootstrap, new ConsumerEnvDotenvFilePredicate(), new DevTrackedProcessTreeKiller(), nextHostConsumerServerCommandFactory, new DevApiRuntimeFactory(devSessionServices.loopbackPortAllocator, appConfigLoader, pluginDiscovery), new CliDevProxyServerFactory(), new DevRebuildQueueFactory()), new ServeWebCommand(pathResolver, new CodemationConsumerConfigLoader(), pluginDiscovery, artifactsPublisher, tsRuntime, sourceMapNodeOptions, outputBuilderLoader, new ConsumerEnvLoader(), new ListenPortResolver(), nextHostConsumerServerCommandFactory, new CodemationFrontendAuthSnapshotFactory(), new FrontendAppConfigJsonCodec()), new ServeWorkerCommand(pathResolver, appConfigLoader, new AppContainerFactory()), new DbMigrateCommand(databaseMigrationsApplyService), new UserCreateCommand(new LocalUserCreator(userAdminBootstrap), userAdminCliOptionsParser), new UserListCommand(cliLogger, userAdminBootstrap, new CliDatabaseUrlDescriptor(), userAdminCliOptionsParser));
2433
3172
  }
2434
3173
  };
2435
3174