fix(prd-7): M1 architecture review — consolidate duplication, fix bugs, memoize detection
- F1: Consolidated updateForgejoCommentBody into unified updateCommentBody (~150 lines removed) - F2: Fixed wrong run identifier in update-with-branch.ts (context.runId → getForgejoRunIndex()) - F3: Memoized detectPlatform() with _resetPlatformCache() for tests - F4: Added getServerUrl() runtime helper to resolve constant-vs-function inconsistency - F5: Fixed sticky comment bot matching on Forgejo via context.inputs.botName - F6: Documented GHES heuristic misidentification in docs/forgejo.md - Updated PRD M1 checkbox to complete Progress: 17% complete — M1 done, M2 (error handling review) next Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
ae326659b1
commit
ac58fb6c14
15 files changed
+149
-264
No files matched your search
@@ -88,6 +88,8 @@ The action detects the platform using a three-tier strategy:
|
|||||||
|
|
||||||
**Recommendation**: Always set `platform: "forgejo"` explicitly. Auto-detection works but the explicit input eliminates ambiguity.
|
**Recommendation**: Always set `platform: "forgejo"` explicitly. Auto-detection works but the explicit input eliminates ambiguity.
|
||||||
|
|
||||||
|
> **Note for GitHub Enterprise Server (GHES) users**: The tier-3 heuristic classifies any non-`github.com` URL as Forgejo. If you run on GHES (e.g., `https://github.mycompany.com`), you **must** set `platform: "github"` explicitly to avoid misdetection.
|
||||||
|
|
||||||
## Differences from GitHub
|
## Differences from GitHub
|
||||||
|
|
||||||
### Authentication
|
### Authentication
|
||||||
@@ -340,6 +342,7 @@ All execution modes and trigger types have been validated end-to-end on a Forgej
|
|||||||
|
|
||||||
## Known Limitations
|
## Known Limitations
|
||||||
|
|
||||||
|
- **GitHub Enterprise Server (GHES) misdetection**: The tier-3 platform detection heuristic treats any `GITHUB_SERVER_URL` that isn't `github.com` as Forgejo. GHES instances (e.g., `https://github.mycompany.com`) will be misidentified. Set `platform: "github"` explicitly when running on GHES.
|
||||||
- **Git ref API**: Forgejo uses `/git/refs` (plural) instead of GitHub's `/git/ref` (singular). The action handles this by using `repos.getBranch()` on Forgejo for branch SHA lookups.
|
- **Git ref API**: Forgejo uses `/git/refs` (plural) instead of GitHub's `/git/ref` (singular). The action handles this by using `repos.getBranch()` on Forgejo for branch SHA lookups.
|
||||||
- **User display name fetch**: `GET /users/{username}` returns 403 without `read:user` scope. Non-fatal — falls back to login name.
|
- **User display name fetch**: `GET /users/{username}` returns 403 without `read:user` scope. Non-fatal — falls back to login name.
|
||||||
- **`pull_request_target` event**: May not exist in Forgejo. The action degrades gracefully (maps to `pull_request`).
|
- **`pull_request_target` event**: May not exist in Forgejo. The action degrades gracefully (maps to `pull_request`).
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ Perform a thorough, documented code review of all Forgejo-related changes. The r
|
|||||||
|
|
||||||
## Milestones
|
## Milestones
|
||||||
|
|
||||||
- [ ] **M1: Architecture & design review**
|
- [x] **M1: Architecture & design review**
|
||||||
|
|
||||||
- Review the lean adapter pattern: is it the right abstraction level?
|
- Review the lean adapter pattern: is it the right abstraction level?
|
||||||
- Assess merge-conflict surface with upstream: are platform branches minimal?
|
- Assess merge-conflict surface with upstream: are platform branches minimal?
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
type CommentUpdateInput,
|
type CommentUpdateInput,
|
||||||
} from "../github/operations/comment-logic";
|
} from "../github/operations/comment-logic";
|
||||||
import {
|
import {
|
||||||
updateForgejoCommentBody,
|
|
||||||
createForgejoPrCompareUrl,
|
createForgejoPrCompareUrl,
|
||||||
getForgejoRunIndex,
|
getForgejoRunIndex,
|
||||||
} from "../forgejo/comment-logic";
|
} from "../forgejo/comment-logic";
|
||||||
@@ -19,7 +18,7 @@ import {
|
|||||||
isEntityContext,
|
isEntityContext,
|
||||||
} from "../github/context";
|
} from "../github/context";
|
||||||
import type { ParsedGitHubContext } from "../github/context";
|
import type { ParsedGitHubContext } from "../github/context";
|
||||||
import { GITHUB_SERVER_URL } from "../github/api/config";
|
import { getServerUrl } from "../github/api/config";
|
||||||
import { checkAndCommitOrDeleteBranch } from "../github/operations/branch-cleanup";
|
import { checkAndCommitOrDeleteBranch } from "../github/operations/branch-cleanup";
|
||||||
import { updateClaudeComment } from "../github/operations/comments/update-claude-comment";
|
import { updateClaudeComment } from "../github/operations/comments/update-claude-comment";
|
||||||
|
|
||||||
@@ -54,7 +53,7 @@ export async function updateCommentLink(
|
|||||||
const { owner, repo } = context.repository;
|
const { owner, repo } = context.repository;
|
||||||
const platform = detectPlatform();
|
const platform = detectPlatform();
|
||||||
|
|
||||||
const serverUrl = GITHUB_SERVER_URL;
|
const serverUrl = getServerUrl();
|
||||||
const runIdentifier =
|
const runIdentifier =
|
||||||
platform === "forgejo" ? getForgejoRunIndex() : process.env.GITHUB_RUN_ID;
|
platform === "forgejo" ? getForgejoRunIndex() : process.env.GITHUB_RUN_ID;
|
||||||
const jobUrl = `${serverUrl}/${owner}/${repo}/actions/runs/${runIdentifier}`;
|
const jobUrl = `${serverUrl}/${owner}/${repo}/actions/runs/${runIdentifier}`;
|
||||||
@@ -250,10 +249,7 @@ export async function updateCommentLink(
|
|||||||
errorDetails,
|
errorDetails,
|
||||||
};
|
};
|
||||||
|
|
||||||
const updatedBody =
|
const updatedBody = updateCommentBody(commentInput);
|
||||||
platform === "forgejo"
|
|
||||||
? updateForgejoCommentBody(commentInput)
|
|
||||||
: updateCommentBody(commentInput);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await updateClaudeComment(octokit.rest, {
|
await updateClaudeComment(octokit.rest, {
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
/**
|
/**
|
||||||
* Forgejo-specific comment URL formatting and body construction.
|
* Forgejo-specific comment URL formatting and body construction.
|
||||||
*
|
*
|
||||||
* Mirrors the GitHub versions in:
|
* Contains Forgejo-specific helpers for:
|
||||||
* - src/github/operations/comments/common.ts (spinner, link builders, initial comment)
|
|
||||||
* - src/github/operations/comment-logic.ts (updateCommentBody for final comment)
|
|
||||||
*
|
|
||||||
* Differences from GitHub:
|
|
||||||
* - Unicode spinner instead of GitHub CDN image (CDN URLs don't resolve on Forgejo)
|
* - Unicode spinner instead of GitHub CDN image (CDN URLs don't resolve on Forgejo)
|
||||||
* - PR compare URL omits `quick_pull=1` (GitHub-specific parameter)
|
* - PR compare URL with `expand=1` instead of GitHub's `quick_pull=1`
|
||||||
* - Dynamic server URL extraction instead of hardcoded github.com regex
|
* - Forgejo run index (GITHUB_RUN_NUMBER vs GITHUB_RUN_ID)
|
||||||
|
*
|
||||||
|
* The final comment body logic (updateCommentBody) is unified in
|
||||||
|
* src/github/operations/comment-logic.ts and works for both platforms.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ensureProperlyEncodedUrl } from "../github/operations/comment-logic";
|
import { getServerUrl } from "../github/api/config";
|
||||||
import type { CommentUpdateInput } from "../github/operations/comment-logic";
|
|
||||||
|
|
||||||
// Re-export types so consumers don't need to import from two places
|
// Re-export types so consumers don't need to import from two places
|
||||||
export type {
|
export type {
|
||||||
@@ -21,19 +19,6 @@ export type {
|
|||||||
} from "../github/operations/comment-logic";
|
} from "../github/operations/comment-logic";
|
||||||
export { ensureProperlyEncodedUrl } from "../github/operations/comment-logic";
|
export { ensureProperlyEncodedUrl } from "../github/operations/comment-logic";
|
||||||
|
|
||||||
/**
|
|
||||||
* Read the server URL at call time (not module load time).
|
|
||||||
*
|
|
||||||
* The GitHub module uses a top-level constant from config.ts, which is fine
|
|
||||||
* when GITHUB_SERVER_URL doesn't change. For Forgejo we read from env at
|
|
||||||
* call time so that:
|
|
||||||
* 1. Tests can override the env var and see the effect immediately.
|
|
||||||
* 2. The same module works regardless of import order.
|
|
||||||
*/
|
|
||||||
function getServerUrl(): string {
|
|
||||||
return process.env.GITHUB_SERVER_URL || "https://github.com";
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Run index helper
|
// Run index helper
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -118,189 +103,3 @@ I'll analyze this and get back to you.
|
|||||||
|
|
||||||
${jobRunLink}${branchLink}`;
|
${jobRunLink}${branchLink}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Owner/repo extraction helper
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract owner/repo from a URL using the configured server URL.
|
|
||||||
*
|
|
||||||
* Unlike the GitHub version (which hardcodes `github.com`), this dynamically
|
|
||||||
* matches against any server URL — works for both GitHub and Forgejo.
|
|
||||||
*/
|
|
||||||
export function extractOwnerRepoFromUrl(
|
|
||||||
url: string,
|
|
||||||
serverUrl: string = getServerUrl(),
|
|
||||||
): { owner: string; repo: string } | null {
|
|
||||||
try {
|
|
||||||
const parsedServer = new URL(serverUrl);
|
|
||||||
const parsedUrl = new URL(url);
|
|
||||||
|
|
||||||
// Host must match (handles both github.com and git.wxs.ro etc.)
|
|
||||||
if (parsedUrl.host !== parsedServer.host) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract first two path segments as owner/repo
|
|
||||||
const segments = parsedUrl.pathname.split("/").filter(Boolean);
|
|
||||||
const owner = segments[0];
|
|
||||||
const repo = segments[1];
|
|
||||||
if (owner && repo) {
|
|
||||||
return { owner, repo };
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// Invalid URLs — fall through to null
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Final comment body (shown after Claude finishes)
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Forgejo version of updateCommentBody.
|
|
||||||
*
|
|
||||||
* Identical logic to src/github/operations/comment-logic.ts:updateCommentBody
|
|
||||||
* with one critical fix: uses extractOwnerRepoFromUrl() for dynamic server URL
|
|
||||||
* extraction instead of the hardcoded `github.com` regex.
|
|
||||||
*/
|
|
||||||
export function updateForgejoCommentBody(input: CommentUpdateInput): string {
|
|
||||||
const originalBody = input.currentBody;
|
|
||||||
const {
|
|
||||||
executionDetails,
|
|
||||||
jobUrl,
|
|
||||||
branchLink,
|
|
||||||
prLink,
|
|
||||||
actionFailed,
|
|
||||||
branchName,
|
|
||||||
triggerUsername,
|
|
||||||
errorDetails,
|
|
||||||
} = input;
|
|
||||||
|
|
||||||
// Extract content from the original comment body
|
|
||||||
// First, remove the "Claude Code is working…" or "Claude Code is working..." message
|
|
||||||
const workingPattern = /Claude Code is working[…\.]{1,3}(?:\s*<img[^>]*>)?/i;
|
|
||||||
let bodyContent = originalBody.replace(workingPattern, "").trim();
|
|
||||||
|
|
||||||
// Also remove the Unicode spinner used on Forgejo
|
|
||||||
bodyContent = bodyContent.replace(/⏳/g, "").trim();
|
|
||||||
|
|
||||||
// Check if there's a PR link in the content
|
|
||||||
let prLinkFromContent = "";
|
|
||||||
|
|
||||||
// Match the entire markdown link structure
|
|
||||||
const prLinkPattern = /\[Create .* PR\]\((.*)\)$/m;
|
|
||||||
const prLinkMatch = bodyContent.match(prLinkPattern);
|
|
||||||
|
|
||||||
if (prLinkMatch && prLinkMatch[1]) {
|
|
||||||
const encodedUrl = ensureProperlyEncodedUrl(prLinkMatch[1]);
|
|
||||||
if (encodedUrl) {
|
|
||||||
prLinkFromContent = encodedUrl;
|
|
||||||
// Remove the PR link from the content
|
|
||||||
bodyContent = bodyContent.replace(prLinkMatch[0], "").trim();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate duration string if available
|
|
||||||
let durationStr = "";
|
|
||||||
if (executionDetails?.duration_ms !== undefined) {
|
|
||||||
const totalSeconds = Math.round(executionDetails.duration_ms / 1000);
|
|
||||||
const minutes = Math.floor(totalSeconds / 60);
|
|
||||||
const seconds = totalSeconds % 60;
|
|
||||||
durationStr = minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the header
|
|
||||||
let header = "";
|
|
||||||
|
|
||||||
if (actionFailed) {
|
|
||||||
header = "**Claude encountered an error";
|
|
||||||
if (durationStr) {
|
|
||||||
header += ` after ${durationStr}`;
|
|
||||||
}
|
|
||||||
header += "**";
|
|
||||||
} else {
|
|
||||||
// Get the username from triggerUsername or extract from content
|
|
||||||
const usernameMatch = bodyContent.match(/@([a-zA-Z0-9-]+)/);
|
|
||||||
const username =
|
|
||||||
triggerUsername || (usernameMatch ? usernameMatch[1] : "user");
|
|
||||||
|
|
||||||
header = `**Claude finished @${username}'s task`;
|
|
||||||
if (durationStr) {
|
|
||||||
header += ` in ${durationStr}`;
|
|
||||||
}
|
|
||||||
header += "**";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add links section
|
|
||||||
let links = ` —— [View job](${jobUrl})`;
|
|
||||||
|
|
||||||
// Add branch name with link
|
|
||||||
if (branchName || branchLink) {
|
|
||||||
let finalBranchName = branchName;
|
|
||||||
let branchUrl = "";
|
|
||||||
|
|
||||||
if (branchLink) {
|
|
||||||
// Extract the branch URL from the link
|
|
||||||
const urlMatch = branchLink.match(/\((https?:\/\/.*)\)/);
|
|
||||||
if (urlMatch && urlMatch[1]) {
|
|
||||||
branchUrl = urlMatch[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract branch name from link if not provided
|
|
||||||
if (!finalBranchName) {
|
|
||||||
const branchNameMatch = branchLink.match(/tree\/([^"'\)]+)/);
|
|
||||||
if (branchNameMatch) {
|
|
||||||
finalBranchName = branchNameMatch[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we don't have a URL yet but have a branch name, construct it
|
|
||||||
// FIX: Uses extractOwnerRepoFromUrl instead of hardcoded github.com regex
|
|
||||||
if (!branchUrl && finalBranchName) {
|
|
||||||
const extracted = extractOwnerRepoFromUrl(jobUrl);
|
|
||||||
if (extracted) {
|
|
||||||
branchUrl = `${getServerUrl()}/${extracted.owner}/${extracted.repo}/tree/${finalBranchName}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (finalBranchName && branchUrl) {
|
|
||||||
links += ` • [\`${finalBranchName}\`](${branchUrl})`;
|
|
||||||
} else if (finalBranchName) {
|
|
||||||
links += ` • \`${finalBranchName}\``;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add PR link (either from content or provided)
|
|
||||||
const prUrl =
|
|
||||||
prLinkFromContent || (prLink ? prLink.match(/\(([^)]+)\)/)?.[1] : "");
|
|
||||||
if (prUrl) {
|
|
||||||
links += ` • [Create PR ➔](${prUrl})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the new body with blank line between header and separator
|
|
||||||
let newBody = `${header}${links}`;
|
|
||||||
|
|
||||||
// Add error details if available
|
|
||||||
if (actionFailed && errorDetails) {
|
|
||||||
newBody += `\n\n\`\`\`\n${errorDetails}\n\`\`\``;
|
|
||||||
}
|
|
||||||
|
|
||||||
newBody += `\n\n---\n`;
|
|
||||||
|
|
||||||
// Clean up the body content
|
|
||||||
// Remove any existing View job run, branch links from the bottom
|
|
||||||
bodyContent = bodyContent.replace(/\n?\[View job run\]\([^\)]+\)/g, "");
|
|
||||||
bodyContent = bodyContent.replace(/\n?\[View branch\]\([^\)]+\)/g, "");
|
|
||||||
|
|
||||||
// Remove any existing duration info at the bottom
|
|
||||||
bodyContent = bodyContent.replace(/\n*---\n*Duration: [0-9]+m? [0-9]+s/g, "");
|
|
||||||
|
|
||||||
// Add the cleaned body content
|
|
||||||
newBody += bodyContent;
|
|
||||||
|
|
||||||
return newBody.trim();
|
|
||||||
}
|
|
||||||
@@ -2,3 +2,13 @@ export const GITHUB_API_URL =
|
|||||||
process.env.GITHUB_API_URL || "https://api.github.com";
|
process.env.GITHUB_API_URL || "https://api.github.com";
|
||||||
export const GITHUB_SERVER_URL =
|
export const GITHUB_SERVER_URL =
|
||||||
process.env.GITHUB_SERVER_URL || "https://github.com";
|
process.env.GITHUB_SERVER_URL || "https://github.com";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the server URL at call time (not module load time).
|
||||||
|
*
|
||||||
|
* Use this instead of GITHUB_SERVER_URL in code paths where tests may
|
||||||
|
* override process.env.GITHUB_SERVER_URL after module import.
|
||||||
|
*/
|
||||||
|
export function getServerUrl(): string {
|
||||||
|
return process.env.GITHUB_SERVER_URL || "https://github.com";
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { GITHUB_SERVER_URL } from "../api/config";
|
import { getServerUrl } from "../api/config";
|
||||||
|
|
||||||
export type ExecutionDetails = {
|
export type ExecutionDetails = {
|
||||||
total_cost_usd?: number;
|
total_cost_usd?: number;
|
||||||
@@ -66,6 +66,37 @@ export function ensureProperlyEncodedUrl(url: string): string | null {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract owner/repo from a URL using the configured server URL.
|
||||||
|
*
|
||||||
|
* Dynamically matches against any server URL — works for both GitHub and Forgejo.
|
||||||
|
*/
|
||||||
|
export function extractOwnerRepoFromUrl(
|
||||||
|
url: string,
|
||||||
|
serverUrl: string = getServerUrl(),
|
||||||
|
): { owner: string; repo: string } | null {
|
||||||
|
try {
|
||||||
|
const parsedServer = new URL(serverUrl);
|
||||||
|
const parsedUrl = new URL(url);
|
||||||
|
|
||||||
|
// Host must match (handles both github.com and custom domains)
|
||||||
|
if (parsedUrl.host !== parsedServer.host) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract first two path segments as owner/repo
|
||||||
|
const segments = parsedUrl.pathname.split("/").filter(Boolean);
|
||||||
|
const owner = segments[0];
|
||||||
|
const repo = segments[1];
|
||||||
|
if (owner && repo) {
|
||||||
|
return { owner, repo };
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Invalid URLs — fall through to null
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
export function updateCommentBody(input: CommentUpdateInput): string {
|
export function updateCommentBody(input: CommentUpdateInput): string {
|
||||||
const originalBody = input.currentBody;
|
const originalBody = input.currentBody;
|
||||||
const {
|
const {
|
||||||
@@ -84,6 +115,9 @@ export function updateCommentBody(input: CommentUpdateInput): string {
|
|||||||
const workingPattern = /Claude Code is working[…\.]{1,3}(?:\s*<img[^>]*>)?/i;
|
const workingPattern = /Claude Code is working[…\.]{1,3}(?:\s*<img[^>]*>)?/i;
|
||||||
let bodyContent = originalBody.replace(workingPattern, "").trim();
|
let bodyContent = originalBody.replace(workingPattern, "").trim();
|
||||||
|
|
||||||
|
// Also remove the Unicode spinner used on Forgejo (safe no-op on GitHub)
|
||||||
|
bodyContent = bodyContent.replace(/⏳/g, "").trim();
|
||||||
|
|
||||||
// Check if there's a PR link in the content
|
// Check if there's a PR link in the content
|
||||||
let prLinkFromContent = "";
|
let prLinkFromContent = "";
|
||||||
|
|
||||||
@@ -141,7 +175,7 @@ export function updateCommentBody(input: CommentUpdateInput): string {
|
|||||||
|
|
||||||
if (branchLink) {
|
if (branchLink) {
|
||||||
// Extract the branch URL from the link
|
// Extract the branch URL from the link
|
||||||
const urlMatch = branchLink.match(/\((https:\/\/.*)\)/);
|
const urlMatch = branchLink.match(/\((https?:\/\/.*)\)/);
|
||||||
if (urlMatch && urlMatch[1]) {
|
if (urlMatch && urlMatch[1]) {
|
||||||
branchUrl = urlMatch[1];
|
branchUrl = urlMatch[1];
|
||||||
}
|
}
|
||||||
@@ -156,11 +190,11 @@ export function updateCommentBody(input: CommentUpdateInput): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If we don't have a URL yet but have a branch name, construct it
|
// If we don't have a URL yet but have a branch name, construct it
|
||||||
|
// Uses dynamic URL extraction that works for both GitHub and Forgejo
|
||||||
if (!branchUrl && finalBranchName) {
|
if (!branchUrl && finalBranchName) {
|
||||||
// Extract owner/repo from jobUrl
|
const extracted = extractOwnerRepoFromUrl(jobUrl);
|
||||||
const repoMatch = jobUrl.match(/github\.com\/([^\/]+)\/([^\/]+)\//);
|
if (extracted) {
|
||||||
if (repoMatch) {
|
branchUrl = `${getServerUrl()}/${extracted.owner}/${extracted.repo}/tree/${finalBranchName}`;
|
||||||
branchUrl = `${GITHUB_SERVER_URL}/${repoMatch[1]}/${repoMatch[2]}/tree/${finalBranchName}`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,9 +56,14 @@ export async function createInitialComment(
|
|||||||
const botNameMatch =
|
const botNameMatch =
|
||||||
comment.user?.type === "Bot" &&
|
comment.user?.type === "Bot" &&
|
||||||
comment.user?.login.toLowerCase().includes("claude");
|
comment.user?.login.toLowerCase().includes("claude");
|
||||||
|
// On Forgejo, the bot is a regular user account identified by bot_name input
|
||||||
|
const forgejoBotMatch =
|
||||||
|
platform === "forgejo" &&
|
||||||
|
context.inputs.botName &&
|
||||||
|
comment.user?.login === context.inputs.botName;
|
||||||
const bodyMatch = comment.body === initialBody;
|
const bodyMatch = comment.body === initialBody;
|
||||||
|
|
||||||
return idMatch || botNameMatch || bodyMatch;
|
return idMatch || botNameMatch || forgejoBotMatch || bodyMatch;
|
||||||
});
|
});
|
||||||
if (existingComment) {
|
if (existingComment) {
|
||||||
response = await octokit.rest.issues.updateComment({
|
response = await octokit.rest.issues.updateComment({
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
createForgejoJobRunLink,
|
createForgejoJobRunLink,
|
||||||
createForgejoBranchLink,
|
createForgejoBranchLink,
|
||||||
createForgejoCommentBody,
|
createForgejoCommentBody,
|
||||||
|
getForgejoRunIndex,
|
||||||
} from "../../../forgejo/comment-logic";
|
} from "../../../forgejo/comment-logic";
|
||||||
import { detectPlatform } from "../../../platform/detector";
|
import { detectPlatform } from "../../../platform/detector";
|
||||||
import { type Octokits } from "../../api/client";
|
import { type Octokits } from "../../api/client";
|
||||||
@@ -34,7 +35,7 @@ export async function updateTrackingComment(
|
|||||||
|
|
||||||
const jobRunLink =
|
const jobRunLink =
|
||||||
platform === "forgejo"
|
platform === "forgejo"
|
||||||
? createForgejoJobRunLink(owner, repo, context.runId)
|
? createForgejoJobRunLink(owner, repo, getForgejoRunIndex())
|
||||||
: createJobRunLink(owner, repo, context.runId);
|
: createJobRunLink(owner, repo, context.runId);
|
||||||
|
|
||||||
// Add branch link for issues (not PRs)
|
// Add branch link for issues (not PRs)
|
||||||
|
|||||||
@@ -6,24 +6,47 @@
|
|||||||
* 2. FORGEJO env var (set by Forgejo runner v7.0+)
|
* 2. FORGEJO env var (set by Forgejo runner v7.0+)
|
||||||
* 3. GITHUB_SERVER_URL heuristic (non-github.com = Forgejo)
|
* 3. GITHUB_SERVER_URL heuristic (non-github.com = Forgejo)
|
||||||
* 4. Default to GitHub
|
* 4. Default to GitHub
|
||||||
|
*
|
||||||
|
* The result is memoized after first detection — platform doesn't change
|
||||||
|
* at runtime and this avoids redundant env reads across 12+ call sites.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type Platform = "github" | "forgejo";
|
export type Platform = "github" | "forgejo";
|
||||||
|
|
||||||
|
let _cachedPlatform: Platform | undefined;
|
||||||
|
|
||||||
export function detectPlatform(): Platform {
|
export function detectPlatform(): Platform {
|
||||||
|
if (_cachedPlatform !== undefined) return _cachedPlatform;
|
||||||
|
|
||||||
|
let result: Platform = "github";
|
||||||
|
|
||||||
// 1. Explicit platform from action.yml detect step or user input
|
// 1. Explicit platform from action.yml detect step or user input
|
||||||
const platformEnv = process.env.PLATFORM;
|
const platformEnv = process.env.PLATFORM;
|
||||||
if (platformEnv === "forgejo") return "forgejo";
|
if (platformEnv === "forgejo") {
|
||||||
if (platformEnv === "github") return "github";
|
result = "forgejo";
|
||||||
|
} else if (platformEnv === "github") {
|
||||||
|
result = "github";
|
||||||
|
}
|
||||||
// 2. FORGEJO env var (set by Forgejo runner v7.0+)
|
// 2. FORGEJO env var (set by Forgejo runner v7.0+)
|
||||||
if (process.env.FORGEJO === "true") return "forgejo";
|
else if (process.env.FORGEJO === "true") {
|
||||||
|
result = "forgejo";
|
||||||
|
}
|
||||||
// 3. GITHUB_SERVER_URL heuristic — if not github.com, likely Forgejo/Gitea
|
// 3. GITHUB_SERVER_URL heuristic — if not github.com, likely Forgejo/Gitea
|
||||||
|
else {
|
||||||
const serverUrl = process.env.GITHUB_SERVER_URL || "";
|
const serverUrl = process.env.GITHUB_SERVER_URL || "";
|
||||||
if (serverUrl && !serverUrl.includes("github.com")) {
|
if (serverUrl && !serverUrl.includes("github.com")) {
|
||||||
return "forgejo";
|
result = "forgejo";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "github";
|
_cachedPlatform = result;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the memoized platform detection result.
|
||||||
|
* Only needed in tests that change platform-related env vars between cases.
|
||||||
|
*/
|
||||||
|
export function _resetPlatformCache(): void {
|
||||||
|
_cachedPlatform = undefined;
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
|
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
|
||||||
import { checkHumanActor } from "../src/github/validation/actor";
|
import { checkHumanActor } from "../src/github/validation/actor";
|
||||||
|
import { _resetPlatformCache } from "../src/platform/detector";
|
||||||
import type { Octokit } from "@octokit/rest";
|
import type { Octokit } from "@octokit/rest";
|
||||||
import { createMockContext } from "./mockContext";
|
import { createMockContext } from "./mockContext";
|
||||||
|
|
||||||
@@ -116,6 +117,7 @@ describe("checkHumanActor", () => {
|
|||||||
let savedServerUrl: string | undefined;
|
let savedServerUrl: string | undefined;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
_resetPlatformCache();
|
||||||
savedPlatform = process.env.PLATFORM;
|
savedPlatform = process.env.PLATFORM;
|
||||||
savedServerUrl = process.env.GITHUB_SERVER_URL;
|
savedServerUrl = process.env.GITHUB_SERVER_URL;
|
||||||
process.env.PLATFORM = "forgejo";
|
process.env.PLATFORM = "forgejo";
|
||||||
@@ -127,6 +129,7 @@ describe("checkHumanActor", () => {
|
|||||||
if (savedServerUrl !== undefined)
|
if (savedServerUrl !== undefined)
|
||||||
process.env.GITHUB_SERVER_URL = savedServerUrl;
|
process.env.GITHUB_SERVER_URL = savedServerUrl;
|
||||||
else delete process.env.GITHUB_SERVER_URL;
|
else delete process.env.GITHUB_SERVER_URL;
|
||||||
|
_resetPlatformCache();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should skip actor check entirely on Forgejo", async () => {
|
test("should skip actor check entirely on Forgejo", async () => {
|
||||||
|
|||||||
@@ -6,10 +6,12 @@ import {
|
|||||||
createForgejoBranchLink,
|
createForgejoBranchLink,
|
||||||
createForgejoPrCompareUrl,
|
createForgejoPrCompareUrl,
|
||||||
createForgejoCommentBody,
|
createForgejoCommentBody,
|
||||||
extractOwnerRepoFromUrl,
|
|
||||||
updateForgejoCommentBody,
|
|
||||||
type CommentUpdateInput,
|
|
||||||
} from "../src/forgejo/comment-logic";
|
} from "../src/forgejo/comment-logic";
|
||||||
|
import {
|
||||||
|
extractOwnerRepoFromUrl,
|
||||||
|
updateCommentBody,
|
||||||
|
type CommentUpdateInput,
|
||||||
|
} from "../src/github/operations/comment-logic";
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Test helpers
|
// Test helpers
|
||||||
@@ -307,10 +309,10 @@ describe("extractOwnerRepoFromUrl", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// updateForgejoCommentBody
|
// updateCommentBody
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
describe("updateForgejoCommentBody", () => {
|
describe("updateCommentBody", () => {
|
||||||
const forgejoBaseInput: CommentUpdateInput = {
|
const forgejoBaseInput: CommentUpdateInput = {
|
||||||
currentBody: "Initial comment body",
|
currentBody: "Initial comment body",
|
||||||
actionFailed: false,
|
actionFailed: false,
|
||||||
@@ -332,7 +334,7 @@ describe("updateForgejoCommentBody", () => {
|
|||||||
triggerUsername: "testuser",
|
triggerUsername: "testuser",
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = updateForgejoCommentBody(input);
|
const result = updateCommentBody(input);
|
||||||
expect(result).toContain("**Claude finished @testuser's task**");
|
expect(result).toContain("**Claude finished @testuser's task**");
|
||||||
expect(result).not.toContain("Claude Code is working");
|
expect(result).not.toContain("Claude Code is working");
|
||||||
expect(result).not.toContain("<img");
|
expect(result).not.toContain("<img");
|
||||||
@@ -345,7 +347,7 @@ describe("updateForgejoCommentBody", () => {
|
|||||||
triggerUsername: "testuser",
|
triggerUsername: "testuser",
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = updateForgejoCommentBody(input);
|
const result = updateCommentBody(input);
|
||||||
expect(result).toContain("**Claude finished @testuser's task**");
|
expect(result).toContain("**Claude finished @testuser's task**");
|
||||||
expect(result).not.toContain("Claude Code is working");
|
expect(result).not.toContain("Claude Code is working");
|
||||||
expect(result).not.toContain("⏳");
|
expect(result).not.toContain("⏳");
|
||||||
@@ -359,7 +361,7 @@ describe("updateForgejoCommentBody", () => {
|
|||||||
triggerUsername: "trigger-user",
|
triggerUsername: "trigger-user",
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = updateForgejoCommentBody(input);
|
const result = updateCommentBody(input);
|
||||||
expect(result).toContain(
|
expect(result).toContain(
|
||||||
"**Claude finished @trigger-user's task in 1m 14s**",
|
"**Claude finished @trigger-user's task in 1m 14s**",
|
||||||
);
|
);
|
||||||
@@ -373,7 +375,7 @@ describe("updateForgejoCommentBody", () => {
|
|||||||
executionDetails: { duration_ms: 45000 },
|
executionDetails: { duration_ms: 45000 },
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = updateForgejoCommentBody(input);
|
const result = updateCommentBody(input);
|
||||||
expect(result).toContain("**Claude encountered an error after 45s**");
|
expect(result).toContain("**Claude encountered an error after 45s**");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -386,7 +388,7 @@ describe("updateForgejoCommentBody", () => {
|
|||||||
errorDetails: "Failed to fetch issue data",
|
errorDetails: "Failed to fetch issue data",
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = updateForgejoCommentBody(input);
|
const result = updateCommentBody(input);
|
||||||
expect(result).toContain("**Claude encountered an error after 45s**");
|
expect(result).toContain("**Claude encountered an error after 45s**");
|
||||||
expect(result).toContain("[View job]");
|
expect(result).toContain("[View job]");
|
||||||
expect(result).toContain("```\nFailed to fetch issue data\n```");
|
expect(result).toContain("```\nFailed to fetch issue data\n```");
|
||||||
@@ -400,7 +402,7 @@ describe("updateForgejoCommentBody", () => {
|
|||||||
currentBody: "Some comment",
|
currentBody: "Some comment",
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = updateForgejoCommentBody(input);
|
const result = updateCommentBody(input);
|
||||||
expect(result).toContain(`—— [View job](${forgejoBaseInput.jobUrl})`);
|
expect(result).toContain(`—— [View job](${forgejoBaseInput.jobUrl})`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -412,7 +414,7 @@ describe("updateForgejoCommentBody", () => {
|
|||||||
branchName: "claude/issue-5-20240101-1200",
|
branchName: "claude/issue-5-20240101-1200",
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = updateForgejoCommentBody(input);
|
const result = updateCommentBody(input);
|
||||||
// This is the critical fix: GitHub's version uses a hardcoded github.com regex
|
// This is the critical fix: GitHub's version uses a hardcoded github.com regex
|
||||||
// which fails for Forgejo URLs. Our version uses extractOwnerRepoFromUrl.
|
// which fails for Forgejo URLs. Our version uses extractOwnerRepoFromUrl.
|
||||||
expect(result).toContain(
|
expect(result).toContain(
|
||||||
@@ -427,7 +429,7 @@ describe("updateForgejoCommentBody", () => {
|
|||||||
branchName: "feature-branch",
|
branchName: "feature-branch",
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = updateForgejoCommentBody(input);
|
const result = updateCommentBody(input);
|
||||||
expect(result).toContain("• `feature-branch`");
|
expect(result).toContain("• `feature-branch`");
|
||||||
expect(result).not.toContain("[`feature-branch`]");
|
expect(result).not.toContain("[`feature-branch`]");
|
||||||
});
|
});
|
||||||
@@ -440,7 +442,7 @@ describe("updateForgejoCommentBody", () => {
|
|||||||
"\n[View branch](https://git.wxs.ro/wxs/myrepo/tree/feature-branch)",
|
"\n[View branch](https://git.wxs.ro/wxs/myrepo/tree/feature-branch)",
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = updateForgejoCommentBody(input);
|
const result = updateCommentBody(input);
|
||||||
expect(result).toContain(
|
expect(result).toContain(
|
||||||
"• [`feature-branch`](https://git.wxs.ro/wxs/myrepo/tree/feature-branch)",
|
"• [`feature-branch`](https://git.wxs.ro/wxs/myrepo/tree/feature-branch)",
|
||||||
);
|
);
|
||||||
@@ -453,7 +455,7 @@ describe("updateForgejoCommentBody", () => {
|
|||||||
"\n[View branch](https://git.wxs.ro/wxs/myrepo/tree/auto-detected-branch)",
|
"\n[View branch](https://git.wxs.ro/wxs/myrepo/tree/auto-detected-branch)",
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = updateForgejoCommentBody(input);
|
const result = updateCommentBody(input);
|
||||||
expect(result).toContain(
|
expect(result).toContain(
|
||||||
"• [`auto-detected-branch`](https://git.wxs.ro/wxs/myrepo/tree/auto-detected-branch)",
|
"• [`auto-detected-branch`](https://git.wxs.ro/wxs/myrepo/tree/auto-detected-branch)",
|
||||||
);
|
);
|
||||||
@@ -468,7 +470,7 @@ describe("updateForgejoCommentBody", () => {
|
|||||||
"\n[Create a PR](https://git.wxs.ro/wxs/myrepo/compare/main...feature)",
|
"\n[Create a PR](https://git.wxs.ro/wxs/myrepo/compare/main...feature)",
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = updateForgejoCommentBody(input);
|
const result = updateCommentBody(input);
|
||||||
expect(result).toContain(
|
expect(result).toContain(
|
||||||
"• [Create PR ➔](https://git.wxs.ro/wxs/myrepo/compare/main...feature)",
|
"• [Create PR ➔](https://git.wxs.ro/wxs/myrepo/compare/main...feature)",
|
||||||
);
|
);
|
||||||
@@ -481,7 +483,7 @@ describe("updateForgejoCommentBody", () => {
|
|||||||
"Some comment with [Create a PR](https://git.wxs.ro/wxs/myrepo/compare/main...fix)",
|
"Some comment with [Create a PR](https://git.wxs.ro/wxs/myrepo/compare/main...fix)",
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = updateForgejoCommentBody(input);
|
const result = updateCommentBody(input);
|
||||||
expect(result).toContain(
|
expect(result).toContain(
|
||||||
"• [Create PR ➔](https://git.wxs.ro/wxs/myrepo/compare/main...fix)",
|
"• [Create PR ➔](https://git.wxs.ro/wxs/myrepo/compare/main...fix)",
|
||||||
);
|
);
|
||||||
@@ -496,7 +498,7 @@ describe("updateForgejoCommentBody", () => {
|
|||||||
currentBody: `Done!\n\n[Create a PR](${forgejoUrl})`,
|
currentBody: `Done!\n\n[Create a PR](${forgejoUrl})`,
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = updateForgejoCommentBody(input);
|
const result = updateCommentBody(input);
|
||||||
expect(result).toContain(`• [Create PR ➔](${forgejoUrl})`);
|
expect(result).toContain(`• [Create PR ➔](${forgejoUrl})`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -509,7 +511,7 @@ describe("updateForgejoCommentBody", () => {
|
|||||||
triggerUsername: "testuser",
|
triggerUsername: "testuser",
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = updateForgejoCommentBody(input);
|
const result = updateCommentBody(input);
|
||||||
expect(result).toContain(
|
expect(result).toContain(
|
||||||
"**Claude finished @testuser's task in 1m 15s**",
|
"**Claude finished @testuser's task in 1m 15s**",
|
||||||
);
|
);
|
||||||
@@ -522,7 +524,7 @@ describe("updateForgejoCommentBody", () => {
|
|||||||
triggerUsername: "testuser",
|
triggerUsername: "testuser",
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = updateForgejoCommentBody(input);
|
const result = updateCommentBody(input);
|
||||||
expect(result).toContain("**Claude finished @testuser's task in 31s**");
|
expect(result).toContain("**Claude finished @testuser's task in 31s**");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -533,7 +535,7 @@ describe("updateForgejoCommentBody", () => {
|
|||||||
triggerUsername: "testuser",
|
triggerUsername: "testuser",
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = updateForgejoCommentBody(input);
|
const result = updateCommentBody(input);
|
||||||
expect(result).toContain("**Claude finished @testuser's task**");
|
expect(result).toContain("**Claude finished @testuser's task**");
|
||||||
expect(result).not.toContain(" in ");
|
expect(result).not.toContain(" in ");
|
||||||
});
|
});
|
||||||
@@ -553,7 +555,7 @@ describe("updateForgejoCommentBody", () => {
|
|||||||
triggerUsername: "trigger-user",
|
triggerUsername: "trigger-user",
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = updateForgejoCommentBody(input);
|
const result = updateCommentBody(input);
|
||||||
|
|
||||||
// Check header
|
// Check header
|
||||||
expect(result).toContain(
|
expect(result).toContain(
|
||||||
@@ -594,7 +596,7 @@ describe("updateForgejoCommentBody", () => {
|
|||||||
prLink: "",
|
prLink: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = updateForgejoCommentBody(input);
|
const result = updateCommentBody(input);
|
||||||
|
|
||||||
expect(result).toContain("Claude finished @claude's task in 1m 30s");
|
expect(result).toContain("Claude finished @claude's task in 1m 30s");
|
||||||
expect(result).toContain(`[View job](${forgejoBaseInput.jobUrl})`);
|
expect(result).toContain(`[View job](${forgejoBaseInput.jobUrl})`);
|
||||||
@@ -612,7 +614,7 @@ describe("updateForgejoCommentBody", () => {
|
|||||||
triggerUsername: "user",
|
triggerUsername: "user",
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = updateForgejoCommentBody(input);
|
const result = updateCommentBody(input);
|
||||||
expect(result).not.toContain("[View job run]");
|
expect(result).not.toContain("[View job run]");
|
||||||
expect(result).toContain("[View job]");
|
expect(result).toContain("[View job]");
|
||||||
});
|
});
|
||||||
@@ -626,7 +628,7 @@ describe("updateForgejoCommentBody", () => {
|
|||||||
triggerUsername: "user",
|
triggerUsername: "user",
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = updateForgejoCommentBody(input);
|
const result = updateCommentBody(input);
|
||||||
// Old link in body removed
|
// Old link in body removed
|
||||||
expect(result).not.toContain("[View branch]");
|
expect(result).not.toContain("[View branch]");
|
||||||
// New branch in header
|
// New branch in header
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { prepareMcpConfig } from "../src/mcp/install-mcp-server";
|
|||||||
import * as core from "@actions/core";
|
import * as core from "@actions/core";
|
||||||
import type { ParsedGitHubContext } from "../src/github/context";
|
import type { ParsedGitHubContext } from "../src/github/context";
|
||||||
import { CLAUDE_APP_BOT_ID, CLAUDE_BOT_LOGIN } from "../src/github/constants";
|
import { CLAUDE_APP_BOT_ID, CLAUDE_BOT_LOGIN } from "../src/github/constants";
|
||||||
|
import { _resetPlatformCache } from "../src/platform/detector";
|
||||||
|
|
||||||
describe("prepareMcpConfig", () => {
|
describe("prepareMcpConfig", () => {
|
||||||
let consoleInfoSpy: any;
|
let consoleInfoSpy: any;
|
||||||
@@ -321,6 +322,7 @@ describe("prepareMcpConfig", () => {
|
|||||||
let savedServerUrl: string | undefined;
|
let savedServerUrl: string | undefined;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
_resetPlatformCache();
|
||||||
savedPlatform = process.env.PLATFORM;
|
savedPlatform = process.env.PLATFORM;
|
||||||
savedForgejo = process.env.FORGEJO;
|
savedForgejo = process.env.FORGEJO;
|
||||||
savedServerUrl = process.env.GITHUB_SERVER_URL;
|
savedServerUrl = process.env.GITHUB_SERVER_URL;
|
||||||
@@ -344,6 +346,7 @@ describe("prepareMcpConfig", () => {
|
|||||||
} else {
|
} else {
|
||||||
delete process.env.GITHUB_SERVER_URL;
|
delete process.env.GITHUB_SERVER_URL;
|
||||||
}
|
}
|
||||||
|
_resetPlatformCache();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should skip GitHub public MCP server on Forgejo", async () => {
|
test("should skip GitHub public MCP server on Forgejo", async () => {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { describe, it, expect, beforeEach, afterEach } from "bun:test";
|
import { describe, it, expect, beforeEach, afterEach } from "bun:test";
|
||||||
import { detectPlatform } from "../src/platform/detector";
|
import { detectPlatform, _resetPlatformCache } from "../src/platform/detector";
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Helpers
|
// Helpers
|
||||||
@@ -40,11 +40,13 @@ function restoreEnv() {
|
|||||||
|
|
||||||
describe("M7: Platform routing for mode handlers", () => {
|
describe("M7: Platform routing for mode handlers", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
_resetPlatformCache();
|
||||||
saveEnv("PLATFORM", "FORGEJO", "GITHUB_SERVER_URL");
|
saveEnv("PLATFORM", "FORGEJO", "GITHUB_SERVER_URL");
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
restoreEnv();
|
restoreEnv();
|
||||||
|
_resetPlatformCache();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("detectPlatform() — used by tag mode and update-comment-link", () => {
|
describe("detectPlatform() — used by tag mode and update-comment-link", () => {
|
||||||
@@ -156,13 +158,13 @@ describe("M7: Forgejo PR compare URL format", () => {
|
|||||||
// update-comment-link: platform-aware final comment body
|
// update-comment-link: platform-aware final comment body
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
describe("M7: updateForgejoCommentBody vs updateCommentBody", () => {
|
describe("M7: unified updateCommentBody handles both platforms", () => {
|
||||||
it("Forgejo version strips Unicode spinner", async () => {
|
it("strips Unicode spinner (Forgejo format)", async () => {
|
||||||
const { updateForgejoCommentBody } = await import(
|
const { updateCommentBody } = await import(
|
||||||
"../src/forgejo/comment-logic"
|
"../src/github/operations/comment-logic"
|
||||||
);
|
);
|
||||||
|
|
||||||
const result = updateForgejoCommentBody({
|
const result = updateCommentBody({
|
||||||
currentBody:
|
currentBody:
|
||||||
"Claude Code is working… ⏳\n\nI'll analyze this and get back to you.\n\n[View job run](https://git.wxs.ro/owner/repo/actions/runs/123)",
|
"Claude Code is working… ⏳\n\nI'll analyze this and get back to you.\n\n[View job run](https://git.wxs.ro/owner/repo/actions/runs/123)",
|
||||||
actionFailed: false,
|
actionFailed: false,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import * as core from "@actions/core";
|
|||||||
import { checkWritePermissions } from "../src/github/validation/permissions";
|
import { checkWritePermissions } from "../src/github/validation/permissions";
|
||||||
import type { ParsedGitHubContext } from "../src/github/context";
|
import type { ParsedGitHubContext } from "../src/github/context";
|
||||||
import { CLAUDE_APP_BOT_ID, CLAUDE_BOT_LOGIN } from "../src/github/constants";
|
import { CLAUDE_APP_BOT_ID, CLAUDE_BOT_LOGIN } from "../src/github/constants";
|
||||||
|
import { _resetPlatformCache } from "../src/platform/detector";
|
||||||
|
|
||||||
describe("checkWritePermissions", () => {
|
describe("checkWritePermissions", () => {
|
||||||
let coreInfoSpy: any;
|
let coreInfoSpy: any;
|
||||||
@@ -310,6 +311,7 @@ describe("checkWritePermissions", () => {
|
|||||||
let savedServerUrl: string | undefined;
|
let savedServerUrl: string | undefined;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
_resetPlatformCache();
|
||||||
savedPlatform = process.env.PLATFORM;
|
savedPlatform = process.env.PLATFORM;
|
||||||
savedForgejo = process.env.FORGEJO;
|
savedForgejo = process.env.FORGEJO;
|
||||||
savedServerUrl = process.env.GITHUB_SERVER_URL;
|
savedServerUrl = process.env.GITHUB_SERVER_URL;
|
||||||
@@ -324,6 +326,7 @@ describe("checkWritePermissions", () => {
|
|||||||
if (savedServerUrl !== undefined)
|
if (savedServerUrl !== undefined)
|
||||||
process.env.GITHUB_SERVER_URL = savedServerUrl;
|
process.env.GITHUB_SERVER_URL = savedServerUrl;
|
||||||
else delete process.env.GITHUB_SERVER_URL;
|
else delete process.env.GITHUB_SERVER_URL;
|
||||||
|
_resetPlatformCache();
|
||||||
});
|
});
|
||||||
|
|
||||||
const createForgejoOctokit = (permissions: {
|
const createForgejoOctokit = (permissions: {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { detectPlatform } from "../src/platform/detector";
|
import { detectPlatform, _resetPlatformCache } from "../src/platform/detector";
|
||||||
import { describe, it, expect, beforeEach } from "bun:test";
|
import { describe, it, expect, beforeEach } from "bun:test";
|
||||||
|
|
||||||
describe("detectPlatform", () => {
|
describe("detectPlatform", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Reset relevant env vars before each test
|
// Reset memoized result and env vars before each test
|
||||||
|
_resetPlatformCache();
|
||||||
delete process.env.PLATFORM;
|
delete process.env.PLATFORM;
|
||||||
delete process.env.FORGEJO;
|
delete process.env.FORGEJO;
|
||||||
delete process.env.GITHUB_SERVER_URL;
|
delete process.env.GITHUB_SERVER_URL;
|
||||||
|
|||||||
Reference in new issue
Block a user