Skip to content

Commit 30c7a27

Browse files
authored
Merge pull request #27 from yamadashy/chore/devcon
chore(devcontainer): Replace PHP environment with Claude Code sandbox
2 parents 7b9e045 + aa79abf commit 30c7a27

File tree

4 files changed

+184
-79
lines changed

4 files changed

+184
-79
lines changed

.devcontainer/Dockerfile

Lines changed: 89 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,92 @@
1-
FROM mcr.microsoft.com/vscode/devcontainers/php:8.4
1+
FROM node:24
22

3-
# PHP memory limit
4-
RUN echo "memory_limit=768M" > /usr/local/etc/php/php.ini
3+
ARG TZ
4+
ENV TZ="$TZ"
55

6+
ARG CLAUDE_CODE_VERSION=latest
7+
8+
# Install basic development tools
9+
RUN apt-get update && apt-get install -y --no-install-recommends \
10+
less \
11+
git \
12+
procps \
13+
sudo \
14+
fzf \
15+
zsh \
16+
man-db \
17+
unzip \
18+
gnupg2 \
19+
gh \
20+
jq \
21+
nano \
22+
vim \
23+
ca-certificates \
24+
curl \
25+
lsb-release \
26+
&& apt-get clean && rm -rf /var/lib/apt/lists/*
27+
28+
# Install PHP 8.4 from Sury repository
29+
RUN curl -sSL https://packages.sury.org/php/README.txt | bash -x && \
30+
apt-get update && apt-get install -y --no-install-recommends \
31+
php8.4-cli \
32+
php8.4-mbstring \
33+
php8.4-xml \
34+
php8.4-curl \
35+
php8.4-zip \
36+
&& apt-get clean && rm -rf /var/lib/apt/lists/*
37+
38+
# Install Composer
639
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer
40+
41+
# Ensure default node user has access to /usr/local/share
42+
RUN mkdir -p /usr/local/share/npm-global && \
43+
chown -R node:node /usr/local/share/npm-global
44+
45+
ARG USERNAME=node
46+
47+
# Persist shell history
48+
RUN mkdir /commandhistory \
49+
&& touch /commandhistory/.bash_history \
50+
&& chown -R $USERNAME /commandhistory
51+
52+
# Set `DEVCONTAINER` environment variable to help with orientation
53+
ENV DEVCONTAINER=true
54+
55+
# Create workspace and config directories and set permissions
56+
RUN mkdir -p /workspace /home/node/.claude && \
57+
chown -R node:node /workspace /home/node/.claude
58+
59+
WORKDIR /workspace
60+
61+
ARG GIT_DELTA_VERSION=0.18.2
62+
RUN ARCH=$(dpkg --print-architecture) && \
63+
wget "https://github.com/dandavison/delta/releases/download/${GIT_DELTA_VERSION}/git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb" && \
64+
dpkg -i "git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb" && \
65+
rm "git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb"
66+
67+
# Set up non-root user
68+
USER node
69+
70+
# Install global packages
71+
ENV NPM_CONFIG_PREFIX=/usr/local/share/npm-global
72+
ENV PATH=$PATH:/usr/local/share/npm-global/bin
73+
74+
# Set the default shell to zsh rather than sh
75+
ENV SHELL=/bin/zsh
76+
77+
# Set the default editor and visual
78+
ENV EDITOR=nano
79+
ENV VISUAL=nano
80+
81+
# Default powerline10k theme
82+
ARG ZSH_IN_DOCKER_VERSION=1.2.0
83+
RUN sh -c "$(wget -O- https://github.com/deluan/zsh-in-docker/releases/download/v${ZSH_IN_DOCKER_VERSION}/zsh-in-docker.sh)" -- \
84+
-p git \
85+
-p fzf \
86+
-a "source /usr/share/doc/fzf/examples/key-bindings.zsh" \
87+
-a "source /usr/share/doc/fzf/examples/completion.zsh" \
88+
-a "setopt APPEND_HISTORY INC_APPEND_HISTORY SHARE_HISTORY; export HISTFILE=/commandhistory/.bash_history" \
89+
-x
90+
91+
# Install Claude
92+
RUN npm install -g @anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}

.devcontainer/devcontainer.json

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,46 @@
11
{
2-
"name": "PHP",
3-
"build": {
4-
"dockerfile": "Dockerfile"
5-
},
6-
7-
// Set *default* container specific settings.json values on container create.
8-
"settings": {
9-
"terminal.integrated.shell.linux": "/bin/bash"
10-
},
11-
12-
// Add the IDs of extensions you want installed when the container is created.
13-
"extensions": [
14-
"felixfbecker.php-debug",
15-
"felixfbecker.php-intellisense",
16-
"mrmlnc.vscode-apache"
17-
],
18-
19-
// Use 'forwardPorts' to make a list of ports inside the container available locally.
20-
"forwardPorts": [8080],
21-
22-
// Use 'postCreateCommand' to run commands after the container is created.
23-
"postCreateCommand": "composer install"
24-
25-
// Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root.
26-
// "remoteUser": "vscode"
2+
"name": "Claude Code Sandbox",
3+
"build": {
4+
"dockerfile": "Dockerfile",
5+
"args": {
6+
"TZ": "${localEnv:TZ:America/Los_Angeles}",
7+
"CLAUDE_CODE_VERSION": "latest",
8+
"GIT_DELTA_VERSION": "0.18.2",
9+
"ZSH_IN_DOCKER_VERSION": "1.2.0"
10+
}
11+
},
12+
"customizations": {
13+
"vscode": {
14+
"extensions": [
15+
"anthropic.claude-code",
16+
"eamodio.gitlens"
17+
],
18+
"settings": {
19+
"terminal.integrated.defaultProfile.linux": "zsh",
20+
"terminal.integrated.profiles.linux": {
21+
"bash": {
22+
"path": "bash",
23+
"icon": "terminal-bash"
24+
},
25+
"zsh": {
26+
"path": "zsh"
27+
}
28+
}
29+
}
30+
}
31+
},
32+
"remoteUser": "node",
33+
"mounts": [
34+
"source=claude-code-bashhistory-${devcontainerId},target=/commandhistory,type=volume",
35+
"source=claude-code-config-${devcontainerId},target=/home/node/.claude,type=volume"
36+
],
37+
"containerEnv": {
38+
"NODE_OPTIONS": "--max-old-space-size=4096",
39+
"CLAUDE_CONFIG_DIR": "/home/node/.claude",
40+
"POWERLEVEL9K_DISABLE_GITSTATUS": "true"
41+
},
42+
"workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=delegated",
43+
"workspaceFolder": "/workspace",
44+
"postStartCommand": "composer install"
2745
}
46+

tests/CodeHighlight/CodeHighlighterTest.php

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,29 @@
1313
*/
1414
final class CodeHighlighterTest extends TestCase
1515
{
16+
/**
17+
* @dataProvider provideHighlightCases
18+
*
19+
* @covers ::highlight
20+
*/
21+
public function testHighlight(
22+
string $filePath,
23+
int $lineNumber,
24+
int $lineBefore,
25+
int $lineAfter,
26+
string $expectedOutput
27+
): void {
28+
$codeHighlighter = new CodeHighlighter();
29+
30+
$fileContent = (string) file_get_contents($filePath);
31+
32+
$output = $codeHighlighter->highlight($fileContent, $lineNumber, $lineBefore, $lineAfter);
33+
$output = StringUtil::escapeTextColors($output);
34+
$output = StringUtil::rtrimByLines($output);
35+
36+
self::assertSame($expectedOutput, $output);
37+
}
38+
1639
/**
1740
* @return \Generator<string, (int|string)[], void, void>
1841
*/
@@ -81,27 +104,4 @@ public static function provideHighlightCases(): iterable
81104
' > 13| return 2;',
82105
];
83106
}
84-
85-
/**
86-
* @dataProvider provideHighlightCases
87-
*
88-
* @covers ::highlight
89-
*/
90-
public function testHighlight(
91-
string $filePath,
92-
int $lineNumber,
93-
int $lineBefore,
94-
int $lineAfter,
95-
string $expectedOutput
96-
): void {
97-
$codeHighlighter = new CodeHighlighter();
98-
99-
$fileContent = (string) file_get_contents($filePath);
100-
101-
$output = $codeHighlighter->highlight($fileContent, $lineNumber, $lineBefore, $lineAfter);
102-
$output = StringUtil::escapeTextColors($output);
103-
$output = StringUtil::rtrimByLines($output);
104-
105-
self::assertSame($expectedOutput, $output);
106-
}
107107
}

tests/FriendlyErrorFormatterTest.php

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,34 @@
1818
*/
1919
final class FriendlyErrorFormatterTest extends ErrorFormatterTestCase
2020
{
21+
/**
22+
* @dataProvider provideFormatErrorsCases
23+
*
24+
* @param list<string> $expectedOutputSubstrings
25+
*
26+
* @covers ::formatErrors
27+
*/
28+
public function testFormatErrors(
29+
int $expectedExitCode,
30+
int $numFileErrors,
31+
int $numGenericErrors,
32+
int $numWarnings,
33+
array $expectedOutputSubstrings
34+
): void {
35+
$relativePathHelper = new FuzzyRelativePathHelper(new NullRelativePathHelper(), '', [], '/');
36+
$formatter = new FriendlyErrorFormatter($relativePathHelper, 3, 3, null);
37+
$dummyAnalysisResult = $this->getDummyAnalysisResult($numFileErrors, $numGenericErrors, $numWarnings);
38+
39+
$exitCode = $formatter->formatErrors($dummyAnalysisResult, $this->getOutput());
40+
$outputContent = StringUtil::escapeTextColors($this->getOutputContent());
41+
$outputContent = StringUtil::rtrimByLines($outputContent);
42+
43+
self::assertSame($expectedExitCode, $exitCode);
44+
foreach ($expectedOutputSubstrings as $expectedOutputSubstring) {
45+
self::assertStringContainsString($expectedOutputSubstring, $outputContent);
46+
}
47+
}
48+
2149
/**
2250
* @return \Generator<string, (int|list<string>)[], void, void>
2351
*/
@@ -164,34 +192,6 @@ public static function provideFormatErrorsCases(): iterable
164192
];
165193
}
166194

167-
/**
168-
* @dataProvider provideFormatErrorsCases
169-
*
170-
* @param list<string> $expectedOutputSubstrings
171-
*
172-
* @covers ::formatErrors
173-
*/
174-
public function testFormatErrors(
175-
int $expectedExitCode,
176-
int $numFileErrors,
177-
int $numGenericErrors,
178-
int $numWarnings,
179-
array $expectedOutputSubstrings
180-
): void {
181-
$relativePathHelper = new FuzzyRelativePathHelper(new NullRelativePathHelper(), '', [], '/');
182-
$formatter = new FriendlyErrorFormatter($relativePathHelper, 3, 3, null);
183-
$dummyAnalysisResult = $this->getDummyAnalysisResult($numFileErrors, $numGenericErrors, $numWarnings);
184-
185-
$exitCode = $formatter->formatErrors($dummyAnalysisResult, $this->getOutput());
186-
$outputContent = StringUtil::escapeTextColors($this->getOutputContent());
187-
$outputContent = StringUtil::rtrimByLines($outputContent);
188-
189-
self::assertSame($expectedExitCode, $exitCode);
190-
foreach ($expectedOutputSubstrings as $expectedOutputSubstring) {
191-
self::assertStringContainsString($expectedOutputSubstring, $outputContent);
192-
}
193-
}
194-
195195
/**
196196
* @throws ShouldNotHappenException
197197
*/

0 commit comments

Comments
 (0)