feat(FN-3868): add changeset for github tracking settings
Adds a changeset for the GitHub tracking settings feature (FN-3868), preparing it for the next release. The feature itself was implemented in earlier steps; this changeset marks it for publication to npm. Fusion-Task-Id: FN-3868
This commit is contained in:
@@ -69,6 +69,23 @@ describe("settings key parity", () => {
|
||||
expect(DEFAULT_PROJECT_SETTINGS.completionDocumentationMode).toBe("off");
|
||||
});
|
||||
|
||||
it("keeps github tracking keys in expected scopes with documented defaults", () => {
|
||||
expect(DEFAULT_PROJECT_SETTINGS.githubTrackingEnabledByDefault).toBe(false);
|
||||
expect(DEFAULT_PROJECT_SETTINGS.githubTrackingDefaultRepo).toBeUndefined();
|
||||
expect(DEFAULT_PROJECT_SETTINGS.githubAuthMode).toBe("gh-cli");
|
||||
expect(DEFAULT_PROJECT_SETTINGS.githubAuthToken).toBeUndefined();
|
||||
expect(DEFAULT_GLOBAL_SETTINGS.githubTrackingDefaultRepo).toBeUndefined();
|
||||
|
||||
expect(isProjectSettingsKey("githubTrackingEnabledByDefault")).toBe(true);
|
||||
expect(isGlobalSettingsKey("githubTrackingEnabledByDefault")).toBe(false);
|
||||
expect(isProjectSettingsKey("githubAuthMode")).toBe(true);
|
||||
expect(isGlobalSettingsKey("githubAuthMode")).toBe(false);
|
||||
expect(isProjectSettingsKey("githubAuthToken")).toBe(true);
|
||||
expect(isGlobalSettingsKey("githubAuthToken")).toBe(false);
|
||||
expect(isProjectSettingsKey("githubTrackingDefaultRepo")).toBe(true);
|
||||
expect(isGlobalSettingsKey("githubTrackingDefaultRepo")).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps remoteAccess scoped to global settings only", () => {
|
||||
const globalKeys = GLOBAL_SETTINGS_KEYS as readonly string[];
|
||||
const projectKeys = PROJECT_SETTINGS_KEYS as readonly string[];
|
||||
@@ -89,10 +106,10 @@ describe("settings key parity", () => {
|
||||
expect((DEFAULT_PROJECT_SETTINGS as Record<string, unknown>).experimentalFeatures).toBeUndefined();
|
||||
});
|
||||
|
||||
it("No key appears in both GLOBAL_SETTINGS_KEYS and PROJECT_SETTINGS_KEYS", () => {
|
||||
it("only intentional shared keys appear in both global and project scopes", () => {
|
||||
const projectKeySet = new Set(PROJECT_SETTINGS_KEYS as readonly string[]);
|
||||
const overlap = (GLOBAL_SETTINGS_KEYS as readonly string[]).filter((key) => projectKeySet.has(key));
|
||||
expect(overlap).toEqual([]);
|
||||
expect(overlap).toEqual(["githubTrackingDefaultRepo"]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
54
packages/core/src/__tests__/settings-validation.test.ts
Normal file
54
packages/core/src/__tests__/settings-validation.test.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
validateGithubAuthMode,
|
||||
validateGithubRepoSlug,
|
||||
validateUnavailableNodePolicy,
|
||||
} from "../settings-validation.js";
|
||||
|
||||
describe("settings-validation", () => {
|
||||
describe("validateUnavailableNodePolicy", () => {
|
||||
it("accepts supported policies", () => {
|
||||
expect(validateUnavailableNodePolicy("block")).toBe("block");
|
||||
expect(validateUnavailableNodePolicy("fallback-local")).toBe("fallback-local");
|
||||
});
|
||||
|
||||
it("returns undefined for invalid values", () => {
|
||||
expect(validateUnavailableNodePolicy("fallback")).toBeUndefined();
|
||||
expect(validateUnavailableNodePolicy(123)).toBeUndefined();
|
||||
expect(validateUnavailableNodePolicy(undefined)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("validateGithubAuthMode", () => {
|
||||
it("accepts supported auth modes", () => {
|
||||
expect(validateGithubAuthMode("gh-cli")).toBe("gh-cli");
|
||||
expect(validateGithubAuthMode("token")).toBe("token");
|
||||
});
|
||||
|
||||
it("returns undefined for invalid values", () => {
|
||||
expect(validateGithubAuthMode("oauth")).toBeUndefined();
|
||||
expect(validateGithubAuthMode(123)).toBeUndefined();
|
||||
expect(validateGithubAuthMode(undefined)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("validateGithubRepoSlug", () => {
|
||||
it("accepts valid owner/repo slugs", () => {
|
||||
expect(validateGithubRepoSlug("owner/repo")).toBe("owner/repo");
|
||||
expect(validateGithubRepoSlug("Owner.Name/repo_name-1")).toBe("Owner.Name/repo_name-1");
|
||||
});
|
||||
|
||||
it("treats empty strings as unset", () => {
|
||||
expect(validateGithubRepoSlug("")).toBeUndefined();
|
||||
expect(validateGithubRepoSlug(" ")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns undefined for malformed slugs and invalid types", () => {
|
||||
expect(validateGithubRepoSlug("owner")).toBeUndefined();
|
||||
expect(validateGithubRepoSlug("owner/repo/extra")).toBeUndefined();
|
||||
expect(validateGithubRepoSlug("owner repo/repo")).toBeUndefined();
|
||||
expect(validateGithubRepoSlug(42)).toBeUndefined();
|
||||
expect(validateGithubRepoSlug(undefined)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -52,6 +52,7 @@ export const DEFAULT_GLOBAL_SETTINGS = {
|
||||
updateCheckFrequency: "daily",
|
||||
autoReloadOnVersionChange: true,
|
||||
showGitHubStarButton: true,
|
||||
githubTrackingDefaultRepo: undefined,
|
||||
modelOnboardingComplete: undefined,
|
||||
useClaudeCli: undefined,
|
||||
useDroidCli: undefined,
|
||||
@@ -229,6 +230,10 @@ export const DEFAULT_PROJECT_SETTINGS = {
|
||||
autoUpdatePrStatus: false,
|
||||
githubCommentOnDone: false,
|
||||
githubCommentTemplate: undefined,
|
||||
githubTrackingEnabledByDefault: false,
|
||||
githubTrackingDefaultRepo: undefined,
|
||||
githubAuthMode: "gh-cli",
|
||||
githubAuthToken: undefined,
|
||||
autoBackupEnabled: false,
|
||||
autoBackupSchedule: "0 2 * * *",
|
||||
autoBackupRetention: 7,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { UnavailableNodePolicy } from "./types.js";
|
||||
import type { GithubAuthMode, UnavailableNodePolicy } from "./types.js";
|
||||
|
||||
const UNAVAILABLE_NODE_POLICIES: readonly UnavailableNodePolicy[] = ["block", "fallback-local"] as const;
|
||||
const GITHUB_AUTH_MODES: readonly GithubAuthMode[] = ["gh-cli", "token"] as const;
|
||||
const GITHUB_REPO_SLUG_PATTERN = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
|
||||
|
||||
/**
|
||||
* Validates a project unavailable-node routing policy value.
|
||||
@@ -18,3 +20,29 @@ export function validateUnavailableNodePolicy(value: unknown): UnavailableNodePo
|
||||
? (value as UnavailableNodePolicy)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
/** Returns a validated GitHub auth mode for project settings, otherwise undefined. */
|
||||
export function validateGithubAuthMode(value: unknown): GithubAuthMode | undefined {
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
return (GITHUB_AUTH_MODES as readonly string[]).includes(value) ? (value as GithubAuthMode) : undefined;
|
||||
}
|
||||
|
||||
/** Returns a validated owner/repo GitHub slug, otherwise undefined. Empty string is treated as unset. */
|
||||
export function validateGithubRepoSlug(value: unknown): string | undefined {
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
if (trimmed.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return GITHUB_REPO_SLUG_PATTERN.test(trimmed) ? trimmed : undefined;
|
||||
}
|
||||
|
||||
@@ -1616,6 +1616,9 @@ export interface GlobalSettings {
|
||||
* header. Defaults to true (visible). The button is also hidden once the
|
||||
* user has clicked it (tracked client-side in localStorage). */
|
||||
showGitHubStarButton?: boolean;
|
||||
/** Global fallback GitHub tracking repo in `owner/repo` format (FN-3868).
|
||||
* Used when a project has no githubTrackingDefaultRepo. */
|
||||
githubTrackingDefaultRepo?: string;
|
||||
/** Cadence for automatic update checks. The dashboard's `/update-check`
|
||||
* route uses this to decide whether to consult npm or return a cached
|
||||
* result.
|
||||
@@ -1849,6 +1852,9 @@ export interface RemoteAccessProjectSettings {
|
||||
lifecycle: RemoteAccessLifecycleConfig;
|
||||
}
|
||||
|
||||
/** GitHub authentication strategy used by project issue-tracking settings (FN-3868). */
|
||||
export type GithubAuthMode = "gh-cli" | "token";
|
||||
|
||||
/**
|
||||
* Project-level settings stored in `.fusion/config.json`.
|
||||
*
|
||||
@@ -2187,6 +2193,18 @@ export interface ProjectSettings {
|
||||
/** Optional template used for GitHub issue comments posted on task completion.
|
||||
* Supports `{taskId}` and `{taskTitle}` placeholders. */
|
||||
githubCommentTemplate?: string;
|
||||
/** When true, new tasks default GitHub tracking to enabled for this project (FN-3868).
|
||||
* Default: false. */
|
||||
githubTrackingEnabledByDefault?: boolean;
|
||||
/** Project default GitHub tracking repo in `owner/repo` format (FN-3868).
|
||||
* Falls back to global githubTrackingDefaultRepo when unset. */
|
||||
githubTrackingDefaultRepo?: string;
|
||||
/** GitHub auth strategy for issue-tracking API calls in this project (FN-3868).
|
||||
* Default: "gh-cli". */
|
||||
githubAuthMode?: GithubAuthMode;
|
||||
/** Personal access token used when githubAuthMode is "token" (FN-3868).
|
||||
* Stored as a plain settings string in this phase. */
|
||||
githubAuthToken?: string;
|
||||
/** When true, automatic database backups are enabled. Default: false. */
|
||||
autoBackupEnabled?: boolean;
|
||||
/** Cron expression for backup schedule. Default: "0 2 * * *" (daily at 2 AM). */
|
||||
|
||||
Reference in New Issue
Block a user