Skip to content
This repository was archived by the owner on Feb 20, 2026. It is now read-only.

Commit 56c120e

Browse files
authored
Fix detection of deletions (#5)
* Fix detection of deletions * Checkpoint * Checkpoint * Refactor
1 parent 312c366 commit 56c120e

21 files changed

Lines changed: 1586 additions & 35 deletions

.eslintrc.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
module.exports = {
2+
parser: '@typescript-eslint/parser',
3+
extends: [
4+
'eslint:recommended',
5+
'plugin:@typescript-eslint/recommended'
6+
],
7+
parserOptions: {
8+
ecmaVersion: 2022,
9+
sourceType: 'module',
10+
project: './tsconfig.json'
11+
},
12+
env: {
13+
node: true,
14+
es2022: true,
15+
jest: true
16+
},
17+
plugins: ['@typescript-eslint'],
18+
rules: {
19+
'@typescript-eslint/explicit-module-boundary-types': 'off',
20+
'@typescript-eslint/no-explicit-any': 'warn',
21+
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
22+
'no-console': 'off',
23+
'semi': ['error', 'always'],
24+
'quotes': ['error', 'single'],
25+
'comma-dangle': ['error', 'never']
26+
},
27+
ignorePatterns: ['dist/', 'node_modules/', 'coverage/', '*.js']
28+
};

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,3 +144,5 @@ dist
144144
.yarn/build-state.yml
145145
.yarn/install-state.gz
146146
.pnp.*
147+
148+
e2e-tests/dummy-repo/

TODO.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@
1111
- [ ] How should we deal with it if the local shadow repo falls behind the remote branch? Options:
1212
- a. Let user merge changes from remote to local: We would need to implement a conflict resolver somehow.
1313
- b. If conflicts arise, we could just block the operation and let user dump the current state in order not to lose work. This is the simplest option.
14-
- Either way, we need to think about how to apply new commits from the remote, because changes currently only flow from the sandbox to the shadow repo.
14+
- Either way, we need to think about how to apply new commits from the remote, because changes currently only flow from the sandbox to the shadow repo.
15+
- [ ] rsync, inotifywait, etc. should be included in the image, not installed in the fly

jest.config.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
module.exports = {
2+
preset: 'ts-jest',
3+
testEnvironment: 'node',
4+
roots: ['<rootDir>/test'],
5+
testMatch: [
6+
'**/test/**/*.test.ts',
7+
'**/test/**/*.test.js',
8+
'**/test/**/*.spec.ts',
9+
'**/test/**/*.spec.js'
10+
],
11+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
12+
transform: {
13+
'^.+\\.tsx?$': 'ts-jest',
14+
'^.+\\.jsx?$': 'babel-jest'
15+
},
16+
collectCoverageFrom: [
17+
'src/**/*.{ts,tsx}',
18+
'!src/**/*.d.ts',
19+
'!src/**/*.test.{ts,tsx}',
20+
'!src/**/*.spec.{ts,tsx}'
21+
],
22+
coverageDirectory: '<rootDir>/coverage',
23+
coverageReporters: ['text', 'lcov', 'html'],
24+
testPathIgnorePatterns: [
25+
'/node_modules/',
26+
'/dist/'
27+
],
28+
moduleNameMapper: {
29+
'^@/(.*)$': '<rootDir>/src/$1'
30+
},
31+
globals: {
32+
'ts-jest': {
33+
tsconfig: {
34+
esModuleInterop: true,
35+
allowJs: true
36+
}
37+
}
38+
}
39+
};

package-lock.json

Lines changed: 68 additions & 25 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212
"start": "node dist/cli.js",
1313
"lint": "eslint src/**/*.ts",
1414
"test": "jest",
15+
"test:watch": "jest --watch",
16+
"test:unit": "jest test/unit",
17+
"test:integration": "jest test/integration",
18+
"test:e2e": "cd test/e2e && ./run-tests.sh",
19+
"test:coverage": "jest --coverage",
1520
"purge-containers": "docker ps -a --filter \"ancestor=claude-code-sandbox:latest\" -q | xargs -r docker rm -f && docker rmi claude-code-sandbox:latest"
1621
},
1722
"keywords": [
@@ -37,6 +42,7 @@
3742
"simple-git": "^3.22.0",
3843
"socket.io": "^4.8.1",
3944
"tar-stream": "^3.1.7",
45+
"ws": "^8.18.2",
4046
"xterm": "^5.3.0",
4147
"xterm-addon-fit": "^0.8.0",
4248
"xterm-addon-web-links": "^0.9.0"

src/git/shadow-repository.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,10 @@ export class ShadowRepository {
166166
try {
167167
await execAsync("git add .", { cwd: this.shadowPath });
168168
console.log(chalk.gray(" Staged all files for tracking"));
169+
170+
// Create initial commit to ensure deletions can be tracked
171+
await execAsync('git commit -m "Initial snapshot of working directory" --allow-empty', { cwd: this.shadowPath });
172+
console.log(chalk.gray(" Created initial commit for change tracking"));
169173
} catch (stageError: any) {
170174
console.log(chalk.gray(" Could not stage files:", stageError.message));
171175
}
@@ -342,6 +346,13 @@ export class ShadowRepository {
342346
await this.syncWithDockerCp(containerId, containerPath);
343347
}
344348

349+
// Stage all changes including deletions
350+
try {
351+
await execAsync("git add -A", { cwd: this.shadowPath });
352+
} catch (stageError) {
353+
console.log(chalk.gray(" Could not stage changes:", stageError));
354+
}
355+
345356
console.log(chalk.green("✓ Files synced successfully"));
346357
}
347358

@@ -413,6 +424,10 @@ export class ShadowRepository {
413424
`docker cp ${this.rsyncExcludeFile} ${containerId}:${containerExcludeFile}`,
414425
);
415426

427+
// Rsync directly from container to shadow repo with proper deletion handling
428+
// First, clear the shadow repo (except .git) to ensure deletions are reflected
429+
await execAsync(`find ${this.shadowPath} -mindepth 1 -not -path '${this.shadowPath}/.git*' -delete`);
430+
416431
// Rsync within container to staging area using exclude file
417432
const rsyncCmd = `docker exec ${containerId} rsync -av --delete \
418433
--exclude-from=${containerExcludeFile} \

test/README.md

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,69 @@
1-
# Claude Code Sandbox Tests
1+
# Test Directory Structure
22

3-
This directory contains tests for the Claude Code Sandbox project.
3+
This directory contains all tests for the Claude Code Sandbox project.
44

5-
## Test Structure
5+
## Directory Layout
66

7-
- `unit/` - Unit tests for individual components
8-
- `integration/` - Integration tests for testing multiple components together
9-
- `e2e/` - End-to-end tests for testing full workflows
7+
```
8+
test/
9+
├── unit/ # Unit tests for individual modules
10+
├── integration/ # Integration tests for module interactions
11+
├── e2e/ # End-to-end tests for full workflow scenarios
12+
├── fixtures/ # Test data, mock responses, sample files
13+
└── helpers/ # Shared test utilities and helpers
14+
```
1015

1116
## Running Tests
1217

1318
```bash
1419
# Run all tests
1520
npm test
1621

17-
# Run specific test file
18-
npm test test/unit/container.test.js
22+
# Run tests in watch mode
23+
npm run test:watch
24+
25+
# Run only unit tests
26+
npm run test:unit
27+
28+
# Run only integration tests
29+
npm run test:integration
30+
31+
# Run only E2E tests
32+
npm run test:e2e
33+
34+
# Run tests with coverage
35+
npm run test:coverage
1936
```
2037

38+
## Test Naming Conventions
39+
40+
- Unit tests: `*.test.ts` or `*.spec.ts`
41+
- Test files should mirror the source structure
42+
- Example: `test/unit/container.test.ts` tests `src/container.ts`
43+
2144
## Writing Tests
2245

23-
Tests should be written using a testing framework like Jest or Mocha. Each test file should be self-contained and test a specific component or feature.
46+
Tests are written using Jest with TypeScript support. The Jest configuration is in `jest.config.js` at the project root.
47+
48+
### Example Unit Test
49+
50+
```typescript
51+
import { someFunction } from '../../src/someModule';
52+
53+
describe('someFunction', () => {
54+
it('should do something', () => {
55+
const result = someFunction('input');
56+
expect(result).toBe('expected output');
57+
});
58+
});
59+
```
60+
61+
## E2E Tests
62+
63+
End-to-end tests are located in `test/e2e/` and test the complete workflow of the CLI tool. These tests:
64+
- Create actual Docker containers
65+
- Run Claude commands
66+
- Verify git operations
67+
- Test the full user experience
68+
69+
Run E2E tests with: `npm run test:e2e`

0 commit comments

Comments
 (0)