Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/patch-enterprise-safe-output-url.md

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

2 changes: 1 addition & 1 deletion actions/setup/js/autofix_code_scanning_alert.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ async function main(config = {}) {
headers: { "X-GitHub-Api-Version": "2022-11-28" },
});

const autofixUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/security/code-scanning/${alertNumber}`;
const autofixUrl = `${process.env.GITHUB_SERVER_URL || "https://github.com"}/${context.repo.owner}/${context.repo.repo}/security/code-scanning/${alertNumber}`;
core.info(`✓ Successfully created autofix for code scanning alert ${alertNumber}: ${autofixUrl}`);

processedAutofixes.push({
Expand Down
13 changes: 7 additions & 6 deletions actions/setup/js/check_workflow_timestamp_api.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ async function main() {

const { owner, repo } = context.repo;
const ref = context.sha;
const githubServerUrl = process.env.GITHUB_SERVER_URL || "https://github.com";

// Helper function to get the last commit for a file
async function getLastCommitForFile(path) {
Expand Down Expand Up @@ -151,10 +152,10 @@ async function main() {
.addRaw("**Files:**\n")
.addRaw(`- Source: \`${workflowMdPath}\`\n`)
.addRaw(` - Last commit: ${workflowTimestamp}\n`)
.addRaw(` - Commit SHA: [\`${workflowCommit.sha.substring(0, 7)}\`](https://github.com/${owner}/${repo}/commit/${workflowCommit.sha})\n`)
.addRaw(` - Commit SHA: [\`${workflowCommit.sha.substring(0, 7)}\`](${githubServerUrl}/${owner}/${repo}/commit/${workflowCommit.sha})\n`)
.addRaw(`- Lock: \`${lockFilePath}\`\n`)
.addRaw(` - Last commit: ${lockTimestamp}\n`)
.addRaw(` - Commit SHA: [\`${lockCommit.sha.substring(0, 7)}\`](https://github.com/${owner}/${repo}/commit/${lockCommit.sha})\n\n`)
.addRaw(` - Commit SHA: [\`${lockCommit.sha.substring(0, 7)}\`](${githubServerUrl}/${owner}/${repo}/commit/${lockCommit.sha})\n\n`)
.addRaw("**Action Required:** Run `gh aw compile` to regenerate the lock file.\n\n");

await summary.write();
Expand All @@ -179,11 +180,11 @@ async function main() {
.addRaw("**Files:**\n")
.addRaw(`- Source: \`${workflowMdPath}\`\n`)
.addRaw(` - Last commit: ${workflowTimestamp}\n`)
.addRaw(` - Commit SHA: [\`${workflowCommit.sha.substring(0, 7)}\`](https://github.com/${owner}/${repo}/commit/${workflowCommit.sha})\n`)
.addRaw(` - Commit SHA: [\`${workflowCommit.sha.substring(0, 7)}\`](${githubServerUrl}/${owner}/${repo}/commit/${workflowCommit.sha})\n`)
.addRaw(` - Frontmatter hash: \`${hashComparison.recomputedHash.substring(0, 12)}...\`\n`)
.addRaw(`- Lock: \`${lockFilePath}\`\n`)
.addRaw(` - Last commit: ${lockTimestamp}\n`)
.addRaw(` - Commit SHA: [\`${lockCommit.sha.substring(0, 7)}\`](https://github.com/${owner}/${repo}/commit/${lockCommit.sha})\n`)
.addRaw(` - Commit SHA: [\`${lockCommit.sha.substring(0, 7)}\`](${githubServerUrl}/${owner}/${repo}/commit/${lockCommit.sha})\n`)
.addRaw(` - Stored hash: \`${hashComparison.storedHash.substring(0, 12)}...\`\n\n`)
.addRaw("**Action Required:** Run `gh aw compile` to regenerate the lock file.\n\n");

Expand Down Expand Up @@ -223,11 +224,11 @@ async function main() {
.addRaw("**Files:**\n")
.addRaw(`- Source: \`${workflowMdPath}\`\n`)
.addRaw(` - Last commit: ${workflowTimestamp}\n`)
.addRaw(` - Commit SHA: [\`${workflowCommit.sha.substring(0, 7)}\`](https://github.com/${owner}/${repo}/commit/${workflowCommit.sha})\n`)
.addRaw(` - Commit SHA: [\`${workflowCommit.sha.substring(0, 7)}\`](${githubServerUrl}/${owner}/${repo}/commit/${workflowCommit.sha})\n`)
.addRaw(` - Frontmatter hash: \`${hashComparison.recomputedHash.substring(0, 12)}...\`\n`)
.addRaw(`- Lock: \`${lockFilePath}\`\n`)
.addRaw(` - Last commit: ${lockTimestamp}\n`)
.addRaw(` - Commit SHA: [\`${lockCommit.sha.substring(0, 7)}\`](https://github.com/${owner}/${repo}/commit/${lockCommit.sha})\n`)
.addRaw(` - Commit SHA: [\`${lockCommit.sha.substring(0, 7)}\`](${githubServerUrl}/${owner}/${repo}/commit/${lockCommit.sha})\n`)
.addRaw(` - Stored hash: \`${hashComparison.storedHash.substring(0, 12)}...\`\n\n`)
.addRaw("**Action Required:** Run `gh aw compile` to regenerate the lock file.\n\n");

Expand Down
2 changes: 1 addition & 1 deletion actions/setup/js/create_project.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ async function main(config = {}, githubClient = null) {

if (resolved && resolved.repo && resolved.number) {
// Build the proper GitHub issue URL
const resolvedUrl = `https://github.com/${resolved.repo}/issues/${resolved.number}`;
const resolvedUrl = `${process.env.GITHUB_SERVER_URL || "https://github.com"}/${resolved.repo}/issues/${resolved.number}`;
core.info(`Resolved temporary ID ${tempIdStr} in item_url to ${resolvedUrl}`);
item_url = resolvedUrl;
} else {
Expand Down
4 changes: 3 additions & 1 deletion actions/setup/js/extra_empty_commit.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,9 @@ async function pushExtraEmptyCommit({ branchName, repoOwner, repoName, commitMes
core.info(`Cycle check passed: ${emptyCommitCount} empty commit(s) in last ${COMMITS_TO_CHECK} (limit: ${MAX_EMPTY_COMMITS})`);

// Configure git remote with the token for authentication
const remoteUrl = `https://x-access-token:${token}@github.com/${repoOwner}/${repoName}.git`;
const githubServerUrl = process.env.GITHUB_SERVER_URL || "https://github.com";
Copy link
Contributor

Choose a reason for hiding this comment

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

The replace(/^https?:\/\//, "") correctly strips the protocol. This pattern is consistent with the enterprise support approach used elsewhere in this PR.

const serverHostStripped = githubServerUrl.replace(/^https?:\/\//, "");
const remoteUrl = `https://x-access-token:${token}@${serverHostStripped}/${repoOwner}/${repoName}.git`;

// Add a temporary remote with the token
try {
Expand Down
40 changes: 40 additions & 0 deletions actions/setup/js/extra_empty_commit.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ describe("extra_empty_commit.cjs", () => {
let pushExtraEmptyCommit;
let originalEnv;
let originalGithubRepo;
let originalGithubServerUrl;

beforeEach(() => {
originalEnv = process.env.GH_AW_CI_TRIGGER_TOKEN;
originalGithubRepo = process.env.GITHUB_REPOSITORY;
originalGithubServerUrl = process.env.GITHUB_SERVER_URL;
// Set GITHUB_REPOSITORY to match the default test owner/repo so the
// cross-repo guard doesn't interfere with unrelated tests.
process.env.GITHUB_REPOSITORY = "test-owner/test-repo";
Expand Down Expand Up @@ -44,6 +46,11 @@ describe("extra_empty_commit.cjs", () => {
} else {
delete process.env.GITHUB_REPOSITORY;
}
if (originalGithubServerUrl !== undefined) {
process.env.GITHUB_SERVER_URL = originalGithubServerUrl;
} else {
delete process.env.GITHUB_SERVER_URL;
}
delete global.core;
delete global.exec;
vi.clearAllMocks();
Expand Down Expand Up @@ -154,6 +161,39 @@ describe("extra_empty_commit.cjs", () => {
expect(removeRemoteCalls.length).toBeGreaterThanOrEqual(1);
});

it("should use github.com by default when GITHUB_SERVER_URL is not set", async () => {
delete process.env.GITHUB_SERVER_URL;
delete require.cache[require.resolve("./extra_empty_commit.cjs")];
({ pushExtraEmptyCommit } = require("./extra_empty_commit.cjs"));

await pushExtraEmptyCommit({
branchName: "feature-branch",
repoOwner: "test-owner",
repoName: "test-repo",
});

const addRemote = mockExec.exec.mock.calls.find(c => c[0] === "git" && c[1] && c[1][0] === "remote" && c[1][1] === "add");
expect(addRemote).toBeDefined();
expect(addRemote[1][3]).toContain("github.com/test-owner/test-repo.git");
});

it("should use GITHUB_SERVER_URL hostname for GitHub Enterprise", async () => {
process.env.GITHUB_SERVER_URL = "https://github.example.com";
delete require.cache[require.resolve("./extra_empty_commit.cjs")];
({ pushExtraEmptyCommit } = require("./extra_empty_commit.cjs"));

await pushExtraEmptyCommit({
branchName: "feature-branch",
repoOwner: "test-owner",
repoName: "test-repo",
});

const addRemote = mockExec.exec.mock.calls.find(c => c[0] === "git" && c[1] && c[1][0] === "remote" && c[1][1] === "add");
expect(addRemote).toBeDefined();
expect(addRemote[1][3]).toContain("github.example.com/test-owner/test-repo.git");
expect(addRemote[1][3]).not.toContain("github.com/test-owner/test-repo.git");
});

it("should use default commit message when none provided", async () => {
await pushExtraEmptyCommit({
branchName: "feature-branch",
Expand Down
3 changes: 2 additions & 1 deletion actions/setup/js/merge_remote_agent_github_folder.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,8 @@ function sparseCheckoutGithubFolder(owner, repo, ref, tempDir) {
validateGitParameter(repo, "repo");
validateGitParameter(ref, "ref");

const repoUrl = `https://github.com/${owner}/${repo}.git`;
const serverUrl = process.env.GITHUB_SERVER_URL || "https://github.com";
const repoUrl = `${serverUrl}/${owner}/${repo}.git`;

try {
// Initialize git repository
Expand Down
13 changes: 12 additions & 1 deletion actions/setup/js/safe_outputs_handlers.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,18 @@ function createHandlers(server, appendSafeOutput, config = {}) {

const githubServer = process.env.GITHUB_SERVER_URL || "https://github.com";
const repo = process.env.GITHUB_REPOSITORY || "owner/repo";
const url = `${githubServer.replace("github.com", "raw.githubusercontent.com")}/${repo}/${normalizedBranchName}/${targetFileName}`;
let url;
try {
const serverHostname = new URL(githubServer).hostname;
if (serverHostname === "github.com") {
url = `https://raw.githubusercontent.com/${repo}/${normalizedBranchName}/${targetFileName}`;
} else {
// GitHub Enterprise Server - raw content is served from the same host with /raw/ path
url = `${githubServer}/${repo}/raw/${normalizedBranchName}/${targetFileName}`;
}
} catch {
url = `${githubServer}/${repo}/raw/${normalizedBranchName}/${targetFileName}`;
}

// Create entry for safe outputs
const entry = {
Copy link
Contributor

Choose a reason for hiding this comment

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

Good approach using new URL() for hostname extraction. One minor note: the catch block fallback uses the same pattern as the GHE branch, which is correct. Consider adding a comment explaining the raw.githubusercontent.com vs /raw/ path difference for future maintainers.

Expand Down
32 changes: 32 additions & 0 deletions actions/setup/js/safe_outputs_handlers.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,38 @@ describe("safe_outputs_handlers", () => {
});

describe("uploadAssetHandler", () => {
it("should generate raw.githubusercontent.com URL for github.com", () => {
process.env.GH_AW_ASSETS_BRANCH = "test-branch";
process.env.GITHUB_SERVER_URL = "https://github.com";
process.env.GITHUB_REPOSITORY = "myorg/myrepo";

const testFile = path.join(testWorkspaceDir, "test.png");
fs.writeFileSync(testFile, "test content");

handlers.uploadAssetHandler({ path: testFile });

const entry = mockAppendSafeOutput.mock.calls[0][0];
expect(entry.url).toContain("raw.githubusercontent.com");
expect(entry.url).toContain("myorg/myrepo");
});

it("should generate enterprise URL for GitHub Enterprise Server", () => {
process.env.GH_AW_ASSETS_BRANCH = "test-branch";
process.env.GITHUB_SERVER_URL = "https://github.example.com";
process.env.GITHUB_REPOSITORY = "myorg/myrepo";

const testFile = path.join(testWorkspaceDir, "test2.png");
fs.writeFileSync(testFile, "test content");

handlers = createHandlers(mockServer, mockAppendSafeOutput);
handlers.uploadAssetHandler({ path: testFile });

const entry = mockAppendSafeOutput.mock.calls[0][0];
expect(entry.url).toContain("github.example.com");
expect(entry.url).toContain("/raw/");
expect(entry.url).not.toContain("raw.githubusercontent.com");
});

it("should validate and process valid asset upload", () => {
process.env.GH_AW_ASSETS_BRANCH = "test-branch";

Expand Down
10 changes: 6 additions & 4 deletions actions/setup/js/validate_secrets.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ async function testGitHubRESTAPI(token, owner, repo) {
}

try {
const result = await makeRequest("api.github.com", `/repos/${owner}/${repo}`, {
const apiUrl = new URL(process.env.GITHUB_API_URL || "https://api.github.com");
const result = await makeRequest(apiUrl.hostname, `${apiUrl.pathname.replace(/\/$/, "")}/repos/${owner}/${repo}`, {
"User-Agent": "gh-aw-secret-validation",
Authorization: `Bearer ${token}`,
Accept: "application/vnd.github+json",
Expand Down Expand Up @@ -159,9 +160,10 @@ async function testGitHubGraphQLAPI(token, owner, repo) {
try {
const result = await new Promise((resolve, reject) => {
const postData = JSON.stringify({ query });
const graphqlUrl = new URL(process.env.GITHUB_GRAPHQL_URL || "https://api.github.com/graphql");
const options = {
hostname: "api.github.com",
path: "/graphql",
hostname: graphqlUrl.hostname,
path: graphqlUrl.pathname,
method: "POST",
headers: {
"User-Agent": "gh-aw-secret-validation",
Expand Down Expand Up @@ -564,7 +566,7 @@ function generateMarkdownReport(results) {
report += `> - [\`${secret}\`](${docsLink})\n`;
});
report += `>\n`;
report += `> Configure these secrets in [repository settings](https://github.com/${repository}/settings/secrets/actions) if needed.\n\n`;
report += `> Configure these secrets in [repository settings](${process.env.GITHUB_SERVER_URL || "https://github.com"}/${repository}/settings/secrets/actions) if needed.\n\n`;
}
}

Expand Down
Loading