Skip to content

Commit 82311f5

Browse files
authored
feat: add fail-if-project-not-found input (#54)
1 parent eda628e commit 82311f5

17 files changed

+178
-29
lines changed

.github/workflows/integration-tests.yml

+27
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,15 @@ jobs:
376376
title: New Title ${{ steps.copy-project.outputs.number }}
377377
token: ${{ steps.get-auth-token.outputs.token }}
378378

379+
- name: Find Project (Not Found)
380+
uses: ./find-project/
381+
id: find-project-not-found
382+
with:
383+
fail-if-project-not-found: false
384+
owner: ${{ matrix.owner }}
385+
title: Foobar
386+
token: ${{ steps.get-auth-token.outputs.token }}
387+
379388
- name: Confirm Project Found
380389
uses: ./github-script/
381390
with:
@@ -451,6 +460,15 @@ jobs:
451460
project-number: ${{ steps.copy-project.outputs.number }}
452461
token: ${{ steps.get-auth-token.outputs.token }}
453462

463+
- name: Close Project (Not Found)
464+
uses: ./close-project/
465+
id: close-project-not-found
466+
with:
467+
fail-if-project-not-found: false
468+
owner: ${{ steps.copy-project.outputs.owner }}
469+
project-number: 99999
470+
token: ${{ steps.get-auth-token.outputs.token }}
471+
454472
- name: Reopen Project
455473
uses: ./close-project/
456474
id: reopen-project
@@ -467,3 +485,12 @@ jobs:
467485
owner: ${{ steps.copy-project.outputs.owner }}
468486
project-number: ${{ steps.copy-project.outputs.number }}
469487
token: ${{ steps.get-auth-token.outputs.token }}
488+
489+
- name: Delete Project (Not Found)
490+
uses: ./delete-project/
491+
id: delete-project-not-found
492+
with:
493+
fail-if-project-not-found: false
494+
owner: ${{ steps.copy-project.outputs.owner }}
495+
project-number: 99999
496+
token: ${{ steps.get-auth-token.outputs.token }}

__tests__/close-project.test.mts

+29-6
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,22 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
33
import * as core from '@actions/core';
44

55
import * as index from '../src/close-project.js';
6-
import { ProjectDetails, closeProject } from '../src/lib.js';
7-
import { mockGetInput } from './utils.js';
6+
import {
7+
ProjectDetails,
8+
ProjectNotFoundError,
9+
closeProject
10+
} from '../src/lib.js';
11+
import { mockGetBooleanInput, mockGetInput } from './utils.js';
812

913
vi.mock('@actions/core');
10-
vi.mock('../src/lib');
11-
12-
const { ProjectNotFoundError } =
13-
await vi.importActual<typeof import('../src/lib.js')>('../src/lib');
14+
vi.mock('../src/lib.js', async () => {
15+
const { ProjectNotFoundError: ActualProjectNotFoundError } =
16+
await vi.importActual<typeof import('../src/lib.js')>('../src/lib.js');
17+
return {
18+
closeProject: vi.fn(),
19+
ProjectNotFoundError: ActualProjectNotFoundError
20+
};
21+
});
1422

1523
// Spy the action's entrypoint
1624
const closeProjectActionSpy = vi.spyOn(index, 'closeProjectAction');
@@ -38,6 +46,7 @@ describe('closeProjectAction', () => {
3846

3947
it('handles project not found', async () => {
4048
mockGetInput({ owner, 'project-number': projectNumber });
49+
mockGetBooleanInput({ 'fail-if-project-not-found': true });
4150
vi.mocked(closeProject).mockImplementation(() => {
4251
throw new ProjectNotFoundError();
4352
});
@@ -49,6 +58,20 @@ describe('closeProjectAction', () => {
4958
expect(core.setFailed).toHaveBeenLastCalledWith('Project not found');
5059
});
5160

61+
it('can ignore project not found', async () => {
62+
mockGetInput({ owner, 'project-number': projectNumber });
63+
mockGetBooleanInput({ 'fail-if-project-not-found': false });
64+
vi.mocked(closeProject).mockImplementation(() => {
65+
throw new ProjectNotFoundError();
66+
});
67+
68+
await index.closeProjectAction();
69+
expect(closeProjectActionSpy).toHaveReturned();
70+
71+
expect(core.setFailed).not.toHaveBeenCalled();
72+
expect(core.setOutput).not.toHaveBeenCalled();
73+
});
74+
5275
it('handles generic errors', async () => {
5376
mockGetInput({ owner, 'project-number': projectNumber });
5477
vi.mocked(closeProject).mockImplementation(() => {

__tests__/delete-project.test.mts

+25-6
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
33
import * as core from '@actions/core';
44

55
import * as index from '../src/delete-project.js';
6-
import { deleteProject } from '../src/lib.js';
7-
import { mockGetInput } from './utils.js';
6+
import { deleteProject, ProjectNotFoundError } from '../src/lib.js';
7+
import { mockGetBooleanInput, mockGetInput } from './utils.js';
88

99
vi.mock('@actions/core');
10-
vi.mock('../src/lib');
11-
12-
const { ProjectNotFoundError } =
13-
await vi.importActual<typeof import('../src/lib.js')>('../src/lib');
10+
vi.mock('../src/lib.js', async () => {
11+
const { ProjectNotFoundError: ActualProjectNotFoundError } =
12+
await vi.importActual<typeof import('../src/lib.js')>('../src/lib.js');
13+
return {
14+
deleteProject: vi.fn(),
15+
ProjectNotFoundError: ActualProjectNotFoundError
16+
};
17+
});
1418

1519
// Spy the action's entrypoint
1620
const deleteProjectActionSpy = vi.spyOn(index, 'deleteProjectAction');
@@ -37,6 +41,7 @@ describe('deleteProjectAction', () => {
3741

3842
it('handles project not found', async () => {
3943
mockGetInput({ owner, 'project-number': projectNumber });
44+
mockGetBooleanInput({ 'fail-if-project-not-found': true });
4045
vi.mocked(deleteProject).mockImplementation(() => {
4146
throw new ProjectNotFoundError();
4247
});
@@ -48,6 +53,20 @@ describe('deleteProjectAction', () => {
4853
expect(core.setFailed).toHaveBeenLastCalledWith('Project not found');
4954
});
5055

56+
it('can ignore project not found', async () => {
57+
mockGetInput({ owner, 'project-number': projectNumber });
58+
mockGetBooleanInput({ 'fail-if-project-not-found': false });
59+
vi.mocked(deleteProject).mockImplementation(() => {
60+
throw new ProjectNotFoundError();
61+
});
62+
63+
await index.deleteProjectAction();
64+
expect(deleteProjectActionSpy).toHaveReturned();
65+
66+
expect(core.setFailed).not.toHaveBeenCalled();
67+
expect(core.setOutput).not.toHaveBeenCalled();
68+
});
69+
5170
it('handles generic errors', async () => {
5271
mockGetInput({ owner, 'project-number': projectNumber });
5372
vi.mocked(deleteProject).mockImplementation(() => {

__tests__/find-project.test.ts

+14-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as core from '@actions/core';
44

55
import * as index from '../src/find-project';
66
import { findProject } from '../src/lib';
7-
import { mockGetInput } from './utils';
7+
import { mockGetBooleanInput, mockGetInput } from './utils';
88

99
vi.mock('@actions/core');
1010
vi.mock('../src/lib');
@@ -41,6 +41,7 @@ describe('findProjectAction', () => {
4141

4242
it('handles project not found', async () => {
4343
mockGetInput({ owner, title });
44+
mockGetBooleanInput({ 'fail-if-project-not-found': true });
4445
vi.mocked(findProject).mockResolvedValue(null);
4546

4647
await index.findProjectAction();
@@ -52,6 +53,18 @@ describe('findProjectAction', () => {
5253
);
5354
});
5455

56+
it('can ignore project not found', async () => {
57+
mockGetInput({ owner, title });
58+
mockGetBooleanInput({ 'fail-if-project-not-found': false });
59+
vi.mocked(findProject).mockResolvedValue(null);
60+
61+
await index.findProjectAction();
62+
expect(findProjectActionSpy).toHaveReturned();
63+
64+
expect(core.setFailed).not.toHaveBeenCalled();
65+
expect(core.setOutput).not.toHaveBeenCalled();
66+
});
67+
5568
it('handles generic errors', async () => {
5669
mockGetInput({ owner, title });
5770
vi.mocked(findProject).mockImplementation(() => {

close-project/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Closing a project requires project admin permissions
1616
| `owner` | The owner of the project - either an organization or a user. If not provided, it defaults to the repository owner. | No | `${{ github.repository_owner }}` |
1717
| `project-number` | The project number from the project's URL. | Yes | |
1818
| `closed` | Closed state of the project - set to `false` to reopen a closed project. | No | `true` |
19+
| `fail-if-project-not-found` | Should the action fail if the project is not found | No | `true` |
1920

2021
## Outputs
2122

close-project/action.yml

+4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ inputs:
1717
description: Closed state of the project - set false to reopen a closed project
1818
required: false
1919
default: true
20+
fail-if-project-not-found:
21+
description: Should the action fail if the project is not found
22+
required: false
23+
default: true
2024

2125
outputs:
2226
id:

delete-project/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Deleting a project requires project admin permissions
1515
| `token` | A GitHub access token - either a classic PAT or a GitHub app installation token. | Yes | |
1616
| `owner` | The owner of the project - either an organization or a user. If not provided, it defaults to the repository owner. | No | `${{ github.repository_owner }}` |
1717
| `project-number` | The project number from the project's URL. | Yes | |
18+
| `fail-if-project-not-found` | Should the action fail if the project is not found | No | `true` |
1819

1920
## Outputs
2021

delete-project/action.yml

+4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ inputs:
1313
project-number:
1414
description: The project number from the project's URL
1515
required: true
16+
fail-if-project-not-found:
17+
description: Should the action fail if the project is not found
18+
required: false
19+
default: true
1620

1721
runs:
1822
using: node20

dist/close-project.js

+12-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/delete-project.js

+13-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/find-project.js

+6-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

find-project/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Find a GitHub project
1111
| `token` | A GitHub access token - either a classic PAT or a GitHub app installation token. | Yes | |
1212
| `owner` | The owner of the project - either an organization or a user. If not provided, it defaults to the repository owner. | No | `${{ github.repository_owner }}` |
1313
| `title` | The title of the project to find. | Yes | |
14+
| `fail-if-project-not-found` | Should the action fail if the project is not found | No | `true` |
1415

1516
## Outputs
1617

find-project/action.yml

+4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ inputs:
1313
title:
1414
description: The title of the project to find
1515
required: true
16+
fail-if-project-not-found:
17+
description: Should the action fail if the project is not found
18+
required: false
19+
default: true
1620

1721
outputs:
1822
id:

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
"scripts": {
2020
"bundle": "npm run format:write && npm run package",
2121
"ci-test": "vitest run --coverage --reporter=verbose",
22-
"format:write": "prettier --write \"**/*.ts\"",
23-
"format:check": "prettier --check \"**/*.ts\"",
22+
"format:write": "prettier --write \"**/*.{mts,ts}\"",
23+
"format:check": "prettier --check \"**/*.{mts,ts}\"",
2424
"lint": "npx eslint . -c ./.github/linters/.eslintrc.yml",
2525
"package:add-item": "esbuild add-item/index.ts --bundle --format=esm --outfile=dist/add-item.js --platform=node --target=node20.2 --banner:js=\"import { createRequire } from 'module';const require = createRequire(import.meta.url);\"",
2626
"package:archive-item": "esbuild archive-item/index.ts --bundle --format=esm --outfile=dist/archive-item.js --platform=node --target=node20.2 --banner:js=\"import { createRequire } from 'module';const require = createRequire(import.meta.url);\"",

src/close-project.ts

+14-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as core from '@actions/core';
22

3-
import { closeProject } from './lib.js';
3+
import { closeProject, ProjectNotFoundError } from './lib.js';
44

55
export async function closeProjectAction(): Promise<void> {
66
try {
@@ -10,10 +10,21 @@ export async function closeProjectAction(): Promise<void> {
1010

1111
// Optional inputs
1212
const closed = core.getBooleanInput('closed');
13+
const failIfProjectNotFound = core.getBooleanInput(
14+
'fail-if-project-not-found'
15+
);
16+
17+
try {
18+
const project = await closeProject(owner, projectNumber, closed);
1319

14-
const project = await closeProject(owner, projectNumber, closed);
20+
core.setOutput('id', project.id);
21+
} catch (error) {
22+
if (error instanceof ProjectNotFoundError && !failIfProjectNotFound) {
23+
return;
24+
}
1525

16-
core.setOutput('id', project.id);
26+
throw error;
27+
}
1728
} catch (error) {
1829
// Fail the workflow run if an error occurs
1930
if (error instanceof Error && error.stack) core.debug(error.stack);

0 commit comments

Comments
 (0)