devs-common 3.3.0__tar.gz → 3.3.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.
Files changed (42) hide show
  1. {devs_common-3.3.0 → devs_common-3.3.1}/PKG-INFO +1 -1
  2. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common/core/container.py +109 -21
  3. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common.egg-info/PKG-INFO +1 -1
  4. {devs_common-3.3.0 → devs_common-3.3.1}/pyproject.toml +1 -1
  5. {devs_common-3.3.0 → devs_common-3.3.1}/LICENSE +0 -0
  6. {devs_common-3.3.0 → devs_common-3.3.1}/README.md +0 -0
  7. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common/__init__.py +0 -0
  8. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common/config.py +0 -0
  9. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common/core/__init__.py +0 -0
  10. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common/core/project.py +0 -0
  11. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common/core/workspace.py +0 -0
  12. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common/devs_config.py +0 -0
  13. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common/exceptions.py +0 -0
  14. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common/templates/Dockerfile +0 -0
  15. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common/templates/devcontainer.json +0 -0
  16. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common/templates/scripts/copy-ssh.sh +0 -0
  17. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common/templates/scripts/post-create-wrapper.sh +0 -0
  18. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common/templates/scripts/python-venv-integration.sh +0 -0
  19. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common/templates/scripts/screenshot-webapp.sh +0 -0
  20. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common/templates/scripts/setup-workspace.sh +0 -0
  21. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common/templates/scripts/start-tunnel.sh +0 -0
  22. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common/templates/scripts/tunnel-status.sh +0 -0
  23. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common/templates/scripts/webapp-screenshot.py +0 -0
  24. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common/templates/sudo-scripts/init-firewall.sh +0 -0
  25. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common/templates/sudo-scripts/setup-devs-env.sh +0 -0
  26. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common/templates/sudo-scripts/setup-vscode-cache.sh +0 -0
  27. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common/templates/sudo-scripts/start-services.sh +0 -0
  28. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common/templates/vscode-settings-reference.md +0 -0
  29. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common/utils/__init__.py +0 -0
  30. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common/utils/config_hash.py +0 -0
  31. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common/utils/console.py +0 -0
  32. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common/utils/devcontainer.py +0 -0
  33. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common/utils/devcontainer_template.py +0 -0
  34. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common/utils/docker_client.py +0 -0
  35. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common/utils/file_utils.py +0 -0
  36. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common/utils/git_utils.py +0 -0
  37. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common/utils/repo_cache.py +0 -0
  38. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common.egg-info/SOURCES.txt +0 -0
  39. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common.egg-info/dependency_links.txt +0 -0
  40. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common.egg-info/requires.txt +0 -0
  41. {devs_common-3.3.0 → devs_common-3.3.1}/devs_common.egg-info/top_level.txt +0 -0
  42. {devs_common-3.3.0 → devs_common-3.3.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devs-common
3
- Version: 3.3.0
3
+ Version: 3.3.1
4
4
  Summary: Shared utilities for devs package ecosystem
5
5
  Author: Dan Lester
6
6
  License-Expression: MIT
@@ -908,6 +908,22 @@ class ContainerManager:
908
908
  console.print("[yellow]Authentication was cancelled or failed.[/yellow]")
909
909
  return False
910
910
 
911
+ def _kill_tunnel_processes(self, container_name: str) -> None:
912
+ """Kill all tunnel processes in the container.
913
+
914
+ Uses pkill to ensure detached shell wrappers and tunnel processes
915
+ are fully terminated, not just the service-level shutdown that
916
+ 'code tunnel kill' provides.
917
+ """
918
+ subprocess.run(
919
+ ['docker', 'exec', container_name, '/usr/local/bin/code', 'tunnel', 'kill'],
920
+ capture_output=True
921
+ )
922
+ subprocess.run(
923
+ ['docker', 'exec', container_name, 'pkill', '-f', 'code tunnel'],
924
+ capture_output=True
925
+ )
926
+
911
927
  def _start_and_poll_tunnel(self, container_name: str, dev_name: str, tunnel_cmd: str,
912
928
  log_file: str, vscode_cmd: str, web_url: str, debug: bool = False) -> bool:
913
929
  """Start a detached tunnel and poll the log for startup confirmation.
@@ -915,6 +931,14 @@ class ContainerManager:
915
931
  Returns:
916
932
  True if tunnel started (or timed out), False if auth is needed.
917
933
  """
934
+ # Kill any stale tunnel processes and clear the log before starting
935
+ self._kill_tunnel_processes(container_name)
936
+ subprocess.run(
937
+ ['docker', 'exec', container_name, '/bin/sh', '-c', f'> {log_file}'],
938
+ capture_output=True
939
+ )
940
+ time.sleep(1)
941
+
918
942
  subprocess.run(
919
943
  ['docker', 'exec', '-d', container_name,
920
944
  '/bin/sh', '-c', f'{tunnel_cmd} > {log_file} 2>&1'],
@@ -942,10 +966,7 @@ class ContainerManager:
942
966
  return True
943
967
 
944
968
  if 'log in' in output.lower() or 'device' in output.lower() or 'invalid' in output.lower():
945
- subprocess.run(
946
- ['docker', 'exec', container_name, '/usr/local/bin/code', 'tunnel', 'kill'],
947
- capture_output=True
948
- )
969
+ self._kill_tunnel_processes(container_name)
949
970
  return False
950
971
 
951
972
  if debug and output.strip():
@@ -1078,14 +1099,92 @@ class ContainerManager:
1078
1099
 
1079
1100
  result = subprocess.run(cmd, capture_output=True, text=True)
1080
1101
 
1102
+ # Get auth info (logged in user/provider)
1103
+ user_result = subprocess.run(
1104
+ ['docker', 'exec', container_name,
1105
+ '/usr/local/bin/code', 'tunnel', 'user', 'show'],
1106
+ capture_output=True, text=True
1107
+ )
1108
+ auth_info = user_result.stdout.strip() if user_result.returncode == 0 else None
1109
+
1081
1110
  if result.returncode == 0:
1082
- return True, result.stdout.strip()
1111
+ raw = result.stdout.strip()
1112
+ return True, self._format_tunnel_status(raw, dev_name, auth_info)
1083
1113
  else:
1084
1114
  return False, result.stderr.strip() if result.stderr else "No tunnel running"
1085
1115
 
1086
1116
  except (DockerError, subprocess.SubprocessError) as e:
1087
1117
  raise ContainerError(f"Failed to get tunnel status in {dev_name}: {e}")
1088
1118
 
1119
+ def _format_tunnel_status(self, raw_json: str, dev_name: str, auth_info: Optional[str] = None) -> str:
1120
+ """Format tunnel status JSON into human-readable output."""
1121
+ import json
1122
+ try:
1123
+ data = json.loads(raw_json)
1124
+ except json.JSONDecodeError:
1125
+ return raw_json
1126
+
1127
+ tunnel = data.get("tunnel")
1128
+ if not tunnel:
1129
+ lines = ["[dim]● No tunnel running[/dim]"]
1130
+ if auth_info:
1131
+ lines.append(f" Auth: {auth_info}")
1132
+ return "\n".join(lines)
1133
+ name = tunnel.get("name") or "unknown"
1134
+ state = tunnel.get("tunnel", "Unknown")
1135
+ started_at = tunnel.get("started_at")
1136
+ connected_at = tunnel.get("last_connected_at")
1137
+ disconnected_at = tunnel.get("last_disconnected_at")
1138
+ fail_reason = tunnel.get("last_fail_reason")
1139
+
1140
+ lines = []
1141
+
1142
+ # Status line with color
1143
+ if state == "Connected":
1144
+ lines.append(f"[bold green]● Connected[/bold green] ({name})")
1145
+ elif state == "Disconnected":
1146
+ lines.append(f"[bold red]● Disconnected[/bold red] ({name})")
1147
+ else:
1148
+ lines.append(f"[bold yellow]● {state}[/bold yellow] ({name})")
1149
+
1150
+ # Timestamps
1151
+ if started_at:
1152
+ lines.append(f" Started: {self._format_timestamp(started_at)}")
1153
+ if connected_at:
1154
+ lines.append(f" Connected: {self._format_timestamp(connected_at)}")
1155
+ if disconnected_at:
1156
+ lines.append(f" Disconnected: {self._format_timestamp(disconnected_at)}")
1157
+ if fail_reason:
1158
+ lines.append(f" Fail reason: {fail_reason}")
1159
+ if auth_info:
1160
+ lines.append(f" Auth: {auth_info}")
1161
+
1162
+ # Connection info when connected
1163
+ if state == "Connected":
1164
+ tunnel_name = self._get_tunnel_name(dev_name)
1165
+ lines.append("")
1166
+ lines.append("[bold]Connect:[/bold]")
1167
+ lines.append(f" Desktop: code --remote tunnel+{tunnel_name}")
1168
+ lines.append(f" Browser: https://vscode.dev/tunnel/{tunnel_name}")
1169
+
1170
+ return "\n".join(lines)
1171
+
1172
+ @staticmethod
1173
+ def _format_timestamp(ts: str) -> str:
1174
+ """Format an ISO timestamp into a friendly local time string."""
1175
+ from datetime import datetime
1176
+ try:
1177
+ # Handle nanosecond precision by truncating to microseconds
1178
+ if "." in ts:
1179
+ base, frac = ts.split(".")
1180
+ frac = frac.rstrip("Z")[:6]
1181
+ ts = f"{base}.{frac}Z"
1182
+ dt = datetime.fromisoformat(ts.replace("Z", "+00:00"))
1183
+ local_dt = dt.astimezone()
1184
+ return local_dt.strftime("%H:%M:%S %Z")
1185
+ except (ValueError, TypeError):
1186
+ return ts
1187
+
1089
1188
  def kill_tunnel(self, dev_name: str, workspace_dir: Path, debug: bool = False, live: bool = False, extra_env: Optional[Dict[str, str]] = None) -> bool:
1090
1189
  """Kill a running VS Code tunnel in the container.
1091
1190
 
@@ -1108,24 +1207,13 @@ class ContainerManager:
1108
1207
  dev_name, workspace_dir, debug=debug, live=live, extra_env=extra_env, reuse_existing=True
1109
1208
  )
1110
1209
 
1111
- # Kill the tunnel
1112
- cmd = [
1113
- 'docker', 'exec',
1114
- container_name,
1115
- '/usr/local/bin/code', 'tunnel', 'kill'
1116
- ]
1117
-
1210
+ # Kill the tunnel (both service-level and process-level)
1118
1211
  if debug:
1119
- console.print(f"[dim]Running: {' '.join(cmd)}[/dim]")
1120
-
1121
- result = subprocess.run(cmd, capture_output=True, text=True)
1212
+ console.print(f"[dim]Killing tunnel processes in {container_name}[/dim]")
1122
1213
 
1123
- if result.returncode == 0:
1124
- console.print(f" Tunnel killed in: {dev_name}")
1125
- return True
1126
- else:
1127
- console.print(f" No tunnel to kill in: {dev_name}")
1128
- return False
1214
+ self._kill_tunnel_processes(container_name)
1215
+ console.print(f" Tunnel killed in: {dev_name}")
1216
+ return True
1129
1217
 
1130
1218
  except (DockerError, subprocess.SubprocessError) as e:
1131
1219
  raise ContainerError(f"Failed to kill tunnel in {dev_name}: {e}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devs-common
3
- Version: 3.3.0
3
+ Version: 3.3.1
4
4
  Summary: Shared utilities for devs package ecosystem
5
5
  Author: Dan Lester
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "devs-common"
7
- version = "3.3.0"
7
+ version = "3.3.1"
8
8
  description = "Shared utilities for devs package ecosystem"
9
9
  authors = [
10
10
  {name = "Dan Lester"},
File without changes
File without changes
File without changes