@codemation/cli 0.0.14 → 0.0.16
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/README.md +4 -4
- package/dist/{CliBin-BkY_gChN.js → CliBin-C31h2o8w.js} +224 -216
- package/dist/bin.js +1 -1
- package/dist/index.d.ts +203 -258
- package/dist/index.js +1 -1
- package/package.json +3 -3
- package/src/CliProgramFactory.ts +22 -41
- package/src/Program.ts +12 -0
- package/src/commands/BuildCommand.ts +4 -11
- package/src/commands/DevCommand.ts +77 -45
- 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 +19 -0
- package/src/consumer/ConsumerOutputBuilderFactory.ts +16 -0
- package/src/dev/Builder.ts +2 -2
- 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 +8 -41
- package/src/dev/DevRebuildQueue.ts +2 -2
- package/src/dev/DevSessionServices.ts +2 -2
- package/src/dev/DevSourceChangeClassifier.ts +12 -12
- package/src/dev/{DevAuthSettingsLoader.ts → NextHostEdgeSeedLoader.ts} +13 -9
- package/src/dev/PluginDevConfigFactory.ts +64 -0
- package/src/build/ConsumerBuildArtifactsPublisher.ts +0 -77
- package/src/consumer/Loader.ts +0 -8
- package/src/dev/DevConsumerPublishBootstrap.ts +0 -30
|
@@ -374,6 +374,7 @@ export class ConsumerOutputBuilder {
|
|
|
374
374
|
sourcePath,
|
|
375
375
|
});
|
|
376
376
|
}
|
|
377
|
+
await this.emitConfigSourceFile(outputAppRoot, configSourcePath, runtimeSourcePaths);
|
|
377
378
|
},
|
|
378
379
|
});
|
|
379
380
|
}
|
|
@@ -463,6 +464,24 @@ export class ConsumerOutputBuilder {
|
|
|
463
464
|
}
|
|
464
465
|
}
|
|
465
466
|
|
|
467
|
+
private async emitConfigSourceFile(
|
|
468
|
+
outputAppRoot: string,
|
|
469
|
+
configSourcePath: string,
|
|
470
|
+
runtimeSourcePaths: ReadonlyArray<string>,
|
|
471
|
+
): Promise<void> {
|
|
472
|
+
const normalizedConfigSourcePath = path.resolve(configSourcePath);
|
|
473
|
+
const alreadyEmitted = runtimeSourcePaths.some(
|
|
474
|
+
(sourcePath) => path.resolve(sourcePath) === normalizedConfigSourcePath,
|
|
475
|
+
);
|
|
476
|
+
if (alreadyEmitted) {
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
await this.emitSourceFile({
|
|
480
|
+
outputAppRoot,
|
|
481
|
+
sourcePath: normalizedConfigSourcePath,
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
|
|
466
485
|
private createCompilerOptions(): ts.CompilerOptions {
|
|
467
486
|
const scriptTarget = this.buildOptions.target === "es2020" ? ts.ScriptTarget.ES2020 : ts.ScriptTarget.ES2022;
|
|
468
487
|
return {
|
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
logger?: Logger;
|
|
12
|
+
}>,
|
|
13
|
+
): ConsumerOutputBuilder {
|
|
14
|
+
return new ConsumerOutputBuilder(consumerRoot, args?.logger, args?.buildOptions);
|
|
15
|
+
}
|
|
16
|
+
}
|
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(),
|
|
@@ -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,4 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
1
|
import process from "node:process";
|
|
3
|
-
import type { CodemationAuthConfig } from "@codemation/host";
|
|
4
|
-
import { CodemationFrontendAuthSnapshotFactory, FrontendAppConfigJsonCodec } from "@codemation/host";
|
|
5
2
|
|
|
6
3
|
import { ConsumerEnvLoader } from "../consumer/ConsumerEnvLoader";
|
|
7
4
|
import { SourceMapNodeOptions } from "../runtime/SourceMapNodeOptions";
|
|
@@ -10,14 +7,12 @@ export class DevNextHostEnvironmentBuilder {
|
|
|
10
7
|
constructor(
|
|
11
8
|
private readonly consumerEnvLoader: ConsumerEnvLoader,
|
|
12
9
|
private readonly sourceMapNodeOptions: SourceMapNodeOptions,
|
|
13
|
-
private readonly frontendAuthSnapshotFactory: CodemationFrontendAuthSnapshotFactory = new CodemationFrontendAuthSnapshotFactory(),
|
|
14
|
-
private readonly frontendAppConfigJsonCodec: FrontendAppConfigJsonCodec = new FrontendAppConfigJsonCodec(),
|
|
15
10
|
) {}
|
|
16
11
|
|
|
17
12
|
buildConsumerUiProxy(
|
|
18
13
|
args: Readonly<{
|
|
19
|
-
authConfigJson: string;
|
|
20
14
|
authSecret: string;
|
|
15
|
+
configPathOverride?: string;
|
|
21
16
|
consumerRoot: string;
|
|
22
17
|
developmentServerToken: string;
|
|
23
18
|
nextPort: number;
|
|
@@ -25,20 +20,18 @@ export class DevNextHostEnvironmentBuilder {
|
|
|
25
20
|
runtimeDevUrl: string;
|
|
26
21
|
skipUiAuth: boolean;
|
|
27
22
|
websocketPort: number;
|
|
28
|
-
consumerOutputManifestPath?: string;
|
|
29
23
|
}>,
|
|
30
24
|
): NodeJS.ProcessEnv {
|
|
31
25
|
return {
|
|
32
26
|
...this.build({
|
|
33
|
-
authConfigJson: args.authConfigJson,
|
|
34
27
|
authSecret: args.authSecret,
|
|
28
|
+
configPathOverride: args.configPathOverride,
|
|
35
29
|
consumerRoot: args.consumerRoot,
|
|
36
30
|
developmentServerToken: args.developmentServerToken,
|
|
37
31
|
nextPort: args.nextPort,
|
|
38
32
|
runtimeDevUrl: args.runtimeDevUrl,
|
|
39
33
|
skipUiAuth: args.skipUiAuth,
|
|
40
34
|
websocketPort: args.websocketPort,
|
|
41
|
-
consumerOutputManifestPath: args.consumerOutputManifestPath,
|
|
42
35
|
}),
|
|
43
36
|
// Standalone `server.js` uses `process.env.HOSTNAME || '0.0.0.0'` for bind; Docker sets HOSTNAME to the
|
|
44
37
|
// container id, which breaks loopback health checks — force IPv4 loopback for the UI child only.
|
|
@@ -50,44 +43,22 @@ export class DevNextHostEnvironmentBuilder {
|
|
|
50
43
|
|
|
51
44
|
build(
|
|
52
45
|
args: Readonly<{
|
|
53
|
-
authConfigJson: string;
|
|
54
46
|
authSecret?: string;
|
|
47
|
+
configPathOverride?: string;
|
|
55
48
|
consumerRoot: string;
|
|
56
49
|
developmentServerToken: string;
|
|
57
50
|
nextPort: number;
|
|
58
51
|
skipUiAuth: boolean;
|
|
59
52
|
websocketPort: number;
|
|
60
53
|
runtimeDevUrl?: string;
|
|
61
|
-
/** Same manifest as `codemation build` / serve-web so @codemation/next-host can load consumer config (whitelabel, etc.). */
|
|
62
|
-
consumerOutputManifestPath?: string;
|
|
63
54
|
}>,
|
|
64
55
|
): NodeJS.ProcessEnv {
|
|
65
56
|
const merged = this.consumerEnvLoader.mergeConsumerRootIntoProcessEnvironment(args.consumerRoot, process.env);
|
|
66
|
-
const manifestPath =
|
|
67
|
-
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
57
|
return {
|
|
82
58
|
...merged,
|
|
83
59
|
PORT: String(args.nextPort),
|
|
84
60
|
CODEMATION_CONSUMER_ROOT: args.consumerRoot,
|
|
85
|
-
|
|
86
|
-
CODEMATION_FRONTEND_APP_CONFIG_JSON: this.frontendAppConfigJsonCodec.serialize({
|
|
87
|
-
auth: authSnapshot,
|
|
88
|
-
productName: "Codemation",
|
|
89
|
-
logoUrl: null,
|
|
90
|
-
}),
|
|
61
|
+
CODEMATION_UI_AUTH_ENABLED: String(!args.skipUiAuth),
|
|
91
62
|
CODEMATION_WS_PORT: String(args.websocketPort),
|
|
92
63
|
NEXT_PUBLIC_CODEMATION_WS_PORT: String(args.websocketPort),
|
|
93
64
|
CODEMATION_DEV_SERVER_TOKEN: args.developmentServerToken,
|
|
@@ -95,17 +66,13 @@ export class DevNextHostEnvironmentBuilder {
|
|
|
95
66
|
NODE_OPTIONS: this.sourceMapNodeOptions.appendToNodeOptions(process.env.NODE_OPTIONS),
|
|
96
67
|
WS_NO_BUFFER_UTIL: "1",
|
|
97
68
|
WS_NO_UTF_8_VALIDATE: "1",
|
|
69
|
+
...(args.authSecret && args.authSecret.trim().length > 0 ? { AUTH_SECRET: args.authSecret.trim() } : {}),
|
|
70
|
+
...(args.configPathOverride && args.configPathOverride.trim().length > 0
|
|
71
|
+
? { CODEMATION_CONFIG_PATH: args.configPathOverride }
|
|
72
|
+
: {}),
|
|
98
73
|
...(args.runtimeDevUrl !== undefined && args.runtimeDevUrl.trim().length > 0
|
|
99
74
|
? { CODEMATION_RUNTIME_DEV_URL: args.runtimeDevUrl.trim() }
|
|
100
75
|
: {}),
|
|
101
76
|
};
|
|
102
77
|
}
|
|
103
|
-
|
|
104
|
-
private parseAuthConfig(authConfigJson: string): CodemationAuthConfig | undefined {
|
|
105
|
-
if (authConfigJson.trim().length === 0) {
|
|
106
|
-
return undefined;
|
|
107
|
-
}
|
|
108
|
-
const parsed = JSON.parse(authConfigJson) as CodemationAuthConfig | null;
|
|
109
|
-
return parsed ?? undefined;
|
|
110
|
-
}
|
|
111
78
|
}
|
|
@@ -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
|
}
|
|
@@ -4,13 +4,12 @@ import { CodemationConsumerConfigLoader } from "@codemation/host/server";
|
|
|
4
4
|
|
|
5
5
|
import type { ConsumerEnvLoader } from "../consumer/ConsumerEnvLoader";
|
|
6
6
|
|
|
7
|
-
export type
|
|
8
|
-
authConfigJson: string;
|
|
7
|
+
export type NextHostEdgeSeed = Readonly<{
|
|
9
8
|
authSecret: string;
|
|
10
|
-
|
|
9
|
+
uiAuthEnabled: boolean;
|
|
11
10
|
}>;
|
|
12
11
|
|
|
13
|
-
export class
|
|
12
|
+
export class NextHostEdgeSeedLoader {
|
|
14
13
|
static readonly defaultDevelopmentAuthSecret = "codemation-dev-auth-secret-not-for-production";
|
|
15
14
|
|
|
16
15
|
constructor(
|
|
@@ -25,13 +24,18 @@ export class DevAuthSettingsLoader {
|
|
|
25
24
|
return randomUUID();
|
|
26
25
|
}
|
|
27
26
|
|
|
28
|
-
async loadForConsumer(
|
|
29
|
-
|
|
27
|
+
async loadForConsumer(
|
|
28
|
+
consumerRoot: string,
|
|
29
|
+
options?: Readonly<{ configPathOverride?: string }>,
|
|
30
|
+
): Promise<NextHostEdgeSeed> {
|
|
31
|
+
const resolution = await this.configLoader.load({
|
|
32
|
+
consumerRoot,
|
|
33
|
+
configPathOverride: options?.configPathOverride,
|
|
34
|
+
});
|
|
30
35
|
const envForAuthSecret = this.consumerEnvLoader.mergeConsumerRootIntoProcessEnvironment(consumerRoot, process.env);
|
|
31
36
|
return {
|
|
32
|
-
authConfigJson: JSON.stringify(resolution.config.auth ?? null),
|
|
33
37
|
authSecret: this.resolveDevelopmentAuthSecret(envForAuthSecret),
|
|
34
|
-
|
|
38
|
+
uiAuthEnabled: resolution.config.auth?.allowUnauthenticatedInDevelopment !== true,
|
|
35
39
|
};
|
|
36
40
|
}
|
|
37
41
|
|
|
@@ -40,6 +44,6 @@ export class DevAuthSettingsLoader {
|
|
|
40
44
|
if (configuredSecret && configuredSecret.trim().length > 0) {
|
|
41
45
|
return configuredSecret;
|
|
42
46
|
}
|
|
43
|
-
return
|
|
47
|
+
return NextHostEdgeSeedLoader.defaultDevelopmentAuthSecret;
|
|
44
48
|
}
|
|
45
49
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { access, mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export type PluginDevConfigBootstrap = Readonly<{
|
|
5
|
+
configPath: string;
|
|
6
|
+
}>;
|
|
7
|
+
|
|
8
|
+
export class PluginDevConfigFactory {
|
|
9
|
+
async prepare(pluginRoot: string): Promise<PluginDevConfigBootstrap> {
|
|
10
|
+
const pluginEntryPath = await this.resolvePluginEntryPath(pluginRoot);
|
|
11
|
+
const configPath = path.resolve(pluginRoot, ".codemation", "plugin-dev", "codemation.config.ts");
|
|
12
|
+
await mkdir(path.dirname(configPath), { recursive: true });
|
|
13
|
+
await writeFile(configPath, this.createConfigSource(configPath, pluginEntryPath), "utf8");
|
|
14
|
+
return {
|
|
15
|
+
configPath,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
private async resolvePluginEntryPath(pluginRoot: string): Promise<string> {
|
|
20
|
+
const candidates = [
|
|
21
|
+
path.resolve(pluginRoot, "codemation.plugin.ts"),
|
|
22
|
+
path.resolve(pluginRoot, "codemation.plugin.js"),
|
|
23
|
+
path.resolve(pluginRoot, "src", "codemation.plugin.ts"),
|
|
24
|
+
path.resolve(pluginRoot, "src", "codemation.plugin.js"),
|
|
25
|
+
];
|
|
26
|
+
for (const candidate of candidates) {
|
|
27
|
+
if (await this.exists(candidate)) {
|
|
28
|
+
return candidate;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
throw new Error('Plugin config not found. Expected "codemation.plugin.ts" in the plugin root or "src/".');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private createConfigSource(configPath: string, pluginEntryPath: string): string {
|
|
35
|
+
const relativeImportPath = this.toRelativeImportPath(configPath, pluginEntryPath);
|
|
36
|
+
return [
|
|
37
|
+
'import type { CodemationConfig } from "@codemation/host";',
|
|
38
|
+
`import plugin from ${JSON.stringify(relativeImportPath)};`,
|
|
39
|
+
"",
|
|
40
|
+
"const sandbox = plugin.sandbox ?? {};",
|
|
41
|
+
"const config: CodemationConfig = {",
|
|
42
|
+
" ...sandbox,",
|
|
43
|
+
" plugins: [...(sandbox.plugins ?? []), plugin],",
|
|
44
|
+
"};",
|
|
45
|
+
"",
|
|
46
|
+
"export default config;",
|
|
47
|
+
"",
|
|
48
|
+
].join("\n");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private toRelativeImportPath(fromPath: string, targetPath: string): string {
|
|
52
|
+
const relativePath = path.relative(path.dirname(fromPath), targetPath).replace(/\\/g, "/");
|
|
53
|
+
return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private async exists(filePath: string): Promise<boolean> {
|
|
57
|
+
try {
|
|
58
|
+
await access(filePath);
|
|
59
|
+
return true;
|
|
60
|
+
} catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import type { CodemationDiscoveredPluginPackage } from "@codemation/host/server";
|
|
2
|
-
import { randomUUID } from "node:crypto";
|
|
3
|
-
import { mkdir, rename, writeFile } from "node:fs/promises";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { pathToFileURL } from "node:url";
|
|
6
|
-
|
|
7
|
-
import type { ConsumerOutputBuildSnapshot } from "../consumer/ConsumerOutputBuilder";
|
|
8
|
-
|
|
9
|
-
export type ConsumerBuildManifest = Readonly<{
|
|
10
|
-
buildVersion: string;
|
|
11
|
-
consumerRoot: string;
|
|
12
|
-
entryPath: string;
|
|
13
|
-
manifestPath: string;
|
|
14
|
-
pluginEntryPath: string;
|
|
15
|
-
workflowSourcePaths: ReadonlyArray<string>;
|
|
16
|
-
}>;
|
|
17
|
-
|
|
18
|
-
export class ConsumerBuildArtifactsPublisher {
|
|
19
|
-
async publish(
|
|
20
|
-
snapshot: ConsumerOutputBuildSnapshot,
|
|
21
|
-
discoveredPlugins: ReadonlyArray<CodemationDiscoveredPluginPackage>,
|
|
22
|
-
): Promise<ConsumerBuildManifest> {
|
|
23
|
-
const pluginEntryPath = await this.writeDiscoveredPluginsOutput(snapshot, discoveredPlugins);
|
|
24
|
-
return await this.writeBuildManifest(snapshot, pluginEntryPath);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
private async writeDiscoveredPluginsOutput(
|
|
28
|
-
snapshot: ConsumerOutputBuildSnapshot,
|
|
29
|
-
discoveredPlugins: ReadonlyArray<CodemationDiscoveredPluginPackage>,
|
|
30
|
-
): Promise<string> {
|
|
31
|
-
const outputPath = path.resolve(snapshot.emitOutputRoot, "plugins.js");
|
|
32
|
-
await mkdir(path.dirname(outputPath), { recursive: true });
|
|
33
|
-
const outputLines: string[] = ["const codemationDiscoveredPlugins = [];", ""];
|
|
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`;
|
|
41
|
-
outputLines.push(`const pluginModule${index} = await import(${JSON.stringify(pluginFileUrl)});`);
|
|
42
|
-
outputLines.push(`const pluginValue${index} = ${exportNameAccessor};`);
|
|
43
|
-
outputLines.push(`if (pluginValue${index} && typeof pluginValue${index}.register === "function") {`);
|
|
44
|
-
outputLines.push(` codemationDiscoveredPlugins.push(pluginValue${index});`);
|
|
45
|
-
outputLines.push(
|
|
46
|
-
`} else if (typeof pluginValue${index} === "function" && pluginValue${index}.prototype && typeof pluginValue${index}.prototype.register === "function") {`,
|
|
47
|
-
);
|
|
48
|
-
outputLines.push(` codemationDiscoveredPlugins.push(new pluginValue${index}());`);
|
|
49
|
-
outputLines.push("}");
|
|
50
|
-
outputLines.push("");
|
|
51
|
-
});
|
|
52
|
-
outputLines.push("export { codemationDiscoveredPlugins };");
|
|
53
|
-
outputLines.push("export default codemationDiscoveredPlugins;");
|
|
54
|
-
outputLines.push("");
|
|
55
|
-
await writeFile(outputPath, outputLines.join("\n"), "utf8");
|
|
56
|
-
return outputPath;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
private async writeBuildManifest(
|
|
60
|
-
snapshot: ConsumerOutputBuildSnapshot,
|
|
61
|
-
pluginEntryPath: string,
|
|
62
|
-
): Promise<ConsumerBuildManifest> {
|
|
63
|
-
const manifest: ConsumerBuildManifest = {
|
|
64
|
-
buildVersion: snapshot.buildVersion,
|
|
65
|
-
consumerRoot: snapshot.consumerRoot,
|
|
66
|
-
entryPath: snapshot.outputEntryPath,
|
|
67
|
-
manifestPath: snapshot.manifestPath,
|
|
68
|
-
pluginEntryPath,
|
|
69
|
-
workflowSourcePaths: snapshot.workflowSourcePaths,
|
|
70
|
-
};
|
|
71
|
-
await mkdir(path.dirname(snapshot.manifestPath), { recursive: true });
|
|
72
|
-
const temporaryManifestPath = `${snapshot.manifestPath}.${snapshot.buildVersion}.${randomUUID()}.tmp`;
|
|
73
|
-
await writeFile(temporaryManifestPath, JSON.stringify(manifest, null, 2), "utf8");
|
|
74
|
-
await rename(temporaryManifestPath, snapshot.manifestPath);
|
|
75
|
-
return manifest;
|
|
76
|
-
}
|
|
77
|
-
}
|
package/src/consumer/Loader.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { ConsumerBuildOptions } from "./consumerBuildOptions.types";
|
|
2
|
-
import { ConsumerOutputBuilder } from "./ConsumerOutputBuilder";
|
|
3
|
-
|
|
4
|
-
export class ConsumerOutputBuilderLoader {
|
|
5
|
-
create(consumerRoot: string, buildOptions: ConsumerBuildOptions): ConsumerOutputBuilder {
|
|
6
|
-
return new ConsumerOutputBuilder(consumerRoot, undefined, buildOptions);
|
|
7
|
-
}
|
|
8
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import type { Logger } from "@codemation/host/next/server";
|
|
2
|
-
import { CodemationPluginDiscovery } from "@codemation/host/server";
|
|
3
|
-
|
|
4
|
-
import { ConsumerBuildArtifactsPublisher } from "../build/ConsumerBuildArtifactsPublisher";
|
|
5
|
-
import { ConsumerBuildOptionsParser } from "../build/ConsumerBuildOptionsParser";
|
|
6
|
-
import { ConsumerOutputBuilderLoader } from "../consumer/Loader";
|
|
7
|
-
import type { CliPaths } from "../path/CliPathResolver";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Ensures `.codemation/output/current.json` and transpiled consumer config exist before the Next host boots.
|
|
11
|
-
* Without this, `codemation dev` can serve a stale built `codemation.config.js` (e.g. missing whitelabel).
|
|
12
|
-
*/
|
|
13
|
-
export class DevConsumerPublishBootstrap {
|
|
14
|
-
constructor(
|
|
15
|
-
private readonly cliLogger: Logger,
|
|
16
|
-
private readonly pluginDiscovery: CodemationPluginDiscovery,
|
|
17
|
-
private readonly artifactsPublisher: ConsumerBuildArtifactsPublisher,
|
|
18
|
-
private readonly outputBuilderLoader: ConsumerOutputBuilderLoader,
|
|
19
|
-
private readonly buildOptionsParser: ConsumerBuildOptionsParser,
|
|
20
|
-
) {}
|
|
21
|
-
|
|
22
|
-
async ensurePublished(paths: CliPaths): Promise<void> {
|
|
23
|
-
const buildOptions = this.buildOptionsParser.parse({});
|
|
24
|
-
const builder = this.outputBuilderLoader.create(paths.consumerRoot, buildOptions);
|
|
25
|
-
const snapshot = await builder.ensureBuilt();
|
|
26
|
-
const discoveredPlugins = await this.pluginDiscovery.discover(paths.consumerRoot);
|
|
27
|
-
await this.artifactsPublisher.publish(snapshot, discoveredPlugins);
|
|
28
|
-
this.cliLogger.debug(`Dev: consumer output published (${snapshot.buildVersion}).`);
|
|
29
|
-
}
|
|
30
|
-
}
|