@codemation/cli 0.0.5 → 0.0.7

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-Bx1lFBi5.js} +1041 -365
  3. package/dist/bin.js +1 -1
  4. package/dist/index.d.ts +644 -207
  5. package/dist/index.js +1 -1
  6. package/package.json +9 -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 +202 -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 +447 -0
  18. package/src/dev/CliDevProxyServerFactory.ts +7 -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/WatchRootsResolver.ts +6 -4
  33. package/src/runtime/NextHostConsumerServerCommandFactory.ts +11 -2
  34. package/src/runtime/TypeScriptRuntimeConfigurator.ts +7 -0
  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
10
  import { spawn } from "node:child_process";
10
- import { ApiPaths, ApplicationTokens, CodemationApplication, CodemationBootstrapRequest, ListUserAccountsQuery, PrismaClient, UpsertLocalBootstrapUserCommand } from "@codemation/host";
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
- }
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
309
281
  });
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}.`));
314
- });
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,111 @@ 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
+ proxyServer.setBuildStatus("building");
395
+ proxyServer.broadcastBuildStarted();
396
+ try {
397
+ if (request.shouldRepublishConsumerOutput) await this.devConsumerPublishBootstrap.ensurePublished(prepared.paths);
398
+ await this.stopCurrentRuntime(state, proxyServer);
399
+ process$1.stdout.write("[codemation] Waiting for runtime to accept traffic…\n");
400
+ const runtime = await this.createRuntime(prepared);
401
+ state.currentRuntime = runtime;
402
+ await proxyServer.activateRuntime({
403
+ httpPort: runtime.httpPort,
404
+ workflowWebSocketPort: runtime.workflowWebSocketPort
405
+ });
406
+ if (request.shouldRestartUi) await this.restartUiAfterSourceChange(prepared, state, gatewayBaseUrl);
407
+ await this.session.devHttpProbe.waitUntilBootstrapSummaryReady(gatewayBaseUrl);
408
+ const json = await this.devBootstrapSummaryFetcher.fetch(gatewayBaseUrl);
409
+ if (json) this.devCliBannerRenderer.renderCompact(json);
410
+ proxyServer.setBuildStatus("idle");
411
+ proxyServer.broadcastBuildCompleted(runtime.buildVersion);
412
+ process$1.stdout.write("[codemation] Runtime ready.\n");
413
+ } catch (error) {
414
+ proxyServer.setBuildStatus("idle");
415
+ proxyServer.broadcastBuildFailed(error instanceof Error ? error.message : String(error));
416
+ throw error;
417
+ }
418
+ }
419
+ async restartUiAfterSourceChange(prepared, state, gatewayBaseUrl) {
437
420
  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;
421
+ process$1.stdout.write("[codemation] Restarting the UI process to apply source changes…\n");
422
+ state.isRestartingUi = true;
440
423
  try {
441
- if (prepared.devMode === "consumer") {
442
- await this.restartConsumerUiProxy(prepared, state, refreshedAuthSettings);
424
+ if (prepared.devMode === "packaged-ui") {
425
+ await this.restartPackagedUi(prepared, state, refreshedAuthSettings);
443
426
  return;
444
427
  }
445
- await this.restartFrameworkNextHost(prepared, state, gatewayBaseUrl, refreshedAuthSettings);
428
+ await this.restartDevUi(prepared, state, gatewayBaseUrl, refreshedAuthSettings);
446
429
  } finally {
447
- state.isRestartingNextHost = false;
430
+ state.isRestartingUi = false;
448
431
  }
449
432
  }
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);
433
+ async restartPackagedUi(prepared, state, authSettings) {
434
+ if (state.currentPackagedUi && state.currentPackagedUi.exitCode === null && state.currentPackagedUi.signalCode === null) await this.devTrackedProcessTreeKiller.killProcessTreeRootedAt(state.currentPackagedUi);
435
+ state.currentPackagedUi = null;
436
+ const uiProxyBaseUrl = state.currentPackagedUiBaseUrl;
437
+ if (!uiProxyBaseUrl) throw new Error("Packaged UI proxy base URL is missing during UI restart.");
438
+ await this.spawnPackagedUi(prepared, state, authSettings, prepared.gatewayPort, uiProxyBaseUrl);
456
439
  }
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);
440
+ async restartDevUi(prepared, state, gatewayBaseUrl, authSettings) {
441
+ if (state.currentDevUi && state.currentDevUi.exitCode === null && state.currentDevUi.signalCode === null) await this.devTrackedProcessTreeKiller.killProcessTreeRootedAt(state.currentDevUi);
442
+ state.currentDevUi = null;
443
+ await this.spawnDevUi(prepared, state, gatewayBaseUrl, authSettings);
461
444
  }
462
- async failDevSessionAfterIrrecoverableSourceError(state, error) {
445
+ async failDevSessionAfterIrrecoverableSourceError(state, proxyServer, error) {
463
446
  const exception = error instanceof Error ? error : new Error(String(error));
464
447
  state.stopRequested = true;
465
- await this.stopLiveChildProcesses(state);
448
+ await this.stopLiveProcesses(state, proxyServer);
466
449
  state.stopReject?.(exception);
467
450
  }
468
- async stopLiveChildProcesses(state) {
451
+ async stopLiveProcesses(state, proxyServer) {
452
+ await this.stopCurrentRuntime(state, proxyServer);
469
453
  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);
454
+ for (const child of [state.currentPackagedUi, state.currentDevUi]) if (child && child.exitCode === null && child.signalCode === null) children.push(child);
475
455
  await Promise.all(children.map((child) => this.devTrackedProcessTreeKiller.killProcessTreeRootedAt(child)));
456
+ if (proxyServer) await proxyServer.stop();
457
+ }
458
+ async stopCurrentRuntime(state, proxyServer) {
459
+ const runtime = state.currentRuntime;
460
+ state.currentRuntime = null;
461
+ if (proxyServer) await proxyServer.activateRuntime(null);
462
+ if (runtime) await runtime.stop();
463
+ }
464
+ async createRuntime(prepared) {
465
+ const runtimeEnvironment = this.session.consumerEnvLoader.mergeIntoProcessEnvironment(process$1.env, prepared.consumerEnv);
466
+ return await this.devApiRuntimeFactory.create({
467
+ consumerRoot: prepared.paths.consumerRoot,
468
+ runtimeWorkingDirectory: process$1.cwd(),
469
+ env: {
470
+ ...runtimeEnvironment,
471
+ CODEMATION_DEV_SERVER_TOKEN: prepared.developmentServerToken,
472
+ CODEMATION_SKIP_STARTUP_MIGRATIONS: "true",
473
+ NODE_OPTIONS: this.session.sourceMapNodeOptions.appendToNodeOptions(process$1.env.NODE_OPTIONS),
474
+ WS_NO_BUFFER_UTIL: "1",
475
+ WS_NO_UTF_8_VALIDATE: "1"
476
+ }
477
+ });
476
478
  }
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.`);
479
+ logPackagedUiDevHintWhenNeeded(devMode, gatewayPort) {
480
+ if (devMode !== "packaged-ui") return;
481
+ 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
482
  }
481
483
  };
482
484
 
@@ -484,8 +486,9 @@ var DevCommand = class {
484
486
  //#region src/commands/ServeWebCommand.ts
485
487
  var ServeWebCommand = class {
486
488
  require = createRequire(import.meta.url);
487
- constructor(pathResolver, pluginDiscovery, artifactsPublisher, tsRuntime, sourceMapNodeOptions, outputBuilderLoader, envLoader, listenPortResolver, nextHostConsumerServerCommandFactory) {
489
+ constructor(pathResolver, configLoader, pluginDiscovery, artifactsPublisher, tsRuntime, sourceMapNodeOptions, outputBuilderLoader, envLoader, listenPortResolver, nextHostConsumerServerCommandFactory, frontendAuthSnapshotFactory, frontendAppConfigJsonCodec) {
488
490
  this.pathResolver = pathResolver;
491
+ this.configLoader = configLoader;
489
492
  this.pluginDiscovery = pluginDiscovery;
490
493
  this.artifactsPublisher = artifactsPublisher;
491
494
  this.tsRuntime = tsRuntime;
@@ -494,6 +497,8 @@ var ServeWebCommand = class {
494
497
  this.envLoader = envLoader;
495
498
  this.listenPortResolver = listenPortResolver;
496
499
  this.nextHostConsumerServerCommandFactory = nextHostConsumerServerCommandFactory;
500
+ this.frontendAuthSnapshotFactory = frontendAuthSnapshotFactory;
501
+ this.frontendAppConfigJsonCodec = frontendAppConfigJsonCodec;
497
502
  }
498
503
  async execute(consumerRoot, buildOptions) {
499
504
  const paths = await this.pathResolver.resolve(consumerRoot);
@@ -504,6 +509,15 @@ var ServeWebCommand = class {
504
509
  const nextHostRoot = path.dirname(this.require.resolve("@codemation/next-host/package.json"));
505
510
  const nextHostCommand = await this.nextHostConsumerServerCommandFactory.create({ nextHostRoot });
506
511
  const consumerEnv = this.envLoader.load(paths.consumerRoot);
512
+ const configResolution = await this.configLoader.load({ consumerRoot: paths.consumerRoot });
513
+ const frontendAuthSnapshot = this.frontendAuthSnapshotFactory.createFromResolvedInputs({
514
+ authConfig: configResolution.config.auth,
515
+ env: {
516
+ ...process$1.env,
517
+ ...consumerEnv
518
+ },
519
+ uiAuthEnabled: !(consumerEnv.NODE_ENV !== "production" && configResolution.config.auth?.allowUnauthenticatedInDevelopment === true)
520
+ });
507
521
  const nextPort = this.listenPortResolver.resolvePrimaryApplicationPort(process$1.env.PORT);
508
522
  const websocketPort = this.listenPortResolver.resolveWebsocketPortRelativeToHttp({
509
523
  nextPort,
@@ -517,6 +531,11 @@ var ServeWebCommand = class {
517
531
  ...process$1.env,
518
532
  ...consumerEnv,
519
533
  PORT: String(nextPort),
534
+ CODEMATION_FRONTEND_APP_CONFIG_JSON: this.frontendAppConfigJsonCodec.serialize({
535
+ auth: frontendAuthSnapshot,
536
+ productName: "Codemation",
537
+ logoUrl: null
538
+ }),
520
539
  CODEMATION_CONSUMER_OUTPUT_MANIFEST_PATH: manifest.manifestPath,
521
540
  CODEMATION_CONSUMER_ROOT: paths.consumerRoot,
522
541
  CODEMATION_WS_PORT: String(websocketPort),
@@ -542,34 +561,46 @@ var ServeWebCommand = class {
542
561
  //#endregion
543
562
  //#region src/commands/ServeWorkerCommand.ts
544
563
  var ServeWorkerCommand = class {
545
- require = createRequire(import.meta.url);
546
- constructor(sourceMapNodeOptions) {
547
- this.sourceMapNodeOptions = sourceMapNodeOptions;
564
+ constructor(pathResolver, appConfigLoader, appContainerFactory) {
565
+ this.pathResolver = pathResolver;
566
+ this.appConfigLoader = appConfigLoader;
567
+ this.appContainerFactory = appContainerFactory;
548
568
  }
549
569
  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
- }
570
+ const paths = await this.pathResolver.resolve(consumerRoot);
571
+ const loadResult = await this.appConfigLoader.load({
572
+ consumerRoot,
573
+ repoRoot: paths.repoRoot,
574
+ env: process$1.env,
575
+ configPathOverride
561
576
  });
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);
577
+ if (loadResult.appConfig.scheduler.kind !== "bullmq") throw new Error("Worker mode requires runtime.scheduler.kind = \"bullmq\".");
578
+ const container = await this.appContainerFactory.create({
579
+ appConfig: loadResult.appConfig,
580
+ sharedWorkflowWebsocketServer: null
581
+ });
582
+ const workerQueues = loadResult.appConfig.scheduler.workerQueues.length > 0 ? loadResult.appConfig.scheduler.workerQueues : ["default"];
583
+ const handle = await container.resolve(WorkerRuntime).start(workerQueues);
584
+ await new Promise((resolve) => {
585
+ this.bindSignals(handle.stop, resolve);
571
586
  });
572
587
  }
588
+ bindSignals(stop, resolve) {
589
+ let stopping = false;
590
+ const onSignal = async () => {
591
+ if (stopping) return;
592
+ stopping = true;
593
+ try {
594
+ await stop();
595
+ } finally {
596
+ resolve();
597
+ process$1.exit(0);
598
+ }
599
+ };
600
+ process$1.on("SIGINT", () => void onSignal());
601
+ process$1.on("SIGTERM", () => void onSignal());
602
+ process$1.on("SIGQUIT", () => void onSignal());
603
+ }
573
604
  };
574
605
 
575
606
  //#endregion
@@ -1270,13 +1301,36 @@ var ConsumerOutputBuilderLoader = class {
1270
1301
  * Resolves TCP PostgreSQL vs PGlite vs none from env + {@link CodemationConfig} (same rules as the host runtime).
1271
1302
  */
1272
1303
  var ConsumerDatabaseConnectionResolver = class {
1273
- resolver = new DatabasePersistenceResolver();
1274
1304
  resolve(processEnv, config$1, consumerRoot) {
1275
- return this.resolver.resolve({
1276
- runtimeConfig: config$1.runtime ?? {},
1277
- env: processEnv,
1278
- consumerRoot
1279
- });
1305
+ const database = config$1.runtime?.database;
1306
+ if (!database) return { kind: "none" };
1307
+ if (this.resolveDatabaseKind(database.kind, database.url, processEnv) === "postgresql") {
1308
+ const databaseUrl = database.url?.trim() ?? "";
1309
+ if (!databaseUrl) throw new Error("runtime.database.kind is \"postgresql\" but no database URL was set (runtime.database.url).");
1310
+ return {
1311
+ kind: "postgresql",
1312
+ databaseUrl
1313
+ };
1314
+ }
1315
+ return {
1316
+ kind: "pglite",
1317
+ dataDir: this.resolvePgliteDataDir(database.pgliteDataDir, processEnv, consumerRoot)
1318
+ };
1319
+ }
1320
+ resolveDatabaseKind(configuredKind, databaseUrl, env) {
1321
+ const kindFromEnv = env.CODEMATION_DATABASE_KIND?.trim();
1322
+ if (kindFromEnv === "postgresql" || kindFromEnv === "pglite") return kindFromEnv;
1323
+ if (configuredKind) return configuredKind;
1324
+ const trimmedUrl = databaseUrl?.trim();
1325
+ if (trimmedUrl && (trimmedUrl.startsWith("postgresql://") || trimmedUrl.startsWith("postgres://"))) return "postgresql";
1326
+ return "pglite";
1327
+ }
1328
+ resolvePgliteDataDir(configuredPath, env, consumerRoot) {
1329
+ const envPath = env.CODEMATION_PGLITE_DATA_DIR?.trim();
1330
+ if (envPath && envPath.length > 0) return path.isAbsolute(envPath) ? envPath : path.resolve(consumerRoot, envPath);
1331
+ const trimmedConfiguredPath = configuredPath?.trim();
1332
+ if (trimmedConfiguredPath && trimmedConfiguredPath.length > 0) return path.isAbsolute(trimmedConfiguredPath) ? trimmedConfiguredPath : path.resolve(consumerRoot, trimmedConfiguredPath);
1333
+ return path.resolve(consumerRoot, ".codemation", "pglite");
1280
1334
  }
1281
1335
  };
1282
1336
 
@@ -1352,7 +1406,7 @@ var HostPackageRootResolver = class {
1352
1406
  //#endregion
1353
1407
  //#region src/dev/DevBootstrapSummaryFetcher.ts
1354
1408
  /**
1355
- * Fetches {@link DevBootstrapSummaryJson} from the dev gateway (proxied to runtime-dev).
1409
+ * Fetches {@link DevBootstrapSummaryJson} from the stable CLI-owned dev endpoint.
1356
1410
  */
1357
1411
  var DevBootstrapSummaryFetcher = class {
1358
1412
  async fetch(gatewayBaseUrl) {
@@ -1502,6 +1556,710 @@ var DevConsumerPublishBootstrap = class {
1502
1556
  }
1503
1557
  };
1504
1558
 
1559
+ //#endregion
1560
+ //#region src/dev/CliDevProxyServer.ts
1561
+ var CliDevProxyServer = class {
1562
+ proxy = httpProxy.createProxyServer({
1563
+ ws: true,
1564
+ xfwd: true
1565
+ });
1566
+ devClients = /* @__PURE__ */ new Set();
1567
+ devWss = new WebSocketServer({ noServer: true });
1568
+ workflowClients = /* @__PURE__ */ new Set();
1569
+ workflowWss = new WebSocketServer({ noServer: true });
1570
+ roomIdsByWorkflowClient = /* @__PURE__ */ new Map();
1571
+ workflowClientCountByRoomId = /* @__PURE__ */ new Map();
1572
+ activeRuntime = null;
1573
+ activeBuildStatus = "idle";
1574
+ childWorkflowSocket = null;
1575
+ server = null;
1576
+ uiProxyTarget = null;
1577
+ constructor(listenPort) {
1578
+ this.listenPort = listenPort;
1579
+ }
1580
+ async start() {
1581
+ if (this.server) return;
1582
+ this.bindDevWebSocket();
1583
+ this.bindWorkflowWebSocket();
1584
+ this.proxy.on("error", (error, _req, res) => {
1585
+ if (res && "writeHead" in res && typeof res.writeHead === "function") {
1586
+ const serverResponse = res;
1587
+ if (!serverResponse.headersSent) {
1588
+ serverResponse.writeHead(502, { "content-type": "text/plain" });
1589
+ serverResponse.end(`Bad gateway: ${error.message}`);
1590
+ }
1591
+ }
1592
+ });
1593
+ const server = createServer((req, res) => {
1594
+ this.handleHttpRequest(req, res);
1595
+ });
1596
+ server.on("upgrade", (request, socket, head) => {
1597
+ this.handleUpgrade(request, socket, head);
1598
+ });
1599
+ await new Promise((resolve, reject) => {
1600
+ server.once("error", reject);
1601
+ server.listen(this.listenPort, "127.0.0.1", () => {
1602
+ resolve();
1603
+ });
1604
+ });
1605
+ this.server = server;
1606
+ }
1607
+ async stop() {
1608
+ await this.disconnectChildWorkflowSocket();
1609
+ this.activeRuntime = null;
1610
+ const server = this.server;
1611
+ this.server = null;
1612
+ for (const client of this.devClients) client.terminate();
1613
+ this.devClients.clear();
1614
+ for (const client of this.workflowClients) client.terminate();
1615
+ this.workflowClients.clear();
1616
+ this.roomIdsByWorkflowClient.clear();
1617
+ this.workflowClientCountByRoomId.clear();
1618
+ if (!server) return;
1619
+ await new Promise((resolve, reject) => {
1620
+ server.close((error) => {
1621
+ if (error) {
1622
+ reject(error);
1623
+ return;
1624
+ }
1625
+ resolve();
1626
+ });
1627
+ });
1628
+ }
1629
+ setUiProxyTarget(target) {
1630
+ this.uiProxyTarget = target?.trim() ? target.trim() : null;
1631
+ }
1632
+ async activateRuntime(target) {
1633
+ this.activeRuntime = target;
1634
+ await this.connectChildWorkflowSocket();
1635
+ }
1636
+ setBuildStatus(status) {
1637
+ this.activeBuildStatus = status;
1638
+ }
1639
+ broadcastBuildStarted() {
1640
+ this.broadcastDev({ kind: "devBuildStarted" });
1641
+ this.broadcastWorkflowLifecycleToSubscribedRooms((roomId) => ({
1642
+ kind: "devBuildStarted",
1643
+ workflowId: roomId
1644
+ }));
1645
+ }
1646
+ broadcastBuildCompleted(buildVersion) {
1647
+ this.broadcastDev({
1648
+ kind: "devBuildCompleted",
1649
+ buildVersion
1650
+ });
1651
+ this.broadcastWorkflowLifecycleToSubscribedRooms((roomId) => ({
1652
+ kind: "devBuildCompleted",
1653
+ workflowId: roomId,
1654
+ buildVersion
1655
+ }));
1656
+ }
1657
+ broadcastBuildFailed(message) {
1658
+ this.broadcastDev({
1659
+ kind: "devBuildFailed",
1660
+ message
1661
+ });
1662
+ this.broadcastWorkflowLifecycleToSubscribedRooms((roomId) => ({
1663
+ kind: "devBuildFailed",
1664
+ workflowId: roomId,
1665
+ message
1666
+ }));
1667
+ }
1668
+ bindDevWebSocket() {
1669
+ this.devWss.on("connection", (socket) => {
1670
+ this.devClients.add(socket);
1671
+ socket.on("close", () => {
1672
+ this.devClients.delete(socket);
1673
+ });
1674
+ });
1675
+ }
1676
+ bindWorkflowWebSocket() {
1677
+ this.workflowWss.on("connection", (socket) => {
1678
+ this.connectWorkflowClient(socket);
1679
+ });
1680
+ }
1681
+ async handleHttpRequest(req, res) {
1682
+ const pathname = this.safePathname(req.url ?? "");
1683
+ const uiProxyTarget = this.uiProxyTarget;
1684
+ const activeRuntimeTarget = this.activeRuntime ? `http://127.0.0.1:${this.activeRuntime.httpPort}` : void 0;
1685
+ if (pathname === "/api/dev/health" && req.method === "GET") {
1686
+ res.writeHead(200, { "content-type": "application/json" });
1687
+ res.end(JSON.stringify({
1688
+ ok: true,
1689
+ runtime: { status: this.activeRuntime ? this.activeBuildStatus === "building" ? "building" : "ready" : "stopped" }
1690
+ }));
1691
+ return;
1692
+ }
1693
+ if (pathname === "/api/dev/bootstrap-summary") {
1694
+ if (!activeRuntimeTarget) {
1695
+ res.writeHead(503, { "content-type": "text/plain" });
1696
+ res.end("Runtime is rebuilding.");
1697
+ return;
1698
+ }
1699
+ this.proxy.web(req, res, { target: activeRuntimeTarget });
1700
+ return;
1701
+ }
1702
+ if (uiProxyTarget && pathname.startsWith("/api/auth/")) {
1703
+ this.proxy.web(req, res, { target: uiProxyTarget.replace(/\/$/, "") });
1704
+ return;
1705
+ }
1706
+ if (pathname.startsWith("/api/")) {
1707
+ if (this.activeBuildStatus === "building" || !this.activeRuntime) {
1708
+ res.writeHead(503, { "content-type": "text/plain" });
1709
+ res.end("Runtime is rebuilding.");
1710
+ return;
1711
+ }
1712
+ this.proxy.web(req, res, { target: activeRuntimeTarget });
1713
+ return;
1714
+ }
1715
+ if (uiProxyTarget) {
1716
+ this.proxy.web(req, res, { target: uiProxyTarget.replace(/\/$/, "") });
1717
+ return;
1718
+ }
1719
+ res.writeHead(404, { "content-type": "text/plain" });
1720
+ res.end("Not found.");
1721
+ }
1722
+ handleUpgrade(request, socket, head) {
1723
+ const pathname = this.safePathname(request.url ?? "");
1724
+ if (pathname === ApiPaths.devGatewaySocket()) {
1725
+ this.devWss.handleUpgrade(request, socket, head, (ws) => {
1726
+ this.devWss.emit("connection", ws, request);
1727
+ });
1728
+ return;
1729
+ }
1730
+ if (pathname === ApiPaths.workflowWebsocket()) {
1731
+ this.workflowWss.handleUpgrade(request, socket, head, (ws) => {
1732
+ this.workflowWss.emit("connection", ws, request);
1733
+ });
1734
+ return;
1735
+ }
1736
+ const uiProxyTarget = this.uiProxyTarget;
1737
+ if (uiProxyTarget && !pathname.startsWith("/api/")) {
1738
+ this.proxy.ws(request, socket, head, { target: uiProxyTarget.replace(/\/$/, "") });
1739
+ return;
1740
+ }
1741
+ socket.destroy();
1742
+ }
1743
+ safePathname(url) {
1744
+ try {
1745
+ return new URL(url, "http://127.0.0.1").pathname;
1746
+ } catch {
1747
+ return url.split("?")[0] ?? url;
1748
+ }
1749
+ }
1750
+ broadcastDev(message) {
1751
+ const text = JSON.stringify(message);
1752
+ for (const client of this.devClients) if (client.readyState === WebSocket.OPEN) client.send(text);
1753
+ }
1754
+ broadcastWorkflowLifecycleToSubscribedRooms(createMessage) {
1755
+ for (const roomId of this.workflowClientCountByRoomId.keys()) this.broadcastWorkflowTextToRoom(roomId, JSON.stringify(createMessage(roomId)));
1756
+ }
1757
+ async connectWorkflowClient(socket) {
1758
+ this.workflowClients.add(socket);
1759
+ this.roomIdsByWorkflowClient.set(socket, /* @__PURE__ */ new Set());
1760
+ socket.send(JSON.stringify({ kind: "ready" }));
1761
+ socket.on("message", (rawData) => {
1762
+ this.handleWorkflowClientMessage(socket, rawData);
1763
+ });
1764
+ socket.on("close", () => {
1765
+ this.disconnectWorkflowClient(socket);
1766
+ });
1767
+ socket.on("error", () => {
1768
+ this.disconnectWorkflowClient(socket);
1769
+ });
1770
+ }
1771
+ disconnectWorkflowClient(socket) {
1772
+ const roomIds = this.roomIdsByWorkflowClient.get(socket);
1773
+ if (roomIds) for (const roomId of roomIds) this.releaseWorkflowRoom(roomId);
1774
+ this.roomIdsByWorkflowClient.delete(socket);
1775
+ this.workflowClients.delete(socket);
1776
+ }
1777
+ async handleWorkflowClientMessage(socket, rawData) {
1778
+ try {
1779
+ const message = this.parseWorkflowClientMessage(rawData);
1780
+ if (message.kind === "subscribe") {
1781
+ const roomIds$1 = this.roomIdsByWorkflowClient.get(socket);
1782
+ if (!roomIds$1) return;
1783
+ if (!roomIds$1.has(message.roomId)) {
1784
+ roomIds$1.add(message.roomId);
1785
+ this.retainWorkflowRoom(message.roomId);
1786
+ }
1787
+ socket.send(JSON.stringify({
1788
+ kind: "subscribed",
1789
+ roomId: message.roomId
1790
+ }));
1791
+ return;
1792
+ }
1793
+ const roomIds = this.roomIdsByWorkflowClient.get(socket);
1794
+ if (!roomIds) return;
1795
+ if (roomIds.delete(message.roomId)) this.releaseWorkflowRoom(message.roomId);
1796
+ socket.send(JSON.stringify({
1797
+ kind: "unsubscribed",
1798
+ roomId: message.roomId
1799
+ }));
1800
+ } catch (error) {
1801
+ const exception = error instanceof Error ? error : new Error(String(error));
1802
+ if (socket.readyState === WebSocket.OPEN) socket.send(JSON.stringify({
1803
+ kind: "error",
1804
+ message: exception.message
1805
+ }));
1806
+ }
1807
+ }
1808
+ parseWorkflowClientMessage(rawData) {
1809
+ const value = typeof rawData === "string" ? rawData : Buffer.isBuffer(rawData) ? rawData.toString("utf8") : "";
1810
+ const message = JSON.parse(value);
1811
+ if (message.kind === "subscribe" && typeof message.roomId === "string") return {
1812
+ kind: "subscribe",
1813
+ roomId: message.roomId
1814
+ };
1815
+ if (message.kind === "unsubscribe" && typeof message.roomId === "string") return {
1816
+ kind: "unsubscribe",
1817
+ roomId: message.roomId
1818
+ };
1819
+ throw new Error("Unsupported websocket client message.");
1820
+ }
1821
+ retainWorkflowRoom(roomId) {
1822
+ const nextCount = (this.workflowClientCountByRoomId.get(roomId) ?? 0) + 1;
1823
+ this.workflowClientCountByRoomId.set(roomId, nextCount);
1824
+ if (nextCount === 1) this.sendToChildWorkflowSocket({
1825
+ kind: "subscribe",
1826
+ roomId
1827
+ });
1828
+ }
1829
+ releaseWorkflowRoom(roomId) {
1830
+ const currentCount = this.workflowClientCountByRoomId.get(roomId) ?? 0;
1831
+ if (currentCount <= 1) {
1832
+ this.workflowClientCountByRoomId.delete(roomId);
1833
+ this.sendToChildWorkflowSocket({
1834
+ kind: "unsubscribe",
1835
+ roomId
1836
+ });
1837
+ return;
1838
+ }
1839
+ this.workflowClientCountByRoomId.set(roomId, currentCount - 1);
1840
+ }
1841
+ sendToChildWorkflowSocket(message) {
1842
+ if (!this.childWorkflowSocket || this.childWorkflowSocket.readyState !== WebSocket.OPEN) return;
1843
+ this.childWorkflowSocket.send(JSON.stringify(message));
1844
+ }
1845
+ async connectChildWorkflowSocket() {
1846
+ await this.disconnectChildWorkflowSocket();
1847
+ if (!this.activeRuntime || this.activeBuildStatus === "building") return;
1848
+ const childWorkflowSocket = await this.openChildWorkflowSocket(this.activeRuntime.workflowWebSocketPort);
1849
+ this.childWorkflowSocket = childWorkflowSocket;
1850
+ childWorkflowSocket.on("message", (rawData) => {
1851
+ this.handleChildWorkflowSocketMessage(rawData);
1852
+ });
1853
+ childWorkflowSocket.on("close", () => {
1854
+ if (this.childWorkflowSocket === childWorkflowSocket) this.childWorkflowSocket = null;
1855
+ });
1856
+ childWorkflowSocket.on("error", () => {
1857
+ if (this.childWorkflowSocket === childWorkflowSocket) this.childWorkflowSocket = null;
1858
+ });
1859
+ for (const roomId of this.workflowClientCountByRoomId.keys()) this.sendToChildWorkflowSocket({
1860
+ kind: "subscribe",
1861
+ roomId
1862
+ });
1863
+ }
1864
+ openChildWorkflowSocket(workflowWebSocketPort) {
1865
+ return new Promise((resolve, reject) => {
1866
+ const socket = new WebSocket(`ws://127.0.0.1:${workflowWebSocketPort}${ApiPaths.workflowWebsocket()}`);
1867
+ socket.once("open", () => {
1868
+ resolve(socket);
1869
+ });
1870
+ socket.once("error", (error) => {
1871
+ socket.close();
1872
+ reject(error);
1873
+ });
1874
+ });
1875
+ }
1876
+ async disconnectChildWorkflowSocket() {
1877
+ if (!this.childWorkflowSocket) return;
1878
+ const socket = this.childWorkflowSocket;
1879
+ this.childWorkflowSocket = null;
1880
+ await new Promise((resolve) => {
1881
+ socket.once("close", () => {
1882
+ resolve();
1883
+ });
1884
+ socket.close();
1885
+ });
1886
+ }
1887
+ handleChildWorkflowSocketMessage(rawData) {
1888
+ const text = typeof rawData === "string" ? rawData : Buffer.isBuffer(rawData) ? rawData.toString("utf8") : "";
1889
+ if (text.trim().length === 0) return;
1890
+ try {
1891
+ const message = JSON.parse(text);
1892
+ if (message.kind === "event" && typeof message.event?.workflowId === "string") {
1893
+ this.broadcastWorkflowTextToRoom(message.event.workflowId, text);
1894
+ return;
1895
+ }
1896
+ if ((message.kind === "workflowChanged" || message.kind === "devBuildStarted" || message.kind === "devBuildCompleted" || message.kind === "devBuildFailed") && typeof message.workflowId === "string") {
1897
+ this.broadcastWorkflowTextToRoom(message.workflowId, text);
1898
+ return;
1899
+ }
1900
+ if (message.kind === "error" && typeof message.message === "string") this.broadcastWorkflowTextToAll(text);
1901
+ } catch {}
1902
+ }
1903
+ broadcastWorkflowTextToRoom(roomId, text) {
1904
+ for (const [client, roomIds] of this.roomIdsByWorkflowClient) {
1905
+ if (client.readyState !== WebSocket.OPEN || !roomIds.has(roomId)) continue;
1906
+ client.send(text);
1907
+ }
1908
+ }
1909
+ broadcastWorkflowTextToAll(text) {
1910
+ for (const client of this.workflowClients) if (client.readyState === WebSocket.OPEN) client.send(text);
1911
+ }
1912
+ };
1913
+
1914
+ //#endregion
1915
+ //#region src/dev/CliDevProxyServerFactory.ts
1916
+ var CliDevProxyServerFactory = class {
1917
+ create(gatewayPort) {
1918
+ return new CliDevProxyServer(gatewayPort);
1919
+ }
1920
+ };
1921
+
1922
+ //#endregion
1923
+ //#region ../host/src/presentation/server/CodemationTsyringeParamInfoReader.ts
1924
+ var CodemationTsyringeParamInfoReader = class {
1925
+ static injectionTokenMetadataKey = "injectionTokens";
1926
+ static designParamTypesMetadataKey = "design:paramtypes";
1927
+ static read(target) {
1928
+ const designParamTypes = this.readDesignParamTypes(target);
1929
+ const injectionTokens = this.readInjectionTokens(target);
1930
+ Object.keys(injectionTokens).forEach((key) => {
1931
+ designParamTypes[Number(key)] = injectionTokens[key];
1932
+ });
1933
+ return designParamTypes;
1934
+ }
1935
+ static readDesignParamTypes(target) {
1936
+ const reflected = Reflect.getMetadata?.(this.designParamTypesMetadataKey, target);
1937
+ return Array.isArray(reflected) ? [...reflected] : [];
1938
+ }
1939
+ static readInjectionTokens(target) {
1940
+ const reflected = Reflect.getOwnMetadata?.(this.injectionTokenMetadataKey, target);
1941
+ if (!reflected || typeof reflected !== "object") return {};
1942
+ return reflected;
1943
+ }
1944
+ };
1945
+
1946
+ //#endregion
1947
+ //#region ../host/src/presentation/server/CodemationTsyringeTypeInfoRegistrar.ts
1948
+ var CodemationTsyringeTypeInfoRegistrar = class {
1949
+ visitedTokens = /* @__PURE__ */ new Set();
1950
+ visitedConfigObjects = /* @__PURE__ */ new Set();
1951
+ constructor(container) {
1952
+ this.container = container;
1953
+ }
1954
+ registerWorkflowDefinitions(workflows) {
1955
+ for (const workflow of workflows) for (const node of workflow.nodes) {
1956
+ this.registerTypeToken(node.type);
1957
+ this.registerConfigTokens(node.config);
1958
+ }
1959
+ }
1960
+ registerTypeToken(token) {
1961
+ if (typeof token !== "function" || this.visitedTokens.has(token)) return;
1962
+ this.visitedTokens.add(token);
1963
+ const paramInfo = CodemationTsyringeParamInfoReader.read(token);
1964
+ for (const dependency of paramInfo) this.registerDependency(dependency);
1965
+ this.registerFactoryProvider(token, paramInfo);
1966
+ }
1967
+ registerDependency(dependency) {
1968
+ const token = this.resolveDependencyToken(dependency);
1969
+ if (typeof token !== "function") return;
1970
+ if (!this.container.isRegistered(token, true)) return;
1971
+ this.registerTypeToken(token);
1972
+ }
1973
+ registerConfigTokens(value) {
1974
+ if (Array.isArray(value)) {
1975
+ value.forEach((entry) => this.registerConfigTokens(entry));
1976
+ return;
1977
+ }
1978
+ if (!value || typeof value !== "object") return;
1979
+ if (this.visitedConfigObjects.has(value)) return;
1980
+ this.visitedConfigObjects.add(value);
1981
+ if ("type" in value && typeof value.type === "function") this.registerTypeToken(value.type);
1982
+ Object.values(value).forEach((entry) => this.registerConfigTokens(entry));
1983
+ }
1984
+ registerFactoryProvider(token, paramInfo) {
1985
+ if (this.container.isRegistered(token, true)) return;
1986
+ const classToken = token;
1987
+ const constructorToken = token;
1988
+ this.container.register(classToken, { useFactory: (dependencyContainer) => {
1989
+ return new constructorToken(...paramInfo.map((dependency) => this.resolveFactoryDependency(dependencyContainer, dependency)));
1990
+ } });
1991
+ }
1992
+ resolveDependencyToken(dependency) {
1993
+ if (this.isInjectionDescriptor(dependency)) return dependency.token;
1994
+ return dependency;
1995
+ }
1996
+ resolveFactoryDependency(dependencyContainer, dependency) {
1997
+ const token = this.resolveDependencyToken(dependency);
1998
+ if (typeof token === "function") {
1999
+ if (dependencyContainer.isRegistered(token, true)) try {
2000
+ return dependencyContainer.resolve(token);
2001
+ } catch (error) {
2002
+ if (!this.isMissingTypeInfoError(error)) throw error;
2003
+ }
2004
+ this.registerTypeToken(token);
2005
+ return new token(...CodemationTsyringeParamInfoReader.read(token).map((entry) => this.resolveFactoryDependency(dependencyContainer, entry)));
2006
+ }
2007
+ return dependencyContainer.resolve(token);
2008
+ }
2009
+ isInjectionDescriptor(value) {
2010
+ return value !== null && typeof value === "object" && "token" in value;
2011
+ }
2012
+ isMissingTypeInfoError(error) {
2013
+ return error instanceof Error && error.message.includes("TypeInfo not known for");
2014
+ }
2015
+ };
2016
+
2017
+ //#endregion
2018
+ //#region src/dev/DevApiRuntimeHost.ts
2019
+ var DevApiRuntimeHost = class {
2020
+ pluginListMerger = new CodemationPluginListMerger();
2021
+ contextPromise = null;
2022
+ constructor(configLoader, pluginDiscovery, args) {
2023
+ this.configLoader = configLoader;
2024
+ this.pluginDiscovery = pluginDiscovery;
2025
+ this.args = args;
2026
+ }
2027
+ async prepare() {
2028
+ if (!this.contextPromise) this.contextPromise = this.createContext();
2029
+ return await this.contextPromise;
2030
+ }
2031
+ async stop() {
2032
+ const contextPromise = this.contextPromise;
2033
+ this.contextPromise = null;
2034
+ if (!contextPromise) return;
2035
+ await (await contextPromise).container.resolve(AppContainerLifecycle$1).stop();
2036
+ }
2037
+ async createContext() {
2038
+ const consumerRoot = path.resolve(this.args.consumerRoot);
2039
+ const repoRoot = await this.detectWorkspaceRoot(consumerRoot);
2040
+ const prismaCliOverride = await this.resolvePrismaCliOverride();
2041
+ const hostPackageRoot = path.resolve(repoRoot, "packages", "host");
2042
+ const env = { ...this.args.env };
2043
+ if (prismaCliOverride) env.CODEMATION_PRISMA_CLI_PATH = prismaCliOverride;
2044
+ env.CODEMATION_HOST_PACKAGE_ROOT = hostPackageRoot;
2045
+ env.CODEMATION_PRISMA_CONFIG_PATH = path.resolve(hostPackageRoot, "prisma.config.ts");
2046
+ env.CODEMATION_CONSUMER_ROOT = consumerRoot;
2047
+ const configResolution = await this.configLoader.load({
2048
+ consumerRoot,
2049
+ repoRoot,
2050
+ env
2051
+ });
2052
+ const discoveredPlugins = await this.loadDiscoveredPlugins(consumerRoot);
2053
+ const appConfig = {
2054
+ ...configResolution.appConfig,
2055
+ env,
2056
+ plugins: discoveredPlugins.length > 0 ? this.pluginListMerger.merge(configResolution.appConfig.plugins, discoveredPlugins) : configResolution.appConfig.plugins
2057
+ };
2058
+ const container = await new AppContainerFactory$1().create({
2059
+ appConfig,
2060
+ sharedWorkflowWebsocketServer: null
2061
+ });
2062
+ new CodemationTsyringeTypeInfoRegistrar(container).registerWorkflowDefinitions(appConfig.workflows ?? []);
2063
+ await container.resolve(FrontendRuntime).start();
2064
+ return {
2065
+ buildVersion: this.createBuildVersion(),
2066
+ container,
2067
+ consumerRoot,
2068
+ repoRoot,
2069
+ workflowIds: appConfig.workflows.map((workflow) => workflow.id),
2070
+ workflowSources: appConfig.workflowSources
2071
+ };
2072
+ }
2073
+ async loadDiscoveredPlugins(consumerRoot) {
2074
+ return (await this.pluginDiscovery.resolvePlugins(consumerRoot)).map((resolvedPackage) => resolvedPackage.plugin);
2075
+ }
2076
+ async detectWorkspaceRoot(startDirectory) {
2077
+ let currentDirectory = path.resolve(startDirectory);
2078
+ while (true) {
2079
+ if (await this.exists(path.resolve(currentDirectory, "pnpm-workspace.yaml"))) return currentDirectory;
2080
+ const parentDirectory = path.dirname(currentDirectory);
2081
+ if (parentDirectory === currentDirectory) return startDirectory;
2082
+ currentDirectory = parentDirectory;
2083
+ }
2084
+ }
2085
+ async resolvePrismaCliOverride() {
2086
+ const candidate = path.resolve(this.args.runtimeWorkingDirectory, "node_modules", "prisma", "build", "index.js");
2087
+ return await this.exists(candidate) ? candidate : null;
2088
+ }
2089
+ createBuildVersion() {
2090
+ return `${Date.now()}-${process$1.pid}`;
2091
+ }
2092
+ async exists(filePath) {
2093
+ try {
2094
+ await access(filePath);
2095
+ return true;
2096
+ } catch {
2097
+ return false;
2098
+ }
2099
+ }
2100
+ };
2101
+
2102
+ //#endregion
2103
+ //#region src/dev/DevApiRuntimeServer.ts
2104
+ var DevApiRuntimeServer = class {
2105
+ bootstrapLogger = new ServerLoggerFactory(logLevelPolicyFactory).create("codemation-cli.dev-runtime");
2106
+ server = null;
2107
+ constructor(httpPort, workflowWebSocketPort, host) {
2108
+ this.httpPort = httpPort;
2109
+ this.workflowWebSocketPort = workflowWebSocketPort;
2110
+ this.host = host;
2111
+ }
2112
+ async start() {
2113
+ const root = new Hono();
2114
+ root.get("/health", (c) => c.json({ ok: true }));
2115
+ root.all("*", async (c) => {
2116
+ return (await this.host.prepare()).container.resolve(CodemationHonoApiApp).fetch(c.req.raw);
2117
+ });
2118
+ await this.listen(root);
2119
+ try {
2120
+ return await this.host.prepare();
2121
+ } catch (error) {
2122
+ await this.stop();
2123
+ throw error;
2124
+ }
2125
+ }
2126
+ async stop() {
2127
+ const server = this.server;
2128
+ this.server = null;
2129
+ const failures = [];
2130
+ if (server) try {
2131
+ await this.closeServer(server);
2132
+ } catch (error) {
2133
+ failures.push(this.normalizeError(error));
2134
+ }
2135
+ try {
2136
+ await this.host.stop();
2137
+ } catch (error) {
2138
+ failures.push(this.normalizeError(error));
2139
+ }
2140
+ if (failures.length > 0) throw failures[0];
2141
+ }
2142
+ async listen(root) {
2143
+ if (this.server) return;
2144
+ await new Promise((resolve, reject) => {
2145
+ let resolved = false;
2146
+ const server = serve({
2147
+ fetch: root.fetch,
2148
+ port: this.httpPort,
2149
+ hostname: "127.0.0.1"
2150
+ }, () => {
2151
+ resolved = true;
2152
+ this.server = server;
2153
+ resolve();
2154
+ });
2155
+ server.on("error", (error) => {
2156
+ if (resolved) {
2157
+ this.bootstrapLogger.error("runtime HTTP server error", this.normalizeError(error));
2158
+ return;
2159
+ }
2160
+ reject(error);
2161
+ });
2162
+ });
2163
+ this.bootstrapLogger.debug(`runtime listening httpPort=${this.httpPort} workflowWebSocketPort=${this.workflowWebSocketPort}`);
2164
+ }
2165
+ closeServer(server) {
2166
+ return new Promise((resolve, reject) => {
2167
+ server.close((error) => {
2168
+ if (error) {
2169
+ reject(error);
2170
+ return;
2171
+ }
2172
+ resolve();
2173
+ });
2174
+ });
2175
+ }
2176
+ normalizeError(error) {
2177
+ return error instanceof Error ? error : new Error(String(error));
2178
+ }
2179
+ };
2180
+
2181
+ //#endregion
2182
+ //#region src/dev/DevApiRuntimeFactory.ts
2183
+ var DevApiRuntimeFactory = class {
2184
+ constructor(portAllocator, configLoader, pluginDiscovery) {
2185
+ this.portAllocator = portAllocator;
2186
+ this.configLoader = configLoader;
2187
+ this.pluginDiscovery = pluginDiscovery;
2188
+ }
2189
+ async create(args) {
2190
+ const httpPort = await this.portAllocator.allocate();
2191
+ const workflowWebSocketPort = await this.portAllocator.allocate();
2192
+ const runtime = new DevApiRuntimeServer(httpPort, workflowWebSocketPort, new DevApiRuntimeHost(this.configLoader, this.pluginDiscovery, {
2193
+ consumerRoot: args.consumerRoot,
2194
+ env: {
2195
+ ...args.env,
2196
+ CODEMATION_WS_PORT: String(workflowWebSocketPort),
2197
+ NEXT_PUBLIC_CODEMATION_WS_PORT: String(workflowWebSocketPort)
2198
+ },
2199
+ runtimeWorkingDirectory: args.runtimeWorkingDirectory
2200
+ }));
2201
+ const context = await runtime.start();
2202
+ return {
2203
+ buildVersion: context.buildVersion,
2204
+ httpPort,
2205
+ stop: async () => {
2206
+ await runtime.stop();
2207
+ },
2208
+ workflowIds: context.workflowIds,
2209
+ workflowWebSocketPort
2210
+ };
2211
+ }
2212
+ };
2213
+
2214
+ //#endregion
2215
+ //#region src/dev/DevRebuildQueue.ts
2216
+ var DevRebuildQueue = class {
2217
+ pendingRequest = null;
2218
+ drainPromise = null;
2219
+ constructor(handler) {
2220
+ this.handler = handler;
2221
+ }
2222
+ async enqueue(request) {
2223
+ this.pendingRequest = this.mergePendingRequest(this.pendingRequest, request);
2224
+ if (!this.drainPromise) this.drainPromise = this.drain();
2225
+ return await this.drainPromise;
2226
+ }
2227
+ async drain() {
2228
+ try {
2229
+ while (this.pendingRequest) {
2230
+ const nextRequest = this.pendingRequest;
2231
+ this.pendingRequest = null;
2232
+ await this.handler.run(nextRequest);
2233
+ }
2234
+ } finally {
2235
+ this.drainPromise = null;
2236
+ if (this.pendingRequest) {
2237
+ this.drainPromise = this.drain();
2238
+ await this.drainPromise;
2239
+ }
2240
+ }
2241
+ }
2242
+ mergePendingRequest(current, next) {
2243
+ if (!current) return {
2244
+ ...next,
2245
+ changedPaths: [...next.changedPaths]
2246
+ };
2247
+ return {
2248
+ changedPaths: [...new Set([...current.changedPaths, ...next.changedPaths])],
2249
+ shouldRepublishConsumerOutput: current.shouldRepublishConsumerOutput || next.shouldRepublishConsumerOutput,
2250
+ shouldRestartUi: current.shouldRestartUi || next.shouldRestartUi
2251
+ };
2252
+ }
2253
+ };
2254
+
2255
+ //#endregion
2256
+ //#region src/dev/DevRebuildQueueFactory.ts
2257
+ var DevRebuildQueueFactory = class {
2258
+ create(handler) {
2259
+ return new DevRebuildQueue(handler);
2260
+ }
2261
+ };
2262
+
1505
2263
  //#endregion
1506
2264
  //#region src/runtime/ListenPortResolver.ts
1507
2265
  /**
@@ -1536,36 +2294,13 @@ var SourceMapNodeOptions = class {
1536
2294
  }
1537
2295
  };
1538
2296
 
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
2297
  //#endregion
1564
2298
  //#region src/dev/DevAuthSettingsLoader.ts
1565
2299
  var DevAuthSettingsLoader = class DevAuthSettingsLoader {
1566
2300
  static defaultDevelopmentAuthSecret = "codemation-dev-auth-secret-not-for-production";
1567
- constructor(configLoader) {
2301
+ constructor(configLoader, consumerEnvLoader) {
1568
2302
  this.configLoader = configLoader;
2303
+ this.consumerEnvLoader = consumerEnvLoader;
1569
2304
  }
1570
2305
  resolveDevelopmentServerToken(rawToken) {
1571
2306
  if (rawToken && rawToken.trim().length > 0) return rawToken;
@@ -1573,14 +2308,15 @@ var DevAuthSettingsLoader = class DevAuthSettingsLoader {
1573
2308
  }
1574
2309
  async loadForConsumer(consumerRoot) {
1575
2310
  const resolution = await this.configLoader.load({ consumerRoot });
2311
+ const envForAuthSecret = this.consumerEnvLoader.mergeConsumerRootIntoProcessEnvironment(consumerRoot, process.env);
1576
2312
  return {
1577
2313
  authConfigJson: JSON.stringify(resolution.config.auth ?? null),
1578
- authSecret: this.resolveDevelopmentAuthSecret(process.env),
2314
+ authSecret: this.resolveDevelopmentAuthSecret(envForAuthSecret),
1579
2315
  skipUiAuth: resolution.config.auth?.allowUnauthenticatedInDevelopment === true
1580
2316
  };
1581
2317
  }
1582
2318
  resolveDevelopmentAuthSecret(env) {
1583
- const configuredSecret = env.AUTH_SECRET ?? env.NEXTAUTH_SECRET;
2319
+ const configuredSecret = env.AUTH_SECRET;
1584
2320
  if (configuredSecret && configuredSecret.trim().length > 0) return configuredSecret;
1585
2321
  return DevAuthSettingsLoader.defaultDevelopmentAuthSecret;
1586
2322
  }
@@ -1607,10 +2343,10 @@ var DevHttpProbe = class {
1607
2343
  } catch {}
1608
2344
  await setTimeout$1(50);
1609
2345
  }
1610
- throw new Error("Timed out waiting for dev gateway HTTP health check.");
2346
+ throw new Error("Timed out waiting for the stable dev HTTP health check.");
1611
2347
  }
1612
2348
  /**
1613
- * Polls until the runtime child serves bootstrap summary (after gateway is up, the disposable runtime may still be wiring).
2349
+ * Polls until the active disposable runtime serves bootstrap summary through the stable CLI dev endpoint.
1614
2350
  */
1615
2351
  async waitUntilBootstrapSummaryReady(gatewayBaseUrl) {
1616
2352
  const url = `${gatewayBaseUrl.replace(/\/$/, "")}/api/dev/bootstrap-summary`;
@@ -1630,14 +2366,17 @@ var DevHttpProbe = class {
1630
2366
  //#endregion
1631
2367
  //#region src/dev/DevNextHostEnvironmentBuilder.ts
1632
2368
  var DevNextHostEnvironmentBuilder = class {
1633
- constructor(consumerEnvLoader, sourceMapNodeOptions) {
2369
+ constructor(consumerEnvLoader, sourceMapNodeOptions, frontendAuthSnapshotFactory = new CodemationFrontendAuthSnapshotFactory$1(), frontendAppConfigJsonCodec = new FrontendAppConfigJsonCodec$1()) {
1634
2370
  this.consumerEnvLoader = consumerEnvLoader;
1635
2371
  this.sourceMapNodeOptions = sourceMapNodeOptions;
2372
+ this.frontendAuthSnapshotFactory = frontendAuthSnapshotFactory;
2373
+ this.frontendAppConfigJsonCodec = frontendAppConfigJsonCodec;
1636
2374
  }
1637
2375
  buildConsumerUiProxy(args) {
1638
2376
  return {
1639
2377
  ...this.build({
1640
2378
  authConfigJson: args.authConfigJson,
2379
+ authSecret: args.authSecret,
1641
2380
  consumerRoot: args.consumerRoot,
1642
2381
  developmentServerToken: args.developmentServerToken,
1643
2382
  nextPort: args.nextPort,
@@ -1646,23 +2385,33 @@ var DevNextHostEnvironmentBuilder = class {
1646
2385
  websocketPort: args.websocketPort,
1647
2386
  consumerOutputManifestPath: args.consumerOutputManifestPath
1648
2387
  }),
2388
+ HOSTNAME: "127.0.0.1",
1649
2389
  AUTH_SECRET: args.authSecret,
1650
- AUTH_URL: args.publicBaseUrl,
1651
- NEXTAUTH_SECRET: args.authSecret,
1652
- NEXTAUTH_URL: args.publicBaseUrl
2390
+ AUTH_URL: args.publicBaseUrl
1653
2391
  };
1654
2392
  }
1655
2393
  build(args) {
1656
2394
  const merged = this.consumerEnvLoader.mergeConsumerRootIntoProcessEnvironment(args.consumerRoot, process$1.env);
1657
2395
  const manifestPath = args.consumerOutputManifestPath ?? path.resolve(args.consumerRoot, ".codemation", "output", "current.json");
2396
+ const authSecret = args.authSecret ?? merged.AUTH_SECRET;
2397
+ const authSnapshot = this.frontendAuthSnapshotFactory.createFromResolvedInputs({
2398
+ authConfig: this.parseAuthConfig(args.authConfigJson),
2399
+ env: {
2400
+ ...merged,
2401
+ ...typeof authSecret === "string" && authSecret.trim().length > 0 ? { AUTH_SECRET: authSecret } : {}
2402
+ },
2403
+ uiAuthEnabled: !args.skipUiAuth
2404
+ });
1658
2405
  return {
1659
2406
  ...merged,
1660
2407
  PORT: String(args.nextPort),
1661
- CODEMATION_AUTH_CONFIG_JSON: args.authConfigJson,
1662
2408
  CODEMATION_CONSUMER_ROOT: args.consumerRoot,
1663
2409
  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",
2410
+ CODEMATION_FRONTEND_APP_CONFIG_JSON: this.frontendAppConfigJsonCodec.serialize({
2411
+ auth: authSnapshot,
2412
+ productName: "Codemation",
2413
+ logoUrl: null
2414
+ }),
1666
2415
  CODEMATION_WS_PORT: String(args.websocketPort),
1667
2416
  NEXT_PUBLIC_CODEMATION_WS_PORT: String(args.websocketPort),
1668
2417
  CODEMATION_DEV_SERVER_TOKEN: args.developmentServerToken,
@@ -1673,6 +2422,10 @@ var DevNextHostEnvironmentBuilder = class {
1673
2422
  ...args.runtimeDevUrl !== void 0 && args.runtimeDevUrl.trim().length > 0 ? { CODEMATION_RUNTIME_DEV_URL: args.runtimeDevUrl.trim() } : {}
1674
2423
  };
1675
2424
  }
2425
+ parseAuthConfig(authConfigJson) {
2426
+ if (authConfigJson.trim().length === 0) return;
2427
+ return JSON.parse(authConfigJson) ?? void 0;
2428
+ }
1676
2429
  };
1677
2430
 
1678
2431
  //#endregion
@@ -1686,7 +2439,7 @@ var DevSessionPortsResolver = class {
1686
2439
  const nextPort = this.listenPorts.resolvePrimaryApplicationPort(args.portEnv);
1687
2440
  return {
1688
2441
  nextPort,
1689
- gatewayPort: this.listenPorts.parsePositiveInteger(args.gatewayPortEnv) ?? (args.devMode === "consumer" ? nextPort : await this.loopbackPorts.allocate())
2442
+ gatewayPort: this.listenPorts.parsePositiveInteger(args.gatewayPortEnv) ?? (args.devMode === "packaged-ui" ? nextPort : await this.loopbackPorts.allocate())
1690
2443
  };
1691
2444
  }
1692
2445
  };
@@ -1697,86 +2450,48 @@ var DevSessionPortsResolver = class {
1697
2450
  * Bundles dependencies for {@link DevCommand} so the command stays a thin orchestrator.
1698
2451
  */
1699
2452
  var DevSessionServices = class {
1700
- constructor(consumerEnvLoader, sourceMapNodeOptions, sessionPorts, loopbackPortAllocator, devHttpProbe, runtimeEntrypointResolver, devAuthLoader, nextHostEnvBuilder, watchRootsResolver, sourceChangeClassifier, sourceRestartCoordinator) {
2453
+ constructor(consumerEnvLoader, sourceMapNodeOptions, sessionPorts, loopbackPortAllocator, devHttpProbe, devAuthLoader, nextHostEnvBuilder, watchRootsResolver, sourceChangeClassifier) {
1701
2454
  this.consumerEnvLoader = consumerEnvLoader;
1702
2455
  this.sourceMapNodeOptions = sourceMapNodeOptions;
1703
2456
  this.sessionPorts = sessionPorts;
1704
2457
  this.loopbackPortAllocator = loopbackPortAllocator;
1705
2458
  this.devHttpProbe = devHttpProbe;
1706
- this.runtimeEntrypointResolver = runtimeEntrypointResolver;
1707
2459
  this.devAuthLoader = devAuthLoader;
1708
2460
  this.nextHostEnvBuilder = nextHostEnvBuilder;
1709
2461
  this.watchRootsResolver = watchRootsResolver;
1710
2462
  this.sourceChangeClassifier = sourceChangeClassifier;
1711
- this.sourceRestartCoordinator = sourceRestartCoordinator;
1712
2463
  }
1713
2464
  };
1714
2465
 
1715
2466
  //#endregion
1716
2467
  //#region src/dev/DevSourceChangeClassifier.ts
1717
- var DevSourceChangeClassifier = class {
2468
+ var DevSourceChangeClassifier = class DevSourceChangeClassifier {
2469
+ static configFileNames = new Set([
2470
+ "codemation.config.ts",
2471
+ "codemation.config.js",
2472
+ "codemation.config.mjs"
2473
+ ]);
1718
2474
  shouldRepublishConsumerOutput(args) {
1719
2475
  const resolvedConsumerRoot = path.resolve(args.consumerRoot);
1720
2476
  return args.changedPaths.some((changedPath) => this.isPathInsideDirectory(changedPath, resolvedConsumerRoot));
1721
2477
  }
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
- ];
2478
+ requiresUiRestart(args) {
2479
+ const resolvedConsumerRoot = path.resolve(args.consumerRoot);
2480
+ return args.changedPaths.some((changedPath) => this.pathRequiresUiRestart(path.resolve(changedPath), resolvedConsumerRoot));
1734
2481
  }
1735
2482
  isPathInsideDirectory(filePath, directoryPath) {
1736
2483
  const resolvedFilePath = path.resolve(filePath);
1737
2484
  const relativePath = path.relative(directoryPath, resolvedFilePath);
1738
2485
  return relativePath.length === 0 || !relativePath.startsWith("..") && !path.isAbsolute(relativePath);
1739
2486
  }
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
- }
2487
+ pathRequiresUiRestart(resolvedPath, consumerRoot) {
2488
+ if (!this.isPathInsideDirectory(resolvedPath, consumerRoot)) return false;
2489
+ const relativePath = path.relative(consumerRoot, resolvedPath);
2490
+ if (DevSourceChangeClassifier.configFileNames.has(path.basename(relativePath))) return true;
2491
+ if (relativePath.startsWith(path.join("src", "workflows"))) return false;
2492
+ if (relativePath.startsWith(path.join("src", "plugins"))) return false;
2493
+ if (relativePath.includes("credential")) return true;
2494
+ return false;
1780
2495
  }
1781
2496
  };
1782
2497
 
@@ -1785,7 +2500,7 @@ var DevSourceRestartCoordinator = class {
1785
2500
  var LoopbackPortAllocator = class {
1786
2501
  async allocate() {
1787
2502
  return await new Promise((resolve, reject) => {
1788
- const server = createServer();
2503
+ const server = createServer$1();
1789
2504
  server.once("error", reject);
1790
2505
  server.listen(0, "127.0.0.1", () => {
1791
2506
  const address = server.address();
@@ -1801,52 +2516,20 @@ var LoopbackPortAllocator = class {
1801
2516
  }
1802
2517
  };
1803
2518
 
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
2519
  //#endregion
1836
2520
  //#region src/dev/WatchRootsResolver.ts
1837
2521
  var WatchRootsResolver = class {
1838
2522
  resolve(args) {
1839
- if (args.devMode === "consumer") return [args.consumerRoot];
2523
+ if (args.devMode === "packaged-ui") return [args.consumerRoot];
1840
2524
  return [
1841
2525
  args.consumerRoot,
2526
+ path.resolve(args.repoRoot, "packages", "cli"),
1842
2527
  path.resolve(args.repoRoot, "packages", "core"),
1843
2528
  path.resolve(args.repoRoot, "packages", "core-nodes"),
1844
2529
  path.resolve(args.repoRoot, "packages", "core-nodes-gmail"),
1845
2530
  path.resolve(args.repoRoot, "packages", "eventbus-redis"),
1846
2531
  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")
2532
+ path.resolve(args.repoRoot, "packages", "node-example")
1850
2533
  ];
1851
2534
  }
1852
2535
  };
@@ -1854,16 +2537,12 @@ var WatchRootsResolver = class {
1854
2537
  //#endregion
1855
2538
  //#region src/dev/Builder.ts
1856
2539
  var DevSessionServicesBuilder = class {
1857
- constructor(loggerFactory$1) {
1858
- this.loggerFactory = loggerFactory$1;
1859
- }
1860
2540
  build() {
1861
2541
  const consumerEnvLoader = new ConsumerEnvLoader();
1862
2542
  const sourceMapNodeOptions = new SourceMapNodeOptions();
1863
2543
  const listenPortResolver = new ListenPortResolver();
1864
2544
  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));
2545
+ 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
2546
  }
1868
2547
  };
1869
2548
 
@@ -2136,14 +2815,17 @@ var CliProgram = class {
2136
2815
  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
2816
  await this.buildCommand.execute(resolveConsumerRoot(opts.consumerRoot), this.buildOptionsParser.parse(opts));
2138
2817
  });
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));
2818
+ 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) => {
2819
+ await this.devCommand.execute({
2820
+ consumerRoot: resolveConsumerRoot(opts.consumerRoot),
2821
+ watchFramework: opts.watchFramework === true
2822
+ });
2141
2823
  });
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) => {
2824
+ const serve$1 = program.command("serve").description("Run production web or worker processes (no dev watchers).");
2825
+ 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
2826
  await this.serveWebCommand.execute(resolveConsumerRoot(opts.consumerRoot), this.buildOptionsParser.parse(opts));
2145
2827
  });
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) => {
2828
+ 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
2829
  await this.serveWorkerCommand.execute(resolveConsumerRoot(opts.consumerRoot), opts.config);
2148
2830
  });
2149
2831
  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 +2898,9 @@ var NextHostConsumerServerCommandFactory = class {
2216
2898
  args: [
2217
2899
  "exec",
2218
2900
  "next",
2219
- "start"
2901
+ "start",
2902
+ "-H",
2903
+ "127.0.0.1"
2220
2904
  ],
2221
2905
  cwd: args.nextHostRoot
2222
2906
  };
@@ -2235,8 +2919,12 @@ var NextHostConsumerServerCommandFactory = class {
2235
2919
  //#region src/runtime/TypeScriptRuntimeConfigurator.ts
2236
2920
  var TypeScriptRuntimeConfigurator = class {
2237
2921
  configure(repoRoot) {
2922
+ if (this.hasExplicitOverride()) return;
2238
2923
  process$1.env.CODEMATION_TSCONFIG_PATH = path.resolve(repoRoot, "tsconfig.base.json");
2239
2924
  }
2925
+ hasExplicitOverride() {
2926
+ return typeof process$1.env.CODEMATION_TSCONFIG_PATH === "string" && process$1.env.CODEMATION_TSCONFIG_PATH.length > 0;
2927
+ }
2240
2928
  };
2241
2929
 
2242
2930
  //#endregion
@@ -2288,22 +2976,20 @@ var CliDatabaseUrlDescriptor = class {
2288
2976
  //#endregion
2289
2977
  //#region src/bootstrap/CodemationCliApplicationSession.ts
2290
2978
  /**
2291
- * Opens a {@link CodemationApplication} with persistence + command/query buses (no HTTP/WebSocket servers),
2979
+ * Opens an app container with persistence + command/query buses (no HTTP/WebSocket servers),
2292
2980
  * for CLI tools that dispatch application commands or queries (e.g. user admin).
2293
2981
  */
2294
2982
  var CodemationCliApplicationSession = class CodemationCliApplicationSession {
2295
- constructor(application) {
2296
- this.application = application;
2983
+ constructor(container) {
2984
+ this.container = container;
2297
2985
  }
2298
2986
  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);
2987
+ const container = await new AppContainerFactory().create({
2988
+ appConfig: args.appConfig,
2989
+ sharedWorkflowWebsocketServer: null
2990
+ });
2991
+ if (args.appConfig.env.CODEMATION_SKIP_STARTUP_MIGRATIONS !== "true") await container.resolve(DatabaseMigrations).migrate();
2992
+ return new CodemationCliApplicationSession(container);
2307
2993
  }
2308
2994
  getPrismaClient() {
2309
2995
  const container = this.getContainer();
@@ -2317,10 +3003,10 @@ var CodemationCliApplicationSession = class CodemationCliApplicationSession {
2317
3003
  return this.getContainer().resolve(ApplicationTokens.QueryBus);
2318
3004
  }
2319
3005
  async close() {
2320
- await this.application.stop({ stopWebsocketServer: false });
3006
+ await this.container.resolve(AppContainerLifecycle).stop({ stopWebsocketServer: false });
2321
3007
  }
2322
3008
  getContainer() {
2323
- return this.application.getContainer();
3009
+ return this.container;
2324
3010
  }
2325
3011
  };
2326
3012
 
@@ -2330,37 +3016,26 @@ var CodemationCliApplicationSession = class CodemationCliApplicationSession {
2330
3016
  * Shared env/config/session wiring for `codemation user *` commands (local auth + database).
2331
3017
  */
2332
3018
  var UserAdminCliBootstrap = class {
2333
- constructor(configLoader, pathResolver, consumerDotenvLoader, tsconfigPreparation, databasePersistenceResolver) {
2334
- this.configLoader = configLoader;
3019
+ constructor(appConfigLoader, pathResolver, consumerDotenvLoader, tsconfigPreparation) {
3020
+ this.appConfigLoader = appConfigLoader;
2335
3021
  this.pathResolver = pathResolver;
2336
3022
  this.consumerDotenvLoader = consumerDotenvLoader;
2337
3023
  this.tsconfigPreparation = tsconfigPreparation;
2338
- this.databasePersistenceResolver = databasePersistenceResolver;
2339
3024
  }
2340
3025
  async withSession(options, fn) {
2341
3026
  const consumerRoot = options.consumerRoot ?? process.cwd();
2342
3027
  this.consumerDotenvLoader.load(consumerRoot);
2343
3028
  this.tsconfigPreparation.applyWorkspaceTsconfigForTsxIfPresent(consumerRoot);
2344
- const resolution = await this.configLoader.load({
3029
+ const paths = await this.pathResolver.resolve(consumerRoot);
3030
+ const loadResult = await this.appConfigLoader.load({
2345
3031
  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 ?? {},
3032
+ repoRoot: paths.repoRoot,
2351
3033
  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
- })
3034
+ configPathOverride: options.configPath
2363
3035
  });
3036
+ if (loadResult.appConfig.auth?.kind !== "local") throw new Error("Codemation user commands require CodemationConfig.auth.kind to be \"local\".");
3037
+ if (loadResult.appConfig.persistence.kind === "none") throw new Error("Database persistence is not configured. Set CodemationConfig.runtime.database (postgresql URL or PGlite).");
3038
+ const session = await CodemationCliApplicationSession.open({ appConfig: loadResult.appConfig });
2364
3039
  try {
2365
3040
  return await fn(session);
2366
3041
  } finally {
@@ -2414,6 +3089,7 @@ const loggerFactory = new ServerLoggerFactory(logLevelPolicyFactory);
2414
3089
  var CliProgramFactory = class {
2415
3090
  create() {
2416
3091
  const cliLogger = loggerFactory.create("codemation-cli");
3092
+ const appConfigLoader = new AppConfigLoader();
2417
3093
  const pathResolver = new CliPathResolver();
2418
3094
  const pluginDiscovery = new CodemationPluginDiscovery();
2419
3095
  const artifactsPublisher = new ConsumerBuildArtifactsPublisher();
@@ -2421,15 +3097,15 @@ var CliProgramFactory = class {
2421
3097
  const outputBuilderLoader = new ConsumerOutputBuilderLoader();
2422
3098
  const sourceMapNodeOptions = new SourceMapNodeOptions();
2423
3099
  const nextHostConsumerServerCommandFactory = new NextHostConsumerServerCommandFactory();
3100
+ const devSessionServices = new DevSessionServicesBuilder().build();
2424
3101
  const tsconfigPreparation = new ConsumerCliTsconfigPreparation();
2425
- const databasePersistenceResolver = new DatabasePersistenceResolver();
2426
- const userAdminBootstrap = new UserAdminCliBootstrap(new CodemationConsumerConfigLoader(), pathResolver, new UserAdminConsumerDotenvLoader(), tsconfigPreparation, databasePersistenceResolver);
3102
+ const userAdminBootstrap = new UserAdminCliBootstrap(appConfigLoader, pathResolver, new UserAdminConsumerDotenvLoader(), tsconfigPreparation);
2427
3103
  const hostPackageRoot = new HostPackageRootResolver().resolveHostPackageRoot();
2428
3104
  const userAdminCliOptionsParser = new UserAdminCliOptionsParser();
2429
3105
  const databaseMigrationsApplyService = new DatabaseMigrationsApplyService(cliLogger, new UserAdminConsumerDotenvLoader(), tsconfigPreparation, new CodemationConsumerConfigLoader(), new ConsumerDatabaseConnectionResolver(), new CliDatabaseUrlDescriptor(), hostPackageRoot, new PrismaMigrationDeployer());
2430
3106
  const buildOptionsParser = new ConsumerBuildOptionsParser();
2431
3107
  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));
3108
+ 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
3109
  }
2434
3110
  };
2435
3111