@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.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as CliProgram, i as CliPathResolver, n as CliProgramFactory, o as ConsumerOutputBuilder, r as CodemationCliApplicationSession, s as ConsumerBuildOptionsParser, t as CliBin } from "./CliBin-BkY_gChN.js";
1
+ import { a as CliProgram, i as CliPathResolver, n as CliProgramFactory, o as ConsumerOutputBuilder, r as CodemationCliApplicationSession, s as ConsumerBuildOptionsParser, t as CliBin } from "./CliBin-BYHuUedo.js";
2
2
  import { CodemationPluginDiscovery } from "@codemation/host/server";
3
3
 
4
4
  export { CliBin, CliPathResolver, CliProgram, CliProgramFactory, CodemationCliApplicationSession, CodemationPluginDiscovery, ConsumerBuildOptionsParser, ConsumerOutputBuilder };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemation/cli",
3
- "version": "0.0.15",
3
+ "version": "0.0.18",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -11,6 +11,7 @@
11
11
  "url": "https://github.com/MadeRelevant/codemation",
12
12
  "directory": "packages/cli"
13
13
  },
14
+ "license": "SEE LICENSE IN LICENSE",
14
15
  "type": "module",
15
16
  "main": "./dist/index.js",
16
17
  "types": "./dist/index.d.ts",
@@ -37,8 +38,8 @@
37
38
  "reflect-metadata": "^0.2.2",
38
39
  "typescript": "^5.9.3",
39
40
  "ws": "^8.19.0",
40
- "@codemation/host": "0.0.15",
41
- "@codemation/next-host": "0.0.15"
41
+ "@codemation/host": "0.0.18",
42
+ "@codemation/next-host": "0.0.18"
42
43
  },
43
44
  "devDependencies": {
44
45
  "@types/http-proxy": "^1.17.15",
@@ -49,7 +50,7 @@
49
50
  "tsx": "^4.21.0",
50
51
  "typescript": "^5.9.3",
51
52
  "vitest": "4.0.18",
52
- "@codemation/eslint-config": "0.0.0"
53
+ "@codemation/eslint-config": "0.0.1"
53
54
  },
54
55
  "peerDependencies": {
55
56
  "tsx": ">=4.0.0"
@@ -1,36 +1,31 @@
1
- import {
2
- AppConfigLoader,
3
- CodemationConsumerConfigLoader,
4
- CodemationFrontendAuthSnapshotFactory,
5
- CodemationPluginDiscovery,
6
- FrontendAppConfigJsonCodec,
7
- } from "@codemation/host/server";
1
+ import { AppConfigLoader, CodemationConsumerConfigLoader, CodemationPluginDiscovery } from "@codemation/host/server";
8
2
  import { AppContainerFactory } from "@codemation/host";
9
3
  import { logLevelPolicyFactory, ServerLoggerFactory } from "@codemation/host/next/server";
10
4
 
11
- import { ConsumerBuildArtifactsPublisher } from "./build/ConsumerBuildArtifactsPublisher";
12
5
  import { ConsumerBuildOptionsParser } from "./build/ConsumerBuildOptionsParser";
6
+ import { ConsumerBuildArtifactsPublisher } from "./build/ConsumerBuildArtifactsPublisher";
13
7
  import { BuildCommand } from "./commands/BuildCommand";
14
8
  import { DbMigrateCommand } from "./commands/DbMigrateCommand";
15
9
  import { DevCommand } from "./commands/DevCommand";
10
+ import { DevPluginCommand } from "./commands/DevPluginCommand";
16
11
  import { ServeWebCommand } from "./commands/ServeWebCommand";
17
12
  import { ServeWorkerCommand } from "./commands/ServeWorkerCommand";
18
13
  import { UserCreateCommand } from "./commands/UserCreateCommand";
19
14
  import { UserListCommand } from "./commands/UserListCommand";
20
15
  import { ConsumerCliTsconfigPreparation } from "./consumer/ConsumerCliTsconfigPreparation";
21
16
  import { ConsumerEnvLoader } from "./consumer/ConsumerEnvLoader";
22
- import { ConsumerOutputBuilderLoader } from "./consumer/Loader";
17
+ import { ConsumerOutputBuilderFactory } from "./consumer/ConsumerOutputBuilderFactory";
23
18
  import { ConsumerDatabaseConnectionResolver } from "./database/ConsumerDatabaseConnectionResolver";
24
19
  import { DatabaseMigrationsApplyService } from "./database/DatabaseMigrationsApplyService";
25
20
  import { HostPackageRootResolver } from "./database/HostPackageRootResolver";
26
21
  import { PrismaMigrationDeployer } from "@codemation/host/persistence";
27
22
  import { DevBootstrapSummaryFetcher } from "./dev/DevBootstrapSummaryFetcher";
28
23
  import { DevCliBannerRenderer } from "./dev/DevCliBannerRenderer";
29
- import { DevConsumerPublishBootstrap } from "./dev/DevConsumerPublishBootstrap";
30
24
  import { CliDevProxyServerFactory } from "./dev/CliDevProxyServerFactory";
31
25
  import { DevApiRuntimeFactory } from "./dev/DevApiRuntimeFactory";
32
26
  import { DevRebuildQueueFactory } from "./dev/DevRebuildQueueFactory";
33
27
  import { DevSessionServicesBuilder } from "./dev/Builder";
28
+ import { PluginDevConfigFactory } from "./dev/PluginDevConfigFactory";
34
29
  import { DevLockFactory } from "./dev/Factory";
35
30
  import { ConsumerEnvDotenvFilePredicate } from "./dev/ConsumerEnvDotenvFilePredicate";
36
31
  import { DevTrackedProcessTreeKiller } from "./dev/DevTrackedProcessTreeKiller";
@@ -59,9 +54,7 @@ export class CliProgramFactory {
59
54
  const appConfigLoader = new AppConfigLoader();
60
55
  const pathResolver = new CliPathResolver();
61
56
  const pluginDiscovery = new CodemationPluginDiscovery();
62
- const artifactsPublisher = new ConsumerBuildArtifactsPublisher();
63
57
  const tsRuntime = new TypeScriptRuntimeConfigurator();
64
- const outputBuilderLoader = new ConsumerOutputBuilderLoader();
65
58
  const sourceMapNodeOptions = new SourceMapNodeOptions();
66
59
  const nextHostConsumerServerCommandFactory = new NextHostConsumerServerCommandFactory();
67
60
  const devSessionServices = new DevSessionServicesBuilder().build();
@@ -86,47 +79,48 @@ export class CliProgramFactory {
86
79
  );
87
80
 
88
81
  const buildOptionsParser = new ConsumerBuildOptionsParser();
89
- const devConsumerPublishBootstrap = new DevConsumerPublishBootstrap(
82
+ const consumerOutputBuilderFactory = new ConsumerOutputBuilderFactory();
83
+ const consumerBuildArtifactsPublisher = new ConsumerBuildArtifactsPublisher();
84
+ const devCommand = new DevCommand(
85
+ pathResolver,
86
+ tsRuntime,
87
+ new DevLockFactory(),
88
+ new DevSourceWatcherFactory(),
90
89
  cliLogger,
90
+ devSessionServices,
91
+ databaseMigrationsApplyService,
92
+ consumerOutputBuilderFactory,
91
93
  pluginDiscovery,
92
- artifactsPublisher,
93
- outputBuilderLoader,
94
- buildOptionsParser,
94
+ consumerBuildArtifactsPublisher,
95
+ new DevBootstrapSummaryFetcher(),
96
+ new DevCliBannerRenderer(),
97
+ new ConsumerEnvDotenvFilePredicate(),
98
+ new DevTrackedProcessTreeKiller(),
99
+ nextHostConsumerServerCommandFactory,
100
+ new DevApiRuntimeFactory(devSessionServices.loopbackPortAllocator, appConfigLoader, pluginDiscovery),
101
+ new CliDevProxyServerFactory(),
102
+ new DevRebuildQueueFactory(),
95
103
  );
96
104
  return new CliProgram(
97
105
  buildOptionsParser,
98
- new BuildCommand(cliLogger, pathResolver, pluginDiscovery, artifactsPublisher, tsRuntime, outputBuilderLoader),
99
- new DevCommand(
106
+ new BuildCommand(
107
+ cliLogger,
100
108
  pathResolver,
109
+ consumerOutputBuilderFactory,
110
+ pluginDiscovery,
111
+ consumerBuildArtifactsPublisher,
101
112
  tsRuntime,
102
- new DevLockFactory(),
103
- new DevSourceWatcherFactory(),
104
- cliLogger,
105
- devSessionServices,
106
- databaseMigrationsApplyService,
107
- new DevBootstrapSummaryFetcher(),
108
- new DevCliBannerRenderer(),
109
- devConsumerPublishBootstrap,
110
- new ConsumerEnvDotenvFilePredicate(),
111
- new DevTrackedProcessTreeKiller(),
112
- nextHostConsumerServerCommandFactory,
113
- new DevApiRuntimeFactory(devSessionServices.loopbackPortAllocator, appConfigLoader, pluginDiscovery),
114
- new CliDevProxyServerFactory(),
115
- new DevRebuildQueueFactory(),
116
113
  ),
114
+ devCommand,
115
+ new DevPluginCommand(new PluginDevConfigFactory(), devCommand),
117
116
  new ServeWebCommand(
118
117
  pathResolver,
119
118
  new CodemationConsumerConfigLoader(),
120
- pluginDiscovery,
121
- artifactsPublisher,
122
119
  tsRuntime,
123
120
  sourceMapNodeOptions,
124
- outputBuilderLoader,
125
121
  new ConsumerEnvLoader(),
126
122
  new ListenPortResolver(),
127
123
  nextHostConsumerServerCommandFactory,
128
- new CodemationFrontendAuthSnapshotFactory(),
129
- new FrontendAppConfigJsonCodec(),
130
124
  ),
131
125
  new ServeWorkerCommand(pathResolver, appConfigLoader, new AppContainerFactory()),
132
126
  new DbMigrateCommand(databaseMigrationsApplyService),
package/src/Program.ts CHANGED
@@ -8,6 +8,7 @@ import { ConsumerBuildOptionsParser } from "./build/ConsumerBuildOptionsParser";
8
8
  import { BuildCommand } from "./commands/BuildCommand";
9
9
  import type { DbMigrateCommand } from "./commands/DbMigrateCommand";
10
10
  import { DevCommand } from "./commands/DevCommand";
11
+ import type { DevPluginCommand } from "./commands/DevPluginCommand";
11
12
  import { ServeWebCommand } from "./commands/ServeWebCommand";
12
13
  import { ServeWorkerCommand } from "./commands/ServeWorkerCommand";
13
14
  import { UserCreateCommand } from "./commands/UserCreateCommand";
@@ -18,6 +19,7 @@ export class CliProgram {
18
19
  private readonly buildOptionsParser: ConsumerBuildOptionsParser,
19
20
  private readonly buildCommand: BuildCommand,
20
21
  private readonly devCommand: DevCommand,
22
+ private readonly devPluginCommand: DevPluginCommand,
21
23
  private readonly serveWebCommand: ServeWebCommand,
22
24
  private readonly serveWorkerCommand: ServeWorkerCommand,
23
25
  private readonly dbMigrateCommand: DbMigrateCommand,
@@ -68,6 +70,16 @@ export class CliProgram {
68
70
  });
69
71
  });
70
72
 
73
+ program
74
+ .command("dev:plugin")
75
+ .description("Start plugin sandbox development using `codemation.plugin.ts`.")
76
+ .option("--plugin-root <path>", "Path to the plugin project root (defaults to cwd)")
77
+ .action(async (opts: Readonly<{ pluginRoot?: string }>) => {
78
+ await this.devPluginCommand.execute({
79
+ pluginRoot: resolveConsumerRoot(opts.pluginRoot),
80
+ });
81
+ });
82
+
71
83
  const serve = program.command("serve").description("Run production web or worker processes (no dev watchers).");
72
84
 
73
85
  serve
@@ -32,14 +32,12 @@ export class ConsumerBuildArtifactsPublisher {
32
32
  await mkdir(path.dirname(outputPath), { recursive: true });
33
33
  const outputLines: string[] = ["const codemationDiscoveredPlugins = [];", ""];
34
34
  discoveredPlugins.forEach((discoveredPlugin: CodemationDiscoveredPluginPackage, index: number) => {
35
- const pluginFileUrl = pathToFileURL(
36
- path.resolve(discoveredPlugin.packageRoot, discoveredPlugin.manifest.entry),
37
- ).href;
38
- const exportNameAccessor = discoveredPlugin.manifest.exportName
39
- ? `pluginModule${index}[${JSON.stringify(discoveredPlugin.manifest.exportName)}]`
40
- : `pluginModule${index}.default ?? pluginModule${index}.codemationPlugin`;
35
+ const pluginModulePath = path.resolve(discoveredPlugin.packageRoot, this.resolvePluginEntry(discoveredPlugin));
36
+ const pluginFileUrl = pathToFileURL(pluginModulePath).href;
41
37
  outputLines.push(`const pluginModule${index} = await import(${JSON.stringify(pluginFileUrl)});`);
42
- outputLines.push(`const pluginValue${index} = ${exportNameAccessor};`);
38
+ outputLines.push(
39
+ `const pluginValue${index} = pluginModule${index}.default ?? pluginModule${index}.codemationPlugin;`,
40
+ );
43
41
  outputLines.push(`if (pluginValue${index} && typeof pluginValue${index}.register === "function") {`);
44
42
  outputLines.push(` codemationDiscoveredPlugins.push(pluginValue${index});`);
45
43
  outputLines.push(
@@ -74,4 +72,11 @@ export class ConsumerBuildArtifactsPublisher {
74
72
  await rename(temporaryManifestPath, snapshot.manifestPath);
75
73
  return manifest;
76
74
  }
75
+
76
+ private resolvePluginEntry(discoveredPlugin: CodemationDiscoveredPluginPackage): string {
77
+ if (typeof discoveredPlugin.developmentEntry === "string" && discoveredPlugin.developmentEntry.trim().length > 0) {
78
+ return discoveredPlugin.developmentEntry;
79
+ }
80
+ return discoveredPlugin.pluginEntry;
81
+ }
77
82
  }
@@ -1,9 +1,9 @@
1
+ import type { CodemationPluginDiscovery } from "@codemation/host/server";
1
2
  import type { Logger } from "@codemation/host/next/server";
2
- import { CodemationPluginDiscovery } from "@codemation/host/server";
3
3
 
4
- import { ConsumerBuildArtifactsPublisher } from "../build/ConsumerBuildArtifactsPublisher";
4
+ import type { ConsumerBuildArtifactsPublisher } from "../build/ConsumerBuildArtifactsPublisher";
5
+ import type { ConsumerOutputBuilderFactory } from "../consumer/ConsumerOutputBuilderFactory";
5
6
  import type { ConsumerBuildOptions } from "../consumer/consumerBuildOptions.types";
6
- import { ConsumerOutputBuilderLoader } from "../consumer/Loader";
7
7
  import { CliPathResolver } from "../path/CliPathResolver";
8
8
  import { TypeScriptRuntimeConfigurator } from "../runtime/TypeScriptRuntimeConfigurator";
9
9
 
@@ -11,21 +11,21 @@ export class BuildCommand {
11
11
  constructor(
12
12
  private readonly cliLogger: Logger,
13
13
  private readonly pathResolver: CliPathResolver,
14
+ private readonly consumerOutputBuilderFactory: ConsumerOutputBuilderFactory,
14
15
  private readonly pluginDiscovery: CodemationPluginDiscovery,
15
- private readonly artifactsPublisher: ConsumerBuildArtifactsPublisher,
16
+ private readonly consumerBuildArtifactsPublisher: ConsumerBuildArtifactsPublisher,
16
17
  private readonly tsRuntime: TypeScriptRuntimeConfigurator,
17
- private readonly outputBuilderLoader: ConsumerOutputBuilderLoader,
18
18
  ) {}
19
19
 
20
20
  async execute(consumerRoot: string, buildOptions: ConsumerBuildOptions): Promise<void> {
21
21
  const paths = await this.pathResolver.resolve(consumerRoot);
22
22
  this.tsRuntime.configure(paths.repoRoot);
23
- const builder = this.outputBuilderLoader.create(paths.consumerRoot, buildOptions);
23
+ const builder = this.consumerOutputBuilderFactory.create(paths.consumerRoot, { buildOptions });
24
24
  const snapshot = await builder.ensureBuilt();
25
25
  const discoveredPlugins = await this.pluginDiscovery.discover(paths.consumerRoot);
26
- const manifest = await this.artifactsPublisher.publish(snapshot, discoveredPlugins);
26
+ const manifest = await this.consumerBuildArtifactsPublisher.publish(snapshot, discoveredPlugins);
27
27
  this.cliLogger.info(`Built consumer output: ${snapshot.outputEntryPath}`);
28
- this.cliLogger.info(`Discovered plugins: ${discoveredPlugins.length}`);
29
- this.cliLogger.info(`Published build: ${manifest.buildVersion}`);
28
+ this.cliLogger.info(`Build manifest: ${manifest.manifestPath}`);
29
+ this.cliLogger.info(`Workflow modules emitted: ${snapshot.workflowSourcePaths.length}`);
30
30
  }
31
31
  }
@@ -1,16 +1,18 @@
1
+ import type { CodemationPluginDiscovery } from "@codemation/host/server";
1
2
  import type { Logger } from "@codemation/host/next/server";
2
3
  import { spawn, type ChildProcess } from "node:child_process";
3
4
  import { createRequire } from "node:module";
4
5
  import path from "node:path";
5
6
  import process from "node:process";
6
7
 
8
+ import type { ConsumerBuildArtifactsPublisher } from "../build/ConsumerBuildArtifactsPublisher";
9
+ import type { ConsumerOutputBuilderFactory } from "../consumer/ConsumerOutputBuilderFactory";
7
10
  import type { DatabaseMigrationsApplyService } from "../database/DatabaseMigrationsApplyService";
8
11
  import type { DevApiRuntimeFactory, DevApiRuntimeServerHandle } from "../dev/DevApiRuntimeFactory";
9
12
  import type { DevBootstrapSummaryFetcher } from "../dev/DevBootstrapSummaryFetcher";
10
13
  import type { CliDevProxyServer } from "../dev/CliDevProxyServer";
11
14
  import type { CliDevProxyServerFactory } from "../dev/CliDevProxyServerFactory";
12
15
  import type { DevCliBannerRenderer } from "../dev/DevCliBannerRenderer";
13
- import type { DevConsumerPublishBootstrap } from "../dev/DevConsumerPublishBootstrap";
14
16
  import { ConsumerEnvDotenvFilePredicate } from "../dev/ConsumerEnvDotenvFilePredicate";
15
17
  import type { DevRebuildQueueFactory } from "../dev/DevRebuildQueueFactory";
16
18
  import type { DevSourceWatcher } from "../dev/DevSourceWatcher";
@@ -18,7 +20,7 @@ import { DevSessionServices } from "../dev/DevSessionServices";
18
20
  import { DevLockFactory } from "../dev/Factory";
19
21
  import { DevTrackedProcessTreeKiller } from "../dev/DevTrackedProcessTreeKiller";
20
22
  import { DevSourceWatcherFactory } from "../dev/Runner";
21
- import type { DevResolvedAuthSettings } from "../dev/DevAuthSettingsLoader";
23
+ import type { NextHostEdgeSeed } from "../dev/NextHostEdgeSeedLoader";
22
24
  import { CliPathResolver, type CliPaths } from "../path/CliPathResolver";
23
25
  import { NextHostConsumerServerCommandFactory } from "../runtime/NextHostConsumerServerCommandFactory";
24
26
  import { TypeScriptRuntimeConfigurator } from "../runtime/TypeScriptRuntimeConfigurator";
@@ -36,9 +38,11 @@ export class DevCommand {
36
38
  private readonly cliLogger: Logger,
37
39
  private readonly session: DevSessionServices,
38
40
  private readonly databaseMigrationsApplyService: DatabaseMigrationsApplyService,
41
+ private readonly consumerOutputBuilderFactory: ConsumerOutputBuilderFactory,
42
+ private readonly pluginDiscovery: CodemationPluginDiscovery,
43
+ private readonly consumerBuildArtifactsPublisher: ConsumerBuildArtifactsPublisher,
39
44
  private readonly devBootstrapSummaryFetcher: DevBootstrapSummaryFetcher,
40
45
  private readonly devCliBannerRenderer: DevCliBannerRenderer,
41
- private readonly devConsumerPublishBootstrap: DevConsumerPublishBootstrap,
42
46
  private readonly consumerEnvDotenvFilePredicate: ConsumerEnvDotenvFilePredicate,
43
47
  private readonly devTrackedProcessTreeKiller: DevTrackedProcessTreeKiller,
44
48
  private readonly nextHostConsumerServerCommandFactory: NextHostConsumerServerCommandFactory,
@@ -47,12 +51,22 @@ export class DevCommand {
47
51
  private readonly devRebuildQueueFactory: DevRebuildQueueFactory,
48
52
  ) {}
49
53
 
50
- async execute(args: Readonly<{ consumerRoot: string; watchFramework?: boolean }>): Promise<void> {
54
+ async execute(
55
+ args: Readonly<{
56
+ consumerRoot: string;
57
+ watchFramework?: boolean;
58
+ commandName?: "dev" | "dev:plugin";
59
+ configPathOverride?: string;
60
+ }>,
61
+ ): Promise<void> {
51
62
  const paths = await this.pathResolver.resolve(args.consumerRoot);
63
+ const commandName = args.commandName ?? "dev";
64
+ const previousDevelopmentServerToken = process.env.CODEMATION_DEV_SERVER_TOKEN;
52
65
  this.devCliBannerRenderer.renderBrandHeader();
53
66
  this.tsRuntime.configure(paths.repoRoot);
54
- await this.databaseMigrationsApplyService.applyForConsumer(paths.consumerRoot);
55
- await this.devConsumerPublishBootstrap.ensurePublished(paths);
67
+ await this.databaseMigrationsApplyService.applyForConsumer(paths.consumerRoot, {
68
+ configPath: args.configPathOverride,
69
+ });
56
70
  const devMode = this.resolveDevMode(args);
57
71
  const { nextPort, gatewayPort } = await this.session.sessionPorts.resolve({
58
72
  devMode,
@@ -64,14 +78,29 @@ export class DevCommand {
64
78
  consumerRoot: paths.consumerRoot,
65
79
  nextPort: devMode === "watch-framework" ? nextPort : gatewayPort,
66
80
  });
67
- const authSettings = await this.session.devAuthLoader.loadForConsumer(paths.consumerRoot);
81
+ const authSettings = await this.session.nextHostEdgeSeedLoader.loadForConsumer(paths.consumerRoot, {
82
+ configPathOverride: args.configPathOverride,
83
+ });
68
84
  const watcher = this.devSourceWatcherFactory.create();
69
85
  const processState = this.createInitialProcessState();
70
86
  let proxyServer: CliDevProxyServer | null = null;
71
87
  try {
72
- const prepared = await this.prepareDevRuntime(paths, devMode, nextPort, gatewayPort, authSettings);
88
+ const prepared = await this.prepareDevRuntime(
89
+ paths,
90
+ devMode,
91
+ nextPort,
92
+ gatewayPort,
93
+ authSettings,
94
+ args.configPathOverride,
95
+ );
96
+ if (prepared.devMode === "packaged-ui") {
97
+ await this.publishConsumerArtifacts(prepared.paths, prepared.configPathOverride);
98
+ }
99
+ // The disposable runtime is created in-process, so config reloads must see the same token in
100
+ // `process.env` that we also pass through the child-facing env object.
101
+ process.env.CODEMATION_DEV_SERVER_TOKEN = prepared.developmentServerToken;
73
102
  const stopPromise = this.wireStopPromise(processState);
74
- const uiProxyBase = await this.startPackagedUiWhenNeeded(prepared, processState);
103
+ const uiProxyBase = await this.preparePackagedUiBaseUrlWhenNeeded(prepared, processState);
75
104
  proxyServer = await this.startProxyServer(prepared.gatewayPort, uiProxyBase);
76
105
  const gatewayBaseUrl = this.gatewayBaseHttpUrl(gatewayPort);
77
106
  await this.bootInitialRuntime(prepared, processState, proxyServer);
@@ -80,12 +109,21 @@ export class DevCommand {
80
109
  if (initialSummary) {
81
110
  this.devCliBannerRenderer.renderRuntimeSummary(initialSummary);
82
111
  }
112
+ await this.startPackagedUiWhenNeeded(prepared, processState, uiProxyBase);
83
113
  this.bindShutdownSignalsToChildProcesses(processState, proxyServer);
84
114
  await this.spawnDevUiWhenNeeded(prepared, processState, gatewayBaseUrl);
85
- await this.startWatcherForSourceRestart(prepared, processState, watcher, devMode, gatewayBaseUrl, proxyServer);
86
- this.logPackagedUiDevHintWhenNeeded(devMode, gatewayPort);
115
+ await this.startWatcherForSourceRestart(prepared, processState, watcher, devMode, gatewayBaseUrl, proxyServer, {
116
+ commandName,
117
+ configPathOverride: args.configPathOverride,
118
+ });
119
+ this.logPackagedUiDevHintWhenNeeded(devMode, gatewayPort, commandName);
87
120
  await stopPromise;
88
121
  } finally {
122
+ if (previousDevelopmentServerToken === undefined) {
123
+ delete process.env.CODEMATION_DEV_SERVER_TOKEN;
124
+ } else {
125
+ process.env.CODEMATION_DEV_SERVER_TOKEN = previousDevelopmentServerToken;
126
+ }
89
127
  processState.stopRequested = true;
90
128
  await this.stopLiveProcesses(processState, proxyServer);
91
129
  await watcher.stop();
@@ -105,14 +143,16 @@ export class DevCommand {
105
143
  devMode: DevMode,
106
144
  nextPort: number,
107
145
  gatewayPort: number,
108
- authSettings: DevResolvedAuthSettings,
146
+ authSettings: NextHostEdgeSeed,
147
+ configPathOverride?: string,
109
148
  ): Promise<DevPreparedRuntime> {
110
- const developmentServerToken = this.session.devAuthLoader.resolveDevelopmentServerToken(
149
+ const developmentServerToken = this.session.nextHostEdgeSeedLoader.resolveDevelopmentServerToken(
111
150
  process.env.CODEMATION_DEV_SERVER_TOKEN,
112
151
  );
113
152
  const consumerEnv = this.session.consumerEnvLoader.load(paths.consumerRoot);
114
153
  return {
115
154
  paths,
155
+ configPathOverride,
116
156
  devMode,
117
157
  nextPort,
118
158
  gatewayPort,
@@ -146,48 +186,50 @@ export class DevCommand {
146
186
  return `http://127.0.0.1:${gatewayPort}`;
147
187
  }
148
188
 
149
- private async startPackagedUiWhenNeeded(
189
+ private async preparePackagedUiBaseUrlWhenNeeded(
150
190
  prepared: DevPreparedRuntime,
151
191
  state: DevMutableProcessState,
152
192
  ): Promise<string> {
153
193
  if (prepared.devMode !== "packaged-ui") {
154
194
  return "";
155
195
  }
156
- const websocketPort = prepared.gatewayPort;
157
196
  const uiProxyBase =
158
197
  state.currentPackagedUiBaseUrl ?? `http://127.0.0.1:${await this.session.loopbackPortAllocator.allocate()}`;
159
198
  state.currentPackagedUiBaseUrl = uiProxyBase;
160
- await this.spawnPackagedUi(prepared, state, prepared.authSettings, websocketPort, uiProxyBase);
161
199
  return uiProxyBase;
162
200
  }
163
201
 
202
+ private async startPackagedUiWhenNeeded(
203
+ prepared: DevPreparedRuntime,
204
+ state: DevMutableProcessState,
205
+ uiProxyBase: string,
206
+ ): Promise<void> {
207
+ if (prepared.devMode !== "packaged-ui" || uiProxyBase.length === 0) {
208
+ return;
209
+ }
210
+ await this.spawnPackagedUi(prepared, state, prepared.authSettings, prepared.gatewayPort, uiProxyBase);
211
+ }
212
+
164
213
  private async spawnPackagedUi(
165
214
  prepared: DevPreparedRuntime,
166
215
  state: DevMutableProcessState,
167
- authSettings: DevResolvedAuthSettings,
216
+ authSettings: NextHostEdgeSeed,
168
217
  websocketPort: number,
169
218
  uiProxyBase: string,
170
219
  ): Promise<void> {
171
220
  const nextHostPackageJsonPath = this.require.resolve("@codemation/next-host/package.json");
172
221
  const nextHostRoot = path.dirname(nextHostPackageJsonPath);
173
222
  const nextHostCommand = await this.nextHostConsumerServerCommandFactory.create({ nextHostRoot });
174
- const consumerOutputManifestPath = path.resolve(
175
- prepared.paths.consumerRoot,
176
- ".codemation",
177
- "output",
178
- "current.json",
179
- );
180
223
  const uiPort = Number(new URL(uiProxyBase).port);
181
224
  const nextHostEnvironment = this.session.nextHostEnvBuilder.buildConsumerUiProxy({
182
- authConfigJson: authSettings.authConfigJson,
183
225
  authSecret: authSettings.authSecret,
226
+ configPathOverride: prepared.configPathOverride,
184
227
  consumerRoot: prepared.paths.consumerRoot,
185
- consumerOutputManifestPath,
186
228
  developmentServerToken: prepared.developmentServerToken,
187
229
  nextPort: uiPort,
188
230
  publicBaseUrl: this.gatewayBaseHttpUrl(prepared.gatewayPort),
189
231
  runtimeDevUrl: this.gatewayBaseHttpUrl(prepared.gatewayPort),
190
- skipUiAuth: authSettings.skipUiAuth,
232
+ skipUiAuth: !authSettings.uiAuthEnabled,
191
233
  websocketPort,
192
234
  });
193
235
  state.currentPackagedUi = spawn(nextHostCommand.command, nextHostCommand.args, {
@@ -282,17 +324,18 @@ export class DevCommand {
282
324
  prepared: DevPreparedRuntime,
283
325
  state: DevMutableProcessState,
284
326
  gatewayBaseUrl: string,
285
- authSettings: DevResolvedAuthSettings,
327
+ authSettings: NextHostEdgeSeed,
286
328
  ): Promise<void> {
287
329
  const websocketPort = prepared.gatewayPort;
288
330
  const nextHostPackageJsonPath = this.require.resolve("@codemation/next-host/package.json");
289
331
  const nextHostRoot = path.dirname(nextHostPackageJsonPath);
290
332
  const nextHostEnvironment = this.session.nextHostEnvBuilder.build({
291
- authConfigJson: authSettings.authConfigJson,
333
+ authSecret: authSettings.authSecret,
334
+ configPathOverride: prepared.configPathOverride,
292
335
  consumerRoot: prepared.paths.consumerRoot,
293
336
  developmentServerToken: prepared.developmentServerToken,
294
337
  nextPort: prepared.nextPort,
295
- skipUiAuth: authSettings.skipUiAuth,
338
+ skipUiAuth: !authSettings.uiAuthEnabled,
296
339
  websocketPort,
297
340
  runtimeDevUrl: gatewayBaseUrl,
298
341
  });
@@ -331,6 +374,10 @@ export class DevCommand {
331
374
  devMode: DevMode,
332
375
  gatewayBaseUrl: string,
333
376
  proxyServer: CliDevProxyServer,
377
+ options: Readonly<{
378
+ commandName: "dev" | "dev:plugin";
379
+ configPathOverride?: string;
380
+ }>,
334
381
  ): Promise<void> {
335
382
  const rebuildQueue = this.devRebuildQueueFactory.create({
336
383
  run: async (request) => {
@@ -351,22 +398,18 @@ export class DevCommand {
351
398
  return;
352
399
  }
353
400
  try {
354
- const shouldRepublishConsumerOutput = this.session.sourceChangeClassifier.shouldRepublishConsumerOutput({
355
- changedPaths,
356
- consumerRoot: prepared.paths.consumerRoot,
357
- });
358
401
  const shouldRestartUi = this.session.sourceChangeClassifier.requiresUiRestart({
359
402
  changedPaths,
360
403
  consumerRoot: prepared.paths.consumerRoot,
361
404
  });
362
405
  process.stdout.write(
363
406
  shouldRestartUi
364
- ? "\n[codemation] Source change detected — rebuilding consumer, restarting the runtime, and restarting the UI…\n"
365
- : "\n[codemation] Source change detected — rebuilding consumer and restarting the runtime…\n",
407
+ ? `\n[codemation] Source change detected — rebuilding for \`${options.commandName}\`, restarting the runtime, and restarting the UI…\n`
408
+ : `\n[codemation] Source change detected — rebuilding for \`${options.commandName}\` and restarting the runtime…\n`,
366
409
  );
367
410
  await rebuildQueue.enqueue({
368
411
  changedPaths,
369
- shouldRepublishConsumerOutput,
412
+ configPathOverride: options.configPathOverride,
370
413
  shouldRestartUi,
371
414
  });
372
415
  } catch (error) {
@@ -383,7 +426,7 @@ export class DevCommand {
383
426
  proxyServer: CliDevProxyServer,
384
427
  request: Readonly<{
385
428
  changedPaths: ReadonlyArray<string>;
386
- shouldRepublishConsumerOutput: boolean;
429
+ configPathOverride?: string;
387
430
  shouldRestartUi: boolean;
388
431
  }>,
389
432
  ): Promise<void> {
@@ -391,8 +434,8 @@ export class DevCommand {
391
434
  proxyServer.setBuildStatus("building");
392
435
  proxyServer.broadcastBuildStarted();
393
436
  try {
394
- if (request.shouldRepublishConsumerOutput) {
395
- await this.devConsumerPublishBootstrap.ensurePublished(prepared.paths);
437
+ if (prepared.devMode === "packaged-ui") {
438
+ await this.publishConsumerArtifacts(prepared.paths, request.configPathOverride);
396
439
  }
397
440
  await this.stopCurrentRuntime(state, proxyServer);
398
441
  process.stdout.write("[codemation] Waiting for runtime to accept traffic…\n");
@@ -402,15 +445,18 @@ export class DevCommand {
402
445
  httpPort: runtime.httpPort,
403
446
  workflowWebSocketPort: runtime.workflowWebSocketPort,
404
447
  });
405
- if (request.shouldRestartUi) {
406
- await this.restartUiAfterSourceChange(prepared, state, gatewayBaseUrl);
407
- }
408
448
  await this.session.devHttpProbe.waitUntilBootstrapSummaryReady(gatewayBaseUrl);
409
449
  const json = await this.devBootstrapSummaryFetcher.fetch(gatewayBaseUrl);
410
450
  if (json) {
411
451
  this.devCliBannerRenderer.renderCompact(json);
412
452
  }
413
453
  proxyServer.setBuildStatus("idle");
454
+ // Let the new runtime become queryable through the stable gateway before restarting the
455
+ // packaged UI; otherwise the UI bootstrap hits `/api/bootstrap/*` while the gateway still
456
+ // reports "Runtime is rebuilding" and the restart can deadlock indefinitely.
457
+ if (request.shouldRestartUi) {
458
+ await this.restartUiAfterSourceChange(prepared, state, gatewayBaseUrl);
459
+ }
414
460
  proxyServer.broadcastBuildCompleted(runtime.buildVersion);
415
461
  process.stdout.write("[codemation] Runtime ready.\n");
416
462
  } catch (error) {
@@ -425,7 +471,12 @@ export class DevCommand {
425
471
  state: DevMutableProcessState,
426
472
  gatewayBaseUrl: string,
427
473
  ): Promise<void> {
428
- const refreshedAuthSettings = await this.session.devAuthLoader.loadForConsumer(prepared.paths.consumerRoot);
474
+ const refreshedAuthSettings = await this.session.nextHostEdgeSeedLoader.loadForConsumer(
475
+ prepared.paths.consumerRoot,
476
+ {
477
+ configPathOverride: prepared.configPathOverride,
478
+ },
479
+ );
429
480
  process.stdout.write("[codemation] Restarting the UI process to apply source changes…\n");
430
481
  state.isRestartingUi = true;
431
482
  try {
@@ -442,7 +493,7 @@ export class DevCommand {
442
493
  private async restartPackagedUi(
443
494
  prepared: DevPreparedRuntime,
444
495
  state: DevMutableProcessState,
445
- authSettings: DevResolvedAuthSettings,
496
+ authSettings: NextHostEdgeSeed,
446
497
  ): Promise<void> {
447
498
  if (
448
499
  state.currentPackagedUi &&
@@ -463,7 +514,7 @@ export class DevCommand {
463
514
  prepared: DevPreparedRuntime,
464
515
  state: DevMutableProcessState,
465
516
  gatewayBaseUrl: string,
466
- authSettings: DevResolvedAuthSettings,
517
+ authSettings: NextHostEdgeSeed,
467
518
  ): Promise<void> {
468
519
  if (state.currentDevUi && state.currentDevUi.exitCode === null && state.currentDevUi.signalCode === null) {
469
520
  await this.devTrackedProcessTreeKiller.killProcessTreeRootedAt(state.currentDevUi);
@@ -517,6 +568,7 @@ export class DevCommand {
517
568
  prepared.consumerEnv,
518
569
  );
519
570
  return await this.devApiRuntimeFactory.create({
571
+ configPathOverride: prepared.configPathOverride,
520
572
  consumerRoot: prepared.paths.consumerRoot,
521
573
  runtimeWorkingDirectory: process.cwd(),
522
574
  env: {
@@ -530,12 +582,26 @@ export class DevCommand {
530
582
  });
531
583
  }
532
584
 
533
- private logPackagedUiDevHintWhenNeeded(devMode: DevMode, gatewayPort: number): void {
585
+ private async publishConsumerArtifacts(paths: CliPaths, configPathOverride?: string): Promise<void> {
586
+ const builder = this.consumerOutputBuilderFactory.create(paths.consumerRoot, {
587
+ configPathOverride,
588
+ });
589
+ const snapshot = await builder.ensureBuilt();
590
+ const discoveredPlugins = await this.pluginDiscovery.discover(paths.consumerRoot);
591
+ await this.consumerBuildArtifactsPublisher.publish(snapshot, discoveredPlugins);
592
+ this.cliLogger.debug(`Dev: consumer output published (${snapshot.buildVersion}).`);
593
+ }
594
+
595
+ private logPackagedUiDevHintWhenNeeded(
596
+ devMode: DevMode,
597
+ gatewayPort: number,
598
+ commandName: "dev" | "dev:plugin",
599
+ ): void {
534
600
  if (devMode !== "packaged-ui") {
535
601
  return;
536
602
  }
537
603
  this.cliLogger.info(
538
- `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.`,
604
+ `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.`,
539
605
  );
540
606
  }
541
607
  }