Skip to content
8 changes: 6 additions & 2 deletions action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,12 @@ inputs:
required: true

github-user-id:
description: "GitHub user ID to create task for"
required: true
description: "GitHub user ID to create task for. If provided, `coder-username` must not be set."
required: false

coder-username:
description: "Coder username to create task for. If provided, github-user-id must not be set. Useful for automated workflows without a triggering user."
required: false

# Optional inputs
coder-organization:
Expand Down
49 changes: 35 additions & 14 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26918,13 +26918,20 @@ class CoderTaskAction {
}
}
async run() {
core.info(`GitHub user ID: ${this.inputs.githubUserID}`);
const coderUser = await this.coder.getCoderUserByGitHubId(this.inputs.githubUserID);
let coderUsername;
if (this.inputs.coderUsername) {
core.info(`Using provided Coder username: ${this.inputs.coderUsername}`);
coderUsername = this.inputs.coderUsername;
} else {
core.info(`Looking up Coder user by GitHub user ID: ${this.inputs.githubUserID}`);
const coderUser = await this.coder.getCoderUserByGitHubId(this.inputs.githubUserID);
coderUsername = coderUser.username;
}
const { githubOrg, githubRepo, githubIssueNumber } = this.parseGithubIssueURL();
core.info(`GitHub owner: ${githubOrg}`);
core.info(`GitHub repo: ${githubRepo}`);
core.info(`GitHub issue number: ${githubIssueNumber}`);
core.info(`Coder username: ${coderUser.username}`);
core.info(`Coder username: ${coderUsername}`);
if (!this.inputs.coderTaskNamePrefix || !this.inputs.githubIssueURL) {
throw new Error("either taskName or both taskNamePrefix and issueURL must be provided");
}
Expand Down Expand Up @@ -26956,20 +26963,20 @@ class CoderTaskAction {
throw new Error(`Preset ${this.inputs.coderTemplatePreset} not found`);
}
core.info(`Coder Template: Preset ID: ${presetID}`);
const existingTask = await this.coder.getTask(coderUser.username, taskName);
const existingTask = await this.coder.getTask(coderUsername, taskName);
if (existingTask) {
core.info(`Coder Task: already exists: ${existingTask.name} (id: ${existingTask.id} status: ${existingTask.status})`);
if (existingTask.status !== "active") {
core.info(`Coder Task: waiting for task ${existingTask.name} to become active...`);
await this.coder.waitForTaskActive(coderUser.username, existingTask.id, core.debug, 1200000);
await this.coder.waitForTaskActive(coderUsername, existingTask.id, core.debug, 1200000);
}
core.info("Coder Task: Sending prompt to existing task...");
await this.coder.sendTaskInput(coderUser.username, existingTask.id, this.inputs.coderTaskPrompt);
await this.coder.sendTaskInput(coderUsername, existingTask.id, this.inputs.coderTaskPrompt);
core.info("Coder Task: Prompt sent successfully");
return {
coderUsername: coderUser.username,
coderUsername,
taskName: existingTask.name,
taskUrl: this.generateTaskUrl(coderUser.username, existingTask.id),
taskUrl: this.generateTaskUrl(coderUsername, existingTask.id),
taskCreated: false
};
}
Expand All @@ -26980,9 +26987,9 @@ class CoderTaskAction {
template_version_preset_id: presetID,
input: this.inputs.coderTaskPrompt
};
const createdTask = await this.coder.createTask(coderUser.username, req);
const createdTask = await this.coder.createTask(coderUsername, req);
core.info(`Coder Task: created successfully (status: ${createdTask.status})`);
const taskUrl = this.generateTaskUrl(coderUser.username, createdTask.id);
const taskUrl = this.generateTaskUrl(coderUsername, createdTask.id);
core.info(`Coder Task: URL: ${taskUrl}`);
if (this.inputs.commentOnIssue) {
core.info(`Commenting on issue ${githubOrg}/${githubRepo}#${githubIssueNumber}`);
Expand All @@ -26992,7 +26999,7 @@ class CoderTaskAction {
core.info(`Skipping comment on issue (commentOnIssue is false)`);
}
return {
coderUsername: coderUser.username,
coderUsername,
taskName,
taskUrl,
taskCreated: true
Expand All @@ -27001,19 +27008,30 @@ class CoderTaskAction {
}

// src/schemas.ts
var ActionInputsSchema = exports_external.object({
var BaseInputsSchema = exports_external.object({
coderTaskPrompt: exports_external.string().min(1),
coderToken: exports_external.string().min(1),
coderURL: exports_external.string().url(),
coderTemplateName: exports_external.string().min(1),
githubIssueURL: exports_external.string().url(),
githubToken: exports_external.string(),
githubUserID: exports_external.number().min(1),
coderOrganization: exports_external.string().min(1).optional().default("default"),
coderTaskNamePrefix: exports_external.string().min(1).optional().default("gh"),
coderTemplatePreset: exports_external.string().optional(),
commentOnIssue: exports_external.boolean().default(true)
});
var WithGithubUserIDSchema = BaseInputsSchema.extend({
githubUserID: exports_external.number().min(1),
coderUsername: exports_external.undefined()
});
var WithCoderUsernameSchema = BaseInputsSchema.extend({
githubUserID: exports_external.undefined(),
coderUsername: exports_external.string().min(1)
});
var ActionInputsSchema = exports_external.union([
WithGithubUserIDSchema,
WithCoderUsernameSchema
]);
var ActionOutputsSchema = exports_external.object({
coderUsername: exports_external.string(),
taskName: exports_external.string(),
Expand All @@ -27024,6 +27042,8 @@ var ActionOutputsSchema = exports_external.object({
// src/index.ts
async function main() {
try {
const githubUserIdInput = core2.getInput("github-user-id");
const githubUserID = githubUserIdInput ? Number.parseInt(githubUserIdInput, 10) : undefined;
const inputs = ActionInputsSchema.parse({
coderURL: core2.getInput("coder-url", { required: true }),
coderToken: core2.getInput("coder-token", { required: true }),
Expand All @@ -27039,7 +27059,8 @@ async function main() {
}),
githubIssueURL: core2.getInput("github-issue-url", { required: true }),
githubToken: core2.getInput("github-token", { required: true }),
githubUserID: Number.parseInt(core2.getInput("github-user-id", { required: true }), 10),
githubUserID,
coderUsername: core2.getInput("coder-username") || undefined,
coderTemplatePreset: core2.getInput("coder-template-preset") || undefined,
commentOnIssue: core2.getBooleanInput("comment-on-issue")
});
Expand Down
40 changes: 40 additions & 0 deletions src/action.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,46 @@ describe("CoderTaskAction", () => {
assertActionOutputs(parsedResult, true);
});

test("creates new task using direct coder-username (without github-user-id)", async () => {
// Setup - no user lookup needed when coder-username is provided directly
coderClient.mockGetTemplateByOrganizationAndName.mockResolvedValue(
mockTemplate,
);
coderClient.mockGetTemplateVersionPresets.mockResolvedValue([]);
coderClient.mockGetTask.mockResolvedValue(null);
coderClient.mockCreateTask.mockResolvedValue(mockTask);
coderClient.mockWaitForTaskActive.mockResolvedValue(undefined);

const inputs = createMockInputs({
githubUserID: undefined,
coderUsername: mockUser.username,
});
const action = new CoderTaskAction(
coderClient,
octokit as unknown as Octokit,
inputs,
);

// Execute
const result = await action.run();

// Verify - should NOT call any user lookup API when username is provided directly
expect(coderClient.mockGetCoderUserByGithubID).not.toHaveBeenCalled();
expect(coderClient.mockGetTask).toHaveBeenCalledWith(
mockUser.username,
mockTask.name,
);
expect(coderClient.mockCreateTask).toHaveBeenCalledWith(mockUser.username, {
name: mockTask.name,
template_version_id: mockTemplate.active_version_id,
template_version_preset_id: undefined,
input: inputs.coderTaskPrompt,
});

const parsedResult = ActionOutputsSchema.parse(result);
assertActionOutputs(parsedResult, true);
});

test("sends prompt to existing task", async () => {
// Setup
coderClient.mockGetCoderUserByGithubID.mockResolvedValue(mockUser);
Expand Down
37 changes: 23 additions & 14 deletions src/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,16 +104,25 @@ export class CoderTaskAction {
* Main action execution
*/
async run(): Promise<ActionOutputs> {
core.info(`GitHub user ID: ${this.inputs.githubUserID}`);
const coderUser = await this.coder.getCoderUserByGitHubId(
this.inputs.githubUserID,
);
let coderUsername: string;
if (this.inputs.coderUsername) {
core.info(`Using provided Coder username: ${this.inputs.coderUsername}`);
coderUsername = this.inputs.coderUsername;
} else {
core.info(
`Looking up Coder user by GitHub user ID: ${this.inputs.githubUserID}`,
);
const coderUser = await this.coder.getCoderUserByGitHubId(
this.inputs.githubUserID,
);
coderUsername = coderUser.username;
}
const { githubOrg, githubRepo, githubIssueNumber } =
this.parseGithubIssueURL();
core.info(`GitHub owner: ${githubOrg}`);
core.info(`GitHub repo: ${githubRepo}`);
core.info(`GitHub issue number: ${githubIssueNumber}`);
core.info(`Coder username: ${coderUser.username}`);
core.info(`Coder username: ${coderUsername}`);
if (!this.inputs.coderTaskNamePrefix || !this.inputs.githubIssueURL) {
throw new Error(
"either taskName or both taskNamePrefix and issueURL must be provided",
Expand Down Expand Up @@ -158,7 +167,7 @@ export class CoderTaskAction {
}
core.info(`Coder Template: Preset ID: ${presetID}`);

const existingTask = await this.coder.getTask(coderUser.username, taskName);
const existingTask = await this.coder.getTask(coderUsername, taskName);
if (existingTask) {
core.info(
`Coder Task: already exists: ${existingTask.name} (id: ${existingTask.id} status: ${existingTask.status})`,
Expand All @@ -170,7 +179,7 @@ export class CoderTaskAction {
`Coder Task: waiting for task ${existingTask.name} to become active...`,
);
await this.coder.waitForTaskActive(
coderUser.username,
coderUsername,
existingTask.id,
core.debug,
1_200_000,
Expand All @@ -180,15 +189,15 @@ export class CoderTaskAction {
core.info("Coder Task: Sending prompt to existing task...");
// Send prompt to existing task using the task ID (UUID)
await this.coder.sendTaskInput(
coderUser.username,
coderUsername,
existingTask.id,
this.inputs.coderTaskPrompt,
);
core.info("Coder Task: Prompt sent successfully");
return {
coderUsername: coderUser.username,
coderUsername,
taskName: existingTask.name,
taskUrl: this.generateTaskUrl(coderUser.username, existingTask.id),
taskUrl: this.generateTaskUrl(coderUsername, existingTask.id),
taskCreated: false,
};
}
Expand All @@ -201,13 +210,13 @@ export class CoderTaskAction {
input: this.inputs.coderTaskPrompt,
};
// Create new task
const createdTask = await this.coder.createTask(coderUser.username, req);
const createdTask = await this.coder.createTask(coderUsername, req);
core.info(
`Coder Task: created successfully (status: ${createdTask.status})`,
);

// 5. Generate task URL
const taskUrl = this.generateTaskUrl(coderUser.username, createdTask.id);
const taskUrl = this.generateTaskUrl(coderUsername, createdTask.id);
core.info(`Coder Task: URL: ${taskUrl}`);

// 6. Comment on issue if requested
Expand All @@ -226,8 +235,8 @@ export class CoderTaskAction {
core.info(`Skipping comment on issue (commentOnIssue is false)`);
}
return {
coderUsername: coderUser.username,
taskName: taskName,
coderUsername,
taskName,
taskUrl,
taskCreated: true,
};
Expand Down
11 changes: 7 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import { ActionInputsSchema } from "./schemas";
async function main() {
try {
// Parse and validate inputs
const githubUserIdInput = core.getInput("github-user-id");
const githubUserID = githubUserIdInput
? Number.parseInt(githubUserIdInput, 10)
: undefined;

const inputs = ActionInputsSchema.parse({
coderURL: core.getInput("coder-url", { required: true }),
coderToken: core.getInput("coder-token", { required: true }),
Expand All @@ -22,10 +27,8 @@ async function main() {
}),
githubIssueURL: core.getInput("github-issue-url", { required: true }),
githubToken: core.getInput("github-token", { required: true }),
githubUserID: Number.parseInt(
core.getInput("github-user-id", { required: true }),
10,
),
githubUserID,
coderUsername: core.getInput("coder-username") || undefined,
coderTemplatePreset: core.getInput("coder-template-preset") || undefined,
commentOnIssue: core.getBooleanInput("comment-on-issue"),
});
Expand Down
Loading
Loading