devs-common 2.0.3__tar.gz → 2.0.5__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 (41) hide show
  1. {devs_common-2.0.3 → devs_common-2.0.5}/PKG-INFO +1 -1
  2. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common/core/container.py +83 -42
  3. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common/templates/Dockerfile +2 -2
  4. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common/templates/devcontainer.json +2 -1
  5. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common.egg-info/PKG-INFO +1 -1
  6. {devs_common-2.0.3 → devs_common-2.0.5}/pyproject.toml +1 -1
  7. {devs_common-2.0.3 → devs_common-2.0.5}/LICENSE +0 -0
  8. {devs_common-2.0.3 → devs_common-2.0.5}/README.md +0 -0
  9. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common/__init__.py +0 -0
  10. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common/config.py +0 -0
  11. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common/core/__init__.py +0 -0
  12. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common/core/project.py +0 -0
  13. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common/core/workspace.py +0 -0
  14. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common/devs_config.py +0 -0
  15. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common/exceptions.py +0 -0
  16. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common/templates/scripts/copy-ssh.sh +0 -0
  17. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common/templates/scripts/post-create-wrapper.sh +0 -0
  18. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common/templates/scripts/python-venv-integration.sh +0 -0
  19. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common/templates/scripts/screenshot-webapp.sh +0 -0
  20. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common/templates/scripts/setup-workspace.sh +0 -0
  21. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common/templates/scripts/start-tunnel.sh +0 -0
  22. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common/templates/scripts/tunnel-status.sh +0 -0
  23. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common/templates/scripts/webapp-screenshot.py +0 -0
  24. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common/templates/sudo-scripts/init-firewall.sh +0 -0
  25. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common/templates/sudo-scripts/setup-devs-env.sh +0 -0
  26. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common/templates/sudo-scripts/setup-vscode-cache.sh +0 -0
  27. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common/templates/sudo-scripts/start-services.sh +0 -0
  28. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common/templates/vscode-settings-reference.md +0 -0
  29. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common/utils/__init__.py +0 -0
  30. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common/utils/config_hash.py +0 -0
  31. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common/utils/console.py +0 -0
  32. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common/utils/devcontainer.py +0 -0
  33. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common/utils/devcontainer_template.py +0 -0
  34. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common/utils/docker_client.py +0 -0
  35. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common/utils/file_utils.py +0 -0
  36. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common/utils/git_utils.py +0 -0
  37. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common.egg-info/SOURCES.txt +0 -0
  38. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common.egg-info/dependency_links.txt +0 -0
  39. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common.egg-info/requires.txt +0 -0
  40. {devs_common-2.0.3 → devs_common-2.0.5}/devs_common.egg-info/top_level.txt +0 -0
  41. {devs_common-2.0.3 → devs_common-2.0.5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devs-common
3
- Version: 2.0.3
3
+ Version: 2.0.5
4
4
  Summary: Shared utilities for devs package ecosystem
5
5
  Author: Dan Lester
6
6
  License-Expression: MIT
@@ -2,6 +2,7 @@
2
2
 
3
3
  import os
4
4
  import re
5
+ import time
5
6
  from datetime import datetime, timezone
6
7
  from pathlib import Path
7
8
  from typing import Dict, List, Optional, Tuple
@@ -817,14 +818,34 @@ class ContainerManager:
817
818
  extra_env=extra_env
818
819
  )
819
820
 
820
- def start_tunnel(self, dev_name: str, workspace_dir: Path, debug: bool = False, live: bool = False, extra_env: Optional[Dict[str, str]] = None) -> None:
821
- """Start a VS Code tunnel in the container.
821
+ def _get_tunnel_name(self, dev_name: str) -> str:
822
+ """Generate a VS Code tunnel name, truncated to 20 chars.
823
+
824
+ VS Code tunnel names have a 20 character limit. We keep the dev name
825
+ suffix and shorten the project portion if needed.
826
+ """
827
+ project_prefix = self.config.project_prefix if self.config else "dev"
828
+ tunnel_name = f"{project_prefix}-{self.project.info.name}-{dev_name}"
829
+ # Sanitize: tunnel names can only contain alphanumeric, dash, underscore
830
+ tunnel_name = tunnel_name.replace(".", "-").replace("_", "-")
831
+
832
+ if len(tunnel_name) > 20:
833
+ suffix = f"-{dev_name}"
834
+ prefix_budget = 20 - len(suffix)
835
+ if prefix_budget >= 3:
836
+ project_part = f"{project_prefix}-{self.project.info.name}"
837
+ project_part = project_part[:prefix_budget].rstrip("-")
838
+ tunnel_name = f"{project_part}{suffix}"
839
+ else:
840
+ tunnel_name = tunnel_name[:20]
822
841
 
823
- This runs an interactive tunnel that allows VS Code to connect directly
824
- to the container without SSH. The tunnel process will run in the foreground
825
- and can be stopped with Ctrl+C.
842
+ return tunnel_name
826
843
 
827
- First-time usage requires authentication via a device code flow.
844
+ def start_tunnel(self, dev_name: str, workspace_dir: Path, debug: bool = False, live: bool = False, extra_env: Optional[Dict[str, str]] = None) -> None:
845
+ """Start a VS Code tunnel in the background.
846
+
847
+ Requires prior authentication via tunnel_auth(). The tunnel runs
848
+ detached inside the container.
828
849
 
829
850
  Args:
830
851
  dev_name: Development environment name
@@ -837,59 +858,79 @@ class ContainerManager:
837
858
  ContainerError: If tunnel startup fails
838
859
  """
839
860
  try:
840
- # Prepare container for execution
841
861
  container_name, container_workspace_dir = self._prepare_container_exec(
842
862
  dev_name, workspace_dir, debug=debug, live=live, extra_env=extra_env
843
863
  )
844
864
 
845
- # Create a tunnel name from the container's devcontainer name
846
- # This will be the name that appears in VS Code Remote Explorer
847
- # VS Code tunnel names have a 20 character limit
848
- project_prefix = self.config.project_prefix if self.config else "dev"
849
- tunnel_name = f"{project_prefix}-{self.project.info.name}-{dev_name}"
850
- # Sanitize: tunnel names can only contain alphanumeric, dash, underscore
851
- tunnel_name = tunnel_name.replace(".", "-").replace("_", "-")
852
-
853
- # Truncate to 20 chars if needed (VS Code tunnel limit)
854
- if len(tunnel_name) > 20:
855
- # Keep the dev name suffix, shorten the project part
856
- suffix = f"-{dev_name}"
857
- prefix_budget = 20 - len(suffix)
858
- if prefix_budget >= 3:
859
- project_part = f"{project_prefix}-{self.project.info.name}"
860
- project_part = project_part[:prefix_budget].rstrip("-")
861
- tunnel_name = f"{project_part}{suffix}"
862
- else:
863
- # Dev name itself is very long, just truncate the whole thing
864
- tunnel_name = tunnel_name[:20]
865
+ tunnel_name = self._get_tunnel_name(dev_name)
866
+ log_file = '/tmp/vscode-tunnel.log'
867
+ tunnel_cmd = f'/usr/local/bin/code tunnel --accept-server-license-terms --name {tunnel_name}'
868
+ vscode_cmd = f"code --remote tunnel+{tunnel_name} {container_workspace_dir}"
865
869
 
866
870
  console.print(f"[bold cyan]Starting VS Code tunnel for: {dev_name}[/bold cyan]")
867
871
  console.print(f" Container: {container_name}")
868
872
  console.print(f" Tunnel name: {tunnel_name}")
869
873
  console.print("")
870
- console.print("[yellow]First-time usage requires authentication:[/yellow]")
871
- console.print(" 1. A URL and code will be displayed")
872
- console.print(" 2. Open the URL in your browser")
873
- console.print(" 3. Enter the code to authenticate with GitHub/Microsoft")
874
- console.print("")
875
- console.print("[dim]Press Ctrl+C to stop the tunnel[/dim]")
876
- console.print("")
877
874
 
878
- # Start the tunnel interactively
879
- # The code CLI binary is installed at /usr/local/bin/code in the container
875
+ # Start detached
880
876
  cmd = [
881
- 'docker', 'exec', '-it',
877
+ 'docker', 'exec', '-d',
882
878
  container_name,
883
- '/usr/local/bin/code', 'tunnel',
884
- '--accept-server-license-terms',
885
- '--name', tunnel_name
879
+ '/bin/sh', '-c',
880
+ f'{tunnel_cmd} > {log_file} 2>&1'
886
881
  ]
887
882
 
888
883
  if debug:
889
884
  console.print(f"[dim]Running: {' '.join(cmd)}[/dim]")
890
885
 
891
- # Run interactively so user can see auth prompts and tunnel status
892
- subprocess.run(cmd, check=False)
886
+ subprocess.run(cmd, check=True)
887
+
888
+ # Poll the log to confirm startup and show the URL
889
+ console.print("[dim]Waiting for tunnel to start...[/dim]")
890
+ for i in range(15):
891
+ time.sleep(1)
892
+ result = subprocess.run(
893
+ ['docker', 'exec', container_name, 'cat', log_file],
894
+ capture_output=True, text=True
895
+ )
896
+ output = result.stdout
897
+
898
+ if 'Open this link' in output or 'vscode.dev/tunnel' in output:
899
+ console.print(f"[bold green]Tunnel running![/bold green]")
900
+ console.print("")
901
+ console.print("[bold]Connect from your local machine:[/bold]")
902
+ console.print(f" {vscode_cmd}")
903
+ console.print("")
904
+ for line in output.split('\n'):
905
+ if 'vscode.dev/tunnel' in line:
906
+ console.print(f"[dim]Or open in browser: {line.strip()}[/dim]")
907
+ console.print(f"[dim]Use 'devs tunnel {dev_name} --status' to check status[/dim]")
908
+ console.print(f"[dim]Use 'devs tunnel {dev_name} --kill' to stop[/dim]")
909
+ return
910
+
911
+ if 'log in' in output.lower() or 'device' in output.lower() or 'invalid' in output.lower():
912
+ console.print("[yellow]Authentication required.[/yellow]")
913
+ console.print(f" Run: devs tunnel {dev_name} --auth")
914
+ subprocess.run(
915
+ ['docker', 'exec', container_name, '/usr/local/bin/code', 'tunnel', 'kill'],
916
+ capture_output=True
917
+ )
918
+ return
919
+
920
+ if debug and output.strip():
921
+ console.print(f"[dim]Log ({i+1}s): {output.strip()[-100:]}[/dim]")
922
+
923
+ # Timeout - show what we have
924
+ result = subprocess.run(
925
+ ['docker', 'exec', container_name, 'cat', log_file],
926
+ capture_output=True, text=True
927
+ )
928
+ if result.stdout.strip():
929
+ console.print("[yellow]Tunnel may still be starting. Log output:[/yellow]")
930
+ console.print(result.stdout.strip())
931
+ else:
932
+ console.print("[yellow]Tunnel started but no output yet. Check status with:[/yellow]")
933
+ console.print(f" devs tunnel {dev_name} --status")
893
934
 
894
935
  except (DockerError, subprocess.SubprocessError) as e:
895
936
  raise ContainerError(f"Failed to start tunnel in {dev_name}: {e}")
@@ -87,9 +87,9 @@ RUN SNIPPET="export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhisto
87
87
  ENV DEVCONTAINER=true
88
88
 
89
89
  # Create workspace and config directories and set permissions
90
- RUN mkdir -p /workspaces /home/node/claudeconfig /home/node/codexconfig /home/node/.vscode-server/extensions && \
90
+ RUN mkdir -p /workspaces /home/node/claudeconfig /home/node/codexconfig /home/node/.vscode-server/extensions /home/node/.vscode/cli && \
91
91
  echo '[]' > /home/node/.vscode-server/extensions/extensions.json && \
92
- chown -R node:node /workspaces /home/node /home/node/.vscode-server && \
92
+ chown -R node:node /workspaces /home/node /home/node/.vscode-server /home/node/.vscode && \
93
93
  chmod 755 /workspaces
94
94
 
95
95
  WORKDIR /workspaces
@@ -83,7 +83,8 @@
83
83
  // "source=${localEnv:WORKSPACE_FOLDER_NAME}-vscode-extensions,target=/home/node/.vscode-server/extensions,type=volume",
84
84
  // .vscode-server includes extension state which we don't want to keep
85
85
  // "source=${localEnv:WORKSPACE_FOLDER_NAME}-vscode-data,target=/home/node/.vscode-server,type=volume",
86
- "source=${localEnv:WORKSPACE_FOLDER_NAME}-vscode-downloads,target=/home/node/.vscode-extensions-cache-volume,type=volume"
86
+ "source=${localEnv:WORKSPACE_FOLDER_NAME}-vscode-downloads,target=/home/node/.vscode-extensions-cache-volume,type=volume",
87
+ "source=${localEnv:HOME}/.devs/vscode-cli,target=/home/node/.vscode/cli,type=bind"
87
88
  ],
88
89
  "remoteEnv": {
89
90
  "NODE_OPTIONS": "--max-old-space-size=4096",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devs-common
3
- Version: 2.0.3
3
+ Version: 2.0.5
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 = "2.0.3"
7
+ version = "2.0.5"
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