@codemation/cli 0.0.21 → 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-BYHuUedo.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.21",
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/host": "0.0.19",
42
- "@codemation/next-host": "0.0.21"
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,
@@ -5,7 +5,7 @@ import {
5
5
  AppContainerLifecycle,
6
6
  type AppConfig,
7
7
  DatabaseMigrations,
8
- PrismaClient,
8
+ type PrismaClient,
9
9
  type CommandBus,
10
10
  type QueryBus,
11
11
  } from "@codemation/host";
@@ -34,10 +34,10 @@ export class CodemationCliApplicationSession {
34
34
 
35
35
  getPrismaClient(): PrismaClient | undefined {
36
36
  const container = this.getContainer();
37
- if (!container.isRegistered(PrismaClient, true)) {
37
+ if (!container.isRegistered(ApplicationTokens.PrismaClient, true)) {
38
38
  return undefined;
39
39
  }
40
- return container.resolve(PrismaClient);
40
+ return container.resolve(ApplicationTokens.PrismaClient);
41
41
  }
42
42
 
43
43
  getCommandBus(): CommandBus {
@@ -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
  }
@@ -3,7 +3,7 @@ import type { AppPersistenceConfig } from "@codemation/host/persistence";
3
3
  import path from "node:path";
4
4
 
5
5
  /**
6
- * Resolves TCP PostgreSQL vs PGlite vs none from env + {@link CodemationConfig} (same rules as the host runtime).
6
+ * Resolves TCP PostgreSQL vs SQLite vs none from env + {@link CodemationConfig} (same rules as the host runtime).
7
7
  */
8
8
  export class ConsumerDatabaseConnectionResolver {
9
9
  resolve(processEnv: NodeJS.ProcessEnv, config: CodemationConfig, consumerRoot: string): AppPersistenceConfig {
@@ -20,18 +20,18 @@ export class ConsumerDatabaseConnectionResolver {
20
20
  return { kind: "postgresql", databaseUrl };
21
21
  }
22
22
  return {
23
- kind: "pglite",
24
- dataDir: this.resolvePgliteDataDir(database.pgliteDataDir, processEnv, consumerRoot),
23
+ kind: "sqlite",
24
+ databaseFilePath: this.resolveSqliteFilePath(database.sqliteFilePath, processEnv, consumerRoot),
25
25
  };
26
26
  }
27
27
 
28
28
  private resolveDatabaseKind(
29
- configuredKind: "postgresql" | "pglite" | undefined,
29
+ configuredKind: "postgresql" | "sqlite" | undefined,
30
30
  databaseUrl: string | undefined,
31
31
  env: NodeJS.ProcessEnv,
32
- ): "postgresql" | "pglite" {
32
+ ): "postgresql" | "sqlite" {
33
33
  const kindFromEnv = env.CODEMATION_DATABASE_KIND?.trim();
34
- if (kindFromEnv === "postgresql" || kindFromEnv === "pglite") {
34
+ if (kindFromEnv === "postgresql" || kindFromEnv === "sqlite") {
35
35
  return kindFromEnv;
36
36
  }
37
37
  if (configuredKind) {
@@ -41,15 +41,15 @@ export class ConsumerDatabaseConnectionResolver {
41
41
  if (trimmedUrl && (trimmedUrl.startsWith("postgresql://") || trimmedUrl.startsWith("postgres://"))) {
42
42
  return "postgresql";
43
43
  }
44
- return "pglite";
44
+ return "sqlite";
45
45
  }
46
46
 
47
- private resolvePgliteDataDir(
47
+ private resolveSqliteFilePath(
48
48
  configuredPath: string | undefined,
49
49
  env: NodeJS.ProcessEnv,
50
50
  consumerRoot: string,
51
51
  ): string {
52
- const envPath = env.CODEMATION_PGLITE_DATA_DIR?.trim();
52
+ const envPath = env.CODEMATION_SQLITE_FILE_PATH?.trim();
53
53
  if (envPath && envPath.length > 0) {
54
54
  return path.isAbsolute(envPath) ? envPath : path.resolve(consumerRoot, envPath);
55
55
  }
@@ -59,6 +59,6 @@ export class ConsumerDatabaseConnectionResolver {
59
59
  ? trimmedConfiguredPath
60
60
  : path.resolve(consumerRoot, trimmedConfiguredPath);
61
61
  }
62
- return path.resolve(consumerRoot, ".codemation", "pglite");
62
+ return path.resolve(consumerRoot, ".codemation", "codemation.sqlite");
63
63
  }
64
64
  }
@@ -60,7 +60,7 @@ export class DatabaseMigrationsApplyService {
60
60
  if (persistence.kind === "none") {
61
61
  if (requirePersistence) {
62
62
  throw new Error(
63
- "Database persistence is not configured. Set CodemationConfig.runtime.database (postgresql URL or PGlite).",
63
+ "Database persistence is not configured. Set CodemationConfig.runtime.database (postgresql URL or SQLite file path).",
64
64
  );
65
65
  }
66
66
  return;
@@ -12,7 +12,7 @@ export class HostPackageRootResolver {
12
12
  const entry = fileURLToPath(entryUrl);
13
13
  let dir = path.dirname(entry);
14
14
  for (let depth = 0; depth < 8; depth += 1) {
15
- if (existsSync(path.join(dir, "prisma", "schema.prisma"))) {
15
+ if (existsSync(path.join(dir, "prisma", "schema.postgresql.prisma"))) {
16
16
  return dir;
17
17
  }
18
18
  const parent = path.dirname(dir);
@@ -21,6 +21,6 @@ export class HostPackageRootResolver {
21
21
  }
22
22
  dir = parent;
23
23
  }
24
- throw new Error(`Could not locate prisma/schema.prisma near @codemation/host entry: ${entry}`);
24
+ throw new Error(`Could not locate prisma/schema.postgresql.prisma near @codemation/host entry: ${entry}`);
25
25
  }
26
26
  }
@@ -9,11 +9,12 @@ export interface PrismaMigrateDeployRunner {
9
9
  }
10
10
 
11
11
  /**
12
- * Runs `pnpm exec prisma migrate deploy` in the host package (where the Prisma schema lives).
12
+ * Runs `pnpm exec prisma migrate deploy --config prisma.config.ts` in the host package
13
+ * so the selected PostgreSQL or SQLite Prisma track is respected.
13
14
  */
14
15
  export class PrismaMigrateDeployInvoker implements PrismaMigrateDeployRunner {
15
16
  run(args: Readonly<{ hostPackageRoot: string; env: NodeJS.ProcessEnv }>): PrismaMigrateDeployResult {
16
- const result = spawnSync("pnpm", ["exec", "prisma", "migrate", "deploy"], {
17
+ const result = spawnSync("pnpm", ["exec", "prisma", "migrate", "deploy", "--config", "prisma.config.ts"], {
17
18
  cwd: args.hostPackageRoot,
18
19
  env: args.env,
19
20
  stdio: "inherit",
@@ -165,12 +165,9 @@ export class CliDevProxyServer {
165
165
  );
166
166
  return;
167
167
  }
168
- if (uiProxyTarget && pathname.startsWith("/api/auth/")) {
169
- this.proxy.web(req, res, {
170
- target: uiProxyTarget.replace(/\/$/, ""),
171
- });
172
- return;
173
- }
168
+ // Host-owned `/api/auth/*` is served by the disposable Hono runtime (same DB as other APIs).
169
+ // Do not route it to the Next UI — that used to cause gateway → Next → gateway loops when Next
170
+ // proxied auth back through CODEMATION_RUNTIME_DEV_URL.
174
171
  if (pathname.startsWith("/api/")) {
175
172
  const runtimeTarget = this.activeRuntime;
176
173
  if (pathname === "/api/dev/bootstrap-summary" && runtimeTarget) {
@@ -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
+ }
@@ -42,6 +42,7 @@ export class DevNextHostEnvironmentBuilder {
42
42
  HOSTNAME: "127.0.0.1",
43
43
  AUTH_SECRET: args.authSecret,
44
44
  AUTH_URL: args.publicBaseUrl,
45
+ CODEMATION_PUBLIC_BASE_URL: args.publicBaseUrl,
45
46
  CODEMATION_PUBLIC_WS_PORT: String(publicWebsocketPort),
46
47
  NEXT_PUBLIC_CODEMATION_WS_PORT: String(publicWebsocketPort),
47
48
  };
@@ -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
+ }
@@ -11,7 +11,7 @@ export class CliDatabaseUrlDescriptor {
11
11
  if (persistence.kind === "postgresql") {
12
12
  return this.describeForDisplay(persistence.databaseUrl);
13
13
  }
14
- return `PGlite (${persistence.dataDir})`;
14
+ return `SQLite (${persistence.databaseFilePath})`;
15
15
  }
16
16
 
17
17
  describeForDisplay(databaseUrl: string | undefined): string {
@@ -40,7 +40,7 @@ export class UserAdminCliBootstrap {
40
40
  }
41
41
  if (loadResult.appConfig.persistence.kind === "none") {
42
42
  throw new Error(
43
- "Database persistence is not configured. Set CodemationConfig.runtime.database (postgresql URL or PGlite).",
43
+ "Database persistence is not configured. Set CodemationConfig.runtime.database (postgresql URL or SQLite file path).",
44
44
  );
45
45
  }
46
46
  const session = await CodemationCliApplicationSession.open({