import * as plugins from "./plugins.js"; import { rename, writeFile } from "fs/promises"; export const CLI_NAMESPACE = "@git.zone/cli"; const isPlainObject = (value: unknown): value is Record => { return typeof value === "object" && value !== null && !Array.isArray(value); }; export const getSmartconfigPath = (cwd: string = process.cwd()): string => { return plugins.path.join(cwd, ".smartconfig.json"); }; export const readSmartconfigFile = async ( cwd: string = process.cwd(), ): Promise> => { const smartconfigPath = getSmartconfigPath(cwd); if (!(await plugins.smartfs.file(smartconfigPath).exists())) { return {}; } const content = (await plugins.smartfs .file(smartconfigPath) .encoding("utf8") .read()) as string; if (content.trim() === "") { return {}; } return JSON.parse(content); }; export const writeSmartconfigFile = async ( data: Record, cwd: string = process.cwd(), ): Promise => { const smartconfigPath = getSmartconfigPath(cwd); const tempPath = `${smartconfigPath}.tmp-${Date.now()}`; const content = JSON.stringify(data, null, 2); await writeFile(tempPath, content, "utf8"); await rename(tempPath, smartconfigPath); }; export const normalizeCliConfigPath = (configPath: string): string => { const trimmedPath = configPath.trim(); if (!trimmedPath || trimmedPath === CLI_NAMESPACE) { return ""; } if (trimmedPath.startsWith(`${CLI_NAMESPACE}.`)) { return trimmedPath.slice(`${CLI_NAMESPACE}.`.length); } return trimmedPath; }; export const getCliConfigPathSegments = (configPath: string): string[] => { const normalizedPath = normalizeCliConfigPath(configPath); if (!normalizedPath) { return []; } return normalizedPath .split(".") .map((segment) => segment.trim()) .filter(Boolean); }; export const getCliNamespaceConfig = ( smartconfigData: Record, ): Record => { const cliConfig = smartconfigData[CLI_NAMESPACE]; if (isPlainObject(cliConfig)) { return cliConfig; } return {}; }; export const getCliConfigValueFromData = ( smartconfigData: Record, configPath: string, ): any => { const segments = getCliConfigPathSegments(configPath); let currentValue: any = getCliNamespaceConfig(smartconfigData); for (const segment of segments) { if (!isPlainObject(currentValue) && !Array.isArray(currentValue)) { return undefined; } currentValue = (currentValue as any)?.[segment]; } return currentValue; }; export const getCliConfigValue = async ( configPath: string, defaultValue: T, cwd: string = process.cwd(), ): Promise => { const smartconfigData = await readSmartconfigFile(cwd); const configValue = getCliConfigValueFromData(smartconfigData, configPath); if (configValue === undefined) { return defaultValue; } if (isPlainObject(defaultValue) && isPlainObject(configValue)) { return { ...defaultValue, ...configValue, } as T; } return configValue as T; }; export const setCliConfigValueInData = ( smartconfigData: Record, configPath: string, value: any, ): Record => { const segments = getCliConfigPathSegments(configPath); if (!isPlainObject(smartconfigData[CLI_NAMESPACE])) { smartconfigData[CLI_NAMESPACE] = {}; } if (segments.length === 0) { smartconfigData[CLI_NAMESPACE] = value; return smartconfigData; } let currentValue = smartconfigData[CLI_NAMESPACE]; for (const segment of segments.slice(0, -1)) { if (!isPlainObject(currentValue[segment])) { currentValue[segment] = {}; } currentValue = currentValue[segment]; } currentValue[segments[segments.length - 1]] = value; return smartconfigData; }; export const unsetCliConfigValueInData = ( smartconfigData: Record, configPath: string, ): boolean => { const segments = getCliConfigPathSegments(configPath); if (segments.length === 0) { if (smartconfigData[CLI_NAMESPACE] !== undefined) { delete smartconfigData[CLI_NAMESPACE]; return true; } return false; } const parentSegments = segments.slice(0, -1); let currentValue: any = getCliNamespaceConfig(smartconfigData); const objectPath: Array> = [currentValue]; for (const segment of parentSegments) { if (!isPlainObject(currentValue[segment])) { return false; } currentValue = currentValue[segment]; objectPath.push(currentValue); } const lastSegment = segments[segments.length - 1]; if (!(lastSegment in currentValue)) { return false; } delete currentValue[lastSegment]; for (let i = objectPath.length - 1; i >= 1; i--) { if (Object.keys(objectPath[i]).length > 0) { break; } const parentObject = objectPath[i - 1]; const parentKey = parentSegments[i - 1]; delete parentObject[parentKey]; } if (Object.keys(getCliNamespaceConfig(smartconfigData)).length === 0) { delete smartconfigData[CLI_NAMESPACE]; } return true; };