feat(macos): configuration window + curl-backed macOS updater (#71)

This commit is contained in:
2026-05-17 02:23:44 -07:00
committed by GitHub
parent 6ca5cede3e
commit e84674e3b5
100 changed files with 13890 additions and 235 deletions
+200
View File
@@ -0,0 +1,200 @@
import {
applyEdits,
modify,
parse as parseJsonc,
type FormattingOptions,
type ParseError,
} from 'jsonc-parser';
import type { ConfigValidationWarning, RawConfig, ResolvedConfig } from '../../types/config';
import type {
ConfigSettingsField,
ConfigSettingsPatchOperation,
ConfigSettingsSnapshot,
} from '../../types/settings';
import { resolveConfig } from '../resolve';
import { getConfigValueAtPath } from './registry';
const JSONC_FORMATTING_OPTIONS: FormattingOptions = {
insertSpaces: true,
tabSize: 2,
eol: '\n',
};
export type ConfigSettingsPatchApplyResult =
| {
ok: true;
content: string;
rawConfig: RawConfig;
resolvedConfig: ResolvedConfig;
warnings: ConfigValidationWarning[];
}
| {
ok: false;
content: string;
warnings: ConfigValidationWarning[];
error: string;
};
interface ApplyConfigSettingsPatchOptions {
content: string;
operations: ConfigSettingsPatchOperation[];
previousWarnings: ConfigValidationWarning[];
}
interface BuildConfigSettingsSnapshotOptions {
configPath: string;
rawConfig: RawConfig;
resolvedConfig: ResolvedConfig;
warnings: ConfigValidationWarning[];
fields: ConfigSettingsField[];
}
function pathToSegments(path: string): string[] {
return path.split('.').filter(Boolean);
}
function isRecord(value: unknown): value is Record<string, unknown> {
return value !== null && typeof value === 'object' && !Array.isArray(value);
}
function pathStartsWith(path: string, prefix: string): boolean {
return path === prefix || path.startsWith(`${prefix}.`);
}
function warningBelongsToModifiedPath(
warning: ConfigValidationWarning,
operation: ConfigSettingsPatchOperation,
): boolean {
return (
pathStartsWith(warning.path, operation.path) || pathStartsWith(operation.path, warning.path)
);
}
function warningIdentity(warning: ConfigValidationWarning): string {
return `${warning.path}\n${JSON.stringify(warning.value)}\n${warning.message}`;
}
function parseRawConfig(content: string): RawConfig {
const errors: ParseError[] = [];
const parsed = parseJsonc(content || '{}', errors, {
allowTrailingComma: true,
disallowComments: false,
});
if (errors.length > 0) {
throw new Error(`Invalid JSONC (${errors[0]?.error ?? 'unknown'})`);
}
return isRecord(parsed) ? (parsed as RawConfig) : {};
}
function normalizeContent(content: string): string {
return content.trim().length > 0 ? content : '{}\n';
}
function applySingleOperation(content: string, operation: ConfigSettingsPatchOperation): string {
const edits = modify(
content,
pathToSegments(operation.path),
operation.op === 'reset' ? undefined : operation.value,
{
formattingOptions: JSONC_FORMATTING_OPTIONS,
getInsertionIndex: (properties) => properties.length,
},
);
return applyEdits(content, edits);
}
function collectModifiedWarnings(
warnings: ConfigValidationWarning[],
operations: ConfigSettingsPatchOperation[],
previousWarnings: ConfigValidationWarning[],
): ConfigValidationWarning[] {
const previous = new Set(previousWarnings.map(warningIdentity));
return warnings.filter((warning) => {
if (!operations.some((operation) => warningBelongsToModifiedPath(warning, operation))) {
return false;
}
return !previous.has(warningIdentity(warning));
});
}
export function applyConfigSettingsPatchToContent(
options: ApplyConfigSettingsPatchOptions,
): ConfigSettingsPatchApplyResult {
let content = normalizeContent(options.content);
try {
parseRawConfig(content);
} catch (error) {
return {
ok: false,
content,
warnings: [],
error: error instanceof Error ? error.message : 'Invalid JSONC.',
};
}
try {
for (const operation of options.operations) {
content = applySingleOperation(content, operation);
}
const rawConfig = parseRawConfig(content);
const { resolved, warnings } = resolveConfig(rawConfig);
const modifiedWarnings = collectModifiedWarnings(
warnings,
options.operations,
options.previousWarnings,
);
if (modifiedWarnings.length > 0) {
return {
ok: false,
content,
warnings: modifiedWarnings,
error: 'One or more modified settings failed validation.',
};
}
return {
ok: true,
content,
rawConfig,
resolvedConfig: resolved,
warnings,
};
} catch (error) {
return {
ok: false,
content,
warnings: [],
error: error instanceof Error ? error.message : 'Failed to update config content.',
};
}
}
export function buildConfigSettingsSnapshot(
options: BuildConfigSettingsSnapshotOptions,
): ConfigSettingsSnapshot {
const values: Record<string, unknown> = {};
for (const field of options.fields) {
const rawValue = getConfigValueAtPath(options.rawConfig, field.configPath);
const resolvedValue = getConfigValueAtPath(options.resolvedConfig, field.configPath);
if (field.secret) {
values[field.configPath] = {
configured:
(typeof rawValue === 'string' && rawValue.length > 0) ||
(typeof resolvedValue === 'string' && resolvedValue.length > 0),
};
continue;
}
values[field.configPath] = structuredClone(rawValue !== undefined ? rawValue : resolvedValue);
}
return {
configPath: options.configPath,
fields: options.fields,
values,
warnings: options.warnings,
};
}