Skip to content

Commit a2542bb

Browse files
committed
refactor(cli): consolidate artifact option handling
1 parent 7b82498 commit a2542bb

4 files changed

Lines changed: 223 additions & 148 deletions

File tree

src/cli/build-command.test.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
22

33
import { getAddress } from "viem";
4+
import { ARTIFACT_DEFAULTS } from "../constants/artifact-defaults.ts";
45
import type { BesuAllocAccount } from "../genesis/besu-genesis.service.ts";
56
import { ALGORITHM } from "../genesis/besu-genesis.service.ts";
67
import type { GeneratedNodeKey } from "../keys/node-key-factory.ts";
@@ -16,11 +17,13 @@ const EXPECTED_DEFAULT_VALIDATOR = 4;
1617
const DEFAULT_STATIC_NODE_PORT = 30_303;
1718
const CUSTOM_STATIC_NODE_PORT = 40_000;
1819
const LEADING_DOT_REGEX = /^\./u;
19-
const DEFAULT_SERVICE_NAME = "besu-node";
20-
const DEFAULT_POD_PREFIX = "besu-node-validator";
21-
const DEFAULT_GENESIS_CONFIGMAP_NAME = "besu-genesis";
22-
const DEFAULT_STATIC_NODES_CONFIGMAP_NAME = "besu-static-nodes";
23-
const DEFAULT_FAUCET_PREFIX = "besu-faucet";
20+
const {
21+
staticNodeServiceName: DEFAULT_SERVICE_NAME,
22+
staticNodePodPrefix: DEFAULT_POD_PREFIX,
23+
genesisConfigMapName: DEFAULT_GENESIS_CONFIGMAP_NAME,
24+
staticNodesConfigMapName: DEFAULT_STATIC_NODES_CONFIGMAP_NAME,
25+
faucetArtifactPrefix: DEFAULT_FAUCET_PREFIX,
26+
} = ARTIFACT_DEFAULTS;
2427
const UNCOMPRESSED_PUBLIC_KEY_PREFIX = "04";
2528
const UNCOMPRESSED_PUBLIC_KEY_LENGTH = 130;
2629
const HEX_RADIX = 16;

src/cli/build-command.ts

Lines changed: 118 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Command, InvalidArgumentError } from "commander";
2-
2+
import { ARTIFACT_DEFAULTS } from "../constants/artifact-defaults.ts";
33
import {
44
ALGORITHM,
55
type Algorithm,
@@ -55,11 +55,13 @@ type BootstrapDependencies = {
5555

5656
const DEFAULT_VALIDATOR_COUNT = 4;
5757
const DEFAULT_STATIC_NODE_PORT = 30_303;
58-
const DEFAULT_STATIC_NODE_SERVICE_NAME = "besu-node";
59-
const DEFAULT_STATIC_NODE_POD_PREFIX = "besu-node-validator";
60-
const DEFAULT_GENESIS_CONFIGMAP_NAME = "besu-genesis";
61-
const DEFAULT_STATIC_NODES_CONFIGMAP_NAME = "besu-static-nodes";
62-
const DEFAULT_FAUCET_ARTIFACT_PREFIX = "besu-faucet";
58+
const {
59+
staticNodeServiceName: DEFAULT_STATIC_NODE_SERVICE_NAME,
60+
staticNodePodPrefix: DEFAULT_STATIC_NODE_POD_PREFIX,
61+
genesisConfigMapName: DEFAULT_GENESIS_CONFIGMAP_NAME,
62+
staticNodesConfigMapName: DEFAULT_STATIC_NODES_CONFIGMAP_NAME,
63+
faucetArtifactPrefix: DEFAULT_FAUCET_ARTIFACT_PREFIX,
64+
} = ARTIFACT_DEFAULTS;
6365
const OUTPUT_CHOICES: OutputType[] = ["screen", "file", "kubernetes"];
6466
const LEADING_DOT_REGEX = /^\./u;
6567
const UNCOMPRESSED_PUBLIC_KEY_PREFIX = "04";
@@ -139,6 +141,81 @@ const normalizeStaticNodeNamespace = (
139141
return trimmed.length === 0 ? undefined : trimmed;
140142
};
141143

144+
type TextOptionKey =
145+
| "staticNodeDomain"
146+
| "staticNodeNamespace"
147+
| "staticNodeServiceName"
148+
| "staticNodePodPrefix"
149+
| "genesisConfigmapName"
150+
| "staticNodesConfigmapName"
151+
| "faucetArtifactPrefix";
152+
153+
type TextOptionDescriptor<T extends TextOptionKey> = {
154+
key: T;
155+
flag: string;
156+
description: string;
157+
parser?: (value: string) => CliOptions[T];
158+
sanitize?: (value: NonNullable<CliOptions[T]>) => CliOptions[T] | undefined;
159+
};
160+
161+
const TEXT_OPTION_DESCRIPTORS: TextOptionDescriptor<TextOptionKey>[] = [
162+
{
163+
key: "staticNodeDomain",
164+
flag: "--static-node-domain <domain>",
165+
description:
166+
"DNS suffix appended to validator peer hostnames for static-nodes entries.",
167+
parser: stripSurroundingQuotes,
168+
sanitize: (value) => normalizeStaticNodeDomain(value) ?? undefined,
169+
},
170+
{
171+
key: "staticNodeNamespace",
172+
flag: "--static-node-namespace <name>",
173+
description:
174+
"Namespace segment inserted between service name and domain for static-nodes entries.",
175+
parser: stripSurroundingQuotes,
176+
sanitize: (value) => normalizeStaticNodeNamespace(value) ?? undefined,
177+
},
178+
{
179+
key: "staticNodeServiceName",
180+
flag: "--static-node-service-name <name>",
181+
description:
182+
"Headless Service name used when constructing static-nodes hostnames.",
183+
parser: stripSurroundingQuotes,
184+
sanitize: (value) => stripSurroundingQuotes(value),
185+
},
186+
{
187+
key: "staticNodePodPrefix",
188+
flag: "--static-node-pod-prefix <prefix>",
189+
description:
190+
"StatefulSet prefix used when constructing validator pod hostnames.",
191+
parser: stripSurroundingQuotes,
192+
sanitize: (value) => stripSurroundingQuotes(value),
193+
},
194+
{
195+
key: "genesisConfigmapName",
196+
flag: "--genesis-configmap-name <name>",
197+
description:
198+
"ConfigMap name that stores the generated genesis.json payload.",
199+
parser: stripSurroundingQuotes,
200+
sanitize: (value) => stripSurroundingQuotes(value),
201+
},
202+
{
203+
key: "staticNodesConfigmapName",
204+
flag: "--static-nodes-configmap-name <name>",
205+
description:
206+
"ConfigMap name that stores the generated static-nodes.json payload.",
207+
parser: stripSurroundingQuotes,
208+
sanitize: (value) => stripSurroundingQuotes(value),
209+
},
210+
{
211+
key: "faucetArtifactPrefix",
212+
flag: "--faucet-artifact-prefix <prefix>",
213+
description: "Prefix applied to faucet ConfigMaps and Secrets.",
214+
parser: stripSurroundingQuotes,
215+
sanitize: (value) => stripSurroundingQuotes(value),
216+
},
217+
];
218+
142219
const deriveNodeId = (publicKey: string): string => {
143220
const trimmed = publicKey.startsWith("0x") ? publicKey.slice(2) : publicKey;
144221
if (
@@ -368,6 +445,16 @@ const createCliCommand = (
368445
"Generate node identities, configure consensus, and emit a Besu genesis."
369446
);
370447

448+
const identityParser = <T>(value: T): T => value;
449+
for (const descriptor of TEXT_OPTION_DESCRIPTORS) {
450+
const parser = descriptor.parser ?? identityParser;
451+
generate.option(
452+
descriptor.flag,
453+
descriptor.description,
454+
parser as (value: string) => unknown
455+
);
456+
}
457+
371458
generate
372459
.option(
373460
"-v, --validators <count>",
@@ -393,16 +480,6 @@ const createCliCommand = (
393480
},
394481
"screen"
395482
)
396-
.option(
397-
"--static-node-domain <domain>",
398-
"DNS suffix appended to validator peer hostnames for static-nodes entries.",
399-
(value: string) => stripSurroundingQuotes(value)
400-
)
401-
.option(
402-
"--static-node-namespace <name>",
403-
"Namespace segment inserted between service name and domain for static-nodes entries.",
404-
(value: string) => stripSurroundingQuotes(value)
405-
)
406483
.option(
407484
"--static-node-port <number>",
408485
"P2P port used for static-nodes enode URIs.",
@@ -416,31 +493,6 @@ const createCliCommand = (
416493
parseNonNegativeInteger(value, "Static node discovery port"),
417494
DEFAULT_STATIC_NODE_PORT
418495
)
419-
.option(
420-
"--static-node-service-name <name>",
421-
"Headless Service name used when constructing static-nodes hostnames.",
422-
(value: string) => stripSurroundingQuotes(value)
423-
)
424-
.option(
425-
"--static-node-pod-prefix <prefix>",
426-
"StatefulSet prefix used when constructing validator pod hostnames.",
427-
(value: string) => stripSurroundingQuotes(value)
428-
)
429-
.option(
430-
"--genesis-configmap-name <name>",
431-
"ConfigMap name that stores the generated genesis.json payload.",
432-
(value: string) => stripSurroundingQuotes(value)
433-
)
434-
.option(
435-
"--static-nodes-configmap-name <name>",
436-
"ConfigMap name that stores the generated static-nodes.json payload.",
437-
(value: string) => stripSurroundingQuotes(value)
438-
)
439-
.option(
440-
"--faucet-artifact-prefix <prefix>",
441-
"Prefix applied to faucet ConfigMaps and Secrets.",
442-
(value: string) => stripSurroundingQuotes(value)
443-
)
444496
.option(
445497
"--consensus <algorithm>",
446498
`Consensus algorithm (${Object.values(ALGORITHM).join(", ")}). (default: ${
@@ -502,14 +554,6 @@ const createCliCommand = (
502554
cmd.getOptionValueSource("validators") === "default"
503555
? undefined
504556
: options.validators,
505-
staticNodeDomain:
506-
cmd.getOptionValueSource("staticNodeDomain") === "default"
507-
? undefined
508-
: options.staticNodeDomain,
509-
staticNodeNamespace:
510-
cmd.getOptionValueSource("staticNodeNamespace") === "default"
511-
? undefined
512-
: options.staticNodeNamespace,
513557
staticNodePort:
514558
cmd.getOptionValueSource("staticNodePort") === "default"
515559
? undefined
@@ -518,64 +562,41 @@ const createCliCommand = (
518562
cmd.getOptionValueSource("staticNodeDiscoveryPort") === "default"
519563
? undefined
520564
: options.staticNodeDiscoveryPort,
521-
staticNodeServiceName:
522-
cmd.getOptionValueSource("staticNodeServiceName") === "default"
523-
? undefined
524-
: options.staticNodeServiceName,
525-
staticNodePodPrefix:
526-
cmd.getOptionValueSource("staticNodePodPrefix") === "default"
527-
? undefined
528-
: options.staticNodePodPrefix,
529-
genesisConfigmapName:
530-
cmd.getOptionValueSource("genesisConfigmapName") === "default"
531-
? undefined
532-
: options.genesisConfigmapName,
533-
staticNodesConfigmapName:
534-
cmd.getOptionValueSource("staticNodesConfigmapName") === "default"
535-
? undefined
536-
: options.staticNodesConfigmapName,
537-
faucetArtifactPrefix:
538-
cmd.getOptionValueSource("faucetArtifactPrefix") === "default"
539-
? undefined
540-
: options.faucetArtifactPrefix,
541565
};
542566

567+
for (const { key } of TEXT_OPTION_DESCRIPTORS) {
568+
if (cmd.getOptionValueSource(key) === "default") {
569+
normalizedOptions[key] = undefined;
570+
}
571+
}
572+
543573
const sanitizedOptions: CliOptions = {
544574
...normalizedOptions,
545575
allocations:
546576
normalizedOptions.allocations === undefined
547577
? undefined
548578
: stripSurroundingQuotes(normalizedOptions.allocations),
549-
staticNodeDomain: normalizeStaticNodeDomain(
550-
normalizedOptions.staticNodeDomain
551-
),
552-
staticNodeNamespace: normalizeStaticNodeNamespace(
553-
normalizedOptions.staticNodeNamespace
554-
),
555-
staticNodeServiceName:
556-
normalizedOptions.staticNodeServiceName === undefined
557-
? undefined
558-
: stripSurroundingQuotes(normalizedOptions.staticNodeServiceName),
559-
staticNodePodPrefix:
560-
normalizedOptions.staticNodePodPrefix === undefined
561-
? undefined
562-
: stripSurroundingQuotes(normalizedOptions.staticNodePodPrefix),
563-
genesisConfigmapName:
564-
normalizedOptions.genesisConfigmapName === undefined
565-
? undefined
566-
: stripSurroundingQuotes(normalizedOptions.genesisConfigmapName),
567-
staticNodesConfigmapName:
568-
normalizedOptions.staticNodesConfigmapName === undefined
569-
? undefined
570-
: stripSurroundingQuotes(
571-
normalizedOptions.staticNodesConfigmapName
572-
),
573-
faucetArtifactPrefix:
574-
normalizedOptions.faucetArtifactPrefix === undefined
575-
? undefined
576-
: stripSurroundingQuotes(normalizedOptions.faucetArtifactPrefix),
577579
};
578580

581+
for (const { key, sanitize } of TEXT_OPTION_DESCRIPTORS) {
582+
const currentValue = normalizedOptions[key];
583+
if (currentValue === undefined) {
584+
sanitizedOptions[key] = undefined;
585+
continue;
586+
}
587+
588+
if (!sanitize) {
589+
sanitizedOptions[key] = currentValue;
590+
continue;
591+
}
592+
593+
const sanitizedValue = sanitize(
594+
currentValue as NonNullable<CliOptions[typeof key]>
595+
);
596+
sanitizedOptions[key] = (sanitizedValue ??
597+
undefined) as CliOptions[typeof key];
598+
}
599+
579600
await runBootstrap(sanitizedOptions, deps);
580601
});
581602

0 commit comments

Comments
 (0)