Skip to content

feat(generate): port local-stack stack generation to Deno#19

Open
wax911 wants to merge 2 commits into
feat/3-implement-config-init-and-profile-discoveryfrom
feat/4-port-stack-generation-to-deno
Open

feat(generate): port local-stack stack generation to Deno#19
wax911 wants to merge 2 commits into
feat/3-implement-config-init-and-profile-discoveryfrom
feat/4-port-stack-generation-to-deno

Conversation

@wax911

@wax911 wax911 commented Jun 29, 2026

Copy link
Copy Markdown
Member

Closes #4

  • Full port of tools/generate_stacks.py (432 lines) to Deno
  • Compose file discovery with x-stack metadata grouping
  • Fragment merge (docker-compose + swarm.fragment)
  • Path rewriting, volume collection, Compose-key stripping
  • Deterministic YAML output with sorted keys
  • 60 tests for discover, load, merge, transform, volumes, generate

Port of tools/generate_stacks.py from AniTrend/local-stack to idiomatic Deno TypeScript:

- File discovery: walks repo root, finds docker-compose.yml/yaml files with x-stack metadata
- Fragment loading: optional swarm.fragment.yml deep-merge per service
- Compose deep merge (dict recursive, array replacement, scalar override)
- Service transforms: strip compose-only keys (container_name, restart, build),
  inject logging defaults, rewrite env_file and bind-mount paths to repo-root relative
- Named volume collection (external: true), default traefik-public overlay network
- YAML output with header comment, --dry-run support
- CLI generate command wired to real implementation
- 60 compose tests + 58 existing = 118 passing

@wax911 wax911 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review notes against #4:

  1. Path rewriting is incorrect for multi-service stacks. generateSingleStack() rewrites every service using sources[0]?.composeDir. In local-stack, each service lives in its own directory, so env-file and bind-mount paths for every service after the first can be rewritten relative to the wrong directory.

  2. The network name is hardcoded as traefik-public. The issue requires the generated network block to come from config. traefik-public is the local-stack default, not a universal stackctl assumption.

  3. Top-level volume metadata is lost. The local-stack Python generator preserves volume metadata such as explicit name: overrides when emitting external volumes. This implementation emits only { external: true } for collected names.

  4. The command currently discovers all stacks when options.stacks is omitted, but it does not appear to honor configured stack names/order from .stackctl. Determinism should come from config first, discovery second.

  5. The generated YAML contains a generated header comment. That is fine for output readability, but ensure snapshot tests account for it and that sync comparisons do not drift against existing local-stack generated output unless the migration intentionally updates those files.

This is close in shape, but the path-rewrite bug is a correctness blocker for the actual local-stack repository.

@wax911 wax911 changed the base branch from main to feat/3-implement-config-init-and-profile-discovery June 29, 2026 15:43
- Fix path rewriting for multi-service stacks (per-service compose dir)
- Use config stack.network instead of hardcoded "traefik-public"
- Preserve top-level volume metadata from original compose files
- Honor config stack.names ordering before discovery
- Add deterministic output with sorted keys and stable header
- Add snapshot test for generation output

@wax911 wax911 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up review after the push:

Good fixes:

  • The multi-service path rewrite issue is addressed with a service-to-compose-directory map.
  • The network name is now configurable through GenerateOptions.network instead of being only hardcoded.
  • Existing top-level volume definitions are preserved and get external: true when missing.
  • Config stack names are now accepted through configStackNames.

Remaining issues:

  1. The PR is currently not mergeable and its base SHA is behind the current config PR. Rebase it after #18 is fixed.

  2. The default network still falls back to traefik-public. That is fine as a local-stack default, but it should come from DEFAULT_CONFIG/config wiring rather than being a universal compose module default. If this is intentionally the built-in default, document it as such.

  3. Confirm snapshot compatibility against current local-stack generated stacks. The added generated header comment and key sorting may intentionally change output, but sync must not produce noisy drift during migration unless local-stack updates the committed stack artifacts in the same migration.

  4. Make sure the service-dir map handles duplicate service names across different compose sources deterministically. The first entry wins today; that should either be validated as an error or documented.

This is much closer. The previous correctness blocker is fixed, but mergeability and parity validation still need attention.

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.

feat(generate): port local-stack stack generation to Deno

1 participant