csrd-utils 0.5.4__tar.gz → 0.5.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/PKG-INFO +1 -1
  2. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/pyproject.toml +1 -1
  3. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/__main__.py +47 -44
  4. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/compose/operations.py +31 -1
  5. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/.gitignore +0 -0
  6. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/README.md +0 -0
  7. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/AGENTS.md +0 -0
  8. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/__init__.py +0 -0
  9. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/audit.py +0 -0
  10. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/compose/__init__.py +0 -0
  11. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/compose/augments.py +0 -0
  12. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/compose/git.py +0 -0
  13. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/compose/infra.py +0 -0
  14. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/compose/loader.py +0 -0
  15. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/compose/presets.py +0 -0
  16. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/compose/renderer.py +0 -0
  17. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/compose/scaffolder.py +0 -0
  18. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/compose/service_renderers.py +0 -0
  19. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/compose/yaml_editor.py +0 -0
  20. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/doctor.py +0 -0
  21. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/generate/__init__.py +0 -0
  22. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/generate/handlers.py +0 -0
  23. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/generate/helpers.py +0 -0
  24. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/generate/menu.py +0 -0
  25. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/models/__init__.py +0 -0
  26. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/models/base.py +0 -0
  27. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/models/spec.py +0 -0
  28. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/models/types.py +0 -0
  29. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/__init__.py +0 -0
  30. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/__init__.py +0 -0
  31. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/auth-passthrough/__init__.py +0 -0
  32. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/auth-passthrough/delegates/__init__.py +0 -0
  33. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/auth-passthrough/delegates/auth_delegate.py +0 -0
  34. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/auth-passthrough/dependencies/__init__.py +0 -0
  35. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/auth-passthrough/dependencies/auth_passthrough.py +0 -0
  36. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/auth-passthrough/models/__init__.py +0 -0
  37. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/auth-passthrough/models/auth_passthrough.py +0 -0
  38. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/auth-passthrough/views/__init__.py +0 -0
  39. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/auth-passthrough/views/auth_passthrough_view.py +0 -0
  40. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/caching/dependencies/cache.py.template +0 -0
  41. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/caching/views/cache_view.py.template +0 -0
  42. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/celery-dispatcher/celery_client.py.template +0 -0
  43. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/celery-dispatcher/models/an/__init__.py +0 -0
  44. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/celery-dispatcher/models/an/tasks.py.template +0 -0
  45. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/celery-dispatcher/models/tasks.py.template +0 -0
  46. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/celery-dispatcher/views/tasks_view.py.template +0 -0
  47. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/celery-worker/celery_app.py.template +0 -0
  48. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/celery-worker/tasks/__init__.py +0 -0
  49. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/celery-worker/tasks/example.py.template +0 -0
  50. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/crud-scaffold/dependencies/${entity_name_snake}_repository.py.template +0 -0
  51. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/crud-scaffold/migrations_${entity_name_snake}.py.template +0 -0
  52. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/crud-scaffold/models/${entity_name_snake}.py.template +0 -0
  53. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/crud-scaffold/repositories/${entity_name_snake}_repository.py.template +0 -0
  54. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/crud-scaffold/views/${entity_name_snake}_view.py.template +0 -0
  55. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/db-config/dependencies/db.py.template +0 -0
  56. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/gateway/dependencies/proxy.py.template +0 -0
  57. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/gateway/middleware/auth_guard.py.template +0 -0
  58. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/gateway/views/proxy_view.py.template +0 -0
  59. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/jwt-auth-consumer/dependencies/auth.py.template +0 -0
  60. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/jwt-auth-provider/AUTH.md +0 -0
  61. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/jwt-auth-provider/__init__.py +0 -0
  62. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/jwt-auth-provider/dependencies/__init__.py +0 -0
  63. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/jwt-auth-provider/dependencies/auth.py +0 -0
  64. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/jwt-auth-provider/dependencies/db.py.template +0 -0
  65. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/jwt-auth-provider/dependencies/token_service.py +0 -0
  66. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/jwt-auth-provider/migrations.py.template +0 -0
  67. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/jwt-auth-provider/models/__init__.py +0 -0
  68. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/jwt-auth-provider/models/auth.py +0 -0
  69. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/jwt-auth-provider/models/jwks.py +0 -0
  70. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/jwt-auth-provider/models/users.py +0 -0
  71. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/jwt-auth-provider/repositories/__init__.py +0 -0
  72. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/jwt-auth-provider/repositories/user_repository.py +0 -0
  73. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/jwt-auth-provider/services/__init__.py +0 -0
  74. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/jwt-auth-provider/services/token_service.py +0 -0
  75. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/jwt-auth-provider/views/__init__.py +0 -0
  76. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/jwt-auth-provider/views/auth_view.py +0 -0
  77. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/jwt-auth-provider/views/jwks_view.py +0 -0
  78. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/jwt-auth-provider/views/users_admin_view.py +0 -0
  79. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/jwt-auth-provider/views/users_view.py +0 -0
  80. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/metrics/middleware/metrics.py.template +0 -0
  81. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/metrics/views/metrics_view.py.template +0 -0
  82. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/rabbit-messaging/dependencies/rabbit.py.template +0 -0
  83. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/rabbit-messaging/handlers/example_handler.py.template +0 -0
  84. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/rabbit-messaging/handlers/ping_handler.py.template +0 -0
  85. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/rabbit-messaging/views/messaging_view.py.template +0 -0
  86. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/augments/tracing/middleware/tracing.py.template +0 -0
  87. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/service/cookiecutter.json +0 -0
  88. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/service/{{cookiecutter.__service_name_snake}}/Dockerfile +0 -0
  89. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/service/{{cookiecutter.__service_name_snake}}/README.md +0 -0
  90. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/service/{{cookiecutter.__service_name_snake}}/__init__.py +0 -0
  91. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/service/{{cookiecutter.__service_name_snake}}/dependencies/__init__.py +0 -0
  92. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/service/{{cookiecutter.__service_name_snake}}/models/__init__.py +0 -0
  93. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/service/{{cookiecutter.__service_name_snake}}/requirements.txt +0 -0
  94. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/service/{{cookiecutter.__service_name_snake}}/settings.py +0 -0
  95. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/service/{{cookiecutter.__service_name_snake}}/tests/__init__.py +0 -0
  96. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/service/{{cookiecutter.__service_name_snake}}/tests/conftest.py +0 -0
  97. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/csrd_utils/templates/service/{{cookiecutter.__service_name_snake}}/views/__init__.py +0 -0
  98. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/tui_wizard/__init__.py +0 -0
  99. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/tui_wizard/exceptions.py +0 -0
  100. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/tui_wizard/menu.py +0 -0
  101. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/tui_wizard/models.py +0 -0
  102. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/tui_wizard/prompts.py +0 -0
  103. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/tui_wizard/terminal.py +0 -0
  104. {csrd_utils-0.5.4 → csrd_utils-0.5.6}/src/tui_wizard/wizard.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: csrd-utils
3
- Version: 0.5.4
3
+ Version: 0.5.6
4
4
  Summary: CLI utilities for csrd service generation and feature augmentation
5
5
  Project-URL: Repository, https://github.com/csrd-api/fastapi-common
6
6
  Project-URL: Documentation, https://github.com/csrd-api/fastapi-common/tree/main/packages/utils
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "csrd-utils"
3
- version = "0.5.4"
3
+ version = "0.5.6"
4
4
  description = "CLI utilities for csrd service generation and feature augmentation"
5
5
  license = { text = "MIT" }
6
6
  requires-python = ">=3.12"
@@ -42,48 +42,45 @@ def _parser_shape(
42
42
  return commands, options
43
43
 
44
44
 
45
- def _render_bash_completion(parser: argparse.ArgumentParser) -> str:
45
+ def _render_bash_completion() -> str:
46
+ """Render a dynamic bash completion script.
47
+
48
+ Instead of baking a static word list, the script calls
49
+ ``csrd --_complete`` at tab-time so it always reflects the
50
+ currently installed version's commands and flags.
51
+ """
52
+ return (
53
+ "# bash completion for csrd — dynamic (calls csrd --_complete)\n"
54
+ "_csrd_completion() {\n"
55
+ " local cur words\n"
56
+ ' cur="${COMP_WORDS[COMP_CWORD]}"\n'
57
+ ' words=$(csrd --_complete "${COMP_WORDS[@]:1:COMP_CWORD-1}" 2>/dev/null)\n'
58
+ ' COMPREPLY=( $(compgen -W "$words" -- "$cur") )\n'
59
+ "}\n"
60
+ "complete -F _csrd_completion csrd csrd-utils csrt\n"
61
+ )
62
+
63
+
64
+ def _handle_complete(parser: argparse.ArgumentParser, words: list[str]) -> int:
65
+ """Print completion candidates for the given partial command line.
66
+
67
+ Called by the bash completion script via ``csrd --_complete <words>``.
68
+ """
46
69
  commands, options = _parser_shape(parser)
47
70
 
48
- lines = [
49
- "# bash completion for csrd",
50
- "_csrd_completion() {",
51
- " local cur key token",
52
- ' cur="${COMP_WORDS[COMP_CWORD]}"',
53
- ' key=""',
54
- "",
55
- ' for token in "${COMP_WORDS[@]:1:COMP_CWORD-1}"; do',
56
- ' case "$token" in',
57
- " -*) continue ;;",
58
- " esac",
59
- ' if [[ -n "$key" ]]; then',
60
- ' key="$key $token"',
61
- " else",
62
- ' key="$token"',
63
- " fi",
64
- " done",
65
- "",
66
- ' case "$key" in',
67
- ]
68
-
69
- for chain in sorted(commands.keys()):
70
- key = " ".join(chain)
71
- command_candidates = commands.get(chain, [])
72
- option_candidates = options.get(chain, [])
73
- candidates = " ".join(command_candidates + option_candidates).strip()
74
- if not candidates:
71
+ # Walk the word chain to find the deepest matching subcommand
72
+ chain: tuple[str, ...] = ()
73
+ for word in words:
74
+ if word.startswith("-"):
75
75
  continue
76
- lines.append(f' "{key}") COMPREPLY=( $(compgen -W "{candidates}" -- "$cur") ) ;;')
77
-
78
- lines.extend(
79
- [
80
- " *) COMPREPLY=() ;;",
81
- " esac",
82
- "}",
83
- "complete -F _csrd_completion csrd csrd-utils csrt",
84
- ]
85
- )
86
- return "\n".join(lines) + "\n"
76
+ candidate = (*chain, word)
77
+ if candidate in commands:
78
+ chain = candidate
79
+
80
+ command_candidates = commands.get(chain, [])
81
+ option_candidates = options.get(chain, [])
82
+ print(" ".join(command_candidates + option_candidates))
83
+ return 0
87
84
 
88
85
 
89
86
  # ---------------------------------------------------------------------------
@@ -94,16 +91,17 @@ _COMPLETION_DIR = Path.home() / ".local" / "share" / "bash-completion" / "comple
94
91
  _COMPLETION_FILE = _COMPLETION_DIR / "csrd"
95
92
 
96
93
 
97
- def _install_completion(parser: argparse.ArgumentParser) -> int:
94
+ def _install_completion() -> int:
98
95
  """Write the bash completion script to the user completions directory.
99
96
 
100
97
  bash-completion auto-loads files from ``~/.local/share/bash-completion/completions/``
101
- so no ``.bashrc`` edit is needed.
98
+ so no ``.bashrc`` edit is needed. The script is a thin shim that calls
99
+ ``csrd --_complete`` at tab-time, so it never goes stale after upgrades.
102
100
  """
103
101
 
104
102
  _COMPLETION_DIR.mkdir(parents=True, exist_ok=True)
105
103
  already = _COMPLETION_FILE.is_file()
106
- _COMPLETION_FILE.write_text(_render_bash_completion(parser), encoding="utf-8")
104
+ _COMPLETION_FILE.write_text(_render_bash_completion(), encoding="utf-8")
107
105
  if already:
108
106
  print(f"Updated bash completion at {_COMPLETION_FILE}")
109
107
  else:
@@ -266,6 +264,11 @@ def main() -> int:
266
264
  sys.argv = [sys.argv[0], "compose", "apply", "--output", second, *raw_argv[2:]]
267
265
 
268
266
  parser = _build_parser()
267
+
268
+ # Hidden --_complete for dynamic tab completion (called by the bash shim)
269
+ if raw_argv and raw_argv[0] == "--_complete":
270
+ return _handle_complete(parser, raw_argv[1:])
271
+
269
272
  args = parser.parse_args()
270
273
 
271
274
  # ── generate ──────────────────────────────────────────────────────
@@ -379,10 +382,10 @@ def main() -> int:
379
382
  if args.command == "completion":
380
383
  shell = getattr(args, "completion_shell", None)
381
384
  if shell == "bash":
382
- print(_render_bash_completion(parser), end="")
385
+ print(_render_bash_completion(), end="")
383
386
  return 0
384
387
  if shell == "install":
385
- return _install_completion(parser)
388
+ return _install_completion()
386
389
  if shell == "uninstall":
387
390
  return _uninstall_completion()
388
391
 
@@ -9,6 +9,7 @@ the spec, renders all workspace files, scaffolds services, and optionally
9
9
  initializes a git repository.
10
10
  """
11
11
 
12
+ import re
12
13
  from pathlib import Path
13
14
 
14
15
  from ..models import (
@@ -28,6 +29,32 @@ from .loader import default_spec, load_spec, save_spec, spec_file_path
28
29
  # Workspace rendering (orchestration)
29
30
  # ---------------------------------------------------------------------------
30
31
 
32
+ _ENV_KEY_RE = re.compile(r"^([A-Z][A-Z0-9_]*)=", re.MULTILINE)
33
+
34
+
35
+ def _merge_new_env_vars(env_path: Path, env_example_content: str) -> None:
36
+ """Append env vars from .env.example that are missing in .env.
37
+
38
+ Preserves all existing user values. Only adds new keys with their
39
+ default values so services don't crash after adding augments.
40
+ """
41
+ existing = env_path.read_text(encoding="utf-8")
42
+ existing_keys = set(_ENV_KEY_RE.findall(existing))
43
+
44
+ new_lines: list[str] = []
45
+ for line in env_example_content.splitlines():
46
+ m = _ENV_KEY_RE.match(line)
47
+ if m and m.group(1) not in existing_keys:
48
+ new_lines.append(line)
49
+
50
+ if new_lines:
51
+ # Ensure trailing newline before appending
52
+ if existing and not existing.endswith("\n"):
53
+ existing += "\n"
54
+ existing += "\n# Added by csrd (new augment vars)\n"
55
+ existing += "\n".join(new_lines) + "\n"
56
+ env_path.write_text(existing, encoding="utf-8")
57
+
31
58
 
32
59
  def render_workspace(output_dir: Path, *, git_init: bool = False) -> Path:
33
60
  """Render deterministic workspace files from the current spec.
@@ -74,10 +101,13 @@ def render_workspace(output_dir: Path, *, git_init: bool = False) -> Path:
74
101
 
75
102
  # Seed .env from .env.example only on first render so services can
76
103
  # start immediately. On subsequent renders the user may have set
77
- # strong passwords or API keys — never overwrite those.
104
+ # strong passwords or API keys — never overwrite existing values.
105
+ # However, new keys added by augments/services must be appended.
78
106
  env_path = output_dir / ".env"
79
107
  if not env_path.exists():
80
108
  env_path.write_text(env_example_content, encoding="utf-8")
109
+ else:
110
+ _merge_new_env_vars(env_path, env_example_content)
81
111
  (output_dir / "README.md").write_text(_render_readme(spec), encoding="utf-8")
82
112
  (output_dir / ".gitignore").write_text(_render_gitignore(), encoding="utf-8")
83
113
  (output_dir / ".dockerignore").write_text(_render_dockerignore(), encoding="utf-8")
File without changes
File without changes