feat(scaffolder): add returnWorkflowRunDetails to github:actions:dispatch action
Signed-off-by: abdellahhanane <abdellahhanane44@gmail.com>
This commit is contained in:
@@ -24,10 +24,13 @@ export function createGithubActionsDispatchAction(options: {
|
||||
workflowId: string;
|
||||
branchOrTagName: string;
|
||||
workflowInputs?: Record<string, string> | undefined;
|
||||
returnWorkflowRunDetails?: boolean | undefined;
|
||||
token?: string | undefined;
|
||||
},
|
||||
{
|
||||
[x: string]: any;
|
||||
workflowRunId?: number | undefined;
|
||||
workflowRunUrl?: string | undefined;
|
||||
workflowRunHtmlUrl?: string | undefined;
|
||||
},
|
||||
'v2'
|
||||
>;
|
||||
@@ -250,8 +253,8 @@ export function createGithubRepoCreateAction(options: {
|
||||
access: string;
|
||||
}
|
||||
| {
|
||||
team: string;
|
||||
access: string;
|
||||
team: string;
|
||||
}
|
||||
)[]
|
||||
| undefined;
|
||||
@@ -441,8 +444,8 @@ export function createPublishGithubAction(options: {
|
||||
access: string;
|
||||
}
|
||||
| {
|
||||
team: string;
|
||||
access: string;
|
||||
team: string;
|
||||
}
|
||||
)[]
|
||||
| undefined;
|
||||
|
||||
+41
@@ -93,6 +93,7 @@ describe('github:actions:dispatch', () => {
|
||||
).toHaveBeenCalledWith({
|
||||
owner: 'owner',
|
||||
repo: 'repo',
|
||||
return_run_details: false,
|
||||
workflow_id: workflowId,
|
||||
ref: branchOrTagName,
|
||||
});
|
||||
@@ -119,9 +120,49 @@ describe('github:actions:dispatch', () => {
|
||||
).toHaveBeenCalledWith({
|
||||
owner: 'owner',
|
||||
repo: 'repo',
|
||||
return_run_details: false,
|
||||
workflow_id: workflowId,
|
||||
ref: branchOrTagName,
|
||||
inputs: workflowInputs,
|
||||
});
|
||||
});
|
||||
|
||||
it('should call createWorkflowDispatch with return_run_details when using the returnWorkflowRunDetails example', async () => {
|
||||
mockOctokit.rest.actions.createWorkflowDispatch.mockResolvedValue({
|
||||
data: {
|
||||
workflow_run_id: 123,
|
||||
run_url: 'https://api.github.com/repos/owner/repo/actions/runs/123',
|
||||
html_url: 'https://github.com/owner/repo/actions/runs/123',
|
||||
},
|
||||
});
|
||||
|
||||
const exampleInput = yaml.parse(examples[3].example).steps[0].input;
|
||||
const ctx = Object.assign({}, mockContext, {
|
||||
input: {
|
||||
...exampleInput,
|
||||
repoUrl: 'github.com?repo=repo&owner=owner',
|
||||
},
|
||||
});
|
||||
await action.handler(ctx);
|
||||
|
||||
expect(
|
||||
mockOctokit.rest.actions.createWorkflowDispatch,
|
||||
).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
owner: 'owner',
|
||||
repo: 'repo',
|
||||
return_run_details: true,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(ctx.output).toHaveBeenCalledWith('workflowRunId', 123);
|
||||
expect(ctx.output).toHaveBeenCalledWith(
|
||||
'workflowRunUrl',
|
||||
'https://api.github.com/repos/owner/repo/actions/runs/123',
|
||||
);
|
||||
expect(ctx.output).toHaveBeenCalledWith(
|
||||
'workflowRunHtmlUrl',
|
||||
'https://github.com/owner/repo/actions/runs/123',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
+17
@@ -71,4 +71,21 @@ export const examples: TemplateExample[] = [
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
description: 'GitHub Action Workflow returning run details',
|
||||
example: yaml.stringify({
|
||||
steps: [
|
||||
{
|
||||
action: 'github:actions:dispatch',
|
||||
name: 'Dispatch GitHub Action Workflow and get run ID',
|
||||
input: {
|
||||
repoUrl: 'github.com?repo=repo&owner=owner',
|
||||
workflowId: 'WORKFLOW_ID',
|
||||
branchOrTagName: 'main',
|
||||
returnWorkflowRunDetails: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -101,6 +101,7 @@ describe('github:actions:dispatch', () => {
|
||||
repo: 'repo',
|
||||
workflow_id: workflowId,
|
||||
ref: branchOrTagName,
|
||||
return_run_details: false,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -128,6 +129,67 @@ describe('github:actions:dispatch', () => {
|
||||
workflow_id: workflowId,
|
||||
ref: branchOrTagName,
|
||||
inputs: workflowInputs,
|
||||
return_run_details: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should call createWorkflowDispatch with return_run_details when returnWorkflowRunDetails is true', async () => {
|
||||
mockOctokit.rest.actions.createWorkflowDispatch.mockResolvedValue({
|
||||
data: {
|
||||
workflow_run_id: 123,
|
||||
run_url: 'https://api.github.com/repos/owner/repo/actions/runs/123',
|
||||
html_url: 'https://github.com/owner/repo/actions/runs/123',
|
||||
},
|
||||
});
|
||||
|
||||
const ctx = Object.assign({}, mockContext, {
|
||||
input: {
|
||||
repoUrl: 'github.com?repo=repo&owner=owner',
|
||||
workflowId: 'dispatch_workflow',
|
||||
branchOrTagName: 'main',
|
||||
returnWorkflowRunDetails: true,
|
||||
},
|
||||
});
|
||||
await action.handler(ctx);
|
||||
|
||||
expect(
|
||||
mockOctokit.rest.actions.createWorkflowDispatch,
|
||||
).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
owner: 'owner',
|
||||
repo: 'repo',
|
||||
workflow_id: 'dispatch_workflow',
|
||||
ref: 'main',
|
||||
return_run_details: true,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(ctx.output).toHaveBeenCalledWith('workflowRunId', 123);
|
||||
expect(ctx.output).toHaveBeenCalledWith(
|
||||
'workflowRunUrl',
|
||||
'https://api.github.com/repos/owner/repo/actions/runs/123',
|
||||
);
|
||||
expect(ctx.output).toHaveBeenCalledWith(
|
||||
'workflowRunHtmlUrl',
|
||||
'https://github.com/owner/repo/actions/runs/123',
|
||||
);
|
||||
});
|
||||
|
||||
it('should not set outputs when returnWorkflowRunDetails is false', async () => {
|
||||
mockOctokit.rest.actions.createWorkflowDispatch.mockResolvedValue({
|
||||
data: undefined,
|
||||
});
|
||||
|
||||
const ctx = Object.assign({}, mockContext, {
|
||||
input: {
|
||||
repoUrl: 'github.com?repo=repo&owner=owner',
|
||||
workflowId: 'dispatch_workflow',
|
||||
branchOrTagName: 'main',
|
||||
returnWorkflowRunDetails: false,
|
||||
},
|
||||
});
|
||||
await action.handler(ctx);
|
||||
|
||||
expect(ctx.output).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { InputError } from '@backstage/errors';
|
||||
import { assertError, InputError } from '@backstage/errors';
|
||||
import {
|
||||
GithubCredentialsProvider,
|
||||
ScmIntegrations,
|
||||
@@ -62,7 +62,14 @@ export function createGithubActionsDispatchAction(options: {
|
||||
z
|
||||
.record(z.string(), {
|
||||
description:
|
||||
'Inputs keys and values to send to GitHub Action configured on the workflow file. The maximum number of properties is 10.',
|
||||
'Inputs keys and values to send to GitHub Action configured on the workflow file. The maximum number of properties is 25.',
|
||||
})
|
||||
.optional(),
|
||||
returnWorkflowRunDetails: z =>
|
||||
z
|
||||
.boolean({
|
||||
description:
|
||||
'If true, returns the workflow run ID and URLs as action outputs.',
|
||||
})
|
||||
.optional(),
|
||||
token: z =>
|
||||
@@ -73,6 +80,14 @@ export function createGithubActionsDispatchAction(options: {
|
||||
})
|
||||
.optional(),
|
||||
},
|
||||
output: {
|
||||
workflowRunId: z =>
|
||||
z.number({ description: 'The triggered workflow run ID' }).optional(),
|
||||
workflowRunUrl: z =>
|
||||
z.string({ description: 'API URL of the workflow run' }).optional(),
|
||||
workflowRunHtmlUrl: z =>
|
||||
z.string({ description: 'HTML URL of the workflow run' }).optional(),
|
||||
},
|
||||
},
|
||||
async handler(ctx) {
|
||||
const {
|
||||
@@ -80,6 +95,7 @@ export function createGithubActionsDispatchAction(options: {
|
||||
workflowId,
|
||||
branchOrTagName,
|
||||
workflowInputs,
|
||||
returnWorkflowRunDetails,
|
||||
token: providedToken,
|
||||
} = ctx.input;
|
||||
|
||||
@@ -106,20 +122,56 @@ export function createGithubActionsDispatchAction(options: {
|
||||
log: ctx.logger,
|
||||
});
|
||||
|
||||
await ctx.checkpoint({
|
||||
key: `create.workflow.dispatch.${owner}.${repo}.${workflowId}`,
|
||||
fn: async () => {
|
||||
await client.rest.actions.createWorkflowDispatch({
|
||||
owner,
|
||||
repo,
|
||||
workflow_id: workflowId,
|
||||
ref: branchOrTagName,
|
||||
inputs: workflowInputs,
|
||||
});
|
||||
try {
|
||||
const runDetails = await ctx.checkpoint({
|
||||
key: `create.workflow.dispatch.${owner}.${repo}.${workflowId}`,
|
||||
fn: async () => {
|
||||
const response = await client.rest.actions.createWorkflowDispatch({
|
||||
owner,
|
||||
repo,
|
||||
workflow_id: workflowId,
|
||||
ref: branchOrTagName,
|
||||
inputs: workflowInputs,
|
||||
return_run_details: returnWorkflowRunDetails ?? false,
|
||||
});
|
||||
|
||||
ctx.logger.info(`Workflow ${workflowId} dispatched successfully`);
|
||||
},
|
||||
});
|
||||
ctx.logger.info(`Workflow ${workflowId} dispatched successfully`);
|
||||
|
||||
if (returnWorkflowRunDetails && response.data) {
|
||||
// GitHub's API returns 200 with run details when return_run_details is true.
|
||||
// @octokit/openapi-types still types this as OctokitResponse<never, 204> because
|
||||
// it hasn't picked up the updated GitHub OpenAPI spec yet.
|
||||
// See: https://github.blog/changelog/2026-02-19-workflow-dispatch-api-now-returns-run-ids/
|
||||
// This cast can be removed once @octokit/openapi-types includes the updated spec.
|
||||
// Note: only supported on GitHub.com and GitHub Enterprise Cloud, not GitHub Enterprise Server
|
||||
const data = response.data as unknown as {
|
||||
workflow_run_id: number;
|
||||
run_url: string;
|
||||
html_url: string;
|
||||
};
|
||||
|
||||
return {
|
||||
workflow_run_id: data.workflow_run_id,
|
||||
run_url: data.run_url,
|
||||
workflowRunHtmlUrl: data.html_url,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
if (runDetails) {
|
||||
ctx.output('workflowRunId', runDetails.workflow_run_id);
|
||||
ctx.output('workflowRunUrl', runDetails.run_url);
|
||||
ctx.output('workflowRunHtmlUrl', runDetails.workflowRunHtmlUrl);
|
||||
}
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
ctx.logger.warn(
|
||||
`Failed: dispatching workflow '${workflowId}' on repo: '${repo}', ${e.message}`,
|
||||
);
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user