Skip to content

Commit 07a1462

Browse files
committed
add force option
1 parent 1eaf74e commit 07a1462

File tree

4 files changed

+148
-6
lines changed

4 files changed

+148
-6
lines changed

TESTING.md

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ jobs:
8686
access-token: ${{ secrets.LAUNCHDARKLY_TOKEN }}
8787
repo: ${{ github.repository }}
8888
branch: ${{ github.ref_name }}
89+
force: true # Optional: force delete even on non-branch events
8990
```
9091
9192
## 4. API Testing
@@ -99,7 +100,25 @@ curl -X POST https://app.launchdarkly.com/api/v2/code-refs/repositories/your-rep
99100
-d '["branch-to-delete"]'
100101
```
101102

102-
## 5. Test Scenarios
103+
## 5. Force Option
104+
105+
The `force` option allows you to delete LaunchDarkly branches even when the GitHub event is not a branch delete event:
106+
107+
- **Default behavior**: Only runs on branch delete events (`ref_type: "branch"`)
108+
- **With `force: true`**: Runs on any delete event (tags, branches, etc.)
109+
- **Use case**: Useful for cleanup workflows or manual branch deletion
110+
111+
Example:
112+
```yaml
113+
- name: Force Delete Branch
114+
uses: ./
115+
with:
116+
access-token: ${{ secrets.LAUNCHDARKLY_TOKEN }}
117+
branch: "feature-branch"
118+
force: true
119+
```
120+
121+
## 6. Test Scenarios
103122
104123
The test suite covers:
105124
@@ -121,7 +140,7 @@ The test suite covers:
121140
- Network timeouts
122141
- Exponential backoff
123142
124-
## 6. Debugging
143+
## 7. Debugging
125144
126145
### Enable Debug Logging
127146
@@ -140,7 +159,7 @@ The action logs detailed information about:
140159
- Error messages
141160
- Retry attempts
142161

143-
## 7. Continuous Integration
162+
## 8. Continuous Integration
144163

145164
The action includes:
146165
- **Linting** with ESLint

__tests__/integration.test.ts

Lines changed: 119 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as core from '@actions/core';
22
import * as github from '@actions/github';
33
import nock from 'nock';
4-
import { deleteBranch } from '../src/index';
4+
import { deleteBranch, run } from '../src/index';
55

66
// Mock the modules
77
jest.mock('@actions/core');
@@ -63,4 +63,122 @@ describe('GitHub Action Integration', () => {
6363

6464
expect(scope.isDone()).toBe(true);
6565
});
66+
});
67+
68+
describe('run function', () => {
69+
beforeEach(() => {
70+
jest.clearAllMocks();
71+
nock.cleanAll();
72+
73+
// Reset environment
74+
delete process.env.GITHUB_REPOSITORY;
75+
delete process.env.NODE_ENV;
76+
77+
// Mock core.getInput
78+
(core.getInput as jest.Mock).mockImplementation((name: string) => {
79+
const inputs: Record<string, string> = {
80+
'access-token': 'test-token',
81+
'repo': 'test-repo',
82+
'branch': 'test-branch',
83+
'base-uri': 'https://app.launchdarkly.com',
84+
'force': 'false'
85+
};
86+
return inputs[name] || '';
87+
});
88+
89+
// Mock core.getBooleanInput
90+
(core.getBooleanInput as jest.Mock).mockImplementation((name: string) => {
91+
const inputs: Record<string, boolean> = {
92+
'force': false
93+
};
94+
return inputs[name] || false;
95+
});
96+
97+
// Mock github context
98+
(github.context as any) = {
99+
eventName: 'delete',
100+
payload: {
101+
ref_type: 'branch',
102+
ref: 'test-branch',
103+
repository: {
104+
name: 'test-repo'
105+
}
106+
}
107+
};
108+
});
109+
110+
afterEach(() => {
111+
nock.cleanAll();
112+
});
113+
114+
it('should skip non-branch delete events by default', async () => {
115+
(github.context as any).payload.ref_type = 'tag';
116+
117+
await run();
118+
119+
expect(core.info).toHaveBeenCalledWith('Skipping non-branch delete event (ref_type=tag). Use force: true to override.');
120+
expect(core.setFailed).not.toHaveBeenCalled();
121+
});
122+
123+
it('should proceed with non-branch delete events when force-delete is true', async () => {
124+
(github.context as any).payload.ref_type = 'tag';
125+
(core.getBooleanInput as jest.Mock).mockReturnValue(true);
126+
127+
const scope = nock('https://app.launchdarkly.com')
128+
.post('/api/v2/code-refs/repositories/test-repo/branch-delete-tasks')
129+
.reply(200, { success: true });
130+
131+
await run();
132+
133+
expect(core.info).toHaveBeenCalledWith("Deleting LaunchDarkly Code Refs branch 'test-branch' in repo 'test-repo'...");
134+
expect(core.info).toHaveBeenCalledWith('✅ LaunchDarkly Code Refs branch delete queued successfully.');
135+
expect(scope.isDone()).toBe(true);
136+
});
137+
138+
it('should proceed with branch delete events normally', async () => {
139+
(github.context as any).payload.ref_type = 'branch';
140+
141+
const scope = nock('https://app.launchdarkly.com')
142+
.post('/api/v2/code-refs/repositories/test-repo/branch-delete-tasks')
143+
.reply(200, { success: true });
144+
145+
await run();
146+
147+
expect(core.info).toHaveBeenCalledWith("Deleting LaunchDarkly Code Refs branch 'test-branch' in repo 'test-repo'...");
148+
expect(core.info).toHaveBeenCalledWith('✅ LaunchDarkly Code Refs branch delete queued successfully.');
149+
expect(scope.isDone()).toBe(true);
150+
});
151+
152+
it('should handle missing repository key', async () => {
153+
(core.getInput as jest.Mock).mockImplementation((name: string) => {
154+
const inputs: Record<string, string> = {
155+
'access-token': 'test-token',
156+
'branch': 'test-branch'
157+
};
158+
return inputs[name] || '';
159+
});
160+
161+
(github.context as any).payload.repository = null;
162+
delete process.env.GITHUB_REPOSITORY;
163+
164+
await run();
165+
166+
expect(core.setFailed).toHaveBeenCalledWith('Action failed: Repository key not found');
167+
});
168+
169+
it('should handle missing branch ref', async () => {
170+
(core.getInput as jest.Mock).mockImplementation((name: string) => {
171+
const inputs: Record<string, string> = {
172+
'access-token': 'test-token',
173+
'repo': 'test-repo'
174+
};
175+
return inputs[name] || '';
176+
});
177+
178+
(github.context as any).payload.ref = null;
179+
180+
await run();
181+
182+
expect(core.setFailed).toHaveBeenCalledWith('Action failed: Branch ref not found');
183+
});
66184
});

action.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ inputs:
1919
description: 'LaunchDarkly base URI (defaults to https://app.launchdarkly.com)'
2020
required: false
2121
default: 'https://app.launchdarkly.com'
22+
force:
23+
description: 'Force delete even when not triggered by a branch delete event (defaults to false)'
24+
required: false
25+
default: 'false'
2226

2327
runs:
2428
using: 'node20'

src/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ export async function run() {
8484
try {
8585
const token = core.getInput("access-token", { required: true });
8686
const baseUri = core.getInput("base-uri") || "https://app.launchdarkly.com";
87+
const forceDelete = core.getBooleanInput("force");
8788
const ghRepoName =
8889
github.context?.payload?.repository?.name || process.env.GITHUB_REPOSITORY?.split("/")?.[1];
8990
const repoKey = core.getInput("repo") || ghRepoName;
@@ -94,8 +95,8 @@ export async function run() {
9495

9596
if (!repoKey) throw new Error("Repository key not found");
9697
if (!ref) throw new Error("Branch ref not found");
97-
if (github.context.eventName === "delete" && refType && refType !== "branch") {
98-
core.info(`Skipping non-branch delete event (ref_type=${refType})`);
98+
if (github.context.eventName === "delete" && refType && refType !== "branch" && !forceDelete) {
99+
core.info(`Skipping non-branch delete event (ref_type=${refType}). Use force: true to override.`);
99100
return;
100101
}
101102

0 commit comments

Comments
 (0)