deepctl-cmd-init 0.1.0__tar.gz → 0.1.1__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.
- {deepctl_cmd_init-0.1.0 → deepctl_cmd_init-0.1.1}/PKG-INFO +1 -1
- {deepctl_cmd_init-0.1.0 → deepctl_cmd_init-0.1.1}/pyproject.toml +1 -1
- {deepctl_cmd_init-0.1.0 → deepctl_cmd_init-0.1.1}/src/deepctl_cmd_init/command.py +1 -1
- {deepctl_cmd_init-0.1.0 → deepctl_cmd_init-0.1.1}/src/deepctl_cmd_init/lifecycle.py +139 -7
- {deepctl_cmd_init-0.1.0 → deepctl_cmd_init-0.1.1}/src/deepctl_cmd_init.egg-info/PKG-INFO +1 -1
- {deepctl_cmd_init-0.1.0 → deepctl_cmd_init-0.1.1}/README.md +0 -0
- {deepctl_cmd_init-0.1.0 → deepctl_cmd_init-0.1.1}/setup.cfg +0 -0
- {deepctl_cmd_init-0.1.0 → deepctl_cmd_init-0.1.1}/src/deepctl_cmd_init/__init__.py +0 -0
- {deepctl_cmd_init-0.1.0 → deepctl_cmd_init-0.1.1}/src/deepctl_cmd_init/models.py +0 -0
- {deepctl_cmd_init-0.1.0 → deepctl_cmd_init-0.1.1}/src/deepctl_cmd_init/templates_api.py +0 -0
- {deepctl_cmd_init-0.1.0 → deepctl_cmd_init-0.1.1}/src/deepctl_cmd_init.egg-info/SOURCES.txt +0 -0
- {deepctl_cmd_init-0.1.0 → deepctl_cmd_init-0.1.1}/src/deepctl_cmd_init.egg-info/dependency_links.txt +0 -0
- {deepctl_cmd_init-0.1.0 → deepctl_cmd_init-0.1.1}/src/deepctl_cmd_init.egg-info/entry_points.txt +0 -0
- {deepctl_cmd_init-0.1.0 → deepctl_cmd_init-0.1.1}/src/deepctl_cmd_init.egg-info/requires.txt +0 -0
- {deepctl_cmd_init-0.1.0 → deepctl_cmd_init-0.1.1}/src/deepctl_cmd_init.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "deepctl-cmd-init"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.1" # x-release-please-version
|
|
8
8
|
description = "Init command for deepctl — scaffold Deepgram starter apps"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -208,7 +208,7 @@ class InitCommand(BaseCommand):
|
|
|
208
208
|
api_key = auth_manager.get_api_key()
|
|
209
209
|
if api_key:
|
|
210
210
|
lifecycle.inject_env(dest, api_key)
|
|
211
|
-
except AuthenticationError as exc:
|
|
211
|
+
except (AuthenticationError, OSError) as exc:
|
|
212
212
|
console.print(
|
|
213
213
|
f"[yellow]Could not inject Deepgram credentials:[/yellow] {exc}"
|
|
214
214
|
)
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import os
|
|
5
6
|
import shutil
|
|
6
7
|
import subprocess
|
|
7
8
|
from typing import TYPE_CHECKING
|
|
8
9
|
|
|
10
|
+
from deepctl_core.output import print_hint, print_info, print_warning
|
|
9
11
|
from rich.console import Console
|
|
10
12
|
|
|
11
13
|
if TYPE_CHECKING:
|
|
@@ -76,7 +78,9 @@ def clone_template(github_url: str, target_dir: Path) -> None:
|
|
|
76
78
|
raise RuntimeError(f"Directory already exists: {target_dir}")
|
|
77
79
|
|
|
78
80
|
result = subprocess.run(
|
|
79
|
-
|
|
81
|
+
# "--" ends git's options: a server-supplied URL that starts with "-"
|
|
82
|
+
# is taken as the repository, never as a git option.
|
|
83
|
+
["git", "clone", "--", github_url, str(target_dir)],
|
|
80
84
|
capture_output=True,
|
|
81
85
|
text=True,
|
|
82
86
|
)
|
|
@@ -87,19 +91,148 @@ def clone_template(github_url: str, target_dir: Path) -> None:
|
|
|
87
91
|
# ── .env injection ────────────────────────────────────────────────────────────
|
|
88
92
|
|
|
89
93
|
|
|
94
|
+
#: Lines in a .gitignore that already keep ``.env`` out of git. Used only
|
|
95
|
+
#: when git itself isn't available to ask.
|
|
96
|
+
_GITIGNORE_ENV_PATTERNS = {".env", "/.env", ".env*", "/.env*", "*.env"}
|
|
97
|
+
|
|
98
|
+
_NOFOLLOW = getattr(os, "O_NOFOLLOW", 0)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _write_private(path: Path, text: str) -> None:
|
|
102
|
+
"""Write ``text`` to ``path`` readable by the owner only (0600).
|
|
103
|
+
|
|
104
|
+
The file is created 0600 (no window where the umask applies), and an
|
|
105
|
+
existing file is narrowed to 0600 before its new content is written.
|
|
106
|
+
"""
|
|
107
|
+
fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC | _NOFOLLOW, 0o600)
|
|
108
|
+
try:
|
|
109
|
+
if os.name == "posix":
|
|
110
|
+
os.fchmod(fd, 0o600)
|
|
111
|
+
os.write(fd, text.encode())
|
|
112
|
+
finally:
|
|
113
|
+
os.close(fd)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _restrict_to_owner(path: Path) -> None:
|
|
117
|
+
"""Narrow an existing file that holds a key to owner-only (POSIX)."""
|
|
118
|
+
if os.name != "posix":
|
|
119
|
+
return
|
|
120
|
+
fd = os.open(path, os.O_RDONLY | _NOFOLLOW)
|
|
121
|
+
try:
|
|
122
|
+
os.fchmod(fd, 0o600)
|
|
123
|
+
finally:
|
|
124
|
+
os.close(fd)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _git(target_dir: Path, *args: str) -> int | None:
|
|
128
|
+
"""Exit code of ``git -C target_dir args``, or None if git can't run."""
|
|
129
|
+
if shutil.which("git") is None:
|
|
130
|
+
return None
|
|
131
|
+
try:
|
|
132
|
+
# Only this project's repository: an inherited GIT_DIR, GIT_WORK_TREE
|
|
133
|
+
# or GIT_INDEX_FILE would point git at another one.
|
|
134
|
+
env = {k: v for k, v in os.environ.items() if not k.startswith("GIT_")}
|
|
135
|
+
result = subprocess.run(
|
|
136
|
+
["git", "-C", str(target_dir), *args],
|
|
137
|
+
capture_output=True,
|
|
138
|
+
text=True,
|
|
139
|
+
env=env,
|
|
140
|
+
)
|
|
141
|
+
except OSError:
|
|
142
|
+
return None
|
|
143
|
+
return result.returncode
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _env_ignored(target_dir: Path) -> bool | None:
|
|
147
|
+
"""git's answer to "is .env ignored?", or None when git can't say."""
|
|
148
|
+
rc = _git(target_dir, "check-ignore", "-q", ".env")
|
|
149
|
+
if rc in (0, 1):
|
|
150
|
+
return rc == 0
|
|
151
|
+
return None
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _append_env_line(gitignore: Path) -> None:
|
|
155
|
+
"""Append ``.env`` as the last line: the last matching pattern wins,
|
|
156
|
+
so it also overrides an earlier ``!.env``."""
|
|
157
|
+
content = gitignore.read_bytes() if gitignore.exists() else b""
|
|
158
|
+
newline = b"\r\n" if b"\r\n" in content else b"\n"
|
|
159
|
+
sep = newline if content and not content.endswith(b"\n") else b""
|
|
160
|
+
with open(gitignore, "ab") as f:
|
|
161
|
+
f.write(sep + b".env" + newline)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def ensure_env_gitignored(target_dir: Path) -> None:
|
|
165
|
+
"""Keep ``.env`` out of git in ``target_dir`` and say what happened.
|
|
166
|
+
|
|
167
|
+
Asks git (``check-ignore``) when it can, so negations and order count,
|
|
168
|
+
and warns when the template already tracks ``.env``: ignoring a
|
|
169
|
+
tracked file doesn't stop a commit from including it.
|
|
170
|
+
"""
|
|
171
|
+
gitignore = target_dir / ".gitignore"
|
|
172
|
+
tracked = _git(target_dir, "ls-files", "--error-unmatch", ".env") == 0
|
|
173
|
+
if tracked:
|
|
174
|
+
# Ignoring a tracked file changes nothing; untracking it does.
|
|
175
|
+
print_warning(
|
|
176
|
+
"This template tracks .env in git, so a commit would include your API key.",
|
|
177
|
+
stderr=True,
|
|
178
|
+
)
|
|
179
|
+
print_hint("Before you commit, run: git rm --cached .env")
|
|
180
|
+
return
|
|
181
|
+
if gitignore.is_symlink():
|
|
182
|
+
# A cloned repo controls its links: never write through one.
|
|
183
|
+
print_warning(
|
|
184
|
+
"Did not add .env to .gitignore: it is a symbolic link. "
|
|
185
|
+
"Make sure git ignores .env before you commit.",
|
|
186
|
+
stderr=True,
|
|
187
|
+
)
|
|
188
|
+
return
|
|
189
|
+
ignored = _env_ignored(target_dir)
|
|
190
|
+
if ignored is None:
|
|
191
|
+
lines = (
|
|
192
|
+
{ln.strip() for ln in gitignore.read_text().splitlines()}
|
|
193
|
+
if gitignore.exists()
|
|
194
|
+
else set()
|
|
195
|
+
)
|
|
196
|
+
if not lines & _GITIGNORE_ENV_PATTERNS:
|
|
197
|
+
_append_env_line(gitignore)
|
|
198
|
+
print_info(_ADDED, stderr=True)
|
|
199
|
+
elif not ignored:
|
|
200
|
+
_append_env_line(gitignore)
|
|
201
|
+
if _env_ignored(target_dir):
|
|
202
|
+
print_info(_ADDED, stderr=True)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
_ADDED = "Added .env to .gitignore to keep your API key out of git."
|
|
206
|
+
|
|
207
|
+
|
|
90
208
|
def inject_env(target_dir: Path, api_key: str) -> None:
|
|
91
209
|
"""Create or update DEEPGRAM_API_KEY in .env inside target_dir.
|
|
92
210
|
|
|
211
|
+
The .env file is owner-only (0600) and kept out of git, since it holds
|
|
212
|
+
the API key.
|
|
213
|
+
|
|
93
214
|
Args:
|
|
94
215
|
target_dir: Directory containing the cloned template.
|
|
95
216
|
api_key: Deepgram API key to inject.
|
|
96
217
|
"""
|
|
97
218
|
env_path = target_dir / ".env"
|
|
98
219
|
|
|
220
|
+
if env_path.is_symlink():
|
|
221
|
+
# A cloned repo controls its links: never write the key through one.
|
|
222
|
+
print_warning(
|
|
223
|
+
"Not writing your API key to .env: it is a symbolic link.", stderr=True
|
|
224
|
+
)
|
|
225
|
+
print_hint(
|
|
226
|
+
"If you trust the file it links to, add DEEPGRAM_API_KEY there yourself."
|
|
227
|
+
)
|
|
228
|
+
return
|
|
229
|
+
|
|
99
230
|
if env_path.exists():
|
|
100
231
|
content = env_path.read_text()
|
|
101
232
|
if "DEEPGRAM_API_KEY=" in content and "DEEPGRAM_API_KEY=$" not in content:
|
|
233
|
+
_restrict_to_owner(env_path)
|
|
102
234
|
console.print("[dim]DEEPGRAM_API_KEY already set in .env[/dim]")
|
|
235
|
+
ensure_env_gitignored(target_dir)
|
|
103
236
|
return
|
|
104
237
|
# Replace placeholder or append
|
|
105
238
|
if "DEEPGRAM_API_KEY=" in content:
|
|
@@ -110,16 +243,15 @@ def inject_env(target_dir: Path, api_key: str) -> None:
|
|
|
110
243
|
else ln
|
|
111
244
|
for ln in lines
|
|
112
245
|
]
|
|
113
|
-
env_path
|
|
246
|
+
_write_private(env_path, "\n".join(new_lines) + "\n")
|
|
114
247
|
else:
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
f.write("\n")
|
|
118
|
-
f.write(f"DEEPGRAM_API_KEY={api_key}\n")
|
|
248
|
+
sep = "" if content.endswith("\n") or not content else "\n"
|
|
249
|
+
_write_private(env_path, f"{content}{sep}DEEPGRAM_API_KEY={api_key}\n")
|
|
119
250
|
else:
|
|
120
|
-
env_path
|
|
251
|
+
_write_private(env_path, f"DEEPGRAM_API_KEY={api_key}\n")
|
|
121
252
|
|
|
122
253
|
console.print("[green]✓[/green] Injected DEEPGRAM_API_KEY into .env")
|
|
254
|
+
ensure_env_gitignored(target_dir)
|
|
123
255
|
|
|
124
256
|
|
|
125
257
|
# ── make init ─────────────────────────────────────────────────────────────────
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{deepctl_cmd_init-0.1.0 → deepctl_cmd_init-0.1.1}/src/deepctl_cmd_init.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{deepctl_cmd_init-0.1.0 → deepctl_cmd_init-0.1.1}/src/deepctl_cmd_init.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{deepctl_cmd_init-0.1.0 → deepctl_cmd_init-0.1.1}/src/deepctl_cmd_init.egg-info/requires.txt
RENAMED
|
File without changes
|
{deepctl_cmd_init-0.1.0 → deepctl_cmd_init-0.1.1}/src/deepctl_cmd_init.egg-info/top_level.txt
RENAMED
|
File without changes
|