mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-23 06:54:13 +08:00
Compare commits
4 Commits
v1.0.30
...
ashwin/bum
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
142a77fa55 | ||
|
|
ba60ef7ba2 | ||
|
|
f3c892ca8d | ||
|
|
6e896a06bb |
37
.github/workflows/ci-all.yml
vendored
Normal file
37
.github/workflows/ci-all.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Orchestrates all CI workflows - runs on PRs, pushes to main, and manual dispatch
|
||||||
|
# Individual test workflows are called as reusable workflows
|
||||||
|
name: CI All
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ci:
|
||||||
|
uses: ./.github/workflows/ci.yml
|
||||||
|
|
||||||
|
test-base-action:
|
||||||
|
uses: ./.github/workflows/test-base-action.yml
|
||||||
|
secrets: inherit # Required for ANTHROPIC_API_KEY
|
||||||
|
|
||||||
|
test-custom-executables:
|
||||||
|
uses: ./.github/workflows/test-custom-executables.yml
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
test-mcp-servers:
|
||||||
|
uses: ./.github/workflows/test-mcp-servers.yml
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
test-settings:
|
||||||
|
uses: ./.github/workflows/test-settings.yml
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
test-structured-output:
|
||||||
|
uses: ./.github/workflows/test-structured-output.yml
|
||||||
|
secrets: inherit
|
||||||
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@@ -1,9 +1,8 @@
|
|||||||
name: CI
|
name: CI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
pull_request:
|
pull_request:
|
||||||
|
workflow_call:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
|
|||||||
16
.github/workflows/release.yml
vendored
16
.github/workflows/release.yml
vendored
@@ -8,10 +8,23 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
|
workflow_run:
|
||||||
|
workflows: ["CI All"]
|
||||||
|
types:
|
||||||
|
- completed
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
create-release:
|
create-release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
# Run if: manual dispatch OR (CI All succeeded AND commit is a version bump)
|
||||||
|
if: |
|
||||||
|
github.event_name == 'workflow_dispatch' ||
|
||||||
|
(github.event.workflow_run.conclusion == 'success' &&
|
||||||
|
github.event.workflow_run.head_branch == 'main' &&
|
||||||
|
github.event.workflow_run.event == 'push' &&
|
||||||
|
startsWith(github.event.workflow_run.head_commit.message, 'chore: bump Claude Code to'))
|
||||||
environment: production
|
environment: production
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@@ -84,7 +97,8 @@ jobs:
|
|||||||
|
|
||||||
update-major-tag:
|
update-major-tag:
|
||||||
needs: create-release
|
needs: create-release
|
||||||
if: ${{ !inputs.dry_run }}
|
# Skip for dry runs (workflow_run events are never dry runs)
|
||||||
|
if: github.event_name == 'workflow_run' || !inputs.dry_run
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
environment: production
|
environment: production
|
||||||
permissions:
|
permissions:
|
||||||
|
|||||||
4
.github/workflows/test-base-action.yml
vendored
4
.github/workflows/test-base-action.yml
vendored
@@ -1,9 +1,6 @@
|
|||||||
name: Test Claude Code Action
|
name: Test Claude Code Action
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
pull_request:
|
pull_request:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
@@ -11,6 +8,7 @@ on:
|
|||||||
description: "Test prompt for Claude"
|
description: "Test prompt for Claude"
|
||||||
required: false
|
required: false
|
||||||
default: "List the files in the current directory starting with 'package'"
|
default: "List the files in the current directory starting with 'package'"
|
||||||
|
workflow_call:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test-inline-prompt:
|
test-inline-prompt:
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
name: Test Custom Executables
|
name: Test Custom Executables
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
pull_request:
|
pull_request:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
workflow_call:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test-custom-executables:
|
test-custom-executables:
|
||||||
|
|||||||
4
.github/workflows/test-mcp-servers.yml
vendored
4
.github/workflows/test-mcp-servers.yml
vendored
@@ -1,11 +1,9 @@
|
|||||||
name: Test MCP Servers
|
name: Test MCP Servers
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main]
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
workflow_call:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test-mcp-integration:
|
test-mcp-integration:
|
||||||
|
|||||||
4
.github/workflows/test-settings.yml
vendored
4
.github/workflows/test-settings.yml
vendored
@@ -1,11 +1,9 @@
|
|||||||
name: Test Settings Feature
|
name: Test Settings Feature
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
pull_request:
|
pull_request:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
workflow_call:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test-settings-inline-allow:
|
test-settings-inline-allow:
|
||||||
|
|||||||
4
.github/workflows/test-structured-output.yml
vendored
4
.github/workflows/test-structured-output.yml
vendored
@@ -1,11 +1,9 @@
|
|||||||
name: Test Structured Outputs
|
name: Test Structured Outputs
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
pull_request:
|
pull_request:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
workflow_call:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|||||||
@@ -148,9 +148,9 @@ runs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Install Bun
|
- name: Install Bun
|
||||||
if: inputs.path_to_bun_executable == ''
|
if: inputs.path_to_bun_executable == ''
|
||||||
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # https://github.com/oven-sh/setup-bun/releases/tag/v2.0.2
|
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # https://github.com/oven-sh/setup-bun/releases/tag/v2.1.2
|
||||||
with:
|
with:
|
||||||
bun-version: 1.2.11
|
bun-version: 2.1.1
|
||||||
|
|
||||||
- name: Setup Custom Bun Path
|
- name: Setup Custom Bun Path
|
||||||
if: inputs.path_to_bun_executable != ''
|
if: inputs.path_to_bun_executable != ''
|
||||||
@@ -213,7 +213,7 @@ runs:
|
|||||||
|
|
||||||
# Install Claude Code if no custom executable is provided
|
# Install Claude Code if no custom executable is provided
|
||||||
if [ -z "$PATH_TO_CLAUDE_CODE_EXECUTABLE" ]; then
|
if [ -z "$PATH_TO_CLAUDE_CODE_EXECUTABLE" ]; then
|
||||||
CLAUDE_CODE_VERSION="2.1.9"
|
CLAUDE_CODE_VERSION="2.1.11"
|
||||||
echo "Installing Claude Code v${CLAUDE_CODE_VERSION}..."
|
echo "Installing Claude Code v${CLAUDE_CODE_VERSION}..."
|
||||||
for attempt in 1 2 3; do
|
for attempt in 1 2 3; do
|
||||||
echo "Installation attempt $attempt..."
|
echo "Installation attempt $attempt..."
|
||||||
|
|||||||
@@ -97,9 +97,9 @@ runs:
|
|||||||
|
|
||||||
- name: Install Bun
|
- name: Install Bun
|
||||||
if: inputs.path_to_bun_executable == ''
|
if: inputs.path_to_bun_executable == ''
|
||||||
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # https://github.com/oven-sh/setup-bun/releases/tag/v2.0.2
|
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # https://github.com/oven-sh/setup-bun/releases/tag/v2.1.2
|
||||||
with:
|
with:
|
||||||
bun-version: 1.2.11
|
bun-version: 2.1.1
|
||||||
|
|
||||||
- name: Setup Custom Bun Path
|
- name: Setup Custom Bun Path
|
||||||
if: inputs.path_to_bun_executable != ''
|
if: inputs.path_to_bun_executable != ''
|
||||||
@@ -124,7 +124,7 @@ runs:
|
|||||||
PATH_TO_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_executable }}
|
PATH_TO_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_executable }}
|
||||||
run: |
|
run: |
|
||||||
if [ -z "$PATH_TO_CLAUDE_CODE_EXECUTABLE" ]; then
|
if [ -z "$PATH_TO_CLAUDE_CODE_EXECUTABLE" ]; then
|
||||||
CLAUDE_CODE_VERSION="2.1.9"
|
CLAUDE_CODE_VERSION="2.1.11"
|
||||||
echo "Installing Claude Code v${CLAUDE_CODE_VERSION}..."
|
echo "Installing Claude Code v${CLAUDE_CODE_VERSION}..."
|
||||||
for attempt in 1 2 3; do
|
for attempt in 1 2 3; do
|
||||||
echo "Installation attempt $attempt..."
|
echo "Installation attempt $attempt..."
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
"name": "@anthropic-ai/claude-code-base-action",
|
"name": "@anthropic-ai/claude-code-base-action",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.10.1",
|
"@actions/core": "^1.10.1",
|
||||||
"@anthropic-ai/claude-agent-sdk": "^0.2.9",
|
"@anthropic-ai/claude-agent-sdk": "^0.2.11",
|
||||||
"shell-quote": "^1.8.3",
|
"shell-quote": "^1.8.3",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
"@actions/io": ["@actions/io@1.1.3", "", {}, "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="],
|
"@actions/io": ["@actions/io@1.1.3", "", {}, "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="],
|
||||||
|
|
||||||
"@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.2.9", "", { "optionalDependencies": { "@img/sharp-darwin-arm64": "^0.33.5", "@img/sharp-darwin-x64": "^0.33.5", "@img/sharp-linux-arm": "^0.33.5", "@img/sharp-linux-arm64": "^0.33.5", "@img/sharp-linux-x64": "^0.33.5", "@img/sharp-linuxmusl-arm64": "^0.33.5", "@img/sharp-linuxmusl-x64": "^0.33.5", "@img/sharp-win32-x64": "^0.33.5" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-b4JD6ZKCZeVDqpWBnb+zJISWi3HzlweNlV7Oy/uo5G2XAfUV2M5AJ/tomKZCvZsvmr1fYbmmfyde3GL2h0pksA=="],
|
"@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.2.11", "", { "optionalDependencies": { "@img/sharp-darwin-arm64": "^0.33.5", "@img/sharp-darwin-x64": "^0.33.5", "@img/sharp-linux-arm": "^0.33.5", "@img/sharp-linux-arm64": "^0.33.5", "@img/sharp-linux-x64": "^0.33.5", "@img/sharp-linuxmusl-arm64": "^0.33.5", "@img/sharp-linuxmusl-x64": "^0.33.5", "@img/sharp-win32-x64": "^0.33.5" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-50q4vfh57HYTUrwukULp3gvSaOZfRF5zukxhWvW6mmUHuD8+Xwhqn59sZhoDz9ZUw6Um+1lUCgWpJw5/TTMn5w=="],
|
||||||
|
|
||||||
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],
|
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.10.1",
|
"@actions/core": "^1.10.1",
|
||||||
"@anthropic-ai/claude-agent-sdk": "^0.2.9",
|
"@anthropic-ai/claude-agent-sdk": "^0.2.11",
|
||||||
"shell-quote": "^1.8.3"
|
"shell-quote": "^1.8.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
4
bun.lock
4
bun.lock
@@ -7,7 +7,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.10.1",
|
"@actions/core": "^1.10.1",
|
||||||
"@actions/github": "^6.0.1",
|
"@actions/github": "^6.0.1",
|
||||||
"@anthropic-ai/claude-agent-sdk": "^0.2.9",
|
"@anthropic-ai/claude-agent-sdk": "^0.2.11",
|
||||||
"@modelcontextprotocol/sdk": "^1.11.0",
|
"@modelcontextprotocol/sdk": "^1.11.0",
|
||||||
"@octokit/graphql": "^8.2.2",
|
"@octokit/graphql": "^8.2.2",
|
||||||
"@octokit/rest": "^21.1.1",
|
"@octokit/rest": "^21.1.1",
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
|
|
||||||
"@actions/io": ["@actions/io@1.1.3", "", {}, "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="],
|
"@actions/io": ["@actions/io@1.1.3", "", {}, "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="],
|
||||||
|
|
||||||
"@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.2.9", "", { "optionalDependencies": { "@img/sharp-darwin-arm64": "^0.33.5", "@img/sharp-darwin-x64": "^0.33.5", "@img/sharp-linux-arm": "^0.33.5", "@img/sharp-linux-arm64": "^0.33.5", "@img/sharp-linux-x64": "^0.33.5", "@img/sharp-linuxmusl-arm64": "^0.33.5", "@img/sharp-linuxmusl-x64": "^0.33.5", "@img/sharp-win32-x64": "^0.33.5" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-b4JD6ZKCZeVDqpWBnb+zJISWi3HzlweNlV7Oy/uo5G2XAfUV2M5AJ/tomKZCvZsvmr1fYbmmfyde3GL2h0pksA=="],
|
"@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.2.11", "", { "optionalDependencies": { "@img/sharp-darwin-arm64": "^0.33.5", "@img/sharp-darwin-x64": "^0.33.5", "@img/sharp-linux-arm": "^0.33.5", "@img/sharp-linux-arm64": "^0.33.5", "@img/sharp-linux-x64": "^0.33.5", "@img/sharp-linuxmusl-arm64": "^0.33.5", "@img/sharp-linuxmusl-x64": "^0.33.5", "@img/sharp-win32-x64": "^0.33.5" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-50q4vfh57HYTUrwukULp3gvSaOZfRF5zukxhWvW6mmUHuD8+Xwhqn59sZhoDz9ZUw6Um+1lUCgWpJw5/TTMn5w=="],
|
||||||
|
|
||||||
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],
|
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.10.1",
|
"@actions/core": "^1.10.1",
|
||||||
"@actions/github": "^6.0.1",
|
"@actions/github": "^6.0.1",
|
||||||
"@anthropic-ai/claude-agent-sdk": "^0.2.9",
|
"@anthropic-ai/claude-agent-sdk": "^0.2.11",
|
||||||
"@modelcontextprotocol/sdk": "^1.11.0",
|
"@modelcontextprotocol/sdk": "^1.11.0",
|
||||||
"@octokit/graphql": "^8.2.2",
|
"@octokit/graphql": "^8.2.2",
|
||||||
"@octokit/rest": "^21.1.1",
|
"@octokit/rest": "^21.1.1",
|
||||||
|
|||||||
@@ -82,8 +82,13 @@ export async function setupSshSigning(sshSigningKey: string): Promise<void> {
|
|||||||
const sshDir = join(homedir(), ".ssh");
|
const sshDir = join(homedir(), ".ssh");
|
||||||
await mkdir(sshDir, { recursive: true, mode: 0o700 });
|
await mkdir(sshDir, { recursive: true, mode: 0o700 });
|
||||||
|
|
||||||
|
// Ensure key ends with newline (required for ssh-keygen to parse it)
|
||||||
|
const normalizedKey = sshSigningKey.endsWith("\n")
|
||||||
|
? sshSigningKey
|
||||||
|
: sshSigningKey + "\n";
|
||||||
|
|
||||||
// Write the signing key atomically with secure permissions (600)
|
// Write the signing key atomically with secure permissions (600)
|
||||||
await writeFile(SSH_SIGNING_KEY_PATH, sshSigningKey, { mode: 0o600 });
|
await writeFile(SSH_SIGNING_KEY_PATH, normalizedKey, { mode: 0o600 });
|
||||||
console.log(`✓ SSH signing key written to ${SSH_SIGNING_KEY_PATH}`);
|
console.log(`✓ SSH signing key written to ${SSH_SIGNING_KEY_PATH}`);
|
||||||
|
|
||||||
// Configure git to use SSH signing
|
// Configure git to use SSH signing
|
||||||
|
|||||||
@@ -55,6 +55,47 @@ describe("SSH Signing", () => {
|
|||||||
expect(permissions).toBe(0o600);
|
expect(permissions).toBe(0o600);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("should normalize key to have trailing newline", async () => {
|
||||||
|
// ssh-keygen requires a trailing newline to parse the key
|
||||||
|
const keyWithoutNewline =
|
||||||
|
"-----BEGIN OPENSSH PRIVATE KEY-----\ntest-key-content\n-----END OPENSSH PRIVATE KEY-----";
|
||||||
|
const keyWithNewline = keyWithoutNewline + "\n";
|
||||||
|
|
||||||
|
// Create directory
|
||||||
|
await mkdir(testSshDir, { recursive: true, mode: 0o700 });
|
||||||
|
|
||||||
|
// Normalize the key (same logic as setupSshSigning)
|
||||||
|
const normalizedKey = keyWithoutNewline.endsWith("\n")
|
||||||
|
? keyWithoutNewline
|
||||||
|
: keyWithoutNewline + "\n";
|
||||||
|
|
||||||
|
await writeFile(testKeyPath, normalizedKey, { mode: 0o600 });
|
||||||
|
|
||||||
|
// Verify the written key ends with newline
|
||||||
|
const keyContent = await readFile(testKeyPath, "utf-8");
|
||||||
|
expect(keyContent).toBe(keyWithNewline);
|
||||||
|
expect(keyContent.endsWith("\n")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should not add extra newline if key already has one", async () => {
|
||||||
|
const keyWithNewline =
|
||||||
|
"-----BEGIN OPENSSH PRIVATE KEY-----\ntest-key-content\n-----END OPENSSH PRIVATE KEY-----\n";
|
||||||
|
|
||||||
|
await mkdir(testSshDir, { recursive: true, mode: 0o700 });
|
||||||
|
|
||||||
|
// Normalize the key (same logic as setupSshSigning)
|
||||||
|
const normalizedKey = keyWithNewline.endsWith("\n")
|
||||||
|
? keyWithNewline
|
||||||
|
: keyWithNewline + "\n";
|
||||||
|
|
||||||
|
await writeFile(testKeyPath, normalizedKey, { mode: 0o600 });
|
||||||
|
|
||||||
|
// Verify no double newline
|
||||||
|
const keyContent = await readFile(testKeyPath, "utf-8");
|
||||||
|
expect(keyContent).toBe(keyWithNewline);
|
||||||
|
expect(keyContent.endsWith("\n\n")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
test("should create .ssh directory with secure permissions", async () => {
|
test("should create .ssh directory with secure permissions", async () => {
|
||||||
// Clean up first
|
// Clean up first
|
||||||
await rm(testSshDir, { recursive: true, force: true });
|
await rm(testSshDir, { recursive: true, force: true });
|
||||||
|
|||||||
Reference in New Issue
Block a user