Skip to content

Add shared ComposeProjectOptions#138

Merged
Cyb3rDudu merged 12 commits into
Mcrich23:mainfrom
adrum:feat/compose-project-base
Jul 22, 2026
Merged

Add shared ComposeProjectOptions#138
Cyb3rDudu merged 12 commits into
Mcrich23:mainfrom
adrum:feat/compose-project-base

Conversation

@adrum

@adrum adrum commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

I have a set of branches for adding more commands like ps, logs, exec, and pull. However, I noticed an opportunity to simplify how options are parsed and utilized.

This adds ComposeProjectOptions / ComposeProject — a shared layer for locating and decoding the compose file, deriving the project name, selecting services, and resolving container names. up, down, and build each had their own copy of that logic; they now adopt the shared one with a single @OptionGroup, so the upcoming commands can build on it instead of adding more copies.

I also fixed a few bugs along the way:

adrum added 12 commits June 19, 2026 20:15
Introduce a reusable ComposeProjectOptions (compose-file location + decode,
project-name derivation, dependency-ordered service resolution, container-name
mapping) and a resolved ComposeProject value. New subcommands adopt this
instead of re-duplicating the boilerplate that up/down/build/logs each grew.
…d ordering

Covers composePath (--file precedence, discovery order, default),
projectName fallback to the derived cwd name, and orderedServices
topological sort/filtering.
… guess

ComposeProject mapped each service to a single container name
(container_name ?? <project>-<service>), which misses the dotted
<service>.<dnsDomain> names 'up' uses when the project's DNS domain is
registered (Mcrich23#97) — the same bug class fixed for ctrl-c in Mcrich23#127. Replace
ServiceTarget.containerName with candidateContainerNames (explicit name
first, then both conventions, mirroring ComposeDown.stopOldStuff) and
add existingContainer(using:) to resolve which candidate actually
exists, so every subcommand built on ComposeProject shares one correct
lookup instead of re-deriving names.
orderedServices hand-rolled its own name filter, which ignored Compose
profiles gating and didn't expand depends_on — diverging from the
shared selection algorithm up/build/down adopted in Mcrich23#126. Route it
through Service.selectServices with the ComposeFileOptions active
profiles so commands built on ComposeProject select identically to the
existing ones: profile-eligible services plus dependencies by default,
requested services plus transitive dependencies (bypassing the profile
gate) when named explicitly.
First consumer of ComposeProject: drop ComposeDown's own copies of
compose-file discovery, project-name derivation, topo-sort/selection,
and candidate-container-name construction in favor of the shared API.
Behavior is preserved (profile skip note, stop-all-candidates loop for
mixed naming-mode leftovers, extra_hosts cleanup) with one refinement:
explicit 'down <service>' now expands dependencies via
Service.selectServices instead of the topo-sort dependedBy side effect,
which only recorded each service's first visitor and so stopped an
explicit request's dependencies only when visit order happened to
align.
Drop ComposeBuild's copies of compose-file discovery, project-name
derivation, and service selection in favor of ComposeProjectOptions.
Selection semantics are unchanged (Service.selectServices with active
profiles, then keep services with a build config); ordering improves
from dictionary order to topological, so dependencies build before
dependents.
Drop ComposeUp's copies of compose-file discovery/decoding,
project-name derivation, topo-sort/selection, and the hand-built
stop-candidates list in favor of ComposeProjectOptions and
ComposeProject.candidateContainerNames. Behavior is unchanged: 'up'
keeps deciding the single creation-time name itself (explicit
container_name, dotted DNS name when the domain is registered, else
<project>-<service>) since that depends on runtime DNS availability,
and the pre-start cleanup still stops every name shape a previous run
might have used.
Cleanups from a reuse/simplification/efficiency/altitude review of the
branch:

- down: resolve once instead of loadCompose()+resolve() (the compose
  file was read and YAML-decoded twice per run); drop the stop loop's
  remove parameter, which was always false
- up: consume resolve() instead of re-composing loadCompose +
  projectName + orderedServices + candidate names piecewise; this also
  gives up the same 'No such service' warning down/build already had
- move the 'No such service' warning from resolve() into
  orderedServices() so every selection path warns, not just resolve()
- hoist the envFilePath derivation duplicated verbatim in up and build
  onto ComposeProjectOptions
- move sanitizeDnsDomain from ComposeUp to ComposeProject: it is
  project naming policy, and the shared layer should not reach into a
  command for it
- delete dead code: ServiceTarget.existingContainer (no callers, and
  its first-match semantics is the exact behavior down's stop-every-
  candidate loop exists to avoid), ComposeProject.cwd (never read),
  ComposeError.invalidProjectName (no throwers left), and ComposeUp's
  vacuous projectName optionality (guards that could never fire once
  resolution guarantees a name)
- narrow supportedComposeFilenames to private
…ect>-<service>

waitUntilServiceIsHealthy rebuilt the container name by hand, so
healthchecks for services created under an explicit container_name or
the dotted <service>.<dnsDomain> DNS-mode name (Mcrich23#97) exec'd against a
nonexistent container and burned every retry before failing. Use
containerName(for:), which reads the resolved name configService
records before any healthcheck runs — the same fix Mcrich23#127 applied to the
foreground stop paths.
…ution

Closes the two meaningful coverage gaps over this branch's diff: the
composeFileNotFound path in ComposeProjectOptions.loadCompose, and a
dynamic regression test proving healthchecks exec against the resolved
container name — a service created under an explicit container_name
previously health-checked the nonexistent <project>-<service> and
failed up after exhausting retries.
@Cyb3rDudu
Cyb3rDudu merged commit e1b4760 into Mcrich23:main Jul 22, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants