Add shared ComposeProjectOptions#138
Merged
Merged
Conversation
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
approved these changes
Jul 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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: