Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/check-dist.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
id: diff

# If index.js was different than expected, upload the expected version as an artifact
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
if: ${{ failure() && steps.diff.conclusion == 'failure' }}
with:
name: dist
Expand Down
9 changes: 0 additions & 9 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,3 @@ jobs:
- uses: actions/checkout@v3
- run: npm ci
- run: npm test

# test action works running from the graph
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./
with:
milliseconds: 1000
Comment on lines -17 to -25

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leftover from when the repo was bootstrapped as a fork of actions/javascript-action in 327daaf (2023-04-12): at that commit action.yml declared a milliseconds input and index.js was the template's wait code, so the job passed.

Six days later b52f3e3 ("Build action") replaced the template with the Nullplatform action — milliseconds was gone — but test.yml was never updated. Since then: empty action input → no CREATE/UPDATE match → core.setFailed(...) without returnconst { id, ... } = build crashes on null.

No merged PR since has seen it pass (older run logs were purged by retention, but this PR's run_number on units-test was #43 and the only merge since b52f3e3 is docs-only #19). Restoring it properly needs real credentials or a mock server — out of scope here; units already runs npm test.

8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ The "Nullplatform Build" GitHub Action allows you to query and interact with nul
- [`description`](#description)
- [`branch`](#branch)
- [`image-repository-url`](#image-repository-url)
- [`github-token`](#github-token)
- [Outputs](#outputs)
- [`id`](#id-1)
- [`status`](#status-1)
Expand Down Expand Up @@ -69,7 +70,7 @@ The "Nullplatform Build" GitHub Action allows you to query and interact with nul

### `description`

- **Description**: The build description. Defaults to the commit message.
- **Description**: The build description. Defaults to the commit message — fetched via [`github-token`](#github-token) when the event payload does not include `head_commit` (e.g. `workflow_dispatch`), otherwise falls back to `Commit <sha>`.
- **Required**: No

### `branch`
Expand All @@ -82,6 +83,11 @@ The "Nullplatform Build" GitHub Action allows you to query and interact with nul
- **Description**: The image repository URL where the build asset was uploaded.
- **Required**: No

### `github-token`

- **Description**: GitHub token used to fetch commit metadata when it is not available in the event payload (for example on `workflow_dispatch`). Defaults to the automatically provided `${{ github.token }}`. The token needs the `contents: read` permission; if the workflow restricts permissions, add it explicitly.
- **Required**: No

## Outputs

### `id`
Expand Down
6 changes: 5 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ inputs:
required: false
status:
description: "The build status. Can be one of: 'pending', 'in_progress', 'failed', 'successful'"
required:
required: false
application-id:
description: "The application id to build"
required: false
Expand All @@ -28,6 +28,10 @@ inputs:
image-repository-url:
description: "The image repository URL wheres the build image was uploaded"
required: false
github-token:
description: "GitHub token used to fetch commit metadata when not available in the event payload (e.g. workflow_dispatch). Defaults to the automatically provided GITHUB_TOKEN."
required: false
default: ${{ github.token }}
outputs:
id:
description: "The build id"
Expand Down
27 changes: 26 additions & 1 deletion dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

26 changes: 25 additions & 1 deletion src/action.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,30 @@ const setFailed = (error) => {

const truncate = (text, limit = 2000) => (text.length > limit ? text.substring(0, limit) : text);

const createBuild = () => {
const getCommitMessage = async (sha) => {
try {
const token = core.getInput(Input.GITHUB_TOKEN);

if (isEmpty(token)) {
return null;
}

const octokit = github.getOctokit(token);

const { data } = await octokit.rest.git.getCommit({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
commit_sha: sha,
});

return data.message;
} catch (error) {
core.warning(`Could not fetch commit message: ${error.message}`);
return null;
}
};

const createBuild = async () => {
core.info('Validating inputs...');

const status = BuildStatus.IN_PROGRESS;
Expand All @@ -30,6 +53,7 @@ const createBuild = () => {
|| `${github.context.payload.repository.html_url}/commit/${github.context.sha}`;
const description = core.getInput(Input.DESCRIPTION)
|| (github.context.payload.head_commit && github.context.payload.head_commit.message)
|| (await getCommitMessage(github.context.sha))
|| `Commit ${github.context.sha}`;
const branch = core.getInput(Input.BRANCH) || github.context.ref.replace(/^refs\/heads\//, '');

Expand Down
94 changes: 94 additions & 0 deletions src/action.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
const core = require('@actions/core');
const github = require('@actions/github');

jest.mock('@actions/core');
jest.mock('@actions/github', () => ({
getOctokit: jest.fn(),
context: {
repo: { owner: 'nullplatform', repo: 'main-providers-api' },
sha: '',
ref: '',
payload: {},
},
}));

const mockPost = jest.fn();
jest.mock('./client', () => jest.fn(() => ({ post: mockPost })));

const run = require('./action');

const setInputs = (overrides = {}) => {
const defaults = { action: 'create', 'application-id': 'app-1' };
const all = { ...defaults, ...overrides };
core.getInput.mockImplementation((name) => all[name] || '');
};

describe('run (createBuild path)', () => {
beforeEach(() => {
jest.clearAllMocks();
mockPost.mockResolvedValue({ id: 1, status: 'in_progress', application_id: 'app-1' });
github.context.sha = 'abc123';
github.context.ref = 'refs/heads/main';
github.context.payload = { repository: { html_url: 'https://github.com/foo/bar' } };
setInputs();
});

test('uses head_commit.message when payload contains it (push event)', async () => {
github.context.payload.head_commit = { message: 'feat: from push' };

await run();

expect(github.getOctokit).not.toHaveBeenCalled();
expect(mockPost).toHaveBeenCalledWith('build', expect.objectContaining({
description: 'feat: from push',
}));
});

test('fetches the commit message via the GitHub API when head_commit is absent', async () => {
setInputs({ 'github-token': 'ghs_x' });
const getCommit = jest.fn().mockResolvedValue({ data: { message: 'feat: from API' } });
github.getOctokit.mockReturnValue({ rest: { git: { getCommit } } });

await run();

expect(github.getOctokit).toHaveBeenCalledWith('ghs_x');
expect(getCommit).toHaveBeenCalledWith(expect.objectContaining({ commit_sha: 'abc123' }));
expect(mockPost).toHaveBeenCalledWith('build', expect.objectContaining({
description: 'feat: from API',
}));
});

test('falls back to "Commit <sha>" when github-token is empty and head_commit is absent', async () => {
await run();

expect(github.getOctokit).not.toHaveBeenCalled();
expect(mockPost).toHaveBeenCalledWith('build', expect.objectContaining({
description: 'Commit abc123',
}));
});

test('warns and falls back to "Commit <sha>" when the API call fails', async () => {
setInputs({ 'github-token': 'ghs_x' });
const getCommit = jest.fn().mockRejectedValue(new Error('forbidden'));
github.getOctokit.mockReturnValue({ rest: { git: { getCommit } } });

await run();

expect(core.warning).toHaveBeenCalledWith(expect.stringContaining('forbidden'));
expect(mockPost).toHaveBeenCalledWith('build', expect.objectContaining({
description: 'Commit abc123',
}));
});

test('prefers explicit description input over payload and API', async () => {
setInputs({ description: 'from input', 'github-token': 'ghs_x' });
github.context.payload.head_commit = { message: 'feat: from push' };

await run();

expect(github.getOctokit).not.toHaveBeenCalled();
expect(mockPost).toHaveBeenCalledWith('build', expect.objectContaining({
description: 'from input',
}));
});
});
1 change: 1 addition & 0 deletions src/enums.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const Input = Object.freeze({
DESCRIPTION: 'description',
BRANCH: 'branch',
IMAGE_REPOSITORY_URL: 'image-repository-url',
GITHUB_TOKEN: 'github-token',
});

const Output = Object.freeze({
Expand Down
Loading