From 5c5f37a61098d51a80fd688ea7bf8dfa0a219594 Mon Sep 17 00:00:00 2001 From: oycyc Date: Sun, 17 May 2026 11:15:11 -0400 Subject: [PATCH 1/6] fix(mixins): recurse into nested components and only update existing copies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #47. The `mixins:update-all` task previously assumed a flat `./components/*` layout, so nested deployable instances like `aws-iam/delegated-tf-role` were never updated. It also unconditionally copied the mixin into every top-level component directory, which is wrong: a given mixin is not universally applicable (e.g. the `secrets.sops.tf` mixin only belongs in components that actually consume SOPS secrets). Copying it everywhere creates unused files that have to be deleted by hand. Changes: - Locate destinations by recursively finding existing copies of the named mixin under `./components/` (`find ... -type f -name `). A component opts in to a mixin by having a copy of it on disk; this task only keeps those existing copies in sync. To adopt a mixin in a new component, copy it in manually once — that becomes the explicit opt-in and future `update-all` runs will track the template. - Recursion picks up nested instance layouts (e.g. `components/aws-iam/delegated-tf-role/`) automatically. - Prune `.terraform/` and `.terragrunt-cache/` so vendored child-module copies that Terraform/Terragrunt drop on disk during init are not overwritten. - Set `dir: "{{.USER_WORKING_DIR}}"` so `./mixins/` and `./components/` resolve from where the user invoked `task`, not from the included taskfile's own directory. - Drop the `generates:` glob — it cannot express nested outputs and was causing Task to falsely treat the task as up-to-date. - Rewrite the loop to avoid `read -d ''` (not supported by Task's mvdan/sh interpreter). --- lib/mixins/Taskfile.yml | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/lib/mixins/Taskfile.yml b/lib/mixins/Taskfile.yml index feaae2b..7cf3607 100644 --- a/lib/mixins/Taskfile.yml +++ b/lib/mixins/Taskfile.yml @@ -2,21 +2,32 @@ version: "3" tasks: update-all: - desc: "Update a mixin across the components. Example: `task mixins:update-all -- context.tf`." + # Updates (by overwriting) every existing copy of the named mixin under + # ./components/ with the current ./mixins/ template. + # + # Directories opt in to a mixin by having a copy of it on disk; this task only + # touches directories that already contain . It does NOT add the mixin + # to directories that don't already have it — adopt a mixin in a new component + # by copying it in manually first as it needs to be an explicit choice, then + # future `update-all` runs will keep it in sync. + # + # `.terraform/` and `.terragrunt-cache/` are pruned so we don't overwrite the + # mixin inside vendored child-module copies that Terraform/Terragrunt drop on + # disk during init. + desc: "Update (by overwriting) every existing copy of a mixin under ./components/ with the current template. Does not add the mixin to directories that don't already have it. Example: `task mixins:update-all -- context.tf`." + dir: "{{.USER_WORKING_DIR}}" preconditions: - sh: test -f ./mixins/{{.CLI_ARGS}} msg: "File does not exist: ./mixins/{{.CLI_ARGS}}" - - sh: test -d ./components && [ "$(ls -A ./components)" ] - msg: ./components/ directory is empty or does not exist. + - sh: test -d ./components + msg: ./components/ directory does not exist. sources: - ./mixins/{{.CLI_ARGS}} - generates: - - ./components/*/{{.CLI_ARGS}} cmds: - cmd: | - for comp in ./components/*; do - cp -v ./mixins/{{.CLI_ARGS}} $comp/{{.CLI_ARGS}} - done; + find ./components -type d \( -name .terraform -o -name .terragrunt-cache \) -prune -o -type f -name {{.CLI_ARGS}} -print | while IFS= read -r dest; do + cp -v ./mixins/{{.CLI_ARGS}} "$dest" + done silent: true pull-sops: From 2fead6bb756e4c251bf25a4d1b78b93f247640ef Mon Sep 17 00:00:00 2001 From: oycyc Date: Sun, 17 May 2026 11:15:32 -0400 Subject: [PATCH 2/6] fix(mixins): drop ./components/ directory precondition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The remaining precondition for `mixins:update-all` is checking the source template exists. The `./components/` directory check is unnecessary — if it's missing, `find` simply produces no matches and the task no-ops cleanly, which is the correct behavior. Removing the check also lets the task succeed in repos that haven't created a `./components/` directory yet. --- lib/mixins/Taskfile.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/mixins/Taskfile.yml b/lib/mixins/Taskfile.yml index 7cf3607..6af7f8f 100644 --- a/lib/mixins/Taskfile.yml +++ b/lib/mixins/Taskfile.yml @@ -19,8 +19,6 @@ tasks: preconditions: - sh: test -f ./mixins/{{.CLI_ARGS}} msg: "File does not exist: ./mixins/{{.CLI_ARGS}}" - - sh: test -d ./components - msg: ./components/ directory does not exist. sources: - ./mixins/{{.CLI_ARGS}} cmds: From a0305e7a4245ea8641e6d2b3ef68e58938d3927b Mon Sep 17 00:00:00 2001 From: oycyc Date: Sun, 17 May 2026 11:29:13 -0400 Subject: [PATCH 3/6] feat(mixins): search recursively from the user's working directory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the hard-coded `./components/` scope with a recursive search from the current working directory. Existing copies of the mixin are discovered wherever they live in the consuming repo, so a single `update-all` run keeps the mixin in sync across every consumer — root modules, in-repo child modules, and anything else that has opted in by having the file on disk. Motivation: not every consumer repo follows a `./components/` layout, and even repos that do may also have in-repo child modules that consume the same mixin (e.g. `context.tf` from CloudPosse null-label). Hard-coding `./components/` left those out. The source `./mixins/` directory is added to the prune list so we never descend into it and accidentally copy the source onto itself. `.terraform/` and `.terragrunt-cache/` continue to be pruned to avoid vendored child-module copies. --- lib/mixins/Taskfile.yml | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/lib/mixins/Taskfile.yml b/lib/mixins/Taskfile.yml index 6af7f8f..d2ed678 100644 --- a/lib/mixins/Taskfile.yml +++ b/lib/mixins/Taskfile.yml @@ -2,19 +2,25 @@ version: "3" tasks: update-all: - # Updates (by overwriting) every existing copy of the named mixin under - # ./components/ with the current ./mixins/ template. + # Updates (by overwriting) every existing copy of the named mixin found + # recursively under the user's working directory with the current + # ./mixins/ template. # # Directories opt in to a mixin by having a copy of it on disk; this task only - # touches directories that already contain . It does NOT add the mixin - # to directories that don't already have it — adopt a mixin in a new component - # by copying it in manually first as it needs to be an explicit choice, then - # future `update-all` runs will keep it in sync. + # touches files that already exist. It does NOT add the mixin to directories + # that don't already have it — adopt a mixin in a new place by copying it in + # manually first as it needs to be an explicit choice, then future + # `update-all` runs will keep it in sync. # - # `.terraform/` and `.terragrunt-cache/` are pruned so we don't overwrite the - # mixin inside vendored child-module copies that Terraform/Terragrunt drop on - # disk during init. - desc: "Update (by overwriting) every existing copy of a mixin under ./components/ with the current template. Does not add the mixin to directories that don't already have it. Example: `task mixins:update-all -- context.tf`." + # This intentionally searches the whole working directory so a single run + # keeps the mixin in sync across every consumer in the repo (root modules, + # in-repo child modules, etc.). + # + # `.terraform/`, `.terragrunt-cache/`, and the source `./mixins/` directory + # are pruned so we don't overwrite the mixin inside vendored child-module + # copies that Terraform/Terragrunt drop on disk during init, and we don't + # try to copy the source mixin onto itself. + desc: "Update (by overwriting) every existing copy of a mixin found recursively under the current directory with the current template. Picks up every consumer in the repo (root modules, in-repo child modules, etc.). Does not add the mixin to directories that don't already have it. Example: `task mixins:update-all -- context.tf`." dir: "{{.USER_WORKING_DIR}}" preconditions: - sh: test -f ./mixins/{{.CLI_ARGS}} @@ -23,7 +29,7 @@ tasks: - ./mixins/{{.CLI_ARGS}} cmds: - cmd: | - find ./components -type d \( -name .terraform -o -name .terragrunt-cache \) -prune -o -type f -name {{.CLI_ARGS}} -print | while IFS= read -r dest; do + find . \( -type d \( -name .terraform -o -name .terragrunt-cache \) -o -path ./mixins \) -prune -o -type f -name {{.CLI_ARGS}} -print | while IFS= read -r dest; do cp -v ./mixins/{{.CLI_ARGS}} "$dest" done silent: true From 9ba5d7f307df01efeada07191baa6e86025702a6 Mon Sep 17 00:00:00 2001 From: oycyc Date: Sun, 17 May 2026 11:35:38 -0400 Subject: [PATCH 4/6] redundnat comments --- lib/mixins/Taskfile.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/mixins/Taskfile.yml b/lib/mixins/Taskfile.yml index d2ed678..57cfdbb 100644 --- a/lib/mixins/Taskfile.yml +++ b/lib/mixins/Taskfile.yml @@ -9,8 +9,7 @@ tasks: # Directories opt in to a mixin by having a copy of it on disk; this task only # touches files that already exist. It does NOT add the mixin to directories # that don't already have it — adopt a mixin in a new place by copying it in - # manually first as it needs to be an explicit choice, then future - # `update-all` runs will keep it in sync. + # first as it needs to be an explicit choice. # # This intentionally searches the whole working directory so a single run # keeps the mixin in sync across every consumer in the repo (root modules, From 78920b6ab83e294b08a448ae359e6e8a5d702ebe Mon Sep 17 00:00:00 2001 From: oycyc Date: Sun, 17 May 2026 11:47:57 -0400 Subject: [PATCH 5/6] refactor(mixins): resolve source via git repo root, scope search by cwd MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The recursive search remains user-cwd-relative — invoking the task from a subdirectory still scopes the update to that subtree — but the source mixin path no longer depends on cwd. It's resolved from `/mixins/` via `git rev-parse --show-toplevel`, so the task works from any directory inside the repo without the precondition or `cp` source path breaking. Also cleans up the long single-line `find` into a multi-line pipeline with explicit prune/match clauses, factors repeated paths into `vars.MIXIN_SRC`, and tightens the desc. --- lib/mixins/Taskfile.yml | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/lib/mixins/Taskfile.yml b/lib/mixins/Taskfile.yml index 57cfdbb..b2c11ed 100644 --- a/lib/mixins/Taskfile.yml +++ b/lib/mixins/Taskfile.yml @@ -3,8 +3,8 @@ version: "3" tasks: update-all: # Updates (by overwriting) every existing copy of the named mixin found - # recursively under the user's working directory with the current - # ./mixins/ template. + # recursively under the user's current directory with the current + # /mixins/ template. # # Directories opt in to a mixin by having a copy of it on disk; this task only # touches files that already exist. It does NOT add the mixin to directories @@ -19,18 +19,26 @@ tasks: # are pruned so we don't overwrite the mixin inside vendored child-module # copies that Terraform/Terragrunt drop on disk during init, and we don't # try to copy the source mixin onto itself. - desc: "Update (by overwriting) every existing copy of a mixin found recursively under the current directory with the current template. Picks up every consumer in the repo (root modules, in-repo child modules, etc.). Does not add the mixin to directories that don't already have it. Example: `task mixins:update-all -- context.tf`." + desc: "Update (by overwriting) every existing copy of a mixin found recursively under the current directory with the current template. Picks up every consumer in the subtree. Does not add the mixin to directories that don't already have it. Example: `task mixins:update-all -- context.tf`." dir: "{{.USER_WORKING_DIR}}" + vars: + REPO_ROOT: + sh: git rev-parse --show-toplevel + MIXIN_SRC: "{{.REPO_ROOT}}/mixins/{{.CLI_ARGS}}" preconditions: - - sh: test -f ./mixins/{{.CLI_ARGS}} - msg: "File does not exist: ./mixins/{{.CLI_ARGS}}" + - sh: test -f {{.MIXIN_SRC}} + msg: "File does not exist: {{.MIXIN_SRC}}" sources: - - ./mixins/{{.CLI_ARGS}} + - "{{.MIXIN_SRC}}" cmds: - cmd: | - find . \( -type d \( -name .terraform -o -name .terragrunt-cache \) -o -path ./mixins \) -prune -o -type f -name {{.CLI_ARGS}} -print | while IFS= read -r dest; do - cp -v ./mixins/{{.CLI_ARGS}} "$dest" - done + find . \ + \( -type d \( -name .terraform -o -name .terragrunt-cache \) \ + -o -path ./mixins \) -prune \ + -o -type f -name {{.CLI_ARGS}} -print \ + | while IFS= read -r dest; do + cp -v {{.MIXIN_SRC}} "$dest" + done silent: true pull-sops: From bfe1415adcaeb526e8d222250793c2c256c24279 Mon Sep 17 00:00:00 2001 From: oycyc Date: Sun, 17 May 2026 11:52:02 -0400 Subject: [PATCH 6/6] remove redundant comments --- lib/mixins/Taskfile.yml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/mixins/Taskfile.yml b/lib/mixins/Taskfile.yml index b2c11ed..b060bf4 100644 --- a/lib/mixins/Taskfile.yml +++ b/lib/mixins/Taskfile.yml @@ -8,17 +8,13 @@ tasks: # # Directories opt in to a mixin by having a copy of it on disk; this task only # touches files that already exist. It does NOT add the mixin to directories - # that don't already have it — adopt a mixin in a new place by copying it in - # first as it needs to be an explicit choice. + # that don't already have it — adopt a mixin by copying it in, as it's an explicit choice. # # This intentionally searches the whole working directory so a single run - # keeps the mixin in sync across every consumer in the repo (root modules, - # in-repo child modules, etc.). + # keeps the mixin in sync across every consumer in the repo. # # `.terraform/`, `.terragrunt-cache/`, and the source `./mixins/` directory - # are pruned so we don't overwrite the mixin inside vendored child-module - # copies that Terraform/Terragrunt drop on disk during init, and we don't - # try to copy the source mixin onto itself. + # are pruned so we don't overwrite the mixin inside vendored modules. desc: "Update (by overwriting) every existing copy of a mixin found recursively under the current directory with the current template. Picks up every consumer in the subtree. Does not add the mixin to directories that don't already have it. Example: `task mixins:update-all -- context.tf`." dir: "{{.USER_WORKING_DIR}}" vars: