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
36 changes: 33 additions & 3 deletions actions/setup/js/dispatch_workflow.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const { resolveTargetRepoConfig, parseRepoSlug, validateTargetRepo } = require("
const { logStagedPreviewInfo } = require("./staged_preview.cjs");
const { isStagedMode } = require("./safe_output_helpers.cjs");
const { buildAwContext } = require("./aw_context.cjs");
const { loadTemporaryIdMapFromResolved, resolveIssueNumber, replaceTemporaryIdReferences } = require("./temporary_id.cjs");

/**
* Main handler factory for dispatch_workflow
Expand Down Expand Up @@ -175,18 +176,47 @@ async function main(config = {}) {
core.info(`Dispatching workflow: ${workflowName}`);

// Prepare inputs - convert all values to strings as required by workflow_dispatch
// and resolve any #temporary_id references before dispatching
/** @type {Record<string, string>} */
const inputs = {};
if (message.inputs && typeof message.inputs === "object") {
// Build a Map from resolvedTemporaryIds for use with replaceTemporaryIdReferences
const temporaryIdMap = loadTemporaryIdMapFromResolved(resolvedTemporaryIds);

for (const [key, value] of Object.entries(message.inputs)) {
// Convert value to string
let strValue;
if (value === null || value === undefined) {
inputs[key] = "";
strValue = "";
} else if (typeof value === "object") {
inputs[key] = JSON.stringify(value);
strValue = JSON.stringify(value);
} else {
inputs[key] = String(value);
strValue = String(value);
}

// Resolve temporary ID references in string input values.
// If the entire value is a pure temporary ID (e.g. "#aw_slosync"), resolve it
// using the full temporaryIdMap (which retains repo context) so we can emit
// just the number for same-repo targets and "owner/repo#number" for cross-repo,
// consistent with how replaceTemporaryIdReferences handles embedded references.
// For text values with embedded references (e.g. "See #aw_xxx"), replace each
// reference with its cross-repository link (owner/repo#number).
if (strValue !== "") {
const pureIdResult = resolveIssueNumber(strValue, temporaryIdMap);
if (pureIdResult.wasTemporaryId) {
if (pureIdResult.errorMessage || !pureIdResult.resolved) {
core.warning(`Unresolved temporary ID in input "${key}": ${pureIdResult.errorMessage}`);
} else if (pureIdResult.resolved.repo === resolvedRepoSlug) {
strValue = String(pureIdResult.resolved.number);
} else {
strValue = `${pureIdResult.resolved.repo}#${pureIdResult.resolved.number}`;
}
} else if (temporaryIdMap.size > 0) {
strValue = replaceTemporaryIdReferences(strValue, temporaryIdMap, resolvedRepoSlug);
}
}

inputs[key] = strValue;
}
}

Expand Down
203 changes: 203 additions & 0 deletions actions/setup/js/dispatch_workflow.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -860,4 +860,207 @@ describe("dispatch_workflow handler factory", () => {
})
);
});

describe("temporary ID resolution in inputs", () => {
const baseConfig = {
workflows: ["create-pr"],
workflow_files: { "create-pr": ".lock.yml" },
};
Comment on lines +864 to +868

it("should resolve a pure temporary ID input value to the issue number", async () => {
const handler = await main(baseConfig);

const resolvedTemporaryIds = {
aw_slosync: { repo: "test-owner/test-repo", number: 6499 },
};

const message = {
type: "dispatch_workflow",
workflow_name: "create-pr",
inputs: {
issue_number: "#aw_slosync",
},
};

const result = await handler(message, resolvedTemporaryIds);

expect(result.success).toBe(true);
expect(github.rest.actions.createWorkflowDispatch).toHaveBeenCalledWith(
expect.objectContaining({
inputs: expect.objectContaining({
issue_number: "6499",
}),
})
);
});

it("should resolve temporary ID without hash prefix", async () => {
const handler = await main(baseConfig);

const resolvedTemporaryIds = {
aw_myissue: { repo: "test-owner/test-repo", number: 42 },
};

const message = {
type: "dispatch_workflow",
workflow_name: "create-pr",
inputs: {
item_number: "aw_myissue",
},
};

const result = await handler(message, resolvedTemporaryIds);

expect(result.success).toBe(true);
expect(github.rest.actions.createWorkflowDispatch).toHaveBeenCalledWith(
expect.objectContaining({
inputs: expect.objectContaining({
item_number: "42",
}),
})
);
});

it("should replace embedded temporary ID references in text input values", async () => {
const handler = await main(baseConfig);

const resolvedTemporaryIds = {
aw_track: { repo: "test-owner/test-repo", number: 100 },
};

const message = {
type: "dispatch_workflow",
workflow_name: "create-pr",
inputs: {
description: "Tracking issue is #aw_track",
},
};

const result = await handler(message, resolvedTemporaryIds);

expect(result.success).toBe(true);
const callArgs = github.rest.actions.createWorkflowDispatch.mock.calls[0][0];
// Embedded references are replaced with cross-reference format (e.g. #100 or owner/repo#100)
expect(callArgs.inputs.description).toContain("100");
expect(callArgs.inputs.description).not.toContain("#aw_track");
});

it("should warn and keep original value when temporary ID is unresolved", async () => {
const handler = await main(baseConfig);

const message = {
type: "dispatch_workflow",
workflow_name: "create-pr",
inputs: {
issue_number: "#aw_missing",
},
};

const result = await handler(message, {});

expect(result.success).toBe(true);
expect(core.warning).toHaveBeenCalledWith(expect.stringContaining("aw_missing"));
// Original value kept when unresolved
expect(github.rest.actions.createWorkflowDispatch).toHaveBeenCalledWith(
expect.objectContaining({
inputs: expect.objectContaining({
issue_number: "#aw_missing",
}),
})
);
});

it("should leave non-temporary-ID values unchanged", async () => {
const handler = await main(baseConfig);

const resolvedTemporaryIds = {
aw_slosync: { repo: "test-owner/test-repo", number: 6499 },
};

const message = {
type: "dispatch_workflow",
workflow_name: "create-pr",
inputs: {
issue_number: "123",
label: "bug",
count: 5,
},
};

const result = await handler(message, resolvedTemporaryIds);

expect(result.success).toBe(true);
expect(github.rest.actions.createWorkflowDispatch).toHaveBeenCalledWith(
expect.objectContaining({
inputs: expect.objectContaining({
issue_number: "123",
label: "bug",
count: "5",
}),
})
);
});

it("should handle multiple temporary ID inputs in a single dispatch", async () => {
const handler = await main(baseConfig);

const resolvedTemporaryIds = {
aw_issue1: { repo: "test-owner/test-repo", number: 10 },
aw_issue2: { repo: "test-owner/test-repo", number: 20 },
};

const message = {
type: "dispatch_workflow",
workflow_name: "create-pr",
inputs: {
parent_issue: "#aw_issue1",
child_issue: "#aw_issue2",
},
};

const result = await handler(message, resolvedTemporaryIds);

expect(result.success).toBe(true);
expect(github.rest.actions.createWorkflowDispatch).toHaveBeenCalledWith(
expect.objectContaining({
inputs: expect.objectContaining({
parent_issue: "10",
child_issue: "20",
}),
})
);
});

it("should use owner/repo#number format when pure temporary ID resolves to a different repo", async () => {
// The dispatch target is the context repo (test-owner/test-repo).
// The temp ID was created in a different repo. The resolved value should carry
// full cross-repo reference so the dispatched workflow knows which repo the
// issue belongs to, rather than silently passing a bare number that would be
// misinterpreted as an issue in the dispatch-target repo.
const handler = await main(baseConfig);

const resolvedTemporaryIds = {
aw_crossrepo: { repo: "other-org/other-repo", number: 999 },
};

const message = {
type: "dispatch_workflow",
workflow_name: "create-pr",
inputs: {
tracking_issue: "#aw_crossrepo",
},
};

const result = await handler(message, resolvedTemporaryIds);

expect(result.success).toBe(true);
expect(github.rest.actions.createWorkflowDispatch).toHaveBeenCalledWith(
expect.objectContaining({
inputs: expect.objectContaining({
tracking_issue: "other-org/other-repo#999",
}),
})
);
});
});
});
Loading