@codemation/cli 0.0.22 → 0.0.24

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-PdQvm7od.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-CWXW_92Y.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.22",
3
+ "version": "0.0.24",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -38,8 +38,8 @@
38
38
  "reflect-metadata": "^0.2.2",
39
39
  "typescript": "^5.9.3",
40
40
  "ws": "^8.19.0",
41
- "@codemation/next-host": "0.1.0",
42
- "@codemation/host": "0.1.0"
41
+ "@codemation/next-host": "0.1.2",
42
+ "@codemation/host": "0.1.2"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/http-proxy": "^1.17.15",
@@ -21,6 +21,8 @@ import { HostPackageRootResolver } from "./database/HostPackageRootResolver";
21
21
  import { PrismaMigrationDeployer } from "@codemation/host/persistence";
22
22
  import { DevBootstrapSummaryFetcher } from "./dev/DevBootstrapSummaryFetcher";
23
23
  import { DevCliBannerRenderer } from "./dev/DevCliBannerRenderer";
24
+ import { DevNextChildProcessOutputFilter } from "./dev/DevNextChildProcessOutputFilter";
25
+ import { DevNextStartupBannerLineFilter } from "./dev/DevNextStartupBannerLineFilter";
24
26
  import { CliDevProxyServerFactory } from "./dev/CliDevProxyServerFactory";
25
27
  import { DevApiRuntimeFactory } from "./dev/DevApiRuntimeFactory";
26
28
  import { DevRebuildQueueFactory } from "./dev/DevRebuildQueueFactory";
@@ -100,6 +102,7 @@ export class CliProgramFactory {
100
102
  new DevApiRuntimeFactory(devSessionServices.loopbackPortAllocator, appConfigLoader, pluginDiscovery),
101
103
  new CliDevProxyServerFactory(),
102
104
  new DevRebuildQueueFactory(),
105
+ new DevNextChildProcessOutputFilter(new DevNextStartupBannerLineFilter()),
103
106
  );
104
107
  return new CliProgram(
105
108
  buildOptionsParser,
@@ -13,6 +13,7 @@ import type { DevBootstrapSummaryFetcher } from "../dev/DevBootstrapSummaryFetch
13
13
  import type { CliDevProxyServer } from "../dev/CliDevProxyServer";
14
14
  import type { CliDevProxyServerFactory } from "../dev/CliDevProxyServerFactory";
15
15
  import type { DevCliBannerRenderer } from "../dev/DevCliBannerRenderer";
16
+ import type { DevNextChildProcessOutputFilter } from "../dev/DevNextChildProcessOutputFilter";
16
17
  import { ConsumerEnvDotenvFilePredicate } from "../dev/ConsumerEnvDotenvFilePredicate";
17
18
  import type { DevRebuildQueueFactory } from "../dev/DevRebuildQueueFactory";
18
19
  import type { DevSourceWatcher } from "../dev/DevSourceWatcher";
@@ -49,6 +50,7 @@ export class DevCommand {
49
50
  private readonly devApiRuntimeFactory: DevApiRuntimeFactory,
50
51
  private readonly cliDevProxyServerFactory: CliDevProxyServerFactory,
51
52
  private readonly devRebuildQueueFactory: DevRebuildQueueFactory,
53
+ private readonly devNextChildProcessOutputFilter: DevNextChildProcessOutputFilter,
52
54
  ) {}
53
55
 
54
56
  async execute(
@@ -112,11 +114,16 @@ export class DevCommand {
112
114
  await this.startPackagedUiWhenNeeded(prepared, processState, uiProxyBase);
113
115
  this.bindShutdownSignalsToChildProcesses(processState, proxyServer);
114
116
  await this.spawnDevUiWhenNeeded(prepared, processState, gatewayBaseUrl);
117
+ this.devCliBannerRenderer.renderGatewayListeningHint(
118
+ prepared.devMode === "watch-framework" ? prepared.nextPort : prepared.gatewayPort,
119
+ commandName,
120
+ prepared.devMode,
121
+ prepared.devMode === "watch-framework" ? prepared.gatewayPort : undefined,
122
+ );
115
123
  await this.startWatcherForSourceRestart(prepared, processState, watcher, devMode, gatewayBaseUrl, proxyServer, {
116
124
  commandName,
117
125
  configPathOverride: args.configPathOverride,
118
126
  });
119
- this.logPackagedUiDevHintWhenNeeded(devMode, gatewayPort, commandName);
120
127
  await stopPromise;
121
128
  } finally {
122
129
  if (previousDevelopmentServerToken === undefined) {
@@ -234,9 +241,10 @@ export class DevCommand {
234
241
  });
235
242
  state.currentPackagedUi = spawn(nextHostCommand.command, nextHostCommand.args, {
236
243
  cwd: nextHostCommand.cwd,
237
- ...this.devDetachedChildSpawnOptions(),
244
+ ...this.devDetachedChildSpawnPipeOptions(),
238
245
  env: nextHostEnvironment,
239
246
  });
247
+ this.devNextChildProcessOutputFilter.attach(state.currentPackagedUi);
240
248
  state.currentPackagedUi.on("error", (error) => {
241
249
  if (state.stopRequested || state.isRestartingUi) {
242
250
  return;
@@ -276,14 +284,18 @@ export class DevCommand {
276
284
  proxyServer.setBuildStatus("idle");
277
285
  }
278
286
 
279
- private devDetachedChildSpawnOptions(): Readonly<{
280
- stdio: "inherit";
287
+ /**
288
+ * Next startup lines are filtered (see {@link DevNextChildProcessOutputFilter}) so the CLI can
289
+ * own the primary “open this URL” message without the internal loopback port dominating stdout.
290
+ */
291
+ private devDetachedChildSpawnPipeOptions(): Readonly<{
292
+ stdio: ["ignore", "pipe", "pipe"];
281
293
  detached: boolean;
282
294
  windowsHide?: boolean;
283
295
  }> {
284
296
  return process.platform === "win32"
285
- ? { stdio: "inherit", detached: true, windowsHide: true }
286
- : { stdio: "inherit", detached: true };
297
+ ? { stdio: ["ignore", "pipe", "pipe"], detached: true, windowsHide: true }
298
+ : { stdio: ["ignore", "pipe", "pipe"], detached: true };
287
299
  }
288
300
 
289
301
  private bindShutdownSignalsToChildProcesses(
@@ -341,9 +353,10 @@ export class DevCommand {
341
353
  });
342
354
  state.currentDevUi = spawn("pnpm", ["exec", "next", "dev"], {
343
355
  cwd: nextHostRoot,
344
- ...this.devDetachedChildSpawnOptions(),
356
+ ...this.devDetachedChildSpawnPipeOptions(),
345
357
  env: nextHostEnvironment,
346
358
  });
359
+ this.devNextChildProcessOutputFilter.attach(state.currentDevUi);
347
360
  state.currentDevUi.on("exit", (code) => {
348
361
  const normalizedCode = code ?? 0;
349
362
  if (state.stopRequested || state.isRestartingUi) {
@@ -591,17 +604,4 @@ export class DevCommand {
591
604
  await this.consumerBuildArtifactsPublisher.publish(snapshot, discoveredPlugins);
592
605
  this.cliLogger.debug(`Dev: consumer output published (${snapshot.buildVersion}).`);
593
606
  }
594
-
595
- private logPackagedUiDevHintWhenNeeded(
596
- devMode: DevMode,
597
- gatewayPort: number,
598
- commandName: "dev" | "dev:plugin",
599
- ): void {
600
- if (devMode !== "packaged-ui") {
601
- return;
602
- }
603
- this.cliLogger.info(
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.`,
605
- );
606
- }
607
607
  }
@@ -49,6 +49,45 @@ export class DevCliBannerRenderer {
49
49
  this.renderRuntimeSummary(summary);
50
50
  }
51
51
 
52
+ /**
53
+ * URL to open in the browser: packaged UI uses the CLI gateway port; watch-framework uses the
54
+ * Next.js dev port (PORT). When they differ, {@link devGatewayPort} is shown in the footer.
55
+ */
56
+ renderGatewayListeningHint(
57
+ browserPort: number,
58
+ commandName: "dev" | "dev:plugin",
59
+ devMode: "packaged-ui" | "watch-framework",
60
+ devGatewayPort?: number,
61
+ ): void {
62
+ const url = `http://127.0.0.1:${browserPort}`;
63
+ const footer =
64
+ devMode === "watch-framework"
65
+ ? chalk.dim(
66
+ devGatewayPort !== undefined && devGatewayPort !== browserPort
67
+ ? `The dev gateway (API + runtime) is at http://127.0.0.1:${devGatewayPort}. Open the URL above for the Next.js UI (HMR).`
68
+ : "Open the URL above for the Next.js UI.",
69
+ )
70
+ : chalk.dim(
71
+ `The UI is served through this URL (not the internal Next port). Framework UI work in the monorepo: \`codemation ${commandName} --watch-framework\`.`,
72
+ );
73
+ const bodyLines = [
74
+ chalk.whiteBright.bold("Codemation is running"),
75
+ "",
76
+ `${chalk.hex("#9ca3af")("Open in your browser:")} ${chalk.greenBright.underline(url)}`,
77
+ "",
78
+ footer,
79
+ ];
80
+ const box = boxen(bodyLines.join("\n"), {
81
+ padding: { top: 0, bottom: 0, left: 1, right: 1 },
82
+ margin: { top: 1, bottom: 0 },
83
+ borderStyle: "double",
84
+ borderColor: "green",
85
+ title: chalk.bold("Codemation dev"),
86
+ titleAlignment: "center",
87
+ });
88
+ process.stdout.write(`${box}\n`);
89
+ }
90
+
52
91
  /**
53
92
  * Shown after hot reload / watcher restarts (no figlet).
54
93
  */
@@ -0,0 +1,30 @@
1
+ import type { ChildProcess } from "node:child_process";
2
+ import { createInterface } from "node:readline";
3
+
4
+ import type { DevNextStartupBannerLineFilter } from "./DevNextStartupBannerLineFilter";
5
+
6
+ /**
7
+ * Attaches to a spawned Next child process and forwards streams while dropping
8
+ * {@link DevNextStartupBannerLineFilter} matches (startup banner only).
9
+ */
10
+ export class DevNextChildProcessOutputFilter {
11
+ constructor(private readonly lineFilter: DevNextStartupBannerLineFilter) {}
12
+
13
+ attach(child: ChildProcess): void {
14
+ this.pipeFilteredStream(child.stdout, process.stdout);
15
+ this.pipeFilteredStream(child.stderr, process.stderr);
16
+ }
17
+
18
+ private pipeFilteredStream(source: NodeJS.ReadableStream | null, sink: NodeJS.WritableStream): void {
19
+ if (!source) {
20
+ return;
21
+ }
22
+ const rl = createInterface({ input: source, crlfDelay: Infinity });
23
+ rl.on("line", (line) => {
24
+ if (this.lineFilter.shouldSuppress(line)) {
25
+ return;
26
+ }
27
+ sink.write(`${line}\n`);
28
+ });
29
+ }
30
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Filters noisy Next.js `next start` / `next dev` startup lines so the CLI can
3
+ * surface the Codemation gateway URL as the primary “where to browse” signal.
4
+ */
5
+ export class DevNextStartupBannerLineFilter {
6
+ shouldSuppress(line: string): boolean {
7
+ const t = line.replace(/\r$/, "").trimEnd();
8
+ if (t.length === 0) {
9
+ return false;
10
+ }
11
+ if (/^\s*▲\s+Next\.js/.test(t)) {
12
+ return true;
13
+ }
14
+ if (/^\s*-\s+Local:\s+/.test(t)) {
15
+ return true;
16
+ }
17
+ if (/^\s*-\s+Network:\s+/.test(t)) {
18
+ return true;
19
+ }
20
+ if (/^\s*✓\s+Ready\b/.test(t)) {
21
+ return true;
22
+ }
23
+ return false;
24
+ }
25
+ }