@codemation/cli 0.0.15 → 0.0.18

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.
@@ -1,7 +1,7 @@
1
1
  import { createRequire } from "node:module";
2
2
  import process$1 from "node:process";
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";
3
+ import { AppConfigLoader, CodemationConsumerConfigLoader, CodemationPluginDiscovery, WorkflowDiscoveryPathSegmentsComputer, WorkflowModulePathFinder } from "@codemation/host/server";
4
+ import { ApiPaths, AppContainerFactory, AppContainerLifecycle, ApplicationTokens, CodemationPluginPackageMetadata, DatabaseMigrations, ListUserAccountsQuery, PrismaClient, UpsertLocalBootstrapUserCommand, WorkerRuntime } from "@codemation/host";
5
5
  import { AppContainerFactory as AppContainerFactory$1, AppContainerLifecycle as AppContainerLifecycle$1, CodemationHonoApiApp, CodemationPluginListMerger, FrontendRuntime, ServerLoggerFactory, logLevelPolicyFactory } from "@codemation/host/next/server";
6
6
  import { randomUUID } from "node:crypto";
7
7
  import { access, copyFile, cp, mkdir, open, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
@@ -25,6 +25,23 @@ import { setTimeout as setTimeout$1 } from "node:timers/promises";
25
25
  import { createServer as createServer$1 } from "node:net";
26
26
  import { Command } from "commander";
27
27
 
28
+ //#region src/build/ConsumerBuildOptionsParser.ts
29
+ var ConsumerBuildOptionsParser = class {
30
+ parse(args) {
31
+ return {
32
+ sourceMaps: args.noSourceMaps !== true,
33
+ target: this.parseTarget(args.target)
34
+ };
35
+ }
36
+ parseTarget(raw) {
37
+ if (raw === void 0 || raw.trim() === "") return "es2022";
38
+ const normalized = raw.trim();
39
+ if (normalized === "es2020" || normalized === "es2022") return normalized;
40
+ throw new Error(`Invalid --target "${raw}". Use es2020 or es2022.`);
41
+ }
42
+ };
43
+
44
+ //#endregion
28
45
  //#region src/build/ConsumerBuildArtifactsPublisher.ts
29
46
  var ConsumerBuildArtifactsPublisher = class {
30
47
  async publish(snapshot, discoveredPlugins) {
@@ -36,10 +53,9 @@ var ConsumerBuildArtifactsPublisher = class {
36
53
  await mkdir(path.dirname(outputPath), { recursive: true });
37
54
  const outputLines = ["const codemationDiscoveredPlugins = [];", ""];
38
55
  discoveredPlugins.forEach((discoveredPlugin, index) => {
39
- const pluginFileUrl = pathToFileURL(path.resolve(discoveredPlugin.packageRoot, discoveredPlugin.manifest.entry)).href;
40
- const exportNameAccessor = discoveredPlugin.manifest.exportName ? `pluginModule${index}[${JSON.stringify(discoveredPlugin.manifest.exportName)}]` : `pluginModule${index}.default ?? pluginModule${index}.codemationPlugin`;
56
+ const pluginFileUrl = pathToFileURL(path.resolve(discoveredPlugin.packageRoot, this.resolvePluginEntry(discoveredPlugin))).href;
41
57
  outputLines.push(`const pluginModule${index} = await import(${JSON.stringify(pluginFileUrl)});`);
42
- outputLines.push(`const pluginValue${index} = ${exportNameAccessor};`);
58
+ outputLines.push(`const pluginValue${index} = pluginModule${index}.default ?? pluginModule${index}.codemationPlugin;`);
43
59
  outputLines.push(`if (pluginValue${index} && typeof pluginValue${index}.register === "function") {`);
44
60
  outputLines.push(` codemationDiscoveredPlugins.push(pluginValue${index});`);
45
61
  outputLines.push(`} else if (typeof pluginValue${index} === "function" && pluginValue${index}.prototype && typeof pluginValue${index}.prototype.register === "function") {`);
@@ -68,45 +84,32 @@ var ConsumerBuildArtifactsPublisher = class {
68
84
  await rename(temporaryManifestPath, snapshot.manifestPath);
69
85
  return manifest;
70
86
  }
71
- };
72
-
73
- //#endregion
74
- //#region src/build/ConsumerBuildOptionsParser.ts
75
- var ConsumerBuildOptionsParser = class {
76
- parse(args) {
77
- return {
78
- sourceMaps: args.noSourceMaps !== true,
79
- target: this.parseTarget(args.target)
80
- };
81
- }
82
- parseTarget(raw) {
83
- if (raw === void 0 || raw.trim() === "") return "es2022";
84
- const normalized = raw.trim();
85
- if (normalized === "es2020" || normalized === "es2022") return normalized;
86
- throw new Error(`Invalid --target "${raw}". Use es2020 or es2022.`);
87
+ resolvePluginEntry(discoveredPlugin) {
88
+ if (typeof discoveredPlugin.developmentEntry === "string" && discoveredPlugin.developmentEntry.trim().length > 0) return discoveredPlugin.developmentEntry;
89
+ return discoveredPlugin.pluginEntry;
87
90
  }
88
91
  };
89
92
 
90
93
  //#endregion
91
94
  //#region src/commands/BuildCommand.ts
92
95
  var BuildCommand = class {
93
- constructor(cliLogger, pathResolver, pluginDiscovery, artifactsPublisher, tsRuntime, outputBuilderLoader) {
96
+ constructor(cliLogger, pathResolver, consumerOutputBuilderFactory, pluginDiscovery, consumerBuildArtifactsPublisher, tsRuntime) {
94
97
  this.cliLogger = cliLogger;
95
98
  this.pathResolver = pathResolver;
99
+ this.consumerOutputBuilderFactory = consumerOutputBuilderFactory;
96
100
  this.pluginDiscovery = pluginDiscovery;
97
- this.artifactsPublisher = artifactsPublisher;
101
+ this.consumerBuildArtifactsPublisher = consumerBuildArtifactsPublisher;
98
102
  this.tsRuntime = tsRuntime;
99
- this.outputBuilderLoader = outputBuilderLoader;
100
103
  }
101
104
  async execute(consumerRoot, buildOptions) {
102
105
  const paths = await this.pathResolver.resolve(consumerRoot);
103
106
  this.tsRuntime.configure(paths.repoRoot);
104
- const snapshot = await this.outputBuilderLoader.create(paths.consumerRoot, buildOptions).ensureBuilt();
107
+ const snapshot = await this.consumerOutputBuilderFactory.create(paths.consumerRoot, { buildOptions }).ensureBuilt();
105
108
  const discoveredPlugins = await this.pluginDiscovery.discover(paths.consumerRoot);
106
- const manifest = await this.artifactsPublisher.publish(snapshot, discoveredPlugins);
109
+ const manifest = await this.consumerBuildArtifactsPublisher.publish(snapshot, discoveredPlugins);
107
110
  this.cliLogger.info(`Built consumer output: ${snapshot.outputEntryPath}`);
108
- this.cliLogger.info(`Discovered plugins: ${discoveredPlugins.length}`);
109
- this.cliLogger.info(`Published build: ${manifest.buildVersion}`);
111
+ this.cliLogger.info(`Build manifest: ${manifest.manifestPath}`);
112
+ this.cliLogger.info(`Workflow modules emitted: ${snapshot.workflowSourcePaths.length}`);
110
113
  }
111
114
  };
112
115
 
@@ -125,7 +128,7 @@ var DbMigrateCommand = class {
125
128
  //#region src/commands/DevCommand.ts
126
129
  var DevCommand = class {
127
130
  require = createRequire(import.meta.url);
128
- constructor(pathResolver, tsRuntime, devLockFactory, devSourceWatcherFactory, cliLogger, session, databaseMigrationsApplyService, devBootstrapSummaryFetcher, devCliBannerRenderer, devConsumerPublishBootstrap, consumerEnvDotenvFilePredicate, devTrackedProcessTreeKiller, nextHostConsumerServerCommandFactory, devApiRuntimeFactory, cliDevProxyServerFactory, devRebuildQueueFactory) {
131
+ constructor(pathResolver, tsRuntime, devLockFactory, devSourceWatcherFactory, cliLogger, session, databaseMigrationsApplyService, consumerOutputBuilderFactory, pluginDiscovery, consumerBuildArtifactsPublisher, devBootstrapSummaryFetcher, devCliBannerRenderer, consumerEnvDotenvFilePredicate, devTrackedProcessTreeKiller, nextHostConsumerServerCommandFactory, devApiRuntimeFactory, cliDevProxyServerFactory, devRebuildQueueFactory) {
129
132
  this.pathResolver = pathResolver;
130
133
  this.tsRuntime = tsRuntime;
131
134
  this.devLockFactory = devLockFactory;
@@ -133,9 +136,11 @@ var DevCommand = class {
133
136
  this.cliLogger = cliLogger;
134
137
  this.session = session;
135
138
  this.databaseMigrationsApplyService = databaseMigrationsApplyService;
139
+ this.consumerOutputBuilderFactory = consumerOutputBuilderFactory;
140
+ this.pluginDiscovery = pluginDiscovery;
141
+ this.consumerBuildArtifactsPublisher = consumerBuildArtifactsPublisher;
136
142
  this.devBootstrapSummaryFetcher = devBootstrapSummaryFetcher;
137
143
  this.devCliBannerRenderer = devCliBannerRenderer;
138
- this.devConsumerPublishBootstrap = devConsumerPublishBootstrap;
139
144
  this.consumerEnvDotenvFilePredicate = consumerEnvDotenvFilePredicate;
140
145
  this.devTrackedProcessTreeKiller = devTrackedProcessTreeKiller;
141
146
  this.nextHostConsumerServerCommandFactory = nextHostConsumerServerCommandFactory;
@@ -145,10 +150,11 @@ var DevCommand = class {
145
150
  }
146
151
  async execute(args) {
147
152
  const paths = await this.pathResolver.resolve(args.consumerRoot);
153
+ const commandName = args.commandName ?? "dev";
154
+ const previousDevelopmentServerToken = process$1.env.CODEMATION_DEV_SERVER_TOKEN;
148
155
  this.devCliBannerRenderer.renderBrandHeader();
149
156
  this.tsRuntime.configure(paths.repoRoot);
150
- await this.databaseMigrationsApplyService.applyForConsumer(paths.consumerRoot);
151
- await this.devConsumerPublishBootstrap.ensurePublished(paths);
157
+ await this.databaseMigrationsApplyService.applyForConsumer(paths.consumerRoot, { configPath: args.configPathOverride });
152
158
  const devMode = this.resolveDevMode(args);
153
159
  const { nextPort, gatewayPort } = await this.session.sessionPorts.resolve({
154
160
  devMode,
@@ -160,26 +166,34 @@ var DevCommand = class {
160
166
  consumerRoot: paths.consumerRoot,
161
167
  nextPort: devMode === "watch-framework" ? nextPort : gatewayPort
162
168
  });
163
- const authSettings = await this.session.devAuthLoader.loadForConsumer(paths.consumerRoot);
169
+ const authSettings = await this.session.nextHostEdgeSeedLoader.loadForConsumer(paths.consumerRoot, { configPathOverride: args.configPathOverride });
164
170
  const watcher = this.devSourceWatcherFactory.create();
165
171
  const processState = this.createInitialProcessState();
166
172
  let proxyServer = null;
167
173
  try {
168
- const prepared = await this.prepareDevRuntime(paths, devMode, nextPort, gatewayPort, authSettings);
174
+ const prepared = await this.prepareDevRuntime(paths, devMode, nextPort, gatewayPort, authSettings, args.configPathOverride);
175
+ if (prepared.devMode === "packaged-ui") await this.publishConsumerArtifacts(prepared.paths, prepared.configPathOverride);
176
+ process$1.env.CODEMATION_DEV_SERVER_TOKEN = prepared.developmentServerToken;
169
177
  const stopPromise = this.wireStopPromise(processState);
170
- const uiProxyBase = await this.startPackagedUiWhenNeeded(prepared, processState);
178
+ const uiProxyBase = await this.preparePackagedUiBaseUrlWhenNeeded(prepared, processState);
171
179
  proxyServer = await this.startProxyServer(prepared.gatewayPort, uiProxyBase);
172
180
  const gatewayBaseUrl = this.gatewayBaseHttpUrl(gatewayPort);
173
181
  await this.bootInitialRuntime(prepared, processState, proxyServer);
174
182
  await this.session.devHttpProbe.waitUntilBootstrapSummaryReady(gatewayBaseUrl);
175
183
  const initialSummary = await this.devBootstrapSummaryFetcher.fetch(gatewayBaseUrl);
176
184
  if (initialSummary) this.devCliBannerRenderer.renderRuntimeSummary(initialSummary);
185
+ await this.startPackagedUiWhenNeeded(prepared, processState, uiProxyBase);
177
186
  this.bindShutdownSignalsToChildProcesses(processState, proxyServer);
178
187
  await this.spawnDevUiWhenNeeded(prepared, processState, gatewayBaseUrl);
179
- await this.startWatcherForSourceRestart(prepared, processState, watcher, devMode, gatewayBaseUrl, proxyServer);
180
- this.logPackagedUiDevHintWhenNeeded(devMode, gatewayPort);
188
+ await this.startWatcherForSourceRestart(prepared, processState, watcher, devMode, gatewayBaseUrl, proxyServer, {
189
+ commandName,
190
+ configPathOverride: args.configPathOverride
191
+ });
192
+ this.logPackagedUiDevHintWhenNeeded(devMode, gatewayPort, commandName);
181
193
  await stopPromise;
182
194
  } finally {
195
+ if (previousDevelopmentServerToken === void 0) delete process$1.env.CODEMATION_DEV_SERVER_TOKEN;
196
+ else process$1.env.CODEMATION_DEV_SERVER_TOKEN = previousDevelopmentServerToken;
183
197
  processState.stopRequested = true;
184
198
  await this.stopLiveProcesses(processState, proxyServer);
185
199
  await watcher.stop();
@@ -190,14 +204,15 @@ var DevCommand = class {
190
204
  if (args.watchFramework === true || process$1.env.CODEMATION_DEV_MODE === "framework") return "watch-framework";
191
205
  return "packaged-ui";
192
206
  }
193
- async prepareDevRuntime(paths, devMode, nextPort, gatewayPort, authSettings) {
207
+ async prepareDevRuntime(paths, devMode, nextPort, gatewayPort, authSettings, configPathOverride) {
194
208
  return {
195
209
  paths,
210
+ configPathOverride,
196
211
  devMode,
197
212
  nextPort,
198
213
  gatewayPort,
199
214
  authSettings,
200
- developmentServerToken: this.session.devAuthLoader.resolveDevelopmentServerToken(process$1.env.CODEMATION_DEV_SERVER_TOKEN),
215
+ developmentServerToken: this.session.nextHostEdgeSeedLoader.resolveDevelopmentServerToken(process$1.env.CODEMATION_DEV_SERVER_TOKEN),
201
216
  consumerEnv: this.session.consumerEnvLoader.load(paths.consumerRoot)
202
217
  };
203
218
  }
@@ -222,30 +237,30 @@ var DevCommand = class {
222
237
  gatewayBaseHttpUrl(gatewayPort) {
223
238
  return `http://127.0.0.1:${gatewayPort}`;
224
239
  }
225
- async startPackagedUiWhenNeeded(prepared, state) {
240
+ async preparePackagedUiBaseUrlWhenNeeded(prepared, state) {
226
241
  if (prepared.devMode !== "packaged-ui") return "";
227
- const websocketPort = prepared.gatewayPort;
228
242
  const uiProxyBase = state.currentPackagedUiBaseUrl ?? `http://127.0.0.1:${await this.session.loopbackPortAllocator.allocate()}`;
229
243
  state.currentPackagedUiBaseUrl = uiProxyBase;
230
- await this.spawnPackagedUi(prepared, state, prepared.authSettings, websocketPort, uiProxyBase);
231
244
  return uiProxyBase;
232
245
  }
246
+ async startPackagedUiWhenNeeded(prepared, state, uiProxyBase) {
247
+ if (prepared.devMode !== "packaged-ui" || uiProxyBase.length === 0) return;
248
+ await this.spawnPackagedUi(prepared, state, prepared.authSettings, prepared.gatewayPort, uiProxyBase);
249
+ }
233
250
  async spawnPackagedUi(prepared, state, authSettings, websocketPort, uiProxyBase) {
234
251
  const nextHostPackageJsonPath = this.require.resolve("@codemation/next-host/package.json");
235
252
  const nextHostRoot = path.dirname(nextHostPackageJsonPath);
236
253
  const nextHostCommand = await this.nextHostConsumerServerCommandFactory.create({ nextHostRoot });
237
- const consumerOutputManifestPath = path.resolve(prepared.paths.consumerRoot, ".codemation", "output", "current.json");
238
254
  const uiPort = Number(new URL(uiProxyBase).port);
239
255
  const nextHostEnvironment = this.session.nextHostEnvBuilder.buildConsumerUiProxy({
240
- authConfigJson: authSettings.authConfigJson,
241
256
  authSecret: authSettings.authSecret,
257
+ configPathOverride: prepared.configPathOverride,
242
258
  consumerRoot: prepared.paths.consumerRoot,
243
- consumerOutputManifestPath,
244
259
  developmentServerToken: prepared.developmentServerToken,
245
260
  nextPort: uiPort,
246
261
  publicBaseUrl: this.gatewayBaseHttpUrl(prepared.gatewayPort),
247
262
  runtimeDevUrl: this.gatewayBaseHttpUrl(prepared.gatewayPort),
248
- skipUiAuth: authSettings.skipUiAuth,
263
+ skipUiAuth: !authSettings.uiAuthEnabled,
249
264
  websocketPort
250
265
  });
251
266
  state.currentPackagedUi = spawn(nextHostCommand.command, nextHostCommand.args, {
@@ -319,11 +334,12 @@ var DevCommand = class {
319
334
  const nextHostPackageJsonPath = this.require.resolve("@codemation/next-host/package.json");
320
335
  const nextHostRoot = path.dirname(nextHostPackageJsonPath);
321
336
  const nextHostEnvironment = this.session.nextHostEnvBuilder.build({
322
- authConfigJson: authSettings.authConfigJson,
337
+ authSecret: authSettings.authSecret,
338
+ configPathOverride: prepared.configPathOverride,
323
339
  consumerRoot: prepared.paths.consumerRoot,
324
340
  developmentServerToken: prepared.developmentServerToken,
325
341
  nextPort: prepared.nextPort,
326
- skipUiAuth: authSettings.skipUiAuth,
342
+ skipUiAuth: !authSettings.uiAuthEnabled,
327
343
  websocketPort,
328
344
  runtimeDevUrl: gatewayBaseUrl
329
345
  });
@@ -354,7 +370,7 @@ var DevCommand = class {
354
370
  });
355
371
  await this.session.devHttpProbe.waitUntilUrlRespondsOk(`http://127.0.0.1:${prepared.nextPort}/`);
356
372
  }
357
- async startWatcherForSourceRestart(prepared, state, watcher, devMode, gatewayBaseUrl, proxyServer) {
373
+ async startWatcherForSourceRestart(prepared, state, watcher, devMode, gatewayBaseUrl, proxyServer, options) {
358
374
  const rebuildQueue = this.devRebuildQueueFactory.create({ run: async (request) => {
359
375
  await this.runQueuedRebuild(prepared, state, gatewayBaseUrl, proxyServer, request);
360
376
  } });
@@ -370,18 +386,14 @@ var DevCommand = class {
370
386
  return;
371
387
  }
372
388
  try {
373
- const shouldRepublishConsumerOutput = this.session.sourceChangeClassifier.shouldRepublishConsumerOutput({
374
- changedPaths,
375
- consumerRoot: prepared.paths.consumerRoot
376
- });
377
389
  const shouldRestartUi = this.session.sourceChangeClassifier.requiresUiRestart({
378
390
  changedPaths,
379
391
  consumerRoot: prepared.paths.consumerRoot
380
392
  });
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");
393
+ process$1.stdout.write(shouldRestartUi ? `\n[codemation] Source change detected — rebuilding for \`${options.commandName}\`, restarting the runtime, and restarting the UI…\n` : `\n[codemation] Source change detected — rebuilding for \`${options.commandName}\` and restarting the runtime…\n`);
382
394
  await rebuildQueue.enqueue({
383
395
  changedPaths,
384
- shouldRepublishConsumerOutput,
396
+ configPathOverride: options.configPathOverride,
385
397
  shouldRestartUi
386
398
  });
387
399
  } catch (error) {
@@ -395,7 +407,7 @@ var DevCommand = class {
395
407
  proxyServer.setBuildStatus("building");
396
408
  proxyServer.broadcastBuildStarted();
397
409
  try {
398
- if (request.shouldRepublishConsumerOutput) await this.devConsumerPublishBootstrap.ensurePublished(prepared.paths);
410
+ if (prepared.devMode === "packaged-ui") await this.publishConsumerArtifacts(prepared.paths, request.configPathOverride);
399
411
  await this.stopCurrentRuntime(state, proxyServer);
400
412
  process$1.stdout.write("[codemation] Waiting for runtime to accept traffic…\n");
401
413
  const runtime = await this.createRuntime(prepared);
@@ -404,11 +416,11 @@ var DevCommand = class {
404
416
  httpPort: runtime.httpPort,
405
417
  workflowWebSocketPort: runtime.workflowWebSocketPort
406
418
  });
407
- if (request.shouldRestartUi) await this.restartUiAfterSourceChange(prepared, state, gatewayBaseUrl);
408
419
  await this.session.devHttpProbe.waitUntilBootstrapSummaryReady(gatewayBaseUrl);
409
420
  const json = await this.devBootstrapSummaryFetcher.fetch(gatewayBaseUrl);
410
421
  if (json) this.devCliBannerRenderer.renderCompact(json);
411
422
  proxyServer.setBuildStatus("idle");
423
+ if (request.shouldRestartUi) await this.restartUiAfterSourceChange(prepared, state, gatewayBaseUrl);
412
424
  proxyServer.broadcastBuildCompleted(runtime.buildVersion);
413
425
  process$1.stdout.write("[codemation] Runtime ready.\n");
414
426
  } catch (error) {
@@ -418,7 +430,7 @@ var DevCommand = class {
418
430
  }
419
431
  }
420
432
  async restartUiAfterSourceChange(prepared, state, gatewayBaseUrl) {
421
- const refreshedAuthSettings = await this.session.devAuthLoader.loadForConsumer(prepared.paths.consumerRoot);
433
+ const refreshedAuthSettings = await this.session.nextHostEdgeSeedLoader.loadForConsumer(prepared.paths.consumerRoot, { configPathOverride: prepared.configPathOverride });
422
434
  process$1.stdout.write("[codemation] Restarting the UI process to apply source changes…\n");
423
435
  state.isRestartingUi = true;
424
436
  try {
@@ -465,6 +477,7 @@ var DevCommand = class {
465
477
  async createRuntime(prepared) {
466
478
  const runtimeEnvironment = this.session.consumerEnvLoader.mergeIntoProcessEnvironment(process$1.env, prepared.consumerEnv);
467
479
  return await this.devApiRuntimeFactory.create({
480
+ configPathOverride: prepared.configPathOverride,
468
481
  consumerRoot: prepared.paths.consumerRoot,
469
482
  runtimeWorkingDirectory: process$1.cwd(),
470
483
  env: {
@@ -477,9 +490,33 @@ var DevCommand = class {
477
490
  }
478
491
  });
479
492
  }
480
- logPackagedUiDevHintWhenNeeded(devMode, gatewayPort) {
493
+ async publishConsumerArtifacts(paths, configPathOverride) {
494
+ const snapshot = await this.consumerOutputBuilderFactory.create(paths.consumerRoot, { configPathOverride }).ensureBuilt();
495
+ const discoveredPlugins = await this.pluginDiscovery.discover(paths.consumerRoot);
496
+ await this.consumerBuildArtifactsPublisher.publish(snapshot, discoveredPlugins);
497
+ this.cliLogger.debug(`Dev: consumer output published (${snapshot.buildVersion}).`);
498
+ }
499
+ logPackagedUiDevHintWhenNeeded(devMode, gatewayPort, commandName) {
481
500
  if (devMode !== "packaged-ui") return;
482
- this.cliLogger.info(`codemation dev: open http://127.0.0.1:${gatewayPort} — this uses the packaged @codemation/next-host UI. Use \`codemation dev --watch-framework\` only when working on the framework UI itself.`);
501
+ this.cliLogger.info(`codemation ${commandName}: open http://127.0.0.1:${gatewayPort} — this uses the packaged @codemation/next-host UI. Use \`codemation ${commandName} --watch-framework\` only when working on the framework UI itself.`);
502
+ }
503
+ };
504
+
505
+ //#endregion
506
+ //#region src/commands/DevPluginCommand.ts
507
+ var DevPluginCommand = class {
508
+ constructor(pluginDevConfigFactory, devCommand) {
509
+ this.pluginDevConfigFactory = pluginDevConfigFactory;
510
+ this.devCommand = devCommand;
511
+ }
512
+ async execute(args) {
513
+ const pluginConfig = await this.pluginDevConfigFactory.prepare(args.pluginRoot);
514
+ await this.devCommand.execute({
515
+ commandName: "dev:plugin",
516
+ configPathOverride: pluginConfig.configPath,
517
+ consumerRoot: args.pluginRoot,
518
+ watchFramework: args.watchFramework
519
+ });
483
520
  }
484
521
  };
485
522
 
@@ -487,38 +524,22 @@ var DevCommand = class {
487
524
  //#region src/commands/ServeWebCommand.ts
488
525
  var ServeWebCommand = class {
489
526
  require = createRequire(import.meta.url);
490
- constructor(pathResolver, configLoader, pluginDiscovery, artifactsPublisher, tsRuntime, sourceMapNodeOptions, outputBuilderLoader, envLoader, listenPortResolver, nextHostConsumerServerCommandFactory, frontendAuthSnapshotFactory, frontendAppConfigJsonCodec) {
527
+ constructor(pathResolver, configLoader, tsRuntime, sourceMapNodeOptions, envLoader, listenPortResolver, nextHostConsumerServerCommandFactory) {
491
528
  this.pathResolver = pathResolver;
492
529
  this.configLoader = configLoader;
493
- this.pluginDiscovery = pluginDiscovery;
494
- this.artifactsPublisher = artifactsPublisher;
495
530
  this.tsRuntime = tsRuntime;
496
531
  this.sourceMapNodeOptions = sourceMapNodeOptions;
497
- this.outputBuilderLoader = outputBuilderLoader;
498
532
  this.envLoader = envLoader;
499
533
  this.listenPortResolver = listenPortResolver;
500
534
  this.nextHostConsumerServerCommandFactory = nextHostConsumerServerCommandFactory;
501
- this.frontendAuthSnapshotFactory = frontendAuthSnapshotFactory;
502
- this.frontendAppConfigJsonCodec = frontendAppConfigJsonCodec;
503
535
  }
504
536
  async execute(consumerRoot, buildOptions) {
505
537
  const paths = await this.pathResolver.resolve(consumerRoot);
506
538
  this.tsRuntime.configure(paths.repoRoot);
507
- const snapshot = await this.outputBuilderLoader.create(paths.consumerRoot, buildOptions).ensureBuilt();
508
- const discoveredPlugins = await this.pluginDiscovery.discover(paths.consumerRoot);
509
- const manifest = await this.artifactsPublisher.publish(snapshot, discoveredPlugins);
510
539
  const nextHostRoot = path.dirname(this.require.resolve("@codemation/next-host/package.json"));
511
540
  const nextHostCommand = await this.nextHostConsumerServerCommandFactory.create({ nextHostRoot });
512
541
  const consumerEnv = this.envLoader.load(paths.consumerRoot);
513
542
  const configResolution = await this.configLoader.load({ consumerRoot: paths.consumerRoot });
514
- const frontendAuthSnapshot = this.frontendAuthSnapshotFactory.createFromResolvedInputs({
515
- authConfig: configResolution.config.auth,
516
- env: {
517
- ...process$1.env,
518
- ...consumerEnv
519
- },
520
- uiAuthEnabled: !(consumerEnv.NODE_ENV !== "production" && configResolution.config.auth?.allowUnauthenticatedInDevelopment === true)
521
- });
522
543
  const nextPort = this.listenPortResolver.resolvePrimaryApplicationPort(process$1.env.PORT);
523
544
  const websocketPort = this.listenPortResolver.resolveWebsocketPortRelativeToHttp({
524
545
  nextPort,
@@ -532,13 +553,8 @@ var ServeWebCommand = class {
532
553
  ...process$1.env,
533
554
  ...consumerEnv,
534
555
  PORT: String(nextPort),
535
- CODEMATION_FRONTEND_APP_CONFIG_JSON: this.frontendAppConfigJsonCodec.serialize({
536
- auth: frontendAuthSnapshot,
537
- productName: "Codemation",
538
- logoUrl: null
539
- }),
540
- CODEMATION_CONSUMER_OUTPUT_MANIFEST_PATH: manifest.manifestPath,
541
556
  CODEMATION_CONSUMER_ROOT: paths.consumerRoot,
557
+ CODEMATION_UI_AUTH_ENABLED: String(!(consumerEnv.NODE_ENV !== "production" && configResolution.config.auth?.allowUnauthenticatedInDevelopment === true)),
542
558
  CODEMATION_WS_PORT: String(websocketPort),
543
559
  NEXT_PUBLIC_CODEMATION_WS_PORT: String(websocketPort),
544
560
  NODE_OPTIONS: this.sourceMapNodeOptions.appendToNodeOptions(process$1.env.NODE_OPTIONS),
@@ -765,8 +781,9 @@ var ConsumerOutputBuilder = class ConsumerOutputBuilder {
765
781
  lastIssuedBuildVersion = 0;
766
782
  log;
767
783
  buildOptions;
768
- constructor(consumerRoot, logOverride, buildOptionsOverride) {
784
+ constructor(consumerRoot, logOverride, buildOptionsOverride, configPathOverride) {
769
785
  this.consumerRoot = consumerRoot;
786
+ this.configPathOverride = configPathOverride;
770
787
  this.log = logOverride ?? defaultConsumerOutputLogger;
771
788
  this.buildOptions = buildOptionsOverride ?? defaultConsumerBuildOptions;
772
789
  }
@@ -978,6 +995,7 @@ var ConsumerOutputBuilder = class ConsumerOutputBuilder {
978
995
  sourcePath
979
996
  });
980
997
  }
998
+ await this.emitConfigSourceFile(outputAppRoot, configSourcePath, runtimeSourcePaths);
981
999
  }
982
1000
  });
983
1001
  }
@@ -1036,6 +1054,14 @@ var ConsumerOutputBuilder = class ConsumerOutputBuilder {
1036
1054
  await writeFile(outputPath, rewrittenOutputText, "utf8");
1037
1055
  if (transpiledOutput.sourceMapText) await writeFile(`${outputPath}.map`, transpiledOutput.sourceMapText, "utf8");
1038
1056
  }
1057
+ async emitConfigSourceFile(outputAppRoot, configSourcePath, runtimeSourcePaths) {
1058
+ const normalizedConfigSourcePath = path.resolve(configSourcePath);
1059
+ if (runtimeSourcePaths.some((sourcePath) => path.resolve(sourcePath) === normalizedConfigSourcePath)) return;
1060
+ await this.emitSourceFile({
1061
+ outputAppRoot,
1062
+ sourcePath: normalizedConfigSourcePath
1063
+ });
1064
+ }
1039
1065
  createCompilerOptions() {
1040
1066
  const scriptTarget = this.buildOptions.target === "es2020" ? ts.ScriptTarget.ES2020 : ts.ScriptTarget.ES2022;
1041
1067
  return {
@@ -1176,6 +1202,12 @@ var ConsumerOutputBuilder = class ConsumerOutputBuilder {
1176
1202
  return `${nextBuildVersion}-${process$1.pid}`;
1177
1203
  }
1178
1204
  async resolveConfigPath(consumerRoot) {
1205
+ const configuredOverride = this.configPathOverride?.trim();
1206
+ if (configuredOverride && configuredOverride.length > 0) {
1207
+ const resolvedOverride = path.resolve(configuredOverride);
1208
+ if (await this.fileExists(resolvedOverride)) return resolvedOverride;
1209
+ throw new Error(`Codemation config override not found at ${resolvedOverride}.`);
1210
+ }
1179
1211
  for (const candidate of this.getConventionCandidates(consumerRoot)) if (await this.fileExists(candidate)) return candidate;
1180
1212
  return null;
1181
1213
  }
@@ -1289,10 +1321,10 @@ var ConsumerOutputBuilder = class ConsumerOutputBuilder {
1289
1321
  };
1290
1322
 
1291
1323
  //#endregion
1292
- //#region src/consumer/Loader.ts
1293
- var ConsumerOutputBuilderLoader = class {
1294
- create(consumerRoot, buildOptions) {
1295
- return new ConsumerOutputBuilder(consumerRoot, void 0, buildOptions);
1324
+ //#region src/consumer/ConsumerOutputBuilderFactory.ts
1325
+ var ConsumerOutputBuilderFactory = class {
1326
+ create(consumerRoot, args) {
1327
+ return new ConsumerOutputBuilder(consumerRoot, args?.logger, args?.buildOptions, args?.configPathOverride);
1296
1328
  }
1297
1329
  };
1298
1330
 
@@ -1464,8 +1496,9 @@ var DevCliBannerRenderer = class {
1464
1496
  title: chalk.bold("Runtime"),
1465
1497
  titleAlignment: "center"
1466
1498
  });
1499
+ const pluginsSection = this.buildPluginsSection(summary);
1467
1500
  const activeSection = this.buildActiveWorkflowsSection(summary);
1468
- process.stdout.write(`${detailBox}\n${activeSection}\n`);
1501
+ process.stdout.write(`${detailBox}\n${pluginsSection}\n${activeSection}\n`);
1469
1502
  }
1470
1503
  renderFull(summary) {
1471
1504
  this.renderBrandHeader();
@@ -1492,8 +1525,9 @@ var DevCliBannerRenderer = class {
1492
1525
  title: chalk.bold("Runtime (updated)"),
1493
1526
  titleAlignment: "center"
1494
1527
  });
1528
+ const pluginsSection = this.buildPluginsSection(summary);
1495
1529
  const activeSection = this.buildActiveWorkflowsSection(summary);
1496
- process.stdout.write(`\n${detailBox}\n${activeSection}\n`);
1530
+ process.stdout.write(`\n${detailBox}\n${pluginsSection}\n${activeSection}\n`);
1497
1531
  }
1498
1532
  renderFigletTitle() {
1499
1533
  try {
@@ -1532,28 +1566,23 @@ var DevCliBannerRenderer = class {
1532
1566
  titleAlignment: "left"
1533
1567
  });
1534
1568
  }
1535
- };
1536
-
1537
- //#endregion
1538
- //#region src/dev/DevConsumerPublishBootstrap.ts
1539
- /**
1540
- * Ensures `.codemation/output/current.json` and transpiled consumer config exist before the Next host boots.
1541
- * Without this, `codemation dev` can serve a stale built `codemation.config.js` (e.g. missing whitelabel).
1542
- */
1543
- var DevConsumerPublishBootstrap = class {
1544
- constructor(cliLogger, pluginDiscovery, artifactsPublisher, outputBuilderLoader, buildOptionsParser) {
1545
- this.cliLogger = cliLogger;
1546
- this.pluginDiscovery = pluginDiscovery;
1547
- this.artifactsPublisher = artifactsPublisher;
1548
- this.outputBuilderLoader = outputBuilderLoader;
1549
- this.buildOptionsParser = buildOptionsParser;
1550
- }
1551
- async ensurePublished(paths) {
1552
- const buildOptions = this.buildOptionsParser.parse({});
1553
- const snapshot = await this.outputBuilderLoader.create(paths.consumerRoot, buildOptions).ensureBuilt();
1554
- const discoveredPlugins = await this.pluginDiscovery.discover(paths.consumerRoot);
1555
- await this.artifactsPublisher.publish(snapshot, discoveredPlugins);
1556
- this.cliLogger.debug(`Dev: consumer output published (${snapshot.buildVersion}).`);
1569
+ buildPluginsSection(summary) {
1570
+ return boxen((summary.plugins.length === 0 ? [chalk.dim(" (none discovered or configured)")] : summary.plugins.map((plugin) => `${chalk.whiteBright(` • ${plugin.packageName} `)}${chalk.dim(`(${plugin.source})`)}`)).join("\n"), {
1571
+ padding: {
1572
+ top: 0,
1573
+ bottom: 0,
1574
+ left: 0,
1575
+ right: 0
1576
+ },
1577
+ margin: {
1578
+ top: 1,
1579
+ bottom: 0
1580
+ },
1581
+ borderStyle: "single",
1582
+ borderColor: "cyan",
1583
+ title: chalk.bold("Plugins"),
1584
+ titleAlignment: "left"
1585
+ });
1557
1586
  }
1558
1587
  };
1559
1588
 
@@ -1746,12 +1775,27 @@ var CliDevProxyServer = class {
1746
1775
  return url.split("?")[0] ?? url;
1747
1776
  }
1748
1777
  }
1778
+ extractOccupyingPids(listenerDescription) {
1779
+ const seen = /* @__PURE__ */ new Set();
1780
+ const re = /pid=(\d+)/g;
1781
+ let match;
1782
+ while ((match = re.exec(listenerDescription)) !== null) {
1783
+ const pid = Number.parseInt(match[1] ?? "0", 10);
1784
+ if (Number.isFinite(pid) && pid > 0) seen.add(pid);
1785
+ }
1786
+ return [...seen];
1787
+ }
1749
1788
  async rejectListenError(error, reject) {
1750
1789
  if (error.code !== "EADDRINUSE") {
1751
1790
  reject(error);
1752
1791
  return;
1753
1792
  }
1754
1793
  const description = await this.listenPortConflictDescriber.describeLoopbackPort(this.listenPort);
1794
+ const occupyingPids = description !== null ? this.extractOccupyingPids(description) : [];
1795
+ if (occupyingPids.length > 0) {
1796
+ const pidList = occupyingPids.join(", ");
1797
+ process.stderr.write(`[codemation] Dev gateway port ${this.listenPort} is already in use (occupying pid(s): ${pidList}).\n`);
1798
+ }
1755
1799
  const baseMessage = `Dev gateway port ${this.listenPort} is already in use on 127.0.0.1.`;
1756
1800
  const suffix = description === null ? " Stop the process using that port or change the configured Codemation dev port." : ` Listener: ${description}. Stop that process or change the configured Codemation dev port.`;
1757
1801
  reject(new Error(`${baseMessage}${suffix}`, { cause: error instanceof Error ? error : void 0 }));
@@ -1929,12 +1973,19 @@ var ListenPortConflictDescriber = class {
1929
1973
  async describeLoopbackPort(port) {
1930
1974
  if (!Number.isInteger(port) || port <= 0) return null;
1931
1975
  if (this.platform !== "linux" && this.platform !== "darwin") return null;
1932
- const raw = await this.readLsofOutput(port);
1933
- if (raw === null) return null;
1934
- const occupants = this.parseLsofOutput(raw);
1976
+ const occupants = await this.resolveLoopbackOccupants(port);
1935
1977
  if (occupants.length === 0) return null;
1936
1978
  return occupants.map((occupant) => `pid=${occupant.pid} command=${occupant.command} endpoint=${occupant.endpoint}`).join("; ");
1937
1979
  }
1980
+ async resolveLoopbackOccupants(port) {
1981
+ const lsofRaw = await this.readLsofOutput(port);
1982
+ const fromLsof = lsofRaw !== null ? this.parseLsofOutput(lsofRaw) : [];
1983
+ if (fromLsof.length > 0) return fromLsof;
1984
+ if (this.platform !== "linux") return [];
1985
+ const ssRaw = await this.readSsOutput(port);
1986
+ if (ssRaw === null) return [];
1987
+ return this.parseSsListenOutput(ssRaw, port);
1988
+ }
1938
1989
  async readLsofOutput(port) {
1939
1990
  try {
1940
1991
  return await new Promise((resolve, reject) => {
@@ -1980,6 +2031,44 @@ var ListenPortConflictDescriber = class {
1980
2031
  }
1981
2032
  return occupants;
1982
2033
  }
2034
+ async readSsOutput(port) {
2035
+ const filtered = await this.execFileStdout("ss", ["-lntp", `sport = :${port}`]);
2036
+ if (filtered !== null && filtered.trim().length > 0) return filtered;
2037
+ return this.execFileStdout("ss", ["-lntp"]);
2038
+ }
2039
+ async execFileStdout(command, args) {
2040
+ try {
2041
+ return await new Promise((resolve, reject) => {
2042
+ execFile(command, [...args], (error, stdout) => {
2043
+ if (error) {
2044
+ reject(error);
2045
+ return;
2046
+ }
2047
+ resolve(stdout);
2048
+ });
2049
+ });
2050
+ } catch {
2051
+ return null;
2052
+ }
2053
+ }
2054
+ parseSsListenOutput(raw, port) {
2055
+ const occupants = [];
2056
+ const portSuffix = `:${port}`;
2057
+ for (const line of raw.split("\n")) {
2058
+ if (!line.includes("LISTEN") || !line.includes(portSuffix)) continue;
2059
+ const pidMatch = line.match(/pid=(\d+)/);
2060
+ if (!pidMatch) continue;
2061
+ const pid = Number.parseInt(pidMatch[1] ?? "0", 10);
2062
+ const command = line.match(/users:\(\("([^"]*)"/)?.[1] ?? "unknown";
2063
+ const endpoint = line.match(/\s+(\S+:\d+|\[[^\]]+\]:\d+)\s+/)?.[1] ?? `tcp:${String(port)}`;
2064
+ occupants.push({
2065
+ pid,
2066
+ command,
2067
+ endpoint
2068
+ });
2069
+ }
2070
+ return occupants;
2071
+ }
1983
2072
  };
1984
2073
 
1985
2074
  //#endregion
@@ -2089,7 +2178,8 @@ var CodemationTsyringeTypeInfoRegistrar = class {
2089
2178
  //#endregion
2090
2179
  //#region src/dev/DevApiRuntimeHost.ts
2091
2180
  var DevApiRuntimeHost = class {
2092
- pluginListMerger = new CodemationPluginListMerger();
2181
+ pluginPackageMetadata = new CodemationPluginPackageMetadata();
2182
+ pluginListMerger = new CodemationPluginListMerger(this.pluginPackageMetadata);
2093
2183
  contextPromise = null;
2094
2184
  constructor(configLoader, pluginDiscovery, args) {
2095
2185
  this.configLoader = configLoader;
@@ -2118,14 +2208,17 @@ var DevApiRuntimeHost = class {
2118
2208
  env.CODEMATION_CONSUMER_ROOT = consumerRoot;
2119
2209
  const configResolution = await this.configLoader.load({
2120
2210
  consumerRoot,
2211
+ configPathOverride: this.args.configPathOverride,
2121
2212
  repoRoot,
2122
2213
  env
2123
2214
  });
2124
2215
  const discoveredPlugins = await this.loadDiscoveredPlugins(consumerRoot);
2216
+ const mergedPlugins = discoveredPlugins.length > 0 ? this.pluginListMerger.merge(configResolution.appConfig.plugins, discoveredPlugins) : configResolution.appConfig.plugins;
2125
2217
  const appConfig = {
2126
2218
  ...configResolution.appConfig,
2127
2219
  env,
2128
- plugins: discoveredPlugins.length > 0 ? this.pluginListMerger.merge(configResolution.appConfig.plugins, discoveredPlugins) : configResolution.appConfig.plugins
2220
+ plugins: mergedPlugins,
2221
+ pluginLoadSummary: this.createPluginLoadSummary(configResolution.appConfig.plugins, discoveredPlugins, mergedPlugins)
2129
2222
  };
2130
2223
  const container = await new AppContainerFactory$1().create({
2131
2224
  appConfig,
@@ -2145,6 +2238,20 @@ var DevApiRuntimeHost = class {
2145
2238
  async loadDiscoveredPlugins(consumerRoot) {
2146
2239
  return (await this.pluginDiscovery.resolvePlugins(consumerRoot)).map((resolvedPackage) => resolvedPackage.plugin);
2147
2240
  }
2241
+ createPluginLoadSummary(configuredPlugins, discoveredPlugins, mergedPlugins) {
2242
+ const configuredPluginSet = new Set(configuredPlugins);
2243
+ const discoveredPluginSet = new Set(discoveredPlugins);
2244
+ const summaries = [];
2245
+ for (const plugin of mergedPlugins) {
2246
+ const packageName = this.pluginPackageMetadata.readPackageName(plugin);
2247
+ if (!packageName) continue;
2248
+ summaries.push({
2249
+ packageName,
2250
+ source: configuredPluginSet.has(plugin) || !discoveredPluginSet.has(plugin) ? "configured" : "discovered"
2251
+ });
2252
+ }
2253
+ return summaries;
2254
+ }
2148
2255
  async detectWorkspaceRoot(startDirectory) {
2149
2256
  let currentDirectory = path.resolve(startDirectory);
2150
2257
  while (true) {
@@ -2262,6 +2369,7 @@ var DevApiRuntimeFactory = class {
2262
2369
  const httpPort = await this.portAllocator.allocate();
2263
2370
  const workflowWebSocketPort = await this.portAllocator.allocate();
2264
2371
  const runtime = new DevApiRuntimeServer(httpPort, workflowWebSocketPort, new DevApiRuntimeHost(this.configLoader, this.pluginDiscovery, {
2372
+ configPathOverride: args.configPathOverride,
2265
2373
  consumerRoot: args.consumerRoot,
2266
2374
  env: {
2267
2375
  ...args.env,
@@ -2318,7 +2426,7 @@ var DevRebuildQueue = class {
2318
2426
  };
2319
2427
  return {
2320
2428
  changedPaths: [...new Set([...current.changedPaths, ...next.changedPaths])],
2321
- shouldRepublishConsumerOutput: current.shouldRepublishConsumerOutput || next.shouldRepublishConsumerOutput,
2429
+ configPathOverride: next.configPathOverride ?? current.configPathOverride,
2322
2430
  shouldRestartUi: current.shouldRestartUi || next.shouldRestartUi
2323
2431
  };
2324
2432
  }
@@ -2366,34 +2474,6 @@ var SourceMapNodeOptions = class {
2366
2474
  }
2367
2475
  };
2368
2476
 
2369
- //#endregion
2370
- //#region src/dev/DevAuthSettingsLoader.ts
2371
- var DevAuthSettingsLoader = class DevAuthSettingsLoader {
2372
- static defaultDevelopmentAuthSecret = "codemation-dev-auth-secret-not-for-production";
2373
- constructor(configLoader, consumerEnvLoader) {
2374
- this.configLoader = configLoader;
2375
- this.consumerEnvLoader = consumerEnvLoader;
2376
- }
2377
- resolveDevelopmentServerToken(rawToken) {
2378
- if (rawToken && rawToken.trim().length > 0) return rawToken;
2379
- return randomUUID();
2380
- }
2381
- async loadForConsumer(consumerRoot) {
2382
- const resolution = await this.configLoader.load({ consumerRoot });
2383
- const envForAuthSecret = this.consumerEnvLoader.mergeConsumerRootIntoProcessEnvironment(consumerRoot, process.env);
2384
- return {
2385
- authConfigJson: JSON.stringify(resolution.config.auth ?? null),
2386
- authSecret: this.resolveDevelopmentAuthSecret(envForAuthSecret),
2387
- skipUiAuth: resolution.config.auth?.allowUnauthenticatedInDevelopment === true
2388
- };
2389
- }
2390
- resolveDevelopmentAuthSecret(env) {
2391
- const configuredSecret = env.AUTH_SECRET;
2392
- if (configuredSecret && configuredSecret.trim().length > 0) return configuredSecret;
2393
- return DevAuthSettingsLoader.defaultDevelopmentAuthSecret;
2394
- }
2395
- };
2396
-
2397
2477
  //#endregion
2398
2478
  //#region src/dev/DevHttpProbe.ts
2399
2479
  var DevHttpProbe = class {
@@ -2438,52 +2518,41 @@ var DevHttpProbe = class {
2438
2518
  //#endregion
2439
2519
  //#region src/dev/DevNextHostEnvironmentBuilder.ts
2440
2520
  var DevNextHostEnvironmentBuilder = class {
2441
- constructor(consumerEnvLoader, sourceMapNodeOptions, frontendAuthSnapshotFactory = new CodemationFrontendAuthSnapshotFactory$1(), frontendAppConfigJsonCodec = new FrontendAppConfigJsonCodec$1()) {
2521
+ constructor(consumerEnvLoader, sourceMapNodeOptions) {
2442
2522
  this.consumerEnvLoader = consumerEnvLoader;
2443
2523
  this.sourceMapNodeOptions = sourceMapNodeOptions;
2444
- this.frontendAuthSnapshotFactory = frontendAuthSnapshotFactory;
2445
- this.frontendAppConfigJsonCodec = frontendAppConfigJsonCodec;
2446
2524
  }
2447
2525
  buildConsumerUiProxy(args) {
2526
+ const publicWebsocketPort = this.resolvePublicWebsocketPort(args.publicBaseUrl, args.websocketPort);
2448
2527
  return {
2449
2528
  ...this.build({
2450
- authConfigJson: args.authConfigJson,
2451
2529
  authSecret: args.authSecret,
2530
+ configPathOverride: args.configPathOverride,
2531
+ consumerOutputManifestPath: args.consumerOutputManifestPath,
2452
2532
  consumerRoot: args.consumerRoot,
2453
2533
  developmentServerToken: args.developmentServerToken,
2454
2534
  nextPort: args.nextPort,
2455
2535
  runtimeDevUrl: args.runtimeDevUrl,
2456
2536
  skipUiAuth: args.skipUiAuth,
2457
- websocketPort: args.websocketPort,
2458
- consumerOutputManifestPath: args.consumerOutputManifestPath
2537
+ websocketPort: args.websocketPort
2459
2538
  }),
2460
2539
  HOSTNAME: "127.0.0.1",
2461
2540
  AUTH_SECRET: args.authSecret,
2462
- AUTH_URL: args.publicBaseUrl
2541
+ AUTH_URL: args.publicBaseUrl,
2542
+ CODEMATION_PUBLIC_WS_PORT: String(publicWebsocketPort),
2543
+ NEXT_PUBLIC_CODEMATION_WS_PORT: String(publicWebsocketPort)
2463
2544
  };
2464
2545
  }
2465
2546
  build(args) {
2466
2547
  const merged = this.consumerEnvLoader.mergeConsumerRootIntoProcessEnvironment(args.consumerRoot, process$1.env);
2467
- const manifestPath = args.consumerOutputManifestPath ?? path.resolve(args.consumerRoot, ".codemation", "output", "current.json");
2468
- const authSecret = args.authSecret ?? merged.AUTH_SECRET;
2469
- const authSnapshot = this.frontendAuthSnapshotFactory.createFromResolvedInputs({
2470
- authConfig: this.parseAuthConfig(args.authConfigJson),
2471
- env: {
2472
- ...merged,
2473
- ...typeof authSecret === "string" && authSecret.trim().length > 0 ? { AUTH_SECRET: authSecret } : {}
2474
- },
2475
- uiAuthEnabled: !args.skipUiAuth
2476
- });
2548
+ const consumerOutputManifestPath = args.consumerOutputManifestPath ?? path.resolve(args.consumerRoot, ".codemation", "output", "current.json");
2477
2549
  return {
2478
2550
  ...merged,
2479
2551
  PORT: String(args.nextPort),
2480
2552
  CODEMATION_CONSUMER_ROOT: args.consumerRoot,
2481
- CODEMATION_CONSUMER_OUTPUT_MANIFEST_PATH: manifestPath,
2482
- CODEMATION_FRONTEND_APP_CONFIG_JSON: this.frontendAppConfigJsonCodec.serialize({
2483
- auth: authSnapshot,
2484
- productName: "Codemation",
2485
- logoUrl: null
2486
- }),
2553
+ CODEMATION_CONSUMER_OUTPUT_MANIFEST_PATH: consumerOutputManifestPath,
2554
+ CODEMATION_UI_AUTH_ENABLED: String(!args.skipUiAuth),
2555
+ CODEMATION_PUBLIC_WS_PORT: String(args.websocketPort),
2487
2556
  CODEMATION_WS_PORT: String(args.websocketPort),
2488
2557
  NEXT_PUBLIC_CODEMATION_WS_PORT: String(args.websocketPort),
2489
2558
  CODEMATION_DEV_SERVER_TOKEN: args.developmentServerToken,
@@ -2491,12 +2560,18 @@ var DevNextHostEnvironmentBuilder = class {
2491
2560
  NODE_OPTIONS: this.sourceMapNodeOptions.appendToNodeOptions(process$1.env.NODE_OPTIONS),
2492
2561
  WS_NO_BUFFER_UTIL: "1",
2493
2562
  WS_NO_UTF_8_VALIDATE: "1",
2563
+ ...args.authSecret && args.authSecret.trim().length > 0 ? { AUTH_SECRET: args.authSecret.trim() } : {},
2564
+ ...args.configPathOverride && args.configPathOverride.trim().length > 0 ? { CODEMATION_CONFIG_PATH: args.configPathOverride } : {},
2494
2565
  ...args.runtimeDevUrl !== void 0 && args.runtimeDevUrl.trim().length > 0 ? { CODEMATION_RUNTIME_DEV_URL: args.runtimeDevUrl.trim() } : {}
2495
2566
  };
2496
2567
  }
2497
- parseAuthConfig(authConfigJson) {
2498
- if (authConfigJson.trim().length === 0) return;
2499
- return JSON.parse(authConfigJson) ?? void 0;
2568
+ resolvePublicWebsocketPort(publicBaseUrl, fallbackPort) {
2569
+ try {
2570
+ const parsedUrl = new URL(publicBaseUrl);
2571
+ const parsedPort = Number(parsedUrl.port);
2572
+ if (Number.isInteger(parsedPort) && parsedPort > 0) return parsedPort;
2573
+ } catch {}
2574
+ return fallbackPort;
2500
2575
  }
2501
2576
  };
2502
2577
 
@@ -2522,13 +2597,13 @@ var DevSessionPortsResolver = class {
2522
2597
  * Bundles dependencies for {@link DevCommand} so the command stays a thin orchestrator.
2523
2598
  */
2524
2599
  var DevSessionServices = class {
2525
- constructor(consumerEnvLoader, sourceMapNodeOptions, sessionPorts, loopbackPortAllocator, devHttpProbe, devAuthLoader, nextHostEnvBuilder, watchRootsResolver, sourceChangeClassifier) {
2600
+ constructor(consumerEnvLoader, sourceMapNodeOptions, sessionPorts, loopbackPortAllocator, devHttpProbe, nextHostEdgeSeedLoader, nextHostEnvBuilder, watchRootsResolver, sourceChangeClassifier) {
2526
2601
  this.consumerEnvLoader = consumerEnvLoader;
2527
2602
  this.sourceMapNodeOptions = sourceMapNodeOptions;
2528
2603
  this.sessionPorts = sessionPorts;
2529
2604
  this.loopbackPortAllocator = loopbackPortAllocator;
2530
2605
  this.devHttpProbe = devHttpProbe;
2531
- this.devAuthLoader = devAuthLoader;
2606
+ this.nextHostEdgeSeedLoader = nextHostEdgeSeedLoader;
2532
2607
  this.nextHostEnvBuilder = nextHostEnvBuilder;
2533
2608
  this.watchRootsResolver = watchRootsResolver;
2534
2609
  this.sourceChangeClassifier = sourceChangeClassifier;
@@ -2538,15 +2613,16 @@ var DevSessionServices = class {
2538
2613
  //#endregion
2539
2614
  //#region src/dev/DevSourceChangeClassifier.ts
2540
2615
  var DevSourceChangeClassifier = class DevSourceChangeClassifier {
2541
- static configFileNames = new Set([
2616
+ static uiConfigFileNames = new Set([
2542
2617
  "codemation.config.ts",
2543
2618
  "codemation.config.js",
2544
2619
  "codemation.config.mjs"
2545
2620
  ]);
2546
- shouldRepublishConsumerOutput(args) {
2547
- const resolvedConsumerRoot = path.resolve(args.consumerRoot);
2548
- return args.changedPaths.some((changedPath) => this.isPathInsideDirectory(changedPath, resolvedConsumerRoot));
2549
- }
2621
+ static pluginConfigFileNames = new Set([
2622
+ "codemation.plugin.ts",
2623
+ "codemation.plugin.js",
2624
+ "codemation.plugin.mjs"
2625
+ ]);
2550
2626
  requiresUiRestart(args) {
2551
2627
  const resolvedConsumerRoot = path.resolve(args.consumerRoot);
2552
2628
  return args.changedPaths.some((changedPath) => this.pathRequiresUiRestart(path.resolve(changedPath), resolvedConsumerRoot));
@@ -2559,7 +2635,9 @@ var DevSourceChangeClassifier = class DevSourceChangeClassifier {
2559
2635
  pathRequiresUiRestart(resolvedPath, consumerRoot) {
2560
2636
  if (!this.isPathInsideDirectory(resolvedPath, consumerRoot)) return false;
2561
2637
  const relativePath = path.relative(consumerRoot, resolvedPath);
2562
- if (DevSourceChangeClassifier.configFileNames.has(path.basename(relativePath))) return true;
2638
+ const fileName = path.basename(relativePath);
2639
+ if (DevSourceChangeClassifier.uiConfigFileNames.has(fileName)) return true;
2640
+ if (DevSourceChangeClassifier.pluginConfigFileNames.has(fileName)) return false;
2563
2641
  if (relativePath.startsWith(path.join("src", "workflows"))) return false;
2564
2642
  if (relativePath.startsWith(path.join("src", "plugins"))) return false;
2565
2643
  if (relativePath.includes("credential")) return true;
@@ -2588,6 +2666,36 @@ var LoopbackPortAllocator = class {
2588
2666
  }
2589
2667
  };
2590
2668
 
2669
+ //#endregion
2670
+ //#region src/dev/NextHostEdgeSeedLoader.ts
2671
+ var NextHostEdgeSeedLoader = class NextHostEdgeSeedLoader {
2672
+ static defaultDevelopmentAuthSecret = "codemation-dev-auth-secret-not-for-production";
2673
+ constructor(configLoader, consumerEnvLoader) {
2674
+ this.configLoader = configLoader;
2675
+ this.consumerEnvLoader = consumerEnvLoader;
2676
+ }
2677
+ resolveDevelopmentServerToken(rawToken) {
2678
+ if (rawToken && rawToken.trim().length > 0) return rawToken;
2679
+ return randomUUID();
2680
+ }
2681
+ async loadForConsumer(consumerRoot, options) {
2682
+ const resolution = await this.configLoader.load({
2683
+ consumerRoot,
2684
+ configPathOverride: options?.configPathOverride
2685
+ });
2686
+ const envForAuthSecret = this.consumerEnvLoader.mergeConsumerRootIntoProcessEnvironment(consumerRoot, process.env);
2687
+ return {
2688
+ authSecret: this.resolveDevelopmentAuthSecret(envForAuthSecret),
2689
+ uiAuthEnabled: resolution.config.auth?.allowUnauthenticatedInDevelopment !== true
2690
+ };
2691
+ }
2692
+ resolveDevelopmentAuthSecret(env) {
2693
+ const configuredSecret = env.AUTH_SECRET;
2694
+ if (configuredSecret && configuredSecret.trim().length > 0) return configuredSecret;
2695
+ return NextHostEdgeSeedLoader.defaultDevelopmentAuthSecret;
2696
+ }
2697
+ };
2698
+
2591
2699
  //#endregion
2592
2700
  //#region src/dev/WatchRootsResolver.ts
2593
2701
  var WatchRootsResolver = class {
@@ -2614,7 +2722,57 @@ var DevSessionServicesBuilder = class {
2614
2722
  const sourceMapNodeOptions = new SourceMapNodeOptions();
2615
2723
  const listenPortResolver = new ListenPortResolver();
2616
2724
  const loopbackPortAllocator = new LoopbackPortAllocator();
2617
- 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());
2725
+ return new DevSessionServices(consumerEnvLoader, sourceMapNodeOptions, new DevSessionPortsResolver(listenPortResolver, loopbackPortAllocator), loopbackPortAllocator, new DevHttpProbe(), new NextHostEdgeSeedLoader(new CodemationConsumerConfigLoader(), consumerEnvLoader), new DevNextHostEnvironmentBuilder(consumerEnvLoader, sourceMapNodeOptions), new WatchRootsResolver(), new DevSourceChangeClassifier());
2726
+ }
2727
+ };
2728
+
2729
+ //#endregion
2730
+ //#region src/dev/PluginDevConfigFactory.ts
2731
+ var PluginDevConfigFactory = class {
2732
+ async prepare(pluginRoot) {
2733
+ const pluginEntryPath = await this.resolvePluginEntryPath(pluginRoot);
2734
+ const configPath = path.resolve(pluginRoot, ".codemation", "plugin-dev", "codemation.config.ts");
2735
+ await mkdir(path.dirname(configPath), { recursive: true });
2736
+ await writeFile(configPath, this.createConfigSource(configPath, pluginEntryPath), "utf8");
2737
+ return { configPath };
2738
+ }
2739
+ async resolvePluginEntryPath(pluginRoot) {
2740
+ const candidates = [
2741
+ path.resolve(pluginRoot, "codemation.plugin.ts"),
2742
+ path.resolve(pluginRoot, "codemation.plugin.js"),
2743
+ path.resolve(pluginRoot, "src", "codemation.plugin.ts"),
2744
+ path.resolve(pluginRoot, "src", "codemation.plugin.js")
2745
+ ];
2746
+ for (const candidate of candidates) if (await this.exists(candidate)) return candidate;
2747
+ throw new Error("Plugin config not found. Expected \"codemation.plugin.ts\" in the plugin root or \"src/\".");
2748
+ }
2749
+ createConfigSource(configPath, pluginEntryPath) {
2750
+ const relativeImportPath = this.toRelativeImportPath(configPath, pluginEntryPath);
2751
+ return [
2752
+ "import type { CodemationConfig } from \"@codemation/host\";",
2753
+ `import plugin from ${JSON.stringify(relativeImportPath)};`,
2754
+ "",
2755
+ "const sandbox = plugin.sandbox ?? {};",
2756
+ "const config: CodemationConfig = {",
2757
+ " ...sandbox,",
2758
+ " plugins: [...(sandbox.plugins ?? []), plugin],",
2759
+ "};",
2760
+ "",
2761
+ "export default config;",
2762
+ ""
2763
+ ].join("\n");
2764
+ }
2765
+ toRelativeImportPath(fromPath, targetPath) {
2766
+ const relativePath = path.relative(path.dirname(fromPath), targetPath).replace(/\\/g, "/");
2767
+ return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
2768
+ }
2769
+ async exists(filePath) {
2770
+ try {
2771
+ await access(filePath);
2772
+ return true;
2773
+ } catch {
2774
+ return false;
2775
+ }
2618
2776
  }
2619
2777
  };
2620
2778
 
@@ -2870,10 +3028,11 @@ var DevSourceWatcherFactory = class {
2870
3028
  //#endregion
2871
3029
  //#region src/Program.ts
2872
3030
  var CliProgram = class {
2873
- constructor(buildOptionsParser, buildCommand, devCommand, serveWebCommand, serveWorkerCommand, dbMigrateCommand, userCreateCommand, userListCommand) {
3031
+ constructor(buildOptionsParser, buildCommand, devCommand, devPluginCommand, serveWebCommand, serveWorkerCommand, dbMigrateCommand, userCreateCommand, userListCommand) {
2874
3032
  this.buildOptionsParser = buildOptionsParser;
2875
3033
  this.buildCommand = buildCommand;
2876
3034
  this.devCommand = devCommand;
3035
+ this.devPluginCommand = devPluginCommand;
2877
3036
  this.serveWebCommand = serveWebCommand;
2878
3037
  this.serveWorkerCommand = serveWorkerCommand;
2879
3038
  this.dbMigrateCommand = dbMigrateCommand;
@@ -2893,6 +3052,9 @@ var CliProgram = class {
2893
3052
  watchFramework: opts.watchFramework === true
2894
3053
  });
2895
3054
  });
3055
+ program.command("dev:plugin").description("Start plugin sandbox development using `codemation.plugin.ts`.").option("--plugin-root <path>", "Path to the plugin project root (defaults to cwd)").action(async (opts) => {
3056
+ await this.devPluginCommand.execute({ pluginRoot: resolveConsumerRoot(opts.pluginRoot) });
3057
+ });
2896
3058
  const serve$1 = program.command("serve").description("Run production web or worker processes (no dev watchers).");
2897
3059
  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) => {
2898
3060
  await this.serveWebCommand.execute(resolveConsumerRoot(opts.consumerRoot), this.buildOptionsParser.parse(opts));
@@ -3160,9 +3322,7 @@ var CliProgramFactory = class {
3160
3322
  const appConfigLoader = new AppConfigLoader();
3161
3323
  const pathResolver = new CliPathResolver();
3162
3324
  const pluginDiscovery = new CodemationPluginDiscovery();
3163
- const artifactsPublisher = new ConsumerBuildArtifactsPublisher();
3164
3325
  const tsRuntime = new TypeScriptRuntimeConfigurator();
3165
- const outputBuilderLoader = new ConsumerOutputBuilderLoader();
3166
3326
  const sourceMapNodeOptions = new SourceMapNodeOptions();
3167
3327
  const nextHostConsumerServerCommandFactory = new NextHostConsumerServerCommandFactory();
3168
3328
  const devSessionServices = new DevSessionServicesBuilder().build();
@@ -3172,8 +3332,10 @@ var CliProgramFactory = class {
3172
3332
  const userAdminCliOptionsParser = new UserAdminCliOptionsParser();
3173
3333
  const databaseMigrationsApplyService = new DatabaseMigrationsApplyService(cliLogger, new UserAdminConsumerDotenvLoader(), tsconfigPreparation, new CodemationConsumerConfigLoader(), new ConsumerDatabaseConnectionResolver(), new CliDatabaseUrlDescriptor(), hostPackageRoot, new PrismaMigrationDeployer());
3174
3334
  const buildOptionsParser = new ConsumerBuildOptionsParser();
3175
- const devConsumerPublishBootstrap = new DevConsumerPublishBootstrap(cliLogger, pluginDiscovery, artifactsPublisher, outputBuilderLoader, buildOptionsParser);
3176
- 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));
3335
+ const consumerOutputBuilderFactory = new ConsumerOutputBuilderFactory();
3336
+ const consumerBuildArtifactsPublisher = new ConsumerBuildArtifactsPublisher();
3337
+ const devCommand = new DevCommand(pathResolver, tsRuntime, new DevLockFactory(), new DevSourceWatcherFactory(), cliLogger, devSessionServices, databaseMigrationsApplyService, consumerOutputBuilderFactory, pluginDiscovery, consumerBuildArtifactsPublisher, new DevBootstrapSummaryFetcher(), new DevCliBannerRenderer(), new ConsumerEnvDotenvFilePredicate(), new DevTrackedProcessTreeKiller(), nextHostConsumerServerCommandFactory, new DevApiRuntimeFactory(devSessionServices.loopbackPortAllocator, appConfigLoader, pluginDiscovery), new CliDevProxyServerFactory(), new DevRebuildQueueFactory());
3338
+ return new CliProgram(buildOptionsParser, new BuildCommand(cliLogger, pathResolver, consumerOutputBuilderFactory, pluginDiscovery, consumerBuildArtifactsPublisher, tsRuntime), devCommand, new DevPluginCommand(new PluginDevConfigFactory(), devCommand), new ServeWebCommand(pathResolver, new CodemationConsumerConfigLoader(), tsRuntime, sourceMapNodeOptions, new ConsumerEnvLoader(), new ListenPortResolver(), nextHostConsumerServerCommandFactory), new ServeWorkerCommand(pathResolver, appConfigLoader, new AppContainerFactory()), new DbMigrateCommand(databaseMigrationsApplyService), new UserCreateCommand(new LocalUserCreator(userAdminBootstrap), userAdminCliOptionsParser), new UserListCommand(cliLogger, userAdminBootstrap, new CliDatabaseUrlDescriptor(), userAdminCliOptionsParser));
3177
3339
  }
3178
3340
  };
3179
3341