repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
extensions/intellij/src/pi-ext-settings.ts
Raw// Generated by scripts/codegen.mjs. DO NOT EDIT.
// ============================================================================
// THIS FILE IS GENERATED. DO NOT EDIT IT DIRECTLY.
// Instead, edit scripts/codegen/templates/pi-ext-settings.ts.
// Then run: mise run //:codegen
// ============================================================================
import {
type ExtensionAPI,
type ExtensionContext,
SettingsManager,
} from "@earendil-works/pi-coding-agent";
/**
* Extension-owned setting declaration.
* Resolution order per key: flag, env, trusted project settings, user settings, default.
* When project and user values are both plain objects, they deep-merge with project
* winning per key; arrays and scalars replace.
*/
export interface SettingDeclaration<T> {
/** Dotted path inside Pi settings.json, for example `nushell.enable`. */
key: string;
/**
* Convert a raw flag, env, or settings value.
* Return undefined or throw an Error to reject it; thrown messages are reported.
*/
parse: (raw: unknown, source: SettingSource) => T | undefined;
default: T;
/** Pi CLI flag. Pi flags are boolean or string; boolean flags can only express true. */
flag?: { name: string; type: "boolean" | "string"; description: string };
/** Namespaced environment variable. An empty value is present, not missing. */
env?: string;
}
export type SettingSource = "flag" | "env" | "project" | "user" | "default";
/** Invalid input never falls through to a lower-priority source. */
export type SettingResult<T> =
| { ok: true; value: T; source: SettingSource }
| { ok: false; source: SettingSource; error: string };
const MISSING = Symbol("missing");
/** Boolean parser: booleans anywhere; `true|false|1|0` strings from flags or env only. */
export function parseBooleanSetting(
raw: unknown,
source: SettingSource,
): boolean | undefined {
if (typeof raw === "boolean") return raw;
if (source !== "flag" && source !== "env") return undefined;
if (raw === "true" || raw === "1") return true;
if (raw === "false" || raw === "0") return false;
return undefined;
}
/** Largest delay Node timers and execFile timeouts accept; larger values fire after 1 ms. */
export const MAX_TIMER_MS = 2_147_483_647;
/** Finite number parser: numbers anywhere; numeric strings from flags or env only. */
export function parseNumberSetting(
raw: unknown,
source: SettingSource,
): number | undefined {
const value =
typeof raw === "number"
? raw
: typeof raw === "string" &&
(source === "flag" || source === "env") &&
raw.trim() !== ""
? Number(raw)
: Number.NaN;
return Number.isFinite(value) ? value : undefined;
}
/** Register the declared flag without a default, keeping omission distinct from values. */
export function registerSettingFlag<T>(
pi: Pick<ExtensionAPI, "registerFlag">,
declaration: SettingDeclaration<T>,
): void {
const { flag } = declaration;
if (flag)
pi.registerFlag(flag.name, {
description: flag.description,
type: flag.type,
});
}
function isPlainObject(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function deepMerge(
base: Record<string, unknown>,
override: Record<string, unknown>,
): Record<string, unknown> {
const merged: Record<string, unknown> = { ...base };
for (const [key, value] of Object.entries(override)) {
const current = Object.hasOwn(merged, key) ? merged[key] : undefined;
// defineProperty keeps JSON keys such as __proto__ as own data properties.
Object.defineProperty(merged, key, {
value:
isPlainObject(current) && isPlainObject(value)
? deepMerge(current, value)
: value,
enumerable: true,
writable: true,
configurable: true,
});
}
return merged;
}
function settingAt(settings: unknown, key: string): unknown {
let value = settings;
for (const part of key.split(".")) {
if (
typeof value !== "object" ||
value === null ||
Array.isArray(value) ||
!Object.hasOwn(value, part)
)
return MISSING;
value = (value as Record<string, unknown>)[part];
}
return value;
}
/** Resolve one setting when both flags and project trust are available. */
export function resolveSetting<T>(
pi: Pick<ExtensionAPI, "getFlag">,
ctx: Pick<ExtensionContext, "cwd" | "isProjectTrusted">,
declaration: SettingDeclaration<T>,
): SettingResult<T> {
const { key, flag, env } = declaration;
const candidates: [SettingSource, string, () => unknown][] = [];
if (flag)
candidates.push([
"flag",
`flag --${flag.name}`,
() => pi.getFlag(flag.name) ?? MISSING,
]);
if (env)
candidates.push([
"env",
`environment variable ${env}`,
() => (Object.hasOwn(process.env, env) ? process.env[env] : MISSING),
]);
let settings: SettingsManager | undefined;
const load = () => {
settings ??= SettingsManager.create(ctx.cwd, undefined, {
projectTrusted: ctx.isProjectTrusted(),
});
return settings;
};
candidates.push(
[
"project",
"project settings",
() => settingAt(load().getProjectSettings(), key),
],
["user", "user settings", () => settingAt(load().getGlobalSettings(), key)],
);
for (const [index, [source, label, read]] of candidates.entries()) {
let raw = read();
if (raw === MISSING) continue;
let from = label;
if (source === "project") {
const user = candidates[index + 1]?.[2]();
if (isPlainObject(raw) && isPlainObject(user)) {
raw = deepMerge(user, raw);
from = "merged project and user settings";
}
}
let value: T | undefined;
try {
value = declaration.parse(raw, source);
} catch (error) {
if (!(error instanceof Error)) throw error;
return {
ok: false,
source,
error: `Invalid ${key} from ${from}: ${error.message}`,
};
}
if (value === undefined)
return { ok: false, source, error: `Invalid ${key} from ${from}` };
return { ok: true, value, source };
}
return { ok: true, value: declaration.default, source: "default" };
}