c64bridge 1.0.3 → 1.0.5
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 +12 -12
- package/data/embeddings_basic.json +1 -1
- package/data/embeddings_hardware.json +1 -1
- package/data/embeddings_mixed.json +1 -1
- package/data/embeddings_other.json +1 -1
- package/data/graphics/character-set.csv +64 -64
- package/dist/.tsbuildinfo +1 -1
- package/dist/c64Client.js +68 -61
- package/dist/config.js +75 -38
- package/dist/device.js +154 -120
- package/dist/knowledge.js +2 -1
- package/dist/petscii.js +18 -15
- package/dist/petsciiArt.js +40 -14
- package/dist/rag/indexer.js +2 -1
- package/dist/sidwave.js +1 -1
- package/dist/sidwaveCompiler.js +3 -9
- package/dist/tools/audio.js +34 -8
- package/dist/tools/debug.js +9 -9
- package/dist/tools/errors.js +12 -1
- package/dist/tools/hex.js +9 -0
- package/dist/tools/input.js +13 -7
- package/dist/tools/memory.js +5 -4
- package/dist/tools/meta/artifacts.js +8 -3
- package/dist/tools/meta/audio.js +25 -28
- package/dist/tools/meta/background.js +6 -1
- package/dist/tools/meta/config.js +12 -4
- package/dist/tools/meta/configInventory.js +4 -1
- package/dist/tools/meta/filesystem.js +37 -10
- package/dist/tools/meta/graphics.js +10 -15
- package/dist/tools/meta/memory.js +12 -18
- package/dist/tools/meta/program.js +44 -16
- package/dist/tools/pollValidator.js +30 -2
- package/dist/tools/programRunners.js +5 -4
- package/dist/tools/registry/batch.js +4 -2
- package/dist/tools/registry/config.js +1 -1
- package/dist/tools/registry/disk.js +5 -0
- package/dist/tools/registry/extract.js +3 -0
- package/dist/tools/registry/graphics.js +1 -0
- package/dist/tools/registry/program.js +1 -1
- package/dist/tools/registry/sound.js +3 -3
- package/dist/tools/registry/system.js +3 -3
- package/dist/tools/storage.js +2 -2
- package/dist/tools/translation/assembler.js +150 -47
- package/dist/tools/translation/basicTokenizer.js +39 -9
- package/dist/tools/types.js +11 -4
- package/dist/vicBitmap.js +16 -0
- package/dist/vice/mockServer.js +7 -3
- package/dist/vice/viceClient.js +16 -6
- package/mcp/schemas/c64_config.schema.json +2 -2
- package/mcp/schemas/c64_program.schema.json +10 -18
- package/mcp/schemas/c64_sound.schema.json +8 -6
- package/mcp/schemas/c64_system.schema.json +9 -7
- package/mcp/server.json +1 -1
- package/mcp/tools.json +58 -43
- package/mcp.json +1 -1
- package/package.json +1 -1
- package/scripts/mockC64Server.mjs +98 -19
- package/server.json +2 -2
package/dist/c64Client.js
CHANGED
|
@@ -11,7 +11,8 @@ import { createSocket } from "node:dgram";
|
|
|
11
11
|
import { networkInterfaces } from "node:os";
|
|
12
12
|
import axios from "axios";
|
|
13
13
|
import { basicToPrg } from "./tools/translation/basicTokenizer.js";
|
|
14
|
-
import { assemblyToPrg } from "./tools/translation/assembler.js";
|
|
14
|
+
import { assemblyToPrg, prgEntryAddress } from "./tools/translation/assembler.js";
|
|
15
|
+
import { stripHexByteSeparators } from "./tools/hex.js";
|
|
15
16
|
import { petsciiToAscii, screenCodesToAscii } from "./petscii.js";
|
|
16
17
|
import { resolveAddressSymbol } from "./knowledge.js";
|
|
17
18
|
import { createAllFacades, createFacade, } from "./device.js";
|
|
@@ -302,9 +303,6 @@ export class C64Client {
|
|
|
302
303
|
}
|
|
303
304
|
writeDiagnosticEvent("client_switch_backend", { backend: type });
|
|
304
305
|
}
|
|
305
|
-
async shouldUseC64uMockBypass() {
|
|
306
|
-
return process.env.C64_TEST_TARGET === "mock" && (await this.getBackendType()) === "c64u";
|
|
307
|
-
}
|
|
308
306
|
async withViceMonitor(fn) {
|
|
309
307
|
const backend = await this.requireViceBackend();
|
|
310
308
|
return backend.withMonitor(fn);
|
|
@@ -546,7 +544,8 @@ export class C64Client {
|
|
|
546
544
|
* tokens, and setting BASIC's TXTTAB/VARTAB/ARYTAB/STREND to the code
|
|
547
545
|
* range corrupts the workspace. Instead this loads the assembled bytes
|
|
548
546
|
* without running them, then types `SYS <entry>` to transfer control
|
|
549
|
-
*
|
|
547
|
+
* to the entry point: the target of a leading SYS stub at $0801, or else
|
|
548
|
+
* the lowest assembled address.
|
|
550
549
|
*/
|
|
551
550
|
async uploadAndRunAsm(program) {
|
|
552
551
|
try {
|
|
@@ -554,7 +553,16 @@ export class C64Client {
|
|
|
554
553
|
if (prg.length < 2) {
|
|
555
554
|
return { success: false, details: { message: "Assembled program produced no output" } };
|
|
556
555
|
}
|
|
557
|
-
const entryAddress = prg
|
|
556
|
+
const entryAddress = prgEntryAddress(prg);
|
|
557
|
+
if (entryAddress === 0x0801) {
|
|
558
|
+
// Loading makes BASIC relink $0801 as program lines, which overwrites raw code there.
|
|
559
|
+
return {
|
|
560
|
+
success: false,
|
|
561
|
+
details: {
|
|
562
|
+
message: "Machine code at $0801 is overwritten when BASIC relinks the loaded program. Omit the origin to get a BASIC SYS stub automatically, start the code with a BASIC SYS line, or assemble at another address such as $C000.",
|
|
563
|
+
},
|
|
564
|
+
};
|
|
565
|
+
}
|
|
558
566
|
const facade = await this.facadePromise;
|
|
559
567
|
const loadResult = await facade.loadPrg(prg);
|
|
560
568
|
if (!loadResult.success) {
|
|
@@ -573,13 +581,6 @@ export class C64Client {
|
|
|
573
581
|
}
|
|
574
582
|
async runPrg(prg) {
|
|
575
583
|
try {
|
|
576
|
-
if (await this.shouldUseC64uMockBypass()) {
|
|
577
|
-
const payload = Buffer.isBuffer(prg) ? prg : Buffer.from(prg);
|
|
578
|
-
const response = await this.api.v1.runnersRunPrgCreate(":run_prg", payload, {
|
|
579
|
-
headers: { "Content-Type": "application/octet-stream" },
|
|
580
|
-
});
|
|
581
|
-
return { success: true, details: response.data };
|
|
582
|
-
}
|
|
583
584
|
const facade = await this.facadePromise;
|
|
584
585
|
const res = await facade.runPrg(prg);
|
|
585
586
|
return { success: res.success, details: res.details };
|
|
@@ -659,10 +660,6 @@ export class C64Client {
|
|
|
659
660
|
}
|
|
660
661
|
async reset() {
|
|
661
662
|
try {
|
|
662
|
-
if (await this.shouldUseC64uMockBypass()) {
|
|
663
|
-
const response = await this.api.v1.machineResetUpdate(":reset");
|
|
664
|
-
return { success: true, details: response.data };
|
|
665
|
-
}
|
|
666
663
|
const facade = await this.facadePromise;
|
|
667
664
|
const res = await facade.reset();
|
|
668
665
|
return { success: res.success, details: res.details };
|
|
@@ -673,10 +670,6 @@ export class C64Client {
|
|
|
673
670
|
}
|
|
674
671
|
async reboot() {
|
|
675
672
|
try {
|
|
676
|
-
if (await this.shouldUseC64uMockBypass()) {
|
|
677
|
-
const response = await this.api.v1.machineRebootUpdate(":reboot");
|
|
678
|
-
return { success: true, details: response.data };
|
|
679
|
-
}
|
|
680
673
|
const facade = await this.facadePromise;
|
|
681
674
|
const res = await facade.reboot();
|
|
682
675
|
return { success: res.success, details: res.details };
|
|
@@ -794,7 +787,8 @@ export class C64Client {
|
|
|
794
787
|
return { success: false, details: { message: "Voice must be 1..3" } };
|
|
795
788
|
}
|
|
796
789
|
const system = options.system ?? "PAL";
|
|
797
|
-
|
|
790
|
+
// The tool schema documents that a note name overrides frequencyHz.
|
|
791
|
+
const hz = options.note ? this.noteNameToHz(options.note) : options.frequencyHz ?? 440;
|
|
798
792
|
const freq16 = this.hzToSidFrequency(hz, system);
|
|
799
793
|
const freqLo = freq16 & 0xff;
|
|
800
794
|
const freqHi = (freq16 >> 8) & 0xff;
|
|
@@ -857,10 +851,6 @@ export class C64Client {
|
|
|
857
851
|
}
|
|
858
852
|
async pause(pinnedFacade) {
|
|
859
853
|
try {
|
|
860
|
-
if (!pinnedFacade && await this.shouldUseC64uMockBypass()) {
|
|
861
|
-
const res = await this.api.v1.machinePauseUpdate(":pause");
|
|
862
|
-
return { success: true, details: res.data };
|
|
863
|
-
}
|
|
864
854
|
const facade = pinnedFacade ?? await this.facadePromise;
|
|
865
855
|
return await facade.pause();
|
|
866
856
|
}
|
|
@@ -870,10 +860,6 @@ export class C64Client {
|
|
|
870
860
|
}
|
|
871
861
|
async resume(pinnedFacade) {
|
|
872
862
|
try {
|
|
873
|
-
if (!pinnedFacade && await this.shouldUseC64uMockBypass()) {
|
|
874
|
-
const res = await this.api.v1.machineResumeUpdate(":resume");
|
|
875
|
-
return { success: true, details: res.data };
|
|
876
|
-
}
|
|
877
863
|
const facade = pinnedFacade ?? await this.facadePromise;
|
|
878
864
|
return await facade.resume();
|
|
879
865
|
}
|
|
@@ -981,10 +967,6 @@ export class C64Client {
|
|
|
981
967
|
}
|
|
982
968
|
async menuButton() {
|
|
983
969
|
try {
|
|
984
|
-
if (await this.shouldUseC64uMockBypass()) {
|
|
985
|
-
const res = await this.api.v1.machineMenuButtonUpdate(":menu_button");
|
|
986
|
-
return { success: true, details: res.data };
|
|
987
|
-
}
|
|
988
970
|
const facade = await this.facadePromise;
|
|
989
971
|
return await facade.menuButton();
|
|
990
972
|
}
|
|
@@ -1006,10 +988,6 @@ export class C64Client {
|
|
|
1006
988
|
}
|
|
1007
989
|
async debugregRead() {
|
|
1008
990
|
try {
|
|
1009
|
-
if (await this.shouldUseC64uMockBypass()) {
|
|
1010
|
-
const res = await this.api.v1.machineDebugregList(":debugreg");
|
|
1011
|
-
return { success: true, value: res.data.value, details: res.data };
|
|
1012
|
-
}
|
|
1013
991
|
const facade = await this.facadePromise;
|
|
1014
992
|
return await facade.debugregRead();
|
|
1015
993
|
}
|
|
@@ -1019,10 +997,6 @@ export class C64Client {
|
|
|
1019
997
|
}
|
|
1020
998
|
async debugregWrite(value) {
|
|
1021
999
|
try {
|
|
1022
|
-
if (await this.shouldUseC64uMockBypass()) {
|
|
1023
|
-
const res = await this.api.v1.machineDebugregUpdate(":debugreg", { value });
|
|
1024
|
-
return { success: true, value: res.data.value, details: res.data };
|
|
1025
|
-
}
|
|
1026
1000
|
const facade = await this.facadePromise;
|
|
1027
1001
|
return await facade.debugregWrite(value);
|
|
1028
1002
|
}
|
|
@@ -1083,10 +1057,6 @@ export class C64Client {
|
|
|
1083
1057
|
try {
|
|
1084
1058
|
if (!drive || !path)
|
|
1085
1059
|
throw new Error("Drive and path are required");
|
|
1086
|
-
if (await this.shouldUseC64uMockBypass()) {
|
|
1087
|
-
const res = await this.api.v1.drivesLoadRomUpdate(drive, ":load_rom", { file: path });
|
|
1088
|
-
return { success: true, details: res.data };
|
|
1089
|
-
}
|
|
1090
1060
|
const facade = await this.facadePromise;
|
|
1091
1061
|
const result = await facade.driveLoadRom(drive, path);
|
|
1092
1062
|
return { success: result.success, details: result.details };
|
|
@@ -1141,6 +1111,9 @@ export class C64Client {
|
|
|
1141
1111
|
if (facade.type === "vice") {
|
|
1142
1112
|
return this.captureViceFrames(requestedCount);
|
|
1143
1113
|
}
|
|
1114
|
+
if (facade.type !== "c64u") {
|
|
1115
|
+
throw new Error("Video frame capture is only available on C64U/U64 and VICE; U2 has no video stream");
|
|
1116
|
+
}
|
|
1144
1117
|
return this.captureC64uVideoFrames(facade, requestedCount, {
|
|
1145
1118
|
reuseSession: options?.reuseSession === true,
|
|
1146
1119
|
keepAliveMs: Math.max(0, Math.trunc(options?.keepAliveMs ?? 0)),
|
|
@@ -2181,8 +2154,7 @@ export class C64Client {
|
|
|
2181
2154
|
if (trimmed.length === 0) {
|
|
2182
2155
|
throw new Error("Expected non-empty byte string");
|
|
2183
2156
|
}
|
|
2184
|
-
const
|
|
2185
|
-
const cleaned = withoutPrefix.replace(/[\s_]/g, "").toLowerCase();
|
|
2157
|
+
const cleaned = stripHexByteSeparators(trimmed).toLowerCase();
|
|
2186
2158
|
if (cleaned.length === 0) {
|
|
2187
2159
|
throw new Error("No hexadecimal data provided");
|
|
2188
2160
|
}
|
|
@@ -2412,9 +2384,12 @@ export function buildPrinterBasicProgram(opts) {
|
|
|
2412
2384
|
ln += 10;
|
|
2413
2385
|
continue;
|
|
2414
2386
|
}
|
|
2415
|
-
|
|
2387
|
+
// Chunk by the length of the expression, not the text: CHR$ splicing can make
|
|
2388
|
+
// an expression up to ten times longer, and a BASIC line must stay short
|
|
2389
|
+
// (the ROM's LINKPRG scans each line with an 8-bit index).
|
|
2390
|
+
const chunks = chunkByExpressionLength(logical, (text) => printerTextExpression(text, target, saddr), PRINTER_EXPRESSION_MAX);
|
|
2416
2391
|
for (let i = 0; i < chunks.length; i += 1) {
|
|
2417
|
-
const chunk =
|
|
2392
|
+
const chunk = printerTextExpression(chunks[i], target, saddr);
|
|
2418
2393
|
const tail = i < chunks.length - 1 ? ";" : ""; // avoid CR between chunks within the same logical line
|
|
2419
2394
|
lines.push(`${ln} PRINT#1,${chunk}${tail}`);
|
|
2420
2395
|
ln += 10;
|
|
@@ -2429,21 +2404,53 @@ export function buildPrinterBasicProgram(opts) {
|
|
|
2429
2404
|
lines.push(`${ln} CLOSE1`);
|
|
2430
2405
|
return lines.join("\n");
|
|
2431
2406
|
}
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2407
|
+
/** Longest PRINT#1 expression per BASIC line; keeps each line under the 80-character input limit. */
|
|
2408
|
+
const PRINTER_EXPRESSION_MAX = 64;
|
|
2409
|
+
function chunkByExpressionLength(input, expression, maxLen) {
|
|
2435
2410
|
const parts = [];
|
|
2436
|
-
let
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2411
|
+
let current = "";
|
|
2412
|
+
for (const char of input) {
|
|
2413
|
+
if (current && expression(current + char).length > maxLen) {
|
|
2414
|
+
parts.push(current);
|
|
2415
|
+
current = "";
|
|
2416
|
+
}
|
|
2417
|
+
current += char;
|
|
2440
2418
|
}
|
|
2419
|
+
if (current)
|
|
2420
|
+
parts.push(current);
|
|
2441
2421
|
return parts;
|
|
2442
2422
|
}
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2423
|
+
/**
|
|
2424
|
+
* BASIC V2 has no doubled-quote escaping, so quotes are spliced in as CHR$(34).
|
|
2425
|
+
* String literals also store a-z as the PETSCII letters $41-$5A, so printer
|
|
2426
|
+
* text that needs other codes is spliced in with CHR$: an Epson printer takes
|
|
2427
|
+
* ASCII, so lowercase stays $61-$7A; a Commodore printer in lowercase mode
|
|
2428
|
+
* (secondary address 7) prints $41-$5A as lowercase and $C1-$DA as capitals.
|
|
2429
|
+
*/
|
|
2430
|
+
function printerTextExpression(input, target, secondaryAddress) {
|
|
2431
|
+
const needsChr = (char) => {
|
|
2432
|
+
if (target === "epson" && char >= "a" && char <= "z")
|
|
2433
|
+
return char.charCodeAt(0);
|
|
2434
|
+
if (target === "commodore" && secondaryAddress === 7 && char >= "A" && char <= "Z")
|
|
2435
|
+
return char.charCodeAt(0) + 0x80;
|
|
2436
|
+
return undefined;
|
|
2437
|
+
};
|
|
2438
|
+
const pieces = [];
|
|
2439
|
+
let literal = "";
|
|
2440
|
+
for (const char of input) {
|
|
2441
|
+
const code = char === '"' ? 34 : needsChr(char);
|
|
2442
|
+
if (code === undefined) {
|
|
2443
|
+
literal += char;
|
|
2444
|
+
continue;
|
|
2445
|
+
}
|
|
2446
|
+
if (literal)
|
|
2447
|
+
pieces.push(`"${literal}"`);
|
|
2448
|
+
literal = "";
|
|
2449
|
+
pieces.push(`CHR$(${code})`);
|
|
2450
|
+
}
|
|
2451
|
+
if (literal || pieces.length === 0)
|
|
2452
|
+
pieces.push(`"${literal}"`);
|
|
2453
|
+
return pieces.join(";");
|
|
2447
2454
|
}
|
|
2448
2455
|
export function buildCommodoreBitmapBasicProgram(opts) {
|
|
2449
2456
|
const repeats = Math.max(1, Math.floor(opts.repeats ?? 1));
|
package/dist/config.js
CHANGED
|
@@ -4,13 +4,6 @@ import os from "node:os";
|
|
|
4
4
|
import { fileURLToPath } from "url";
|
|
5
5
|
const DEFAULT_HOST = "c64u";
|
|
6
6
|
const DEFAULT_PORT = 80;
|
|
7
|
-
const DEFAULT_CONFIG = {
|
|
8
|
-
c64_host: DEFAULT_HOST,
|
|
9
|
-
baseUrl: buildBaseUrl(DEFAULT_HOST, DEFAULT_PORT),
|
|
10
|
-
c64_port: DEFAULT_PORT,
|
|
11
|
-
vicePrewarm: false,
|
|
12
|
-
backendConfig: {},
|
|
13
|
-
};
|
|
14
7
|
export function loadConfig() {
|
|
15
8
|
const configPath = process.env.C64BRIDGE_CONFIG;
|
|
16
9
|
const cwdConfigPath = join(process.cwd(), ".c64bridge.json");
|
|
@@ -39,61 +32,105 @@ export function loadConfig() {
|
|
|
39
32
|
rawConfig ?? (rawConfig = {});
|
|
40
33
|
// New schema: choose the configured Ultimate hardware profile; retain
|
|
41
34
|
// c64u and legacy fields as the default when C64_MODE is not set to u2.
|
|
42
|
-
const c64u = rawConfig?.c64u;
|
|
43
|
-
const u2 = rawConfig?.u2;
|
|
35
|
+
const c64u = ultimateSection(rawConfig?.c64u);
|
|
36
|
+
const u2 = ultimateSection(rawConfig?.u2);
|
|
44
37
|
const useU2 = process.env.C64_MODE?.trim().toLowerCase() === "u2";
|
|
45
|
-
|
|
38
|
+
// Legacy top-level endpoint fields describe the single Ultimate of a
|
|
39
|
+
// pre-profile config, so they configure a backend only when the document
|
|
40
|
+
// has neither a c64u nor a u2 section. Each backend otherwise uses its own
|
|
41
|
+
// section, so one device's host, port or password never reaches the other.
|
|
42
|
+
const legacyUltimateEndpoint = !c64u && !u2 && LEGACY_ENDPOINT_KEYS.some((key) => hasLegacyValue(rawConfig?.[key]));
|
|
43
|
+
const ultimate = {
|
|
44
|
+
c64u: resolveUltimateEndpoint("c64u", c64u, c64u || !u2 ? rawConfig : undefined),
|
|
45
|
+
u2: resolveUltimateEndpoint("u2", u2 ?? c64u, u2 ? undefined : rawConfig),
|
|
46
|
+
};
|
|
47
|
+
// The selected profile reported by loadConfig keeps the documented fallback
|
|
48
|
+
// between sections and to the legacy fields.
|
|
49
|
+
const selected = useU2
|
|
50
|
+
? resolveUltimateEndpoint("u2", u2 ?? c64u, rawConfig)
|
|
51
|
+
: resolveUltimateEndpoint("c64u", c64u ?? u2, rawConfig);
|
|
46
52
|
const vice = rawConfig?.vice;
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
const config = {
|
|
54
|
+
c64_host: selected.c64_host,
|
|
55
|
+
baseUrl: selected.baseUrl,
|
|
56
|
+
c64_port: selected.port,
|
|
57
|
+
networkPassword: selected.networkPassword,
|
|
58
|
+
vicePrewarm: firstDefined(configuredBoolean(process.env.VICE_PREWARM), configuredBoolean(vice?.prewarm), configuredBoolean(rawConfig?.vicePrewarm)) ?? false,
|
|
59
|
+
ultimate,
|
|
60
|
+
legacyUltimateEndpoint,
|
|
61
|
+
backendConfig: {
|
|
62
|
+
...(c64u ? { c64u } : {}),
|
|
63
|
+
...(u2 ? { u2 } : {}),
|
|
64
|
+
...(rawConfig?.vice && typeof rawConfig.vice === "object" && !Array.isArray(rawConfig.vice) ? { vice: rawConfig.vice } : {}),
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
return config;
|
|
68
|
+
}
|
|
69
|
+
/** Top-level fields from the pre-profile config schema that name an endpoint. */
|
|
70
|
+
const LEGACY_ENDPOINT_KEYS = ["baseUrl", "c64_host", "c64_ip", "c64_port"];
|
|
71
|
+
function hasLegacyValue(value) {
|
|
72
|
+
return (typeof value === "string" && value.trim().length > 0) || (typeof value === "number" && Number.isFinite(value));
|
|
73
|
+
}
|
|
74
|
+
function ultimateSection(value) {
|
|
75
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The single host/port/password precedence for an Ultimate profile, used by
|
|
79
|
+
* loadConfig() and by every C64U/U2 backend construction:
|
|
80
|
+
* env > section host/hostname/port > legacy top-level c64_host/c64_ip/c64_port
|
|
81
|
+
* > section baseUrl > legacy top-level baseUrl > defaults. Passwords: env >
|
|
82
|
+
* section > legacy top-level networkPassword.
|
|
83
|
+
*/
|
|
84
|
+
export function resolveUltimateEndpoint(profile, section, legacy) {
|
|
85
|
+
const envHost = profile === "u2" ? process.env.U2_HOST ?? process.env.C64U_HOST : process.env.C64U_HOST;
|
|
86
|
+
const envPort = profile === "u2" ? process.env.U2_PORT ?? process.env.C64U_PORT : process.env.C64U_PORT;
|
|
87
|
+
const envPassword = profile === "u2" ? process.env.U2_PASSWORD ?? process.env.C64U_PASSWORD : process.env.C64U_PASSWORD;
|
|
88
|
+
const parsedSectionHost = parseEndpoint(configuredString(section?.host));
|
|
89
|
+
const parsedSectionHostname = parseEndpoint(configuredString(section?.hostname));
|
|
90
|
+
const parsedLegacyHost = parseEndpoint(configuredString(legacy?.c64_host));
|
|
91
|
+
const parsedLegacyIp = parseEndpoint(configuredString(legacy?.c64_ip));
|
|
92
|
+
const parsedEnvHost = parseEndpoint(configuredString(envHost));
|
|
52
93
|
const parsedBaseOverrides = [
|
|
53
|
-
parseEndpoint(normaliseBaseUrl(
|
|
54
|
-
parseEndpoint(normaliseBaseUrl(
|
|
94
|
+
parseEndpoint(normaliseBaseUrl(configuredString(section?.baseUrl))),
|
|
95
|
+
parseEndpoint(normaliseBaseUrl(configuredString(legacy?.baseUrl))),
|
|
55
96
|
];
|
|
56
97
|
const hostCandidates = [
|
|
57
98
|
parsedEnvHost.hostname,
|
|
58
|
-
|
|
59
|
-
|
|
99
|
+
parsedSectionHost.hostname,
|
|
100
|
+
parsedSectionHostname.hostname,
|
|
60
101
|
parsedLegacyHost.hostname,
|
|
61
102
|
parsedLegacyIp.hostname,
|
|
62
103
|
...parsedBaseOverrides.map((entry) => entry.hostname),
|
|
63
104
|
];
|
|
64
105
|
const portCandidates = [
|
|
65
|
-
configuredPort(
|
|
106
|
+
configuredPort(envPort),
|
|
66
107
|
parsedEnvHost.port,
|
|
67
|
-
configuredPort(
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
configuredPort(
|
|
108
|
+
configuredPort(section?.port),
|
|
109
|
+
parsedSectionHost.port,
|
|
110
|
+
parsedSectionHostname.port,
|
|
111
|
+
configuredPort(legacy?.c64_port),
|
|
71
112
|
parsedLegacyHost.port,
|
|
72
113
|
parsedLegacyIp.port,
|
|
73
114
|
...parsedBaseOverrides.map((entry) => entry.port),
|
|
74
115
|
];
|
|
75
116
|
const host = firstDefined(...hostCandidates) ?? DEFAULT_HOST;
|
|
76
117
|
const port = firstDefined(...portCandidates) ?? DEFAULT_PORT;
|
|
77
|
-
const baseUrl = buildBaseUrl(host, port);
|
|
78
118
|
const hostLabel = formatHost(host);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
baseUrl,
|
|
83
|
-
|
|
84
|
-
networkPassword: firstDefined(
|
|
85
|
-
vicePrewarm: firstDefined(configuredBoolean(process.env.VICE_PREWARM), configuredBoolean(vice?.prewarm), configuredBoolean(rawConfig?.vicePrewarm)) ?? false,
|
|
86
|
-
backendConfig: {
|
|
87
|
-
...(rawConfig?.c64u && typeof rawConfig.c64u === "object" && !Array.isArray(rawConfig.c64u) ? { c64u: rawConfig.c64u } : {}),
|
|
88
|
-
...(rawConfig?.u2 && typeof rawConfig.u2 === "object" && !Array.isArray(rawConfig.u2) ? { u2: rawConfig.u2 } : {}),
|
|
89
|
-
...(rawConfig?.vice && typeof rawConfig.vice === "object" && !Array.isArray(rawConfig.vice) ? { vice: rawConfig.vice } : {}),
|
|
90
|
-
},
|
|
119
|
+
return {
|
|
120
|
+
host,
|
|
121
|
+
port,
|
|
122
|
+
baseUrl: buildBaseUrl(host, port),
|
|
123
|
+
c64_host: port === DEFAULT_PORT ? hostLabel : `${hostLabel}:${port}`,
|
|
124
|
+
networkPassword: firstDefined(nonEmptyString(envPassword), nonEmptyString(section?.networkPassword), nonEmptyString(legacy?.networkPassword)),
|
|
91
125
|
};
|
|
92
|
-
return config;
|
|
93
126
|
}
|
|
94
127
|
function configuredString(value) {
|
|
95
128
|
return typeof value === "string" ? value.trim() : undefined;
|
|
96
129
|
}
|
|
130
|
+
function nonEmptyString(value) {
|
|
131
|
+
const trimmed = configuredString(value);
|
|
132
|
+
return trimmed ? trimmed : undefined;
|
|
133
|
+
}
|
|
97
134
|
function configuredPort(value) {
|
|
98
135
|
if (typeof value === "number" && Number.isInteger(value) && value > 0 && value <= 65535) {
|
|
99
136
|
return value;
|