diff --git a/actions/setup/js/create_project.cjs b/actions/setup/js/create_project.cjs index ebe8f46178..8e257a60fb 100644 --- a/actions/setup/js/create_project.cjs +++ b/actions/setup/js/create_project.cjs @@ -459,6 +459,11 @@ async function main(config = {}, githubClient = null) { core.info(`✓ Successfully created project: ${projectInfo.projectUrl}`); + // Store temporary ID mapping so subsequent operations can reference this project + const normalizedTempId = normalizeTemporaryId(temporaryId ?? ""); + temporaryIdMap.set(normalizedTempId, { projectUrl: projectInfo.projectUrl }); + core.info(`Stored temporary ID mapping: ${temporaryId} -> ${projectInfo.projectUrl}`); + // Create configured views if any if (configuredViews.length > 0) { core.info(`Creating ${configuredViews.length} configured view(s) on project: ${projectInfo.projectUrl}`); @@ -488,6 +493,7 @@ async function main(config = {}, githubClient = null) { projectTitle: projectInfo.projectTitle, projectUrl: projectInfo.projectUrl, itemId: projectInfo.itemId, + temporaryId, }; } catch (err) { // prettier-ignore diff --git a/actions/setup/js/create_project_status_update.cjs b/actions/setup/js/create_project_status_update.cjs index 65e6d70949..6c3e287cfb 100644 --- a/actions/setup/js/create_project_status_update.cjs +++ b/actions/setup/js/create_project_status_update.cjs @@ -5,6 +5,7 @@ const { loadAgentOutput } = require("./load_agent_output.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); const { sanitizeContent } = require("./sanitize_content.cjs"); const { logStagedPreviewInfo } = require("./staged_preview.cjs"); +const { isTemporaryId, normalizeTemporaryId } = require("./temporary_id.cjs"); const { ERR_CONFIG, ERR_NOT_FOUND, ERR_PARSE, ERR_VALIDATION } = require("./error_codes.cjs"); /** @@ -328,7 +329,8 @@ async function main(config = {}, githubClient = null) { // Validate that project field is explicitly provided in the message // The project field is required in agent output messages and must be a full GitHub project URL - const effectiveProjectUrl = output.project; + // or a temporary project ID (e.g., aw_abc123 or #aw_abc123) from create_project + let effectiveProjectUrl = output.project; if (!effectiveProjectUrl || typeof effectiveProjectUrl !== "string" || effectiveProjectUrl.trim() === "") { core.error('Missing required "project" field. The agent must explicitly include the project URL in the output message: {"type": "create_project_status_update", "project": "https://github.com/orgs/myorg/projects/42", "body": "..."}'); @@ -338,6 +340,24 @@ async function main(config = {}, githubClient = null) { }; } + // Resolve temporary project ID if present + const projectStr = effectiveProjectUrl.trim(); + const projectWithoutHash = projectStr.startsWith("#") ? projectStr.substring(1) : projectStr; + if (isTemporaryId(projectWithoutHash)) { + const normalizedId = normalizeTemporaryId(projectWithoutHash); + const resolved = temporaryIdMap.get(normalizedId); + if (resolved && typeof resolved === "object" && "projectUrl" in resolved && resolved.projectUrl) { + core.info(`Resolved temporary project ID ${projectStr} to ${resolved.projectUrl}`); + effectiveProjectUrl = resolved.projectUrl; + } else { + core.error(`Temporary project ID '${projectStr}' not found. Ensure create_project was called before create_project_status_update.`); + return { + success: false, + error: `${ERR_NOT_FOUND}: Temporary project ID '${projectStr}' not found. Ensure create_project was called before create_project_status_update.`, + }; + } + } + if (!output.body) { core.error("Missing required field: body (status update content)"); return { diff --git a/actions/setup/js/create_project_status_update.test.cjs b/actions/setup/js/create_project_status_update.test.cjs index 59e18fe63b..bdc8084722 100644 --- a/actions/setup/js/create_project_status_update.test.cjs +++ b/actions/setup/js/create_project_status_update.test.cjs @@ -600,4 +600,114 @@ describe("create_project_status_update", () => { // Cleanup delete process.env.GH_AW_PROJECT_URL; }); + + it("should resolve a temporary project ID from temporaryIdMap", async () => { + mockGithub.graphql + .mockResolvedValueOnce({ + organization: { + projectV2: { + id: "PVT_test123", + number: 42, + title: "Test Project", + url: "https://github.com/orgs/test-org/projects/42", + }, + }, + }) + .mockResolvedValueOnce({ + createProjectV2StatusUpdate: { + statusUpdate: { + id: "PVTSU_test123", + body: "Test status update", + bodyHTML: "
Test status update
", + startDate: "2025-01-01", + targetDate: "2025-12-31", + status: "ON_TRACK", + createdAt: "2025-01-06T12:00:00Z", + }, + }, + }); + + const handler = await main({ max: 10 }); + + const temporaryIdMap = new Map(); + temporaryIdMap.set("aw_abc12345", { projectUrl: "https://github.com/orgs/test-org/projects/42" }); + + const result = await handler( + { + project: "aw_abc12345", + body: "Test status update", + status: "ON_TRACK", + start_date: "2025-01-01", + target_date: "2025-12-31", + }, + temporaryIdMap + ); + + expect(result.success).toBe(true); + expect(result.status_update_id).toBe("PVTSU_test123"); + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Resolved temporary project ID aw_abc12345")); + }); + + it("should resolve a temporary project ID with hash prefix from temporaryIdMap", async () => { + mockGithub.graphql + .mockResolvedValueOnce({ + organization: { + projectV2: { + id: "PVT_test123", + number: 42, + title: "Test Project", + url: "https://github.com/orgs/test-org/projects/42", + }, + }, + }) + .mockResolvedValueOnce({ + createProjectV2StatusUpdate: { + statusUpdate: { + id: "PVTSU_test123", + body: "Test status update", + bodyHTML: "Test status update
", + startDate: "2025-01-01", + targetDate: "2025-12-31", + status: "ON_TRACK", + createdAt: "2025-01-06T12:00:00Z", + }, + }, + }); + + const handler = await main({ max: 10 }); + + const temporaryIdMap = new Map(); + temporaryIdMap.set("aw_abc12345", { projectUrl: "https://github.com/orgs/test-org/projects/42" }); + + const result = await handler( + { + project: "#aw_abc12345", + body: "Test status update", + }, + temporaryIdMap + ); + + expect(result.success).toBe(true); + expect(result.status_update_id).toBe("PVTSU_test123"); + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Resolved temporary project ID #aw_abc12345")); + }); + + it("should return error when temporary project ID is not found in temporaryIdMap", async () => { + const handler = await main({ max: 10 }); + + const temporaryIdMap = new Map(); + + const result = await handler( + { + project: "aw_notfound", + body: "Test status update", + }, + temporaryIdMap + ); + + expect(result.success).toBe(false); + expect(result.error).toContain("aw_notfound"); + expect(mockCore.error).toHaveBeenCalledWith(expect.stringContaining("aw_notfound")); + expect(mockGithub.graphql).not.toHaveBeenCalled(); + }); });