sandboxbox 2.5.5 → 3.0.1

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.
@@ -0,0 +1,106 @@
1
+ import { mkdtempSync, rmSync, cpSync, existsSync, mkdirSync } from 'fs';
2
+ import { tmpdir, homedir } from 'os';
3
+ import { join } from 'path';
4
+ import { spawn, execSync } from 'child_process';
5
+
6
+ export function createSandbox(projectDir) {
7
+ const sandboxDir = mkdtempSync(join(tmpdir(), 'sandboxbox-'));
8
+ const workspaceDir = join(sandboxDir, 'workspace');
9
+
10
+ if (projectDir && existsSync(projectDir)) {
11
+ const isGitRepo = existsSync(join(projectDir, '.git'));
12
+
13
+ if (isGitRepo) {
14
+ execSync(`git clone "${projectDir}" "${workspaceDir}"`, {
15
+ stdio: 'pipe',
16
+ shell: true,
17
+ windowsHide: true
18
+ });
19
+ } else {
20
+ cpSync(projectDir, workspaceDir, {
21
+ recursive: true,
22
+ filter: (src) => !src.includes('node_modules')
23
+ });
24
+ }
25
+ }
26
+
27
+ const claudeDir = join(sandboxDir, '.claude');
28
+ const hostClaudeDir = join(homedir(), '.claude');
29
+
30
+ if (existsSync(hostClaudeDir)) {
31
+ try {
32
+ cpSync(hostClaudeDir, claudeDir, {
33
+ recursive: true,
34
+ filter: (src) => {
35
+ return !src.includes('shell-snapshots') &&
36
+ !src.includes('logs') &&
37
+ !src.includes('debug') &&
38
+ !src.includes('.log');
39
+ }
40
+ });
41
+ } catch (e) {
42
+ console.error('Warning: Failed to copy Claude config:', e.message);
43
+ }
44
+ } else {
45
+ mkdirSync(claudeDir, { recursive: true });
46
+ }
47
+
48
+ const playwrightDir = join(sandboxDir, '.playwright');
49
+ mkdirSync(playwrightDir, { recursive: true });
50
+
51
+ const cleanup = () => {
52
+ try {
53
+ rmSync(sandboxDir, { recursive: true, force: true });
54
+ } catch (e) {
55
+ console.error('Cleanup failed:', e.message);
56
+ }
57
+ };
58
+
59
+ return { sandboxDir, cleanup };
60
+ }
61
+
62
+ export function createSandboxEnv(sandboxDir, options = {}) {
63
+ const hostHome = homedir();
64
+
65
+ const env = {
66
+ PATH: process.env.PATH,
67
+ HOME: sandboxDir,
68
+ USERPROFILE: sandboxDir,
69
+ TMPDIR: join(sandboxDir, 'tmp'),
70
+ TEMP: join(sandboxDir, 'tmp'),
71
+ TMP: join(sandboxDir, 'tmp'),
72
+ PLAYWRIGHT_BROWSERS_PATH: join(sandboxDir, 'browsers'),
73
+ PLAYWRIGHT_STORAGE_STATE: join(sandboxDir, '.playwright', 'storage-state.json'),
74
+ ANTHROPIC_AUTH_TOKEN: process.env.ANTHROPIC_AUTH_TOKEN,
75
+ CLAUDECODE: '1',
76
+ NPM_CONFIG_CACHE: process.env.NPM_CONFIG_CACHE || join(hostHome, '.npm'),
77
+ npm_config_cache: process.env.npm_config_cache || join(hostHome, '.npm'),
78
+ ...options
79
+ };
80
+
81
+ return env;
82
+ }
83
+
84
+ export function runInSandbox(commandStr, args, sandboxDir, env) {
85
+ return new Promise((resolve, reject) => {
86
+ const fullCommand = args.length > 0 ? `${commandStr} ${args.join(' ')}` : commandStr;
87
+
88
+ const proc = spawn(fullCommand, [], {
89
+ cwd: join(sandboxDir, 'workspace'),
90
+ env,
91
+ stdio: 'inherit',
92
+ shell: true,
93
+ windowsHide: false
94
+ });
95
+
96
+ proc.on('close', (code) => {
97
+ if (code === 0) {
98
+ resolve();
99
+ } else {
100
+ reject(new Error(`Process exited with code ${code}`));
101
+ }
102
+ });
103
+
104
+ proc.on('error', reject);
105
+ });
106
+ }
@@ -1,103 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Auto-download Podman portable binaries
5
- * Similar to how sqlite/playwright auto-downloads platform-specific binaries
6
- */
7
-
8
- import { existsSync, mkdirSync, chmodSync, unlinkSync } from 'fs';
9
- import { join, dirname } from 'path';
10
- import { fileURLToPath } from 'url';
11
- import { PODMAN_VERSION, DOWNLOADS } from './podman-config.js';
12
- import { download, extractZip, extractTarGz } from './podman-extract.js';
13
-
14
- const __filename = fileURLToPath(import.meta.url);
15
- const __dirname = dirname(__filename);
16
- const binDir = join(__dirname, '..', 'bin');
17
-
18
- async function main() {
19
- const platform = process.platform;
20
-
21
- console.log(`\n📦 Setting up Podman portable binaries for ${platform}...`);
22
-
23
- if (!DOWNLOADS[platform]) {
24
- console.log(`⚠️ Platform ${platform} not supported for auto-download`);
25
- console.log(` Skipping auto-download (will use system Podman if available)`);
26
- return;
27
- }
28
-
29
- if (!existsSync(binDir)) {
30
- mkdirSync(binDir, { recursive: true });
31
- }
32
-
33
- const { url, binary, extract } = DOWNLOADS[platform];
34
- const binaryPath = join(binDir, binary);
35
-
36
- if (existsSync(binaryPath)) {
37
- console.log(`✅ Podman already installed at ${binaryPath}`);
38
- return;
39
- }
40
-
41
- console.log(`📥 Downloading Podman v${PODMAN_VERSION}...`);
42
-
43
- const archiveName = url.split('/').pop();
44
- const archivePath = join(binDir, archiveName);
45
-
46
- try {
47
- const extractedDir = join(binDir, 'podman-4.9.3');
48
- if (existsSync(extractedDir)) {
49
- const fs = await import('fs/promises');
50
- await fs.rm(extractedDir, { recursive: true, force: true });
51
- }
52
-
53
- console.log(` Downloading from GitHub releases...`);
54
- await download(url, archivePath);
55
- console.log(`✅ Downloaded successfully`);
56
-
57
- console.log(`📦 Extracting...`);
58
- if (extract === 'tar') {
59
- await extractTarGz(archivePath, binDir, 1);
60
- } else if (extract === 'unzip') {
61
- await extractZip(archivePath, binDir);
62
-
63
- if (platform === 'win32') {
64
- const extractedDir = join(binDir, `podman-4.9.3`);
65
- const extractedPodman = join(extractedDir, 'usr', 'bin', 'podman.exe');
66
- const targetPodman = join(binDir, binary);
67
-
68
- if (existsSync(extractedPodman)) {
69
- const fs = await import('fs/promises');
70
- await fs.copyFile(extractedPodman, targetPodman);
71
- await fs.rm(extractedDir, { recursive: true, force: true });
72
- }
73
- }
74
- }
75
-
76
- if (platform !== 'win32' && existsSync(binaryPath)) {
77
- chmodSync(binaryPath, 0o755);
78
- }
79
-
80
- console.log(`✅ Podman installed successfully!`);
81
- console.log(` Binary: ${binaryPath}\n`);
82
-
83
- if (existsSync(archivePath)) {
84
- unlinkSync(archivePath);
85
- }
86
-
87
- } catch (error) {
88
- console.error(`⚠️ Auto-download failed: ${error.message}`);
89
- console.log(`\n💡 No problem! You can install Podman manually:`);
90
- if (platform === 'win32') {
91
- console.log(` winget install RedHat.Podman`);
92
- } else if (platform === 'darwin') {
93
- console.log(` brew install podman && podman machine init && podman machine start`);
94
- } else {
95
- console.log(` sudo apt-get install podman # Ubuntu/Debian`);
96
- }
97
- console.log(`\n Or it will use system Podman if installed.\n`);
98
- }
99
- }
100
-
101
- main().catch(() => {
102
- // Silently fail - will use system Podman
103
- });
@@ -1,20 +0,0 @@
1
- export const PODMAN_VERSION = '4.9.3';
2
- export const ARCH = process.arch === 'arm64' ? 'arm64' : 'amd64';
3
-
4
- export const DOWNLOADS = {
5
- win32: {
6
- url: `https://github.com/containers/podman/releases/download/v${PODMAN_VERSION}/podman-remote-release-windows_${ARCH}.zip`,
7
- binary: 'podman.exe',
8
- extract: 'unzip'
9
- },
10
- darwin: {
11
- url: `https://github.com/containers/podman/releases/download/v${PODMAN_VERSION}/podman-remote-release-darwin_${ARCH}.tar.gz`,
12
- binary: 'podman',
13
- extract: 'tar'
14
- },
15
- linux: {
16
- url: `https://github.com/containers/podman/releases/download/v${PODMAN_VERSION}/podman-remote-static-linux_${ARCH}.tar.gz`,
17
- binary: `podman-remote-static-linux_${ARCH}`,
18
- extract: 'tar'
19
- }
20
- };
@@ -1,117 +0,0 @@
1
- import { createWriteStream, createReadStream, unlinkSync } from 'fs';
2
- import { execSync } from 'child_process';
3
- import { createGunzip } from 'zlib';
4
- import { pipeline } from 'stream/promises';
5
-
6
- export async function extractZip(zipPath, extractTo) {
7
- return new Promise((resolve, reject) => {
8
- try {
9
- const psCommand = `Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory('${zipPath.replace(/'/g, "''")}', '${extractTo.replace(/'/g, "''")}')`;
10
-
11
- // Add retry logic for ZIP extraction (file lock issues)
12
- let retries = 0;
13
- const maxRetries = 3;
14
-
15
- while (retries < maxRetries) {
16
- try {
17
- execSync(`powershell -Command "${psCommand}"`, {
18
- stdio: 'pipe',
19
- shell: true,
20
- windowsHide: true,
21
- timeout: 120000 // ZIP extraction can take time
22
- });
23
- resolve();
24
- return;
25
- } catch (error) {
26
- retries++;
27
- if (retries < maxRetries && error.message.includes('being used by another process')) {
28
- console.log(` File locked, retrying extraction (${retries}/${maxRetries})...`);
29
- // Wait 2 seconds before retry
30
- const start = Date.now();
31
- while (Date.now() - start < 2000) {
32
- // Wait
33
- }
34
- continue;
35
- }
36
- reject(error);
37
- return;
38
- }
39
- }
40
- } catch (error) {
41
- reject(error);
42
- }
43
- });
44
- }
45
-
46
- export async function extractTarGz(tarPath, extractTo, stripComponents = 0) {
47
- return new Promise(async (resolve, reject) => {
48
- try {
49
- const tarWithoutGz = tarPath.replace('.gz', '');
50
- const readStream = createReadStream(tarPath);
51
- const writeStream = createWriteStream(tarWithoutGz);
52
- const gunzip = createGunzip();
53
-
54
- await pipeline(readStream, gunzip, writeStream);
55
-
56
- try {
57
- execSync(`tar -xf "${tarWithoutGz}" -C "${extractTo}"${stripComponents ? ` --strip-components=${stripComponents}` : ''}`, {
58
- stdio: 'pipe',
59
- shell: process.platform === 'win32',
60
- windowsHide: process.platform === 'win32',
61
- timeout: 120000
62
- });
63
- } catch (tarError) {
64
- if (process.platform === 'win32') {
65
- try {
66
- execSync(`bsdtar -xf "${tarWithoutGz}" -C "${extractTo}"${stripComponents ? ` --strip-components=${stripComponents}` : ''}`, {
67
- stdio: 'pipe',
68
- shell: true,
69
- windowsHide: true,
70
- timeout: 120000
71
- });
72
- } catch (bsdtarError) {
73
- throw new Error(`Failed to extract tar archive. Please install tar or bsdtar: ${tarError.message}`);
74
- }
75
- } else {
76
- throw tarError;
77
- }
78
- }
79
-
80
- unlinkSync(tarWithoutGz);
81
- resolve();
82
- } catch (error) {
83
- reject(error);
84
- }
85
- });
86
- }
87
-
88
- export function download(url, dest) {
89
- return new Promise(async (resolve, reject) => {
90
- const { get: httpsGet } = await import('https');
91
- const { get: httpGet } = await import('http');
92
- const { createWriteStream } = await import('fs');
93
-
94
- const get = url.startsWith('https') ? httpsGet : httpGet;
95
-
96
- get(url, (response) => {
97
- if (response.statusCode === 302 || response.statusCode === 301) {
98
- return download(response.headers.location, dest).then(resolve).catch(reject);
99
- }
100
-
101
- if (response.statusCode !== 200) {
102
- reject(new Error(`Download failed: ${response.statusCode}`));
103
- return;
104
- }
105
-
106
- const file = createWriteStream(dest);
107
- response.pipe(file);
108
-
109
- file.on('finish', () => {
110
- file.close();
111
- resolve();
112
- });
113
-
114
- file.on('error', reject);
115
- }).on('error', reject);
116
- });
117
- }
@@ -1,69 +0,0 @@
1
- import { execSync } from 'child_process';
2
- import { buildContainerMounts } from './isolation.js';
3
-
4
- export function getClaudeEnvironment() {
5
- const envVars = {};
6
-
7
- // Collect Anthropic and Claude environment variables
8
- for (const [key, value] of Object.entries(process.env)) {
9
- if (key.startsWith('ANTHROPIC_') || key.startsWith('CLAUDE')) {
10
- envVars[key] = value;
11
- }
12
- }
13
-
14
- return envVars;
15
- }
16
-
17
- export function buildClaudeContainerCommand(projectPath, podmanPath, command = 'claude', customMounts = null) {
18
- const envVars = getClaudeEnvironment();
19
- const envArgs = Object.entries(envVars)
20
- .map(([key, value]) => `-e ${key}="${value}"`)
21
- .join(' ');
22
-
23
- const homeDir = process.platform === 'win32' ? process.env.USERPROFILE : process.env.HOME;
24
-
25
- let allMounts = [];
26
-
27
- if (customMounts) {
28
- // Use provided custom mounts (includes git identity and host remote)
29
- allMounts = customMounts;
30
- } else {
31
- // Build base mounts from isolation utility (includes git identity)
32
- const baseMounts = buildContainerMounts(projectPath);
33
- allMounts = baseMounts;
34
- }
35
-
36
- // Add Claude-specific mounts
37
- const claudeMounts = [
38
- `-v "${homeDir}/.claude:/root/.claude-host:ro"`,
39
- `-v "${process.cwd()}/claude-settings.json:/root/.claude/settings.json:ro"`
40
- ];
41
-
42
- allMounts = [...allMounts, ...claudeMounts];
43
-
44
- return `${podmanPath} run --rm -it ${allMounts.join(' ')} ${envArgs} --env HOME=/root sandboxbox-local:latest ${command}`;
45
- }
46
-
47
- export function createClaudeDockerfile() {
48
- return `FROM node:20
49
-
50
- # Install development tools
51
- RUN apt-get update && apt-get install -y --no-install-recommends \\
52
- git curl bash sudo nano vim \\
53
- && apt-get clean && rm -rf /var/lib/apt/lists/*
54
-
55
- WORKDIR /workspace
56
-
57
- # Install Claude Code
58
- RUN npm install -g @anthropic-ai/claude-code@latest
59
-
60
- # Setup MCP servers after Claude installation
61
- RUN claude mcp add glootie -- npx -y mcp-glootie@latest && \\
62
- claude mcp add vexify -- npx -y mcp-vexify@latest && \\
63
- claude mcp add playwright -- npx @playwright/mcp@latest
64
-
65
- # Create isolated workspace script with cleanup
66
- RUN echo '#!/bin/bash\\nset -e\\n\\necho "🚀 Starting SandboxBox with Claude Code in isolated environment..."\\necho "📁 Working directory: /workspace"\\necho "🎯 This is an isolated copy of your repository"\\n\\n# Cleanup function for temporary files\\ncleanup_temp_files() {\\n echo "🧹 Cleaning up temporary files..."\\n find /tmp -user root -name "claude-*" -type f -delete 2>/dev/null || true\\n find /tmp -user root -name "*.tmp" -type f -delete 2>/dev/null || true\\n find /var/tmp -user root -name "claude-*" -type f -delete 2>/dev/null || true\\n}\\n\\n# Set up cleanup trap\\ntrap cleanup_temp_files EXIT INT TERM\\n\\nif [ -d "/workspace/.git" ]; then\\n echo "✅ Git repository detected in workspace"\\n echo "📋 Current status:"\\n git status\\n echo ""\\n echo "🔧 Starting Claude Code..."\\n echo "💡 Changes will be isolated and will NOT affect the original repository"\\n echo "📝 To save changes, use git commands to commit and push before exiting"\\n echo "🔧 MCP servers: glootie, vexify, playwright"\\n exec claude\\nelse\\n echo "❌ Error: /workspace is not a valid git repository"\\n exit 1\\nfi' > /usr/local/bin/start-isolated-sandbox.sh && chmod +x /usr/local/bin/start-isolated-sandbox.sh
67
-
68
- CMD ["/usr/local/bin/start-isolated-sandbox.sh"]`;
69
- }
@@ -1,222 +0,0 @@
1
- import { mkdtempSync, rmSync, existsSync } from 'fs';
2
- import { tmpdir } from 'os';
3
- import { join } from 'path';
4
- import { execSync } from 'child_process';
5
-
6
- /**
7
- * Builds container volume mounts with git identity and host remote
8
- * @param {string} tempProjectDir - Temporary project directory
9
- * @param {string} originalProjectDir - Original host project directory
10
- * @returns {Array} - Array of volume mount strings
11
- */
12
- export function buildContainerMounts(tempProjectDir, originalProjectDir) {
13
- const mounts = [`-v "${tempProjectDir}:/workspace:rw"`];
14
-
15
- // Add host repository as git remote
16
- mounts.push(`-v "${originalProjectDir}:/host-repo:rw"`);
17
-
18
- // Add git identity mounts
19
- const homeDir = process.platform === 'win32' ? process.env.USERPROFILE : process.env.HOME;
20
-
21
- if (existsSync(`${homeDir}/.gitconfig`)) {
22
- mounts.push(`-v "${homeDir}/.gitconfig:/root/.gitconfig:ro"`);
23
- }
24
-
25
- if (existsSync(`${homeDir}/.ssh`)) {
26
- mounts.push(`-v "${homeDir}/.ssh:/root/.ssh:ro"`);
27
- }
28
-
29
- return mounts;
30
- }
31
-
32
- /**
33
- * Creates an isolated temporary environment for running commands
34
- * @param {string} projectDir - Source project directory
35
- * @returns {Object} - Contains tempDir, tempProjectDir, and cleanup function
36
- */
37
- export function createIsolatedEnvironment(projectDir) {
38
- const tempDir = mkdtempSync(join(tmpdir(), 'sandboxbox-'));
39
- const projectName = projectDir.split(/[\\\/]/).pop() || 'project';
40
- const tempProjectDir = join(tempDir, projectName);
41
-
42
- // Copy project to temporary directory (creates isolation)
43
- // First create the directory (cross-platform)
44
- if (process.platform === 'win32') {
45
- execSync(`powershell -Command "New-Item -ItemType Directory -Path '${tempProjectDir}' -Force"`, {
46
- stdio: 'pipe',
47
- shell: true,
48
- windowsHide: true,
49
- timeout: 30000
50
- });
51
- } else {
52
- execSync(`mkdir -p "${tempProjectDir}"`, {
53
- stdio: 'pipe',
54
- shell: true,
55
- timeout: 30000
56
- });
57
- }
58
-
59
- if (process.platform === 'win32') {
60
- // Windows approach - include hidden files like .git
61
- execSync(`powershell -Command "Copy-Item -Path '${projectDir}\\*' -Destination '${tempProjectDir}' -Recurse -Force -Exclude 'node_modules'"`, {
62
- stdio: 'pipe',
63
- shell: true,
64
- windowsHide: true,
65
- timeout: 60000 // Copy operations can take longer
66
- });
67
- // Also copy hidden files separately
68
- execSync(`powershell -Command "Get-ChildItem -Path '${projectDir}' -Force -Name | Where-Object { $_ -like '.*' } | ForEach-Object { Copy-Item -Path (Join-Path '${projectDir}' $_) -Destination '${tempProjectDir}' -Recurse -Force }"`, {
69
- stdio: 'pipe',
70
- shell: true,
71
- windowsHide: true,
72
- timeout: 60000
73
- });
74
- } else {
75
- // Unix approach - include hidden files
76
- execSync(`cp -r "${projectDir}"/.* "${tempProjectDir}/" 2>/dev/null || true`, {
77
- stdio: 'pipe',
78
- shell: true,
79
- timeout: 60000
80
- });
81
- execSync(`cp -r "${projectDir}"/* "${tempProjectDir}/"`, {
82
- stdio: 'pipe',
83
- shell: true,
84
- timeout: 60000
85
- });
86
- }
87
-
88
- // Configure git remote to point to mounted host repository (only if git repo)
89
- try {
90
- // Check if the project directory is a git repository
91
- const gitDirPath = process.platform === 'win32'
92
- ? `${projectDir}\\.git`
93
- : `${projectDir}/.git`;
94
-
95
- if (!existsSync(gitDirPath)) {
96
- // Not a git repository, skip git setup
97
- // Define cleanup function early for early return
98
- const cleanup = () => {
99
- try {
100
- rmSync(tempDir, { recursive: true, force: true });
101
- } catch (cleanupError) {
102
- // Ignore cleanup errors
103
- }
104
- };
105
- return { tempDir, tempProjectDir, cleanup };
106
- }
107
-
108
- // Also check if the temp directory has .git after copy
109
- const tempGitDirPath = process.platform === 'win32'
110
- ? `${tempProjectDir}\\.git`
111
- : `${tempProjectDir}/.git`;
112
-
113
- if (!existsSync(tempGitDirPath)) {
114
- // Copy didn't preserve git, skip git setup
115
- // Define cleanup function early for early return
116
- const cleanup = () => {
117
- try {
118
- rmSync(tempDir, { recursive: true, force: true });
119
- } catch (cleanupError) {
120
- // Ignore cleanup errors
121
- }
122
- };
123
- return { tempDir, tempProjectDir, cleanup };
124
- }
125
-
126
- // Normalize paths for cross-platform compatibility
127
- const normalizedTempDir = tempProjectDir.replace(/\\/g, '/');
128
- const normalizedOriginalDir = projectDir.replace(/\\/g, '/');
129
-
130
- // Configure git to allow operations in mounted directories
131
- execSync(`git config --global --add safe.directory /workspace`, {
132
- stdio: 'pipe',
133
- shell: true,
134
- windowsHide: process.platform === 'win32',
135
- timeout: 60000 // 1 minute for git operations
136
- });
137
-
138
- // Configure host repository to accept pushes to checked-out branch
139
- if (process.platform === 'win32') {
140
- try {
141
- execSync(`cd "${normalizedOriginalDir}" && git config receive.denyCurrentBranch ignore`, {
142
- stdio: 'pipe',
143
- shell: true,
144
- windowsHide: true,
145
- timeout: 60000 // 1 minute for git operations
146
- });
147
- } catch (e) {
148
- // Ignore if git config fails
149
- }
150
- } else {
151
- execSync(`cd "${normalizedOriginalDir}" && git config receive.denyCurrentBranch ignore`, {
152
- stdio: 'pipe',
153
- shell: true,
154
- timeout: 60000 // 1 minute for git operations
155
- });
156
- }
157
-
158
- // Remove any existing origin first (Windows-compatible)
159
- if (process.platform === 'win32') {
160
- try {
161
- execSync(`cd "${normalizedTempDir}" && git remote remove origin`, {
162
- stdio: 'pipe',
163
- shell: true,
164
- windowsHide: true,
165
- timeout: 60000 // 1 minute for git operations
166
- });
167
- } catch (e) {
168
- // Ignore if origin doesn't exist
169
- }
170
- } else {
171
- execSync(`cd "${normalizedTempDir}" && git remote remove origin 2>/dev/null || true`, {
172
- stdio: 'pipe',
173
- shell: true,
174
- timeout: 60000 // 1 minute for git operations
175
- });
176
- }
177
-
178
- // Add origin pointing to mounted host repository (accessible from container)
179
- execSync(`cd "${normalizedTempDir}" && git remote add origin /host-repo`, {
180
- stdio: 'pipe',
181
- shell: true,
182
- windowsHide: process.platform === 'win32',
183
- timeout: 60000 // 1 minute for git operations
184
- });
185
-
186
- // Set up upstream tracking for current branch (use push -u to set upstream)
187
- const currentBranch = execSync(`cd "${normalizedTempDir}" && git branch --show-current`, {
188
- encoding: 'utf8',
189
- stdio: 'pipe',
190
- windowsHide: process.platform === 'win32',
191
- timeout: 60000 // 1 minute for git operations
192
- }).trim();
193
-
194
- // Note: Upstream will be set automatically on first push with -u flag
195
- // No need to set up upstream manually as it may not exist yet
196
- } catch (error) {
197
- // Log git remote setup errors for debugging
198
- console.error(`Git remote setup failed: ${error.message}`);
199
- }
200
-
201
- // Ensure cleanup on exit
202
- const cleanup = () => {
203
- try {
204
- rmSync(tempDir, { recursive: true, force: true });
205
- } catch (cleanupError) {
206
- // Ignore cleanup errors
207
- }
208
- };
209
-
210
- return { tempDir, tempProjectDir, cleanup };
211
- }
212
-
213
- /**
214
- * Sets up cleanup handlers for process signals
215
- * @param {Function} cleanup - Cleanup function to call
216
- */
217
- export function setupCleanupHandlers(cleanup) {
218
- // Set up cleanup handlers
219
- process.on('exit', cleanup);
220
- process.on('SIGINT', () => { cleanup(); process.exit(130); });
221
- process.on('SIGTERM', () => { cleanup(); process.exit(143); });
222
- }