feat(FN-3921): align dashboard githubTracking with core REPO_OVERRIDE_RE ex

Restored the `REPO_OVERRIDE_RE` regex export from core and aligned the dashboard's `githubTracking` component to consume it, fixing the export contract that was broken in FN-3921. Added tests in both core and dashboard packages to cover the regex behavior.

Fusion-Task-Id: FN-3921
This commit is contained in:
Fusion
2026-05-10 05:25:26 -07:00
committed by gsxdsm
parent 860d18382d
commit 0fa5af3cfe
6 changed files with 21 additions and 22 deletions

View File

@@ -1,12 +1,16 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { REPO_OVERRIDE_RE, isValidRepoSlug, parseRepoSlug, resolveTaskGithubTracking } from "../github-tracking.js"; import { isValidRepoSlug, parseRepoSlug, resolveTaskGithubTracking } from "../github-tracking.js";
import { REPO_OVERRIDE_RE } from "../types.js";
describe("REPO_OVERRIDE_RE", () => { describe("REPO_OVERRIDE_RE", () => {
it("matches valid owner/repo overrides", () => { it("matches owner/repo slugs and rejects malformed values", () => {
expect(REPO_OVERRIDE_RE.test("owner/repo")).toBe(true); expect(REPO_OVERRIDE_RE.test("owner/repo")).toBe(true);
expect(REPO_OVERRIDE_RE.test("org.name/repo_name-1")).toBe(true); expect(REPO_OVERRIDE_RE.test("org.name/repo_name-1")).toBe(true);
expect(REPO_OVERRIDE_RE.test("owner")).toBe(false); expect(REPO_OVERRIDE_RE.test("owner")).toBe(false);
expect(REPO_OVERRIDE_RE.test("owner/repo/extra")).toBe(false);
expect(REPO_OVERRIDE_RE.test("owner repo/repo")).toBe(false);
expect(REPO_OVERRIDE_RE.test(" owner/repo ")).toBe(false);
}); });
}); });

View File

@@ -1,6 +1,4 @@
import type { GlobalSettings, ProjectSettings, Task } from "./types.js"; import { REPO_OVERRIDE_RE, type GlobalSettings, type ProjectSettings, type Task } from "./types.js";
export const REPO_OVERRIDE_RE = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
export interface RepoSlug { export interface RepoSlug {
owner: string; owner: string;
@@ -16,14 +14,10 @@ export interface ResolvedTaskGithubTracking {
}; };
} }
export const REPO_OVERRIDE_RE = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
function parseRepoSlugCandidate(input: unknown): RepoSlug | null { function parseRepoSlugCandidate(input: unknown): RepoSlug | null {
if (typeof input !== "string") return null; if (typeof input !== "string") return null;
const trimmed = input.trim(); const trimmed = input.trim();
if (!trimmed) return null; if (!trimmed || !REPO_OVERRIDE_RE.test(trimmed)) return null;
if (!REPO_OVERRIDE_RE.test(trimmed)) return null;
const [owner, repo] = trimmed.split("/"); const [owner, repo] = trimmed.split("/");
return { owner, repo }; return { owner, repo };

View File

@@ -1,4 +1,4 @@
export { COLUMNS, DEFAULT_COLUMN, isColumn, normalizeColumn, COLUMN_LABELS, COLUMN_DESCRIPTIONS, VALID_TRANSITIONS, DEFAULT_SETTINGS, DEFAULT_GLOBAL_SETTINGS, DEFAULT_PROJECT_SETTINGS, GLOBAL_SETTINGS_KEYS, PROJECT_SETTINGS_KEYS, isGlobalSettingsKey, isProjectSettingsKey, THINKING_LEVELS, THEME_MODES, COLOR_THEMES, WORKFLOW_STEP_TEMPLATES, AGENT_PERMISSIONS, PERMANENT_AGENT_ACTION_CATEGORIES, AGENT_PERMISSION_POLICY_ACTION_CATEGORIES, AGENT_PERMISSION_POLICY_PRESET_IDS, LEGACY_AGENT_PERMISSION_POLICY_ACTION_CATEGORY_ALIASES, APPROVAL_REQUEST_STATUSES, APPROVAL_REQUEST_AUDIT_EVENT_TYPES, normalizeApprovalRequestActionCategory, isValidApprovalRequestTransition, agentToConfigSnapshot, diffConfigSnapshots, isEphemeralAgent, hasAgentIdentity, CheckoutConflictError, DEFAULT_HEARTBEAT_PROCEDURE_PATH, getDefaultHeartbeatProcedurePath, EXECUTION_MODES, DEFAULT_EXECUTION_MODE, TASK_PRIORITIES, DEFAULT_TASK_PRIORITY, DASHBOARD_USER_ID, normalizeMessageParticipant, validateMessageMetadata, validateDockerNodeConfig, sanitizeDockerNodeConfigForResponse, normalizeMergeConflictStrategy, buildResearchDocumentKey, SHARED_STATE_SNAPSHOT_VERSION } from "./types.js"; export { COLUMNS, DEFAULT_COLUMN, isColumn, normalizeColumn, COLUMN_LABELS, COLUMN_DESCRIPTIONS, VALID_TRANSITIONS, DEFAULT_SETTINGS, DEFAULT_GLOBAL_SETTINGS, DEFAULT_PROJECT_SETTINGS, GLOBAL_SETTINGS_KEYS, PROJECT_SETTINGS_KEYS, isGlobalSettingsKey, isProjectSettingsKey, THINKING_LEVELS, THEME_MODES, COLOR_THEMES, WORKFLOW_STEP_TEMPLATES, AGENT_PERMISSIONS, PERMANENT_AGENT_ACTION_CATEGORIES, AGENT_PERMISSION_POLICY_ACTION_CATEGORIES, AGENT_PERMISSION_POLICY_PRESET_IDS, LEGACY_AGENT_PERMISSION_POLICY_ACTION_CATEGORY_ALIASES, APPROVAL_REQUEST_STATUSES, APPROVAL_REQUEST_AUDIT_EVENT_TYPES, normalizeApprovalRequestActionCategory, isValidApprovalRequestTransition, agentToConfigSnapshot, diffConfigSnapshots, isEphemeralAgent, hasAgentIdentity, CheckoutConflictError, DEFAULT_HEARTBEAT_PROCEDURE_PATH, getDefaultHeartbeatProcedurePath, EXECUTION_MODES, DEFAULT_EXECUTION_MODE, TASK_PRIORITIES, DEFAULT_TASK_PRIORITY, DASHBOARD_USER_ID, normalizeMessageParticipant, validateMessageMetadata, validateDockerNodeConfig, sanitizeDockerNodeConfigForResponse, normalizeMergeConflictStrategy, buildResearchDocumentKey, REPO_OVERRIDE_RE, SHARED_STATE_SNAPSHOT_VERSION } from "./types.js";
export type { Column, IssueInfo, IssueState, TaskSourceIssue, PrInfo, PrStatus, Task, TaskTokenUsage, TaskAttachment, TaskComment, TaskCommentInput, TaskDocument, TaskDocumentRevision, TaskDocumentCreateInput, TaskDocumentWithTask, TaskCreateInput, MeshReplicatedTaskCreatePayload, MeshReplicatedTaskApplyResult, TaskSource, SourceType, TaskDetail, InboxTask, TodoList, TodoItem, TodoListCreateInput, TodoListUpdateInput, TodoItemCreateInput, TodoItemUpdateInput, TodoListWithItems, AgentLogEntry, AgentLogType, AgentRole, BoardConfig, DistributedTaskIdReserveInput, DistributedTaskIdReserveResult, DistributedTaskIdCommitInput, DistributedTaskIdCommitResult, DistributedTaskIdAbortInput, DistributedTaskIdAbortResult, DistributedTaskIdStateInput, DistributedTaskIdStateResult, AutostashOutcome, MergeDetails, MergeResult, MergeConflictStrategy, CanonicalMergeConflictStrategy, Settings, GlobalSettings, ProjectSettings, WebSearchBackend, ResearchEnabledSources, ResearchGlobalDefaults, ResearchProjectLimits, ResearchProjectSettings, EvalFollowUpPolicy, EvalProjectSettings, ResolvedEvalSettings, SettingsScope, DaemonTokenSettings, TaskStep, StepStatus, TaskLogEntry, RunMutationContext, ActivityLogEntry, ActivityEventType, ThinkingLevel, ThemeMode, ColorTheme, ExecutionMode, TaskPriority, UnavailableNodePolicy, PlanningQuestion, PlanningSummary, PlanningResponse, PlanningQuestionType, ArchivedTaskEntry, BatchStatusRequest, BatchStatusResponse, BatchStatusEntry, BatchStatusResult, ModelPreset, WorkflowStep, WorkflowStepMode, WorkflowStepPhase, WorkflowStepInput, WorkflowStepResult, WorkflowStepTemplate, Agent, OrgTreeNode, AgentState, AgentDetail, AgentCreateInput, AgentUpdateInput, AgentApiKey, AgentApiKeyCreateResult, AgentCapability, AgentPromptTemplate, AgentPromptsConfig, AgentPermission, PermanentAgentActionCategory, PermanentAgentSensitiveActionCategory, PermanentAgentGatingContext, AgentPermissionPolicy, AgentPermissionPolicyRules, AgentPermissionPolicyActionCategory, LegacyAgentPermissionPolicyActionCategory, ApprovalRequestActionCategoryInput, AgentPermissionPolicyDisposition, AgentPermissionPolicyPresetId, ApprovalRequestStatus, ApprovalRequestAuditEventType, ApprovalRequestActorSnapshot, ApprovalRequestTargetAction, ApprovalRequestAuditEvent, ApprovalRequest, ApprovalRequestCreateInput, ApprovalRequestDecisionInput, ApprovalRequestCompletionInput, ApprovalRequestListInput, TaskAssignSource, AgentAccessState, AgentHeartbeatConfig, AgentBudgetConfig, AgentBudgetStatus, InstructionsBundleConfig, MessageResponseMode, AgentHeartbeatEvent, AgentHeartbeatRun, BlockedStateSnapshot, HeartbeatInvocationSource, AgentTaskSession, AgentRating, AgentRatingSummary, AgentRatingInput, AgentConfigSnapshot, RevisionFieldDiff, AgentConfigRevision, AgentStats, ReflectionTrigger, ReflectionMetrics, AgentReflection, AgentPerformanceSummary, NtfyNotificationEvent, NotificationEvent, NotificationPayload, NotificationProviderConfig, CustomProvider, SteeringComment, ParticipantType, MessageType, Message, MessageCreateInput, MessageFilter, MessageMetadata, MessageReplyReference, Mailbox, CheckoutLease, RunAuditDomain, RunAuditEvent, RunAuditEventInput, RunAuditEventFilter } from "./types.js"; export type { Column, IssueInfo, IssueState, TaskSourceIssue, PrInfo, PrStatus, Task, TaskTokenUsage, TaskAttachment, TaskComment, TaskCommentInput, TaskDocument, TaskDocumentRevision, TaskDocumentCreateInput, TaskDocumentWithTask, TaskCreateInput, MeshReplicatedTaskCreatePayload, MeshReplicatedTaskApplyResult, TaskSource, SourceType, TaskDetail, InboxTask, TodoList, TodoItem, TodoListCreateInput, TodoListUpdateInput, TodoItemCreateInput, TodoItemUpdateInput, TodoListWithItems, AgentLogEntry, AgentLogType, AgentRole, BoardConfig, DistributedTaskIdReserveInput, DistributedTaskIdReserveResult, DistributedTaskIdCommitInput, DistributedTaskIdCommitResult, DistributedTaskIdAbortInput, DistributedTaskIdAbortResult, DistributedTaskIdStateInput, DistributedTaskIdStateResult, AutostashOutcome, MergeDetails, MergeResult, MergeConflictStrategy, CanonicalMergeConflictStrategy, Settings, GlobalSettings, ProjectSettings, WebSearchBackend, ResearchEnabledSources, ResearchGlobalDefaults, ResearchProjectLimits, ResearchProjectSettings, EvalFollowUpPolicy, EvalProjectSettings, ResolvedEvalSettings, SettingsScope, DaemonTokenSettings, TaskStep, StepStatus, TaskLogEntry, RunMutationContext, ActivityLogEntry, ActivityEventType, ThinkingLevel, ThemeMode, ColorTheme, ExecutionMode, TaskPriority, UnavailableNodePolicy, PlanningQuestion, PlanningSummary, PlanningResponse, PlanningQuestionType, ArchivedTaskEntry, BatchStatusRequest, BatchStatusResponse, BatchStatusEntry, BatchStatusResult, ModelPreset, WorkflowStep, WorkflowStepMode, WorkflowStepPhase, WorkflowStepInput, WorkflowStepResult, WorkflowStepTemplate, Agent, OrgTreeNode, AgentState, AgentDetail, AgentCreateInput, AgentUpdateInput, AgentApiKey, AgentApiKeyCreateResult, AgentCapability, AgentPromptTemplate, AgentPromptsConfig, AgentPermission, PermanentAgentActionCategory, PermanentAgentSensitiveActionCategory, PermanentAgentGatingContext, AgentPermissionPolicy, AgentPermissionPolicyRules, AgentPermissionPolicyActionCategory, LegacyAgentPermissionPolicyActionCategory, ApprovalRequestActionCategoryInput, AgentPermissionPolicyDisposition, AgentPermissionPolicyPresetId, ApprovalRequestStatus, ApprovalRequestAuditEventType, ApprovalRequestActorSnapshot, ApprovalRequestTargetAction, ApprovalRequestAuditEvent, ApprovalRequest, ApprovalRequestCreateInput, ApprovalRequestDecisionInput, ApprovalRequestCompletionInput, ApprovalRequestListInput, TaskAssignSource, AgentAccessState, AgentHeartbeatConfig, AgentBudgetConfig, AgentBudgetStatus, InstructionsBundleConfig, MessageResponseMode, AgentHeartbeatEvent, AgentHeartbeatRun, BlockedStateSnapshot, HeartbeatInvocationSource, AgentTaskSession, AgentRating, AgentRatingSummary, AgentRatingInput, AgentConfigSnapshot, RevisionFieldDiff, AgentConfigRevision, AgentStats, ReflectionTrigger, ReflectionMetrics, AgentReflection, AgentPerformanceSummary, NtfyNotificationEvent, NotificationEvent, NotificationPayload, NotificationProviderConfig, CustomProvider, SteeringComment, ParticipantType, MessageType, Message, MessageCreateInput, MessageFilter, MessageMetadata, MessageReplyReference, Mailbox, CheckoutLease, RunAuditDomain, RunAuditEvent, RunAuditEventInput, RunAuditEventFilter } from "./types.js";
export { AGENT_VALID_TRANSITIONS } from "./types.js"; export { AGENT_VALID_TRANSITIONS } from "./types.js";
export type { TaskReviewData, TaskReviewSummary, TaskReviewItem } from "./types.js"; export type { TaskReviewData, TaskReviewSummary, TaskReviewItem } from "./types.js";
@@ -109,7 +109,6 @@ export {
type GhError, type GhError,
} from "./gh-cli.js"; } from "./gh-cli.js";
export { export {
REPO_OVERRIDE_RE,
parseRepoSlug, parseRepoSlug,
isValidRepoSlug, isValidRepoSlug,
resolveTaskGithubTracking, resolveTaskGithubTracking,

View File

@@ -961,6 +961,9 @@ export interface TaskDocumentWithTask extends TaskDocument {
export const DOCUMENT_KEY_RE = /^[a-zA-Z0-9_-]{1,64}$/; export const DOCUMENT_KEY_RE = /^[a-zA-Z0-9_-]{1,64}$/;
/** Shared GitHub owner/repo slug validation for repo override inputs. */
export const REPO_OVERRIDE_RE = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
export function validateDocumentKey(key: string): void { export function validateDocumentKey(key: string): void {
if (!DOCUMENT_KEY_RE.test(key)) { if (!DOCUMENT_KEY_RE.test(key)) {
throw new Error( throw new Error(

View File

@@ -1,13 +1,12 @@
import { describe, expect, it, vi } from "vitest"; import { REPO_OVERRIDE_RE as CORE_REPO_OVERRIDE_RE } from "@fusion/core";
import { describe, expect, it } from "vitest";
vi.mock("@fusion/core", () => ({ import { REPO_OVERRIDE_RE, resolveEffectiveGithubRepoDefault } from "../githubTracking";
REPO_OVERRIDE_RE: /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/,
}));
import { REPO_OVERRIDE_RE } from "@fusion/core";
import { resolveEffectiveGithubRepoDefault } from "../githubTracking";
describe("githubTracking helper", () => { describe("githubTracking helper", () => {
it("reuses the shared core regex export", () => {
expect(REPO_OVERRIDE_RE).toBe(CORE_REPO_OVERRIDE_RE);
});
it("accepts valid owner/repo values and rejects malformed values", () => { it("accepts valid owner/repo values and rejects malformed values", () => {
expect(REPO_OVERRIDE_RE.test("owner/repo")).toBe(true); expect(REPO_OVERRIDE_RE.test("owner/repo")).toBe(true);
expect(REPO_OVERRIDE_RE.test("org.name/repo_name-1")).toBe(true); expect(REPO_OVERRIDE_RE.test("org.name/repo_name-1")).toBe(true);

View File

@@ -1,6 +1,6 @@
import type { GlobalSettings, ProjectSettings } from "@fusion/core"; import { REPO_OVERRIDE_RE, type GlobalSettings, type ProjectSettings } from "@fusion/core";
export const REPO_OVERRIDE_RE = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/; export { REPO_OVERRIDE_RE };
function normalizeRepoValue(value: string | null | undefined): string { function normalizeRepoValue(value: string | null | undefined): string {
const trimmed = value?.trim() ?? ""; const trimmed = value?.trim() ?? "";