Refactoring Opportunity
Summary
- File:
src/compose-generator.ts
- Current size: 355 lines
- Function of concern:
generateDockerCompose spans lines 23–331 (309 lines in a single function)
- Responsibilities identified: 5 distinct concerns mixed into one function body
Evidence
The generateDockerCompose function (lines 23–331) is 309 lines long and mixes five distinct concerns:
1. Filesystem side effect (lines 58–62): Creates the init-signal/ directory directly inside the compose-generation function:
const initSignalDir = path.join(config.workDir, 'init-signal');
if (!fs.existsSync(initSignalDir)) {
fs.mkdirSync(initSignalDir, { recursive: true });
}
This is a setup phase that does not belong in a function named "generate".
2. Environment mutation before service definitions (lines 88–101): Pre-sets AWF_API_PROXY_IP and AWF_CLI_PROXY_IP in the environment map before the init container object is constructed — because object literals capture values at definition time. This coupling is a footgun and the comment explains the fragility:
// Pre-set API proxy IP in environment before the init container definition.
// The init container's environment object captures values at definition time,
// so AWF_API_PROXY_IP must be set before the init container is defined.
3. Sysroot volume post-processing (lines 120–135): After buildAgentVolumes(...) returns, the function immediately mutates the returned array to strip out volumes that conflict with the sysroot staged volume:
const filteredVolumes = agentVolumes.filter(volume => {
const target = volume.split(':')[1];
return !sysrootShadowedTargets.has(target);
});
agentVolumes.length = 0;
agentVolumes.push(...filteredVolumes);
4. Optional service insertion + dependency graph wiring (lines 168–266): Four separate if blocks (sysroot-stage, iptables-init, api-proxy, doh-proxy, cli-proxy) each build an optional service and mutate agentService.depends_on — 100 lines of conditional orchestration inline in the main function.
5. Dual network topology assembly (lines 268–330): Two distinct code paths — networkIsolation returns a topology compose; otherwise returns a standard external-network compose — each with their own composeResult object.
Proposed Split
src/compose-generator.ts (355 lines, one 309-line function) could be refactored as:
-
src/compose-workdir-setup.ts — Filesystem side effects (create init-signal/ dir). Already partially modelled by src/workdir-setup.ts; this fits naturally there (~10 lines, possibly inlined into writeConfigs in config-writer.ts).
-
src/services/optional-services.ts — assembleOptionalServices(services, agentService, environment, config, networkConfig, imageConfig, logPaths): inserts the conditional sidecars (api-proxy, doh-proxy, cli-proxy, sysroot-stage, iptables-init) and wires depends_on edges (~130 lines extracted, making each optional service a self-contained block).
-
src/compose-network.ts — buildComposeNetworks(services, config, networkConfig, namedVolumes): encapsulates the two network topology code paths (isolation vs. external) (~50 lines).
-
src/compose-generator.ts — Orchestrating facade reduced to ~60 lines: resolve image config, call service builders, call assembleOptionalServices, call buildComposeNetworks, return result.
Affected Callers
src/config-writer.ts:10: import { generateDockerCompose, redactDockerComposeSecrets } from './compose-generator';
src/config-writer.ts:278: const dockerCompose = generateDockerCompose(config, networkConfig, sslConfig, squidConfig);
The public API of generateDockerCompose does not need to change — only internal extraction. Test utilities (src/services/service-test-setup.test-utils.ts) import generateDockerCompose directly and will not be affected.
Effort Estimate
Medium (no API surface change; pure internal restructuring with test coverage already in place via src/compose-generator.test.ts)
Benefits
- Navigation: A developer working on CLI proxy sidecar config navigates to
assembleOptionalServices, not a 309-line function
- Testability: The
assembleOptionalServices extraction is independently testable without constructing a full compose
- Reduced footgun: The environment-mutation-before-init-container ordering constraint can be documented and enforced in a smaller scope
- Filesystem side effects isolated:
generateDockerCompose becomes a pure function (or near-pure), making it easier to test without fs stubs
Detected by Refactoring Scanner workflow. Run date: 2026-06-30
Generated by Refactoring Opportunity Scanner · 97.6 AIC · ⊞ 7K · ◷
Refactoring Opportunity
Summary
src/compose-generator.tsgenerateDockerComposespans lines 23–331 (309 lines in a single function)Evidence
The
generateDockerComposefunction (lines 23–331) is 309 lines long and mixes five distinct concerns:1. Filesystem side effect (lines 58–62): Creates the
init-signal/directory directly inside the compose-generation function:This is a setup phase that does not belong in a function named "generate".
2. Environment mutation before service definitions (lines 88–101): Pre-sets
AWF_API_PROXY_IPandAWF_CLI_PROXY_IPin the environment map before the init container object is constructed — because object literals capture values at definition time. This coupling is a footgun and the comment explains the fragility:3. Sysroot volume post-processing (lines 120–135): After
buildAgentVolumes(...)returns, the function immediately mutates the returned array to strip out volumes that conflict with the sysroot staged volume:4. Optional service insertion + dependency graph wiring (lines 168–266): Four separate
ifblocks (sysroot-stage, iptables-init, api-proxy, doh-proxy, cli-proxy) each build an optional service and mutateagentService.depends_on— 100 lines of conditional orchestration inline in the main function.5. Dual network topology assembly (lines 268–330): Two distinct code paths —
networkIsolationreturns a topology compose; otherwise returns a standard external-network compose — each with their owncomposeResultobject.Proposed Split
src/compose-generator.ts(355 lines, one 309-line function) could be refactored as:src/compose-workdir-setup.ts— Filesystem side effects (createinit-signal/dir). Already partially modelled bysrc/workdir-setup.ts; this fits naturally there (~10 lines, possibly inlined intowriteConfigsinconfig-writer.ts).src/services/optional-services.ts—assembleOptionalServices(services, agentService, environment, config, networkConfig, imageConfig, logPaths): inserts the conditional sidecars (api-proxy, doh-proxy, cli-proxy, sysroot-stage, iptables-init) and wiresdepends_onedges (~130 lines extracted, making each optional service a self-contained block).src/compose-network.ts—buildComposeNetworks(services, config, networkConfig, namedVolumes): encapsulates the two network topology code paths (isolation vs. external) (~50 lines).src/compose-generator.ts— Orchestrating facade reduced to ~60 lines: resolve image config, call service builders, callassembleOptionalServices, callbuildComposeNetworks, return result.Affected Callers
The public API of
generateDockerComposedoes not need to change — only internal extraction. Test utilities (src/services/service-test-setup.test-utils.ts) importgenerateDockerComposedirectly and will not be affected.Effort Estimate
Medium (no API surface change; pure internal restructuring with test coverage already in place via
src/compose-generator.test.ts)Benefits
assembleOptionalServices, not a 309-line functionassembleOptionalServicesextraction is independently testable without constructing a full composegenerateDockerComposebecomes a pure function (or near-pure), making it easier to test withoutfsstubsDetected by Refactoring Scanner workflow. Run date: 2026-06-30