@codemation/cli 0.0.16 → 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/dist/{CliBin-C31h2o8w.js → CliBin-BYHuUedo.js} +170 -16
- package/dist/bin.js +1 -1
- package/dist/index.d.ts +95 -60
- package/dist/index.js +1 -1
- package/package.json +5 -4
- package/src/CliProgramFactory.ts +14 -1
- package/src/build/ConsumerBuildArtifactsPublisher.ts +82 -0
- package/src/commands/BuildCommand.ts +7 -0
- package/src/commands/DevCommand.ts +37 -3
- package/src/consumer/ConsumerOutputBuilder.ts +9 -0
- package/src/consumer/ConsumerOutputBuilderFactory.ts +2 -1
- package/src/dev/CliDevProxyServer.ts +20 -0
- package/src/dev/DevNextHostEnvironmentBuilder.ts +24 -0
- package/src/dev/ListenPortConflictDescriber.ts +66 -5
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# @codemation/cli
|
|
2
|
+
|
|
3
|
+
## 0.0.18
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- f0c6878: Introduce Changesets, a single CI status check for branch protection, and the Codemation pre-stable license across published packages.
|
|
8
|
+
- Updated dependencies [f0c6878]
|
|
9
|
+
- @codemation/host@0.0.18
|
|
10
|
+
- @codemation/next-host@0.0.18
|
package/LICENSE
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Codemation Pre-Stable License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Made Relevant B.V. All rights reserved.
|
|
4
|
+
|
|
5
|
+
1. Definitions
|
|
6
|
+
|
|
7
|
+
"Software" means the Codemation source code, documentation, and artifacts in this repository and any published npm packages in the Codemation monorepo.
|
|
8
|
+
|
|
9
|
+
"Stable Version" means the first published release of the package `@codemation/core` on the public npm registry with version 1.0.0 or higher.
|
|
10
|
+
|
|
11
|
+
2. Permitted use (before Stable Version)
|
|
12
|
+
|
|
13
|
+
Until a Stable Version exists, you may use, copy, modify, and distribute the Software only for non-commercial purposes, including personal learning, research, evaluation, and internal use within your organization that does not charge third parties for access to the Software or a product or service whose primary value is the Software.
|
|
14
|
+
|
|
15
|
+
3. Restrictions (before Stable Version)
|
|
16
|
+
|
|
17
|
+
Until a Stable Version exists, you must not:
|
|
18
|
+
|
|
19
|
+
a) Sell, rent, lease, or sublicense the Software or a derivative work for a fee;
|
|
20
|
+
|
|
21
|
+
b) Offer the Software or a derivative work as part of a paid product or service (including hosting, support, or consulting) where the Software is a material part of the offering;
|
|
22
|
+
|
|
23
|
+
c) Use the Software or a derivative work primarily to generate revenue or commercial advantage for you or others.
|
|
24
|
+
|
|
25
|
+
These restrictions apply to all versions published before a Stable Version, even if a later Stable Version is released under different terms.
|
|
26
|
+
|
|
27
|
+
4. After Stable Version
|
|
28
|
+
|
|
29
|
+
The maintainers may publish a Stable Version under different license terms. If they do, those terms apply only to that Stable Version and subsequent releases they designate; they do not automatically apply to earlier pre-stable versions.
|
|
30
|
+
|
|
31
|
+
5. No warranty
|
|
32
|
+
|
|
33
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
34
|
+
|
|
35
|
+
6. Third-party components
|
|
36
|
+
|
|
37
|
+
The Software may include third-party components under their own licenses. Those licenses govern those components.
|
|
@@ -3,15 +3,15 @@ import process$1 from "node:process";
|
|
|
3
3
|
import { AppConfigLoader, CodemationConsumerConfigLoader, CodemationPluginDiscovery, WorkflowDiscoveryPathSegmentsComputer, WorkflowModulePathFinder } from "@codemation/host/server";
|
|
4
4
|
import { ApiPaths, AppContainerFactory, AppContainerLifecycle, ApplicationTokens, CodemationPluginPackageMetadata, DatabaseMigrations, ListUserAccountsQuery, PrismaClient, UpsertLocalBootstrapUserCommand, WorkerRuntime } from "@codemation/host";
|
|
5
5
|
import { AppContainerFactory as AppContainerFactory$1, AppContainerLifecycle as AppContainerLifecycle$1, CodemationHonoApiApp, CodemationPluginListMerger, FrontendRuntime, ServerLoggerFactory, logLevelPolicyFactory } from "@codemation/host/next/server";
|
|
6
|
-
import {
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
7
|
+
import { access, copyFile, cp, mkdir, open, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
7
8
|
import path from "node:path";
|
|
9
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
10
|
+
import { execFile, spawn } from "node:child_process";
|
|
8
11
|
import { existsSync, readFileSync } from "node:fs";
|
|
9
12
|
import { config, parse } from "dotenv";
|
|
10
13
|
import { watch } from "chokidar";
|
|
11
|
-
import { randomUUID } from "node:crypto";
|
|
12
|
-
import { access, copyFile, cp, mkdir, open, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
13
14
|
import ts from "typescript";
|
|
14
|
-
import { fileURLToPath } from "node:url";
|
|
15
15
|
import { PrismaMigrationDeployer } from "@codemation/host/persistence";
|
|
16
16
|
import boxen from "boxen";
|
|
17
17
|
import chalk from "chalk";
|
|
@@ -41,20 +41,74 @@ var ConsumerBuildOptionsParser = class {
|
|
|
41
41
|
}
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/build/ConsumerBuildArtifactsPublisher.ts
|
|
46
|
+
var ConsumerBuildArtifactsPublisher = class {
|
|
47
|
+
async publish(snapshot, discoveredPlugins) {
|
|
48
|
+
const pluginEntryPath = await this.writeDiscoveredPluginsOutput(snapshot, discoveredPlugins);
|
|
49
|
+
return await this.writeBuildManifest(snapshot, pluginEntryPath);
|
|
50
|
+
}
|
|
51
|
+
async writeDiscoveredPluginsOutput(snapshot, discoveredPlugins) {
|
|
52
|
+
const outputPath = path.resolve(snapshot.emitOutputRoot, "plugins.js");
|
|
53
|
+
await mkdir(path.dirname(outputPath), { recursive: true });
|
|
54
|
+
const outputLines = ["const codemationDiscoveredPlugins = [];", ""];
|
|
55
|
+
discoveredPlugins.forEach((discoveredPlugin, index) => {
|
|
56
|
+
const pluginFileUrl = pathToFileURL(path.resolve(discoveredPlugin.packageRoot, this.resolvePluginEntry(discoveredPlugin))).href;
|
|
57
|
+
outputLines.push(`const pluginModule${index} = await import(${JSON.stringify(pluginFileUrl)});`);
|
|
58
|
+
outputLines.push(`const pluginValue${index} = pluginModule${index}.default ?? pluginModule${index}.codemationPlugin;`);
|
|
59
|
+
outputLines.push(`if (pluginValue${index} && typeof pluginValue${index}.register === "function") {`);
|
|
60
|
+
outputLines.push(` codemationDiscoveredPlugins.push(pluginValue${index});`);
|
|
61
|
+
outputLines.push(`} else if (typeof pluginValue${index} === "function" && pluginValue${index}.prototype && typeof pluginValue${index}.prototype.register === "function") {`);
|
|
62
|
+
outputLines.push(` codemationDiscoveredPlugins.push(new pluginValue${index}());`);
|
|
63
|
+
outputLines.push("}");
|
|
64
|
+
outputLines.push("");
|
|
65
|
+
});
|
|
66
|
+
outputLines.push("export { codemationDiscoveredPlugins };");
|
|
67
|
+
outputLines.push("export default codemationDiscoveredPlugins;");
|
|
68
|
+
outputLines.push("");
|
|
69
|
+
await writeFile(outputPath, outputLines.join("\n"), "utf8");
|
|
70
|
+
return outputPath;
|
|
71
|
+
}
|
|
72
|
+
async writeBuildManifest(snapshot, pluginEntryPath) {
|
|
73
|
+
const manifest = {
|
|
74
|
+
buildVersion: snapshot.buildVersion,
|
|
75
|
+
consumerRoot: snapshot.consumerRoot,
|
|
76
|
+
entryPath: snapshot.outputEntryPath,
|
|
77
|
+
manifestPath: snapshot.manifestPath,
|
|
78
|
+
pluginEntryPath,
|
|
79
|
+
workflowSourcePaths: snapshot.workflowSourcePaths
|
|
80
|
+
};
|
|
81
|
+
await mkdir(path.dirname(snapshot.manifestPath), { recursive: true });
|
|
82
|
+
const temporaryManifestPath = `${snapshot.manifestPath}.${snapshot.buildVersion}.${randomUUID()}.tmp`;
|
|
83
|
+
await writeFile(temporaryManifestPath, JSON.stringify(manifest, null, 2), "utf8");
|
|
84
|
+
await rename(temporaryManifestPath, snapshot.manifestPath);
|
|
85
|
+
return manifest;
|
|
86
|
+
}
|
|
87
|
+
resolvePluginEntry(discoveredPlugin) {
|
|
88
|
+
if (typeof discoveredPlugin.developmentEntry === "string" && discoveredPlugin.developmentEntry.trim().length > 0) return discoveredPlugin.developmentEntry;
|
|
89
|
+
return discoveredPlugin.pluginEntry;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
44
93
|
//#endregion
|
|
45
94
|
//#region src/commands/BuildCommand.ts
|
|
46
95
|
var BuildCommand = class {
|
|
47
|
-
constructor(cliLogger, pathResolver, consumerOutputBuilderFactory, tsRuntime) {
|
|
96
|
+
constructor(cliLogger, pathResolver, consumerOutputBuilderFactory, pluginDiscovery, consumerBuildArtifactsPublisher, tsRuntime) {
|
|
48
97
|
this.cliLogger = cliLogger;
|
|
49
98
|
this.pathResolver = pathResolver;
|
|
50
99
|
this.consumerOutputBuilderFactory = consumerOutputBuilderFactory;
|
|
100
|
+
this.pluginDiscovery = pluginDiscovery;
|
|
101
|
+
this.consumerBuildArtifactsPublisher = consumerBuildArtifactsPublisher;
|
|
51
102
|
this.tsRuntime = tsRuntime;
|
|
52
103
|
}
|
|
53
104
|
async execute(consumerRoot, buildOptions) {
|
|
54
105
|
const paths = await this.pathResolver.resolve(consumerRoot);
|
|
55
106
|
this.tsRuntime.configure(paths.repoRoot);
|
|
56
107
|
const snapshot = await this.consumerOutputBuilderFactory.create(paths.consumerRoot, { buildOptions }).ensureBuilt();
|
|
108
|
+
const discoveredPlugins = await this.pluginDiscovery.discover(paths.consumerRoot);
|
|
109
|
+
const manifest = await this.consumerBuildArtifactsPublisher.publish(snapshot, discoveredPlugins);
|
|
57
110
|
this.cliLogger.info(`Built consumer output: ${snapshot.outputEntryPath}`);
|
|
111
|
+
this.cliLogger.info(`Build manifest: ${manifest.manifestPath}`);
|
|
58
112
|
this.cliLogger.info(`Workflow modules emitted: ${snapshot.workflowSourcePaths.length}`);
|
|
59
113
|
}
|
|
60
114
|
};
|
|
@@ -74,7 +128,7 @@ var DbMigrateCommand = class {
|
|
|
74
128
|
//#region src/commands/DevCommand.ts
|
|
75
129
|
var DevCommand = class {
|
|
76
130
|
require = createRequire(import.meta.url);
|
|
77
|
-
constructor(pathResolver, tsRuntime, devLockFactory, devSourceWatcherFactory, cliLogger, session, databaseMigrationsApplyService, devBootstrapSummaryFetcher, devCliBannerRenderer, consumerEnvDotenvFilePredicate, devTrackedProcessTreeKiller, nextHostConsumerServerCommandFactory, devApiRuntimeFactory, cliDevProxyServerFactory, devRebuildQueueFactory) {
|
|
131
|
+
constructor(pathResolver, tsRuntime, devLockFactory, devSourceWatcherFactory, cliLogger, session, databaseMigrationsApplyService, consumerOutputBuilderFactory, pluginDiscovery, consumerBuildArtifactsPublisher, devBootstrapSummaryFetcher, devCliBannerRenderer, consumerEnvDotenvFilePredicate, devTrackedProcessTreeKiller, nextHostConsumerServerCommandFactory, devApiRuntimeFactory, cliDevProxyServerFactory, devRebuildQueueFactory) {
|
|
78
132
|
this.pathResolver = pathResolver;
|
|
79
133
|
this.tsRuntime = tsRuntime;
|
|
80
134
|
this.devLockFactory = devLockFactory;
|
|
@@ -82,6 +136,9 @@ var DevCommand = class {
|
|
|
82
136
|
this.cliLogger = cliLogger;
|
|
83
137
|
this.session = session;
|
|
84
138
|
this.databaseMigrationsApplyService = databaseMigrationsApplyService;
|
|
139
|
+
this.consumerOutputBuilderFactory = consumerOutputBuilderFactory;
|
|
140
|
+
this.pluginDiscovery = pluginDiscovery;
|
|
141
|
+
this.consumerBuildArtifactsPublisher = consumerBuildArtifactsPublisher;
|
|
85
142
|
this.devBootstrapSummaryFetcher = devBootstrapSummaryFetcher;
|
|
86
143
|
this.devCliBannerRenderer = devCliBannerRenderer;
|
|
87
144
|
this.consumerEnvDotenvFilePredicate = consumerEnvDotenvFilePredicate;
|
|
@@ -94,6 +151,7 @@ var DevCommand = class {
|
|
|
94
151
|
async execute(args) {
|
|
95
152
|
const paths = await this.pathResolver.resolve(args.consumerRoot);
|
|
96
153
|
const commandName = args.commandName ?? "dev";
|
|
154
|
+
const previousDevelopmentServerToken = process$1.env.CODEMATION_DEV_SERVER_TOKEN;
|
|
97
155
|
this.devCliBannerRenderer.renderBrandHeader();
|
|
98
156
|
this.tsRuntime.configure(paths.repoRoot);
|
|
99
157
|
await this.databaseMigrationsApplyService.applyForConsumer(paths.consumerRoot, { configPath: args.configPathOverride });
|
|
@@ -114,6 +172,8 @@ var DevCommand = class {
|
|
|
114
172
|
let proxyServer = null;
|
|
115
173
|
try {
|
|
116
174
|
const prepared = await this.prepareDevRuntime(paths, devMode, nextPort, gatewayPort, authSettings, args.configPathOverride);
|
|
175
|
+
if (prepared.devMode === "packaged-ui") await this.publishConsumerArtifacts(prepared.paths, prepared.configPathOverride);
|
|
176
|
+
process$1.env.CODEMATION_DEV_SERVER_TOKEN = prepared.developmentServerToken;
|
|
117
177
|
const stopPromise = this.wireStopPromise(processState);
|
|
118
178
|
const uiProxyBase = await this.preparePackagedUiBaseUrlWhenNeeded(prepared, processState);
|
|
119
179
|
proxyServer = await this.startProxyServer(prepared.gatewayPort, uiProxyBase);
|
|
@@ -132,6 +192,8 @@ var DevCommand = class {
|
|
|
132
192
|
this.logPackagedUiDevHintWhenNeeded(devMode, gatewayPort, commandName);
|
|
133
193
|
await stopPromise;
|
|
134
194
|
} finally {
|
|
195
|
+
if (previousDevelopmentServerToken === void 0) delete process$1.env.CODEMATION_DEV_SERVER_TOKEN;
|
|
196
|
+
else process$1.env.CODEMATION_DEV_SERVER_TOKEN = previousDevelopmentServerToken;
|
|
135
197
|
processState.stopRequested = true;
|
|
136
198
|
await this.stopLiveProcesses(processState, proxyServer);
|
|
137
199
|
await watcher.stop();
|
|
@@ -345,6 +407,7 @@ var DevCommand = class {
|
|
|
345
407
|
proxyServer.setBuildStatus("building");
|
|
346
408
|
proxyServer.broadcastBuildStarted();
|
|
347
409
|
try {
|
|
410
|
+
if (prepared.devMode === "packaged-ui") await this.publishConsumerArtifacts(prepared.paths, request.configPathOverride);
|
|
348
411
|
await this.stopCurrentRuntime(state, proxyServer);
|
|
349
412
|
process$1.stdout.write("[codemation] Waiting for runtime to accept traffic…\n");
|
|
350
413
|
const runtime = await this.createRuntime(prepared);
|
|
@@ -353,11 +416,11 @@ var DevCommand = class {
|
|
|
353
416
|
httpPort: runtime.httpPort,
|
|
354
417
|
workflowWebSocketPort: runtime.workflowWebSocketPort
|
|
355
418
|
});
|
|
356
|
-
if (request.shouldRestartUi) await this.restartUiAfterSourceChange(prepared, state, gatewayBaseUrl);
|
|
357
419
|
await this.session.devHttpProbe.waitUntilBootstrapSummaryReady(gatewayBaseUrl);
|
|
358
420
|
const json = await this.devBootstrapSummaryFetcher.fetch(gatewayBaseUrl);
|
|
359
421
|
if (json) this.devCliBannerRenderer.renderCompact(json);
|
|
360
422
|
proxyServer.setBuildStatus("idle");
|
|
423
|
+
if (request.shouldRestartUi) await this.restartUiAfterSourceChange(prepared, state, gatewayBaseUrl);
|
|
361
424
|
proxyServer.broadcastBuildCompleted(runtime.buildVersion);
|
|
362
425
|
process$1.stdout.write("[codemation] Runtime ready.\n");
|
|
363
426
|
} catch (error) {
|
|
@@ -427,6 +490,12 @@ var DevCommand = class {
|
|
|
427
490
|
}
|
|
428
491
|
});
|
|
429
492
|
}
|
|
493
|
+
async publishConsumerArtifacts(paths, configPathOverride) {
|
|
494
|
+
const snapshot = await this.consumerOutputBuilderFactory.create(paths.consumerRoot, { configPathOverride }).ensureBuilt();
|
|
495
|
+
const discoveredPlugins = await this.pluginDiscovery.discover(paths.consumerRoot);
|
|
496
|
+
await this.consumerBuildArtifactsPublisher.publish(snapshot, discoveredPlugins);
|
|
497
|
+
this.cliLogger.debug(`Dev: consumer output published (${snapshot.buildVersion}).`);
|
|
498
|
+
}
|
|
430
499
|
logPackagedUiDevHintWhenNeeded(devMode, gatewayPort, commandName) {
|
|
431
500
|
if (devMode !== "packaged-ui") return;
|
|
432
501
|
this.cliLogger.info(`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.`);
|
|
@@ -712,8 +781,9 @@ var ConsumerOutputBuilder = class ConsumerOutputBuilder {
|
|
|
712
781
|
lastIssuedBuildVersion = 0;
|
|
713
782
|
log;
|
|
714
783
|
buildOptions;
|
|
715
|
-
constructor(consumerRoot, logOverride, buildOptionsOverride) {
|
|
784
|
+
constructor(consumerRoot, logOverride, buildOptionsOverride, configPathOverride) {
|
|
716
785
|
this.consumerRoot = consumerRoot;
|
|
786
|
+
this.configPathOverride = configPathOverride;
|
|
717
787
|
this.log = logOverride ?? defaultConsumerOutputLogger;
|
|
718
788
|
this.buildOptions = buildOptionsOverride ?? defaultConsumerBuildOptions;
|
|
719
789
|
}
|
|
@@ -1132,6 +1202,12 @@ var ConsumerOutputBuilder = class ConsumerOutputBuilder {
|
|
|
1132
1202
|
return `${nextBuildVersion}-${process$1.pid}`;
|
|
1133
1203
|
}
|
|
1134
1204
|
async resolveConfigPath(consumerRoot) {
|
|
1205
|
+
const configuredOverride = this.configPathOverride?.trim();
|
|
1206
|
+
if (configuredOverride && configuredOverride.length > 0) {
|
|
1207
|
+
const resolvedOverride = path.resolve(configuredOverride);
|
|
1208
|
+
if (await this.fileExists(resolvedOverride)) return resolvedOverride;
|
|
1209
|
+
throw new Error(`Codemation config override not found at ${resolvedOverride}.`);
|
|
1210
|
+
}
|
|
1135
1211
|
for (const candidate of this.getConventionCandidates(consumerRoot)) if (await this.fileExists(candidate)) return candidate;
|
|
1136
1212
|
return null;
|
|
1137
1213
|
}
|
|
@@ -1248,7 +1324,7 @@ var ConsumerOutputBuilder = class ConsumerOutputBuilder {
|
|
|
1248
1324
|
//#region src/consumer/ConsumerOutputBuilderFactory.ts
|
|
1249
1325
|
var ConsumerOutputBuilderFactory = class {
|
|
1250
1326
|
create(consumerRoot, args) {
|
|
1251
|
-
return new ConsumerOutputBuilder(consumerRoot, args?.logger, args?.buildOptions);
|
|
1327
|
+
return new ConsumerOutputBuilder(consumerRoot, args?.logger, args?.buildOptions, args?.configPathOverride);
|
|
1252
1328
|
}
|
|
1253
1329
|
};
|
|
1254
1330
|
|
|
@@ -1699,12 +1775,27 @@ var CliDevProxyServer = class {
|
|
|
1699
1775
|
return url.split("?")[0] ?? url;
|
|
1700
1776
|
}
|
|
1701
1777
|
}
|
|
1778
|
+
extractOccupyingPids(listenerDescription) {
|
|
1779
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1780
|
+
const re = /pid=(\d+)/g;
|
|
1781
|
+
let match;
|
|
1782
|
+
while ((match = re.exec(listenerDescription)) !== null) {
|
|
1783
|
+
const pid = Number.parseInt(match[1] ?? "0", 10);
|
|
1784
|
+
if (Number.isFinite(pid) && pid > 0) seen.add(pid);
|
|
1785
|
+
}
|
|
1786
|
+
return [...seen];
|
|
1787
|
+
}
|
|
1702
1788
|
async rejectListenError(error, reject) {
|
|
1703
1789
|
if (error.code !== "EADDRINUSE") {
|
|
1704
1790
|
reject(error);
|
|
1705
1791
|
return;
|
|
1706
1792
|
}
|
|
1707
1793
|
const description = await this.listenPortConflictDescriber.describeLoopbackPort(this.listenPort);
|
|
1794
|
+
const occupyingPids = description !== null ? this.extractOccupyingPids(description) : [];
|
|
1795
|
+
if (occupyingPids.length > 0) {
|
|
1796
|
+
const pidList = occupyingPids.join(", ");
|
|
1797
|
+
process.stderr.write(`[codemation] Dev gateway port ${this.listenPort} is already in use (occupying pid(s): ${pidList}).\n`);
|
|
1798
|
+
}
|
|
1708
1799
|
const baseMessage = `Dev gateway port ${this.listenPort} is already in use on 127.0.0.1.`;
|
|
1709
1800
|
const suffix = description === null ? " Stop the process using that port or change the configured Codemation dev port." : ` Listener: ${description}. Stop that process or change the configured Codemation dev port.`;
|
|
1710
1801
|
reject(new Error(`${baseMessage}${suffix}`, { cause: error instanceof Error ? error : void 0 }));
|
|
@@ -1882,12 +1973,19 @@ var ListenPortConflictDescriber = class {
|
|
|
1882
1973
|
async describeLoopbackPort(port) {
|
|
1883
1974
|
if (!Number.isInteger(port) || port <= 0) return null;
|
|
1884
1975
|
if (this.platform !== "linux" && this.platform !== "darwin") return null;
|
|
1885
|
-
const
|
|
1886
|
-
if (raw === null) return null;
|
|
1887
|
-
const occupants = this.parseLsofOutput(raw);
|
|
1976
|
+
const occupants = await this.resolveLoopbackOccupants(port);
|
|
1888
1977
|
if (occupants.length === 0) return null;
|
|
1889
1978
|
return occupants.map((occupant) => `pid=${occupant.pid} command=${occupant.command} endpoint=${occupant.endpoint}`).join("; ");
|
|
1890
1979
|
}
|
|
1980
|
+
async resolveLoopbackOccupants(port) {
|
|
1981
|
+
const lsofRaw = await this.readLsofOutput(port);
|
|
1982
|
+
const fromLsof = lsofRaw !== null ? this.parseLsofOutput(lsofRaw) : [];
|
|
1983
|
+
if (fromLsof.length > 0) return fromLsof;
|
|
1984
|
+
if (this.platform !== "linux") return [];
|
|
1985
|
+
const ssRaw = await this.readSsOutput(port);
|
|
1986
|
+
if (ssRaw === null) return [];
|
|
1987
|
+
return this.parseSsListenOutput(ssRaw, port);
|
|
1988
|
+
}
|
|
1891
1989
|
async readLsofOutput(port) {
|
|
1892
1990
|
try {
|
|
1893
1991
|
return await new Promise((resolve, reject) => {
|
|
@@ -1933,6 +2031,44 @@ var ListenPortConflictDescriber = class {
|
|
|
1933
2031
|
}
|
|
1934
2032
|
return occupants;
|
|
1935
2033
|
}
|
|
2034
|
+
async readSsOutput(port) {
|
|
2035
|
+
const filtered = await this.execFileStdout("ss", ["-lntp", `sport = :${port}`]);
|
|
2036
|
+
if (filtered !== null && filtered.trim().length > 0) return filtered;
|
|
2037
|
+
return this.execFileStdout("ss", ["-lntp"]);
|
|
2038
|
+
}
|
|
2039
|
+
async execFileStdout(command, args) {
|
|
2040
|
+
try {
|
|
2041
|
+
return await new Promise((resolve, reject) => {
|
|
2042
|
+
execFile(command, [...args], (error, stdout) => {
|
|
2043
|
+
if (error) {
|
|
2044
|
+
reject(error);
|
|
2045
|
+
return;
|
|
2046
|
+
}
|
|
2047
|
+
resolve(stdout);
|
|
2048
|
+
});
|
|
2049
|
+
});
|
|
2050
|
+
} catch {
|
|
2051
|
+
return null;
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
parseSsListenOutput(raw, port) {
|
|
2055
|
+
const occupants = [];
|
|
2056
|
+
const portSuffix = `:${port}`;
|
|
2057
|
+
for (const line of raw.split("\n")) {
|
|
2058
|
+
if (!line.includes("LISTEN") || !line.includes(portSuffix)) continue;
|
|
2059
|
+
const pidMatch = line.match(/pid=(\d+)/);
|
|
2060
|
+
if (!pidMatch) continue;
|
|
2061
|
+
const pid = Number.parseInt(pidMatch[1] ?? "0", 10);
|
|
2062
|
+
const command = line.match(/users:\(\("([^"]*)"/)?.[1] ?? "unknown";
|
|
2063
|
+
const endpoint = line.match(/\s+(\S+:\d+|\[[^\]]+\]:\d+)\s+/)?.[1] ?? `tcp:${String(port)}`;
|
|
2064
|
+
occupants.push({
|
|
2065
|
+
pid,
|
|
2066
|
+
command,
|
|
2067
|
+
endpoint
|
|
2068
|
+
});
|
|
2069
|
+
}
|
|
2070
|
+
return occupants;
|
|
2071
|
+
}
|
|
1936
2072
|
};
|
|
1937
2073
|
|
|
1938
2074
|
//#endregion
|
|
@@ -2387,10 +2523,12 @@ var DevNextHostEnvironmentBuilder = class {
|
|
|
2387
2523
|
this.sourceMapNodeOptions = sourceMapNodeOptions;
|
|
2388
2524
|
}
|
|
2389
2525
|
buildConsumerUiProxy(args) {
|
|
2526
|
+
const publicWebsocketPort = this.resolvePublicWebsocketPort(args.publicBaseUrl, args.websocketPort);
|
|
2390
2527
|
return {
|
|
2391
2528
|
...this.build({
|
|
2392
2529
|
authSecret: args.authSecret,
|
|
2393
2530
|
configPathOverride: args.configPathOverride,
|
|
2531
|
+
consumerOutputManifestPath: args.consumerOutputManifestPath,
|
|
2394
2532
|
consumerRoot: args.consumerRoot,
|
|
2395
2533
|
developmentServerToken: args.developmentServerToken,
|
|
2396
2534
|
nextPort: args.nextPort,
|
|
@@ -2400,15 +2538,21 @@ var DevNextHostEnvironmentBuilder = class {
|
|
|
2400
2538
|
}),
|
|
2401
2539
|
HOSTNAME: "127.0.0.1",
|
|
2402
2540
|
AUTH_SECRET: args.authSecret,
|
|
2403
|
-
AUTH_URL: args.publicBaseUrl
|
|
2541
|
+
AUTH_URL: args.publicBaseUrl,
|
|
2542
|
+
CODEMATION_PUBLIC_WS_PORT: String(publicWebsocketPort),
|
|
2543
|
+
NEXT_PUBLIC_CODEMATION_WS_PORT: String(publicWebsocketPort)
|
|
2404
2544
|
};
|
|
2405
2545
|
}
|
|
2406
2546
|
build(args) {
|
|
2547
|
+
const merged = this.consumerEnvLoader.mergeConsumerRootIntoProcessEnvironment(args.consumerRoot, process$1.env);
|
|
2548
|
+
const consumerOutputManifestPath = args.consumerOutputManifestPath ?? path.resolve(args.consumerRoot, ".codemation", "output", "current.json");
|
|
2407
2549
|
return {
|
|
2408
|
-
...
|
|
2550
|
+
...merged,
|
|
2409
2551
|
PORT: String(args.nextPort),
|
|
2410
2552
|
CODEMATION_CONSUMER_ROOT: args.consumerRoot,
|
|
2553
|
+
CODEMATION_CONSUMER_OUTPUT_MANIFEST_PATH: consumerOutputManifestPath,
|
|
2411
2554
|
CODEMATION_UI_AUTH_ENABLED: String(!args.skipUiAuth),
|
|
2555
|
+
CODEMATION_PUBLIC_WS_PORT: String(args.websocketPort),
|
|
2412
2556
|
CODEMATION_WS_PORT: String(args.websocketPort),
|
|
2413
2557
|
NEXT_PUBLIC_CODEMATION_WS_PORT: String(args.websocketPort),
|
|
2414
2558
|
CODEMATION_DEV_SERVER_TOKEN: args.developmentServerToken,
|
|
@@ -2421,6 +2565,14 @@ var DevNextHostEnvironmentBuilder = class {
|
|
|
2421
2565
|
...args.runtimeDevUrl !== void 0 && args.runtimeDevUrl.trim().length > 0 ? { CODEMATION_RUNTIME_DEV_URL: args.runtimeDevUrl.trim() } : {}
|
|
2422
2566
|
};
|
|
2423
2567
|
}
|
|
2568
|
+
resolvePublicWebsocketPort(publicBaseUrl, fallbackPort) {
|
|
2569
|
+
try {
|
|
2570
|
+
const parsedUrl = new URL(publicBaseUrl);
|
|
2571
|
+
const parsedPort = Number(parsedUrl.port);
|
|
2572
|
+
if (Number.isInteger(parsedPort) && parsedPort > 0) return parsedPort;
|
|
2573
|
+
} catch {}
|
|
2574
|
+
return fallbackPort;
|
|
2575
|
+
}
|
|
2424
2576
|
};
|
|
2425
2577
|
|
|
2426
2578
|
//#endregion
|
|
@@ -3180,8 +3332,10 @@ var CliProgramFactory = class {
|
|
|
3180
3332
|
const userAdminCliOptionsParser = new UserAdminCliOptionsParser();
|
|
3181
3333
|
const databaseMigrationsApplyService = new DatabaseMigrationsApplyService(cliLogger, new UserAdminConsumerDotenvLoader(), tsconfigPreparation, new CodemationConsumerConfigLoader(), new ConsumerDatabaseConnectionResolver(), new CliDatabaseUrlDescriptor(), hostPackageRoot, new PrismaMigrationDeployer());
|
|
3182
3334
|
const buildOptionsParser = new ConsumerBuildOptionsParser();
|
|
3183
|
-
const
|
|
3184
|
-
|
|
3335
|
+
const consumerOutputBuilderFactory = new ConsumerOutputBuilderFactory();
|
|
3336
|
+
const consumerBuildArtifactsPublisher = new ConsumerBuildArtifactsPublisher();
|
|
3337
|
+
const devCommand = new DevCommand(pathResolver, tsRuntime, new DevLockFactory(), new DevSourceWatcherFactory(), cliLogger, devSessionServices, databaseMigrationsApplyService, consumerOutputBuilderFactory, pluginDiscovery, consumerBuildArtifactsPublisher, new DevBootstrapSummaryFetcher(), new DevCliBannerRenderer(), new ConsumerEnvDotenvFilePredicate(), new DevTrackedProcessTreeKiller(), nextHostConsumerServerCommandFactory, new DevApiRuntimeFactory(devSessionServices.loopbackPortAllocator, appConfigLoader, pluginDiscovery), new CliDevProxyServerFactory(), new DevRebuildQueueFactory());
|
|
3338
|
+
return new CliProgram(buildOptionsParser, new BuildCommand(cliLogger, pathResolver, consumerOutputBuilderFactory, pluginDiscovery, consumerBuildArtifactsPublisher, tsRuntime), devCommand, new DevPluginCommand(new PluginDevConfigFactory(), devCommand), new ServeWebCommand(pathResolver, new CodemationConsumerConfigLoader(), tsRuntime, sourceMapNodeOptions, new ConsumerEnvLoader(), new ListenPortResolver(), nextHostConsumerServerCommandFactory), new ServeWorkerCommand(pathResolver, appConfigLoader, new AppContainerFactory()), new DbMigrateCommand(databaseMigrationsApplyService), new UserCreateCommand(new LocalUserCreator(userAdminBootstrap), userAdminCliOptionsParser), new UserListCommand(cliLogger, userAdminBootstrap, new CliDatabaseUrlDescriptor(), userAdminCliOptionsParser));
|
|
3185
3339
|
}
|
|
3186
3340
|
};
|
|
3187
3341
|
|
package/dist/bin.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -223,6 +223,58 @@ interface PersistedRunState {
|
|
|
223
223
|
connectionInvocations?: ReadonlyArray<ConnectionInvocationRecord>;
|
|
224
224
|
}
|
|
225
225
|
//#endregion
|
|
226
|
+
//#region ../core/src/events/runEvents.d.ts
|
|
227
|
+
type RunEvent = Readonly<{
|
|
228
|
+
kind: "runCreated";
|
|
229
|
+
runId: RunId;
|
|
230
|
+
workflowId: WorkflowId;
|
|
231
|
+
parent?: ParentExecutionRef;
|
|
232
|
+
at: string;
|
|
233
|
+
}> | Readonly<{
|
|
234
|
+
kind: "runSaved";
|
|
235
|
+
runId: RunId;
|
|
236
|
+
workflowId: WorkflowId;
|
|
237
|
+
parent?: ParentExecutionRef;
|
|
238
|
+
at: string;
|
|
239
|
+
state: PersistedRunState;
|
|
240
|
+
}> | Readonly<{
|
|
241
|
+
kind: "nodeQueued";
|
|
242
|
+
runId: RunId;
|
|
243
|
+
workflowId: WorkflowId;
|
|
244
|
+
parent?: ParentExecutionRef;
|
|
245
|
+
at: string;
|
|
246
|
+
snapshot: NodeExecutionSnapshot;
|
|
247
|
+
}> | Readonly<{
|
|
248
|
+
kind: "nodeStarted";
|
|
249
|
+
runId: RunId;
|
|
250
|
+
workflowId: WorkflowId;
|
|
251
|
+
parent?: ParentExecutionRef;
|
|
252
|
+
at: string;
|
|
253
|
+
snapshot: NodeExecutionSnapshot;
|
|
254
|
+
}> | Readonly<{
|
|
255
|
+
kind: "nodeCompleted";
|
|
256
|
+
runId: RunId;
|
|
257
|
+
workflowId: WorkflowId;
|
|
258
|
+
parent?: ParentExecutionRef;
|
|
259
|
+
at: string;
|
|
260
|
+
snapshot: NodeExecutionSnapshot;
|
|
261
|
+
}> | Readonly<{
|
|
262
|
+
kind: "nodeFailed";
|
|
263
|
+
runId: RunId;
|
|
264
|
+
workflowId: WorkflowId;
|
|
265
|
+
parent?: ParentExecutionRef;
|
|
266
|
+
at: string;
|
|
267
|
+
snapshot: NodeExecutionSnapshot;
|
|
268
|
+
}>;
|
|
269
|
+
//#endregion
|
|
270
|
+
//#region ../core/src/policies/executionLimits/EngineExecutionLimitsPolicy.d.ts
|
|
271
|
+
interface EngineExecutionLimitsPolicyConfig {
|
|
272
|
+
readonly defaultMaxNodeActivations: number;
|
|
273
|
+
readonly hardMaxNodeActivations: number;
|
|
274
|
+
readonly defaultMaxSubworkflowDepth: number;
|
|
275
|
+
readonly hardMaxSubworkflowDepth: number;
|
|
276
|
+
}
|
|
277
|
+
//#endregion
|
|
226
278
|
//#region ../core/src/contracts/runtimeTypes.d.ts
|
|
227
279
|
interface NodeExecutionStatePublisher {
|
|
228
280
|
markQueued(args: {
|
|
@@ -288,10 +340,10 @@ interface ExecutionContext {
|
|
|
288
340
|
binary: ExecutionBinaryService;
|
|
289
341
|
getCredential<TSession = unknown>(slotKey: string): Promise<TSession>;
|
|
290
342
|
}
|
|
291
|
-
interface NodeExecutionContext<TConfig extends NodeConfigBase = NodeConfigBase> extends ExecutionContext {
|
|
343
|
+
interface NodeExecutionContext<TConfig$1 extends NodeConfigBase = NodeConfigBase> extends ExecutionContext {
|
|
292
344
|
nodeId: NodeId;
|
|
293
345
|
activationId: NodeActivationId;
|
|
294
|
-
config: TConfig;
|
|
346
|
+
config: TConfig$1;
|
|
295
347
|
binary: NodeBinaryAttachmentService;
|
|
296
348
|
}
|
|
297
349
|
//#endregion
|
|
@@ -460,15 +512,15 @@ interface WorkflowErrorContext {
|
|
|
460
512
|
readonly finishedAt: string;
|
|
461
513
|
}
|
|
462
514
|
type WorkflowErrorHandlerSpec = TypeToken<WorkflowErrorHandler> | WorkflowErrorHandler;
|
|
463
|
-
interface NodeErrorHandlerArgs<TConfig extends NodeConfigBase = NodeConfigBase> {
|
|
515
|
+
interface NodeErrorHandlerArgs<TConfig$1 extends NodeConfigBase = NodeConfigBase> {
|
|
464
516
|
readonly kind: "single" | "multi";
|
|
465
517
|
readonly items: Items;
|
|
466
518
|
readonly inputsByPort: Readonly<Record<InputPortKey, Items>> | undefined;
|
|
467
|
-
readonly ctx: NodeExecutionContext<TConfig>;
|
|
519
|
+
readonly ctx: NodeExecutionContext<TConfig$1>;
|
|
468
520
|
readonly error: Error;
|
|
469
521
|
}
|
|
470
522
|
interface NodeErrorHandler {
|
|
471
|
-
handle<TConfig extends NodeConfigBase>(args: NodeErrorHandlerArgs<TConfig>): Promise<NodeOutputs>;
|
|
523
|
+
handle<TConfig$1 extends NodeConfigBase>(args: NodeErrorHandlerArgs<TConfig$1>): Promise<NodeOutputs>;
|
|
472
524
|
}
|
|
473
525
|
type NodeErrorHandlerSpec = TypeToken<NodeErrorHandler> | NodeErrorHandler;
|
|
474
526
|
//#endregion
|
|
@@ -588,58 +640,6 @@ type CredentialType<TPublicConfig extends CredentialJsonRecord = CredentialJsonR
|
|
|
588
640
|
*/
|
|
589
641
|
type AnyCredentialType = CredentialType<any, any, unknown>;
|
|
590
642
|
//#endregion
|
|
591
|
-
//#region ../core/src/events/runEvents.d.ts
|
|
592
|
-
type RunEvent = Readonly<{
|
|
593
|
-
kind: "runCreated";
|
|
594
|
-
runId: RunId;
|
|
595
|
-
workflowId: WorkflowId;
|
|
596
|
-
parent?: ParentExecutionRef;
|
|
597
|
-
at: string;
|
|
598
|
-
}> | Readonly<{
|
|
599
|
-
kind: "runSaved";
|
|
600
|
-
runId: RunId;
|
|
601
|
-
workflowId: WorkflowId;
|
|
602
|
-
parent?: ParentExecutionRef;
|
|
603
|
-
at: string;
|
|
604
|
-
state: PersistedRunState;
|
|
605
|
-
}> | Readonly<{
|
|
606
|
-
kind: "nodeQueued";
|
|
607
|
-
runId: RunId;
|
|
608
|
-
workflowId: WorkflowId;
|
|
609
|
-
parent?: ParentExecutionRef;
|
|
610
|
-
at: string;
|
|
611
|
-
snapshot: NodeExecutionSnapshot;
|
|
612
|
-
}> | Readonly<{
|
|
613
|
-
kind: "nodeStarted";
|
|
614
|
-
runId: RunId;
|
|
615
|
-
workflowId: WorkflowId;
|
|
616
|
-
parent?: ParentExecutionRef;
|
|
617
|
-
at: string;
|
|
618
|
-
snapshot: NodeExecutionSnapshot;
|
|
619
|
-
}> | Readonly<{
|
|
620
|
-
kind: "nodeCompleted";
|
|
621
|
-
runId: RunId;
|
|
622
|
-
workflowId: WorkflowId;
|
|
623
|
-
parent?: ParentExecutionRef;
|
|
624
|
-
at: string;
|
|
625
|
-
snapshot: NodeExecutionSnapshot;
|
|
626
|
-
}> | Readonly<{
|
|
627
|
-
kind: "nodeFailed";
|
|
628
|
-
runId: RunId;
|
|
629
|
-
workflowId: WorkflowId;
|
|
630
|
-
parent?: ParentExecutionRef;
|
|
631
|
-
at: string;
|
|
632
|
-
snapshot: NodeExecutionSnapshot;
|
|
633
|
-
}>;
|
|
634
|
-
//#endregion
|
|
635
|
-
//#region ../core/src/policies/executionLimits/EngineExecutionLimitsPolicy.d.ts
|
|
636
|
-
interface EngineExecutionLimitsPolicyConfig {
|
|
637
|
-
readonly defaultMaxNodeActivations: number;
|
|
638
|
-
readonly hardMaxNodeActivations: number;
|
|
639
|
-
readonly defaultMaxSubworkflowDepth: number;
|
|
640
|
-
readonly hardMaxSubworkflowDepth: number;
|
|
641
|
-
}
|
|
642
|
-
//#endregion
|
|
643
643
|
//#region ../host/src/application/logging/Logger.d.ts
|
|
644
644
|
interface Logger {
|
|
645
645
|
info(message: string, exception?: Error): void;
|
|
@@ -832,6 +832,7 @@ interface CodemationPluginContext extends CodemationRegistrationContextBase {
|
|
|
832
832
|
readonly loggerFactory: LoggerFactory;
|
|
833
833
|
}
|
|
834
834
|
interface CodemationPluginConfig {
|
|
835
|
+
readonly pluginPackageId?: string;
|
|
835
836
|
readonly credentialTypes?: ReadonlyArray<AnyCredentialType>;
|
|
836
837
|
readonly register?: (context: CodemationPluginContext) => void | Promise<void>;
|
|
837
838
|
readonly sandbox?: CodemationConfig;
|
|
@@ -22975,6 +22976,7 @@ type CodemationConsumerConfigResolution = Readonly<{
|
|
|
22975
22976
|
}>;
|
|
22976
22977
|
declare class CodemationConsumerConfigLoader {
|
|
22977
22978
|
private static readonly importerRegistrationsByTsconfig;
|
|
22979
|
+
private static readonly importerNamespaceVersionByTsconfig;
|
|
22978
22980
|
private readonly configExportsResolver;
|
|
22979
22981
|
private readonly configNormalizer;
|
|
22980
22982
|
private readonly workflowModulePathFinder;
|
|
@@ -22996,6 +22998,7 @@ declare class CodemationConsumerConfigLoader {
|
|
|
22996
22998
|
private resolveTsconfigPath;
|
|
22997
22999
|
private getOrCreateImporter;
|
|
22998
23000
|
private resetImporter;
|
|
23001
|
+
private nextNamespaceVersion;
|
|
22999
23002
|
private toNamespace;
|
|
23000
23003
|
private resolveTsxImporterModuleSpecifier;
|
|
23001
23004
|
private findNearestTsconfig;
|
|
@@ -23127,6 +23130,7 @@ type ConsumerOutputBuildSnapshot = Readonly<{
|
|
|
23127
23130
|
}>;
|
|
23128
23131
|
declare class ConsumerOutputBuilder {
|
|
23129
23132
|
private readonly consumerRoot;
|
|
23133
|
+
private readonly configPathOverride?;
|
|
23130
23134
|
private static readonly ignoredDirectoryNames;
|
|
23131
23135
|
private static readonly supportedSourceExtensions;
|
|
23132
23136
|
private static readonly watchBuildDebounceMs;
|
|
@@ -23143,7 +23147,7 @@ declare class ConsumerOutputBuilder {
|
|
|
23143
23147
|
private lastIssuedBuildVersion;
|
|
23144
23148
|
private readonly log;
|
|
23145
23149
|
private readonly buildOptions;
|
|
23146
|
-
constructor(consumerRoot: string, logOverride?: Logger, buildOptionsOverride?: ConsumerBuildOptions);
|
|
23150
|
+
constructor(consumerRoot: string, logOverride?: Logger, buildOptionsOverride?: ConsumerBuildOptions, configPathOverride?: string | undefined);
|
|
23147
23151
|
ensureBuilt(): Promise<ConsumerOutputBuildSnapshot>;
|
|
23148
23152
|
/**
|
|
23149
23153
|
* Stops the chokidar watcher and clears debounce timers. Safe to call when not watching.
|
|
@@ -23208,10 +23212,27 @@ declare class ConsumerOutputBuilder {
|
|
|
23208
23212
|
private fileExists;
|
|
23209
23213
|
}
|
|
23210
23214
|
//#endregion
|
|
23215
|
+
//#region src/build/ConsumerBuildArtifactsPublisher.d.ts
|
|
23216
|
+
type ConsumerBuildManifest = Readonly<{
|
|
23217
|
+
buildVersion: string;
|
|
23218
|
+
consumerRoot: string;
|
|
23219
|
+
entryPath: string;
|
|
23220
|
+
manifestPath: string;
|
|
23221
|
+
pluginEntryPath: string;
|
|
23222
|
+
workflowSourcePaths: ReadonlyArray<string>;
|
|
23223
|
+
}>;
|
|
23224
|
+
declare class ConsumerBuildArtifactsPublisher {
|
|
23225
|
+
publish(snapshot: ConsumerOutputBuildSnapshot, discoveredPlugins: ReadonlyArray<CodemationDiscoveredPluginPackage>): Promise<ConsumerBuildManifest>;
|
|
23226
|
+
private writeDiscoveredPluginsOutput;
|
|
23227
|
+
private writeBuildManifest;
|
|
23228
|
+
private resolvePluginEntry;
|
|
23229
|
+
}
|
|
23230
|
+
//#endregion
|
|
23211
23231
|
//#region src/consumer/ConsumerOutputBuilderFactory.d.ts
|
|
23212
23232
|
declare class ConsumerOutputBuilderFactory {
|
|
23213
23233
|
create(consumerRoot: string, args?: Readonly<{
|
|
23214
23234
|
buildOptions?: ConsumerBuildOptions;
|
|
23235
|
+
configPathOverride?: string;
|
|
23215
23236
|
logger?: Logger;
|
|
23216
23237
|
}>): ConsumerOutputBuilder;
|
|
23217
23238
|
}
|
|
@@ -23237,8 +23258,10 @@ declare class BuildCommand {
|
|
|
23237
23258
|
private readonly cliLogger;
|
|
23238
23259
|
private readonly pathResolver;
|
|
23239
23260
|
private readonly consumerOutputBuilderFactory;
|
|
23261
|
+
private readonly pluginDiscovery;
|
|
23262
|
+
private readonly consumerBuildArtifactsPublisher;
|
|
23240
23263
|
private readonly tsRuntime;
|
|
23241
|
-
constructor(cliLogger: Logger, pathResolver: CliPathResolver, consumerOutputBuilderFactory: ConsumerOutputBuilderFactory, tsRuntime: TypeScriptRuntimeConfigurator);
|
|
23264
|
+
constructor(cliLogger: Logger, pathResolver: CliPathResolver, consumerOutputBuilderFactory: ConsumerOutputBuilderFactory, pluginDiscovery: CodemationPluginDiscovery, consumerBuildArtifactsPublisher: ConsumerBuildArtifactsPublisher, tsRuntime: TypeScriptRuntimeConfigurator);
|
|
23242
23265
|
execute(consumerRoot: string, buildOptions: ConsumerBuildOptions): Promise<void>;
|
|
23243
23266
|
}
|
|
23244
23267
|
//#endregion
|
|
@@ -23368,8 +23391,12 @@ declare class ListenPortConflictDescriber {
|
|
|
23368
23391
|
private readonly platform;
|
|
23369
23392
|
constructor(platform?: NodeJS.Platform);
|
|
23370
23393
|
describeLoopbackPort(port: number): Promise<string | null>;
|
|
23394
|
+
private resolveLoopbackOccupants;
|
|
23371
23395
|
private readLsofOutput;
|
|
23372
23396
|
private parseLsofOutput;
|
|
23397
|
+
private readSsOutput;
|
|
23398
|
+
private execFileStdout;
|
|
23399
|
+
private parseSsListenOutput;
|
|
23373
23400
|
}
|
|
23374
23401
|
//#endregion
|
|
23375
23402
|
//#region src/dev/CliDevProxyServer.d.ts
|
|
@@ -23407,6 +23434,7 @@ declare class CliDevProxyServer {
|
|
|
23407
23434
|
private handleHttpRequest;
|
|
23408
23435
|
private handleUpgrade;
|
|
23409
23436
|
private safePathname;
|
|
23437
|
+
private extractOccupyingPids;
|
|
23410
23438
|
private rejectListenError;
|
|
23411
23439
|
private broadcastDev;
|
|
23412
23440
|
private broadcastWorkflowLifecycleToSubscribedRooms;
|
|
@@ -23529,6 +23557,7 @@ declare class DevNextHostEnvironmentBuilder {
|
|
|
23529
23557
|
buildConsumerUiProxy(args: Readonly<{
|
|
23530
23558
|
authSecret: string;
|
|
23531
23559
|
configPathOverride?: string;
|
|
23560
|
+
consumerOutputManifestPath?: string;
|
|
23532
23561
|
consumerRoot: string;
|
|
23533
23562
|
developmentServerToken: string;
|
|
23534
23563
|
nextPort: number;
|
|
@@ -23540,6 +23569,7 @@ declare class DevNextHostEnvironmentBuilder {
|
|
|
23540
23569
|
build(args: Readonly<{
|
|
23541
23570
|
authSecret?: string;
|
|
23542
23571
|
configPathOverride?: string;
|
|
23572
|
+
consumerOutputManifestPath?: string;
|
|
23543
23573
|
consumerRoot: string;
|
|
23544
23574
|
developmentServerToken: string;
|
|
23545
23575
|
nextPort: number;
|
|
@@ -23547,6 +23577,7 @@ declare class DevNextHostEnvironmentBuilder {
|
|
|
23547
23577
|
websocketPort: number;
|
|
23548
23578
|
runtimeDevUrl?: string;
|
|
23549
23579
|
}>): NodeJS.ProcessEnv;
|
|
23580
|
+
private resolvePublicWebsocketPort;
|
|
23550
23581
|
}
|
|
23551
23582
|
//#endregion
|
|
23552
23583
|
//#region src/runtime/ListenPortResolver.d.ts
|
|
@@ -23719,6 +23750,9 @@ declare class DevCommand {
|
|
|
23719
23750
|
private readonly cliLogger;
|
|
23720
23751
|
private readonly session;
|
|
23721
23752
|
private readonly databaseMigrationsApplyService;
|
|
23753
|
+
private readonly consumerOutputBuilderFactory;
|
|
23754
|
+
private readonly pluginDiscovery;
|
|
23755
|
+
private readonly consumerBuildArtifactsPublisher;
|
|
23722
23756
|
private readonly devBootstrapSummaryFetcher;
|
|
23723
23757
|
private readonly devCliBannerRenderer;
|
|
23724
23758
|
private readonly consumerEnvDotenvFilePredicate;
|
|
@@ -23728,7 +23762,7 @@ declare class DevCommand {
|
|
|
23728
23762
|
private readonly cliDevProxyServerFactory;
|
|
23729
23763
|
private readonly devRebuildQueueFactory;
|
|
23730
23764
|
private readonly require;
|
|
23731
|
-
constructor(pathResolver: CliPathResolver, tsRuntime: TypeScriptRuntimeConfigurator, devLockFactory: DevLockFactory, devSourceWatcherFactory: DevSourceWatcherFactory, cliLogger: Logger, session: DevSessionServices, databaseMigrationsApplyService: DatabaseMigrationsApplyService, devBootstrapSummaryFetcher: DevBootstrapSummaryFetcher, devCliBannerRenderer: DevCliBannerRenderer, consumerEnvDotenvFilePredicate: ConsumerEnvDotenvFilePredicate, devTrackedProcessTreeKiller: DevTrackedProcessTreeKiller, nextHostConsumerServerCommandFactory: NextHostConsumerServerCommandFactory, devApiRuntimeFactory: DevApiRuntimeFactory, cliDevProxyServerFactory: CliDevProxyServerFactory, devRebuildQueueFactory: DevRebuildQueueFactory);
|
|
23765
|
+
constructor(pathResolver: CliPathResolver, tsRuntime: TypeScriptRuntimeConfigurator, devLockFactory: DevLockFactory, devSourceWatcherFactory: DevSourceWatcherFactory, cliLogger: Logger, session: DevSessionServices, databaseMigrationsApplyService: DatabaseMigrationsApplyService, consumerOutputBuilderFactory: ConsumerOutputBuilderFactory, pluginDiscovery: CodemationPluginDiscovery, consumerBuildArtifactsPublisher: ConsumerBuildArtifactsPublisher, devBootstrapSummaryFetcher: DevBootstrapSummaryFetcher, devCliBannerRenderer: DevCliBannerRenderer, consumerEnvDotenvFilePredicate: ConsumerEnvDotenvFilePredicate, devTrackedProcessTreeKiller: DevTrackedProcessTreeKiller, nextHostConsumerServerCommandFactory: NextHostConsumerServerCommandFactory, devApiRuntimeFactory: DevApiRuntimeFactory, cliDevProxyServerFactory: CliDevProxyServerFactory, devRebuildQueueFactory: DevRebuildQueueFactory);
|
|
23732
23766
|
execute(args: Readonly<{
|
|
23733
23767
|
consumerRoot: string;
|
|
23734
23768
|
watchFramework?: boolean;
|
|
@@ -23758,6 +23792,7 @@ declare class DevCommand {
|
|
|
23758
23792
|
private stopLiveProcesses;
|
|
23759
23793
|
private stopCurrentRuntime;
|
|
23760
23794
|
private createRuntime;
|
|
23795
|
+
private publishConsumerArtifacts;
|
|
23761
23796
|
private logPackagedUiDevHintWhenNeeded;
|
|
23762
23797
|
}
|
|
23763
23798
|
//#endregion
|
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-
|
|
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.
|
|
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.
|
|
41
|
-
"@codemation/next-host": "0.0.
|
|
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.
|
|
53
|
+
"@codemation/eslint-config": "0.0.1"
|
|
53
54
|
},
|
|
54
55
|
"peerDependencies": {
|
|
55
56
|
"tsx": ">=4.0.0"
|
package/src/CliProgramFactory.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { AppContainerFactory } from "@codemation/host";
|
|
|
3
3
|
import { logLevelPolicyFactory, ServerLoggerFactory } from "@codemation/host/next/server";
|
|
4
4
|
|
|
5
5
|
import { ConsumerBuildOptionsParser } from "./build/ConsumerBuildOptionsParser";
|
|
6
|
+
import { ConsumerBuildArtifactsPublisher } from "./build/ConsumerBuildArtifactsPublisher";
|
|
6
7
|
import { BuildCommand } from "./commands/BuildCommand";
|
|
7
8
|
import { DbMigrateCommand } from "./commands/DbMigrateCommand";
|
|
8
9
|
import { DevCommand } from "./commands/DevCommand";
|
|
@@ -78,6 +79,8 @@ export class CliProgramFactory {
|
|
|
78
79
|
);
|
|
79
80
|
|
|
80
81
|
const buildOptionsParser = new ConsumerBuildOptionsParser();
|
|
82
|
+
const consumerOutputBuilderFactory = new ConsumerOutputBuilderFactory();
|
|
83
|
+
const consumerBuildArtifactsPublisher = new ConsumerBuildArtifactsPublisher();
|
|
81
84
|
const devCommand = new DevCommand(
|
|
82
85
|
pathResolver,
|
|
83
86
|
tsRuntime,
|
|
@@ -86,6 +89,9 @@ export class CliProgramFactory {
|
|
|
86
89
|
cliLogger,
|
|
87
90
|
devSessionServices,
|
|
88
91
|
databaseMigrationsApplyService,
|
|
92
|
+
consumerOutputBuilderFactory,
|
|
93
|
+
pluginDiscovery,
|
|
94
|
+
consumerBuildArtifactsPublisher,
|
|
89
95
|
new DevBootstrapSummaryFetcher(),
|
|
90
96
|
new DevCliBannerRenderer(),
|
|
91
97
|
new ConsumerEnvDotenvFilePredicate(),
|
|
@@ -97,7 +103,14 @@ export class CliProgramFactory {
|
|
|
97
103
|
);
|
|
98
104
|
return new CliProgram(
|
|
99
105
|
buildOptionsParser,
|
|
100
|
-
new BuildCommand(
|
|
106
|
+
new BuildCommand(
|
|
107
|
+
cliLogger,
|
|
108
|
+
pathResolver,
|
|
109
|
+
consumerOutputBuilderFactory,
|
|
110
|
+
pluginDiscovery,
|
|
111
|
+
consumerBuildArtifactsPublisher,
|
|
112
|
+
tsRuntime,
|
|
113
|
+
),
|
|
101
114
|
devCommand,
|
|
102
115
|
new DevPluginCommand(new PluginDevConfigFactory(), devCommand),
|
|
103
116
|
new ServeWebCommand(
|
|
@@ -0,0 +1,82 @@
|
|
|
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 pluginModulePath = path.resolve(discoveredPlugin.packageRoot, this.resolvePluginEntry(discoveredPlugin));
|
|
36
|
+
const pluginFileUrl = pathToFileURL(pluginModulePath).href;
|
|
37
|
+
outputLines.push(`const pluginModule${index} = await import(${JSON.stringify(pluginFileUrl)});`);
|
|
38
|
+
outputLines.push(
|
|
39
|
+
`const pluginValue${index} = pluginModule${index}.default ?? pluginModule${index}.codemationPlugin;`,
|
|
40
|
+
);
|
|
41
|
+
outputLines.push(`if (pluginValue${index} && typeof pluginValue${index}.register === "function") {`);
|
|
42
|
+
outputLines.push(` codemationDiscoveredPlugins.push(pluginValue${index});`);
|
|
43
|
+
outputLines.push(
|
|
44
|
+
`} else if (typeof pluginValue${index} === "function" && pluginValue${index}.prototype && typeof pluginValue${index}.prototype.register === "function") {`,
|
|
45
|
+
);
|
|
46
|
+
outputLines.push(` codemationDiscoveredPlugins.push(new pluginValue${index}());`);
|
|
47
|
+
outputLines.push("}");
|
|
48
|
+
outputLines.push("");
|
|
49
|
+
});
|
|
50
|
+
outputLines.push("export { codemationDiscoveredPlugins };");
|
|
51
|
+
outputLines.push("export default codemationDiscoveredPlugins;");
|
|
52
|
+
outputLines.push("");
|
|
53
|
+
await writeFile(outputPath, outputLines.join("\n"), "utf8");
|
|
54
|
+
return outputPath;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private async writeBuildManifest(
|
|
58
|
+
snapshot: ConsumerOutputBuildSnapshot,
|
|
59
|
+
pluginEntryPath: string,
|
|
60
|
+
): Promise<ConsumerBuildManifest> {
|
|
61
|
+
const manifest: ConsumerBuildManifest = {
|
|
62
|
+
buildVersion: snapshot.buildVersion,
|
|
63
|
+
consumerRoot: snapshot.consumerRoot,
|
|
64
|
+
entryPath: snapshot.outputEntryPath,
|
|
65
|
+
manifestPath: snapshot.manifestPath,
|
|
66
|
+
pluginEntryPath,
|
|
67
|
+
workflowSourcePaths: snapshot.workflowSourcePaths,
|
|
68
|
+
};
|
|
69
|
+
await mkdir(path.dirname(snapshot.manifestPath), { recursive: true });
|
|
70
|
+
const temporaryManifestPath = `${snapshot.manifestPath}.${snapshot.buildVersion}.${randomUUID()}.tmp`;
|
|
71
|
+
await writeFile(temporaryManifestPath, JSON.stringify(manifest, null, 2), "utf8");
|
|
72
|
+
await rename(temporaryManifestPath, snapshot.manifestPath);
|
|
73
|
+
return manifest;
|
|
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
|
+
}
|
|
82
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import type { CodemationPluginDiscovery } from "@codemation/host/server";
|
|
1
2
|
import type { Logger } from "@codemation/host/next/server";
|
|
2
3
|
|
|
4
|
+
import type { ConsumerBuildArtifactsPublisher } from "../build/ConsumerBuildArtifactsPublisher";
|
|
3
5
|
import type { ConsumerOutputBuilderFactory } from "../consumer/ConsumerOutputBuilderFactory";
|
|
4
6
|
import type { ConsumerBuildOptions } from "../consumer/consumerBuildOptions.types";
|
|
5
7
|
import { CliPathResolver } from "../path/CliPathResolver";
|
|
@@ -10,6 +12,8 @@ export class BuildCommand {
|
|
|
10
12
|
private readonly cliLogger: Logger,
|
|
11
13
|
private readonly pathResolver: CliPathResolver,
|
|
12
14
|
private readonly consumerOutputBuilderFactory: ConsumerOutputBuilderFactory,
|
|
15
|
+
private readonly pluginDiscovery: CodemationPluginDiscovery,
|
|
16
|
+
private readonly consumerBuildArtifactsPublisher: ConsumerBuildArtifactsPublisher,
|
|
13
17
|
private readonly tsRuntime: TypeScriptRuntimeConfigurator,
|
|
14
18
|
) {}
|
|
15
19
|
|
|
@@ -18,7 +22,10 @@ export class BuildCommand {
|
|
|
18
22
|
this.tsRuntime.configure(paths.repoRoot);
|
|
19
23
|
const builder = this.consumerOutputBuilderFactory.create(paths.consumerRoot, { buildOptions });
|
|
20
24
|
const snapshot = await builder.ensureBuilt();
|
|
25
|
+
const discoveredPlugins = await this.pluginDiscovery.discover(paths.consumerRoot);
|
|
26
|
+
const manifest = await this.consumerBuildArtifactsPublisher.publish(snapshot, discoveredPlugins);
|
|
21
27
|
this.cliLogger.info(`Built consumer output: ${snapshot.outputEntryPath}`);
|
|
28
|
+
this.cliLogger.info(`Build manifest: ${manifest.manifestPath}`);
|
|
22
29
|
this.cliLogger.info(`Workflow modules emitted: ${snapshot.workflowSourcePaths.length}`);
|
|
23
30
|
}
|
|
24
31
|
}
|
|
@@ -1,9 +1,12 @@
|
|
|
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";
|
|
@@ -35,6 +38,9 @@ export class DevCommand {
|
|
|
35
38
|
private readonly cliLogger: Logger,
|
|
36
39
|
private readonly session: DevSessionServices,
|
|
37
40
|
private readonly databaseMigrationsApplyService: DatabaseMigrationsApplyService,
|
|
41
|
+
private readonly consumerOutputBuilderFactory: ConsumerOutputBuilderFactory,
|
|
42
|
+
private readonly pluginDiscovery: CodemationPluginDiscovery,
|
|
43
|
+
private readonly consumerBuildArtifactsPublisher: ConsumerBuildArtifactsPublisher,
|
|
38
44
|
private readonly devBootstrapSummaryFetcher: DevBootstrapSummaryFetcher,
|
|
39
45
|
private readonly devCliBannerRenderer: DevCliBannerRenderer,
|
|
40
46
|
private readonly consumerEnvDotenvFilePredicate: ConsumerEnvDotenvFilePredicate,
|
|
@@ -55,6 +61,7 @@ export class DevCommand {
|
|
|
55
61
|
): Promise<void> {
|
|
56
62
|
const paths = await this.pathResolver.resolve(args.consumerRoot);
|
|
57
63
|
const commandName = args.commandName ?? "dev";
|
|
64
|
+
const previousDevelopmentServerToken = process.env.CODEMATION_DEV_SERVER_TOKEN;
|
|
58
65
|
this.devCliBannerRenderer.renderBrandHeader();
|
|
59
66
|
this.tsRuntime.configure(paths.repoRoot);
|
|
60
67
|
await this.databaseMigrationsApplyService.applyForConsumer(paths.consumerRoot, {
|
|
@@ -86,6 +93,12 @@ export class DevCommand {
|
|
|
86
93
|
authSettings,
|
|
87
94
|
args.configPathOverride,
|
|
88
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;
|
|
89
102
|
const stopPromise = this.wireStopPromise(processState);
|
|
90
103
|
const uiProxyBase = await this.preparePackagedUiBaseUrlWhenNeeded(prepared, processState);
|
|
91
104
|
proxyServer = await this.startProxyServer(prepared.gatewayPort, uiProxyBase);
|
|
@@ -106,6 +119,11 @@ export class DevCommand {
|
|
|
106
119
|
this.logPackagedUiDevHintWhenNeeded(devMode, gatewayPort, commandName);
|
|
107
120
|
await stopPromise;
|
|
108
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
|
+
}
|
|
109
127
|
processState.stopRequested = true;
|
|
110
128
|
await this.stopLiveProcesses(processState, proxyServer);
|
|
111
129
|
await watcher.stop();
|
|
@@ -416,6 +434,9 @@ export class DevCommand {
|
|
|
416
434
|
proxyServer.setBuildStatus("building");
|
|
417
435
|
proxyServer.broadcastBuildStarted();
|
|
418
436
|
try {
|
|
437
|
+
if (prepared.devMode === "packaged-ui") {
|
|
438
|
+
await this.publishConsumerArtifacts(prepared.paths, request.configPathOverride);
|
|
439
|
+
}
|
|
419
440
|
await this.stopCurrentRuntime(state, proxyServer);
|
|
420
441
|
process.stdout.write("[codemation] Waiting for runtime to accept traffic…\n");
|
|
421
442
|
const runtime = await this.createRuntime(prepared);
|
|
@@ -424,15 +445,18 @@ export class DevCommand {
|
|
|
424
445
|
httpPort: runtime.httpPort,
|
|
425
446
|
workflowWebSocketPort: runtime.workflowWebSocketPort,
|
|
426
447
|
});
|
|
427
|
-
if (request.shouldRestartUi) {
|
|
428
|
-
await this.restartUiAfterSourceChange(prepared, state, gatewayBaseUrl);
|
|
429
|
-
}
|
|
430
448
|
await this.session.devHttpProbe.waitUntilBootstrapSummaryReady(gatewayBaseUrl);
|
|
431
449
|
const json = await this.devBootstrapSummaryFetcher.fetch(gatewayBaseUrl);
|
|
432
450
|
if (json) {
|
|
433
451
|
this.devCliBannerRenderer.renderCompact(json);
|
|
434
452
|
}
|
|
435
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
|
+
}
|
|
436
460
|
proxyServer.broadcastBuildCompleted(runtime.buildVersion);
|
|
437
461
|
process.stdout.write("[codemation] Runtime ready.\n");
|
|
438
462
|
} catch (error) {
|
|
@@ -558,6 +582,16 @@ export class DevCommand {
|
|
|
558
582
|
});
|
|
559
583
|
}
|
|
560
584
|
|
|
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
|
+
|
|
561
595
|
private logPackagedUiDevHintWhenNeeded(
|
|
562
596
|
devMode: DevMode,
|
|
563
597
|
gatewayPort: number,
|
|
@@ -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;
|
|
@@ -716,6 +717,14 @@ export class ConsumerOutputBuilder {
|
|
|
716
717
|
}
|
|
717
718
|
|
|
718
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
|
+
}
|
|
719
728
|
for (const candidate of this.getConventionCandidates(consumerRoot)) {
|
|
720
729
|
if (await this.fileExists(candidate)) {
|
|
721
730
|
return candidate;
|
|
@@ -8,9 +8,10 @@ export class ConsumerOutputBuilderFactory {
|
|
|
8
8
|
consumerRoot: string,
|
|
9
9
|
args?: Readonly<{
|
|
10
10
|
buildOptions?: ConsumerBuildOptions;
|
|
11
|
+
configPathOverride?: string;
|
|
11
12
|
logger?: Logger;
|
|
12
13
|
}>,
|
|
13
14
|
): ConsumerOutputBuilder {
|
|
14
|
-
return new ConsumerOutputBuilder(consumerRoot, args?.logger, args?.buildOptions);
|
|
15
|
+
return new ConsumerOutputBuilder(consumerRoot, args?.logger, args?.buildOptions, args?.configPathOverride);
|
|
15
16
|
}
|
|
16
17
|
}
|
|
@@ -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,3 +1,4 @@
|
|
|
1
|
+
import path from "node:path";
|
|
1
2
|
import process from "node:process";
|
|
2
3
|
|
|
3
4
|
import { ConsumerEnvLoader } from "../consumer/ConsumerEnvLoader";
|
|
@@ -13,6 +14,7 @@ export class DevNextHostEnvironmentBuilder {
|
|
|
13
14
|
args: Readonly<{
|
|
14
15
|
authSecret: string;
|
|
15
16
|
configPathOverride?: string;
|
|
17
|
+
consumerOutputManifestPath?: string;
|
|
16
18
|
consumerRoot: string;
|
|
17
19
|
developmentServerToken: string;
|
|
18
20
|
nextPort: number;
|
|
@@ -22,10 +24,12 @@ export class DevNextHostEnvironmentBuilder {
|
|
|
22
24
|
websocketPort: number;
|
|
23
25
|
}>,
|
|
24
26
|
): NodeJS.ProcessEnv {
|
|
27
|
+
const publicWebsocketPort = this.resolvePublicWebsocketPort(args.publicBaseUrl, args.websocketPort);
|
|
25
28
|
return {
|
|
26
29
|
...this.build({
|
|
27
30
|
authSecret: args.authSecret,
|
|
28
31
|
configPathOverride: args.configPathOverride,
|
|
32
|
+
consumerOutputManifestPath: args.consumerOutputManifestPath,
|
|
29
33
|
consumerRoot: args.consumerRoot,
|
|
30
34
|
developmentServerToken: args.developmentServerToken,
|
|
31
35
|
nextPort: args.nextPort,
|
|
@@ -38,6 +42,8 @@ export class DevNextHostEnvironmentBuilder {
|
|
|
38
42
|
HOSTNAME: "127.0.0.1",
|
|
39
43
|
AUTH_SECRET: args.authSecret,
|
|
40
44
|
AUTH_URL: args.publicBaseUrl,
|
|
45
|
+
CODEMATION_PUBLIC_WS_PORT: String(publicWebsocketPort),
|
|
46
|
+
NEXT_PUBLIC_CODEMATION_WS_PORT: String(publicWebsocketPort),
|
|
41
47
|
};
|
|
42
48
|
}
|
|
43
49
|
|
|
@@ -45,6 +51,7 @@ export class DevNextHostEnvironmentBuilder {
|
|
|
45
51
|
args: Readonly<{
|
|
46
52
|
authSecret?: string;
|
|
47
53
|
configPathOverride?: string;
|
|
54
|
+
consumerOutputManifestPath?: string;
|
|
48
55
|
consumerRoot: string;
|
|
49
56
|
developmentServerToken: string;
|
|
50
57
|
nextPort: number;
|
|
@@ -54,11 +61,15 @@ export class DevNextHostEnvironmentBuilder {
|
|
|
54
61
|
}>,
|
|
55
62
|
): NodeJS.ProcessEnv {
|
|
56
63
|
const merged = this.consumerEnvLoader.mergeConsumerRootIntoProcessEnvironment(args.consumerRoot, process.env);
|
|
64
|
+
const consumerOutputManifestPath =
|
|
65
|
+
args.consumerOutputManifestPath ?? path.resolve(args.consumerRoot, ".codemation", "output", "current.json");
|
|
57
66
|
return {
|
|
58
67
|
...merged,
|
|
59
68
|
PORT: String(args.nextPort),
|
|
60
69
|
CODEMATION_CONSUMER_ROOT: args.consumerRoot,
|
|
70
|
+
CODEMATION_CONSUMER_OUTPUT_MANIFEST_PATH: consumerOutputManifestPath,
|
|
61
71
|
CODEMATION_UI_AUTH_ENABLED: String(!args.skipUiAuth),
|
|
72
|
+
CODEMATION_PUBLIC_WS_PORT: String(args.websocketPort),
|
|
62
73
|
CODEMATION_WS_PORT: String(args.websocketPort),
|
|
63
74
|
NEXT_PUBLIC_CODEMATION_WS_PORT: String(args.websocketPort),
|
|
64
75
|
CODEMATION_DEV_SERVER_TOKEN: args.developmentServerToken,
|
|
@@ -75,4 +86,17 @@ export class DevNextHostEnvironmentBuilder {
|
|
|
75
86
|
: {}),
|
|
76
87
|
};
|
|
77
88
|
}
|
|
89
|
+
|
|
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.
|
|
99
|
+
}
|
|
100
|
+
return fallbackPort;
|
|
101
|
+
}
|
|
78
102
|
}
|
|
@@ -18,11 +18,7 @@ export class ListenPortConflictDescriber {
|
|
|
18
18
|
return null;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
const
|
|
22
|
-
if (raw === null) {
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
const occupants = this.parseLsofOutput(raw);
|
|
21
|
+
const occupants = await this.resolveLoopbackOccupants(port);
|
|
26
22
|
if (occupants.length === 0) {
|
|
27
23
|
return null;
|
|
28
24
|
}
|
|
@@ -32,6 +28,22 @@ export class ListenPortConflictDescriber {
|
|
|
32
28
|
.join("; ");
|
|
33
29
|
}
|
|
34
30
|
|
|
31
|
+
private async resolveLoopbackOccupants(port: number): Promise<ReadonlyArray<PortOccupant>> {
|
|
32
|
+
const lsofRaw = await this.readLsofOutput(port);
|
|
33
|
+
const fromLsof = lsofRaw !== null ? this.parseLsofOutput(lsofRaw) : [];
|
|
34
|
+
if (fromLsof.length > 0) {
|
|
35
|
+
return fromLsof;
|
|
36
|
+
}
|
|
37
|
+
if (this.platform !== "linux") {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
const ssRaw = await this.readSsOutput(port);
|
|
41
|
+
if (ssRaw === null) {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
return this.parseSsListenOutput(ssRaw, port);
|
|
45
|
+
}
|
|
46
|
+
|
|
35
47
|
private async readLsofOutput(port: number): Promise<string | null> {
|
|
36
48
|
try {
|
|
37
49
|
return await new Promise<string>((resolve, reject) => {
|
|
@@ -80,4 +92,53 @@ export class ListenPortConflictDescriber {
|
|
|
80
92
|
|
|
81
93
|
return occupants;
|
|
82
94
|
}
|
|
95
|
+
|
|
96
|
+
private async readSsOutput(port: number): Promise<string | null> {
|
|
97
|
+
const filtered = await this.execFileStdout("ss", ["-lntp", `sport = :${port}`]);
|
|
98
|
+
if (filtered !== null && filtered.trim().length > 0) {
|
|
99
|
+
return filtered;
|
|
100
|
+
}
|
|
101
|
+
return this.execFileStdout("ss", ["-lntp"]);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private async execFileStdout(command: string, args: readonly string[]): Promise<string | null> {
|
|
105
|
+
try {
|
|
106
|
+
return await new Promise<string>((resolve, reject) => {
|
|
107
|
+
execFile(command, [...args], (error, stdout) => {
|
|
108
|
+
if (error) {
|
|
109
|
+
reject(error);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
resolve(stdout);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
} catch {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private parseSsListenOutput(raw: string, port: number): ReadonlyArray<PortOccupant> {
|
|
121
|
+
const occupants: PortOccupant[] = [];
|
|
122
|
+
const portSuffix = `:${port}`;
|
|
123
|
+
for (const line of raw.split("\n")) {
|
|
124
|
+
if (!line.includes("LISTEN") || !line.includes(portSuffix)) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const pidMatch = line.match(/pid=(\d+)/);
|
|
128
|
+
if (!pidMatch) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
const pid = Number.parseInt(pidMatch[1] ?? "0", 10);
|
|
132
|
+
const cmdMatch = line.match(/users:\(\("([^"]*)"/);
|
|
133
|
+
const command = cmdMatch?.[1] ?? "unknown";
|
|
134
|
+
const localAddrMatch = line.match(/\s+(\S+:\d+|\[[^\]]+\]:\d+)\s+/);
|
|
135
|
+
const endpoint = localAddrMatch?.[1] ?? `tcp:${String(port)}`;
|
|
136
|
+
occupants.push({
|
|
137
|
+
pid,
|
|
138
|
+
command,
|
|
139
|
+
endpoint,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return occupants;
|
|
143
|
+
}
|
|
83
144
|
}
|