@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/CHANGELOG.md +10 -0
- package/LICENSE +37 -0
- package/README.md +4 -4
- package/dist/{CliBin-BkY_gChN.js → CliBin-BYHuUedo.js} +348 -186
- package/dist/bin.js +1 -1
- package/dist/index.d.ts +278 -298
- package/dist/index.js +1 -1
- package/package.json +5 -4
- package/src/CliProgramFactory.ts +31 -37
- package/src/Program.ts +12 -0
- package/src/build/ConsumerBuildArtifactsPublisher.ts +12 -7
- package/src/commands/BuildCommand.ts +9 -9
- package/src/commands/DevCommand.ts +113 -47
- package/src/commands/DevPluginCommand.ts +19 -0
- package/src/commands/ServeWebCommand.ts +8 -34
- package/src/commands/devCommandLifecycle.types.ts +3 -2
- package/src/consumer/ConsumerOutputBuilder.ts +28 -0
- package/src/consumer/ConsumerOutputBuilderFactory.ts +17 -0
- package/src/dev/Builder.ts +2 -2
- package/src/dev/CliDevProxyServer.ts +20 -0
- package/src/dev/DevApiRuntimeFactory.ts +1 -0
- package/src/dev/DevApiRuntimeHost.ts +37 -6
- package/src/dev/DevApiRuntimeTypes.ts +1 -0
- package/src/dev/DevCliBannerRenderer.ts +21 -2
- package/src/dev/DevNextHostEnvironmentBuilder.ts +27 -36
- package/src/dev/DevRebuildQueue.ts +2 -2
- package/src/dev/DevSessionServices.ts +2 -2
- package/src/dev/DevSourceChangeClassifier.ts +12 -12
- package/src/dev/ListenPortConflictDescriber.ts +66 -5
- package/src/dev/{DevAuthSettingsLoader.ts → NextHostEdgeSeedLoader.ts} +13 -9
- package/src/dev/PluginDevConfigFactory.ts +64 -0
- package/src/consumer/Loader.ts +0 -8
- package/src/dev/DevConsumerPublishBootstrap.ts +0 -30
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { DevCommand } from "./DevCommand";
|
|
2
|
+
import type { PluginDevConfigFactory } from "../dev/PluginDevConfigFactory";
|
|
3
|
+
|
|
4
|
+
export class DevPluginCommand {
|
|
5
|
+
constructor(
|
|
6
|
+
private readonly pluginDevConfigFactory: PluginDevConfigFactory,
|
|
7
|
+
private readonly devCommand: DevCommand,
|
|
8
|
+
) {}
|
|
9
|
+
|
|
10
|
+
async execute(args: Readonly<{ pluginRoot: string; watchFramework?: boolean }>): Promise<void> {
|
|
11
|
+
const pluginConfig = await this.pluginDevConfigFactory.prepare(args.pluginRoot);
|
|
12
|
+
await this.devCommand.execute({
|
|
13
|
+
commandName: "dev:plugin",
|
|
14
|
+
configPathOverride: pluginConfig.configPath,
|
|
15
|
+
consumerRoot: args.pluginRoot,
|
|
16
|
+
watchFramework: args.watchFramework,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -1,18 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CodemationConsumerConfigLoader,
|
|
3
|
-
CodemationFrontendAuthSnapshotFactory,
|
|
4
|
-
CodemationPluginDiscovery,
|
|
5
|
-
FrontendAppConfigJsonCodec,
|
|
6
|
-
} from "@codemation/host/server";
|
|
1
|
+
import { CodemationConsumerConfigLoader } from "@codemation/host/server";
|
|
7
2
|
import { spawn } from "node:child_process";
|
|
8
3
|
import { createRequire } from "node:module";
|
|
9
4
|
import path from "node:path";
|
|
10
5
|
import process from "node:process";
|
|
11
6
|
|
|
12
|
-
import { ConsumerBuildArtifactsPublisher } from "../build/ConsumerBuildArtifactsPublisher";
|
|
13
7
|
import { ConsumerEnvLoader } from "../consumer/ConsumerEnvLoader";
|
|
14
8
|
import type { ConsumerBuildOptions } from "../consumer/consumerBuildOptions.types";
|
|
15
|
-
import { ConsumerOutputBuilderLoader } from "../consumer/Loader";
|
|
16
9
|
import { CliPathResolver } from "../path/CliPathResolver";
|
|
17
10
|
import { ListenPortResolver } from "../runtime/ListenPortResolver";
|
|
18
11
|
import { NextHostConsumerServerCommandFactory } from "../runtime/NextHostConsumerServerCommandFactory";
|
|
@@ -25,40 +18,21 @@ export class ServeWebCommand {
|
|
|
25
18
|
constructor(
|
|
26
19
|
private readonly pathResolver: CliPathResolver,
|
|
27
20
|
private readonly configLoader: CodemationConsumerConfigLoader,
|
|
28
|
-
private readonly pluginDiscovery: CodemationPluginDiscovery,
|
|
29
|
-
private readonly artifactsPublisher: ConsumerBuildArtifactsPublisher,
|
|
30
21
|
private readonly tsRuntime: TypeScriptRuntimeConfigurator,
|
|
31
22
|
private readonly sourceMapNodeOptions: SourceMapNodeOptions,
|
|
32
|
-
private readonly outputBuilderLoader: ConsumerOutputBuilderLoader,
|
|
33
23
|
private readonly envLoader: ConsumerEnvLoader,
|
|
34
24
|
private readonly listenPortResolver: ListenPortResolver,
|
|
35
25
|
private readonly nextHostConsumerServerCommandFactory: NextHostConsumerServerCommandFactory,
|
|
36
|
-
private readonly frontendAuthSnapshotFactory: CodemationFrontendAuthSnapshotFactory,
|
|
37
|
-
private readonly frontendAppConfigJsonCodec: FrontendAppConfigJsonCodec,
|
|
38
26
|
) {}
|
|
39
27
|
|
|
40
28
|
async execute(consumerRoot: string, buildOptions: ConsumerBuildOptions): Promise<void> {
|
|
29
|
+
void buildOptions;
|
|
41
30
|
const paths = await this.pathResolver.resolve(consumerRoot);
|
|
42
31
|
this.tsRuntime.configure(paths.repoRoot);
|
|
43
|
-
const builder = this.outputBuilderLoader.create(paths.consumerRoot, buildOptions);
|
|
44
|
-
const snapshot = await builder.ensureBuilt();
|
|
45
|
-
const discoveredPlugins = await this.pluginDiscovery.discover(paths.consumerRoot);
|
|
46
|
-
const manifest = await this.artifactsPublisher.publish(snapshot, discoveredPlugins);
|
|
47
32
|
const nextHostRoot = path.dirname(this.require.resolve("@codemation/next-host/package.json"));
|
|
48
33
|
const nextHostCommand = await this.nextHostConsumerServerCommandFactory.create({ nextHostRoot });
|
|
49
34
|
const consumerEnv = this.envLoader.load(paths.consumerRoot);
|
|
50
35
|
const configResolution = await this.configLoader.load({ consumerRoot: paths.consumerRoot });
|
|
51
|
-
const frontendAuthSnapshot = this.frontendAuthSnapshotFactory.createFromResolvedInputs({
|
|
52
|
-
authConfig: configResolution.config.auth,
|
|
53
|
-
env: {
|
|
54
|
-
...process.env,
|
|
55
|
-
...consumerEnv,
|
|
56
|
-
},
|
|
57
|
-
uiAuthEnabled: !(
|
|
58
|
-
consumerEnv.NODE_ENV !== "production" &&
|
|
59
|
-
configResolution.config.auth?.allowUnauthenticatedInDevelopment === true
|
|
60
|
-
),
|
|
61
|
-
});
|
|
62
36
|
const nextPort = this.listenPortResolver.resolvePrimaryApplicationPort(process.env.PORT);
|
|
63
37
|
const websocketPort = this.listenPortResolver.resolveWebsocketPortRelativeToHttp({
|
|
64
38
|
nextPort,
|
|
@@ -72,13 +46,13 @@ export class ServeWebCommand {
|
|
|
72
46
|
...process.env,
|
|
73
47
|
...consumerEnv,
|
|
74
48
|
PORT: String(nextPort),
|
|
75
|
-
CODEMATION_FRONTEND_APP_CONFIG_JSON: this.frontendAppConfigJsonCodec.serialize({
|
|
76
|
-
auth: frontendAuthSnapshot,
|
|
77
|
-
productName: "Codemation",
|
|
78
|
-
logoUrl: null,
|
|
79
|
-
}),
|
|
80
|
-
CODEMATION_CONSUMER_OUTPUT_MANIFEST_PATH: manifest.manifestPath,
|
|
81
49
|
CODEMATION_CONSUMER_ROOT: paths.consumerRoot,
|
|
50
|
+
CODEMATION_UI_AUTH_ENABLED: String(
|
|
51
|
+
!(
|
|
52
|
+
consumerEnv.NODE_ENV !== "production" &&
|
|
53
|
+
configResolution.config.auth?.allowUnauthenticatedInDevelopment === true
|
|
54
|
+
),
|
|
55
|
+
),
|
|
82
56
|
CODEMATION_WS_PORT: String(websocketPort),
|
|
83
57
|
NEXT_PUBLIC_CODEMATION_WS_PORT: String(websocketPort),
|
|
84
58
|
NODE_OPTIONS: this.sourceMapNodeOptions.appendToNodeOptions(process.env.NODE_OPTIONS),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ChildProcess } from "node:child_process";
|
|
2
2
|
|
|
3
|
-
import type { DevResolvedAuthSettings } from "../dev/DevAuthSettingsLoader";
|
|
4
3
|
import type { DevApiRuntimeServerHandle } from "../dev/DevApiRuntimeFactory";
|
|
4
|
+
import type { NextHostEdgeSeed } from "../dev/NextHostEdgeSeedLoader";
|
|
5
5
|
import type { CliPaths } from "../path/CliPathResolver";
|
|
6
6
|
|
|
7
7
|
export type DevMode = "packaged-ui" | "watch-framework";
|
|
@@ -21,10 +21,11 @@ export type DevMutableProcessState = {
|
|
|
21
21
|
/** Immutable inputs resolved before any child processes are spawned. */
|
|
22
22
|
export type DevPreparedRuntime = Readonly<{
|
|
23
23
|
paths: CliPaths;
|
|
24
|
+
configPathOverride?: string;
|
|
24
25
|
devMode: DevMode;
|
|
25
26
|
nextPort: number;
|
|
26
27
|
gatewayPort: number;
|
|
27
|
-
authSettings:
|
|
28
|
+
authSettings: NextHostEdgeSeed;
|
|
28
29
|
developmentServerToken: string;
|
|
29
30
|
consumerEnv: Readonly<Record<string, string>>;
|
|
30
31
|
}>;
|
|
@@ -63,6 +63,7 @@ export class ConsumerOutputBuilder {
|
|
|
63
63
|
private readonly consumerRoot: string,
|
|
64
64
|
logOverride?: Logger,
|
|
65
65
|
buildOptionsOverride?: ConsumerBuildOptions,
|
|
66
|
+
private readonly configPathOverride?: string,
|
|
66
67
|
) {
|
|
67
68
|
this.log = logOverride ?? defaultConsumerOutputLogger;
|
|
68
69
|
this.buildOptions = buildOptionsOverride ?? defaultConsumerBuildOptions;
|
|
@@ -374,6 +375,7 @@ export class ConsumerOutputBuilder {
|
|
|
374
375
|
sourcePath,
|
|
375
376
|
});
|
|
376
377
|
}
|
|
378
|
+
await this.emitConfigSourceFile(outputAppRoot, configSourcePath, runtimeSourcePaths);
|
|
377
379
|
},
|
|
378
380
|
});
|
|
379
381
|
}
|
|
@@ -463,6 +465,24 @@ export class ConsumerOutputBuilder {
|
|
|
463
465
|
}
|
|
464
466
|
}
|
|
465
467
|
|
|
468
|
+
private async emitConfigSourceFile(
|
|
469
|
+
outputAppRoot: string,
|
|
470
|
+
configSourcePath: string,
|
|
471
|
+
runtimeSourcePaths: ReadonlyArray<string>,
|
|
472
|
+
): Promise<void> {
|
|
473
|
+
const normalizedConfigSourcePath = path.resolve(configSourcePath);
|
|
474
|
+
const alreadyEmitted = runtimeSourcePaths.some(
|
|
475
|
+
(sourcePath) => path.resolve(sourcePath) === normalizedConfigSourcePath,
|
|
476
|
+
);
|
|
477
|
+
if (alreadyEmitted) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
await this.emitSourceFile({
|
|
481
|
+
outputAppRoot,
|
|
482
|
+
sourcePath: normalizedConfigSourcePath,
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
|
|
466
486
|
private createCompilerOptions(): ts.CompilerOptions {
|
|
467
487
|
const scriptTarget = this.buildOptions.target === "es2020" ? ts.ScriptTarget.ES2020 : ts.ScriptTarget.ES2022;
|
|
468
488
|
return {
|
|
@@ -697,6 +717,14 @@ export class ConsumerOutputBuilder {
|
|
|
697
717
|
}
|
|
698
718
|
|
|
699
719
|
private async resolveConfigPath(consumerRoot: string): Promise<string | null> {
|
|
720
|
+
const configuredOverride = this.configPathOverride?.trim();
|
|
721
|
+
if (configuredOverride && configuredOverride.length > 0) {
|
|
722
|
+
const resolvedOverride = path.resolve(configuredOverride);
|
|
723
|
+
if (await this.fileExists(resolvedOverride)) {
|
|
724
|
+
return resolvedOverride;
|
|
725
|
+
}
|
|
726
|
+
throw new Error(`Codemation config override not found at ${resolvedOverride}.`);
|
|
727
|
+
}
|
|
700
728
|
for (const candidate of this.getConventionCandidates(consumerRoot)) {
|
|
701
729
|
if (await this.fileExists(candidate)) {
|
|
702
730
|
return candidate;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Logger } from "@codemation/host/next/server";
|
|
2
|
+
|
|
3
|
+
import { ConsumerOutputBuilder } from "./ConsumerOutputBuilder";
|
|
4
|
+
import type { ConsumerBuildOptions } from "./consumerBuildOptions.types";
|
|
5
|
+
|
|
6
|
+
export class ConsumerOutputBuilderFactory {
|
|
7
|
+
create(
|
|
8
|
+
consumerRoot: string,
|
|
9
|
+
args?: Readonly<{
|
|
10
|
+
buildOptions?: ConsumerBuildOptions;
|
|
11
|
+
configPathOverride?: string;
|
|
12
|
+
logger?: Logger;
|
|
13
|
+
}>,
|
|
14
|
+
): ConsumerOutputBuilder {
|
|
15
|
+
return new ConsumerOutputBuilder(consumerRoot, args?.logger, args?.buildOptions, args?.configPathOverride);
|
|
16
|
+
}
|
|
17
|
+
}
|
package/src/dev/Builder.ts
CHANGED
|
@@ -4,13 +4,13 @@ import { ConsumerEnvLoader } from "../consumer/ConsumerEnvLoader";
|
|
|
4
4
|
import { ListenPortResolver } from "../runtime/ListenPortResolver";
|
|
5
5
|
import { SourceMapNodeOptions } from "../runtime/SourceMapNodeOptions";
|
|
6
6
|
|
|
7
|
-
import { DevAuthSettingsLoader } from "./DevAuthSettingsLoader";
|
|
8
7
|
import { DevHttpProbe } from "./DevHttpProbe";
|
|
9
8
|
import { DevNextHostEnvironmentBuilder } from "./DevNextHostEnvironmentBuilder";
|
|
10
9
|
import { DevSessionPortsResolver } from "./DevSessionPortsResolver";
|
|
11
10
|
import { DevSessionServices } from "./DevSessionServices";
|
|
12
11
|
import { DevSourceChangeClassifier } from "./DevSourceChangeClassifier";
|
|
13
12
|
import { LoopbackPortAllocator } from "./LoopbackPortAllocator";
|
|
13
|
+
import { NextHostEdgeSeedLoader } from "./NextHostEdgeSeedLoader";
|
|
14
14
|
import { WatchRootsResolver } from "./WatchRootsResolver";
|
|
15
15
|
|
|
16
16
|
export class DevSessionServicesBuilder {
|
|
@@ -25,7 +25,7 @@ export class DevSessionServicesBuilder {
|
|
|
25
25
|
new DevSessionPortsResolver(listenPortResolver, loopbackPortAllocator),
|
|
26
26
|
loopbackPortAllocator,
|
|
27
27
|
new DevHttpProbe(),
|
|
28
|
-
new
|
|
28
|
+
new NextHostEdgeSeedLoader(new CodemationConsumerConfigLoader(), consumerEnvLoader),
|
|
29
29
|
new DevNextHostEnvironmentBuilder(consumerEnvLoader, sourceMapNodeOptions),
|
|
30
30
|
new WatchRootsResolver(),
|
|
31
31
|
new DevSourceChangeClassifier(),
|
|
@@ -231,6 +231,19 @@ export class CliDevProxyServer {
|
|
|
231
231
|
}
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
+
private extractOccupyingPids(listenerDescription: string): ReadonlyArray<number> {
|
|
235
|
+
const seen = new Set<number>();
|
|
236
|
+
const re = /pid=(\d+)/g;
|
|
237
|
+
let match: RegExpExecArray | null;
|
|
238
|
+
while ((match = re.exec(listenerDescription)) !== null) {
|
|
239
|
+
const pid = Number.parseInt(match[1] ?? "0", 10);
|
|
240
|
+
if (Number.isFinite(pid) && pid > 0) {
|
|
241
|
+
seen.add(pid);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return [...seen];
|
|
245
|
+
}
|
|
246
|
+
|
|
234
247
|
private async rejectListenError(error: unknown, reject: (reason?: unknown) => void): Promise<void> {
|
|
235
248
|
const errorWithCode = error as Error & Readonly<{ code?: unknown }>;
|
|
236
249
|
if (errorWithCode.code !== "EADDRINUSE") {
|
|
@@ -239,6 +252,13 @@ export class CliDevProxyServer {
|
|
|
239
252
|
}
|
|
240
253
|
|
|
241
254
|
const description = await this.listenPortConflictDescriber.describeLoopbackPort(this.listenPort);
|
|
255
|
+
const occupyingPids = description !== null ? this.extractOccupyingPids(description) : [];
|
|
256
|
+
if (occupyingPids.length > 0) {
|
|
257
|
+
const pidList = occupyingPids.join(", ");
|
|
258
|
+
process.stderr.write(
|
|
259
|
+
`[codemation] Dev gateway port ${this.listenPort} is already in use (occupying pid(s): ${pidList}).\n`,
|
|
260
|
+
);
|
|
261
|
+
}
|
|
242
262
|
const baseMessage = `Dev gateway port ${this.listenPort} is already in use on 127.0.0.1.`;
|
|
243
263
|
const suffix =
|
|
244
264
|
description === null
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { CodemationPlugin } from "@codemation/host";
|
|
1
|
+
import type { AppConfig, AppPluginLoadSummary, CodemationPlugin } from "@codemation/host";
|
|
2
|
+
import { CodemationPluginPackageMetadata } from "@codemation/host";
|
|
2
3
|
import {
|
|
3
4
|
AppContainerFactory,
|
|
4
5
|
AppContainerLifecycle,
|
|
@@ -18,13 +19,15 @@ import { CodemationTsyringeTypeInfoRegistrar } from "@codemation/host-src/presen
|
|
|
18
19
|
import type { DevApiRuntimeContext } from "./DevApiRuntimeTypes";
|
|
19
20
|
|
|
20
21
|
export class DevApiRuntimeHost {
|
|
21
|
-
private readonly
|
|
22
|
+
private readonly pluginPackageMetadata = new CodemationPluginPackageMetadata();
|
|
23
|
+
private readonly pluginListMerger = new CodemationPluginListMerger(this.pluginPackageMetadata);
|
|
22
24
|
private contextPromise: Promise<DevApiRuntimeContext> | null = null;
|
|
23
25
|
|
|
24
26
|
constructor(
|
|
25
27
|
private readonly configLoader: AppConfigLoader,
|
|
26
28
|
private readonly pluginDiscovery: CodemationPluginDiscovery,
|
|
27
29
|
private readonly args: Readonly<{
|
|
30
|
+
configPathOverride?: string;
|
|
28
31
|
consumerRoot: string;
|
|
29
32
|
env: NodeJS.ProcessEnv;
|
|
30
33
|
runtimeWorkingDirectory: string;
|
|
@@ -62,17 +65,24 @@ export class DevApiRuntimeHost {
|
|
|
62
65
|
env.CODEMATION_CONSUMER_ROOT = consumerRoot;
|
|
63
66
|
const configResolution = await this.configLoader.load({
|
|
64
67
|
consumerRoot,
|
|
68
|
+
configPathOverride: this.args.configPathOverride,
|
|
65
69
|
repoRoot,
|
|
66
70
|
env,
|
|
67
71
|
});
|
|
68
72
|
const discoveredPlugins = await this.loadDiscoveredPlugins(consumerRoot);
|
|
73
|
+
const mergedPlugins =
|
|
74
|
+
discoveredPlugins.length > 0
|
|
75
|
+
? this.pluginListMerger.merge(configResolution.appConfig.plugins, discoveredPlugins)
|
|
76
|
+
: configResolution.appConfig.plugins;
|
|
69
77
|
const appConfig = {
|
|
70
78
|
...configResolution.appConfig,
|
|
71
79
|
env,
|
|
72
|
-
plugins:
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
80
|
+
plugins: mergedPlugins,
|
|
81
|
+
pluginLoadSummary: this.createPluginLoadSummary(
|
|
82
|
+
configResolution.appConfig.plugins,
|
|
83
|
+
discoveredPlugins,
|
|
84
|
+
mergedPlugins,
|
|
85
|
+
),
|
|
76
86
|
};
|
|
77
87
|
const container = await new AppContainerFactory().create({
|
|
78
88
|
appConfig,
|
|
@@ -96,6 +106,27 @@ export class DevApiRuntimeHost {
|
|
|
96
106
|
return resolvedPackages.map((resolvedPackage: CodemationResolvedPluginPackage) => resolvedPackage.plugin);
|
|
97
107
|
}
|
|
98
108
|
|
|
109
|
+
private createPluginLoadSummary(
|
|
110
|
+
configuredPlugins: ReadonlyArray<CodemationPlugin>,
|
|
111
|
+
discoveredPlugins: ReadonlyArray<CodemationPlugin>,
|
|
112
|
+
mergedPlugins: ReadonlyArray<CodemationPlugin>,
|
|
113
|
+
): AppConfig["pluginLoadSummary"] {
|
|
114
|
+
const configuredPluginSet = new Set(configuredPlugins);
|
|
115
|
+
const discoveredPluginSet = new Set(discoveredPlugins);
|
|
116
|
+
const summaries: AppPluginLoadSummary[] = [];
|
|
117
|
+
for (const plugin of mergedPlugins) {
|
|
118
|
+
const packageName = this.pluginPackageMetadata.readPackageName(plugin);
|
|
119
|
+
if (!packageName) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
summaries.push({
|
|
123
|
+
packageName,
|
|
124
|
+
source: configuredPluginSet.has(plugin) || !discoveredPluginSet.has(plugin) ? "configured" : "discovered",
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return summaries;
|
|
128
|
+
}
|
|
129
|
+
|
|
99
130
|
private async detectWorkspaceRoot(startDirectory: string): Promise<string> {
|
|
100
131
|
let currentDirectory = path.resolve(startDirectory);
|
|
101
132
|
while (true) {
|
|
@@ -39,8 +39,9 @@ export class DevCliBannerRenderer {
|
|
|
39
39
|
title: chalk.bold("Runtime"),
|
|
40
40
|
titleAlignment: "center",
|
|
41
41
|
});
|
|
42
|
+
const pluginsSection = this.buildPluginsSection(summary);
|
|
42
43
|
const activeSection = this.buildActiveWorkflowsSection(summary);
|
|
43
|
-
process.stdout.write(`${detailBox}\n${activeSection}\n`);
|
|
44
|
+
process.stdout.write(`${detailBox}\n${pluginsSection}\n${activeSection}\n`);
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
renderFull(summary: DevBootstrapSummaryJson): void {
|
|
@@ -62,8 +63,9 @@ export class DevCliBannerRenderer {
|
|
|
62
63
|
title: chalk.bold("Runtime (updated)"),
|
|
63
64
|
titleAlignment: "center",
|
|
64
65
|
});
|
|
66
|
+
const pluginsSection = this.buildPluginsSection(summary);
|
|
65
67
|
const activeSection = this.buildActiveWorkflowsSection(summary);
|
|
66
|
-
process.stdout.write(`\n${detailBox}\n${activeSection}\n`);
|
|
68
|
+
process.stdout.write(`\n${detailBox}\n${pluginsSection}\n${activeSection}\n`);
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
private renderFigletTitle(): string {
|
|
@@ -103,4 +105,21 @@ export class DevCliBannerRenderer {
|
|
|
103
105
|
titleAlignment: "left",
|
|
104
106
|
});
|
|
105
107
|
}
|
|
108
|
+
|
|
109
|
+
private buildPluginsSection(summary: DevBootstrapSummaryJson): string {
|
|
110
|
+
const lines =
|
|
111
|
+
summary.plugins.length === 0
|
|
112
|
+
? [chalk.dim(" (none discovered or configured)")]
|
|
113
|
+
: summary.plugins.map(
|
|
114
|
+
(plugin) => `${chalk.whiteBright(` • ${plugin.packageName} `)}${chalk.dim(`(${plugin.source})`)}`,
|
|
115
|
+
);
|
|
116
|
+
return boxen(lines.join("\n"), {
|
|
117
|
+
padding: { top: 0, bottom: 0, left: 0, right: 0 },
|
|
118
|
+
margin: { top: 1, bottom: 0 },
|
|
119
|
+
borderStyle: "single",
|
|
120
|
+
borderColor: "cyan",
|
|
121
|
+
title: chalk.bold("Plugins"),
|
|
122
|
+
titleAlignment: "left",
|
|
123
|
+
});
|
|
124
|
+
}
|
|
106
125
|
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import process from "node:process";
|
|
3
|
-
import type { CodemationAuthConfig } from "@codemation/host";
|
|
4
|
-
import { CodemationFrontendAuthSnapshotFactory, FrontendAppConfigJsonCodec } from "@codemation/host";
|
|
5
3
|
|
|
6
4
|
import { ConsumerEnvLoader } from "../consumer/ConsumerEnvLoader";
|
|
7
5
|
import { SourceMapNodeOptions } from "../runtime/SourceMapNodeOptions";
|
|
@@ -10,14 +8,13 @@ export class DevNextHostEnvironmentBuilder {
|
|
|
10
8
|
constructor(
|
|
11
9
|
private readonly consumerEnvLoader: ConsumerEnvLoader,
|
|
12
10
|
private readonly sourceMapNodeOptions: SourceMapNodeOptions,
|
|
13
|
-
private readonly frontendAuthSnapshotFactory: CodemationFrontendAuthSnapshotFactory = new CodemationFrontendAuthSnapshotFactory(),
|
|
14
|
-
private readonly frontendAppConfigJsonCodec: FrontendAppConfigJsonCodec = new FrontendAppConfigJsonCodec(),
|
|
15
11
|
) {}
|
|
16
12
|
|
|
17
13
|
buildConsumerUiProxy(
|
|
18
14
|
args: Readonly<{
|
|
19
|
-
authConfigJson: string;
|
|
20
15
|
authSecret: string;
|
|
16
|
+
configPathOverride?: string;
|
|
17
|
+
consumerOutputManifestPath?: string;
|
|
21
18
|
consumerRoot: string;
|
|
22
19
|
developmentServerToken: string;
|
|
23
20
|
nextPort: number;
|
|
@@ -25,69 +22,54 @@ export class DevNextHostEnvironmentBuilder {
|
|
|
25
22
|
runtimeDevUrl: string;
|
|
26
23
|
skipUiAuth: boolean;
|
|
27
24
|
websocketPort: number;
|
|
28
|
-
consumerOutputManifestPath?: string;
|
|
29
25
|
}>,
|
|
30
26
|
): NodeJS.ProcessEnv {
|
|
27
|
+
const publicWebsocketPort = this.resolvePublicWebsocketPort(args.publicBaseUrl, args.websocketPort);
|
|
31
28
|
return {
|
|
32
29
|
...this.build({
|
|
33
|
-
authConfigJson: args.authConfigJson,
|
|
34
30
|
authSecret: args.authSecret,
|
|
31
|
+
configPathOverride: args.configPathOverride,
|
|
32
|
+
consumerOutputManifestPath: args.consumerOutputManifestPath,
|
|
35
33
|
consumerRoot: args.consumerRoot,
|
|
36
34
|
developmentServerToken: args.developmentServerToken,
|
|
37
35
|
nextPort: args.nextPort,
|
|
38
36
|
runtimeDevUrl: args.runtimeDevUrl,
|
|
39
37
|
skipUiAuth: args.skipUiAuth,
|
|
40
38
|
websocketPort: args.websocketPort,
|
|
41
|
-
consumerOutputManifestPath: args.consumerOutputManifestPath,
|
|
42
39
|
}),
|
|
43
40
|
// Standalone `server.js` uses `process.env.HOSTNAME || '0.0.0.0'` for bind; Docker sets HOSTNAME to the
|
|
44
41
|
// container id, which breaks loopback health checks — force IPv4 loopback for the UI child only.
|
|
45
42
|
HOSTNAME: "127.0.0.1",
|
|
46
43
|
AUTH_SECRET: args.authSecret,
|
|
47
44
|
AUTH_URL: args.publicBaseUrl,
|
|
45
|
+
CODEMATION_PUBLIC_WS_PORT: String(publicWebsocketPort),
|
|
46
|
+
NEXT_PUBLIC_CODEMATION_WS_PORT: String(publicWebsocketPort),
|
|
48
47
|
};
|
|
49
48
|
}
|
|
50
49
|
|
|
51
50
|
build(
|
|
52
51
|
args: Readonly<{
|
|
53
|
-
authConfigJson: string;
|
|
54
52
|
authSecret?: string;
|
|
53
|
+
configPathOverride?: string;
|
|
54
|
+
consumerOutputManifestPath?: string;
|
|
55
55
|
consumerRoot: string;
|
|
56
56
|
developmentServerToken: string;
|
|
57
57
|
nextPort: number;
|
|
58
58
|
skipUiAuth: boolean;
|
|
59
59
|
websocketPort: number;
|
|
60
60
|
runtimeDevUrl?: string;
|
|
61
|
-
/** Same manifest as `codemation build` / serve-web so @codemation/next-host can load consumer config (whitelabel, etc.). */
|
|
62
|
-
consumerOutputManifestPath?: string;
|
|
63
61
|
}>,
|
|
64
62
|
): NodeJS.ProcessEnv {
|
|
65
63
|
const merged = this.consumerEnvLoader.mergeConsumerRootIntoProcessEnvironment(args.consumerRoot, process.env);
|
|
66
|
-
const
|
|
64
|
+
const consumerOutputManifestPath =
|
|
67
65
|
args.consumerOutputManifestPath ?? path.resolve(args.consumerRoot, ".codemation", "output", "current.json");
|
|
68
|
-
const authSecret = args.authSecret ?? merged.AUTH_SECRET;
|
|
69
|
-
const authSnapshot = this.frontendAuthSnapshotFactory.createFromResolvedInputs({
|
|
70
|
-
authConfig: this.parseAuthConfig(args.authConfigJson),
|
|
71
|
-
env: {
|
|
72
|
-
...merged,
|
|
73
|
-
...(typeof authSecret === "string" && authSecret.trim().length > 0
|
|
74
|
-
? {
|
|
75
|
-
AUTH_SECRET: authSecret,
|
|
76
|
-
}
|
|
77
|
-
: {}),
|
|
78
|
-
},
|
|
79
|
-
uiAuthEnabled: !args.skipUiAuth,
|
|
80
|
-
});
|
|
81
66
|
return {
|
|
82
67
|
...merged,
|
|
83
68
|
PORT: String(args.nextPort),
|
|
84
69
|
CODEMATION_CONSUMER_ROOT: args.consumerRoot,
|
|
85
|
-
CODEMATION_CONSUMER_OUTPUT_MANIFEST_PATH:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
productName: "Codemation",
|
|
89
|
-
logoUrl: null,
|
|
90
|
-
}),
|
|
70
|
+
CODEMATION_CONSUMER_OUTPUT_MANIFEST_PATH: consumerOutputManifestPath,
|
|
71
|
+
CODEMATION_UI_AUTH_ENABLED: String(!args.skipUiAuth),
|
|
72
|
+
CODEMATION_PUBLIC_WS_PORT: String(args.websocketPort),
|
|
91
73
|
CODEMATION_WS_PORT: String(args.websocketPort),
|
|
92
74
|
NEXT_PUBLIC_CODEMATION_WS_PORT: String(args.websocketPort),
|
|
93
75
|
CODEMATION_DEV_SERVER_TOKEN: args.developmentServerToken,
|
|
@@ -95,17 +77,26 @@ export class DevNextHostEnvironmentBuilder {
|
|
|
95
77
|
NODE_OPTIONS: this.sourceMapNodeOptions.appendToNodeOptions(process.env.NODE_OPTIONS),
|
|
96
78
|
WS_NO_BUFFER_UTIL: "1",
|
|
97
79
|
WS_NO_UTF_8_VALIDATE: "1",
|
|
80
|
+
...(args.authSecret && args.authSecret.trim().length > 0 ? { AUTH_SECRET: args.authSecret.trim() } : {}),
|
|
81
|
+
...(args.configPathOverride && args.configPathOverride.trim().length > 0
|
|
82
|
+
? { CODEMATION_CONFIG_PATH: args.configPathOverride }
|
|
83
|
+
: {}),
|
|
98
84
|
...(args.runtimeDevUrl !== undefined && args.runtimeDevUrl.trim().length > 0
|
|
99
85
|
? { CODEMATION_RUNTIME_DEV_URL: args.runtimeDevUrl.trim() }
|
|
100
86
|
: {}),
|
|
101
87
|
};
|
|
102
88
|
}
|
|
103
89
|
|
|
104
|
-
private
|
|
105
|
-
|
|
106
|
-
|
|
90
|
+
private resolvePublicWebsocketPort(publicBaseUrl: string, fallbackPort: number): number {
|
|
91
|
+
try {
|
|
92
|
+
const parsedUrl = new URL(publicBaseUrl);
|
|
93
|
+
const parsedPort = Number(parsedUrl.port);
|
|
94
|
+
if (Number.isInteger(parsedPort) && parsedPort > 0) {
|
|
95
|
+
return parsedPort;
|
|
96
|
+
}
|
|
97
|
+
} catch {
|
|
98
|
+
// Fall back to the runtime websocket port when the public URL is malformed.
|
|
107
99
|
}
|
|
108
|
-
|
|
109
|
-
return parsed ?? undefined;
|
|
100
|
+
return fallbackPort;
|
|
110
101
|
}
|
|
111
102
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type DevRebuildRequest = Readonly<{
|
|
2
2
|
changedPaths: ReadonlyArray<string>;
|
|
3
|
-
|
|
3
|
+
configPathOverride?: string;
|
|
4
4
|
shouldRestartUi: boolean;
|
|
5
5
|
}>;
|
|
6
6
|
|
|
@@ -47,7 +47,7 @@ export class DevRebuildQueue {
|
|
|
47
47
|
}
|
|
48
48
|
return {
|
|
49
49
|
changedPaths: [...new Set([...current.changedPaths, ...next.changedPaths])],
|
|
50
|
-
|
|
50
|
+
configPathOverride: next.configPathOverride ?? current.configPathOverride,
|
|
51
51
|
shouldRestartUi: current.shouldRestartUi || next.shouldRestartUi,
|
|
52
52
|
};
|
|
53
53
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { ConsumerEnvLoader } from "../consumer/ConsumerEnvLoader";
|
|
2
2
|
import { SourceMapNodeOptions } from "../runtime/SourceMapNodeOptions";
|
|
3
3
|
|
|
4
|
-
import { DevAuthSettingsLoader } from "./DevAuthSettingsLoader";
|
|
5
4
|
import { DevHttpProbe } from "./DevHttpProbe";
|
|
6
5
|
import { DevNextHostEnvironmentBuilder } from "./DevNextHostEnvironmentBuilder";
|
|
7
6
|
import { DevSessionPortsResolver } from "./DevSessionPortsResolver";
|
|
8
7
|
import { DevSourceChangeClassifier } from "./DevSourceChangeClassifier";
|
|
9
8
|
import { LoopbackPortAllocator } from "./LoopbackPortAllocator";
|
|
9
|
+
import { NextHostEdgeSeedLoader } from "./NextHostEdgeSeedLoader";
|
|
10
10
|
import { WatchRootsResolver } from "./WatchRootsResolver";
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -19,7 +19,7 @@ export class DevSessionServices {
|
|
|
19
19
|
readonly sessionPorts: DevSessionPortsResolver,
|
|
20
20
|
readonly loopbackPortAllocator: LoopbackPortAllocator,
|
|
21
21
|
readonly devHttpProbe: DevHttpProbe,
|
|
22
|
-
readonly
|
|
22
|
+
readonly nextHostEdgeSeedLoader: NextHostEdgeSeedLoader,
|
|
23
23
|
readonly nextHostEnvBuilder: DevNextHostEnvironmentBuilder,
|
|
24
24
|
readonly watchRootsResolver: WatchRootsResolver,
|
|
25
25
|
readonly sourceChangeClassifier: DevSourceChangeClassifier,
|
|
@@ -1,21 +1,16 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
|
|
3
3
|
export class DevSourceChangeClassifier {
|
|
4
|
-
private static readonly
|
|
4
|
+
private static readonly uiConfigFileNames = new Set([
|
|
5
5
|
"codemation.config.ts",
|
|
6
6
|
"codemation.config.js",
|
|
7
7
|
"codemation.config.mjs",
|
|
8
8
|
]);
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}>,
|
|
15
|
-
): boolean {
|
|
16
|
-
const resolvedConsumerRoot = path.resolve(args.consumerRoot);
|
|
17
|
-
return args.changedPaths.some((changedPath) => this.isPathInsideDirectory(changedPath, resolvedConsumerRoot));
|
|
18
|
-
}
|
|
9
|
+
private static readonly pluginConfigFileNames = new Set([
|
|
10
|
+
"codemation.plugin.ts",
|
|
11
|
+
"codemation.plugin.js",
|
|
12
|
+
"codemation.plugin.mjs",
|
|
13
|
+
]);
|
|
19
14
|
|
|
20
15
|
requiresUiRestart(
|
|
21
16
|
args: Readonly<{
|
|
@@ -40,10 +35,15 @@ export class DevSourceChangeClassifier {
|
|
|
40
35
|
return false;
|
|
41
36
|
}
|
|
42
37
|
const relativePath = path.relative(consumerRoot, resolvedPath);
|
|
43
|
-
|
|
38
|
+
const fileName = path.basename(relativePath);
|
|
39
|
+
if (DevSourceChangeClassifier.uiConfigFileNames.has(fileName)) {
|
|
44
40
|
// Config changes affect auth and branding projections consumed by the packaged Next host.
|
|
45
41
|
return true;
|
|
46
42
|
}
|
|
43
|
+
if (DevSourceChangeClassifier.pluginConfigFileNames.has(fileName)) {
|
|
44
|
+
// Plugin sandbox configs also define workflows, so keep them on the cheap runtime-only reload path.
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
47
|
if (relativePath.startsWith(path.join("src", "workflows"))) {
|
|
48
48
|
return false;
|
|
49
49
|
}
|