@alexgorbatchev/dotfiles 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +397 -0
- package/cli-fj2hdbnx.js +5 -0
- package/cli-fj2hdbnx.js.map +9 -0
- package/cli-w822cqdk.js +4 -0
- package/cli-w822cqdk.js.map +10 -0
- package/cli.js +449 -0
- package/cli.js.map +283 -0
- package/dashboard-0ebz5sqb.js +159 -0
- package/dashboard-0ebz5sqb.js.map +102 -0
- package/dashboard-3axqywva.css +1 -0
- package/dashboard.js +13 -0
- package/package.json +63 -0
- package/prerender-kpxyx916.js +3 -0
- package/prerender-kpxyx916.js.map +11 -0
- package/schemas.d.ts +2730 -0
- package/skill/SKILL.md +74 -0
- package/skill/references/api-reference.md +614 -0
- package/skill/references/configuration.md +1154 -0
- package/skill/references/installation-methods/brew.md +62 -0
- package/skill/references/installation-methods/cargo.md +86 -0
- package/skill/references/installation-methods/curl-binary.md +73 -0
- package/skill/references/installation-methods/curl-script.md +132 -0
- package/skill/references/installation-methods/curl-tar.md +58 -0
- package/skill/references/installation-methods/dmg.md +113 -0
- package/skill/references/installation-methods/gitea-release.md +106 -0
- package/skill/references/installation-methods/github-release.md +97 -0
- package/skill/references/installation-methods/manual.md +74 -0
- package/skill/references/installation-methods/npm.md +75 -0
- package/skill/references/installation-methods/overview.md +293 -0
- package/skill/references/installation-methods/zsh-plugin.md +156 -0
- package/skill/references/make-tool.md +866 -0
- package/skill/references/shell-and-hooks.md +833 -0
- package/tool-types.d.ts +14 -0
- package/wasm-n3cagcre.js +3 -0
- package/wasm-n3cagcre.js.map +10 -0
package/cli.js.map
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../packages/utils/src/contractHomePath.ts", "../packages/utils/src/dedentString.ts", "../packages/utils/src/dedentTemplate.ts", "../packages/utils/src/normalizeVersion.ts", "../packages/utils/src/detectVersionViaCli.ts", "../packages/utils/src/exitCli.ts", "../packages/utils/src/expandHomePath.ts", "../packages/utils/src/expandToolConfigPath.ts", "../packages/utils/src/formatPermissions.ts", "../packages/utils/src/generateTimestamp.ts", "../packages/utils/src/generateToolTypes.ts", "../packages/utils/src/getBuiltPackageName.ts", "../packages/utils/src/getAllFilesRecursively.ts", "../packages/utils/src/getCliBinPath.ts", "../packages/utils/src/proxyFetch.ts", "../packages/unwrap-value/src/resolveValue.ts", "../packages/utils/src/replaceInFile.ts", "../packages/core/src/common/platform.types.ts", "../packages/core/src/config/projectConfigSchema.ts", "../packages/core/src/context/createToolConfigContext.ts", "../packages/logger/src/createSafeLogMessage.ts", "../packages/logger/src/LogLevel.ts", "../packages/logger/src/SafeLogger.ts", "../packages/logger/src/filterErrorStack.ts", "../packages/logger/src/formatZodErrors.ts", "../packages/logger/src/createTsLogger.ts", "../packages/logger/src/getLogLevelFromFlags.ts", "../packages/core/src/log-messages.ts", "../packages/core/src/InstallerPluginRegistry.ts", "../packages/core/src/platformConfigSchema.ts", "../packages/core/src/tool-config/base/baseInstallParamsSchema.ts", "../packages/core/src/tool-config/hooks/installHookSchema.ts", "../packages/core/src/tool-config/base/baseToolConfigPropertiesSchema.ts", "../packages/core/src/tool-config/base/commonToolConfigPropertiesSchema.ts", "../packages/core/src/tool-config/shell/shellConfigsSchema.ts", "../packages/core/src/tool-config/shell/shellTypeConfigSchema.ts", "../packages/core/src/tool-config/shell/shellScriptSchema.ts", "../packages/core/src/tool-config/toolConfigUpdateCheckSchema.ts", "../packages/core/src/tool-config/base/binaryConfigSchema.ts", "../packages/core/src/tool-config/base/copyConfigSchema.ts", "../packages/core/src/tool-config/base/symlinkConfigSchema.ts", "../packages/core/src/tool-config/baseToolConfigWithPlatformsSchema.ts", "../packages/core/src/tool-config/platformConfigEntrySchema.ts", "../packages/core/src/tool-config/shell/shellCompletionConfigSchema.ts", "../packages/core/src/shell/types.ts", "../packages/core/src/shell/createLoggingShell.ts", "../packages/core/src/shell/ShellError.ts", "../packages/core/src/shell/createShell.ts", "../packages/core/src/shell/hasLoggingShell.ts", "../packages/core/src/shell/shellScript.types.ts", "../packages/installer/src/Installer.ts", "../packages/installer/src/context/InstallContextFactory.ts", "../packages/registry/src/file/log-messages.ts", "../packages/registry/src/file/FileRegistry.ts", "../packages/file-system/src/IResolvedFileSystem.ts", "../packages/file-system/src/MemFileSystem.ts", "../packages/file-system/src/NodeFileSystem.ts", "../packages/file-system/src/ResolvedFileSystem.ts", "../packages/registry/src/file/TrackedFileSystem.ts", "../packages/installer/src/utils/createBinaryEntrypoint.ts", "../packages/installer/src/utils/log-messages.ts", "../packages/installer/src/utils/createConfiguredShell.ts", "../packages/installer/src/utils/createToolFileSystem.ts", "../packages/downloader/cache/FileCache.ts", "../packages/downloader/cache/log-messages.ts", "../packages/downloader/cache/helpers.ts", "../packages/downloader/CachedDownloadStrategy.ts", "../packages/downloader/log-messages.ts", "../packages/downloader/errors.ts", "../packages/downloader/NodeFetchStrategy.ts", "../packages/downloader/Downloader.ts", "../packages/downloader/ProgressBar.ts", "../packages/installer/src/utils/downloadWithProgress.ts", "../packages/installer/src/utils/executeHooks.ts", "../packages/installer/src/utils/extractErrorCause.ts", "../packages/installer/src/utils/normalizeBinaries.ts", "../packages/installer/src/utils/getBinaryNames.ts", "../packages/installer/src/utils/getBinaryPaths.ts", "../packages/installer/src/utils/HookExecutor.ts", "../packages/installer/src/utils/writeHookErrorDetails.ts", "../packages/installer/src/utils/setupBinariesFromArchive.ts", "../packages/installer/src/utils/setupBinariesFromDirectDownload.ts", "../packages/installer/src/utils/withErrorHandling.ts", "../packages/installer/src/hooks/HookLifecycle.ts", "../packages/installer/src/state/InstallationStateWriter.ts", "../packages/installer-brew/src/installFromBrew.ts", "../packages/installer-brew/src/log-messages.ts", "../packages/installer-brew/src/schemas/brewInstallParamsSchema.ts", "../packages/installer-brew/src/schemas/brewToolConfigSchema.ts", "../packages/installer-brew/src/BrewInstallerPlugin.ts", "../packages/installer-cargo/src/cargo-client/CargoClient.ts", "../packages/installer-cargo/src/cargo-client/CargoClientError.ts", "../packages/installer-cargo/src/cargo-client/log-messages.ts", "../packages/installer-cargo/src/installFromCargo.ts", "../packages/installer-cargo/src/log-messages.ts", "../packages/installer-cargo/src/schemas/cargoInstallParamsSchema.ts", "../packages/installer-cargo/src/schemas/cargoToolConfigSchema.ts", "../packages/installer-cargo/src/CargoInstallerPlugin.ts", "../packages/installer-curl-script/src/installFromCurlScript.ts", "../packages/installer-curl-script/src/log-messages.ts", "../packages/installer-curl-script/src/schemas/curlScriptInstallParamsSchema.ts", "../packages/installer-curl-script/src/schemas/curlScriptToolConfigSchema.ts", "../packages/installer-curl-script/src/CurlScriptInstallerPlugin.ts", "../packages/installer-curl-tar/src/installFromCurlTar.ts", "../packages/installer-curl-tar/src/log-messages.ts", "../packages/installer-curl-tar/src/schemas/curlTarInstallParamsSchema.ts", "../packages/installer-curl-tar/src/schemas/curlTarToolConfigSchema.ts", "../packages/installer-curl-tar/src/CurlTarInstallerPlugin.ts", "../packages/installer-github/src/github-client/detectTagPrefix.ts", "../packages/installer-github/src/github-client/normalizeUserVersion.ts", "../packages/installer-github/src/github-client/buildCorrectedTag.ts", "../packages/installer-github/src/github-client/GhCliApiClient.ts", "../packages/installer-github/src/github-client/GitHubApiClientError.ts", "../packages/installer-github/src/github-client/log-messages.ts", "../packages/installer-github/src/github-client/GitHubApiClient.ts", "../packages/arch/src/createArchitectureRegex.ts", "../packages/arch/src/getArchitecturePatterns.ts", "../packages/arch/src/getArchitectureRegex.ts", "../packages/arch/src/selectBestMatch.ts", "../packages/installer-github/src/installFromGitHubRelease.ts", "../packages/installer-github/src/log-messages.ts", "../packages/installer-github/src/matchAssetPattern.ts", "../packages/installer-github/src/schemas/githubReleaseInstallParamsSchema.ts", "../packages/installer-github/src/schemas/githubReleaseToolConfigSchema.ts", "../packages/installer-github/src/GitHubReleaseInstallerPlugin.ts", "../packages/installer-dmg/src/installFromDmg.ts", "../packages/installer-dmg/src/log-messages.ts", "../packages/installer-dmg/src/schemas/dmgInstallParamsSchema.ts", "../packages/installer-dmg/src/schemas/dmgToolConfigSchema.ts", "../packages/installer-dmg/src/DmgInstallerPlugin.ts", "../packages/installer-gitea/src/gitea-client/GiteaApiClient.ts", "../packages/installer-gitea/src/gitea-client/GiteaApiClientError.ts", "../packages/installer-gitea/src/gitea-client/giteaApiTypes.ts", "../packages/installer-gitea/src/gitea-client/log-messages.ts", "../packages/installer-gitea/src/installFromGiteaRelease.ts", "../packages/installer-gitea/src/log-messages.ts", "../packages/installer-gitea/src/matchAssetPattern.ts", "../packages/installer-gitea/src/schemas/giteaReleaseInstallParamsSchema.ts", "../packages/installer-gitea/src/schemas/giteaReleaseToolConfigSchema.ts", "../packages/installer-gitea/src/GiteaReleaseInstallerPlugin.ts", "../packages/installer-manual/src/installManually.ts", "../packages/installer-manual/src/log-messages.ts", "../packages/installer-manual/src/schemas/manualInstallParamsSchema.ts", "../packages/installer-manual/src/schemas/manualToolConfigSchema.ts", "../packages/installer-manual/src/ManualInstallerPlugin.ts", "../packages/utils/src/resolvePlatformConfig.ts", "../packages/utils/src/resolveToolRelativePath.ts", "../packages/utils/src/stripVersionPrefix.ts", "../packages/archive-extractor/src/ArchiveExtractor.ts", "../packages/archive-extractor/src/log-messages.ts", "../packages/archive-extractor/src/isSupportedArchiveFile.ts", "../packages/tool-config-builder/src/log-messages.ts", "../packages/shell-init-generator/src/completion-generator/CompletionCommandExecutor.ts", "../packages/shell-init-generator/src/completion-generator/log-messages.ts", "../packages/shell-init-generator/src/completion-generator/CompletionGenerator.ts", "../packages/shell-init-generator/src/constants.ts", "../packages/shell-init-generator/src/formatters/BaseEmissionFormatter.ts", "../packages/shell-init-generator/src/formatters/BasePosixEmissionFormatter.ts", "../packages/shell-emissions/src/errors/BlockValidationError.ts", "../packages/shell-emissions/src/errors/EmissionValidationError.ts", "../packages/shell-emissions/src/emissions/validation.ts", "../packages/shell-emissions/src/emissions/factories.ts", "../packages/shell-emissions/src/emissions/guards.ts", "../packages/shell-emissions/src/blocks/BlockBuilder.ts", "../packages/shell-emissions/src/renderer/constants.ts", "../packages/shell-emissions/src/renderer/BlockRenderer.ts", "../packages/shell-init-generator/src/formatters/BashEmissionFormatter.ts", "../packages/shell-init-generator/src/formatters/PowerShellEmissionFormatter.ts", "../packages/shell-init-generator/src/formatters/ZshEmissionFormatter.ts", "../packages/shell-init-generator/src/formatters/EmissionFormatterFactory.ts", "../packages/shell-init-generator/src/profile-updater/ProfileUpdater.ts", "../packages/shell-init-generator/src/shellTemplates.ts", "../packages/shell-init-generator/src/ShellInitGenerator.ts", "../packages/shell-init-generator/src/log-messages.ts", "../packages/shell-init-generator/src/shell-generators/BashGenerator.ts", "../packages/shell-init-generator/src/shell-generators/BaseShellGenerator.ts", "../packages/shell-init-generator/src/shell-generators/PowerShellGenerator.ts", "../packages/shell-init-generator/src/shell-generators/ZshGenerator.ts", "../packages/shell-init-generator/src/shell-generators/ShellGeneratorFactory.ts", "../packages/tool-config-builder/src/ShellConfigurator.ts", "../packages/tool-config-builder/src/toolConfigBuilder.ts", "../packages/tool-config-builder/src/createInstallFunction.ts", "../packages/config/src/loadToolConfigs.ts", "../packages/config/src/log-messages.ts", "../packages/config/src/ConfigService.ts", "../packages/config/src/defineConfig.ts", "../packages/config/src/tsConfigLoader.ts", "../packages/config/src/stagedProjectConfigLoader.ts", "../packages/config/src/loadConfig.ts", "../packages/features/src/readme-service/constants.ts", "../packages/features/src/readme-service/log-messages.ts", "../packages/features/src/readme-service/ReadmeCache.ts", "../packages/features/src/readme-service/ReadmeService.ts", "../packages/generator-orchestrator/src/GeneratorOrchestrator.ts", "../packages/generator-orchestrator/src/log-messages.ts", "../packages/generator-orchestrator/src/orderToolConfigsByDependencies.ts", "../packages/installer-curl-binary/src/installFromCurlBinary.ts", "../packages/installer-curl-binary/src/log-messages.ts", "../packages/installer-curl-binary/src/schemas/curlBinaryInstallParamsSchema.ts", "../packages/installer-curl-binary/src/schemas/curlBinaryToolConfigSchema.ts", "../packages/installer-curl-binary/src/CurlBinaryInstallerPlugin.ts", "../packages/installer-npm/src/installFromNpm.ts", "../packages/installer-npm/src/log-messages.ts", "../packages/installer-npm/src/schemas/npmInstallParamsSchema.ts", "../packages/installer-npm/src/schemas/npmToolConfigSchema.ts", "../packages/installer-npm/src/NpmInstallerPlugin.ts", "../packages/installer-zsh-plugin/src/installFromZshPlugin.ts", "../packages/installer-zsh-plugin/src/log-messages.ts", "../packages/installer-zsh-plugin/src/schemas/zshPluginInstallParamsSchema.ts", "../packages/installer-zsh-plugin/src/schemas/zshPluginToolConfigSchema.ts", "../packages/installer-zsh-plugin/src/ZshPluginInstallerPlugin.ts", "../packages/shim-generator/src/ShimGenerator.ts", "../packages/shim-generator/src/log-messages.ts", "../packages/symlink-generator/src/CopyGenerator.ts", "../packages/symlink-generator/src/log-messages.ts", "../packages/symlink-generator/src/SymlinkGenerator.ts", "../packages/version-checker/src/VersionChecker.ts", "../packages/version-checker/src/log-messages.ts", "../packages/cli/src/cli.ts", "../packages/cli/src/binCommand.ts", "../packages/cli/src/checkUpdatesCommand.ts", "../packages/cli/src/log-messages.ts", "../packages/cli/src/cleanupCommand.ts", "../packages/cli/src/createProgram.ts", "../packages/dashboard/src/server/dashboard-server.ts", "../packages/dashboard/src/server/log-messages.ts", "../packages/dashboard/src/shared/types.ts", "../packages/dashboard/src/server/routes/activity.ts", "../packages/dashboard/src/server/routes/config.ts", "../packages/dashboard/src/server/routes/health.ts", "../packages/dashboard/src/server/routes/helpers/directory-size.ts", "../packages/dashboard/src/server/routes/helpers/git-dates.ts", "../packages/dashboard/src/server/routes/helpers/tool-configs.ts", "../packages/dashboard/src/server/routes/recent-tools.ts", "../packages/dashboard/src/server/routes/shell-integration.ts", "../packages/dashboard/src/server/routes/stats.ts", "../packages/dashboard/src/server/routes/tool-check-update.ts", "../packages/dashboard/src/server/routes/tool-configs-tree.ts", "../packages/dashboard/src/server/routes/tool-history.ts", "../packages/dashboard/src/server/routes/tool-install.ts", "../packages/dashboard/src/server/routes/tool-readme.ts", "../packages/dashboard/src/server/routes/tool-source.ts", "../packages/dashboard/src/server/routes/tool-update.ts", "../packages/dashboard/src/server/routes/tools.ts", "../packages/dashboard/src/server/routes/index.ts", "../packages/cli/src/dashboardCommand.ts", "../packages/cli/src/detectConflictsCommand.ts", "../packages/virtual-env/src/constants.ts", "../packages/virtual-env/src/generateDefaultConfig.ts", "../packages/virtual-env/src/generatePowerShellSourceScript.ts", "../packages/virtual-env/src/generateSourceScript.ts", "../packages/virtual-env/src/schemas.ts", "../packages/virtual-env/src/VirtualEnvManager.ts", "../packages/virtual-env/src/log-messages.ts", "../packages/cli/src/envCommand.ts", "../packages/cli/src/featuresCommand.ts", "../packages/cli/src/filesCommand.ts", "../packages/cli/src/generateCommand.ts", "../packages/cli/src/generateCommandCompletion.ts", "../packages/cli/src/installCommand.ts", "../packages/cli/src/logCommand.ts", "../packages/cli/src/skillCommand.ts", "../packages/cli/src/updateCommand.ts", "../packages/cli/src/generateZshCompletion.ts", "../packages/registry-database/src/RegistryDatabase.ts", "../packages/registry-database/src/log-messages.ts", "../packages/registry/src/tool/log-messages.ts", "../packages/registry/src/tool/ToolInstallationRegistry.ts", "../packages/cli/src/runtime/createBaseRuntimeContext.ts", "../packages/cli/src/resolveConfigPath.ts", "../packages/cli/src/light/createLightRuntimeContext.ts", "../packages/cli/src/light/runTrackUsageCommand.ts", "../packages/cli/src/populateMemFsForDryRun.ts", "../packages/cli/src/defineTool.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * Contracts absolute paths by replacing the home directory with ~ for more readable logging.\n * @param homeDir - The user's home directory.\n * @param path - The file path to contract.\n * @returns The path with the home directory replaced by ~.\n */\nexport function contractHomePath(homeDir: string, path: string): string {\n if (path.startsWith(homeDir)) {\n const remainder = path.slice(homeDir.length);\n return remainder.startsWith('/') || remainder === '' ? `~${remainder}` : path;\n }\n return path;\n}\n",
|
|
6
|
+
"/**\n * Strips common leading whitespace from all lines in a string.\n * This is useful for cleaning up template literals that are indented for readability.\n *\n * @param str - The string to dedent\n * @returns The dedented string with common leading whitespace removed\n *\n * @example\n * ```typescript\n * const indented = `\n * function hello() {\n * echo \"Hello World\"\n * }\n * `;\n *\n * const dedented = dedentString(indented);\n * // Result:\n * // function hello() {\n * // echo \"Hello World\"\n * // }\n * ```\n */\nexport function dedentString(str: string): string {\n const lines = str.split('\\n');\n const nonEmptyLines = lines.filter((line) => line.trim().length > 0);\n\n if (nonEmptyLines.length === 0) {\n return str;\n }\n\n // Find the minimum indentation level among non-empty lines\n const minIndent = Math.min(...nonEmptyLines.map((line) => line.match(/^ */)?.[0].length ?? 0));\n\n // Remove the common indentation from all lines\n return lines\n .map((line) => line.slice(minIndent))\n .join('\\n')\n .trim(); // Remove leading/trailing empty lines\n}\n",
|
|
7
|
+
"import { dedentString } from './dedentString';\n\n/**\n * Processes a standalone placeholder line and returns the processed lines\n */\nfunction processStandalonePlaceholder(\n line: string,\n standalonePlaceholderMatch: RegExpMatchArray,\n values: Record<string, string>,\n): string[] {\n const key = standalonePlaceholderMatch[1];\n\n if (key && key in values) {\n const value = values[key];\n if (value !== undefined) {\n const lineIndent = line.match(/^(\\s*)/)?.[1] ?? '';\n const valueLines = value.split('\\n');\n return valueLines.map((valueLine: string) => lineIndent + valueLine);\n }\n }\n\n // Keep the placeholder if no value provided\n return [line];\n}\n\n/**\n * Processes inline placeholders within a line\n */\nfunction processInlinePlaceholders(line: string, values: Record<string, string>): string {\n let processedLine = line;\n const placeholderRegex = /{(\\w+)}/g;\n let match: RegExpExecArray | null;\n\n match = placeholderRegex.exec(processedLine);\n while (match !== null) {\n const fullMatch = match[0];\n const key = match[1];\n\n if (key && key in values) {\n const value = values[key];\n if (value !== undefined) {\n processedLine = processedLine.replace(fullMatch, value);\n placeholderRegex.lastIndex = 0;\n }\n }\n\n match = placeholderRegex.exec(processedLine);\n }\n\n return processedLine;\n}\n\n/**\n * Processes a template string by removing common indentation and replacing placeholders with values.\n *\n * This function:\n * - Removes common leading whitespace using the dedent function\n * - Replaces standalone placeholders (e.g., {key}) with properly indented multiline values\n * - Replaces inline placeholders within text with their corresponding values\n *\n * @param template - The template string containing placeholders in the format {key}\n * @param values - An object mapping placeholder keys to their replacement values\n * @returns The processed string with proper indentation and replaced placeholders\n */\nexport function dedentTemplate(template: string, values: Record<string, string>): string {\n const dedentedText = dedentString(template);\n const dedentedLines = dedentedText.split('\\n');\n const resultLines: string[] = [];\n\n for (const line of dedentedLines) {\n const trimmedLine = line.trim();\n const standalonePlaceholderMatch = trimmedLine.match(/^{(\\w+)}$/);\n\n if (standalonePlaceholderMatch) {\n const processedLines = processStandalonePlaceholder(line, standalonePlaceholderMatch, values);\n resultLines.push(...processedLines);\n } else {\n const processedLine = processInlinePlaceholders(line, values);\n resultLines.push(processedLine);\n }\n }\n\n return resultLines.join('\\n');\n}\n",
|
|
8
|
+
"/**\n * Normalizes a version string to be safe for use in file paths.\n *\n * This function replaces characters that are unsafe for file systems\n * (like /, :, \\, etc.) with safe alternatives or removes them.\n *\n * @param version - Raw version string\n * @returns Path-safe version string\n */\nexport function normalizeVersion(version: string): string {\n if (!version) {\n return version;\n }\n\n // Replace common unsafe characters\n // / -> -\n // \\ -> -\n // : -> -\n // < -> _\n // > -> _\n // \" -> _\n // | -> _\n // ? -> _\n // * -> _\n return version\n .replace(/[/\\\\]/g, '-')\n .replace(/[:]/g, '-')\n .replace(/[<>\"|?*]/g, '_')\n .trim();\n}\n",
|
|
9
|
+
"import type { Shell } from '@dotfiles/core';\nimport { normalizeVersion } from './normalizeVersion';\n\nexport interface DetectVersionOptions {\n /**\n * The shell executor to use for running commands.\n */\n shellExecutor: Shell;\n /**\n * The binary to run.\n */\n binaryPath: string;\n /**\n * Arguments to pass to the binary to get the version.\n * @default ['--version']\n */\n args?: string[];\n /**\n * Custom regex to extract the version from the output.\n * If provided, the first capture group will be used as the version.\n */\n regex?: string | RegExp;\n /**\n * Environment variables to set when running the binary.\n */\n env?: Record<string, string>;\n}\n\n/**\n * Detects the version of a tool by running it with --version (or custom args)\n * and parsing the output.\n */\nexport async function detectVersionViaCli(options: DetectVersionOptions): Promise<string | undefined> {\n const { binaryPath, args = ['--version'], regex, env, shellExecutor } = options;\n\n try {\n const result = await shellExecutor`${binaryPath} ${args}`\n .env({ ...process.env, ...env })\n .quiet()\n .noThrow();\n const output = (result.stdout.toString() + result.stderr.toString()).trim();\n\n if (regex) {\n const re = typeof regex === 'string' ? new RegExp(regex) : regex;\n const match = output.match(re);\n if (match?.[1]) {\n return normalizeVersion(match[1]);\n }\n throw new Error(`Version detection failed: regex ${re} did not match output: ${output}`);\n }\n\n // Default heuristics\n // Look for semver-like strings: v1.2.3, 1.2.3\n // We want to be careful not to match things that look like versions but aren't,\n // but generally the first thing that looks like a version in --version output is the version.\n\n // Matches:\n // v1.2.3\n // 1.2.3\n // 1.2.3-beta.1\n // 1.2.3+build\n const semverRegex = /v?(\\d+\\.\\d+\\.\\d+(?:-[\\w.]+)?(?:\\+[\\w.]+)?)/i;\n const match = output.match(semverRegex);\n if (match?.[1]) {\n return normalizeVersion(match[1]);\n }\n\n return undefined;\n } catch (error) {\n if (error instanceof Error && error.message.startsWith('Version detection failed')) {\n throw error;\n }\n return undefined;\n }\n}\n",
|
|
10
|
+
"export const ExitCode = {\n SUCCESS: 0,\n ERROR: 1,\n} as const;\n\nexport type ExitCode = (typeof ExitCode)[keyof typeof ExitCode];\n\nexport function exitCli(exitCode: number): never {\n if (process.env.NODE_ENV !== 'test') {\n process.exit(exitCode);\n }\n // In test environment, throw to signify termination for testing purposes\n // and to satisfy the 'never' return type.\n throw new Error(`MOCK_EXIT_CLI_CALLED_WITH_${exitCode}`);\n}\n",
|
|
11
|
+
"/**\n * Expands the tilde (~) prefix in file paths to the user's home directory.\n * @param homeDir - The user's home directory.\n * @param path - The file path that may contain a tilde.\n * @returns The path with the tilde expanded to the user's home directory.\n */\nexport function expandHomePath(homeDir: string, path: string): string {\n if (path === '~' || path.startsWith('~/') || path.startsWith('~\\\\')) {\n return path.replace(/^~(?=$|\\/|\\\\)/, homeDir);\n }\n return path;\n}\n",
|
|
12
|
+
"import type { ISystemInfo } from '@dotfiles/core';\nimport path from 'node:path';\nimport { expandHomePath } from './expandHomePath';\n\n/**\n * Config paths interface to avoid circular dependency with the config package.\n */\ninterface IConfigWithPaths {\n paths: {\n homeDir: string;\n dotfilesDir: string;\n [key: string]: unknown;\n };\n [key: string]: unknown;\n}\n\n/**\n * Expands a path from a tool configuration file.\n *\n * Handles:\n * 1. Variable expansion: ${paths.homeDir}, ${paths.dotfilesDir}, etc.\n * 2. Home directory expansion: ~/some/path -> /home/user/some/path (using projectConfig.paths.homeDir)\n * 3. Relative path resolution: ./some/path -> resolved relative to tool config file\n * 4. Absolute paths: /some/path -> used as-is\n *\n * @param toolConfigFilePath - Absolute path to the tool config file (can be undefined for legacy configs)\n * @param inputPath - The path from the tool config (may contain variables, ~, or be relative)\n * @param projectConfig - The loaded project configuration for variable substitution and home directory\n * @param systemInfo - System information (kept for compatibility but homeDir comes from projectConfig)\n * @returns The fully resolved absolute path\n */\nexport function expandToolConfigPath(\n toolConfigFilePath: string | undefined,\n inputPath: string,\n projectConfig: IConfigWithPaths,\n _systemInfo: ISystemInfo,\n): string {\n // Step 1: Expand variables like ${paths.homeDir}\n let expandedPath = expandVariables(inputPath, projectConfig);\n\n // Step 2: Expand home directory (~)\n expandedPath = expandHomePath(projectConfig.paths.homeDir, expandedPath);\n\n // Step 3: If still relative, resolve relative to tool config file directory or fallback to dotfiles dir\n if (!path.isAbsolute(expandedPath)) {\n if (toolConfigFilePath) {\n const toolConfigDir = path.dirname(toolConfigFilePath);\n expandedPath = path.resolve(toolConfigDir, expandedPath);\n } else {\n // Fallback to dotfiles directory for legacy configs without configFilePath\n expandedPath = path.resolve(projectConfig.paths.dotfilesDir, expandedPath);\n }\n }\n\n return expandedPath;\n}\n\n/**\n * Expands variables in a path string using the project configuration.\n * Supports syntax like ${paths.homeDir}, ${paths.dotfilesDir}, etc.\n */\nfunction expandVariables(inputPath: string, projectConfig: IConfigWithPaths): string {\n return inputPath.replace(/(?<!\\$)\\{([a-zA-Z0-9_.]+)\\}/g, (match, varName) => {\n if (varName.includes('.')) {\n const parts = varName.split('.');\n let value: unknown = projectConfig;\n\n for (const part of parts) {\n if (value && typeof value === 'object' && part in (value as Record<string, unknown>)) {\n value = (value as Record<string, unknown>)[part];\n } else {\n // Variable not found, return original match\n return match;\n }\n }\n\n return typeof value === 'string' ? value : match;\n }\n\n // Simple variable name - not supported in this context, return as-is\n return match;\n });\n}\n",
|
|
13
|
+
"/**\n * Convert numeric file permissions to human-readable format\n *\n * @param mode - The numeric mode (e.g., 493, '755', 0o755)\n * @returns Human-readable permission string (e.g., 'rwxr-xr-x')\n */\nexport function formatPermissions(mode: string | number): string {\n // Convert to number if string\n const numericMode = typeof mode === 'string' ? parseInt(mode, 8) : mode;\n\n // Extract the last 3 digits (file permissions, ignore file type bits)\n const permissions = numericMode & 0o777;\n\n const chars = ['---', '--x', '-w-', '-wx', 'r--', 'r-x', 'rw-', 'rwx'];\n\n const owner = chars[(permissions >> 6) & 7] ?? '---';\n const group = chars[(permissions >> 3) & 7] ?? '---';\n const other = chars[permissions & 7] ?? '---';\n\n return owner + group + other;\n}\n",
|
|
14
|
+
"/**\n * Generate a human-readable timestamp for installation directories\n * Format: YYYY-MM-DD-HH-MM-SS\n *\n * @param date Optional date to use, defaults to current date\n * @returns Timestamp string suitable for directory names\n */\nexport function generateTimestamp(date: Date = new Date()): string {\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, '0');\n const day = String(date.getDate()).padStart(2, '0');\n const hours = String(date.getHours()).padStart(2, '0');\n const minutes = String(date.getMinutes()).padStart(2, '0');\n const seconds = String(date.getSeconds()).padStart(2, '0');\n\n return `${year}-${month}-${day}-${hours}-${minutes}-${seconds}`;\n}\n",
|
|
15
|
+
"import type { IBinaryConfig, ToolConfig } from '@dotfiles/core';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport path from 'node:path';\nimport { getBuiltPackageName } from './getBuiltPackageName';\n\n/**\n * Header comment shared by all generated tool-types.d.ts files.\n */\nconst TOOL_TYPES_HEADER: string = `/**\n * Type-safe binary names for tool dependencies.\n *\n * This file is automatically generated by the build system and the \\`generate\\` command.\n * It extracts binary names from loaded tool configurations to provide autocomplete\n * for the \\`dependsOn()\\` method.\n */`;\n\n/**\n * Extracts all binary names from loaded tool configurations.\n *\n * @param toolConfigs - Record of loaded tool configurations\n * @returns Set of unique binary names from all tool configurations\n */\nexport function extractBinaryNames(toolConfigs: Record<string, ToolConfig>): Set<string> {\n const binaryNames: Set<string> = new Set();\n\n for (const [toolName, toolConfig] of Object.entries(toolConfigs)) {\n if (toolConfig.binaries && toolConfig.binaries.length > 0) {\n for (const binary of toolConfig.binaries) {\n if (typeof binary === 'string') {\n binaryNames.add(binary);\n } else {\n const binaryConfig: IBinaryConfig = binary;\n binaryNames.add(binaryConfig.name);\n }\n }\n } else {\n binaryNames.add(toolName);\n }\n }\n\n return binaryNames;\n}\n\n/**\n * Generates the TypeScript union type string from binary names.\n * Falls back to 'string' if no binary names are provided.\n *\n * @param binaryNames - Set of binary names to include in the union\n * @returns TypeScript union type string\n */\nexport function generateUnionType(binaryNames: Set<string>): string {\n if (binaryNames.size === 0) {\n return 'string';\n }\n\n const sortedNames: string[] = Array.from(binaryNames).toSorted();\n const quotedNames: string[] = sortedNames.map((name: string): string => `'${name}'`);\n return quotedNames.join(' | ');\n}\n\nexport function generateToolTypesContent(toolConfigs: Record<string, ToolConfig>, moduleName?: string): string {\n const binaryNames: Set<string> = extractBinaryNames(toolConfigs);\n const unionType: string = generateUnionType(binaryNames);\n const sortedNames: string[] = unionType === 'string' ? [] : Array.from(binaryNames).toSorted();\n const registryEntries: string = sortedNames.map((name: string): string => ` '${name}': never;`).join('\\n');\n const hasEntries: boolean = registryEntries.length > 0;\n const registryBody: string = hasEntries\n ? ` interface IKnownBinNameRegistry {\\n${registryEntries}\\n }`\n : ' interface IKnownBinNameRegistry {}';\n const resolvedModuleName: string = moduleName ?? getBuiltPackageName();\n const moduleBlock: string = `declare module '${resolvedModuleName}' {\\n${registryBody}\\n}`;\n const contentParts: string[] = [TOOL_TYPES_HEADER, moduleBlock, 'export {};', ''];\n const content: string = contentParts.join('\\n\\n');\n return content;\n}\n\n/**\n * Generates and writes the tool-types.d.ts file.\n *\n * @param toolConfigs - Record of loaded tool configurations\n * @param outputPath - Path where the tool-types.d.ts file should be written\n * @param fs - File system interface for writing the file\n * @param moduleName - Optional module name to use in the declaration (defaults to @alexgorbatchev/dotfiles)\n */\nexport async function generateToolTypes(\n toolConfigs: Record<string, ToolConfig>,\n outputPath: string,\n fs: IFileSystem,\n moduleName?: string,\n): Promise<void> {\n const content: string = generateToolTypesContent(toolConfigs, moduleName);\n await fs.ensureDir(path.dirname(outputPath));\n await fs.writeFile(outputPath, content, 'utf8');\n}\n",
|
|
16
|
+
"const DEFAULT_BUILT_PACKAGE_NAME = '@alexgorbatchev/dotfiles';\n\ndeclare global {\n namespace NodeJS {\n interface IProcessEnvOverrides {\n DOTFILES_BUILT_PACKAGE_NAME?: string;\n }\n\n interface ProcessEnv extends IProcessEnvOverrides {}\n }\n}\n\nexport interface IBuiltPackageEnvironment {\n DOTFILES_BUILT_PACKAGE_NAME?: string;\n}\n\n/**\n * We intentionally augment ProcessEnv via IProcessEnvOverrides so the bundler can statically replace\n * process.env.DOTFILES_BUILT_PACKAGE_NAME with its configured value.\n * Using bracket notation prevents this optimization and breaks the build output.\n */\nexport function getBuiltPackageName(env?: IBuiltPackageEnvironment): string {\n const configuredName: string | undefined = env?.DOTFILES_BUILT_PACKAGE_NAME ??\n process.env.DOTFILES_BUILT_PACKAGE_NAME;\n\n if (configuredName !== undefined) {\n const trimmedName: string = configuredName.trim();\n\n if (trimmedName !== '') {\n return trimmedName;\n }\n }\n\n return DEFAULT_BUILT_PACKAGE_NAME;\n}\n",
|
|
17
|
+
"import type { IFileSystem } from '@dotfiles/file-system';\nimport path from 'node:path';\n\n/**\n * Recursively collects all file paths in a directory.\n *\n * @param fs - The file system interface to use\n * @param dirPath - The directory to scan\n * @param baseDir - The base directory for calculating relative paths. If provided, returns relative paths.\n * If not provided (undefined), returns absolute paths.\n * @returns Array of file paths (absolute if baseDir is not provided, relative to baseDir otherwise)\n */\nexport async function getAllFilesRecursively(fs: IFileSystem, dirPath: string, baseDir?: string): Promise<string[]> {\n const files: string[] = [];\n // Track whether caller wants relative paths (baseDir was explicitly provided)\n const wantsRelativePaths = baseDir !== undefined;\n const base = baseDir ?? dirPath;\n const entries = await fs.readdir(dirPath);\n\n for (const entry of entries) {\n const fullPath = path.join(dirPath, entry);\n const stats = await fs.stat(fullPath);\n\n if (stats.isDirectory()) {\n // Always pass base to recursive calls, but preserve the \"wants relative\" intent\n const subFiles = await getAllFilesRecursively(fs, fullPath, wantsRelativePaths ? base : undefined);\n files.push(...subFiles);\n } else {\n // Return relative paths only if caller explicitly requested them\n const resultPath = wantsRelativePaths ? path.relative(base, fullPath) : fullPath;\n files.push(resultPath);\n }\n }\n\n return files;\n}\n",
|
|
18
|
+
"declare const CLI_BIN_PATH: string | undefined;\n\n/**\n * Gets the path to the CLI binary.\n *\n * `CLI_BIN_PATH` is set by the `scripts/compile.sh` at build time.\n */\nexport function getCliBinPath(): string {\n if (typeof CLI_BIN_PATH === 'undefined') {\n return process.argv.slice(0, 2).join(' ');\n }\n\n return CLI_BIN_PATH;\n}\n",
|
|
19
|
+
"/**\n * Configuration for the proxy fetch function.\n */\nexport interface ProxyFetchConfig {\n /** Whether the proxy is enabled */\n enabled: boolean;\n /** The port the proxy server is listening on */\n port: number;\n}\n\n/**\n * Fetch function that routes requests through an HTTP proxy when enabled.\n *\n * When proxy is enabled, URLs are rewritten:\n * `https://api.github.com/repos` → `http://localhost:3128/https://api.github.com/repos`\n *\n * When disabled, requests pass through to the native fetch unchanged.\n *\n * @param input - URL or Request object\n * @param init - Optional fetch init options\n * @param proxyConfig - Proxy configuration with enabled flag and port\n * @returns Promise resolving to Response\n *\n * @example\n * ```typescript\n * const response = await proxyFetch(\n * 'https://api.github.com/repos/owner/repo',\n * { headers: { Accept: 'application/json' } },\n * { enabled: true, port: 3128 }\n * );\n * ```\n */\nexport async function proxyFetch(\n input: RequestInfo | URL,\n init: RequestInit | undefined,\n proxyConfig: ProxyFetchConfig | undefined,\n): Promise<Response> {\n const url = input instanceof Request ? input.url : input.toString();\n const targetUrl = proxyConfig?.enabled ? `http://localhost:${proxyConfig.port}/${url}` : url;\n\n if (input instanceof Request) {\n return fetch(targetUrl, {\n method: input.method,\n headers: input.headers,\n body: input.body,\n mode: input.mode,\n credentials: input.credentials,\n cache: input.cache,\n redirect: input.redirect,\n referrer: input.referrer,\n integrity: input.integrity,\n ...init,\n });\n }\n\n return fetch(targetUrl, init);\n}\n",
|
|
20
|
+
"import type { Resolvable } from './types';\n\n/**\n * Resolves a Resolvable value to its actual value.\n *\n * Handles three cases:\n * - Static value: returns the value directly\n * - Sync function: calls the function and returns the result\n * - Async function: calls the function and awaits the result\n *\n * @template TParams - The type of parameters passed to the resolver function\n * @template TReturn - The type of the resolved value\n * @param params - Parameters to pass to the resolver function if it's a function\n * @param resolvable - The value to resolve (static, sync function, or async function)\n * @returns A promise that resolves to the unwrapped value\n */\nexport async function resolveValue<TParams, TReturn>(\n params: TParams,\n resolvable: Resolvable<TParams, TReturn>,\n): Promise<TReturn> {\n if (typeof resolvable === 'function') {\n const fn = resolvable as (params: TParams) => TReturn | Promise<TReturn>;\n return await fn(params);\n }\n return resolvable;\n}\n",
|
|
21
|
+
"import type { IResolvedFileSystem } from '@dotfiles/file-system';\nimport type { Resolvable } from '@dotfiles/unwrap-value';\nimport { resolveValue } from '@dotfiles/unwrap-value';\n\nexport type ReplaceInFileMode = 'file' | 'line';\n\ntype ReplaceCapture = string | undefined;\ntype ReplaceGroups = Record<string, string>;\n\nexport interface IReplaceInFileMatch {\n substring: string;\n captures: ReplaceCapture[];\n offset: number;\n input: string;\n groups?: ReplaceGroups;\n}\n\nexport type ReplaceInFileReplacer = Resolvable<IReplaceInFileMatch, string>;\nexport type ReplaceInFilePattern = RegExp | string;\n\nexport interface IReplaceInFileOptions {\n /** Optional. Defaults to `'file'` when not provided. */\n mode?: ReplaceInFileMode;\n}\n\n/**\n * Performs a regex-based replacement within a file.\n *\n * This utility is designed for code-mod style operations where the replacement value may be\n * computed asynchronously.\n *\n * **Key behaviors**\n * - Always replaces *all* matches (global replacement), even if `from` does not include the `g` flag.\n * - Supports `to` as either a string or a (a)sync callback.\n * - Supports `mode: 'file'` (default, process the whole file as one string) and `mode: 'line'`\n * (process each line separately, preserving the original end-of-line sequences encountered in the file).\n * - No-op write: if the computed output is identical to the input content, the file is not written.\n * - Returns `true` if replacements were made, `false` otherwise.\n *\n * **Replacement callback arguments**\n *\n * When `to` is a function, it receives an `IReplaceInFileMatch` object:\n * - `substring`: the matched substring\n * - `captures`: array of capture groups (which may be `undefined`)\n * - `offset`: the match offset (number)\n * - `input`: the original input string\n * - `groups`: named capture groups object (if present)\n *\n * @returns `true` if any replacements were made, `false` if no matches were found.\n *\n * @example\n * ```ts\n * const wasReplaced = await replaceInFile(fileSystem, '/tmp/input.txt', /foo/, 'bar');\n * if (!wasReplaced) {\n * console.log('Pattern not found');\n * }\n * ```\n *\n * @example\n * ```ts\n * await replaceInFile(fileSystem, '~/config.txt', 'foo', 'bar', { mode: 'line' });\n * ```\n */\nexport async function replaceInFile(\n fileSystem: IResolvedFileSystem,\n filePath: string,\n from: ReplaceInFilePattern,\n to: ReplaceInFileReplacer,\n options?: IReplaceInFileOptions,\n): Promise<boolean> {\n const mode: ReplaceInFileMode = options?.mode ?? 'file';\n const pattern: RegExp = normalizePattern(from);\n\n const content = await fileSystem.readFile(filePath, 'utf8');\n\n const finalContent: string = mode === 'line'\n ? await replaceInLines(content, pattern, to)\n : await replaceInString(content, pattern, to);\n\n const wasReplaced: boolean = finalContent !== content;\n\n if (wasReplaced) {\n await fileSystem.writeFile(filePath, finalContent, 'utf8');\n }\n\n return wasReplaced;\n}\n\nfunction normalizePattern(from: ReplaceInFilePattern): RegExp {\n if (typeof from === 'string') {\n const escaped: string = from.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const pattern: RegExp = new RegExp(escaped, 'g');\n return pattern;\n }\n\n const flags: string = from.flags.includes('g') ? from.flags : `${from.flags}g`;\n const pattern: RegExp = new RegExp(from.source, flags);\n return pattern;\n}\n\nasync function replaceInString(input: string, pattern: RegExp, to: ReplaceInFileReplacer): Promise<string> {\n const matches: RegExpMatchArray[] = Array.from(input.matchAll(pattern));\n\n let result: string = input;\n let offset: number = 0;\n\n for (const match of matches) {\n const substring: string = match[0] ?? '';\n const index: number = match.index ?? 0;\n const captures: ReplaceCapture[] = match.slice(1);\n const groups: ReplaceGroups | undefined = match.groups;\n\n const matchParams: IReplaceInFileMatch = {\n substring,\n captures,\n offset: index,\n input,\n groups,\n };\n\n const replacement: string = await resolveValue(matchParams, to);\n const start: number = index + offset;\n const end: number = start + substring.length;\n\n result = result.slice(0, start) + replacement + result.slice(end);\n offset += replacement.length - substring.length;\n }\n\n return result;\n}\n\nasync function replaceInLines(content: string, pattern: RegExp, to: ReplaceInFileReplacer): Promise<string> {\n const parts: string[] = content.split(/(\\r\\n|\\n)/);\n\n let result: string = '';\n\n for (let index = 0; index < parts.length; index += 2) {\n const line: string = parts[index] ?? '';\n const eol: string = parts[index + 1] ?? '';\n\n const replacedLine: string = await replaceInString(line, pattern, to);\n result += replacedLine + eol;\n }\n\n return result;\n}\n",
|
|
22
|
+
"import { z } from 'zod';\n\n/**\n * Represents operating system platforms using a bitwise enum, allowing for\n * combinations of multiple platforms.\n *\n * @example\n * ```typescript\n * const supported = Platform.Linux | Platform.MacOS;\n * if (supported & Platform.Linux) {\n * // Supported on Linux\n * }\n * ```\n */\nexport enum Platform {\n /** Represents no specific platform. */\n None = 0,\n /** The Linux operating system. */\n Linux = 1 << 0, // 1\n /** The macOS operating system. */\n MacOS = 1 << 1, // 2\n /** The Windows operating system. */\n Windows = 1 << 2, // 4\n /** A combination of Unix-like systems (Linux and macOS). */\n Unix = Platform.Linux | Platform.MacOS, // 3\n /** A combination of all supported platforms. */\n All = Platform.Linux | Platform.MacOS | Platform.Windows, // 7\n}\n\n/**\n * Represents CPU architectures using a bitwise enum, allowing for combinations.\n *\n * @example\n * ```typescript\n * const supported = Architecture.X86_64 | Architecture.Arm64;\n * if (supported & Architecture.Arm64) {\n * // Supported on ARM64\n * }\n * ```\n */\nexport enum Architecture {\n /** Represents no specific architecture. */\n None = 0,\n /** The 64-bit x86 architecture (also known as AMD64). */\n X86_64 = 1 << 0, // 1\n /** The 64-bit ARM architecture. */\n Arm64 = 1 << 1, // 2\n /** A combination of all supported architectures. */\n All = Architecture.X86_64 | Architecture.Arm64, // 3\n}\n\n/**\n * A Zod schema for validating `Platform` enum values.\n *\n * Ensures that a given number is a valid bitmask composed of `Platform` enum members.\n */\nexport const platformSchema = z\n .number()\n .int()\n .min(0)\n .max(Platform.All)\n .refine(\n (value) => {\n // Check if the value is a valid combination of Platform enum values\n const validBits = Platform.All;\n return (value & ~validBits) === 0;\n },\n {\n message:\n 'Must be a valid Platform value. Use `Platform.None`, `Platform.Linux`, `Platform.MacOS`, `Platform.Windows`, `Platform.Unix`, or `Platform.All`. You can combine values with a bitwise OR (e.g., `Platform.Linux | Platform.MacOS`).',\n },\n );\n\n/**\n * A Zod schema for validating `Architecture` enum values.\n *\n * Ensures that a given number is a valid bitmask composed of `Architecture` enum members.\n */\nexport const architectureSchema = z\n .number()\n .int()\n .min(0)\n .max(Architecture.All)\n .refine(\n (value) => {\n // Check if the value is a valid combination of Architecture enum values\n const validBits = Architecture.All;\n return (value & ~validBits) === 0;\n },\n {\n message:\n 'Must be a valid Architecture value. Use `Architecture.None`, `Architecture.X86_64`, `Architecture.Arm64`, or `Architecture.All`. You can combine values with a bitwise OR (e.g., `Architecture.X86_64 | Architecture.Arm64`).',\n },\n );\n\n/**\n * Checks if a specific platform is included within a bitmask of target platforms.\n *\n * @param targetPlatforms - A bitmask representing the set of allowed platforms.\n * @param platform - The platform to check for inclusion.\n * @returns `true` if the `platform` is included in `targetPlatforms`, otherwise `false`.\n */\nexport function hasPlatform(targetPlatforms: Platform, platform: Platform): boolean {\n if (platform === Platform.None) {\n return targetPlatforms === Platform.None;\n }\n return (targetPlatforms & platform) === platform;\n}\n\n/**\n * Checks if a specific architecture is included within a bitmask of target architectures.\n *\n * @param targetArchitectures - A bitmask representing the set of allowed architectures.\n * @param architecture - The architecture to check for inclusion.\n * @returns `true` if the `architecture` is included in `targetArchitectures`, otherwise `false`.\n */\nexport function hasArchitecture(targetArchitectures: Architecture, architecture: Architecture): boolean {\n if (architecture === Architecture.None) {\n return targetArchitectures === Architecture.None;\n }\n return (targetArchitectures & architecture) === architecture;\n}\n\n/**\n * Converts a Node.js platform string to a Platform enum value.\n *\n * @param platform - The platform string from `process.platform`\n * @returns The corresponding Platform enum value, or `Platform.None` if not supported\n */\nexport function platformFromNodeJS(platform: NodeJS.Platform): Platform {\n switch (platform) {\n case 'darwin':\n return Platform.MacOS;\n case 'linux':\n return Platform.Linux;\n case 'win32':\n return Platform.Windows;\n default:\n return Platform.None;\n }\n}\n\n/**\n * Converts a Node.js architecture string to an Architecture enum value.\n *\n * @param arch - The architecture string from `process.arch`\n * @returns The corresponding Architecture enum value, or `Architecture.None` if not supported\n */\nexport function architectureFromNodeJS(arch: NodeJS.Architecture): Architecture {\n switch (arch) {\n case 'x64':\n return Architecture.X86_64;\n case 'arm64':\n return Architecture.Arm64;\n default:\n return Architecture.None;\n }\n}\n\n/**\n * Converts a Platform enum value to a human-readable string.\n *\n * @param platform - The platform enum value\n * @returns The platform name as a string\n */\nexport function platformToString(platform: Platform): string {\n switch (platform) {\n case Platform.MacOS:\n return 'macos';\n case Platform.Linux:\n return 'linux';\n case Platform.Windows:\n return 'windows';\n case Platform.None:\n return 'none';\n default:\n return 'unknown';\n }\n}\n\n/**\n * Converts an Architecture enum value to a human-readable string.\n *\n * @param arch - The architecture enum value\n * @returns The architecture name as a string\n */\nexport function architectureToString(arch: Architecture): string {\n switch (arch) {\n case Architecture.X86_64:\n return 'x86_64';\n case Architecture.Arm64:\n return 'arm64';\n case Architecture.None:\n return 'none';\n default:\n return 'unknown';\n }\n}\n",
|
|
23
|
+
"import type { PartialDeep } from '@dotfiles/core';\nimport { z } from 'zod';\n\n/**\n * Creates a Zod schema for cache configuration.\n *\n * This factory function is used to generate consistent cache settings for\n * different services or hosts, ensuring that they all share the same structure\n * for enabling/disabling caching and setting a time-to-live (TTL).\n *\n * @param defaults - Optional default values for `enabled` and `ttl`.\n * @returns A Zod object schema for cache configuration.\n *\n * @internal\n */\nfunction createCacheSchema(defaults?: { enabled?: boolean; ttl?: number; }) {\n const enabledDefault = defaults?.enabled ?? true;\n const ttlDefault = defaults?.ttl ?? 86400000; // 24 hours in milliseconds\n\n return z\n .object({\n /**\n * Enables or disables caching for the associated service or host.\n * @default true\n */\n enabled: z.boolean().default(enabledDefault),\n /**\n * The time-to-live (TTL) for cache entries, specified in milliseconds.\n * After this duration, the cached data is considered stale.\n * @default 86400000 (24 hours)\n */\n ttl: z.number().default(ttlDefault),\n })\n .strict();\n}\n\n/**\n * A reusable Zod schema for cache configuration with default values.\n *\n * @see {@link createCacheSchema}\n */\nexport const cacheConfigSchema = createCacheSchema();\n\n/**\n * Creates a generic Zod schema for a host configuration.\n *\n * This factory is designed to build schemas for services that interact with a\n * remote host. It includes common settings like the host URL, caching rules,\n * and optional authentication tokens or user-agent strings.\n *\n * @param options - Configuration options for the host schema.\n * @param options.defaultHost - The default URL for the host.\n * @param options.includeToken - If `true`, a `token` field is included.\n * @param options.includeUserAgent - If `true`, a `userAgent` field is included.\n * @param options.defaultUserAgent - The default user-agent string.\n * @param options.tokenDefault - The default value for the `token` field.\n * @returns A Zod object schema for the host configuration.\n *\n * @internal\n */\nfunction createHostSchema(options: {\n defaultHost: string;\n includeToken?: boolean;\n includeUserAgent?: boolean;\n defaultUserAgent?: string;\n tokenDefault?: string;\n}) {\n const {\n defaultHost,\n includeToken,\n includeUserAgent,\n defaultUserAgent = 'dotfiles-generator',\n tokenDefault = '',\n } = options;\n\n return z\n .object({\n /** The base URL of the host. */\n host: z.string().default(defaultHost),\n /** Caching configuration for this host. */\n cache: cacheConfigSchema.default(cacheConfigSchema.parse({})),\n /** An optional authentication token for accessing the host's API. */\n token: z.string().default(includeToken ? tokenDefault : ''),\n /** The User-Agent string to be sent with requests to this host. */\n userAgent: z.string().default(includeUserAgent ? defaultUserAgent : 'dotfiles-generator'),\n })\n .strict();\n}\n\n/**\n * A Zod schema for path configurations within the application.\n *\n * This schema defines all the key directory paths used by the application. It\n * supports variable expansion, allowing paths to be defined relative to each\n * other or to environment variables.\n *\n * ### Variable Expansion\n *\n * Path variables can reference environment variables (e.g., `{HOME}`) and other\n * paths defined within this schema (e.g., `{paths.dotfilesDir}`).\n *\n * The resolution order is critical and follows this staged model:\n *\n * ```\n * 1. Bootstrap: {HOME} (from system environment)\n * 2. Resolve: paths.homeDir using bootstrap {HOME}\n * 3. Post-home: Remaining {TOKEN} substitution using resolved paths.homeDir\n * 4. Tilde: ~ expansion (only in paths.* fields) using paths.homeDir\n * └── dotfilesDir\n * ├── toolConfigsDir\n * └── generatedDir\n * ├── shellScriptsDir\n * └── binariesDir\n *\n * targetDir (independent)\n * ```\n */\nconst pathsConfigSchema = z\n .object({\n /**\n * The user's home directory.\n * @default `{HOME}` (resolved from the environment variable)\n */\n homeDir: z.string().default(`{HOME}`),\n\n /**\n * The root directory of the user's dotfiles repository.\n * It is strongly recommended to set this value.\n * @default `{configFileDir}` (the directory containing the config file)\n */\n dotfilesDir: z.string().default(`{configFileDir}`),\n\n /**\n * The directory where executable shims for tools will be placed. This\n * directory should be in the system's `PATH`.\n * @default `{paths.generatedDir}/bin-default`\n */\n targetDir: z.string().default(`{paths.generatedDir}/bin-default`),\n\n /**\n * The directory where all generated files (e.g., binaries, shell scripts)\n * will be stored.\n * @default `{paths.dotfilesDir}/.generated`\n */\n generatedDir: z.string().default(`{paths.dotfilesDir}/.generated`),\n\n /**\n * The directory containing `*.tool.ts` tool configuration files.\n * @default `{paths.dotfilesDir}/tools`\n */\n toolConfigsDir: z.string().default(`{paths.dotfilesDir}/tools`),\n\n /**\n * The directory where generated shell initialization scripts are stored.\n * @default `{paths.generatedDir}/shell-scripts`\n */\n shellScriptsDir: z.string().default(`{paths.generatedDir}/shell-scripts`),\n\n /**\n * The directory where downloaded tool binaries and archives are stored.\n * @default `{paths.generatedDir}/binaries`\n */\n binariesDir: z.string().default(`{paths.generatedDir}/binaries`),\n })\n .strict();\n\n/**\n * A Zod schema for system-level configurations.\n */\nconst systemConfigSchema = z\n .object({\n /**\n * A custom prompt message to display when `sudo` is required for a command.\n * @default 'Please enter your password to continue:'\n */\n sudoPrompt: z.string().default('Please enter your password to continue:'),\n })\n .strict();\n\n/**\n * A Zod schema for logging configurations.\n */\nconst loggingConfigSchema = z\n .object({\n /**\n * A string controlling debug logging output. Can be used to enable debug\n * logs for specific modules.\n * @default ''\n */\n debug: z.string().default(''),\n })\n .strict();\n\n/**\n * A Zod schema for tool update configurations.\n */\nconst updatesConfigSchema = z\n .object({\n /**\n * If `true`, the application will automatically check for tool updates\n * during certain runs.\n * @default true\n */\n checkOnRun: z.boolean().default(true),\n /**\n * The interval in seconds between automatic update checks for tools.\n * @default 86400 (24 hours)\n */\n checkInterval: z.number().default(86400),\n })\n .strict();\n\n/**\n * A Zod schema for GitHub API configuration.\n *\n * This schema includes the API host, authentication token, user-agent, and\n * caching settings for interactions with GitHub.\n *\n * @see {@link createHostSchema}\n */\nconst gitHubConfigSchema = createHostSchema({\n defaultHost: 'https://api.github.com',\n includeToken: true,\n includeUserAgent: true,\n});\n\n// Define individual host schemas for cargo with their own defaults so that cargoConfigSchema.parse({}) succeeds\nconst cargoCratesIoHostSchema = createHostSchema({ defaultHost: 'https://crates.io' });\nconst cargoGithubRawHostSchema = createHostSchema({ defaultHost: 'https://raw.githubusercontent.com' });\nconst cargoGithubReleaseHostSchema = createHostSchema({ defaultHost: 'https://github.com' });\n\n/**\n * A Zod schema for Cargo (Rust package manager) related configurations.\n *\n * This schema defines settings for interacting with different services that\n * Cargo uses, such as `crates.io` and GitHub for fetching package information\n * and release assets.\n */\nconst cargoConfigSchema = z\n .object({\n /**\n * Configuration for the `crates.io` API host.\n */\n cratesIo: cargoCratesIoHostSchema.default(cargoCratesIoHostSchema.parse({})),\n /**\n * Configuration for accessing raw file content on GitHub, typically for\n * reading `Cargo.toml` files.\n */\n githubRaw: cargoGithubRawHostSchema.default(cargoGithubRawHostSchema.parse({})),\n /**\n * Configuration for accessing GitHub releases and downloading assets.\n */\n githubRelease: cargoGithubReleaseHostSchema.default(cargoGithubReleaseHostSchema.parse({})),\n /**\n * A custom User-Agent string for requests made by the Cargo client.\n * @default 'dotfiles-generator'\n */\n userAgent: z.string().default('dotfiles-generator'),\n })\n .strict();\n\n/**\n * A Zod schema for the asset downloader configuration.\n */\nconst downloaderConfigSchema = z\n .object({\n /**\n * The timeout in milliseconds for download operations.\n * @default 300000 (5 minutes)\n */\n timeout: z.number().default(300000),\n /**\n * The number of times to retry a failed download.\n * @default 3\n */\n retryCount: z.number().default(3),\n /**\n * The delay in milliseconds between download retry attempts.\n * @default 1000 (1 second)\n */\n retryDelay: z.number().default(1000),\n /**\n * Caching configuration for downloaded files.\n */\n cache: cacheConfigSchema.default(cacheConfigSchema.parse({})),\n })\n .strict();\n\n/**\n * A Zod schema for feature-specific configurations.\n */\nconst featuresConfigSchema = z\n .object({\n /**\n * Configuration for the catalog feature, which generates a markdown file\n * listing all managed tools.\n */\n catalog: z\n .object({\n /**\n * If `true`, the catalog file will be generated.\n * @default true\n */\n generate: z.boolean().default(true),\n /**\n * The path where the catalog file will be generated. Supports variable\n * expansion.\n * @default `{paths.dotfilesDir}/CATALOG.md`\n */\n filePath: z.string().default(`{paths.dotfilesDir}/CATALOG.md`),\n })\n .strict()\n .default({ generate: true, filePath: `{paths.dotfilesDir}/CATALOG.md` }),\n /**\n * Configuration for shell initialization.\n * Controls where the shell initialization scripts are sourced.\n */\n shellInstall: z\n .object({\n /**\n * The path to the zsh configuration file (e.g., ~/.zshrc).\n * If not provided, zsh initialization will be skipped.\n */\n zsh: z.string().optional(),\n /**\n * The path to the bash configuration file (e.g., ~/.bashrc).\n * If not provided, bash initialization will be skipped.\n */\n bash: z.string().optional(),\n /**\n * The path to the powershell configuration file (e.g., ~/.config/powershell/profile.ps1).\n * If not provided, powershell initialization will be skipped.\n */\n powershell: z.string().optional(),\n })\n .strict()\n .optional(),\n })\n .strict();\n\n/**\n * An array of supported operating system identifiers.\n */\nexport const OS_VALUES = ['macos', 'linux', 'windows'] as const;\n\n/**\n * An array of supported CPU architecture identifiers.\n */\nexport const ARCH_VALUES = ['x86_64', 'arm64'] as const;\n\n/**\n * A Zod schema for matching platform-specific criteria (OS and architecture).\n *\n * This schema is used in platform overrides to target configurations to\n * specific environments.\n *\n * @internal\n */\nconst platformMatchSchema = z.union([\n z\n .object({\n os: z.enum(OS_VALUES),\n arch: z.enum(ARCH_VALUES).optional(),\n })\n .strict(),\n z\n .object({\n os: z.enum(OS_VALUES).optional(),\n arch: z.enum(ARCH_VALUES),\n })\n .strict(),\n]);\n\n/**\n * The base Zod schema for the main application configuration, with all\n * properties required and default values applied.\n *\n * @internal\n */\nconst baseProjectConfigSchemaRequired = z\n .object({\n paths: pathsConfigSchema.required().default(pathsConfigSchema.parse({})),\n system: systemConfigSchema.required().default(systemConfigSchema.parse({})),\n logging: loggingConfigSchema.required().default(loggingConfigSchema.parse({})),\n updates: updatesConfigSchema.required().default(updatesConfigSchema.parse({})),\n github: gitHubConfigSchema.required().default(gitHubConfigSchema.parse({})),\n cargo: cargoConfigSchema.required().default(cargoConfigSchema.parse({})),\n downloader: downloaderConfigSchema.required().default(downloaderConfigSchema.parse({})),\n features: featuresConfigSchema.default(featuresConfigSchema.parse({})),\n })\n .strict();\n\n/**\n * A partial version of the base Zod schema, used for platform-specific\n * overrides where only a subset of properties may be provided.\n *\n * @internal\n */\nconst baseProjectConfigSchemaPartial = z\n .object({\n paths: pathsConfigSchema.partial().optional(),\n system: systemConfigSchema.partial().optional(),\n logging: loggingConfigSchema.partial().optional(),\n updates: updatesConfigSchema.partial().optional(),\n github: gitHubConfigSchema.partial().optional(),\n cargo: cargoConfigSchema.partial().optional(),\n downloader: downloaderConfigSchema.partial().optional(),\n features: featuresConfigSchema.partial().optional(),\n })\n .strict();\n\n/**\n * A Zod schema for a platform-specific override configuration.\n *\n * This allows certain configuration values to be applied only when the\n * specified OS and/or architecture conditions are met.\n *\n * @internal\n */\nconst platformOverrideSchema = z\n .object({\n /** An array of platform-matching criteria. */\n match: z.array(platformMatchSchema).nonempty(),\n /** The partial configuration to apply if the criteria match. */\n get config() {\n return baseProjectConfigSchemaPartial.partial();\n },\n })\n .strict();\n\n/**\n * The main Zod schema for the application's `config.ts` file.\n *\n * This schema combines the base configuration with an optional array of\n * platform-specific overrides. It is used to parse and validate the entire\n * configuration file.\n */\nexport const projectConfigSchema = baseProjectConfigSchemaRequired\n .extend({\n /**\n * An optional array of platform-specific overrides. Each override applies\n * a partial configuration when the specified OS and/or architecture matches\n * the current system.\n */\n platform: z.array(platformOverrideSchema).optional(),\n })\n .strict();\n\n/**\n * A TypeScript type representing the paths section of the YAML configuration.\n */\nexport type ProjectConfigPaths = z.infer<typeof pathsConfigSchema>;\n\n/**\n * A deep partial TypeScript type for the project configuration.\n *\n * This is useful for functions that merge or override configuration values,\n * allowing any part of the configuration to be optionally provided.\n */\nexport type ProjectConfigPartial = PartialDeep<ProjectConfig>;\n\n/**\n * A Zod schema for private fields that are added to the configuration object\n * after it is loaded. These fields are not part of the user-facing `config.ts`.\n *\n * @internal\n */\nexport const privateProjectConfigFields = z.object({\n /** The absolute path to the loaded configuration file. */\n configFilePath: z.string(),\n /** The absolute path to the directory containing the configuration file. */\n configFileDir: z.string(),\n});\n\n/**\n * The complete TypeScript type for the application's configuration object.\n *\n * This type is inferred from the main Zod schema and includes both the\n * user-defined configuration from `config.ts` and the private, internally-managed\n * fields.\n */\nexport type ProjectConfig = z.infer<typeof projectConfigSchema> & z.infer<typeof privateProjectConfigFields>;\n",
|
|
24
|
+
"import type { IResolvedFileSystem } from '@dotfiles/file-system';\nimport type { SafeLogMessage, TsLogger } from '@dotfiles/logger';\nimport type { ReplaceInFilePattern, ReplaceInFileReplacer } from '@dotfiles/utils';\nimport { replaceInFile } from '@dotfiles/utils';\nimport { Glob } from 'bun';\nimport path from 'node:path';\nimport type { IToolConfigContext } from '../builder';\nimport type {\n BoundReplaceInFile,\n BoundResolve,\n IBoundReplaceInFileOptions,\n ISystemInfo,\n IToolLog,\n} from '../common';\nimport type { ProjectConfig } from '../config';\nimport { messages } from '../log-messages';\n\n/**\n * Error thrown when a resolve pattern matches zero or multiple paths.\n */\nexport class ResolveError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ResolveError';\n }\n}\n\n/**\n * Creates a user-facing logger wrapper that accepts plain strings.\n *\n * Wraps the internal SafeLogger to provide a simplified API for tool configurations.\n * The wrapper converts plain strings to SafeLogMessage format and uses toolName as context.\n *\n * @param logger - The parent logger instance\n * @param toolName - The tool name to use as context prefix\n * @returns An IToolLog instance for user-facing logging\n */\nexport function createToolLog(logger: TsLogger, toolName: string): IToolLog {\n const toolLogger = logger.getSubLogger({ context: toolName });\n\n const toolLog: IToolLog = {\n trace: (message: string): void => {\n toolLogger.trace(message as SafeLogMessage);\n },\n debug: (message: string): void => {\n toolLogger.debug(message as SafeLogMessage);\n },\n info: (message: string): void => {\n toolLogger.info(message as SafeLogMessage);\n },\n warn: (message: string): void => {\n toolLogger.warn(message as SafeLogMessage);\n },\n error: (message: string, error?: unknown): void => {\n if (error !== undefined) {\n toolLogger.error(message as SafeLogMessage, error);\n } else {\n toolLogger.error(message as SafeLogMessage);\n }\n },\n };\n\n return toolLog;\n}\n\nexport function createToolConfigContext(\n projectConfig: ProjectConfig,\n systemInfo: ISystemInfo,\n toolName: string,\n toolDir: string,\n fileSystem: IResolvedFileSystem,\n logger: TsLogger,\n): IToolConfigContext {\n const currentDir = path.join(projectConfig.paths.binariesDir, toolName, 'current');\n\n const normalizedSystemInfo: ISystemInfo = {\n platform: systemInfo.platform,\n arch: systemInfo.arch,\n homeDir: projectConfig.paths.homeDir,\n hostname: systemInfo.hostname,\n };\n\n const boundReplaceInFile: BoundReplaceInFile = async (\n filePath: string,\n from: ReplaceInFilePattern,\n to: ReplaceInFileReplacer,\n options?: IBoundReplaceInFileOptions,\n ): Promise<boolean> => {\n const wasReplaced = await replaceInFile(fileSystem, filePath, from, to, options);\n\n if (!wasReplaced && options?.errorMessage) {\n const patternString = from instanceof RegExp ? from.source : from;\n logger.error(messages.replaceInFileNoMatch(patternString, filePath));\n }\n\n return wasReplaced;\n };\n\n const boundResolve: BoundResolve = (pattern: string): string => {\n // Determine the base directory for the glob scan\n const isAbsolute = path.isAbsolute(pattern);\n\n let cwd: string;\n let globPattern: string;\n\n if (isAbsolute) {\n // For absolute patterns, use the parent directory of the first path segment\n // that contains a glob character, or the directory containing the file\n const dirname = path.dirname(pattern);\n const basename = path.basename(pattern);\n\n // Check if dirname contains glob characters\n const hasGlobInDir = /[*?[\\]{]/.test(dirname);\n\n if (hasGlobInDir) {\n // Find the first non-glob part of the path to use as cwd\n const parts = pattern.split(path.sep);\n const cwdParts: string[] = [];\n const patternParts: string[] = [];\n let foundGlob = false;\n\n for (const part of parts) {\n if (!foundGlob && !/[*?[\\]{]/.test(part)) {\n cwdParts.push(part);\n } else {\n foundGlob = true;\n patternParts.push(part);\n }\n }\n\n cwd = cwdParts.join(path.sep) || '/';\n globPattern = patternParts.join(path.sep);\n } else {\n // Glob is only in the basename\n cwd = dirname;\n globPattern = basename;\n }\n } else {\n cwd = toolDir;\n globPattern = pattern;\n }\n\n const glob = new Glob(globPattern);\n const matches: string[] = [];\n\n for (const match of glob.scanSync({ cwd, onlyFiles: false })) {\n matches.push(path.join(cwd, match));\n }\n\n if (matches.length === 0) {\n logger.error(messages.resolveNoMatches(pattern));\n throw new ResolveError(`No matches found for pattern: ${pattern}`);\n }\n\n if (matches.length > 1) {\n const matchList = matches.slice(0, 5).join(', ') + (matches.length > 5 ? ', ...' : '');\n logger.error(messages.resolveMultipleMatches(pattern, matches.length, matchList));\n throw new ResolveError(`Pattern '${pattern}' matched ${matches.length} paths (expected exactly 1)`);\n }\n\n const firstMatch = matches[0];\n if (firstMatch === undefined) {\n logger.error(messages.resolveNoMatches(pattern));\n throw new ResolveError(`No matches found for pattern: ${pattern}`);\n }\n\n return firstMatch;\n };\n\n const toolLog = createToolLog(logger, toolName);\n\n const context: IToolConfigContext = {\n toolName,\n toolDir,\n currentDir,\n projectConfig,\n systemInfo: normalizedSystemInfo,\n replaceInFile: boundReplaceInFile,\n resolve: boundResolve,\n log: toolLog,\n };\n\n return context;\n}\n",
|
|
25
|
+
"import type { SafeLogMessage } from './types';\n\n/**\n * Creates a `SafeLogMessage`, a branded type that represents a string that is\n * safe for logging and does not contain sensitive information.\n *\n * This function should only be used within template functions that are\n * responsible for constructing log messages. It serves as a type assertion to\n * indicate that the message has been properly sanitized or constructed.\n *\n * @param message - The string to be branded as a `SafeLogMessage`.\n * @returns The message as a {@link SafeLogMessage}.\n *\n * @example\n * ```typescript\n * import { createSafeLogMessage } from '@dotfiles/logger';\n *\n * function userLoginMessage(userId: string) {\n * // In a real-world scenario, you would ensure that userId is not sensitive\n * // or that it is properly anonymized before creating the message.\n * return createSafeLogMessage(`User ${userId} logged in.`);\n * }\n * ```\n *\n * @see {@link SafeLogMessage}\n * @see {@link isSafeLogMessage}\n */\nexport function createSafeLogMessage(message: string): SafeLogMessage {\n return message as SafeLogMessage;\n}\n",
|
|
26
|
+
"/**\n * Defines the log levels for controlling the verbosity of application output.\n *\n * Each level corresponds to a specific `tslog` level number, determining which\n * messages are displayed based on their severity.\n *\n * @property {number} VERBOSE - Level 1: Equivalent to a debug level, showing debug, info, warn, error, and fatal messages.\n * @property {number} DEFAULT - Level 3: The standard info level, showing info, warn, error, and fatal messages.\n * @property {number} QUIET - Level 5: The error level, showing only error and fatal messages.\n *\n * @example\n * ```typescript\n * import { LogLevel } from '@dotfiles/logger';\n *\n * const logLevel = LogLevel.VERBOSE;\n * ```\n */\nexport const LogLevel = {\n VERBOSE: 1,\n DEFAULT: 3,\n QUIET: 5,\n} as const;\n\n/**\n * Represents the numeric value of a log level.\n */\nexport type LogLevelValue = (typeof LogLevel)[keyof typeof LogLevel];\n\n/**\n * Defines the valid string representations of log levels, typically used for\n * parsing command-line arguments or configuration values.\n */\nexport const LOG_LEVEL_NAMES = ['verbose', 'default', 'quiet'] as const;\n\n/**\n * A type representing the string name of a log level.\n */\nexport type LogLevelName = (typeof LOG_LEVEL_NAMES)[number];\n\n/**\n * A mapping from log level names to their corresponding numeric values.\n *\n * @example\n * ```typescript\n * import { LOG_LEVEL_MAP } from '@dotfiles/logger';\n *\n * const logLevel = LOG_LEVEL_MAP['verbose']; // 1\n * ```\n */\nexport const LOG_LEVEL_MAP: Record<LogLevelName, LogLevelValue> = {\n verbose: LogLevel.VERBOSE,\n default: LogLevel.DEFAULT,\n quiet: LogLevel.QUIET,\n};\n\n/**\n * Parses a log level name string and returns its corresponding numeric value.\n *\n * The function is case-insensitive. If the provided name is not a valid log\n * level, it throws an error.\n *\n * @param levelName - The log level name to parse (e.g., 'trace', 'verbose').\n * @returns The numeric {@link LogLevelValue}.\n * @throws An `Error` if the `levelName` is invalid.\n *\n * @example\n * ```typescript\n * import { parseLogLevel } from '@dotfiles/logger';\n *\n * const logLevel = parseLogLevel('VERBOSE'); // 1\n * ```\n */\nexport function parseLogLevel(levelName: string): LogLevelValue {\n const normalizedName = levelName.toLowerCase() as LogLevelName;\n\n if (!(normalizedName in LOG_LEVEL_MAP)) {\n throw new Error(`Invalid log level: ${levelName}. Valid levels are: ${LOG_LEVEL_NAMES.join(', ')}`);\n }\n\n return LOG_LEVEL_MAP[normalizedName];\n}\n",
|
|
27
|
+
"import { type ILogObjMeta, type ISettingsParam, Logger } from 'tslog';\nimport type { ZodError } from 'zod';\nimport { formatErrorForUser, isError } from './filterErrorStack';\nimport { formatZodErrors } from './formatZodErrors';\nimport type { SafeLogMessage } from './types';\n\n/**\n * Extended settings for SafeLogger that adds context support.\n */\nexport interface ISafeLoggerSettings<LogObj> extends ISettingsParam<LogObj> {\n /**\n * A context string to prepend to log messages as `[context]`.\n * Multiple contexts from parent loggers are chained together.\n * Uses tslog's built-in `prefix` array internally.\n */\n context?: string;\n /**\n * Whether source location tracing is enabled.\n */\n trace?: boolean;\n}\n\n/**\n * Wraps a context string with brackets for display.\n */\nfunction formatContext(context: string): string {\n return `[${context}]`;\n}\n\n/**\n * A type-safe logger that extends `tslog`'s `Logger` to enforce the use of\n * {@link SafeLogMessage} objects for all log messages.\n *\n * This class prevents raw strings from being passed to log methods, ensuring\n * that all logged messages are constructed through approved template functions.\n *\n * Supports context strings that are prepended to log messages as `[context]`.\n * Multiple contexts from parent loggers are chained together (e.g., `[Parent][Child]`).\n * Uses tslog's built-in `prefix` array internally.\n */\nexport class SafeLogger<LogObj = unknown> extends Logger<LogObj> {\n private readonly _trace: boolean;\n\n constructor(settings?: ISafeLoggerSettings<LogObj>) {\n const existingPrefix = settings?.prefix ?? [];\n const prefix: string[] = existingPrefix.map((p) => String(p));\n if (settings?.context) {\n prefix.push(formatContext(settings.context));\n }\n super({ ...settings, prefix });\n this._trace = settings?.trace === true;\n }\n\n /**\n * Checks if this logger has tracing enabled.\n * When enabled, full error stacks and source locations are shown.\n */\n public isTracingEnabled(): boolean {\n return this._trace;\n }\n\n /**\n * Processes error arguments based on tracing mode.\n * In trace mode, error objects pass through unchanged for full debugging.\n * In non-trace mode, errors are replaced with a formatted string showing\n * only .tool.ts file:line locations. If no .tool.ts frames exist, the\n * error is dropped entirely — the SafeLogMessage already describes the failure.\n */\n private filterErrorArgs(args: unknown[]): unknown[] {\n if (this.isTracingEnabled()) {\n return args;\n }\n\n const filteredArgs: unknown[] = [];\n for (const arg of args) {\n if (isError(arg)) {\n const formatted = formatErrorForUser(arg);\n if (formatted) {\n filteredArgs.push(formatted);\n }\n } else {\n filteredArgs.push(arg);\n }\n }\n return filteredArgs;\n }\n\n /** @inheritdoc */\n override trace(message: SafeLogMessage, ...args: unknown[]): (LogObj & ILogObjMeta) | undefined {\n return super.trace(message as string, ...args);\n }\n\n /** @inheritdoc */\n override debug(message: SafeLogMessage, ...args: unknown[]): (LogObj & ILogObjMeta) | undefined {\n return super.debug(message as string, ...args);\n }\n\n /** @inheritdoc */\n override info(message: SafeLogMessage, ...args: unknown[]): (LogObj & ILogObjMeta) | undefined {\n const filteredArgs = this.filterErrorArgs(args);\n return super.info(message as string, ...filteredArgs);\n }\n\n /** @inheritdoc */\n override warn(message: SafeLogMessage, ...args: unknown[]): (LogObj & ILogObjMeta) | undefined {\n const filteredArgs = this.filterErrorArgs(args);\n return super.warn(message as string, ...filteredArgs);\n }\n\n /** @inheritdoc */\n override error(message: SafeLogMessage, ...args: unknown[]): (LogObj & ILogObjMeta) | undefined {\n const filteredArgs = this.filterErrorArgs(args);\n return super.error(message as string, ...filteredArgs);\n }\n\n /** @inheritdoc */\n override fatal(message: SafeLogMessage, ...args: unknown[]): (LogObj & ILogObjMeta) | undefined {\n const filteredArgs = this.filterErrorArgs(args);\n return super.fatal(message as string, ...filteredArgs);\n }\n\n /** @inheritdoc */\n override getSubLogger(settings?: ISafeLoggerSettings<LogObj>): SafeLogger<LogObj> {\n const parentNames = [...(this.settings.parentNames ?? [])];\n // Only add parent name to hierarchy when creating a named sublogger\n // Context-only subloggers don't create a new hierarchy level\n if (this.settings.name && settings?.name) {\n parentNames.push(this.settings.name);\n }\n\n return new SafeLogger({\n ...this.settings,\n ...settings,\n parentNames,\n });\n }\n\n /**\n * Logs Zod validation errors in a readable format using the `error` log level.\n * @param error - The `ZodError` object to log.\n */\n zodErrors(error: ZodError): void {\n const messages = formatZodErrors(error);\n for (const message of messages) {\n this.error(message as SafeLogMessage);\n }\n }\n\n /**\n * Sets the prefix for this logger instance.\n * The prefix is prepended to all log messages.\n *\n * @param context - A context string to set as the prefix (will be wrapped as `[context]`)\n * @returns This logger instance for chaining\n *\n * @example\n * ```typescript\n * const logger = parentLogger.getSubLogger({ name: 'method' });\n * logger.setPrefix('toolName');\n * logger.info(messages.installing()); // Output: [toolName] Installing...\n * ```\n */\n setPrefix(context: string): this {\n this.settings.prefix = [formatContext(context)];\n return this;\n }\n}\n",
|
|
28
|
+
"const TOOL_FILE_PATTERN = /\\.tool\\.(ts|js)/;\n\n/**\n * Regex to extract filename and line number from a stack frame.\n * Matches patterns like: `at fn (/path/to/file.tool.ts:14:13)`\n * Captures: [1] = filename (e.g. `flux.tool.ts`), [2] = line number\n */\nconst FRAME_LOCATION_PATTERN = /([^/\\\\]+\\.tool\\.(?:ts|js)):(\\d+)/;\n\n/**\n * Checks if a stack frame is from a .tool.ts file.\n */\nfunction isToolFrame(frameLine: string): boolean {\n return TOOL_FILE_PATTERN.test(frameLine);\n}\n\n/**\n * Extracts `filename:line` from a stack frame string.\n * Returns null if the frame doesn't match the expected pattern.\n */\nfunction extractLocation(frameLine: string): string | null {\n const match = FRAME_LOCATION_PATTERN.exec(frameLine);\n if (!match) {\n return null;\n }\n return `${match[1]}:${match[2]}`;\n}\n\n/**\n * Extracts .tool.ts file locations from a stack trace string.\n * Returns an array of `filename:line` strings (e.g. `[\"flux.tool.ts:14\"]`).\n */\nexport function extractToolFileLocations(stack: string | undefined): string[] {\n if (!stack) {\n return [];\n }\n\n const lines = stack.split('\\n');\n const locations: string[] = [];\n\n for (const line of lines) {\n if (isToolFrame(line)) {\n const location = extractLocation(line);\n if (location) {\n locations.push(location);\n }\n }\n }\n\n return locations;\n}\n\n/**\n * Formats an error for user-facing output in non-trace mode.\n * Returns a string with .tool.ts file:line locations, or null if\n * no .tool.ts frames exist (meaning the error is purely internal).\n *\n * Output format: `(flux.tool.ts:14)` or `(flux.tool.ts:14, navi.tool.ts:8)`\n */\nexport function formatErrorForUser(error: Error): string | null {\n const stack = typeof error.stack === 'string' ? error.stack : undefined;\n const locations = extractToolFileLocations(stack);\n\n if (locations.length === 0) {\n return null;\n }\n\n return `(${locations.join(', ')})`;\n}\n\n/**\n * Type guard to check if a value is an Error object.\n */\nexport function isError(value: unknown): value is Error {\n return value instanceof Error;\n}\n",
|
|
29
|
+
"import type { ZodError } from 'zod';\n\n/**\n * Formats Zod validation errors into an array of human-readable log messages.\n *\n * This function processes a `ZodError` object, sorting the issues by path\n * length and generating a clear, formatted message for each issue.\n *\n * @param error - The `ZodError` object to format.\n * @returns An array of formatted error messages.\n *\n * @example\n * ```typescript\n * import { formatZodErrors } from '@dotfiles/logger';\n * import { z } from 'zod';\n *\n * const schema = z.object({\n * name: z.string().min(1),\n * email: z.string().email(),\n * });\n *\n * try {\n * schema.parse({ name: '', email: 'invalid' });\n * } catch (error) {\n * if (error instanceof z.ZodError) {\n * const messages = formatZodErrors(error);\n * messages.forEach(msg => console.error(msg));\n * }\n * }\n * // Output:\n * // ✖ String must contain at least 1 character(s)\n * // → at name\n * // ✖ Invalid email\n * // → at email\n * ```\n *\n * @internal\n */\nexport function formatZodErrors(error: ZodError): string[] {\n const messages: string[] = [];\n // sort by path length\n const issues = [...error.issues].toSorted((a, b) => (a.path ?? []).length - (b.path ?? []).length);\n\n // Process each issue\n for (const issue of issues) {\n messages.push(`✖ ${issue.message}`);\n if (issue.path?.length) {\n const dotPath = issue.path.map(String).join('.');\n messages.push(` → at ${dotPath}`);\n }\n }\n\n return messages;\n}\n",
|
|
30
|
+
"import { LogLevel } from './LogLevel';\nimport { SafeLogger } from './SafeLogger';\nimport type { ILoggerConfig, TsLogger } from './types';\n\n/**\n * Creates a type-safe `tslog` logger instance with a configurable name and log level.\n *\n * The returned {@link SafeLogger} only accepts {@link SafeLogMessage} objects as the\n * first argument to its log methods, preventing arbitrary strings from being\n * logged. This ensures that all log messages are constructed through\n * predefined template functions.\n *\n * @param name - The name of the logger.\n * @returns A {@link TsLogger} instance.\n *\n * @example\n * ```typescript\n * import { createTsLogger, messages } from '@dotfiles/logger';\n *\n * const logger = createTsLogger('my-app');\n * logger.info(messages.info.appStarted());\n * ```\n */\nexport function createTsLogger(name: string): TsLogger;\n/**\n * Creates a type-safe `tslog` logger instance with a configurable name and log level.\n *\n * The returned {@link SafeLogger} only accepts {@link SafeLogMessage} objects as the\n * first argument to its log methods, preventing arbitrary strings from being\n * logged. This ensures that all log messages are constructed through\n * predefined template functions.\n *\n * @param config - The logger configuration.\n * @returns A {@link TsLogger} instance.\n *\n * @example\n * ```typescript\n * import { createTsLogger, LogLevel, messages } from '@dotfiles/logger';\n *\n * const logger = createTsLogger({ name: 'my-app', level: LogLevel.VERBOSE });\n * logger.debug(messages.debug.configLoaded({ config: { setting: 'value' } }));\n * ```\n */\nexport function createTsLogger(config: ILoggerConfig): TsLogger;\nexport function createTsLogger(configOrName: ILoggerConfig | string): TsLogger {\n let config: ILoggerConfig = {} as ILoggerConfig;\n\n if (typeof configOrName === 'string') {\n config.name = configOrName;\n } else {\n config = { ...configOrName };\n }\n\n config.level = config.level ?? LogLevel.DEFAULT;\n\n const prettyLogTemplate = config.trace ? '{{logLevelName}}\\t{{filePathWithLine}} - ' : '{{logLevelName}}\\t';\n\n const isColorDisabled = process.env['NO_COLOR'] === '1' || process.env['TERM'] === 'dumb';\n\n return new SafeLogger({\n name: config.name,\n minLevel: config.level,\n trace: config.trace,\n prettyLogTemplate,\n stylePrettyLogs: !isColorDisabled,\n\n hideLogPositionForProduction: false,\n\n prettyErrorTemplate: '\\n{{errorName}} {{errorMessage}}\\nerror stack:\\n{{errorStack}}',\n\n prettyLogStyles: {\n logLevelName: {\n FATAL: ['bold', 'red'],\n ERROR: ['bold', 'red'],\n WARN: ['bold', 'yellow'],\n INFO: ['bold', 'blue'],\n DEBUG: ['bold', 'white'],\n TRACE: ['bold', 'white'],\n },\n },\n\n prettyInspectOptions: {\n breakLength: Infinity,\n compact: true,\n },\n });\n}\n",
|
|
31
|
+
"import { LogLevel, type LogLevelValue, parseLogLevel } from './LogLevel';\n\n/**\n * Determines the appropriate log level based on command-line flags.\n *\n * This function interprets a combination of `--log`, `--quiet`, and `--verbose`\n * flags to determine the final log level. The `--quiet` and `--verbose` flags\n * act as aliases for `--log=quiet` and `--log=verbose`, respectively, and take\n * precedence over the `--log` flag.\n *\n * @param log - The value of the `--log` flag (e.g., 'default', 'verbose').\n * @param quiet - A boolean indicating if the `--quiet` flag is present.\n * @param verbose - A boolean indicating if the `--verbose` flag is present.\n * @returns The appropriate {@link LogLevelValue}.\n *\n * @example\n * ```typescript\n * import { getLogLevelFromFlags, LogLevel } from '@dotfiles/logger';\n *\n * // --quiet flag is present\n * getLogLevelFromFlags('default', true, false); // LogLevel.QUIET\n *\n * // --verbose flag is present\n * getLogLevelFromFlags('default', false, true); // LogLevel.VERBOSE\n * ```\n *\n * @see {@link LogLevel}\n * @see {@link parseLogLevel}\n */\nexport function getLogLevelFromFlags(log: string, quiet: boolean, verbose: boolean): LogLevelValue {\n // Handle alias flags first\n if (quiet) {\n return LogLevel.QUIET;\n }\n if (verbose) {\n return LogLevel.VERBOSE;\n }\n\n // Parse the --log flag value\n return parseLogLevel(log);\n}\n",
|
|
32
|
+
"import { createSafeLogMessage, type SafeLogMessageMap } from '@dotfiles/logger';\n\nexport const messages = {\n pluginAlreadyRegistered: (method: string) =>\n createSafeLogMessage(`Plugin ${method} is already registered, replacing...`),\n\n pluginRegistered: (method: string, displayName: string, version: string) =>\n createSafeLogMessage(`Registered installer plugin: ${method} (${displayName} v${version})`),\n\n pluginRegistrationFailed: (method: string) => createSafeLogMessage(`Failed to register plugin ${method}`),\n\n schemasComposed: (count: number, methods: string) =>\n createSafeLogMessage(`Composed schemas from ${count} plugins: ${methods}`),\n\n noPluginForMethod: (method: string, availableMethods: string) =>\n createSafeLogMessage(\n `No plugin registered for installation method: ${method}. Available methods: ${availableMethods}`,\n ),\n\n pluginValidationFailed: (errors: string) => createSafeLogMessage(`Plugin validation failed: ${errors}`),\n\n validationFailed: (method: string, errors: string) =>\n createSafeLogMessage(`Plugin validation failed for ${method}: ${errors}`),\n\n validationWarning: (method: string, warning: string) =>\n createSafeLogMessage(`Validation warning for ${method}: ${warning}`),\n\n delegatingToPlugin: (method: string) => createSafeLogMessage(`Delegating installation to plugin: ${method}`),\n\n validationCacheCleared: () => createSafeLogMessage('Validation cache cleared'),\n\n cleaningUpPlugins: () => createSafeLogMessage('Cleaning up plugins...'),\n\n pluginCleanedUp: (method: string) => createSafeLogMessage(`Cleaned up plugin: ${method}`),\n\n pluginCleanupFailed: (method: string) => createSafeLogMessage(`Failed to cleanup plugin ${method}`),\n\n pluginCleanupComplete: () => createSafeLogMessage('Plugin cleanup complete'),\n\n replaceInFileNoMatch: (pattern: string, filePath: string) =>\n createSafeLogMessage(`Could not find '${pattern}' in ${filePath}`),\n\n resolveNoMatches: (pattern: string) => createSafeLogMessage(`No matches found for pattern: ${pattern}`),\n\n resolveMultipleMatches: (pattern: string, count: number, matches: string) =>\n createSafeLogMessage(`Pattern '${pattern}' matched ${count} paths (expected exactly 1): ${matches}`),\n} satisfies SafeLogMessageMap;\n",
|
|
33
|
+
"import type { IInstallContext } from '@dotfiles/core';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { z } from 'zod';\nimport type { PluginEmittedHookEvent } from './builder/builder.types';\nimport { messages } from './log-messages';\nimport type {\n AggregateInstallResult,\n IInstallerPlugin,\n IInstallOptions,\n InstallResult,\n IValidationResult,\n} from './types';\n\ntype InstallEventHandler = (event: InstallEvent) => Promise<void>;\n\nexport interface InstallEvent {\n type: PluginEmittedHookEvent;\n toolName: string;\n context: IInstallContext & Record<string, unknown>;\n}\n\n/**\n * Central registry for installer plugins\n */\nexport class InstallerPluginRegistry {\n private plugins = new Map<string, IInstallerPlugin>();\n private validationCache = new Map<string, IValidationResult>();\n private composedToolConfigSchema?: z.ZodTypeAny;\n private composedInstallParamsSchema?: z.ZodTypeAny;\n private logger: TsLogger;\n private schemasComposed = false;\n private eventHandlers: InstallEventHandler[] = [];\n\n constructor(parentLogger: TsLogger) {\n this.logger = parentLogger.getSubLogger({ name: 'InstallerPluginRegistry' });\n }\n\n /**\n * Register an event handler for installation events\n */\n onEvent(handler: InstallEventHandler): void {\n this.eventHandlers.push(handler);\n }\n\n /**\n * Emit an installation event to all registered handlers\n */\n async emitEvent(event: InstallEvent): Promise<void> {\n for (const handler of this.eventHandlers) {\n await handler(event);\n }\n }\n\n /**\n * Register a plugin (fails fast on error)\n */\n async register<T extends IInstallerPlugin>(plugin: T): Promise<void> {\n const { method } = plugin;\n\n try {\n // Validate plugin\n if (!method || typeof method !== 'string') {\n throw new Error('Plugin must have a valid method name');\n }\n\n if (this.schemasComposed) {\n throw new Error('Cannot register plugins after schemas have been composed');\n }\n\n if (this.plugins.has(method)) {\n this.logger.warn(messages.pluginAlreadyRegistered(method));\n }\n\n // Initialize plugin if needed\n if (plugin.initialize) {\n await plugin.initialize();\n }\n\n this.plugins.set(method, plugin);\n this.logger.debug(messages.pluginRegistered(method, plugin.displayName, plugin.version));\n } catch (error) {\n // Fail fast - don't skip invalid plugins\n this.logger.error(messages.pluginRegistrationFailed(method), error);\n throw new Error(`Plugin registration failed: ${method}`, { cause: error });\n }\n }\n\n /**\n * Get a plugin by method name\n */\n get(method: string): IInstallerPlugin | undefined {\n return this.plugins.get(method);\n }\n\n /**\n * Check if a plugin is registered\n */\n has(method: string): boolean {\n return this.plugins.has(method);\n }\n\n /**\n * Get all registered plugins\n */\n getAll(): IInstallerPlugin[] {\n return Array.from(this.plugins.values());\n }\n\n /**\n * Get all method names\n */\n getMethods(): string[] {\n return Array.from(this.plugins.keys());\n }\n\n /**\n * Get method names for plugins that manage binaries externally (e.g., Homebrew, apt).\n */\n getExternallyManagedMethods(): Set<string> {\n const methods = new Set<string>();\n for (const [method, plugin] of this.plugins) {\n if (plugin.externallyManaged) {\n methods.add(method);\n }\n }\n return methods;\n }\n\n /**\n * Get per-method missing-binary messages for plugins that provide one.\n */\n getMissingBinaryMessagesByMethod(): Map<string, string> {\n const messagesByMethod = new Map<string, string>();\n for (const [method, plugin] of this.plugins) {\n if (plugin.missingBinaryMessage) {\n messagesByMethod.set(method, plugin.missingBinaryMessage);\n }\n }\n return messagesByMethod;\n }\n\n /**\n * Compose schemas from all registered plugins (call once after all plugins registered)\n */\n composeSchemas(): void {\n const plugins = this.getAll();\n\n if (plugins.length === 0) {\n throw new Error('No plugins registered');\n }\n\n // Compose tool config schema\n const toolConfigSchemas = plugins.map((plugin) => plugin.toolConfigSchema);\n if (toolConfigSchemas.length === 1) {\n this.composedToolConfigSchema = toolConfigSchemas[0];\n } else {\n // Zod discriminatedUnion requires specific tuple type [DiscriminableSchema, ...DiscriminableSchema[]]\n // Runtime: all plugin schemas have 'installationMethod' discriminator (enforced by plugin interface)\n // Type: ZodTypeAny cannot satisfy Zod's $ZodTypeDiscriminable constraint, requiring cast\n const [first, ...rest] = toolConfigSchemas;\n // @ts-expect-error: Zod 4 discriminatedUnion requires $ZodTypeDiscriminable but plugins use ZodTypeAny\n this.composedToolConfigSchema = z.discriminatedUnion('installationMethod', [first, ...rest]);\n }\n\n // Compose install params schema\n const paramsSchemas = plugins.map((plugin) => plugin.paramsSchema);\n if (paramsSchemas.length === 1) {\n this.composedInstallParamsSchema = paramsSchemas[0];\n } else {\n // Zod union requires tuple type [Schema, Schema, ...Schema[]] - we verified length > 1\n const [first, second, ...rest] = paramsSchemas;\n // @ts-expect-error: Zod 4 union requires specific tuple but array destructure produces array | undefined\n this.composedInstallParamsSchema = z.union([first, second, ...rest]);\n }\n\n this.schemasComposed = true;\n this.logger.info(messages.schemasComposed(plugins.length, this.getMethods().join(', ')));\n }\n\n /**\n * Get the composed tool config schema (throws if not composed yet)\n */\n getToolConfigSchema(): z.ZodTypeAny {\n if (!this.composedToolConfigSchema) {\n throw new Error('Schemas not composed. Call composeSchemas() after registering all plugins.');\n }\n return this.composedToolConfigSchema;\n }\n\n /**\n * Get the composed install params schema (throws if not composed yet)\n */\n getInstallParamsSchema(): z.ZodTypeAny {\n if (!this.composedInstallParamsSchema) {\n throw new Error('Schemas not composed. Call composeSchemas() after registering all plugins.');\n }\n return this.composedInstallParamsSchema;\n }\n\n /**\n * Validate plugin can run (with caching for static validations)\n */\n private async validatePlugin(\n plugin: IInstallerPlugin,\n context: IInstallContext,\n logger: TsLogger,\n ): Promise<InstallResult | null> {\n if (!plugin.validate) {\n return null;\n }\n\n let validation = this.validationCache.get(plugin.method);\n\n if (!validation) {\n validation = await plugin.validate(context);\n\n if (plugin.staticValidation) {\n this.validationCache.set(plugin.method, validation);\n }\n }\n\n if (!validation.valid) {\n const error = `Plugin validation failed: ${validation.errors?.join(', ')}`;\n logger.error(messages.validationFailed(plugin.method, validation.errors?.join(', ') ?? 'Unknown error'));\n return {\n success: false,\n error,\n };\n }\n\n if (validation.warnings && validation.warnings.length > 0) {\n for (const warning of validation.warnings) {\n logger.warn(messages.validationWarning(plugin.method, warning));\n }\n }\n\n return null;\n }\n\n /**\n * Execute installation using appropriate plugin\n */\n async install(\n parentLogger: TsLogger,\n method: string,\n toolName: string,\n toolConfig: unknown,\n context: IInstallContext,\n options?: IInstallOptions,\n ): Promise<AggregateInstallResult> {\n const logger = parentLogger.getSubLogger({ name: 'InstallerPluginRegistry' }).getSubLogger({ name: 'install' });\n const plugin = this.get(method);\n\n if (!plugin) {\n const error = `No plugin registered for installation method: ${method}. Available methods: ${\n this.getMethods().join(', ')\n }`;\n logger.error(messages.noPluginForMethod(method, this.getMethods().join(', ')));\n return {\n success: false,\n error,\n } as AggregateInstallResult;\n }\n\n const validationError = await this.validatePlugin(plugin, context, logger);\n if (validationError) {\n return validationError as AggregateInstallResult;\n }\n\n logger.debug(messages.delegatingToPlugin(method));\n return (await plugin.install(toolName, toolConfig, context, options, logger)) as AggregateInstallResult;\n }\n\n /**\n * Clear validation cache (useful for testing or when environment changes)\n */\n clearValidationCache(): void {\n this.validationCache.clear();\n this.logger.debug(messages.validationCacheCleared());\n }\n\n /**\n * Cleanup all plugins (useful for graceful shutdown)\n */\n async cleanup(): Promise<void> {\n const logger = this.logger.getSubLogger({ name: 'cleanup' });\n logger.info(messages.cleaningUpPlugins());\n\n const plugins = this.getAll();\n for (const plugin of plugins) {\n if (plugin.cleanup) {\n try {\n await plugin.cleanup();\n logger.debug(messages.pluginCleanedUp(plugin.method));\n } catch (error) {\n logger.error(messages.pluginCleanupFailed(plugin.method), error);\n }\n }\n }\n\n logger.info(messages.pluginCleanupComplete());\n }\n}\n",
|
|
34
|
+
"import { z } from 'zod';\nimport { commonToolConfigPropertiesSchema } from './tool-config';\nimport type { ToolConfig } from './types';\n\n/**\n * Platform configuration schema for runtime validation.\n * Platform configs contain common tool properties that can be overridden per-platform.\n * Additionally, they can override installationMethod and installParams to use different\n * installation strategies per platform.\n */\nexport const platformConfigSchema = commonToolConfigPropertiesSchema\n .extend({\n /** Optional platform-specific installation method override. */\n installationMethod: z.string().optional(),\n /** Optional platform-specific installation parameters override. */\n installParams: z.unknown().optional(),\n })\n .strict();\n\n/**\n * Platform configuration type - contains common tool properties plus optional installation overrides.\n * Used for platform-specific overrides in tool configurations.\n *\n * The runtime schema validates with loose types (string, unknown), but the TypeScript type\n * is properly constrained to match the ToolConfig discriminated union.\n */\nexport type PlatformConfig = Omit<z.infer<typeof platformConfigSchema>, 'installationMethod' | 'installParams'> & {\n installationMethod?: ToolConfig['installationMethod'];\n installParams?: ToolConfig['installParams'];\n};\n",
|
|
35
|
+
"import type { Resolvable } from '@dotfiles/unwrap-value';\nimport { z } from 'zod';\nimport type { IEnvContext } from '../../installer/installHooks.types';\nimport { installHookSchema } from '../hooks/installHookSchema';\nimport type { InstallHook } from '../hooks/installHookSchema';\n\n/**\n * Environment variables type - can be static object or function returning object.\n */\nexport type BaseEnv = Resolvable<IEnvContext, Record<string, string>>;\n\n/**\n * Hook configuration for installation lifecycle events.\n */\nexport interface InstallHooks {\n /** Runs before any other installation steps (download, extract, main install command) begin. */\n 'before-install'?: InstallHook[];\n /** Runs after download but before extraction or execution. */\n 'after-download'?: InstallHook[];\n /** Runs after extraction but before the main binary is finalized. */\n 'after-extract'?: InstallHook[];\n /** Runs after the main installation command completes. */\n 'after-install'?: InstallHook[];\n}\n\nexport const baseInstallParamsSchema = z\n .object({\n /**\n * When true, the tool will be automatically installed during the `generate` command\n * if not already installed. This is useful for tools that must be installed before\n * shell initialization can be generated (e.g., zsh plugins that need to be sourced).\n *\n * Default varies by installer:\n * - `zsh-plugin`: defaults to `true`\n * - All other installers: defaults to `false`\n */\n auto: z.boolean().optional(),\n /**\n * A record of environment variables to be set specifically for the duration of this tool's installation process.\n * These variables are applied before any installation commands or hooks are executed.\n * Can be a static object or a function that receives context and returns the object.\n * @example\n * // Static\n * env: { CUSTOM_FLAG: 'true' }\n * // Dynamic\n * env: (ctx) => ({ INSTALL_DIR: ctx.stagingDir })\n */\n env: z.custom<BaseEnv>().optional(),\n /**\n * A collection of optional asynchronous hook functions that can be executed at different stages\n * of the installation lifecycle.\n *\n * Hooks are specified as arrays of functions with kebab-case keys:\n * - 'before-install', 'after-download', 'after-extract', 'after-install'\n */\n hooks: z\n .object({\n /** Runs before any other installation steps (download, extract, main install command) begin. */\n 'before-install': z.array(installHookSchema).optional(),\n /** Runs after download but before extraction or execution. */\n 'after-download': z.array(installHookSchema).optional(),\n /** Runs after extraction but before the main binary is finalized. */\n 'after-extract': z.array(installHookSchema).optional(),\n /** Runs after the main installation command completes. */\n 'after-install': z.array(installHookSchema).optional(),\n })\n .partial()\n .optional(),\n })\n .strict();\n\n/**\n * Base interface for parameters common to all installation methods.\n * This includes environment variables to set during installation and a set of lifecycle hooks.\n *\n * NOTE: This is an explicit interface (not z.infer) to ensure TypeScript fully resolves\n * the property names, which is required for proper `keyof` behavior in declaration files.\n */\nexport interface BaseInstallParams {\n /**\n * When true, the tool will be automatically installed during the `generate` command\n * if not already installed. This is useful for tools that must be installed before\n * shell initialization can be generated (e.g., zsh plugins that need to be sourced).\n */\n auto?: boolean;\n /**\n * A record of environment variables to be set specifically for the duration of this tool's installation process.\n * Can be a static object or a function that receives context and returns the object.\n */\n env?: BaseEnv;\n /**\n * A collection of optional asynchronous hook functions that can be executed at different stages\n * of the installation lifecycle.\n */\n hooks?: InstallHooks;\n}\n",
|
|
36
|
+
"import { z } from 'zod';\nimport type { AsyncInstallHook, IInstallBaseContext } from '../../installer/installHooks.types';\n\nexport type InstallHook = AsyncInstallHook<IInstallBaseContext>;\n\n// Hook function schema - validates that hooks are functions with correct signature\nexport const installHookSchema = z.custom<InstallHook>((val) => typeof val === 'function', 'Must be a function');\n",
|
|
37
|
+
"import { z } from 'zod';\nimport { commonToolConfigPropertiesSchema } from './commonToolConfigPropertiesSchema';\n\n/**\n * Base properties for tool configurations. This is the foundation schema that all plugin-specific\n * tool configs extend from.\n *\n * Note: platformConfigs are NOT included here because they are a composition concern handled by\n * the plugin system, not individual plugin schemas. Platform configurations are added at the\n * ToolConfig union level in the installer-plugin-system package.\n */\nexport const baseToolConfigPropertiesSchema = commonToolConfigPropertiesSchema\n .extend({\n /** The unique name of the tool, as defined by `c.name()`. */\n name: z.string().min(1),\n /** The desired version of the tool, defined by `c.version()`. Defaults to 'latest'. */\n version: z.string(),\n /** The absolute path to the tool configuration file that defined this configuration. */\n configFilePath: z.string().optional(),\n })\n .strict();\n",
|
|
38
|
+
"import { z } from 'zod';\nimport { shellConfigsSchema } from '../shell/shellConfigsSchema';\nimport { toolConfigUpdateCheckSchema } from '../toolConfigUpdateCheckSchema';\nimport { binaryConfigSchema } from './binaryConfigSchema';\nimport { copyConfigSchema } from './copyConfigSchema';\nimport { symlinkConfigSchema } from './symlinkConfigSchema';\n\n/**\n * Common properties shared between base tool config and platform config schemas.\n * This includes all configurable properties that can be overridden at the platform level.\n */\nexport const commonToolConfigPropertiesSchema = z\n .object({\n /**\n * An array of binary names or configurations that should have shims generated for this tool.\n * Can be simple strings for basic binaries or IBinaryConfig objects for pattern-based location.\n * Defined by `c.bin()`. Can be undefined if no binaries are specified (e.g., for a config-only tool).\n */\n binaries: z.array(z.union([z.string().min(1), binaryConfigSchema])).optional(),\n /** Binary dependencies that must be available before generating this tool. */\n dependencies: z.array(z.string().min(1)).optional(),\n /**\n * When true, the tool is skipped during generation.\n * Useful for temporarily disabling a tool without removing its configuration.\n */\n disabled: z.boolean().optional(),\n /**\n * Hostname pattern to match for this tool.\n * Can be a literal string or a regex pattern (prefixed with / and suffixed with /).\n * When set, the tool is only installed on machines where the hostname matches.\n */\n hostname: z.string().optional(),\n /** The desired version of the tool, defined by `c.version()`. Defaults to 'latest'. */\n version: z.string().optional(),\n /** Shell configurations organized by shell type, added via `c.zsh()`, `c.bash()`, `c.powershell()`. */\n shellConfigs: shellConfigsSchema.optional(),\n /**\n * An array of symlink configurations, added via `c.symlink()`. Each object has `source` and `target` paths where\n * `source` is real file and `target` is the symlink.\n *\n * Analogous to `ln -s source target`.\n */\n symlinks: z.array(symlinkConfigSchema).optional(),\n /**\n * An array of copy configurations, added via `c.copy()`. Each object has `source` and `target` paths where\n * `source` is the real file or directory and `target` is the destination.\n *\n * Analogous to `cp -r source target`.\n */\n copies: z.array(copyConfigSchema).optional(),\n /**\n * Configuration for automatic update checking for this tool.\n */\n updateCheck: toolConfigUpdateCheckSchema.optional(),\n })\n .strict();\n",
|
|
39
|
+
"import { z } from 'zod';\nimport type { ShellTypeConfig } from './shellTypeConfigSchema';\nimport { shellTypeConfigSchema } from './shellTypeConfigSchema';\n\nexport const shellConfigsSchema = z\n .object({\n /** Zsh shell configuration */\n zsh: shellTypeConfigSchema.optional(),\n /** Bash shell configuration */\n bash: shellTypeConfigSchema.optional(),\n /** PowerShell configuration */\n powershell: shellTypeConfigSchema.optional(),\n })\n .strict();\n\n/**\n * Shell configuration organized by shell type.\n * Manually typed to properly represent ShellCompletionConfigInput in completions.\n */\nexport interface ShellConfigs {\n /** Zsh shell configuration */\n zsh?: ShellTypeConfig;\n /** Bash shell configuration */\n bash?: ShellTypeConfig;\n /** PowerShell configuration */\n powershell?: ShellTypeConfig;\n}\n",
|
|
40
|
+
"import type { Resolvable } from '@dotfiles/unwrap-value';\nimport { z } from 'zod';\nimport type { ShellCompletionConfigInput } from '../../builder';\nimport type { ShellScript } from '../../shell';\nimport { shellScriptSchema } from './shellScriptSchema';\n\n/**\n * Input type for path configuration - can be static or resolved via callback.\n */\nexport type PathConfigInput = Resolvable<void, string>;\n\nexport const shellTypeConfigSchema = z\n .object({\n /** Shell initialization scripts */\n scripts: z.array(shellScriptSchema).optional(),\n /** Shell aliases (alias name -> command) */\n aliases: z.record(z.string(), z.string()).optional(),\n /** Environment variables to define (variable name -> value) */\n env: z.record(z.string(), z.string()).optional(),\n /** Shell functions (function name -> body) */\n functions: z.record(z.string(), z.string()).optional(),\n /** Paths to add to PATH environment variable */\n paths: z.array(z.unknown()).optional(),\n /**\n * Shell completion configuration (static value or callback).\n * Accepts string, ShellCompletionConfig object, or callback function.\n * Validation happens at generation time after resolution.\n */\n completions: z.unknown().optional(),\n })\n .strict();\n\n/**\n * Configuration for a specific shell type (zsh, bash, powershell).\n * Manually typed to properly represent ShellCompletionConfigInput.\n */\nexport interface ShellTypeConfig {\n /** Shell initialization scripts */\n scripts?: ShellScript[];\n /** Shell aliases (alias name -> command) */\n aliases?: Record<string, string>;\n /** Environment variables to define (variable name -> value) */\n env?: Record<string, string>;\n /** Shell functions (function name -> body) */\n functions?: Record<string, string>;\n /**\n * Paths to add to PATH environment variable.\n * Paths are deduplicated during shell init generation.\n */\n paths?: PathConfigInput[];\n /**\n * Shell completion configuration.\n * Can be a static string path, config object, or callback function.\n */\n completions?: ShellCompletionConfigInput;\n}\n",
|
|
41
|
+
"import { z } from 'zod';\nimport type { ShellScript } from '../../shell/shellScript.types';\n\n// Zod schema for ShellScript discriminated union\nexport const shellScriptSchema = z.object({\n kind: z.enum(['once', 'always']),\n value: z.string(),\n}) as z.ZodType<ShellScript>;\n",
|
|
42
|
+
"import { z } from 'zod';\n\nexport const toolConfigUpdateCheckSchema = z\n .object({\n /**\n * Whether update checking is enabled for this tool.\n * Can be overridden globally by `ProjectConfig.checkUpdatesOnRun`.\n * @default true\n */\n enabled: z.boolean().optional(),\n /**\n * An optional SemVer constraint for updates. If specified, only updates satisfying\n * this constraint relative to the currently installed version will be considered.\n * E.g., if `1.2.3` is installed and constraint is `~1.2.x`, then `1.2.4` is an update, but `1.3.0` is not.\n * If `^1.2.3` is installed, then `1.3.0` is an update, but `2.0.0` is not.\n */\n constraint: z.string().optional(),\n })\n .strict();\n\n/**\n * Configuration for automatic update checking for this tool.\n */\nexport type ToolConfigUpdateCheck = z.infer<typeof toolConfigUpdateCheckSchema>;\n",
|
|
43
|
+
"import { z } from 'zod';\n\n/**\n * Schema for binary configuration with name and pattern\n */\nexport const binaryConfigSchema = z\n .object({\n name: z.string().min(1),\n pattern: z.string().min(1),\n })\n .strict();\n\n/**\n * Configuration for a single binary within a tool\n */\nexport type IBinaryConfig = z.infer<typeof binaryConfigSchema>;\n",
|
|
44
|
+
"import { z } from 'zod';\n\nexport const copyConfigSchema = z\n .object({\n /** The source path (real file or directory) to copy from */\n source: z.string().min(1),\n /** The target path where the copy should be placed */\n target: z.string().min(1),\n })\n .strict();\n",
|
|
45
|
+
"import { z } from 'zod';\n\nexport const symlinkConfigSchema = z\n .object({\n /** The source path (real file) for the symlink */\n source: z.string().min(1),\n /** The target path where the symlink should be created */\n target: z.string().min(1),\n })\n .strict();\n",
|
|
46
|
+
"import { z } from 'zod';\nimport { baseToolConfigPropertiesSchema } from './base';\nimport { type PlatformConfigEntry, platformConfigEntrySchema } from './platformConfigEntrySchema';\n\n/**\n * Extended base tool config schema that includes platformConfigs.\n * This is defined in installer-plugin-system (not schemas) to avoid circular dependencies.\n *\n * Architecture:\n * - schemas package: baseToolConfigPropertiesSchema (no platform configs)\n * - plugin-system package: baseToolConfigWithPlatformsSchema (adds platform configs)\n * - plugin packages: extend from baseToolConfigWithPlatformsSchema\n *\n * This allows: schemas → plugins → plugin-system (no circular dependency)\n */\nexport const baseToolConfigWithPlatformsSchema = baseToolConfigPropertiesSchema.extend({\n /**\n * An array of platform-specific configurations.\n * Each entry defines configurations for a specific set of platforms and optionally architectures.\n */\n platformConfigs: z.array(platformConfigEntrySchema).optional(),\n});\n\n/**\n * Helper type for plugins to properly type their tool configs.\n * Plugins should use this instead of direct z.infer to get properly-typed platformConfigs.\n *\n * @example\n * ```typescript\n * export const myPluginToolConfigSchema = baseToolConfigWithPlatformsSchema.extend({\n * installationMethod: z.literal('my-plugin'),\n * installParams: myParamsSchema,\n * });\n *\n * export type MyPluginToolConfig = InferToolConfigWithPlatforms<typeof myPluginToolConfigSchema>;\n * ```\n */\nexport type InferToolConfigWithPlatforms<TSchema extends z.ZodType> = Omit<z.infer<TSchema>, 'platformConfigs'> & {\n platformConfigs?: PlatformConfigEntry[];\n};\n",
|
|
47
|
+
"import { z } from 'zod';\nimport { architectureSchema, platformSchema } from '../common';\nimport type { PlatformConfig } from '../platformConfigSchema';\n\n/**\n * Runtime schema for platform config entry structure.\n * Uses z.unknown() for config field because plugins register their types via module augmentation,\n * which zod cannot see. Runtime validation of the config field happens in the plugin system.\n */\nexport const platformConfigEntrySchema = z\n .object({\n /** A bitmask of target platforms for this configuration. */\n platforms: platformSchema,\n /** An optional bitmask of target architectures for this configuration. If undefined, applies to all architectures on the specified platforms. */\n architectures: architectureSchema.optional(),\n /** The actual configuration settings for this platform/architecture combination. */\n config: z.unknown(),\n })\n .strict();\n\n/**\n * Base type inferred from schema (has config: unknown).\n */\nexport type BasePlatformConfigEntry = z.infer<typeof platformConfigEntrySchema>;\n\n/**\n * Represents a single platform-specific configuration entry with fully-typed config.\n * The config field is properly typed as PlatformConfig (discriminated union built from\n * PlatformConfigRegistry that plugins augment via module augmentation).\n */\nexport type PlatformConfigEntry = Omit<BasePlatformConfigEntry, 'config'> & {\n config: PlatformConfig;\n};\n",
|
|
48
|
+
"import { z } from 'zod';\n\n/**\n * Configuration for installing command-line completion for a specific shell.\n * Specifies either a source file from the extracted archive or a command to generate completions dynamically.\n */\nexport const shellCompletionConfigSchema = z\n .object({\n /**\n * Path to a completion file.\n *\n * When used alone:\n * - **Relative paths** resolve to `toolDir` (directory containing `.tool.ts`)\n * - **Absolute paths** are used as-is (e.g., `${ctx.currentDir}/completions/_tool`)\n *\n * When used with `url`:\n * - Path within the extracted archive (e.g., `${ctx.currentDir}/completions/_tool`)\n * - The archive is extracted to `ctx.currentDir`\n *\n * @example\n * // Relative path (next to .tool.ts file)\n * source: '_tool.zsh'\n *\n * // From extracted archive\n * source: `${ctx.currentDir}/completions/_tool`\n */\n source: z.string().min(1).optional(),\n /**\n * URL to download the completion archive from.\n * Must be used together with `source` to specify the file within the extracted archive.\n *\n * @example\n * url: 'https://github.com/user/repo/releases/download/v1.0/completions.tar.gz'\n * source: `${ctx.currentDir}/completions/_tool`\n */\n url: z.string().url().optional(),\n /**\n * Command to execute to generate completion content dynamically.\n * The command is executed in the tool's installation directory after installation.\n *\n * @example\n * cmd: 'my-tool completion zsh'\n * cmd: 'kubectl completion bash'\n */\n cmd: z.string().min(1).optional(),\n /**\n * Optional binary name for the completion file.\n * When provided, shell-specific naming conventions are applied (e.g., `_bin` for Zsh, `bin.bash` for Bash).\n * Use this when the tool filename differs from the binary name.\n *\n * @example\n * // For a tool file named 'curl-script--fnm.tool.ts' with binary 'fnm'\n * bin: 'fnm' // Results in '_fnm' for zsh, 'fnm.bash' for bash\n */\n bin: z.string().optional(),\n })\n .strict()\n .refine(\n (data) => {\n // Valid combinations:\n // 1. source only (local file - relative or absolute)\n // 2. cmd only (generate via command)\n // 3. url + source (archive download with path within archive)\n const hasSource = Boolean(data.source);\n const hasCmd = Boolean(data.cmd);\n const hasUrl = Boolean(data.url);\n\n if (hasCmd && hasUrl) return false; // cmd and url are mutually exclusive\n if (hasCmd && hasSource) return false; // cmd and source are mutually exclusive\n if (hasUrl && !hasSource) return false; // url requires source\n if (!hasSource && !hasCmd) return false; // must have source or cmd\n\n return true;\n },\n {\n message:\n \"Invalid completion config: use 'source' alone, 'cmd' alone, or 'url' with 'source'. Cannot combine 'cmd' with 'url' or 'source'. Cannot use 'url' alone.\",\n },\n );\n\n/**\n * Configuration for installing command-line completion for a specific shell.\n * Specifies a source file, a command to generate completions, or a URL to download an archive from.\n */\nexport type ShellCompletionConfig = z.infer<typeof shellCompletionConfigSchema>;\n",
|
|
49
|
+
"import type { TsLogger } from '@dotfiles/logger';\n\n/**\n * Result of a shell command execution.\n */\nexport interface ShellResult {\n /** Exit code of the process */\n code: number;\n /** Stdout as string */\n stdout: string;\n /** Stderr as string */\n stderr: string;\n}\n\n/**\n * Options for shell command execution.\n */\nexport interface ShellOptions {\n /** Working directory for the command */\n cwd?: string;\n /** Environment variables to set */\n env?: Record<string, string | undefined>;\n /** Logger for real-time output streaming */\n logger?: TsLogger;\n /** If true, don't log output (command still logged if not skipCommandLog) */\n quiet?: boolean;\n /** If true, don't throw on non-zero exit code */\n noThrow?: boolean;\n /** If true, only log output, not the command itself (for wrapped shells) */\n skipCommandLog?: boolean;\n}\n\n/**\n * A chainable shell command builder.\n * Supports fluent API: shell`cmd`.cwd('/tmp').env({FOO: 'bar'}).quiet().text()\n */\nexport interface ShellCommand extends PromiseLike<ShellResult> {\n /** Set working directory */\n cwd(path: string): ShellCommand;\n /** Set/merge environment variables */\n env(vars: Record<string, string | undefined>): ShellCommand;\n /** Suppress output logging (command still logged) */\n quiet(): ShellCommand;\n /** Don't throw on non-zero exit code, return result with code instead */\n noThrow(): ShellCommand;\n /** Get stdout as trimmed string */\n text(): Promise<string>;\n /** Parse stdout as JSON */\n json<T = unknown>(): Promise<T>;\n /** Get stdout as array of lines */\n lines(): Promise<string[]>;\n /** Get stdout as bytes */\n bytes(): Promise<Uint8Array>;\n}\n\n/**\n * Shell factory function type - callable with template literals.\n */\nexport interface Shell {\n (strings: TemplateStringsArray, ...values: unknown[]): ShellCommand;\n (command: string): ShellCommand;\n}\n\nexport const loggingShellBrand: unique symbol = Symbol('loggingShellBrand');\n",
|
|
50
|
+
"import { createSafeLogMessage, type TsLogger } from '@dotfiles/logger';\nimport { loggingShellBrand, type Shell, type ShellCommand } from './types';\n\n/**\n * Wraps an existing shell to add command logging.\n *\n * Use this when you have a shell already configured (with cwd, env, etc.)\n * and want to add logging on top. The wrapper logs each command before\n * delegating to the base shell.\n *\n * For creating a new shell with logging, use `createShell({ logger })` instead.\n *\n * @param baseShell - The shell instance to wrap\n * @param logger - Logger for command logging\n * @returns A wrapped shell that logs commands\n *\n * @example\n * ```typescript\n * const baseShell = createShell();\n * const configuredShell = createConfiguredShell(baseShell, env);\n * const loggingShell = createLoggingShell(configuredShell, logger);\n * await loggingShell`echo hello`;\n * ```\n */\nexport function createLoggingShell(baseShell: Shell, logger: TsLogger): Shell {\n // Create a wrapper that logs the command and delegates to the base shell\n const loggingWrapper = (first: TemplateStringsArray, ...expressions: unknown[]): ShellCommand => {\n // Build the command string for logging BEFORE any transformations\n let commandStr: string = first[0] || '';\n for (let i = 0; i < expressions.length; i++) {\n const expr = expressions[i];\n if (Array.isArray(expr)) {\n commandStr += expr.map((e) => escapeArg(String(e))).join(' ');\n } else {\n commandStr += escapeArg(String(expr));\n }\n commandStr += first[i + 1] || '';\n }\n\n // Log the command BEFORE passing to wrappers (which may transform it)\n logger.info(createSafeLogMessage(`$ ${commandStr}`));\n\n // Delegate to base shell - it will handle streaming if it has a logger\n return baseShell(first, ...expressions);\n };\n\n // Copy properties from base shell and add brands\n Object.assign(loggingWrapper, baseShell);\n Object.defineProperty(loggingWrapper, loggingShellBrand, { value: true, enumerable: false });\n\n return loggingWrapper as unknown as Shell;\n}\n\n// Simple argument escaping for logging display\nfunction escapeArg(arg: string): string {\n if (arg === '' || /[\\s\"'`$\\\\]/.test(arg)) {\n return `'${arg.replace(/'/g, \"'\\\\''\")}'`;\n }\n return arg;\n}\n",
|
|
51
|
+
"/**\n * Error thrown when a shell command exits with non-zero code.\n */\nexport class ShellError extends Error {\n override name = 'ShellError';\n\n constructor(\n public readonly code: number,\n public readonly stdout: string,\n public readonly stderr: string,\n _command: string,\n ) {\n super(`Exited with code: ${code}`);\n // Preserve command in message for debugging\n if (stderr) {\n this.message += `\\n${stderr}`;\n }\n }\n}\n",
|
|
52
|
+
"import { createSafeLogMessage } from '@dotfiles/logger';\nimport { ShellError } from './ShellError';\nimport type { Shell, ShellCommand, ShellOptions, ShellResult } from './types';\n\n/**\n * Creates a shell instance with optional default options.\n *\n * @example\n * ```typescript\n * const $ = createShell({ logger });\n * const result = await $`echo hello`;\n * const text = await $`cat file.txt`.cwd('/tmp').text();\n * ```\n */\nexport function createShell(defaultOptions: ShellOptions = {}): Shell {\n const shell: Shell = (first: TemplateStringsArray | string, ...values: unknown[]): ShellCommand => {\n const command = typeof first === 'string' ? first : reconstructCommand(first, values);\n return createShellCommand(command, defaultOptions);\n };\n return shell;\n}\n\n/**\n * Creates a shell command with chainable options.\n */\nfunction createShellCommand(command: string, options: ShellOptions): ShellCommand {\n let currentOptions = { ...options };\n let executed = false;\n let resultPromise: Promise<ShellResult> | null = null;\n\n const execute = async (): Promise<ShellResult> => {\n if (resultPromise) return resultPromise;\n executed = true;\n\n resultPromise = executeCommand(command, currentOptions);\n return resultPromise;\n };\n\n const cmd: ShellCommand = {\n cwd(path: string): ShellCommand {\n if (executed) throw new Error('Cannot modify command after execution');\n currentOptions = { ...currentOptions, cwd: path };\n return cmd;\n },\n\n env(vars: Record<string, string | undefined>): ShellCommand {\n if (executed) throw new Error('Cannot modify command after execution');\n currentOptions = {\n ...currentOptions,\n env: { ...currentOptions.env, ...vars },\n };\n return cmd;\n },\n\n quiet(): ShellCommand {\n if (executed) throw new Error('Cannot modify command after execution');\n currentOptions = { ...currentOptions, quiet: true };\n return cmd;\n },\n\n noThrow(): ShellCommand {\n if (executed) throw new Error('Cannot modify command after execution');\n currentOptions = { ...currentOptions, noThrow: true };\n return cmd;\n },\n\n async text(): Promise<string> {\n const result = await execute();\n return result.stdout.replace(/\\r?\\n$/, '');\n },\n\n async json<T = unknown>(): Promise<T> {\n const result = await execute();\n return JSON.parse(result.stdout) as T;\n },\n\n async lines(): Promise<string[]> {\n const result = await execute();\n return result.stdout.replace(/\\r?\\n$/, '').split('\\n');\n },\n\n async bytes(): Promise<Uint8Array> {\n const result = await execute();\n return new TextEncoder().encode(result.stdout);\n },\n\n // oxlint-disable-next-line unicorn/no-thenable -- Required for PromiseLike interface to enable await syntax\n then<TResult1 = ShellResult, TResult2 = never>(\n onfulfilled?: ((value: ShellResult) => TResult1 | PromiseLike<TResult1>) | null,\n onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null,\n ): Promise<TResult1 | TResult2> {\n return execute().then(onfulfilled, onrejected);\n },\n };\n\n return cmd;\n}\n\n/**\n * Executes a shell command using Bun.spawn with sh -c.\n * Streams output in real-time if logger is provided.\n *\n * Note: The `quiet` option is kept for API compatibility but does NOT suppress\n * logger output. If a logger is provided, it will always log. The purpose of\n * a logging shell is to log - calling .quiet() shouldn't defeat that purpose.\n */\nasync function executeCommand(command: string, options: ShellOptions): Promise<ShellResult> {\n const { cwd, env, logger, noThrow, skipCommandLog } = options;\n\n // Log the command (unless skipCommandLog is set - used when wrapper logs command)\n if (logger && !skipCommandLog) {\n logger.info(createSafeLogMessage(`$ ${command}`));\n }\n\n // Build environment - merge with process.env\n const mergedEnv: Record<string, string> = { ...process.env } as Record<string, string>;\n if (env) {\n for (const [key, value] of Object.entries(env)) {\n if (value === undefined) {\n delete mergedEnv[key];\n } else {\n mergedEnv[key] = value;\n }\n }\n }\n\n const proc = Bun.spawn(['sh', '-c', command], {\n cwd,\n env: mergedEnv,\n stdout: 'pipe',\n stderr: 'pipe',\n });\n\n // Collect output while streaming to logger (always logs if logger provided)\n const [stdout, stderr] = await Promise.all([\n collectStream(proc.stdout, logger ? (line: string) => logger.info(createSafeLogMessage(`| ${line}`)) : undefined),\n collectStream(proc.stderr, logger ? (line: string) => logger.info(createSafeLogMessage(`| ${line}`)) : undefined),\n ]);\n\n const code = await proc.exited;\n\n if (code !== 0 && !noThrow) {\n throw new ShellError(code, stdout, stderr, command);\n }\n\n return { code, stdout, stderr };\n}\n\n/**\n * Collects a readable stream into a string, optionally logging each line in real-time.\n */\nasync function collectStream(\n stream: ReadableStream<Uint8Array>,\n onLine?: (line: string) => void,\n): Promise<string> {\n const reader = stream.getReader();\n const decoder = new TextDecoder();\n const chunks: string[] = [];\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n const text = decoder.decode(value, { stream: true });\n chunks.push(text);\n\n if (onLine) {\n buffer += text;\n const lines = buffer.split('\\n');\n buffer = lines.pop() ?? '';\n for (const line of lines) {\n if (line) onLine(line);\n }\n }\n }\n\n // Flush remaining buffer\n if (onLine && buffer) {\n onLine(buffer);\n }\n\n return chunks.join('');\n}\n\n/**\n * Reconstructs a command string from template literal parts.\n */\nfunction reconstructCommand(strings: TemplateStringsArray, values: unknown[]): string {\n let command = strings[0] || '';\n for (let i = 0; i < values.length; i++) {\n const value = values[i];\n if (Array.isArray(value)) {\n command += value.map(escapeArg).join(' ');\n } else {\n command += escapeArg(value);\n }\n command += strings[i + 1] || '';\n }\n return command;\n}\n\n/**\n * Escapes a value for safe shell interpolation.\n */\nfunction escapeArg(value: unknown): string {\n const str = String(value);\n // If already quoted or contains no special chars, return as-is\n if (/^[a-zA-Z0-9_\\-./=:@]+$/.test(str)) {\n return str;\n }\n // Escape single quotes and wrap in single quotes\n return `'${str.replace(/'/g, \"'\\\\''\")}'`;\n}\n",
|
|
53
|
+
"import { loggingShellBrand } from './types';\n\n/**\n * Checks if a shell has logging capabilities attached.\n * Used to prevent double-wrapping with createLoggingShell.\n */\nexport function hasLoggingShell($shell: unknown): boolean {\n return typeof $shell === 'function' && loggingShellBrand in $shell;\n}\n",
|
|
54
|
+
"/**\n * This module defines shell script types using a discriminated union pattern.\n * The `kind` property is used by type guards (`isOnceScript` and `isAlwaysScript`)\n * in `BaseShellGenerator` to correctly route scripts into appropriate execution\n * contexts (one-time setup vs. every shell startup).\n */\n\n/**\n * A shell script that should run only once, typically after a tool is installed or updated.\n * Used for tasks like generating completions or performing initial setup.\n */\nexport type OnceScript = { readonly kind: 'once'; readonly value: string; };\n\n/**\n * A shell script that should run on every shell startup.\n * Used for tasks like setting environment variables or running `eval` commands.\n */\nexport type AlwaysScript = { readonly kind: 'always'; readonly value: string; };\n\n/**\n * A raw shell script that runs on every shell startup without any wrapping.\n * Used for scripts that should run directly in the current shell context,\n * such as `source <(fnName)` which needs to affect the current shell.\n */\nexport type RawScript = { readonly kind: 'raw'; readonly value: string; };\n\n/**\n * A union type representing any kind of shell script, whether it runs once or\n * on every shell startup.\n */\nexport type ShellScript = OnceScript | AlwaysScript | RawScript;\n\n/**\n * Creates a shell script that runs only once after tool installation or update.\n *\n * @param value - The shell script content.\n * @returns A {@link OnceScript} object.\n */\nexport function once(value: string): OnceScript {\n return { kind: 'once', value };\n}\n\n/**\n * Creates a shell script that runs on every shell startup.\n *\n * @param value - The shell script content.\n * @returns An {@link AlwaysScript} object.\n */\nexport function always(value: string): AlwaysScript {\n return { kind: 'always', value };\n}\n\n/**\n * Creates a raw shell script that runs without any wrapping.\n * Used for scripts like `source <(fnName)` that need to run in the current shell context.\n *\n * @param value - The shell script content.\n * @returns A {@link RawScript} object.\n */\nexport function raw(value: string): RawScript {\n return { kind: 'raw', value };\n}\n\n/**\n * A type guard to check if a given script is a {@link OnceScript}.\n *\n * @param script - The script to check.\n * @returns `true` if the script is a `OnceScript`, otherwise `false`.\n */\nexport function isOnceScript(script: ShellScript): script is OnceScript {\n return script.kind === 'once';\n}\n\n/**\n * A type guard to check if a given script is an {@link AlwaysScript}.\n *\n * @param script - The script to check.\n * @returns `true` if the script is an `AlwaysScript`, otherwise `false`.\n */\nexport function isAlwaysScript(script: ShellScript): script is AlwaysScript {\n return script.kind === 'always';\n}\n\n/**\n * A type guard to check if a given script is a {@link RawScript}.\n *\n * @param script - The script to check.\n * @returns `true` if the script is a `RawScript`, otherwise `false`.\n */\nexport function isRawScript(script: ShellScript): script is RawScript {\n return script.kind === 'raw';\n}\n\n/**\n * Extracts the raw string content from a shell script.\n *\n * @param script - The {@link ShellScript} to unwrap.\n * @returns The raw string content of the script.\n */\nexport function getScriptContent(script: ShellScript): string {\n return script.value;\n}\n",
|
|
55
|
+
"import type { ProjectConfig } from '@dotfiles/config';\nimport type {\n IAfterInstallContext,\n IInstallContext,\n InstallerPluginRegistry,\n ISystemInfo,\n Shell,\n ToolConfig,\n} from '@dotfiles/core';\nimport { Platform } from '@dotfiles/core';\nimport type { IResolvedFileSystem } from '@dotfiles/file-system';\nimport { createSafeLogMessage, type TsLogger } from '@dotfiles/logger';\nimport type { TrackedFileSystem } from '@dotfiles/registry/file';\nimport type { IToolInstallationRegistry } from '@dotfiles/registry/tool';\nimport type { ISymlinkGenerator } from '@dotfiles/symlink-generator';\nimport { generateTimestamp, resolvePlatformConfig } from '@dotfiles/utils';\nimport { randomUUID } from 'node:crypto';\nimport path from 'node:path';\nimport { type ICreateBaseInstallContextResult, InstallContextFactory } from './context';\nimport { HookLifecycle } from './hooks/HookLifecycle';\nimport { InstallationStateWriter } from './state';\nimport type { IInstaller, IInstallOptions, InstallResult } from './types';\nimport { createConfiguredShell, getBinaryPaths, type HookExecutor, messages } from './utils';\n\n/**\n * Orchestrates the tool installation process by delegating to plugin-based installation methods\n * registered in the `InstallerPluginRegistry`. Manages the entire installation lifecycle including\n * timestamped directory creation, hook execution, installation tracking, and cleanup.\n *\n * ### Installation Flow\n * 1. Resolves platform-specific configuration using `resolvePlatformConfig`\n * 2. Creates tool-specific file system instance for tracking\n * 3. Checks if tool is already installed (unless force option is used)\n * 4. Creates timestamped installation directory (e.g., `binaries/toolname/2025-11-05-12-34-56`)\n * 5. Executes beforeInstall hook if defined\n * 6. Delegates to plugin for actual installation via `registry.install()`\n * 7. Cleans up failed installations by removing empty directories\n * 8. Executes afterInstall hook if defined\n * 9. Records successful installation in tool installation registry\n *\n * ### Plugin System Integration\n * The installer acts as an orchestrator that coordinates with the plugin registry:\n * - Determines installation method from `toolConfig.installationMethod`\n * - Delegates to appropriate plugin via `registry.install()`\n * - Plugins handle method-specific logic (GitHub releases, Homebrew, Cargo, etc.)\n * - Plugins emit events that trigger corresponding hooks\n *\n * ### Hook Execution\n * Hooks are executed at different stages of installation:\n * - `beforeInstall`: Before any installation steps begin\n * - `afterDownload`: After asset download (emitted by plugins)\n * - `afterExtract`: After archive extraction (emitted by plugins)\n * - `afterInstall`: After installation completes successfully or fails\n *\n * The installer registers an event handler with the plugin registry to execute hooks\n * when plugins emit installation events. Hook execution uses `HookExecutor` for proper\n * error handling, timeouts, and context management.\n *\n * ### File System Tracking\n * Uses `TrackedFileSystem` to track all file operations performed during installation.\n * This enables the registry to record which files belong to which tools for proper\n * management and cleanup. Logging can be suppressed in shim mode.\n *\n * ### Installation Registry\n * Records successful installations with metadata including:\n * - Tool name and version\n * - Installation path and timestamp\n * - Binary paths created\n * - Download URL and asset name (when applicable)\n * - Configured version and original tag (when applicable)\n *\n * ### Error Handling and Cleanup\n * - Failed installations trigger cleanup of empty directories\n * - afterInstall hook runs even on failure for cleanup tasks\n * - Errors are logged with context and returned in InstallResult\n */\nexport class Installer implements IInstaller {\n private readonly logger: TsLogger;\n private readonly fs: TrackedFileSystem;\n private readonly resolvedFs: IResolvedFileSystem;\n private readonly projectConfig: ProjectConfig;\n public readonly hookExecutor: HookExecutor;\n private readonly hookLifecycle: HookLifecycle;\n private readonly toolInstallationRegistry: IToolInstallationRegistry;\n private readonly systemInfo: ISystemInfo;\n private readonly registry: InstallerPluginRegistry;\n private readonly installationStateWriter: InstallationStateWriter;\n private readonly $: Shell;\n private readonly installContextFactory: InstallContextFactory;\n private currentToolConfig?: ToolConfig;\n\n constructor(\n parentLogger: TsLogger,\n fileSystem: TrackedFileSystem,\n resolvedFileSystem: IResolvedFileSystem,\n projectConfig: ProjectConfig,\n toolInstallationRegistry: IToolInstallationRegistry,\n systemInfo: ISystemInfo,\n registry: InstallerPluginRegistry,\n symlinkGenerator: ISymlinkGenerator,\n $shell: Shell,\n hookExecutor: HookExecutor,\n ) {\n this.logger = parentLogger.getSubLogger({ name: 'Installer' });\n this.fs = fileSystem;\n this.resolvedFs = resolvedFileSystem;\n this.projectConfig = projectConfig;\n this.hookExecutor = hookExecutor;\n this.hookLifecycle = new HookLifecycle(hookExecutor);\n this.toolInstallationRegistry = toolInstallationRegistry;\n this.systemInfo = systemInfo;\n this.registry = registry;\n this.installationStateWriter = new InstallationStateWriter({\n projectConfig: this.projectConfig,\n toolInstallationRegistry: this.toolInstallationRegistry,\n symlinkGenerator,\n });\n this.$ = $shell;\n this.installContextFactory = new InstallContextFactory({\n projectConfig: this.projectConfig,\n systemInfo: this.systemInfo,\n resolvedFileSystem: this.resolvedFs,\n fileSystem: this.fs,\n $shell: this.$,\n emitInstallEvent: async (event) => {\n await this.registry.emitEvent(event);\n },\n });\n\n // Register event handler for installation events to execute hooks\n this.registry.onEvent(async (event) => {\n await this.hookLifecycle.handleInstallEvent(event, this.currentToolConfig, this.logger);\n });\n }\n\n /**\n * Determines if installation should be skipped based on existing installation and version.\n * Returns true when tool is already installed at target version and force option is not used.\n *\n * Logic:\n * 1. Returns false if force option is enabled (always install)\n * 2. Queries tool installation registry for existing installation\n * 3. Returns false if tool is not currently installed\n * 4. Gets target version from resolved tool config\n * 5. Compares existing version with target version\n * 6. Returns true if versions match (skip installation)\n * 7. Returns false if versions differ (outdated, needs update)\n *\n * @param toolName - Name of the tool to check\n * @param resolvedToolConfig - Platform-resolved tool configuration\n * @param options - Installation options (checks force flag)\n * @param parentLogger - Logger for diagnostic messages\n * @returns True if installation should be skipped, false otherwise\n */\n private async shouldSkipInstallation(\n toolName: string,\n resolvedToolConfig: ToolConfig,\n options: IInstallOptions | undefined,\n parentLogger: TsLogger,\n ): Promise<InstallResult | null> {\n const logger = parentLogger.getSubLogger({ name: 'shouldSkipInstallation' });\n if (options?.force || options?.skipVersionCheck) {\n return null;\n }\n\n const existingInstallation = await this.toolInstallationRegistry.getToolInstallation(toolName);\n if (!existingInstallation) {\n return null;\n }\n\n // Compute binaryPaths for already-installed case\n // This is needed for completion generation to know where binaries are\n const currentDir = path.join(this.projectConfig.paths.binariesDir, toolName, 'current');\n const binaryPaths = getBinaryPaths(resolvedToolConfig.binaries, currentDir);\n\n // Get shellInit from plugin if available (for plugins like zsh-plugin that emit shellInit)\n const plugin = this.registry.get(resolvedToolConfig.installationMethod);\n const shellInit = plugin?.getShellInit?.(toolName, resolvedToolConfig, currentDir);\n\n const targetVersion = await this.getTargetVersion(toolName, resolvedToolConfig);\n if (targetVersion) {\n if (existingInstallation.version === targetVersion) {\n logger.debug(messages.outcome.installSuccess(toolName, targetVersion, 'already-installed'));\n // Use type assertion: \"already-installed\" is a skip scenario, not a real plugin execution\n const result = {\n success: true,\n version: existingInstallation.version,\n installationMethod: 'already-installed',\n binaryPaths,\n shellInit,\n } as InstallResult;\n return result;\n }\n logger.debug(messages.outcome.outdatedVersion(toolName, existingInstallation.version, targetVersion));\n return null;\n }\n\n // If target version is NOT explicit (e.g. 'latest'), and we have an installation\n // We should skip unless we want to force update.\n // Since we don't support update checks for some plugins (like curl-script),\n // assuming \"installed is good enough\" prevents the infinite reinstall loop.\n // Users can use --force to update.\n logger.debug(messages.outcome.installSuccess(toolName, existingInstallation.version, 'already-installed-latest'));\n // Use type assertion: \"already-installed\" is a skip scenario, not a real plugin execution\n const result = {\n success: true,\n version: existingInstallation.version,\n installationMethod: 'already-installed',\n binaryPaths,\n shellInit,\n } as InstallResult;\n return result;\n }\n\n /**\n * Delegates installation to the appropriate plugin based on installation method.\n * Calls `registry.install()` which routes to the registered plugin for the method.\n * The plugin returns a result that conforms to the InstallResult union type.\n *\n * @param toolName - Name of the tool being installed\n * @param resolvedToolConfig - Platform-resolved tool configuration\n * @param context - Base install context with paths and system info\n * @param options - Installation options (force, quiet, verbose, shimMode)\n * @param parentLogger - Logger with tool context for plugin operations\n * @returns Installation result from the plugin\n */\n private async executeInstallationMethod(\n toolName: string,\n resolvedToolConfig: ToolConfig,\n context: IInstallContext,\n options: IInstallOptions | undefined,\n parentLogger: TsLogger,\n ): Promise<InstallResult> {\n const result: InstallResult = await this.registry.install(\n parentLogger,\n resolvedToolConfig.installationMethod,\n toolName,\n resolvedToolConfig,\n context,\n options,\n );\n return result;\n }\n\n /**\n * Installs a tool based on its configuration through a multi-stage process.\n * Main entry point that coordinates the entire installation flow.\n *\n * @param toolName - Name of the tool to install\n * @param toolConfig - Tool configuration with installation method and parameters\n * @param options - Optional installation options (force, quiet, verbose, shimMode)\n * @returns Installation result with success status, binary paths, version, and metadata\n */\n async install(toolName: string, toolConfig: ToolConfig, options?: IInstallOptions): Promise<InstallResult> {\n const logger = this.logger.getSubLogger({ name: 'install', context: toolName });\n\n // Resolve platform-specific configuration\n const systemInfo = this.getSystemInfo();\n const resolvedToolConfig = resolvePlatformConfig(toolConfig, systemInfo);\n\n // Store current tool config for event handler access\n this.currentToolConfig = resolvedToolConfig;\n\n // Create a tool-specific TrackedFileSystem\n const toolFs = this.fs.withToolName(toolName);\n\n // Suppress logging in shim mode\n if (options?.shimMode) {\n toolFs.setSuppressLogging(true);\n }\n\n try {\n // Check if tool is already installed (unless force option is used)\n const skipResult = await this.shouldSkipInstallation(toolName, resolvedToolConfig, options, logger);\n if (skipResult) {\n return skipResult;\n }\n\n // Check if plugin is externally managed (e.g., Homebrew, apt)\n const plugin = this.registry.get(resolvedToolConfig.installationMethod);\n const isExternallyManaged: boolean = plugin?.externallyManaged === true;\n\n // Try to resolve version before creating installation directory\n // Fall back to timestamp if version cannot be resolved\n const binariesDir: string = this.projectConfig.paths.binariesDir;\n const toolRootDir: string = path.join(binariesDir, toolName);\n await toolFs.ensureDir(toolRootDir);\n\n const timestamp: string = generateTimestamp();\n let versionOrTimestamp: string = timestamp;\n\n if (resolvedToolConfig.version && resolvedToolConfig.version !== 'latest') {\n versionOrTimestamp = resolvedToolConfig.version;\n }\n\n if (!isExternallyManaged && plugin?.resolveVersion) {\n // Create minimal context for version resolution\n const tempContext: IInstallContext = this.createMinimalContext(toolName, resolvedToolConfig, logger);\n\n try {\n const resolvedVersion: string | null = await plugin.resolveVersion(\n toolName,\n resolvedToolConfig,\n tempContext,\n logger,\n );\n\n if (resolvedVersion) {\n versionOrTimestamp = resolvedVersion;\n logger.debug(messages.lifecycle.versionResolved(resolvedVersion));\n } else {\n logger.debug(messages.lifecycle.versionFallbackToTimestamp());\n }\n } catch (error) {\n logger.debug(messages.lifecycle.versionResolutionFailed(error));\n }\n }\n\n // Create staging directory for the install attempt (skip creating for externally managed plugins)\n const stagingId: string = randomUUID();\n const stagingDir: string = path.join(toolRootDir, stagingId);\n\n if (!isExternallyManaged) {\n await toolFs.ensureDir(stagingDir);\n logger.debug(messages.lifecycle.directoryCreated(stagingDir));\n }\n\n // Build installation environment with recursion guard and modified PATH\n // This environment is passed to the configured shell so all commands inherit it.\n // We avoid modifying process.env directly as that's a global mutation.\n const envVarName = `DOTFILES_INSTALLING_${toolName.toUpperCase().replace(/[^A-Z0-9_]/g, '_')}`;\n const originalPath = process.env['PATH'] || '';\n const pathSeparator = systemInfo.platform === Platform.Windows ? ';' : ':';\n const installPath: string = isExternallyManaged ? originalPath : `${stagingDir}${pathSeparator}${originalPath}`;\n\n const installEnv: Record<string, string | undefined> = {\n ...process.env,\n [envVarName]: 'true',\n PATH: installPath,\n };\n\n // Create a configured shell with the installation environment\n // This ensures plugins and hooks see the recursion guard and modified PATH\n const configuredShell = createConfiguredShell(this.$, installEnv);\n\n const { context, logger: contextLogger } = this.createBaseInstallContext(\n toolName,\n stagingDir,\n timestamp,\n resolvedToolConfig,\n logger,\n configuredShell,\n installEnv,\n );\n\n // Run beforeInstall hook if defined\n const beforeInstallResult = await this.hookLifecycle.executeBeforeInstallHook(\n resolvedToolConfig,\n context,\n toolFs,\n contextLogger,\n );\n if (beforeInstallResult) {\n return beforeInstallResult;\n }\n\n let result: InstallResult;\n\n try {\n // Install based on the installation method\n result = await this.executeInstallationMethod(toolName, resolvedToolConfig, context, options, contextLogger);\n\n // Add the resolved installation method to the result\n result.installationMethod = resolvedToolConfig.installationMethod;\n\n const detectedVersion: string | undefined = result.success && 'version' in result ? result.version : undefined;\n const finalVersionOrTimestamp: string =\n versionOrTimestamp === timestamp && detectedVersion && detectedVersion !== timestamp\n ? detectedVersion\n : versionOrTimestamp;\n\n const installedDir: string = isExternallyManaged\n ? path.join(toolRootDir, 'external')\n : path.join(toolRootDir, finalVersionOrTimestamp);\n\n if (result.success && !isExternallyManaged) {\n if (await toolFs.exists(installedDir)) {\n await toolFs.rm(installedDir, { recursive: true, force: true });\n }\n\n await toolFs.rename(stagingDir, installedDir);\n logger.debug(messages.lifecycle.directoryRenamed(stagingDir, installedDir));\n\n if (result.success && 'binaryPaths' in result && result.binaryPaths) {\n result.binaryPaths = result.binaryPaths.map((p: string) =>\n p.startsWith(stagingDir) ? p.replace(stagingDir, installedDir) : p\n );\n }\n }\n\n if (result.success && isExternallyManaged) {\n await toolFs.ensureDir(installedDir);\n }\n\n // Create stable binary entrypoints for all tools.\n // Shims always execute via toolDir/current/<binary>, so <binary> must be a direct executable file.\n // Filter out paths that don't exist - these may have been handled by setupBinariesFromArchive\n const binaryPaths = result.success && 'binaryPaths' in result ? result.binaryPaths : undefined;\n if (result.success && binaryPaths) {\n const existingPaths: string[] = [];\n for (const binaryPath of binaryPaths) {\n const exists = await toolFs.exists(binaryPath);\n if (exists) {\n existingPaths.push(binaryPath);\n }\n }\n if (existingPaths.length > 0) {\n await this.installationStateWriter.createBinaryEntrypoints(\n toolName,\n existingPaths,\n toolFs,\n logger,\n installedDir,\n isExternallyManaged,\n );\n }\n }\n\n // Update current symlink after installation and any rename.\n // This provides a stable directory for shims: {binariesDir}/{toolName}/current/...\n if (result.success) {\n await this.installationStateWriter.updateCurrentSymlink(\n toolName,\n toolFs,\n logger,\n installedDir,\n isExternallyManaged,\n );\n }\n\n // If installation failed, clean up the empty installation directory (skip for externally managed)\n if (!isExternallyManaged && !result.success && (await toolFs.exists(stagingDir))) {\n logger.debug(messages.lifecycle.cleaningFailedInstallDir(stagingDir));\n await toolFs.rm(stagingDir, { recursive: true, force: true });\n\n // Also try to remove the parent tool directory if it's now empty\n const toolDir = path.dirname(stagingDir);\n try {\n const entries = await toolFs.readdir(toolDir);\n if (entries.length === 0) {\n await toolFs.rmdir(toolDir);\n }\n } catch {\n // Parent directory might not be empty or might not exist, which is fine\n }\n }\n\n // Log the error message when installation fails\n if (!result.success && result.error) {\n logger.error(createSafeLogMessage(result.error));\n }\n\n if (result.success) {\n const resultBinaryPaths: string[] = 'binaryPaths' in result && Array.isArray(result.binaryPaths)\n ? result.binaryPaths\n : [];\n const version: string | undefined = 'version' in result ? result.version : undefined;\n\n // Create after-install environment with PATH pointing to installedDir\n // This ensures after-install hooks can find the freshly installed binaries\n const afterInstallPath: string = isExternallyManaged\n ? originalPath\n : `${installedDir}${pathSeparator}${originalPath}`;\n const afterInstallEnv: Record<string, string | undefined> = {\n ...process.env,\n [envVarName]: 'true',\n PATH: afterInstallPath,\n };\n const afterInstallShell = createConfiguredShell(this.$, afterInstallEnv);\n\n const afterInstallContext: IAfterInstallContext = {\n ...context,\n $: afterInstallShell,\n installedDir,\n binaryPaths: resultBinaryPaths,\n version,\n installEnv: afterInstallEnv,\n };\n\n await this.hookLifecycle.executeAfterInstallHook(\n resolvedToolConfig,\n afterInstallContext,\n toolFs,\n contextLogger,\n );\n\n // Record successful installation in the registry\n await this.installationStateWriter.recordInstallation(\n toolName,\n resolvedToolConfig,\n installedDir,\n context,\n result,\n contextLogger,\n );\n }\n\n return result;\n } catch (error) {\n // If installation method throws (e.g., from hook failure), create failure result\n result = {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n installationMethod: resolvedToolConfig.installationMethod,\n };\n logger.error(createSafeLogMessage(result.error));\n return result;\n }\n // No finally block needed - we don't modify process.env, so no cleanup required.\n // The recursion guard and PATH modifications are scoped to the shell environment.\n } catch (error) {\n const errorResult: InstallResult = {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n installationMethod: resolvedToolConfig.installationMethod,\n };\n logger.error(createSafeLogMessage(errorResult.error));\n return errorResult;\n }\n }\n\n /**\n * Determines the target version that would be installed for a tool.\n * Used by `shouldSkipInstallation` to compare with existing installation.\n *\n * Logic:\n * - Returns explicit version if set and not 'latest'\n * - Returns null for 'latest' or unspecified (requires plugin execution to determine)\n *\n * Most version resolution is handled by plugins during actual installation.\n * This method provides a quick check for explicit version matches.\n *\n * @param _toolName - Tool name (unused, reserved for future use)\n * @param toolConfig - Tool configuration with version information\n * @returns Target version string or null if cannot be determined without plugin execution\n */\n private async getTargetVersion(_toolName: string, toolConfig: ToolConfig): Promise<string | null> {\n // If the version is explicitly set and not 'latest', return it\n if (toolConfig.version && toolConfig.version !== 'latest') {\n return toolConfig.version;\n }\n // For 'latest' or unspecified versions, we can't determine the target version\n // without executing the plugin logic, so return null to skip the version check\n return null;\n }\n\n /**\n * Creates a minimal installation context for version resolution.\n * This lightweight context contains only system information needed\n * to resolve versions before installation directories are created.\n *\n * @param toolName - Tool name\n * @param toolConfig - Complete tool configuration\n * @param parentLogger - Parent logger for context creation\n * @returns Minimal context with system info\n */\n private createMinimalContext(toolName: string, toolConfig: ToolConfig, parentLogger: TsLogger): IInstallContext {\n return this.installContextFactory.createMinimalContext({\n toolName,\n toolConfig,\n parentLogger,\n });\n }\n\n /**\n * Creates a complete InstallContext with all required properties for installation.\n * Includes properties from IBaseToolContext plus installation-specific fields.\n *\n * @param toolName - Name of the tool being installed\n * @param stagingDir - Per-attempt staging directory path\n * @param timestamp - Installation timestamp string\n * @param toolConfig - Tool configuration\n * @param parentLogger - Parent logger for creating context logger\n * @returns Complete install context with all required properties and event emitter\n */\n private createBaseInstallContext(\n toolName: string,\n stagingDir: string,\n timestamp: string,\n toolConfig: ToolConfig,\n parentLogger: TsLogger,\n $shell: Shell = createConfiguredShell(this.$, process.env),\n installEnv?: Record<string, string | undefined>,\n ): ICreateBaseInstallContextResult {\n const methodLogger = parentLogger.getSubLogger({ name: 'createBaseInstallContext' });\n\n return this.installContextFactory.createBaseInstallContext({\n toolName,\n stagingDir,\n timestamp,\n toolConfig,\n parentLogger: methodLogger,\n $shell,\n installEnv,\n });\n }\n\n /**\n * Returns the system information used for platform-specific configuration resolution.\n * Provides platform and architecture details needed for asset selection and binary matching.\n *\n * @returns System information with platform and architecture\n */\n private getSystemInfo(): ISystemInfo {\n return this.systemInfo;\n }\n}\n",
|
|
56
|
+
"import type { ProjectConfig } from '@dotfiles/config';\nimport type { IInstallContext, ISystemInfo, PluginEmittedHookEvent, Shell, ToolConfig } from '@dotfiles/core';\nimport { createToolConfigContext, type InstallEvent } from '@dotfiles/core';\nimport type { IResolvedFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport type { TrackedFileSystem } from '@dotfiles/registry/file';\nimport path from 'node:path';\nimport { createConfiguredShell } from '../utils';\n\ntype UnknownRecord = Record<string, unknown>;\n\ntype EmitEvent = (type: PluginEmittedHookEvent, data: UnknownRecord) => Promise<void>;\n\nexport interface IInstallContextWithEmitter extends IInstallContext {\n emitEvent?: EmitEvent;\n}\n\nexport interface ICreateBaseInstallContextResult {\n context: IInstallContextWithEmitter;\n logger: TsLogger;\n}\n\ninterface ICreateMinimalContextOptions {\n toolName: string;\n toolConfig: ToolConfig;\n parentLogger: TsLogger;\n $shell?: Shell;\n}\n\ninterface ICreateBaseInstallContextOptions {\n toolName: string;\n stagingDir: string;\n timestamp: string;\n toolConfig: ToolConfig;\n parentLogger: TsLogger;\n $shell?: Shell;\n installEnv?: Record<string, string | undefined>;\n}\n\ninterface IInstallContextFactoryDependencies {\n projectConfig: ProjectConfig;\n systemInfo: ISystemInfo;\n resolvedFileSystem: IResolvedFileSystem;\n fileSystem: TrackedFileSystem;\n $shell: Shell;\n emitInstallEvent: (event: InstallEvent) => Promise<void>;\n}\n\nexport class InstallContextFactory {\n private readonly projectConfig: ProjectConfig;\n private readonly systemInfo: ISystemInfo;\n private readonly resolvedFileSystem: IResolvedFileSystem;\n private readonly fileSystem: TrackedFileSystem;\n private readonly $shell: Shell;\n private readonly emitInstallEvent: (event: InstallEvent) => Promise<void>;\n\n constructor(dependencies: IInstallContextFactoryDependencies) {\n this.projectConfig = dependencies.projectConfig;\n this.systemInfo = dependencies.systemInfo;\n this.resolvedFileSystem = dependencies.resolvedFileSystem;\n this.fileSystem = dependencies.fileSystem;\n this.$shell = dependencies.$shell;\n this.emitInstallEvent = dependencies.emitInstallEvent;\n }\n\n createMinimalContext(options: ICreateMinimalContextOptions): IInstallContext {\n const toolDir = this.getToolDirectory(options.toolConfig);\n const contextLogger = options.parentLogger.getSubLogger({ name: 'minimalContext' });\n\n const baseContext = createToolConfigContext(\n this.projectConfig,\n this.systemInfo,\n options.toolName,\n toolDir,\n this.resolvedFileSystem,\n contextLogger,\n );\n\n const context: IInstallContext = {\n ...baseContext,\n stagingDir: '',\n timestamp: '',\n toolConfig: options.toolConfig,\n $: options.$shell ?? createConfiguredShell(this.$shell, process.env),\n fileSystem: this.fileSystem,\n };\n\n return context;\n }\n\n createBaseInstallContext(options: ICreateBaseInstallContextOptions): ICreateBaseInstallContextResult {\n const toolDir = this.getToolDirectory(options.toolConfig);\n const contextLogger = options.parentLogger.getSubLogger({ name: `install-${options.toolName}` });\n\n const baseContext = createToolConfigContext(\n this.projectConfig,\n this.systemInfo,\n options.toolName,\n toolDir,\n this.resolvedFileSystem,\n contextLogger,\n );\n\n const shell = options.$shell ?? createConfiguredShell(this.$shell, process.env);\n\n const createInstallContext = (data: UnknownRecord = {}): IInstallContextWithEmitter => ({\n ...baseContext,\n stagingDir: options.stagingDir,\n timestamp: options.timestamp,\n toolConfig: options.toolConfig,\n $: shell,\n fileSystem: this.fileSystem,\n installEnv: options.installEnv,\n ...data,\n });\n\n const context = createInstallContext();\n\n const emitEvent: EmitEvent = async (type: PluginEmittedHookEvent, data: UnknownRecord): Promise<void> => {\n await this.emitInstallEvent({\n type,\n toolName: options.toolName,\n context: {\n ...createInstallContext(data),\n emitEvent,\n logger: contextLogger,\n },\n });\n };\n\n context.emitEvent = emitEvent;\n\n const result: ICreateBaseInstallContextResult = {\n context,\n logger: contextLogger,\n };\n\n return result;\n }\n\n private getToolDirectory(toolConfig: ToolConfig): string {\n return toolConfig.configFilePath\n ? path.dirname(toolConfig.configFilePath)\n : this.projectConfig.paths.toolConfigsDir;\n }\n}\n",
|
|
57
|
+
"import { createSafeLogMessage, type SafeLogMessageMap } from '@dotfiles/logger';\n\nexport const messages = {\n directoryDeletionError: () => createSafeLogMessage('Error tracking directory deletion %s: %s'),\n rmdirTracked: () => createSafeLogMessage('Tracked rmdir operation: %s'),\n operationRecorded: () => createSafeLogMessage('Recorded %s operation for %s: %s'),\n operationsRetrieved: () => createSafeLogMessage('Retrieved %d operations with filter: %o'),\n fileStatesComputed: () => createSafeLogMessage('Computed %d file states for tool: %s'),\n fileStateComputed: () => createSafeLogMessage('Computed file state for %s: %s'),\n noOperationsFound: () => createSafeLogMessage('No operations found for file: %s'),\n toolsFound: () => createSafeLogMessage('Found %d registered tools'),\n operationsRemoved: () => createSafeLogMessage('Removed %d operations for tool: %s'),\n compactionComplete: () => createSafeLogMessage('Compaction complete: %d -> %d operations'),\n validationComplete: () => createSafeLogMessage('Validation complete: %d issues found, %d repaired'),\n registryClosed: () => createSafeLogMessage('Closed SQLite file registry'),\n schemaInitialized: () => createSafeLogMessage('Schema initialization complete'),\n fileCreated: (path: string) => createSafeLogMessage(`write ${path}`),\n fileUpdated: (path: string) => createSafeLogMessage(`write ${path}`),\n fileRemoved: (path: string) => createSafeLogMessage(`rm ${path}`),\n fileMoved: (oldPath: string, newPath: string) => createSafeLogMessage(`mv ${oldPath} ${newPath}`),\n fileCopied: (srcPath: string, destPath: string) => createSafeLogMessage(`cp ${srcPath} ${destPath}`),\n symlinkCreated: (linkPath: string, targetPath: string) => createSafeLogMessage(`ln -s ${targetPath} ${linkPath}`),\n permissionsChanged: (path: string, mode: string) => createSafeLogMessage(`chmod ${mode} ${path}`),\n directoryCreated: (path: string) => createSafeLogMessage(`mkdir ${path}`),\n} satisfies SafeLogMessageMap;\n",
|
|
58
|
+
"import type { TsLogger } from '@dotfiles/logger';\nimport type { Database } from 'bun:sqlite';\nimport type { IFileOperation, IFileOperationFilter, IFileRegistry, IFileState } from './IFileRegistry';\nimport { messages } from './log-messages';\n\ninterface IDatabaseRow {\n id: number;\n tool_name: string;\n operation_type: IFileOperation['operationType'];\n file_path: string;\n target_path: string | null;\n file_type: IFileOperation['fileType'];\n metadata: string | null;\n size_bytes: number | null;\n permissions: string | null;\n created_at: string;\n operation_id: string;\n}\n\n/**\n * SQLite-based implementation of the file registry.\n *\n * This class provides a persistent, append-only registry for tracking all filesystem\n * operations performed by the dotfiles system. It records operations like file creation,\n * deletion, symlinking, and permission changes, allowing for accurate tracking of which\n * files belong to which tools.\n *\n * The append-only design ensures crash safety - operations are never modified once written,\n * only new operations are added. This allows the system to rebuild the current state of\n * any file by replaying all operations in chronological order.\n *\n * Key features:\n * - Operation tracking: Records all file operations with metadata\n * - State computation: Reconstructs current file state from operation history\n * - Tool isolation: Tracks which files belong to which tools\n * - Compaction: Removes obsolete operations to keep database size manageable\n * - Validation: Checks registry integrity and repairs issues\n */\nexport class FileRegistry implements IFileRegistry {\n private readonly db: Database;\n private readonly logger: TsLogger;\n\n constructor(parentLogger: TsLogger, db: Database) {\n this.logger = parentLogger.getSubLogger({ name: 'SqliteFileRegistry' });\n this.db = db;\n this.initializeSchema();\n }\n\n async recordOperation(operation: Omit<IFileOperation, 'id' | 'createdAt'>): Promise<void> {\n const logger = this.logger.getSubLogger({ name: 'recordOperation' });\n\n const stmt = this.db.prepare(`\n INSERT INTO file_operations (\n tool_name, operation_type, file_path, target_path, file_type,\n metadata, size_bytes, permissions, created_at, operation_id\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `);\n\n const now = Date.now();\n const metadataJson = operation.metadata ? JSON.stringify(operation.metadata) : null;\n\n stmt.run(\n operation.toolName,\n operation.operationType,\n operation.filePath,\n operation.targetPath || null,\n operation.fileType,\n metadataJson,\n operation.sizeBytes || null,\n operation.permissions || null,\n now,\n operation.operationId,\n );\n\n logger.debug(messages.operationRecorded(), operation.operationType, operation.toolName, operation.filePath);\n }\n\n async getOperations(filter: IFileOperationFilter = {}): Promise<IFileOperation[]> {\n const logger = this.logger.getSubLogger({ name: 'getOperations' });\n\n let sql = 'SELECT * FROM file_operations WHERE 1=1';\n const params: (string | number)[] = [];\n\n if (filter.toolName) {\n sql += ' AND tool_name = ?';\n params.push(filter.toolName);\n }\n\n if (filter.operationType) {\n sql += ' AND operation_type = ?';\n params.push(filter.operationType);\n }\n\n if (filter.fileType) {\n sql += ' AND file_type = ?';\n params.push(filter.fileType);\n }\n\n if (filter.filePath) {\n sql += ' AND file_path = ?';\n params.push(filter.filePath);\n }\n\n if (filter.createdAfter) {\n sql += ' AND created_at > ?';\n params.push(filter.createdAfter);\n }\n\n if (filter.createdBefore) {\n sql += ' AND created_at < ?';\n params.push(filter.createdBefore);\n }\n\n if (filter.operationId) {\n sql += ' AND operation_id = ?';\n params.push(filter.operationId);\n }\n\n sql += ' ORDER BY created_at DESC, id DESC';\n\n const stmt = this.db.prepare(sql);\n const rows = params.length > 0 ? (stmt.all(...params) as IDatabaseRow[]) : (stmt.all() as IDatabaseRow[]);\n\n logger.debug(messages.operationsRetrieved(), rows.length, filter);\n\n return rows.map((row) => ({\n id: row.id,\n toolName: row.tool_name,\n operationType: row.operation_type,\n filePath: row.file_path,\n targetPath: row.target_path ?? undefined,\n fileType: row.file_type,\n metadata: row.metadata ? JSON.parse(row.metadata) : undefined,\n sizeBytes: row.size_bytes ?? undefined,\n permissions: row.permissions ? parseInt(row.permissions, 10) : undefined,\n createdAt: parseInt(row.created_at, 10),\n operationId: row.operation_id,\n }));\n }\n\n async getFileStatesForTool(toolName: string): Promise<IFileState[]> {\n const logger = this.logger.getSubLogger({ name: 'getFileStatesForTool' });\n\n // Get all operations for this tool, ordered reverse chronologically (newest first)\n const operations = await this.getOperations({ toolName });\n const fileStates = new Map<string, IFileState>();\n\n // Process operations chronologically (oldest first) - create a copy to reverse\n for (const op of [...operations].toReversed()) {\n if (op.operationType === 'rm') {\n // Mark file as deleted by removing it from the map\n fileStates.delete(op.filePath);\n } else {\n // Create or update file state\n fileStates.set(op.filePath, {\n filePath: op.filePath,\n toolName: op.toolName,\n fileType: op.fileType,\n lastOperation: op.operationType,\n targetPath: op.targetPath,\n lastModified: op.createdAt,\n metadata: op.metadata,\n sizeBytes: op.sizeBytes,\n permissions: op.permissions,\n });\n }\n }\n\n // Return all active file states\n const activeStates = Array.from(fileStates.values());\n\n logger.debug(messages.fileStatesComputed(), activeStates.length, toolName);\n\n return activeStates;\n }\n\n async getFileState(filePath: string): Promise<IFileState | null> {\n const logger = this.logger.getSubLogger({ name: 'getFileState' });\n\n // Get all operations for this file path, ordered reverse chronologically (newest first)\n const operations = await this.getOperations({ filePath });\n\n if (operations.length === 0) {\n logger.debug(messages.noOperationsFound(), filePath);\n return null;\n }\n\n // Process operations chronologically (oldest first) to get final state\n let state: IFileState | null = null;\n\n for (const op of [...operations].toReversed()) {\n if (op.operationType === 'rm') {\n // File was deleted\n state = null;\n } else {\n // File was created/updated\n state = {\n filePath: op.filePath,\n toolName: op.toolName,\n fileType: op.fileType,\n lastOperation: op.operationType,\n targetPath: op.targetPath,\n lastModified: op.createdAt,\n metadata: op.metadata,\n sizeBytes: op.sizeBytes,\n permissions: op.permissions,\n };\n }\n }\n\n logger.debug(messages.fileStateComputed(), filePath, state ? 'active' : 'deleted');\n\n return state;\n }\n\n async getRegisteredTools(): Promise<string[]> {\n const logger = this.logger.getSubLogger({ name: 'getRegisteredTools' });\n const operations = await this.getOperations();\n const latestOperationByFilePath = new Map<string, IFileOperation>();\n\n for (const operation of operations) {\n if (!latestOperationByFilePath.has(operation.filePath)) {\n latestOperationByFilePath.set(operation.filePath, operation);\n }\n }\n\n const tools = Array.from(\n new Set(\n Array.from(latestOperationByFilePath.values())\n .filter((operation) => operation.operationType !== 'rm')\n .map((operation) => operation.toolName),\n ),\n ).toSorted();\n\n logger.debug(messages.toolsFound(), tools.length);\n\n return tools;\n }\n\n async removeToolOperations(toolName: string): Promise<void> {\n const logger = this.logger.getSubLogger({ name: 'removeToolOperations' });\n\n const stmt = this.db.prepare('DELETE FROM file_operations WHERE tool_name = ?');\n const result = stmt.run(toolName);\n\n logger.debug(messages.operationsRemoved(), result.changes, toolName);\n }\n\n async compact(): Promise<void> {\n const logger = this.logger.getSubLogger({ name: 'compact' });\n\n // This is a simplified compaction - in a full implementation,\n // we would analyze operation patterns and remove redundant entries\n const before = await this.getStats();\n\n // For now, just clean up any operations for files that were ultimately deleted\n const deletedFiles = await this.getOperations({ operationType: 'rm' });\n\n for (const deleteOp of deletedFiles) {\n // Remove all operations for this file if the final state is deleted\n const finalState = await this.getFileState(deleteOp.filePath);\n\n if (!finalState) {\n // File is ultimately deleted, remove all its operations\n const stmt = this.db.prepare('DELETE FROM file_operations WHERE file_path = ?');\n stmt.run(deleteOp.filePath);\n }\n }\n\n const after = await this.getStats();\n logger.debug(messages.compactionComplete(), before.totalOperations, after.totalOperations);\n }\n\n async validate(): Promise<{ valid: boolean; issues: string[]; repaired: string[]; }> {\n const logger = this.logger.getSubLogger({ name: 'validate' });\n const issues: string[] = [];\n const repaired: string[] = [];\n\n // Check for duplicate operation IDs within the same transaction\n const duplicateIds = this.db\n .prepare(`\n SELECT operation_id, COUNT(*) as count \n FROM file_operations \n GROUP BY operation_id \n HAVING count > 1\n `)\n .all() as { operation_id: string; count: number; }[];\n\n if (duplicateIds.length > 0) {\n issues.push(`Found ${duplicateIds.length} duplicate operation IDs`);\n }\n\n // Check for orphaned symlinks (symlinks with missing targets)\n const symlinks = await this.getOperations({ operationType: 'symlink' });\n for (const symlink of symlinks) {\n if (symlink.targetPath) {\n const targetState = await this.getFileState(symlink.targetPath);\n if (!targetState) {\n issues.push(`Symlink ${symlink.filePath} points to missing target ${symlink.targetPath}`);\n }\n }\n }\n\n logger.debug(messages.validationComplete(), issues.length, repaired.length);\n\n return {\n valid: issues.length === 0,\n issues,\n repaired,\n };\n }\n\n async getStats(): Promise<{\n totalOperations: number;\n totalFiles: number;\n totalTools: number;\n oldestOperation: number;\n newestOperation: number;\n }> {\n const totalOperations = this.db.prepare('SELECT COUNT(*) as count FROM file_operations').get() as {\n count: number;\n };\n const totalFiles = this.db.prepare('SELECT COUNT(DISTINCT file_path) as count FROM file_operations').get() as {\n count: number;\n };\n const totalTools = this.db.prepare('SELECT COUNT(DISTINCT tool_name) as count FROM file_operations').get() as {\n count: number;\n };\n const timeRange = this.db\n .prepare('SELECT MIN(created_at) as oldest, MAX(created_at) as newest FROM file_operations')\n .get() as { oldest: number; newest: number; };\n\n return {\n totalOperations: totalOperations.count,\n totalFiles: totalFiles.count,\n totalTools: totalTools.count,\n oldestOperation: timeRange.oldest || 0,\n newestOperation: timeRange.newest || 0,\n };\n }\n\n async close(): Promise<void> {\n this.db.close();\n this.logger.debug(messages.registryClosed());\n }\n\n private initializeSchema(): void {\n const logger = this.logger.getSubLogger({ name: 'initializeSchema' });\n\n // Create the main operations table\n this.db.run(`\n CREATE TABLE IF NOT EXISTS file_operations (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n tool_name TEXT NOT NULL,\n operation_type TEXT NOT NULL,\n file_path TEXT NOT NULL,\n target_path TEXT,\n file_type TEXT NOT NULL,\n metadata TEXT,\n size_bytes INTEGER,\n permissions TEXT,\n created_at INTEGER NOT NULL,\n operation_id TEXT NOT NULL\n )\n `);\n\n // Create indices for common queries\n this.db.run(`\n CREATE INDEX IF NOT EXISTS idx_tool_name ON file_operations(tool_name);\n CREATE INDEX IF NOT EXISTS idx_file_path ON file_operations(file_path);\n CREATE INDEX IF NOT EXISTS idx_operation_type ON file_operations(operation_type);\n CREATE INDEX IF NOT EXISTS idx_created_at ON file_operations(created_at);\n CREATE INDEX IF NOT EXISTS idx_operation_id ON file_operations(operation_id);\n `);\n\n logger.debug(messages.schemaInitialized());\n }\n}\n",
|
|
59
|
+
"import type { IFileSystem } from './IFileSystem';\n\nexport const resolvedFileSystemBrand = Symbol('resolvedFileSystemBrand');\nexport type ResolvedFileSystemBrand = typeof resolvedFileSystemBrand;\n\n/**\n * A branded file system interface that automatically expands home directory paths (`~`).\n *\n * This interface wraps an `IFileSystem` and intercepts all path arguments to expand `~` to the\n * configured home directory (`projectConfig.paths.homeDir` or `os.homedir()` by default)\n * before delegating to the underlying file system. The brand ensures compile-time safety by\n * distinguishing file systems that perform this expansion from those that don't, preventing\n * accidental use of unexpanded paths in operations that expect them.\n *\n * @see ResolvedFileSystem - The decorator class that implements this interface.\n */\nexport interface IResolvedFileSystem extends IFileSystem {\n readonly [resolvedFileSystemBrand]: true;\n}\n",
|
|
60
|
+
"import { type DirectoryJSON, Volume } from 'memfs';\nimport type { IFileSystem } from './IFileSystem';\nexport type { DirectoryJSON }; // Re-export DirectoryJSON\n\nimport type { Stats } from 'node:fs'; // memfs Stats is compatible\n\n/**\n * In-memory implementation of the {@link IFileSystem} interface using `memfs`.\n *\n * This class provides a virtual file system that is useful for testing and\n * dry-run scenarios, allowing file operations to be performed without\n * affecting the actual file system.\n *\n * @example\n * ```typescript\n * import { MemFileSystem } from '@dotfiles/file-system';\n *\n * const fs = new MemFileSystem({\n * '/home/user/file.txt': 'Hello, world!',\n * });\n *\n * const content = await fs.readFile('/home/user/file.txt');\n * console.log(content); // 'Hello, world!'\n * ```\n *\n * @see {@link createMemFileSystem}\n * @see {@link IFileSystem}\n * @see {@link NodeFileSystem}\n */\nexport class MemFileSystem implements IFileSystem {\n private vol: Volume;\n\n /**\n * Constructs a new `MemFileSystem` instance.\n * @param json - An optional directory structure to initialize the file system with.\n */\n constructor(json?: DirectoryJSON) {\n // Always create a new, clean volume instance.\n // If json is provided, then populate. Otherwise, it's an empty volume.\n this.vol = new Volume();\n if (json) {\n this.vol.fromJSON(json);\n }\n }\n\n /**\n * @inheritdoc IFileSystem.readFile\n */\n public async readFile(path: string, encoding: BufferEncoding = 'utf8'): Promise<string> {\n // memfs vol.promises.readFile returns a string if encoding is provided, otherwise a Buffer.\n // Our IFileSystem interface expects a string.\n const content = await this.vol.promises.readFile(path, { encoding });\n return content.toString(); // Ensure it's treated as string, as per memfs behavior with encoding.\n }\n\n /**\n * @inheritdoc IFileSystem.readFileBuffer\n */\n public async readFileBuffer(path: string): Promise<Buffer> {\n // memfs vol.promises.readFile returns a Buffer when no encoding is provided\n const content = await this.vol.promises.readFile(path);\n return Buffer.from(content);\n }\n\n /**\n * @inheritdoc IFileSystem.writeFile\n */\n public async writeFile(\n path: string,\n content: string | NodeJS.ArrayBufferView,\n encoding: BufferEncoding = 'utf8',\n ): Promise<void> {\n // memfs.promises.writeFile expects Buffer or string.\n // If content is ArrayBufferView but not Buffer, convert.\n const data = typeof content === 'string'\n ? content\n : Buffer.isBuffer(content)\n ? content\n : Buffer.from(content.buffer, content.byteOffset, content.byteLength);\n\n // The 'encoding' option in memfs.promises.writeFile applies when 'data' is a string.\n // If 'data' is a Buffer, the encoding option is ignored.\n await this.vol.promises.writeFile(path, data, { encoding });\n }\n\n /**\n * @inheritdoc IFileSystem.exists\n *\n * Uses stat() instead of access() to match Node.js behavior with broken symlinks.\n * Node.js access() fails with ENOENT for broken symlinks because it follows the symlink.\n * memfs access() incorrectly succeeds for broken symlinks, so we use stat() which\n * properly follows symlinks and fails if the target doesn't exist.\n */\n public async exists(path: string): Promise<boolean> {\n try {\n await this.vol.promises.stat(path);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * @inheritdoc IFileSystem.mkdir\n */\n public async mkdir(path: string, options?: { recursive?: boolean; }): Promise<void> {\n await this.vol.promises.mkdir(path, options);\n }\n\n /**\n * @inheritdoc IFileSystem.readdir\n */\n public async readdir(path: string): Promise<string[]> {\n // memfs.promises.readdir returns string[] | Buffer[] | Dirent[]\n // IFileSystem expects string[], so we map and convert to string.\n const entries = await this.vol.promises.readdir(path);\n return entries.map((entry) => entry.toString());\n }\n\n /**\n * @inheritdoc IFileSystem.rm\n */\n public async rm(path: string, options?: { recursive?: boolean; force?: boolean; }): Promise<void> {\n // memfs.promises.rm handles both files and directories.\n // The `force` option in memfs.promises.rm will suppress ENOENT errors.\n // The `recursive` option is needed for directories.\n\n // WORKAROUND: memfs has a bug where rm() doesn't properly remove symlinks before creating new ones.\n // Check if the target is a symlink and use unlink() instead.\n try {\n const stats = await this.vol.promises.lstat(path);\n if (stats.isSymbolicLink()) {\n await this.vol.promises.unlink(path);\n return;\n }\n } catch (e: unknown) {\n const error = e as NodeJS.ErrnoException;\n if (options?.force && error?.code === 'ENOENT') {\n return;\n }\n // If lstat fails for another reason, fall through to regular rm() which will handle it\n }\n\n try {\n await this.vol.promises.rm(path, options);\n } catch (e: unknown) {\n // If force is true and error is ENOENT, suppress it. Otherwise, rethrow.\n const error = e as NodeJS.ErrnoException;\n if (options?.force && error?.code === 'ENOENT') {\n return;\n }\n throw e;\n }\n }\n\n /**\n * @inheritdoc IFileSystem.rmdir\n */\n public async rmdir(path: string, options?: { recursive?: boolean; }): Promise<void> {\n await this.vol.promises.rmdir(path, options);\n }\n\n /**\n * @inheritdoc IFileSystem.stat\n */\n public async stat(path: string): Promise<Stats> {\n // memfs.promises.stat returns a Promise<Stats>.\n // The Stats object from memfs is generally compatible with node:fs Stats.\n // vol.promises.stat on a link path returns stats of the target (like Node's fs.stat).\n // For link's own stats, lstat should be used.\n const stats = await this.vol.promises.stat(path);\n return stats as Stats; // Cast to Node's Stats type for interface compatibility\n }\n\n /**\n * @inheritdoc IFileSystem.lstat\n */\n public async lstat(path: string): Promise<Stats> {\n // memfs.promises.lstat returns a Promise<Stats> for the link itself.\n const stats = await this.vol.promises.lstat(path);\n return stats as Stats; // Cast to Node's Stats type\n }\n\n /**\n * @inheritdoc IFileSystem.symlink\n */\n public async symlink(\n target: string,\n path: string,\n type?: 'file' | 'dir' | 'junction', // memfs.promises.symlink also accepts type, behavior might vary.\n ): Promise<void> {\n // memfs.promises.symlink type argument might be handled differently than node:fs.\n // We pass it along; memfs typically infers if not strictly 'file'/'dir'.\n await this.vol.promises.symlink(target, path, type);\n }\n\n /**\n * @inheritdoc IFileSystem.readlink\n */\n public async readlink(path: string): Promise<string> {\n // memfs.promises.readlink returns a Promise<string | Buffer>.\n // IFileSystem expects Promise<string>.\n const linkString = await this.vol.promises.readlink(path);\n return linkString.toString();\n }\n\n /**\n * @inheritdoc IFileSystem.chmod\n */\n public async chmod(path: string, mode: number | string): Promise<void> {\n await this.vol.promises.chmod(path, typeof mode === 'string' ? parseInt(mode, 8) : mode);\n }\n\n /**\n * @inheritdoc IFileSystem.copyFile\n */\n public async copyFile(src: string, dest: string, flags?: number): Promise<void> {\n // memfs.promises.copyFile is available and preferred.\n // The `flags` argument is part of the Node.js fs.copyFile signature,\n // memfs.promises.copyFile also accepts it.\n await this.vol.promises.copyFile(src, dest, flags);\n }\n\n /**\n * @inheritdoc IFileSystem.rename\n */\n public async rename(oldPath: string, newPath: string): Promise<void> {\n await this.vol.promises.rename(oldPath, newPath);\n }\n\n /**\n * @inheritdoc IFileSystem.ensureDir\n */\n public async ensureDir(path: string): Promise<void> {\n // ensureDir is equivalent to mkdir with recursive: true.\n // memfs.promises.mkdir will not throw if the directory already exists when recursive is true.\n await this.vol.promises.mkdir(path, { recursive: true });\n }\n\n /**\n * Returns the underlying `memfs` volume instance.\n *\n * This method is intended for testing purposes, allowing direct manipulation\n * and inspection of the in-memory file system.\n *\n * @returns The `Volume` instance.\n *\n * @internal\n */\n public getVolume(): Volume {\n return this.vol;\n }\n}\n",
|
|
61
|
+
"import type { Stats } from 'node:fs';\nimport { constants as fsConstants, promises as fsPromises } from 'node:fs';\nimport type { IFileSystem } from './IFileSystem';\n\ntype FsPromises = typeof fsPromises;\n\n/**\n * A concrete implementation of the {@link IFileSystem} interface that uses the\n * Node.js `fs` module.\n *\n * This class is a thin wrapper around `fs.promises`, providing an\n * object-oriented and testable way to interact with the file system.\n *\n * @example\n * ```typescript\n * import { NodeFileSystem } from '@dotfiles/file-system';\n *\n * const fs = new NodeFileSystem();\n *\n * async function main() {\n * await fs.writeFile('hello.txt', 'Hello, world!');\n * const content = await fs.readFile('hello.txt');\n * console.log(content); // 'Hello, world!'\n * await fs.rm('hello.txt');\n * }\n *\n * main();\n * ```\n *\n * @see {@link IFileSystem}\n * @see {@link MemFileSystem}\n */\nexport class NodeFileSystem implements IFileSystem {\n private readonly fs: FsPromises;\n private readonly constants: typeof fsConstants;\n\n /**\n * Constructs a new `NodeFileSystem` instance.\n * @param fs - An optional `fs.promises` compatible object.\n * @param constants - An optional `fs.constants` compatible object.\n *\n * @internal\n */\n constructor(fs: FsPromises = fsPromises, constants: typeof fsConstants = fsConstants) {\n this.fs = fs;\n this.constants = constants;\n }\n\n /**\n * @inheritdoc IFileSystem.readFile\n */\n public async readFile(path: string, encoding: BufferEncoding = 'utf8'): Promise<string> {\n return this.fs.readFile(path, { encoding });\n }\n\n /**\n * @inheritdoc IFileSystem.readFileBuffer\n */\n public async readFileBuffer(path: string): Promise<Buffer> {\n return this.fs.readFile(path);\n }\n\n /**\n * @inheritdoc IFileSystem.writeFile\n */\n public async writeFile(\n path: string,\n content: string | NodeJS.ArrayBufferView,\n encoding: BufferEncoding = 'utf8',\n ): Promise<void> {\n return this.fs.writeFile(path, content, { encoding });\n }\n\n /**\n * @inheritdoc IFileSystem.exists\n */\n public async exists(path: string): Promise<boolean> {\n try {\n await this.fs.access(path, this.constants.F_OK);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * @inheritdoc IFileSystem.mkdir\n */\n public async mkdir(path: string, options?: { recursive?: boolean; }): Promise<void> {\n await this.fs.mkdir(path, options);\n }\n\n /**\n * @inheritdoc IFileSystem.readdir\n */\n public async readdir(path: string): Promise<string[]> {\n return this.fs.readdir(path);\n }\n\n /**\n * @inheritdoc IFileSystem.rm\n */\n public async rm(path: string, options?: { recursive?: boolean; force?: boolean; }): Promise<void> {\n return this.fs.rm(path, options);\n }\n\n /**\n * @inheritdoc IFileSystem.rmdir\n */\n public async rmdir(path: string, options?: { recursive?: boolean; }): Promise<void> {\n if (options?.recursive) {\n return this.fs.rm(path, { recursive: true, force: true });\n }\n return this.fs.rmdir(path);\n }\n\n /**\n * @inheritdoc IFileSystem.stat\n */\n public async stat(path: string): Promise<Stats> {\n return this.fs.stat(path);\n }\n\n /**\n * @inheritdoc IFileSystem.lstat\n */\n public async lstat(path: string): Promise<Stats> {\n return this.fs.lstat(path);\n }\n\n /**\n * @inheritdoc IFileSystem.symlink\n */\n public async symlink(target: string, path: string, type?: 'file' | 'dir' | 'junction'): Promise<void> {\n return this.fs.symlink(target, path, type);\n }\n\n /**\n * @inheritdoc IFileSystem.readlink\n */\n public async readlink(path: string): Promise<string> {\n return this.fs.readlink(path);\n }\n\n /**\n * @inheritdoc IFileSystem.chmod\n */\n public async chmod(path: string, mode: number | string): Promise<void> {\n return this.fs.chmod(path, mode);\n }\n\n /**\n * @inheritdoc IFileSystem.copyFile\n */\n public async copyFile(src: string, dest: string, flags?: number): Promise<void> {\n return this.fs.copyFile(src, dest, flags);\n }\n\n /**\n * @inheritdoc IFileSystem.rename\n */\n public async rename(oldPath: string, newPath: string): Promise<void> {\n return this.fs.rename(oldPath, newPath);\n }\n\n /**\n * @inheritdoc IFileSystem.ensureDir\n */\n public async ensureDir(path: string): Promise<void> {\n // this.fs.mkdir with recursive: true already behaves like ensureDir\n // It doesn't throw an error if the directory already exists.\n await this.fs.mkdir(path, { recursive: true });\n }\n}\n",
|
|
62
|
+
"import { expandHomePath } from '@dotfiles/utils';\nimport type { IFileSystem, NodeStats } from './IFileSystem';\nimport type { IResolvedFileSystem } from './IResolvedFileSystem';\nimport { resolvedFileSystemBrand } from './IResolvedFileSystem';\n\nexport class ResolvedFileSystem implements IResolvedFileSystem {\n public readonly [resolvedFileSystemBrand]: true;\n\n private readonly inner: IFileSystem;\n private readonly homeDir: string;\n\n public constructor(inner: IFileSystem, homeDir: string) {\n this.inner = inner;\n this.homeDir = homeDir;\n this[resolvedFileSystemBrand] = true;\n }\n\n public async readFile(filePath: string, encoding?: BufferEncoding): Promise<string> {\n return this.inner.readFile(expandHomePath(this.homeDir, filePath), encoding);\n }\n\n public async readFileBuffer(filePath: string): Promise<Buffer> {\n return this.inner.readFileBuffer(expandHomePath(this.homeDir, filePath));\n }\n\n public async writeFile(\n filePath: string,\n content: string | NodeJS.ArrayBufferView,\n encoding?: BufferEncoding,\n ): Promise<void> {\n await this.inner.writeFile(expandHomePath(this.homeDir, filePath), content, encoding);\n }\n\n public async exists(filePath: string): Promise<boolean> {\n return this.inner.exists(expandHomePath(this.homeDir, filePath));\n }\n\n public async mkdir(dirPath: string, options?: { recursive?: boolean; }): Promise<void> {\n await this.inner.mkdir(expandHomePath(this.homeDir, dirPath), options);\n }\n\n public async readdir(dirPath: string): Promise<string[]> {\n return this.inner.readdir(expandHomePath(this.homeDir, dirPath));\n }\n\n public async rm(filePath: string, options?: { recursive?: boolean; force?: boolean; }): Promise<void> {\n await this.inner.rm(expandHomePath(this.homeDir, filePath), options);\n }\n\n public async rmdir(dirPath: string, options?: { recursive?: boolean; }): Promise<void> {\n await this.inner.rmdir(expandHomePath(this.homeDir, dirPath), options);\n }\n\n public async stat(filePath: string): Promise<NodeStats> {\n return this.inner.stat(expandHomePath(this.homeDir, filePath));\n }\n\n public async lstat(filePath: string): Promise<NodeStats> {\n return this.inner.lstat(expandHomePath(this.homeDir, filePath));\n }\n\n public async symlink(target: string, linkPath: string, type?: 'file' | 'dir' | 'junction'): Promise<void> {\n await this.inner.symlink(expandHomePath(this.homeDir, target), expandHomePath(this.homeDir, linkPath), type);\n }\n\n public async readlink(linkPath: string): Promise<string> {\n return this.inner.readlink(expandHomePath(this.homeDir, linkPath));\n }\n\n public async chmod(filePath: string, mode: number | string): Promise<void> {\n await this.inner.chmod(expandHomePath(this.homeDir, filePath), mode);\n }\n\n public async copyFile(src: string, dest: string, flags?: number): Promise<void> {\n await this.inner.copyFile(expandHomePath(this.homeDir, src), expandHomePath(this.homeDir, dest), flags);\n }\n\n public async rename(oldPath: string, newPath: string): Promise<void> {\n await this.inner.rename(expandHomePath(this.homeDir, oldPath), expandHomePath(this.homeDir, newPath));\n }\n\n public async ensureDir(dirPath: string): Promise<void> {\n await this.inner.ensureDir(expandHomePath(this.homeDir, dirPath));\n }\n}\n",
|
|
63
|
+
"import type { ProjectConfig } from '@dotfiles/core';\nimport type { IResolvedFileSystem, Stats } from '@dotfiles/file-system';\nimport { resolvedFileSystemBrand } from '@dotfiles/file-system';\nimport type { SafeLogMessage, TsLogger } from '@dotfiles/logger';\nimport { contractHomePath, formatPermissions } from '@dotfiles/utils';\nimport { randomUUID } from 'node:crypto';\nimport path from 'node:path';\nimport type { IFileOperation, IFileRegistry } from './IFileRegistry';\nimport { messages } from './log-messages';\n\n/**\n * Context for tracking filesystem operations.\n * Passed to TrackedFileSystem to provide operation metadata.\n */\nexport interface ITrackingContext {\n /** Tool performing the operations */\n toolName: string;\n /** Type of file being operated on */\n fileType: IFileOperation['fileType'];\n /** UUID to group related operations */\n operationId: string;\n /** Additional metadata to store */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Wrapper around IFileSystem that automatically tracks all filesystem operations\n * in the file registry. Users don't need to interact with the registry directly.\n */\nexport class TrackedFileSystem implements IResolvedFileSystem {\n readonly [resolvedFileSystemBrand] = true as const;\n\n private readonly fs: IResolvedFileSystem;\n private readonly registry: IFileRegistry;\n private readonly logger: TsLogger;\n private readonly parentLogger: TsLogger;\n private readonly context: ITrackingContext;\n private readonly projectConfig: ProjectConfig;\n private suppressLogging = false;\n\n constructor(\n parentLogger: TsLogger,\n fs: IResolvedFileSystem,\n registry: IFileRegistry,\n context: ITrackingContext,\n projectConfig: ProjectConfig,\n ) {\n this.parentLogger = parentLogger;\n this.logger = parentLogger.getSubLogger({ name: 'TrackedFileSystem', context: context.toolName });\n this.fs = fs;\n this.registry = registry;\n this.context = context;\n this.projectConfig = projectConfig;\n }\n\n /**\n * Creates a new ITrackingContext with a unique operation ID.\n */\n static createContext(\n toolName: string,\n fileType: IFileOperation['fileType'],\n metadata?: Record<string, unknown>,\n ): ITrackingContext {\n return {\n toolName,\n fileType,\n operationId: randomUUID(),\n metadata,\n };\n }\n\n /**\n * Creates a new TrackedFileSystem with a different context.\n * Useful for changing file type or metadata within the same tool operation.\n */\n withContext(context: Partial<ITrackingContext>): TrackedFileSystem {\n const newContext: ITrackingContext = {\n ...this.context,\n ...context,\n };\n\n const newInstance = new TrackedFileSystem(\n this.parentLogger,\n this.fs,\n this.registry,\n newContext,\n this.projectConfig,\n );\n // Preserve the suppressLogging setting\n newInstance.setSuppressLogging(this.suppressLogging);\n return newInstance;\n }\n\n /**\n * Temporarily suppress logging for this TrackedFileSystem instance\n */\n setSuppressLogging(suppress: boolean): void {\n this.suppressLogging = suppress;\n }\n\n /**\n * Log info message only if logging is not suppressed\n */\n private logInfo(message: SafeLogMessage): void {\n if (!this.suppressLogging) {\n this.logger.info(message);\n }\n }\n\n /**\n * Creates a new TrackedFileSystem for a specific tool.\n * This is used to attribute filesystem operations to the correct tool.\n * Creates a logger with context set to the tool name for prefixed log output.\n */\n withToolName(toolName: string): TrackedFileSystem {\n return this.withContext({ toolName });\n }\n\n /**\n * Creates a new TrackedFileSystem for a specific file type.\n * This is used to attribute filesystem operations to the correct file type.\n */\n withFileType(fileType: IFileOperation['fileType']): TrackedFileSystem {\n return this.withContext({ fileType });\n }\n\n async readFile(filePath: string, encoding?: BufferEncoding): Promise<string> {\n // Read operations are not tracked since they don't modify the filesystem\n return this.fs.readFile(filePath, encoding);\n }\n\n async readFileBuffer(filePath: string): Promise<Buffer> {\n // Read operations are not tracked since they don't modify the filesystem\n return this.fs.readFileBuffer(filePath);\n }\n\n async writeFile(\n filePath: string,\n content: string | NodeJS.ArrayBufferView,\n encoding?: BufferEncoding,\n ): Promise<void> {\n const fileExists = await this.fs.exists(filePath);\n let contentChanged = true;\n\n // Check if content is identical to avoid unnecessary write\n if (fileExists) {\n try {\n const existingContent = await this.fs.readFile(filePath, encoding || 'utf8');\n const newContent = typeof content === 'string' ? content : content.toString();\n contentChanged = existingContent !== newContent;\n } catch {\n // If we can't read the file, assume content is different\n contentChanged = true;\n }\n }\n\n if (!contentChanged) {\n // Content is identical, skip the write operation\n return;\n }\n\n // Perform the actual file operation\n await this.fs.writeFile(filePath, content, encoding);\n\n // Get file stats for tracking\n const stats = await this.getFileStats(filePath);\n\n // Record the operation\n await this.recordOperation('writeFile', filePath, {\n sizeBytes: stats?.sizeBytes,\n permissions: stats?.permissions,\n });\n\n // Log user-facing filesystem changes\n if (!fileExists) {\n this.logInfo(messages.fileCreated(contractHomePath(this.projectConfig.paths.homeDir, filePath)));\n } else {\n this.logInfo(messages.fileUpdated(contractHomePath(this.projectConfig.paths.homeDir, filePath)));\n }\n }\n\n // Note: appendFile is not in IFileSystem interface, removing it\n\n async copyFile(src: string, dest: string, flags?: number): Promise<void> {\n // Perform the actual operation\n await this.fs.copyFile(src, dest, flags);\n\n // Get file stats for tracking\n const stats = await this.getFileStats(dest);\n\n // Record the operation\n await this.recordOperation('cp', dest, {\n targetPath: src,\n sizeBytes: stats?.sizeBytes,\n permissions: stats?.permissions,\n });\n\n this.logInfo(\n messages.fileCopied(\n contractHomePath(this.projectConfig.paths.homeDir, src),\n contractHomePath(this.projectConfig.paths.homeDir, dest),\n ),\n );\n }\n\n async rename(oldPath: string, newPath: string): Promise<void> {\n // Perform the actual operation\n await this.fs.rename(oldPath, newPath);\n\n // Get file stats for tracking\n const stats = await this.getFileStats(newPath);\n\n // Record the rename operation\n await this.recordOperation('rename', newPath, {\n targetPath: oldPath,\n sizeBytes: stats?.sizeBytes,\n permissions: stats?.permissions,\n });\n\n this.logInfo(\n messages.fileMoved(\n contractHomePath(this.projectConfig.paths.homeDir, oldPath),\n contractHomePath(this.projectConfig.paths.homeDir, newPath),\n ),\n );\n }\n\n async symlink(target: string, linkPath: string, type?: 'file' | 'dir' | 'junction'): Promise<void> {\n // Perform the actual operation\n await this.fs.symlink(target, linkPath, type);\n\n // Record the operation using context fileType (e.g., 'completion', 'symlink', 'binary')\n await this.recordOperation('symlink', linkPath, { targetPath: target });\n\n this.logInfo(\n messages.symlinkCreated(\n contractHomePath(this.projectConfig.paths.homeDir, linkPath),\n contractHomePath(this.projectConfig.paths.homeDir, target),\n ),\n );\n }\n\n /**\n * Records an existing symlink in the registry without creating it on disk.\n * Used when a symlink already exists and is correct, but needs to be tracked.\n * Skips recording if the symlink is already registered with the same target.\n *\n * @param target - The target path the symlink points to.\n * @param linkPath - The path of the symlink.\n */\n async recordExistingSymlink(target: string, linkPath: string): Promise<void> {\n const resolvedLinkPath = path.resolve(linkPath);\n const resolvedTarget = path.resolve(target);\n\n // Check if symlink is already registered with the same target\n const existingState = await this.registry.getFileState(resolvedLinkPath);\n if (existingState && existingState.targetPath === resolvedTarget) {\n // Already registered with correct target, skip\n return;\n }\n\n // Record the symlink operation\n await this.recordOperation('symlink', linkPath, { targetPath: target });\n }\n\n async rm(filePath: string, options?: { recursive?: boolean; force?: boolean; }): Promise<void> {\n // If removing recursively, we need to track all files being removed\n if (options?.recursive && (await this.fs.exists(filePath))) {\n const stat = await this.fs.stat(filePath);\n if (stat.isDirectory()) {\n await this.trackDirectoryDeletion(filePath);\n } else {\n await this.trackFileDeletion(filePath);\n }\n } else if (await this.fs.exists(filePath)) {\n await this.trackFileDeletion(filePath);\n }\n\n // Perform the actual operation\n await this.fs.rm(filePath, options);\n\n this.logInfo(messages.fileRemoved(contractHomePath(this.projectConfig.paths.homeDir, filePath)));\n }\n\n async chmod(filePath: string, mode: string | number): Promise<void> {\n // Perform the actual operation\n await this.fs.chmod(filePath, mode);\n\n // Get updated file stats\n const stats = await this.getFileStats(filePath);\n\n // Record as chmod operation\n await this.recordOperation('chmod', filePath, { permissions: stats?.permissions });\n\n this.logInfo(\n messages.permissionsChanged(\n contractHomePath(this.projectConfig.paths.homeDir, filePath),\n formatPermissions(mode),\n ),\n );\n }\n\n // Non-modifying operations - these don't need tracking\n async exists(filePath: string): Promise<boolean> {\n return this.fs.exists(filePath);\n }\n\n async stat(filePath: string): Promise<Stats> {\n return this.fs.stat(filePath);\n }\n\n async lstat(filePath: string): Promise<Stats> {\n return this.fs.lstat(filePath);\n }\n\n async readlink(filePath: string): Promise<string> {\n return this.fs.readlink(filePath);\n }\n\n async readdir(dirPath: string): Promise<string[]> {\n return this.fs.readdir(dirPath);\n }\n\n async mkdir(dirPath: string, options?: { recursive?: boolean; }): Promise<void> {\n const existed = await this.fs.exists(dirPath);\n\n // Perform the actual operation\n await this.fs.mkdir(dirPath, options);\n\n // Only track if directory was actually created\n if (!existed) {\n await this.recordOperation('mkdir', dirPath);\n\n this.logInfo(messages.directoryCreated(contractHomePath(this.projectConfig.paths.homeDir, dirPath)));\n }\n }\n\n async rmdir(dirPath: string, options?: { recursive?: boolean; }): Promise<void> {\n const logger = this.logger.getSubLogger({ name: 'rmdir' });\n\n // Track directory deletion\n if (await this.fs.exists(dirPath)) {\n if (options?.recursive) {\n await this.trackDirectoryDeletion(dirPath);\n } else {\n await this.trackFileDeletion(dirPath);\n }\n }\n\n // Perform the actual operation\n await this.fs.rmdir(dirPath, options);\n\n logger.debug(messages.rmdirTracked(), dirPath);\n }\n\n async ensureDir(dirPath: string): Promise<void> {\n const existed = await this.fs.exists(dirPath);\n\n // Perform the actual operation\n await this.fs.ensureDir(dirPath);\n\n // Only track if directory was actually created\n if (!existed) {\n await this.recordOperation('mkdir', dirPath);\n\n this.logInfo(messages.directoryCreated(contractHomePath(this.projectConfig.paths.homeDir, dirPath)));\n }\n }\n\n /**\n * Helper method to get file stats for tracking purposes.\n */\n private async getFileStats(filePath: string): Promise<{ sizeBytes: number; permissions: number; } | null> {\n try {\n const stats = await this.fs.stat(filePath);\n return {\n sizeBytes: stats.size,\n permissions: stats.mode & 0o777,\n };\n } catch {\n return null;\n }\n }\n\n /**\n * Helper method to record a file operation with common context fields.\n */\n private async recordOperation(\n operationType: IFileOperation['operationType'],\n filePath: string,\n options?: {\n targetPath?: string;\n sizeBytes?: number;\n permissions?: number;\n },\n ): Promise<void> {\n await this.registry.recordOperation({\n toolName: this.context.toolName,\n operationType,\n filePath: path.resolve(filePath),\n fileType: this.context.fileType,\n operationId: this.context.operationId,\n metadata: this.context.metadata,\n targetPath: options?.targetPath ? path.resolve(options.targetPath) : undefined,\n sizeBytes: options?.sizeBytes,\n permissions: options?.permissions,\n });\n }\n\n /**\n * Tracks deletion of a single file.\n */\n private async trackFileDeletion(filePath: string): Promise<void> {\n await this.recordOperation('rm', filePath);\n }\n\n /**\n * Recursively tracks deletion of a directory and all its contents.\n */\n private async trackDirectoryDeletion(dirPath: string): Promise<void> {\n try {\n const entries = await this.fs.readdir(dirPath);\n\n for (const entry of entries) {\n const fullPath = path.join(dirPath, entry);\n const stat = await this.fs.stat(fullPath);\n\n if (stat.isDirectory()) {\n await this.trackDirectoryDeletion(fullPath);\n } else {\n await this.trackFileDeletion(fullPath);\n }\n }\n\n // Track the directory itself\n await this.trackFileDeletion(dirPath);\n } catch (error) {\n this.logger.debug(\n messages.directoryDeletionError(),\n dirPath,\n error instanceof Error ? error.message : String(error),\n );\n }\n }\n}\n",
|
|
64
|
+
"import type { IFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { TrackedFileSystem } from '@dotfiles/registry/file';\nimport path from 'node:path';\n\nimport { messages } from './log-messages';\n\nexport async function createBinaryEntrypoint(\n fs: IFileSystem,\n toolName: string,\n binaryName: string,\n timestamp: string,\n binaryPath: string,\n binariesDir: string,\n parentLogger: TsLogger,\n): Promise<void> {\n const logger = parentLogger.getSubLogger({ name: 'createBinaryEntrypoint' });\n const toolDir = path.join(binariesDir, toolName);\n const timestampedDir = path.join(toolDir, timestamp);\n const entrypointPath = path.join(timestampedDir, binaryName);\n const actualBinaryPath = path.join(timestampedDir, binaryPath);\n\n if (actualBinaryPath === entrypointPath) {\n return;\n }\n\n if (!(await fs.exists(actualBinaryPath))) {\n const errorMsg = `Cannot create entrypoint: target binary does not exist at ${actualBinaryPath}`;\n logger.error(messages.binarySymlink.targetBinaryMissing(toolName, binaryName, actualBinaryPath));\n throw new Error(errorMsg);\n }\n\n await fs.ensureDir(timestampedDir);\n\n try {\n if (await fs.exists(entrypointPath)) {\n logger.debug(messages.binarySymlink.removingExisting(entrypointPath));\n await fs.rm(entrypointPath, { force: true });\n }\n } catch (error) {\n logger.error(messages.binarySymlink.removeExistingFailed(entrypointPath), error);\n const reason = error instanceof Error ? error.message : String(error);\n throw new Error(`Failed to remove old entrypoint ${entrypointPath}: ${reason}`, { cause: error });\n }\n\n try {\n const targetPath = path.relative(timestampedDir, actualBinaryPath);\n logger.debug(messages.binarySymlink.creating(entrypointPath, targetPath));\n\n // Use withFileType('symlink') so the symlink is recorded with correct fileType\n const symlinkFs = fs instanceof TrackedFileSystem ? fs.withFileType('symlink') : fs;\n await symlinkFs.symlink(targetPath, entrypointPath);\n\n const entrypointStats = await fs.lstat(entrypointPath);\n if (!entrypointStats.isSymbolicLink()) {\n throw new Error('Entrypoint unexpectedly created as regular file instead of symlink');\n }\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n logger.error(messages.binarySymlink.creationFailed(entrypointPath, actualBinaryPath), error);\n throw new Error(`Failed to create entrypoint at ${entrypointPath}: ${reason}`, { cause: error });\n }\n\n const didEntrypointCreate: boolean = await fs.exists(entrypointPath);\n if (!didEntrypointCreate) {\n logger.error(messages.binarySymlink.verificationFailed(entrypointPath));\n throw new Error(`Entrypoint creation appeared to succeed but file does not exist at ${entrypointPath}`);\n }\n\n logger.debug(messages.binarySymlink.createdAndVerified(entrypointPath, actualBinaryPath));\n}\n\nexport async function createAllBinaryEntrypoints(\n fs: IFileSystem,\n toolName: string,\n binaries: string[],\n timestamp: string,\n binaryBasePath: string,\n binariesDir: string,\n parentLogger: TsLogger,\n): Promise<void> {\n const logger = parentLogger.getSubLogger({ name: 'createAllBinaryEntrypoints' });\n for (const binaryName of binaries) {\n const binaryPath = path.join(binaryBasePath, binaryName);\n await createBinaryEntrypoint(fs, toolName, binaryName, timestamp, binaryPath, binariesDir, logger);\n }\n}\n",
|
|
65
|
+
"import { createSafeLogMessage, type SafeLogMessageMap } from '@dotfiles/logger';\nexport const messages = {\n binarySetupService: {\n binaryNotFound: (binaryName: string, pattern: string) =>\n createSafeLogMessage(`Binary ${binaryName} not found at ${pattern}, skipping`),\n extractedFilesTree: (extractDir: string, treeLines: string) =>\n createSafeLogMessage(`Extracted files in ${extractDir}:\\n${treeLines}`),\n cleaningFailedInstall: (extractDir: string) =>\n createSafeLogMessage(`No binaries found, cleaning up installation directory: ${extractDir}`),\n fallbackCleanup: (extractDir: string) =>\n createSafeLogMessage(`Directory still exists after rm(), trying rmdir: ${extractDir}`),\n searchingWithPattern: (pattern: string, directoryPath: string) =>\n createSafeLogMessage(`Searching for binary using pattern ${pattern} in directory ${directoryPath}`),\n fallbackPattern: (pattern: string, directoryPath: string) =>\n createSafeLogMessage(`Trying fallback binary pattern ${pattern} in directory ${directoryPath}`),\n patternPathMissing: (missingPath: string) => createSafeLogMessage(`Pattern path does not exist: ${missingPath}`),\n noPatternMatch: (patternSegment: string, directoryPath: string) =>\n createSafeLogMessage(`No matches found for pattern ${patternSegment} in directory ${directoryPath}`),\n directDownloadSingleBinary: (configuredCount: number, primaryBinary: string) =>\n createSafeLogMessage(\n `Direct download only provides one binary, but ${configuredCount} were configured. Only ${primaryBinary} will be available.`,\n ),\n patternDebug: (pattern: string, parts: string[], extractDir: string) =>\n createSafeLogMessage(`Pattern: ${pattern}, Parts: [${parts.join(', ')}], ExtractDir: ${extractDir}`),\n processingPart: (part: string, currentDir: string) =>\n createSafeLogMessage(`Processing part: \"${part}\", currentDir: ${currentDir}`),\n wildcardMatchResult: (matchedDir: string | null) =>\n createSafeLogMessage(`Wildcard match result: ${matchedDir ?? 'null'}`),\n directPath: (currentDir: string) => createSafeLogMessage(`Direct path: ${currentDir}`),\n finalResult: (currentDir: string) => createSafeLogMessage(`Final result: ${currentDir}`),\n } satisfies SafeLogMessageMap,\n binarySymlink: {\n targetBinaryMissing: (toolName: string, binaryName: string, targetPath: string) =>\n createSafeLogMessage(\n `Cannot create entrypoint for ${toolName}/${binaryName}: target binary missing at ${targetPath}`,\n ),\n removingExisting: (symlinkPath: string) => createSafeLogMessage(`Removing old entrypoint: ${symlinkPath}`),\n removeExistingFailed: (symlinkPath: string) =>\n createSafeLogMessage(`Failed to remove old entrypoint ${symlinkPath}`),\n creating: (symlinkPath: string, targetPath: string) =>\n createSafeLogMessage(`Creating entrypoint: ${symlinkPath} <- ${targetPath}`),\n creationFailed: (symlinkPath: string, targetPath: string) =>\n createSafeLogMessage(`Failed to create entrypoint ${symlinkPath} <- ${targetPath}`),\n verificationMismatch: (symlinkPath: string, expectedTarget: string, actualTarget: string) =>\n createSafeLogMessage(`Entrypoint ${symlinkPath} is ${actualTarget}, expected ${expectedTarget}`),\n verificationFailed: (symlinkPath: string) => createSafeLogMessage(`Failed to verify entrypoint ${symlinkPath}`),\n createdAndVerified: (symlinkPath: string, targetPath: string) =>\n createSafeLogMessage(`Successfully created and verified entrypoint: ${symlinkPath} <- ${targetPath}`),\n } satisfies SafeLogMessageMap,\n lifecycle: {\n startingInstallation: (toolName: string) => createSafeLogMessage(`Starting installation for ${toolName}`),\n hookExecution: (hookName: string) => createSafeLogMessage(`install: Running ${hookName} hook`),\n directoryCreated: (directoryPath: string) =>\n createSafeLogMessage(`install: Created installation directory: ${directoryPath}`),\n directoryRenamed: (oldPath: string, newPath: string) =>\n createSafeLogMessage(`install: Renamed installation directory from ${oldPath} to ${newPath}`),\n cleaningFailedInstallDir: (directoryPath: string) =>\n createSafeLogMessage(`install: Cleaning up failed installation directory: ${directoryPath}`),\n versionResolved: (version: string) => createSafeLogMessage(`install: Resolved version: ${version}`),\n versionFallbackToTimestamp: () =>\n createSafeLogMessage('install: Version resolution returned null, using timestamp'),\n versionResolutionFailed: (error: unknown) =>\n createSafeLogMessage(`install: Version resolution failed, using timestamp: ${String(error)}`),\n externalBinaryMissing: (toolName: string, binaryName: string, binaryPath: string) =>\n createSafeLogMessage(\n `Cannot create symlink for ${toolName}/${binaryName}: external binary missing at ${binaryPath}`,\n ),\n removingExistingSymlink: (symlinkPath: string) => createSafeLogMessage(`Removing existing symlink: ${symlinkPath}`),\n creatingExternalSymlink: (symlinkPath: string, targetPath: string) =>\n createSafeLogMessage(`Creating external symlink: ${symlinkPath} -> ${targetPath}`),\n symlinkVerificationFailed: (symlinkPath: string) =>\n createSafeLogMessage(`Symlink verification failed: ${symlinkPath}`),\n externalSymlinkCreated: (symlinkPath: string, targetPath: string) =>\n createSafeLogMessage(`External symlink created: ${symlinkPath} -> ${targetPath}`),\n } satisfies SafeLogMessageMap,\n outcome: {\n installSuccess: (toolName: string, version: string, method: string) =>\n createSafeLogMessage(`Tool \"${toolName}\" \\`${version}\\` installed successfully using ${method}`),\n outdatedVersion: (toolName: string, currentVersion: string, latestVersion: string) =>\n createSafeLogMessage(`Tool \"${toolName}\" version ${currentVersion} is outdated (latest: ${latestVersion})`),\n installFailed: (method: string) => createSafeLogMessage(`Installation failed via ${method}`),\n hookFailed: (cause: string) => createSafeLogMessage(`Hook failed: ${cause}`),\n unsupportedOperation: (operation: string, details: string) =>\n createSafeLogMessage(`${operation} not yet supported (${details})`),\n } satisfies SafeLogMessageMap,\n\n archive: {\n extracting: (pathOrName: string) => createSafeLogMessage(`Extracting archive: ${pathOrName}`),\n extracted: () => createSafeLogMessage('Archive extracted: %o'),\n cleaning: (resourcePath: string) => createSafeLogMessage(`Cleaning up downloaded archive: ${resourcePath}`),\n } satisfies SafeLogMessageMap,\n\n binaryMovement: {\n moving: (sourcePath: string, targetPath: string) =>\n createSafeLogMessage(`Moving binary from ${sourcePath} to ${targetPath}`),\n } satisfies SafeLogMessageMap,\n\n completion: {\n noCompletionsConfigured: () => createSafeLogMessage('install: no completions configured'),\n generatingCompletions: (count: number) => createSafeLogMessage(`install: generating ${count} completion files`),\n generatedCompletion: (filename: string, targetPath: string) =>\n createSafeLogMessage(`install: generated completion: ${filename} -> ${targetPath}`),\n symlinking: (shellType: string, sourcePath: string, targetFile: string) =>\n createSafeLogMessage(`Symlinking completion for ${shellType} from ${sourcePath} to ${targetFile}`),\n notFound: (sourcePath: string) => createSafeLogMessage(`Completion file not found: ${sourcePath}`),\n directoryListing: (filePath: string) => createSafeLogMessage(` ${filePath}`),\n } satisfies SafeLogMessageMap,\n hookExecutor: {\n executingHook: (hookName: string, timeoutMs: number) =>\n createSafeLogMessage(`Executing ${hookName} hook with ${timeoutMs}ms timeout`),\n hookCompleted: (hookName: string, durationMs: number) =>\n createSafeLogMessage(`Hook ${hookName} completed successfully in ${durationMs}ms`),\n continuingDespiteFailure: (hookName: string) =>\n createSafeLogMessage(`Continuing installation despite ${hookName} hook failure`),\n stoppingDueToFailure: (hookName: string) =>\n createSafeLogMessage(`Stopping hook execution due to failure in ${hookName} hook`),\n timeoutExceeded: (hookName: string, timeoutMs: number) =>\n createSafeLogMessage(`Hook ${hookName} timed out after ${timeoutMs}ms`),\n } satisfies SafeLogMessageMap,\n} as const;\n",
|
|
66
|
+
"import { hasLoggingShell, loggingShellBrand, type Shell } from '@dotfiles/core';\n\n/**\n * Creates a configured shell instance that automatically applies the provided environment variables\n * to all commands executed with it.\n *\n * With Shell, subsequent .env() calls are additive (they merge with existing env vars),\n * so we simply need to ensure the base environment is applied to each command.\n *\n * @param $shell - The base Shell instance\n * @param env - The environment variables to apply to all commands\n * @returns A new shell instance that wraps the base shell with the provided environment\n */\nexport function createConfiguredShell(\n $shell: Shell,\n env: Record<string, string | undefined>,\n): Shell {\n // Create a wrapper function that applies the environment to every command\n const configuredShell = (first: TemplateStringsArray | string, ...expressions: unknown[]) => {\n if (typeof first === 'string') {\n return $shell(first).env(env);\n }\n return $shell(first, ...expressions).env(env);\n };\n\n // Copy all properties from the original shell to the wrapper\n Object.assign(configuredShell, $shell);\n\n // Preserve the logging shell brand if present on the source shell\n // (Object.assign doesn't copy non-enumerable symbol properties)\n if (hasLoggingShell($shell)) {\n Object.defineProperty(configuredShell, loggingShellBrand, { value: true, enumerable: false });\n }\n\n return configuredShell as unknown as Shell;\n}\n",
|
|
67
|
+
"import type { IFileSystem } from '@dotfiles/file-system';\nimport { TrackedFileSystem } from '@dotfiles/registry/file';\n\n/**\n * Creates a tool-specific file system instance for proper operation tracking.\n * If the provided filesystem is a TrackedFileSystem, returns a new instance scoped to the tool name.\n * Otherwise, returns the original filesystem unchanged.\n *\n * This enables the registry to track which files belong to which tools by associating\n * all file operations with the tool name.\n *\n * @param fs - Base file system instance (may be TrackedFileSystem)\n * @param toolName - Name of the tool for tracking operations\n * @returns Tool-scoped file system or original filesystem\n */\nexport function createToolFileSystem(fs: IFileSystem, toolName: string): IFileSystem {\n return fs instanceof TrackedFileSystem ? fs.withToolName(toolName) : fs;\n}\n",
|
|
68
|
+
"import type { IFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport crypto from 'node:crypto';\nimport path from 'node:path';\nimport { messages } from './log-messages';\nimport type {\n CacheEntry,\n IBinaryCacheEntry,\n ICache,\n ICacheConfig,\n IDownloadCacheEntry,\n IJsonCacheEntry,\n} from './types';\n\n/**\n * File-based cache implementation that supports both JSON and binary storage strategies.\n * - JSON strategy: Stores everything in a single JSON file (good for small API responses)\n * - Binary strategy: Stores binary content separately with JSON metadata (good for large files)\n */\nexport class FileCache implements ICache {\n private readonly config: ICacheConfig;\n private readonly fileSystem: IFileSystem;\n private readonly logger: TsLogger;\n private readonly metadataDir: string;\n private readonly binariesDir?: string;\n\n constructor(parentLogger: TsLogger, fileSystem: IFileSystem, config: ICacheConfig) {\n this.logger = parentLogger.getSubLogger({ name: 'FileCache' });\n this.fileSystem = fileSystem;\n this.config = config;\n\n if (config.storageStrategy === 'json') {\n // For JSON strategy, everything goes in the main cache directory\n this.metadataDir = config.cacheDir;\n } else {\n // For binary strategy, separate metadata and binaries\n this.metadataDir = path.join(config.cacheDir, 'metadata');\n this.binariesDir = path.join(config.cacheDir, 'binaries');\n }\n\n this.logger.debug(messages.initialized(config.cacheDir, config.defaultTtl, config.storageStrategy, config.enabled));\n }\n\n async get<T>(key: string): Promise<T | null> {\n const logger = this.logger.getSubLogger({ name: 'get' });\n if (!this.config.enabled) {\n logger.debug(messages.cachingDisabled('returning null', key));\n return null;\n }\n\n try {\n const metadataPath = this.getMetadataFilePath(key);\n\n if (!(await this.fileSystem.exists(metadataPath))) {\n logger.debug(messages.entryMissing(key));\n return null;\n }\n\n const metadataContent = await this.fileSystem.readFile(metadataPath, 'utf8');\n const entry: CacheEntry<T> = JSON.parse(metadataContent);\n\n // Check if entry is expired\n if (this.isExpired(entry)) {\n logger.debug(messages.entryExpired(key));\n await this.deleteEntry(key, entry);\n return null;\n }\n\n if (entry.type === 'json') {\n // For JSON entries, data is stored directly\n logger.debug(messages.cacheHit(key, 'JSON'));\n return entry.data;\n } else {\n // For binary entries, data is in a separate file\n if (!this.binariesDir) {\n throw new Error(messages.binaryDirectoryNotConfigured());\n }\n\n const binaryPath = path.join(this.binariesDir, entry.binaryFileName);\n if (!(await this.fileSystem.exists(binaryPath))) {\n logger.warn(messages.binaryFileMissing(key, binaryPath));\n await this.fileSystem.rm(metadataPath).catch(() => {}); // Clean up orphaned metadata\n return null;\n }\n\n const binaryContent = await this.fileSystem.readFile(binaryPath);\n const buffer = Buffer.isBuffer(binaryContent) ? binaryContent : Buffer.from(binaryContent);\n\n // Verify content integrity for binary data\n const actualHash = crypto.createHash('sha256').update(buffer).digest('hex');\n const expectedHash = entry.contentHash;\n\n if (actualHash !== expectedHash) {\n logger.warn(messages.contentHashMismatch(key, expectedHash, actualHash));\n await this.deleteEntry(key, entry);\n return null;\n }\n\n logger.debug(messages.cacheHit(key, 'binary', buffer.length));\n return buffer as T;\n }\n } catch (error) {\n logger.warn(messages.retrievalFailed(key, this.getErrorMessage(error)));\n return null;\n }\n }\n\n async set<T>(key: string, data: T, ttlMs?: number): Promise<void> {\n const logger = this.logger.getSubLogger({ name: 'set' });\n if (!this.config.enabled) {\n logger.debug(messages.cachingDisabled('skipping set', key));\n return;\n }\n\n try {\n await this.ensureCacheDirectories();\n\n const actualTtlMs = ttlMs ?? this.config.defaultTtl;\n const now = Date.now();\n\n if (this.config.storageStrategy === 'json') {\n // For JSON strategy, store everything in one file\n const entry: IJsonCacheEntry<T> = {\n type: 'json',\n data,\n timestamp: now,\n expiresAt: now + actualTtlMs,\n };\n\n const metadataPath = this.getMetadataFilePath(key);\n await this.fileSystem.writeFile(metadataPath, JSON.stringify(entry, null, 2), 'utf8');\n\n logger.debug(messages.cacheStored(key, 'JSON', new Date(entry.expiresAt).toISOString()));\n } else {\n // For binary strategy, store data and metadata separately\n if (!Buffer.isBuffer(data)) {\n throw new Error(messages.binaryDataRequired());\n }\n\n if (!this.binariesDir) {\n throw new Error(messages.binaryDirectoryNotConfigured());\n }\n\n const buffer = data;\n const contentHash = crypto.createHash('sha256').update(buffer).digest('hex');\n const binaryFileName = `${contentHash}.bin`;\n const binaryFilePath = path.join(this.binariesDir, binaryFileName);\n\n // Write binary content to file\n await this.fileSystem.writeFile(binaryFilePath, buffer);\n\n // Store metadata with reference to binary file\n const entry: IBinaryCacheEntry = {\n type: 'binary',\n binaryFileName,\n contentHash,\n size: buffer.length,\n timestamp: now,\n expiresAt: now + actualTtlMs,\n };\n\n const metadataPath = this.getMetadataFilePath(key);\n await this.fileSystem.writeFile(metadataPath, JSON.stringify(entry, null, 2), 'utf8');\n\n logger.debug(messages.cacheStored(key, 'binary', new Date(entry.expiresAt).toISOString(), buffer.length));\n }\n } catch (error) {\n const errorMessage = this.getErrorMessage(error);\n logger.warn(messages.storageFailed(key, errorMessage));\n throw new Error(`Failed to cache data: ${errorMessage}`, { cause: error });\n }\n }\n\n async setDownload(\n key: string,\n data: Buffer,\n ttlMs: number | undefined,\n url: string,\n contentType?: string,\n ): Promise<void> {\n const logger = this.logger.getSubLogger({ name: 'setDownload' });\n if (!this.config.enabled) {\n logger.debug(messages.cachingDisabled('skipping setDownload', key));\n return;\n }\n\n try {\n await this.ensureCacheDirectories();\n\n const actualTtlMs = ttlMs ?? this.config.defaultTtl;\n const now = Date.now();\n\n if (this.config.storageStrategy === 'json') {\n throw new Error('Download caching requires binary storage strategy');\n }\n\n if (!this.binariesDir) {\n throw new Error(messages.binaryDirectoryNotConfigured());\n }\n\n const contentHash = crypto.createHash('sha256').update(data).digest('hex');\n const binaryFileName = `${contentHash}.bin`;\n const binaryFilePath = path.join(this.binariesDir, binaryFileName);\n\n // Write binary content to file\n await this.fileSystem.writeFile(binaryFilePath, data);\n\n // Store metadata with download information\n const entry: IDownloadCacheEntry = {\n type: 'binary',\n binaryFileName,\n contentHash,\n size: data.length,\n url,\n contentType,\n timestamp: now,\n expiresAt: now + actualTtlMs,\n };\n\n const metadataPath = this.getMetadataFilePath(key);\n await this.fileSystem.writeFile(metadataPath, JSON.stringify(entry, null, 2), 'utf8');\n\n logger.debug(messages.cacheStored(key, 'download', new Date(entry.expiresAt).toISOString(), data.length));\n } catch (error) {\n const errorMessage = this.getErrorMessage(error);\n logger.warn(messages.storageFailed(key, errorMessage));\n throw new Error(`Failed to cache download: ${errorMessage}`, { cause: error });\n }\n }\n\n async has(key: string): Promise<boolean> {\n const logger = this.logger.getSubLogger({ name: 'has' });\n if (!this.config.enabled) {\n logger.debug(messages.cachingDisabled('returning false', key));\n return false;\n }\n\n try {\n const metadataPath = this.getMetadataFilePath(key);\n\n if (!(await this.fileSystem.exists(metadataPath))) {\n logger.debug(messages.entryMissing(key));\n return false;\n }\n\n const metadataContent = await this.fileSystem.readFile(metadataPath, 'utf8');\n const entry: CacheEntry = JSON.parse(metadataContent);\n\n if (this.isExpired(entry)) {\n logger.debug(messages.entryExpired(key));\n return false;\n }\n\n // For binary entries, also check if binary file exists\n if (entry.type === 'binary' && this.binariesDir) {\n const binaryPath = path.join(this.binariesDir, entry.binaryFileName);\n const binaryExists = await this.fileSystem.exists(binaryPath);\n\n if (!binaryExists) {\n logger.debug(messages.binaryFileMissing(key, binaryPath));\n return false;\n }\n }\n\n logger.debug(messages.cacheEntryExists(key));\n return true;\n } catch (error) {\n logger.warn(messages.checkFailed(key, this.getErrorMessage(error)));\n return false;\n }\n }\n\n async delete(key: string): Promise<void> {\n const logger = this.logger.getSubLogger({ name: 'delete' });\n if (!this.config.enabled) {\n logger.debug(messages.cachingDisabled('skipping delete', key));\n return;\n }\n\n try {\n const metadataPath = this.getMetadataFilePath(key);\n\n if (await this.fileSystem.exists(metadataPath)) {\n const metadataContent = await this.fileSystem.readFile(metadataPath, 'utf8');\n const entry: CacheEntry = JSON.parse(metadataContent);\n await this.deleteEntry(key, entry);\n logger.debug(messages.cacheEntryRemoved(key));\n } else {\n logger.debug(messages.noEntryToDelete(key));\n }\n } catch (error) {\n const errorMessage = this.getErrorMessage(error);\n logger.warn(messages.deleteFailed(key, errorMessage));\n throw new Error(`Failed to delete cache entry: ${errorMessage}`, { cause: error });\n }\n }\n\n async clearExpired(): Promise<void> {\n const logger = this.logger.getSubLogger({ name: 'clearExpired' });\n if (!this.config.enabled) {\n logger.debug(messages.cachingDisabled('skipping clearExpired', 'N/A'));\n return;\n }\n\n try {\n if (!(await this.fileSystem.exists(this.metadataDir))) {\n logger.debug(messages.cacheDirectoryMissing());\n return;\n }\n\n const metadataFiles = await this.fileSystem.readdir(this.metadataDir);\n let expiredCount = 0;\n\n for (const file of metadataFiles) {\n if (!file.endsWith('.json')) {\n continue;\n }\n\n const metadataPath = path.join(this.metadataDir, file);\n\n try {\n const metadataContent = await this.fileSystem.readFile(metadataPath, 'utf8');\n const entry: CacheEntry = JSON.parse(metadataContent);\n\n if (this.isExpired(entry)) {\n const key = path.basename(file, '.json');\n await this.deleteEntry(key, entry);\n expiredCount++;\n }\n } catch (error) {\n logger.warn(messages.metadataProcessingWarning(file, this.getErrorMessage(error)));\n // Remove problematic metadata file\n await this.fileSystem.rm(metadataPath).catch(() => {});\n expiredCount++;\n }\n }\n\n logger.debug(messages.expiredEntriesCleared(expiredCount));\n } catch (error) {\n const errorMessage = this.getErrorMessage(error);\n logger.warn(messages.clearExpiredFailed(errorMessage));\n throw new Error(`Failed to clear expired cache entries: ${errorMessage}`, { cause: error });\n }\n }\n\n async clear(): Promise<void> {\n const logger = this.logger.getSubLogger({ name: 'clear' });\n if (!this.config.enabled) {\n logger.debug(messages.cachingDisabled('skipping clear', 'N/A'));\n return;\n }\n\n try {\n if (await this.fileSystem.exists(this.config.cacheDir)) {\n await this.fileSystem.rm(this.config.cacheDir, { recursive: true, force: true });\n logger.debug(messages.cacheCleared());\n } else {\n logger.debug(messages.cacheDirectoryMissing());\n }\n // Always ensure the cache directory exists after attempting to clear\n await this.ensureCacheDirectories();\n } catch (error) {\n const errorMessage = this.getErrorMessage(error);\n logger.warn(messages.clearFailed(errorMessage));\n throw new Error(`Failed to clear cache: ${errorMessage}`, { cause: error });\n }\n }\n\n private getMetadataFilePath(key: string): string {\n const hashedKey = crypto.createHash('md5').update(key).digest('hex');\n return path.join(this.metadataDir, `${hashedKey}.json`);\n }\n\n private async ensureCacheDirectories(): Promise<void> {\n const logger = this.logger.getSubLogger({ name: 'ensureCacheDirectories' });\n try {\n await this.fileSystem.ensureDir(this.metadataDir);\n if (this.binariesDir) {\n await this.fileSystem.ensureDir(this.binariesDir);\n }\n } catch (error) {\n const errorMessage = this.getErrorMessage(error);\n logger.warn(messages.directoryCreationFailed(errorMessage));\n throw new Error(`Failed to create cache directories: ${errorMessage}`, { cause: error });\n }\n }\n\n private getErrorMessage(error: unknown): string {\n if (error instanceof Error) {\n return error.message;\n }\n\n return String(error);\n }\n\n private isExpired(entry: CacheEntry): boolean {\n return Date.now() > entry.expiresAt;\n }\n\n private async deleteEntry(key: string, entry: CacheEntry): Promise<void> {\n const metadataPath = this.getMetadataFilePath(key);\n\n // Remove metadata file\n if (await this.fileSystem.exists(metadataPath)) {\n await this.fileSystem.rm(metadataPath);\n }\n\n // Remove binary file if it's a binary entry\n if (entry.type === 'binary' && this.binariesDir) {\n const binaryPath = path.join(this.binariesDir, entry.binaryFileName);\n if (await this.fileSystem.exists(binaryPath)) {\n await this.fileSystem.rm(binaryPath);\n }\n }\n }\n}\n",
|
|
69
|
+
"import { createSafeLogMessage, type SafeLogMessageMap } from '@dotfiles/logger';\n\nexport const messages = {\n initialized: (cacheDir: string, defaultTtl: number, strategy: string, enabled: boolean) =>\n createSafeLogMessage(\n `Cache directory: ${cacheDir}, TTL: ${defaultTtl} ms, Strategy: ${strategy}, Enabled: ${enabled}`,\n ),\n cachingDisabled: (operation: string, key: string) =>\n createSafeLogMessage(`Cache disabled, ${operation} for key: ${key}`),\n entryMissing: (key: string) => createSafeLogMessage(`No cache entry found for key: ${key}`),\n entryExpired: (key: string) => createSafeLogMessage(`Cache entry expired for key: ${key}`),\n cacheHit: (key: string, strategy: string, size?: number) => {\n const sizeDescription = size !== undefined ? `, size: ${size} bytes` : '';\n return createSafeLogMessage(`Cache hit for key: ${key} (${strategy})${sizeDescription}`);\n },\n cacheStored: (key: string, strategy: string, expiresAt: string, size?: number) => {\n const sizeDescription = size !== undefined ? `, size: ${size} bytes` : '';\n return createSafeLogMessage(`Cached data for key: ${key} (${strategy})${sizeDescription}, expires: ${expiresAt}`);\n },\n cacheEntryRemoved: (key: string) => createSafeLogMessage(`Removed cache entry for key: ${key}`),\n cacheCleared: () => createSafeLogMessage('Removed entire cache directory'),\n expiredEntriesCleared: (count: number) => createSafeLogMessage(`Removed ${count} expired cache entries`),\n cacheEntryExists: (key: string) => createSafeLogMessage(`Valid cache entry exists for key: ${key}`),\n noEntryToDelete: (key: string) => createSafeLogMessage(`No cache entry to delete for key: ${key}`),\n cacheDirectoryMissing: () => createSafeLogMessage('Cache directory does not exist, nothing to clear'),\n binaryFileMissing: (key: string, filePath: string) =>\n createSafeLogMessage(`Binary file missing for key: ${key}, path: ${filePath}`),\n contentHashMismatch: (key: string, expected: string, actual: string) =>\n createSafeLogMessage(`Content hash mismatch for key: ${key}, expected: ${expected}, actual: ${actual}`),\n metadataProcessingWarning: (file: string, reason: string) =>\n createSafeLogMessage(`Error processing cache file ${file}: ${reason}`),\n retrievalFailed: (key: string, reason: string) =>\n createSafeLogMessage(`Error retrieving cache for key: ${key}, error: ${reason}`),\n storageFailed: (key: string, reason: string) =>\n createSafeLogMessage(`Error caching data for key: ${key}, error: ${reason}`),\n checkFailed: (key: string, reason: string) =>\n createSafeLogMessage(`Error checking cache for key: ${key}, error: ${reason}`),\n deleteFailed: (key: string, reason: string) =>\n createSafeLogMessage(`Error deleting cache entry for key: ${key}, error: ${reason}`),\n clearExpiredFailed: (reason: string) => createSafeLogMessage(`Error clearing expired cache entries: ${reason}`),\n clearFailed: (reason: string) => createSafeLogMessage(`Error clearing cache: ${reason}`),\n directoryCreationFailed: (reason: string) =>\n createSafeLogMessage(`Error ensuring cache directories exist: ${reason}`),\n binaryDirectoryNotConfigured: () => createSafeLogMessage('Binary directory not configured for binary strategy'),\n binaryDataRequired: () => createSafeLogMessage('Binary storage strategy requires Buffer data'),\n} satisfies SafeLogMessageMap;\n",
|
|
70
|
+
"import crypto from 'node:crypto';\nimport type { IDownloadOptions } from '../IDownloader';\n\n/**\n * Creates a cache key for a download operation.\n * The key is based on the URL and relevant options that affect the download result.\n * @param url The URL to download from\n * @param options The download options\n * @returns A unique cache key for this download\n */\nexport function createCacheKey(url: string, options: IDownloadOptions = {}): string {\n // Only include options that affect the actual download content\n const relevantOptions = {\n headers: options.headers || {},\n // Don't include progress callbacks, retry settings, or timeouts as they don't affect content\n };\n\n const keyData = {\n url,\n options: relevantOptions,\n };\n\n // Create a hash of the key data to ensure consistent, filesystem-safe keys\n const hash = crypto.createHash('sha256').update(JSON.stringify(keyData)).digest('hex');\n return `download:${hash}`;\n}\n\n/**\n * Creates a cache key for API responses.\n * @param url The API endpoint URL\n * @param headers Optional headers that might affect the response\n * @returns A unique cache key for this API call\n */\nexport function createApiCacheKey(url: string, headers?: Record<string, string>): string {\n const keyData = {\n url,\n headers: headers || {},\n };\n\n const hash = crypto.createHash('sha256').update(JSON.stringify(keyData)).digest('hex');\n return `api:${hash}`;\n}\n\n/**\n * Checks if a cache key is for a download operation.\n * @param key The cache key to check\n * @returns True if the key is for a download operation\n */\nexport function isDownloadKey(key: string): boolean {\n return key.startsWith('download:');\n}\n\n/**\n * Checks if a cache key is for an API operation.\n * @param key The cache key to check\n * @returns True if the key is for an API operation\n */\nexport function isApiKey(key: string): boolean {\n return key.startsWith('api:');\n}\n",
|
|
71
|
+
"import type { IFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport path from 'node:path';\nimport { createCacheKey } from './cache/helpers';\nimport type { ICache } from './cache/types';\nimport type { IDownloadOptions } from './IDownloader';\nimport type { IDownloadStrategy } from './IDownloadStrategy';\nimport { cachedDownloadStrategyLogMessages } from './log-messages';\n\n/**\n * A download strategy decorator that adds caching functionality.\n * This strategy checks the cache before delegating to the underlying strategy.\n */\nexport class CachedDownloadStrategy implements IDownloadStrategy {\n public readonly name: string;\n private readonly cache: ICache;\n private readonly underlyingStrategy: IDownloadStrategy;\n private readonly logger: TsLogger;\n private readonly cacheTtl: number;\n private readonly fileSystem: IFileSystem;\n\n /**\n * Creates a new CachedDownloadStrategy.\n * @param parentLogger The logger instance\n * @param fileSystem The file system implementation to use\n * @param cache The cache implementation to use (should be configured with 'binary' strategy)\n * @param underlyingStrategy The strategy to use when cache misses occur\n * @param cacheTtl TTL for cached downloads in milliseconds\n */\n constructor(\n parentLogger: TsLogger,\n fileSystem: IFileSystem,\n cache: ICache,\n underlyingStrategy: IDownloadStrategy,\n cacheTtl: number = 24 * 60 * 60 * 1000, // Default 24 hours\n ) {\n this.logger = parentLogger.getSubLogger({ name: 'CachedDownloadStrategy' });\n this.fileSystem = fileSystem;\n this.cache = cache;\n this.underlyingStrategy = underlyingStrategy;\n this.cacheTtl = cacheTtl;\n this.name = `cached-${underlyingStrategy.name}`;\n\n this.logger.debug(cachedDownloadStrategyLogMessages.strategyWrapped(underlyingStrategy.name, cacheTtl));\n }\n\n /**\n * Checks if this download strategy is available.\n * @returns A promise that resolves to true if the underlying strategy is available\n */\n async isAvailable(): Promise<boolean> {\n return await this.underlyingStrategy.isAvailable();\n }\n\n private async handleCacheHit(\n logger: TsLogger,\n cachedBuffer: Buffer,\n cacheKey: string,\n url: string,\n options: IDownloadOptions,\n ): Promise<Buffer | undefined> {\n logger.trace(cachedDownloadStrategyLogMessages.cacheHit(cacheKey, 'binary', cachedBuffer.length), { url });\n\n if (options.onProgress) {\n options.onProgress(0, cachedBuffer.length);\n }\n\n // If destinationPath is specified, write cached data to file and return void\n if (options.destinationPath) {\n await this.fileSystem.ensureDir(path.dirname(options.destinationPath));\n await this.fileSystem.writeFile(options.destinationPath, cachedBuffer);\n logger.trace(cachedDownloadStrategyLogMessages.cachedFileWritten(options.destinationPath));\n if (options.onProgress) {\n options.onProgress(cachedBuffer.length, cachedBuffer.length);\n }\n return; // Return void for file downloads\n }\n\n if (options.onProgress) {\n options.onProgress(cachedBuffer.length, cachedBuffer.length);\n }\n\n return cachedBuffer;\n }\n\n private async readFileForCaching(logger: TsLogger, destinationPath: string): Promise<Buffer | null> {\n logger.trace(cachedDownloadStrategyLogMessages.readFileForCaching(destinationPath));\n\n try {\n const fileExists = await this.fileSystem.exists(destinationPath);\n logger.trace(cachedDownloadStrategyLogMessages.downloadedFileExists(destinationPath, fileExists));\n\n if (fileExists) {\n const bufferToCache = await this.fileSystem.readFileBuffer(destinationPath);\n logger.trace(cachedDownloadStrategyLogMessages.downloadedFileCached(destinationPath, bufferToCache.length), {\n path: destinationPath,\n size: bufferToCache.length,\n });\n return bufferToCache;\n } else {\n logger.trace(cachedDownloadStrategyLogMessages.downloadedFileMissing(destinationPath));\n return null;\n }\n } catch (error) {\n logger.trace(cachedDownloadStrategyLogMessages.downloadedFileReadFailed(destinationPath), error);\n return null;\n }\n }\n\n private async determineBufferToCache(\n logger: TsLogger,\n result: Buffer | undefined,\n options: IDownloadOptions,\n ): Promise<Buffer | null> {\n if (result instanceof Buffer) {\n return result;\n } else if (options.destinationPath && result === undefined) {\n // For file downloads that return void, we need to read the file to cache it\n return await this.readFileForCaching(logger, options.destinationPath);\n }\n return null;\n }\n\n private async cacheResult(\n logger: TsLogger,\n bufferToCache: Buffer,\n cacheKey: string,\n url: string,\n options: IDownloadOptions,\n ): Promise<void> {\n try {\n await this.cache.setDownload(\n cacheKey,\n bufferToCache,\n this.cacheTtl,\n url,\n this.extractContentTypeFromHeaders(options.headers),\n );\n logger.trace(\n cachedDownloadStrategyLogMessages.cacheStored(cacheKey, 'binary', 'TTL-based', bufferToCache.length),\n { url },\n );\n } catch (error) {\n logger.trace(cachedDownloadStrategyLogMessages.cacheStorageFailed(cacheKey), error);\n // Don't fail the download if caching fails\n }\n }\n\n /**\n * Downloads a file from the given URL, checking cache first.\n * @param url The URL of the file to download\n * @param options The download options\n * @returns A promise that resolves with a Buffer containing the downloaded file's content\n */\n async download(url: string, options: IDownloadOptions = {}): Promise<Buffer | undefined> {\n const logger = this.logger.getSubLogger({ name: 'download' });\n\n const cacheKey = createCacheKey(url, options);\n\n try {\n // Cache remains enabled even when onProgress is provided.\n // On cache hits, handleCacheHit emits synthetic progress updates.\n // Check cache first\n const cachedBuffer = await this.cache.get<Buffer>(cacheKey);\n if (cachedBuffer) {\n return await this.handleCacheHit(logger, cachedBuffer, cacheKey, url, options);\n }\n\n logger.trace(cachedDownloadStrategyLogMessages.cacheMiss(cacheKey), { url });\n } catch (error) {\n logger.trace(cachedDownloadStrategyLogMessages.cacheCheckFailed(cacheKey), error);\n // Continue with download if cache check fails\n }\n\n // Download from underlying strategy\n logger.trace(cachedDownloadStrategyLogMessages.downloadFromStrategy(this.underlyingStrategy.name), { url });\n const result = await this.underlyingStrategy.download(url, options);\n\n // Cache the result if possible\n const bufferToCache = await this.determineBufferToCache(logger, result, options);\n\n if (bufferToCache) {\n await this.cacheResult(logger, bufferToCache, cacheKey, url, options);\n }\n\n return result;\n }\n\n /**\n * Extracts content type from request headers for metadata.\n * @param headers The request headers\n * @returns The content type or undefined\n * @private\n */\n private extractContentTypeFromHeaders(headers?: Record<string, string>): string | undefined {\n if (!headers) return undefined;\n\n // Look for Accept header as a hint for expected content type\n return headers['Accept'] || headers['accept'];\n }\n}\n",
|
|
72
|
+
"import { createSafeLogMessage, type SafeLogMessageMap } from '@dotfiles/logger';\n\nexport const downloaderLogMessages = {\n strategyCreated: (strategyName: string, detail: string) => createSafeLogMessage(`Created ${strategyName}${detail}`),\n downloadStarted: (url: string) => createSafeLogMessage(`Downloading URL: ${url}`),\n downloadToFileStarted: (url: string, filePath: string) =>\n createSafeLogMessage(`Downloading URL ${url} to file: ${filePath}`),\n} satisfies SafeLogMessageMap;\n\nexport const proxiedFetchStrategyLogMessages = {\n proxyingRequest: (originalUrl: string, proxiedUrl: string) =>\n createSafeLogMessage(`Proxying request: ${originalUrl} → ${proxiedUrl}`),\n} satisfies SafeLogMessageMap;\n\nexport const cachedDownloadStrategyLogMessages = {\n strategyWrapped: (strategyName: string, ttlMs: number) =>\n createSafeLogMessage(`Wrapping strategy ${strategyName} with cache, TTL: ${ttlMs} ms`),\n cachingDisabled: (operation: string, key: string, reason: string) =>\n createSafeLogMessage(`Cache disabled, ${operation} for key: ${key} (${reason})`),\n cacheHit: (key: string, strategy: string, size?: number) => {\n const sizeDescription = size !== undefined ? `, size: ${size} bytes` : '';\n return createSafeLogMessage(`Cache hit for key: ${key} (${strategy})${sizeDescription}`);\n },\n cacheStored: (key: string, strategy: string, expiresAt: string, size?: number) => {\n const sizeDescription = size !== undefined ? `, size: ${size} bytes` : '';\n return createSafeLogMessage(`Cached data for key: ${key} (${strategy})${sizeDescription}, expires: ${expiresAt}`);\n },\n cacheStorageFailed: (key: string) => createSafeLogMessage(`Error caching data for key: ${key}`),\n cacheCheckFailed: (key: string) => createSafeLogMessage(`Error checking cache for key: ${key}`),\n cacheMiss: (key: string) => createSafeLogMessage(`No cache entry found for key: ${key}`),\n cacheDisabledForProgress: (url: string) =>\n createSafeLogMessage(`Cache disabled, caching for key: ${url} (reason: progress callback)`),\n readFileForCaching: (path: string) => createSafeLogMessage(`read file for caching: ${path}`),\n downloadedFileExists: (path: string, exists: boolean) =>\n createSafeLogMessage(`Downloaded file exists (${path}): ${exists}`),\n downloadedFileCached: (path: string, size: number) =>\n createSafeLogMessage(`Successfully read file for caching from ${path}, size: ${size} bytes`),\n downloadedFileMissing: (path: string) => createSafeLogMessage(`Downloaded file not found: ${path}`),\n downloadedFileReadFailed: (path: string) => createSafeLogMessage(`Failed to read ${path}`),\n downloadFromStrategy: (strategyName: string) => createSafeLogMessage(`download from ${strategyName}`),\n cachedFileWritten: (path: string) => createSafeLogMessage(`[CachedDownloadStrategy] write ${path}`),\n} satisfies SafeLogMessageMap;\n\nexport const nodeFetchStrategyLogMessages = {\n constructed: (fsStatus: string) => createSafeLogMessage(`NodeFetchStrategy constructed (fileSystem ${fsStatus})`),\n downloadTimeout: (url: string) => createSafeLogMessage(`Download timeout for ${url}`),\n responseBodyReadFailed: (url: string, reason: unknown) =>\n createSafeLogMessage(`Failed to read response body for ${url}: ${String(reason)}`),\n downloadFailed: (url: string, statusCode: number, statusText: string, responseBody?: string) =>\n createSafeLogMessage(\n `Download failed: url=${url}, statusCode=${statusCode}, statusText=${statusText}, responseBody=${\n responseBody ?? 'N/A'\n }`,\n ),\n downloadAttempt: (attempt: number, url: string) => createSafeLogMessage(`Attempt ${attempt}: Downloading ${url}`),\n downloadSuccessful: (url: string, size: number) =>\n createSafeLogMessage(`Download successful for ${url}, size: ${size} bytes`),\n savingToDestination: (destinationPath: string) => createSafeLogMessage(`Saving to destination: ${destinationPath}`),\n savedSuccessfully: (destinationPath: string) =>\n createSafeLogMessage(`Successfully wrote to ${destinationPath} using IFileSystem`),\n downloadAttemptError: (attempt: number, url: string, error: unknown) =>\n createSafeLogMessage(`Error during download attempt ${attempt} for ${url}: ${String(error)}`),\n retryingDownload: (url: string, attempt: number, retryCount: number, retryDelay: number) =>\n createSafeLogMessage(`Retrying download for ${url}, attempt ${attempt}/${retryCount} after ${retryDelay}ms`),\n exhaustedRetries: (url: string) => createSafeLogMessage(`Exhausted retries for ${url}`),\n} satisfies SafeLogMessageMap;\n\nexport const downloaderErrorLogMessages = {\n errorCreated: (errorName: string, message: string, url: string) =>\n createSafeLogMessage(`${errorName} created: message=${message}, url=${url}`),\n networkErrorCreated: (message: string, url: string, originalError?: Error) =>\n createSafeLogMessage(\n `NetworkError created: message=${message}, url=${url}, originalError=${String(originalError)}`,\n ),\n httpErrorCreated: (\n message: string,\n url: string,\n statusCode: number,\n statusText: string,\n responseBody?: string | Buffer | object,\n responseHeaders?: Record<string, string | string[] | undefined>,\n ) =>\n createSafeLogMessage(\n `HttpError created: message=${message}, url=${url}, statusCode=${statusCode}, statusText=${statusText}, responseBody=${\n String(responseBody)\n }, responseHeaders=${JSON.stringify(responseHeaders ?? {})}`,\n ),\n notFoundErrorCreated: (\n url: string,\n responseBody?: string | Buffer | object,\n responseHeaders?: Record<string, string | string[] | undefined>,\n ) =>\n createSafeLogMessage(\n `NotFoundError created: url=${url}, responseBody=${String(responseBody)}, responseHeaders=${\n JSON.stringify(responseHeaders ?? {})\n }`,\n ),\n forbiddenErrorCreated: (\n url: string,\n responseBody?: string | Buffer | object,\n responseHeaders?: Record<string, string | string[] | undefined>,\n ) =>\n createSafeLogMessage(\n `ForbiddenError created: url=${url}, responseBody=${String(responseBody)}, responseHeaders=${\n JSON.stringify(responseHeaders ?? {})\n }`,\n ),\n rateLimitErrorCreated: (\n message: string,\n url: string,\n statusCode: number,\n statusText: string,\n responseBody?: string | Buffer | object,\n responseHeaders?: Record<string, string | string[] | undefined>,\n resetTimestamp?: number,\n ) =>\n createSafeLogMessage(\n `RateLimitError created: message=${message}, url=${url}, statusCode=${statusCode}, statusText=${statusText}, responseBody=${\n String(responseBody)\n }, responseHeaders=${JSON.stringify(responseHeaders ?? {})}, resetTimestamp=${String(resetTimestamp)}`,\n ),\n clientErrorCreated: (\n url: string,\n statusCode: number,\n statusText: string,\n responseBody?: string | Buffer | object,\n responseHeaders?: Record<string, string | string[] | undefined>,\n ) =>\n createSafeLogMessage(\n `ClientError created: url=${url}, statusCode=${statusCode}, statusText=${statusText}, responseBody=${\n String(responseBody)\n }, responseHeaders=${JSON.stringify(responseHeaders ?? {})}`,\n ),\n serverErrorCreated: (\n url: string,\n statusCode: number,\n statusText: string,\n responseBody?: string | Buffer | object,\n responseHeaders?: Record<string, string | string[] | undefined>,\n ) =>\n createSafeLogMessage(\n `ServerError created: url=${url}, statusCode=${statusCode}, statusText=${statusText}, responseBody=${\n String(responseBody)\n }, responseHeaders=${JSON.stringify(responseHeaders ?? {})}`,\n ),\n} satisfies SafeLogMessageMap;\n",
|
|
73
|
+
"import type { TsLogger } from '@dotfiles/logger';\nimport { downloaderErrorLogMessages } from './log-messages';\n\n/**\n * Base error class for all downloader-related errors.\n *\n * This error is used when a download operation fails but doesn't fit into more specific\n * error categories. All downloader errors inherit from this class and include the URL\n * that was being downloaded for better error diagnostics.\n */\nexport class DownloaderError extends Error {\n public readonly url: string;\n\n constructor(parentLogger: TsLogger, message: string, url: string) {\n super(message);\n const logger = parentLogger.getSubLogger({ name: 'DownloaderError' });\n this.name = 'DownloaderError';\n this.url = url;\n logger.debug(downloaderErrorLogMessages.errorCreated('DownloaderError', message, url));\n }\n}\n\n/**\n * Represents an error that occurred at the network level.\n *\n * Network errors include DNS resolution failures, connection refused, timeouts,\n * and other low-level network issues that prevent the HTTP request from completing.\n * This error wraps the original error from the network layer for debugging.\n */\nexport class NetworkError extends DownloaderError {\n public readonly originalError?: Error;\n\n constructor(parentLogger: TsLogger, message: string, url: string, originalError?: Error) {\n super(parentLogger, message, url);\n const logger = parentLogger.getSubLogger({ name: 'NetworkError' });\n this.name = 'NetworkError';\n this.originalError = originalError;\n logger.debug(downloaderErrorLogMessages.networkErrorCreated(message, url, originalError), originalError);\n }\n}\n\n/**\n * Base error class for HTTP errors with status code >= 400.\n *\n * This error is thrown when the server responds with an error status code.\n * It includes the status code, status text, response body, and headers for\n * detailed error analysis. Specific HTTP errors (404, 403, etc.) extend this class.\n */\nexport class HttpError extends DownloaderError {\n public readonly statusCode: number;\n public readonly statusText: string;\n public readonly responseBody?: string | Buffer | object;\n public readonly responseHeaders?: Record<string, string | string[] | undefined>;\n\n constructor(\n parentLogger: TsLogger,\n message: string,\n url: string,\n statusCode: number,\n statusText: string,\n responseBody?: string | Buffer | object,\n responseHeaders?: Record<string, string | string[] | undefined>,\n ) {\n super(parentLogger, message, url);\n const logger = parentLogger.getSubLogger({ name: 'HttpError' });\n this.name = 'HttpError';\n this.statusCode = statusCode;\n this.statusText = statusText;\n this.responseBody = responseBody;\n this.responseHeaders = responseHeaders;\n logger.debug(\n downloaderErrorLogMessages.httpErrorCreated(message, url, statusCode, statusText, responseBody, responseHeaders),\n {\n url,\n statusCode,\n statusText,\n responseBody,\n responseHeaders,\n },\n );\n }\n}\n\n/**\n * Represents an HTTP 404 Not Found error.\n *\n * This error is thrown when the requested resource does not exist on the server.\n * It's a specific case of HttpError for 404 status codes.\n */\nexport class NotFoundError extends HttpError {\n constructor(\n parentLogger: TsLogger,\n url: string,\n responseBody?: string | Buffer | object,\n responseHeaders?: Record<string, string | string[] | undefined>,\n ) {\n super(parentLogger, 'Resource not found', url, 404, 'Not Found', responseBody, responseHeaders);\n const logger = parentLogger.getSubLogger({ name: 'NotFoundError' });\n this.name = 'NotFoundError';\n logger.debug(downloaderErrorLogMessages.notFoundErrorCreated(url, responseBody, responseHeaders), {\n url,\n responseBody,\n responseHeaders,\n });\n }\n}\n\n/**\n * Represents an HTTP 403 Forbidden error.\n *\n * This error is thrown when the server refuses to authorize the request.\n * Common causes include insufficient permissions, authentication failures,\n * or IP-based access restrictions. It's a specific case of HttpError for 403 status codes.\n */\nexport class ForbiddenError extends HttpError {\n constructor(\n parentLogger: TsLogger,\n url: string,\n responseBody?: string | Buffer | object,\n responseHeaders?: Record<string, string | string[] | undefined>,\n ) {\n super(parentLogger, 'Access forbidden', url, 403, 'Forbidden', responseBody, responseHeaders);\n const logger = parentLogger.getSubLogger({ name: 'ForbiddenError' });\n this.name = 'ForbiddenError';\n logger.debug(downloaderErrorLogMessages.forbiddenErrorCreated(url, responseBody, responseHeaders), {\n url,\n responseBody,\n responseHeaders,\n });\n }\n}\n\n/**\n * Represents an HTTP 429 Too Many Requests or rate-limited 403 Forbidden error.\n *\n * This error is thrown when the server indicates that the client has exceeded\n * rate limits. It may include a resetTimestamp indicating when the rate limit\n * will be lifted. This is commonly used by APIs like GitHub that enforce\n * rate limiting on requests.\n */\nexport class RateLimitError extends HttpError {\n public readonly resetTimestamp?: number;\n\n constructor(\n parentLogger: TsLogger,\n message: string,\n url: string,\n statusCode: number,\n statusText: string,\n responseBody?: string | Buffer | object,\n responseHeaders?: Record<string, string | string[] | undefined>,\n resetTimestamp?: number,\n ) {\n super(parentLogger, message, url, statusCode, statusText, responseBody, responseHeaders);\n const logger = parentLogger.getSubLogger({ name: 'RateLimitError' });\n this.name = 'RateLimitError';\n this.resetTimestamp = resetTimestamp;\n logger.debug(\n downloaderErrorLogMessages.rateLimitErrorCreated(\n message,\n url,\n statusCode,\n statusText,\n responseBody,\n responseHeaders,\n resetTimestamp,\n ),\n {\n url,\n statusCode,\n statusText,\n responseBody,\n responseHeaders,\n resetTimestamp,\n },\n );\n }\n}\n\n/**\n * Represents a generic HTTP client error in the 4xx range.\n *\n * This error is used for 4xx status codes that don't have specific error classes\n * (excludes 403, 404, and 429 which have dedicated error types). It indicates\n * that the request was malformed or invalid in some way.\n */\nexport class ClientError extends HttpError {\n constructor(\n parentLogger: TsLogger,\n url: string,\n statusCode: number,\n statusText: string,\n responseBody?: string | Buffer | object,\n responseHeaders?: Record<string, string | string[] | undefined>,\n ) {\n super(parentLogger, `Client error: ${statusText}`, url, statusCode, statusText, responseBody, responseHeaders);\n const logger = parentLogger.getSubLogger({ name: 'ClientError' });\n this.name = 'ClientError';\n logger.debug(\n downloaderErrorLogMessages.clientErrorCreated(url, statusCode, statusText, responseBody, responseHeaders),\n {\n url,\n statusCode,\n statusText,\n responseBody,\n responseHeaders,\n },\n );\n }\n}\n\n/**\n * Represents a generic HTTP server error in the 5xx range.\n *\n * This error is thrown when the server encounters an internal error while\n * processing the request. It indicates a problem on the server side rather\n * than with the client's request.\n */\nexport class ServerError extends HttpError {\n constructor(\n parentLogger: TsLogger,\n url: string,\n statusCode: number,\n statusText: string,\n responseBody?: string | Buffer | object,\n responseHeaders?: Record<string, string | string[] | undefined>,\n ) {\n super(parentLogger, `Server error: ${statusText}`, url, statusCode, statusText, responseBody, responseHeaders);\n const logger = parentLogger.getSubLogger({ name: 'ServerError' });\n this.name = 'ServerError';\n logger.debug(\n downloaderErrorLogMessages.serverErrorCreated(url, statusCode, statusText, responseBody, responseHeaders),\n {\n url,\n statusCode,\n statusText,\n responseBody,\n responseHeaders,\n },\n );\n }\n}\n",
|
|
74
|
+
"import type { IFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { proxyFetch, type ProxyFetchConfig } from '@dotfiles/utils';\nimport {\n ClientError,\n ForbiddenError,\n HttpError,\n NetworkError,\n NotFoundError,\n RateLimitError,\n ServerError,\n} from './errors';\nimport type { IDownloadOptions } from './IDownloader';\nimport type { IDownloadStrategy } from './IDownloadStrategy';\nimport { nodeFetchStrategyLogMessages } from './log-messages';\n\nexport class NodeFetchStrategy implements IDownloadStrategy {\n public readonly name = 'node-fetch';\n private readonly logger: TsLogger;\n private readonly fileSystem: IFileSystem;\n private readonly proxyConfig: ProxyFetchConfig | undefined;\n\n constructor(parentLogger: TsLogger, fileSystem: IFileSystem, proxyConfig?: ProxyFetchConfig) {\n this.logger = parentLogger.getSubLogger({ name: 'NodeFetchStrategy' });\n this.logger.debug(nodeFetchStrategyLogMessages.constructed(fileSystem ? 'provided' : 'undefined'));\n this.fileSystem = fileSystem;\n this.proxyConfig = proxyConfig;\n }\n\n public async isAvailable(): Promise<boolean> {\n // Node.js fetch is generally available in modern Node versions.\n // Bun also provides a compatible fetch.\n return typeof fetch === 'function';\n }\n\n private getResponseHeaders(headers: Headers): Record<string, string | string[] | undefined> {\n const result: Record<string, string | string[] | undefined> = {};\n headers.forEach((value, key) => {\n // For simplicity, we're not handling multi-value headers explicitly here\n // as 'getSetCookie' is specific and 'getAll' is deprecated.\n // Most common headers are single value. If multi-value is needed,\n // this part might need refinement based on specific header names.\n result[key] = value;\n });\n return result;\n }\n\n public parseRateLimitReset(headers: Headers): number | undefined {\n const rateLimitResetHeader = headers.get('X-RateLimit-Reset');\n if (rateLimitResetHeader) {\n const timestamp = parseInt(rateLimitResetHeader, 10);\n if (!Number.isNaN(timestamp)) {\n return timestamp * 1000; // Convert seconds to milliseconds\n }\n }\n const retryAfterHeader = headers.get('Retry-After');\n if (retryAfterHeader) {\n const delaySeconds = parseInt(retryAfterHeader, 10);\n if (!Number.isNaN(delaySeconds)) {\n return Date.now() + delaySeconds * 1000;\n }\n // Check if it's an HTTP-date\n const dateTimestamp = Date.parse(retryAfterHeader);\n if (!Number.isNaN(dateTimestamp)) {\n return dateTimestamp;\n }\n }\n return undefined;\n }\n\n private async setupDownloadRequest(\n url: string,\n headers: Record<string, string> | undefined,\n timeout: number | undefined,\n ): Promise<{ response: Response; timeoutId?: NodeJS.Timeout; }> {\n const controller = new AbortController();\n let timeoutId: NodeJS.Timeout | undefined;\n\n if (timeout) {\n timeoutId = setTimeout(() => {\n this.logger.debug(nodeFetchStrategyLogMessages.downloadTimeout(url));\n controller.abort();\n }, timeout);\n }\n\n const response = await proxyFetch(url, {\n headers,\n signal: controller.signal,\n }, this.proxyConfig);\n\n return { response, timeoutId };\n }\n\n private async handleErrorResponse(response: Response, url: string): Promise<never> {\n let responseBody: string | undefined;\n try {\n responseBody = await response.text();\n } catch (e) {\n this.logger.debug(nodeFetchStrategyLogMessages.responseBodyReadFailed(url, e));\n }\n\n const responseHeaders = this.getResponseHeaders(response.headers);\n const statusCode = response.status;\n const statusText = response.statusText;\n\n this.logger.debug(\n nodeFetchStrategyLogMessages.downloadFailed(url, statusCode, statusText, responseBody?.substring(0, 100)),\n );\n\n if (statusCode === 404) {\n throw new NotFoundError(this.logger, url, responseBody, responseHeaders);\n }\n\n const resetTimestamp = this.parseRateLimitReset(response.headers);\n\n if (statusCode === 403) {\n if (resetTimestamp) {\n throw new RateLimitError(\n this.logger,\n 'Forbidden: Rate limit likely exceeded',\n url,\n statusCode,\n statusText,\n responseBody,\n responseHeaders,\n resetTimestamp,\n );\n }\n throw new ForbiddenError(this.logger, url, responseBody, responseHeaders);\n }\n\n if (statusCode === 429) {\n throw new RateLimitError(\n this.logger,\n 'Too Many Requests',\n url,\n statusCode,\n statusText,\n responseBody,\n responseHeaders,\n resetTimestamp,\n );\n }\n\n if (statusCode >= 400 && statusCode < 500) {\n throw new ClientError(this.logger, url, statusCode, statusText, responseBody, responseHeaders);\n }\n\n if (statusCode >= 500 && statusCode < 600) {\n throw new ServerError(this.logger, url, statusCode, statusText, responseBody, responseHeaders);\n }\n\n // Fallback HttpError\n throw new HttpError(\n this.logger,\n `HTTP error ${statusCode}`,\n url,\n statusCode,\n statusText,\n responseBody,\n responseHeaders,\n );\n }\n\n private async processResponseStream(\n response: Response,\n url: string,\n onProgress?: (bytesDownloaded: number, totalBytes: number | null) => void,\n ): Promise<Buffer> {\n const contentLength = response.headers.get('content-length');\n let totalBytes: number | null = null;\n if (contentLength) {\n const parsedTotal = parseInt(contentLength, 10);\n if (!Number.isNaN(parsedTotal)) {\n totalBytes = parsedTotal;\n }\n }\n\n let bytesDownloaded = 0;\n\n if (onProgress) {\n onProgress(bytesDownloaded, totalBytes);\n }\n\n const chunks: Buffer[] = [];\n const reader = response.body?.getReader();\n if (!reader) {\n throw new NetworkError(this.logger, 'Response body is not readable.', url);\n }\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n if (value) {\n chunks.push(Buffer.from(value));\n bytesDownloaded += value.length;\n if (onProgress) {\n onProgress(bytesDownloaded, totalBytes);\n }\n }\n }\n\n return Buffer.concat(chunks);\n }\n\n private async handleDownloadAttempt(\n url: string,\n options: IDownloadOptions,\n attempt: number,\n ): Promise<Buffer | undefined> {\n const { headers, timeout, onProgress, destinationPath } = options;\n\n this.logger.debug(nodeFetchStrategyLogMessages.downloadAttempt(attempt + 1, url));\n\n const { response, timeoutId } = await this.setupDownloadRequest(url, headers, timeout);\n\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n\n if (!response.ok) {\n await this.handleErrorResponse(response, url);\n }\n\n const resultBuffer = await this.processResponseStream(response, url, onProgress);\n this.logger.debug(nodeFetchStrategyLogMessages.downloadSuccessful(url, resultBuffer.length));\n\n if (destinationPath) {\n this.logger.debug(nodeFetchStrategyLogMessages.savingToDestination(destinationPath));\n await this.fileSystem.writeFile(destinationPath, resultBuffer);\n this.logger.debug(nodeFetchStrategyLogMessages.savedSuccessfully(destinationPath));\n return;\n } else {\n return resultBuffer;\n }\n }\n\n private handleDownloadError(\n error: unknown,\n url: string,\n attempt: number,\n retryCount: number,\n _onProgress?: (bytesDownloaded: number, totalBytes: number | null) => void,\n ): void {\n this.logger.debug(nodeFetchStrategyLogMessages.downloadAttemptError(attempt + 1, url, error));\n\n if (attempt >= retryCount) {\n if (error instanceof HttpError || error instanceof NetworkError) {\n throw error;\n }\n\n let message = `Failed to download ${url}`;\n if (error instanceof Error && error.name === 'AbortError') {\n message = `Download timed out for ${url}`;\n } else if (error instanceof Error) {\n message = error.message;\n }\n throw new NetworkError(this.logger, message, url, error instanceof Error ? error : undefined);\n }\n }\n\n private async retryDownload(\n url: string,\n attempt: number,\n retryCount: number,\n retryDelay: number,\n onProgress?: (bytesDownloaded: number, totalBytes: number | null) => void,\n ): Promise<void> {\n this.logger.debug(nodeFetchStrategyLogMessages.retryingDownload(url, attempt + 2, retryCount + 1, retryDelay));\n if (onProgress) {\n // onProgress({ bytesDownloaded: 0, totalBytes: undefined, percentage: 0, status: `Retrying (${attempt}/${retryCount})...` });\n }\n await new Promise((resolve) => setTimeout(resolve, retryDelay));\n }\n\n public async download(url: string, options: IDownloadOptions): Promise<Buffer | undefined> {\n const { retryCount = 0, retryDelay = 1000, onProgress } = options;\n\n let attempt = 0;\n while (attempt <= retryCount) {\n try {\n return await this.handleDownloadAttempt(url, options, attempt);\n } catch (error: unknown) {\n this.handleDownloadError(error, url, attempt, retryCount, onProgress);\n\n if (attempt < retryCount) {\n await this.retryDownload(url, attempt, retryCount, retryDelay, onProgress);\n attempt++;\n }\n }\n }\n\n // Fallback, should ideally not be reached if retryCount >= 0\n this.logger.debug(nodeFetchStrategyLogMessages.exhaustedRetries(url));\n throw new NetworkError(this.logger, `Download failed for ${url} after ${retryCount} retries.`, url);\n }\n}\n",
|
|
75
|
+
"import type { IOperationFailure, IOperationSuccess } from '@dotfiles/core';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport type { ProxyFetchConfig } from '@dotfiles/utils';\nimport type { ICache } from './cache/types';\nimport { CachedDownloadStrategy } from './CachedDownloadStrategy';\nimport type { IDownloader, IDownloadOptions } from './IDownloader';\nimport type { IDownloadStrategy } from './IDownloadStrategy';\nimport { downloaderLogMessages } from './log-messages';\nimport { NodeFetchStrategy } from './NodeFetchStrategy';\n\ntype DownloadAttemptSuccess = IOperationSuccess & { buffer: Buffer | undefined; };\ntype DownloadAttemptFailure = IOperationFailure;\n\n/**\n * Main downloader class that orchestrates file downloads using pluggable strategies.\n *\n * The Downloader class manages multiple download strategies and selects the appropriate one\n * for each download operation. It handles strategy registration, fallback behavior, and\n * optional caching. By default, it uses NodeFetchStrategy, optionally wrapped with\n * CachedDownloadStrategy if a cache is provided.\n */\nexport class Downloader implements IDownloader {\n private strategies: IDownloadStrategy[] = [];\n private fs: IFileSystem;\n private logger: TsLogger;\n\n /**\n * Creates a new Downloader instance.\n *\n * @param parentLogger - The parent logger for creating sub-loggers.\n * @param fileSystem - The file system interface for file operations.\n * @param strategies - Optional array of download strategies. If not provided, defaults to NodeFetchStrategy.\n * @param cache - Optional cache instance. If provided, wraps the default strategy with caching.\n * @param proxyConfig - Optional proxy configuration. If provided and enabled, routes requests through proxy.\n */\n constructor(\n parentLogger: TsLogger,\n fileSystem: IFileSystem,\n strategies?: IDownloadStrategy[],\n cache?: ICache,\n proxyConfig?: ProxyFetchConfig,\n ) {\n this.logger = parentLogger.getSubLogger({ name: 'Downloader' });\n this.fs = fileSystem;\n\n if (typeof strategies !== 'undefined') {\n this.strategies = strategies;\n } else {\n // Create default strategy with optional proxy support\n const strategy: IDownloadStrategy = new NodeFetchStrategy(this.logger, this.fs, proxyConfig);\n const proxyStatus = proxyConfig?.enabled ? ` (proxy port ${proxyConfig.port})` : '';\n\n // Wrap with cache if provided\n if (cache) {\n this.logger.debug(\n downloaderLogMessages.strategyCreated('CachedDownloadStrategy', ` wrapping NodeFetchStrategy${proxyStatus}`),\n );\n this.strategies.push(new CachedDownloadStrategy(this.logger, this.fs, cache, strategy));\n } else {\n this.logger.debug(downloaderLogMessages.strategyCreated('NodeFetchStrategy', proxyStatus || ' (no cache)'));\n this.strategies.push(strategy);\n }\n }\n }\n\n /**\n * @inheritdoc IDownloader.registerStrategy\n */\n public registerStrategy(strategy: IDownloadStrategy): void {\n this.strategies.unshift(strategy);\n }\n\n /**\n * Attempts to download using a specific strategy.\n *\n * @param strategy - The download strategy to use.\n * @param url - The URL to download from.\n * @param options - Download options.\n * @returns An operation result with the buffer if successful.\n */\n private async tryDownloadWithStrategy(\n strategy: IDownloadStrategy,\n url: string,\n options: IDownloadOptions,\n ): Promise<DownloadAttemptSuccess | DownloadAttemptFailure> {\n if (!(await strategy.isAvailable())) {\n return { success: false, error: 'Strategy not available' };\n }\n\n const buffer = await strategy.download(url, options);\n return { success: true, buffer };\n }\n\n /**\n * @inheritdoc IDownloader.download\n */\n public async download(\n parentLogger: TsLogger,\n url: string,\n options: IDownloadOptions = {},\n ): Promise<Buffer | undefined> {\n const logger = parentLogger.getSubLogger({ name: 'Downloader' }).getSubLogger({ name: 'download' });\n logger.debug(downloaderLogMessages.downloadStarted(url));\n\n if (this.strategies.length === 0) {\n throw new Error('No download strategies registered.');\n }\n\n let lastError: Error | undefined;\n\n for (const strategy of this.strategies) {\n try {\n const result = await this.tryDownloadWithStrategy(strategy, url, options);\n if (result.success) {\n return result.buffer;\n }\n } catch (error) {\n lastError = this.normalizeError(error);\n }\n }\n\n if (lastError) {\n throw lastError;\n }\n\n throw new Error(`No available download strategy succeeded for ${url}.`);\n }\n\n /**\n * Attempts to download a file to disk using a specific strategy.\n *\n * @param strategy - The download strategy to use.\n * @param url - The URL to download from.\n * @param fileOptions - Download options with destinationPath set.\n * @returns An operation result indicating success or failure.\n * @throws {Error} If the strategy returns a Buffer instead of writing to the file.\n */\n private async tryDownloadToFileWithStrategy(\n strategy: IDownloadStrategy,\n url: string,\n fileOptions: IDownloadOptions,\n ): Promise<IOperationSuccess | IOperationFailure> {\n if (!(await strategy.isAvailable())) {\n return { success: false, error: 'Strategy not available' };\n }\n\n const result = await strategy.download(url, fileOptions);\n if (result === undefined) {\n return { success: true }; // Successfully saved to file\n }\n throw new Error('Strategy returned Buffer instead of void for downloadToFile method');\n }\n\n /**\n * Normalizes unknown errors into Error instances.\n *\n * @param error - The error to normalize (can be any type).\n * @returns A proper Error instance.\n */\n private normalizeError(error: unknown): Error {\n if (error instanceof Error) {\n return error;\n } else if (typeof error === 'string') {\n return new Error(error);\n } else {\n return new Error(`An unknown error occurred during download: ${JSON.stringify(error)}`);\n }\n }\n\n /**\n * @inheritdoc IDownloader.downloadToFile\n */\n public async downloadToFile(\n parentLogger: TsLogger,\n url: string,\n filePath: string,\n options: IDownloadOptions = {},\n ): Promise<void> {\n const logger = parentLogger.getSubLogger({ name: 'Downloader' }).getSubLogger({ name: 'downloadToFile' });\n logger.debug(downloaderLogMessages.downloadToFileStarted(url, filePath));\n\n // Set destination path in options to indicate file download\n const fileOptions = { ...options, destinationPath: filePath };\n\n if (this.strategies.length === 0) {\n throw new Error('No download strategies registered.');\n }\n\n let lastError: Error | undefined;\n\n for (const strategy of this.strategies) {\n try {\n const result = await this.tryDownloadToFileWithStrategy(strategy, url, fileOptions);\n if (result.success) {\n return;\n }\n } catch (error) {\n lastError = this.normalizeError(error);\n }\n }\n\n if (lastError) {\n throw lastError;\n }\n\n throw new Error(`No available download strategy succeeded for ${url}.`);\n }\n}\n",
|
|
76
|
+
"import * as cliProgress from 'cli-progress';\nimport type { ProgressCallback } from './IDownloader';\n\n/**\n * Options for configuring the ProgressBar behavior.\n */\nexport interface IProgressBarOptions {\n /** Whether progress bar should be shown at all */\n enabled?: boolean;\n}\n\n/**\n * Progress bar for displaying download progress in the terminal.\n *\n * This class wraps cli-progress to provide visual feedback during file downloads.\n * It supports both determinate progress (when total size is known) and indeterminate\n * progress (when total size is unknown). The bar shows download speed, ETA, and\n * percentage complete.\n */\nexport class ProgressBar {\n private progressBar: cliProgress.SingleBar | null = null;\n private enabled: boolean;\n private startTime: number = 0;\n\n /**\n * Creates a new ProgressBar instance.\n *\n * @param filename - The name of the file being downloaded (used in the progress display).\n * @param options - Optional configuration for the progress bar.\n */\n constructor(\n private filename: string,\n options: IProgressBarOptions = {},\n ) {\n this.enabled = options.enabled ?? true;\n }\n\n /**\n * Creates a progress callback function compatible with the downloader.\n *\n * This method returns a callback that can be passed to download operations.\n * The callback initializes and updates the progress bar as the download progresses.\n * If the progress bar is disabled, returns undefined.\n *\n * @returns A progress callback function, or undefined if progress is disabled.\n */\n createCallback(): ProgressCallback | undefined {\n if (!this.enabled) {\n return undefined;\n }\n\n return (bytesDownloaded: number, totalBytes: number | null) => {\n // Initialize progress bar on first call\n if (this.startTime === 0) {\n this.startTime = Date.now();\n\n if (totalBytes) {\n // Create determinate progress bar\n this.progressBar = new cliProgress.SingleBar(\n {\n format:\n `Downloading ${this.filename} |{bar}| {percentage}% | {value}/{total} | {speed} | ETA: {eta_formatted}`,\n barCompleteChar: '█',\n barIncompleteChar: '░',\n hideCursor: true,\n stream: process.stderr,\n },\n cliProgress.Presets.shades_classic,\n );\n\n this.progressBar.start(totalBytes, 0, {\n speed: '0 B/s',\n });\n } else {\n // Create indeterminate progress bar\n this.progressBar = new cliProgress.SingleBar(\n {\n format: `Downloading ${this.filename} |{bar}| {value} | {speed}`,\n barCompleteChar: '█',\n barIncompleteChar: '░',\n hideCursor: true,\n stream: process.stderr,\n },\n cliProgress.Presets.shades_classic,\n );\n\n this.progressBar.start(100, 0, {\n speed: '0 B/s',\n });\n }\n }\n\n if (this.progressBar) {\n const elapsed = (Date.now() - this.startTime) / 1000;\n const speed = elapsed > 0 ? `${this.formatBytes(bytesDownloaded / elapsed)}/s` : '0 B/s';\n\n if (totalBytes) {\n // Update determinate progress bar\n this.progressBar.update(bytesDownloaded, {\n speed,\n });\n\n // Complete the bar if done\n if (bytesDownloaded >= totalBytes) {\n this.progressBar.stop();\n }\n } else {\n // Update indeterminate progress bar - show spinning effect\n const progress = Math.floor(Date.now() / 100) % 100;\n this.progressBar.update(progress, {\n speed,\n value: this.formatBytes(bytesDownloaded),\n });\n }\n }\n };\n }\n\n /**\n * Clears the progress bar from the terminal.\n *\n * This method stops the progress bar and removes it from the display.\n * It should be called when the download is complete or has failed.\n */\n clear(): void {\n if (this.progressBar) {\n this.progressBar.stop();\n this.progressBar = null;\n }\n }\n\n /**\n * Finishes and clears the progress bar, ensuring proper cleanup.\n *\n * This method is similar to clear() but emphasizes that the operation has\n * completed successfully. It stops the progress bar and cleans up the display.\n */\n finish(): void {\n if (this.progressBar) {\n this.progressBar.stop();\n this.progressBar = null;\n }\n }\n\n /**\n * Formats bytes into a human-readable string with appropriate units.\n *\n * @param bytes - The number of bytes to format.\n * @returns A formatted string (e.g., \"1.5 MB\", \"500 KB\").\n */\n private formatBytes(bytes: number): string {\n const units = ['B', 'KB', 'MB', 'GB'];\n let size = bytes;\n let unitIndex = 0;\n\n while (size >= 1024 && unitIndex < units.length - 1) {\n size /= 1024;\n unitIndex++;\n }\n\n return `${size.toFixed(size >= 10 ? 0 : 1)} ${units[unitIndex]}`;\n }\n}\n\n/**\n * Determines whether progress bars should be displayed based on environment.\n *\n * Progress bars are hidden when:\n * - Explicitly set to quiet mode\n * - stderr is not a TTY (piped to file or process)\n * - Running in CI environment\n * - NO_COLOR environment variable is set\n *\n * @param quiet - Whether quiet mode is enabled (default: false).\n * @returns True if progress should be shown, false otherwise.\n */\nexport function shouldShowProgress(quiet: boolean = false): boolean {\n // Don't show progress if explicitly quiet\n if (quiet) {\n return false;\n }\n\n // Don't show progress if stdout/stderr is not a TTY (e.g., piped to file or another process)\n if (!process.stderr.isTTY) {\n return false;\n }\n\n // Don't show progress in CI environments\n if (process.env['CI'] === 'true' || process.env['CI'] === '1') {\n return false;\n }\n\n // Don't show progress if NO_COLOR is set (often indicates non-interactive use)\n if (process.env['NO_COLOR']) {\n return false;\n }\n\n return true;\n}\n",
|
|
77
|
+
"import { type IDownloader, ProgressBar, shouldShowProgress } from '@dotfiles/downloader';\nimport type { TsLogger } from '@dotfiles/logger';\nimport type { IInstallOptions } from '../types';\n\n/**\n * Downloads a file with progress tracking via progress bar display.\n * Determines whether to show progress based on quiet option and displays\n * a progress bar during download. Always ensures the progress bar is cleaned up.\n *\n * Progress bar behavior:\n * - Shown by default unless quiet option is true\n * - Displays download filename and progress percentage\n * - Automatically finished and cleaned up after download completes or errors\n *\n * @param parentLogger - Logger with context from calling operation (e.g., tool name)\n * @param url - URL to download from\n * @param destinationPath - Full path where file should be saved\n * @param filename - Display name for progress bar\n * @param downloader - Downloader instance to perform the download\n * @param options - Installation options (checks quiet flag)\n */\nexport async function downloadWithProgress(\n parentLogger: TsLogger,\n url: string,\n destinationPath: string,\n filename: string,\n downloader: IDownloader,\n options?: IInstallOptions,\n): Promise<void> {\n const showProgress = shouldShowProgress(options?.quiet);\n const progressBar = new ProgressBar(filename, { enabled: showProgress });\n\n try {\n await downloader.download(parentLogger, url, {\n destinationPath,\n onProgress: progressBar.createCallback(),\n });\n } finally {\n progressBar.finish();\n }\n}\n",
|
|
78
|
+
"import type {\n BaseInstallParams,\n IDownloadContext,\n IExtractContext,\n IOperationFailure,\n IOperationSuccess,\n ToolConfig,\n} from '@dotfiles/core';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport type { HookExecutor } from './HookExecutor';\nimport { messages } from './log-messages';\n\n/**\n * Result type for hook execution indicating success or failure with error message.\n */\nexport type ExecuteHooksResult = IOperationSuccess | IOperationFailure;\n\n/**\n * Extracts hooks from tool configuration's install params.\n * Uses type assertion since all install params extend BaseInstallParams which has hooks.\n */\nfunction getHooksFromConfig(toolConfig: ToolConfig): BaseInstallParams['hooks'] | undefined {\n const installParams = toolConfig.installParams as BaseInstallParams | undefined;\n return installParams?.hooks;\n}\n\n/**\n * Executes the afterDownload hook if defined in tool configuration.\n * Returns immediately with success if no hook is configured.\n *\n * The afterDownload hook runs after a file has been downloaded but before\n * any extraction or processing. Common uses include:\n * - Validating downloaded file integrity\n * - Modifying download before extraction\n * - Preparing environment for extraction\n *\n * @param toolConfig - Tool configuration that may contain afterDownload hook\n * @param context - Post-download context with downloadPath\n * @param hookExecutor - Hook executor for proper context and error handling\n * @param fs - File system interface for hook file operations\n * @param logger - Logger for hook execution messages\n * @returns Success result or failure with error message\n */\nexport async function executeAfterDownloadHook(\n toolConfig: ToolConfig,\n context: IDownloadContext,\n hookExecutor: HookExecutor,\n fs: IFileSystem,\n logger: TsLogger,\n): Promise<ExecuteHooksResult> {\n const hooks = getHooksFromConfig(toolConfig);\n const afterDownloadHooks = hooks?.['after-download'];\n\n if (!afterDownloadHooks) {\n return { success: true };\n }\n\n logger.debug(messages.lifecycle.hookExecution('after-download'));\n\n const enhancedContext = hookExecutor.createEnhancedContext(context, fs);\n\n for (const hook of afterDownloadHooks) {\n const hookResult = await hookExecutor.executeHook(logger, 'after-download', hook, enhancedContext);\n\n if (!hookResult.success) {\n return {\n success: false,\n error: `afterDownload hook failed: ${hookResult.error}`,\n };\n }\n }\n\n return { success: true };\n}\n\n/**\n * Executes the afterExtract hook if defined in tool configuration.\n * Returns immediately with success if no hook is configured.\n *\n * The afterExtract hook runs after an archive has been extracted but before\n * binary setup. Common uses include:\n * - Moving binaries to expected locations\n * - Renaming extracted files\n * - Building from source\n * - Cleaning up unnecessary files\n *\n * @param toolConfig - Tool configuration that may contain afterExtract hook\n * @param context - Post-extract context with extractDir and extractResult\n * @param hookExecutor - Hook executor for proper context and error handling\n * @param fs - File system interface for hook file operations\n * @param logger - Logger for hook execution messages\n * @returns Success result or failure with error message\n */\nexport async function executeAfterExtractHook(\n toolConfig: ToolConfig,\n context: IExtractContext,\n hookExecutor: HookExecutor,\n fs: IFileSystem,\n logger: TsLogger,\n): Promise<ExecuteHooksResult> {\n const hooks = getHooksFromConfig(toolConfig);\n const afterExtractHooks = hooks?.['after-extract'];\n\n if (!afterExtractHooks) {\n return { success: true };\n }\n\n logger.debug(messages.lifecycle.hookExecution('after-extract'));\n\n const enhancedContext = hookExecutor.createEnhancedContext(context, fs);\n\n for (const hook of afterExtractHooks) {\n const hookResult = await hookExecutor.executeHook(logger, 'after-extract', hook, enhancedContext);\n\n if (!hookResult.success) {\n return {\n success: false,\n error: `afterExtract hook failed: ${hookResult.error}`,\n };\n }\n }\n\n return { success: true };\n}\n",
|
|
79
|
+
"/**\n * Extracts a user-friendly error cause from an error object.\n * For ShellError, extracts the trimmed stderr content.\n * For other errors, returns the error message.\n */\n\ninterface IShellErrorLike {\n name: string;\n message?: string;\n exitCode?: number;\n stdout?: unknown;\n stderr?: unknown;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction isShellErrorLike(value: unknown): value is IShellErrorLike {\n if (!isRecord(value)) {\n return false;\n }\n\n const nameValue = value['name'];\n if (typeof nameValue !== 'string') {\n return false;\n }\n\n return nameValue === 'ShellError';\n}\n\nfunction normalizeShellStream(value: unknown): string {\n if (typeof value === 'string') {\n return value.trim();\n }\n\n if (value instanceof Uint8Array) {\n const result = Buffer.from(value).toString('utf8').trim();\n return result;\n }\n\n return '';\n}\n\n/**\n * Extracts a concise error cause from an error for display in log messages.\n * For ShellError, extracts stderr content if available, otherwise uses the message.\n * For other errors, returns the error message.\n *\n * @param error - The error to extract the cause from\n * @returns A trimmed string containing the error cause\n */\nexport function extractErrorCause(error: unknown): string {\n if (isShellErrorLike(error)) {\n const stderr = normalizeShellStream(error.stderr);\n if (stderr.length > 0) {\n return stderr;\n }\n\n const stdout = normalizeShellStream(error.stdout);\n if (stdout.length > 0) {\n return stdout;\n }\n\n if (error.message) {\n return error.message;\n }\n\n return `exit code ${error.exitCode ?? 'unknown'}`;\n }\n\n if (error instanceof Error) {\n return error.message;\n }\n\n return String(error);\n}\n",
|
|
80
|
+
"import type { IBinaryConfig } from '@dotfiles/core';\n\n/**\n * Normalizes a mixed array of strings and IBinaryConfig objects to a consistent IBinaryConfig array.\n * Converts string entries to IBinaryConfig objects with flexible minimatch glob patterns.\n *\n * Pattern Generation:\n * - String 'tool' becomes object with name: 'tool', pattern: '{,star/}tool'\n * - Pattern matches both 'tool' and 'dir/tool' using minimatch glob syntax\n * - Allows binaries to be located at root or one directory deep in archives\n *\n * Fallback Behavior:\n * - If binaries array is empty or undefined, returns an empty array (no binaries configured)\n * - Tools that do not call .bin() will have no shims generated\n *\n * @param binaries - Array of strings or IBinaryConfig objects, or undefined\n * @returns Array of IBinaryConfig objects with minimatch glob patterns for locating binaries\n */\nexport function normalizeBinaries(binaries: (string | IBinaryConfig)[] | undefined): IBinaryConfig[] {\n if (!binaries || binaries.length === 0) {\n return [];\n }\n\n return binaries.map((binary) => (typeof binary === 'string' ? { name: binary, pattern: `{,*/}${binary}` } : binary));\n}\n",
|
|
81
|
+
"import type { IBinaryConfig } from '@dotfiles/core';\nimport { normalizeBinaries } from './normalizeBinaries';\n\n/**\n * Extracts binary names from a mixed binaries array without path information.\n * Normalizes the input and returns only the name property from each IBinaryConfig.\n *\n * Used when you need just the binary names for operations like:\n * - Listing installed binaries\n * - Checking binary existence\n * - Creating completion files\n *\n * @param binaries - Array of strings or IBinaryConfig objects, or undefined\n * @returns Array of binary names (e.g., ['rg', 'ripgrep'])\n */\nexport function getBinaryNames(binaries: (string | IBinaryConfig)[] | undefined): string[] {\n const normalizedBinaries = normalizeBinaries(binaries);\n return normalizedBinaries.map((config) => config.name);\n}\n",
|
|
82
|
+
"import type { IBinaryConfig } from '@dotfiles/core';\nimport { join } from 'node:path';\nimport { normalizeBinaries } from './normalizeBinaries';\n\n/**\n * Generates full paths for all binaries by combining install directory with binary names.\n * Normalizes the binaries input and constructs absolute paths for each binary.\n *\n * Used to create the binaryPaths array in InstallResult, providing full paths to\n * all installed binaries. These paths are used for:\n * - Registry tracking\n * - Shim generation\n * - Verification\n *\n * @param binaries - Array of strings or IBinaryConfig objects, or undefined\n * @param installedDir - Directory where binaries are installed\n * @returns Array of absolute paths to binaries (e.g., ['/path/to/install/rg', '/path/to/install/ripgrep'])\n */\nexport function getBinaryPaths(\n binaries: (string | IBinaryConfig)[] | undefined,\n installedDir: string,\n): string[] {\n const normalizedBinaries = normalizeBinaries(binaries);\n return normalizedBinaries.map((binary) => join(installedDir, binary.name));\n}\n",
|
|
83
|
+
"import {\n type AsyncInstallHook,\n createLoggingShell,\n createShell,\n hasLoggingShell,\n type IInstallBaseContext,\n type IOperationFailure,\n type IOperationSuccess,\n Platform,\n type Shell,\n} from '@dotfiles/core';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { TrackedFileSystem } from '@dotfiles/registry/file';\nimport path from 'node:path';\nimport { extractErrorCause } from './extractErrorCause';\nimport { messages } from './log-messages';\nimport { writeHookErrorDetails } from './writeHookErrorDetails';\n\nfunction isShellError(error: unknown): boolean {\n if (typeof error !== 'object' || error === null) {\n return false;\n }\n const errorObj = error as Record<string, unknown>;\n return errorObj['name'] === 'ShellError';\n}\n\nexport type HookHandler<TContext extends IInstallBaseContext = IInstallBaseContext> = AsyncInstallHook<TContext>;\n\nfunction createToolConfigCwdShell($shell: Shell, cwdPath: string): Shell {\n const configuredShell: Shell = Object.assign((strings: TemplateStringsArray, ...expressions: unknown[]) => {\n const shellPromise = $shell(strings, ...expressions).cwd(cwdPath);\n return shellPromise;\n }, $shell);\n\n return configuredShell;\n}\n\n/**\n * Creates a shell wrapper that includes additional directories in the PATH environment variable.\n * This allows commands to find binaries in the specified directories without needing full paths.\n *\n * WORKAROUND: Due to a bun shell bug where `.env()` PATH changes don't affect command resolution\n * (command lookup uses process.env.PATH, not the shell's export_env), we wrap commands in `sh -c`.\n * This delegates command resolution to the subshell which correctly inherits the modified PATH.\n *\n * @param $shell - The base shell instance to wrap\n * @param additionalPaths - Array of directory paths to prepend to PATH\n * @param platform - The target platform\n * @param baseEnv - Base environment to use (preserves recursion guard and other install-time env vars)\n * @returns A new shell instance with enhanced PATH\n */\nfunction createShellWithEnhancedPath(\n $shell: Shell,\n additionalPaths: string[],\n platform: Platform,\n baseEnv: Record<string, string | undefined>,\n): Shell {\n if (additionalPaths.length === 0) {\n return $shell;\n }\n\n const pathSeparator = platform === Platform.Windows ? ';' : ':';\n const currentPath = baseEnv['PATH'] || '';\n const enhancedPath = [...additionalPaths, currentPath].join(pathSeparator);\n\n // Merge the enhanced PATH with baseEnv to preserve recursion guard and other install-time env vars\n const enhancedEnv: Record<string, string | undefined> = {\n ...baseEnv,\n PATH: enhancedPath,\n };\n\n const configuredShell: Shell = Object.assign((strings: TemplateStringsArray, ...expressions: unknown[]) => {\n // Build the command string from the template literal\n // Bun shell escapes ${} expressions, so we reconstruct the intended command\n let command = strings[0] || '';\n for (let i = 0; i < expressions.length; i++) {\n command += String(expressions[i]) + (strings[i + 1] || '');\n }\n\n // Wrap in sh -c so the subshell inherits PATH and performs command resolution\n // Bun escapes ${command} as a single argument, preventing injection\n const shellPromise = $shell`sh -c ${command}`.env(enhancedEnv);\n return shellPromise;\n }, $shell);\n\n return configuredShell;\n}\n\n/**\n * Configuration options for controlling hook execution behavior.\n */\nexport interface IHookExecutionOptions {\n /** Timeout in milliseconds for hook execution (default: 60000ms) */\n timeoutMs?: number;\n /** Whether to continue installation if hook fails (default: false) */\n continueOnError?: boolean;\n}\n\n/**\n * Result of hook execution including success status, error details, and timing information.\n * Indicates whether hook completed successfully or failed, and whether it was skipped.\n */\nexport type HookExecutionResult =\n | (IOperationSuccess & {\n /** Duration of hook execution in milliseconds */\n durationMs: number;\n /** Whether hook was skipped due to timeout or other reason */\n skipped: boolean;\n })\n | (IOperationFailure & {\n /** Duration of hook execution in milliseconds */\n durationMs: number;\n /** Whether hook was skipped due to timeout or other reason */\n skipped: boolean;\n });\n\n/**\n * Complete definition of a hook including the function, name, and execution options.\n * Used by `executeHooks` to run multiple hooks in sequence.\n */\nexport interface IHookDefinition<TContext extends IInstallBaseContext = IInstallBaseContext> {\n /** Name of the hook */\n name: string;\n /** Hook function to execute */\n hook: HookHandler<TContext>;\n /** Optional execution options */\n options?: IHookExecutionOptions;\n}\n\n/**\n * Executes installation hooks with proper error handling, timeouts, and context management.\n * Provides a consistent way to run beforeInstall, afterDownload, afterExtract, and afterInstall hooks.\n *\n * Features:\n * - Timeout enforcement (default 60 seconds)\n * - Error handling with optional continue-on-error\n * - Execution duration tracking\n * - Tool-specific logging via subloggers\n * - Enhanced context creation with file system and shell access\n * - Sequential execution of multiple hooks\n *\n * Note: This class does NOT store a logger. All methods require a parentLogger parameter\n * to ensure proper tool context propagation through the logging hierarchy.\n */\ntype WriteOutput = (chunk: string) => void;\n\nexport class HookExecutor {\n private readonly defaultTimeoutMs = 60000; // 1 minute default\n private readonly writeOutput: WriteOutput;\n\n constructor(writeOutput: WriteOutput) {\n this.writeOutput = writeOutput;\n }\n\n /**\n * Executes a single hook with proper error handling, timeout enforcement, and logging.\n * Creates a hook-specific logger and races the hook execution against a timeout promise.\n *\n * The hook receives an enhanced context with:\n * - fileSystem: Tool-specific TrackedFileSystem for file operations\n * - toolConfig: Tool configuration (if available in base context)\n * - $: Bun's shell operator for executing commands\n *\n * @param parentLogger - Logger with tool context for proper log hierarchy\n * @param hookName - Name of the hook for logging (e.g., 'beforeInstall', 'afterDownload')\n * @param hook - Hook function to execute\n * @param enhancedContext - Enhanced context with file system and shell access\n * @param options - Execution options (timeout, continueOnError)\n * @returns Result with success status, duration, and error details if failed\n */\n async executeHook<TContext extends IInstallBaseContext>(\n parentLogger: TsLogger,\n hookName: string,\n hook: HookHandler<TContext>,\n enhancedContext: TContext,\n options: IHookExecutionOptions = {},\n ): Promise<HookExecutionResult> {\n const methodLogger = parentLogger\n .getSubLogger({ name: 'HookExecutor' })\n .getSubLogger({ name: 'executeHook', context: hookName });\n const timeoutMs: number = options.timeoutMs ?? this.defaultTimeoutMs;\n const continueOnError: boolean = options.continueOnError ?? false;\n const startTime = Date.now();\n\n methodLogger.debug(messages.hookExecutor.executingHook(hookName, timeoutMs));\n\n // Track the timeout timer so we can clear it when hook completes\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n try {\n // Create a promise that resolves when the hook completes\n const hookPromise = hook(enhancedContext);\n\n // Create a timeout promise that rejects after timeoutMs\n const timeoutPromise: Promise<never> = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => {\n reject(new Error(messages.hookExecutor.timeoutExceeded(hookName, timeoutMs)));\n }, timeoutMs);\n });\n\n // Race the hook against the timeout\n await Promise.race([hookPromise, timeoutPromise]);\n\n // Clear the timeout to prevent it from keeping the event loop alive\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId);\n }\n\n const durationMs: number = Date.now() - startTime;\n methodLogger.debug(messages.hookExecutor.hookCompleted(hookName, durationMs));\n\n const result: HookExecutionResult = {\n success: true,\n durationMs,\n skipped: false,\n };\n return result;\n } catch (error) {\n // Clear the timeout to prevent it from keeping the event loop alive\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId);\n }\n\n const durationMs: number = Date.now() - startTime;\n const errorMessage = error instanceof Error ? error.message : String(error);\n const errorCause = extractErrorCause(error);\n\n // Log the error with cause in message - the logger filters stack traces for user-facing levels\n methodLogger.error(messages.outcome.hookFailed(errorCause), error);\n\n // For shell errors, write additional details (code frame) via writeHookErrorDetails\n if (isShellError(error)) {\n await writeHookErrorDetails({\n fileSystem: enhancedContext.fileSystem,\n logger: methodLogger,\n hookName,\n toolName: enhancedContext.toolName,\n error,\n writeOutput: this.writeOutput,\n });\n }\n\n if (continueOnError) {\n methodLogger.debug(messages.hookExecutor.continuingDespiteFailure(hookName));\n }\n\n const result: HookExecutionResult = {\n success: false,\n error: errorMessage,\n durationMs,\n skipped: false,\n };\n return result;\n }\n }\n\n /**\n * Creates an enhanced context for hook execution by adding file system, shell access, and toolConfig.\n * Ensures hooks have all necessary dependencies while maintaining file tracking capabilities.\n *\n * Enhancements:\n * - fileSystem: Tool-specific TrackedFileSystem or provided filesystem\n * - toolConfig: Extracted from InstallContext if available\n * - $: Bun's shell operator for executing commands with:\n * - Working directory set to tool config directory (if configFilePath exists)\n * - PATH enhanced with binary directories (if binaryPaths exists, e.g., for after-install hooks)\n * This allows hooks to execute freshly installed binaries by name without full paths.\n * - Command logging (if logger provided): Commands logged as `$ cmd`, output as `| line`\n *\n * The TrackedFileSystem integration allows proper tracking of file operations\n * performed by hooks for registry management.\n *\n * @example\n * ```typescript\n * // In after-install hooks, binaries are automatically in PATH:\n * .hook('after-install', async ({ $ }) => {\n * await $`my-tool --version`; // Works without full path\n * })\n * ```\n *\n * @param baseContext - Base install or hook context with tool information\n * @param fileSystem - File system instance (may be TrackedFileSystem)\n * @param logger - Optional logger for command/output logging\n * @returns Enhanced context ready for hook execution\n */\n createEnhancedContext<TContext extends IInstallBaseContext>(\n baseContext: TContext,\n fileSystem: IFileSystem,\n logger?: TsLogger,\n ): TContext {\n // Create a tool-specific TrackedFileSystem if we have one\n const enhancedFileSystem: IFileSystem = fileSystem instanceof TrackedFileSystem\n ? fileSystem.withToolName(baseContext.toolName)\n : fileSystem;\n\n const toolConfigFilePath: string | undefined = baseContext.toolConfig?.configFilePath;\n const toolConfigDirPath: string | undefined = toolConfigFilePath ? path.dirname(toolConfigFilePath) : undefined;\n\n // Extract unique binary directories from binaryPaths (if present, e.g., for after-install context)\n const binaryPaths: string[] = 'binaryPaths' in baseContext && Array.isArray(baseContext.binaryPaths)\n ? baseContext.binaryPaths\n : [];\n const binaryDirs: string[] = [...new Set(binaryPaths.map((p) => path.dirname(p)))];\n\n // Create the base shell - if we have a logger and installEnv, create a streaming shell\n // that will log command output in real-time. Otherwise use the base context's shell.\n let enhancedShell: Shell;\n const needsLogging = logger && !hasLoggingShell(baseContext.$);\n\n if (needsLogging && baseContext.installEnv) {\n // Create a new shell with output streaming and the install environment\n // skipCommandLog: true because createLoggingShell will log the command\n enhancedShell = createShell({ logger, skipCommandLog: true, env: baseContext.installEnv });\n } else {\n // Use the base context's shell (preserves any environment configuration)\n enhancedShell = baseContext.$;\n }\n\n // Add binary directories to PATH if present\n // Use installEnv if available (preserves recursion guard), otherwise fall back to process.env\n const baseEnv: Record<string, string | undefined> = baseContext.installEnv ?? process.env;\n if (binaryDirs.length > 0) {\n enhancedShell = createShellWithEnhancedPath(enhancedShell, binaryDirs, baseContext.systemInfo.platform, baseEnv);\n }\n\n // Set working directory to tool config directory if available\n if (toolConfigDirPath) {\n enhancedShell = createToolConfigCwdShell(enhancedShell, toolConfigDirPath);\n }\n\n // Wrap shell with logging for command logging\n // This wraps the existing shell to preserve its environment configuration\n if (needsLogging) {\n enhancedShell = createLoggingShell(enhancedShell, logger);\n }\n\n const result: TContext = {\n ...baseContext,\n $: enhancedShell,\n fileSystem: enhancedFileSystem,\n };\n return result;\n }\n\n /**\n * Executes multiple hooks in sequence with proper error handling and result tracking.\n * Stops execution if a hook fails and continueOnError is not set for that hook.\n *\n * Use this method to run a series of hooks at once, useful for batch operations\n * or when hooks have dependencies on each other's execution order.\n *\n * @param parentLogger - Logger with tool context for proper log hierarchy\n * @param hooks - Array of hook definitions with names, functions, and options\n * @param enhancedContext - Enhanced context shared across all hooks\n * @returns Array of execution results for each hook\n */\n async executeHooks<TContext extends IInstallBaseContext>(\n parentLogger: TsLogger,\n hooks: IHookDefinition<TContext>[],\n enhancedContext: TContext,\n ): Promise<HookExecutionResult[]> {\n const methodLogger = parentLogger.getSubLogger({ name: 'HookExecutor' }).getSubLogger({ name: 'executeHooks' });\n const results: HookExecutionResult[] = [];\n\n for (const hookDefinition of hooks) {\n const name: string = hookDefinition.name;\n const hook: HookHandler<TContext> = hookDefinition.hook;\n const options: IHookExecutionOptions | undefined = hookDefinition.options;\n\n const result = await this.executeHook(parentLogger, name, hook, enhancedContext, options);\n results.push(result);\n\n // If hook failed and we're not continuing on error, stop execution\n if (!result.success && !options?.continueOnError) {\n methodLogger.debug(messages.hookExecutor.stoppingDueToFailure(name));\n break;\n }\n }\n\n const finalResults: HookExecutionResult[] = results;\n return finalResults;\n }\n}\n",
|
|
84
|
+
"import { codeFrameColumns } from '@babel/code-frame';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\n\ninterface IToolStackFrame {\n filePath: string;\n line: number;\n column: number;\n}\n\ninterface IShellErrorLike {\n name: string;\n message?: string;\n exitCode?: number;\n stdout?: unknown;\n stderr?: unknown;\n stack?: string;\n}\n\ntype WriteOutput = (chunk: string) => void;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction isShellErrorLike(value: unknown): value is IShellErrorLike {\n if (!isRecord(value)) {\n return false;\n }\n\n const nameValue = value['name'];\n if (typeof nameValue !== 'string') {\n return false;\n }\n\n return nameValue === 'ShellError';\n}\n\nfunction isTraceEnabled(logger: TsLogger): boolean {\n return logger.isTracingEnabled();\n}\n\nfunction normalizeMultiline(value: string): string {\n const normalizedValue = value.endsWith('\\n') ? value : `${value}\\n`;\n return normalizedValue;\n}\n\nfunction buildToolStackFrame(filePath: string, line: number, column: number): IToolStackFrame {\n const result: IToolStackFrame = {\n filePath,\n line,\n column,\n };\n return result;\n}\n\nfunction parseToolFrameWithColumn(stackLine: string, regex: RegExp): IToolStackFrame | null {\n const match = regex.exec(stackLine);\n if (!match) {\n return null;\n }\n\n const filePath = match[1];\n const line = Number(match[2]);\n const column = Number(match[3]);\n\n if (!filePath || Number.isNaN(line) || Number.isNaN(column)) {\n return null;\n }\n\n const result = buildToolStackFrame(filePath, line, column);\n return result;\n}\n\nfunction parseToolFrameLineOnly(stackLine: string): IToolStackFrame | null {\n const match = /([^\\s]+\\.tool\\.ts):(\\d+)\\s*$/.exec(stackLine);\n if (!match) {\n return null;\n }\n\n const filePath = match[1];\n const line = Number(match[2]);\n\n if (!filePath || Number.isNaN(line)) {\n return null;\n }\n\n const result = buildToolStackFrame(filePath, line, 1);\n return result;\n}\n\nfunction parseFirstToolFrame(stack: string): IToolStackFrame | null {\n const stackLines = stack.split('\\n');\n\n for (const stackLine of stackLines) {\n if (!stackLine.includes('.tool.ts')) {\n continue;\n }\n\n const frameFromParen = parseToolFrameWithColumn(stackLine, /\\(([^)]+\\.tool\\.ts):(\\d+):(\\d+)\\)\\s*$/);\n if (frameFromParen) {\n return frameFromParen;\n }\n\n const frameFromBare = parseToolFrameWithColumn(stackLine, /\\s+at\\s+([^\\s]+\\.tool\\.ts):(\\d+):(\\d+)\\s*$/);\n if (frameFromBare) {\n return frameFromBare;\n }\n\n const frameFromLineOnly = parseToolFrameLineOnly(stackLine);\n if (frameFromLineOnly) {\n return frameFromLineOnly;\n }\n }\n\n return null;\n}\n\nasync function buildToolCodeFrame(fileSystem: IFileSystem, frame: IToolStackFrame): Promise<string | null> {\n const exists = await fileSystem.exists(frame.filePath);\n if (!exists) {\n return null;\n }\n\n const source = await fileSystem.readFile(frame.filePath, 'utf8');\n\n const codeFrame = codeFrameColumns(\n source,\n {\n start: {\n line: frame.line,\n column: frame.column,\n },\n },\n {\n linesAbove: 2,\n linesBelow: 2,\n highlightCode: true,\n forceColor: true,\n },\n );\n\n const result = `${frame.filePath}:${frame.line}:${frame.column}\\n${codeFrame}\\n`;\n return result;\n}\n\nfunction buildShellOutputDetails(error: IShellErrorLike): string {\n const chunks: string[] = [];\n\n const exitCode = typeof error.exitCode === 'number' ? String(error.exitCode) : 'unknown';\n chunks.push(`exit code: ${exitCode}`);\n\n const stderrValue = normalizeShellStream(error.stderr);\n if (stderrValue.length > 0) {\n chunks.push('stderr:');\n chunks.push(stderrValue);\n }\n\n const stdoutValue = normalizeShellStream(error.stdout);\n if (stdoutValue.length > 0) {\n chunks.push('stdout:');\n chunks.push(stdoutValue);\n }\n\n const result = chunks.join('\\n');\n return result;\n}\n\nfunction normalizeShellStream(value: unknown): string {\n if (typeof value === 'string') {\n return value;\n }\n\n if (value instanceof Uint8Array) {\n const result = Buffer.from(value).toString('utf8');\n return result;\n }\n\n return '';\n}\n\nfunction buildNonShellErrorDetails(error: unknown, includeStack: boolean): string {\n const chunks: string[] = [];\n\n if (error instanceof Error) {\n chunks.push(`${error.name}: ${error.message}`);\n if (includeStack && typeof error.stack === 'string' && error.stack.length > 0) {\n chunks.push('stack:');\n chunks.push(error.stack);\n }\n\n const result = chunks.join('\\n');\n return result;\n }\n\n chunks.push(String(error));\n\n const result = chunks.join('\\n');\n return result;\n}\n\ninterface IWriteHookErrorDetailsParams {\n fileSystem: IFileSystem;\n logger: TsLogger;\n hookName: string;\n toolName: string;\n error: unknown;\n writeOutput: WriteOutput;\n}\n\nasync function buildCodeFrameSection(fileSystem: IFileSystem, stack: string | undefined): Promise<string | null> {\n if (!stack) {\n return null;\n }\n\n const toolFrame = parseFirstToolFrame(stack);\n if (!toolFrame) {\n return null;\n }\n\n const codeFrame = await buildToolCodeFrame(fileSystem, toolFrame);\n return codeFrame;\n}\n\nfunction buildStackLines(stack: string | undefined, includeStack: boolean): string[] {\n if (!includeStack) {\n const result: string[] = [];\n return result;\n }\n\n if (!stack || stack.length === 0) {\n const result: string[] = [];\n return result;\n }\n\n const result: string[] = ['stack:', stack];\n return result;\n}\n\nasync function buildShellErrorVerboseSection(error: IShellErrorLike, includeStack: boolean): Promise<string[]> {\n const chunks: string[] = [buildShellOutputDetails(error)];\n\n const stack = typeof error.stack === 'string' ? error.stack : undefined;\n const stackLines = buildStackLines(stack, includeStack);\n chunks.push(...stackLines);\n\n const result: string[] = chunks;\n return result;\n}\n\nasync function buildNonShellErrorVerboseSection(error: unknown, includeStack: boolean): Promise<string[]> {\n const chunks: string[] = [buildNonShellErrorDetails(error, includeStack)];\n\n const result: string[] = chunks;\n return result;\n}\n\nasync function buildHookErrorOutput(params: IWriteHookErrorDetailsParams): Promise<string> {\n const includeVerbose = isTraceEnabled(params.logger);\n const chunks: string[] = [];\n\n // Get the stack from the error\n const stack = getErrorStack(params.error);\n\n // Build the code frame section (always shown if available)\n const codeFrame = await buildCodeFrameSection(params.fileSystem, stack);\n if (codeFrame) {\n chunks.push('---');\n chunks.push(codeFrame);\n chunks.push('---');\n }\n\n // In verbose mode, also include detailed error info\n if (includeVerbose) {\n if (isShellErrorLike(params.error)) {\n const verboseLines = await buildShellErrorVerboseSection(params.error, true);\n chunks.push(...verboseLines);\n } else {\n const verboseLines = await buildNonShellErrorVerboseSection(params.error, true);\n chunks.push(...verboseLines);\n }\n }\n\n if (chunks.length === 0) {\n return '';\n }\n\n const output = normalizeMultiline(chunks.join('\\n'));\n return output;\n}\n\nfunction getErrorStack(error: unknown): string | undefined {\n if (isShellErrorLike(error)) {\n return typeof error.stack === 'string' ? error.stack : undefined;\n }\n if (error instanceof Error) {\n return error.stack;\n }\n return undefined;\n}\n\nexport async function writeHookErrorDetails(params: IWriteHookErrorDetailsParams): Promise<void> {\n const output = await buildHookErrorOutput(params);\n if (output.length > 0) {\n params.writeOutput(output);\n }\n}\n",
|
|
85
|
+
"import type { IBinaryConfig, IInstallContext, ToolConfig } from '@dotfiles/core';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { getAllFilesRecursively } from '@dotfiles/utils';\nimport { minimatch } from 'minimatch';\nimport path from 'node:path';\nimport { createBinaryEntrypoint } from './createBinaryEntrypoint';\nimport { messages } from './log-messages';\nimport { normalizeBinaries } from './normalizeBinaries';\n\n/**\n * Sets up binaries from an extracted archive by finding them using patterns and creating symlinks.\n * Primary function for handling binaries after archive extraction (tar.gz, zip, etc.).\n *\n * Process:\n * 1. Normalizes binary configurations to include default patterns\n * 2. Uses minimatch patterns to locate binaries in extracted directory\n * 3. Creates symlinks in binaries directory pointing to found binaries\n * 4. Logs errors and shows directory tree if binaries not found\n *\n * The subdirectory name (version or timestamp) is extracted from context.stagingDir\n * and used for creating the symlink structure.\n *\n * @param fs - File system interface for file operations\n * @param toolName - Name of the tool being installed\n * @param toolConfig - Tool configuration with binary definitions\n * @param context - Install context with paths\n * @param extractDir - Directory where archive was extracted\n * @param parentLogger - Logger for diagnostic messages\n */\nexport async function setupBinariesFromArchive(\n fs: IFileSystem,\n toolName: string,\n toolConfig: ToolConfig,\n context: IInstallContext,\n extractDir: string,\n parentLogger: TsLogger,\n): Promise<void> {\n const logger = parentLogger.getSubLogger({ name: 'setupBinariesFromArchive' });\n const binariesDir = path.join(context.projectConfig.paths.generatedDir, 'binaries');\n const binaryConfigs = normalizeBinaries(toolConfig.binaries);\n\n // Extract subdirectory name from context.stagingDir\n // This will be either a version (e.g., \"1.0.0\") or timestamp (e.g., \"2025-11-04-20-53-47\")\n const subdirName = path.basename(context.stagingDir);\n\n await setupBinariesUsingPatterns(fs, toolName, binaryConfigs, subdirName, extractDir, binariesDir, logger);\n}\n\n/**\n * Setup binaries using pattern-based location (new approach)\n * @returns true if at least one binary was found and set up, false otherwise\n */\nasync function setupBinariesUsingPatterns(\n fs: IFileSystem,\n toolName: string,\n binaryConfigs: IBinaryConfig[],\n versionOrTimestamp: string,\n extractDir: string,\n binariesDir: string,\n parentLogger: TsLogger,\n): Promise<boolean> {\n const logger = parentLogger.getSubLogger({ name: 'setupBinariesUsingPatterns' });\n let foundAnyBinary = false;\n\n for (const binaryConfig of binaryConfigs) {\n const { name: binaryName, pattern } = binaryConfig;\n\n // Find the binary using its pattern\n const binaryPath = await findBinaryUsingPattern(fs, extractDir, pattern, binaryName, logger);\n\n if (!binaryPath) {\n logger.error(messages.binarySetupService.binaryNotFound(binaryName, pattern));\n\n // Show extracted files to help user find the correct binary\n const tree = await generateDirectoryTree(fs, extractDir);\n if (tree.length > 0) {\n const treeString = tree.join('\\n');\n logger.error(messages.binarySetupService.extractedFilesTree(extractDir, treeString));\n }\n\n // Continue to next binary - don't fail the installation\n // The extracted files remain cached so user can fix the pattern and re-run\n continue;\n }\n\n // Create stable entrypoint file for this binary\n const relativePath = path.relative(extractDir, binaryPath);\n await createBinaryEntrypoint(fs, toolName, binaryName, versionOrTimestamp, relativePath, binariesDir, logger);\n foundAnyBinary = true;\n }\n\n return foundAnyBinary;\n}\n\n/**\n * Generate a tree-like listing of directory contents\n */\nasync function generateDirectoryTree(\n fs: IFileSystem,\n dirPath: string,\n prefix = '',\n maxDepth = 3,\n currentDepth = 0,\n): Promise<string[]> {\n const lines: string[] = [];\n\n if (currentDepth >= maxDepth) {\n return lines;\n }\n\n let entries: string[] = [];\n try {\n entries = await fs.readdir(dirPath);\n } catch {\n return lines; // Skip directories that can't be read\n }\n\n const sortedEntries = entries.toSorted();\n\n for (let i = 0; i < sortedEntries.length; i++) {\n const entry = sortedEntries[i];\n if (!entry) continue;\n\n const isLastEntry = i === sortedEntries.length - 1;\n const entryPath = path.join(dirPath, entry);\n const connector = isLastEntry ? '└── ' : '├── ';\n const childPrefix = prefix + (isLastEntry ? ' ' : '│ ');\n\n const entryLines = await formatDirectoryEntry(\n fs,\n entryPath,\n entry,\n prefix,\n connector,\n childPrefix,\n maxDepth,\n currentDepth,\n );\n lines.push(...entryLines);\n }\n\n return lines;\n}\n\n/**\n * Format a single directory entry for tree output\n */\nasync function formatDirectoryEntry(\n fs: IFileSystem,\n entryPath: string,\n entry: string,\n prefix: string,\n connector: string,\n childPrefix: string,\n maxDepth: number,\n currentDepth: number,\n): Promise<string[]> {\n const lines: string[] = [];\n\n try {\n const stat = await fs.stat(entryPath);\n const displayName = stat.isDirectory() ? `${entry}/` : entry;\n lines.push(`${prefix}${connector}${displayName}`);\n\n if (stat.isDirectory() && currentDepth < maxDepth - 1) {\n const childLines = await generateDirectoryTree(fs, entryPath, childPrefix, maxDepth, currentDepth + 1);\n lines.push(...childLines);\n }\n } catch {\n // Skip entries that can't be accessed\n lines.push(`${prefix}${connector}${entry} (inaccessible)`);\n }\n\n return lines;\n}\n\n/**\n * Check if a file is executable\n */\nasync function isExecutable(fs: IFileSystem, filePath: string): Promise<boolean> {\n const stats = await fs.stat(filePath);\n const mode = stats.mode;\n return (mode & 0o111) !== 0;\n}\n\n/**\n * Find binary file using minimatch pattern.\n * Prefers executable with exact basename match, but falls back to first matching executable\n * when pattern uses wildcards (e.g., 'hermit-*' matching 'hermit-darwin-arm64').\n */\nexport async function findBinaryUsingPattern(\n fs: IFileSystem,\n extractDir: string,\n pattern: string,\n binaryName: string,\n parentLogger: TsLogger,\n): Promise<string | null> {\n const logger = parentLogger.getSubLogger({ name: 'findBinaryUsingPattern' });\n logger.debug(messages.binarySetupService.searchingWithPattern(pattern, extractDir));\n\n const allFiles = await getAllFilesRecursively(fs, extractDir, extractDir);\n const matchedFiles = allFiles.filter((file) => minimatch(file, pattern));\n\n if (matchedFiles.length === 0) {\n return null;\n }\n\n const executables: string[] = [];\n for (const file of matchedFiles) {\n const fullPath = path.join(extractDir, file);\n if (await isExecutable(fs, fullPath)) {\n executables.push(file);\n }\n }\n\n if (executables.length === 0) {\n return null;\n }\n\n // Prefer exact basename match if available\n const exactMatch = executables.find((file) => path.basename(file) === binaryName);\n if (exactMatch) {\n return path.join(extractDir, exactMatch);\n }\n\n // Fall back to first matching executable (for patterns like 'hermit-*' matching 'hermit-darwin-arm64')\n const firstMatch = executables[0];\n if (firstMatch) {\n return path.join(extractDir, firstMatch);\n }\n\n return null;\n}\n",
|
|
86
|
+
"import type { IInstallContext, ToolConfig } from '@dotfiles/core';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport path from 'node:path';\nimport { createBinaryEntrypoint } from './createBinaryEntrypoint';\nimport { messages } from './log-messages';\nimport { normalizeBinaries } from './normalizeBinaries';\n\n/**\n * Setup binaries from direct download - handles all binaries in toolConfig.binaries[]\n */\nexport async function setupBinariesFromDirectDownload(\n fs: IFileSystem,\n toolName: string,\n toolConfig: ToolConfig,\n context: IInstallContext,\n downloadPath: string,\n parentLogger: TsLogger,\n): Promise<void> {\n const logger = parentLogger.getSubLogger({ name: 'setupBinariesFromDirectDownload' });\n const binaryConfigs = normalizeBinaries(toolConfig.binaries);\n const primaryBinary = binaryConfigs[0]?.name || toolName;\n\n await fs.chmod(downloadPath, 0o755);\n\n const binariesDir = path.join(context.projectConfig.paths.generatedDir, 'binaries');\n const downloadFileName = path.basename(downloadPath);\n\n // Extract subdirectory name from context.stagingDir\n // This will be either a version (e.g., \"1.0.0\") or timestamp (e.g., \"2025-11-04-20-53-47\")\n const subdirName = path.basename(context.stagingDir);\n\n await createBinaryEntrypoint(fs, toolName, primaryBinary, subdirName, downloadFileName, binariesDir, logger);\n\n if (binaryConfigs.length > 1) {\n logger.debug(messages.binarySetupService.directDownloadSingleBinary(binaryConfigs.length, primaryBinary));\n }\n}\n",
|
|
87
|
+
"import type { TsLogger } from '@dotfiles/logger';\nimport type { InstallResult } from '../types';\nimport { messages } from './log-messages';\n\n/**\n * Wraps installation operations with consistent error handling and logging.\n * Catches any errors thrown by the operation and converts them to failed InstallResult.\n *\n * Ensures all installation methods have uniform error handling:\n * - Catches exceptions from async operations\n * - Logs error with method and tool context\n * - Returns failed InstallResult with error message\n * - Preserves original error details when available\n *\n * @param methodName - Installation method name for logging (e.g., 'github-release', 'brew')\n * @param _toolName - Name of tool being installed (reserved for future use)\n * @param logger - Logger for error messages\n * @param operation - Async function that performs installation and returns InstallResult\n * @returns InstallResult from operation, or failure result if operation throws\n */\nexport async function withInstallErrorHandling<T extends InstallResult>(\n methodName: string,\n _toolName: string,\n logger: TsLogger,\n operation: () => Promise<T>,\n): Promise<T> {\n try {\n return await operation();\n } catch (error) {\n logger.error(messages.outcome.installFailed(methodName), error);\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n } as T;\n }\n}\n",
|
|
88
|
+
"import type {\n AsyncInstallHook,\n IAfterInstallContext,\n IInstallBaseContext,\n IInstallContext,\n InstallEvent,\n ToolConfig,\n} from '@dotfiles/core';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport type { InstallResult } from '../types';\nimport type { HookExecutor } from '../utils/HookExecutor';\nimport { messages } from '../utils/log-messages';\n\ntype UnknownRecord = Record<string, unknown>;\ntype InstallHooks = Record<string, AsyncInstallHook<IInstallBaseContext>[]>;\n\nfunction isUnknownRecord(value: unknown): value is UnknownRecord {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isAsyncInstallHookArray(value: unknown): value is AsyncInstallHook<IInstallBaseContext>[] {\n return Array.isArray(value) && value.every((item) => typeof item === 'function');\n}\n\nfunction isTsLogger(value: unknown): value is TsLogger {\n if (!isUnknownRecord(value)) {\n return false;\n }\n\n return typeof value['getSubLogger'] === 'function';\n}\n\nfunction getInstallHooksFromToolConfig(toolConfig: unknown): InstallHooks | undefined {\n if (!isUnknownRecord(toolConfig)) {\n return undefined;\n }\n\n const installParams: unknown = toolConfig['installParams'];\n if (!isUnknownRecord(installParams)) {\n return undefined;\n }\n\n const hooks: unknown = installParams['hooks'];\n if (!isUnknownRecord(hooks)) {\n return undefined;\n }\n\n const hookEntries: [string, unknown][] = Object.entries(hooks);\n if (hookEntries.length === 0) {\n return undefined;\n }\n\n const normalizedHooks: InstallHooks = {};\n for (const [hookName, maybeHookArray] of hookEntries) {\n if (!isAsyncInstallHookArray(maybeHookArray)) {\n return undefined;\n }\n\n normalizedHooks[hookName] = maybeHookArray;\n }\n\n return normalizedHooks;\n}\n\nexport class HookLifecycle {\n private readonly hookExecutor: HookExecutor;\n\n constructor(hookExecutor: HookExecutor) {\n this.hookExecutor = hookExecutor;\n }\n\n async handleInstallEvent(\n event: InstallEvent,\n currentToolConfig: ToolConfig | undefined,\n parentLogger: TsLogger,\n ): Promise<void> {\n if (!currentToolConfig) {\n return;\n }\n\n const hooks = getInstallHooksFromToolConfig(currentToolConfig);\n if (!hooks) {\n return;\n }\n\n const hookArray = hooks[event.type];\n if (!hookArray) {\n return;\n }\n\n const eventLoggerCandidate: unknown = event.context['logger'];\n const eventLogger: TsLogger = isTsLogger(eventLoggerCandidate) ? eventLoggerCandidate : parentLogger;\n\n const toolFs = event.context.fileSystem;\n const enhancedContext = this.hookExecutor.createEnhancedContext(event.context, toolFs, eventLogger);\n\n for (const hook of hookArray) {\n const result = await this.hookExecutor.executeHook(eventLogger, event.type, hook, enhancedContext);\n\n if (!result.success) {\n const errorMessage = result.error ? `${event.type} hook failed: ${result.error}` : `Hook ${event.type} failed`;\n throw new Error(errorMessage);\n }\n }\n }\n\n async executeBeforeInstallHook(\n resolvedToolConfig: ToolConfig,\n context: IInstallContext,\n toolFs: IFileSystem,\n parentLogger: TsLogger,\n ): Promise<InstallResult | null> {\n const logger = parentLogger.getSubLogger({ name: 'executeBeforeInstallHook' });\n const hooks = getInstallHooksFromToolConfig(resolvedToolConfig);\n const beforeInstallHooks = hooks?.['before-install'];\n\n if (!beforeInstallHooks) {\n return null;\n }\n\n logger.debug(messages.lifecycle.hookExecution('before-install'));\n const enhancedContext = this.hookExecutor.createEnhancedContext(context, toolFs, logger);\n\n for (const hook of beforeInstallHooks) {\n const result = await this.hookExecutor.executeHook(logger, 'before-install', hook, enhancedContext);\n\n if (!result.success) {\n const failureResult: InstallResult = {\n success: false,\n error: `beforeInstall hook failed: ${result.error}`,\n };\n return failureResult;\n }\n }\n\n return null;\n }\n\n async executeAfterInstallHook(\n resolvedToolConfig: ToolConfig,\n context: IAfterInstallContext,\n toolFs: IFileSystem,\n parentLogger: TsLogger,\n ): Promise<void> {\n const logger = parentLogger.getSubLogger({ name: 'executeAfterInstallHook' });\n const hooks = getInstallHooksFromToolConfig(resolvedToolConfig);\n const afterInstallHooks = hooks?.['after-install'];\n\n if (!afterInstallHooks) {\n return;\n }\n\n logger.debug(messages.lifecycle.hookExecution('after-install'));\n\n const enhancedContext = this.hookExecutor.createEnhancedContext(context, toolFs, logger);\n\n for (const hook of afterInstallHooks) {\n await this.hookExecutor.executeHook(logger, 'after-install', hook, enhancedContext, { continueOnError: true });\n }\n }\n}\n",
|
|
89
|
+
"import type { ProjectConfig } from '@dotfiles/config';\nimport type { IInstallContext, ToolConfig } from '@dotfiles/core';\nimport type { TsLogger } from '@dotfiles/logger';\nimport type { TrackedFileSystem } from '@dotfiles/registry/file';\nimport type { IToolInstallationRegistry } from '@dotfiles/registry/tool';\nimport type { ISymlinkGenerator } from '@dotfiles/symlink-generator';\nimport path from 'node:path';\nimport type { InstallResult } from '../types';\nimport { messages } from '../utils';\n\ntype UnknownRecord = Record<string, unknown>;\n\nfunction isUnknownRecord(value: unknown): value is UnknownRecord {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction getPluginMetadataRecord(result: InstallResult): UnknownRecord {\n const emptyMetadata: UnknownRecord = {};\n\n if (!result.success) {\n return emptyMetadata;\n }\n\n if (!('metadata' in result)) {\n return emptyMetadata;\n }\n\n const metadata: unknown = result.metadata;\n if (!isUnknownRecord(metadata)) {\n return emptyMetadata;\n }\n\n const { method, ...rest } = metadata;\n if (typeof method === 'string') {\n return { ...rest, installMethod: method };\n }\n\n return metadata;\n}\n\ninterface IInstallationStateWriterDependencies {\n projectConfig: ProjectConfig;\n toolInstallationRegistry: IToolInstallationRegistry;\n symlinkGenerator: ISymlinkGenerator;\n}\n\nexport class InstallationStateWriter {\n private readonly projectConfig: ProjectConfig;\n private readonly toolInstallationRegistry: IToolInstallationRegistry;\n private readonly symlinkGenerator: ISymlinkGenerator;\n\n constructor(dependencies: IInstallationStateWriterDependencies) {\n this.projectConfig = dependencies.projectConfig;\n this.toolInstallationRegistry = dependencies.toolInstallationRegistry;\n this.symlinkGenerator = dependencies.symlinkGenerator;\n }\n\n async recordInstallation(\n toolName: string,\n resolvedToolConfig: ToolConfig,\n installedDir: string,\n context: IInstallContext,\n result: InstallResult,\n parentLogger: TsLogger,\n ): Promise<void> {\n const logger = parentLogger.getSubLogger({ name: 'recordInstallation' });\n if (!result.success) {\n return;\n }\n\n try {\n const version: string = 'version' in result && result.version ? result.version : context.timestamp;\n\n const installParams: unknown = resolvedToolConfig.installParams;\n const configuredVersion: string | undefined = installParams &&\n typeof installParams === 'object' &&\n 'version' in installParams &&\n typeof installParams.version === 'string'\n ? installParams.version\n : undefined;\n\n const originalTag: string | undefined = 'originalTag' in result && typeof result.originalTag === 'string'\n ? result.originalTag\n : undefined;\n\n await this.toolInstallationRegistry.recordToolInstallation({\n toolName,\n version,\n installPath: installedDir,\n timestamp: context.timestamp,\n binaryPaths: result.binaryPaths,\n configuredVersion,\n originalTag,\n ...getPluginMetadataRecord(result),\n });\n logger.debug(messages.outcome.installSuccess(toolName, version, 'registry-recorded'));\n } catch (error) {\n logger.error(messages.outcome.installFailed('registry-record'), error);\n }\n }\n\n async createBinaryEntrypoints(\n toolName: string,\n binaryPaths: string[],\n fs: TrackedFileSystem,\n parentLogger: TsLogger,\n installedDir: string,\n isExternallyManaged: boolean,\n ): Promise<void> {\n const logger = parentLogger.getSubLogger({ name: 'createBinaryEntrypoints' });\n const toolDir = path.join(this.projectConfig.paths.binariesDir, toolName);\n\n await fs.ensureDir(toolDir);\n\n if (isExternallyManaged) {\n const externalDir = path.join(toolDir, 'external');\n await fs.ensureDir(externalDir);\n\n for (const binaryPath of binaryPaths) {\n const binaryName = path.basename(binaryPath);\n const symlinkPath = path.join(externalDir, binaryName);\n\n try {\n await this.symlinkGenerator.createBinarySymlink(logger, binaryPath, symlinkPath);\n } catch (error) {\n logger.error(messages.lifecycle.externalBinaryMissing(toolName, binaryName, binaryPath));\n throw error;\n }\n }\n\n return;\n }\n\n for (const binaryPath of binaryPaths) {\n const binaryName = path.basename(binaryPath);\n const entrypointPath = path.join(installedDir, binaryName);\n\n if (binaryPath === entrypointPath) {\n continue;\n }\n\n try {\n if (await fs.exists(entrypointPath)) {\n await fs.rm(entrypointPath, { force: true });\n }\n } catch (error) {\n logger.error(messages.binarySymlink.removeExistingFailed(entrypointPath), error);\n throw error;\n }\n\n try {\n await fs.copyFile(binaryPath, entrypointPath);\n\n const binaryStats = await fs.stat(binaryPath);\n const binaryMode: number = binaryStats.mode & 0o777;\n await fs.chmod(entrypointPath, binaryMode);\n } catch (error) {\n logger.error(messages.binarySymlink.creationFailed(entrypointPath, binaryPath), error);\n throw error;\n }\n }\n }\n\n async updateCurrentSymlink(\n toolName: string,\n fs: TrackedFileSystem,\n parentLogger: TsLogger,\n installedDir: string,\n isExternallyManaged: boolean,\n ): Promise<void> {\n const logger = parentLogger.getSubLogger({ name: 'updateCurrentSymlink' });\n const toolDir = path.join(this.projectConfig.paths.binariesDir, toolName);\n const currentSymlinkPath = path.join(toolDir, 'current');\n\n await fs.ensureDir(toolDir);\n\n const currentTarget: string = isExternallyManaged ? 'external' : path.basename(installedDir);\n\n try {\n if (await fs.exists(currentSymlinkPath)) {\n await fs.rm(currentSymlinkPath, { force: true, recursive: true });\n }\n } catch (error) {\n logger.error(messages.lifecycle.removingExistingSymlink(currentSymlinkPath), error);\n throw error;\n }\n\n try {\n const symlinkFs = fs.withFileType('symlink');\n await symlinkFs.symlink(currentTarget, currentSymlinkPath, 'dir');\n } catch (error) {\n logger.error(messages.lifecycle.creatingExternalSymlink(currentSymlinkPath, currentTarget), error);\n throw error;\n }\n\n try {\n const linkTarget = await fs.readlink(currentSymlinkPath);\n if (linkTarget !== currentTarget) {\n logger.error(messages.lifecycle.symlinkVerificationFailed(currentSymlinkPath));\n throw new Error(\n `Symlink verification failed: ${currentSymlinkPath} points to ${linkTarget}, expected ${currentTarget}`,\n );\n }\n } catch (error) {\n logger.error(messages.lifecycle.symlinkVerificationFailed(currentSymlinkPath), error);\n throw error;\n }\n }\n}\n",
|
|
90
|
+
"import { createShell, type IInstallContext, type IInstallOptions, type Shell } from '@dotfiles/core';\nimport { getBinaryPaths, withInstallErrorHandling } from '@dotfiles/installer';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { detectVersionViaCli, normalizeVersion } from '@dotfiles/utils';\nimport { z } from 'zod';\nimport { messages } from './log-messages';\nimport type { BrewToolConfig } from './schemas';\nimport type { BrewInstallResult, IBrewInstallMetadata } from './types';\n\nconst BrewInfoSchema = z.object({\n name: z.string(),\n versions: z.object({\n stable: z.string(),\n head: z.string().optional(),\n bottle: z.boolean().optional(),\n }),\n});\n\ntype BrewInfo = z.infer<typeof BrewInfoSchema>;\n\n/**\n * Installs a tool using Homebrew.\n *\n * This function handles the complete installation process for Homebrew tools:\n * 1. Taps custom repositories if specified\n * 2. Installs the formula or cask using `brew install`\n * 3. Retrieves version information via `brew info`\n * 4. Determines binary paths using the Homebrew prefix\n *\n * @param toolName - The name of the tool to install.\n * @param toolConfig - The configuration for the Homebrew tool.\n * @param context - The base installation context with project config and file system.\n * @param options - Optional installation options (supports --force flag).\n * @param parentLogger - The parent logger for creating sub-loggers.\n * @param shellExecutor - The shell executor function (defaults to Bun's $ operator).\n * @returns A promise that resolves to the installation result.\n */\nexport async function installFromBrew(\n toolName: string,\n toolConfig: BrewToolConfig,\n _context: IInstallContext,\n options: IInstallOptions | undefined,\n parentLogger: TsLogger,\n shellExecutor: Shell,\n installShell?: Shell,\n): Promise<BrewInstallResult> {\n const logger = parentLogger.getSubLogger({ name: 'installFromBrew' });\n logger.debug(messages.installing(toolName), toolConfig.installParams);\n\n if (!toolConfig.installParams) {\n return {\n success: false,\n error: 'Install parameters not specified',\n };\n }\n\n const params = toolConfig.installParams;\n const formula = params.formula || toolName;\n const isCask = params.cask || false;\n const tap = params.tap;\n\n const operation = async (): Promise<BrewInstallResult> => {\n const loggingShell = installShell ?? createShell({ logger, skipCommandLog: true });\n await executeBrewInstall(formula, isCask, tap, options?.force, logger, shellExecutor, loggingShell);\n\n const formulaPrefix: string = await getBrewPrefix(formula, logger, shellExecutor);\n const binaryPaths = getBinaryPaths(toolConfig.binaries, `${formulaPrefix}/bin`);\n\n let version: string | undefined;\n\n const mainBinaryPath = binaryPaths[0];\n if (params.versionArgs && params.versionRegex && mainBinaryPath) {\n version = await detectVersionViaCli({\n binaryPath: mainBinaryPath,\n args: params.versionArgs,\n regex: params.versionRegex,\n shellExecutor,\n });\n } else {\n version = await getBrewVersion(formula, logger, shellExecutor);\n }\n\n const metadata: IBrewInstallMetadata = {\n method: 'brew',\n formula,\n isCask,\n tap,\n };\n\n const result: BrewInstallResult = {\n success: true,\n binaryPaths,\n version: version || undefined,\n metadata,\n };\n\n return result;\n };\n\n return withInstallErrorHandling('brew', toolName, logger, operation);\n}\n\n/**\n * Retrieves the installed version of a Homebrew formula.\n *\n * @param formula - The name of the Homebrew formula.\n * @param logger - The logger instance for logging operations.\n * @param shell - The shell executor.\n * @returns A promise that resolves to the version string, or null if not found.\n */\nasync function getBrewVersion(formula: string, logger: TsLogger, shell: Shell): Promise<string | undefined> {\n try {\n logger.debug(messages.fetchingVersion(formula));\n const result = await shell`brew info --json ${formula}`.quiet().noThrow();\n const output: string = result.stdout.toString();\n const rawData = JSON.parse(output);\n const info: BrewInfo[] = z.array(BrewInfoSchema).parse(rawData);\n\n if (info.length > 0 && info[0]?.versions.stable) {\n const rawVersion: string = info[0].versions.stable;\n const version: string = normalizeVersion(rawVersion);\n logger.debug(messages.versionFetched(formula, version));\n return version;\n }\n\n logger.debug(messages.versionNotFound(formula));\n return undefined;\n } catch (error) {\n logger.debug(messages.versionFetchFailed(formula), error);\n return undefined;\n }\n}\n\n/**\n * Gets the Homebrew prefix (installation directory) for a formula.\n *\n * @param formula - The name of the Homebrew formula.\n * @param logger - The logger instance for logging operations.\n * @param shell - The shell executor.\n * @returns A promise that resolves to the prefix path.\n * @throws {Error} If the prefix cannot be determined.\n */\nasync function getBrewPrefix(formula: string, logger: TsLogger, shell: Shell): Promise<string> {\n try {\n const result = await shell`brew --prefix ${formula}`.quiet();\n const prefix: string = result.stdout.toString().trim();\n logger.debug(messages.prefixFetched(formula, prefix));\n return prefix;\n } catch (error) {\n logger.debug(messages.prefixFetchFailed(formula), error);\n // Fall back to /opt/homebrew/opt/{formula} on Apple Silicon\n // or /usr/local/opt/{formula} on Intel\n const brewPrefix = await shell`brew --prefix`.quiet();\n const fallbackPrefix: string = `${brewPrefix.stdout.toString().trim()}/opt/${formula}`;\n logger.debug(messages.prefixFallback(formula, fallbackPrefix));\n return fallbackPrefix;\n }\n}\n\n/**\n * Executes the Homebrew install command for a formula or cask.\n *\n * This function handles tapping custom repositories if needed, then runs the\n * appropriate `brew install` command with optional --force flag.\n *\n * @param formula - The name of the formula or cask to install.\n * @param isCask - Whether this is a cask installation.\n * @param tap - Optional tap repository or array of repositories to add.\n * @param force - Whether to force reinstallation.\n * @param logger - The logger instance for logging operations.\n * @param shell - The shell executor.\n * @returns A promise that resolves when installation is complete.\n * @throws {Error} If the installation fails.\n */\nasync function executeBrewInstall(\n formula: string,\n isCask: boolean,\n tap: string | string[] | undefined,\n force: boolean | undefined,\n logger: TsLogger,\n shell: Shell,\n installShell: Shell,\n): Promise<void> {\n if (tap) {\n const taps = Array.isArray(tap) ? tap : [tap];\n for (const t of taps) {\n logger.debug(messages.executingCommand(`brew tap ${t}`));\n await shell`brew tap ${t}`.quiet();\n }\n }\n\n const installArgs = ['install'];\n if (isCask) {\n installArgs.push('--cask');\n }\n if (force) {\n installArgs.push('--force');\n }\n installArgs.push(formula);\n\n logger.info(messages.executingCommand(`brew ${installArgs.join(' ')}`));\n await installShell`brew ${installArgs}`;\n}\n",
|
|
91
|
+
"import { createSafeLogMessage, type SafeLogMessageMap } from '@dotfiles/logger';\n\nexport const messages = {\n installing: (toolName: string) => createSafeLogMessage(`Installing from brew: toolName=${toolName}`),\n executingCommand: (command: string) => createSafeLogMessage(`installFromBrew: Executing command: ${command}`),\n fetchingVersion: (formula: string) => createSafeLogMessage(`Fetching version info for formula: ${formula}`),\n versionFetched: (formula: string, version: string) =>\n createSafeLogMessage(`Fetched version ${version} for formula ${formula}`),\n versionNotFound: (formula: string) => createSafeLogMessage(`No stable version found for formula ${formula}`),\n versionFetchFailed: (formula: string) => createSafeLogMessage(`Failed to fetch version for formula ${formula}`),\n prefixFetched: (formula: string, prefix: string) =>\n createSafeLogMessage(`Formula ${formula} installed at prefix: ${prefix}`),\n prefixFetchFailed: (formula: string) => createSafeLogMessage(`Failed to fetch prefix for formula ${formula}`),\n prefixFallback: (formula: string, prefix: string) =>\n createSafeLogMessage(`Using fallback prefix for formula ${formula}: ${prefix}`),\n} as const satisfies SafeLogMessageMap;\n",
|
|
92
|
+
"import type { BaseInstallParams } from '@dotfiles/core';\nimport { baseInstallParamsSchema } from '@dotfiles/core';\nimport { z } from 'zod';\n\nexport const brewInstallParamsSchema = baseInstallParamsSchema.extend({\n /**\n * The name of the Homebrew formula to install (e.g., `ripgrep`).\n * Either `formula` or `cask` (by setting `cask: true` and using `formula` for the cask name) should be specified.\n */\n formula: z.string().optional(),\n /**\n * If `true`, the `formula` property is treated as a Homebrew Cask name (e.g., `visual-studio-code`).\n * @default false\n */\n cask: z.boolean().optional(),\n /**\n * An optional Homebrew tap or an array of taps that need to be added (`brew tap <tap_name>`)\n * before the formula can be installed.\n * Example: `homebrew/core` or `['user/custom-tap', 'another/tap']`.\n */\n tap: z.union([z.string(), z.array(z.string())]).optional(),\n /** Arguments to pass to the binary to check the version (e.g. ['--version']). */\n versionArgs: z.array(z.string()).optional(),\n /** Regex to extract version from output. */\n versionRegex: z.string().optional(),\n});\n\n/**\n * Parameters for installing a tool using Homebrew (`brew`).\n * This method is typically used on macOS and Linux (via Linuxbrew).\n * It involves running `brew install` commands.\n *\n * NOTE: This is an explicit interface (not z.infer) to ensure TypeScript fully resolves\n * the property names, which is required for proper `keyof` behavior in declaration files.\n */\nexport interface BrewInstallParams extends BaseInstallParams {\n /** The name of the Homebrew formula to install (e.g., `ripgrep`). */\n formula?: string;\n /** If `true`, the `formula` property is treated as a Homebrew Cask name. */\n cask?: boolean;\n /** An optional Homebrew tap or an array of taps. */\n tap?: string | string[];\n /** Arguments to pass to the binary to check the version. */\n versionArgs?: string[];\n /** Regex to extract version from output. */\n versionRegex?: string;\n}\n",
|
|
93
|
+
"import {\n baseToolConfigWithPlatformsSchema,\n type InferToolConfigWithPlatforms,\n} from '@dotfiles/core';\nimport { z } from 'zod';\nimport { brewInstallParamsSchema } from './brewInstallParamsSchema';\n\nexport const brewToolConfigSchema = baseToolConfigWithPlatformsSchema.extend({\n /** Resolved tool configuration for the 'brew' installation method */\n installationMethod: z.literal('brew'),\n /** Homebrew installation parameters */\n installParams: brewInstallParamsSchema,\n});\n\n/** Resolved tool configuration for the 'brew' installation method. */\nexport type BrewToolConfig = InferToolConfigWithPlatforms<typeof brewToolConfigSchema>;\n",
|
|
94
|
+
"import type { IInstallContext, IInstallerPlugin, IInstallOptions, InstallResult, Shell } from '@dotfiles/core';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { installFromBrew } from './installFromBrew';\nimport { type BrewInstallParams, brewInstallParamsSchema, type BrewToolConfig, brewToolConfigSchema } from './schemas';\n\nconst PLUGIN_VERSION = '1.0.0';\n\ntype BrewPluginMetadata = {\n method: 'brew';\n formula: string;\n isCask: boolean;\n tap?: string | string[];\n};\n\n/**\n * Installer plugin for tools installed via Homebrew.\n *\n * This plugin handles installation of tools through the Homebrew package manager\n * on macOS and Linux. It supports both formulae (command-line tools) and casks\n * (macOS applications). The plugin can handle custom taps, version checking via\n * `brew info`, and respects the --force flag for reinstallations.\n *\n * Note: Tools installed via Homebrew are externally managed, meaning Homebrew\n * handles the actual file placement and versioning.\n */\nexport class BrewInstallerPlugin implements\n IInstallerPlugin<\n 'brew',\n BrewInstallParams,\n BrewToolConfig,\n BrewPluginMetadata\n >\n{\n readonly method = 'brew';\n readonly displayName = 'Homebrew Installer';\n readonly version = PLUGIN_VERSION;\n readonly paramsSchema = brewInstallParamsSchema;\n readonly toolConfigSchema = brewToolConfigSchema;\n\n /**\n * Creates a new BrewInstallerPlugin instance.\n *\n * @param shell - The shell executor for running commands.\n */\n constructor(private readonly shell: Shell) {}\n readonly externallyManaged = true;\n\n /**\n * Installs a tool using Homebrew.\n *\n * @param toolName - The name of the tool to install.\n * @param toolConfig - The configuration for the Homebrew tool.\n * @param context - The base installation context.\n * @param options - Optional installation options.\n * @param logger - The logger with tool context for logging operations.\n * @returns A promise that resolves to the installation result.\n */\n async install(\n toolName: string,\n toolConfig: BrewToolConfig,\n context: IInstallContext,\n options: IInstallOptions | undefined,\n logger: TsLogger,\n ): Promise<InstallResult<BrewPluginMetadata>> {\n const result = await installFromBrew(toolName, toolConfig, context, options, logger, this.shell);\n\n if (!result.success) {\n return {\n success: false,\n error: result.error,\n };\n }\n\n const installResult: InstallResult<BrewPluginMetadata> = {\n success: true,\n binaryPaths: result.binaryPaths,\n version: result.version,\n metadata: result.metadata,\n };\n\n return installResult;\n }\n\n /**\n * Indicates whether this plugin supports version update checking.\n *\n * @returns True, as Homebrew provides version information via `brew info`.\n */\n supportsUpdate(): boolean {\n return true;\n }\n\n supportsUpdateCheck(): boolean {\n return false;\n }\n\n /**\n\n * Indicates whether this plugin supports README fetching.\n *\n * @returns False, as Homebrew formulas don't have direct README URLs.\n */\n supportsReadme(): boolean {\n return false; // Brew formulas don't have direct README URLs\n }\n}\n",
|
|
95
|
+
"import type { ProjectConfig } from '@dotfiles/config';\nimport type { ICache } from '@dotfiles/downloader';\nimport { type IDownloader, NetworkError, NotFoundError } from '@dotfiles/downloader';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { parse } from 'smol-toml';\nimport { z } from 'zod';\nimport { CargoClientError } from './CargoClientError';\nimport type { ICargoClient } from './ICargoClient';\nimport { messages } from './log-messages';\n\n/**\n * Cargo crate metadata from crates.io API\n */\nexport interface ICrateMetadata {\n crate: {\n name: string;\n newest_version: string;\n repository?: string;\n };\n versions: Array<{\n num: string;\n bin_names?: string[];\n }>;\n}\n\n/**\n * Supported cache kinds for Cargo related requests.\n */\ntype CargoCacheKind = 'cratesIo' | 'githubRaw';\n\n/**\n * Cache options used by requests to determine which host level cache to use.\n */\ninterface ICargoCacheOptions {\n kind: CargoCacheKind;\n}\n\n/**\n * Zod schema for validating Cargo.toml package section\n */\nconst cargoPackageSchema = z.object({\n package: z.object({\n name: z.string(),\n version: z.string(),\n edition: z.string().optional(),\n description: z.string().optional(),\n authors: z.array(z.string()).optional(),\n license: z.string().optional(),\n repository: z.string().optional(),\n homepage: z.string().optional(),\n }),\n});\n\n/**\n * Parsed Cargo.toml package section - derived from Zod schema\n */\nexport type CargoTomlPackage = z.infer<typeof cargoPackageSchema>['package'];\n\n/**\n * Implements the ICargoClient interface for interacting with Cargo-related APIs.\n *\n * This client handles requests to crates.io API and Cargo.toml file parsing.\n * It delegates all caching to the underlying downloader's CachedDownloadStrategy.\n *\n * ### Caching Strategy\n * The client relies on the downloader's built-in CachedDownloadStrategy for all caching.\n * This provides consistent caching behavior across all HTTP requests in the application.\n *\n * ### Error Handling\n * It translates HTTP errors from the downloader into specific, custom error classes\n * like `NotFoundError` and `RateLimitError`. This allows consumers of the client\n * to handle API errors in a predictable manner.\n */\nexport class CargoClient implements ICargoClient {\n private readonly downloader: IDownloader;\n private readonly logger: TsLogger;\n private readonly cargoConfig: ProjectConfig['cargo'];\n private readonly cratesIoCache?: ICache;\n private readonly githubRawCache?: ICache;\n private readonly cratesIoCacheEnabled: boolean;\n private readonly cratesIoCacheTtl: number;\n private readonly githubRawCacheEnabled: boolean;\n private readonly githubRawCacheTtl: number;\n\n constructor(\n parentLogger: TsLogger,\n config: ProjectConfig,\n downloader: IDownloader,\n cratesIoCache?: ICache,\n githubRawCache?: ICache,\n ) {\n this.logger = parentLogger.getSubLogger({ name: 'CargoClient' });\n this.downloader = downloader;\n this.cratesIoCache = cratesIoCache;\n this.githubRawCache = githubRawCache;\n this.cargoConfig = config.cargo;\n\n // Host level cache controls (inherit defaults from schema)\n this.cratesIoCacheEnabled = this.cargoConfig.cratesIo.cache.enabled && Boolean(this.cratesIoCache);\n this.cratesIoCacheTtl = this.cargoConfig.cratesIo.cache.ttl;\n this.githubRawCacheEnabled = this.cargoConfig.githubRaw.cache.enabled && Boolean(this.githubRawCache);\n this.githubRawCacheTtl = this.cargoConfig.githubRaw.cache.ttl;\n\n const logger = this.logger.getSubLogger({ name: 'constructor' });\n logger.debug(messages.constructor.initialized('CargoClient', this.cargoConfig.userAgent));\n }\n\n private async request<T>(url: string, cacheOptions?: ICargoCacheOptions): Promise<T> {\n const logger = this.logger.getSubLogger({ name: 'request' });\n logger.debug(messages.request.makingRequest('GET', url));\n const headers = this.buildRequestHeaders();\n const { useCache, cacheKey, cacheTtl } = this.resolveCacheOptions(url, cacheOptions);\n const cached = await this.tryReadCache<T>(cacheKey, useCache);\n if (cached) {\n return cached;\n }\n\n const buffer = await this.performDownload(url, headers);\n const data = this.parseJson<T>(buffer, url);\n await this.tryStoreCache(cacheKey, data, cacheTtl, useCache);\n return data;\n }\n\n private resolveCacheOptions(\n url: string,\n cacheOptions?: ICargoCacheOptions,\n ): {\n useCache: boolean;\n cacheKey?: string;\n cacheTtl: number;\n } {\n if (!cacheOptions) {\n return { useCache: false, cacheTtl: 0 };\n }\n const kind = cacheOptions.kind;\n const useCache = kind === 'cratesIo' ? this.cratesIoCacheEnabled : this.githubRawCacheEnabled;\n const cacheTtl = kind === 'cratesIo' ? this.cratesIoCacheTtl : this.githubRawCacheTtl;\n const cacheKey = useCache ? `cargo:${kind}:${url}` : undefined;\n return { useCache, cacheKey, cacheTtl };\n }\n\n private async tryReadCache<T>(cacheKey: string | undefined, useCache: boolean): Promise<T | null> {\n const cacheImpl = cacheKey?.includes('cratesIo:') ? this.cratesIoCache : this.githubRawCache;\n if (!useCache || !cacheKey || !cacheImpl) {\n return null;\n }\n try {\n const cached = await cacheImpl.get<T>(cacheKey);\n if (cached) {\n return cached;\n }\n } catch {\n // cache errors are ignored\n }\n return null;\n }\n\n private async tryStoreCache<T>(cacheKey: string | undefined, data: T, ttl: number, useCache: boolean): Promise<void> {\n const cacheImpl = cacheKey?.includes('cratesIo:') ? this.cratesIoCache : this.githubRawCache;\n if (!useCache || !cacheKey || !cacheImpl) {\n return;\n }\n try {\n await cacheImpl.set(cacheKey, data, ttl);\n } catch {\n // cache errors are ignored\n }\n }\n\n private parseJson<T>(buffer: Buffer, url: string): T {\n const logger = this.logger.getSubLogger({ name: 'parseJson' });\n if (!buffer || buffer.length === 0) {\n logger.error(messages.errors.emptyResponse(url));\n throw new NetworkError(logger, 'Empty response received from API', url);\n }\n try {\n return JSON.parse(buffer.toString('utf-8')) as T;\n } catch (error) {\n if (error instanceof SyntaxError) {\n logger.error(messages.errors.jsonParseError(url), error);\n throw new CargoClientError(`Invalid JSON response from ${url}: ${error.message}`, undefined, error);\n }\n throw error;\n }\n }\n\n private async performDownload(url: string, headers: Record<string, string>): Promise<Buffer> {\n const logger = this.logger.getSubLogger({ name: 'performDownload' });\n const result = await this.downloader.download(logger, url, { headers });\n if (!result) {\n logger.error(messages.errors.emptyResponse(url));\n throw new NetworkError(logger, 'Empty response received from API', url);\n }\n return result;\n }\n\n private buildRequestHeaders(): Record<string, string> {\n return {\n Accept: 'application/json',\n 'User-Agent': this.cargoConfig.userAgent,\n };\n }\n\n async getCrateMetadata(crateName: string): Promise<ICrateMetadata | null> {\n const logger = this.logger.getSubLogger({ name: 'getCrateMetadata' });\n logger.debug(messages.cratesIo.querying(crateName));\n const url = `${this.cargoConfig.cratesIo.host}/api/v1/crates/${crateName}`;\n try {\n return await this.request<ICrateMetadata>(url, { kind: 'cratesIo' });\n } catch (error) {\n if (error instanceof NotFoundError) {\n logger.error(messages.cratesIo.notFound(crateName));\n return null;\n }\n logger.error(messages.cratesIo.metadataError(crateName), error);\n throw error;\n }\n }\n\n /**\n * Builds a GitHub raw URL for a Cargo.toml file\n */\n buildCargoTomlUrl(githubRepo: string, branch = 'main'): string {\n return `${this.cargoConfig.githubRaw.host}/${githubRepo}/${branch}/Cargo.toml`;\n }\n\n async getCargoTomlPackage(url: string): Promise<CargoTomlPackage | null> {\n const logger = this.logger.getSubLogger({ name: 'getCargoTomlPackage' });\n logger.debug(messages.parsing.parsingCrateMetadata(url));\n const { useCache, cacheKey } = this.resolveCacheOptions(url, { kind: 'githubRaw' });\n if (useCache) {\n const cached = await this.tryReadCache<CargoTomlPackage>(cacheKey, useCache);\n if (cached) {\n return cached;\n }\n }\n\n try {\n const responseBuffer = await this.downloader.download(logger, url, { headers: this.buildRequestHeaders() });\n if (!responseBuffer || responseBuffer.length === 0) {\n logger.error(messages.errors.emptyResponse(url));\n return null;\n }\n const pkg = this.parseCargoToml(responseBuffer);\n await this.tryStoreCache(cacheKey, pkg, this.githubRawCacheTtl, useCache);\n return pkg;\n } catch (error) {\n if (error instanceof CargoClientError) {\n throw error;\n }\n if (error instanceof NotFoundError) {\n return null;\n }\n // Let other downloader errors (NetworkError, etc.) bubble up\n logger.error(messages.parsing.cargoTomlParseError(url), error);\n throw error;\n }\n }\n\n private parseCargoToml(buffer: Buffer): CargoTomlPackage {\n const logger = this.logger.getSubLogger({ name: 'parseCargoToml' });\n const cargoToml = buffer.toString('utf-8');\n const parsed = parse(cargoToml);\n const validationResult = cargoPackageSchema.safeParse(parsed);\n if (!validationResult.success) {\n logger.zodErrors(validationResult.error);\n throw new CargoClientError('Could not parse version from Cargo.toml [package] section', undefined);\n }\n return validationResult.data.package;\n }\n\n async getLatestVersion(crateName: string): Promise<string | null> {\n const metadata = await this.getCrateMetadata(crateName);\n return metadata?.crate.newest_version || null;\n }\n}\n",
|
|
96
|
+
"/**\n * Custom error class for Cargo API client errors.\n * Provides structured error information for better error handling.\n */\nexport class CargoClientError extends Error {\n public readonly statusCode?: number;\n public override readonly cause?: Error;\n\n constructor(message: string, statusCode?: number, cause?: Error) {\n super(message);\n this.name = 'CargoClientError';\n this.statusCode = statusCode;\n this.cause = cause;\n\n // Maintain proper stack trace for where our error was thrown (only available on V8)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, CargoClientError);\n }\n }\n}\n",
|
|
97
|
+
"import { createSafeLogMessage, type SafeLogMessageMap } from '@dotfiles/logger';\n\nexport const messages = {\n constructor: {\n initialized: (clientName: string, userAgent: string) =>\n createSafeLogMessage(`Initializing ${clientName} with user agent: ${userAgent}`),\n } satisfies SafeLogMessageMap,\n request: {\n makingRequest: (method: string, url: string) => createSafeLogMessage(`Making ${method} request to ${url}`),\n } satisfies SafeLogMessageMap,\n errors: {\n emptyResponse: (url: string) => createSafeLogMessage(`Empty response received from ${url}`),\n jsonParseError: (url: string) => createSafeLogMessage(`JSON parse error for ${url}`),\n } satisfies SafeLogMessageMap,\n cratesIo: {\n querying: (crateName: string) => createSafeLogMessage(`Querying crates.io for crate ${crateName}`),\n notFound: (crateName: string) => createSafeLogMessage(`Crate not found: ${crateName}`),\n metadataError: (crateName: string) => createSafeLogMessage(`Error fetching crate metadata for ${crateName}`),\n } satisfies SafeLogMessageMap,\n parsing: {\n parsingCrateMetadata: (sourceUrl: string) => createSafeLogMessage(`Parsing Cargo.toml from ${sourceUrl}`),\n cargoTomlParseError: (sourceUrl: string) => createSafeLogMessage(`Error parsing Cargo.toml from ${sourceUrl}`),\n } satisfies SafeLogMessageMap,\n} as const;\n",
|
|
98
|
+
"import type { IArchiveExtractor } from '@dotfiles/archive-extractor';\nimport type { IExtractResult, IInstallContext, IInstallOptions } from '@dotfiles/core';\nimport { Architecture, Platform } from '@dotfiles/core';\nimport type { IDownloader } from '@dotfiles/downloader';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { HookExecutor } from '@dotfiles/installer';\nimport {\n createToolFileSystem,\n downloadWithProgress,\n getBinaryPaths,\n setupBinariesFromArchive,\n withInstallErrorHandling,\n} from '@dotfiles/installer';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { normalizeVersion } from '@dotfiles/utils';\nimport path from 'node:path';\nimport type { ICargoClient } from './cargo-client';\nimport { messages } from './log-messages';\nimport type { CargoInstallParams, CargoToolConfig } from './schemas';\nimport type {\n CargoInstallResult,\n HookExecutionResult,\n ICargoHookContext,\n ICargoInstallMetadata,\n IVersionResult,\n} from './types';\n\nexport async function installFromCargo(\n toolName: string,\n toolConfig: CargoToolConfig,\n context: IInstallContext,\n options: IInstallOptions | undefined,\n fs: IFileSystem,\n downloader: IDownloader,\n cargoClient: ICargoClient,\n archiveExtractor: IArchiveExtractor,\n hookExecutor: HookExecutor,\n parentLogger: TsLogger,\n githubHost: string,\n): Promise<CargoInstallResult> {\n const logger = parentLogger.getSubLogger({ name: 'installFromCargo' });\n logger.debug(messages.installing(toolName));\n\n if (!toolConfig['installParams']) {\n return {\n success: false,\n error: 'Install parameters not specified',\n };\n }\n\n const params = toolConfig['installParams'];\n const crateName = params.crateName || toolName;\n\n const operation = async (): Promise<CargoInstallResult> => {\n const toolFs = createToolFileSystem(fs, toolName);\n\n const versionResult = await determineVersion(crateName, params, cargoClient, logger);\n logger.debug(messages.foundVersion(crateName, versionResult.version));\n\n const downloadUrl = await buildDownloadUrl(crateName, versionResult.version, params, context, githubHost);\n logger.debug(messages.downloadingAsset(`${crateName}-${versionResult.version}`, downloadUrl));\n\n const filename = `${crateName}-${versionResult.version}.tar.gz`;\n const downloadPath = path.join(context.stagingDir, filename);\n\n await downloadWithProgress(logger, downloadUrl, downloadPath, filename, downloader, options);\n\n const hookContext: ICargoHookContext = { ...context, version: versionResult.version };\n const afterDownloadResult = await executeAfterDownloadHook(\n toolConfig,\n hookExecutor,\n hookContext,\n downloadPath,\n toolFs,\n logger,\n );\n if (!afterDownloadResult.success) {\n return afterDownloadResult;\n }\n\n const extractResult = await archiveExtractor.extract(logger, downloadPath, {\n targetDir: context.stagingDir,\n });\n logger.debug(messages.archiveExtracted(), extractResult);\n\n await setupBinariesFromArchive(fs, toolName, toolConfig, context, context.stagingDir, logger);\n\n const afterInstallResult = await executeAfterInstallHook(\n toolConfig,\n hookExecutor,\n hookContext,\n extractResult,\n toolFs,\n logger,\n );\n if (!afterInstallResult.success) {\n return afterInstallResult;\n }\n\n if (await fs.exists(downloadPath)) {\n await fs.rm(downloadPath);\n logger.debug(messages.cleaningArchive(downloadPath));\n }\n\n const binaryPaths = getBinaryPaths(toolConfig.binaries, context.stagingDir);\n\n const metadata: ICargoInstallMetadata = {\n method: 'cargo',\n crateName,\n binarySource: params.binarySource || 'cargo-quickinstall',\n downloadUrl,\n };\n\n return {\n success: true,\n binaryPaths,\n version: versionResult.version,\n originalTag: versionResult.originalTag,\n metadata,\n };\n };\n\n return withInstallErrorHandling('cargo', toolName, logger, operation);\n}\n\nasync function executeAfterDownloadHook(\n toolConfig: CargoToolConfig,\n hookExecutor: HookExecutor,\n hookContext: ICargoHookContext,\n downloadPath: string,\n toolFs: IFileSystem,\n logger: TsLogger,\n): Promise<HookExecutionResult> {\n const afterDownloadHooks = toolConfig['installParams']?.hooks?.['after-download'];\n if (!afterDownloadHooks) {\n return { success: true };\n }\n\n const enhancedContext = hookExecutor.createEnhancedContext({ ...hookContext, downloadPath }, toolFs);\n\n for (const hook of afterDownloadHooks) {\n const hookResult = await hookExecutor.executeHook(logger, 'afterDownload', hook, enhancedContext);\n if (!hookResult.success) {\n return { success: false, error: hookResult.error };\n }\n }\n\n return { success: true };\n}\n\nasync function executeAfterInstallHook(\n toolConfig: CargoToolConfig,\n hookExecutor: HookExecutor,\n hookContext: ICargoHookContext,\n extractResult: IExtractResult,\n toolFs: IFileSystem,\n logger: TsLogger,\n): Promise<HookExecutionResult> {\n const afterInstallHooks = toolConfig['installParams']?.hooks?.['after-install'];\n if (!afterInstallHooks) {\n return { success: true };\n }\n\n const enhancedContext = hookExecutor.createEnhancedContext({ ...hookContext, extractResult }, toolFs);\n\n for (const hook of afterInstallHooks) {\n const finalHookResult = await hookExecutor.executeHook(logger, 'afterInstall', hook, enhancedContext);\n if (!finalHookResult.success) {\n return { success: false, error: finalHookResult.error };\n }\n }\n return { success: true };\n}\n\nasync function determineVersion(\n crateName: string,\n params: CargoInstallParams,\n cargoClient: ICargoClient,\n logger: TsLogger,\n): Promise<IVersionResult> {\n const versionSource = params.versionSource || 'cargo-toml';\n\n switch (versionSource) {\n case 'cargo-toml': {\n const cargoTomlUrl = params.cargoTomlUrl ||\n cargoClient.buildCargoTomlUrl(params.githubRepo || `${crateName}-community/${crateName}`);\n\n logger.debug(messages.parsingMetadata(cargoTomlUrl));\n\n const packageInfo = await cargoClient.getCargoTomlPackage(cargoTomlUrl);\n if (!packageInfo) {\n throw new Error(`Failed to fetch or parse Cargo.toml from ${cargoTomlUrl}`);\n }\n return { version: normalizeVersion(packageInfo.version) };\n }\n case 'crates-io': {\n logger.debug(messages.queryingCratesIo(crateName));\n\n const version = await cargoClient.getLatestVersion(crateName);\n if (!version) {\n throw new Error(`Failed to get latest version for crate ${crateName} from crates.io`);\n }\n return { version: normalizeVersion(version) };\n }\n case 'github-releases': {\n if (!params.githubRepo) {\n throw new Error('githubRepo is required when using github-releases version source');\n }\n return getVersionFromGitHubReleases(params.githubRepo, logger);\n }\n default:\n throw new Error(`Unknown version source: ${versionSource}`);\n }\n}\n\nasync function getVersionFromGitHubReleases(githubRepo: string, logger: TsLogger): Promise<IVersionResult> {\n logger.debug(messages.queryingGitHubReleases(githubRepo));\n throw new Error('GitHub releases version source not yet implemented');\n}\n\nasync function buildDownloadUrl(\n crateName: string,\n version: string,\n params: CargoInstallParams,\n context: IInstallContext,\n githubReleaseHost: string,\n): Promise<string> {\n const binarySource = params.binarySource || 'cargo-quickinstall';\n const platform = getPlatformString(context.systemInfo.platform);\n const arch = getArchString(context.systemInfo.arch);\n\n switch (binarySource) {\n case 'cargo-quickinstall': {\n const url =\n `${githubReleaseHost}/cargo-bins/cargo-quickinstall/releases/download/${crateName}-${version}/${crateName}-${version}-${arch}-${platform}.tar.gz`;\n return url;\n }\n\n case 'github-releases': {\n if (!params.githubRepo) {\n throw new Error('githubRepo is required when using github-releases binary source');\n }\n const assetPattern = params.assetPattern || '{crateName}-{version}-{platform}-{arch}.tar.gz';\n const assetName = assetPattern\n .replace('{crateName}', crateName)\n .replace('{version}', version)\n .replace('{platform}', platform)\n .replace('{arch}', arch);\n\n const url = `${githubReleaseHost}/${params.githubRepo}/releases/download/v${version}/${assetName}`;\n return url;\n }\n\n default:\n throw new Error(`Unknown binary source: ${binarySource}`);\n }\n}\n\nfunction getPlatformString(platform: Platform): string {\n switch (platform) {\n case Platform.MacOS:\n return 'apple-darwin';\n case Platform.Linux:\n return 'unknown-linux-gnu';\n case Platform.Windows:\n return 'pc-windows-msvc';\n default:\n return 'unknown';\n }\n}\n\nfunction getArchString(arch: Architecture): string {\n switch (arch) {\n case Architecture.Arm64:\n return 'aarch64';\n case Architecture.X86_64:\n return 'x86_64';\n default:\n return 'unknown';\n }\n}\n",
|
|
99
|
+
"import { createSafeLogMessage, type SafeLogMessageMap } from '@dotfiles/logger';\n\nexport const messages = {\n installing: (toolName: string) => createSafeLogMessage(`Installing from cargo: ${toolName}`),\n foundVersion: (crateName: string, version: string) =>\n createSafeLogMessage(`Found crate ${crateName} version ${version}`),\n downloadingAsset: (assetName: string, url: string) =>\n createSafeLogMessage(`Downloading asset ${assetName} from ${url}`),\n archiveExtracted: () => createSafeLogMessage('Archive extracted: %o'),\n cleaningArchive: (archivePath: string) => createSafeLogMessage(`Cleaning up downloaded archive: ${archivePath}`),\n parsingMetadata: (cargoTomlUrl: string) => createSafeLogMessage(`Parsing crate metadata from: ${cargoTomlUrl}`),\n queryingCratesIo: (crateName: string) => createSafeLogMessage(`Querying crates.io API for crate: ${crateName}`),\n queryingGitHubReleases: (repo: string) => createSafeLogMessage(`Querying GitHub releases for ${repo}`),\n versionResolutionResolved: (toolName: string, version: string) =>\n createSafeLogMessage(`Resolved version for ${toolName}: ${version}`),\n versionResolutionFailed: (toolName: string, error: string) =>\n createSafeLogMessage(`Failed to resolve version for ${toolName}: ${error}`),\n versionResolutionException: (toolName: string, error: unknown) =>\n createSafeLogMessage(`Exception while resolving version for ${toolName}: ${String(error)}`),\n updateCheckFailed: (toolName: string) => createSafeLogMessage(`Failed to check update for cargo tool: ${toolName}`),\n} as const satisfies SafeLogMessageMap;\n",
|
|
100
|
+
"import { baseInstallParamsSchema } from '@dotfiles/core';\nimport { z } from 'zod';\n\n/**\n * Zod schema for Cargo installation parameters\n */\nexport const cargoInstallParamsSchema = baseInstallParamsSchema.extend({\n /**\n * The crate name\n */\n crateName: z.string(),\n\n /**\n * Source for binaries - either cargo-quickinstall or GitHub releases\n */\n binarySource: z.enum(['cargo-quickinstall', 'github-releases']).optional(),\n\n /**\n * GitHub repository for the crate (required for github-releases source)\n * Format: \"owner/repo\"\n */\n githubRepo: z.string().optional(),\n\n /**\n * Asset pattern for GitHub releases\n * Supports placeholders: {version}, {platform}, {arch}, {crateName}\n */\n assetPattern: z.string().optional(),\n\n /**\n * Version source - where to get the version information\n */\n versionSource: z.enum(['cargo-toml', 'crates-io', 'github-releases']).optional(),\n\n /**\n * Custom Cargo.toml URL if different from standard GitHub location\n */\n cargoTomlUrl: z.string().optional(),\n});\n",
|
|
101
|
+
"import type { BaseInstallParams } from '@dotfiles/core';\nimport { baseToolConfigWithPlatformsSchema, type InferToolConfigWithPlatforms } from '@dotfiles/core';\nimport { z } from 'zod';\nimport { cargoInstallParamsSchema } from './cargoInstallParamsSchema';\n\n/**\n * Zod schema for Cargo tool configuration\n */\nexport const cargoToolConfigSchema = baseToolConfigWithPlatformsSchema.extend({\n installationMethod: z.literal('cargo'),\n installParams: cargoInstallParamsSchema,\n});\n\n/**\n * Installation parameters for Cargo-based tools using pre-compiled binaries.\n *\n * NOTE: This is an explicit interface (not z.infer) to ensure TypeScript fully resolves\n * the property names, which is required for proper `keyof` behavior in declaration files.\n */\nexport interface CargoInstallParams extends BaseInstallParams {\n /** The crate name */\n crateName: string;\n /** Source for binaries - either cargo-quickinstall or GitHub releases */\n binarySource?: 'cargo-quickinstall' | 'github-releases';\n /** GitHub repository for the crate (required for github-releases source). Format: \"owner/repo\" */\n githubRepo?: string;\n /** Asset pattern for GitHub releases. Supports placeholders: {version}, {platform}, {arch}, {crateName} */\n assetPattern?: string;\n /** Version source - where to get the version information */\n versionSource?: 'cargo-toml' | 'crates-io' | 'github-releases';\n /** Custom Cargo.toml URL if different from standard GitHub location */\n cargoTomlUrl?: string;\n}\n\n/**\n * Tool configuration for Cargo-based installations\n */\nexport type CargoToolConfig = InferToolConfigWithPlatforms<typeof cargoToolConfigSchema>;\n",
|
|
102
|
+
"import type { IArchiveExtractor } from '@dotfiles/archive-extractor';\nimport type {\n IInstallContext,\n IInstallerPlugin,\n IInstallOptions,\n InstallResult,\n UpdateCheckResult,\n} from '@dotfiles/core';\nimport type { IDownloader } from '@dotfiles/downloader';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { HookExecutor } from '@dotfiles/installer';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { stripVersionPrefix } from '@dotfiles/utils';\nimport type { ICargoClient } from './cargo-client';\nimport { installFromCargo } from './installFromCargo';\nimport { messages } from './log-messages';\nimport {\n type CargoInstallParams,\n cargoInstallParamsSchema,\n type CargoToolConfig,\n cargoToolConfigSchema,\n} from './schemas';\n\nconst PLUGIN_VERSION = '1.0.0';\n\ntype CargoPluginMetadata = {\n method: 'cargo';\n crateName: string;\n binarySource: string;\n downloadUrl?: string;\n};\n\n/**\n * Installer plugin for Rust tools distributed via Cargo/crates.io.\n *\n * This plugin handles tools that are published as Rust crates and can be installed\n * using Cargo, Rust's package manager. It supports multiple installation methods:\n *\n * **Installation Sources:**\n * - **crates.io**: Official Rust package registry (default)\n * - **GitHub repository**: Install from a Git repository URL\n * - **Local path**: Install from a local directory with a Cargo.toml\n *\n * **Binary Acquisition:**\n * - Compiles from source using `cargo install`\n * - Downloads pre-built binaries from GitHub releases (when available)\n * - Extracts binaries from Cargo artifacts\n *\n * The plugin uses a Cargo client to check versions, download crates, and compile\n * binaries. It supports lifecycle hooks and can detect whether a tool needs updating\n * by comparing installed versions with the latest available on crates.io or GitHub.\n *\n * **Version Handling:**\n * - Supports semantic versioning (e.g., \"^1.0.0\", \"~2.1.0\")\n * - Can install specific versions or \"latest\"\n * - Tracks installed versions for update detection\n */\nexport class CargoInstallerPlugin implements\n IInstallerPlugin<\n 'cargo',\n CargoInstallParams,\n CargoToolConfig,\n CargoPluginMetadata\n >\n{\n readonly method = 'cargo';\n readonly displayName = 'Cargo Installer';\n readonly version = PLUGIN_VERSION;\n readonly paramsSchema = cargoInstallParamsSchema;\n readonly toolConfigSchema = cargoToolConfigSchema;\n\n /**\n * Creates a new CargoInstallerPlugin instance.\n *\n * @param fs - The file system interface for file operations.\n * @param downloader - The downloader for fetching crates and binaries.\n * @param cargoClient - The Cargo client for interacting with crates.io and cargo commands.\n * @param archiveExtractor - The archive extractor for unpacking downloaded files.\n * @param hookExecutor - The hook executor for running lifecycle hooks.\n * @param githubHost - The GitHub hostname for API requests (e.g., 'api.github.com').\n */\n constructor(\n private readonly fs: IFileSystem,\n private readonly downloader: IDownloader,\n private readonly cargoClient: ICargoClient,\n private readonly archiveExtractor: IArchiveExtractor,\n private readonly hookExecutor: HookExecutor,\n private readonly githubHost: string,\n ) {}\n\n async install(\n toolName: string,\n toolConfig: CargoToolConfig,\n context: IInstallContext,\n options: IInstallOptions | undefined,\n logger: TsLogger,\n ): Promise<InstallResult<CargoPluginMetadata>> {\n const result = await installFromCargo(\n toolName,\n toolConfig,\n context,\n options,\n this.fs,\n this.downloader,\n this.cargoClient,\n this.archiveExtractor,\n this.hookExecutor,\n logger,\n this.githubHost,\n );\n\n if (!result.success) {\n return {\n success: false,\n error: result.error,\n };\n }\n\n const installResult: InstallResult<CargoPluginMetadata> = {\n success: true,\n binaryPaths: result.binaryPaths,\n version: result.version,\n metadata: result.metadata,\n };\n\n return installResult;\n }\n\n /**\n * Resolves the version of a Cargo crate before installation.\n *\n * This method queries crates.io for the latest version of the specified crate\n * and returns a normalized version string that can be used for the installation\n * directory name.\n *\n * @param toolName - Name of the tool (for logging purposes)\n * @param toolConfig - Complete tool configuration including crate name and version\n * @param _context - Installation context (not used but required by interface)\n * @param logger - Logger instance for debug output\n * @returns Normalized version string, or null if version cannot be resolved\n */\n async resolveVersion(\n toolName: string,\n toolConfig: CargoToolConfig,\n _context: IInstallContext,\n logger: TsLogger,\n ): Promise<string | null> {\n const subLogger: TsLogger = logger.getSubLogger({ name: 'resolveVersion' });\n\n try {\n const cargoParams = toolConfig.installParams;\n const crateName: string | undefined = cargoParams?.crateName;\n\n if (!crateName) {\n subLogger.debug(messages.versionResolutionFailed(toolName, 'Missing crateName in install params'));\n return null;\n }\n\n // Query crates.io for the latest version\n const latestVersion: string | null = await this.cargoClient.getLatestVersion(crateName);\n\n if (!latestVersion) {\n subLogger.debug(messages.versionResolutionFailed(toolName, `Could not fetch version for crate: ${crateName}`));\n return null;\n }\n\n // Strip v prefix and return the version\n const normalizedVersion: string = stripVersionPrefix(latestVersion);\n subLogger.debug(messages.versionResolutionResolved(toolName, normalizedVersion));\n return normalizedVersion;\n } catch (error) {\n subLogger.debug(messages.versionResolutionException(toolName, error));\n return null;\n }\n }\n\n supportsUpdate(): boolean {\n return true;\n }\n\n supportsUpdateCheck(): boolean {\n return true;\n }\n\n async checkUpdate(\n toolName: string,\n toolConfig: CargoToolConfig,\n _context: IInstallContext,\n logger: TsLogger,\n ): Promise<UpdateCheckResult> {\n try {\n const cargoParams = toolConfig.installParams;\n const crateName = cargoParams?.crateName;\n\n if (!crateName) {\n const result: UpdateCheckResult = {\n success: false,\n error: 'Missing crateName in install params',\n };\n return result;\n }\n\n const latestVersion = await this.cargoClient.getLatestVersion(crateName);\n if (!latestVersion) {\n const result: UpdateCheckResult = {\n success: false,\n error: `Could not fetch latest version for crate: ${crateName}`,\n };\n return result;\n }\n\n const configuredVersion = toolConfig.version || 'latest';\n\n if (configuredVersion === 'latest') {\n const result: UpdateCheckResult = {\n success: true,\n hasUpdate: false,\n currentVersion: latestVersion,\n latestVersion,\n };\n return result;\n }\n\n const result: UpdateCheckResult = {\n success: true,\n hasUpdate: configuredVersion !== latestVersion,\n currentVersion: configuredVersion,\n latestVersion,\n };\n return result;\n } catch (error) {\n logger.error(messages.updateCheckFailed(toolName), error);\n const result: UpdateCheckResult = {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n };\n return result;\n }\n }\n\n supportsReadme(): boolean {\n return false; // Could be implemented to fetch from crates.io\n }\n}\n",
|
|
103
|
+
"import { createShell, type IInstallContext, type Shell } from '@dotfiles/core';\nimport type { IDownloader } from '@dotfiles/downloader';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { HookExecutor, IInstallOptions } from '@dotfiles/installer';\nimport {\n createToolFileSystem,\n downloadWithProgress,\n executeAfterDownloadHook,\n getBinaryNames,\n getBinaryPaths,\n withInstallErrorHandling,\n} from '@dotfiles/installer';\nimport { createSafeLogMessage, type TsLogger } from '@dotfiles/logger';\nimport { resolveValue } from '@dotfiles/unwrap-value';\nimport { detectVersionViaCli } from '@dotfiles/utils';\nimport path from 'node:path';\nimport { messages } from './log-messages';\nimport type { CurlScriptToolConfig } from './schemas';\nimport type {\n CurlScriptInstallResult,\n ICurlScriptArgsContext,\n ICurlScriptInstallMetadata,\n} from './types';\n\n/**\n * Handles binary installation by copying from system directories to versioned directory\n */\nasync function handleBinaryInstallation(\n toolConfig: CurlScriptToolConfig,\n context: IInstallContext,\n fs: IFileSystem,\n logger: TsLogger,\n): Promise<void> {\n const binaryNames = getBinaryNames(toolConfig.binaries);\n\n for (const binaryName of binaryNames) {\n const finalBinaryPath = path.join(context.stagingDir, binaryName);\n\n if (await fs.exists(finalBinaryPath)) {\n logger.debug(messages.binaryFoundInInstallDir(finalBinaryPath));\n continue;\n }\n\n const searchDirs = ['/usr/local/bin', path.join(context.systemInfo.homeDir, '.local', 'bin'), '/usr/bin'];\n let found = false;\n\n for (const dir of searchDirs) {\n const sourcePath = path.join(dir, binaryName);\n if (await fs.exists(sourcePath)) {\n logger.debug(messages.movingBinary(sourcePath, finalBinaryPath));\n await fs.copyFile(sourcePath, finalBinaryPath);\n await fs.chmod(finalBinaryPath, 0o755);\n found = true;\n break;\n }\n }\n\n if (!found) {\n logger.warn(messages.binaryNotFound(binaryName, `${context.stagingDir}, ${searchDirs.join(', ')}`));\n }\n }\n}\n\n/**\n * Installs a tool using a curl script.\n *\n * This function downloads an installation script from the specified URL, makes it\n * executable, and runs it using the configured shell. The script is responsible for\n * installing the tool to the system. After execution, the function attempts to locate\n * the installed binaries and create the necessary symlinks.\n *\n * @param toolName - The name of the tool to install.\n * @param toolConfig - The configuration for the curl-script tool.\n * @param context - The base installation context.\n * @param options - Optional installation options.\n * @param fs - The file system interface for file operations.\n * @param downloader - The downloader for fetching the installation script.\n * @param hookExecutor - The hook executor for running post-download hooks.\n * @param parentLogger - The parent logger for creating sub-loggers.\n * @returns A promise that resolves to the installation result.\n */\nexport async function installFromCurlScript(\n toolName: string,\n toolConfig: CurlScriptToolConfig,\n context: IInstallContext,\n options: IInstallOptions | undefined,\n fs: IFileSystem,\n downloader: IDownloader,\n hookExecutor: HookExecutor,\n parentLogger: TsLogger,\n shellExecutor: Shell,\n installShell?: Shell,\n): Promise<CurlScriptInstallResult> {\n const toolFs = createToolFileSystem(fs, toolName);\n const logger = parentLogger.getSubLogger({ name: 'installFromCurlScript' });\n logger.debug(messages.installing(toolName));\n\n if (!toolConfig.installParams || !('url' in toolConfig.installParams) || !('shell' in toolConfig.installParams)) {\n return {\n success: false,\n error: 'URL or shell not specified in installParams',\n };\n }\n\n const params = toolConfig.installParams;\n const url = params.url;\n const shell = params.shell;\n\n const operation = async (): Promise<CurlScriptInstallResult> => {\n // Download the script\n logger.debug(messages.downloadingScript(url));\n const scriptPath = path.join(context.stagingDir, `${toolName}-install.sh`);\n\n await downloadWithProgress(logger, url, scriptPath, `${toolName}-install.sh`, downloader, options);\n\n // Make the script executable\n await toolFs.chmod(scriptPath, 0o755);\n\n // Run afterDownload hook if defined\n const postDownloadContext = {\n ...context,\n downloadPath: scriptPath,\n };\n\n const afterDownloadResult = await executeAfterDownloadHook(\n toolConfig,\n postDownloadContext,\n hookExecutor,\n fs,\n logger,\n );\n if (!afterDownloadResult.success) {\n return afterDownloadResult;\n }\n\n // Execute the script and capture output for debugging\n logger.debug(messages.executingScript(shell));\n\n const argsContext: ICurlScriptArgsContext = {\n projectConfig: context.projectConfig,\n scriptPath,\n stagingDir: context.stagingDir,\n };\n\n const resolvedArgs = params.args ? await resolveValue(argsContext, params.args) : [];\n const resolvedEnv = params.env ? await resolveValue(argsContext, params.env) : {};\n\n const env = {\n ...process.env,\n ...resolvedEnv,\n };\n\n const loggingShell = installShell ?? createShell({ logger, skipCommandLog: true });\n let scriptOutput = '';\n if (shell === 'bash') {\n const result = await loggingShell`bash ${scriptPath} ${resolvedArgs}`.env(env);\n scriptOutput = [result.stdout, result.stderr].filter(Boolean).join('\\n');\n } else {\n const result = await loggingShell`sh ${scriptPath} ${resolvedArgs}`.env(env);\n scriptOutput = [result.stdout, result.stderr].filter(Boolean).join('\\n');\n }\n\n await handleBinaryInstallation(toolConfig, context, fs, logger);\n\n // Return paths to all binaries\n const binaryPaths = getBinaryPaths(toolConfig.binaries, context.stagingDir);\n\n // Verify at least one binary was actually installed\n const installedBinaries: string[] = [];\n for (const binaryPath of binaryPaths) {\n if (await fs.exists(binaryPath)) {\n installedBinaries.push(binaryPath);\n }\n }\n\n if (installedBinaries.length === 0) {\n const expectedPaths = binaryPaths.join(', ');\n logger.error(messages.noBinariesInstalled(expectedPaths));\n if (scriptOutput.trim()) {\n logger.error(messages.scriptOutput());\n // Print script output line by line for readability\n for (const line of scriptOutput.trim().split('\\n')) {\n logger.error(createSafeLogMessage(line));\n }\n }\n return {\n success: false,\n error: `Installation script completed but no binaries were found at expected locations: ${expectedPaths}`,\n };\n }\n\n let detectedVersion: string | undefined;\n const mainBinaryPath = binaryPaths[0];\n if (mainBinaryPath) {\n detectedVersion = await detectVersionViaCli({\n shellExecutor,\n binaryPath: mainBinaryPath,\n args: params.versionArgs,\n regex: params.versionRegex,\n });\n }\n\n const metadata: ICurlScriptInstallMetadata = {\n method: 'curl-script',\n scriptUrl: url,\n shell,\n };\n\n return {\n success: true,\n binaryPaths,\n metadata,\n version: detectedVersion || (toolConfig.version !== 'latest' ? toolConfig.version : undefined),\n };\n };\n\n return withInstallErrorHandling('curl-script', toolName, logger, operation);\n}\n",
|
|
104
|
+
"import { createSafeLogMessage, type SafeLogMessageMap } from '@dotfiles/logger';\n\nexport const messages = {\n installing: (toolName: string) => createSafeLogMessage(`Installing from curl-script: toolName=${toolName}`),\n downloadingScript: (url: string) => createSafeLogMessage(`Downloading install script from: ${url}`),\n executingScript: (shell: string) => createSafeLogMessage(`Executing install script using: ${shell}`),\n movingBinary: (sourcePath: string, targetPath: string) =>\n createSafeLogMessage(`Moving binary from ${sourcePath} to ${targetPath}`),\n binaryFoundInInstallDir: (path: string) => createSafeLogMessage(`Binary found in install directory: ${path}`),\n binaryNotFound: (binaryName: string, searchPaths: string) =>\n createSafeLogMessage(`Binary ${binaryName} not found in search paths: ${searchPaths}`),\n detectedVersion: (version: string) => createSafeLogMessage(`Detected version: ${version}`),\n versionDetectionFailed: (error: string) => createSafeLogMessage(`Failed to detect version: ${error}`),\n noBinariesInstalled: (expectedPaths: string) =>\n createSafeLogMessage(`No binaries were installed. Expected at: ${expectedPaths}`),\n scriptOutput: () => createSafeLogMessage('Install script output:'),\n} as const satisfies SafeLogMessageMap;\n",
|
|
105
|
+
"import type { BaseInstallParams } from '@dotfiles/core';\nimport { baseInstallParamsSchema } from '@dotfiles/core';\nimport { z } from 'zod';\nimport type { CurlScriptArgs, CurlScriptEnv } from '../types';\n\n/**\n * Parameters for installing a tool by downloading and executing a shell script using `curl`.\n * This method involves fetching a script from a URL and piping it to a shell.\n * Example: `curl -fsSL <url> | sh`.\n * This is analogous to Zinit's `dl` ice combined with `atclone` for script execution.\n * @example Zinit equivalent:\n * \\`\\`\\`zsh\n * zinit ice dl\"https://install.sh/myscript\" atclone\"sh myscript\"\n * zinit snippet \"https://install.sh/myscript\"\n * \\`\\`\\`\n */\nexport const curlScriptInstallParamsSchema = baseInstallParamsSchema.extend({\n /** The URL of the installation script to download. */\n url: z.string().url(),\n /** The shell to use for executing the downloaded script (e.g., `bash`, `sh`). */\n shell: z.enum(['bash', 'sh']),\n /** Arguments to pass to the script - can be static array or function returning array. */\n args: z.custom<CurlScriptArgs>().optional(),\n /** Environment variables to pass to the script - can be static object or function returning object. */\n env: z.custom<CurlScriptEnv>().optional(),\n /** Arguments to pass to the binary to check the version (e.g. ['--version']). */\n versionArgs: z.array(z.string()).optional(),\n /** Regex to extract version from output. */\n versionRegex: z.string().optional(),\n});\n\n/**\n * Parameters for installing a tool by downloading and executing a shell script using `curl`.\n *\n * NOTE: This is an explicit interface (not z.infer) to ensure TypeScript fully resolves\n * the property names, which is required for proper `keyof` behavior in declaration files.\n * Uses Omit because `env` has a more specific type than BaseInstallParams.env.\n */\nexport interface CurlScriptInstallParams extends Omit<BaseInstallParams, 'env'> {\n /** The URL of the installation script to download. */\n url: string;\n /** The shell to use for executing the downloaded script. */\n shell: 'bash' | 'sh';\n /** Arguments to pass to the script - can be static array or function returning array. */\n args?: CurlScriptArgs;\n /** Environment variables to pass to the script - can be static object or function returning object. */\n env?: CurlScriptEnv;\n /** Arguments to pass to the binary to check the version. */\n versionArgs?: string[];\n /** Regex to extract version from output. */\n versionRegex?: string;\n}\n",
|
|
106
|
+
"import {\n baseToolConfigWithPlatformsSchema,\n binaryConfigSchema,\n type InferToolConfigWithPlatforms,\n} from '@dotfiles/core';\nimport { z } from 'zod';\nimport { curlScriptInstallParamsSchema } from './curlScriptInstallParamsSchema';\n\nexport const curlScriptToolConfigSchema = baseToolConfigWithPlatformsSchema.extend({\n /** Resolved tool configuration for the 'curl-script' installation method */\n installationMethod: z.literal('curl-script'),\n /** Curl script installation parameters */\n installParams: curlScriptInstallParamsSchema,\n /** Binaries are typically required for this installation method */\n binaries: z.array(z.union([z.string().min(1), binaryConfigSchema])).min(1),\n});\n\n/** Resolved tool configuration for the 'curl-script' installation method. */\nexport type CurlScriptToolConfig = InferToolConfigWithPlatforms<typeof curlScriptToolConfigSchema>;\n",
|
|
107
|
+
"import type { IInstallContext, IInstallerPlugin, IInstallOptions, InstallResult, Shell } from '@dotfiles/core';\nimport type { IDownloader } from '@dotfiles/downloader';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { HookExecutor } from '@dotfiles/installer';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { installFromCurlScript } from './installFromCurlScript';\nimport {\n type CurlScriptInstallParams,\n curlScriptInstallParamsSchema,\n type CurlScriptToolConfig,\n curlScriptToolConfigSchema,\n} from './schemas';\nimport type { ICurlScriptInstallMetadata } from './types';\n\nconst PLUGIN_VERSION = '1.0.0';\n\n/**\n * Installer plugin for tools installed via curl scripts.\n *\n * This plugin handles tools that provide installation scripts accessed via URL.\n * It downloads the installation script, makes it executable, and runs it using\n * the specified shell (sh, bash, etc.). The script is responsible for installing\n * the tool to the appropriate location. This method is commonly used by tools\n * like rustup, nvm, and other language/runtime installers.\n *\n * Note: This plugin does not support version checking or automatic updates since\n * the installation is delegated to the external script.\n */\nexport class CurlScriptInstallerPlugin implements\n IInstallerPlugin<\n 'curl-script',\n CurlScriptInstallParams,\n CurlScriptToolConfig,\n ICurlScriptInstallMetadata\n >\n{\n readonly method = 'curl-script';\n readonly displayName = 'Curl Script Installer';\n readonly version = PLUGIN_VERSION;\n readonly paramsSchema = curlScriptInstallParamsSchema;\n readonly toolConfigSchema = curlScriptToolConfigSchema;\n\n /**\n * Creates a new CurlScriptInstallerPlugin instance.\n *\n * @param fs - The file system interface for file operations.\n * @param downloader - The downloader for fetching installation scripts.\n * @param hookExecutor - The hook executor for running post-download hooks.\n * @param shell - The shell executor for running commands.\n */\n constructor(\n private readonly fs: IFileSystem,\n private readonly downloader: IDownloader,\n private readonly hookExecutor: HookExecutor,\n private readonly shell: Shell,\n ) {}\n\n /**\n * Installs a tool using a curl script.\n *\n * @param toolName - The name of the tool to install.\n * @param toolConfig - The configuration for the curl-script tool.\n * @param context - The base installation context.\n * @param options - Optional installation options.\n * @param logger - The logger with tool context for logging operations.\n * @returns A promise that resolves to the installation result.\n */\n async install(\n toolName: string,\n toolConfig: CurlScriptToolConfig,\n context: IInstallContext,\n options: IInstallOptions | undefined,\n logger: TsLogger,\n ): Promise<InstallResult<ICurlScriptInstallMetadata>> {\n const result = await installFromCurlScript(\n toolName,\n toolConfig,\n context,\n options,\n this.fs,\n this.downloader,\n this.hookExecutor,\n logger,\n this.shell,\n );\n\n if (!result.success) {\n return {\n success: false,\n error: result.error,\n };\n }\n\n const installResult: InstallResult<ICurlScriptInstallMetadata> = {\n success: true,\n binaryPaths: result.binaryPaths,\n metadata: result.metadata,\n version: result.version,\n };\n\n return installResult;\n }\n\n /**\n * Indicates whether this plugin supports version update checking.\n *\n * @returns False, as curl-script installations delegate to external scripts.\n */\n supportsUpdate(): boolean {\n return false;\n }\n\n supportsUpdateCheck(): boolean {\n return false; // curl-script doesn't support version checking\n }\n\n /**\n\n * Indicates whether this plugin supports README fetching.\n *\n * @returns False, as curl-script installations don't have associated READMEs to fetch.\n */\n supportsReadme(): boolean {\n return false;\n }\n}\n",
|
|
108
|
+
"import type { IArchiveExtractor } from '@dotfiles/archive-extractor';\nimport {\n type IDownloadContext,\n type IExtractContext,\n type IExtractResult,\n type IInstallContext,\n type Shell,\n} from '@dotfiles/core';\nimport type { IDownloader } from '@dotfiles/downloader';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { HookExecutor, IInstallOptions } from '@dotfiles/installer';\nimport {\n createToolFileSystem,\n downloadWithProgress,\n executeAfterDownloadHook,\n executeAfterExtractHook,\n getBinaryPaths,\n setupBinariesFromArchive,\n withInstallErrorHandling,\n} from '@dotfiles/installer';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { detectVersionViaCli } from '@dotfiles/utils';\nimport path from 'node:path';\nimport { messages } from './log-messages';\nimport type { CurlTarToolConfig } from './schemas';\nimport type { CurlTarInstallResult, ICurlTarInstallMetadata } from './types';\n\n/**\n * Installs a tool from a tarball accessible via URL.\n *\n * This function orchestrates the complete installation process:\n * 1. Downloads the tarball from the specified URL\n * 2. Executes afterDownload hook if configured\n * 3. Extracts the archive to the installation directory\n * 4. Executes afterExtract hook if configured\n * 5. Sets up binary paths and creates necessary symlinks\n *\n * The function supports lifecycle hooks that allow custom processing at different\n * stages, such as modifying extracted files or setting permissions.\n *\n * @param toolName - The name of the tool to install.\n * @param toolConfig - The configuration for the curl-tar tool.\n * @param context - The base installation context.\n * @param options - Optional installation options.\n * @param fs - The file system interface for file operations.\n * @param downloader - The downloader for fetching the tarball.\n * @param archiveExtractor - The archive extractor for unpacking.\n * @param hookExecutor - The hook executor for running lifecycle hooks.\n * @param parentLogger - The parent logger for creating sub-loggers.\n * @param shellExecutor - The shell executor function (defaults to Bun's $ operator).\n * @returns A promise that resolves to the installation result.\n */\nexport async function installFromCurlTar(\n toolName: string,\n toolConfig: CurlTarToolConfig,\n context: IInstallContext,\n options: IInstallOptions | undefined,\n fs: IFileSystem,\n downloader: IDownloader,\n archiveExtractor: IArchiveExtractor,\n hookExecutor: HookExecutor,\n parentLogger: TsLogger,\n shellExecutor: Shell,\n): Promise<CurlTarInstallResult> {\n const toolFs = createToolFileSystem(fs, toolName);\n const logger = parentLogger.getSubLogger({ name: 'installFromCurlTar' });\n logger.debug(messages.installing(toolName));\n\n // Context variables for lifecycle stages\n let postDownloadContext: IDownloadContext;\n let postExtractContext: IExtractContext | undefined;\n\n if (!toolConfig.installParams || !('url' in toolConfig.installParams)) {\n return {\n success: false,\n error: 'URL not specified in installParams',\n };\n }\n\n const params = toolConfig.installParams;\n const url = params.url;\n\n const operation = async (): Promise<CurlTarInstallResult> => {\n // Download the tarball\n logger.debug(messages.downloadingTarball(url));\n const tarballPath = path.join(context.stagingDir, `${toolName}.tar.gz`);\n\n await downloadWithProgress(logger, url, tarballPath, `${toolName}.tar.gz`, downloader, options);\n\n // Update context with download path\n postDownloadContext = {\n ...context,\n downloadPath: tarballPath,\n };\n\n // Run afterDownload hook if defined\n const afterDownloadResult = await executeAfterDownloadHook(\n toolConfig,\n postDownloadContext,\n hookExecutor,\n fs,\n logger,\n );\n if (!afterDownloadResult.success) {\n return {\n success: false,\n error: afterDownloadResult.error,\n };\n }\n\n // Extract the tarball directly to install directory\n logger.debug(messages.extractingTarball());\n\n const extractResult: IExtractResult = await archiveExtractor.extract(logger, tarballPath, {\n targetDir: context.stagingDir,\n });\n logger.debug(messages.tarballExtracted(), extractResult);\n\n // Update context with extract directory and result\n postExtractContext = {\n ...postDownloadContext,\n extractDir: context.stagingDir,\n extractResult,\n };\n\n // Run afterExtract hook if defined\n const afterExtractResult = await executeAfterExtractHook(toolConfig, postExtractContext, hookExecutor, fs, logger);\n if (!afterExtractResult.success) {\n return {\n success: false,\n error: afterExtractResult.error,\n };\n }\n\n // Handle all binaries from extracted archive\n await setupBinariesFromArchive(toolFs, toolName, toolConfig, context, context.stagingDir, logger);\n\n // Clean up downloaded tarball\n if (await toolFs.exists(tarballPath)) {\n logger.debug(messages.cleaningArchive(tarballPath));\n await toolFs.rm(tarballPath);\n }\n\n // Return paths to all binaries\n const binaryPaths = getBinaryPaths(toolConfig.binaries, context.stagingDir);\n\n let detectedVersion: string | undefined;\n const mainBinaryPath = binaryPaths[0];\n if (mainBinaryPath) {\n detectedVersion = await detectVersionViaCli({\n binaryPath: mainBinaryPath,\n args: params.versionArgs,\n regex: params.versionRegex,\n shellExecutor,\n });\n }\n\n const metadata: ICurlTarInstallMetadata = {\n method: 'curl-tar',\n downloadUrl: url,\n tarballUrl: url,\n };\n\n return {\n success: true,\n binaryPaths,\n metadata,\n version: detectedVersion || (toolConfig.version !== 'latest' ? toolConfig.version : undefined),\n };\n };\n\n return withInstallErrorHandling('curl-tar', toolName, logger, operation);\n}\n",
|
|
109
|
+
"import { createSafeLogMessage, type SafeLogMessageMap } from '@dotfiles/logger';\n\nexport const messages = {\n installing: (toolName: string) => createSafeLogMessage(`Installing from curl-tar: toolName=${toolName}`),\n downloadingArchive: (url: string) => createSafeLogMessage(`Downloading archive from: ${url}`),\n downloadingTarball: (url: string) => createSafeLogMessage(`Downloading tarball from: ${url}`),\n extractingTarball: () => createSafeLogMessage('Extracting tarball'),\n tarballExtracted: () => createSafeLogMessage('Tarball extracted successfully'),\n archiveExtracted: () => createSafeLogMessage('Archive extracted: %o'),\n cleaningArchive: (archivePath: string) => createSafeLogMessage(`Cleaning up downloaded archive: ${archivePath}`),\n} as const satisfies SafeLogMessageMap;\n",
|
|
110
|
+
"import type { BaseInstallParams } from '@dotfiles/core';\nimport { baseInstallParamsSchema } from '@dotfiles/core';\nimport { z } from 'zod';\n\n/**\n * Parameters for installing a tool by downloading a tarball (`.tar`, `.tar.gz`, etc.) using `curl`,\n * then extracting it and locating binaries using patterns.\n * This is analogous to Zinit's `dl` ice for archives, combined with `extract` and `pick`.\n * @example Zinit equivalent:\n * ```zsh\n * zinit ice dl\"https://example.com/tool.tar.gz\" extract pick\"bin/tool\" # Conceptual\n * zinit light \"user/tool-from-tarball\"\n * ```\n */\nexport const curlTarInstallParamsSchema = baseInstallParamsSchema.extend({\n /** The URL of the tarball to download. */\n url: z.string().url(),\n /** Arguments to pass to the binary to check the version (e.g. ['--version']). */\n versionArgs: z.array(z.string()).optional(),\n /** Regex to extract version from output. */\n versionRegex: z.string().optional(),\n});\n\n/**\n * Parameters for installing a tool by downloading a tarball (`.tar`, `.tar.gz`, etc.) using `curl`,\n * then extracting it and potentially moving a binary from within.\n *\n * NOTE: This is an explicit interface (not z.infer) to ensure TypeScript fully resolves\n * the property names, which is required for proper `keyof` behavior in declaration files.\n */\nexport interface CurlTarInstallParams extends BaseInstallParams {\n /** The URL of the tarball to download. */\n url: string;\n /** Arguments to pass to the binary to check the version. */\n versionArgs?: string[];\n /** Regex to extract version from output. */\n versionRegex?: string;\n}\n",
|
|
111
|
+
"import {\n baseToolConfigWithPlatformsSchema,\n binaryConfigSchema,\n type InferToolConfigWithPlatforms,\n} from '@dotfiles/core';\nimport { z } from 'zod';\nimport { curlTarInstallParamsSchema } from './curlTarInstallParamsSchema';\n\nexport const curlTarToolConfigSchema = baseToolConfigWithPlatformsSchema.extend({\n /** Resolved tool configuration for the 'curl-tar' installation method */\n installationMethod: z.literal('curl-tar'),\n /** Curl tar installation parameters */\n installParams: curlTarInstallParamsSchema,\n /** Binaries are typically required for this installation method */\n binaries: z.array(z.union([z.string().min(1), binaryConfigSchema])).min(1),\n});\n\n/** Resolved tool configuration for the 'curl-tar' installation method. */\nexport type CurlTarToolConfig = InferToolConfigWithPlatforms<typeof curlTarToolConfigSchema>;\n",
|
|
112
|
+
"import type { IArchiveExtractor } from '@dotfiles/archive-extractor';\nimport type { IInstallContext, IInstallerPlugin, IInstallOptions, InstallResult, Shell } from '@dotfiles/core';\nimport type { IDownloader } from '@dotfiles/downloader';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { HookExecutor } from '@dotfiles/installer';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { installFromCurlTar } from './installFromCurlTar';\nimport {\n type CurlTarInstallParams,\n curlTarInstallParamsSchema,\n type CurlTarToolConfig,\n curlTarToolConfigSchema,\n} from './schemas';\nimport type { ICurlTarInstallMetadata } from './types';\n\nconst PLUGIN_VERSION = '1.0.0';\n\n/**\n * Installer plugin for tools distributed as tarballs via direct URLs.\n *\n * This plugin handles tools that are distributed as compressed archives (tar.gz, tar.bz2, etc.)\n * accessible via HTTP/HTTPS URLs. It downloads the tarball, extracts it to the installation\n * directory, and sets up the binaries. This method is commonly used for pre-compiled binaries\n * and tools that provide direct download links for their releases.\n *\n * The plugin supports lifecycle hooks for custom processing at different stages (after download,\n * after extraction). It does not support version checking or automatic updates since the URLs\n * are typically static.\n */\nexport class CurlTarInstallerPlugin implements\n IInstallerPlugin<\n 'curl-tar',\n CurlTarInstallParams,\n CurlTarToolConfig,\n ICurlTarInstallMetadata\n >\n{\n readonly method = 'curl-tar';\n readonly displayName = 'Curl Tar Installer';\n readonly version = PLUGIN_VERSION;\n readonly paramsSchema = curlTarInstallParamsSchema;\n readonly toolConfigSchema = curlTarToolConfigSchema;\n\n /**\n * Creates a new CurlTarInstallerPlugin instance.\n *\n * @param fs - The file system interface for file operations.\n * @param downloader - The downloader for fetching tarballs.\n * @param archiveExtractor - The archive extractor for unpacking tarballs.\n * @param hookExecutor - The hook executor for running lifecycle hooks.\n * @param shell - The shell executor for running commands.\n */\n constructor(\n private readonly fs: IFileSystem,\n private readonly downloader: IDownloader,\n private readonly archiveExtractor: IArchiveExtractor,\n private readonly hookExecutor: HookExecutor,\n private readonly shell: Shell,\n ) {}\n\n /**\n * Installs a tool from a tarball URL.\n *\n * @param toolName - The name of the tool to install.\n * @param toolConfig - The configuration for the curl-tar tool.\n * @param context - The base installation context.\n * @param options - Optional installation options.\n * @param logger - The logger with tool context for logging operations.\n * @returns A promise that resolves to the installation result.\n */\n async install(\n toolName: string,\n toolConfig: CurlTarToolConfig,\n context: IInstallContext,\n options: IInstallOptions | undefined,\n logger: TsLogger,\n ): Promise<InstallResult<ICurlTarInstallMetadata>> {\n const result = await installFromCurlTar(\n toolName,\n toolConfig,\n context,\n options,\n this.fs,\n this.downloader,\n this.archiveExtractor,\n this.hookExecutor,\n logger,\n this.shell,\n );\n\n if (!result.success) {\n return {\n success: false,\n error: result.error,\n };\n }\n\n const installResult: InstallResult<ICurlTarInstallMetadata> = {\n success: true,\n binaryPaths: result.binaryPaths,\n version: result.version,\n metadata: result.metadata,\n };\n\n return installResult;\n }\n\n /**\n * Indicates whether this plugin supports version update checking.\n *\n * @returns False, as curl-tar installations use static URLs.\n */\n supportsUpdate(): boolean {\n return false;\n }\n\n supportsUpdateCheck(): boolean {\n return false; // curl-tar doesn't support version checking\n }\n\n /**\n\n * Indicates whether this plugin supports README fetching.\n *\n * @returns False, as curl-tar installations don't have associated READMEs to fetch.\n */\n supportsReadme(): boolean {\n return false;\n }\n}\n",
|
|
113
|
+
"/**\n * Detects the tag prefix used by a GitHub repository by analyzing a tag string.\n *\n * Examples:\n * - \"v2.24.0\" → \"v\"\n * - \"jq-1.8.1\" → \"jq-\"\n * - \"15.1.0\" → \"\"\n * - \"tool-v1.2.3\" → \"tool-v\"\n */\nexport function detectTagPrefix(tag: string): string {\n // Match semver-like pattern: major.minor with optional patch and prerelease\n // This regex finds the first occurrence of a version number\n const semverPattern = /\\d+\\.\\d+(?:\\.\\d+)?(?:-[\\w.]+)?/;\n const match = tag.match(semverPattern);\n\n if (!match || match.index === undefined) {\n // No version pattern found, return empty prefix\n return '';\n }\n\n // Everything before the version number is the prefix\n const prefix: string = tag.slice(0, match.index);\n return prefix;\n}\n",
|
|
114
|
+
"/**\n * Normalizes user-provided version by stripping any prefix to get the core version.\n *\n * Examples:\n * - \"2.24.0\" → \"2.24.0\"\n * - \"v2.24.0\" → \"2.24.0\"\n * - \"1.0.0-beta.1\" → \"1.0.0-beta.1\"\n * - \"v1.0.0-rc1\" → \"1.0.0-rc1\"\n */\nexport function normalizeUserVersion(version: string): string {\n // Match semver-like pattern: major.minor with optional patch and prerelease\n const semverPattern = /(\\d+\\.\\d+(?:\\.\\d+)?(?:-[\\w.]+)?)/;\n const match = version.match(semverPattern);\n\n if (!match) {\n // No version pattern found, return original\n return version;\n }\n\n // Return just the version number portion\n const matchedVersion = match[1];\n if (!matchedVersion) {\n return version;\n }\n return matchedVersion;\n}\n",
|
|
115
|
+
"import { detectTagPrefix } from './detectTagPrefix';\nimport { normalizeUserVersion } from './normalizeUserVersion';\n\n/**\n * Builds a corrected tag by combining the detected prefix from the latest tag\n * with the normalized user-provided version.\n *\n * Examples:\n * - latestTag: \"v2.24.0\", userVersion: \"2.23.0\" → \"v2.23.0\"\n * - latestTag: \"jq-1.8.1\", userVersion: \"1.7.0\" → \"jq-1.7.0\"\n * - latestTag: \"15.1.0\", userVersion: \"v15.0.0\" → \"15.0.0\"\n */\nexport function buildCorrectedTag(latestTag: string, userVersion: string): string {\n const prefix = detectTagPrefix(latestTag);\n const normalizedVersion = normalizeUserVersion(userVersion);\n const correctedTag: string = `${prefix}${normalizedVersion}`;\n return correctedTag;\n}\n",
|
|
116
|
+
"import type { ProjectConfig } from '@dotfiles/config';\nimport type { IGitHubRateLimit, IGitHubRelease, Shell } from '@dotfiles/core';\nimport type { ICache } from '@dotfiles/downloader';\nimport type { TsLogger } from '@dotfiles/logger';\nimport crypto from 'node:crypto';\nimport semver from 'semver';\nimport { GitHubApiClientError } from './GitHubApiClientError';\nimport type { IGitHubApiClient } from './IGitHubApiClient';\nimport { messages } from './log-messages';\n\n/**\n * GitHub API client implementation using the `gh` CLI.\n *\n * This client uses the `gh api` command to make requests to the GitHub API,\n * leveraging the CLI's built-in authentication and other features. It implements\n * the same caching mechanism as GitHubApiClient for consistency.\n *\n * ### Benefits of using gh CLI:\n * - Uses existing `gh auth` credentials (no need for separate token config)\n * - Better handling of corporate proxies and network configurations\n * - Consistent with other `gh` usage patterns\n *\n * ### Caching\n * The client uses the same ICache interface as GitHubApiClient. While `gh api`\n * has its own `--cache` flag, we implement caching at our layer for consistency\n * with the fetch-based client and to use the same cache storage.\n */\nexport class GhCliApiClient implements IGitHubApiClient {\n private readonly hostname: string;\n private readonly shell: Shell;\n private readonly cache?: ICache;\n private readonly cacheEnabled: boolean;\n private readonly cacheTtlMs: number;\n private readonly logger: TsLogger;\n\n constructor(\n parentLogger: TsLogger,\n projectConfig: ProjectConfig,\n shell: Shell,\n cache?: ICache,\n ) {\n this.logger = parentLogger.getSubLogger({ name: 'GhCliApiClient' });\n // Extract hostname from project config (e.g., 'api.github.com' -> 'github.com')\n this.hostname = this.extractHostname(projectConfig.github.host);\n this.shell = shell;\n this.cache = cache;\n this.cacheEnabled = projectConfig.github.cache.enabled;\n this.cacheTtlMs = projectConfig.github.cache.ttl;\n\n const logger = this.logger.getSubLogger({ name: 'constructor' });\n logger.debug(messages.ghCli.initialized(this.hostname));\n\n if (this.cache && this.cacheEnabled) {\n logger.debug(messages.cache.enabled(this.cacheTtlMs));\n } else if (this.cache && !this.cacheEnabled) {\n logger.debug(messages.cache.disabled());\n } else {\n logger.debug(messages.cache.missing());\n }\n }\n\n /**\n * Extracts the hostname for gh CLI from the API URL.\n * @param apiHost The full API URL (e.g., 'https://api.github.com')\n * @returns The hostname for gh CLI (e.g., 'github.com')\n */\n private extractHostname(apiHost: string): string {\n try {\n const url = new URL(apiHost);\n // Convert api.github.com -> github.com for standard GitHub\n if (url.hostname === 'api.github.com') {\n return 'github.com';\n }\n // For GitHub Enterprise, remove 'api.' prefix if present\n return url.hostname.replace(/^api\\./, '');\n } catch {\n return 'github.com';\n }\n }\n\n /**\n * Generates a unique cache key for a GitHub API request.\n * Uses the same format as GitHubApiClient for cache interoperability.\n */\n private generateCacheKey(endpoint: string, method: string): string {\n // Use same key format as GitHubApiClient\n let key = `${method}:${endpoint}`;\n\n // gh CLI uses its own auth, but we still include a marker for cache separation\n const cliMarker = crypto.createHash('sha256').update('gh-cli').digest('hex').substring(0, 8);\n key += `:${cliMarker}`;\n\n return key;\n }\n\n /**\n * Makes a request using gh CLI.\n */\n private async request<T>(endpoint: string, method: 'GET' = 'GET'): Promise<T> {\n const logger = this.logger.getSubLogger({ name: 'request' });\n const cacheKey = this.generateCacheKey(endpoint, method);\n\n // Check cache first\n const cachedResult = await this.tryGetFromCache<T>(cacheKey, method, endpoint);\n if (cachedResult) {\n return cachedResult;\n }\n\n logger.debug(messages.ghCli.executing(endpoint));\n\n // Build gh api command arguments\n const args = this.buildGhApiArgs(endpoint, method);\n\n try {\n const data = await this.executeGhCommand<T>(endpoint, args);\n await this.tryCacheResponse(cacheKey, data, method);\n return data;\n } catch (error) {\n return this.handleRequestError(error, endpoint);\n }\n }\n\n /**\n * Builds arguments for the gh api command.\n */\n private buildGhApiArgs(endpoint: string, method: string): string[] {\n const args: string[] = ['api'];\n\n // Add hostname if not default github.com\n if (this.hostname !== 'github.com') {\n args.push('--hostname', this.hostname);\n }\n\n // Add method if not GET\n if (method !== 'GET') {\n args.push('--method', method);\n }\n\n // Add the endpoint (without leading slash for gh api)\n const cleanEndpoint = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint;\n args.push(cleanEndpoint);\n\n return args;\n }\n\n /**\n * Executes the gh command and parses the response.\n */\n private async executeGhCommand<T>(endpoint: string, args: string[]): Promise<T> {\n const logger = this.logger.getSubLogger({ name: 'executeGhCommand' });\n const command = ['gh', ...args].join(' ');\n const result = await this.shell(command).quiet().noThrow();\n\n if (result.code !== 0) {\n logger.debug(messages.ghCli.commandFailed(result.code));\n throw this.parseGhError(result.stderr, result.code, endpoint);\n }\n\n try {\n return JSON.parse(result.stdout) as T;\n } catch {\n logger.debug(messages.ghCli.parseError(endpoint));\n throw new GitHubApiClientError(`Failed to parse gh api response for ${endpoint}`, undefined);\n }\n }\n\n /**\n * Parses error output from gh CLI and returns appropriate error.\n */\n private parseGhError(stderr: string, exitCode: number, endpoint: string): GitHubApiClientError {\n const stderrLower = stderr.toLowerCase();\n\n // Handle common gh CLI errors\n if (stderrLower.includes('not found') || stderrLower.includes('404')) {\n return new GitHubApiClientError(`GitHub resource not found: ${endpoint}. Status: 404`, 404);\n }\n\n if (stderrLower.includes('rate limit') || stderrLower.includes('403')) {\n return new GitHubApiClientError(`GitHub API rate limit exceeded for ${endpoint}. Status: 403`, 403);\n }\n\n if (stderrLower.includes('unauthorized') || stderrLower.includes('401')) {\n return new GitHubApiClientError(`GitHub API unauthorized for ${endpoint}. Status: 401`, 401);\n }\n\n if (stderrLower.includes('forbidden')) {\n return new GitHubApiClientError(`GitHub API forbidden for ${endpoint}. Status: 403`, 403);\n }\n\n // Generic error\n return new GitHubApiClientError(\n `gh api command failed for ${endpoint} with exit code ${exitCode}: ${stderr}`,\n undefined,\n );\n }\n\n private async tryGetFromCache<T>(cacheKey: string, method: string, endpoint: string): Promise<T | null> {\n const logger = this.logger.getSubLogger({ name: 'tryGetFromCache' });\n\n if (!this.cache || !this.cacheEnabled || method !== 'GET') {\n return null;\n }\n\n try {\n const cachedData = await this.cache.get<T>(cacheKey);\n if (cachedData) {\n logger.debug(messages.ghCli.cacheHit(endpoint));\n return cachedData;\n }\n logger.debug(messages.ghCli.cacheMiss(endpoint));\n } catch {\n // Cache layer logs retrieval failures\n }\n\n return null;\n }\n\n private async tryCacheResponse<T>(cacheKey: string, data: T, method: string): Promise<void> {\n if (!this.cache || !this.cacheEnabled || method !== 'GET') {\n return;\n }\n\n try {\n await this.cache.set<T>(cacheKey, data, this.cacheTtlMs);\n } catch {\n // Cache layer logs storage failures\n }\n }\n\n private handleRequestError(error: unknown, endpoint: string): never {\n const logger = this.logger.getSubLogger({ name: 'handleRequestError' });\n logger.debug(messages.errors.requestFailure(endpoint), error);\n\n if (error instanceof GitHubApiClientError) {\n throw error;\n }\n\n if (error instanceof Error) {\n throw new GitHubApiClientError(`Error during gh api request to ${endpoint}: ${error.message}`, undefined, error);\n }\n\n throw new GitHubApiClientError(`Unknown error during gh api request to ${endpoint}`);\n }\n\n async getLatestRelease(owner: string, repo: string): Promise<IGitHubRelease | null> {\n const logger = this.logger.getSubLogger({ name: 'getLatestRelease' });\n logger.debug(messages.releases.fetchingLatest(owner, repo));\n try {\n return await this.request<IGitHubRelease>(`/repos/${owner}/${repo}/releases/latest`);\n } catch (error) {\n if (error instanceof GitHubApiClientError && error.statusCode === 404) {\n logger.debug(messages.releases.latestNotFound(owner, repo));\n return null;\n }\n logger.debug(messages.releases.latestError(owner, repo), error);\n throw error;\n }\n }\n\n async getReleaseByTag(owner: string, repo: string, tag: string): Promise<IGitHubRelease | null> {\n const logger = this.logger.getSubLogger({ name: 'getReleaseByTag' });\n logger.debug(messages.releases.fetchingByTag(tag, owner, repo));\n try {\n return await this.request<IGitHubRelease>(`/repos/${owner}/${repo}/releases/tags/${tag}`);\n } catch (error) {\n if (error instanceof GitHubApiClientError && error.statusCode === 404) {\n logger.debug(messages.releases.tagNotFound(tag, owner, repo));\n return null;\n }\n logger.debug(messages.releases.tagError(tag, owner, repo), error);\n throw error;\n }\n }\n\n async getAllReleases(\n owner: string,\n repo: string,\n options?: { perPage?: number; includePrerelease?: boolean; limit?: number; },\n ): Promise<IGitHubRelease[]> {\n const logger = this.logger.getSubLogger({ name: 'getAllReleases' });\n logger.debug(messages.releases.fetchingAll(owner, repo), options);\n\n const perPage = options?.perPage || 30;\n const limit = options?.limit;\n let page = 1;\n let allReleases: IGitHubRelease[] = [];\n let keepFetching = true;\n\n while (keepFetching) {\n const endpoint = `/repos/${owner}/${repo}/releases?per_page=${perPage}&page=${page}`;\n logger.debug(messages.releases.fetchingPage(page, endpoint));\n const releasesPage = await this.request<IGitHubRelease[]>(endpoint);\n\n if (releasesPage.length === 0) {\n keepFetching = false;\n } else {\n allReleases = allReleases.concat(releasesPage);\n page++;\n if (releasesPage.length < perPage) {\n keepFetching = false;\n }\n // Stop if we've reached the limit\n if (limit !== undefined && allReleases.length >= limit) {\n allReleases = allReleases.slice(0, limit);\n keepFetching = false;\n }\n }\n }\n\n logger.debug(messages.releases.totalFetched(allReleases.length, owner, repo));\n\n if (options?.includePrerelease === false) {\n const filteredReleases = allReleases.filter((release) => !release.prerelease);\n logger.debug(messages.releases.filteredPrereleases(filteredReleases.length));\n return filteredReleases;\n }\n\n return allReleases;\n }\n\n async getReleaseByConstraint(owner: string, repo: string, constraint: string): Promise<IGitHubRelease | null> {\n const logger = this.logger.getSubLogger({ name: 'getReleaseByConstraint' });\n logger.debug(messages.constraints.searching(constraint, owner, repo));\n\n if (constraint === 'latest') {\n return await this.handleLatestConstraint(owner, repo);\n }\n\n return await this.findReleaseByVersionConstraint(owner, repo, constraint);\n }\n\n private async handleLatestConstraint(owner: string, repo: string): Promise<IGitHubRelease | null> {\n try {\n return await this.getLatestRelease(owner, repo);\n } catch (error) {\n const logger = this.logger.getSubLogger({ name: 'handleLatestConstraint' });\n logger.debug(messages.errors.constraintLatestError(), error);\n return null;\n }\n }\n\n private async findReleaseByVersionConstraint(\n owner: string,\n repo: string,\n constraint: string,\n ): Promise<IGitHubRelease | null> {\n const logger = this.logger.getSubLogger({ name: 'findReleaseByVersionConstraint' });\n logger.debug(messages.constraints.pageFetch(constraint));\n\n let latestSatisfyingRelease: IGitHubRelease | null = null;\n let latestSatisfyingVersionClean: string | null = null;\n let page = 1;\n const perPage = 30;\n const maxPages = 100;\n\n while (page <= maxPages) {\n const releasesPage = await this.fetchReleasesPage(owner, repo, page, perPage, constraint);\n if (!releasesPage) {\n break;\n }\n\n if (releasesPage.length === 0) {\n break;\n }\n\n const bestFromPage = this.findBestReleaseFromPage(\n releasesPage,\n constraint,\n latestSatisfyingRelease,\n latestSatisfyingVersionClean,\n );\n\n if (bestFromPage.release) {\n latestSatisfyingRelease = bestFromPage.release;\n latestSatisfyingVersionClean = bestFromPage.version;\n }\n\n if (releasesPage.length < perPage) {\n break;\n }\n\n page++;\n }\n\n this.logConstraintResult(constraint, latestSatisfyingRelease);\n return latestSatisfyingRelease;\n }\n\n private async fetchReleasesPage(\n owner: string,\n repo: string,\n page: number,\n perPage: number,\n constraint: string,\n ): Promise<IGitHubRelease[] | null> {\n const endpoint = `/repos/${owner}/${repo}/releases?per_page=${perPage}&page=${page}`;\n const logger = this.logger.getSubLogger({ name: 'fetchReleasesPage' });\n logger.debug(messages.constraints.pageRequest(page, owner, repo));\n\n try {\n return await this.request<IGitHubRelease[]>(endpoint);\n } catch (error) {\n logger.debug(messages.errors.constraintError(constraint, owner, repo), error);\n return null;\n }\n }\n\n private findBestReleaseFromPage(\n releasesPage: IGitHubRelease[],\n constraint: string,\n currentBest: IGitHubRelease | null,\n currentBestVersion: string | null,\n ): { release: IGitHubRelease | null; version: string | null; } {\n const logger = this.logger.getSubLogger({ name: 'findBestReleaseFromPage' });\n let bestRelease = currentBest;\n let bestVersion = currentBestVersion;\n\n for (const release of releasesPage) {\n if (!release.tag_name) {\n continue;\n }\n\n const cleanVersion = release.tag_name.startsWith('v') ? release.tag_name.substring(1) : release.tag_name;\n\n if (this.isVersionSatisfying(cleanVersion, constraint)) {\n if (this.isBetterVersion(cleanVersion, bestVersion)) {\n bestRelease = release;\n bestVersion = cleanVersion;\n logger.debug(messages.constraints.bestCandidate(release.tag_name, cleanVersion));\n }\n }\n }\n\n return { release: bestRelease, version: bestVersion };\n }\n\n private isVersionSatisfying(version: string, constraint: string): boolean {\n return Boolean(semver.valid(version)) && semver.satisfies(version, constraint, { includePrerelease: true });\n }\n\n private isBetterVersion(newVersion: string, currentBestVersion: string | null): boolean {\n return !currentBestVersion || semver.gt(newVersion, currentBestVersion);\n }\n\n private logConstraintResult(constraint: string, result: IGitHubRelease | null): void {\n const logger = this.logger.getSubLogger({ name: 'logConstraintResult' });\n if (result) {\n logger.debug(messages.constraints.resultFound(constraint, result.tag_name));\n } else {\n logger.debug(messages.constraints.resultMissing(constraint));\n }\n }\n\n async getRateLimit(): Promise<IGitHubRateLimit> {\n const logger = this.logger.getSubLogger({ name: 'getRateLimit' });\n logger.debug(messages.rateLimit.fetching());\n\n type RateLimitResponse = {\n resources: {\n core: IGitHubRateLimit;\n };\n rate: IGitHubRateLimit;\n };\n\n const response = await this.request<RateLimitResponse>('/rate_limit');\n return response.resources.core;\n }\n\n async getLatestReleaseTags(owner: string, repo: string, count: number = 5): Promise<string[]> {\n const logger = this.logger.getSubLogger({ name: 'getLatestReleaseTags' });\n logger.debug(messages.releases.fetchingLatestTags(owner, repo, count));\n\n try {\n const releases = await this.request<IGitHubRelease[]>(`/repos/${owner}/${repo}/releases?per_page=${count}`);\n const tags = releases.map((release) => release.tag_name);\n logger.debug(messages.releases.fetchedTags(tags.length));\n return tags;\n } catch (error) {\n logger.debug(messages.releases.fetchTagsError(owner, repo), error);\n return [];\n }\n }\n\n async probeLatestTag(owner: string, repo: string): Promise<string | null> {\n const logger = this.logger.getSubLogger({ name: 'probeLatestTag' });\n logger.debug(messages.tagPattern.probing(owner, repo));\n\n // For gh CLI, we can just fetch the latest release directly\n // This is more reliable than the redirect-based probe\n try {\n const release = await this.getLatestRelease(owner, repo);\n if (release) {\n logger.debug(messages.tagPattern.detected(release.tag_name));\n return release.tag_name;\n }\n return null;\n } catch {\n logger.debug(messages.tagPattern.probeFailed(owner, repo));\n return null;\n }\n }\n\n /**\n * Downloads a release asset using gh release download.\n * This enables downloading from private repositories using gh CLI authentication.\n *\n * @param owner The owner of the repository.\n * @param repo The name of the repository (without owner).\n * @param tag The release tag.\n * @param assetName The name of the asset to download.\n * @param destinationPath The full path where the asset should be saved.\n */\n async downloadAsset(\n owner: string,\n repo: string,\n tag: string,\n assetName: string,\n destinationPath: string,\n ): Promise<void> {\n const logger = this.logger.getSubLogger({ name: 'downloadAsset' });\n logger.debug(messages.ghCli.downloadingAsset(assetName, `${owner}/${repo}`, tag));\n\n // Extract directory from destination path\n const destinationDir = destinationPath.substring(0, destinationPath.lastIndexOf('/'));\n\n // Build gh release download command\n // --pattern matches the specific asset name\n // --dir specifies the output directory\n // --clobber overwrites if file exists\n const args = ['release', 'download', tag];\n args.push('--repo', `${owner}/${repo}`);\n args.push('--pattern', assetName);\n args.push('--dir', destinationDir);\n args.push('--clobber');\n\n // Add hostname if not default github.com\n if (this.hostname !== 'github.com') {\n // gh release download doesn't have --hostname, but the repo can include the host\n // For now, default gh CLI uses the authenticated host\n }\n\n const command = ['gh', ...args].join(' ');\n const result = await this.shell(command).quiet().noThrow();\n\n if (result.code !== 0) {\n logger.debug(messages.ghCli.downloadFailed(assetName, result.code));\n throw new GitHubApiClientError(\n `gh release download failed for ${assetName}: ${result.stderr.trim() || `exit code ${result.code}`}`,\n undefined,\n );\n }\n\n logger.debug(messages.ghCli.downloadComplete(assetName));\n }\n}\n",
|
|
117
|
+
"/**\n * Custom error class for errors originating from the GitHub API client.\n */\nexport class GitHubApiClientError extends Error {\n public readonly originalError?: unknown;\n public readonly statusCode?: number;\n\n /**\n * Creates an instance of GitHubApiClientError.\n * @param message - The error message.\n * @param statusCode - Optional HTTP status code associated with the error.\n * @param originalError - Optional original error that was caught.\n */\n constructor(message: string, statusCode?: number, originalError?: unknown) {\n super(message);\n this.name = 'GitHubApiClientError';\n this.statusCode = statusCode;\n this.originalError = originalError;\n\n // This line is needed to restore the prototype chain in ES5\n Object.setPrototypeOf(this, GitHubApiClientError.prototype);\n }\n}\n",
|
|
118
|
+
"import { createSafeLogMessage, type SafeLogMessageMap } from '@dotfiles/logger';\n\nexport const messages = {\n constructor: {\n initialized: (baseUrl: string, userAgent: string) =>\n createSafeLogMessage(`GitHub API client initialized with base URL ${baseUrl} and User-Agent ${userAgent}`),\n authTokenPresent: () => createSafeLogMessage('GitHub token authentication enabled'),\n authTokenMissing: () => createSafeLogMessage('No GitHub token provided; requests will be unauthenticated'),\n } satisfies SafeLogMessageMap,\n ghCli: {\n initialized: (hostname: string) =>\n createSafeLogMessage(`GitHub CLI API client initialized for hostname ${hostname}`),\n executing: (endpoint: string) => createSafeLogMessage(`Executing gh api request for ${endpoint}`),\n cacheHit: (endpoint: string) => createSafeLogMessage(`Cache hit for gh api ${endpoint}`),\n cacheMiss: (endpoint: string) => createSafeLogMessage(`Cache miss for gh api ${endpoint}`),\n commandFailed: (exitCode: number) => createSafeLogMessage(`gh cli command failed with exit code ${exitCode}`),\n parseError: (endpoint: string) => createSafeLogMessage(`Failed to parse gh api response for ${endpoint}`),\n notAvailable: () => createSafeLogMessage('gh cli is not available or not authenticated'),\n downloadingAsset: (assetName: string, repo: string, tag: string) =>\n createSafeLogMessage(`Downloading ${assetName} from ${repo}@${tag} via gh release download`),\n downloadComplete: (assetName: string) => createSafeLogMessage(`Downloaded ${assetName} via gh cli`),\n downloadFailed: (assetName: string, exitCode: number) =>\n createSafeLogMessage(`gh release download failed for ${assetName} with exit code ${exitCode}`),\n } satisfies SafeLogMessageMap,\n cache: {\n enabled: (ttlMs: number) => createSafeLogMessage(`GitHub API cache enabled with TTL ${ttlMs} ms`),\n disabled: () => createSafeLogMessage('GitHub API cache provided but disabled by configuration'),\n missing: () => createSafeLogMessage('No GitHub API cache provided; responses will not be cached'),\n } satisfies SafeLogMessageMap,\n request: {\n performing: (method: string, url: string) => createSafeLogMessage(`GitHub API ${method} request to ${url}`),\n emptyResponse: (url: string) => createSafeLogMessage(`GitHub API returned an empty response buffer for ${url}`),\n } satisfies SafeLogMessageMap,\n errors: {\n requestFailure: (url: string) => createSafeLogMessage(`GitHub API request failure for ${url}`),\n notFound: (url: string) => createSafeLogMessage(`GitHub resource not found for ${url}`),\n rateLimit: (url: string, resetTime: string) =>\n createSafeLogMessage(`GitHub API rate limit exceeded for ${url}, resets at ${resetTime}`),\n forbidden: (url: string) => createSafeLogMessage(`GitHub API request forbidden for ${url}`),\n client: (url: string, statusCode: number) =>\n createSafeLogMessage(`GitHub API client error for ${url} with status ${statusCode}`),\n server: (url: string, statusCode: number) =>\n createSafeLogMessage(`GitHub API server error for ${url} with status ${statusCode}`),\n http: (url: string, statusCode: number) =>\n createSafeLogMessage(`GitHub API HTTP error for ${url} with status ${statusCode}`),\n network: (url: string) => createSafeLogMessage(`GitHub API network error for ${url}`),\n unknown: (url: string) => createSafeLogMessage(`Unknown GitHub API error for ${url}`),\n constraintLatestError: () => createSafeLogMessage(`GitHub API latest constraint lookup failed`),\n constraintError: (constraint: string, owner: string, repo: string) =>\n createSafeLogMessage(`GitHub API error while evaluating constraint ${constraint} for ${owner}/${repo}`),\n } satisfies SafeLogMessageMap,\n releases: {\n fetchingLatest: (owner: string, repo: string) =>\n createSafeLogMessage(`Fetching latest GitHub release for ${owner}/${repo}`),\n latestNotFound: (owner: string, repo: string) =>\n createSafeLogMessage(`Latest GitHub release not found for ${owner}/${repo}`),\n latestError: (owner: string, repo: string) =>\n createSafeLogMessage(`Error fetching latest GitHub release for ${owner}/${repo}`),\n fetchingByTag: (tag: string, owner: string, repo: string) =>\n createSafeLogMessage(`Fetching GitHub release ${tag} for ${owner}/${repo}`),\n tagNotFound: (tag: string, owner: string, repo: string) =>\n createSafeLogMessage(`GitHub release ${tag} not found for ${owner}/${repo}`),\n tagError: (tag: string, owner: string, repo: string) =>\n createSafeLogMessage(`Error fetching GitHub release ${tag} for ${owner}/${repo}`),\n fetchingAll: (owner: string, repo: string) =>\n createSafeLogMessage(`Fetching all GitHub releases for ${owner}/${repo}`),\n fetchingPage: (page: number, endpoint: string) =>\n createSafeLogMessage(`Fetching GitHub releases page ${page} via ${endpoint}`),\n totalFetched: (count: number, owner: string, repo: string) =>\n createSafeLogMessage(`Fetched ${count} GitHub releases for ${owner}/${repo}`),\n filteredPrereleases: (count: number) =>\n createSafeLogMessage(`Filtered prereleases leaving ${count} GitHub releases`),\n fetchingLatestTags: (owner: string, repo: string, count: number) =>\n createSafeLogMessage(`Fetching ${count} latest release tags for ${owner}/${repo}`),\n fetchedTags: (count: number) => createSafeLogMessage(`Fetched ${count} release tags`),\n fetchTagsError: (owner: string, repo: string) =>\n createSafeLogMessage(`Error fetching release tags for ${owner}/${repo}`),\n } satisfies SafeLogMessageMap,\n constraints: {\n searching: (constraint: string, owner: string, repo: string) =>\n createSafeLogMessage(`Searching for GitHub release matching constraint ${constraint} in ${owner}/${repo}`),\n pageFetch: (constraint: string) =>\n createSafeLogMessage(`Iterating GitHub releases pages for constraint ${constraint}`),\n pageRequest: (page: number, owner: string, repo: string) =>\n createSafeLogMessage(`Fetching GitHub releases page ${page} for ${owner}/${repo}`),\n bestCandidate: (tag: string, version: string) =>\n createSafeLogMessage(`Best GitHub release candidate so far ${tag} (version ${version})`),\n resultFound: (constraint: string, tag: string) =>\n createSafeLogMessage(`Found GitHub release ${tag} for constraint ${constraint}`),\n resultMissing: (constraint: string) => createSafeLogMessage(`No GitHub release found for constraint ${constraint}`),\n } satisfies SafeLogMessageMap,\n rateLimit: {\n fetching: () => createSafeLogMessage('Fetching GitHub API rate limit status'),\n } satisfies SafeLogMessageMap,\n tagPattern: {\n probing: (owner: string, repo: string) => createSafeLogMessage(`Probing release tag pattern for ${owner}/${repo}`),\n detected: (tag: string) => createSafeLogMessage(`Detected latest release tag: ${tag}`),\n noRedirect: (owner: string, repo: string) =>\n createSafeLogMessage(`No redirect found for ${owner}/${repo} latest release`),\n probeFailed: (owner: string, repo: string) =>\n createSafeLogMessage(`Failed to probe tag pattern for ${owner}/${repo}`),\n } satisfies SafeLogMessageMap,\n} as const;\n",
|
|
119
|
+
"import type { ProjectConfig } from '@dotfiles/config';\nimport type { IGitHubRateLimit, IGitHubRelease } from '@dotfiles/core';\nimport type { ICache } from '@dotfiles/downloader';\nimport {\n ClientError,\n ForbiddenError,\n HttpError,\n type IDownloader,\n NetworkError,\n NotFoundError,\n RateLimitError,\n ServerError,\n} from '@dotfiles/downloader';\nimport type { TsLogger } from '@dotfiles/logger';\nimport crypto from 'node:crypto';\nimport semver from 'semver';\nimport { GitHubApiClientError } from './GitHubApiClientError';\nimport type { IGitHubApiClient } from './IGitHubApiClient';\nimport { messages } from './log-messages';\n\n/**\n * Implements the IGitHubApiClient interface for interacting with the GitHub API.\n *\n * This client handles the construction of API requests, authentication,\n * and response parsing. It also integrates a caching layer to reduce\n * redundant API calls and avoid rate-limiting issues.\n *\n * ### API Caching\n * The client features a built-in, configurable caching mechanism that utilizes\n * an `IGitHubApiCache` implementation (e.g., `FileGitHubApiCache`). Caching behavior\n * is controlled by `projectConfig.github.cache.enabled` and `projectConfig.github.cache.ttl`.\n * Cache keys are uniquely generated based on the API endpoint and the authentication\n * token to ensure data integrity.\n *\n * ### Error Handling\n * It translates HTTP errors from the downloader into specific, custom error classes\n * like `NotFoundError` and `RateLimitError`. This allows consumers of the client\n * to handle API errors in a predictable manner.\n *\n * ### Host Configuration\n * The GitHub API base URL is configurable via `projectConfig.github.host`, which is\n * essential for testing against a mock server.\n *\n * For testing this client, two primary helpers are available:\n * - `FetchMockHelper`: For mocking `fetch` requests and their responses.\n * (from `src/testing-helpers/FetchMockHelper.ts`)\n * - `createMockApiServer`: For creating a lightweight mock API server\n * using `express`, allowing for end-to-end testing of the client's\n * interaction with a live server.\n * (from `src/testing-helpers/createMockApiServer.ts`)\n */\nexport class GitHubApiClient implements IGitHubApiClient {\n private readonly baseUrl: string;\n private readonly githubToken?: string;\n private readonly downloader: IDownloader;\n private readonly userAgent: string;\n private readonly cache?: ICache;\n private readonly cacheEnabled: boolean;\n private readonly cacheTtlMs: number;\n private readonly logger: TsLogger;\n\n constructor(parentLogger: TsLogger, projectConfig: ProjectConfig, downloader: IDownloader, cache?: ICache) {\n this.logger = parentLogger.getSubLogger({ name: 'GitHubApiClient' });\n this.baseUrl = projectConfig.github.host;\n this.githubToken = projectConfig.github.token;\n this.downloader = downloader;\n this.userAgent = projectConfig.github.userAgent;\n this.cache = cache;\n this.cacheEnabled = projectConfig.github.cache.enabled;\n this.cacheTtlMs = projectConfig.github.cache.ttl;\n const logger = this.logger.getSubLogger({ name: 'constructor' });\n logger.debug(messages.constructor.initialized(this.baseUrl, this.userAgent));\n if (this.githubToken) {\n logger.debug(messages.constructor.authTokenPresent());\n } else {\n logger.debug(messages.constructor.authTokenMissing());\n }\n\n if (this.cache && this.cacheEnabled) {\n logger.debug(messages.cache.enabled(this.cacheTtlMs));\n } else if (this.cache && !this.cacheEnabled) {\n logger.debug(messages.cache.disabled());\n } else {\n logger.debug(messages.cache.missing());\n }\n }\n\n /**\n * Generates a unique cache key for a GitHub API request.\n * @param endpoint The API endpoint path\n * @param method The HTTP method\n * @returns A unique cache key\n * @private\n */\n private generateCacheKey(endpoint: string, method: string): string {\n // Create a base key from the method and endpoint\n let key = `${method}:${endpoint}`;\n\n // If a token is used, include a hash of it in the key\n // This ensures cache invalidation when the token changes\n if (this.githubToken && typeof this.githubToken === 'string' && this.githubToken.length > 0) {\n const tokenHash = crypto.createHash('sha256').update(this.githubToken).digest('hex').substring(0, 8); // Use only first 8 chars of hash\n key += `:${tokenHash}`;\n }\n\n return key;\n }\n\n private async request<T>(endpoint: string, method: 'GET' = 'GET'): Promise<T> {\n const logger = this.logger.getSubLogger({ name: 'request' });\n const url = `${this.baseUrl}${endpoint}`;\n const cacheKey = this.generateCacheKey(endpoint, method);\n\n const cachedResult = await this.tryGetFromCache<T>(cacheKey, method);\n if (cachedResult) {\n return cachedResult;\n }\n\n logger.debug(messages.request.performing(method, url));\n const headers = this.buildRequestHeaders();\n\n try {\n const data = await this.performRequest<T>(url, headers);\n await this.tryCacheResponse(cacheKey, data, method);\n return data;\n } catch (error) {\n return this.handleRequestError(error, url);\n }\n }\n\n private async tryGetFromCache<T>(cacheKey: string, method: string): Promise<T | null> {\n if (!this.cache || !this.cacheEnabled || method !== 'GET') {\n return null;\n }\n\n try {\n const cachedData = await this.cache.get<T>(cacheKey);\n if (cachedData) {\n return cachedData;\n }\n } catch {\n // Cache layer logs retrieval failures\n }\n\n return null;\n }\n\n private buildRequestHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n Accept: 'application/vnd.github.v3+json',\n 'User-Agent': this.userAgent,\n };\n\n if (this.githubToken) {\n headers['Authorization'] = `token ${this.githubToken}`;\n }\n\n return headers;\n }\n\n private async performRequest<T>(url: string, headers: Record<string, string>): Promise<T> {\n const logger = this.logger.getSubLogger({ name: 'performRequest' });\n const responseBuffer = await this.downloader.download(logger, url, { headers });\n if (!responseBuffer || responseBuffer.length === 0) {\n logger.debug(messages.request.emptyResponse(url));\n throw new NetworkError(this.logger, 'Empty response received from API', url);\n }\n const responseText = responseBuffer.toString('utf-8');\n return JSON.parse(responseText) as T;\n }\n\n private async tryCacheResponse<T>(cacheKey: string, data: T, method: string): Promise<void> {\n if (!this.cache || !this.cacheEnabled || method !== 'GET') {\n return;\n }\n\n try {\n await this.cache.set<T>(cacheKey, data, this.cacheTtlMs);\n } catch {\n // Cache layer logs storage failures\n }\n }\n\n private handleRequestError(error: unknown, url: string): never {\n const logger = this.logger.getSubLogger({ name: 'handleRequestError' });\n logger.debug(messages.errors.requestFailure(url), error);\n\n if (error instanceof NotFoundError) {\n logger.debug(messages.errors.notFound(url), error.responseBody);\n throw new Error(`GitHub resource not found: ${url}. Status: ${error.statusCode}`);\n }\n if (error instanceof RateLimitError) {\n return this.handleRateLimitError(error, url);\n }\n if (error instanceof ForbiddenError) {\n return this.handleForbiddenError(error, url);\n }\n if (error instanceof ClientError) {\n return this.handleClientError(error, url);\n }\n if (error instanceof ServerError) {\n return this.handleServerError(error, url);\n }\n if (error instanceof HttpError) {\n return this.handleHttpError(error, url);\n }\n if (error instanceof NetworkError) {\n return this.handleNetworkError(error, url);\n }\n\n return this.handleUnknownError(error, url);\n }\n\n private handleRateLimitError(error: RateLimitError, url: string): never {\n const logger = this.logger.getSubLogger({ name: 'handleRateLimitError' });\n const resetTime = error.resetTimestamp ? new Date(error.resetTimestamp).toISOString() : 'N/A';\n logger.debug(messages.errors.rateLimit(url, resetTime), error.responseBody);\n throw new GitHubApiClientError(\n `GitHub API rate limit exceeded for ${url}. Status: ${error.statusCode}. Resets at ${resetTime}.`,\n error.statusCode,\n error,\n );\n }\n\n private handleForbiddenError(error: ForbiddenError, url: string): never {\n const logger = this.logger.getSubLogger({ name: 'handleForbiddenError' });\n logger.debug(messages.errors.forbidden(url), error.responseBody);\n throw new GitHubApiClientError(\n `GitHub API request forbidden for ${url}. Status: ${error.statusCode}.`,\n error.statusCode,\n error,\n );\n }\n\n private handleClientError(error: ClientError, url: string): never {\n const logger = this.logger.getSubLogger({ name: 'handleClientError' });\n logger.debug(messages.errors.client(url, error.statusCode), error.responseBody);\n throw new GitHubApiClientError(\n `GitHub API client error for ${url}. Status: ${error.statusCode} ${error.statusText}.`,\n error.statusCode,\n error,\n );\n }\n\n private handleServerError(error: ServerError, url: string): never {\n const logger = this.logger.getSubLogger({ name: 'handleServerError' });\n logger.debug(messages.errors.server(url, error.statusCode), error.responseBody);\n throw new GitHubApiClientError(\n `GitHub API server error for ${url}. Status: ${error.statusCode} ${error.statusText}.`,\n error.statusCode,\n error,\n );\n }\n\n private handleHttpError(error: HttpError, url: string): never {\n const logger = this.logger.getSubLogger({ name: 'handleHttpError' });\n logger.debug(messages.errors.http(url, error.statusCode), error);\n throw new GitHubApiClientError(\n `GitHub API HTTP error for ${url}. Status: ${error.statusCode} ${error.statusText}.`,\n error.statusCode,\n error,\n );\n }\n\n private handleNetworkError(error: NetworkError, url: string): never {\n const logger = this.logger.getSubLogger({ name: 'handleNetworkError' });\n logger.debug(messages.errors.network(url), error);\n throw new GitHubApiClientError(`Network error while requesting ${url}: ${error.message}`, undefined, error);\n }\n\n private handleUnknownError(error: unknown, url: string): never {\n const logger = this.logger.getSubLogger({ name: 'handleUnknownError' });\n logger.debug(messages.errors.unknown(url), error);\n if (error instanceof Error) {\n throw new GitHubApiClientError(\n `Unknown error during GitHub API request to ${url}: ${error.message}`,\n undefined,\n error,\n );\n }\n throw new GitHubApiClientError(`Unknown error during GitHub API request to ${url}`);\n }\n\n async getLatestRelease(owner: string, repo: string): Promise<IGitHubRelease | null> {\n const logger = this.logger.getSubLogger({ name: 'getLatestRelease' });\n logger.debug(messages.releases.fetchingLatest(owner, repo));\n try {\n return await this.request<IGitHubRelease>(`/repos/${owner}/${repo}/releases/latest`);\n } catch (error) {\n if (error instanceof Error && error.message.includes('GitHub resource not found')) {\n logger.debug(messages.releases.latestNotFound(owner, repo));\n return null;\n }\n logger.debug(messages.releases.latestError(owner, repo), error);\n throw error;\n }\n }\n\n async getReleaseByTag(owner: string, repo: string, tag: string): Promise<IGitHubRelease | null> {\n const logger = this.logger.getSubLogger({ name: 'getReleaseByTag' });\n logger.debug(messages.releases.fetchingByTag(tag, owner, repo));\n try {\n return await this.request<IGitHubRelease>(`/repos/${owner}/${repo}/releases/tags/${tag}`);\n } catch (error) {\n if (error instanceof Error && error.message.includes('GitHub resource not found')) {\n logger.debug(messages.releases.tagNotFound(tag, owner, repo));\n return null;\n }\n logger.debug(messages.releases.tagError(tag, owner, repo), error);\n throw error;\n }\n }\n\n async getAllReleases(\n owner: string,\n repo: string,\n options?: { perPage?: number; includePrerelease?: boolean; limit?: number; },\n ): Promise<IGitHubRelease[]> {\n const logger = this.logger.getSubLogger({ name: 'getAllReleases' });\n logger.debug(messages.releases.fetchingAll(owner, repo), options);\n const perPage = options?.perPage || 30; // Default to 30, max 100\n const limit = options?.limit;\n let page = 1;\n let allReleases: IGitHubRelease[] = [];\n let keepFetching = true;\n\n while (keepFetching) {\n const endpoint = `/repos/${owner}/${repo}/releases?per_page=${perPage}&page=${page}`;\n logger.debug(messages.releases.fetchingPage(page, endpoint));\n const releasesPage = await this.request<IGitHubRelease[]>(endpoint);\n\n if (releasesPage.length === 0) {\n keepFetching = false;\n } else {\n allReleases = allReleases.concat(releasesPage);\n page++;\n if (releasesPage.length < perPage) {\n keepFetching = false; // Last page\n }\n // Stop if we've reached the limit\n if (limit !== undefined && allReleases.length >= limit) {\n allReleases = allReleases.slice(0, limit);\n keepFetching = false;\n }\n }\n }\n logger.debug(messages.releases.totalFetched(allReleases.length, owner, repo));\n\n if (options?.includePrerelease === false) {\n // GitHub API doesn't directly filter out prereleases in the /releases endpoint AFAIK\n // We need to filter them client-side if includePrerelease is explicitly false.\n // If includePrerelease is true or undefined, we return all (including prereleases).\n const filteredReleases = allReleases.filter((release) => !release.prerelease);\n logger.debug(messages.releases.filteredPrereleases(filteredReleases.length));\n return filteredReleases;\n }\n\n return allReleases;\n }\n\n async getReleaseByConstraint(owner: string, repo: string, constraint: string): Promise<IGitHubRelease | null> {\n const logger = this.logger.getSubLogger({ name: 'getReleaseByConstraint' });\n logger.debug(messages.constraints.searching(constraint, owner, repo));\n\n if (constraint === 'latest') {\n return await this.handleLatestConstraint(owner, repo);\n }\n\n return await this.findReleaseByVersionConstraint(owner, repo, constraint);\n }\n\n private async handleLatestConstraint(owner: string, repo: string): Promise<IGitHubRelease | null> {\n try {\n return await this.getLatestRelease(owner, repo);\n } catch (error) {\n const logger = this.logger.getSubLogger({ name: 'handleLatestConstraint' });\n logger.debug(messages.errors.constraintLatestError(), error);\n return null;\n }\n }\n\n private async findReleaseByVersionConstraint(\n owner: string,\n repo: string,\n constraint: string,\n ): Promise<IGitHubRelease | null> {\n const logger = this.logger.getSubLogger({ name: 'findReleaseByVersionConstraint' });\n logger.debug(messages.constraints.pageFetch(constraint));\n\n let latestSatisfyingRelease: IGitHubRelease | null = null;\n let latestSatisfyingVersionClean: string | null = null;\n let page = 1;\n const perPage = 30;\n const maxPages = 100;\n\n while (page <= maxPages) {\n const releasesPage = await this.fetchReleasesPage(owner, repo, page, perPage, constraint);\n if (!releasesPage) {\n break;\n }\n\n if (releasesPage.length === 0) {\n break;\n }\n\n const bestFromPage = this.findBestReleaseFromPage(\n releasesPage,\n constraint,\n latestSatisfyingRelease,\n latestSatisfyingVersionClean,\n );\n\n if (bestFromPage.release) {\n latestSatisfyingRelease = bestFromPage.release;\n latestSatisfyingVersionClean = bestFromPage.version;\n }\n\n if (releasesPage.length < perPage) {\n break;\n }\n\n page++;\n }\n\n this.logConstraintResult(constraint, latestSatisfyingRelease);\n return latestSatisfyingRelease;\n }\n\n private async fetchReleasesPage(\n owner: string,\n repo: string,\n page: number,\n perPage: number,\n constraint: string,\n ): Promise<IGitHubRelease[] | null> {\n const endpoint = `/repos/${owner}/${repo}/releases?per_page=${perPage}&page=${page}`;\n const logger = this.logger.getSubLogger({ name: 'fetchReleasesPage' });\n logger.debug(messages.constraints.pageRequest(page, owner, repo));\n\n try {\n return await this.request<IGitHubRelease[]>(endpoint);\n } catch (error) {\n logger.debug(messages.errors.constraintError(constraint, owner, repo), error);\n return null;\n }\n }\n\n private findBestReleaseFromPage(\n releasesPage: IGitHubRelease[],\n constraint: string,\n currentBest: IGitHubRelease | null,\n currentBestVersion: string | null,\n ): { release: IGitHubRelease | null; version: string | null; } {\n const logger = this.logger.getSubLogger({ name: 'findBestReleaseFromPage' });\n let bestRelease = currentBest;\n let bestVersion = currentBestVersion;\n\n for (const release of releasesPage) {\n if (!release.tag_name) {\n continue;\n }\n\n const cleanVersion = release.tag_name.startsWith('v') ? release.tag_name.substring(1) : release.tag_name;\n\n if (this.isVersionSatisfying(cleanVersion, constraint)) {\n if (this.isBetterVersion(cleanVersion, bestVersion)) {\n bestRelease = release;\n bestVersion = cleanVersion;\n logger.debug(messages.constraints.bestCandidate(release.tag_name, cleanVersion));\n }\n }\n }\n\n return { release: bestRelease, version: bestVersion };\n }\n\n private isVersionSatisfying(version: string, constraint: string): boolean {\n return Boolean(semver.valid(version)) && semver.satisfies(version, constraint, { includePrerelease: true });\n }\n\n private isBetterVersion(newVersion: string, currentBestVersion: string | null): boolean {\n return !currentBestVersion || semver.gt(newVersion, currentBestVersion);\n }\n\n private logConstraintResult(constraint: string, result: IGitHubRelease | null): void {\n const logger = this.logger.getSubLogger({ name: 'logConstraintResult' });\n if (result) {\n logger.debug(messages.constraints.resultFound(constraint, result.tag_name));\n } else {\n logger.debug(messages.constraints.resultMissing(constraint));\n }\n }\n\n async getRateLimit(): Promise<IGitHubRateLimit> {\n const logger = this.logger.getSubLogger({ name: 'getRateLimit' });\n logger.debug(messages.rateLimit.fetching());\n // The actual rate limit data is nested under \"resources\"\n type RateLimitResponse = {\n resources: {\n core: IGitHubRateLimit;\n search: IGitHubRateLimit;\n graphql: IGitHubRateLimit;\n integration_manifest: IGitHubRateLimit;\n source_import: IGitHubRateLimit;\n code_scanning_upload: IGitHubRateLimit;\n actions_runner_registration: IGitHubRateLimit;\n scim: IGitHubRateLimit;\n };\n rate: IGitHubRateLimit; // This is the primary one usually referred to\n };\n const response = await this.request<RateLimitResponse>('/rate_limit');\n return response.resources.core; // Or response.rate, depending on which one is more relevant\n }\n\n async getLatestReleaseTags(owner: string, repo: string, count: number = 5): Promise<string[]> {\n const logger = this.logger.getSubLogger({ name: 'getLatestReleaseTags' });\n logger.debug(messages.releases.fetchingLatestTags(owner, repo, count));\n\n try {\n // Fetch just enough releases to get the requested count\n const releases = await this.request<IGitHubRelease[]>(`/repos/${owner}/${repo}/releases?per_page=${count}`);\n const tags: string[] = releases.map((release) => release.tag_name);\n logger.debug(messages.releases.fetchedTags(tags.length));\n return tags;\n } catch (error) {\n logger.debug(messages.releases.fetchTagsError(owner, repo), error);\n return [];\n }\n }\n\n async probeLatestTag(owner: string, repo: string): Promise<string | null> {\n const logger = this.logger.getSubLogger({ name: 'probeLatestTag' });\n logger.debug(messages.tagPattern.probing(owner, repo));\n\n // Use github.com (not api.github.com) - this does NOT count against API rate limits\n const probeUrl = `https://github.com/${owner}/${repo}/releases/latest`;\n\n try {\n // HEAD request with redirect: 'manual' to capture the redirect location\n const response = await fetch(probeUrl, {\n method: 'HEAD',\n redirect: 'manual',\n });\n\n // GitHub returns 302 redirect to the actual release tag URL\n const location = response.headers.get('location');\n if (!location) {\n logger.debug(messages.tagPattern.noRedirect(owner, repo));\n return null;\n }\n\n // Extract tag from URL: https://github.com/{owner}/{repo}/releases/tag/{tag}\n const tagMatch = location.match(/\\/releases\\/tag\\/(.+)$/);\n const extractedTag = tagMatch?.[1];\n if (!extractedTag) {\n logger.debug(messages.tagPattern.noRedirect(owner, repo));\n return null;\n }\n\n const tag = decodeURIComponent(extractedTag);\n logger.debug(messages.tagPattern.detected(tag));\n return tag;\n } catch (error) {\n logger.debug(messages.tagPattern.probeFailed(owner, repo), error);\n return null;\n }\n }\n}\n",
|
|
120
|
+
"import type { IArchitecturePatterns, IArchitectureRegex } from './types';\n\n// Escape special regex characters in pattern strings\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Creates a set of combined regular expression patterns from architecture patterns.\n *\n * These patterns can be used to match against GitHub release asset names to find\n * compatible binaries. The function escapes special regex characters in the input\n * patterns to ensure they are treated as literal strings.\n *\n * @param patterns - The architecture patterns generated by `getArchitecturePatterns`.\n * @returns An object containing combined regex patterns for system, CPU, and variants.\n */\nexport function createArchitectureRegex(patterns: IArchitecturePatterns): IArchitectureRegex {\n // Create alternations for each pattern group\n const systemPattern = patterns.system.length > 0 ? `(${patterns.system.map(escapeRegex).join('|')})` : '';\n const cpuPattern = patterns.cpu.length > 0 ? `(${patterns.cpu.map(escapeRegex).join('|')})` : '';\n const variantPattern = patterns.variants.length > 0 ? `(${patterns.variants.map(escapeRegex).join('|')})` : '';\n\n const result: IArchitectureRegex = {\n systemPattern,\n cpuPattern,\n variantPattern,\n };\n\n return result;\n}\n",
|
|
121
|
+
"import type { ISystemInfo } from '@dotfiles/core';\nimport { Architecture, Platform } from '@dotfiles/core';\nimport type { IArchitecturePatterns } from './types';\n\n/**\n * Generates a set of architecture-specific patterns based on the provided\n * system information.\n *\n * This function translates system properties like OS and CPU architecture into a\n * collection of string patterns that are commonly found in the names of release\n * assets on platforms like GitHub. The logic is based on the architecture\n * detection mechanism used in Zinit.\n *\n * @param systemInfo - An object containing system information, typically from `os.platform()` and `os.arch()`.\n * @returns An object containing arrays of patterns for the system, CPU, and variants.\n *\n * @see {@link https://github.com/zdharma-continuum/zinit/blob/master/zinit-install.zsh} for the original implementation.\n */\nexport function getArchitecturePatterns(systemInfo: ISystemInfo): IArchitecturePatterns {\n const patterns: IArchitecturePatterns = {\n system: [],\n cpu: [],\n variants: [],\n };\n\n // Based on the Zinit script, the order of pattern matching is roughly:\n // 1. OS (e.g., 'darwin', 'linux')\n // 2. Architecture (e.g., 'amd64', 'arm64')\n // 3. Variants (e.g., 'musl', 'gnu', 'eabihf')\n // This function generates the patterns; the matching logic is in `selectBestMatch`.\n\n // Handle OS/Platform patterns\n // Zinit logic for OS detection:\n // https://github.com/zdharma-continuum/zinit/blob/158796e49c553293228c02b043c6373878500533/zinit-install.zsh#L194-L208\n switch (systemInfo.platform) {\n case Platform.MacOS:\n // Zinit original:\n // _sys='(apple|darwin|apple-darwin|dmg|mac((-|)os|)|os(-|64|)x)'\n // where mac((-|)os|) expands to: mac, mac-os, macos\n // and os(-|64|)x expands to: osx, os-x, os64x\n patterns.system = ['apple', 'darwin', 'apple-darwin', 'dmg', 'mac', 'macos', 'mac-os', 'osx', 'os-x', 'os64x'];\n patterns.variants = ['darwin'];\n break;\n\n case Platform.Linux:\n // Zinit original:\n // _sys='(musl|gnu)*~^*(unknown|)linux*'\n // This is a complex zsh pattern with negation. Breaking it down:\n // - The pattern matches (musl|gnu) followed by anything\n // - BUT excludes patterns that don't contain 'unknown' or 'linux'\n // For simplicity and correctness, we just use 'linux' for system matching\n // and provide musl/gnu/unknown-linux as variants\n patterns.system = ['linux'];\n patterns.variants = ['musl', 'gnu', 'unknown-linux'];\n break;\n\n case Platform.Windows:\n // Zinit original:\n // (MINGW* | MSYS* | CYGWIN* | Windows_NT)\n // _sys='pc-windows-gnu'\n //\n // We expand this to include common Windows platform identifiers\n patterns.system = ['windows', 'win32', 'win64', 'pc-windows-gnu'];\n patterns.variants = ['mingw', 'msys', 'cygwin', 'pc-windows'];\n break;\n\n default:\n // Handle Platform.None or unknown platforms\n // Return empty patterns since we can't match\n patterns.system = [];\n patterns.variants = [];\n break;\n }\n\n // Handle CPU Architecture patterns\n // Zinit logic for architecture detection:\n // https://github.com/zdharma-continuum/zinit/blob/158796e49c553293228c02b043c6373878500533/zinit-install.zsh#L210-L228\n switch (systemInfo.arch) {\n case Architecture.Arm64:\n // Zinit original:\n // (aarch64 | arm64)\n // _cpu='(arm|aarch)64'\n //\n // BUG FIX: Zinit includes 'arm' which incorrectly matches armv5/v6/v7\n // These are completely different architectures. ARM64/aarch64 is 64-bit ARMv8.\n // We only match specific 64-bit ARM patterns.\n patterns.cpu = ['arm64', 'aarch64', 'aarch'];\n break;\n\n case Architecture.X86_64:\n // Zinit original (partially):\n // (amd64 | i386 | i486 | i686| i786 | x64 | x86 | x86-64 | x86_64)\n // _cpu='(amd64|x86_64|x64)'\n //\n // Note: Zinit's case statement mixes 32-bit and 64-bit x86 architectures\n // and outputs the same pattern. We separate them correctly.\n patterns.cpu = ['amd64', 'x86_64', 'x64', 'x86-64'];\n break;\n\n default:\n // Handle Architecture.None or unknown architectures\n // Return empty patterns since we can't match\n patterns.cpu = [];\n break;\n }\n\n return patterns;\n}\n",
|
|
122
|
+
"import type { ISystemInfo } from '@dotfiles/core';\nimport { createArchitectureRegex } from './createArchitectureRegex';\nimport { getArchitecturePatterns } from './getArchitecturePatterns';\nimport type { IArchitectureRegex } from './types';\n\n/**\n * The main function that combines pattern generation and regex creation.\n *\n * This is the primary entry point for generating the architecture-specific\n * regular expressions used to match release assets. It takes system information,\n * generates the corresponding string patterns, and then compiles them into\n * a set of regex patterns.\n *\n * @param systemInfo - An object containing system information, such as OS and CPU architecture.\n * @returns An object containing combined regex patterns for asset matching.\n */\nexport function getArchitectureRegex(systemInfo: ISystemInfo): IArchitectureRegex {\n const patterns = getArchitecturePatterns(systemInfo);\n const regex = createArchitectureRegex(patterns);\n return regex;\n}\n",
|
|
123
|
+
"import type { ISystemInfo } from '@dotfiles/core';\nimport { getArchitecturePatterns } from './getArchitecturePatterns';\nimport { getArchitectureRegex } from './getArchitectureRegex';\n\n/**\n * Patterns for non-binary files that should be excluded from asset selection.\n * Based on zinit's junk filtering logic:\n * ```zsh\n * local junk='*((s(ha256|ig|um)|386|asc|md5|txt|vsix)*|(apk|b3|deb|json|pkg|rpm|sh|zst)(#e))';\n * filtered=( ${(m@)list:#(#i)${~junk}} )\n * ```\n *\n * Categories:\n * - Checksum files: .sha256, .sha256sum, .sha512, .sha1, .md5, .sum, SHASUMS*\n * - Signature files: .sig, .asc, .pem\n * - Metadata files: .json, .txt, .sbom\n * - Package formats: .deb, .rpm, .apk, .pkg\n * - Build artifacts: buildable-artifact, .vsix\n * - Other: .b3 (BLAKE3), .zst (zstd)\n */\nconst NON_BINARY_PATTERNS: RegExp[] = [\n // Checksum files\n /\\.sha\\d+(sum)?$/i,\n /\\.md5(sum)?$/i,\n /\\.sum$/i,\n /^shasums/i,\n // Signature files\n /\\.sig$/i,\n /\\.asc$/i,\n /\\.pem$/i,\n // Metadata files\n /\\.json$/i,\n /\\.txt$/i,\n /\\.sbom$/i,\n // Package formats (not portable binaries)\n /\\.deb$/i,\n /\\.rpm$/i,\n /\\.apk$/i,\n /\\.pkg$/i,\n // Build artifacts\n /buildable-artifact/i,\n /\\.vsix$/i,\n // Other non-binary formats\n /\\.b3$/i,\n /\\.zst$/i,\n];\n\n/**\n * Filters out non-binary files from asset names.\n * Only applies filter if it results in non-empty list (preserves candidates if all would be filtered).\n */\nfunction filterNonBinaryAssets(assetNames: string[]): string[] {\n const filtered = assetNames.filter((name) => !NON_BINARY_PATTERNS.some((pattern) => pattern.test(name)));\n // Only apply filter if it yields results (zinit behavior)\n return filtered.length > 0 ? filtered : assetNames;\n}\n\n/**\n * Applies a regex filter to a list of candidates, keeping the result only if\n * it yields a non-empty subset. This is the core of zinit's iterative filtering.\n *\n * ```shell\n * filtered=( ${(M)list[@]:#(#i)*${~part}*} ) && (( $#filtered > 0 )) && list=( ${filtered[@]} )\n * ```\n */\nfunction applySoftFilter(candidates: string[], pattern: string): string[] {\n const regex = new RegExp(pattern, 'i');\n const filtered = candidates.filter((name) => regex.test(name.toLowerCase()));\n return filtered.length > 0 ? filtered : candidates;\n}\n\n/**\n * Selects the best matching asset from a list based on architecture patterns,\n * using zinit's iterative filtering approach.\n *\n * The filtering works in two phases:\n * 1. **Hard filter**: System pattern must match. Assets that don't match the OS\n * are eliminated. If nothing matches, returns `undefined`.\n * 2. **Soft filters**: CPU and variant patterns are applied iteratively. Each\n * filter is only applied if it yields results AND there are still multiple\n * candidates. This handles assets that omit CPU identifiers (e.g.,\n * `onefetch-mac.tar.gz`) by treating them as architecture-agnostic.\n *\n * ```shell\n * # zinit filtering logic\n * for part in \"${parts[@]}\"; do\n * if (( $#list > 1 )); then\n * filtered=( ${(M)list[@]:#(#i)*${~part}*} ) && (( $#filtered > 0 )) && list=( ${filtered[@]} )\n * else\n * break\n * fi\n * done\n * ```\n *\n * @param assetNames - An array of asset names to select from.\n * @param systemInfo - An object containing the system's architecture information.\n * @returns The name of the best matching asset, or `undefined` if no suitable match is found.\n */\nexport function selectBestMatch(assetNames: string[], systemInfo: ISystemInfo): string | undefined {\n const architectureRegex = getArchitectureRegex(systemInfo);\n const patterns = getArchitecturePatterns(systemInfo);\n\n // First pass: filter out non-binary files (checksums, signatures, etc.)\n const binaryAssets = filterNonBinaryAssets(assetNames);\n\n // Hard filter: system pattern is required — assets must match the OS.\n let matches: string[];\n\n if (architectureRegex.systemPattern) {\n const systemRegex = new RegExp(architectureRegex.systemPattern, 'i');\n matches = binaryAssets.filter((name) => systemRegex.test(name.toLowerCase()));\n } else {\n matches = [...binaryAssets];\n }\n\n if (matches.length === 0) {\n return undefined;\n }\n\n // Soft filters: CPU then variants, applied iteratively (zinit behavior).\n // Each filter only narrows when it yields results and >1 candidates remain.\n const softFilters: string[] = [];\n\n if (architectureRegex.cpuPattern) {\n softFilters.push(architectureRegex.cpuPattern);\n }\n\n softFilters.push(...patterns.variants);\n\n for (const filter of softFilters) {\n if (matches.length <= 1) {\n break;\n }\n\n matches = applySoftFilter(matches, filter);\n }\n\n // Return the first match. If only one remains, it's the best one.\n // If multiple still remain, the first one is chosen as the default.\n return matches[0];\n}\n",
|
|
124
|
+
"import { selectBestMatch } from '@dotfiles/arch';\nimport { type IArchiveExtractor, isSupportedArchiveFile } from '@dotfiles/archive-extractor';\nimport type { ProjectConfig } from '@dotfiles/config';\nimport type {\n IDownloadContext,\n IExtractContext,\n IExtractResult,\n IGitHubRelease,\n IGitHubReleaseAsset,\n IInstallContext,\n ISystemInfo,\n} from '@dotfiles/core';\nimport { architectureToString, platformToString } from '@dotfiles/core';\nimport type { IDownloader } from '@dotfiles/downloader';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { HookExecutor, IInstallOptions } from '@dotfiles/installer';\nimport {\n downloadWithProgress,\n executeAfterDownloadHook as executeAfterDownloadHookUtil,\n executeAfterExtractHook as executeAfterExtractHookUtil,\n getBinaryPaths,\n setupBinariesFromArchive,\n setupBinariesFromDirectDownload,\n} from '@dotfiles/installer';\nimport type {\n GithubReleaseInstallParams,\n GithubReleaseToolConfig,\n IAssetSelectionContext,\n} from '@dotfiles/installer-github';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { normalizeVersion } from '@dotfiles/utils';\nimport path from 'node:path';\nimport { buildCorrectedTag } from './github-client';\nimport type { IGitHubApiClient } from './github-client';\nimport { messages } from './log-messages';\nimport { type AssetPattern, formatAssetPatternForLog, matchAssetPattern } from './matchAssetPattern';\nimport type { GitHubReleaseInstallResult, IGitHubReleaseInstallMetadata } from './types';\n\n/**\n * Install a tool from GitHub releases\n */\nexport async function installFromGitHubRelease(\n toolName: string,\n toolConfig: GithubReleaseToolConfig,\n context: IInstallContext,\n options: IInstallOptions | undefined,\n toolFs: IFileSystem,\n downloader: IDownloader,\n githubApiClient: IGitHubApiClient,\n archiveExtractor: IArchiveExtractor,\n projectConfig: ProjectConfig,\n hookExecutor: HookExecutor,\n parentLogger: TsLogger,\n): Promise<GitHubReleaseInstallResult> {\n const logger = parentLogger.getSubLogger({ name: 'installFromGitHubRelease' });\n logger.debug(messages.startingInstallation(toolName));\n\n if (!toolConfig.installParams || !('repo' in toolConfig.installParams)) {\n const result: GitHubReleaseInstallResult = {\n success: false,\n error: 'GitHub repository not specified in installParams',\n };\n return result;\n }\n\n const params = toolConfig.installParams;\n const repo = params.repo;\n const version = params.version || 'latest';\n\n // Parse owner and repo name for API calls\n const [owner, repoName] = repo.split('/');\n if (!owner || !repoName) {\n const result: GitHubReleaseInstallResult = {\n success: false,\n error: `Invalid GitHub repository format: ${repo}. Expected format: owner/repo`,\n };\n return result;\n }\n\n try {\n const release = await fetchGitHubRelease(repo, version, params.prerelease ?? false, githubApiClient, logger);\n if (!release.success) {\n const result: GitHubReleaseInstallResult = release;\n return result;\n }\n\n const asset = await selectAsset(release.data, params, context, logger);\n if (!asset.success) {\n const result: GitHubReleaseInstallResult = asset;\n return result;\n }\n\n const downloadUrl = constructDownloadUrl(asset.data.browser_download_url, projectConfig, logger);\n if (!downloadUrl.success) {\n const result: GitHubReleaseInstallResult = downloadUrl;\n return result;\n }\n\n // Use gh CLI download for clients that support it (e.g., for private repos)\n const downloadPath = path.join(context.stagingDir, asset.data.name);\n const downloadResult = await downloadAssetWithFallback(\n downloadUrl.data,\n asset.data,\n downloader,\n githubApiClient,\n owner,\n repoName,\n release.data.tag_name,\n downloadPath,\n options,\n logger,\n );\n if (!downloadResult.success) {\n const result: GitHubReleaseInstallResult = downloadResult;\n return result;\n }\n\n const postDownloadContext: IDownloadContext = {\n ...context,\n downloadPath: downloadResult.data.downloadPath,\n };\n\n const hookResult = await executeAfterDownloadHook(toolConfig, postDownloadContext, hookExecutor, toolFs, logger);\n if (!hookResult.success) {\n const result: GitHubReleaseInstallResult = hookResult;\n return result;\n }\n\n const resolvedVersion = normalizeVersion(release.data.tag_name);\n\n const installResult = await processAssetInstallation(\n asset.data,\n downloadResult.data.downloadPath,\n toolName,\n toolConfig,\n context,\n postDownloadContext,\n toolFs,\n archiveExtractor,\n hookExecutor,\n toolFs,\n logger,\n );\n if (!installResult.success) {\n const result: GitHubReleaseInstallResult = installResult;\n return result;\n }\n\n const binaryPaths = getBinaryPaths(toolConfig.binaries, context.stagingDir);\n\n const metadata: IGitHubReleaseInstallMetadata = {\n method: 'github-release',\n releaseUrl: release.data.html_url,\n publishedAt: release.data.published_at,\n releaseName: release.data.name,\n downloadUrl: downloadUrl.data,\n assetName: asset.data.name,\n };\n\n const result: GitHubReleaseInstallResult = {\n success: true,\n binaryPaths,\n version: resolvedVersion,\n originalTag: release.data.tag_name,\n metadata,\n };\n return result;\n } catch (error) {\n const result: GitHubReleaseInstallResult = {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n return result;\n }\n}\n\ntype OperationResult<T> = { success: true; data: T; } | { success: false; error: string; };\n\ninterface IDownloadAssetResultData {\n downloadPath: string;\n}\n\nconst TAG_SUGGESTIONS_COUNT = 5;\n\nexport async function fetchGitHubRelease(\n repo: string,\n version: string,\n includePrerelease: boolean,\n githubApiClient: IGitHubApiClient,\n parentLogger: TsLogger,\n): Promise<OperationResult<IGitHubRelease>> {\n const logger = parentLogger.getSubLogger({ name: 'fetchGitHubRelease' });\n const [owner, repoName] = repo.split('/');\n if (!owner || !repoName) {\n const result: OperationResult<IGitHubRelease> = {\n success: false,\n error: `Invalid GitHub repository format: ${repo}. Expected format: owner/repo`,\n };\n return result;\n }\n\n // Handle 'latest' version request\n if (version === 'latest') {\n logger.debug(messages.fetchLatest(repo));\n\n // When includePrerelease is true, we need to use getAllReleases because\n // GitHub's /releases/latest endpoint excludes prereleases by design\n if (includePrerelease) {\n const releases = await githubApiClient.getAllReleases(owner, repoName, {\n perPage: 1,\n includePrerelease: true,\n limit: 1,\n });\n const firstRelease = releases[0];\n if (!firstRelease) {\n const result: OperationResult<IGitHubRelease> = {\n success: false,\n error: `Failed to fetch latest release for ${repo}`,\n };\n return result;\n }\n const result: OperationResult<IGitHubRelease> = { success: true, data: firstRelease };\n return result;\n }\n\n const release = await githubApiClient.getLatestRelease(owner, repoName);\n if (!release) {\n const result: OperationResult<IGitHubRelease> = {\n success: false,\n error: `Failed to fetch latest release for ${repo}`,\n };\n return result;\n }\n const result: OperationResult<IGitHubRelease> = { success: true, data: release };\n return result;\n }\n\n // Try fetching with the exact version provided\n logger.debug(messages.fetchByTag(version, repo));\n const release = await githubApiClient.getReleaseByTag(owner, repoName, version);\n if (release) {\n const result: OperationResult<IGitHubRelease> = { success: true, data: release };\n return result;\n }\n\n // First attempt failed - try to detect the tag pattern\n const releaseWithCorrectedTag = await fetchWithTagPatternDetection(owner, repoName, version, githubApiClient, logger);\n if (releaseWithCorrectedTag) {\n const result: OperationResult<IGitHubRelease> = { success: true, data: releaseWithCorrectedTag };\n return result;\n }\n\n // All attempts failed - show available tags to help the user\n await showAvailableReleaseTags(owner, repoName, githubApiClient, logger);\n\n const result: OperationResult<IGitHubRelease> = {\n success: false,\n error: `Release '${version}' not found for ${repo}. Check the available tags above.`,\n };\n return result;\n}\n\nasync function fetchWithTagPatternDetection(\n owner: string,\n repoName: string,\n version: string,\n githubApiClient: IGitHubApiClient,\n parentLogger: TsLogger,\n): Promise<IGitHubRelease | null> {\n const logger = parentLogger.getSubLogger({ name: 'fetchWithTagPatternDetection' });\n\n // Probe the latest release to detect the tag pattern\n logger.debug(messages.detectingTagPattern());\n const latestTag = await githubApiClient.probeLatestTag(owner, repoName);\n\n if (!latestTag) {\n logger.debug(messages.tagPatternDetectionFailed());\n return null;\n }\n\n // Build corrected tag using detected pattern\n const correctedTag = buildCorrectedTag(latestTag, version);\n\n // If the corrected tag is different, try fetching with it\n if (correctedTag !== version) {\n logger.debug(messages.tryingCorrectedTag(correctedTag, version));\n const release = await githubApiClient.getReleaseByTag(owner, repoName, correctedTag);\n if (release) {\n logger.info(messages.usingCorrectedTag(correctedTag, version));\n return release;\n }\n }\n\n return null;\n}\n\nasync function showAvailableReleaseTags(\n owner: string,\n repoName: string,\n githubApiClient: IGitHubApiClient,\n parentLogger: TsLogger,\n): Promise<void> {\n const logger = parentLogger.getSubLogger({ name: 'showAvailableReleaseTags' });\n const tags = await githubApiClient.getLatestReleaseTags(owner, repoName, TAG_SUGGESTIONS_COUNT);\n\n if (tags.length === 0) {\n logger.error(messages.noReleaseTagsAvailable());\n return;\n }\n\n logger.info(messages.availableReleaseTags());\n for (const tag of tags) {\n logger.info(messages.releaseTagItem(tag));\n }\n}\n\nexport async function selectAsset(\n release: IGitHubRelease,\n params: GithubReleaseInstallParams,\n context: IInstallContext,\n logger: TsLogger,\n): Promise<OperationResult<IGitHubReleaseAsset>> {\n let asset: IGitHubReleaseAsset | undefined;\n\n if (params.assetSelector) {\n logger.debug(messages.assetSelectorCustom());\n const selectionContext: IAssetSelectionContext = {\n ...context,\n assets: release.assets,\n release,\n assetPattern: params.assetPattern,\n };\n asset = params.assetSelector(selectionContext);\n } else if (params.assetPattern) {\n logger.debug(messages.assetPatternMatch(formatAssetPatternForLog(params.assetPattern)));\n const pattern: AssetPattern = params.assetPattern;\n const matchingAssets = release.assets.filter((a) => matchAssetPattern(a.name, pattern));\n // Apply platform detection on pattern-filtered assets\n asset = findPlatformAsset(matchingAssets, context.systemInfo);\n // Fall back to first match if no platform-specific asset found\n if (!asset && matchingAssets.length > 0) {\n asset = matchingAssets[0];\n }\n } else {\n logger.debug(\n messages.assetPlatformMatch(\n platformToString(context.systemInfo.platform),\n architectureToString(context.systemInfo.arch),\n ),\n );\n asset = findPlatformAsset(release.assets, context.systemInfo);\n }\n\n if (!asset) {\n const result: OperationResult<IGitHubReleaseAsset> = {\n success: false,\n error: createAssetNotFoundError(release, params, context),\n };\n return result;\n }\n\n const result: OperationResult<IGitHubReleaseAsset> = { success: true, data: asset };\n return result;\n}\n\nfunction findPlatformAsset(assets: IGitHubReleaseAsset[], systemInfo: ISystemInfo): IGitHubReleaseAsset | undefined {\n const assetNames = assets.map((a) => a.name);\n const selectedName = selectBestMatch(assetNames, systemInfo);\n\n if (!selectedName) {\n return undefined;\n }\n\n return assets.find((a) => a.name === selectedName);\n}\n\nfunction createAssetNotFoundError(\n release: IGitHubRelease,\n params: GithubReleaseInstallParams,\n context: IInstallContext,\n): string {\n const availableAssetNames = release.assets.map((a) => a.name);\n const platform = platformToString(context.systemInfo.platform);\n const arch = architectureToString(context.systemInfo.arch);\n let searchedForMessage = '';\n\n if (params.assetSelector) {\n searchedForMessage = `using a custom assetSelector function for ${platform}/${arch}.`;\n } else if (params.assetPattern) {\n searchedForMessage = `for asset pattern: \"${params.assetPattern}\" for ${platform}/${arch}.`;\n } else {\n searchedForMessage = `for platform \"${platform}\" and architecture \"${arch}\".`;\n }\n\n const errorLines: string[] = [\n `No suitable asset found in release \"${release.tag_name}\" ${searchedForMessage}`,\n `Available assets in release \"${release.tag_name}\":`,\n ...availableAssetNames.map((name) => ` - ${name}`),\n ];\n\n return errorLines.join('\\n');\n}\n\nfunction constructDownloadUrl(\n rawBrowserDownloadUrl: string,\n projectConfig: ProjectConfig,\n logger: TsLogger,\n): OperationResult<string> {\n const customHost = projectConfig.github.host;\n const hasCustomHost = Boolean(customHost);\n logger.debug(messages.determiningDownloadUrl(rawBrowserDownloadUrl, hasCustomHost));\n\n try {\n const isAbsolute = isAbsoluteUrl(rawBrowserDownloadUrl);\n const downloadUrl = isAbsolute\n ? handleAbsoluteUrl(rawBrowserDownloadUrl, logger)\n : handleRelativeUrl(rawBrowserDownloadUrl, customHost, logger);\n\n if (!downloadUrl.success) {\n return downloadUrl;\n }\n\n logger.debug(messages.finalDownloadUrl(rawBrowserDownloadUrl, downloadUrl.data, hasCustomHost));\n\n return downloadUrl;\n } catch (error) {\n logger.error(messages.invalidUrl(rawBrowserDownloadUrl));\n logger.debug(messages.downloadUrlError(rawBrowserDownloadUrl, hasCustomHost), error);\n const result: OperationResult<string> = {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n return result;\n }\n}\n\nfunction isAbsoluteUrl(url: string): boolean {\n return URL.canParse(url);\n}\n\nfunction handleAbsoluteUrl(url: string, logger: TsLogger): OperationResult<string> {\n logger.debug(messages.usingAbsoluteUrl(url));\n const result: OperationResult<string> = { success: true, data: url };\n return result;\n}\n\nfunction handleRelativeUrl(rawUrl: string, customHost: string | undefined, logger: TsLogger): OperationResult<string> {\n if (!rawUrl.startsWith('/')) {\n logger.debug(messages.invalidRelativeUrl(rawUrl));\n const result: OperationResult<string> = {\n success: false,\n error: `Invalid asset download URL format: ${rawUrl}`,\n };\n return result;\n }\n\n let base = customHost && !customHost.includes('api.github.com') ? customHost : 'https://github.com';\n if (!/^https?:\\/\\//.test(base)) {\n base = `https:${base.startsWith('//') ? '' : '//'}${base}`;\n }\n const finalUrl = new URL(rawUrl, base);\n const downloadUrl = finalUrl.toString();\n logger.debug(messages.resolvedRelativeUrl(base, rawUrl, downloadUrl));\n const result: OperationResult<string> = { success: true, data: downloadUrl };\n return result;\n}\n\n/**\n * Downloads a release asset, preferring gh CLI download when available.\n * This is important for private repositories where HTTP download requires authentication.\n *\n * Falls back to HTTP download if:\n * - API client doesn't support downloadAsset (e.g., fetch-based client)\n * - gh CLI download fails\n */\nasync function downloadAssetWithFallback(\n downloadUrl: string,\n asset: IGitHubReleaseAsset,\n downloader: IDownloader,\n githubApiClient: IGitHubApiClient,\n owner: string,\n repoName: string,\n tag: string,\n downloadPath: string,\n options: IInstallOptions | undefined,\n logger: TsLogger,\n): Promise<OperationResult<IDownloadAssetResultData>> {\n // Try gh CLI download first if available (supports private repos)\n if (githubApiClient.downloadAsset) {\n logger.debug(messages.downloadingViaGhCli(asset.name));\n try {\n await githubApiClient.downloadAsset(owner, repoName, tag, asset.name, downloadPath);\n const data: IDownloadAssetResultData = { downloadPath };\n const result: OperationResult<IDownloadAssetResultData> = { success: true, data };\n return result;\n } catch (error) {\n // Log the error but continue to HTTP fallback\n logger.debug(\n messages.downloadingAsset(downloadUrl),\n error instanceof Error ? error : new Error(String(error)),\n );\n }\n }\n\n // Fall back to HTTP download\n logger.debug(messages.downloadingViaHttp(asset.name));\n try {\n await downloadWithProgress(logger, downloadUrl, downloadPath, asset.name, downloader, options);\n const data: IDownloadAssetResultData = { downloadPath };\n const result: OperationResult<IDownloadAssetResultData> = { success: true, data };\n return result;\n } catch (error) {\n const result: OperationResult<IDownloadAssetResultData> = {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n return result;\n }\n}\n\nasync function executeAfterDownloadHook(\n toolConfig: GithubReleaseToolConfig,\n postDownloadContext: IDownloadContext,\n hookExecutor: HookExecutor,\n fs: IFileSystem,\n logger: TsLogger,\n): Promise<OperationResult<void>> {\n const result = await executeAfterDownloadHookUtil(toolConfig, postDownloadContext, hookExecutor, fs, logger);\n if (result.success) {\n const finalResult: OperationResult<void> = { success: true, data: undefined };\n return finalResult;\n }\n\n const finalResult: OperationResult<void> = { success: false, error: result.error || 'Hook execution failed' };\n return finalResult;\n}\n\nasync function processAssetInstallation(\n asset: IGitHubReleaseAsset,\n downloadPath: string,\n toolName: string,\n toolConfig: GithubReleaseToolConfig,\n context: IInstallContext,\n postDownloadContext: IDownloadContext,\n toolFs: IFileSystem,\n archiveExtractor: IArchiveExtractor,\n hookExecutor: HookExecutor,\n fs: IFileSystem,\n logger: TsLogger,\n): Promise<OperationResult<void>> {\n if (isSupportedArchiveFile(asset.name)) {\n return await processArchiveInstallation(\n asset,\n downloadPath,\n toolName,\n toolConfig,\n context,\n postDownloadContext,\n toolFs,\n archiveExtractor,\n hookExecutor,\n fs,\n logger,\n );\n } else {\n await setupBinariesFromDirectDownload(toolFs, toolName, toolConfig, context, downloadPath, logger);\n const result: OperationResult<void> = { success: true, data: undefined };\n return result;\n }\n}\n\nasync function processArchiveInstallation(\n asset: IGitHubReleaseAsset,\n downloadPath: string,\n toolName: string,\n toolConfig: GithubReleaseToolConfig,\n context: IInstallContext,\n postDownloadContext: IDownloadContext,\n toolFs: IFileSystem,\n archiveExtractor: IArchiveExtractor,\n hookExecutor: HookExecutor,\n fs: IFileSystem,\n logger: TsLogger,\n): Promise<OperationResult<void>> {\n logger.debug(messages.extractingArchive(asset.name));\n\n const extractResult: IExtractResult = await archiveExtractor.extract(logger, downloadPath, {\n targetDir: context.stagingDir,\n });\n logger.debug(messages.archiveExtracted(extractResult.extractedFiles.length, extractResult.executables.length));\n\n const postExtractContext: IExtractContext = {\n ...postDownloadContext,\n extractDir: context.stagingDir,\n extractResult,\n };\n\n const hookResult = await executeAfterExtractHook(toolConfig, postExtractContext, fs, hookExecutor, logger);\n if (!hookResult.success) {\n return hookResult;\n }\n\n await setupBinariesFromArchive(toolFs, toolName, toolConfig, context, context.stagingDir, logger);\n\n if (await toolFs.exists(downloadPath)) {\n logger.debug(messages.cleaningArchive(downloadPath));\n await toolFs.rm(downloadPath);\n }\n\n const result: OperationResult<void> = { success: true, data: undefined };\n return result;\n}\n\nasync function executeAfterExtractHook(\n toolConfig: GithubReleaseToolConfig,\n postExtractContext: IExtractContext,\n fs: IFileSystem,\n hookExecutor: HookExecutor,\n logger: TsLogger,\n): Promise<OperationResult<void>> {\n const result = await executeAfterExtractHookUtil(toolConfig, postExtractContext, hookExecutor, fs, logger);\n if (result.success) {\n const finalResult: OperationResult<void> = { success: true, data: undefined };\n return finalResult;\n }\n\n const finalResult: OperationResult<void> = { success: false, error: result.error || 'Hook execution failed' };\n return finalResult;\n}\n",
|
|
125
|
+
"import { createSafeLogMessage, type SafeLogMessageMap } from '@dotfiles/logger';\n\nexport const messages = {\n startingInstallation: (toolName: string) => createSafeLogMessage(`Starting installation: ${toolName}`),\n fetchLatest: (repo: string) => createSafeLogMessage(`Getting latest release for ${repo}`),\n fetchByTag: (version: string, repo: string) => createSafeLogMessage(`Fetching release ${version} for ${repo}`),\n assetSelectorCustom: () => createSafeLogMessage('Using custom asset selector'),\n assetPatternMatch: (pattern: string) => createSafeLogMessage(`Finding asset matching pattern: ${pattern}`),\n assetPlatformMatch: (platform: string, arch: string) =>\n createSafeLogMessage(`Selecting asset for platform ${platform} and architecture ${arch}`),\n determiningDownloadUrl: (rawUrl: string, hasCustomHost: boolean) =>\n createSafeLogMessage(`Determining download URL. rawBrowserDownloadUrl=\"${rawUrl}\", hasCustomHost=${hasCustomHost}`),\n usingAbsoluteUrl: (url: string) => createSafeLogMessage(`Using absolute browser_download_url directly: \"${url}\"`),\n invalidRelativeUrl: (rawUrl: string) => createSafeLogMessage(`Invalid asset download URL format: ${rawUrl}`),\n resolvedRelativeUrl: (base: string, rawUrl: string, resolved: string) =>\n createSafeLogMessage(`Resolved relative URL. Base: \"${base}\", Relative Path: \"${rawUrl}\", Result: \"${resolved}\"`),\n finalDownloadUrl: (rawUrl: string, resolved: string, hasCustomHost: boolean) =>\n createSafeLogMessage(\n `Final download URL determined. Raw: \"${rawUrl}\", Result: \"${resolved}\", hasCustomHost=${hasCustomHost}`,\n ),\n downloadUrlError: (rawUrl: string, hasCustomHost: boolean) =>\n createSafeLogMessage(`Download URL construction failed: Raw: \"${rawUrl}\", hasCustomHost=${hasCustomHost}`),\n downloadingAsset: (downloadUrl: string) => createSafeLogMessage(`Downloading asset: ${downloadUrl}`),\n downloadingViaGhCli: (assetName: string) =>\n createSafeLogMessage(`Downloading ${assetName} via gh release download (authenticated)`),\n downloadingViaHttp: (assetName: string) => createSafeLogMessage(`Downloading ${assetName} via HTTP`),\n extractingArchive: (assetName: string) => createSafeLogMessage(`Extracting archive: ${assetName}`),\n archiveExtracted: (fileCount: number, executableCount: number) =>\n createSafeLogMessage(`Archive extracted. fileCount=${fileCount}, executableCount=${executableCount}`),\n cleaningArchive: (downloadPath: string) => createSafeLogMessage(`Cleaning up downloaded archive: ${downloadPath}`),\n invalidUrl: (url: string) => createSafeLogMessage(`Invalid URL: ${url}`),\n versionResolutionResolved: (toolName: string, version: string) =>\n createSafeLogMessage(`Resolved version for ${toolName}: ${version}`),\n versionResolutionFailed: (toolName: string, error: string) =>\n createSafeLogMessage(`Failed to resolve version for ${toolName}: ${error}`),\n versionResolutionException: (toolName: string) =>\n createSafeLogMessage(`Exception while resolving version for ${toolName}`),\n updateCheckFailed: (toolName: string) => createSafeLogMessage(`Failed to check update for ${toolName}`),\n detectingTagPattern: () => createSafeLogMessage('Detecting tag pattern from latest release'),\n tagPatternDetectionFailed: () => createSafeLogMessage('Failed to detect tag pattern'),\n tryingCorrectedTag: (correctedTag: string, originalTag: string) =>\n createSafeLogMessage(`Trying corrected tag '${correctedTag}' (original: '${originalTag}')`),\n usingCorrectedTag: (correctedTag: string, originalTag: string) =>\n createSafeLogMessage(`Found release with corrected tag '${correctedTag}' (you specified: '${originalTag}')`),\n availableReleaseTags: () => createSafeLogMessage('Available release tags:'),\n releaseTagItem: (tag: string) => createSafeLogMessage(` - ${tag}`),\n noReleaseTagsAvailable: () => createSafeLogMessage('No release tags available for this repository'),\n} as const satisfies SafeLogMessageMap;\n",
|
|
126
|
+
"import { minimatch } from 'minimatch';\n\nexport type AssetPattern = string | RegExp;\n\nfunction isValidRegexFlags(flags: string): boolean {\n return /^[dgimsuvy]*$/.test(flags);\n}\n\nfunction isRegexPatternString(value: string): boolean {\n if (!value.startsWith('/')) {\n return false;\n }\n\n const lastSlashIndex = value.lastIndexOf('/');\n if (lastSlashIndex <= 0) {\n return false;\n }\n\n return true;\n}\n\nfunction parseRegexPatternString(value: string): RegExp {\n const lastSlashIndex = value.lastIndexOf('/');\n if (lastSlashIndex <= 0) {\n throw new Error('Invalid regex string: missing closing \"/\"');\n }\n\n const pattern = value.slice(1, lastSlashIndex);\n const flags = value.slice(lastSlashIndex + 1);\n\n if (!isValidRegexFlags(flags)) {\n throw new Error('Invalid regex string: invalid flags');\n }\n\n const regex = new RegExp(pattern, flags);\n return regex;\n}\n\nexport function isValidAssetPatternString(value: string): boolean {\n if (!isRegexPatternString(value)) {\n return true;\n }\n\n try {\n parseRegexPatternString(value);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function formatAssetPatternForLog(assetPattern: AssetPattern): string {\n if (typeof assetPattern === 'string') {\n return assetPattern;\n }\n\n return assetPattern.toString();\n}\n\nexport function matchAssetPattern(candidate: string, assetPattern: AssetPattern): boolean {\n if (typeof assetPattern === 'string') {\n if (isRegexPatternString(assetPattern)) {\n const regex = parseRegexPatternString(assetPattern);\n return regex.test(candidate);\n }\n\n return minimatch(candidate, assetPattern);\n }\n\n return assetPattern.test(candidate);\n}\n",
|
|
127
|
+
"import type { BaseInstallParams, IGitHubRelease, IGitHubReleaseAsset, IInstallContext } from '@dotfiles/core';\nimport { baseInstallParamsSchema } from '@dotfiles/core';\nimport { z } from 'zod';\nimport { type AssetPattern, isValidAssetPatternString } from '../matchAssetPattern';\n\n/**\n * Context object for asset selection functions.\n * Provides consistent interface with install hooks, including access to\n * system information, logging, tool configuration, and release data.\n */\nexport interface IAssetSelectionContext extends IInstallContext {\n /** Available release assets to choose from */\n assets: IGitHubReleaseAsset[];\n /** The GitHub release being processed */\n release: IGitHubRelease;\n /** Asset pattern from configuration (if provided) */\n assetPattern?: AssetPattern;\n}\n\n/**\n * Asset selector function signature using context object.\n * Receives rich context object for consistent interface with install hooks.\n *\n * @param context - Complete context including assets, system info, log, and configuration\n * @returns Selected asset or undefined if no suitable asset found\n *\n * @example\n * ```typescript\n * const assetSelector: AssetSelector = (context) => {\n * context.log.debug('Selecting asset for ' + context.toolName);\n *\n * const { assets, systemInfo } = context;\n * const osMap = { 'darwin': 'macos', 'linux': 'linux', 'win32': 'windows' };\n * const archMap = { 'x64': 'amd64', 'arm64': 'arm64' };\n *\n * const osKey = osMap[systemInfo.platform];\n * const archKey = archMap[systemInfo.arch];\n *\n * return assets.find(asset =>\n * asset.name.toLowerCase().includes(osKey) &&\n * asset.name.toLowerCase().includes(archKey) &&\n * asset.name.endsWith('.tar.gz')\n * );\n * };\n * ```\n */\nexport type AssetSelector = (context: IAssetSelectionContext) => IGitHubReleaseAsset | undefined;\n\nexport const githubReleaseInstallParamsSchema = baseInstallParamsSchema.extend({\n /**\n * The GitHub repository in \"owner/repo\" format (e.g., `junegunn/fzf`).\n * Corresponds to the main argument for Zinit's `from\"gh-r\"`.\n */\n repo: z.string().regex(/^[^/]+\\/[^/]+$/, 'Repository must be in \"owner/repo\" format'),\n /**\n * A glob pattern or regular expression string used to match the desired asset filename within a GitHub Release.\n * This helps select the correct file if a release has multiple assets (e.g., for different OS/architectures).\n * Example: `*linux_amd64.tar.gz` or `/fzf-.*-linux_amd64\\.tar\\.gz/`.\n * If `assetSelector` is provided, this pattern might be used by it or ignored.\n * Similar to Zinit's `bpick'{pattern}'` ice.\n * @example Zinit `bpick`:\n * ```zsh\n * zinit ice from\"gh-r\" bpick\"*linux_amd64.tar.gz\"\n * zinit light \"sharkdp/bat\"\n * ```\n */\n assetPattern: z\n .union([\n z.string().refine(isValidAssetPatternString, 'assetPattern must be a valid glob or a regex string like /.../'),\n z.instanceof(RegExp),\n ])\n .optional(),\n\n /**\n * A specific version string (e.g., `v1.2.3`, `0.48.0`) or a SemVer constraint\n * (e.g., `^1.0.0`, `~2.3.x`) for the release to target.\n * If omitted, the latest stable release is typically targeted.\n * Similar to Zinit's `ver'{version_tag}'` ice.\n * @example Zinit `ver`:\n * ```zsh\n * zinit ice ver\"v1.2.3\" from\"gh-r\"\n * zinit light \"user/mycli\"\n * ```\n */\n version: z.string().optional(),\n /**\n * An optional custom function to select the desired asset from a list of available assets for a release.\n * This provides more fine-grained control than `assetPattern` for complex selection logic.\n *\n * Uses context-based signature: `(context: IAssetSelectionContext) => IGitHubReleaseAsset | undefined`\n */\n assetSelector: z.custom<AssetSelector>((val) => typeof val === 'function', 'Must be a function').optional(),\n /**\n * When true, uses the `gh` CLI for GitHub API requests instead of direct fetch.\n * This is useful when:\n * - Working behind corporate proxies that `gh` handles better\n * - Leveraging existing `gh` authentication\n * - Avoiding rate limits (gh uses authenticated requests by default)\n *\n * Requires the `gh` CLI to be installed and authenticated.\n * Uses the same caching mechanism as the fetch-based client.\n *\n * @default false\n * @example\n * ```typescript\n * install('github-release', {\n * repo: 'junegunn/fzf',\n * ghCli: true,\n * }).bin('fzf')\n * ```\n */\n ghCli: z.boolean().optional(),\n /**\n * When true, includes prerelease versions when fetching the latest release.\n * By default, GitHub's `/releases/latest` endpoint excludes prereleases.\n * Set this to `true` for repositories that only publish prerelease versions.\n *\n * @default false\n * @example\n * ```typescript\n * install('github-release', {\n * repo: 'owner/repo-with-only-prereleases',\n * prerelease: true,\n * }).bin('tool')\n * ```\n */\n prerelease: z.boolean().optional(),\n});\n\n/**\n * Parameters for installing a tool from a GitHub Release.\n *\n * NOTE: This is an explicit interface (not z.infer) to ensure TypeScript fully resolves\n * the property names, which is required for proper `keyof` behavior in declaration files.\n */\nexport interface GithubReleaseInstallParams extends BaseInstallParams {\n /**\n * The GitHub repository in \"owner/repo\" format (e.g., `junegunn/fzf`).\n */\n repo: string;\n /**\n * A glob pattern or regular expression used to match the desired asset filename.\n * Example: `*linux_amd64.tar.gz` or `/fzf-.*-linux_amd64\\.tar\\.gz/`.\n */\n assetPattern?: string | RegExp;\n /**\n * A specific version string (e.g., `v1.2.3`, `0.48.0`) or a SemVer constraint.\n */\n version?: string;\n /**\n * A custom function to select the desired asset from available release assets.\n */\n assetSelector?: AssetSelector;\n /**\n * When true, uses the `gh` CLI for GitHub API requests instead of direct fetch.\n */\n ghCli?: boolean;\n /**\n * When true, includes prerelease versions when fetching the latest release.\n */\n prerelease?: boolean;\n}\n",
|
|
128
|
+
"import type { ToolConfig } from '@dotfiles/core';\nimport {\n baseToolConfigWithPlatformsSchema,\n binaryConfigSchema,\n type InferToolConfigWithPlatforms,\n} from '@dotfiles/core';\nimport { z } from 'zod';\nimport { githubReleaseInstallParamsSchema } from './githubReleaseInstallParamsSchema';\n\nexport const githubReleaseToolConfigSchema = baseToolConfigWithPlatformsSchema.extend({\n /** Resolved tool configuration for the 'github-release' installation method */\n installationMethod: z.literal('github-release'),\n /** GitHub release installation parameters */\n installParams: githubReleaseInstallParamsSchema,\n /** Binaries are typically required for this installation method */\n binaries: z.array(z.union([z.string().min(1), binaryConfigSchema])).min(1),\n});\n\n/**\n * Resolved tool configuration for the 'github-release' installation method.\n * Uses InferToolConfigWithPlatforms to properly type platformConfigs with PlatformConfigEntry[].\n */\nexport type GithubReleaseToolConfig = InferToolConfigWithPlatforms<typeof githubReleaseToolConfigSchema>;\n\n/**\n * Type guard to check if a config is a GitHub Release tool config\n */\nexport function isGitHubReleaseToolConfig(config: ToolConfig): config is GithubReleaseToolConfig {\n return config.installationMethod === 'github-release';\n}\n",
|
|
129
|
+
"import type { IArchiveExtractor } from '@dotfiles/archive-extractor';\nimport type { ProjectConfig } from '@dotfiles/config';\nimport type {\n IInstallContext,\n IInstallerPlugin,\n IInstallOptions,\n InstallResult,\n UpdateCheckResult,\n} from '@dotfiles/core';\nimport type { IDownloader } from '@dotfiles/downloader';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { HookExecutor } from '@dotfiles/installer';\nimport { createToolFileSystem } from '@dotfiles/installer';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { stripVersionPrefix } from '@dotfiles/utils';\nimport type { IGitHubApiClient } from './github-client';\nimport { fetchGitHubRelease, installFromGitHubRelease } from './installFromGitHubRelease';\nimport { messages } from './log-messages';\nimport {\n type GithubReleaseInstallParams,\n githubReleaseInstallParamsSchema,\n type GithubReleaseToolConfig,\n githubReleaseToolConfigSchema,\n} from './schemas';\nimport type { IGitHubReleaseInstallMetadata } from './types';\n\n/**\n * Installer plugin for tools distributed via GitHub Releases.\n *\n * This plugin handles tools that publish release artifacts on GitHub, supporting both\n * archive files (tar.gz, zip) and standalone binaries. It's one of the most commonly\n * used installers as many CLI tools distribute pre-compiled binaries through GitHub.\n *\n * **Key Features:**\n * - Automatic asset selection based on platform (macOS, Linux, Windows) and architecture (x86_64, arm64)\n * - Support for versioned releases and \"latest\" tag\n * - Automatic archive extraction for compressed releases\n * - Direct binary downloads for standalone executables\n * - Update checking via GitHub API\n * - Release asset pattern matching with glob patterns\n *\n * **Asset Selection:**\n * The plugin intelligently selects the appropriate release asset by:\n * - Matching platform keywords (darwin, linux, windows)\n * - Matching architecture keywords (x86_64, amd64, arm64, aarch64)\n * - Using user-defined patterns when auto-detection isn't sufficient\n * - Falling back to manual asset selection via configuration\n *\n * **Version Management:**\n * - Fetches release metadata from GitHub API\n * - Supports semantic versioning for update detection\n * - Tracks installed versions for update checking\n * - Can install specific versions or latest releases\n */\nexport class GitHubReleaseInstallerPlugin implements\n IInstallerPlugin<\n 'github-release',\n GithubReleaseInstallParams,\n GithubReleaseToolConfig,\n IGitHubReleaseInstallMetadata\n >\n{\n public readonly method = 'github-release' as const;\n public readonly displayName = 'GitHub Release';\n public readonly version = '1.0.0';\n\n // Zod schemas for validation\n public readonly paramsSchema = githubReleaseInstallParamsSchema;\n public readonly toolConfigSchema = githubReleaseToolConfigSchema;\n\n /**\n * Creates a new GitHubReleaseInstallerPlugin instance.\n *\n * @param fs - The file system interface for file operations.\n * @param downloader - The downloader for fetching release assets.\n * @param githubApiClient - The GitHub API client for fetching release metadata (fetch-based).\n * @param ghCliApiClient - Optional gh CLI-based API client for tools that prefer gh CLI.\n * @param archiveExtractor - The archive extractor for unpacking compressed releases.\n * @param projectConfig - The application configuration containing paths and settings.\n * @param hookExecutor - The hook executor for running lifecycle hooks.\n */\n constructor(\n private readonly fs: IFileSystem,\n private readonly downloader: IDownloader,\n private readonly githubApiClient: IGitHubApiClient,\n private readonly ghCliApiClient: IGitHubApiClient | undefined,\n private readonly archiveExtractor: IArchiveExtractor,\n private readonly projectConfig: ProjectConfig,\n private readonly hookExecutor: HookExecutor,\n ) {}\n\n /**\n * Returns the appropriate GitHub API client based on tool configuration.\n * If the tool has `ghCli: true` in installParams and a gh CLI client is available,\n * returns the gh CLI client. Otherwise returns the default fetch-based client.\n */\n private getApiClient(toolConfig: GithubReleaseToolConfig): IGitHubApiClient {\n const params = toolConfig.installParams as GithubReleaseInstallParams;\n if (params.ghCli && this.ghCliApiClient) {\n return this.ghCliApiClient;\n }\n return this.githubApiClient;\n }\n\n async install(\n toolName: string,\n toolConfig: GithubReleaseToolConfig,\n context: IInstallContext,\n options: IInstallOptions | undefined,\n logger: TsLogger,\n ): Promise<InstallResult<IGitHubReleaseInstallMetadata>> {\n // Create tool-specific file system\n const toolFs = createToolFileSystem(this.fs, toolName);\n\n // Delegate to existing implementation\n const result = await installFromGitHubRelease(\n toolName,\n toolConfig,\n context,\n options,\n toolFs,\n this.downloader,\n this.getApiClient(toolConfig),\n this.archiveExtractor,\n this.projectConfig,\n this.hookExecutor,\n logger,\n );\n\n return result;\n }\n\n /**\n * Resolves the version that will be installed by fetching release information from GitHub.\n * This allows the installer to create version-based directories instead of timestamp-based ones.\n *\n * @param toolName - Name of the tool (for logging purposes)\n * @param toolConfig - Complete tool configuration including repository and version\n * @param context - Installation context (not used but required by interface)\n * @param logger - Logger instance for debug output\n * @returns Normalized version string, or null if version cannot be resolved\n */\n async resolveVersion(\n toolName: string,\n toolConfig: GithubReleaseToolConfig,\n _context: IInstallContext,\n logger: TsLogger,\n ): Promise<string | null> {\n try {\n const params = toolConfig.installParams as GithubReleaseInstallParams;\n const version: string = toolConfig.version || 'latest';\n\n // Fetch release information from GitHub API\n const releaseResult = await fetchGitHubRelease(\n params.repo,\n version,\n params.prerelease ?? false,\n this.getApiClient(toolConfig),\n logger,\n );\n\n if (!releaseResult.success) {\n logger.debug(messages.versionResolutionFailed(toolName, releaseResult.error));\n return null;\n }\n\n // Strip v prefix and return the version from the release tag\n const normalizedVersion: string = stripVersionPrefix(releaseResult.data.tag_name);\n logger.debug(messages.versionResolutionResolved(toolName, normalizedVersion));\n return normalizedVersion;\n } catch (error) {\n logger.debug(messages.versionResolutionException(toolName), error);\n return null;\n }\n }\n\n supportsUpdate(): boolean {\n return true;\n }\n\n supportsUpdateCheck(): boolean {\n return true;\n }\n\n async checkUpdate(\n toolName: string,\n toolConfig: GithubReleaseToolConfig,\n _context: IInstallContext,\n logger: TsLogger,\n ): Promise<UpdateCheckResult> {\n try {\n const githubParams = toolConfig.installParams;\n const repo = githubParams.repo;\n const [owner, repoName] = repo.split('/');\n\n if (!owner || !repoName) {\n const result: UpdateCheckResult = {\n success: false,\n error: `Invalid repo format: ${repo}. Expected owner/repo`,\n };\n return result;\n }\n\n const latestRelease = await this.getApiClient(toolConfig).getLatestRelease(owner, repoName);\n if (!latestRelease || !latestRelease.tag_name) {\n const result: UpdateCheckResult = {\n success: false,\n error: `Could not fetch latest release for ${toolName}`,\n };\n return result;\n }\n\n const configuredVersion = toolConfig.version || 'latest';\n const latestVersion = latestRelease.tag_name.replace(/^v/, '');\n\n if (configuredVersion === 'latest') {\n const result: UpdateCheckResult = {\n success: true,\n hasUpdate: false,\n currentVersion: latestVersion,\n latestVersion,\n };\n return result;\n }\n\n const result: UpdateCheckResult = {\n success: true,\n hasUpdate: configuredVersion !== latestVersion,\n currentVersion: configuredVersion,\n latestVersion,\n };\n return result;\n } catch (error) {\n logger.error(messages.updateCheckFailed(toolName), error);\n const result: UpdateCheckResult = {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n };\n return result;\n }\n }\n\n supportsReadme(): boolean {\n return true;\n }\n\n getReadmeUrl(_toolName: string, toolConfig: GithubReleaseToolConfig): string | null {\n const githubParams = toolConfig.installParams;\n const repo = githubParams.repo;\n const [owner, repoName] = repo.split('/');\n\n if (!owner || !repoName) {\n return null;\n }\n\n const branch = toolConfig.version || 'main';\n return `https://raw.githubusercontent.com/${owner}/${repoName}/${branch}/README.md`;\n }\n}\n",
|
|
130
|
+
"import { type IArchiveExtractor, isSupportedArchiveFile } from '@dotfiles/archive-extractor';\nimport {\n createShell,\n type IDownloadContext,\n type IExtractResult,\n type IGitHubReleaseAsset,\n type IInstallContext,\n Platform,\n type Shell,\n} from '@dotfiles/core';\nimport type { IDownloader } from '@dotfiles/downloader';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { HookExecutor, IInstallOptions } from '@dotfiles/installer';\nimport {\n createToolFileSystem,\n downloadWithProgress,\n executeAfterDownloadHook,\n withInstallErrorHandling,\n} from '@dotfiles/installer';\nimport { normalizeBinaries } from '@dotfiles/installer';\nimport type { IGitHubApiClient } from '@dotfiles/installer-github';\nimport { fetchGitHubRelease, selectAsset } from '@dotfiles/installer-github';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { detectVersionViaCli } from '@dotfiles/utils';\nimport path from 'node:path';\nimport { messages } from './log-messages';\nimport type {\n DmgGitHubReleaseSource,\n DmgInstallParams,\n DmgSource,\n DmgToolConfig,\n DmgUrlSource,\n} from './schemas';\nimport type { DmgInstallResult, IDmgInstallMetadata } from './types';\n\ntype OperationResult<T> = { success: true; data: T; } | { success: false; error: string; };\n\ninterface IResolvedDmgSource {\n downloadPath: string;\n downloadName: string;\n sourceUrl: string;\n}\n\nexport async function installFromDmg(\n toolName: string,\n toolConfig: DmgToolConfig,\n context: IInstallContext,\n options: IInstallOptions | undefined,\n fs: IFileSystem,\n downloader: IDownloader,\n archiveExtractor: IArchiveExtractor,\n hookExecutor: HookExecutor,\n parentLogger: TsLogger,\n shellExecutor: Shell,\n githubApiClient?: IGitHubApiClient,\n ghCliApiClient?: IGitHubApiClient,\n): Promise<DmgInstallResult> {\n const toolFs = createToolFileSystem(fs, toolName);\n const logger = parentLogger.getSubLogger({ name: 'installFromDmg' });\n logger.debug(messages.installing(toolName));\n\n // Platform gate: silently skip on non-macOS\n if (context.systemInfo.platform !== Platform.MacOS) {\n logger.info(messages.skippingNonMacOS(toolName));\n return {\n success: true,\n binaryPaths: [],\n metadata: { method: 'dmg', dmgUrl: getSourceLabel(toolConfig.installParams.source) },\n };\n }\n\n const params: DmgInstallParams = toolConfig.installParams;\n\n const operation = async (): Promise<DmgInstallResult> => {\n // DMG installer is externally managed, so stagingDir is not pre-created by Installer.\n // Ensure it exists before any download writes into it.\n await fs.ensureDir(context.stagingDir);\n\n const resolvedSource = await resolveDmgSource(\n params.source,\n context,\n options,\n downloader,\n githubApiClient,\n ghCliApiClient,\n logger,\n );\n if (!resolvedSource.success) {\n return { success: false, error: resolvedSource.error };\n }\n\n // 2. Run after-download hook\n const postDownloadContext: IDownloadContext = {\n ...context,\n downloadPath: resolvedSource.data.downloadPath,\n };\n const afterDownloadResult = await executeAfterDownloadHook(\n toolConfig,\n postDownloadContext,\n hookExecutor,\n fs,\n logger,\n );\n if (!afterDownloadResult.success) {\n return { success: false, error: afterDownloadResult.error };\n }\n\n // 3. If downloaded file is an archive, extract it to find the .dmg inside\n let resolvedDmgPath = resolvedSource.data.downloadPath;\n if (isSupportedArchiveFile(resolvedSource.data.downloadName)) {\n logger.debug(messages.extractingArchive());\n const extractResult: IExtractResult = await archiveExtractor.extract(logger, resolvedSource.data.downloadPath, {\n targetDir: context.stagingDir,\n });\n logger.debug(messages.archiveExtracted(extractResult.extractedFiles.length));\n\n const dmgFile = extractResult.extractedFiles.find((f) => f.endsWith('.dmg'));\n if (!dmgFile) {\n logger.error(messages.noDmgInArchive());\n return { success: false, error: 'No .dmg file found in extracted archive' };\n }\n logger.debug(messages.dmgFoundInArchive(dmgFile));\n resolvedDmgPath = dmgFile;\n }\n\n // 4. Mount the DMG\n const loggingShell = createShell({ logger, skipCommandLog: true });\n const mountPoint = path.join(context.stagingDir, '.dmg-mount');\n await fs.ensureDir(mountPoint);\n logger.debug(messages.mountingDmg(resolvedDmgPath));\n await loggingShell`hdiutil attach -nobrowse -noautoopen -mountpoint ${mountPoint} ${resolvedDmgPath}`;\n logger.debug(messages.dmgMounted(mountPoint));\n\n let appName: string;\n let installedAppPath: string | undefined;\n try {\n // 5. Find the .app bundle\n const resolvedAppName = await findAppBundle(params.appName, mountPoint, fs, logger);\n if (!resolvedAppName) {\n return { success: false, error: 'No .app bundle found in DMG' };\n }\n appName = resolvedAppName;\n\n // 6. Copy the .app to /Applications\n const appSource = path.join(mountPoint, appName);\n const applicationsDir = '/Applications';\n await fs.ensureDir(applicationsDir);\n const appDest = path.join(applicationsDir, appName);\n\n if (await fs.exists(appDest)) {\n await fs.rm(appDest, { recursive: true, force: true });\n }\n\n logger.debug(messages.copyingApp(appName));\n await shellExecutor`cp -R ${appSource} ${appDest}`.quiet();\n installedAppPath = appDest;\n } finally {\n // 8. Always unmount\n logger.debug(messages.unmountingDmg(mountPoint));\n await shellExecutor`hdiutil detach ${mountPoint}`.quiet().noThrow();\n }\n\n // 9. Clean up downloaded DMG and archive\n if (await toolFs.exists(resolvedDmgPath)) {\n await toolFs.rm(resolvedDmgPath);\n }\n if (\n resolvedDmgPath !== resolvedSource.data.downloadPath && (await toolFs.exists(resolvedSource.data.downloadPath))\n ) {\n await toolFs.rm(resolvedSource.data.downloadPath);\n }\n\n // 10. Resolve binary paths and detect version\n if (!installedAppPath) {\n return { success: false, error: 'App installation path was not resolved' };\n }\n\n const binaries = normalizeBinaries(toolConfig.binaries);\n const binaryPaths = binaries.map((binary) =>\n params.binaryPath\n ? path.join(installedAppPath, params.binaryPath)\n : path.join(installedAppPath, 'Contents', 'MacOS', binary.name)\n );\n\n let detectedVersion: string | undefined;\n const mainBinaryPath = binaryPaths[0];\n if (mainBinaryPath) {\n detectedVersion = await detectVersionViaCli({\n binaryPath: mainBinaryPath,\n args: params.versionArgs,\n regex: params.versionRegex,\n shellExecutor,\n });\n }\n\n const metadata: IDmgInstallMetadata = {\n method: 'dmg',\n downloadUrl: resolvedSource.data.sourceUrl,\n dmgUrl: resolvedSource.data.sourceUrl,\n };\n\n return {\n success: true,\n binaryPaths,\n metadata,\n version: detectedVersion || (toolConfig.version !== 'latest' ? toolConfig.version : undefined),\n };\n };\n\n return withInstallErrorHandling('dmg', toolName, logger, operation);\n}\n\nfunction getSourceLabel(source: DmgSource): string {\n if (source.type === 'url') {\n return source.url;\n }\n\n return `github-release:${source.repo}`;\n}\n\nfunction getGitHubApiClient(\n source: DmgGitHubReleaseSource,\n githubApiClient: IGitHubApiClient,\n ghCliApiClient: IGitHubApiClient | undefined,\n): IGitHubApiClient {\n if (source.ghCli && ghCliApiClient) {\n return ghCliApiClient;\n }\n\n return githubApiClient;\n}\n\nasync function resolveDmgSource(\n source: DmgSource,\n context: IInstallContext,\n options: IInstallOptions | undefined,\n downloader: IDownloader,\n githubApiClient: IGitHubApiClient | undefined,\n ghCliApiClient: IGitHubApiClient | undefined,\n logger: TsLogger,\n): Promise<OperationResult<IResolvedDmgSource>> {\n if (source.type === 'url') {\n return await resolveFromUrlSource(source, context, options, downloader, logger);\n }\n\n return await resolveFromGitHubReleaseSource(\n source,\n context,\n options,\n downloader,\n githubApiClient,\n ghCliApiClient,\n logger,\n );\n}\n\nasync function resolveFromUrlSource(\n source: DmgUrlSource,\n context: IInstallContext,\n options: IInstallOptions | undefined,\n downloader: IDownloader,\n logger: TsLogger,\n): Promise<OperationResult<IResolvedDmgSource>> {\n logger.debug(messages.downloadingDmg(source.url));\n const downloadName = inferDownloadFileName(source.url, 'download.dmg');\n const downloadPath = path.join(context.stagingDir, downloadName);\n\n try {\n await downloadWithProgress(logger, source.url, downloadPath, downloadName, downloader, options);\n return {\n success: true,\n data: {\n downloadPath,\n downloadName,\n sourceUrl: source.url,\n },\n };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\nasync function resolveFromGitHubReleaseSource(\n source: DmgGitHubReleaseSource,\n context: IInstallContext,\n options: IInstallOptions | undefined,\n downloader: IDownloader,\n githubApiClient: IGitHubApiClient | undefined,\n ghCliApiClient: IGitHubApiClient | undefined,\n logger: TsLogger,\n): Promise<OperationResult<IResolvedDmgSource>> {\n if (!githubApiClient) {\n return {\n success: false,\n error: 'GitHub API client is not configured for DMG github-release source',\n };\n }\n\n const apiClient = getGitHubApiClient(source, githubApiClient, ghCliApiClient);\n const releaseVersion = source.version || 'latest';\n\n const release = await fetchGitHubRelease(\n source.repo,\n releaseVersion,\n source.prerelease ?? false,\n apiClient,\n logger,\n );\n if (!release.success) {\n return release;\n }\n\n const selectedAsset = await selectAsset(release.data, source, context, logger);\n if (!selectedAsset.success) {\n return selectedAsset;\n }\n\n const isDmgAsset = selectedAsset.data.name.endsWith('.dmg');\n const isArchiveContainingDmg = isSupportedArchiveFile(selectedAsset.data.name);\n if (!isDmgAsset && !isArchiveContainingDmg) {\n return {\n success: false,\n error: `Selected GitHub release asset must be a .dmg or supported archive: ${selectedAsset.data.name}`,\n };\n }\n\n const [owner, repoName] = source.repo.split('/');\n if (!owner || !repoName) {\n return {\n success: false,\n error: `Invalid GitHub repository format: ${source.repo}. Expected format: owner/repo`,\n };\n }\n\n const downloadPath = path.join(context.stagingDir, selectedAsset.data.name);\n const downloadResult = await downloadGitHubAsset(\n source,\n selectedAsset.data,\n owner,\n repoName,\n release.data.tag_name,\n downloadPath,\n options,\n downloader,\n apiClient,\n logger,\n );\n if (!downloadResult.success) {\n return downloadResult;\n }\n\n return {\n success: true,\n data: {\n downloadPath,\n downloadName: selectedAsset.data.name,\n sourceUrl: selectedAsset.data.browser_download_url,\n },\n };\n}\n\nasync function downloadGitHubAsset(\n source: DmgGitHubReleaseSource,\n asset: IGitHubReleaseAsset,\n owner: string,\n repoName: string,\n tagName: string,\n downloadPath: string,\n options: IInstallOptions | undefined,\n downloader: IDownloader,\n apiClient: IGitHubApiClient,\n logger: TsLogger,\n): Promise<OperationResult<void>> {\n if (source.ghCli && apiClient.downloadAsset) {\n try {\n await apiClient.downloadAsset(owner, repoName, tagName, asset.name, downloadPath);\n return { success: true, data: undefined };\n } catch {\n // fall through to HTTP download\n }\n }\n\n try {\n await downloadWithProgress(logger, asset.browser_download_url, downloadPath, asset.name, downloader, options);\n return { success: true, data: undefined };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\nfunction inferDownloadFileName(rawUrl: string, fallback: string): string {\n try {\n const parsedUrl = new URL(rawUrl);\n const lastPathSegment = parsedUrl.pathname.split('/').pop();\n if (!lastPathSegment) {\n return fallback;\n }\n return decodeURIComponent(lastPathSegment);\n } catch {\n return fallback;\n }\n}\n\nasync function findAppBundle(\n appName: string | undefined,\n mountPoint: string,\n fs: IFileSystem,\n logger: TsLogger,\n): Promise<string | null> {\n if (appName) return appName;\n\n const entries = await fs.readdir(mountPoint);\n const appEntry = entries.find((e) => e.endsWith('.app'));\n if (!appEntry) {\n logger.error(messages.appNotFound(mountPoint));\n return null;\n }\n return appEntry;\n}\n",
|
|
131
|
+
"import { createSafeLogMessage, type SafeLogMessageMap } from '@dotfiles/logger';\n\nexport const messages = {\n installing: (toolName: string) => createSafeLogMessage(`Installing from dmg: toolName=${toolName}`),\n skippingNonMacOS: (toolName: string) =>\n createSafeLogMessage(`Skipping DMG installation for ${toolName}: not running on macOS`),\n downloadingDmg: (url: string) => createSafeLogMessage(`Downloading DMG from: ${url}`),\n mountingDmg: (dmgPath: string) => createSafeLogMessage(`Mounting DMG: ${dmgPath}`),\n dmgMounted: (mountPoint: string) => createSafeLogMessage(`DMG mounted at: ${mountPoint}`),\n copyingApp: (appName: string) => createSafeLogMessage(`Copying app bundle: ${appName}`),\n symlinkingBinary: (from: string, to: string) => createSafeLogMessage(`Symlinking binary: ${from} -> ${to}`),\n unmountingDmg: (mountPoint: string) => createSafeLogMessage(`Unmounting DMG: ${mountPoint}`),\n appNotFound: (mountPoint: string) => createSafeLogMessage(`No .app bundle found in DMG at: ${mountPoint}`),\n extractingArchive: () => createSafeLogMessage('Extracting archive to find DMG'),\n archiveExtracted: (fileCount: number) => createSafeLogMessage(`Archive extracted: ${fileCount} files`),\n dmgFoundInArchive: (dmgFile: string) => createSafeLogMessage(`Found DMG in archive: ${dmgFile}`),\n noDmgInArchive: () => createSafeLogMessage('No .dmg file found in archive'),\n} as const satisfies SafeLogMessageMap;\n",
|
|
132
|
+
"import type { BaseInstallParams } from '@dotfiles/core';\nimport { baseInstallParamsSchema } from '@dotfiles/core';\nimport type { GithubReleaseInstallParams } from '@dotfiles/installer-github';\nimport { githubReleaseInstallParamsSchema } from '@dotfiles/installer-github';\nimport { z } from 'zod';\n\nconst githubReleaseSourceParamsSchema = githubReleaseInstallParamsSchema.pick({\n repo: true,\n version: true,\n assetPattern: true,\n assetSelector: true,\n ghCli: true,\n prerelease: true,\n});\n\nconst dmgUrlSourceSchema = z.object({\n type: z.literal('url'),\n /** The URL of the DMG file to download. */\n url: z.string().url(),\n});\n\nconst dmgGitHubReleaseSourceSchema = z.object({\n type: z.literal('github-release'),\n}).extend(githubReleaseSourceParamsSchema.shape);\n\nexport const dmgSourceSchema = z.discriminatedUnion('type', [\n dmgUrlSourceSchema,\n dmgGitHubReleaseSourceSchema,\n]);\n\nexport const dmgInstallParamsSchema = baseInstallParamsSchema.extend({\n /** Source definition for resolving the DMG file. */\n source: dmgSourceSchema,\n /**\n * The name of the .app bundle inside the DMG (e.g., 'MyApp.app').\n * If not provided, the first .app bundle found will be used.\n */\n appName: z.string().optional(),\n /**\n * Relative path to the binary inside the .app bundle.\n * Defaults to Contents/MacOS/{binary name from .bin()}.\n * Example: 'Contents/MacOS/myapp'\n */\n binaryPath: z.string().optional(),\n /** Arguments to pass to the binary to check the version (e.g. ['--version']). */\n versionArgs: z.array(z.string()).optional(),\n /** Regex to extract version from output. */\n versionRegex: z.string().optional(),\n});\n\n/**\n * Parameters for installing a tool from a macOS DMG disk image.\n *\n * NOTE: This is an explicit interface (not z.infer) to ensure TypeScript fully resolves\n * the property names, which is required for proper `keyof` behavior in declaration files.\n */\nexport interface DmgInstallParams extends BaseInstallParams {\n /** Source definition for resolving the DMG file. */\n source: DmgSource;\n /** The name of the .app bundle inside the DMG. */\n appName?: string;\n /** Relative path to the binary inside the .app bundle. */\n binaryPath?: string;\n /** Arguments to pass to the binary to check the version. */\n versionArgs?: string[];\n /** Regex to extract version from output. */\n versionRegex?: string;\n}\n\nexport interface DmgUrlSource {\n type: 'url';\n url: string;\n}\n\nexport interface DmgGitHubReleaseSource extends\n Pick<\n GithubReleaseInstallParams,\n 'repo' | 'version' | 'assetPattern' | 'assetSelector' | 'ghCli' | 'prerelease'\n >\n{\n type: 'github-release';\n}\n\nexport type DmgSource = DmgUrlSource | DmgGitHubReleaseSource;\n",
|
|
133
|
+
"import {\n baseToolConfigWithPlatformsSchema,\n type InferToolConfigWithPlatforms,\n} from '@dotfiles/core';\nimport { z } from 'zod';\nimport { dmgInstallParamsSchema } from './dmgInstallParamsSchema';\n\nexport const dmgToolConfigSchema = baseToolConfigWithPlatformsSchema.extend({\n /** Resolved tool configuration for the 'dmg' installation method */\n installationMethod: z.literal('dmg'),\n /** DMG installation parameters */\n installParams: dmgInstallParamsSchema,\n});\n\n/** Resolved tool configuration for the 'dmg' installation method. */\nexport type DmgToolConfig = InferToolConfigWithPlatforms<typeof dmgToolConfigSchema>;\n",
|
|
134
|
+
"import type { IArchiveExtractor } from '@dotfiles/archive-extractor';\nimport type {\n IInstallContext,\n IInstallerPlugin,\n IInstallOptions,\n InstallResult,\n IValidationResult,\n Shell,\n} from '@dotfiles/core';\nimport { Platform } from '@dotfiles/core';\nimport type { IDownloader } from '@dotfiles/downloader';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { HookExecutor } from '@dotfiles/installer';\nimport type { IGitHubApiClient } from '@dotfiles/installer-github';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { installFromDmg } from './installFromDmg';\nimport {\n type DmgInstallParams,\n dmgInstallParamsSchema,\n type DmgToolConfig,\n dmgToolConfigSchema,\n} from './schemas';\nimport type { IDmgInstallMetadata } from './types';\n\nconst PLUGIN_VERSION = '1.0.0';\n\n/**\n * Installer plugin for macOS applications distributed as DMG disk images.\n *\n * This plugin downloads a .dmg file, mounts it, copies the .app bundle to\n * /Applications, and resolves binaries from Contents/MacOS/ for\n * system-wide availability.\n *\n * On non-macOS platforms, installation is silently skipped with a success result.\n */\nexport class DmgInstallerPlugin\n implements IInstallerPlugin<'dmg', DmgInstallParams, DmgToolConfig, IDmgInstallMetadata>\n{\n readonly method = 'dmg';\n readonly displayName = 'DMG Installer';\n readonly version = PLUGIN_VERSION;\n readonly paramsSchema = dmgInstallParamsSchema;\n readonly toolConfigSchema = dmgToolConfigSchema;\n readonly staticValidation = true;\n readonly externallyManaged = true;\n readonly missingBinaryMessage =\n 'Installation completed. This tool was installed as a macOS app bundle in /Applications. Launch it from Spotlight, Launchpad, or the Applications folder.';\n\n constructor(\n private readonly fs: IFileSystem,\n private readonly downloader: IDownloader,\n private readonly archiveExtractor: IArchiveExtractor,\n private readonly hookExecutor: HookExecutor,\n private readonly shell: Shell,\n private readonly githubApiClient: IGitHubApiClient,\n private readonly ghCliApiClient: IGitHubApiClient | undefined,\n ) {}\n\n async validate(context: IInstallContext): Promise<IValidationResult> {\n // On non-macOS, return valid — the install() method handles the silent skip\n if (context.systemInfo.platform !== Platform.MacOS) {\n return { valid: true, warnings: ['DMG installer only works on macOS'] };\n }\n\n // On macOS, verify hdiutil exists\n try {\n await this.shell`which hdiutil`.quiet();\n return { valid: true };\n } catch {\n return { valid: false, errors: ['hdiutil not found — required for DMG installation'] };\n }\n }\n\n async install(\n toolName: string,\n toolConfig: DmgToolConfig,\n context: IInstallContext,\n options: IInstallOptions | undefined,\n logger: TsLogger,\n ): Promise<InstallResult<IDmgInstallMetadata>> {\n return installFromDmg(\n toolName,\n toolConfig,\n context,\n options,\n this.fs,\n this.downloader,\n this.archiveExtractor,\n this.hookExecutor,\n logger,\n this.shell,\n this.githubApiClient,\n this.ghCliApiClient,\n );\n }\n\n supportsUpdate(): boolean {\n return false;\n }\n\n supportsUpdateCheck(): boolean {\n return false;\n }\n\n supportsReadme(): boolean {\n return false;\n }\n}\n",
|
|
135
|
+
"import type { IGitHubRelease, IGitHubReleaseAsset } from '@dotfiles/core';\nimport type { ICache } from '@dotfiles/downloader';\nimport {\n ClientError,\n ForbiddenError,\n HttpError,\n type IDownloader,\n NetworkError,\n NotFoundError,\n RateLimitError,\n ServerError,\n} from '@dotfiles/downloader';\nimport type { TsLogger } from '@dotfiles/logger';\nimport crypto from 'node:crypto';\nimport { GiteaApiClientError } from './GiteaApiClientError';\nimport type { IGiteaRelease } from './giteaApiTypes';\nimport { mapGiteaAsset } from './giteaApiTypes';\nimport type { IGiteaApiClient } from './IGiteaApiClient';\nimport { messages } from './log-messages';\n\n/**\n * Maps a raw Gitea release response to the shared IGitHubRelease format.\n */\nfunction mapGiteaRelease(raw: IGiteaRelease): IGitHubRelease {\n const assets: IGitHubReleaseAsset[] = raw.assets.map(mapGiteaAsset);\n const result: IGitHubRelease = {\n id: raw.id,\n tag_name: raw.tag_name,\n name: raw.name,\n draft: raw.draft,\n prerelease: raw.prerelease,\n created_at: raw.created_at,\n published_at: raw.published_at,\n assets,\n body: raw.body,\n html_url: raw.html_url,\n };\n return result;\n}\n\n/**\n * Client for interacting with Gitea/Forgejo instances via their REST API.\n *\n * Supports any Gitea-compatible instance (Gitea, Forgejo, Codeberg, etc.).\n * Maps Gitea API responses to the shared IGitHubRelease format for compatibility\n * with the existing installer infrastructure.\n *\n * Includes built-in caching support to reduce API calls.\n */\nexport class GiteaApiClient implements IGiteaApiClient {\n private readonly baseUrl: string;\n private readonly token?: string;\n private readonly downloader: IDownloader;\n private readonly cache?: ICache;\n private readonly cacheEnabled: boolean;\n private readonly cacheTtlMs: number;\n private readonly logger: TsLogger;\n\n constructor(\n parentLogger: TsLogger,\n instanceUrl: string,\n downloader: IDownloader,\n cache?: ICache,\n options?: { token?: string; cacheEnabled?: boolean; cacheTtlMs?: number; },\n ) {\n this.logger = parentLogger.getSubLogger({ name: 'GiteaApiClient' });\n // Normalize the instance URL: strip trailing slash, add /api/v1\n const normalized = instanceUrl.replace(/\\/+$/, '');\n this.baseUrl = `${normalized}/api/v1`;\n this.token = options?.token;\n this.downloader = downloader;\n this.cache = cache;\n this.cacheEnabled = options?.cacheEnabled ?? true;\n this.cacheTtlMs = options?.cacheTtlMs ?? 300_000; // 5 minutes default\n\n const logger = this.logger.getSubLogger({ name: 'constructor' });\n logger.debug(messages.constructor.initialized(this.baseUrl));\n if (this.token) {\n logger.debug(messages.constructor.authTokenPresent());\n } else {\n logger.debug(messages.constructor.authTokenMissing());\n }\n\n if (this.cache && this.cacheEnabled) {\n logger.debug(messages.cache.enabled(this.cacheTtlMs));\n } else if (this.cache && !this.cacheEnabled) {\n logger.debug(messages.cache.disabled());\n } else {\n logger.debug(messages.cache.missing());\n }\n }\n\n private generateCacheKey(endpoint: string, method: string): string {\n let key = `gitea:${method}:${endpoint}`;\n if (this.token && typeof this.token === 'string' && this.token.length > 0) {\n const tokenHash = crypto.createHash('sha256').update(this.token).digest('hex').substring(0, 8);\n key += `:${tokenHash}`;\n }\n return key;\n }\n\n private async request<T>(endpoint: string, method: 'GET' = 'GET'): Promise<T> {\n const logger = this.logger.getSubLogger({ name: 'request' });\n const url = `${this.baseUrl}${endpoint}`;\n const cacheKey = this.generateCacheKey(endpoint, method);\n\n const cachedResult = await this.tryGetFromCache<T>(cacheKey, method);\n if (cachedResult) {\n return cachedResult;\n }\n\n logger.debug(messages.request.performing(method, url));\n const headers = this.buildRequestHeaders();\n\n try {\n const data = await this.performRequest<T>(url, headers);\n await this.tryCacheResponse(cacheKey, data, method);\n return data;\n } catch (error) {\n return this.handleRequestError(error, url);\n }\n }\n\n private async tryGetFromCache<T>(cacheKey: string, method: string): Promise<T | null> {\n if (!this.cache || !this.cacheEnabled || method !== 'GET') {\n return null;\n }\n\n try {\n const cachedData = await this.cache.get<T>(cacheKey);\n if (cachedData) {\n return cachedData;\n }\n } catch {\n // Cache layer logs retrieval failures\n }\n\n return null;\n }\n\n private buildRequestHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n Accept: 'application/json',\n };\n\n if (this.token) {\n headers['Authorization'] = `token ${this.token}`;\n }\n\n return headers;\n }\n\n private async performRequest<T>(url: string, headers: Record<string, string>): Promise<T> {\n const logger = this.logger.getSubLogger({ name: 'performRequest' });\n const responseBuffer = await this.downloader.download(logger, url, { headers });\n if (!responseBuffer || responseBuffer.length === 0) {\n logger.debug(messages.request.emptyResponse(url));\n throw new NetworkError(this.logger, 'Empty response received from API', url);\n }\n const responseText = responseBuffer.toString('utf-8');\n return JSON.parse(responseText) as T;\n }\n\n private async tryCacheResponse<T>(cacheKey: string, data: T, method: string): Promise<void> {\n if (!this.cache || !this.cacheEnabled || method !== 'GET') {\n return;\n }\n\n try {\n await this.cache.set<T>(cacheKey, data, this.cacheTtlMs);\n } catch {\n // Cache layer logs storage failures\n }\n }\n\n private handleRequestError(error: unknown, url: string): never {\n const logger = this.logger.getSubLogger({ name: 'handleRequestError' });\n logger.debug(messages.errors.requestFailure(url), error);\n\n if (error instanceof NotFoundError) {\n logger.debug(messages.errors.notFound(url));\n throw new Error(`Gitea resource not found: ${url}. Status: ${error.statusCode}`);\n }\n if (error instanceof RateLimitError) {\n logger.debug(messages.errors.rateLimit(url));\n throw new GiteaApiClientError(\n `Gitea API rate limit exceeded for ${url}. Status: ${error.statusCode}.`,\n error.statusCode,\n error,\n );\n }\n if (error instanceof ForbiddenError) {\n logger.debug(messages.errors.forbidden(url));\n throw new GiteaApiClientError(\n `Gitea API request forbidden for ${url}. Status: ${error.statusCode}.`,\n error.statusCode,\n error,\n );\n }\n if (error instanceof ClientError) {\n logger.debug(messages.errors.client(url, error.statusCode));\n throw new GiteaApiClientError(\n `Gitea API client error for ${url}. Status: ${error.statusCode} ${error.statusText}.`,\n error.statusCode,\n error,\n );\n }\n if (error instanceof ServerError) {\n logger.debug(messages.errors.server(url, error.statusCode));\n throw new GiteaApiClientError(\n `Gitea API server error for ${url}. Status: ${error.statusCode} ${error.statusText}.`,\n error.statusCode,\n error,\n );\n }\n if (error instanceof HttpError) {\n logger.debug(messages.errors.http(url, error.statusCode));\n throw new GiteaApiClientError(\n `Gitea API HTTP error for ${url}. Status: ${error.statusCode} ${error.statusText}.`,\n error.statusCode,\n error,\n );\n }\n if (error instanceof NetworkError) {\n logger.debug(messages.errors.network(url));\n throw new GiteaApiClientError(`Network error while requesting ${url}: ${error.message}`, undefined, error);\n }\n\n logger.debug(messages.errors.unknown(url), error);\n if (error instanceof Error) {\n throw new GiteaApiClientError(\n `Unknown error during Gitea API request to ${url}: ${error.message}`,\n undefined,\n error,\n );\n }\n throw new GiteaApiClientError(`Unknown error during Gitea API request to ${url}`);\n }\n\n async getLatestRelease(owner: string, repo: string): Promise<IGitHubRelease | null> {\n const logger = this.logger.getSubLogger({ name: 'getLatestRelease' });\n logger.debug(messages.releases.fetchingLatest(owner, repo));\n try {\n const raw = await this.request<IGiteaRelease>(`/repos/${owner}/${repo}/releases/latest`);\n return mapGiteaRelease(raw);\n } catch (error) {\n if (error instanceof Error && error.message.includes('Gitea resource not found')) {\n logger.debug(messages.releases.latestNotFound(owner, repo));\n return null;\n }\n logger.debug(messages.releases.latestError(owner, repo), error);\n throw error;\n }\n }\n\n async getReleaseByTag(owner: string, repo: string, tag: string): Promise<IGitHubRelease | null> {\n const logger = this.logger.getSubLogger({ name: 'getReleaseByTag' });\n logger.debug(messages.releases.fetchingByTag(tag, owner, repo));\n try {\n const raw = await this.request<IGiteaRelease>(`/repos/${owner}/${repo}/releases/tags/${tag}`);\n return mapGiteaRelease(raw);\n } catch (error) {\n if (error instanceof Error && error.message.includes('Gitea resource not found')) {\n logger.debug(messages.releases.tagNotFound(tag, owner, repo));\n return null;\n }\n logger.debug(messages.releases.tagError(tag, owner, repo), error);\n throw error;\n }\n }\n\n async getAllReleases(\n owner: string,\n repo: string,\n options?: { limit?: number; includePrerelease?: boolean; maxResults?: number; },\n ): Promise<IGitHubRelease[]> {\n const logger = this.logger.getSubLogger({ name: 'getAllReleases' });\n logger.debug(messages.releases.fetchingAll(owner, repo));\n const perPage = options?.limit || 30;\n const maxResults = options?.maxResults;\n let page = 1;\n let allReleases: IGitHubRelease[] = [];\n let keepFetching = true;\n\n while (keepFetching) {\n const endpoint = `/repos/${owner}/${repo}/releases?limit=${perPage}&page=${page}`;\n logger.debug(messages.releases.fetchingPage(page, endpoint));\n const rawPage = await this.request<IGiteaRelease[]>(endpoint);\n\n if (rawPage.length === 0) {\n keepFetching = false;\n } else {\n const mapped = rawPage.map(mapGiteaRelease);\n allReleases = allReleases.concat(mapped);\n page++;\n if (rawPage.length < perPage) {\n keepFetching = false;\n }\n if (maxResults !== undefined && allReleases.length >= maxResults) {\n allReleases = allReleases.slice(0, maxResults);\n keepFetching = false;\n }\n }\n }\n\n logger.debug(messages.releases.totalFetched(allReleases.length, owner, repo));\n\n if (options?.includePrerelease === false) {\n const filteredReleases = allReleases.filter((release) => !release.prerelease);\n logger.debug(messages.releases.filteredPrereleases(filteredReleases.length));\n return filteredReleases;\n }\n\n return allReleases;\n }\n\n async getLatestReleaseTags(owner: string, repo: string, count: number = 5): Promise<string[]> {\n const logger = this.logger.getSubLogger({ name: 'getLatestReleaseTags' });\n logger.debug(messages.releases.fetchingLatestTags(owner, repo, count));\n\n try {\n const rawReleases = await this.request<IGiteaRelease[]>(\n `/repos/${owner}/${repo}/releases?limit=${count}`,\n );\n const tags: string[] = rawReleases.map((release) => release.tag_name);\n logger.debug(messages.releases.fetchedTags(tags.length));\n return tags;\n } catch (error) {\n logger.debug(messages.releases.fetchTagsError(owner, repo), error);\n return [];\n }\n }\n}\n",
|
|
136
|
+
"/**\n * Custom error class for errors originating from the Gitea API client.\n */\nexport class GiteaApiClientError extends Error {\n public readonly originalError?: unknown;\n public readonly statusCode?: number;\n\n constructor(message: string, statusCode?: number, originalError?: unknown) {\n super(message);\n this.name = 'GiteaApiClientError';\n this.statusCode = statusCode;\n this.originalError = originalError;\n Object.setPrototypeOf(this, GiteaApiClientError.prototype);\n }\n}\n",
|
|
137
|
+
"import type { IGitHubReleaseAsset } from '@dotfiles/core';\n\n/**\n * Gitea/Forgejo release asset as returned by the API.\n * Contains additional fields not present in the GitHub API response.\n */\nexport interface IGiteaReleaseAsset {\n id: number;\n name: string;\n size: number;\n download_count: number;\n created_at: string;\n uuid: string;\n browser_download_url: string;\n type: string;\n}\n\n/**\n * Gitea/Forgejo release as returned by the API.\n * The structure is similar to GitHub releases but with some differences.\n */\nexport interface IGiteaRelease {\n id: number;\n tag_name: string;\n target_commitish: string;\n name: string;\n body?: string;\n url: string;\n html_url: string;\n tarball_url: string;\n zipball_url: string;\n draft: boolean;\n prerelease: boolean;\n created_at: string;\n published_at: string;\n author: {\n id: number;\n login: string;\n };\n assets: IGiteaReleaseAsset[];\n}\n\n/**\n * Maps a Gitea release asset to the shared IGitHubReleaseAsset format.\n */\nexport function mapGiteaAsset(asset: IGiteaReleaseAsset): IGitHubReleaseAsset {\n const result: IGitHubReleaseAsset = {\n name: asset.name,\n browser_download_url: asset.browser_download_url,\n size: asset.size,\n content_type: 'application/octet-stream',\n state: 'uploaded',\n download_count: asset.download_count,\n created_at: asset.created_at,\n updated_at: asset.created_at,\n };\n return result;\n}\n",
|
|
138
|
+
"import { createSafeLogMessage, type SafeLogMessageMap } from '@dotfiles/logger';\n\nexport const messages = {\n constructor: {\n initialized: (baseUrl: string) => createSafeLogMessage(`Gitea API client initialized. baseUrl=${baseUrl}`),\n authTokenPresent: () => createSafeLogMessage('API token is configured'),\n authTokenMissing: () => createSafeLogMessage('No API token configured'),\n } satisfies SafeLogMessageMap,\n cache: {\n enabled: (ttlMs: number) => createSafeLogMessage(`Cache enabled. TTL=${ttlMs}ms`),\n disabled: () => createSafeLogMessage('Cache is present but disabled'),\n missing: () => createSafeLogMessage('No cache configured'),\n } satisfies SafeLogMessageMap,\n request: {\n performing: (method: string, url: string) => createSafeLogMessage(`Gitea API ${method} request to ${url}`),\n emptyResponse: (url: string) => createSafeLogMessage(`Empty response from ${url}`),\n } satisfies SafeLogMessageMap,\n releases: {\n fetchingLatest: (owner: string, repo: string) =>\n createSafeLogMessage(`Fetching latest release for ${owner}/${repo}`),\n latestNotFound: (owner: string, repo: string) =>\n createSafeLogMessage(`Latest release not found for ${owner}/${repo}`),\n latestError: (owner: string, repo: string) =>\n createSafeLogMessage(`Error fetching latest release for ${owner}/${repo}`),\n fetchingByTag: (tag: string, owner: string, repo: string) =>\n createSafeLogMessage(`Fetching release by tag ${tag} for ${owner}/${repo}`),\n tagNotFound: (tag: string, owner: string, repo: string) =>\n createSafeLogMessage(`Release tag ${tag} not found for ${owner}/${repo}`),\n tagError: (tag: string, owner: string, repo: string) =>\n createSafeLogMessage(`Error fetching release ${tag} for ${owner}/${repo}`),\n fetchingAll: (owner: string, repo: string) => createSafeLogMessage(`Fetching all releases for ${owner}/${repo}`),\n fetchingPage: (page: number, endpoint: string) => createSafeLogMessage(`Fetching page ${page}: ${endpoint}`),\n totalFetched: (count: number, owner: string, repo: string) =>\n createSafeLogMessage(`Fetched ${count} releases for ${owner}/${repo}`),\n filteredPrereleases: (count: number) => createSafeLogMessage(`After filtering prereleases: ${count} releases`),\n fetchingLatestTags: (owner: string, repo: string, count: number) =>\n createSafeLogMessage(`Fetching ${count} latest release tags for ${owner}/${repo}`),\n fetchedTags: (count: number) => createSafeLogMessage(`Fetched ${count} release tags`),\n fetchTagsError: (owner: string, repo: string) =>\n createSafeLogMessage(`Error fetching release tags for ${owner}/${repo}`),\n } satisfies SafeLogMessageMap,\n errors: {\n requestFailure: (url: string) => createSafeLogMessage(`Request failure: ${url}`),\n notFound: (url: string) => createSafeLogMessage(`Resource not found: ${url}`),\n rateLimit: (url: string) => createSafeLogMessage(`Rate limit exceeded: ${url}`),\n forbidden: (url: string) => createSafeLogMessage(`Forbidden: ${url}`),\n client: (url: string, statusCode: number) => createSafeLogMessage(`Client error ${statusCode}: ${url}`),\n server: (url: string, statusCode: number) => createSafeLogMessage(`Server error ${statusCode}: ${url}`),\n http: (url: string, statusCode: number) => createSafeLogMessage(`HTTP error ${statusCode}: ${url}`),\n network: (url: string) => createSafeLogMessage(`Network error: ${url}`),\n unknown: (url: string) => createSafeLogMessage(`Unknown error: ${url}`),\n } satisfies SafeLogMessageMap,\n} as const;\n",
|
|
139
|
+
"import { selectBestMatch } from '@dotfiles/arch';\nimport { type IArchiveExtractor, isSupportedArchiveFile } from '@dotfiles/archive-extractor';\nimport type {\n IDownloadContext,\n IExtractContext,\n IExtractResult,\n IGitHubRelease,\n IGitHubReleaseAsset,\n IInstallContext,\n ISystemInfo,\n} from '@dotfiles/core';\nimport { architectureToString, platformToString } from '@dotfiles/core';\nimport type { IDownloader } from '@dotfiles/downloader';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { HookExecutor, IInstallOptions } from '@dotfiles/installer';\nimport {\n downloadWithProgress,\n executeAfterDownloadHook as executeAfterDownloadHookUtil,\n executeAfterExtractHook as executeAfterExtractHookUtil,\n getBinaryPaths,\n setupBinariesFromArchive,\n setupBinariesFromDirectDownload,\n} from '@dotfiles/installer';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { normalizeVersion } from '@dotfiles/utils';\nimport path from 'node:path';\nimport type { IGiteaApiClient } from './gitea-client';\nimport { messages } from './log-messages';\nimport { type AssetPattern, formatAssetPatternForLog, matchAssetPattern } from './matchAssetPattern';\nimport type {\n GiteaReleaseInstallParams,\n GiteaReleaseToolConfig,\n IGiteaAssetSelectionContext,\n} from './schemas';\nimport type { GiteaReleaseInstallResult, IGiteaReleaseInstallMetadata } from './types';\n\ntype OperationResult<T> = { success: true; data: T; } | { success: false; error: string; };\n\ninterface IDownloadAssetResultData {\n downloadPath: string;\n}\n\nconst TAG_SUGGESTIONS_COUNT = 5;\n\nexport async function installFromGiteaRelease(\n toolName: string,\n toolConfig: GiteaReleaseToolConfig,\n context: IInstallContext,\n options: IInstallOptions | undefined,\n toolFs: IFileSystem,\n downloader: IDownloader,\n giteaApiClient: IGiteaApiClient,\n archiveExtractor: IArchiveExtractor,\n hookExecutor: HookExecutor,\n parentLogger: TsLogger,\n): Promise<GiteaReleaseInstallResult> {\n const logger = parentLogger.getSubLogger({ name: 'installFromGiteaRelease' });\n logger.debug(messages.startingInstallation(toolName));\n\n if (!toolConfig.installParams || !('repo' in toolConfig.installParams)) {\n const result: GiteaReleaseInstallResult = {\n success: false,\n error: 'Repository not specified in installParams',\n };\n return result;\n }\n\n const params = toolConfig.installParams;\n const repo = params.repo;\n const version = params.version || 'latest';\n\n const [owner, repoName] = repo.split('/');\n if (!owner || !repoName) {\n const result: GiteaReleaseInstallResult = {\n success: false,\n error: `Invalid repository format: ${repo}. Expected format: owner/repo`,\n };\n return result;\n }\n\n try {\n const release = await fetchGiteaRelease(repo, version, params.prerelease ?? false, giteaApiClient, logger);\n if (!release.success) {\n const result: GiteaReleaseInstallResult = release;\n return result;\n }\n\n const asset = selectAsset(release.data, params, context, logger);\n if (!asset.success) {\n const result: GiteaReleaseInstallResult = asset;\n return result;\n }\n\n const downloadUrl = asset.data.browser_download_url;\n const downloadPath = path.join(context.stagingDir, asset.data.name);\n\n const downloadResult = await downloadAsset(\n downloadUrl,\n asset.data,\n downloader,\n downloadPath,\n options,\n logger,\n );\n if (!downloadResult.success) {\n const result: GiteaReleaseInstallResult = downloadResult;\n return result;\n }\n\n const postDownloadContext: IDownloadContext = {\n ...context,\n downloadPath: downloadResult.data.downloadPath,\n };\n\n const hookResult = await executeAfterDownloadHook(toolConfig, postDownloadContext, hookExecutor, toolFs, logger);\n if (!hookResult.success) {\n const result: GiteaReleaseInstallResult = hookResult;\n return result;\n }\n\n const resolvedVersion = normalizeVersion(release.data.tag_name);\n\n const installResult = await processAssetInstallation(\n asset.data,\n downloadResult.data.downloadPath,\n toolName,\n toolConfig,\n context,\n postDownloadContext,\n toolFs,\n archiveExtractor,\n hookExecutor,\n toolFs,\n logger,\n );\n if (!installResult.success) {\n const result: GiteaReleaseInstallResult = installResult;\n return result;\n }\n\n const binaryPaths = getBinaryPaths(toolConfig.binaries, context.stagingDir);\n\n const metadata: IGiteaReleaseInstallMetadata = {\n method: 'gitea-release',\n releaseUrl: release.data.html_url,\n publishedAt: release.data.published_at,\n releaseName: release.data.name,\n instanceUrl: params.instanceUrl,\n downloadUrl,\n assetName: asset.data.name,\n };\n\n const result: GiteaReleaseInstallResult = {\n success: true,\n binaryPaths,\n version: resolvedVersion,\n originalTag: release.data.tag_name,\n metadata,\n };\n return result;\n } catch (error) {\n const result: GiteaReleaseInstallResult = {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n return result;\n }\n}\n\nexport async function fetchGiteaRelease(\n repo: string,\n version: string,\n includePrerelease: boolean,\n giteaApiClient: IGiteaApiClient,\n parentLogger: TsLogger,\n): Promise<OperationResult<IGitHubRelease>> {\n const logger = parentLogger.getSubLogger({ name: 'fetchGiteaRelease' });\n const [owner, repoName] = repo.split('/');\n if (!owner || !repoName) {\n const result: OperationResult<IGitHubRelease> = {\n success: false,\n error: `Invalid repository format: ${repo}. Expected format: owner/repo`,\n };\n return result;\n }\n\n if (version === 'latest') {\n logger.debug(messages.fetchLatest(repo));\n\n if (includePrerelease) {\n const releases = await giteaApiClient.getAllReleases(owner, repoName, {\n limit: 1,\n includePrerelease: true,\n maxResults: 1,\n });\n const firstRelease = releases[0];\n if (!firstRelease) {\n const result: OperationResult<IGitHubRelease> = {\n success: false,\n error: `Failed to fetch latest release for ${repo}`,\n };\n return result;\n }\n const result: OperationResult<IGitHubRelease> = { success: true, data: firstRelease };\n return result;\n }\n\n const release = await giteaApiClient.getLatestRelease(owner, repoName);\n if (!release) {\n const result: OperationResult<IGitHubRelease> = {\n success: false,\n error: `Failed to fetch latest release for ${repo}`,\n };\n return result;\n }\n const result: OperationResult<IGitHubRelease> = { success: true, data: release };\n return result;\n }\n\n logger.debug(messages.fetchByTag(version, repo));\n const release = await giteaApiClient.getReleaseByTag(owner, repoName, version);\n if (release) {\n const result: OperationResult<IGitHubRelease> = { success: true, data: release };\n return result;\n }\n\n await showAvailableReleaseTags(owner, repoName, giteaApiClient, logger);\n\n const result: OperationResult<IGitHubRelease> = {\n success: false,\n error: `Release '${version}' not found for ${repo}. Check the available tags above.`,\n };\n return result;\n}\n\nasync function showAvailableReleaseTags(\n owner: string,\n repoName: string,\n giteaApiClient: IGiteaApiClient,\n parentLogger: TsLogger,\n): Promise<void> {\n const logger = parentLogger.getSubLogger({ name: 'showAvailableReleaseTags' });\n const tags = await giteaApiClient.getLatestReleaseTags(owner, repoName, TAG_SUGGESTIONS_COUNT);\n\n if (tags.length === 0) {\n logger.error(messages.noReleaseTagsAvailable());\n return;\n }\n\n logger.info(messages.availableReleaseTags());\n for (const tag of tags) {\n logger.info(messages.releaseTagItem(tag));\n }\n}\n\nexport function selectAsset(\n release: IGitHubRelease,\n params: GiteaReleaseInstallParams,\n context: IInstallContext,\n logger: TsLogger,\n): OperationResult<IGitHubReleaseAsset> {\n let asset: IGitHubReleaseAsset | undefined;\n\n if (params.assetSelector) {\n logger.debug(messages.assetSelectorCustom());\n const selectionContext: IGiteaAssetSelectionContext = {\n ...context,\n assets: release.assets,\n release,\n assetPattern: params.assetPattern,\n };\n asset = params.assetSelector(selectionContext);\n } else if (params.assetPattern) {\n logger.debug(messages.assetPatternMatch(formatAssetPatternForLog(params.assetPattern)));\n const pattern: AssetPattern = params.assetPattern;\n const matchingAssets = release.assets.filter((a) => matchAssetPattern(a.name, pattern));\n asset = findPlatformAsset(matchingAssets, context.systemInfo);\n if (!asset && matchingAssets.length > 0) {\n asset = matchingAssets[0];\n }\n } else {\n logger.debug(\n messages.assetPlatformMatch(\n platformToString(context.systemInfo.platform),\n architectureToString(context.systemInfo.arch),\n ),\n );\n asset = findPlatformAsset(release.assets, context.systemInfo);\n }\n\n if (!asset) {\n const result: OperationResult<IGitHubReleaseAsset> = {\n success: false,\n error: createAssetNotFoundError(release, params, context),\n };\n return result;\n }\n\n const result: OperationResult<IGitHubReleaseAsset> = { success: true, data: asset };\n return result;\n}\n\nfunction findPlatformAsset(assets: IGitHubReleaseAsset[], systemInfo: ISystemInfo): IGitHubReleaseAsset | undefined {\n const assetNames = assets.map((a) => a.name);\n const selectedName = selectBestMatch(assetNames, systemInfo);\n\n if (!selectedName) {\n return undefined;\n }\n\n return assets.find((a) => a.name === selectedName);\n}\n\nfunction createAssetNotFoundError(\n release: IGitHubRelease,\n params: GiteaReleaseInstallParams,\n context: IInstallContext,\n): string {\n const availableAssetNames = release.assets.map((a) => a.name);\n const platform = platformToString(context.systemInfo.platform);\n const arch = architectureToString(context.systemInfo.arch);\n let searchedForMessage = '';\n\n if (params.assetSelector) {\n searchedForMessage = `using a custom assetSelector function for ${platform}/${arch}.`;\n } else if (params.assetPattern) {\n searchedForMessage = `for asset pattern: \"${params.assetPattern}\" for ${platform}/${arch}.`;\n } else {\n searchedForMessage = `for platform \"${platform}\" and architecture \"${arch}\".`;\n }\n\n const errorLines: string[] = [\n `No suitable asset found in release \"${release.tag_name}\" ${searchedForMessage}`,\n `Available assets in release \"${release.tag_name}\":`,\n ...availableAssetNames.map((name) => ` - ${name}`),\n ];\n\n return errorLines.join('\\n');\n}\n\nasync function downloadAsset(\n downloadUrl: string,\n asset: IGitHubReleaseAsset,\n downloader: IDownloader,\n downloadPath: string,\n options: IInstallOptions | undefined,\n logger: TsLogger,\n): Promise<OperationResult<IDownloadAssetResultData>> {\n logger.debug(messages.downloadingAsset(downloadUrl));\n try {\n await downloadWithProgress(logger, downloadUrl, downloadPath, asset.name, downloader, options);\n const data: IDownloadAssetResultData = { downloadPath };\n const result: OperationResult<IDownloadAssetResultData> = { success: true, data };\n return result;\n } catch (error) {\n const result: OperationResult<IDownloadAssetResultData> = {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n return result;\n }\n}\n\nasync function executeAfterDownloadHook(\n toolConfig: GiteaReleaseToolConfig,\n postDownloadContext: IDownloadContext,\n hookExecutor: HookExecutor,\n fs: IFileSystem,\n logger: TsLogger,\n): Promise<OperationResult<void>> {\n const result = await executeAfterDownloadHookUtil(toolConfig, postDownloadContext, hookExecutor, fs, logger);\n if (result.success) {\n const finalResult: OperationResult<void> = { success: true, data: undefined };\n return finalResult;\n }\n\n const finalResult: OperationResult<void> = { success: false, error: result.error || 'Hook execution failed' };\n return finalResult;\n}\n\nasync function processAssetInstallation(\n asset: IGitHubReleaseAsset,\n downloadPath: string,\n toolName: string,\n toolConfig: GiteaReleaseToolConfig,\n context: IInstallContext,\n postDownloadContext: IDownloadContext,\n toolFs: IFileSystem,\n archiveExtractor: IArchiveExtractor,\n hookExecutor: HookExecutor,\n fs: IFileSystem,\n logger: TsLogger,\n): Promise<OperationResult<void>> {\n if (isSupportedArchiveFile(asset.name)) {\n return await processArchiveInstallation(\n asset,\n downloadPath,\n toolName,\n toolConfig,\n context,\n postDownloadContext,\n toolFs,\n archiveExtractor,\n hookExecutor,\n fs,\n logger,\n );\n }\n\n await setupBinariesFromDirectDownload(toolFs, toolName, toolConfig, context, downloadPath, logger);\n const result: OperationResult<void> = { success: true, data: undefined };\n return result;\n}\n\nasync function processArchiveInstallation(\n asset: IGitHubReleaseAsset,\n downloadPath: string,\n toolName: string,\n toolConfig: GiteaReleaseToolConfig,\n context: IInstallContext,\n postDownloadContext: IDownloadContext,\n toolFs: IFileSystem,\n archiveExtractor: IArchiveExtractor,\n hookExecutor: HookExecutor,\n fs: IFileSystem,\n logger: TsLogger,\n): Promise<OperationResult<void>> {\n logger.debug(messages.extractingArchive(asset.name));\n\n const extractResult: IExtractResult = await archiveExtractor.extract(logger, downloadPath, {\n targetDir: context.stagingDir,\n });\n logger.debug(messages.archiveExtracted(extractResult.extractedFiles.length, extractResult.executables.length));\n\n const postExtractContext: IExtractContext = {\n ...postDownloadContext,\n extractDir: context.stagingDir,\n extractResult,\n };\n\n const hookResult = await executeAfterExtractHook(toolConfig, postExtractContext, fs, hookExecutor, logger);\n if (!hookResult.success) {\n return hookResult;\n }\n\n await setupBinariesFromArchive(toolFs, toolName, toolConfig, context, context.stagingDir, logger);\n\n if (await toolFs.exists(downloadPath)) {\n logger.debug(messages.cleaningArchive(downloadPath));\n await toolFs.rm(downloadPath);\n }\n\n const result: OperationResult<void> = { success: true, data: undefined };\n return result;\n}\n\nasync function executeAfterExtractHook(\n toolConfig: GiteaReleaseToolConfig,\n postExtractContext: IExtractContext,\n fs: IFileSystem,\n hookExecutor: HookExecutor,\n logger: TsLogger,\n): Promise<OperationResult<void>> {\n const result = await executeAfterExtractHookUtil(toolConfig, postExtractContext, hookExecutor, fs, logger);\n if (result.success) {\n const finalResult: OperationResult<void> = { success: true, data: undefined };\n return finalResult;\n }\n\n const finalResult: OperationResult<void> = { success: false, error: result.error || 'Hook execution failed' };\n return finalResult;\n}\n",
|
|
140
|
+
"import { createSafeLogMessage, type SafeLogMessageMap } from '@dotfiles/logger';\n\nexport const messages = {\n startingInstallation: (toolName: string) => createSafeLogMessage(`Starting installation: ${toolName}`),\n fetchLatest: (repo: string) => createSafeLogMessage(`Getting latest release for ${repo}`),\n fetchByTag: (version: string, repo: string) => createSafeLogMessage(`Fetching release ${version} for ${repo}`),\n assetSelectorCustom: () => createSafeLogMessage('Using custom asset selector'),\n assetPatternMatch: (pattern: string) => createSafeLogMessage(`Finding asset matching pattern: ${pattern}`),\n assetPlatformMatch: (platform: string, arch: string) =>\n createSafeLogMessage(`Selecting asset for platform ${platform} and architecture ${arch}`),\n downloadingAsset: (downloadUrl: string) => createSafeLogMessage(`Downloading asset: ${downloadUrl}`),\n extractingArchive: (assetName: string) => createSafeLogMessage(`Extracting archive: ${assetName}`),\n archiveExtracted: (fileCount: number, executableCount: number) =>\n createSafeLogMessage(`Archive extracted. fileCount=${fileCount}, executableCount=${executableCount}`),\n cleaningArchive: (downloadPath: string) => createSafeLogMessage(`Cleaning up downloaded archive: ${downloadPath}`),\n versionResolutionResolved: (toolName: string, version: string) =>\n createSafeLogMessage(`Resolved version for ${toolName}: ${version}`),\n versionResolutionFailed: (toolName: string, error: string) =>\n createSafeLogMessage(`Failed to resolve version for ${toolName}: ${error}`),\n versionResolutionException: (toolName: string) =>\n createSafeLogMessage(`Exception while resolving version for ${toolName}`),\n updateCheckFailed: (toolName: string) => createSafeLogMessage(`Failed to check update for ${toolName}`),\n availableReleaseTags: () => createSafeLogMessage('Available release tags:'),\n releaseTagItem: (tag: string) => createSafeLogMessage(` - ${tag}`),\n noReleaseTagsAvailable: () => createSafeLogMessage('No release tags available for this repository'),\n} as const satisfies SafeLogMessageMap;\n",
|
|
141
|
+
"import { minimatch } from 'minimatch';\n\nexport type AssetPattern = string | RegExp;\n\nfunction isValidRegexFlags(flags: string): boolean {\n return /^[dgimsuvy]*$/.test(flags);\n}\n\nfunction isRegexPatternString(value: string): boolean {\n if (!value.startsWith('/')) {\n return false;\n }\n\n const lastSlashIndex = value.lastIndexOf('/');\n if (lastSlashIndex <= 0) {\n return false;\n }\n\n return true;\n}\n\nfunction parseRegexPatternString(value: string): RegExp {\n const lastSlashIndex = value.lastIndexOf('/');\n if (lastSlashIndex <= 0) {\n throw new Error('Invalid regex string: missing closing \"/\"');\n }\n\n const pattern = value.slice(1, lastSlashIndex);\n const flags = value.slice(lastSlashIndex + 1);\n\n if (!isValidRegexFlags(flags)) {\n throw new Error('Invalid regex string: invalid flags');\n }\n\n const regex = new RegExp(pattern, flags);\n return regex;\n}\n\nexport function isValidAssetPatternString(value: string): boolean {\n if (!isRegexPatternString(value)) {\n return true;\n }\n\n try {\n parseRegexPatternString(value);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function formatAssetPatternForLog(assetPattern: AssetPattern): string {\n if (typeof assetPattern === 'string') {\n return assetPattern;\n }\n\n return assetPattern.toString();\n}\n\nexport function matchAssetPattern(candidate: string, assetPattern: AssetPattern): boolean {\n if (typeof assetPattern === 'string') {\n if (isRegexPatternString(assetPattern)) {\n const regex = parseRegexPatternString(assetPattern);\n return regex.test(candidate);\n }\n\n return minimatch(candidate, assetPattern);\n }\n\n return assetPattern.test(candidate);\n}\n",
|
|
142
|
+
"import type { BaseInstallParams, IGitHubRelease, IGitHubReleaseAsset, IInstallContext } from '@dotfiles/core';\nimport { baseInstallParamsSchema } from '@dotfiles/core';\nimport { z } from 'zod';\nimport { type AssetPattern, isValidAssetPatternString } from '../matchAssetPattern';\n\n/**\n * Context object for asset selection functions.\n */\nexport interface IGiteaAssetSelectionContext extends IInstallContext {\n assets: IGitHubReleaseAsset[];\n release: IGitHubRelease;\n assetPattern?: AssetPattern;\n}\n\n/**\n * Asset selector function signature.\n */\nexport type GiteaAssetSelector = (context: IGiteaAssetSelectionContext) => IGitHubReleaseAsset | undefined;\n\nexport const giteaReleaseInstallParamsSchema = baseInstallParamsSchema.extend({\n /**\n * The base URL of the Gitea/Forgejo instance (e.g., `https://codeberg.org`).\n */\n instanceUrl: z.string().url('instanceUrl must be a valid URL'),\n /**\n * The repository in \"owner/repo\" format (e.g., `Codeberg/pages-server`).\n */\n repo: z.string().regex(/^[^/]+\\/[^/]+$/, 'Repository must be in \"owner/repo\" format'),\n /**\n * A glob pattern or regular expression string used to match the desired asset filename.\n */\n assetPattern: z\n .union([\n z.string().refine(isValidAssetPatternString, 'assetPattern must be a valid glob or a regex string like /.../'),\n z.instanceof(RegExp),\n ])\n .optional(),\n /**\n * A specific version string (e.g., `v1.2.3`, `0.48.0`) for the release to target.\n * If omitted, the latest stable release is targeted.\n */\n version: z.string().optional(),\n /**\n * A custom function to select the desired asset from available release assets.\n */\n assetSelector: z.custom<GiteaAssetSelector>((val) => typeof val === 'function', 'Must be a function').optional(),\n /**\n * When true, includes prerelease versions when fetching the latest release.\n */\n prerelease: z.boolean().optional(),\n /**\n * Optional API token for authentication with the Gitea instance.\n */\n token: z.string().optional(),\n});\n\n/**\n * Parameters for installing a tool from a Gitea/Forgejo release.\n */\nexport interface GiteaReleaseInstallParams extends BaseInstallParams {\n instanceUrl: string;\n repo: string;\n assetPattern?: string | RegExp;\n version?: string;\n assetSelector?: GiteaAssetSelector;\n prerelease?: boolean;\n token?: string;\n}\n",
|
|
143
|
+
"import type { ToolConfig } from '@dotfiles/core';\nimport {\n baseToolConfigWithPlatformsSchema,\n binaryConfigSchema,\n type InferToolConfigWithPlatforms,\n} from '@dotfiles/core';\nimport { z } from 'zod';\nimport { giteaReleaseInstallParamsSchema } from './giteaReleaseInstallParamsSchema';\n\nexport const giteaReleaseToolConfigSchema = baseToolConfigWithPlatformsSchema.extend({\n installationMethod: z.literal('gitea-release'),\n installParams: giteaReleaseInstallParamsSchema,\n binaries: z.array(z.union([z.string().min(1), binaryConfigSchema])).min(1),\n});\n\nexport type GiteaReleaseToolConfig = InferToolConfigWithPlatforms<typeof giteaReleaseToolConfigSchema>;\n\nexport function isGiteaReleaseToolConfig(config: ToolConfig): config is GiteaReleaseToolConfig {\n return config.installationMethod === 'gitea-release';\n}\n",
|
|
144
|
+
"import type { IArchiveExtractor } from '@dotfiles/archive-extractor';\nimport type {\n IInstallContext,\n IInstallerPlugin,\n IInstallOptions,\n InstallResult,\n UpdateCheckResult,\n} from '@dotfiles/core';\nimport type { ICache, IDownloader } from '@dotfiles/downloader';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { HookExecutor } from '@dotfiles/installer';\nimport { createToolFileSystem } from '@dotfiles/installer';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { stripVersionPrefix } from '@dotfiles/utils';\nimport { GiteaApiClient } from './gitea-client';\nimport type { IGiteaApiClient } from './gitea-client';\nimport { fetchGiteaRelease, installFromGiteaRelease } from './installFromGiteaRelease';\nimport { messages } from './log-messages';\nimport {\n type GiteaReleaseInstallParams,\n giteaReleaseInstallParamsSchema,\n type GiteaReleaseToolConfig,\n giteaReleaseToolConfigSchema,\n} from './schemas';\nimport type { IGiteaReleaseInstallMetadata } from './types';\n\n/**\n * Installer plugin for tools distributed via Gitea/Forgejo releases.\n *\n * Supports any Gitea-compatible instance (Gitea, Forgejo, Codeberg, etc.).\n * Each tool configuration specifies the instance URL, allowing tools from\n * different instances to be managed together.\n *\n * **Key Features:**\n * - Automatic asset selection based on platform and architecture\n * - Support for versioned releases and \"latest\" tag\n * - Automatic archive extraction for compressed releases\n * - Direct binary downloads for standalone executables\n * - Update checking via Gitea API\n * - Per-tool instance URL configuration\n * - Built-in API response caching\n */\nexport class GiteaReleaseInstallerPlugin implements\n IInstallerPlugin<\n 'gitea-release',\n GiteaReleaseInstallParams,\n GiteaReleaseToolConfig,\n IGiteaReleaseInstallMetadata\n >\n{\n public readonly method = 'gitea-release' as const;\n public readonly displayName = 'Gitea Release';\n public readonly version = '1.0.0';\n\n public readonly paramsSchema = giteaReleaseInstallParamsSchema;\n public readonly toolConfigSchema = giteaReleaseToolConfigSchema;\n\n constructor(\n private readonly fs: IFileSystem,\n private readonly downloader: IDownloader,\n private readonly archiveExtractor: IArchiveExtractor,\n private readonly hookExecutor: HookExecutor,\n private readonly cache?: ICache,\n ) {}\n\n /**\n * Creates a Gitea API client for the given tool configuration.\n * Each tool can point to a different Gitea instance.\n */\n private createApiClient(toolConfig: GiteaReleaseToolConfig, logger: TsLogger): IGiteaApiClient {\n const params = toolConfig.installParams as GiteaReleaseInstallParams;\n return new GiteaApiClient(\n logger,\n params.instanceUrl,\n this.downloader,\n this.cache,\n { token: params.token },\n );\n }\n\n async install(\n toolName: string,\n toolConfig: GiteaReleaseToolConfig,\n context: IInstallContext,\n options: IInstallOptions | undefined,\n logger: TsLogger,\n ): Promise<InstallResult<IGiteaReleaseInstallMetadata>> {\n const toolFs = createToolFileSystem(this.fs, toolName);\n const apiClient = this.createApiClient(toolConfig, logger);\n\n const result = await installFromGiteaRelease(\n toolName,\n toolConfig,\n context,\n options,\n toolFs,\n this.downloader,\n apiClient,\n this.archiveExtractor,\n this.hookExecutor,\n logger,\n );\n\n return result;\n }\n\n async resolveVersion(\n toolName: string,\n toolConfig: GiteaReleaseToolConfig,\n _context: IInstallContext,\n logger: TsLogger,\n ): Promise<string | null> {\n try {\n const params = toolConfig.installParams as GiteaReleaseInstallParams;\n const version: string = toolConfig.version || 'latest';\n const apiClient = this.createApiClient(toolConfig, logger);\n\n const releaseResult = await fetchGiteaRelease(\n params.repo,\n version,\n params.prerelease ?? false,\n apiClient,\n logger,\n );\n\n if (!releaseResult.success) {\n logger.debug(messages.versionResolutionFailed(toolName, releaseResult.error));\n return null;\n }\n\n const normalizedVersion: string = stripVersionPrefix(releaseResult.data.tag_name);\n logger.debug(messages.versionResolutionResolved(toolName, normalizedVersion));\n return normalizedVersion;\n } catch (error) {\n logger.debug(messages.versionResolutionException(toolName), error);\n return null;\n }\n }\n\n supportsUpdate(): boolean {\n return true;\n }\n\n supportsUpdateCheck(): boolean {\n return true;\n }\n\n async checkUpdate(\n toolName: string,\n toolConfig: GiteaReleaseToolConfig,\n _context: IInstallContext,\n logger: TsLogger,\n ): Promise<UpdateCheckResult> {\n try {\n const params = toolConfig.installParams as GiteaReleaseInstallParams;\n const repo = params.repo;\n const [owner, repoName] = repo.split('/');\n\n if (!owner || !repoName) {\n const result: UpdateCheckResult = {\n success: false,\n error: `Invalid repo format: ${repo}. Expected owner/repo`,\n };\n return result;\n }\n\n const apiClient = this.createApiClient(toolConfig, logger);\n const latestRelease = await apiClient.getLatestRelease(owner, repoName);\n if (!latestRelease || !latestRelease.tag_name) {\n const result: UpdateCheckResult = {\n success: false,\n error: `Could not fetch latest release for ${toolName}`,\n };\n return result;\n }\n\n const configuredVersion = toolConfig.version || 'latest';\n const latestVersion = latestRelease.tag_name.replace(/^v/, '');\n\n if (configuredVersion === 'latest') {\n const result: UpdateCheckResult = {\n success: true,\n hasUpdate: false,\n currentVersion: latestVersion,\n latestVersion,\n };\n return result;\n }\n\n const result: UpdateCheckResult = {\n success: true,\n hasUpdate: configuredVersion !== latestVersion,\n currentVersion: configuredVersion,\n latestVersion,\n };\n return result;\n } catch (error) {\n logger.error(messages.updateCheckFailed(toolName), error);\n const result: UpdateCheckResult = {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n };\n return result;\n }\n }\n\n supportsReadme(): boolean {\n return true;\n }\n\n getReadmeUrl(_toolName: string, toolConfig: GiteaReleaseToolConfig): string | null {\n const params = toolConfig.installParams as GiteaReleaseInstallParams;\n const repo = params.repo;\n const [owner, repoName] = repo.split('/');\n\n if (!owner || !repoName) {\n return null;\n }\n\n const instanceUrl = params.instanceUrl.replace(/\\/+$/, '');\n const branch = toolConfig.version || 'main';\n return `${instanceUrl}/${owner}/${repoName}/raw/branch/${branch}/README.md`;\n }\n}\n",
|
|
145
|
+
"import type { IInstallContext } from '@dotfiles/core';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { IInstallOptions } from '@dotfiles/installer';\nimport { createToolFileSystem, getBinaryNames, getBinaryPaths, withInstallErrorHandling } from '@dotfiles/installer';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { expandToolConfigPath } from '@dotfiles/utils';\nimport path from 'node:path';\nimport { messages } from './log-messages';\nimport type { ManualToolConfig } from './schemas';\nimport type { IManualInstallMetadata, ManualInstallResult } from './types';\n\n/**\n * Installs a manually managed tool.\n *\n * This function verifies that the specified binary exists at the configured path,\n * copies it to the installation directory, and sets the appropriate permissions.\n * If no binary path is specified, it creates a placeholder installation record.\n *\n * @param toolName - The name of the tool to install.\n * @param toolConfig - The configuration for the manual tool.\n * @param context - The base installation context.\n * @param _options - Optional installation options (currently unused).\n * @param fs - The file system interface for file operations.\n * @param parentLogger - The parent logger for creating sub-loggers.\n * @returns A promise that resolves to the installation result.\n */\nexport async function installManually(\n toolName: string,\n toolConfig: ManualToolConfig,\n context: IInstallContext,\n _options: IInstallOptions | undefined,\n fs: IFileSystem,\n parentLogger: TsLogger,\n): Promise<ManualInstallResult> {\n const toolFs = createToolFileSystem(fs, toolName);\n const logger = parentLogger.getSubLogger({ name: 'installManually' });\n logger.debug(messages.installing(toolName));\n\n const params = toolConfig.installParams;\n\n const operation = async (): Promise<ManualInstallResult> => {\n let binaryPaths: string[] = [];\n\n // Handle binary installation if binaryPath is specified\n if (params?.binaryPath) {\n const binaryPath = expandToolConfigPath(\n toolConfig.configFilePath,\n params.binaryPath,\n context.projectConfig,\n context.systemInfo,\n );\n\n if (!(await toolFs.exists(binaryPath))) {\n return {\n success: false,\n error: `Binary not found at ${binaryPath}`,\n };\n }\n\n await installBinariesManually(toolConfig, toolName, context, toolFs, binaryPath, logger);\n binaryPaths = getBinaryPaths(toolConfig.binaries, context.stagingDir);\n }\n\n const metadata: IManualInstallMetadata = {\n method: 'manual',\n manualInstall: true,\n };\n\n return {\n success: true,\n binaryPaths,\n metadata,\n };\n };\n\n return withInstallErrorHandling('manual', toolName, logger, operation);\n}\n\n/**\n * Copies manually installed binaries to the installation directory.\n *\n * This function handles copying the binary file from its source location to the\n * installation directory and setting executable permissions. For tools with multiple\n * binaries, only the primary binary (matching the tool name) is currently supported.\n *\n * @param toolConfig - The configuration for the manual tool.\n * @param toolName - The name of the tool being installed.\n * @param context - The base installation context.\n * @param toolFs - The file system interface scoped to this tool.\n * @param binaryPath - The source path of the binary to install.\n * @param parentLogger - The parent logger for creating sub-loggers.\n * @returns A promise that resolves when binary installation is complete.\n */\nasync function installBinariesManually(\n toolConfig: ManualToolConfig,\n toolName: string,\n context: IInstallContext,\n toolFs: IFileSystem,\n binaryPath: string,\n parentLogger: TsLogger,\n): Promise<void> {\n const logger = parentLogger.getSubLogger({ name: 'installBinariesManually' });\n const binaryNames = getBinaryNames(toolConfig.binaries);\n\n for (const binaryName of binaryNames) {\n const finalBinaryPath = path.join(context.stagingDir, binaryName);\n\n if (binaryName === toolName || binaryNames.length === 1) {\n await toolFs.ensureDir(path.dirname(finalBinaryPath));\n await toolFs.copyFile(binaryPath, finalBinaryPath);\n await toolFs.chmod(finalBinaryPath, 0o755);\n } else {\n logger.debug(messages.multipleBinariesNotSupported());\n }\n }\n}\n",
|
|
146
|
+
"import { createSafeLogMessage, type SafeLogMessageMap } from '@dotfiles/logger';\n\nexport const messages = {\n installing: (toolName: string) => createSafeLogMessage(`Manual installation: toolName=${toolName}`),\n manualInstructions: () => createSafeLogMessage('Manual installation requires user action'),\n multipleBinariesNotSupported: () => createSafeLogMessage('Manual installation does not support multiple binaries'),\n} as const satisfies SafeLogMessageMap;\n",
|
|
147
|
+
"import type { BaseInstallParams } from '@dotfiles/core';\nimport { baseInstallParamsSchema } from '@dotfiles/core';\nimport { z } from 'zod';\n\n/**\n * Parameters for a \"manual\" installation method.\n * This method is used to install files from the tool configuration directory\n * (e.g., custom scripts, pre-built binaries, or other resources included with the dotfiles).\n * Hooks can be used to provide custom validation or setup steps.\n */\nexport const manualInstallParamsSchema = baseInstallParamsSchema.extend({\n /**\n * The path to the binary file relative to the tool configuration file location.\n * If not specified, only shell configurations and symlinks will be processed.\n */\n binaryPath: z.string().min(1).optional(),\n});\n\n/**\n * Parameters for a \"manual\" installation method.\n *\n * NOTE: This is an explicit interface (not z.infer) to ensure TypeScript fully resolves\n * the property names, which is required for proper `keyof` behavior in declaration files.\n */\nexport interface ManualInstallParams extends BaseInstallParams {\n /**\n * The path to the binary file relative to the tool configuration file location.\n * If not specified, only shell configurations and symlinks will be processed.\n */\n binaryPath?: string;\n}\n",
|
|
148
|
+
"import {\n baseToolConfigWithPlatformsSchema,\n binaryConfigSchema,\n type InferToolConfigWithPlatforms,\n} from '@dotfiles/core';\nimport { z } from 'zod';\nimport { manualInstallParamsSchema } from './manualInstallParamsSchema';\n\nexport const manualToolConfigSchema = baseToolConfigWithPlatformsSchema.extend({\n /** Resolved tool configuration for the 'manual' installation method */\n installationMethod: z.literal('manual'),\n /** Manual installation parameters */\n installParams: manualInstallParamsSchema.optional(),\n /** Binaries are optional for this installation method */\n binaries: z.array(z.union([z.string().min(1), binaryConfigSchema])).optional(),\n});\n\n/** Resolved tool configuration for the 'manual' installation method. */\nexport type ManualToolConfig = InferToolConfigWithPlatforms<typeof manualToolConfigSchema>;\n",
|
|
149
|
+
"import type { IInstallContext, IInstallerPlugin, IInstallOptions, InstallResult } from '@dotfiles/core';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { installManually } from './installManually';\nimport {\n type ManualInstallParams,\n manualInstallParamsSchema,\n type ManualToolConfig,\n manualToolConfigSchema,\n} from './schemas';\n\nconst PLUGIN_VERSION = '1.0.0';\n\ntype ManualPluginMetadata = {\n method: 'manual';\n};\n\n/**\n * Installer plugin for manually installed tools.\n *\n * This plugin handles tools that are installed manually by the user rather than\n * automatically downloaded and installed. It verifies that specified binaries exist\n * at the configured paths and creates the necessary symlinks in the installation\n * directory. Since manual tools are managed by the user, this plugin does not\n * support version checking or automatic updates.\n */\nexport class ManualInstallerPlugin implements\n IInstallerPlugin<\n 'manual',\n ManualInstallParams,\n ManualToolConfig,\n ManualPluginMetadata\n >\n{\n readonly method = 'manual';\n readonly displayName = 'Manual Installer';\n readonly version = PLUGIN_VERSION;\n readonly paramsSchema = manualInstallParamsSchema;\n readonly toolConfigSchema = manualToolConfigSchema;\n\n /**\n * Creates a new ManualInstallerPlugin instance.\n *\n * @param fs - The file system interface for file operations.\n */\n constructor(private readonly fs: IFileSystem) {}\n\n /**\n * Installs a manually managed tool by verifying binaries and creating symlinks.\n *\n * @param toolName - The name of the tool to install.\n * @param toolConfig - The configuration for the manual tool.\n * @param context - The base installation context.\n * @param options - Optional installation options.\n * @param logger - The logger with tool context for logging operations.\n * @returns A promise that resolves to the installation result.\n */\n async install(\n toolName: string,\n toolConfig: ManualToolConfig,\n context: IInstallContext,\n options: IInstallOptions | undefined,\n logger: TsLogger,\n ): Promise<InstallResult<ManualPluginMetadata>> {\n const result = await installManually(toolName, toolConfig, context, options, this.fs, logger);\n\n if (!result.success) {\n return {\n success: false,\n error: result.error,\n };\n }\n\n const installResult: InstallResult<ManualPluginMetadata> = {\n success: true,\n binaryPaths: result.binaryPaths,\n metadata: result.metadata,\n };\n\n return installResult;\n }\n\n /**\n * Indicates whether this plugin supports version update checking.\n *\n * @returns False, as manual installations don't support automatic version checking.\n */\n supportsUpdate(): boolean {\n return false;\n }\n\n supportsUpdateCheck(): boolean {\n return false; // manual installation doesn't support version checking\n }\n\n /**\n\n * Indicates whether this plugin supports README fetching.\n *\n * @returns False, as manual installations don't have associated READMEs to fetch.\n */\n supportsReadme(): boolean {\n return false;\n }\n}\n",
|
|
150
|
+
"import type { ISystemInfo, PlatformConfig, PlatformConfigEntry, ToolConfig } from '@dotfiles/core';\nimport { Architecture, hasArchitecture, hasPlatform, Platform } from '@dotfiles/core';\n\n/**\n * Checks if a platform config entry matches the given system info.\n * @param entry - The platform configuration entry to check\n * @param systemInfo - The current system information\n * @returns True if the platform config matches the system\n */\nfunction matchesPlatform(entry: PlatformConfigEntry, systemInfo: ISystemInfo): boolean {\n // If system platform is unknown, no match\n if (systemInfo.platform === Platform.None) {\n return false;\n }\n\n // Check if system platform matches the entry's platforms using hasPlatform helper\n const platformMatches = hasPlatform(entry.platforms, systemInfo.platform);\n if (!platformMatches) {\n return false;\n }\n\n // If architectures are specified in the entry, check for architecture match\n if (entry.architectures !== undefined) {\n if (systemInfo.arch === Architecture.None) {\n return false; // Unknown architecture doesn't match any specific requirement\n }\n const archMatches = hasArchitecture(entry.architectures, systemInfo.arch);\n if (!archMatches) {\n return false;\n }\n }\n\n return true;\n}\n\nfunction deepCopyShellTypeConfig(\n config: NonNullable<NonNullable<ToolConfig['shellConfigs']>['zsh']>,\n): NonNullable<NonNullable<ToolConfig['shellConfigs']>['zsh']> {\n return {\n ...config,\n scripts: config.scripts ? [...config.scripts] : undefined,\n functions: config.functions ? { ...config.functions } : undefined,\n paths: config.paths ? [...config.paths] : undefined,\n };\n}\n\nfunction deepCopyShellConfigs(shellConfigs: ToolConfig['shellConfigs']): ToolConfig['shellConfigs'] {\n if (!shellConfigs) return undefined;\n\n return {\n zsh: shellConfigs.zsh ? deepCopyShellTypeConfig(shellConfigs.zsh) : undefined,\n bash: shellConfigs.bash ? deepCopyShellTypeConfig(shellConfigs.bash) : undefined,\n powershell: shellConfigs.powershell ? deepCopyShellTypeConfig(shellConfigs.powershell) : undefined,\n };\n}\n\nfunction initializeShellConfigs(finalConfig: ToolConfig): void {\n if (!finalConfig.shellConfigs) {\n finalConfig.shellConfigs = {\n zsh: undefined,\n bash: undefined,\n powershell: undefined,\n };\n }\n}\n\nfunction mergeShellConfig(\n shellConfigs: NonNullable<ToolConfig['shellConfigs']>,\n shellType: 'zsh' | 'bash' | 'powershell',\n platformShellConfig: NonNullable<ToolConfig['shellConfigs']>[typeof shellType],\n): void {\n if (!platformShellConfig) return;\n\n if (!shellConfigs[shellType]) {\n shellConfigs[shellType] = {};\n }\n\n // oxlint-disable-next-line @typescript-eslint/no-non-null-assertion: shellConfigs[shellType] is guaranteed to exist after the check above\n const targetShellConfig = shellConfigs[shellType]!;\n\n if (platformShellConfig.scripts) {\n targetShellConfig.scripts = [...(targetShellConfig.scripts || []), ...platformShellConfig.scripts];\n }\n\n if (platformShellConfig.completions) {\n targetShellConfig.completions = platformShellConfig.completions;\n }\n\n if (platformShellConfig.aliases) {\n targetShellConfig.aliases = { ...targetShellConfig.aliases, ...platformShellConfig.aliases };\n }\n\n if (platformShellConfig.env) {\n targetShellConfig.env = {\n ...targetShellConfig.env,\n ...platformShellConfig.env,\n };\n }\n\n if (platformShellConfig.functions) {\n targetShellConfig.functions = { ...targetShellConfig.functions, ...platformShellConfig.functions };\n }\n\n if (platformShellConfig.paths) {\n targetShellConfig.paths = [...(targetShellConfig.paths || []), ...platformShellConfig.paths];\n }\n}\n\nfunction mergeShellConfigs(finalConfig: ToolConfig, platformShellConfigs: ToolConfig['shellConfigs']): void {\n if (!platformShellConfigs) return;\n\n initializeShellConfigs(finalConfig);\n // shellConfigs is guaranteed to exist after initializeShellConfigs\n // oxlint-disable-next-line @typescript-eslint/no-non-null-assertion: shellConfigs is guaranteed to exist after initializeShellConfigs\n const shellConfigs = finalConfig.shellConfigs!;\n\n mergeShellConfig(shellConfigs, 'zsh', platformShellConfigs.zsh);\n mergeShellConfig(shellConfigs, 'bash', platformShellConfigs.bash);\n mergeShellConfig(shellConfigs, 'powershell', platformShellConfigs.powershell);\n}\n\nfunction applyPlatformOverrides(finalConfig: ToolConfig, platformConfig: PlatformConfig): void {\n if (platformConfig.binaries !== undefined) {\n finalConfig.binaries = platformConfig.binaries;\n }\n if (platformConfig.dependencies !== undefined) {\n finalConfig.dependencies = platformConfig.dependencies;\n }\n if (platformConfig.version !== undefined) {\n finalConfig.version = platformConfig.version;\n }\n if (platformConfig.updateCheck !== undefined) {\n finalConfig.updateCheck = platformConfig.updateCheck;\n }\n if (platformConfig.installationMethod !== undefined) {\n finalConfig.installationMethod = platformConfig.installationMethod;\n }\n if (platformConfig.installParams !== undefined) {\n finalConfig.installParams = platformConfig.installParams;\n }\n}\n\nfunction createBaseResolvedConfig(toolConfig: ToolConfig): ToolConfig {\n const resolvedConfig = {\n ...toolConfig,\n shellConfigs: deepCopyShellConfigs(toolConfig.shellConfigs),\n dependencies: toolConfig.dependencies ? [...toolConfig.dependencies] : undefined,\n };\n\n const { platformConfigs, ...configWithoutPlatforms } = resolvedConfig;\n return configWithoutPlatforms;\n}\n\n/**\n * Resolves platform-specific configurations for a tool based on system information.\n * Merges the base tool configuration with matching platform-specific overrides.\n *\n * @param toolConfig - The base tool configuration\n * @param systemInfo - The current system information\n * @returns A resolved tool configuration with platform-specific overrides applied\n */\nexport function resolvePlatformConfig(toolConfig: ToolConfig, systemInfo: ISystemInfo): ToolConfig {\n // If no platform configs exist, return the original config\n if (!toolConfig.platformConfigs || toolConfig.platformConfigs.length === 0) {\n return toolConfig;\n }\n\n // Find matching platform configurations\n const matchingConfigs = toolConfig.platformConfigs.filter((entry) => matchesPlatform(entry, systemInfo));\n\n // If no matches found, return the original config without platformConfigs\n if (matchingConfigs.length === 0) {\n const { platformConfigs, ...configWithoutPlatforms } = toolConfig;\n return configWithoutPlatforms;\n }\n\n // Start with a deep copy of the base config (excluding platformConfigs to avoid recursion)\n const finalConfig = createBaseResolvedConfig(toolConfig);\n\n // Apply each matching platform config in order\n for (const match of matchingConfigs) {\n const config = match.config;\n // Merge shell configs\n mergeShellConfigs(finalConfig, config.shellConfigs);\n\n // Merge symlinks arrays\n if (config.symlinks) {\n finalConfig.symlinks = [...(finalConfig.symlinks || []), ...config.symlinks];\n }\n\n // Merge copies arrays\n if (config.copies) {\n finalConfig.copies = [...(finalConfig.copies || []), ...config.copies];\n }\n\n // Override other properties\n applyPlatformOverrides(finalConfig, config);\n }\n\n return finalConfig;\n}\n",
|
|
151
|
+
"import path from 'node:path';\n\n/**\n * Resolves a relative path against the tool configuration directory (toolDir).\n *\n * This provides consistent path resolution for all defineTool() APIs:\n * - Absolute paths are returned as-is\n * - Relative paths are resolved against toolDir (the .tool.ts file's directory)\n *\n * @param toolDir - Absolute path to the tool configuration directory (where .tool.ts lives)\n * @param inputPath - The path to resolve (may be absolute or relative)\n * @returns The resolved absolute path\n *\n * @example\n * // Relative path\n * resolveToolRelativePath('/home/user/dotfiles/tools/fzf', './shell/init.zsh')\n * // Returns: '/home/user/dotfiles/tools/fzf/shell/init.zsh'\n *\n * @example\n * // Absolute path (unchanged)\n * resolveToolRelativePath('/home/user/dotfiles/tools/fzf', '/usr/local/bin/fzf')\n * // Returns: '/usr/local/bin/fzf'\n */\nexport function resolveToolRelativePath(toolDir: string, inputPath: string): string {\n const trimmedPath = inputPath.trim();\n\n if (path.isAbsolute(trimmedPath)) {\n return trimmedPath;\n }\n\n return path.resolve(toolDir, trimmedPath);\n}\n",
|
|
152
|
+
"/**\n * Strips the 'v' or 'V' prefix from a version string.\n *\n * @param version - Raw version string that may have a v/V prefix\n * @returns Version string without the v/V prefix\n *\n * @example\n * ```typescript\n * stripVersionPrefix('v1.2.3'); // Returns: '1.2.3'\n * stripVersionPrefix('V1.2.3'); // Returns: '1.2.3'\n * stripVersionPrefix('1.2.3'); // Returns: '1.2.3'\n * ```\n */\nexport function stripVersionPrefix(version: string): string {\n if (!version) {\n return version;\n }\n\n if (version.startsWith('v') || version.startsWith('V')) {\n return version.slice(1);\n }\n\n return version;\n}\n",
|
|
153
|
+
"import { type ArchiveFormat, type IExtractOptions, type IExtractResult, type Shell } from '@dotfiles/core';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { getAllFilesRecursively } from '@dotfiles/utils';\nimport { randomUUID } from 'node:crypto';\nimport { basename, extname, join } from 'node:path';\nimport type { IArchiveExtractor } from './IArchiveExtractor';\nimport { messages } from './log-messages';\n\n/**\n * Implements archive extraction using system commands.\n *\n * This class provides functionality to extract various archive formats (tar.gz, tar.bz2,\n * tar.xz, tar, zip) using system commands like `tar` and `unzip`. It can auto-detect\n * archive formats by file extension or MIME type, detect and set executable permissions\n * on extracted files, and handle various extraction options.\n *\n * This implementation has a hard dependency on system commands (tar, unzip, file) being\n * available on the system's PATH.\n */\nexport class ArchiveExtractor implements IArchiveExtractor {\n private readonly fs: IFileSystem;\n private readonly logger: TsLogger;\n private readonly shell: Shell;\n\n /**\n * Creates a new ArchiveExtractor instance.\n *\n * @param parentLogger - The parent logger for creating sub-loggers.\n * @param fileSystem - The file system interface for file operations.\n * @param shell - The shell executor for running system commands.\n */\n constructor(parentLogger: TsLogger, fileSystem: IFileSystem, shell: Shell) {\n this.fs = fileSystem;\n this.logger = parentLogger.getSubLogger({ name: 'ArchiveExtractor' });\n this.shell = shell;\n }\n\n /**\n * Detects archive format by examining the file extension.\n *\n * @param fileName - The name of the file to check.\n * @returns The detected ArchiveFormat, or null if the format cannot be determined from the extension.\n */\n private detectFormatByExtension(fileName: string): ArchiveFormat | null {\n const lowerFileName = fileName.toLowerCase();\n\n if (lowerFileName.endsWith('.tar.gz') || lowerFileName.endsWith('.tgz')) return 'tar.gz';\n if (lowerFileName.endsWith('.gz')) return 'gzip';\n if (lowerFileName.endsWith('.tar.bz2') || lowerFileName.endsWith('.tbz2') || lowerFileName.endsWith('.tbz'))\n return 'tar.bz2';\n if (lowerFileName.endsWith('.tar.xz') || lowerFileName.endsWith('.txz')) return 'tar.xz';\n if (lowerFileName.endsWith('.tar.lzma')) return 'tar.lzma';\n if (lowerFileName.endsWith('.tar')) return 'tar';\n if (lowerFileName.endsWith('.zip')) return 'zip';\n if (lowerFileName.endsWith('.rar')) return 'rar';\n if (lowerFileName.endsWith('.7z')) return '7z';\n if (lowerFileName.endsWith('.deb')) return 'deb';\n if (lowerFileName.endsWith('.rpm')) return 'rpm';\n if (lowerFileName.endsWith('.dmg')) return 'dmg';\n\n return null;\n }\n\n /**\n * Detects archive format by parsing MIME type output from the `file` command.\n *\n * @param mimeOutput - The output from the `file --mime-type` command.\n * @returns The detected ArchiveFormat, or null if the format cannot be determined from MIME type.\n */\n private detectFormatByMimeType(mimeOutput: string): ArchiveFormat | null {\n if (mimeOutput.includes('gzip')) return 'tar.gz';\n if (mimeOutput.includes('zip')) return 'zip';\n if (mimeOutput.includes('x-bzip2')) return 'tar.bz2';\n if (mimeOutput.includes('x-xz')) return 'tar.xz';\n if (mimeOutput.includes('x-tar')) return 'tar';\n if (mimeOutput.includes('x-7z-compressed')) return '7z';\n if (mimeOutput.includes('x-rar-compressed')) return 'rar';\n if (mimeOutput.includes('x-debian-package')) return 'deb';\n if (mimeOutput.includes('x-rpm')) return 'rpm';\n if (mimeOutput.includes('x-apple-diskimage')) return 'dmg';\n\n return null;\n }\n\n private async detectFormatUsingFileCommand(filePath: string, logger: TsLogger): Promise<ArchiveFormat | null> {\n try {\n const commandName = 'file';\n logger.debug(messages.shellCommandStarted(commandName));\n const result = await this.shell`file -b --mime-type ${filePath}`.quiet();\n const output = result.stdout.trim();\n\n return this.detectFormatByMimeType(output);\n } catch (error) {\n logger.debug(messages.fileCommandFallbackFailed(filePath), error);\n return null;\n }\n }\n\n /**\n * @inheritdoc IArchiveExtractor.detectFormat\n */\n public async detectFormat(filePath: string): Promise<ArchiveFormat> {\n const logger = this.logger.getSubLogger({ name: 'detectFormat' });\n const fileName = basename(filePath);\n\n // Try detection by file extension first\n const formatByExtension = this.detectFormatByExtension(fileName);\n if (formatByExtension) {\n return formatByExtension;\n }\n\n // Fallback to 'file' command\n const formatByMimeType = await this.detectFormatUsingFileCommand(filePath, logger);\n if (formatByMimeType) {\n return formatByMimeType;\n }\n\n throw new Error(`Unsupported or undetectable archive format for: ${filePath}`);\n }\n\n /**\n * @inheritdoc IArchiveExtractor.isSupported\n */\n public isSupported(format: ArchiveFormat): boolean {\n const supportedFormats: ArchiveFormat[] = [\n 'tar.gz',\n 'tar.bz2',\n 'tar.xz',\n 'tar',\n 'zip',\n 'gzip',\n // 'rar', '7z', 'deb', 'rpm', 'dmg' // Add as implemented\n ];\n return supportedFormats.includes(format);\n }\n\n private getTarFlagForFormat(format: ArchiveFormat): string {\n switch (format) {\n case 'tar.gz':\n return '-xzf';\n case 'tar.bz2':\n return '-xjf';\n case 'tar.xz':\n return '-xJf';\n case 'tar':\n return '-xf';\n default:\n throw new Error(`Unsupported tar format: ${format}`);\n }\n }\n\n /**\n * Extracts an archive using the appropriate system command for the format.\n *\n * @param format - The archive format to extract.\n * @param archivePath - Path to the archive file.\n * @param tempExtractDir - Directory where files will be extracted.\n * @returns A promise that resolves when extraction is complete.\n * @throws {Error} If the format is not implemented or the command fails.\n */\n private async extractArchiveByFormat(\n format: ArchiveFormat,\n archivePath: string,\n tempExtractDir: string,\n ): Promise<void> {\n switch (format) {\n case 'tar.gz':\n case 'tar.bz2':\n case 'tar.xz':\n case 'tar': {\n const tarFlag = this.getTarFlagForFormat(format);\n const commandName = 'tar';\n this.logger.debug(messages.shellCommandStarted(commandName));\n await this.shell`tar ${tarFlag} ${archivePath} -C ${tempExtractDir}`.quiet();\n break;\n }\n case 'zip': {\n const commandName = 'unzip';\n this.logger.debug(messages.shellCommandStarted(commandName));\n await this.shell`unzip -qo ${archivePath} -d ${tempExtractDir}`.quiet();\n break;\n }\n case 'gzip': {\n const commandName = 'gunzip';\n this.logger.debug(messages.shellCommandStarted(commandName));\n // For single-file gzip, output filename is the archive name without .gz extension\n const archiveBasename = basename(archivePath);\n const outputName = archiveBasename.endsWith('.gz')\n ? archiveBasename.slice(0, -3)\n : archiveBasename;\n const outputPath = join(tempExtractDir, outputName);\n await this.shell`gunzip -c ${archivePath} > ${outputPath}`.quiet();\n break;\n }\n default:\n throw new Error(`Extraction for format ${format} not implemented.`);\n }\n }\n\n /**\n * @inheritdoc IArchiveExtractor.extract\n */\n public async extract(\n parentLogger: TsLogger,\n archivePath: string,\n options: IExtractOptions = {},\n ): Promise<IExtractResult> {\n const logger = parentLogger.getSubLogger({ name: 'extract' });\n const {\n format: explicitFormat,\n targetDir = '.', // Default to current directory if not specified\n detectExecutables = true,\n } = options;\n\n logger.debug(messages.extractionRequested(archivePath), options);\n\n const format = explicitFormat || (await this.detectFormat(archivePath));\n\n if (!this.isSupported(format)) {\n throw new Error(`Unsupported archive format: ${format}`);\n }\n\n // Ensure target directory exists\n await this.fs.ensureDir(targetDir);\n\n // Create a temporary subdirectory for extraction to avoid including the archive file itself\n const extractId = randomUUID();\n const tempExtractDir = join(targetDir, `.extract-temp-${extractId}`);\n await this.fs.ensureDir(tempExtractDir);\n\n try {\n // Extract to temporary directory\n await this.extractArchiveByFormat(format, archivePath, tempExtractDir);\n\n // Get all files from the temp directory\n const extractedFiles = await getAllFilesRecursively(this.fs, tempExtractDir);\n\n // Move files from temp directory to target directory\n for (const filePath of extractedFiles) {\n const relativePath = filePath.substring(tempExtractDir.length + 1);\n const targetPath = join(targetDir, relativePath);\n const targetDirPath = join(targetPath, '..');\n\n await this.fs.ensureDir(targetDirPath);\n await this.fs.rename(filePath, targetPath);\n }\n\n // Remove the temporary directory\n await this.fs.rm(tempExtractDir, { recursive: true, force: true });\n\n // Update file paths to reflect final location\n const finalFiles = extractedFiles.map((filePath) => {\n const relativePath = filePath.substring(tempExtractDir.length + 1);\n return join(targetDir, relativePath);\n });\n\n const result: IExtractResult = {\n extractedFiles: finalFiles,\n executables: [],\n };\n\n if (detectExecutables) {\n result.executables = await this.detectAndSetExecutables(finalFiles);\n }\n\n return result;\n } catch (error) {\n // Clean up temp directory on error\n try {\n await this.fs.rm(tempExtractDir, { recursive: true, force: true });\n } catch {\n // Ignore cleanup errors\n }\n throw error;\n }\n }\n\n /**\n * Detects files that should be executable and sets the executable permission bit.\n *\n * This method uses heuristics to identify executables: files without extensions or with\n * common script extensions (.sh, .py, .pl, .rb). It checks if the file already has the\n * owner execute bit set, and if not, adds it.\n *\n * @param baseDir - The base directory containing the extracted files (unused, kept for compatibility).\n * @param files - Array of absolute file paths to check.\n * @returns Array of absolute file paths that were identified as executables.\n */\n private async detectAndSetExecutables(files: string[]): Promise<string[]> {\n const logger = this.logger.getSubLogger({ name: 'detectAndSetExecutables' });\n const executables: string[] = [];\n // This is a simplified check. `file` command is more robust.\n // For `zx`, we'd need to ensure `file` command is available or use Node.js based checks.\n // For now, let's assume a simple extension check or common binary names.\n // A more robust solution would use `await $`file -b ${filePath}`;` and parse output.\n for (const filePath of files) {\n try {\n const stat = await this.fs.stat(filePath);\n if (stat.isFile()) {\n // Heuristic: files without extensions or common script extensions\n // This is very basic and platform-dependent.\n const ext = extname(filePath);\n if (ext === '' || ['.sh', '.py', '.pl', '.rb'].includes(ext)) {\n // Check if it's already executable (owner execute bit)\n if (!(stat.mode & 0o100)) {\n logger.debug(messages.executableFlagApplied(filePath));\n await this.fs.chmod(filePath, stat.mode | 0o100); // Add owner execute\n }\n executables.push(filePath);\n }\n }\n } catch (error) {\n logger.debug(messages.executableCheckFailed(filePath), error);\n }\n }\n return executables;\n }\n}\n",
|
|
154
|
+
"import { createSafeLogMessage, type SafeLogMessageMap } from '@dotfiles/logger';\n\nexport const messages = {\n shellCommandStarted: (command: string) => createSafeLogMessage(`Executing shell command: ${command}`),\n shellCommandFailed: (command: string, exitCode: number | null) =>\n createSafeLogMessage(`Shell command failed (exit ${exitCode ?? 'unknown'}): ${command}`),\n fileCommandFallbackFailed: (filePath: string) =>\n createSafeLogMessage(`Failed to detect archive format using file command: ${filePath}`),\n extractionRequested: (archivePath: string) => createSafeLogMessage(`Extracting archive ${archivePath}`),\n executableFlagApplied: (filePath: string) => createSafeLogMessage(`Marked file as executable: ${filePath}`),\n executableCheckFailed: (filePath: string) =>\n createSafeLogMessage(`Failed to inspect file for executable permissions: ${filePath}`),\n} satisfies SafeLogMessageMap;\n",
|
|
155
|
+
"import type { ArchiveFormat } from '@dotfiles/core';\n\n/**\n * Array of supported archive formats that can be extracted.\n *\n * This list should stay in sync with the ArchiveExtractor.isSupported method.\n */\nexport const SUPPORTED_ARCHIVE_FORMATS: ArchiveFormat[] = [\n 'tar.gz',\n 'tar.bz2',\n 'tar.xz',\n 'tar',\n 'zip',\n 'gzip',\n];\n\n/**\n * Maps file extensions to their corresponding archive formats.\n * Extensions are checked in order - longer/more specific patterns first.\n *\n * Returns the ArchiveFormat if the extension is recognized, or null otherwise.\n */\nfunction detectFormatByExtension(fileName: string): ArchiveFormat | null {\n const lowerFileName = fileName.toLowerCase();\n\n // Check multi-part extensions first (most specific)\n if (lowerFileName.endsWith('.tar.gz') || lowerFileName.endsWith('.tgz')) return 'tar.gz';\n if (lowerFileName.endsWith('.tar.bz2') || lowerFileName.endsWith('.tbz2') || lowerFileName.endsWith('.tbz'))\n return 'tar.bz2';\n if (lowerFileName.endsWith('.tar.xz') || lowerFileName.endsWith('.txz')) return 'tar.xz';\n if (lowerFileName.endsWith('.tar.lzma')) return 'tar.lzma';\n if (lowerFileName.endsWith('.tar')) return 'tar';\n\n // Check single-part extensions\n if (lowerFileName.endsWith('.gz')) return 'gzip';\n if (lowerFileName.endsWith('.zip')) return 'zip';\n if (lowerFileName.endsWith('.rar')) return 'rar';\n if (lowerFileName.endsWith('.7z')) return '7z';\n if (lowerFileName.endsWith('.deb')) return 'deb';\n if (lowerFileName.endsWith('.rpm')) return 'rpm';\n if (lowerFileName.endsWith('.dmg')) return 'dmg';\n\n return null;\n}\n\n/**\n * Checks if a file has an archive extension that is supported for extraction.\n *\n * This function checks both that the file has a recognized archive extension AND\n * that the format is actually supported by the extractor (not all detected formats\n * have extraction implemented).\n *\n * @param filename - The filename to check (can include path).\n * @returns True if the file has a supported archive extension, false otherwise.\n *\n * @example\n * isSupportedArchiveFile('tool.tar.gz') // true\n * isSupportedArchiveFile('tool.tbz') // true\n * isSupportedArchiveFile('tool.zip') // true\n * isSupportedArchiveFile('tool.rar') // false (not implemented)\n * isSupportedArchiveFile('tool.exe') // false (not an archive)\n */\nexport function isSupportedArchiveFile(filename: string): boolean {\n const format = detectFormatByExtension(filename);\n if (!format) return false;\n return SUPPORTED_ARCHIVE_FORMATS.includes(format);\n}\n",
|
|
156
|
+
"import { createSafeLogMessage, type SafeLogMessageMap } from '@dotfiles/logger';\n\nexport const messages = {\n configurationFieldIgnored: (field: string, reason: string) =>\n createSafeLogMessage(`Configuration field \"${field}\" ignored: ${reason}`),\n configurationFieldRequired: (field: string, example?: string) =>\n createSafeLogMessage(`Required configuration missing: ${field}${example ? `. Example: ${example}` : ''}`),\n configurationFieldInvalid: (field: string, value: string, expected: string) =>\n createSafeLogMessage(`Invalid ${field}: \"${value}\" (expected ${expected})`),\n invalidFunctionName: (functionName: string) => createSafeLogMessage(`Invalid function name: \"${functionName}\"`),\n} satisfies SafeLogMessageMap;\n",
|
|
157
|
+
"import { type Shell, type ShellType } from '@dotfiles/core';\nimport type { TsLogger } from '@dotfiles/logger';\nimport path from 'node:path';\nimport { messages } from './log-messages';\nimport type { ICompletionCommandExecutor } from './types';\n\nexport class CompletionCommandExecutor implements ICompletionCommandExecutor {\n private readonly logger: TsLogger;\n private readonly shell: Shell;\n\n constructor(parentLogger: TsLogger, shell: Shell) {\n this.logger = parentLogger.getSubLogger({ name: 'CompletionCommandExecutor' });\n this.shell = shell;\n }\n\n async executeCompletionCommand(\n cmd: string,\n toolName: string,\n shellType: ShellType,\n workingDir: string,\n binaryPaths?: string[],\n ): Promise<string> {\n const logger = this.logger.getSubLogger({ name: 'executeCompletionCommand' }).setPrefix(toolName);\n logger.debug(messages.commandExecutionStarted(toolName, cmd, shellType));\n\n // Build directories to prepend to PATH (from binaryPaths + workingDir)\n const binaryDirs = binaryPaths?.map((p) => path.dirname(p)) ?? [];\n const pathDirs = [...new Set([...binaryDirs, workingDir])];\n const pathPrefix = pathDirs.join(path.delimiter);\n\n // Validate binary existence BEFORE running the command\n // This prevents infinite loops when the command would fall through to a shim\n if (binaryPaths && binaryPaths.length > 0) {\n const binaryNames = [...new Set(binaryPaths.map((p) => path.basename(p)))];\n const foundAny = await this.checkAnyBinaryExistsInPath(binaryNames, pathPrefix);\n if (!foundAny) {\n const searchedLocations = pathDirs.join(', ');\n throw new Error(\n `None of the expected binaries (${binaryNames.join(', ')}) found in: ${searchedLocations}. ` +\n `Skipping completion generation to prevent infinite loop.`,\n );\n }\n }\n\n try {\n // Run the completion command with the enhanced PATH\n const fullCommand = `cd ${workingDir} && PATH=${pathPrefix}:$PATH ${cmd}`;\n const result = await this.shell`sh -c ${fullCommand}`.quiet();\n logger.debug(messages.commandExecutionCompleted(toolName, shellType));\n return result.stdout;\n } catch (error) {\n const exitCode = error && typeof error === 'object' && 'exitCode' in error ? (error.exitCode as number) : -1;\n const stderr = error && typeof error === 'object' && 'stderr' in error\n ? (error.stderr as Buffer).toString()\n : 'Unknown error';\n\n const errorMessage = `Completion command failed for ${toolName}: ${cmd}`;\n logger.error(messages.commandExecutionFailed(toolName, cmd, exitCode, stderr));\n throw new Error(`${errorMessage}\\nExit code: ${exitCode}\\nStderr: ${stderr}`, { cause: error });\n }\n }\n\n /**\n * Check if any of the given binaries exist using shell's `command -v` with the specified PATH.\n * Uses ONLY our path prefix (not inheriting system PATH) to ensure we only find binaries\n * in the expected locations.\n */\n private async checkAnyBinaryExistsInPath(binaryNames: string[], pathPrefix: string): Promise<boolean> {\n for (const binaryName of binaryNames) {\n try {\n const checkCommand = `PATH=${pathPrefix} command -v ${binaryName}`;\n await this.shell`sh -c ${checkCommand}`.quiet();\n return true;\n } catch {\n // Binary not found, continue checking other binaries\n }\n }\n return false;\n }\n}\n",
|
|
158
|
+
"import type { ShellType } from '@dotfiles/core';\nimport { createSafeLogMessage, type SafeLogMessageMap } from '@dotfiles/logger';\n\nexport const messages = {\n generationStarted: (toolName: string, shellType: ShellType) =>\n createSafeLogMessage(`Starting completion generation for \"${toolName}\" (shell: ${shellType})`),\n generationComplete: (filename: string, toolName: string, shellType: string) =>\n createSafeLogMessage(`Generated completion ${filename} for ${toolName} (${shellType})`),\n commandExecutionStarted: (toolName: string, command: string, shellType: ShellType) =>\n createSafeLogMessage(`Executing completion command for \"${toolName}\" (shell: ${shellType}): ${command}`),\n commandExecutionFailed: (toolName: string, command: string, exitCode: number, stderr: string) =>\n createSafeLogMessage(`Completion command failed for \"${toolName}\" [${command}] exit ${exitCode}: ${stderr}`),\n commandExecutionCompleted: (toolName: string, shellType: ShellType) =>\n createSafeLogMessage(`Completion command succeeded for \"${toolName}\" (shell: ${shellType})`),\n symlinkCreated: (sourcePath: string, targetPath: string) =>\n createSafeLogMessage(`Symlinked completion: ${sourcePath} -> ${targetPath}`),\n sourceNotFound: (sourcePath: string) => createSafeLogMessage(`Completion source file not found: ${sourcePath}`),\n downloadingCompletion: (url: string) => createSafeLogMessage(`Downloading completion from: ${url}`),\n completionDownloaded: (filePath: string) => createSafeLogMessage(`Completion downloaded to: ${filePath}`),\n completionAlreadyDownloaded: (filePath: string) => createSafeLogMessage(`Completion already downloaded: ${filePath}`),\n extractingCompletionArchive: (archivePath: string) =>\n createSafeLogMessage(`Extracting completion archive: ${archivePath}`),\n completionArchiveExtracted: (targetDir: string) =>\n createSafeLogMessage(`Completion archive extracted to: ${targetDir}`),\n} satisfies SafeLogMessageMap;\n",
|
|
159
|
+
"import type { IArchiveExtractor } from '@dotfiles/archive-extractor';\nimport type { ArchiveFormat, Shell, ShellCompletionConfig, ShellType } from '@dotfiles/core';\nimport type { IDownloader } from '@dotfiles/downloader';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { getAllFilesRecursively, resolveToolRelativePath } from '@dotfiles/utils';\nimport { minimatch } from 'minimatch';\nimport path from 'node:path';\nimport { CompletionCommandExecutor } from './CompletionCommandExecutor';\nimport { messages } from './log-messages';\nimport type {\n ICompletionCommandExecutor,\n ICompletionGenerationContext,\n ICompletionGenerator,\n IGenerateAndWriteCompletionFileOptions,\n IGeneratedCompletion,\n} from './types';\n\nconst ARCHIVE_EXTENSIONS: ArchiveFormat[] = ['tar.gz', 'tar.xz', 'tar.bz2', 'zip', 'tar', 'tar.lzma', '7z'];\n\nexport interface ICompletionGeneratorDependencies {\n downloader?: IDownloader;\n archiveExtractor?: IArchiveExtractor;\n}\n\nexport class CompletionGenerator implements ICompletionGenerator {\n private readonly logger: TsLogger;\n private readonly commandExecutor: ICompletionCommandExecutor;\n private readonly fs: IFileSystem;\n private readonly downloader?: IDownloader;\n private readonly archiveExtractor?: IArchiveExtractor;\n\n constructor(\n parentLogger: TsLogger,\n fs: IFileSystem,\n shell: Shell,\n commandExecutor?: ICompletionCommandExecutor,\n deps?: ICompletionGeneratorDependencies,\n ) {\n this.logger = parentLogger.getSubLogger({ name: 'CompletionGenerator' });\n this.fs = fs;\n this.commandExecutor = commandExecutor || new CompletionCommandExecutor(this.logger, shell);\n this.downloader = deps?.downloader;\n this.archiveExtractor = deps?.archiveExtractor;\n }\n\n async generateCompletionFile(\n config: ShellCompletionConfig,\n toolName: string,\n shellType: ShellType,\n context: ICompletionGenerationContext,\n ): Promise<IGeneratedCompletion> {\n const logger = this.logger.getSubLogger({ name: 'generateCompletionFile' }).setPrefix(toolName);\n logger.debug(messages.generationStarted(toolName, shellType));\n\n // If URL is provided, download the completion file/archive first\n const isUrlBased = Boolean(config.url);\n if (config.url) {\n await this.downloadCompletionFromUrl(config.url, context.toolInstallDir, toolName);\n }\n\n // Derive source from URL if not explicitly provided\n const effectiveSource = config.source || (config.url ? this.getFilenameFromUrl(config.url) : undefined);\n\n if (config.cmd) {\n return this.generateFromCommand(config, toolName, shellType, context);\n } else if (effectiveSource) {\n const effectiveConfig: ShellCompletionConfig = { ...config, source: effectiveSource };\n return this.generateFromSource(effectiveConfig, toolName, shellType, context, isUrlBased);\n } else {\n throw new Error(`Invalid completion config for ${toolName}: either 'cmd', 'source', or 'url' must be provided`);\n }\n }\n\n /**\n * Downloads a completion file or archive from a URL.\n * If the URL points to an archive, it will be extracted to the tool's install directory.\n */\n private async downloadCompletionFromUrl(url: string, toolInstallDir: string, toolName: string): Promise<void> {\n const logger = this.logger.getSubLogger({ name: 'downloadCompletionFromUrl' }).setPrefix(toolName);\n\n if (!this.downloader) {\n throw new Error('Downloader not provided - cannot download completion from URL');\n }\n\n logger.info(messages.downloadingCompletion(url));\n\n // Ensure the tool install directory exists\n await this.fs.ensureDir(toolInstallDir);\n\n const filename = this.getFilenameFromUrl(url);\n const downloadPath = path.join(toolInstallDir, filename);\n\n // Check if already downloaded\n if (await this.fs.exists(downloadPath)) {\n logger.debug(messages.completionAlreadyDownloaded(downloadPath));\n return;\n }\n\n await this.downloader.downloadToFile(logger, url, downloadPath);\n logger.debug(messages.completionDownloaded(downloadPath));\n\n // Check if it's an archive that needs extraction\n const archiveFormat = this.detectArchiveFormat(filename);\n if (archiveFormat) {\n await this.extractCompletionArchive(downloadPath, toolInstallDir, toolName);\n }\n }\n\n /**\n * Extracts a completion archive to the tool's install directory.\n */\n private async extractCompletionArchive(archivePath: string, toolInstallDir: string, toolName: string): Promise<void> {\n const logger = this.logger.getSubLogger({ name: 'extractCompletionArchive' }).setPrefix(toolName);\n\n if (!this.archiveExtractor) {\n throw new Error('Archive extractor not provided - cannot extract completion archive');\n }\n\n logger.debug(messages.extractingCompletionArchive(archivePath));\n await this.archiveExtractor.extract(logger, archivePath, {\n targetDir: toolInstallDir,\n });\n logger.debug(messages.completionArchiveExtracted(toolInstallDir));\n }\n\n /**\n * Extracts the filename from a URL.\n */\n private getFilenameFromUrl(url: string): string {\n const urlObj = new URL(url);\n const pathname = urlObj.pathname;\n const filename = path.basename(pathname);\n return filename || 'completion-download';\n }\n\n /**\n * Detects if a filename indicates an archive format.\n */\n private detectArchiveFormat(filename: string): ArchiveFormat | null {\n const lowerFilename = filename.toLowerCase();\n for (const format of ARCHIVE_EXTENSIONS) {\n if (lowerFilename.endsWith(`.${format}`)) {\n return format;\n }\n }\n return null;\n }\n\n /**\n * Generates and writes a completion file in one operation.\n * For command-based completions, writes the generated content to a file.\n * For source-based completions, creates a symlink to the source file.\n *\n * @param options - Generation options including optional fs override for tracking.\n */\n async generateAndWriteCompletionFile(options: IGenerateAndWriteCompletionFileOptions): Promise<IGeneratedCompletion> {\n const { config, toolName, shellType, context, fs } = options;\n\n const result = await this.generateCompletionFile(config, toolName, shellType, context);\n\n await fs.ensureDir(path.dirname(result.targetPath));\n\n if (result.generatedBy === 'source' && result.sourcePath) {\n // For source-based completions, create a symlink\n if (await fs.exists(result.targetPath)) {\n await fs.rm(result.targetPath);\n }\n await fs.symlink(result.sourcePath, result.targetPath);\n this.logger.debug(messages.symlinkCreated(result.sourcePath, result.targetPath));\n } else {\n // For command-based completions, write the content\n await fs.writeFile(result.targetPath, result.content);\n }\n\n return result;\n }\n\n private async generateFromCommand(\n config: ShellCompletionConfig,\n toolName: string,\n shellType: ShellType,\n context: ICompletionGenerationContext,\n ): Promise<IGeneratedCompletion> {\n if (!config.cmd) {\n throw new Error(`Command not provided for ${toolName}`);\n }\n\n const content = await this.commandExecutor.executeCompletionCommand(\n config.cmd,\n toolName,\n shellType,\n context.toolInstallDir,\n context.binaryPaths,\n );\n\n const filename = this.generateCompletionFilename(config, toolName, shellType);\n const targetPath = this.resolveTargetPath(shellType, context);\n\n return {\n content,\n filename,\n targetPath: path.join(targetPath, filename),\n generatedBy: 'command',\n };\n }\n\n private async generateFromSource(\n config: ShellCompletionConfig,\n toolName: string,\n shellType: ShellType,\n context: ICompletionGenerationContext,\n isUrlBased: boolean = false,\n ): Promise<IGeneratedCompletion> {\n if (!config.source) {\n throw new Error(`Source not provided for ${toolName}`);\n }\n\n const sourcePath = await this.resolveSourcePath(\n context.toolInstallDir,\n config.source,\n context.configFilePath,\n isUrlBased,\n );\n\n if (!(await this.fs.exists(sourcePath))) {\n this.logger.warn(messages.sourceNotFound(sourcePath));\n throw new Error(`Completion source file not found: ${sourcePath}`);\n }\n\n const filename = this.generateCompletionFilename(config, toolName, shellType);\n const targetPath = this.resolveTargetPath(shellType, context);\n\n return {\n content: '', // No content needed for symlink-based completions\n filename,\n targetPath: path.join(targetPath, filename),\n generatedBy: 'source',\n sourcePath,\n };\n }\n\n /**\n * Resolves the source path for a completion file.\n *\n * Resolution:\n * 1. Absolute paths are used as-is\n * 2. URL-downloaded files resolve to toolInstallDir (where the file was downloaded)\n * 3. Other relative paths resolve to toolDir (directory containing the .tool.ts file)\n */\n private async resolveSourcePath(\n toolInstallDir: string,\n source: string,\n configFilePath?: string,\n isUrlBased: boolean = false,\n ): Promise<string> {\n // Absolute paths are used as-is\n if (path.isAbsolute(source)) {\n return source;\n }\n\n // URL-downloaded files resolve to toolInstallDir where they were downloaded\n if (isUrlBased) {\n return path.join(toolInstallDir, source);\n }\n\n // Determine toolDir from configFilePath\n const toolDir = configFilePath ? path.dirname(configFilePath) : undefined;\n\n if (!toolDir) {\n throw new Error(`Cannot resolve relative path '${source}' without configFilePath`);\n }\n\n // If source contains glob patterns, search in toolDir\n if (source.includes('*') || source.includes('?') || source.includes('[')) {\n const toolDirFiles = await getAllFilesRecursively(this.fs, toolDir, toolDir);\n const matchedInToolDir = toolDirFiles.find((file) => minimatch(file, source));\n if (matchedInToolDir) {\n return path.join(toolDir, matchedInToolDir);\n }\n throw new Error(`No files matching pattern '${source}' found in toolDir: ${toolDir}`);\n }\n\n // For non-glob paths, resolve relative to toolDir\n return resolveToolRelativePath(toolDir, source);\n }\n\n /**\n * Generates the completion filename based on config and shell type.\n *\n * Priority order:\n * 1. config.bin - binary name with shell-specific naming applied\n * 2. toolName - fallback to tool name with shell-specific naming\n */\n private generateCompletionFilename(config: ShellCompletionConfig, toolName: string, shellType: ShellType): string {\n const baseName = config.bin ?? toolName;\n\n switch (shellType) {\n case 'zsh':\n return `_${baseName}`;\n case 'bash':\n return `${baseName}.bash`;\n case 'powershell':\n return `${baseName}.ps1`;\n default:\n return `${baseName}.${shellType}`;\n }\n }\n\n private resolveTargetPath(shellType: ShellType, context: ICompletionGenerationContext): string {\n return path.join(context.shellScriptsDir, shellType, 'completions');\n }\n}\n",
|
|
160
|
+
"/**\n * Valid shell function name pattern.\n * Must start with a letter or underscore, followed by letters, numbers, underscores, or hyphens.\n * This pattern is compatible with bash, zsh, and PowerShell naming conventions.\n */\nexport const VALID_FUNCTION_NAME_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_-]*$/;\n",
|
|
161
|
+
"import type { Block, BlockMetadata, FormatterConfig } from '@dotfiles/shell-emissions';\n\nconst DEFAULT_HEADER_WIDTH = 80;\nconst DEFAULT_INDENT_SIZE = 2;\n\n/**\n * Base class for all shell emission formatters.\n * Contains shared formatting infrastructure (headers, comments, configuration)\n * that is identical across all shell types.\n *\n * Shell-specific subclasses extend this to implement:\n * - comment() method for shell-specific comment syntax\n * - Shell-specific emission formatting\n */\nexport abstract class BaseEmissionFormatter {\n abstract readonly fileExtension: string;\n\n protected readonly headerWidth: number;\n protected readonly indentSize: number;\n protected readonly onceScriptDir?: string;\n\n constructor(config: FormatterConfig) {\n this.headerWidth = config.headerWidth ?? DEFAULT_HEADER_WIDTH;\n this.indentSize = config.indentSize ?? DEFAULT_INDENT_SIZE;\n this.onceScriptDir = config.onceScriptDir;\n }\n\n /**\n * Shell-specific comment syntax.\n * Override in subclasses if the shell uses different comment syntax.\n * Default is '#' which works for POSIX shells and PowerShell.\n */\n comment(text: string): string {\n return `# ${text}`;\n }\n\n commentBlock(lines: string[]): string {\n return lines.map((line) => this.comment(line)).join('\\n');\n }\n\n formatFileHeader(metadata?: BlockMetadata): string {\n const lines = [\n this.headerLine('='),\n this.comment('THIS FILE IS AUTOMATICALLY GENERATED BY THE DOTFILES MANAGEMENT TOOL'),\n this.comment('DO NOT EDIT THIS FILE DIRECTLY - YOUR CHANGES WILL BE OVERWRITTEN'),\n this.headerLine('='),\n '',\n ];\n\n if (metadata?.sourceFile) {\n lines.push(this.comment(`Dotfiles directory: ${metadata.sourceFile}`));\n lines.push('');\n }\n\n return lines.join('\\n');\n }\n\n formatSectionHeader(title: string): string {\n return this.headerLine('=', title);\n }\n\n formatChildBlockHeader(_block: Block): string {\n return this.headerLine('=');\n }\n\n formatFileFooter(): string {\n return this.headerLine('=', 'End of Generated File');\n }\n\n protected headerLine(char: string, title?: string): string {\n const totalWidth = this.headerWidth;\n\n if (!title) {\n return this.comment(char.repeat(totalWidth - 2));\n }\n\n const titleWithSpaces = ` ${title} `;\n const charsNeeded = Math.max(0, totalWidth - 1 - titleWithSpaces.length);\n const leftChars = char.repeat(Math.floor(charsNeeded / 2));\n const rightChars = char.repeat(Math.ceil(charsNeeded / 2));\n\n return this.comment(`${leftChars}${titleWithSpaces}${rightChars}`);\n }\n\n protected generateSourceFunctionName(filePath: string): string {\n const sanitized = filePath\n .replace(/[^a-zA-Z0-9]/g, '_')\n .replace(/^_+|_+$/g, '')\n .toLowerCase();\n return `__source_${sanitized}`;\n }\n}\n",
|
|
162
|
+
"import { BaseEmissionFormatter } from './BaseEmissionFormatter';\n\n/**\n * Base class for POSIX-compatible shell formatters (Bash, Zsh).\n * Extends BaseEmissionFormatter with POSIX-specific shared behavior.\n */\nexport abstract class BasePosixEmissionFormatter extends BaseEmissionFormatter {\n /**\n * Generates content for a once-script that self-deletes after execution.\n * Used by both Bash and Zsh formatters for one-time setup scripts.\n *\n * @param scriptContent - The script content to execute once\n * @param outputPath - Path to the script file (for self-deletion)\n */\n protected generateOnceScriptContent(\n scriptContent: string,\n outputPath: string,\n ): string {\n const lines = ['# Generated once script - will self-delete after execution'];\n lines.push(scriptContent);\n lines.push(`rm \"${outputPath}\"`);\n return lines.join('\\n');\n }\n}\n",
|
|
163
|
+
"/**\n * Error thrown when block validation fails.\n */\nexport class BlockValidationError extends Error {\n constructor(\n public readonly blockId: string,\n message: string,\n ) {\n super(`Block \"${blockId}\": ${message}`);\n this.name = 'BlockValidationError';\n }\n}\n",
|
|
164
|
+
"import type { EmissionKind } from '../types';\n\n/**\n * Error thrown when emission validation fails.\n */\nexport class EmissionValidationError extends Error {\n constructor(\n public readonly emissionKind: EmissionKind,\n public readonly field: string,\n message: string,\n ) {\n super(`${emissionKind}.${field}: ${message}`);\n this.name = 'EmissionValidationError';\n }\n}\n",
|
|
165
|
+
"import { EmissionValidationError } from '../errors';\nimport type { EmissionKind } from '../types';\n\n/** Pattern for valid identifier names (variable, function, alias) */\nconst IDENTIFIER_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;\n\n/** Pattern for valid function/alias names (allows hyphens) */\nconst NAME_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_-]*$/;\n\n/**\n * Validates that a string matches the identifier pattern.\n */\nexport function validateIdentifier(\n kind: EmissionKind,\n field: string,\n value: string,\n): void {\n if (!IDENTIFIER_PATTERN.test(value)) {\n throw new EmissionValidationError(\n kind,\n field,\n `\"${value}\" is not a valid identifier (must match ${IDENTIFIER_PATTERN.source})`,\n );\n }\n}\n\n/**\n * Validates that a string matches the name pattern (allows hyphens).\n */\nexport function validateName(kind: EmissionKind, field: string, value: string): void {\n if (!NAME_PATTERN.test(value)) {\n throw new EmissionValidationError(\n kind,\n field,\n `\"${value}\" is not a valid name (must match ${NAME_PATTERN.source})`,\n );\n }\n}\n\n/**\n * Validates that a string is non-empty.\n */\nexport function validateNonEmpty(\n kind: EmissionKind,\n field: string,\n value: string,\n): void {\n if (value.trim().length === 0) {\n throw new EmissionValidationError(kind, field, 'cannot be empty');\n }\n}\n\n/**\n * Validates that an object has at least one key.\n */\nexport function validateNonEmptyObject(\n kind: EmissionKind,\n field: string,\n value: Record<string, unknown>,\n): void {\n if (Object.keys(value).length === 0) {\n throw new EmissionValidationError(kind, field, 'must have at least one entry');\n }\n}\n\n/**\n * Validates environment variable names.\n */\nexport function validateEnvironmentVariables(\n variables: Record<string, string>,\n): void {\n validateNonEmptyObject('environment', 'variables', variables);\n for (const name of Object.keys(variables)) {\n validateIdentifier('environment', `variables.${name}`, name);\n }\n}\n\n/**\n * Validates alias names.\n */\nexport function validateAliases(aliases: Record<string, string>): void {\n validateNonEmptyObject('alias', 'aliases', aliases);\n for (const name of Object.keys(aliases)) {\n validateName('alias', `aliases.${name}`, name);\n }\n}\n",
|
|
166
|
+
"import { EmissionValidationError } from '../errors';\nimport type {\n AliasEmission,\n CompletionConfig,\n CompletionEmission,\n Emission,\n EnvironmentEmission,\n FunctionEmission,\n PathEmission,\n PathOptions,\n ScriptEmission,\n ScriptTiming,\n SourceEmission,\n SourceFileEmission,\n SourceFunctionEmission,\n} from '../types';\nimport {\n validateAliases,\n validateEnvironmentVariables,\n validateName,\n validateNonEmpty,\n} from './validation';\n\n/**\n * Creates an environment emission.\n */\nexport function environment(variables: Record<string, string>): EnvironmentEmission {\n validateEnvironmentVariables(variables);\n return {\n kind: 'environment',\n variables,\n };\n}\n\n/**\n * Creates an alias emission.\n */\nexport function alias(aliases: Record<string, string>): AliasEmission {\n validateAliases(aliases);\n return {\n kind: 'alias',\n aliases,\n };\n}\n\n/**\n * Creates a function emission.\n * @param name - Function name\n * @param body - Raw function body (without declaration wrapper)\n */\nexport function fn(\n name: string,\n body: string,\n): FunctionEmission {\n validateName('function', 'name', name);\n validateNonEmpty('function', 'body', body);\n return {\n kind: 'function',\n name,\n body,\n };\n}\n\n/**\n * Creates a script emission.\n * @param content - Script content\n * @param timing - Execution timing ('always' | 'once' | 'raw')\n */\nexport function script(\n content: string,\n timing: ScriptTiming,\n): ScriptEmission {\n validateNonEmpty('script', 'content', content);\n return {\n kind: 'script',\n content,\n timing,\n };\n}\n\n/**\n * Creates a source file emission.\n * @param filePath - Path to source (may contain $HOME)\n */\nexport function sourceFile(\n filePath: string,\n): SourceFileEmission {\n validateNonEmpty('sourceFile', 'path', filePath);\n return {\n kind: 'sourceFile',\n path: filePath,\n };\n}\n\n/**\n * Creates a source emission for inline content.\n * The content will be wrapped in a temporary function, sourced, and the function cleaned up.\n *\n * Generates:\n * functionName() { <content> }\n * source <(functionName)\n * unset -f functionName\n *\n * @param content - Content to source (typically shell code that outputs shell code)\n * @param functionName - Unique function name for this source emission\n */\nexport function source(\n content: string,\n functionName: string,\n): SourceEmission {\n validateNonEmpty('source', 'content', content);\n validateName('source', 'functionName', functionName);\n return {\n kind: 'source',\n content,\n functionName,\n };\n}\n\n/**\n * Creates a source function emission.\n * @param functionName - Name of function to source\n */\nexport function sourceFunction(functionName: string): SourceFunctionEmission {\n validateName('sourceFunction', 'functionName', functionName);\n return {\n kind: 'sourceFunction',\n functionName,\n };\n}\n\n/**\n * Creates a completion emission.\n */\nexport function completion(config: CompletionConfig): CompletionEmission {\n const hasDirectories = config.directories && config.directories.length > 0;\n const hasFiles = config.files && config.files.length > 0;\n const hasCommands = config.commands && config.commands.length > 0;\n\n if (!hasDirectories && !hasFiles && !hasCommands) {\n throw new EmissionValidationError(\n 'completion',\n 'config',\n 'at least one of directories, files, or commands must be provided',\n );\n }\n\n return {\n kind: 'completion',\n directories: config.directories,\n files: config.files,\n commands: config.commands,\n };\n}\n\n/**\n * Creates a path emission.\n * @param directory - Directory to add (may contain $HOME)\n * @param options - Position and deduplication options\n */\nexport function path(directory: string, options?: PathOptions): PathEmission {\n validateNonEmpty('path', 'directory', directory);\n return {\n kind: 'path',\n directory,\n position: options?.position ?? 'prepend',\n deduplicate: options?.deduplicate ?? true,\n };\n}\n\n/**\n * Returns a new emission with source attribution set.\n * @param emission - The emission to copy\n * @param attribution - Attribution identifier (e.g., config file path)\n * @returns A new emission with source set\n */\nexport function withSource<T extends Emission>(emission: T, attribution: string): T {\n return { ...emission, source: attribution };\n}\n\n/**\n * Returns a new emission with priority set.\n * @param emission - The emission to copy\n * @param priority - Sort order within block (lower = earlier)\n * @returns A new emission with priority set\n */\nexport function withPriority<T extends Emission>(emission: T, priority: number): T {\n return { ...emission, priority };\n}\n",
|
|
167
|
+
"import type {\n AliasEmission,\n CompletionEmission,\n Emission,\n EnvironmentEmission,\n FunctionEmission,\n PathEmission,\n ScriptEmission,\n SourceEmission,\n SourceFileEmission,\n SourceFunctionEmission,\n} from '../types';\n\n/**\n * Type guard for environment emission.\n */\nexport function isEnvironmentEmission(e: Emission): e is EnvironmentEmission {\n return e.kind === 'environment';\n}\n\n/**\n * Type guard for alias emission.\n */\nexport function isAliasEmission(e: Emission): e is AliasEmission {\n return e.kind === 'alias';\n}\n\n/**\n * Type guard for function emission.\n */\nexport function isFunctionEmission(e: Emission): e is FunctionEmission {\n return e.kind === 'function';\n}\n\n/**\n * Type guard for script emission.\n */\nexport function isScriptEmission(e: Emission): e is ScriptEmission {\n return e.kind === 'script';\n}\n\n/**\n * Type guard for source file emission.\n */\nexport function isSourceFileEmission(e: Emission): e is SourceFileEmission {\n return e.kind === 'sourceFile';\n}\n\n/**\n * Type guard for source emission (inline content).\n */\nexport function isSourceEmission(e: Emission): e is SourceEmission {\n return e.kind === 'source';\n}\n\n/**\n * Type guard for source function emission.\n */\nexport function isSourceFunctionEmission(e: Emission): e is SourceFunctionEmission {\n return e.kind === 'sourceFunction';\n}\n\n/**\n * Type guard for completion emission.\n */\nexport function isCompletionEmission(e: Emission): e is CompletionEmission {\n return e.kind === 'completion';\n}\n\n/**\n * Type guard for path emission.\n */\nexport function isPathEmission(e: Emission): e is PathEmission {\n return e.kind === 'path';\n}\n\n/** Emission kinds that are always hoisted to designated sections */\nconst HOISTED_KINDS = new Set(['environment', 'path', 'completion']);\n\n/**\n * Determines if an emission should be hoisted to a designated section.\n * Hoisted kinds: environment, path, completion\n */\nexport function isHoisted(e: Emission): boolean {\n return HOISTED_KINDS.has(e.kind);\n}\n",
|
|
168
|
+
"import { isCompletionEmission, isHoisted } from '../emissions/guards';\nimport { BlockValidationError } from '../errors';\nimport type { Block, CompletionEmission, Emission, EmissionKind, SectionOptions } from '../types';\n\ninterface SectionDefinition {\n id: string;\n options: SectionOptions;\n emissions: Emission[];\n children: Map<string, ChildBlockDefinition>;\n childInsertionOrder: string[];\n}\n\ninterface ChildBlockDefinition {\n id: string;\n emissions: Emission[];\n sourceFile?: string;\n}\n\n/**\n * Fluent builder for constructing block structures.\n */\nexport class BlockBuilder {\n private sections: Map<string, SectionDefinition> = new Map();\n private sectionOrder: string[] = [];\n\n /**\n * Defines a section in the block structure.\n * Call in priority order for deterministic output.\n */\n addSection(id: string, options: SectionOptions): BlockBuilder {\n if (this.sections.has(id)) {\n throw new BlockValidationError(id, 'section already exists');\n }\n if (options.priority < 0) {\n throw new BlockValidationError(id, 'priority must be non-negative');\n }\n\n this.sections.set(id, {\n id,\n options,\n emissions: [],\n children: new Map(),\n childInsertionOrder: [],\n });\n this.sectionOrder.push(id);\n return this;\n }\n\n /**\n * Adds an emission to the block structure.\n * Routing is automatic based on hoisting rules.\n * @param emission - The emission to add\n * @param childBlockId - For non-hoisted emissions, identifies which child block to use\n */\n addEmission(emission: Emission, childBlockId?: string): BlockBuilder {\n if (isHoisted(emission)) {\n this.addHoistedEmission(emission);\n } else {\n this.addNonHoistedEmission(emission, childBlockId);\n }\n return this;\n }\n\n /**\n * Adds an emission directly to a specific section, bypassing hoisting rules.\n * Use for emissions that should be placed in a particular section regardless of kind.\n * @param emission - The emission to add\n * @param sectionId - The target section ID\n * @throws BlockValidationError if the section doesn't exist\n */\n addEmissionToSection(emission: Emission, sectionId: string): BlockBuilder {\n const section = this.sections.get(sectionId);\n if (!section) {\n throw new BlockValidationError(sectionId, 'section does not exist');\n }\n section.emissions.push(emission);\n return this;\n }\n\n /**\n * Builds the final block structure.\n * Returns top-level blocks only; children are nested within.\n */\n build(): Block[] {\n const blocks: Block[] = [];\n\n for (const sectionId of this.sectionOrder) {\n const section = this.sections.get(sectionId);\n if (!section) {\n continue;\n }\n\n const block = this.buildBlock(section);\n blocks.push(block);\n }\n\n return blocks.toSorted((a, b) => a.priority - b.priority);\n }\n\n private addHoistedEmission(emission: Emission): void {\n const targetSection = this.findHoistTarget(emission.kind);\n if (!targetSection) {\n throw new BlockValidationError(\n 'unknown',\n `no section accepts hoisted emission kind \"${emission.kind}\"`,\n );\n }\n targetSection.emissions.push(emission);\n }\n\n private addNonHoistedEmission(emission: Emission, childBlockId?: string): void {\n const targetSection = this.findChildrenSection();\n if (!targetSection) {\n throw new BlockValidationError(\n 'unknown',\n 'no section allows children for non-hoisted emissions',\n );\n }\n\n if (childBlockId) {\n let childBlock = targetSection.children.get(childBlockId);\n if (!childBlock) {\n childBlock = {\n id: childBlockId,\n emissions: [],\n sourceFile: emission.source,\n };\n targetSection.children.set(childBlockId, childBlock);\n targetSection.childInsertionOrder.push(childBlockId);\n }\n childBlock.emissions.push(emission);\n } else {\n targetSection.emissions.push(emission);\n }\n }\n\n private findHoistTarget(kind: EmissionKind): SectionDefinition | undefined {\n for (const section of this.sections.values()) {\n if (section.options.hoistKinds?.includes(kind)) {\n return section;\n }\n }\n return undefined;\n }\n\n private findChildrenSection(): SectionDefinition | undefined {\n for (const section of this.sections.values()) {\n if (section.options.allowChildren) {\n return section;\n }\n }\n return undefined;\n }\n\n private buildBlock(section: SectionDefinition): Block {\n // Deduplicate completion emissions before building\n const deduplicatedEmissions = this.deduplicateCompletions(section.emissions);\n const sortedEmissions = deduplicatedEmissions.toSorted(\n (a, b) => (a.priority ?? 0) - (b.priority ?? 0),\n );\n\n const children: Block[] = [];\n for (const childId of section.childInsertionOrder) {\n const childDef = section.children.get(childId);\n if (!childDef) {\n continue;\n }\n\n const childEmissions = childDef.emissions.toSorted(\n (a, b) => (a.priority ?? 0) - (b.priority ?? 0),\n );\n\n children.push({\n id: childDef.id,\n title: childDef.id,\n priority: children.length,\n emissions: childEmissions,\n metadata: childDef.sourceFile ? { sourceFile: childDef.sourceFile } : undefined,\n });\n }\n\n return {\n id: section.id,\n title: section.options.title,\n priority: section.options.priority,\n emissions: sortedEmissions,\n children: children.length > 0 ? children : undefined,\n metadata: section.options.metadata,\n isFileHeader: section.options.isFileHeader,\n isFileFooter: section.options.isFileFooter,\n };\n }\n\n /**\n * Merges completion emissions with duplicate directories/files into a single emission.\n * Non-completion emissions pass through unchanged.\n */\n private deduplicateCompletions(emissions: Emission[]): Emission[] {\n const completionEmissions: CompletionEmission[] = [];\n const otherEmissions: Emission[] = [];\n\n for (const emission of emissions) {\n if (isCompletionEmission(emission)) {\n completionEmissions.push(emission);\n } else {\n otherEmissions.push(emission);\n }\n }\n\n if (completionEmissions.length <= 1) {\n return emissions;\n }\n\n // Merge all completion emissions into one\n const mergedDirectories = new Set<string>();\n const mergedFiles = new Set<string>();\n const mergedCommands = new Set<string>();\n let minPriority: number | undefined;\n\n for (const completion of completionEmissions) {\n if (completion.directories) {\n for (const dir of completion.directories) {\n mergedDirectories.add(dir);\n }\n }\n if (completion.files) {\n for (const file of completion.files) {\n mergedFiles.add(file);\n }\n }\n if (completion.commands) {\n for (const cmd of completion.commands) {\n mergedCommands.add(cmd);\n }\n }\n if (completion.priority !== undefined) {\n minPriority = minPriority === undefined\n ? completion.priority\n : Math.min(minPriority, completion.priority);\n }\n }\n\n const mergedCompletion: CompletionEmission = {\n kind: 'completion',\n directories: mergedDirectories.size > 0 ? [...mergedDirectories] : undefined,\n files: mergedFiles.size > 0 ? [...mergedFiles] : undefined,\n commands: mergedCommands.size > 0 ? [...mergedCommands] : undefined,\n priority: minPriority,\n };\n\n return [...otherEmissions, mergedCompletion];\n }\n}\n",
|
|
169
|
+
"/**\n * Section priorities for block structure.\n * These define the standard ordering of sections in the output file.\n */\nexport enum SectionPriority {\n /** File header with \"DO NOT EDIT\" warning */\n FileHeader = 0,\n /** CLI function - provides access to dotfiles command */\n Cli = 50,\n /** PATH modifications - must come first so tools are available */\n Path = 100,\n /** Environment variables */\n Environment = 200,\n /** Main content with tool initializations */\n MainContent = 300,\n /** Once scripts run here (auto-inserted by renderer) */\n OnceScripts = 400,\n /** Shell completions - must come after functions are defined */\n Completions = 500,\n /** End of file marker */\n FileFooter = 999,\n}\n\n/**\n * Starting index for once script numbering.\n */\nexport const ONCE_SCRIPT_STARTING_INDEX = 1;\n\n/**\n * Padding length for once script index in filenames.\n * Exported for formatter implementations to use when generating\n * once script filenames (e.g., 'once-001.zsh', 'once-002.zsh').\n */\nexport const ONCE_SCRIPT_INDEX_PAD_LENGTH = 3;\n",
|
|
170
|
+
"import { isScriptEmission } from '../emissions/guards';\nimport type {\n Block,\n IBlockRenderer,\n IEmissionFormatter,\n OnceScript,\n RenderedOutput,\n ScriptEmission,\n} from '../types';\nimport { ONCE_SCRIPT_STARTING_INDEX, SectionPriority } from './constants';\n\n/**\n * Renders blocks using the provided formatter.\n */\nexport class BlockRenderer implements IBlockRenderer {\n /**\n * Renders blocks to shell content.\n */\n render(blocks: Block[], formatter: IEmissionFormatter): RenderedOutput {\n const sortedBlocks = blocks.toSorted((a, b) => a.priority - b.priority);\n const lines: string[] = [];\n const onceScripts: OnceScript[] = [];\n let onceScriptIndex = ONCE_SCRIPT_STARTING_INDEX;\n\n // Track once scripts to determine where to insert initializer\n const onceScriptEmissions: Array<{ emission: ScriptEmission; blockPriority: number; }> = [];\n\n // First pass: collect once scripts\n for (const block of sortedBlocks) {\n this.collectOnceScripts(block, onceScriptEmissions);\n }\n\n // Find where to insert once script initializer (after last block with priority below threshold)\n let initializerInsertIndex = -1;\n if (onceScriptEmissions.length > 0) {\n for (let i = sortedBlocks.length - 1; i >= 0; i--) {\n const currentBlock = sortedBlocks[i];\n if (currentBlock && currentBlock.priority < SectionPriority.OnceScripts) {\n initializerInsertIndex = i;\n break;\n }\n }\n }\n\n // Second pass: render blocks\n for (const [i, block] of sortedBlocks.entries()) {\n const blockLines = this.renderBlock(\n block,\n formatter,\n onceScripts,\n onceScriptIndex,\n );\n\n if (blockLines.length > 0) {\n if (lines.length > 0) {\n lines.push('');\n }\n lines.push(...blockLines);\n }\n\n // Update once script index\n onceScriptIndex = onceScripts.length + 1;\n\n // Insert once script initializer after appropriate block\n if (i === initializerInsertIndex && onceScriptEmissions.length > 0) {\n lines.push('');\n lines.push(formatter.formatOnceScriptInitializer());\n }\n }\n\n return {\n content: lines.join('\\n'),\n fileExtension: formatter.fileExtension,\n onceScripts,\n };\n }\n\n private collectOnceScripts(\n block: Block,\n results: Array<{ emission: ScriptEmission; blockPriority: number; }>,\n ): void {\n for (const emission of block.emissions) {\n if (isScriptEmission(emission) && emission.timing === 'once') {\n results.push({ emission, blockPriority: block.priority });\n }\n }\n for (const child of block.children ?? []) {\n this.collectOnceScripts(child, results);\n }\n }\n\n private renderBlock(\n block: Block,\n formatter: IEmissionFormatter,\n onceScripts: OnceScript[],\n startIndex: number,\n ): string[] {\n const lines: string[] = [];\n let onceScriptIndex = startIndex;\n\n // Handle file header\n if (block.isFileHeader) {\n lines.push(formatter.formatFileHeader(block.metadata));\n return lines;\n }\n\n // Handle file footer\n if (block.isFileFooter) {\n lines.push(formatter.formatFileFooter());\n return lines;\n }\n\n // Check if block has any content\n const hasEmissions = block.emissions.length > 0;\n const hasChildren = (block.children?.length ?? 0) > 0;\n\n if (!hasEmissions && !hasChildren) {\n return lines;\n }\n\n // Render section header if block has title\n if (block.title) {\n lines.push(formatter.formatSectionHeader(block.title));\n }\n\n // Render emissions\n let previousSource: string | undefined;\n for (const emission of block.emissions) {\n // Emit source comment if source changed\n if (emission.source && emission.source !== previousSource) {\n lines.push(formatter.comment(emission.source));\n previousSource = emission.source;\n }\n\n // Handle once scripts specially\n if (isScriptEmission(emission) && emission.timing === 'once') {\n const onceResult = formatter.formatOnceScript(emission, onceScriptIndex);\n onceScripts.push({\n filename: onceResult.filename,\n content: onceResult.content,\n executable: true,\n });\n onceScriptIndex++;\n } else {\n lines.push(formatter.formatEmission(emission));\n }\n }\n\n // Render children\n for (const child of block.children ?? []) {\n if (child.emissions.length === 0) {\n continue;\n }\n\n lines.push('');\n lines.push(formatter.formatChildBlockHeader(child));\n\n let childPreviousSource: string | undefined;\n for (const emission of child.emissions) {\n if (emission.source && emission.source !== childPreviousSource) {\n lines.push(formatter.comment(emission.source));\n childPreviousSource = emission.source;\n }\n\n if (isScriptEmission(emission) && emission.timing === 'once') {\n const onceResult = formatter.formatOnceScript(emission, onceScriptIndex);\n onceScripts.push({\n filename: onceResult.filename,\n content: onceResult.content,\n executable: true,\n });\n onceScriptIndex++;\n } else {\n lines.push(formatter.formatEmission(emission));\n }\n }\n }\n\n return lines;\n }\n}\n",
|
|
171
|
+
"import type {\n AliasEmission,\n CompletionEmission,\n Emission,\n EnvironmentEmission,\n FunctionEmission,\n IEmissionFormatter,\n OnceScriptContent,\n PathEmission,\n ScriptEmission,\n SourceEmission,\n SourceFileEmission,\n SourceFunctionEmission,\n} from '@dotfiles/shell-emissions';\nimport {\n isAliasEmission,\n isCompletionEmission,\n isEnvironmentEmission,\n isFunctionEmission,\n isPathEmission,\n isScriptEmission,\n isSourceEmission,\n isSourceFileEmission,\n isSourceFunctionEmission,\n ONCE_SCRIPT_INDEX_PAD_LENGTH,\n} from '@dotfiles/shell-emissions';\nimport { dedentString } from '@dotfiles/utils';\nimport { BasePosixEmissionFormatter } from './BasePosixEmissionFormatter';\n\n/**\n * Bash-specific emission formatter.\n * Converts shell-agnostic emissions to Bash syntax.\n */\nexport class BashEmissionFormatter extends BasePosixEmissionFormatter implements IEmissionFormatter {\n readonly fileExtension: string = '.bash';\n\n formatEmission(emission: Emission): string {\n if (isEnvironmentEmission(emission)) {\n return this.formatEnvironment(emission);\n }\n if (isAliasEmission(emission)) {\n return this.formatAlias(emission);\n }\n if (isFunctionEmission(emission)) {\n return this.formatFunction(emission);\n }\n if (isScriptEmission(emission)) {\n return this.formatScript(emission);\n }\n if (isSourceEmission(emission)) {\n return this.formatSource(emission);\n }\n if (isSourceFileEmission(emission)) {\n return this.formatSourceFile(emission);\n }\n if (isSourceFunctionEmission(emission)) {\n return this.formatSourceFunction(emission);\n }\n if (isCompletionEmission(emission)) {\n return this.formatCompletion(emission);\n }\n if (isPathEmission(emission)) {\n return this.formatPath(emission);\n }\n throw new Error(`Unknown emission kind: ${(emission as Emission).kind}`);\n }\n\n formatOnceScript(emission: ScriptEmission, index: number): OnceScriptContent {\n if (!this.onceScriptDir) {\n throw new Error('onceScriptDir is required for once scripts');\n }\n\n const paddedIndex = index.toString().padStart(ONCE_SCRIPT_INDEX_PAD_LENGTH, '0');\n const filename = `once-${paddedIndex}.bash`;\n const outputPath = `${this.onceScriptDir}/${filename}`;\n const scriptContent = dedentString(emission.content);\n\n const content = this.generateOnceScriptContent(scriptContent, outputPath);\n\n return { content, filename };\n }\n\n formatOnceScriptInitializer(): string {\n if (!this.onceScriptDir) {\n throw new Error('onceScriptDir is required for once script initializer');\n }\n\n return dedentString(`\n # Execute once scripts (runs only once per script)\n shopt -s nullglob\n for once_script in \"${this.onceScriptDir}\"/*.bash; do\n [[ -f \"$once_script\" ]] && source \"$once_script\"\n done\n shopt -u nullglob\n `);\n }\n\n private formatEnvironment(emission: EnvironmentEmission): string {\n const lines: string[] = [];\n for (const [key, value] of Object.entries(emission.variables)) {\n lines.push(`export ${key}=${JSON.stringify(value)}`);\n }\n return lines.join('\\n');\n }\n\n private formatAlias(emission: AliasEmission): string {\n const lines: string[] = [];\n for (const [name, command] of Object.entries(emission.aliases)) {\n lines.push(`alias ${name}='${command.replace(/'/g, \"'\\\\''\")}'`);\n }\n return lines.join('\\n');\n }\n\n private formatFunction(emission: FunctionEmission): string {\n const body = dedentString(emission.body);\n const indent = ' '.repeat(this.indentSize);\n\n const indentedBody = body.split('\\n').map((line) => `${indent}${line}`).join('\\n');\n return [\n `${emission.name}() {`,\n indentedBody,\n `}`,\n ].join('\\n');\n }\n\n private formatScript(emission: ScriptEmission): string {\n const content = dedentString(emission.content);\n return content;\n }\n\n private formatSourceFile(emission: SourceFileEmission): string {\n return `source \"${emission.path}\"`;\n }\n\n /**\n * Formats a source emission with inline content.\n * Creates a temporary function, sources its output, and cleans up.\n */\n private formatSource(emission: SourceEmission): string {\n const content = dedentString(emission.content);\n const functionName = emission.functionName;\n const indent = ' '.repeat(this.indentSize);\n\n const indentedContent = content.split('\\n').map((line) => `${indent}${line}`).join('\\n');\n return [\n `${functionName}() {`,\n indentedContent,\n `}`,\n `source <(${functionName})`,\n `unset -f ${functionName}`,\n ].join('\\n');\n }\n\n private formatSourceFunction(emission: SourceFunctionEmission): string {\n return `source <(${emission.functionName})`;\n }\n\n private formatCompletion(emission: CompletionEmission): string {\n const lines: string[] = [];\n\n // Source specific completion files with existence check\n if (emission.files) {\n for (const file of emission.files) {\n lines.push(`[[ -f \"${file}\" ]] && source \"${file}\"`);\n }\n }\n\n return lines.join('\\n');\n }\n\n private formatPath(emission: PathEmission): string {\n const dir = emission.directory;\n\n if (emission.deduplicate) {\n if (emission.position === 'prepend') {\n return [\n `if [[ \":$PATH:\" != *\":${dir}:\"* ]]; then`,\n ` export PATH=\"${dir}:$PATH\"`,\n 'fi',\n ].join('\\n');\n }\n return [\n `if [[ \":$PATH:\" != *\":${dir}:\"* ]]; then`,\n ` export PATH=\"$PATH:${dir}\"`,\n 'fi',\n ].join('\\n');\n }\n\n if (emission.position === 'prepend') {\n return `export PATH=\"${dir}:$PATH\"`;\n }\n return `export PATH=\"$PATH:${dir}\"`;\n }\n}\n",
|
|
172
|
+
"import type {\n AliasEmission,\n CompletionEmission,\n Emission,\n EnvironmentEmission,\n FunctionEmission,\n IEmissionFormatter,\n OnceScriptContent,\n PathEmission,\n ScriptEmission,\n SourceEmission,\n SourceFileEmission,\n SourceFunctionEmission,\n} from '@dotfiles/shell-emissions';\nimport {\n isAliasEmission,\n isCompletionEmission,\n isEnvironmentEmission,\n isFunctionEmission,\n isPathEmission,\n isScriptEmission,\n isSourceEmission,\n isSourceFileEmission,\n isSourceFunctionEmission,\n ONCE_SCRIPT_INDEX_PAD_LENGTH,\n} from '@dotfiles/shell-emissions';\nimport { dedentString } from '@dotfiles/utils';\nimport { BaseEmissionFormatter } from './BaseEmissionFormatter';\n\n/**\n * PowerShell-specific emission formatter.\n * Converts shell-agnostic emissions to PowerShell syntax.\n */\nexport class PowerShellEmissionFormatter extends BaseEmissionFormatter implements IEmissionFormatter {\n readonly fileExtension: string = '.ps1';\n\n formatEmission(emission: Emission): string {\n if (isEnvironmentEmission(emission)) {\n return this.formatEnvironment(emission);\n }\n if (isAliasEmission(emission)) {\n return this.formatAlias(emission);\n }\n if (isFunctionEmission(emission)) {\n return this.formatFunction(emission);\n }\n if (isScriptEmission(emission)) {\n return this.formatScript(emission);\n }\n if (isSourceEmission(emission)) {\n return this.formatSource(emission);\n }\n if (isSourceFileEmission(emission)) {\n return this.formatSourceFile(emission);\n }\n if (isSourceFunctionEmission(emission)) {\n return this.formatSourceFunction(emission);\n }\n if (isCompletionEmission(emission)) {\n return this.formatCompletion(emission);\n }\n if (isPathEmission(emission)) {\n return this.formatPath(emission);\n }\n throw new Error(`Unknown emission kind: ${(emission as Emission).kind}`);\n }\n\n formatOnceScript(emission: ScriptEmission, index: number): OnceScriptContent {\n if (!this.onceScriptDir) {\n throw new Error('onceScriptDir is required for once scripts');\n }\n\n const paddedIndex = index.toString().padStart(ONCE_SCRIPT_INDEX_PAD_LENGTH, '0');\n const filename = `once-${paddedIndex}.ps1`;\n const outputPath = `${this.onceScriptDir}/${filename}`;\n const scriptContent = dedentString(emission.content);\n\n const content = this.generateOnceScriptContent(scriptContent, outputPath);\n\n return { content, filename };\n }\n\n formatOnceScriptInitializer(): string {\n if (!this.onceScriptDir) {\n throw new Error('onceScriptDir is required for once script initializer');\n }\n\n return dedentString(`\n # Execute once scripts (runs only once per script)\n Get-ChildItem -Path \"${this.onceScriptDir}/*.ps1\" -ErrorAction SilentlyContinue | ForEach-Object {\n if (Test-Path $_.FullName) {\n & $_.FullName\n }\n }\n `);\n }\n\n private formatEnvironment(emission: EnvironmentEmission): string {\n const lines: string[] = [];\n for (const [key, value] of Object.entries(emission.variables)) {\n lines.push(`$env:${key} = ${JSON.stringify(value)}`);\n }\n return lines.join('\\n');\n }\n\n private formatAlias(emission: AliasEmission): string {\n const lines: string[] = [];\n for (const [name, command] of Object.entries(emission.aliases)) {\n lines.push(`Set-Alias -Name ${name} -Value '${command.replace(/'/g, \"''\")}'`);\n }\n return lines.join('\\n');\n }\n\n private formatFunction(emission: FunctionEmission): string {\n const body = dedentString(emission.body);\n const indent = ' '.repeat(this.indentSize);\n\n const indentedBody = body.split('\\n').map((line) => `${indent}${line}`).join('\\n');\n return [\n `function ${emission.name} {`,\n indentedBody,\n `}`,\n ].join('\\n');\n }\n\n private formatScript(emission: ScriptEmission): string {\n const content = dedentString(emission.content);\n return content;\n }\n\n private formatSourceFile(emission: SourceFileEmission): string {\n return `. \"${emission.path}\"`;\n }\n\n /**\n * Formats a source emission with inline content.\n * Creates a temporary function, invokes its output, and cleans up.\n */\n private formatSource(emission: SourceEmission): string {\n const content = dedentString(emission.content);\n const functionName = emission.functionName;\n const indent = ' '.repeat(this.indentSize);\n\n const indentedContent = content.split('\\n').map((line) => `${indent}${line}`).join('\\n');\n return [\n `function ${functionName} {`,\n indentedContent,\n `}`,\n `Invoke-Expression (& ${functionName})`,\n `Remove-Item Function:\\\\${functionName} -ErrorAction SilentlyContinue`,\n ].join('\\n');\n }\n\n private formatSourceFunction(emission: SourceFunctionEmission): string {\n // In PowerShell, we invoke the function and evaluate the result\n return `Invoke-Expression (& ${emission.functionName})`;\n }\n\n private formatCompletion(emission: CompletionEmission): string {\n const lines: string[] = [];\n\n // Source specific completion files with existence check\n if (emission.files) {\n for (const file of emission.files) {\n lines.push(`if (Test-Path \"${file}\") { . \"${file}\" }`);\n }\n }\n\n // PowerShell completions are handled differently - typically via modules\n // Directory-based completion loading is less common\n\n return lines.join('\\n');\n }\n\n private formatPath(emission: PathEmission): string {\n const dir = emission.directory;\n\n if (emission.deduplicate) {\n if (emission.position === 'prepend') {\n return `if ($env:PATH -notlike \"*${dir}*\") { $env:PATH = \"${dir};$env:PATH\" }`;\n }\n return `if ($env:PATH -notlike \"*${dir}*\") { $env:PATH = \"$env:PATH;${dir}\" }`;\n }\n\n if (emission.position === 'prepend') {\n return `$env:PATH = \"${dir};$env:PATH\"`;\n }\n return `$env:PATH = \"$env:PATH;${dir}\"`;\n }\n\n private generateOnceScriptContent(\n scriptContent: string,\n outputPath: string,\n ): string {\n const lines = ['# Generated once script - will self-delete after execution'];\n lines.push(scriptContent);\n lines.push(`Remove-Item \"${outputPath}\"`);\n return lines.join('\\n');\n }\n}\n",
|
|
173
|
+
"import type {\n AliasEmission,\n CompletionEmission,\n Emission,\n EnvironmentEmission,\n FunctionEmission,\n IEmissionFormatter,\n OnceScriptContent,\n PathEmission,\n ScriptEmission,\n SourceEmission,\n SourceFileEmission,\n SourceFunctionEmission,\n} from '@dotfiles/shell-emissions';\nimport {\n isAliasEmission,\n isCompletionEmission,\n isEnvironmentEmission,\n isFunctionEmission,\n isPathEmission,\n isScriptEmission,\n isSourceEmission,\n isSourceFileEmission,\n isSourceFunctionEmission,\n ONCE_SCRIPT_INDEX_PAD_LENGTH,\n} from '@dotfiles/shell-emissions';\nimport { dedentString } from '@dotfiles/utils';\nimport { BasePosixEmissionFormatter } from './BasePosixEmissionFormatter';\n\n/**\n * Zsh-specific emission formatter.\n * Converts shell-agnostic emissions to Zsh syntax.\n */\nexport class ZshEmissionFormatter extends BasePosixEmissionFormatter implements IEmissionFormatter {\n readonly fileExtension: string = '.zsh';\n\n formatEmission(emission: Emission): string {\n if (isEnvironmentEmission(emission)) {\n return this.formatEnvironment(emission);\n }\n if (isAliasEmission(emission)) {\n return this.formatAlias(emission);\n }\n if (isFunctionEmission(emission)) {\n return this.formatFunction(emission);\n }\n if (isScriptEmission(emission)) {\n return this.formatScript(emission);\n }\n if (isSourceEmission(emission)) {\n return this.formatSource(emission);\n }\n if (isSourceFileEmission(emission)) {\n return this.formatSourceFile(emission);\n }\n if (isSourceFunctionEmission(emission)) {\n return this.formatSourceFunction(emission);\n }\n if (isCompletionEmission(emission)) {\n return this.formatCompletion(emission);\n }\n if (isPathEmission(emission)) {\n return this.formatPath(emission);\n }\n throw new Error(`Unknown emission kind: ${(emission as Emission).kind}`);\n }\n\n formatOnceScript(emission: ScriptEmission, index: number): OnceScriptContent {\n if (!this.onceScriptDir) {\n throw new Error('onceScriptDir is required for once scripts');\n }\n\n const paddedIndex = index.toString().padStart(ONCE_SCRIPT_INDEX_PAD_LENGTH, '0');\n const filename = `once-${paddedIndex}.zsh`;\n const outputPath = `${this.onceScriptDir}/${filename}`;\n const scriptContent = dedentString(emission.content);\n\n const content = this.generateOnceScriptContent(scriptContent, outputPath);\n\n return { content, filename };\n }\n\n formatOnceScriptInitializer(): string {\n if (!this.onceScriptDir) {\n throw new Error('onceScriptDir is required for once script initializer');\n }\n\n return dedentString(`\n # Execute once scripts (runs only once per script)\n for once_script in \"${this.onceScriptDir}\"/*.zsh(N); do\n [[ -f \"$once_script\" ]] && source \"$once_script\"\n done\n `);\n }\n\n private formatEnvironment(emission: EnvironmentEmission): string {\n const lines: string[] = [];\n for (const [key, value] of Object.entries(emission.variables)) {\n lines.push(`export ${key}=${JSON.stringify(value)}`);\n }\n return lines.join('\\n');\n }\n\n private formatAlias(emission: AliasEmission): string {\n const lines: string[] = [];\n for (const [name, command] of Object.entries(emission.aliases)) {\n lines.push(`alias ${name}='${command.replace(/'/g, \"'\\\\''\")}'`);\n }\n return lines.join('\\n');\n }\n\n private formatFunction(emission: FunctionEmission): string {\n const body = dedentString(emission.body);\n const indent = ' '.repeat(this.indentSize);\n\n const indentedBody = body.split('\\n').map((line) => `${indent}${line}`).join('\\n');\n return [\n `${emission.name}() {`,\n indentedBody,\n `}`,\n ].join('\\n');\n }\n\n private formatScript(emission: ScriptEmission): string {\n const content = dedentString(emission.content);\n return content;\n }\n\n private formatSourceFile(emission: SourceFileEmission): string {\n return `source \"${emission.path}\"`;\n }\n\n /**\n * Formats a source emission with inline content.\n * Creates a temporary function, sources its output, and cleans up.\n */\n private formatSource(emission: SourceEmission): string {\n const content = dedentString(emission.content);\n const functionName = emission.functionName;\n const indent = ' '.repeat(this.indentSize);\n\n const indentedContent = content.split('\\n').map((line) => `${indent}${line}`).join('\\n');\n return [\n `${functionName}() {`,\n indentedContent,\n `}`,\n `source <(${functionName})`,\n `unset -f ${functionName}`,\n ].join('\\n');\n }\n\n private formatSourceFunction(emission: SourceFunctionEmission): string {\n return `source <(${emission.functionName})`;\n }\n\n private formatCompletion(emission: CompletionEmission): string {\n const lines: string[] = [];\n\n // Ensure fpath is deduplicated (zsh-specific)\n lines.push('typeset -U fpath');\n\n // Add directories to fpath\n if (emission.directories) {\n for (const dir of emission.directories) {\n lines.push(`fpath=(${JSON.stringify(dir)} $fpath)`);\n }\n }\n\n // Source specific completion files\n if (emission.files) {\n for (const file of emission.files) {\n lines.push(`source \"${file}\"`);\n }\n }\n\n return lines.join('\\n');\n }\n\n private formatPath(emission: PathEmission): string {\n const dir = emission.directory;\n\n if (emission.deduplicate) {\n if (emission.position === 'prepend') {\n return [\n `if [[ \":$PATH:\" != *\":${dir}:\"* ]]; then`,\n ` export PATH=\"${dir}:$PATH\"`,\n 'fi',\n ].join('\\n');\n }\n return [\n `if [[ \":$PATH:\" != *\":${dir}:\"* ]]; then`,\n ` export PATH=\"$PATH:${dir}\"`,\n 'fi',\n ].join('\\n');\n }\n\n if (emission.position === 'prepend') {\n return `export PATH=\"${dir}:$PATH\"`;\n }\n return `export PATH=\"$PATH:${dir}\"`;\n }\n}\n",
|
|
174
|
+
"import type { ShellType } from '@dotfiles/core';\nimport type { FormatterConfig, IEmissionFormatter } from '@dotfiles/shell-emissions';\nimport { BashEmissionFormatter } from './BashEmissionFormatter';\nimport { PowerShellEmissionFormatter } from './PowerShellEmissionFormatter';\nimport { ZshEmissionFormatter } from './ZshEmissionFormatter';\n\n/**\n * Creates an emission formatter for the specified shell type.\n */\nexport function createEmissionFormatter(\n shellType: ShellType,\n config: FormatterConfig,\n): IEmissionFormatter {\n switch (shellType) {\n case 'zsh':\n return new ZshEmissionFormatter(config);\n case 'bash':\n return new BashEmissionFormatter(config);\n case 'powershell':\n return new PowerShellEmissionFormatter(config);\n default:\n throw new Error(`Unsupported shell type: ${shellType}`);\n }\n}\n",
|
|
175
|
+
"import type { ShellType } from '@dotfiles/core';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport path from 'node:path';\nimport { generateProfileHeader, generateSourceLine } from '../shellTemplates';\nimport type { IProfileUpdateConfig, IProfileUpdater, IProfileUpdateResult } from './IProfileUpdater';\n\n/**\n * Implementation of profile file updater that manages sourcing generated shell scripts\n * in shell-specific profile files.\n */\nexport class ProfileUpdater implements IProfileUpdater {\n private readonly fileSystem: IFileSystem;\n private readonly homeDir: string;\n\n constructor(fileSystem: IFileSystem, homeDir: string) {\n this.fileSystem = fileSystem;\n this.homeDir = homeDir;\n }\n\n async updateProfiles(configs: IProfileUpdateConfig[]): Promise<IProfileUpdateResult[]> {\n const results: IProfileUpdateResult[] = [];\n\n for (const config of configs) {\n const result = await this.updateProfile(config);\n results.push(result);\n }\n\n return results;\n }\n\n getProfilePath(shellType: ShellType): string {\n switch (shellType) {\n case 'zsh':\n return path.join(this.homeDir, '.zshrc');\n case 'bash':\n return path.join(this.homeDir, '.bashrc');\n case 'powershell':\n // PowerShell profile path varies by OS, use cross-platform approach\n return path.join(this.homeDir, '.config/powershell/profile.ps1');\n default:\n throw new Error(`Unsupported shell type: ${shellType}`);\n }\n }\n\n async hasSourceLine(profilePath: string, scriptPath: string): Promise<boolean> {\n try {\n const content = await this.fileSystem.readFile(profilePath);\n const sourcePatterns = this.getSourcePatterns(scriptPath);\n\n return sourcePatterns.some((pattern) => content.includes(pattern));\n } catch (_error) {\n // File doesn't exist or can't be read\n return false;\n }\n }\n\n /**\n * Updates a single profile file based on the provided configuration.\n */\n private async updateProfile(config: IProfileUpdateConfig): Promise<IProfileUpdateResult> {\n const profilePath = config.profilePath ?? this.getProfilePath(config.shellType);\n const fileExists = await this.fileSystem.exists(profilePath);\n\n const result: IProfileUpdateResult = {\n shellType: config.shellType,\n profilePath,\n fileExists,\n wasUpdated: false,\n wasAlreadyPresent: false,\n };\n\n if (!fileExists && config.onlyIfExists) {\n return result;\n }\n\n let content = '';\n if (fileExists) {\n try {\n content = await this.fileSystem.readFile(profilePath);\n } catch (_error) {\n content = '';\n }\n }\n\n const sourceLine = generateSourceLine(config.shellType, config.generatedScriptPath);\n const headerBlock = generateProfileHeader(config.shellType);\n const newBlock = `${headerBlock}\\n${sourceLine}`;\n\n const headerMarker = '# Generated via dotfiles generator - do not modify';\n if (content.includes(headerMarker)) {\n const newContent = this.replaceGeneratedBlocks(content, newBlock);\n if (newContent !== content) {\n await this.fileSystem.writeFile(profilePath, newContent);\n result.wasUpdated = true;\n } else {\n result.wasAlreadyPresent = true;\n }\n return result;\n }\n\n const sourcePatterns = this.getSourcePatterns(config.generatedScriptPath);\n if (sourcePatterns.some((pattern) => content.includes(pattern))) {\n result.wasAlreadyPresent = true;\n return result;\n }\n\n if (content && !content.endsWith('\\n')) {\n content += '\\n';\n }\n const finalContent = `${content}\\n${newBlock}\\n`;\n\n const parentDir = path.dirname(profilePath);\n await this.fileSystem.ensureDir(parentDir);\n\n await this.fileSystem.writeFile(profilePath, finalContent);\n result.wasUpdated = true;\n\n return result;\n }\n\n private replaceGeneratedBlocks(content: string, newBlock: string): string {\n const headerMarker = '# Generated via dotfiles generator - do not modify';\n const escapedMarker = headerMarker.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const blockRegex = new RegExp(`${escapedMarker}[\\\\s\\\\S]*?^\\\\s*(?:source|\\\\.)\\\\s+[\"'].*?[\"'].*?$`, 'gm');\n\n const parts = content.split(blockRegex);\n let newContent = parts[0] || '';\n\n if (newContent && !newContent.endsWith('\\n')) {\n newContent += '\\n';\n }\n\n newContent += `${newBlock}\\n`;\n\n for (let i = 1; i < parts.length; i++) {\n const part = parts[i];\n if (part) {\n newContent += part;\n }\n }\n\n return newContent;\n }\n\n /**\n * Gets possible source patterns to check for in profile files.\n */\n private getSourcePatterns(scriptPath: string): string[] {\n // Check for various ways the script might be sourced\n return [\n `source \"${scriptPath}\"`,\n `source '${scriptPath}'`,\n `source ${scriptPath}`,\n `. \"${scriptPath}\"`,\n `. '${scriptPath}'`,\n `. ${scriptPath}`,\n ];\n }\n}\n",
|
|
176
|
+
"import type { ShellType } from '@dotfiles/core';\n\nconst COMMENT_PREFIX = '#';\n\n/**\n * Creates a commented line with shell-appropriate comment syntax.\n * @param shellType - Type of shell for comment syntax\n * @param value - The text to comment\n * @returns Formatted comment line\n */\nexport function commentLine(_shellType: ShellType, value: string): string {\n return `${COMMENT_PREFIX} ${value}`;\n}\n\n/**\n * Generates a header line with repeated characters, optionally with centered text.\n * @param shellType - Type of shell for comment syntax\n * @param lineChar - Character to repeat (e.g., '=', '-')\n * @param title - Optional title to center in the line\n * @returns Formatted header line\n */\nexport function headerLine(shellType: ShellType, lineChar: string, title?: string): string {\n const totalWidth = 80;\n\n if (!title) {\n return commentLine(shellType, lineChar.repeat(totalWidth - 2));\n }\n\n const titleWithSpaces = ` ${title} `;\n const charsNeeded = Math.max(0, totalWidth - 1 - titleWithSpaces.length); // -1 for comment prefix\n const leftChars = lineChar.repeat(Math.floor(charsNeeded / 2));\n const rightChars = lineChar.repeat(Math.ceil(charsNeeded / 2));\n\n return commentLine(shellType, `${leftChars}${titleWithSpaces}${rightChars}`);\n}\n\n/**\n * Creates a centered section header with triple equals signs spanning 80 characters total.\n * Format: # === Section Title ===\n *\n * @param shellType - Type of shell for comment syntax\n * @param title - The section title to center\n * @returns A formatted header string\n */\nexport function createSectionHeader(shellType: ShellType, title: string): string {\n return headerLine(shellType, '=', title);\n}\n\n/**\n * Generates the file header warning users not to edit the generated file.\n * @param shellType - Type of shell for comment syntax\n * @param dotfilesDir - Path to the dotfiles directory\n * @returns File header string\n */\nexport function generateFileHeader(shellType: ShellType, dotfilesDir: string): string {\n return [\n headerLine(shellType, '='),\n commentLine(shellType, 'THIS FILE IS AUTOMATICALLY GENERATED BY THE DOTFILES MANAGEMENT TOOL'),\n commentLine(shellType, 'DO NOT EDIT THIS FILE DIRECTLY - YOUR CHANGES WILL BE OVERWRITTEN'),\n headerLine(shellType, '='),\n '',\n commentLine(shellType, `Dotfiles directory: ${dotfilesDir}`),\n '',\n ].join('\\n');\n}\n\n/**\n * Generates a section header with consistent 80-character width.\n * @param shellType - Type of shell for comment syntax\n * @param sectionTitle - Title of the section\n * @returns Formatted section header\n */\nexport function generateSectionHeader(shellType: ShellType, sectionTitle: string): string {\n return createSectionHeader(shellType, sectionTitle);\n}\n\n/**\n * Generates a tool attribution header with file path information.\n * Uses simplified format: horizontal line, file path, horizontal line.\n * @param shellType - Type of shell for comment syntax\n * @param configFilePath - Path to the tool's configuration file\n * @returns Tool header string\n */\nexport function generateToolHeader(shellType: ShellType, configFilePath?: string): string {\n const lines = ['', headerLine(shellType, '=')];\n\n if (configFilePath) {\n lines.push(commentLine(shellType, configFilePath));\n }\n\n lines.push(headerLine(shellType, '='));\n\n return lines.join('\\n');\n}\n\n/**\n * Generates hoisting explanation comments for sections that move content\n * from tool-specific configs to centralized sections.\n * @param shellType - Type of shell for comment syntax\n * @param sectionTitle - The section title (e.g., \"PATH Modifications\")\n * @returns Hoisting explanation string\n */\nexport function generateHoistingExplanation(shellType: ShellType, sectionTitle: string): string {\n return [\n commentLine(\n shellType,\n `The following ${sectionTitle.toLowerCase()} have been hoisted from tool-specific configurations`,\n ),\n commentLine(shellType, 'for better organization and to avoid conflicts'),\n '',\n ].join('\\n');\n}\n\n/**\n * Generates a comment indicating which tools contributed to a hoisted item.\n * @param shellType - Type of shell for comment syntax\n * @param sourceTools - Array of tool names that contributed the item\n * @returns Comment line or empty string if no sources\n */\nexport function generateHoistingAttribution(shellType: ShellType, sourceTools: string[]): string {\n if (sourceTools.length === 0) return '';\n return commentLine(shellType, `Hoisted from: ${sourceTools.join(', ')}`);\n}\n\n/**\n * Generates shell-specific default PATH modification with deduplication check.\n * @param shellType - Type of shell (zsh, bash, powershell)\n * @param targetDir - Path to the target directory (where shims are placed)\n * @returns Shell-appropriate PATH modification command with conditional check\n */\nexport function generateDefaultPathModification(shellType: ShellType, targetDir: string): string {\n switch (shellType) {\n case 'powershell':\n return `if ($env:PATH -notlike \"*${targetDir}*\") { $env:PATH = \"${targetDir};$env:PATH\" }`;\n case 'zsh':\n case 'bash':\n return `if [[ \":$PATH:\" != *\":${targetDir}:\"* ]]; then\\n export PATH=\"${targetDir}:$PATH\"\\nfi`;\n default:\n return `if [[ \":$PATH:\" != *\":${targetDir}:\"* ]]; then\\n export PATH=\"${targetDir}:$PATH\"\\nfi`;\n }\n}\n\n/**\n * Generates shell-specific completion setup commands.\n * @param shellType - Type of shell\n * @param completionDir - Directory containing completion files\n * @returns Shell-appropriate completion setup commands\n */\nexport function generateCompletionSetup(shellType: ShellType, completionDir: string): string[] {\n switch (shellType) {\n case 'zsh':\n return ['typeset -U fpath', `fpath=(\"${completionDir}/completions\" $fpath)`];\n default:\n // Bash and PowerShell completions are handled per-tool\n return [];\n }\n}\n\n/**\n * Generates the end-of-file marker.\n * @param shellType - Type of shell for comment syntax\n * @returns End of file section header\n */\nexport function generateEndOfFile(shellType: ShellType): string {\n return generateSectionHeader(shellType, 'End of Generated File');\n}\n\n/**\n * Generates shell-specific source command for including generated scripts.\n * @param shellType - Type of shell\n * @param scriptPath - Path to the script to source\n * @returns Appropriate source command for the shell\n */\nexport function generateSourceLine(shellType: ShellType, scriptPath: string): string {\n switch (shellType) {\n case 'zsh':\n case 'bash':\n return `source \"${scriptPath}\"`;\n case 'powershell':\n return `. \"${scriptPath}\"`;\n default:\n throw new Error(`Unsupported shell type: ${shellType}`);\n }\n}\n\n/**\n * Generates a header comment block for profile file modifications.\n * Creates a double-line header with generator attribution.\n * @param shellType - Type of shell for comment syntax\n * @returns Multi-line header comment block\n */\nexport function generateProfileHeader(shellType: ShellType): string {\n return [commentLine(shellType, 'Generated via dotfiles generator - do not modify'), headerLine(shellType, '-')].join(\n '\\n',\n );\n}\n",
|
|
177
|
+
"import type { ProjectConfig } from '@dotfiles/config';\nimport type { IPluginShellInit, ShellType, ToolConfig } from '@dotfiles/core';\nimport { getScriptContent, isAlwaysScript, isOnceScript, isRawScript } from '@dotfiles/core';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport type { Emission } from '@dotfiles/shell-emissions';\nimport { alias, environment, fn, script, withSource } from '@dotfiles/shell-emissions';\nimport { resolvePlatformConfig } from '@dotfiles/utils';\nimport path from 'node:path';\nimport type { IGenerateShellInitOptions, IShellInitGenerationResult, IShellInitGenerator } from './IShellInitGenerator';\nimport { messages } from './log-messages';\nimport { type IProfileUpdateConfig, ProfileUpdater } from './profile-updater';\nimport {\n createGenerator,\n type IAdditionalShellFile,\n type IShellGenerator,\n} from './shell-generators';\n\n/**\n * Service that generates shell initialization scripts for installed tools.\n *\n * This class orchestrates the creation of shell-specific initialization files (e.g., .zshrc, .bashrc)\n * that configure the environment for installed tools. It consolidates all tool-specific shell\n * configurations into optimized, generated scripts that can be sourced by the user's shell.\n *\n * **Key Responsibilities:**\n * - Generate shell-specific initialization files for multiple shells (zsh, bash, PowerShell)\n * - Aggregate shell configurations from all installed tools\n * - Handle shell completions and custom scripts\n * - Update user profile files to source generated scripts\n * - Support platform-specific configurations\n *\n * **Generated Content:**\n * - Environment variables from tool configurations\n * - Aliases and shell functions\n * - PATH modifications for tool binaries\n * - Shell completion scripts\n * - Custom initialization scripts (run once or always)\n *\n * **Profile Integration:**\n * Optionally updates the user's shell profile files (e.g., ~/.zshrc, ~/.bashrc) to automatically\n * source the generated initialization scripts. This ensures tools are available in new shell sessions.\n */\nexport class ShellInitGenerator implements IShellInitGenerator {\n private readonly fs: IFileSystem;\n private readonly projectConfig: ProjectConfig;\n private readonly logger: TsLogger;\n\n constructor(parentLogger: TsLogger, fileSystem: IFileSystem, projectConfig: ProjectConfig) {\n this.logger = parentLogger.getSubLogger({ name: 'ShellInitGenerator' });\n const logger = this.logger.getSubLogger({ name: 'constructor' });\n logger.debug(messages.constructor.initialized());\n this.fs = fileSystem;\n this.projectConfig = projectConfig;\n }\n\n async generate(\n toolConfigs: Record<string, ToolConfig>,\n options?: IGenerateShellInitOptions,\n ): Promise<IShellInitGenerationResult | null> {\n const logger = this.logger.getSubLogger({ name: 'generate' });\n const shellTypes: ShellType[] = options?.shellTypes ?? ['zsh'];\n const generatedFiles = new Map<ShellType, string>();\n let primaryPath: string | null = null;\n\n const toolConfigsCount = toolConfigs ? Object.keys(toolConfigs).length : 0;\n logger.debug(messages.generate.parsedToolCount(toolConfigsCount));\n\n for (const shellType of shellTypes) {\n const result = await this.generateForShellType(shellType, toolConfigs, options);\n if (result) {\n generatedFiles.set(shellType, result.outputPath);\n if (primaryPath === null) {\n primaryPath = result.outputPath;\n }\n }\n }\n\n if (generatedFiles.size === 0) {\n return null;\n }\n\n const result: IShellInitGenerationResult = {\n files: generatedFiles,\n primaryPath,\n };\n\n const shouldUpdateProfiles = options?.updateProfileFiles ?? true;\n if (shouldUpdateProfiles) {\n result.profileUpdates = await this.updateProfileFiles(generatedFiles);\n }\n\n return result;\n }\n\n private async generateForShellType(\n shellType: ShellType,\n toolConfigs: Record<string, ToolConfig>,\n options?: IGenerateShellInitOptions,\n ): Promise<{ outputPath: string; } | null> {\n const logger = this.logger.getSubLogger({ name: 'generateForShellType' });\n try {\n const generator = createGenerator(shellType, this.projectConfig);\n const outputPath = options?.outputPath ?? generator.getDefaultOutputPath();\n logger.debug(messages.generate.resolvedOutputPath(outputPath));\n\n const toolEmissions = this.extractToolEmissions(toolConfigs, generator, options);\n const fileContent = generator.generateFileContent(toolEmissions);\n\n await this.cleanupOnceScriptsDirectory(shellType);\n const additionalFiles = generator.getAdditionalFiles(toolEmissions);\n\n const writeResult = await this.writeShellFiles(outputPath, fileContent, additionalFiles);\n return writeResult ? { outputPath } : null;\n } catch (error: unknown) {\n logger.debug(messages.generate.shellTypeFailure(shellType), error);\n return null;\n }\n }\n\n /**\n * Extracts emissions from all tool configurations.\n */\n private extractToolEmissions(\n toolConfigs: Record<string, ToolConfig>,\n generator: IShellGenerator,\n options?: IGenerateShellInitOptions,\n ): Map<string, Emission[]> {\n const toolEmissions = new Map<string, Emission[]>();\n\n for (const toolName in toolConfigs) {\n const config = toolConfigs[toolName];\n if (!config) {\n continue;\n }\n\n const resolvedConfig = options?.systemInfo ? resolvePlatformConfig(config, options.systemInfo) : config;\n const emissions = generator.extractEmissions(resolvedConfig);\n\n // Merge plugin-emitted shell init if present\n const pluginShellInit = options?.pluginShellInit?.[toolName]?.[generator.shellType];\n if (pluginShellInit) {\n const pluginEmissions = this.convertPluginShellInit(pluginShellInit, resolvedConfig.configFilePath);\n emissions.push(...pluginEmissions);\n }\n\n if (emissions.length > 0) {\n toolEmissions.set(toolName, emissions);\n }\n }\n\n return toolEmissions;\n }\n\n /**\n * Converts plugin-emitted shell initialization content to typed emissions.\n */\n private convertPluginShellInit(pluginInit: IPluginShellInit, configFilePath?: string): Emission[] {\n const emissions: Emission[] = [];\n const source = configFilePath;\n\n // Convert environment variables\n if (pluginInit.environmentVariables) {\n const emission = environment(pluginInit.environmentVariables);\n emissions.push(source ? withSource(emission, source) : emission);\n }\n\n // Convert aliases\n if (pluginInit.aliases) {\n const emission = alias(pluginInit.aliases);\n emissions.push(source ? withSource(emission, source) : emission);\n }\n\n // Convert scripts\n if (pluginInit.scripts) {\n for (const shellScript of pluginInit.scripts) {\n const scriptContent = getScriptContent(shellScript);\n let emission: Emission | undefined;\n\n if (isOnceScript(shellScript)) {\n emission = script(scriptContent, 'once');\n } else if (isAlwaysScript(shellScript)) {\n emission = script(scriptContent, 'always');\n } else if (isRawScript(shellScript)) {\n emission = script(scriptContent, 'raw');\n }\n\n if (emission) {\n emissions.push(source ? withSource(emission, source) : emission);\n }\n }\n }\n\n // Convert functions\n if (pluginInit.functions) {\n for (const [funcName, funcBody] of Object.entries(pluginInit.functions)) {\n const emission = fn(funcName, funcBody);\n emissions.push(source ? withSource(emission, source) : emission);\n }\n }\n\n return emissions;\n }\n\n private async writeShellFiles(\n outputPath: string,\n fileContent: string,\n additionalFiles: IAdditionalShellFile[],\n ): Promise<boolean> {\n const logger = this.logger.getSubLogger({ name: 'writeShellFiles' });\n try {\n await this.fs.ensureDir(path.dirname(outputPath));\n await this.fs.writeFile(outputPath, fileContent);\n\n for (const additionalFile of additionalFiles) {\n await this.writeAdditionalFile(additionalFile);\n }\n\n return true;\n } catch (error: unknown) {\n logger.debug(messages.generate.writeFailure(outputPath), error);\n return false;\n }\n }\n\n private async writeAdditionalFile(additionalFile: IAdditionalShellFile): Promise<void> {\n const logger = this.logger.getSubLogger({ name: 'writeAdditionalFile' });\n try {\n await this.fs.ensureDir(path.dirname(additionalFile.outputPath));\n await this.fs.writeFile(additionalFile.outputPath, additionalFile.content);\n } catch (error: unknown) {\n logger.debug(messages.generate.writeFailure(additionalFile.outputPath), error);\n }\n }\n\n /**\n * Updates shell profile files to source the generated shell scripts.\n * @param generatedFiles - Map of shell type to generated file path\n * @returns Promise resolving to array of profile update results\n */\n private async updateProfileFiles(generatedFiles: Map<ShellType, string>) {\n const logger = this.logger.getSubLogger({ name: 'updateProfileFiles' });\n const profileUpdater = new ProfileUpdater(this.fs, this.projectConfig.paths.homeDir);\n const shellInstallConfig = this.projectConfig.features.shellInstall;\n\n if (!shellInstallConfig) {\n logger.debug(messages.profiles.skipped('all' as ShellType));\n return [];\n }\n\n const configs: IProfileUpdateConfig[] = [];\n for (const [shellType, scriptPath] of generatedFiles) {\n let profilePath: string | undefined;\n\n if (shellType === 'zsh') {\n profilePath = shellInstallConfig?.zsh;\n } else if (shellType === 'bash') {\n profilePath = shellInstallConfig?.bash;\n } else if (shellType === 'powershell') {\n profilePath = shellInstallConfig?.powershell;\n }\n\n if (!profilePath) {\n logger.debug(messages.profiles.skipped(shellType));\n continue;\n }\n\n if (profilePath?.startsWith('~/')) {\n profilePath = path.join(this.projectConfig.paths.homeDir, profilePath.slice(2));\n } else if (profilePath === '~') {\n profilePath = this.projectConfig.paths.homeDir;\n }\n\n configs.push({\n shellType,\n generatedScriptPath: scriptPath,\n onlyIfExists: true, // Only update profile files if they already exist\n projectConfigPath: this.projectConfig.configFilePath,\n profilePath,\n });\n }\n\n logger.debug(messages.profiles.starting(configs.length));\n return await profileUpdater.updateProfiles(configs);\n }\n\n /**\n * Cleans up the once scripts directory for a specific shell type by removing all existing files.\n * This ensures that removed or changed once scripts don't remain from previous generations.\n * @param shellType - The shell type to clean up once scripts for\n */\n private async cleanupOnceScriptsDirectory(shellType: ShellType): Promise<void> {\n const logger = this.logger.getSubLogger({ name: 'cleanupOnceScriptsDirectory' });\n const onceDir = path.join(this.projectConfig.paths.shellScriptsDir, '.once');\n\n try {\n const onceDirExists = await this.fs.exists(onceDir);\n if (!onceDirExists) {\n return;\n }\n\n const extension = this.getShellExtension(shellType);\n const files = await this.fs.readdir(onceDir);\n\n // Filter to only files matching this shell type and remove them\n for (const file of files) {\n if (file.endsWith(`.${extension}`)) {\n const filePath = path.join(onceDir, file);\n await this.fs.rm(filePath);\n logger.debug(messages.cleanup.onceScriptRemoved(filePath));\n }\n }\n } catch (error: unknown) {\n logger.debug(messages.cleanup.failure(onceDir), error);\n // Continue generation even if cleanup fails\n }\n }\n\n /**\n * Gets the file extension for a shell type\n * @param shellType - The shell type\n * @returns The file extension for the shell\n */\n private getShellExtension(shellType: ShellType): string {\n switch (shellType) {\n case 'zsh':\n return 'zsh';\n case 'bash':\n return 'bash';\n case 'powershell':\n return 'ps1';\n default:\n throw new Error(`Unsupported shell type: ${shellType}`);\n }\n }\n}\n",
|
|
178
|
+
"import type { ShellType } from '@dotfiles/core';\nimport { createSafeLogMessage, type SafeLogMessageMap } from '@dotfiles/logger';\n\nexport const messages = {\n constructor: {\n initialized: () => createSafeLogMessage('ShellInitGenerator initialized'),\n } satisfies SafeLogMessageMap,\n generate: {\n parsedToolCount: (toolConfigCount: number) =>\n createSafeLogMessage(`Resolved ${toolConfigCount} tool configurations for shell init generation`),\n resolvedOutputPath: (outputPath: string) =>\n createSafeLogMessage(`Shell init output path resolved to ${outputPath}`),\n shellTypeFailure: (shellType: ShellType) => createSafeLogMessage(`Shell init generation failed for ${shellType}`),\n writeFailure: (targetPath: string) => createSafeLogMessage(`Failed to write shell init artifact ${targetPath}`),\n } satisfies SafeLogMessageMap,\n profiles: {\n starting: (entryCount: number) => createSafeLogMessage(`Updating ${entryCount} shell profile entries`),\n skipped: (shellType: ShellType) =>\n createSafeLogMessage(`Skipping profile update for ${shellType} as it is not configured`),\n } satisfies SafeLogMessageMap,\n cleanup: {\n onceScriptRemoved: (scriptPath: string) => createSafeLogMessage(`Removed stale once script ${scriptPath}`),\n failure: (directoryPath: string) =>\n createSafeLogMessage(`Failed to clean shell init once directory ${directoryPath}`),\n } satisfies SafeLogMessageMap,\n} as const;\n",
|
|
179
|
+
"import type { ShellType, ShellTypeConfig, ToolConfig } from '@dotfiles/core';\nimport path from 'node:path';\nimport { BaseShellGenerator } from './BaseShellGenerator';\n\n/**\n * Bash-specific shell initialization generator.\n * Handles Bash syntax and conventions for PATH, environment variables,\n * completions, and tool-specific initialization.\n */\nexport class BashGenerator extends BaseShellGenerator {\n readonly shellType: ShellType = 'bash';\n readonly fileExtension: string = '.bash';\n\n protected getShellConfig(toolConfig: ToolConfig): ShellTypeConfig | undefined {\n // ShellTypeConfig is manually typed; ToolConfig uses Zod inference with z.unknown() for completions\n return toolConfig.shellConfigs?.bash as ShellTypeConfig | undefined;\n }\n\n protected getCompletionDir(): string {\n return path.join(this.projectConfig.paths.shellScriptsDir, 'bash', 'completions');\n }\n}\n",
|
|
180
|
+
"import type { ProjectConfig } from '@dotfiles/config';\nimport type {\n PathConfigInput,\n ShellCompletionConfig,\n ShellCompletionConfigInput,\n ShellScript,\n ShellType,\n ShellTypeConfig,\n ToolConfig,\n} from '@dotfiles/core';\nimport { getScriptContent, isAlwaysScript, isOnceScript, isRawScript } from '@dotfiles/core';\nimport type { Emission, FormatterConfig, RenderedOutput } from '@dotfiles/shell-emissions';\nimport {\n alias,\n BlockBuilder,\n BlockRenderer,\n completion,\n environment,\n fn,\n path as pathEmission,\n script,\n SectionPriority,\n withPriority,\n withSource,\n} from '@dotfiles/shell-emissions';\nimport { getCliBinPath } from '@dotfiles/utils';\nimport path from 'node:path';\nimport { createEmissionFormatter } from '../formatters';\nimport type { IAdditionalShellFile, IShellGenerator } from './IShellGenerator';\n\n/**\n * Abstract base class for shell generators that contains all shared logic.\n * Shell-specific implementations only need to provide shell type information\n * and the method to extract shell-specific configuration.\n */\nexport abstract class BaseShellGenerator implements IShellGenerator {\n abstract readonly shellType: ShellType;\n abstract readonly fileExtension: string;\n\n protected readonly projectConfig: ProjectConfig;\n\n constructor(projectConfig: ProjectConfig) {\n this.projectConfig = projectConfig;\n }\n\n /**\n * Extracts shell-specific configuration from a tool config.\n * Each shell generator implements this to return its shell's config section.\n */\n protected abstract getShellConfig(toolConfig: ToolConfig): ShellTypeConfig | undefined;\n\n /**\n * Gets the completion directory path for this shell.\n */\n protected abstract getCompletionDir(): string;\n\n /**\n * Extracts typed emissions directly from a tool configuration.\n * This is the primary content extraction method - emits structured data\n * without intermediate string representation.\n */\n extractEmissions(toolConfig: ToolConfig): Emission[] {\n const emissions: Emission[] = [];\n const source = toolConfig.configFilePath;\n const shellConfig = this.getShellConfig(toolConfig);\n\n // Process environment variables\n if (shellConfig?.env) {\n const envEmission = environment(shellConfig.env);\n emissions.push(source ? withSource(envEmission, source) : envEmission);\n }\n\n // Process aliases\n if (shellConfig?.aliases) {\n const aliasEmission = alias(shellConfig.aliases);\n emissions.push(source ? withSource(aliasEmission, source) : aliasEmission);\n }\n\n // Process shell functions\n if (shellConfig?.functions) {\n for (const [funcName, funcBody] of Object.entries(shellConfig.functions)) {\n const funcEmission = fn(funcName, funcBody);\n emissions.push(source ? withSource(funcEmission, source) : funcEmission);\n }\n }\n\n // Process scripts\n if (shellConfig?.scripts) {\n for (const shellScript of shellConfig.scripts) {\n const scriptEmission = this.createScriptEmission(shellScript);\n if (scriptEmission) {\n emissions.push(source ? withSource(scriptEmission, source) : scriptEmission);\n }\n }\n }\n\n // Process completions\n if (shellConfig?.completions) {\n const resolvedConfig = this.resolveCompletionConfig(shellConfig.completions);\n if (resolvedConfig) {\n const completionEmission = this.createCompletionEmission(resolvedConfig);\n if (completionEmission) {\n emissions.push(source ? withSource(completionEmission, source) : completionEmission);\n }\n }\n }\n\n // Process paths (deduplicated)\n if (shellConfig?.paths) {\n const seenPaths = new Set<string>();\n for (const pathInput of shellConfig.paths) {\n const resolvedPath = this.resolvePathInput(pathInput);\n if (resolvedPath && !seenPaths.has(resolvedPath)) {\n seenPaths.add(resolvedPath);\n const pathEmissionValue = pathEmission(resolvedPath);\n emissions.push(source ? withSource(pathEmissionValue, source) : pathEmissionValue);\n }\n }\n }\n\n return emissions;\n }\n\n /**\n * Creates a script emission from a ShellScript.\n */\n private createScriptEmission(shellScript: ShellScript): Emission | undefined {\n const scriptContent = getScriptContent(shellScript);\n\n if (isOnceScript(shellScript)) {\n return script(scriptContent, 'once');\n } else if (isAlwaysScript(shellScript)) {\n return script(scriptContent, 'always');\n } else if (isRawScript(shellScript)) {\n return script(scriptContent, 'raw');\n }\n\n return undefined;\n }\n\n /**\n * Creates a completion emission from resolved completion config.\n * Default uses files-based completion (Bash, PowerShell).\n * Override in ZshGenerator for fpath directory-based completion.\n */\n protected createCompletionEmission(config: ShellCompletionConfig): Emission | undefined {\n const completionDir = this.getCompletionDir();\n\n if (config.cmd || config.source) {\n return completion({\n files: [completionDir],\n });\n }\n\n return undefined;\n }\n\n /**\n * Resolves a completion config input to a static config for shell init processing.\n * For callbacks, returns a minimal config with default settings since the actual\n * completion generation happens separately in GeneratorOrchestrator.\n */\n private resolveCompletionConfig(input: ShellCompletionConfigInput): ShellCompletionConfig | undefined {\n // If it's a function, return a minimal config to set up fpath\n // The actual completion content is generated by GeneratorOrchestrator\n if (typeof input === 'function') {\n const defaultConfig: ShellCompletionConfig = { source: 'callback' };\n return defaultConfig;\n }\n\n // If it's a string, treat as source path\n if (typeof input === 'string') {\n const config: ShellCompletionConfig = { source: input };\n return config;\n }\n\n // Otherwise it's already a config object\n return input;\n }\n\n /**\n * Resolves a path config input to a string.\n * Handles both static strings and synchronous callbacks.\n */\n private resolvePathInput(input: PathConfigInput): string | undefined {\n if (typeof input === 'function') {\n // Synchronously resolve the path (Resolvable<void, string>)\n const result = input(undefined as void);\n // Handle async functions - they're not supported in sync extraction\n if (result instanceof Promise) {\n return undefined;\n }\n return result;\n }\n return input;\n }\n\n /**\n * Checks if a tool configuration has any meaningful shell content.\n */\n hasEmissions(toolConfig: ToolConfig): boolean {\n const shellConfig = this.getShellConfig(toolConfig);\n if (!shellConfig) {\n return false;\n }\n\n return Boolean(\n shellConfig.env && Object.keys(shellConfig.env).length > 0 ||\n shellConfig.aliases && Object.keys(shellConfig.aliases).length > 0 ||\n shellConfig.functions && Object.keys(shellConfig.functions).length > 0 ||\n shellConfig.scripts && shellConfig.scripts.length > 0 ||\n shellConfig.paths && shellConfig.paths.length > 0 ||\n shellConfig.completions,\n );\n }\n\n generateFileContent(toolEmissions: Map<string, Emission[]>): string {\n const result = this.renderContent(toolEmissions);\n return result.content;\n }\n\n getDefaultOutputPath(): string {\n return path.join(this.projectConfig.paths.shellScriptsDir, `main${this.fileExtension}`);\n }\n\n getAdditionalFiles(toolEmissions: Map<string, Emission[]>): IAdditionalShellFile[] {\n const result = this.renderContent(toolEmissions);\n return result.onceScripts.map((onceScript) => ({\n content: onceScript.content,\n outputPath: path.join(this.projectConfig.paths.shellScriptsDir, '.once', onceScript.filename),\n }));\n }\n\n /**\n * Creates the formatter configuration for this generator.\n */\n private createFormatterConfig(): FormatterConfig {\n return {\n onceScriptDir: path.join(this.projectConfig.paths.shellScriptsDir, '.once'),\n };\n }\n\n /**\n * Renders tool emissions to shell output using the emissions system.\n */\n private renderContent(toolEmissions: Map<string, Emission[]>): RenderedOutput {\n const formatterConfig = this.createFormatterConfig();\n const formatter = createEmissionFormatter(this.shellType, formatterConfig);\n const renderer = new BlockRenderer();\n\n // Build block structure with sections\n const blockBuilder = new BlockBuilder()\n .addSection('header', {\n priority: SectionPriority.FileHeader,\n isFileHeader: true,\n metadata: { sourceFile: this.projectConfig.paths.dotfilesDir },\n })\n .addSection('cli', {\n title: 'Dotfiles CLI',\n priority: SectionPriority.Cli,\n })\n .addSection('path', {\n title: 'PATH Modifications',\n priority: SectionPriority.Path,\n hoistKinds: ['path'],\n })\n .addSection('environment', {\n title: 'Environment Variables',\n priority: SectionPriority.Environment,\n hoistKinds: ['environment'],\n })\n .addSection('main', {\n title: 'Tool-Specific Initializations',\n priority: SectionPriority.MainContent,\n allowChildren: true,\n })\n .addSection('completions', {\n title: 'Shell Completions Setup',\n priority: SectionPriority.Completions,\n hoistKinds: ['completion'],\n })\n .addSection('footer', {\n priority: SectionPriority.FileFooter,\n isFileFooter: true,\n });\n\n // Add default PATH emission for target bin directory (priority -1 ensures it comes first)\n const defaultPathEmission = withPriority(\n pathEmission(this.projectConfig.paths.targetDir, { deduplicate: true }),\n -1,\n );\n blockBuilder.addEmission(defaultPathEmission);\n\n // Add CLI function\n const cliPath = getCliBinPath();\n const escapedConfigPath = this.projectConfig.configFilePath?.replaceAll('\"', '\\\\\"');\n const configFlag = escapedConfigPath ? ` --config \"${escapedConfigPath}\"` : '';\n const cliFunctionBody = `${cliPath}${configFlag} \"$@\"`;\n const cliFunctionEmission = fn('dotfiles', cliFunctionBody);\n blockBuilder.addEmissionToSection(cliFunctionEmission, 'cli');\n\n // Add each tool's emissions to the builder\n for (const [toolName, emissions] of toolEmissions) {\n for (const emission of emissions) {\n blockBuilder.addEmission(emission, toolName);\n }\n }\n\n const blocks = blockBuilder.build();\n return renderer.render(blocks, formatter);\n }\n}\n",
|
|
181
|
+
"import type { ShellType, ShellTypeConfig, ToolConfig } from '@dotfiles/core';\nimport path from 'node:path';\nimport { BaseShellGenerator } from './BaseShellGenerator';\n\n/**\n * PowerShell-specific shell initialization generator.\n * Handles PowerShell syntax and conventions for PATH, environment variables,\n * completions, and tool-specific initialization.\n */\nexport class PowerShellGenerator extends BaseShellGenerator {\n readonly shellType: ShellType = 'powershell';\n readonly fileExtension: string = '.ps1';\n\n protected getShellConfig(toolConfig: ToolConfig): ShellTypeConfig | undefined {\n // ShellTypeConfig is manually typed; ToolConfig uses Zod inference with z.unknown() for completions\n return toolConfig.shellConfigs?.powershell as ShellTypeConfig | undefined;\n }\n\n protected getCompletionDir(): string {\n return path.join(this.projectConfig.paths.shellScriptsDir, 'powershell', 'completions');\n }\n}\n",
|
|
182
|
+
"import type { ShellCompletionConfig, ShellType, ShellTypeConfig, ToolConfig } from '@dotfiles/core';\nimport type { Emission } from '@dotfiles/shell-emissions';\nimport { completion } from '@dotfiles/shell-emissions';\nimport path from 'node:path';\nimport { BaseShellGenerator } from './BaseShellGenerator';\n\n/**\n * Zsh-specific shell initialization generator.\n * Handles Zsh syntax and conventions for PATH, environment variables,\n * completions, and tool-specific initialization.\n */\nexport class ZshGenerator extends BaseShellGenerator {\n readonly shellType: ShellType = 'zsh';\n readonly fileExtension: string = '.zsh';\n\n protected getShellConfig(toolConfig: ToolConfig): ShellTypeConfig | undefined {\n // ShellTypeConfig is manually typed; ToolConfig uses Zod inference with z.unknown() for completions\n return toolConfig.shellConfigs?.zsh as ShellTypeConfig | undefined;\n }\n\n protected getCompletionDir(): string {\n return path.join(this.projectConfig.paths.shellScriptsDir, 'zsh', 'completions');\n }\n\n /**\n * Zsh uses fpath directories for completions.\n */\n protected override createCompletionEmission(config: ShellCompletionConfig): Emission | undefined {\n const completionDir = this.getCompletionDir();\n\n if (config.cmd || config.source) {\n return completion({\n directories: [completionDir],\n });\n }\n\n return undefined;\n }\n}\n",
|
|
183
|
+
"import type { ProjectConfig } from '@dotfiles/config';\nimport type { ShellType } from '@dotfiles/core';\nimport { BashGenerator } from './BashGenerator';\nimport type { IShellGenerator } from './IShellGenerator';\nimport { PowerShellGenerator } from './PowerShellGenerator';\nimport { ZshGenerator } from './ZshGenerator';\n\n/**\n * Map of shell types to their generator factory functions.\n */\nconst generators = new Map<ShellType, (projectConfig: ProjectConfig) => IShellGenerator>([\n ['zsh', (projectConfig: ProjectConfig) => new ZshGenerator(projectConfig)],\n ['bash', (projectConfig: ProjectConfig) => new BashGenerator(projectConfig)],\n ['powershell', (projectConfig: ProjectConfig) => new PowerShellGenerator(projectConfig)],\n]);\n\n/**\n * Creates a shell generator for the specified shell type.\n * @param shellType - The shell type to create a generator for\n * @param projectConfig - Application configuration\n * @returns Shell generator instance\n * @throws Error if the shell type is not supported\n */\nexport function createGenerator(shellType: ShellType, projectConfig: ProjectConfig): IShellGenerator {\n const generatorFactory = generators.get(shellType);\n if (!generatorFactory) {\n throw new Error(`Unsupported shell type: ${shellType}`);\n }\n return generatorFactory(projectConfig);\n}\n\n/**\n * Gets all supported shell types.\n * @returns Array of supported shell types\n */\nexport function getSupportedShellTypes(): ShellType[] {\n return Array.from(generators.keys());\n}\n\n/**\n * Creates generators for all supported shell types.\n * @param projectConfig - Application configuration\n * @returns Map of shell type to generator instance\n */\nexport function createAllGenerators(projectConfig: ProjectConfig): Map<ShellType, IShellGenerator> {\n const result = new Map<ShellType, IShellGenerator>();\n for (const [shellType, factory] of generators) {\n result.set(shellType, factory(projectConfig));\n }\n return result;\n}\n\n/**\n * Checks if a shell type is supported.\n * @param shellType - Shell type to check\n * @returns True if the shell type is supported\n */\nexport function isSupported(shellType: string): shellType is ShellType {\n return generators.has(shellType as ShellType);\n}\n",
|
|
184
|
+
"import type {\n IShellConfigurator,\n IToolConfigContext,\n ShellCompletionConfigInput,\n} from '@dotfiles/core';\nimport { always, once, raw } from '@dotfiles/core';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { VALID_FUNCTION_NAME_PATTERN } from '@dotfiles/shell-init-generator';\nimport type { Resolvable } from '@dotfiles/unwrap-value';\nimport { resolveToolRelativePath } from '@dotfiles/utils';\nimport path from 'node:path';\nimport { messages } from './log-messages';\nimport type { IShellStorage, ShellTypeKey } from './types';\n\n/**\n * Implementation of the shell configurator interface.\n * Handles the accumulation of shell configuration settings for a specific shell type.\n */\nexport class ShellConfigurator implements IShellConfigurator<string> {\n private readonly storage: IShellStorage;\n private readonly shellType: ShellTypeKey;\n private readonly context?: IToolConfigContext;\n private readonly logger: TsLogger;\n private readonly toolName: string;\n private sourceFileCounter = 0;\n private sourceCounter = 0;\n\n constructor(\n storage: IShellStorage,\n shellType: ShellTypeKey,\n context: IToolConfigContext | undefined,\n logger: TsLogger,\n toolName: string,\n ) {\n this.storage = storage;\n this.shellType = shellType;\n this.context = context;\n this.logger = logger.getSubLogger({ name: 'ShellConfigurator' }).setPrefix(toolName);\n this.toolName = toolName;\n }\n\n /** @inheritdoc */\n public env<T extends Record<string, string>>(\n values: 'PATH' extends keyof T ? ['ERROR: Use shell.path() to modify PATH'] : T,\n ): IShellConfigurator<string> {\n this.storage.env = {\n ...this.storage.env,\n ...(values as Record<string, string>),\n };\n return this;\n }\n\n /** @inheritdoc */\n public aliases(values: Record<string, string>): IShellConfigurator<string> {\n this.storage.aliases = {\n ...this.storage.aliases,\n ...values,\n };\n return this;\n }\n\n /** @inheritdoc */\n public sourceFile(relativePath: string): IShellConfigurator<string> {\n const resolvedPath = this.resolvePath(relativePath);\n const functionName = this.generateSourceFileFunctionName();\n\n // Create a function that cats the file content (with existence check)\n const functionBody = this.createSourceFileFunctionBody(resolvedPath);\n this.storage.functions[functionName] = functionBody;\n\n // Add raw scripts to source the function output and then unset the function\n const sourceCommand = this.createSourceFunctionCommand(functionName);\n const unsetCommand = this.createUnsetFunctionCommand(functionName);\n this.storage.scripts.push(raw(sourceCommand));\n this.storage.scripts.push(raw(unsetCommand));\n\n return this;\n }\n\n /** @inheritdoc */\n public sourceFunction(functionName: string): IShellConfigurator<string> {\n const command: string = this.createSourceFunctionCommand(functionName);\n this.storage.scripts.push(raw(command));\n return this;\n }\n\n /** @inheritdoc */\n public source(content: string): IShellConfigurator<string> {\n const functionName = this.generateSourceFunctionName();\n\n // Create a function with the inline content\n // Unlike sourceFile, this doesn't check for file existence - it just outputs the content\n this.storage.functions[functionName] = content;\n\n // Add raw scripts to source the function output and then unset the function\n const sourceCommand = this.createSourceFunctionCommand(functionName);\n const unsetCommand = this.createUnsetFunctionCommand(functionName);\n this.storage.scripts.push(raw(sourceCommand));\n this.storage.scripts.push(raw(unsetCommand));\n\n return this;\n }\n\n /** @inheritdoc */\n public completions(completion: ShellCompletionConfigInput): IShellConfigurator<string> {\n // Store the raw resolvable input - resolution happens at generation time\n // when version and other context properties are available\n this.storage.completions = completion;\n return this;\n }\n\n /** @inheritdoc */\n public once(script: string): IShellConfigurator<string> {\n this.storage.scripts.push(once(script));\n return this;\n }\n\n /** @inheritdoc */\n public always(script: string): IShellConfigurator<string> {\n this.storage.scripts.push(always(script));\n return this;\n }\n\n /** @inheritdoc */\n public functions<K extends string>(values: Record<K, string>): IShellConfigurator<string> {\n const validatedFunctions = this.validateFunctionNames(values);\n this.storage.functions = {\n ...this.storage.functions,\n ...validatedFunctions,\n };\n return this;\n }\n\n /** @inheritdoc */\n public path(pathValue: Resolvable<void, string>): IShellConfigurator<string> {\n this.storage.paths.push(pathValue);\n return this;\n }\n\n /**\n * Validates function names and filters out invalid ones with error logging.\n */\n private validateFunctionNames(values: Record<string, string>): Record<string, string> {\n const result: Record<string, string> = {};\n\n for (const [functionName, functionBody] of Object.entries(values)) {\n if (!functionName || !VALID_FUNCTION_NAME_PATTERN.test(functionName)) {\n this.logger.error(messages.invalidFunctionName(functionName));\n continue;\n }\n result[functionName] = functionBody;\n }\n\n return result;\n }\n\n /**\n * Generates a unique function name for sourceFile operations.\n * Uses a prefix that indicates it's an internal function and should be unset after use.\n */\n private generateSourceFileFunctionName(): string {\n const counter = this.sourceFileCounter++;\n const sanitizedToolName = this.toolName.replace(/[^a-zA-Z0-9]/gu, '_');\n return `__dotfiles_source_${sanitizedToolName}_${counter}`;\n }\n\n /**\n * Generates a unique function name for source operations.\n * Uses a prefix that indicates it's an internal function and should be unset after use.\n */\n private generateSourceFunctionName(): string {\n const counter = this.sourceCounter++;\n const sanitizedToolName = this.toolName.replace(/[^a-zA-Z0-9]/gu, '_');\n return `__dotfiles_source_inline_${sanitizedToolName}_${counter}`;\n }\n\n /**\n * Creates the function body for sourceFile that cats the file content.\n */\n private createSourceFileFunctionBody(resolvedPath: string): string {\n const quotedPath = JSON.stringify(resolvedPath);\n\n if (this.shellType === 'powershell') {\n return `if (Test-Path ${quotedPath}) { Get-Content ${quotedPath} -Raw }`;\n }\n\n return `[[ -f ${quotedPath} ]] && cat ${quotedPath}`;\n }\n\n /**\n * Creates the shell command to unset/remove a function.\n */\n private createUnsetFunctionCommand(functionName: string): string {\n if (this.shellType === 'powershell') {\n return `Remove-Item Function:\\\\${functionName} -ErrorAction SilentlyContinue`;\n }\n\n return `unset -f ${functionName}`;\n }\n\n /**\n * Creates the shell command to source the output of a function.\n * No existence check - the function output is sourced directly.\n */\n private createSourceFunctionCommand(functionName: string): string {\n if (this.shellType === 'powershell') {\n return `. (${functionName})`;\n }\n\n return `source <(${functionName})`;\n }\n\n /**\n * Resolves a relative path to an absolute path based on the tool context.\n * Relative paths are resolved against toolDir (the .tool.ts file's directory).\n * Handles path normalization for the target shell.\n */\n private resolvePath(relativePath: string): string {\n const trimmedPath = relativePath.trim();\n if (trimmedPath.length === 0) {\n const message = messages.configurationFieldInvalid('shell source path', relativePath, 'non-empty value');\n this.logger.error(message);\n throw new Error(message);\n }\n\n if (path.isAbsolute(trimmedPath)) {\n return this.normalizePath(trimmedPath);\n }\n\n if (!this.context) {\n const message = messages.configurationFieldRequired(\n 'tool context',\n `Please ensure createInstallFunction receives tool context before using shell.sourceFile() for \"${this.toolName}\"`,\n );\n this.logger.error(message);\n throw new Error(message);\n }\n\n const resolvedPath = resolveToolRelativePath(this.context.toolDir, trimmedPath);\n return this.normalizePath(resolvedPath);\n }\n\n /**\n * Normalizes path separators for the target shell.\n * Uses backslashes for PowerShell and forward slashes for others.\n */\n private normalizePath(resolvedPath: string): string {\n if (this.shellType === 'powershell') {\n return resolvedPath.replace(/\\//gu, '\\\\');\n }\n return resolvedPath.replace(/\\\\/gu, '/');\n }\n}\n",
|
|
185
|
+
"import type {\n Architecture,\n AsyncInstallHook,\n HookEventName,\n IInstallParamsRegistry,\n InstallMethod,\n IPlatformConfigBuilder as PlatformConfigBuilderInterface,\n IPlatformInstallFunction,\n IToolConfigBuilder as ToolConfigBuilderInterface,\n IToolConfigContext,\n Platform,\n PlatformConfig,\n PlatformConfigEntry,\n ShellConfigs,\n ShellConfiguratorAsyncCallback,\n ShellConfiguratorCallback,\n ToolConfig,\n ToolConfigUpdateCheck,\n} from '@dotfiles/core';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { messages } from './log-messages';\nimport { ShellConfigurator } from './ShellConfigurator';\nimport type { InternalShellConfigs, IShellStorage, ShellTypeKey } from './types';\n\nexport interface IBinaryConfig {\n name: string;\n pattern: string;\n}\n\ntype InstallParams = IInstallParamsRegistry[InstallMethod];\n\n/**\n * A fluent API for creating {@link @dotfiles/core#ToolConfig} objects.\n *\n * This builder provides a chainable interface to define all aspects of a tool's\n * configuration, from its name and version to complex, platform-specific\n * installation instructions and shell integrations.\n *\n * @example\n * ```typescript\n * const nodeConfig = new IToolConfigBuilder(logger, 'node')\n * .version('20.0.0')\n * .bin('node')\n * .bin('npm')\n * .install('github-release', { repo: 'nodejs/node' })\n * .zsh((shell) => shell.env({ NODE_ENV: 'development' }))\n * .platform(Platform.Windows, (p) => {\n * p.install('manual', {\n * // ... windows specific install\n * });\n * })\n * .build();\n * ```\n */\nexport class IToolConfigBuilder implements ToolConfigBuilderInterface {\n private logger: TsLogger;\n public toolName: string;\n public binaries: IBinaryConfig[] = [];\n public versionNum: string = 'latest';\n public currentInstallationMethod?: string;\n public currentInstallParams?: InstallParams | Record<string, unknown>;\n private dependencies: string[] = [];\n private isDisabled: boolean = false;\n private hostnamePattern?: string;\n\n // Organized shell storage matching final ToolConfig structure\n private internalShellConfigs: InternalShellConfigs = {\n zsh: { scripts: [], aliases: {}, env: {}, functions: {}, paths: [] },\n bash: { scripts: [], aliases: {}, env: {}, functions: {}, paths: [] },\n powershell: { scripts: [], aliases: {}, env: {}, functions: {}, paths: [] },\n };\n private context?: IToolConfigContext;\n\n public symlinkPairs: { source: string; target: string; }[] = [];\n public copyPairs: { source: string; target: string; }[] = [];\n private updateCheckConfig?: ToolConfigUpdateCheck;\n private platformConfigEntries: PlatformConfigEntry[] = [];\n\n private isPlatformScope: boolean;\n\n /**\n * Defines a binary executable provided by the tool.\n *\n * A shim will be generated for each binary, making it available in the system's PATH.\n * **This method can be called multiple times** to define all binaries for a tool.\n *\n * @param name - The name of the binary (e.g., `node`, `npm`).\n * @param pattern - An optional glob pattern to locate the binary within the installed files.\n * If not provided, it defaults to `* /${name}`.\n * @returns The `IToolConfigBuilder` instance for chaining.\n */\n bin(name: string, pattern?: string): this {\n const binaryPattern = pattern || `*/${name}`;\n this.binaries.push({ name, pattern: binaryPattern });\n return this;\n }\n\n /**\n * Sets the version of the tool to be installed.\n *\n * This can be a specific version string, a semantic versioning range, or 'latest'.\n * **This method should only be called once**; subsequent calls will override the previous value.\n *\n * @param version - The version identifier.\n * @returns The `IToolConfigBuilder` instance for chaining.\n */\n version(version: string): this {\n this.versionNum = version;\n return this;\n }\n\n /**\n * Specifies the installation method and its required parameters.\n *\n * This is a critical step that defines how the tool is acquired and installed.\n * Type safety for the parameters is provided by each plugin through module augmentation.\n * **This method should only be called once**; subsequent calls will override the previous value.\n *\n * @param method - The installation method (e.g., 'github-release', 'brew').\n * @param params - A configuration object specific to the chosen method.\n * @returns The `IToolConfigBuilder` instance for chaining.\n */\n install<M extends InstallMethod>(method: M, params: IInstallParamsRegistry[M]): this;\n install(method: string, params: Record<string, unknown>): this;\n install(method: string, params: Record<string, unknown>): this {\n this.currentInstallationMethod = method;\n this.currentInstallParams = params;\n return this;\n }\n\n /**\n * Attach a hook handler to a specific lifecycle event.\n *\n * Multiple handlers can be added by calling this method multiple times with the same event name.\n * **This method must be called after {@link IToolConfigBuilder.install}**.\n *\n * @param event - The lifecycle event name (kebab-case: 'before-install', 'after-download', 'after-extract', 'after-install')\n * @param handler - The async hook function to execute\n * @returns The `IToolConfigBuilder` instance for chaining.\n */\n hook(event: HookEventName, handler: AsyncInstallHook<never>): this {\n if (!this.currentInstallParams) {\n this.logger.warn(\n messages.configurationFieldIgnored(\n 'hook',\n `hook() called for tool \"${this.toolName}\" before install(). Hook will not be set as install() was not called first.`,\n ),\n );\n return this;\n }\n\n const hooksObj = (this.currentInstallParams['hooks'] as Record<string, AsyncInstallHook<never>[]>) || {};\n const eventHooks: AsyncInstallHook<never>[] = hooksObj[event] || [];\n eventHooks.push(handler);\n hooksObj[event] = eventHooks;\n this.currentInstallParams['hooks'] = hooksObj;\n\n return this;\n }\n\n /**\n * Configures shell integration for Zsh.\n *\n * This method allows defining shell scripts, environment variables, aliases,\n * and completion scripts that should be sourced by Zsh.\n * **This method can be called multiple times**; configurations are merged.\n *\n * @param config - A {@link @dotfiles/core#ShellConfig} object for Zsh.\n * @returns The `IToolConfigBuilder` instance for chaining.\n */\n zsh(callback: ShellConfiguratorCallback): this;\n zsh(callback: ShellConfiguratorAsyncCallback): Promise<this>;\n zsh(callback: ShellConfiguratorCallback | ShellConfiguratorAsyncCallback): this | Promise<this> {\n return this.configureShell('zsh', callback);\n }\n\n /**\n * Configures shell integration for Bash.\n *\n * This method allows defining shell scripts, environment variables, aliases,\n * and completion scripts that should be sourced by Bash.\n * **This method can be called multiple times**; configurations are merged.\n *\n * @param config - A {@link @dotfiles/core#ShellConfig} object for Bash.\n * @returns The `IToolConfigBuilder` instance for chaining.\n */\n bash(callback: ShellConfiguratorCallback): this;\n bash(callback: ShellConfiguratorAsyncCallback): Promise<this>;\n bash(callback: ShellConfiguratorCallback | ShellConfiguratorAsyncCallback): this | Promise<this> {\n return this.configureShell('bash', callback);\n }\n\n /**\n * Configures shell integration for PowerShell.\n *\n * This method allows defining shell scripts, environment variables, aliases,\n * and completion scripts that should be sourced by PowerShell.\n * **This method can be called multiple times**; configurations are merged.\n *\n * @param config - A {@link @dotfiles/core#ShellConfig} object for PowerShell.\n * @returns The `IToolConfigBuilder` instance for chaining.\n */\n powershell(callback: ShellConfiguratorCallback): this;\n powershell(callback: ShellConfiguratorAsyncCallback): Promise<this>;\n powershell(callback: ShellConfiguratorCallback | ShellConfiguratorAsyncCallback): this | Promise<this> {\n return this.configureShell('powershell', callback);\n }\n\n /**\n * Builds the final shell configurations object by consolidating internal shell storage.\n *\n * Iterates through all shell types (zsh, bash, powershell) and creates a ShellConfigs\n * object containing only the shell types that have actual configuration data.\n *\n * @returns A ShellConfigs object if any shell has configuration, undefined otherwise.\n */\n private buildShellConfigs(): ShellConfigs | undefined {\n const shellTypes = ['zsh', 'bash', 'powershell'] as const;\n const result: ShellConfigs = {};\n let hasAnyConfig = false;\n\n for (const shellType of shellTypes) {\n const config = this.internalShellConfigs[shellType];\n const hasScripts = config.scripts.length > 0;\n const hasAliases = Object.keys(config.aliases).length > 0;\n const hasEnv = Object.keys(config.env).length > 0;\n const hasFunctions = Object.keys(config.functions).length > 0;\n const hasPaths = config.paths.length > 0;\n const hasCompletions = config.completions !== undefined;\n\n const hasAnyShellConfig = hasScripts || hasAliases || hasEnv || hasFunctions || hasPaths ||\n hasCompletions;\n\n if (hasAnyShellConfig) {\n result[shellType] = {\n ...(hasScripts && { scripts: config.scripts }),\n ...(hasAliases && { aliases: config.aliases }),\n ...(hasEnv && { env: config.env }),\n ...(hasFunctions && { functions: config.functions }),\n ...(hasPaths && { paths: config.paths }),\n ...(hasCompletions && { completions: config.completions }),\n };\n hasAnyConfig = true;\n }\n }\n\n return hasAnyConfig ? result : undefined;\n }\n\n /**\n * Defines a symbolic link to be created.\n *\n * This is useful for linking configuration files from a tool's installation\n * directory to a standard location in the user's home directory.\n * **This method can be called multiple times**.\n *\n * @param source - The path to the source file, relative to the location of the current `.tool.ts` file.\n * @param target - The absolute path where the symbolic link should be created.\n * @returns The `IToolConfigBuilder` instance for chaining.\n */\n symlink(source: string, target: string): this {\n this.symlinkPairs.push({ source, target });\n return this;\n }\n\n /**\n * Defines a file or directory to be copied.\n *\n * This is useful for configuration files that should be independently editable\n * at the target location, unlike symlinks which reference the original file.\n * **This method can be called multiple times**.\n *\n * @param source - The path to the source file or directory, relative to the location of the current `.tool.ts` file.\n * @param target - The absolute path where the copy should be placed.\n * @returns The `IToolConfigBuilder` instance for chaining.\n */\n copy(source: string, target: string): this {\n this.copyPairs.push({ source, target });\n return this;\n }\n\n /**\n * Declares binary dependencies required before generating this tool.\n *\n * @param binaryNames - One or more binary names that must be available\n * @returns The `IToolConfigBuilder` instance for chaining.\n */\n dependsOn(...binaryNames: string[]): this {\n for (const rawName of binaryNames) {\n const trimmedName = rawName.trim();\n if (trimmedName.length === 0) {\n const invalidDependencyWarning = messages.configurationFieldInvalid('dependency', rawName, 'non-empty string');\n this.logger.warn(invalidDependencyWarning);\n continue;\n }\n\n if (!this.dependencies.includes(trimmedName)) {\n this.dependencies.push(trimmedName);\n }\n }\n\n return this;\n }\n\n /**\n * Defines platform-specific configuration overrides.\n *\n * This powerful feature allows for different installation methods, binaries,\n * or versions depending on the operating system and architecture.\n * **This method can be called multiple times** to define overrides for different platforms.\n *\n * @param platforms - The target platform(s), using the {@link @dotfiles/core#Platform} enum.\n * Multiple platforms can be combined with a bitwise OR (e.g., `Platform.MacOS | Platform.Linux`).\n * @param architecturesOrConfigure - Either an {@link @dotfiles/core#Architecture} enum to target\n * specific CPU architectures, or the configuration callback function if architecture is not specified.\n * @param configureCallback - The callback function that receives a new `IToolConfigBuilder`\n * instance to define the platform-specific overrides. This is required if `architecturesOrConfigure`\n * is an architecture.\n * @returns The `IToolConfigBuilder` instance for chaining.\n */\n platform(\n platforms: Platform,\n architecturesOrConfigure:\n | Architecture\n | ((install: IPlatformInstallFunction) => Omit<PlatformConfigBuilderInterface, 'bin'>),\n configureCallback?: (install: IPlatformInstallFunction) => Omit<PlatformConfigBuilderInterface, 'bin'>,\n ): this {\n let targetArchitectures: Architecture | undefined;\n let configureFn: (install: IPlatformInstallFunction) => Omit<PlatformConfigBuilderInterface, 'bin'>;\n\n if (typeof architecturesOrConfigure === 'function') {\n configureFn = architecturesOrConfigure;\n targetArchitectures = undefined; // Applies to all architectures for the given platforms\n } else {\n targetArchitectures = architecturesOrConfigure;\n if (typeof configureCallback !== 'function') {\n const missingCallbackError = messages.configurationFieldRequired(\n 'configure callback',\n `platform() called for tool \"${this.toolName}\" with architectures but without a configure callback`,\n );\n this.logger.error(missingCallbackError);\n throw new Error(missingCallbackError);\n }\n configureFn = configureCallback;\n }\n\n const platformBuilder = new IToolConfigBuilder(this.logger, this.toolName, true);\n platformBuilder.setContext(this.context);\n\n // Create platform install function that works like the main install function\n const platformInstall: IPlatformInstallFunction = ((...args: [InstallMethod, InstallParams] | []) => {\n if (args.length === 0) {\n return platformBuilder as unknown as PlatformConfigBuilderInterface;\n }\n\n const [method, params] = args;\n platformBuilder.install(method, params);\n return platformBuilder as unknown as PlatformConfigBuilderInterface;\n }) as IPlatformInstallFunction;\n\n configureFn(platformInstall);\n const platformConfig = platformBuilder.buildPlatformConfig();\n\n this.platformConfigEntries.push({\n platforms,\n architectures: targetArchitectures,\n config: platformConfig,\n });\n\n return this;\n }\n\n /**\n * Marks the tool as disabled.\n *\n * A disabled tool is skipped during generation with a warning message.\n * This is useful for temporarily disabling a tool without removing its configuration.\n *\n * @returns The `IToolConfigBuilder` instance for chaining.\n */\n disable(): this {\n this.isDisabled = true;\n return this;\n }\n\n /**\n * Restricts the tool to specific hostnames.\n *\n * When set, the tool is only installed on machines where the hostname matches.\n * If the hostname doesn't match, the tool is skipped with a warning message.\n *\n * @param pattern - A literal hostname string or a RegExp for pattern matching.\n * - String: exact match (case-sensitive)\n * - RegExp: pattern match (e.g., `/^work-.*$/` matches any hostname starting with \"work-\")\n * @returns The `IToolConfigBuilder` instance for chaining.\n *\n * @example\n * // Exact hostname match\n * install('github-release', { repo: 'tool/repo' })\n * .hostname('my-laptop')\n *\n * @example\n * // Pattern match using regex\n * install('github-release', { repo: 'tool/repo' })\n * .hostname(/^work-.*$/)\n */\n hostname(pattern: string | RegExp): this {\n if (pattern instanceof RegExp) {\n this.hostnamePattern = pattern.source;\n } else {\n this.hostnamePattern = pattern;\n }\n return this;\n }\n\n /**\n * Configures the behavior for checking for tool updates.\n *\n * **This method should only be called once**; subsequent calls will override the previous value.\n *\n * @param config - A {@link @dotfiles/core#ToolConfigUpdateCheck} object.\n * @returns The `IToolConfigBuilder` instance for chaining.\n */\n updateCheck(config: ToolConfig['updateCheck']): this {\n this.updateCheckConfig = config;\n return this;\n }\n\n /**\n * Finalizes the configuration and returns the complete {@link @dotfiles/core#ToolConfig} object.\n *\n * This method validates the constructed configuration and returns the final, immutable\n * tool configuration object.\n *\n * @returns The built {@link @dotfiles/core#ToolConfig}.\n */\n build(): ToolConfig {\n const baseConfig = this.buildBaseConfig();\n\n if (this.hasInstallationMethod()) {\n return this.buildInstallableToolConfig(baseConfig);\n }\n\n this.validateConfigurationOnly(baseConfig);\n return this.buildConfigurationOnlyTool(baseConfig);\n }\n\n /**\n * Builds the base configuration object containing common fields shared by all tool configs.\n *\n * This includes name, binaries, version, shell configurations, symlinks, update check settings,\n * and platform-specific configurations. Optional fields are only included if they have values.\n *\n * @returns The base configuration object.\n */\n private buildBaseConfig() {\n return {\n name: this.toolName,\n binaries: this.binaries.length > 0\n ? this.binaries.map((b) => (b.pattern === `*/${b.name}` ? b.name : b))\n : undefined,\n version: this.versionNum,\n disabled: this.isDisabled ? true : undefined,\n hostname: this.hostnamePattern,\n shellConfigs: this.buildShellConfigs(),\n symlinks: this.symlinkPairs.length > 0 ? this.symlinkPairs : undefined,\n copies: this.copyPairs.length > 0 ? this.copyPairs : undefined,\n updateCheck: this.updateCheckConfig,\n dependencies: this.dependencies.length > 0 ? [...this.dependencies] : undefined,\n platformConfigs: this.isPlatformScope\n ? undefined\n : this.platformConfigEntries.length > 0\n ? this.platformConfigEntries\n : undefined,\n };\n }\n\n /**\n * Builds a platform-specific configuration object.\n *\n * This creates a configuration object suitable for use within a platform override.\n * It excludes the 'name' and 'platformConfigs' fields to avoid circular references,\n * as platform configs are nested within the main tool config.\n *\n * @returns A platform configuration object with undefined values removed.\n */\n private buildPlatformConfig(): PlatformConfig {\n const config: Record<string, unknown> = {\n binaries: this.binaries.length > 0\n ? this.binaries.map((b) => (b.pattern === `*/${b.name}` ? b.name : b))\n : undefined,\n version: this.versionNum !== 'latest' ? this.versionNum : undefined,\n shellConfigs: this.buildShellConfigs(),\n symlinks: this.symlinkPairs.length > 0 ? this.symlinkPairs : undefined,\n copies: this.copyPairs.length > 0 ? this.copyPairs : undefined,\n updateCheck: this.updateCheckConfig,\n dependencies: this.dependencies.length > 0 ? [...this.dependencies] : undefined,\n };\n\n // Add installation method and params if they exist\n if (this.hasInstallationMethod()) {\n config['installationMethod'] = this.currentInstallationMethod;\n config['installParams'] = this.currentInstallParams;\n }\n\n // Remove undefined values to keep the config clean\n Object.keys(config).forEach((key) => {\n if (config[key] === undefined) {\n delete config[key];\n }\n });\n\n return config as PlatformConfig;\n }\n\n /**\n * Checks if an installation method and parameters have been configured.\n *\n * @returns True if both installation method and params are set, false otherwise.\n */\n private hasInstallationMethod(): boolean {\n return Boolean(this.currentInstallationMethod && this.currentInstallParams);\n }\n\n /**\n * Builds the final tool configuration for tools with an installation method.\n *\n * Takes the base configuration and adds the installation method and parameters,\n * returning a properly typed configuration object.\n * Ensures binaries array is always defined for installable tools.\n *\n * @param baseConfig - The base configuration object.\n * @returns A typed ToolConfig based on the installation method.\n */\n private buildInstallableToolConfig(baseConfig: ReturnType<typeof this.buildBaseConfig>): ToolConfig {\n return {\n ...baseConfig,\n binaries: baseConfig.binaries && baseConfig.binaries.length > 0 ? baseConfig.binaries : [],\n installationMethod: this.currentInstallationMethod,\n installParams: this.currentInstallParams,\n } as ToolConfig;\n }\n\n /**\n * Validates that configuration-only tools (without installation method) have meaningful content.\n *\n * Ensures that tools without an installation method still define at least one of:\n * binaries, shell configurations, symlinks, or platform configurations.\n *\n * @param baseConfig - The base configuration object to validate.\n * @throws Error if the configuration is empty and provides no functionality.\n */\n private validateConfigurationOnly(baseConfig: ReturnType<typeof this.buildBaseConfig>): void {\n const finalBinaries = baseConfig.binaries && baseConfig.binaries.length > 0 ? baseConfig.binaries : [];\n const hasContent = finalBinaries.length > 0 ||\n baseConfig.shellConfigs ||\n baseConfig.symlinks ||\n baseConfig.copies ||\n (baseConfig.platformConfigs && baseConfig.platformConfigs.length > 0);\n\n if (!hasContent) {\n const requiredConfigError = messages.configurationFieldRequired(\n 'tool definition',\n `Tool \"${baseConfig.name}\" must define at least binaries, shell init scripts (zsh/bash/powershell), symlinks, or platformConfigs`,\n );\n this.logger.error(requiredConfigError);\n throw new Error(requiredConfigError);\n }\n }\n\n /**\n * Builds the final configuration for tools without an installation method.\n *\n * These are configuration-only tools that provide shell integration, symlinks, or other\n * functionality without being installed through the system. The installation method\n * is set to 'manual' with empty params.\n *\n * @param baseConfig - The base configuration object.\n * @returns A ManualToolConfig representing the configuration-only tool.\n */\n private buildConfigurationOnlyTool(baseConfig: ReturnType<typeof this.buildBaseConfig>): ToolConfig {\n return {\n ...baseConfig,\n binaries: baseConfig.binaries && baseConfig.binaries.length > 0 ? baseConfig.binaries : [],\n installationMethod: 'manual',\n installParams: {},\n } as unknown as ToolConfig;\n }\n\n /**\n * Creates a new IToolConfigBuilder instance.\n *\n * @param parentLogger - The parent logger from which a sublogger will be created.\n * @param toolName - The name of the tool being configured.\n * @param isPlatformScope - Whether this builder is used for platform-specific configuration.\n * When true, the builder will not include platformConfigs in the output to avoid circular references.\n */\n constructor(\n parentLogger: TsLogger,\n toolName: string,\n isPlatformScope = false,\n ) {\n this.logger = parentLogger.getSubLogger({ name: 'IToolConfigBuilder' });\n this.toolName = toolName;\n this.isPlatformScope = isPlatformScope;\n }\n\n public setContext(context: IToolConfigContext | undefined): void {\n this.context = context;\n }\n\n /**\n * Provides read-only access to the internal shell configurations.\n *\n * This getter is used primarily for testing to verify that shell configurations\n * are being stored correctly before they are transformed into the final format.\n *\n * @returns A read-only view of the internal shell configurations.\n */\n get shellConfigs(): Readonly<InternalShellConfigs> {\n return this.internalShellConfigs;\n }\n\n private configureShell(\n shellType: ShellTypeKey,\n callback: ShellConfiguratorCallback | ShellConfiguratorAsyncCallback,\n ): this | Promise<this> {\n const storage: IShellStorage = this.internalShellConfigs[shellType];\n const configurator = new ShellConfigurator(storage, shellType, this.context, this.logger, this.toolName);\n\n const callbackResult = callback(configurator);\n\n if (this.isPromise(callbackResult)) {\n const next: Promise<this> = callbackResult.then(() => this);\n return next;\n }\n\n return this;\n }\n\n private isPromise(value: unknown): value is Promise<unknown> {\n if (value === null || value === undefined) {\n return false;\n }\n\n if (typeof value !== 'object' && typeof value !== 'function') {\n return false;\n }\n\n const maybePromise = value as PromiseLike<unknown>;\n return typeof maybePromise.then === 'function';\n }\n}\n",
|
|
186
|
+
"/**\n * Runtime implementation of InstallFunction for the install-first API.\n *\n * Creates an InstallFunction that instantiates IToolConfigBuilder instances\n * with the installation method and params already configured.\n */\n\nimport type { IInstallParamsRegistry, InstallFunction, InstallMethod, IToolConfigContext } from '@dotfiles/core';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { IToolConfigBuilder } from './toolConfigBuilder';\n\n/**\n * Creates an InstallFunction bound to a specific logger and tool name.\n *\n * This function creates the runtime implementation of the InstallFunction interface,\n * which uses function overloads to provide type-safe installer method selection.\n * The returned function creates IToolConfigBuilder instances pre-configured with\n * the installation method and parameters.\n *\n * @param logger - Logger instance for the builder.\n * @param toolName - Name of the tool being configured.\n * @param context - Tool configuration context providing path resolution helpers.\n * @returns InstallFunction that creates configured IToolConfigBuilder instances.\n *\n * @example\n * ```typescript\n * const install = createInstallFunction(logger, 'ripgrep');\n * const config = install('github-release', { repo: 'BurntSushi/ripgrep' })\n * .bin('rg')\n * .build();\n * ```\n */\nexport function createInstallFunction(\n logger: TsLogger,\n toolName: string,\n context?: IToolConfigContext,\n): InstallFunction {\n let builderInstance: IToolConfigBuilder | null = null;\n\n const getOrCreateBuilder = (): IToolConfigBuilder => {\n if (!builderInstance) {\n builderInstance = new IToolConfigBuilder(logger, toolName);\n }\n\n builderInstance.setContext(context);\n return builderInstance;\n };\n\n function install(method?: InstallMethod, params?: IInstallParamsRegistry[InstallMethod]): IToolConfigBuilder {\n const builder = getOrCreateBuilder();\n\n if (method) {\n const fallbackParams: Record<string, unknown> = {};\n builder.currentInstallationMethod = method;\n builder.currentInstallParams = params ?? fallbackParams;\n }\n\n return builder;\n }\n\n return install as InstallFunction;\n}\n",
|
|
187
|
+
"import type {\n AsyncConfigureTool,\n AsyncConfigureToolWithReturn,\n IBinaryConfig,\n IOperationFailure,\n IOperationSuccess,\n ISystemInfo,\n ProjectConfig,\n ToolConfig,\n} from '@dotfiles/core';\nimport { createToolConfigContext } from '@dotfiles/core';\nimport type { IResolvedFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { createInstallFunction } from '@dotfiles/tool-config-builder';\nimport path from 'node:path';\nimport { messages } from './log-messages';\n\ninterface IToolConfigModule {\n default?: unknown;\n}\n\nfunction isToolConfig(config: unknown): config is ToolConfig {\n if (typeof config !== 'object' || config === null) {\n return false;\n }\n\n return 'name' in config;\n}\n\nfunction isConfigureToolFunction(\n exportedValue: unknown,\n): exportedValue is AsyncConfigureTool | AsyncConfigureToolWithReturn {\n return typeof exportedValue === 'function';\n}\n\n/**\n * Validates a tool configuration has the required shape.\n *\n * Performs basic structural validation to ensure the config object has a name property.\n * Full schema validation occurs at install time via InstallerPluginRegistry, which has\n * access to all plugin schemas. This approach allows config loading to remain decoupled\n * from the plugin system while still ensuring type safety through:\n * 1. TypeScript compile-time validation via the builder pattern\n * 2. Runtime schema validation at install time via the registry\n *\n * @param config - The configuration object to validate.\n * @returns The validated configuration object, or null if basic validation fails.\n */\nfunction validateToolConfig(config: unknown): ToolConfig | null {\n if (isToolConfig(config)) {\n return config;\n }\n\n return null;\n}\n\n/**\n * Processes a function-based tool configuration export.\n *\n * Handles `.tool.ts` files that export a configuration function. The function is called\n * with an InstallFunction and context (new API), and can either use the builder pattern\n * or return a configuration object directly.\n *\n * @param configureToolFn - The configuration function exported from the `.tool.ts` file.\n * @param logger - Logger instance for operations.\n * @param toolName - Name of the tool being configured.\n * @param filePath - Path to the configuration file (for error reporting).\n * @param projectConfig - Parsed project configuration for creating context.\n * @param systemInfo - System information for context creation.\n * @param fileSystem - File system for context utilities.\n * @returns The validated tool configuration, or null if processing failed.\n */\nasync function processFunctionExport(\n configureToolFn: AsyncConfigureTool | AsyncConfigureToolWithReturn,\n logger: TsLogger,\n toolName: string,\n filePath: string,\n projectConfig: ProjectConfig,\n systemInfo: ISystemInfo,\n fileSystem: IResolvedFileSystem,\n): Promise<ToolConfig | null> {\n const toolDir = path.dirname(filePath);\n const context = createToolConfigContext(projectConfig, systemInfo, toolName, toolDir, fileSystem, logger);\n const install = createInstallFunction(logger, toolName, context);\n const result = await configureToolFn(install, context);\n\n // Check if the function returned a ToolConfig object\n if (result && typeof result === 'object' && 'name' in result) {\n const validatedConfig = validateToolConfig(result);\n return validatedConfig;\n }\n\n // Function didn't return an object, use builder pattern\n // The install function creates and returns a builder, so we can call build() on the result\n if (result && typeof result === 'object' && 'build' in result && typeof result.build === 'function') {\n const builtConfig: unknown = result.build();\n const validatedConfig = validateToolConfig(builtConfig);\n return validatedConfig;\n }\n\n logger.error(messages.configurationParseError(filePath, 'ToolConfig', 'Invalid return from configuration function'));\n return null;\n}\n\n/**\n * Processes a direct object export tool configuration.\n *\n * Handles `.tool.ts` files that directly export a configuration object rather than\n * a function. Validates the object and ensures the name matches the filename.\n *\n * @param exportedObject - The configuration object exported from the `.tool.ts` file.\n * @param logger - Logger instance for operations.\n * @param filePath - Path to the configuration file (for error reporting).\n * @param toolName - Expected tool name (derived from filename).\n * @returns The validated tool configuration, or null if processing failed.\n */\nfunction processDirectExport(\n exportedObject: unknown,\n logger: TsLogger,\n filePath: string,\n toolName: string,\n): ToolConfig | null {\n const validatedConfig = validateToolConfig(exportedObject);\n if (validatedConfig) {\n // Ensure the toolConfig.name matches the filename if it's a direct object export\n if (validatedConfig.name !== toolName) {\n logger.warn(\n messages.configurationFieldInvalid('tool config object name', validatedConfig.name, `filename: ${toolName}`),\n filePath,\n );\n }\n }\n return validatedConfig;\n}\n\n/**\n * Loads a `.tool.ts` file as an ES module and derives a {@link @dotfiles/core#ToolConfig}.\n *\n * Supports both direct object exports and function exports.\n */\nasync function loadToolConfigFromModule(\n logger: TsLogger,\n filePath: string,\n toolName: string,\n projectConfig: ProjectConfig,\n systemInfo: ISystemInfo,\n fileSystem: IResolvedFileSystem,\n): Promise<ToolConfig | null> {\n try {\n const moduleExports: IToolConfigModule = await import(filePath);\n if (!moduleExports.default) {\n logger.error(messages.configurationParseError(filePath, 'ToolConfig', 'no default export'));\n return null;\n }\n\n let toolConfig: ToolConfig | null;\n\n const exportedValue: unknown = moduleExports.default;\n\n if (isConfigureToolFunction(exportedValue)) {\n toolConfig = await processFunctionExport(\n exportedValue,\n logger,\n toolName,\n filePath,\n projectConfig,\n systemInfo,\n fileSystem,\n );\n } else {\n toolConfig = processDirectExport(exportedValue, logger, filePath, toolName);\n }\n\n // Set the config file path after building/loading\n if (toolConfig) {\n toolConfig.configFilePath = filePath;\n }\n\n return toolConfig;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : JSON.stringify(error);\n logger.error(messages.configurationLoadFailed(path.relative(projectConfig.configFileDir, filePath)), errorMessage);\n return null;\n }\n}\n\nfunction validateAndStoreToolConfig(\n logger: TsLogger,\n toolConfig: ToolConfig | null,\n filePath: string,\n toolConfigs: Record<string, ToolConfig>,\n): void {\n if (toolConfig?.name) {\n toolConfigs[toolConfig.name] = toolConfig;\n logger.debug(messages.configurationLoaded(filePath, 1), toolConfig.name);\n } else if (toolConfig) {\n logger.warn(messages.configurationFieldInvalid('tool config', 'missing name', 'valid name property'), filePath);\n } else {\n logger.error(messages.configurationParseError(filePath, 'ToolConfig', 'Could not derive valid configuration'));\n }\n}\n\n/**\n * Recursively scans a directory tree for `.tool.ts` configuration files.\n *\n * Traverses the directory structure starting from the given path, collecting all files\n * that end with `.tool.ts` and extracting the tool name from each filename.\n *\n * @param fs - File system interface for directory operations.\n * @param dirPath - Root directory path to start scanning from.\n * @param logger - Logger instance for debug messages.\n * @returns Array of objects containing file paths and corresponding tool names.\n */\nasync function scanDirectoryForToolFiles(\n fs: IResolvedFileSystem,\n dirPath: string,\n logger: TsLogger,\n): Promise<{ filePath: string; toolName: string; }[]> {\n const results: { filePath: string; toolName: string; }[] = [];\n\n try {\n const entries = await fs.readdir(dirPath);\n\n for (const entry of entries) {\n const entryPath = path.join(dirPath, entry);\n\n try {\n const stat = await fs.stat(entryPath);\n\n if (stat.isDirectory()) {\n // Recursively scan subdirectories\n const subResults = await scanDirectoryForToolFiles(fs, entryPath, logger);\n results.push(...subResults);\n } else if (entry.endsWith('.tool.ts')) {\n // Found a .tool.ts file\n const toolName = path.basename(entry, '.tool.ts');\n results.push({\n filePath: entryPath,\n toolName,\n });\n }\n } catch (error) {\n logger.debug(messages.fsReadFailed(entryPath), error);\n }\n }\n } catch (error) {\n logger.debug(messages.fsReadFailed(dirPath), error);\n }\n\n return results;\n}\n\n/**\n * Loads tool configurations from a directory.\n *\n * Recursively scans the specified directory for `.tool.ts` files, loads and validates\n * each configuration, and returns them as a record. Can optionally filter to load only\n * a specific tool by name.\n *\n * @param parentLogger - Parent logger instance (a sublogger will be created).\n * @param toolConfigsDir - Root directory containing `.tool.ts` configuration files.\n * @param fs - File system interface for reading files and directories.\n * @param projectConfig - Parsed project configuration for context creation.\n * @param systemInfo - System information for context creation.\n * @param toolName - Optional tool name to filter by. If provided, only loads that tool's configuration.\n * @returns A record mapping tool names to their configurations.\n */\nexport async function loadToolConfigs(\n parentLogger: TsLogger,\n toolConfigsDir: string,\n fs: IResolvedFileSystem,\n projectConfig: ProjectConfig,\n systemInfo: ISystemInfo,\n toolName?: string,\n): Promise<Record<string, ToolConfig>> {\n const logger = parentLogger.getSubLogger({ name: 'loadToolConfigs' });\n const toolConfigs: Record<string, ToolConfig> = {};\n\n if (toolName) {\n logger.debug(messages.singleToolConfigLoadingStarted(toolName, toolConfigsDir));\n } else {\n logger.debug(messages.toolConfigLoadingStarted(toolConfigsDir));\n }\n\n try {\n if (!(await fs.exists(toolConfigsDir))) {\n logger.debug(messages.fsItemNotFound('tool configs directory', toolConfigsDir));\n const emptyConfigs: Record<string, ToolConfig> = {};\n return emptyConfigs;\n }\n\n logger.trace(messages.toolConfigDirectoryScan(toolConfigsDir));\n\n // Recursively scan for all .tool.ts files\n const allToolFiles = await scanDirectoryForToolFiles(fs, toolConfigsDir, logger);\n\n // Filter files if a specific tool name is requested\n const filesToProcess = toolName\n ? allToolFiles.filter(({ toolName: foundToolName }) => foundToolName === toolName)\n : allToolFiles;\n\n for (const { filePath, toolName: discoveredToolName } of filesToProcess) {\n const toolConfig = await loadToolConfigFromModule(\n logger,\n filePath,\n discoveredToolName,\n projectConfig,\n systemInfo,\n fs,\n );\n validateAndStoreToolConfig(logger, toolConfig, filePath, toolConfigs);\n }\n } catch (error) {\n logger.error(messages.fsReadFailed(toolConfigsDir), error);\n const emptyConfigs: Record<string, ToolConfig> = {};\n return emptyConfigs;\n }\n\n return toolConfigs;\n}\n\n/**\n * Loads configuration for a single tool by name.\n *\n * Convenience wrapper around {@link loadToolConfigs} that filters for a specific tool\n * and returns just that tool's configuration.\n *\n * @param parentLogger - Parent logger instance (a sublogger will be created).\n * @param toolName - The name of the tool to load configuration for.\n * @param toolConfigsDir - Root directory containing `.tool.ts` configuration files.\n * @param fs - File system interface for reading files and directories.\n * @param projectConfig - Parsed project configuration for context creation.\n * @param systemInfo - System information for context creation.\n * @returns The tool's configuration if found, undefined otherwise.\n */\nexport async function loadSingleToolConfig(\n parentLogger: TsLogger,\n toolName: string,\n toolConfigsDir: string,\n fs: IResolvedFileSystem,\n projectConfig: ProjectConfig,\n systemInfo: ISystemInfo,\n): Promise<ToolConfig | undefined> {\n const configs = await loadToolConfigs(parentLogger, toolConfigsDir, fs, projectConfig, systemInfo, toolName);\n return configs[toolName];\n}\n\n/**\n * Extracts the binary name from a binary entry.\n *\n * Binary entries can be either simple strings or IBinaryConfig objects.\n * This function normalizes both formats to return just the binary name.\n *\n * @param binary - A binary entry that is either a string or an IBinaryConfig object.\n * @returns The binary name.\n */\nfunction getBinaryName(binary: string | IBinaryConfig): string {\n if (typeof binary === 'string') {\n return binary;\n }\n return binary.name;\n}\n\n/**\n * Result of searching for a tool by binary name - success case.\n */\nexport type FindToolByBinarySuccess = IOperationSuccess & {\n toolName: string;\n};\n\n/**\n * Result of searching for a tool by binary name - failure case.\n */\nexport type FindToolByBinaryFailure = IOperationFailure & {\n matchingTools?: string[];\n};\n\n/**\n * Result of searching for a tool by binary name.\n */\nexport type FindToolByBinaryResult = FindToolByBinarySuccess | FindToolByBinaryFailure;\n\n/**\n * Finds a tool that provides a specific binary.\n *\n * Scans all tool configurations in the directory and finds tools that define\n * the given binary name via `.bin()`. If multiple tools define the same binary,\n * returns an error result with the list of matching tools.\n *\n * @param parentLogger - Parent logger instance (a sublogger will be created).\n * @param binaryName - The name of the binary to search for.\n * @param toolConfigsDir - Root directory containing `.tool.ts` configuration files.\n * @param fs - File system interface for reading files and directories.\n * @param projectConfig - Parsed project configuration for context creation.\n * @param systemInfo - System information for context creation.\n * @returns A result object indicating whether the tool was found, not found, or an error occurred.\n */\nexport async function findToolByBinary(\n parentLogger: TsLogger,\n binaryName: string,\n toolConfigsDir: string,\n fs: IResolvedFileSystem,\n projectConfig: ProjectConfig,\n systemInfo: ISystemInfo,\n): Promise<FindToolByBinaryResult> {\n const logger = parentLogger.getSubLogger({ name: 'findToolByBinary', context: binaryName });\n logger.debug(messages.binarySearchStarted(binaryName, toolConfigsDir));\n\n // Load all tool configs to search through them\n const allConfigs = await loadToolConfigs(logger, toolConfigsDir, fs, projectConfig, systemInfo);\n const matchingTools: string[] = [];\n\n for (const [toolName, config] of Object.entries(allConfigs)) {\n if (!config.binaries) {\n continue;\n }\n\n const hasBinary = config.binaries.some((binary) => getBinaryName(binary) === binaryName);\n if (hasBinary) {\n matchingTools.push(toolName);\n }\n }\n\n if (matchingTools.length === 0) {\n logger.debug(messages.binaryNotFound(binaryName));\n const result: FindToolByBinaryFailure = { success: false, error: `No tool provides binary '${binaryName}'` };\n return result;\n }\n\n if (matchingTools.length > 1) {\n logger.debug(messages.multipleBinaryProviders(binaryName, matchingTools));\n const result: FindToolByBinaryFailure = {\n success: false,\n error: `Multiple tools provide the binary '${binaryName}': ${\n matchingTools.join(', ')\n }. Please specify the tool name instead.`,\n matchingTools,\n };\n return result;\n }\n\n const toolName = matchingTools[0];\n if (!toolName) {\n const result: FindToolByBinaryFailure = { success: false, error: `No tool provides binary '${binaryName}'` };\n return result;\n }\n\n logger.debug(messages.binaryFoundInTool(binaryName, toolName));\n const result: FindToolByBinarySuccess = { success: true, toolName };\n return result;\n}\n\n/**\n * Loads a tool configuration by searching for a tool that provides the specified binary.\n *\n * This function first searches for a tool that defines the given binary name,\n * then loads that tool's full configuration.\n *\n * @param parentLogger - Parent logger instance (a sublogger will be created).\n * @param binaryName - The name of the binary to search for.\n * @param toolConfigsDir - Root directory containing `.tool.ts` configuration files.\n * @param fs - File system interface for reading files and directories.\n * @param projectConfig - Parsed project configuration for context creation.\n * @param systemInfo - System information for context creation.\n * @returns The tool configuration if found, undefined if not found, or an error object if multiple tools define the binary.\n */\nexport async function loadToolConfigByBinary(\n parentLogger: TsLogger,\n binaryName: string,\n toolConfigsDir: string,\n fs: IResolvedFileSystem,\n projectConfig: ProjectConfig,\n systemInfo: ISystemInfo,\n): Promise<ToolConfig | undefined | { error: string; }> {\n const logger = parentLogger.getSubLogger({ name: 'loadToolConfigByBinary' });\n const findResult = await findToolByBinary(logger, binaryName, toolConfigsDir, fs, projectConfig, systemInfo);\n\n if (!findResult.success) {\n // Only return error if multiple tools provide the binary (matchingTools present)\n // Otherwise return undefined for \"not found\"\n if (findResult.matchingTools) {\n const errorResult: { error: string; } = { error: findResult.error };\n return errorResult;\n }\n return undefined;\n }\n\n // Load the full configuration for the found tool\n const toolConfig = await loadSingleToolConfig(\n logger,\n findResult.toolName,\n toolConfigsDir,\n fs,\n projectConfig,\n systemInfo,\n );\n return toolConfig;\n}\n",
|
|
188
|
+
"import { createSafeLogMessage, type SafeLogMessageMap } from '@dotfiles/logger';\n\nexport const messages = {\n configurationProcessing: () => createSafeLogMessage('config processing'),\n platformOverrides: (platform: string, arch: string) =>\n createSafeLogMessage(`platform overrides: ${platform} ${arch}`),\n configurationValidationFailed: (errors: string[]) =>\n createSafeLogMessage(`Configuration validation failed:\\n${errors.join('\\n')}`),\n configurationParseError: (configPath: string, format: string, reason: string) =>\n createSafeLogMessage(`Failed to parse ${format} configuration ${configPath}: ${reason}`),\n configurationLoadFailed: (toolPath: string) => createSafeLogMessage(`Failed to load configuration: ${toolPath}`),\n configurationLoaded: (configPath: string, toolCount: number) =>\n createSafeLogMessage(`Configuration loaded from ${configPath} (${toolCount} tools configured)`),\n toolConfigLoadingStarted: (toolConfigsDir: string) => createSafeLogMessage(`tool config loading: ${toolConfigsDir}`),\n singleToolConfigLoadingStarted: (toolName: string, toolConfigsDir: string) =>\n createSafeLogMessage(`single tool config load: ${toolName} in ${toolConfigsDir}`),\n toolConfigDirectoryScan: (toolConfigsDir: string) => createSafeLogMessage(`Directory scan: ${toolConfigsDir}`),\n toolConfigLoadingCompleted: () => createSafeLogMessage('tool config loading completed'),\n configurationFieldInvalid: (field: string, value: string, expected: string) =>\n createSafeLogMessage(`Invalid ${field}: \"${value}\" (expected ${expected})`),\n fsItemNotFound: (itemType: string, path: string) => createSafeLogMessage(`${itemType} not found: ${path}`),\n fsReadFailed: (path: string) => createSafeLogMessage(`Failed to read ${path}`),\n loadingTypeScriptConfiguration: () => createSafeLogMessage('Loading TypeScript configuration'),\n binarySearchStarted: (binaryName: string, toolConfigsDir: string) =>\n createSafeLogMessage(`Searching for tool providing binary '${binaryName}' in ${toolConfigsDir}`),\n binaryNotFound: (binaryName: string) => createSafeLogMessage(`No tool provides binary '${binaryName}'`),\n binaryFoundInTool: (binaryName: string, toolName: string) =>\n createSafeLogMessage(`Binary '${binaryName}' is provided by tool '${toolName}'`),\n multipleBinaryProviders: (binaryName: string, toolNames: string[]) =>\n createSafeLogMessage(`Multiple tools provide binary '${binaryName}': ${toolNames.join(', ')}`),\n} satisfies SafeLogMessageMap;\n",
|
|
189
|
+
"import type { ISystemInfo, ProjectConfig, ToolConfig } from '@dotfiles/core';\nimport type { IResolvedFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport type { IConfigService } from './IConfigService';\nimport {\n loadSingleToolConfig as actualLoadSingleToolConfig,\n loadToolConfigByBinary as actualLoadToolConfigByBinary,\n loadToolConfigs as actualLoadToolConfigs,\n} from './loadToolConfigs';\n\n/**\n * Default implementation of {@link IConfigService} that delegates to the actual config loading functions.\n *\n * This service acts as a simple wrapper around the core configuration loading logic,\n * providing a clean interface for dependency injection in consuming code.\n */\nexport class ConfigService implements IConfigService {\n /**\n * @inheritdoc IConfigService.loadSingleToolConfig\n */\n async loadSingleToolConfig(\n logger: TsLogger,\n toolName: string,\n toolConfigsDir: string,\n fs: IResolvedFileSystem,\n projectConfig: ProjectConfig,\n systemInfo: ISystemInfo,\n ): Promise<ToolConfig | undefined> {\n return actualLoadSingleToolConfig(logger, toolName, toolConfigsDir, fs, projectConfig, systemInfo);\n }\n\n /**\n * @inheritdoc IConfigService.loadToolConfigByBinary\n */\n async loadToolConfigByBinary(\n logger: TsLogger,\n binaryName: string,\n toolConfigsDir: string,\n fs: IResolvedFileSystem,\n projectConfig: ProjectConfig,\n systemInfo: ISystemInfo,\n ): Promise<ToolConfig | undefined | { error: string; }> {\n return actualLoadToolConfigByBinary(logger, binaryName, toolConfigsDir, fs, projectConfig, systemInfo);\n }\n\n /**\n * @inheritdoc IConfigService.loadToolConfigs\n */\n async loadToolConfigs(\n logger: TsLogger,\n toolConfigsDir: string,\n fs: IResolvedFileSystem,\n projectConfig: ProjectConfig,\n systemInfo: ISystemInfo,\n ): Promise<Record<string, ToolConfig>> {\n return actualLoadToolConfigs(logger, toolConfigsDir, fs, projectConfig, systemInfo);\n }\n}\n",
|
|
190
|
+
"import type { ISystemInfo, ProjectConfigPartial } from '@dotfiles/core';\n\n/**\n * Context passed to the configuration factory function.\n */\nexport interface ConfigContext {\n /**\n * The directory containing the configuration file.\n */\n configFileDir: string;\n /**\n * Information about the current system (platform, architecture, etc.).\n */\n systemInfo: ISystemInfo;\n}\n\nexport type ConfigFactory = (ctx: ConfigContext) => Promise<ProjectConfigPartial> | ProjectConfigPartial;\n\n/**\n * Wraps a configuration factory so `.ts` config files stay fully typed.\n *\n * The factory can be synchronous or asynchronous and should return a {@link ProjectConfigPartial}.\n *\n * @param configFn - Configuration factory.\n * @returns The configuration factory function.\n *\n * @example Asynchronous factory\n * ```typescript\n * export default defineConfig(async () => ({\n * github: {\n * token: await fetchToken(),\n * },\n * }));\n * ```\n *\n * @example Synchronous factory\n * ```typescript\n * export default defineConfig(() => ({\n * paths: {\n * dotfilesDir: '~/.dotfiles',\n * targetDir: '~/.local/bin',\n * },\n * }));\n * ```\n */\nexport function defineConfig(configFn: ConfigFactory): ConfigFactory {\n return configFn;\n}\n",
|
|
191
|
+
"import type { ISystemInfo, ProjectConfig, ProjectConfigPartial } from '@dotfiles/core';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { exitCli } from '@dotfiles/utils';\nimport path from 'node:path';\nimport type { ConfigContext } from './defineConfig';\nimport { messages } from './log-messages';\nimport { createProjectConfigFromObject } from './stagedProjectConfigLoader';\n\ntype ModuleWithDefaultExport = {\n default?: unknown;\n};\n\ntype ConfigFactory = (ctx: ConfigContext) => unknown;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction hasDefaultExport(value: unknown): value is ModuleWithDefaultExport {\n if (!isRecord(value)) {\n return false;\n }\n\n return 'default' in value;\n}\n\nfunction isConfigFactory(value: unknown): value is ConfigFactory {\n return typeof value === 'function';\n}\n\nfunction isPromise(value: unknown): value is Promise<unknown> {\n return value instanceof Promise;\n}\n\nfunction toProjectConfigPartial(value: Record<string, unknown>): ProjectConfigPartial {\n const result: ProjectConfigPartial = {};\n Object.assign(result, value);\n return result;\n}\n\n/**\n * Loads and validates configuration from a TypeScript file.\n *\n * Handles `.config.ts` files that export a `defineConfig` function or direct configuration object.\n * The function can be async or sync, providing the same flexibility as tool configurations.\n *\n * @param parentLogger - Parent logger instance (a sublogger will be created).\n * @param fileSystem - File system interface for checking file existence.\n * @param userConfigPath - Path to the user's TypeScript config file.\n * @param systemInfo - System information for platform detection and path expansion.\n * @param env - Environment variables for token substitution.\n * @returns A promise that resolves to the fully validated and processed configuration.\n */\nexport async function loadTsConfig(\n parentLogger: TsLogger,\n fileSystem: IFileSystem,\n userConfigPath: string,\n systemInfo: ISystemInfo,\n env: Record<string, string | undefined>,\n): Promise<ProjectConfig> {\n const logger = parentLogger.getSubLogger({ name: 'loadTsConfig' });\n\n if (!(await fileSystem.exists(userConfigPath))) {\n logger.error(messages.fsItemNotFound('Config file', userConfigPath));\n exitCli(1);\n }\n\n let userConfig: ProjectConfigPartial = {};\n\n try {\n const importedModule: unknown = await import(userConfigPath);\n\n if (!hasDefaultExport(importedModule) || !importedModule.default) {\n logger.error(messages.configurationParseError(userConfigPath, 'TypeScript', 'no default export'));\n exitCli(1);\n }\n\n const configFileDir = path.dirname(userConfigPath);\n const ctx: ConfigContext = { configFileDir, systemInfo };\n\n const defaultExport: unknown = importedModule.default;\n const configValue: unknown = isConfigFactory(defaultExport) ? defaultExport(ctx) : defaultExport;\n\n const resolvedConfigValue: unknown = isPromise(configValue) ? await configValue : configValue;\n\n if (isRecord(resolvedConfigValue)) {\n userConfig = toProjectConfigPartial(resolvedConfigValue);\n } else {\n logger.error(\n messages.configurationParseError(\n userConfigPath,\n 'TypeScript',\n 'default export must be an object, function or Promise',\n ),\n );\n exitCli(1);\n }\n } catch (error) {\n logger.error(\n messages.configurationParseError(\n userConfigPath,\n 'TypeScript',\n error instanceof Error ? error.message : String(error),\n ),\n );\n exitCli(1);\n }\n\n // Use the same processing pipeline as YAML config to ensure consistency\n // This handles merging, platform overrides, token substitution, and validation\n return createProjectConfigFromObject(logger, fileSystem, userConfig, systemInfo, env, { userConfigPath });\n}\n",
|
|
192
|
+
"import {\n Architecture,\n hasArchitecture,\n hasPlatform,\n type ISystemInfo,\n Platform,\n privateProjectConfigFields,\n type ProjectConfig,\n type ProjectConfigPartial,\n projectConfigSchema,\n} from '@dotfiles/core';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { expandHomePath } from '@dotfiles/utils';\nimport path from 'node:path';\nimport { z } from 'zod';\nimport { messages } from './log-messages';\n\ntype EnvMap = Record<string, string | undefined>;\n\ntype RecordUnknown = Record<string, unknown>;\n\ntype StringMap = Record<string, string>;\n\nexport interface ICreateProjectConfigFromObjectOptions {\n userConfigPath: string;\n}\n\nfunction isRecord(value: unknown): value is RecordUnknown {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction toRecord(value: unknown): RecordUnknown {\n if (isRecord(value)) {\n return value;\n }\n\n const result: RecordUnknown = {};\n return result;\n}\n\nfunction deepMerge(target: RecordUnknown, source: RecordUnknown): RecordUnknown {\n const output: RecordUnknown = { ...target };\n\n for (const key of Object.keys(source)) {\n const sourceValue: unknown = source[key];\n if (sourceValue === undefined) {\n continue;\n }\n\n const targetValue: unknown = target[key];\n\n if (isRecord(sourceValue) && isRecord(targetValue)) {\n output[key] = deepMerge(targetValue, sourceValue);\n continue;\n }\n\n output[key] = sourceValue;\n }\n\n return output;\n}\n\nfunction detectOS(platform: Platform): string {\n switch (platform) {\n case Platform.MacOS:\n return 'macos';\n case Platform.Linux:\n return 'linux';\n case Platform.Windows:\n return 'windows';\n default:\n return 'unknown';\n }\n}\n\nfunction detectArch(arch: Architecture): string {\n switch (arch) {\n case Architecture.X86_64:\n return 'x86_64';\n case Architecture.Arm64:\n return 'arm64';\n default:\n return 'unknown';\n }\n}\n\nfunction applyPlatformOverrides(parentLogger: TsLogger, config: RecordUnknown, systemInfo: ISystemInfo): RecordUnknown {\n const logger = parentLogger.getSubLogger({ name: 'applyPlatformOverrides' });\n const platformOverridesValue: unknown = config['platform'];\n\n if (!Array.isArray(platformOverridesValue)) {\n const result: RecordUnknown = { ...config };\n delete result['platform'];\n return result;\n }\n\n const currentPlatform: string = detectOS(systemInfo.platform);\n const currentArch: string = detectArch(systemInfo.arch);\n\n logger.debug(messages.platformOverrides(currentPlatform, currentArch));\n\n const currentPlatformEnum: Platform = (\n {\n macos: Platform.MacOS,\n linux: Platform.Linux,\n windows: Platform.Windows,\n } satisfies Record<string, Platform>\n )[currentPlatform] ?? Platform.None;\n\n const currentArchEnum: Architecture = (\n {\n x86_64: Architecture.X86_64,\n arm64: Architecture.Arm64,\n } satisfies Record<string, Architecture>\n )[currentArch] ?? Architecture.None;\n\n let result: RecordUnknown = deepMerge({}, config);\n\n for (const overrideValue of platformOverridesValue) {\n if (!isRecord(overrideValue)) {\n continue;\n }\n\n const matchValue: unknown = overrideValue['match'];\n const overrideConfigValue: unknown = overrideValue['config'];\n\n if (!Array.isArray(matchValue) || !isRecord(overrideConfigValue)) {\n continue;\n }\n\n const matches: boolean = matchValue.some((match) => {\n if (!isRecord(match)) {\n return false;\n }\n\n const matchOs: unknown = match['os'];\n const matchArch: unknown = match['arch'];\n\n const targetPlatform: Platform = typeof matchOs === 'string'\n ? ((\n {\n macos: Platform.MacOS,\n linux: Platform.Linux,\n windows: Platform.Windows,\n } satisfies Record<string, Platform>\n )[matchOs] ?? Platform.None)\n : Platform.None;\n\n const targetArch: Architecture = typeof matchArch === 'string'\n ? ((\n {\n x86_64: Architecture.X86_64,\n arm64: Architecture.Arm64,\n } satisfies Record<string, Architecture>\n )[matchArch] ?? Architecture.None)\n : Architecture.None;\n\n const osMatches: boolean = typeof matchOs !== 'string' || hasPlatform(targetPlatform, currentPlatformEnum);\n const archMatches: boolean = typeof matchArch !== 'string' || hasArchitecture(targetArch, currentArchEnum);\n\n return osMatches && archMatches;\n });\n\n if (matches) {\n result = deepMerge(result, overrideConfigValue);\n }\n }\n\n delete result['platform'];\n return result;\n}\n\nfunction resolveNestedConfigValue(varName: string, fullConfig: RecordUnknown): string | undefined {\n const parts: string[] = varName.split('.');\n let value: unknown = fullConfig;\n\n for (const part of parts) {\n if (!isRecord(value)) {\n return undefined;\n }\n\n if (!(part in value)) {\n return undefined;\n }\n\n value = value[part];\n }\n\n if (typeof value === 'string') {\n return value;\n }\n\n return undefined;\n}\n\nfunction replaceTokensOnce(configStr: string, env: EnvMap, fullConfig: RecordUnknown): string {\n const tokenRegex: RegExp = /(?<!\\$)\\{([a-zA-Z0-9_.]+)\\}/g;\n\n const result: string = configStr.replace(tokenRegex, (match: string, varName: string) => {\n if (varName.includes('.')) {\n const nestedValue = resolveNestedConfigValue(varName, fullConfig);\n return nestedValue ?? match;\n }\n\n const envValue: string | undefined = env[varName];\n return envValue ?? match;\n });\n\n return result;\n}\n\nfunction extractUnresolvedTokens(value: string): string[] {\n const tokenRegex: RegExp = /(?<!\\$)\\{([a-zA-Z0-9_.]+)\\}/g;\n const result: string[] = [];\n\n for (const match of value.matchAll(tokenRegex)) {\n const tokenName: string | undefined = match[1];\n if (typeof tokenName !== 'string') {\n continue;\n }\n\n result.push(`{${tokenName}}`);\n }\n\n const uniqueTokens: string[] = [...new Set(result)].toSorted((a: string, b: string) => a.localeCompare(b));\n return uniqueTokens;\n}\n\nfunction performFixedPointStringSubstitution(configStr: string, env: EnvMap, fullConfig: RecordUnknown): string {\n const maxIterations: number = 20;\n\n let current: string = configStr;\n const seenStates: Set<string> = new Set([current]);\n\n for (let iteration = 0; iteration < maxIterations; iteration++) {\n const next: string = replaceTokensOnce(current, env, fullConfig);\n if (next === current) {\n return current;\n }\n\n if (seenStates.has(next)) {\n const unresolvedTokens: string[] = extractUnresolvedTokens(next);\n const unresolvedSection: string = unresolvedTokens.length > 0\n ? ` Possible cyclic/unresolved tokens: ${unresolvedTokens.join(', ')}.`\n : '';\n throw new Error(`String token substitution did not converge due to a cycle.${unresolvedSection}`);\n }\n\n seenStates.add(next);\n current = next;\n }\n\n const unresolvedTokens: string[] = extractUnresolvedTokens(current);\n const unresolvedSection: string = unresolvedTokens.length > 0\n ? ` Remaining tokens after ${maxIterations} iterations: ${unresolvedTokens.join(', ')}.`\n : '';\n throw new Error(`String token substitution did not converge after ${maxIterations} iterations.${unresolvedSection}`);\n}\n\nfunction substituteTokensInValue(value: unknown, env: EnvMap, fullConfig: RecordUnknown): unknown {\n if (typeof value === 'string') {\n const result: string = performFixedPointStringSubstitution(value, env, fullConfig);\n return result;\n }\n\n if (Array.isArray(value)) {\n const result: unknown[] = value.map((item) => substituteTokensInValue(item, env, fullConfig));\n return result;\n }\n\n if (isRecord(value)) {\n const result: RecordUnknown = {};\n for (const [key, child] of Object.entries(value)) {\n result[key] = substituteTokensInValue(child, env, fullConfig);\n }\n return result;\n }\n\n return value;\n}\n\nfunction performFixedPointObjectSubstitution(config: RecordUnknown, env: EnvMap): RecordUnknown {\n const maxIterations: number = 20;\n\n let current: RecordUnknown = config;\n let previousSerialized: string = '';\n\n for (let i = 0; i < maxIterations; i++) {\n const currentSerialized: string = JSON.stringify(current);\n if (currentSerialized === previousSerialized) {\n break;\n }\n\n previousSerialized = currentSerialized;\n\n const substituted: unknown = substituteTokensInValue(current, env, current);\n const substitutedRecord: RecordUnknown = toRecord(substituted);\n current = substitutedRecord;\n }\n\n return current;\n}\n\nfunction expandTildeInPathsSubtree(config: RecordUnknown, configuredHomeDir: string): RecordUnknown {\n const pathsValue: unknown = config['paths'];\n const pathsRecord: RecordUnknown = toRecord(pathsValue);\n\n const expandedPaths: RecordUnknown = {};\n for (const [key, value] of Object.entries(pathsRecord)) {\n if (typeof value === 'string') {\n expandedPaths[key] = expandHomePath(configuredHomeDir, value);\n continue;\n }\n\n expandedPaths[key] = value;\n }\n\n const result: RecordUnknown = { ...config, paths: expandedPaths };\n return result;\n}\n\nfunction assertNoTildeInPaths(config: RecordUnknown): void {\n const pathsValue: unknown = config['paths'];\n const pathsRecord: RecordUnknown = toRecord(pathsValue);\n\n for (const value of Object.values(pathsRecord)) {\n if (typeof value !== 'string') {\n continue;\n }\n\n if (value.startsWith('~')) {\n throw new Error('Configuration contains unsupported tilde path in paths.*');\n }\n }\n}\n\nfunction resolveConfiguredHomeDir(config: RecordUnknown, bootstrapEnv: EnvMap, bootstrapHomeDir: string): string {\n const pathsValue: unknown = config['paths'];\n const pathsRecord: RecordUnknown = toRecord(pathsValue);\n\n const rawHomeDirValue: unknown = pathsRecord['homeDir'];\n const rawHomeDir: string = typeof rawHomeDirValue === 'string' ? rawHomeDirValue : '{HOME}';\n\n const substitutedHomeDir: string = performFixedPointStringSubstitution(rawHomeDir, bootstrapEnv, config);\n const expandedHomeDir: string = expandHomePath(bootstrapHomeDir, substitutedHomeDir);\n\n return expandedHomeDir;\n}\n\nfunction createStringEnv(env: EnvMap, extra: StringMap): EnvMap {\n const result: EnvMap = { ...env };\n\n for (const [key, value] of Object.entries(extra)) {\n result[key] = value;\n }\n\n return result;\n}\n\nfunction processConfig(\n parentLogger: TsLogger,\n userConfigPath: string,\n defaultConfig: RecordUnknown,\n userConfig: RecordUnknown,\n systemInfo: ISystemInfo,\n env: EnvMap,\n): ProjectConfig {\n const logger = parentLogger.getSubLogger({ name: 'processConfig' });\n logger.debug(messages.configurationProcessing(), userConfigPath);\n\n const mergedConfig: RecordUnknown = deepMerge(defaultConfig, userConfig);\n const configWithPlatformOverrides: RecordUnknown = applyPlatformOverrides(parentLogger, mergedConfig, systemInfo);\n\n const configFileDir: string = path.dirname(userConfigPath);\n\n const injectedConfig: RecordUnknown = {\n ...configWithPlatformOverrides,\n configFilePath: userConfigPath,\n configFileDir,\n };\n\n const bootstrapEnv: EnvMap = createStringEnv(env, { HOME: systemInfo.homeDir, configFileDir });\n\n const configuredHomeDir: string = resolveConfiguredHomeDir(injectedConfig, bootstrapEnv, systemInfo.homeDir);\n\n const injectedPaths: RecordUnknown = { ...toRecord(injectedConfig['paths']), homeDir: configuredHomeDir };\n const configWithResolvedHome: RecordUnknown = { ...injectedConfig, paths: injectedPaths };\n\n const finalEnv: EnvMap = createStringEnv(env, { HOME: configuredHomeDir, configFileDir });\n\n const tokenSubstituted: RecordUnknown = performFixedPointObjectSubstitution(configWithResolvedHome, finalEnv);\n const tildeExpanded: RecordUnknown = expandTildeInPathsSubtree(tokenSubstituted, configuredHomeDir);\n\n assertNoTildeInPaths(tildeExpanded);\n\n const result = projectConfigSchema.extend(privateProjectConfigFields.shape).safeParse(tildeExpanded);\n\n if (!result.success) {\n const pretty: string = z.prettifyError(result.error);\n logger.error(messages.configurationValidationFailed([pretty]));\n throw new Error(`Project configuration is invalid.\\n${pretty}`);\n }\n\n return result.data;\n}\n\nexport async function loadDefaultProjectConfigAsRecord(fileSystem: IFileSystem): Promise<RecordUnknown> {\n // kept for API parity; fileSystem currently unused\n void fileSystem;\n\n const result: RecordUnknown = projectConfigSchema.parse({});\n return result;\n}\n\nexport async function getDefaultConfig(\n parentLogger: TsLogger,\n fileSystem: IFileSystem,\n systemInfo: ISystemInfo,\n env: EnvMap,\n userConfigPath: string,\n): Promise<ProjectConfig> {\n const defaultConfig: RecordUnknown = await loadDefaultProjectConfigAsRecord(fileSystem);\n const result: ProjectConfig = processConfig(\n parentLogger,\n userConfigPath,\n defaultConfig,\n {},\n systemInfo,\n env,\n );\n return result;\n}\n\nexport async function createProjectConfigFromObject(\n parentLogger: TsLogger,\n fileSystem: IFileSystem,\n userConfig: ProjectConfigPartial = {},\n systemInfo: ISystemInfo = {\n platform: Platform.MacOS,\n arch: Architecture.Arm64,\n homeDir: '/Users/testuser',\n hostname: 'test-host',\n },\n env: EnvMap = {},\n options: ICreateProjectConfigFromObjectOptions,\n): Promise<ProjectConfig> {\n const resolvedUserConfigPath: string = options.userConfigPath;\n const defaultConfig: RecordUnknown = await loadDefaultProjectConfigAsRecord(fileSystem);\n\n const userConfigRecord: RecordUnknown = toRecord(userConfig);\n const result: ProjectConfig = processConfig(\n parentLogger,\n resolvedUserConfigPath,\n defaultConfig,\n userConfigRecord,\n systemInfo,\n env,\n );\n return result;\n}\n",
|
|
193
|
+
"import type { ISystemInfo, ProjectConfig } from '@dotfiles/core';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { messages } from './log-messages';\nimport { loadTsConfig } from './tsConfigLoader';\n\n/**\n * Loads configuration from a TypeScript file.\n *\n * Configuration files must have a `.ts` extension and export a default configuration\n * object or function.\n *\n * @param parentLogger - Parent logger instance (a sublogger will be created).\n * @param fileSystem - File system interface for reading configuration files.\n * @param userConfigPath - Path to the user's TypeScript configuration file (`.ts`).\n * @param systemInfo - System information for platform detection and path expansion.\n * @param env - Environment variables for token substitution.\n * @returns A promise that resolves to the fully validated and processed configuration.\n *\n * @example\n * ```typescript\n * const config = await loadConfig(logger, fs, './dotfiles.config.ts', systemInfo, env);\n * ```\n */\nexport async function loadConfig(\n parentLogger: TsLogger,\n fileSystem: IFileSystem,\n userConfigPath: string,\n systemInfo: ISystemInfo,\n env: Record<string, string | undefined>,\n): Promise<ProjectConfig> {\n const logger = parentLogger.getSubLogger({ name: 'loadConfig' });\n\n if (userConfigPath.endsWith('.ts')) {\n logger.debug(messages.loadingTypeScriptConfiguration());\n return loadTsConfig(logger, fileSystem, userConfigPath, systemInfo, env);\n }\n\n throw new Error(`Unsupported configuration file type: ${userConfigPath}. Configuration must use .ts extension.`);\n}\n",
|
|
194
|
+
"/**\n * Default cache TTL for README content (7 days in milliseconds)\n */\nexport const DEFAULT_README_CACHE_TTL: number = 7 * 24 * 60 * 60 * 1000;\n\n/**\n * Base URL for raw GitHub content\n */\nexport const GITHUB_RAW_BASE_URL: string = 'https://raw.githubusercontent.com';\n\n/**\n * README filename to fetch\n */\nexport const README_FILENAME: string = 'README.md';\n",
|
|
195
|
+
"import { createSafeLogMessage } from '@dotfiles/logger';\n\nexport const messages = {\n fetchingReadme: (owner: string, repo: string, version: string) =>\n createSafeLogMessage(`Fetching README for ${owner}/${repo}@${version}`),\n\n readmeFetched: (owner: string, repo: string, version: string, contentLength: number) =>\n createSafeLogMessage(`README fetched for ${owner}/${repo}@${version} (${contentLength} characters)`),\n\n readmeNotFound: (owner: string, repo: string, version: string, url: string) =>\n createSafeLogMessage(`README not found for ${owner}/${repo}@${version} at ${url}`),\n\n readmeCacheHit: (owner: string, repo: string, version: string) =>\n createSafeLogMessage(`README cache hit for ${owner}/${repo}@${version}`),\n\n readmeCacheMiss: (owner: string, repo: string, version: string) =>\n createSafeLogMessage(`README cache miss for ${owner}/${repo}@${version}`),\n\n readmeCached: (owner: string, repo: string, version: string, ttl: number) =>\n createSafeLogMessage(`README cached for ${owner}/${repo}@${version} with TTL ${ttl}ms`),\n\n generatingCombinedReadme: (toolCount: number) =>\n createSafeLogMessage(`Generating combined README for ${toolCount} tools`),\n\n combinedReadmeGenerated: (toolCount: number, contentLength: number) =>\n createSafeLogMessage(`Combined README generated for ${toolCount} tools (${contentLength} characters)`),\n\n fetchingInstalledTools: () => createSafeLogMessage('Fetching installed GitHub tools from registry'),\n\n installedToolsFound: (count: number) => createSafeLogMessage(`Found ${count} GitHub tools in registry`),\n\n githubToolsExtracted: (count: number) => createSafeLogMessage(`Extracted ${count} GitHub tools from configurations`),\n\n clearingExpiredCache: () => createSafeLogMessage('Clearing expired README cache entries'),\n\n cacheCleared: (count: number) => createSafeLogMessage(`Cleared ${count} expired README cache entries`),\n\n fetchError: (owner: string, repo: string, version: string, error: string) =>\n createSafeLogMessage(`Error fetching README for ${owner}/${repo}@${version}: ${error}`),\n\n cacheError: (operation: string, cacheKey: string, error: string) =>\n createSafeLogMessage(`Cache ${operation} error for ${cacheKey}: ${error}`),\n\n urlConstruction: (url: string) => createSafeLogMessage(`Constructed README URL: ${url}`),\n\n serviceInitialized: () => createSafeLogMessage('README service initialized'),\n\n serviceDestroyed: () => createSafeLogMessage('README service destroyed'),\n\n writingReadmeToPath: (toolName: string, version: string, filePath: string) =>\n createSafeLogMessage(`Writing README for ${toolName}@${version} to ${filePath}`),\n\n readmeWritten: (toolName: string, version: string, filePath: string, contentLength: number) =>\n createSafeLogMessage(`README written for ${toolName}@${version} to ${filePath} (${contentLength} characters)`),\n\n readmeWriteError: (toolName: string, version: string, filePath: string, error: string) =>\n createSafeLogMessage(`Error writing README for ${toolName}@${version} to ${filePath}: ${error}`),\n\n readmeNotAvailableForWrite: (toolName: string, version: string) =>\n createSafeLogMessage(`README not available for ${toolName}@${version}, skipping write`),\n\n catalogGeneration: {\n started: (catalogPath: string) => createSafeLogMessage(`Starting catalog generation at ${catalogPath}`),\n\n completed: (catalogPath: string, contentLength: number) =>\n createSafeLogMessage(`Catalog generated at ${catalogPath} (${contentLength} characters)`),\n\n failed: (catalogPath: string, error: string) =>\n createSafeLogMessage(`Failed to generate catalog at ${catalogPath}: ${error}`),\n\n noGitHubTools: () =>\n createSafeLogMessage(\n 'No GitHub tools installed. Run the generate command to install tools before generating a catalog.',\n ),\n },\n};\n",
|
|
196
|
+
"import type { ICache } from '@dotfiles/downloader';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { DEFAULT_README_CACHE_TTL } from './constants';\nimport type { IReadmeCache } from './IReadmeService';\nimport { messages } from './log-messages';\nimport type { IReadmeContent } from './types';\n\n/**\n * Cache implementation specifically for README content.\n *\n * This class wraps a generic cache interface to provide README-specific caching\n * with TTL (time-to-live) support. It handles cache key generation, expiration,\n * and error handling for README fetch operations.\n */\nexport class ReadmeCache implements IReadmeCache {\n private readonly logger: TsLogger;\n private readonly cache: ICache;\n private readonly defaultTtl: number;\n\n /**\n * Creates a new ReadmeCache instance.\n *\n * @param parentLogger - The parent logger for creating sub-loggers.\n * @param cache - The underlying cache implementation.\n * @param defaultTtl - Default time-to-live in milliseconds for cached READMEs.\n */\n constructor(parentLogger: TsLogger, cache: ICache, defaultTtl: number = DEFAULT_README_CACHE_TTL) {\n this.logger = parentLogger.getSubLogger({ name: 'ReadmeCache' });\n this.cache = cache;\n this.defaultTtl = defaultTtl;\n }\n\n /**\n * @inheritdoc IReadmeCache.get\n */\n async get(cacheKey: string): Promise<IReadmeContent | null> {\n try {\n const content: IReadmeContent | null = await this.cache.get<IReadmeContent>(cacheKey);\n if (content) {\n this.logger.debug(messages.readmeCacheHit(content.owner, content.repo, content.version));\n }\n return content;\n } catch (error) {\n this.logger.error(messages.cacheError('get', cacheKey, error instanceof Error ? error.message : String(error)));\n return null;\n }\n }\n\n /**\n * @inheritdoc IReadmeCache.set\n */\n async set(cacheKey: string, content: IReadmeContent, ttlMs: number = this.defaultTtl): Promise<void> {\n try {\n await this.cache.set(cacheKey, content, ttlMs);\n this.logger.debug(messages.readmeCached(content.owner, content.repo, content.version, ttlMs));\n } catch (error) {\n this.logger.error(messages.cacheError('set', cacheKey, error instanceof Error ? error.message : String(error)));\n }\n }\n\n /**\n * @inheritdoc IReadmeCache.has\n */\n async has(cacheKey: string): Promise<boolean> {\n try {\n return await this.cache.has(cacheKey);\n } catch (error) {\n this.logger.error(messages.cacheError('has', cacheKey, error instanceof Error ? error.message : String(error)));\n return false;\n }\n }\n\n /**\n * @inheritdoc IReadmeCache.delete\n */\n async delete(cacheKey: string): Promise<void> {\n try {\n await this.cache.delete(cacheKey);\n } catch (error) {\n this.logger.error(\n messages.cacheError('delete', cacheKey, error instanceof Error ? error.message : String(error)),\n );\n }\n }\n\n /**\n * @inheritdoc IReadmeCache.clearExpired\n */\n async clearExpired(): Promise<void> {\n try {\n this.logger.debug(messages.clearingExpiredCache());\n await this.cache.clearExpired();\n this.logger.debug(messages.cacheCleared(0)); // Cache doesn't return count\n } catch (error) {\n this.logger.error(\n messages.cacheError('clearExpired', 'all', error instanceof Error ? error.message : String(error)),\n );\n }\n }\n\n /**\n * @inheritdoc IReadmeCache.generateCacheKey\n */\n generateCacheKey(owner: string, repo: string, version: string): string {\n return `readme:${owner}/${repo}:${version}`;\n }\n}\n",
|
|
197
|
+
"import type { InstallerPluginRegistry, ToolConfig } from '@dotfiles/core';\nimport type { IDownloader } from '@dotfiles/downloader';\nimport { FileCache } from '@dotfiles/downloader';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport type { IToolInstallationRecord, IToolInstallationRegistry } from '@dotfiles/registry';\nimport type { TrackedFileSystem } from '@dotfiles/registry/file';\nimport path from 'node:path';\nimport { DEFAULT_README_CACHE_TTL, GITHUB_RAW_BASE_URL, README_FILENAME } from './constants';\nimport type { IReadmeService } from './IReadmeService';\nimport { messages } from './log-messages';\nimport { ReadmeCache } from './ReadmeCache';\nimport type { ICombinedReadmeOptions, IReadmeContent } from './types';\n\n/**\n * Service for fetching and managing README files from GitHub repositories\n */\nexport class ReadmeService implements IReadmeService {\n private readonly logger: TsLogger;\n private readonly downloader: IDownloader;\n private readonly registry: IToolInstallationRegistry;\n private readonly fileSystem: IFileSystem;\n private readonly catalogFileSystem: TrackedFileSystem;\n private readonly readmeCache: ReadmeCache;\n private readonly pluginRegistry: InstallerPluginRegistry;\n\n constructor(\n parentLogger: TsLogger,\n downloader: IDownloader,\n registry: IToolInstallationRegistry,\n fileSystem: IFileSystem,\n catalogFileSystem: TrackedFileSystem,\n cacheDir: string,\n pluginRegistry: InstallerPluginRegistry,\n ) {\n this.logger = parentLogger.getSubLogger({ name: 'ReadmeService' });\n this.downloader = downloader;\n this.registry = registry;\n this.fileSystem = fileSystem;\n this.catalogFileSystem = catalogFileSystem;\n this.pluginRegistry = pluginRegistry;\n\n // Create dedicated cache for README content\n const cache = new FileCache(this.logger, fileSystem, {\n enabled: true,\n defaultTtl: DEFAULT_README_CACHE_TTL,\n cacheDir,\n storageStrategy: 'json',\n });\n\n this.readmeCache = new ReadmeCache(this.logger, cache);\n\n this.logger.debug(messages.serviceInitialized());\n }\n\n async fetchReadmeForVersion(\n owner: string,\n repo: string,\n version: string,\n toolName: string,\n ): Promise<IReadmeContent | null> {\n const cacheKey: string = this.readmeCache.generateCacheKey(owner, repo, version);\n\n // Check cache first\n const cached: IReadmeContent | null = await this.readmeCache.get(cacheKey);\n if (cached) {\n return cached;\n }\n\n this.logger.debug(messages.readmeCacheMiss(owner, repo, version));\n\n // Construct raw GitHub URL\n const url: string = this.constructReadmeUrl(owner, repo, version);\n this.logger.debug(messages.urlConstruction(url));\n\n try {\n this.logger.debug(messages.fetchingReadme(owner, repo, version));\n\n // Download README content\n const response: Buffer | undefined = await this.downloader.download(this.logger, url);\n\n if (!response) {\n this.logger.debug(messages.readmeNotFound(owner, repo, version, url));\n return null;\n }\n\n const content: string = response.toString('utf-8');\n const readmeContent: IReadmeContent = {\n content,\n toolName,\n owner,\n repo,\n version,\n sourceUrl: url,\n fetchedAt: Date.now(),\n };\n\n this.logger.debug(messages.readmeFetched(owner, repo, version, content.length));\n\n // Cache the result\n await this.readmeCache.set(cacheKey, readmeContent, DEFAULT_README_CACHE_TTL);\n\n return readmeContent;\n } catch (error) {\n this.logger.error(messages.fetchError(owner, repo, version, 'Download failed'), error);\n return null;\n }\n }\n\n async getCachedReadme(owner: string, repo: string, version: string): Promise<IReadmeContent | null> {\n const cacheKey: string = this.readmeCache.generateCacheKey(owner, repo, version);\n return await this.readmeCache.get(cacheKey);\n }\n\n async generateCombinedReadme(options: ICombinedReadmeOptions = {}): Promise<string> {\n const tools: IToolInstallationRecord[] = await this.getGitHubTools();\n\n this.logger.debug(messages.generatingCombinedReadme(tools.length));\n\n // Set default options\n const combinedOptions: Required<ICombinedReadmeOptions> = {\n title: options.title || 'Installed Tools',\n includeTableOfContents: options.includeTableOfContents ?? true,\n includeVersions: options.includeVersions ?? true,\n };\n\n const sections: string[] = [];\n\n // Add title\n sections.push(`# ${combinedOptions.title}\\n`);\n\n if (tools.length === 0) {\n sections.push('No GitHub tools are currently installed.\\n');\n return sections.join('\\n');\n }\n\n // Add table of contents if requested\n if (combinedOptions.includeTableOfContents) {\n this.addTableOfContents(sections, tools, combinedOptions.includeVersions);\n }\n\n // Fetch and add tool READMEs\n await this.addToolSections(sections, tools, combinedOptions);\n\n const result: string = sections.join('\\n');\n this.logger.debug(messages.combinedReadmeGenerated(tools.length, result.length));\n\n return result;\n }\n\n private addTableOfContents(sections: string[], tools: IToolInstallationRecord[], includeVersions: boolean): void {\n sections.push('## Table of Contents\\n');\n for (const tool of tools) {\n const versionSuffix: string = includeVersions ? ` (${tool.version})` : '';\n sections.push(`- [${tool.toolName}${versionSuffix}](#${tool.toolName.toLowerCase().replace(/[^a-z0-9]/g, '-')})`);\n }\n sections.push('');\n }\n\n private async addToolSections(\n sections: string[],\n tools: IToolInstallationRecord[],\n options: Required<ICombinedReadmeOptions>,\n ): Promise<void> {\n for (const tool of tools) {\n // Extract owner/repo from download URL\n const githubMatch = tool.downloadUrl?.match(/github\\.com\\/([^/]+)\\/([^/]+)/);\n if (!githubMatch) continue;\n\n const [, owner, repo] = githubMatch;\n if (!owner || !repo) continue;\n\n // Use originalTag if available (for GitHub URLs), otherwise use configuredVersion or installed version\n // If version is \"latest\", resolve to \"main\" for raw GitHub URLs\n const version = tool.originalTag || tool.configuredVersion || tool.version;\n const resolvedVersion = version === 'latest' ? 'main' : version;\n\n const readme: IReadmeContent | null = await this.fetchReadmeForVersion(\n owner,\n repo,\n resolvedVersion,\n tool.toolName,\n );\n\n const versionSuffix: string = options.includeVersions ? ` (${tool.version})` : '';\n sections.push(`## ${tool.toolName}${versionSuffix}\\n`);\n\n if (readme) {\n this.addToolWithReadme(sections, tool, readme);\n } else {\n this.addToolWithoutReadme(sections, owner, repo);\n }\n }\n }\n\n private addToolWithReadme(sections: string[], tool: IToolInstallationRecord, readme: IReadmeContent): void {\n sections.push(readme.content);\n\n // Extract owner/repo from download URL\n const githubMatch = tool.downloadUrl?.match(/github\\.com\\/([^/]+)\\/([^/]+)/);\n if (githubMatch) {\n const [, owner, repo] = githubMatch;\n sections.push(`\\n**Source:** [${owner}/${repo}](https://github.com/${owner}/${repo})\\n`);\n }\n }\n\n private addToolWithoutReadme(sections: string[], owner: string, repo: string): void {\n sections.push(`**Repository:** [${owner}/${repo}](https://github.com/${owner}/${repo})\\n`);\n sections.push('*README not available*\\n');\n }\n\n async getGitHubTools(): Promise<IToolInstallationRecord[]> {\n this.logger.debug(messages.fetchingInstalledTools());\n\n try {\n const installations: IToolInstallationRecord[] = await this.registry.getAllToolInstallations();\n\n const githubTools: IToolInstallationRecord[] = installations.filter((installation) => {\n // For now, we'll use a simple heuristic to identify GitHub tools\n // This could be enhanced with better metadata tracking\n return installation.downloadUrl?.includes('github.com') || installation.downloadUrl?.includes('api.github.com');\n });\n\n this.logger.debug(messages.installedToolsFound(githubTools.length));\n return githubTools;\n } catch (error) {\n this.logger.error(messages.fetchError('registry', 'tools', 'unknown', 'Failed to get installed tools'), error);\n return [];\n }\n }\n\n async clearExpiredCache(): Promise<void> {\n await this.readmeCache.clearExpired();\n }\n\n async writeReadmeToPath(\n destPath: string,\n toolName: string,\n version: string,\n owner: string,\n repo: string,\n ): Promise<string | null> {\n try {\n // Fetch or get cached README content\n const readme: IReadmeContent | null = await this.fetchReadmeForVersion(owner, repo, version, toolName);\n\n if (!readme) {\n this.logger.debug(messages.readmeNotAvailableForWrite(toolName, version));\n return null;\n }\n\n // Create file path: destPath/toolName/version/README.md\n const toolDir: string = `${destPath}/${toolName}`;\n const versionDir: string = `${toolDir}/${version}`;\n const filePath: string = `${versionDir}/README.md`;\n\n this.logger.debug(messages.writingReadmeToPath(toolName, version, filePath));\n\n // Ensure directories exist\n await this.fileSystem.ensureDir(versionDir);\n\n // Write README content to file\n await this.fileSystem.writeFile(filePath, readme.content);\n\n this.logger.debug(messages.readmeWritten(toolName, version, filePath, readme.content.length));\n\n return filePath;\n } catch (error) {\n const filePath: string = `${destPath}/${toolName}/${version}/README.md`;\n this.logger.error(messages.readmeWriteError(toolName, version, filePath, 'Write operation failed'), error);\n return null;\n }\n }\n\n private constructReadmeUrl(owner: string, repo: string, version: string): string {\n return `${GITHUB_RAW_BASE_URL}/${owner}/${repo}/${version}/${README_FILENAME}`;\n }\n\n async generateCatalogFromConfigs(\n catalogPath: string,\n toolConfigs: Record<string, ToolConfig>,\n options: ICombinedReadmeOptions = {},\n ): Promise<string | null> {\n try {\n this.logger.debug(messages.catalogGeneration.started(catalogPath));\n\n // Check if there are any installed GitHub tools\n const installedTools: IToolInstallationRecord[] = await this.getGitHubTools();\n\n if (installedTools.length === 0) {\n this.logger.warn(messages.catalogGeneration.noGitHubTools());\n return null;\n }\n\n // Generate catalog content directly from configs\n const catalogContent: string = await this.generateCatalogContentFromConfigs(toolConfigs, options);\n\n // Ensure the parent directory exists before writing\n const parentDir: string = path.dirname(catalogPath);\n await this.fileSystem.ensureDir(parentDir);\n\n // Write the catalog to the specified path\n await this.catalogFileSystem.writeFile(catalogPath, catalogContent);\n\n this.logger.debug(messages.catalogGeneration.completed(catalogPath, catalogContent.length));\n return catalogPath;\n } catch (error) {\n this.logger.error(messages.catalogGeneration.failed(catalogPath, 'Catalog generation failed'), error);\n return null;\n }\n }\n\n private async generateCatalogContentFromConfigs(\n toolConfigs: Record<string, ToolConfig>,\n options: ICombinedReadmeOptions = {},\n ): Promise<string> {\n const combinedOptions: Required<ICombinedReadmeOptions> = {\n title: options.title || 'Tool Catalog',\n includeTableOfContents: options.includeTableOfContents ?? true,\n includeVersions: options.includeVersions ?? true,\n };\n\n const sections: string[] = [];\n sections.push(`# ${combinedOptions.title}\\n`);\n\n const githubConfigs: [string, ToolConfig][] = this.filterGitHubConfigs(toolConfigs);\n\n this.logger.debug(messages.githubToolsExtracted(githubConfigs.length));\n\n if (combinedOptions.includeTableOfContents) {\n this.addCatalogTableOfContents(sections, githubConfigs, combinedOptions.includeVersions);\n }\n\n await this.addCatalogToolSections(sections, githubConfigs, combinedOptions);\n\n return sections.join('\\n');\n }\n\n private filterGitHubConfigs(toolConfigs: Record<string, ToolConfig>): [string, ToolConfig][] {\n return Object.entries(toolConfigs).filter(([, config]) => {\n const plugin = this.pluginRegistry.get(config.installationMethod);\n return plugin?.supportsReadme?.() === true;\n });\n }\n\n private addCatalogTableOfContents(\n sections: string[],\n githubConfigs: [string, ToolConfig][],\n includeVersions: boolean,\n ): void {\n sections.push('## Table of Contents\\n');\n for (const [toolName, config] of githubConfigs) {\n const versionSuffix: string = includeVersions ? ` (${config.version || 'main'})` : '';\n sections.push(`- [${toolName}${versionSuffix}](#${toolName.toLowerCase().replace(/[^a-z0-9]/g, '-')})`);\n }\n sections.push('');\n }\n\n private async addCatalogToolSections(\n sections: string[],\n githubConfigs: [string, ToolConfig][],\n options: Required<ICombinedReadmeOptions>,\n ): Promise<void> {\n for (const [toolName, config] of githubConfigs) {\n if (config.installationMethod !== 'github-release') continue;\n\n const githubParams = config.installParams;\n const repo = githubParams.repo;\n const [owner, repoName] = repo.split('/');\n if (!owner || !repoName) continue;\n\n const version = config.version || 'main';\n const resolvedVersion = version === 'latest' ? 'main' : version;\n\n const readme: IReadmeContent | null = await this.fetchReadmeForVersion(\n owner,\n repoName,\n resolvedVersion,\n toolName,\n );\n\n const versionSuffix: string = options.includeVersions ? ` (${version})` : '';\n sections.push(`## ${toolName}${versionSuffix}\\n`);\n\n if (readme) {\n this.addCatalogToolWithReadme(sections, readme, repo);\n } else {\n this.addCatalogToolWithoutReadme(sections, repo);\n }\n }\n }\n\n private addCatalogToolWithReadme(sections: string[], readme: IReadmeContent, repo: string): void {\n sections.push(readme.content);\n sections.push(`\\n**Source:** [${repo}](https://github.com/${repo})\\n`);\n }\n\n private addCatalogToolWithoutReadme(sections: string[], repo: string): void {\n sections.push(`**Repository:** [${repo}](https://github.com/${repo})\\n`);\n sections.push('*README not available*\\n');\n }\n}\n",
|
|
198
|
+
"import type { ProjectConfig } from '@dotfiles/config';\nimport type {\n BaseInstallParams,\n ICompletionContext,\n ISystemInfo,\n ShellCompletionConfig,\n ShellCompletionConfigInput,\n ShellCompletionConfigValue,\n ShellType,\n ToolConfig,\n} from '@dotfiles/core';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport type { IFileRegistry, IFileState, TrackedFileSystem } from '@dotfiles/registry/file';\nimport type {\n ICompletionGenerationContext,\n ICompletionGenerator,\n IGenerateShellInitOptions,\n IShellInitGenerator,\n PluginShellInitMap,\n} from '@dotfiles/shell-init-generator';\nimport type { IGenerateShimsOptions, IShimGenerator } from '@dotfiles/shim-generator';\nimport type {\n CopyOperationResult,\n ICopyGenerator,\n IGenerateCopiesOptions,\n IGenerateSymlinksOptions,\n ISymlinkGenerator,\n SymlinkOperationResult,\n} from '@dotfiles/symlink-generator';\nimport { resolveValue } from '@dotfiles/unwrap-value';\nimport { resolvePlatformConfig } from '@dotfiles/utils';\nimport { randomUUID } from 'node:crypto';\nimport path from 'node:path';\nimport type { IAutoInstaller, IGenerateAllOptions, IGeneratorOrchestrator } from './IGeneratorOrchestrator';\nimport { messages } from './log-messages';\nimport { orderToolConfigsByDependencies } from './orderToolConfigsByDependencies';\n\n/**\n * File types that should be cleaned up when a tool is disabled.\n * Binary files are intentionally excluded to preserve downloaded tools.\n */\nconst CLEANABLE_FILE_TYPES: Set<IFileState['fileType']> = new Set(['shim', 'symlink', 'copy', 'completion']);\n\n/**\n * Orchestrates the generation of all dotfiles artifacts.\n *\n * This class coordinates the generation of shims, shell initialization scripts,\n * and symlinks by delegating to the respective generator services. It ensures\n * that all artifacts are created in the correct order and that file operations\n * are properly tracked for cleanup and auditing purposes.\n */\nexport class GeneratorOrchestrator implements IGeneratorOrchestrator {\n private readonly logger: TsLogger;\n private readonly shimGenerator: IShimGenerator;\n private readonly shellInitGenerator: IShellInitGenerator;\n private readonly symlinkGenerator: ISymlinkGenerator;\n private readonly copyGenerator: ICopyGenerator;\n private readonly completionGenerator: ICompletionGenerator;\n private readonly systemInfo: ISystemInfo;\n private readonly projectConfig: ProjectConfig;\n private readonly fileRegistry: IFileRegistry;\n private readonly fs: IFileSystem;\n private readonly completionTrackedFs: TrackedFileSystem;\n\n /**\n * Creates a new GeneratorOrchestrator instance.\n *\n * @param parentLogger - The parent logger for creating sub-loggers.\n * @param shimGenerator - The shim generator service.\n * @param shellInitGenerator - The shell initialization generator service.\n * @param symlinkGenerator - The symlink generator service.\n * @param completionGenerator - The completion generator service.\n * @param systemInfo - System information for platform-specific operations.\n * @param projectConfig - Project configuration containing paths and settings.\n * @param fileRegistry - Registry for tracking file operations.\n * @param fs - Filesystem interface for file operations.\n * @param completionTrackedFs - Tracked filesystem for completion operations.\n */\n constructor(\n parentLogger: TsLogger,\n shimGenerator: IShimGenerator,\n shellInitGenerator: IShellInitGenerator,\n symlinkGenerator: ISymlinkGenerator,\n copyGenerator: ICopyGenerator,\n completionGenerator: ICompletionGenerator,\n systemInfo: ISystemInfo,\n projectConfig: ProjectConfig,\n fileRegistry: IFileRegistry,\n fs: IFileSystem,\n completionTrackedFs: TrackedFileSystem,\n ) {\n this.logger = parentLogger.getSubLogger({ name: 'GeneratorOrchestrator' });\n const logger = this.logger.getSubLogger({ name: 'constructor' });\n logger.debug(messages.constructor.initialized());\n this.shimGenerator = shimGenerator;\n this.shellInitGenerator = shellInitGenerator;\n this.symlinkGenerator = symlinkGenerator;\n this.copyGenerator = copyGenerator;\n this.completionGenerator = completionGenerator;\n this.systemInfo = systemInfo;\n this.projectConfig = projectConfig;\n this.fileRegistry = fileRegistry;\n this.fs = fs;\n this.completionTrackedFs = completionTrackedFs;\n }\n\n /**\n * Checks if the current hostname matches the tool's hostname pattern.\n * @param pattern - The hostname pattern (literal string or regex pattern)\n * @returns true if the hostname matches, false otherwise\n */\n private matchesHostname(pattern: string): boolean {\n const currentHostname = this.systemInfo.hostname;\n\n // Check if pattern is a regex (starts and ends with /)\n if (pattern.startsWith('/') && pattern.lastIndexOf('/') > 0) {\n const lastSlash = pattern.lastIndexOf('/');\n const regexBody = pattern.slice(1, lastSlash);\n const flags = pattern.slice(lastSlash + 1);\n try {\n const regex = new RegExp(regexBody, flags);\n return regex.test(currentHostname);\n } catch {\n // Invalid regex, treat as literal string\n return currentHostname === pattern;\n }\n }\n\n // Treat as regex source if it looks like a regex pattern\n try {\n const regex = new RegExp(pattern);\n return regex.test(currentHostname);\n } catch {\n // If regex compilation fails, do exact match\n return currentHostname === pattern;\n }\n }\n\n /**\n * @inheritdoc IGeneratorOrchestrator.generateAll\n */\n async generateAll(toolConfigs: Record<string, ToolConfig>, options?: IGenerateAllOptions): Promise<void> {\n const logger = this.logger.getSubLogger({ name: 'generateAll' });\n\n // Filter out disabled tools and hostname mismatches, cleanup their artifacts\n const enabledToolConfigs: Record<string, ToolConfig> = {};\n for (const [name, config] of Object.entries(toolConfigs)) {\n if (config.disabled) {\n logger.warn(messages.generateAll.toolDisabled(name));\n // Clean up artifacts for disabled tools\n await this.cleanupToolArtifacts(name);\n continue;\n }\n\n // Check hostname restriction\n if (config.hostname && !this.matchesHostname(config.hostname)) {\n logger.warn(messages.generateAll.toolHostnameMismatch(name, config.hostname, this.systemInfo.hostname));\n // Clean up artifacts for hostname-mismatched tools\n await this.cleanupToolArtifacts(name);\n continue;\n }\n\n enabledToolConfigs[name] = config;\n }\n\n // Clean up orphaned tools (tools that exist in the registry but no longer have config files)\n await this.cleanupOrphanedTools(toolConfigs);\n\n const orderedToolConfigs: Record<string, ToolConfig> = orderToolConfigsByDependencies(\n this.logger,\n enabledToolConfigs,\n this.systemInfo,\n );\n\n const toolConfigsCount = Object.keys(orderedToolConfigs).length;\n logger.debug(messages.generateAll.parsedOptions(toolConfigsCount));\n\n // 0. Auto-install tools with auto: true in their install params\n const pluginShellInit: PluginShellInitMap = await this.runAutoInstalls(orderedToolConfigs, options?.installer);\n\n // 1. Generate Shims\n const shimOptions: IGenerateShimsOptions = { overwrite: true, overwriteConflicts: options?.overwrite };\n logger.debug(messages.generateAll.shimGenerate());\n const generatedShimsPaths = await this.shimGenerator.generate(orderedToolConfigs, shimOptions);\n const shimCount = generatedShimsPaths?.length ?? 0;\n logger.debug(messages.generateAll.shimGenerationComplete(shimCount));\n\n // 2. Generate Shell Init for all supported shells\n const shellInitOptions: IGenerateShellInitOptions = {\n shellTypes: ['zsh', 'bash', 'powershell'],\n systemInfo: this.systemInfo,\n pluginShellInit,\n };\n logger.debug(messages.generateAll.shellGenerate());\n const shellInitResult = await this.shellInitGenerator.generate(orderedToolConfigs, shellInitOptions);\n const primaryPath = shellInitResult?.primaryPath ?? 'null';\n logger.debug(messages.generateAll.shellInitComplete(primaryPath));\n\n // 3. Generate Symlinks\n const symlinkOptions: IGenerateSymlinksOptions = { overwrite: true, backup: true };\n const symlinkResults: SymlinkOperationResult[] = await this.symlinkGenerator.generate(\n orderedToolConfigs,\n symlinkOptions,\n );\n const symlinkResultCount = symlinkResults?.length ?? 0;\n logger.debug(messages.generateAll.symlinkGenerationComplete(symlinkResultCount));\n\n // 4. Generate Copies\n const copyOptions: IGenerateCopiesOptions = { overwrite: true, backup: true };\n const copyResults: CopyOperationResult[] = await this.copyGenerator.generate(orderedToolConfigs, copyOptions);\n const copyResultCount = copyResults?.length ?? 0;\n logger.debug(messages.generateAll.copyGenerationComplete(copyResultCount));\n\n // 5. Clean up stale symlinks and copies for enabled tools\n await this.cleanupStaleSymlinks(orderedToolConfigs, symlinkResults);\n await this.cleanupStaleCopies(orderedToolConfigs, copyResults);\n }\n\n /**\n * Runs auto-install for tools that have `auto: true` in their install params.\n * Returns a map of shellInit content emitted by installers for use in shell init generation.\n *\n * @param toolConfigs - The tool configurations to process.\n * @param installer - Optional installer for auto-install operations.\n * @returns Map of tool names to their emitted shellInit content.\n */\n private async runAutoInstalls(\n toolConfigs: Record<string, ToolConfig>,\n installer?: IAutoInstaller,\n ): Promise<PluginShellInitMap> {\n const logger = this.logger.getSubLogger({ name: 'runAutoInstalls' });\n const pluginShellInit: PluginShellInitMap = {};\n\n if (!installer) {\n return pluginShellInit;\n }\n\n for (const [toolName, toolConfig] of Object.entries(toolConfigs)) {\n const installParams = toolConfig.installParams as BaseInstallParams | undefined;\n const shouldAutoInstall = installParams?.auto === true;\n\n if (!shouldAutoInstall) {\n continue;\n }\n\n // Call installer - it will return \"already-installed\" with shellInit if already installed\n const result = await installer.install(toolName, toolConfig);\n\n if (!result.success) {\n continue;\n }\n\n // Only log when actually installed (not when already-installed)\n if (result.installationMethod !== 'already-installed') {\n logger.info(messages.autoInstall.completed(toolName));\n }\n\n // Collect shellInit from successful install\n if (result.shellInit) {\n pluginShellInit[toolName] = result.shellInit;\n }\n }\n\n return pluginShellInit;\n }\n\n /**\n * @inheritdoc IGeneratorOrchestrator.generateCompletionsForTool\n */\n async generateCompletionsForTool(\n toolName: string,\n toolConfig: ToolConfig,\n version?: string,\n binaryPaths?: string[],\n ): Promise<void> {\n const logger = this.logger.getSubLogger({ name: 'generateCompletionsForTool' }).setPrefix(toolName);\n const resolvedConfig = resolvePlatformConfig(toolConfig, this.systemInfo);\n const shellTypes: ShellType[] = ['zsh', 'bash', 'powershell'];\n // Use provided version, or fall back to toolConfig.version\n const resolvedVersion = version ?? resolvedConfig.version;\n\n for (const shellType of shellTypes) {\n const shellConfig = resolvedConfig.shellConfigs?.[shellType];\n // Cast from unknown (Zod schema) to ShellCompletionConfigInput (runtime type)\n const completionInput = shellConfig?.completions as ShellCompletionConfigInput | undefined;\n\n if (!completionInput) {\n continue;\n }\n\n try {\n const currentDir = path.join(this.projectConfig.paths.binariesDir, toolName, 'current');\n\n // Build context for resolving completions callback (only version is exposed to user)\n const completionContext: ICompletionContext = {\n version: resolvedVersion,\n };\n\n // Resolve the completion config (handles static values and callbacks)\n const resolvedCompletionValue: ShellCompletionConfigValue = await resolveValue(\n completionContext,\n completionInput,\n );\n\n // Convert to ShellCompletionConfig format\n const completionConfig = this.normalizeCompletionConfig(resolvedCompletionValue);\n\n // Skip if no valid completion config after resolution\n if (!completionConfig.cmd && !completionConfig.source && !completionConfig.url) {\n continue;\n }\n\n // Build full generation context with internal fields\n const generationContext: ICompletionGenerationContext = {\n ...completionContext,\n homeDir: this.projectConfig.paths.homeDir,\n shellScriptsDir: this.projectConfig.paths.shellScriptsDir,\n toolInstallDir: currentDir,\n toolName,\n configFilePath: toolConfig.configFilePath,\n binaryPaths,\n };\n\n // Create a per-tool tracked filesystem for proper attribution\n const toolTrackedFs = this.completionTrackedFs.withContext({ toolName });\n\n const completionResult = await this.completionGenerator.generateAndWriteCompletionFile({\n config: completionConfig,\n toolName,\n shellType,\n context: generationContext,\n fs: toolTrackedFs,\n });\n\n logger.info(messages.generateAll.completionGeneratedAtPath(completionResult.targetPath));\n } catch {\n logger.warn(messages.generateAll.completionGenerationFailed(toolName, shellType));\n }\n }\n }\n\n /**\n * Normalizes a resolved completion config value to the internal config format.\n */\n private normalizeCompletionConfig(value: ShellCompletionConfigValue): ShellCompletionConfig {\n if (typeof value === 'string') {\n const result: ShellCompletionConfig = { source: value };\n return result;\n }\n\n // Determine which discriminated union variant we have\n if ('cmd' in value) {\n // IShellCompletionCmdConfig\n const result: ShellCompletionConfig = {\n cmd: value.cmd,\n ...(value.bin && { bin: value.bin }),\n };\n return result;\n }\n\n if ('url' in value) {\n // IShellCompletionUrlConfig\n const result: ShellCompletionConfig = {\n url: value.url,\n ...(value.source && { source: value.source }),\n ...(value.bin && { bin: value.bin }),\n };\n return result;\n }\n\n // IShellCompletionSourceConfig\n const result: ShellCompletionConfig = {\n source: value.source,\n ...(value.bin && { bin: value.bin }),\n };\n return result;\n }\n\n /**\n * Cleans up artifacts for tools that exist in the registry but no longer have configuration files.\n * These are \"orphaned\" tools whose tool.ts files were removed.\n *\n * @param toolConfigs - The current set of all tool configurations (including disabled ones).\n */\n private async cleanupOrphanedTools(toolConfigs: Record<string, ToolConfig>): Promise<void> {\n const logger = this.logger.getSubLogger({ name: 'cleanupOrphanedTools' });\n\n const registeredTools = await this.fileRegistry.getRegisteredTools();\n const configuredToolNames = new Set(Object.keys(toolConfigs));\n const orphanedToolsWithCleanableArtifacts: string[] = [];\n\n for (const toolName of registeredTools) {\n if (toolName === 'system' || configuredToolNames.has(toolName)) {\n continue;\n }\n\n const fileStates = await this.fileRegistry.getFileStatesForTool(toolName);\n const hasCleanableArtifacts = fileStates.some((state) => CLEANABLE_FILE_TYPES.has(state.fileType));\n\n if (hasCleanableArtifacts) {\n orphanedToolsWithCleanableArtifacts.push(toolName);\n }\n }\n\n if (orphanedToolsWithCleanableArtifacts.length === 0) {\n return;\n }\n\n logger.warn(messages.orphanCleanup.found(orphanedToolsWithCleanableArtifacts.length));\n\n for (const toolName of orphanedToolsWithCleanableArtifacts) {\n const toolLogger: TsLogger = logger.getSubLogger({ name: 'cleanupOrphanedTools', context: toolName });\n toolLogger.warn(messages.orphanCleanup.cleaningUp());\n await this.cleanupToolArtifacts(toolName);\n }\n }\n\n /**\n * Cleans up generated artifacts for a tool.\n *\n * This removes shims, symlinks, and completions that were generated for the tool,\n * while preserving downloaded binaries. Used when a tool is disabled to ensure\n * its contributions are removed from the system.\n *\n * @param toolName - The name of the tool to clean up.\n */\n async cleanupToolArtifacts(toolName: string): Promise<void> {\n const logger = this.logger.getSubLogger({ name: 'cleanupToolArtifacts', context: toolName });\n logger.debug(messages.cleanup.started(toolName));\n\n // Get all file states for this tool from the registry\n const fileStates = await this.fileRegistry.getFileStatesForTool(toolName);\n\n // Filter for cleanable file types (shims, symlinks, completions - NOT binaries)\n const filesToCleanup = fileStates.filter((state) => CLEANABLE_FILE_TYPES.has(state.fileType));\n\n if (filesToCleanup.length === 0) {\n logger.debug(messages.cleanup.noFilesToCleanup(toolName));\n return;\n }\n\n logger.debug(messages.cleanup.filesFound(toolName, filesToCleanup.length));\n\n const sortedFilesToCleanup = filesToCleanup.toSorted((left, right) => right.filePath.length - left.filePath.length);\n\n // Delete each file\n for (const fileState of sortedFilesToCleanup) {\n try {\n const fileExists = fileState.fileType === 'symlink'\n ? await this.fs.lstat(fileState.filePath).then(() => true, () => false)\n : await this.fs.exists(fileState.filePath);\n\n if (fileExists) {\n if (fileState.lastOperation === 'mkdir') {\n await this.fs.rmdir(fileState.filePath);\n } else {\n await this.fs.rm(fileState.filePath, { recursive: fileState.fileType === 'copy', force: true });\n }\n\n logger.warn(messages.cleanup.fileDeleted(fileState.filePath, fileState.fileType));\n }\n\n await this.fileRegistry.recordOperation({\n toolName,\n operationType: 'rm',\n filePath: fileState.filePath,\n fileType: fileState.fileType,\n operationId: randomUUID(),\n });\n } catch (error) {\n logger.debug(messages.cleanup.deleteError(fileState.filePath, error));\n }\n }\n\n logger.debug(messages.cleanup.completed(toolName, filesToCleanup.length));\n }\n\n /**\n * Cleans up stale symlinks for enabled tools.\n *\n * After symlinks are generated, this method compares the set of currently declared\n * symlinks against previously tracked symlinks in the FileRegistry. Any tracked\n * symlink that is no longer declared is removed from disk.\n *\n * @param toolConfigs - The enabled tool configurations that were just processed.\n * @param symlinkResults - The results from symlink generation.\n */\n private async cleanupStaleSymlinks(\n toolConfigs: Record<string, ToolConfig>,\n symlinkResults: SymlinkOperationResult[],\n ): Promise<void> {\n const logger = this.logger.getSubLogger({ name: 'cleanupStaleSymlinks' });\n\n const generatedTargetPaths: Set<string> = new Set(\n symlinkResults.filter((r) => r.success).map((r) => r.targetPath),\n );\n\n const binariesDir = this.projectConfig.paths.binariesDir;\n\n for (const toolName of Object.keys(toolConfigs)) {\n const fileStates = await this.fileRegistry.getFileStatesForTool(toolName);\n const trackedSymlinks = fileStates.filter(\n (state) =>\n state.fileType === 'symlink' &&\n state.lastOperation === 'symlink' &&\n !state.filePath.startsWith(binariesDir),\n );\n\n for (const trackedSymlink of trackedSymlinks) {\n if (generatedTargetPaths.has(trackedSymlink.filePath)) {\n continue;\n }\n\n logger.warn(messages.staleSymlinkCleanup.removing(trackedSymlink.filePath, toolName));\n\n try {\n // Use lstat (not exists) to detect broken symlinks — exists follows\n // the symlink and returns false when the target is missing.\n try {\n await this.fs.lstat(trackedSymlink.filePath);\n await this.fs.rm(trackedSymlink.filePath);\n } catch {\n // File/symlink doesn't exist on disk, nothing to delete\n }\n await this.fileRegistry.recordOperation({\n toolName,\n operationType: 'rm',\n filePath: trackedSymlink.filePath,\n fileType: 'symlink',\n operationId: randomUUID(),\n });\n } catch (error) {\n logger.debug(messages.cleanup.deleteError(trackedSymlink.filePath, error));\n }\n }\n }\n }\n\n /**\n * Cleans up stale copies for enabled tools.\n *\n * After copies are generated, this method compares the set of currently declared\n * copies against previously tracked copies in the FileRegistry. Any tracked\n * copy that is no longer declared is removed from disk.\n *\n * @param toolConfigs - The enabled tool configurations that were just processed.\n * @param copyResults - The results from copy generation.\n */\n private async cleanupStaleCopies(\n toolConfigs: Record<string, ToolConfig>,\n copyResults: CopyOperationResult[],\n ): Promise<void> {\n const logger = this.logger.getSubLogger({ name: 'cleanupStaleCopies' });\n\n const generatedTargetPaths: Set<string> = new Set(\n copyResults.filter((r) => r.success).map((r) => r.targetPath),\n );\n\n for (const toolName of Object.keys(toolConfigs)) {\n const fileStates = await this.fileRegistry.getFileStatesForTool(toolName);\n const trackedCopies = fileStates.filter(\n (state) => state.fileType === 'copy' && state.lastOperation === 'cp',\n );\n\n for (const trackedCopy of trackedCopies) {\n if (generatedTargetPaths.has(trackedCopy.filePath)) {\n continue;\n }\n\n logger.warn(messages.staleCopyCleanup.removing(trackedCopy.filePath, toolName));\n\n try {\n const fileExists = await this.fs.exists(trackedCopy.filePath);\n if (fileExists) {\n await this.fs.rm(trackedCopy.filePath, { recursive: true, force: true });\n }\n await this.fileRegistry.recordOperation({\n toolName,\n operationType: 'rm',\n filePath: trackedCopy.filePath,\n fileType: 'copy',\n operationId: randomUUID(),\n });\n } catch (error) {\n logger.debug(messages.cleanup.deleteError(trackedCopy.filePath, error));\n }\n }\n }\n }\n}\n",
|
|
199
|
+
"import { createSafeLogMessage, type SafeLogMessageMap } from '@dotfiles/logger';\n\nexport const messages = {\n constructor: {\n initialized: () => createSafeLogMessage('Initializing GeneratorOrchestrator'),\n } satisfies SafeLogMessageMap,\n autoInstall: {\n completed: (toolName: string) => createSafeLogMessage(`Auto-installed: ${toolName}`),\n } satisfies SafeLogMessageMap,\n generateAll: {\n parsedOptions: (toolConfigsCount: number) =>\n createSafeLogMessage(`Parsed ${toolConfigsCount} tool configuration entries`),\n toolDisabled: (toolName: string) => createSafeLogMessage(`Skipping disabled tool: ${toolName}`),\n toolHostnameMismatch: (toolName: string, pattern: string, currentHostname: string) =>\n createSafeLogMessage(\n `Skipping tool \"${toolName}\": hostname \"${currentHostname}\" does not match pattern \"${pattern}\"`,\n ),\n dependenciesValidationStarted: (toolCount: number) =>\n createSafeLogMessage(`Validating tool dependencies (${toolCount} tools)`),\n dependenciesOrderResolved: (orderedTools: string) =>\n createSafeLogMessage(`Dependency order resolved: ${orderedTools}`),\n missingDependency: (toolName: string, dependencyName: string, platform: string, arch: string) =>\n createSafeLogMessage(\n `Missing dependency: tool \"${toolName}\" requires binary \"${dependencyName}\" but no tool provides it for platform ${platform}/${arch}.`,\n ),\n ambiguousDependency: (dependencyName: string, providers: string, toolName: string) =>\n createSafeLogMessage(\n `Ambiguous dependency: binary \"${dependencyName}\" is provided by multiple tools (${providers}). Tool \"${toolName}\" cannot determine which to use.`,\n ),\n circularDependency: (tools: string) => createSafeLogMessage(`Circular dependency detected between tools: ${tools}`),\n shimGenerate: () => createSafeLogMessage('Generating shims with resolved options'),\n shimGenerationComplete: (generatedCount: number) =>\n createSafeLogMessage(`Shim generation completed with ${generatedCount} paths recorded`),\n shellGenerate: () => createSafeLogMessage('Generating shell initialization files with resolved options'),\n shellInitComplete: (primaryPath: string) =>\n createSafeLogMessage(`Shell initialization generation complete; primary path: ${primaryPath}`),\n completionGenerated: (filename: string, toolName: string, shellType: string) =>\n createSafeLogMessage(`Generated completion ${filename} for ${toolName} (${shellType})`),\n completionGeneratedAtPath: (completionPath: string) =>\n createSafeLogMessage(`Generated completion at ${completionPath}`),\n completionGenerationFailed: (toolName: string, shellType: string) =>\n createSafeLogMessage(`Failed to generate completion for ${toolName} (${shellType})`),\n completionSkippedNotInstalled: (toolName: string, shellType: string) =>\n createSafeLogMessage(`Skipping completion generation for ${toolName} (${shellType}) - tool not installed yet`),\n symlinkGenerationComplete: (resultCount: number) =>\n createSafeLogMessage(`Symlink generation completed with ${resultCount} operations recorded`),\n copyGenerationComplete: (resultCount: number) =>\n createSafeLogMessage(`Copy generation completed with ${resultCount} operations recorded`),\n } satisfies SafeLogMessageMap,\n cleanup: {\n started: (toolName: string) => createSafeLogMessage(`Cleaning up artifacts for disabled tool: ${toolName}`),\n noFilesToCleanup: (toolName: string) =>\n createSafeLogMessage(`No tracked artifacts found to clean up for: ${toolName}`),\n filesFound: (toolName: string, count: number) =>\n createSafeLogMessage(`Found ${count} artifacts to clean up for: ${toolName}`),\n fileDeleted: (filePath: string, fileType: string) => createSafeLogMessage(`Removed ${fileType}: ${filePath}`),\n deleteError: (filePath: string, _error: unknown) => createSafeLogMessage(`Failed to delete: ${filePath}`),\n completed: (toolName: string, count: number) =>\n createSafeLogMessage(`Cleanup completed for ${toolName}: ${count} files removed`),\n } satisfies SafeLogMessageMap,\n orphanCleanup: {\n found: (count: number) =>\n createSafeLogMessage(`Found ${count} orphaned tool${count === 1 ? '' : 's'} with no configuration`),\n cleaningUp: () => createSafeLogMessage('Cleaning up orphaned tool'),\n } satisfies SafeLogMessageMap,\n staleSymlinkCleanup: {\n removing: (filePath: string, toolName: string) =>\n createSafeLogMessage(`Removing stale symlink ${filePath} for tool: ${toolName}`),\n } satisfies SafeLogMessageMap,\n staleCopyCleanup: {\n removing: (filePath: string, toolName: string) =>\n createSafeLogMessage(`Removing stale copy ${filePath} for tool: ${toolName}`),\n } satisfies SafeLogMessageMap,\n} as const;\n",
|
|
200
|
+
"import type { ISystemInfo, ToolConfig } from '@dotfiles/core';\nimport { architectureToString, platformToString } from '@dotfiles/core';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { resolvePlatformConfig } from '@dotfiles/utils';\nimport { messages } from './log-messages';\n\ninterface IToolDependencyMetadata {\n providedBinaries: string[];\n dependencies: string[];\n}\n\ninterface IToolMetadataResult {\n metadataByTool: Map<string, IToolDependencyMetadata>;\n binaryProviders: Map<string, Set<string>>;\n hasDependencies: boolean;\n}\n\ninterface IDependencyGraph {\n adjacency: Map<string, Set<string>>;\n inDegree: Map<string, number>;\n}\n\nfunction extractProvidedBinaries(toolName: string, toolConfig: ToolConfig): string[] {\n const providedBinaryNames = new Set<string>();\n\n if (toolConfig.binaries && toolConfig.binaries.length > 0) {\n for (const binary of toolConfig.binaries) {\n if (typeof binary === 'string') {\n providedBinaryNames.add(binary);\n } else {\n providedBinaryNames.add(binary.name);\n }\n }\n }\n\n if (providedBinaryNames.size === 0) {\n providedBinaryNames.add(toolName);\n }\n\n const binaryList: string[] = [...providedBinaryNames];\n return binaryList;\n}\n\nfunction extractDependencies(toolConfig: ToolConfig): string[] {\n if (!toolConfig.dependencies || toolConfig.dependencies.length === 0) {\n const emptyList: string[] = [];\n return emptyList;\n }\n\n const dependencyNames = new Set<string>();\n for (const dependencyName of toolConfig.dependencies) {\n const trimmedDependencyName = dependencyName.trim();\n if (trimmedDependencyName.length > 0) {\n dependencyNames.add(trimmedDependencyName);\n }\n }\n\n const dependencyList: string[] = [...dependencyNames];\n return dependencyList;\n}\n\nfunction insertOrdered(queue: string[], toolName: string, orderIndex: Map<string, number>): void {\n const targetIndex = orderIndex.get(toolName) ?? Number.MAX_SAFE_INTEGER;\n\n let insertPosition = queue.findIndex((queuedTool) => {\n const queuedIndex = orderIndex.get(queuedTool) ?? Number.MAX_SAFE_INTEGER;\n return queuedIndex > targetIndex;\n });\n\n if (insertPosition === -1) {\n insertPosition = queue.length;\n }\n\n queue.splice(insertPosition, 0, toolName);\n}\n\nfunction collectToolMetadata(toolConfigs: Record<string, ToolConfig>, systemInfo: ISystemInfo): IToolMetadataResult {\n const metadataByTool: Map<string, IToolDependencyMetadata> = new Map();\n const binaryProviders: Map<string, Set<string>> = new Map();\n let hasDependencies = false;\n\n for (const [toolName, originalConfig] of Object.entries(toolConfigs)) {\n if (!originalConfig) {\n continue;\n }\n\n const resolvedConfig = resolvePlatformConfig(originalConfig, systemInfo);\n const providedBinaries = extractProvidedBinaries(toolName, resolvedConfig);\n const dependencies = extractDependencies(resolvedConfig);\n\n const metadata: IToolDependencyMetadata = {\n providedBinaries,\n dependencies,\n };\n metadataByTool.set(toolName, metadata);\n\n if (dependencies.length > 0) {\n hasDependencies = true;\n }\n\n for (const binaryName of providedBinaries) {\n const providers = binaryProviders.get(binaryName);\n if (providers) {\n providers.add(toolName);\n } else {\n const providerSet: Set<string> = new Set([toolName]);\n binaryProviders.set(binaryName, providerSet);\n }\n }\n }\n\n const result: IToolMetadataResult = {\n metadataByTool,\n binaryProviders,\n hasDependencies,\n };\n return result;\n}\n\nfunction resolveDependencyProvider(\n logger: TsLogger,\n toolName: string,\n dependencyName: string,\n binaryProviders: Map<string, Set<string>>,\n systemInfo: ISystemInfo,\n): string {\n const providers = binaryProviders.get(dependencyName);\n if (!providers || providers.size === 0) {\n logger.error(\n messages.generateAll.missingDependency(\n toolName,\n dependencyName,\n platformToString(systemInfo.platform),\n architectureToString(systemInfo.arch),\n ),\n );\n throw new Error('Dependency validation failed');\n }\n\n if (providers.size > 1) {\n const providerList: string[] = [...providers];\n logger.error(messages.generateAll.ambiguousDependency(dependencyName, providerList.join(', '), toolName));\n throw new Error('Dependency validation failed');\n }\n\n const [providerToolName] = providers;\n if (!providerToolName) {\n logger.error(\n messages.generateAll.missingDependency(\n toolName,\n dependencyName,\n platformToString(systemInfo.platform),\n architectureToString(systemInfo.arch),\n ),\n );\n throw new Error('Dependency validation failed');\n }\n\n return providerToolName;\n}\n\nfunction buildDependencyGraph(\n logger: TsLogger,\n toolNames: string[],\n metadataByTool: Map<string, IToolDependencyMetadata>,\n binaryProviders: Map<string, Set<string>>,\n systemInfo: ISystemInfo,\n): IDependencyGraph {\n const adjacency: Map<string, Set<string>> = new Map();\n const inDegree: Map<string, number> = new Map();\n\n for (const toolName of toolNames) {\n adjacency.set(toolName, new Set());\n inDegree.set(toolName, 0);\n }\n\n for (const toolName of toolNames) {\n const metadata = metadataByTool.get(toolName);\n if (!metadata) {\n continue;\n }\n\n for (const dependencyName of metadata.dependencies) {\n const providerToolName = resolveDependencyProvider(logger, toolName, dependencyName, binaryProviders, systemInfo);\n\n if (providerToolName === toolName) {\n continue;\n }\n\n const dependents = adjacency.get(providerToolName);\n if (dependents) {\n dependents.add(toolName);\n }\n\n const currentDegree = inDegree.get(toolName) ?? 0;\n inDegree.set(toolName, currentDegree + 1);\n }\n }\n\n const graph: IDependencyGraph = {\n adjacency,\n inDegree,\n };\n return graph;\n}\n\nfunction performTopologicalSort(\n toolNames: string[],\n graph: IDependencyGraph,\n toolOrderIndex: Map<string, number>,\n): string[] {\n const zeroInDegreeQueue: string[] = [];\n\n for (const toolName of toolNames) {\n if ((graph.inDegree.get(toolName) ?? 0) === 0) {\n insertOrdered(zeroInDegreeQueue, toolName, toolOrderIndex);\n }\n }\n\n const orderedToolNames: string[] = [];\n\n while (zeroInDegreeQueue.length > 0) {\n const currentTool = zeroInDegreeQueue.shift();\n if (!currentTool) {\n continue;\n }\n\n orderedToolNames.push(currentTool);\n const dependents = graph.adjacency.get(currentTool);\n if (!dependents) {\n continue;\n }\n\n for (const dependentTool of dependents) {\n const reducedDegree = (graph.inDegree.get(dependentTool) ?? 0) - 1;\n graph.inDegree.set(dependentTool, reducedDegree);\n\n if (reducedDegree === 0) {\n insertOrdered(zeroInDegreeQueue, dependentTool, toolOrderIndex);\n }\n }\n }\n\n return orderedToolNames;\n}\n\nexport function orderToolConfigsByDependencies(\n parentLogger: TsLogger,\n toolConfigs: Record<string, ToolConfig>,\n systemInfo: ISystemInfo,\n): Record<string, ToolConfig> {\n const logger = parentLogger.getSubLogger({ name: 'orderToolConfigsByDependencies' });\n\n const toolNames: string[] = Object.keys(toolConfigs);\n logger.debug(messages.generateAll.dependenciesValidationStarted(toolNames.length));\n\n if (toolNames.length === 0) {\n return toolConfigs;\n }\n\n const toolOrderIndex = new Map<string, number>();\n toolNames.forEach((toolName, index) => {\n toolOrderIndex.set(toolName, index);\n });\n\n const { metadataByTool, binaryProviders, hasDependencies } = collectToolMetadata(toolConfigs, systemInfo);\n\n if (!hasDependencies) {\n return toolConfigs;\n }\n const dependencyGraph = buildDependencyGraph(logger, toolNames, metadataByTool, binaryProviders, systemInfo);\n\n const orderedToolNames = performTopologicalSort(toolNames, dependencyGraph, toolOrderIndex);\n if (orderedToolNames.length !== toolNames.length) {\n const remainingTools = toolNames.filter((toolName) => (dependencyGraph.inDegree.get(toolName) ?? 0) > 0);\n logger.error(messages.generateAll.circularDependency(remainingTools.join(', ')));\n throw new Error('Dependency validation failed');\n }\n\n logger.debug(messages.generateAll.dependenciesOrderResolved(orderedToolNames.join(' -> ')));\n\n const orderedToolConfigs: Record<string, ToolConfig> = {};\n for (const toolName of orderedToolNames) {\n const originalConfig = toolConfigs[toolName];\n if (!originalConfig) {\n throw new Error(`Tool configuration missing for \"${toolName}\" after dependency ordering.`);\n }\n orderedToolConfigs[toolName] = originalConfig;\n }\n\n return orderedToolConfigs;\n}\n",
|
|
201
|
+
"import {\n type IDownloadContext,\n type IInstallContext,\n type Shell,\n} from '@dotfiles/core';\nimport type { IDownloader } from '@dotfiles/downloader';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { HookExecutor, IInstallOptions } from '@dotfiles/installer';\nimport {\n createToolFileSystem,\n downloadWithProgress,\n executeAfterDownloadHook,\n getBinaryPaths,\n setupBinariesFromDirectDownload,\n withInstallErrorHandling,\n} from '@dotfiles/installer';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { detectVersionViaCli } from '@dotfiles/utils';\nimport path from 'node:path';\nimport { messages } from './log-messages';\nimport type { CurlBinaryToolConfig } from './schemas';\nimport type { CurlBinaryInstallResult, ICurlBinaryInstallMetadata } from './types';\n\n/**\n * Installs a tool by downloading a standalone binary file from a URL.\n *\n * This function orchestrates the complete installation process:\n * 1. Downloads the binary from the specified URL\n * 2. Executes afterDownload hook if configured\n * 3. Makes the binary executable\n * 4. Sets up binary paths and creates necessary entrypoints\n *\n * Unlike curl-tar, there is no archive extraction step — the downloaded file\n * is used directly as the binary.\n *\n * @param toolName - The name of the tool to install.\n * @param toolConfig - The configuration for the curl-binary tool.\n * @param context - The base installation context.\n * @param options - Optional installation options.\n * @param fs - The file system interface for file operations.\n * @param downloader - The downloader for fetching the binary.\n * @param hookExecutor - The hook executor for running lifecycle hooks.\n * @param parentLogger - The parent logger for creating sub-loggers.\n * @param shellExecutor - The shell executor function.\n * @returns A promise that resolves to the installation result.\n */\nexport async function installFromCurlBinary(\n toolName: string,\n toolConfig: CurlBinaryToolConfig,\n context: IInstallContext,\n options: IInstallOptions | undefined,\n fs: IFileSystem,\n downloader: IDownloader,\n hookExecutor: HookExecutor,\n parentLogger: TsLogger,\n shellExecutor: Shell,\n): Promise<CurlBinaryInstallResult> {\n const toolFs = createToolFileSystem(fs, toolName);\n const logger = parentLogger.getSubLogger({ name: 'installFromCurlBinary' });\n logger.debug(messages.installing(toolName));\n\n if (!toolConfig.installParams || !('url' in toolConfig.installParams)) {\n return {\n success: false,\n error: 'URL not specified in installParams',\n };\n }\n\n const params = toolConfig.installParams;\n const url = params.url;\n\n const operation = async (): Promise<CurlBinaryInstallResult> => {\n // Download the binary directly to the staging directory\n logger.debug(messages.downloadingBinary(url));\n const binaryPath = path.join(context.stagingDir, toolName);\n\n await downloadWithProgress(logger, url, binaryPath, toolName, downloader, options);\n logger.debug(messages.binaryDownloaded());\n\n // Update context with download path\n const postDownloadContext: IDownloadContext = {\n ...context,\n downloadPath: binaryPath,\n };\n\n // Run afterDownload hook if defined\n const afterDownloadResult = await executeAfterDownloadHook(\n toolConfig,\n postDownloadContext,\n hookExecutor,\n fs,\n logger,\n );\n if (!afterDownloadResult.success) {\n return {\n success: false,\n error: afterDownloadResult.error,\n };\n }\n\n // Make binary executable and set up binary entrypoints\n logger.debug(messages.settingPermissions());\n await setupBinariesFromDirectDownload(toolFs, toolName, toolConfig, context, binaryPath, logger);\n\n // Return paths to all binaries\n const binaryPaths = getBinaryPaths(toolConfig.binaries, context.stagingDir);\n\n let detectedVersion: string | undefined;\n const mainBinaryPath = binaryPaths[0];\n if (mainBinaryPath) {\n detectedVersion = await detectVersionViaCli({\n binaryPath: mainBinaryPath,\n args: params.versionArgs,\n regex: params.versionRegex,\n shellExecutor,\n });\n }\n\n const metadata: ICurlBinaryInstallMetadata = {\n method: 'curl-binary',\n downloadUrl: url,\n binaryUrl: url,\n };\n\n return {\n success: true,\n binaryPaths,\n metadata,\n version: detectedVersion || (toolConfig.version !== 'latest' ? toolConfig.version : undefined),\n };\n };\n\n return withInstallErrorHandling('curl-binary', toolName, logger, operation);\n}\n",
|
|
202
|
+
"import { createSafeLogMessage, type SafeLogMessageMap } from '@dotfiles/logger';\n\nexport const messages = {\n installing: (toolName: string) => createSafeLogMessage(`Installing from curl-binary: toolName=${toolName}`),\n downloadingBinary: (url: string) => createSafeLogMessage(`Downloading binary from: ${url}`),\n binaryDownloaded: () => createSafeLogMessage('Binary downloaded successfully'),\n settingPermissions: () => createSafeLogMessage('Setting binary executable permissions'),\n} as const satisfies SafeLogMessageMap;\n",
|
|
203
|
+
"import type { BaseInstallParams } from '@dotfiles/core';\nimport { baseInstallParamsSchema } from '@dotfiles/core';\nimport { z } from 'zod';\n\n/**\n * Parameters for installing a tool by downloading a standalone binary file using `curl`.\n * Unlike `curl-tar`, the downloaded file IS the binary — no archive extraction is performed.\n *\n * @example\n * ```typescript\n * defineTool((install) =>\n * install('curl-binary', {\n * url: 'https://example.com/tool-v1.0.0-linux-amd64',\n * }).bin('my-tool')\n * );\n * ```\n */\nexport const curlBinaryInstallParamsSchema = baseInstallParamsSchema.extend({\n /** The URL of the binary file to download. */\n url: z.string().url(),\n /** Arguments to pass to the binary to check the version (e.g. ['--version']). */\n versionArgs: z.array(z.string()).optional(),\n /** Regex to extract version from output. */\n versionRegex: z.string().optional(),\n});\n\n/**\n * Parameters for installing a tool by downloading a standalone binary file.\n *\n * NOTE: This is an explicit interface (not z.infer) to ensure TypeScript fully resolves\n * the property names, which is required for proper `keyof` behavior in declaration files.\n */\nexport interface CurlBinaryInstallParams extends BaseInstallParams {\n /** The URL of the binary file to download. */\n url: string;\n /** Arguments to pass to the binary to check the version. */\n versionArgs?: string[];\n /** Regex to extract version from output. */\n versionRegex?: string;\n}\n",
|
|
204
|
+
"import {\n baseToolConfigWithPlatformsSchema,\n binaryConfigSchema,\n type InferToolConfigWithPlatforms,\n} from '@dotfiles/core';\nimport { z } from 'zod';\nimport { curlBinaryInstallParamsSchema } from './curlBinaryInstallParamsSchema';\n\nexport const curlBinaryToolConfigSchema = baseToolConfigWithPlatformsSchema.extend({\n /** Resolved tool configuration for the 'curl-binary' installation method */\n installationMethod: z.literal('curl-binary'),\n /** Curl binary installation parameters */\n installParams: curlBinaryInstallParamsSchema,\n /** Binaries are typically required for this installation method */\n binaries: z.array(z.union([z.string().min(1), binaryConfigSchema])).min(1),\n});\n\n/** Resolved tool configuration for the 'curl-binary' installation method. */\nexport type CurlBinaryToolConfig = InferToolConfigWithPlatforms<typeof curlBinaryToolConfigSchema>;\n",
|
|
205
|
+
"import type { IInstallContext, IInstallerPlugin, IInstallOptions, InstallResult, Shell } from '@dotfiles/core';\nimport type { IDownloader } from '@dotfiles/downloader';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { HookExecutor } from '@dotfiles/installer';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { installFromCurlBinary } from './installFromCurlBinary';\nimport {\n type CurlBinaryInstallParams,\n curlBinaryInstallParamsSchema,\n type CurlBinaryToolConfig,\n curlBinaryToolConfigSchema,\n} from './schemas';\nimport type { ICurlBinaryInstallMetadata } from './types';\n\nconst PLUGIN_VERSION = '1.0.0';\n\n/**\n * Installer plugin for tools distributed as standalone binary files via direct URLs.\n *\n * This plugin handles tools where the download URL points directly to an executable binary,\n * rather than a compressed archive. It downloads the binary, makes it executable, and sets\n * up the binary path. This method is useful for tools that provide platform-specific\n * binaries as direct downloads (e.g., single-file Go binaries, Rust binaries).\n *\n * The plugin supports lifecycle hooks for custom processing after download.\n * It does not support version checking or automatic updates since the URLs\n * are typically static.\n */\nexport class CurlBinaryInstallerPlugin implements\n IInstallerPlugin<\n 'curl-binary',\n CurlBinaryInstallParams,\n CurlBinaryToolConfig,\n ICurlBinaryInstallMetadata\n >\n{\n readonly method = 'curl-binary';\n readonly displayName = 'Curl Binary Installer';\n readonly version = PLUGIN_VERSION;\n readonly paramsSchema = curlBinaryInstallParamsSchema;\n readonly toolConfigSchema = curlBinaryToolConfigSchema;\n\n /**\n * Creates a new CurlBinaryInstallerPlugin instance.\n *\n * @param fs - The file system interface for file operations.\n * @param downloader - The downloader for fetching binaries.\n * @param hookExecutor - The hook executor for running lifecycle hooks.\n * @param shell - The shell executor for running commands.\n */\n constructor(\n private readonly fs: IFileSystem,\n private readonly downloader: IDownloader,\n private readonly hookExecutor: HookExecutor,\n private readonly shell: Shell,\n ) {}\n\n /**\n * Installs a tool from a direct binary URL.\n *\n * @param toolName - The name of the tool to install.\n * @param toolConfig - The configuration for the curl-binary tool.\n * @param context - The base installation context.\n * @param options - Optional installation options.\n * @param logger - The logger with tool context for logging operations.\n * @returns A promise that resolves to the installation result.\n */\n async install(\n toolName: string,\n toolConfig: CurlBinaryToolConfig,\n context: IInstallContext,\n options: IInstallOptions | undefined,\n logger: TsLogger,\n ): Promise<InstallResult<ICurlBinaryInstallMetadata>> {\n const result = await installFromCurlBinary(\n toolName,\n toolConfig,\n context,\n options,\n this.fs,\n this.downloader,\n this.hookExecutor,\n logger,\n this.shell,\n );\n\n if (!result.success) {\n return {\n success: false,\n error: result.error,\n };\n }\n\n const installResult: InstallResult<ICurlBinaryInstallMetadata> = {\n success: true,\n binaryPaths: result.binaryPaths,\n version: result.version,\n metadata: result.metadata,\n };\n\n return installResult;\n }\n\n /**\n * Indicates whether this plugin supports version update checking.\n *\n * @returns False, as curl-binary installations use static URLs.\n */\n supportsUpdate(): boolean {\n return false;\n }\n\n supportsUpdateCheck(): boolean {\n return false;\n }\n\n /**\n\n * Indicates whether this plugin supports README fetching.\n *\n * @returns False, as curl-binary installations don't have associated READMEs to fetch.\n */\n supportsReadme(): boolean {\n return false;\n }\n}\n",
|
|
206
|
+
"import { createShell, type IInstallContext, type IInstallOptions, type Shell } from '@dotfiles/core';\nimport { getBinaryPaths, withInstallErrorHandling } from '@dotfiles/installer';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { detectVersionViaCli, normalizeVersion } from '@dotfiles/utils';\nimport path from 'node:path';\nimport { messages } from './log-messages';\nimport type { NpmToolConfig } from './schemas';\nimport type { INpmInstallMetadata, NpmInstallResult } from './types';\n\n/**\n * Installs a tool using npm/bun globally.\n *\n * This function handles the complete installation process for npm tools:\n * 1. Installs the npm package globally using `npm install -g` or `bun install -g`\n * 2. Resolves the global bin directory to find where binaries land\n * 3. Retrieves version information from the installed package\n * 4. Determines binary paths from the global bin directory\n */\nexport async function installFromNpm(\n toolName: string,\n toolConfig: NpmToolConfig,\n _context: IInstallContext,\n _options: IInstallOptions | undefined,\n parentLogger: TsLogger,\n shellExecutor: Shell,\n installShell?: Shell,\n): Promise<NpmInstallResult> {\n const logger = parentLogger.getSubLogger({ name: 'installFromNpm' });\n\n if (!toolConfig.installParams) {\n return {\n success: false,\n error: 'Install parameters not specified',\n };\n }\n\n const params = toolConfig.installParams;\n const packageName: string = params.package || toolName;\n const packageVersion: string | undefined = params.version;\n const packageSpec: string = packageVersion ? `${packageName}@${packageVersion}` : packageName;\n\n logger.debug(messages.installing(packageName));\n\n const isBun = params.packageManager === 'bun';\n\n const operation = async (): Promise<NpmInstallResult> => {\n const loggingShell = installShell ?? createShell({ logger, skipCommandLog: true });\n\n if (isBun) {\n await executeBunGlobalInstall(packageSpec, logger, loggingShell);\n } else {\n await executeNpmGlobalInstall(packageSpec, logger, loggingShell);\n }\n\n const globalBinDir: string = await getGlobalBinDir(isBun, loggingShell);\n const binaryPaths: string[] = getBinaryPaths(toolConfig.binaries, globalBinDir);\n\n let version: string | undefined;\n\n if (params.versionArgs && params.versionRegex) {\n const mainBinaryPath = binaryPaths[0];\n if (mainBinaryPath) {\n version = await detectVersionViaCli({\n binaryPath: mainBinaryPath,\n args: params.versionArgs,\n regex: params.versionRegex,\n shellExecutor,\n });\n }\n } else if (isBun) {\n const mainBinaryPath = binaryPaths[0];\n if (mainBinaryPath) {\n version = await detectVersionViaCli({\n binaryPath: mainBinaryPath,\n args: ['--version'],\n regex: '(\\\\d+\\\\.\\\\d+\\\\.\\\\d+)',\n shellExecutor,\n });\n }\n } else {\n version = await getNpmViewVersion(packageName, shellExecutor);\n }\n\n const metadata: INpmInstallMetadata = {\n method: 'npm',\n packageName,\n };\n\n const result: NpmInstallResult = {\n success: true,\n binaryPaths,\n version: version ? normalizeVersion(version) : undefined,\n metadata,\n };\n\n return result;\n };\n\n return withInstallErrorHandling('npm', toolName, logger, operation);\n}\n\n/**\n * Returns the global bin directory for the given package manager.\n *\n * - bun: runs `bun pm bin -g` (e.g. `~/.bun/bin`)\n * - npm: runs `npm prefix -g` + `/bin` (e.g. `/usr/local/bin`)\n */\nasync function getGlobalBinDir(isBun: boolean, shell: Shell): Promise<string> {\n if (isBun) {\n const result = await shell`bun pm bin -g`.quiet();\n return result.stdout.toString().trim();\n }\n\n const result = await shell`npm prefix -g`.quiet();\n return path.join(result.stdout.toString().trim(), 'bin');\n}\n\n/**\n * Executes `bun install -g` to install a package globally.\n */\nasync function executeBunGlobalInstall(\n packageSpec: string,\n logger: TsLogger,\n shell: Shell,\n): Promise<void> {\n const command = `bun install -g ${packageSpec}`;\n logger.debug(messages.executingCommand(command));\n await shell`bun install -g ${packageSpec}`;\n}\n\n/**\n * Executes `npm install -g` to install a package globally.\n */\nasync function executeNpmGlobalInstall(\n packageSpec: string,\n logger: TsLogger,\n shell: Shell,\n): Promise<void> {\n const command = `npm install -g ${packageSpec}`;\n logger.debug(messages.executingCommand(command));\n await shell`npm install -g ${packageSpec}`;\n}\n\n/**\n * Retrieves the version of an npm package via `npm view`.\n */\nasync function getNpmViewVersion(\n packageName: string,\n shell: Shell,\n): Promise<string | undefined> {\n try {\n const result = await shell`npm view ${packageName} version`.quiet().noThrow();\n const version: string = result.stdout.toString().trim();\n return version || undefined;\n } catch {\n return undefined;\n }\n}\n",
|
|
207
|
+
"import { createSafeLogMessage, type SafeLogMessageMap } from '@dotfiles/logger';\n\nexport const messages = {\n installing: (packageName: string) => createSafeLogMessage(`Installing from npm: package=${packageName}`),\n executingCommand: (command: string) => createSafeLogMessage(`Executing command: ${command}`),\n versionFetched: (packageName: string, version: string) =>\n createSafeLogMessage(`Fetched version ${version} for npm package ${packageName}`),\n versionFetchFailed: (packageName: string) =>\n createSafeLogMessage(`Failed to fetch version for npm package ${packageName}`),\n updateCheckFailed: () => createSafeLogMessage('Failed to check update for npm tool'),\n} as const satisfies SafeLogMessageMap;\n",
|
|
208
|
+
"import type { BaseInstallParams } from '@dotfiles/core';\nimport { baseInstallParamsSchema } from '@dotfiles/core';\nimport { z } from 'zod';\n\nexport const npmInstallParamsSchema = baseInstallParamsSchema.extend({\n /**\n * The npm package name to install (e.g., `prettier`, `@angular/cli`).\n * If not specified, the tool name is used as the package name.\n */\n package: z.string().min(1).optional(),\n /**\n * The version or version range to install (e.g., `3.0.0`, `latest`).\n * If not specified, the latest version is installed.\n */\n version: z.string().optional(),\n /** Arguments to pass to the binary to check the version (e.g. ['--version']). */\n versionArgs: z.array(z.string()).optional(),\n /** Regex to extract version from output. */\n versionRegex: z.string().optional(),\n /** The package manager to use for installation. Defaults to `'npm'`. */\n packageManager: z.enum(['npm', 'bun']).optional(),\n});\n\n/**\n * Parameters for installing a tool using npm.\n *\n * NOTE: This is an explicit interface (not z.infer) to ensure TypeScript fully resolves\n * the property names, which is required for proper `keyof` behavior in declaration files.\n */\nexport interface NpmInstallParams extends BaseInstallParams {\n /** The npm package name to install. */\n package?: string;\n /** The version or version range to install. */\n version?: string;\n /** Arguments to pass to the binary to check the version. */\n versionArgs?: string[];\n /** Regex to extract version from output. */\n versionRegex?: string;\n /** The package manager to use for installation. Defaults to `'npm'`. */\n packageManager?: 'npm' | 'bun';\n}\n",
|
|
209
|
+
"import {\n baseToolConfigWithPlatformsSchema,\n binaryConfigSchema,\n type InferToolConfigWithPlatforms,\n} from '@dotfiles/core';\nimport { z } from 'zod';\nimport { npmInstallParamsSchema } from './npmInstallParamsSchema';\n\nexport const npmToolConfigSchema = baseToolConfigWithPlatformsSchema.extend({\n /** Resolved tool configuration for the 'npm' installation method */\n installationMethod: z.literal('npm'),\n /** npm installation parameters */\n installParams: npmInstallParamsSchema,\n /** Binaries are typically required for this installation method */\n binaries: z.array(z.union([z.string().min(1), binaryConfigSchema])).min(1),\n});\n\n/** Resolved tool configuration for the 'npm' installation method. */\nexport type NpmToolConfig = InferToolConfigWithPlatforms<typeof npmToolConfigSchema>;\n",
|
|
210
|
+
"import type {\n IInstallContext,\n IInstallerPlugin,\n IInstallOptions,\n InstallResult,\n Shell,\n UpdateCheckResult,\n} from '@dotfiles/core';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { stripVersionPrefix } from '@dotfiles/utils';\nimport { installFromNpm } from './installFromNpm';\nimport { messages } from './log-messages';\nimport { type NpmInstallParams, npmInstallParamsSchema, type NpmToolConfig, npmToolConfigSchema } from './schemas';\n\nconst PLUGIN_VERSION = '1.0.0';\n\ntype NpmPluginMetadata = {\n method: 'npm';\n packageName: string;\n};\n\n/**\n * Installer plugin for tools installed via npm.\n *\n * This plugin handles installation of tools published as npm packages.\n * It installs packages globally using `npm install -g` or `bun install -g`,\n * making binaries available through the global bin directory.\n * The plugin is externally managed — the package manager controls binary placement.\n */\nexport class NpmInstallerPlugin implements\n IInstallerPlugin<\n 'npm',\n NpmInstallParams,\n NpmToolConfig,\n NpmPluginMetadata\n >\n{\n readonly method = 'npm';\n readonly displayName = 'npm Installer';\n readonly version = PLUGIN_VERSION;\n readonly externallyManaged = true;\n readonly paramsSchema = npmInstallParamsSchema;\n readonly toolConfigSchema = npmToolConfigSchema;\n\n /**\n * Creates a new NpmInstallerPlugin instance.\n *\n * @param shell - The shell executor for running commands.\n */\n constructor(private readonly shell: Shell) {}\n\n /**\n * Installs a tool using npm.\n *\n * @param toolName - The name of the tool to install.\n * @param toolConfig - The configuration for the npm tool.\n * @param context - The base installation context.\n * @param options - Optional installation options.\n * @param logger - The logger with tool context for logging operations.\n * @returns A promise that resolves to the installation result.\n */\n async install(\n toolName: string,\n toolConfig: NpmToolConfig,\n context: IInstallContext,\n options: IInstallOptions | undefined,\n logger: TsLogger,\n ): Promise<InstallResult<NpmPluginMetadata>> {\n const result = await installFromNpm(toolName, toolConfig, context, options, logger, this.shell);\n\n if (!result.success) {\n return {\n success: false,\n error: result.error,\n };\n }\n\n const installResult: InstallResult<NpmPluginMetadata> = {\n success: true,\n binaryPaths: result.binaryPaths,\n version: result.version,\n metadata: result.metadata,\n };\n\n return installResult;\n }\n\n /**\n * Resolves the version of an npm package before installation.\n *\n * Queries the npm registry for the latest version of the specified package\n * and returns a normalized version string.\n *\n * @param toolName - Name of the tool (for logging purposes)\n * @param toolConfig - Complete tool configuration including package name and version\n * @param _context - Installation context (not used but required by interface)\n * @param logger - Logger instance for debug output\n * @returns Normalized version string, or null if version cannot be resolved\n */\n async resolveVersion(\n toolName: string,\n toolConfig: NpmToolConfig,\n _context: IInstallContext,\n logger: TsLogger,\n ): Promise<string | null> {\n const subLogger: TsLogger = logger.getSubLogger({ name: 'resolveVersion' });\n\n try {\n const params = toolConfig.installParams;\n const packageName: string = params?.package || toolName;\n\n const result = await this.shell`npm view ${packageName} version`.quiet().noThrow();\n const version: string = result.stdout.toString().trim();\n\n if (!version) {\n subLogger.debug(messages.versionFetchFailed(packageName));\n return null;\n }\n\n const normalizedVersion: string = stripVersionPrefix(version);\n subLogger.debug(messages.versionFetched(packageName, normalizedVersion));\n return normalizedVersion;\n } catch (error) {\n subLogger.debug(messages.versionFetchFailed(toolName), error);\n return null;\n }\n }\n\n /**\n * Indicates whether this plugin supports version updates.\n *\n * @returns True, as npm packages can be updated.\n */\n supportsUpdate(): boolean {\n return true;\n }\n\n supportsUpdateCheck(): boolean {\n return true;\n }\n\n /**\n * Checks for available updates for an npm tool.\n *\n * @param toolName - The name of the tool to check.\n * @param toolConfig - The configuration for the npm tool.\n * @param _context - The base installation context (unused).\n * @param logger - The logger instance.\n * @returns A promise that resolves to the update check result.\n */\n async checkUpdate(\n toolName: string,\n toolConfig: NpmToolConfig,\n _context: IInstallContext,\n logger: TsLogger,\n ): Promise<UpdateCheckResult> {\n const subLogger: TsLogger = logger.getSubLogger({ name: 'checkUpdate', context: toolName });\n\n try {\n const params = toolConfig.installParams;\n const packageName: string = params?.package || toolName;\n\n const result = await this.shell`npm view ${packageName} version`.quiet().noThrow();\n const latestVersion: string = result.stdout.toString().trim();\n\n if (!latestVersion) {\n const failResult: UpdateCheckResult = {\n success: false,\n error: `Could not fetch latest version for npm package: ${packageName}`,\n };\n return failResult;\n }\n\n const configuredVersion: string = toolConfig.version || 'latest';\n\n if (configuredVersion === 'latest') {\n const successResult: UpdateCheckResult = {\n success: true,\n hasUpdate: false,\n currentVersion: latestVersion,\n latestVersion,\n };\n return successResult;\n }\n\n const successResult: UpdateCheckResult = {\n success: true,\n hasUpdate: configuredVersion !== latestVersion,\n currentVersion: configuredVersion,\n latestVersion,\n };\n return successResult;\n } catch (error) {\n subLogger.error(messages.updateCheckFailed(), error);\n const failResult: UpdateCheckResult = {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n };\n return failResult;\n }\n }\n\n /**\n * Indicates whether this plugin supports README fetching.\n *\n * @returns False, as npm packages don't have direct README URLs through this plugin.\n */\n supportsReadme(): boolean {\n return false;\n }\n}\n",
|
|
211
|
+
"import { createShell, type IInstallContext, type Shell } from '@dotfiles/core';\nimport { raw } from '@dotfiles/core';\nimport type { IResolvedFileSystem } from '@dotfiles/file-system';\nimport { withInstallErrorHandling } from '@dotfiles/installer';\nimport type { TsLogger } from '@dotfiles/logger';\nimport path from 'node:path';\nimport { messages } from './log-messages';\nimport type { ZshPluginToolConfig } from './schemas';\nimport type { IZshPluginInstallMetadata, ZshPluginInstallResult } from './types';\n\n/**\n * Installs a zsh plugin by cloning a git repository.\n *\n * This function handles the complete installation process for zsh plugins:\n * 1. Resolves the git URL from repo shorthand or full URL\n * 2. Determines the plugin name (from params or derived from URL)\n * 3. Clones or updates the repository in the staging directory\n * 4. Detects the plugin's source file (.plugin.zsh)\n * 5. Emits shell init content to source the plugin\n *\n * @param toolName - The name of the tool to install.\n * @param toolConfig - The configuration for the zsh plugin.\n * @param context - The base installation context.\n * @param parentLogger - The parent logger for creating sub-loggers.\n * @param fs - The file system interface.\n * @param shell - The shell executor for running git commands.\n * @returns A promise that resolves to the installation result.\n */\nexport async function installFromZshPlugin(\n toolName: string,\n toolConfig: ZshPluginToolConfig,\n context: IInstallContext,\n parentLogger: TsLogger,\n fs: IResolvedFileSystem,\n shell: Shell,\n installShell?: Shell,\n): Promise<ZshPluginInstallResult> {\n const logger = parentLogger.getSubLogger({ name: 'installFromZshPlugin' });\n logger.debug(messages.installing(toolName));\n\n const params = toolConfig.installParams;\n\n if (!params) {\n return {\n success: false,\n error: 'No install parameters provided',\n };\n }\n\n if (!params.repo && !params.url) {\n return {\n success: false,\n error: 'Either repo or url must be specified',\n };\n }\n\n const operation = async (): Promise<ZshPluginInstallResult> => {\n // Resolve git URL\n const gitUrl = params.url ?? `https://github.com/${params.repo}.git`;\n\n // Derive plugin name\n const pluginName = resolvePluginName(params.pluginName, params.repo, params.url);\n\n // Plugin destination path\n const pluginPath = path.join(context.stagingDir, pluginName);\n\n // Check if plugin already exists (update vs fresh clone)\n const exists = await fs.exists(pluginPath);\n const loggingShell = installShell ?? createShell({ logger, skipCommandLog: true });\n\n if (exists) {\n logger.debug(messages.updating(pluginPath));\n await updatePlugin(pluginPath, loggingShell);\n logger.info(messages.updateSuccess(pluginName));\n } else {\n logger.debug(messages.cloning(gitUrl, pluginPath));\n await clonePlugin(gitUrl, pluginPath, loggingShell);\n logger.info(messages.cloneSuccess(pluginName));\n }\n\n // Detect or use specified source file\n const sourceFile = await detectSourceFile(pluginPath, pluginName, params.source, fs, logger);\n if (!sourceFile) {\n return {\n success: false,\n error: `Could not detect plugin source file in ${pluginPath}. Specify 'source' parameter explicitly.`,\n };\n }\n\n // Get version from git\n const version = await getGitVersion(pluginPath, shell, logger);\n\n // Build the source path for shell init (uses currentDir which points to installed version)\n const sourceFilePath = path.join(context.currentDir, pluginName, sourceFile);\n\n const metadata: IZshPluginInstallMetadata = {\n method: 'zsh-plugin',\n pluginName,\n gitUrl,\n pluginPath,\n sourceFile,\n };\n\n return {\n success: true,\n binaryPaths: [],\n version,\n metadata,\n shellInit: {\n zsh: {\n // Use raw script to avoid subshell wrapping\n // source commands need to run in the main shell context\n scripts: [raw(`source \"${sourceFilePath}\"`)],\n },\n },\n };\n };\n\n return withInstallErrorHandling('zsh-plugin', toolName, logger, operation);\n}\n\n/**\n * Resolves the plugin name from parameters.\n */\nexport function resolvePluginName(\n pluginName: string | undefined,\n repo: string | undefined,\n url: string | undefined,\n): string {\n if (pluginName) {\n return pluginName;\n }\n\n if (repo) {\n // Extract repo name from user/repo\n return repo.split('/')[1] ?? repo;\n }\n\n if (url) {\n // Extract from URL: https://github.com/user/repo.git -> repo\n const urlPath = new URL(url).pathname;\n const basename = path.basename(urlPath, '.git');\n return basename;\n }\n\n throw new Error('Cannot determine plugin name');\n}\n\n/**\n * Detects or validates the plugin source file.\n * Checks common zsh plugin file patterns in order of preference.\n */\nasync function detectSourceFile(\n pluginPath: string,\n pluginName: string,\n explicitSource: string | undefined,\n fs: IResolvedFileSystem,\n parentLogger: TsLogger,\n): Promise<string | undefined> {\n const logger = parentLogger.getSubLogger({ name: 'detectSourceFile' });\n\n // If explicitly specified, validate it exists\n if (explicitSource) {\n const fullPath = path.join(pluginPath, explicitSource);\n if (await fs.exists(fullPath)) {\n logger.debug(messages.sourceFileDetected(explicitSource));\n return explicitSource;\n }\n logger.warn(messages.sourceFileNotFound(explicitSource));\n return undefined;\n }\n\n // Common zsh plugin file patterns in order of preference\n const candidates = [\n `${pluginName}.plugin.zsh`,\n `${pluginName}.zsh`,\n 'init.zsh',\n 'plugin.zsh',\n `${pluginName}.zsh-theme`,\n ];\n\n for (const candidate of candidates) {\n const fullPath = path.join(pluginPath, candidate);\n if (await fs.exists(fullPath)) {\n logger.debug(messages.sourceFileDetected(candidate));\n return candidate;\n }\n }\n\n return undefined;\n}\n\n/**\n * Clones a git repository.\n */\nasync function clonePlugin(gitUrl: string, destPath: string, shell: Shell): Promise<void> {\n await shell`git clone --depth 1 ${gitUrl} ${destPath}`;\n}\n\n/**\n * Updates an existing git repository.\n */\nasync function updatePlugin(pluginPath: string, shell: Shell): Promise<void> {\n await shell`git -C ${pluginPath} pull --ff-only`;\n}\n\n/**\n * Gets the git commit hash or tag as version.\n */\nasync function getGitVersion(pluginPath: string, shell: Shell, parentLogger: TsLogger): Promise<string | undefined> {\n const logger = parentLogger.getSubLogger({ name: 'getGitVersion' });\n\n try {\n // Try to get the latest tag first\n const tagResult = await shell`git -C ${pluginPath} describe --tags --abbrev=0`.quiet().noThrow();\n if (tagResult.code === 0) {\n const version = tagResult.stdout.trim();\n logger.debug(messages.versionDetected(version));\n return version;\n }\n\n // Fall back to short commit hash\n const hashResult = await shell`git -C ${pluginPath} rev-parse --short HEAD`.quiet().noThrow();\n if (hashResult.code === 0) {\n const version = hashResult.stdout.trim();\n logger.debug(messages.versionDetected(version));\n return version;\n }\n\n return undefined;\n } catch {\n return undefined;\n }\n}\n",
|
|
212
|
+
"import { createSafeLogMessage, type SafeLogMessage, type SafeLogMessageMap } from '@dotfiles/logger';\n\nexport const messages = {\n installing: (toolName: string): SafeLogMessage => createSafeLogMessage(`Installing zsh plugin: ${toolName}`),\n cloning: (url: string, dest: string): SafeLogMessage => createSafeLogMessage(`Cloning ${url} to ${dest}`),\n updating: (pluginPath: string): SafeLogMessage => createSafeLogMessage(`Updating existing plugin at ${pluginPath}`),\n cloneSuccess: (pluginName: string): SafeLogMessage => createSafeLogMessage(`Cloned plugin: ${pluginName}`),\n updateSuccess: (pluginName: string): SafeLogMessage => createSafeLogMessage(`Updated plugin: ${pluginName}`),\n cloneFailed: (url: string): SafeLogMessage => createSafeLogMessage(`Failed to clone: ${url}`),\n updateFailed: (pluginPath: string): SafeLogMessage => createSafeLogMessage(`Failed to update: ${pluginPath}`),\n versionDetected: (version: string): SafeLogMessage => createSafeLogMessage(`Detected version: ${version}`),\n sourceFileDetected: (file: string): SafeLogMessage => createSafeLogMessage(`Detected source file: ${file}`),\n sourceFileNotFound: (file: string): SafeLogMessage =>\n createSafeLogMessage(`Specified source file not found: ${file}`),\n noParamsProvided: (): SafeLogMessage => createSafeLogMessage('No install parameters provided'),\n invalidParams: (): SafeLogMessage => createSafeLogMessage('Either repo or url must be specified'),\n} as const satisfies SafeLogMessageMap;\n",
|
|
213
|
+
"import type { BaseInstallParams } from '@dotfiles/core';\nimport { baseInstallParamsSchema } from '@dotfiles/core';\nimport { z } from 'zod';\n\n/**\n * Parameters for a \"zsh-plugin\" installation method.\n * This method clones git repositories and automatically sources the plugin.\n */\nexport const zshPluginInstallParamsSchema = baseInstallParamsSchema.extend({\n /**\n * GitHub repository in `user/repo` format.\n * Expanded to `https://github.com/{repo}.git`.\n * Either `repo` or `url` must be specified.\n */\n repo: z.string().regex(/^[^/]+\\/[^/]+$/, 'Must be in user/repo format').optional(),\n /**\n * Full git URL for non-GitHub repositories.\n * Either `repo` or `url` must be specified.\n */\n url: z.string().url().optional(),\n /**\n * Custom plugin name for the cloned directory.\n * Defaults to the repository name (last segment of the path).\n */\n pluginName: z.string().min(1).optional(),\n /**\n * Path to the plugin's main source file relative to the plugin directory.\n * If not specified, auto-detects from common patterns:\n * - {pluginName}.plugin.zsh\n * - {pluginName}.zsh\n * - init.zsh\n * - plugin.zsh\n */\n source: z.string().min(1).optional(),\n /**\n * Whether to automatically source the plugin in shell init.\n * When true (default), the plugin is automatically sourced.\n * Set to false if you want to manually configure sourcing via .zsh().\n * @default true\n */\n auto: z.boolean().default(true),\n}).refine(\n (data) => data.repo || data.url,\n { message: 'Either repo or url must be specified' },\n);\n\n/**\n * Parameters for a \"zsh-plugin\" installation method.\n *\n * NOTE: This is an explicit interface (not z.infer) to ensure TypeScript fully resolves\n * the property names, which is required for proper `keyof` behavior in declaration files.\n */\nexport interface ZshPluginInstallParams extends BaseInstallParams {\n /** GitHub repository in `user/repo` format. Either `repo` or `url` must be specified. */\n repo?: string;\n /** Full git URL for non-GitHub repositories. Either `repo` or `url` must be specified. */\n url?: string;\n /** Custom plugin name for the cloned directory. */\n pluginName?: string;\n /** Path to the plugin's main source file relative to the plugin directory. */\n source?: string;\n /** Whether to automatically source the plugin in shell init. @default true */\n auto?: boolean;\n}\n",
|
|
214
|
+
"import { baseToolConfigWithPlatformsSchema, type InferToolConfigWithPlatforms } from '@dotfiles/core';\nimport { z } from 'zod';\nimport { zshPluginInstallParamsSchema } from './zshPluginInstallParamsSchema';\n\nexport const zshPluginToolConfigSchema = baseToolConfigWithPlatformsSchema.extend({\n /** Resolved tool configuration for the 'zsh-plugin' installation method */\n installationMethod: z.literal('zsh-plugin'),\n /** Zsh plugin installation parameters */\n installParams: zshPluginInstallParamsSchema,\n /** Binaries are optional for zsh plugins (usually none) */\n binaries: z.array(z.string().min(1)).default([]),\n});\n\n/** Resolved tool configuration for the 'zsh-plugin' installation method. */\nexport type ZshPluginToolConfig = InferToolConfigWithPlatforms<typeof zshPluginToolConfigSchema>;\n",
|
|
215
|
+
"import type {\n IInstallContext,\n IInstallerPlugin,\n IInstallOptions,\n InstallResult,\n IPluginShellInit,\n Shell,\n ShellType,\n} from '@dotfiles/core';\nimport { raw } from '@dotfiles/core';\nimport type { IResolvedFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport path from 'node:path';\nimport { installFromZshPlugin, resolvePluginName } from './installFromZshPlugin';\nimport {\n type ZshPluginInstallParams,\n zshPluginInstallParamsSchema,\n type ZshPluginToolConfig,\n zshPluginToolConfigSchema,\n} from './schemas';\n\nconst PLUGIN_VERSION = '1.0.0';\n\ntype ZshPluginPluginMetadata = {\n method: 'zsh-plugin';\n pluginName: string;\n gitUrl: string;\n pluginPath: string;\n};\n\n/**\n * Installer plugin for zsh plugins cloned via git.\n *\n * This plugin handles installation of zsh plugins by cloning git repositories\n * into a plugins directory. It supports both GitHub shorthand (user/repo) and\n * full git URLs for non-GitHub repositories.\n *\n * Plugins are typically loaded by adding them to the plugins array in .zshrc\n * or by sourcing them directly.\n */\nexport class ZshPluginInstallerPlugin implements\n IInstallerPlugin<\n 'zsh-plugin',\n ZshPluginInstallParams,\n ZshPluginToolConfig,\n ZshPluginPluginMetadata\n >\n{\n readonly method = 'zsh-plugin';\n readonly displayName = 'Zsh Plugin Installer';\n readonly version = PLUGIN_VERSION;\n readonly paramsSchema = zshPluginInstallParamsSchema;\n readonly toolConfigSchema = zshPluginToolConfigSchema;\n readonly externallyManaged = false;\n\n /**\n * Creates a new ZshPluginInstallerPlugin instance.\n *\n * @param fs - The file system interface for file operations.\n * @param shell - The shell executor for running git commands.\n */\n constructor(\n private readonly fs: IResolvedFileSystem,\n private readonly shell: Shell,\n ) {}\n\n /**\n * Installs a zsh plugin by cloning its git repository.\n *\n * @param toolName - The name of the tool to install.\n * @param toolConfig - The configuration for the zsh plugin.\n * @param context - The base installation context.\n * @param _options - Optional installation options (currently unused).\n * @param logger - The logger with tool context for logging operations.\n * @returns A promise that resolves to the installation result.\n */\n async install(\n toolName: string,\n toolConfig: ZshPluginToolConfig,\n context: IInstallContext,\n _options: IInstallOptions | undefined,\n logger: TsLogger,\n ): Promise<InstallResult<ZshPluginPluginMetadata>> {\n const result = await installFromZshPlugin(\n toolName,\n toolConfig,\n context,\n logger,\n this.fs,\n this.shell,\n );\n\n if (!result.success) {\n return {\n success: false,\n error: result.error,\n };\n }\n\n return {\n success: true,\n binaryPaths: result.binaryPaths,\n version: result.version,\n metadata: result.metadata,\n shellInit: result.shellInit,\n };\n }\n\n /**\n * Indicates whether this plugin supports version update checking.\n *\n * @returns True, as git repositories can check for updates.\n */\n supportsUpdate(): boolean {\n return true;\n }\n\n supportsUpdateCheck(): boolean {\n return false;\n }\n\n /**\n * Indicates whether this plugin supports README fetching.\n *\n * @returns False, as zsh plugins don't have standardized README URLs.\n */\n supportsReadme(): boolean {\n return false;\n }\n\n /**\n * Gets shell initialization content for an already-installed zsh plugin.\n *\n * This is called when the tool is already installed and the installer skips\n * the installation process. Returns the source command for the plugin.\n *\n * @param _toolName - Name of the tool (unused, pluginName derived from params).\n * @param toolConfig - Complete tool configuration.\n * @param installPath - Path where the tool is installed (currentDir).\n * @returns Shell initialization content with the source command.\n */\n getShellInit(\n _toolName: string,\n toolConfig: ZshPluginToolConfig,\n installPath: string,\n ): Partial<Record<ShellType, IPluginShellInit>> | undefined {\n const params = toolConfig.installParams;\n if (!params) {\n return undefined;\n }\n\n // Derive plugin name from params\n const pluginName = resolvePluginName(params.pluginName, params.repo, params.url);\n\n // Use explicit source or default pattern\n // For already-installed case, we trust the first pattern since plugin was detected on install\n const source = params.source ?? `${pluginName}.plugin.zsh`;\n const sourceFilePath = path.join(installPath, pluginName, source);\n\n return {\n zsh: {\n scripts: [raw(`source \"${sourceFilePath}\"`)],\n },\n };\n }\n}\n",
|
|
216
|
+
"import type { ProjectConfig } from '@dotfiles/config';\nimport type { ISystemInfo, ToolConfig } from '@dotfiles/core';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { TrackedFileSystem } from '@dotfiles/registry/file';\nimport type { IToolInstallationRegistry } from '@dotfiles/registry/tool';\nimport { dedentString, getCliBinPath, resolvePlatformConfig } from '@dotfiles/utils';\nimport path from 'node:path';\nimport type { IGenerateShimsOptions, IShimGenerator } from './IShimGenerator';\nimport { messages } from './log-messages';\n\n/**\n * Generates executable shims for tools.\n *\n * The core logic of the generated Bash shims is to first check for the\n * existence of the target binary. If it's not found, the shim executes the main\n * generator CLI's `install` command to perform an on-demand installation.\n * After a successful installation, it proceeds to execute the actual tool.\n * This ensures that tools are available when needed without requiring\n * them to be installed beforehand.\n *\n * For externally managed tools (e.g., Homebrew), symlinks to the real binaries\n * are created instead of bash shim scripts. This avoids PATH clobbering issues\n * where the shim would intercept the binary lookup.\n */\nexport class ShimGenerator implements IShimGenerator {\n private readonly fs: IFileSystem;\n private readonly config: ProjectConfig;\n private readonly logger: TsLogger;\n private readonly systemInfo: ISystemInfo;\n private readonly externallyManagedMethods: Set<string>;\n private readonly missingBinaryMessagesByMethod: Map<string, string>;\n private readonly toolInstallationRegistry?: IToolInstallationRegistry;\n\n private isConfigurationOnlyToolConfig(toolConfig: ToolConfig): boolean {\n const isManual = toolConfig.installationMethod === 'manual';\n const hasNoInstallParams = !toolConfig.installParams || Object.keys(toolConfig.installParams).length === 0;\n const hasNoBinaries = !toolConfig.binaries || toolConfig.binaries.length === 0;\n const result: boolean = isManual && hasNoInstallParams && hasNoBinaries;\n return result;\n }\n\n /**\n * Creates a new ShimGenerator instance.\n *\n * @param parentLogger - The parent logger for creating sub-loggers.\n * @param fileSystem - The file system interface for file operations.\n * @param config - The YAML configuration containing paths and settings.\n * @param systemInfo - The current system information for platform resolution.\n * @param externallyManagedMethods - Set of installation method names that are externally managed.\n * @param missingBinaryMessagesByMethod - Optional per-method messages shown when binary is missing post-install.\n * @param toolInstallationRegistry - Registry for checking if tools are already installed.\n */\n constructor(\n parentLogger: TsLogger,\n fileSystem: IFileSystem,\n config: ProjectConfig,\n systemInfo: ISystemInfo,\n externallyManagedMethods?: Set<string>,\n missingBinaryMessagesByMethod?: Map<string, string>,\n toolInstallationRegistry?: IToolInstallationRegistry,\n ) {\n const logger = parentLogger.getSubLogger({ name: 'ShimGenerator' });\n this.logger = logger;\n const constructorLogger = logger.getSubLogger({ name: 'constructor' });\n constructorLogger.debug(messages.constructor.initialized());\n this.fs = fileSystem;\n this.config = config;\n this.systemInfo = systemInfo;\n this.externallyManagedMethods = externallyManagedMethods ?? new Set();\n this.missingBinaryMessagesByMethod = missingBinaryMessagesByMethod ?? new Map();\n this.toolInstallationRegistry = toolInstallationRegistry;\n }\n\n /**\n * @inheritdoc IShimGenerator.generate\n */\n async generate(toolConfigs: Record<string, ToolConfig>, options?: IGenerateShimsOptions): Promise<string[]> {\n const logger = this.logger.getSubLogger({ name: 'generate' });\n const generatedShimPaths: string[] = [];\n\n for (const toolName in toolConfigs) {\n if (Object.hasOwn(toolConfigs, toolName)) {\n const toolConfig = toolConfigs[toolName];\n if (toolConfig) {\n const shimPaths = await this.generateForTool(toolName, toolConfig, options);\n generatedShimPaths.push(...shimPaths);\n } else {\n logger.debug(messages.generate.missingToolConfig(toolName));\n }\n }\n }\n return generatedShimPaths;\n }\n\n /**\n * @inheritdoc IShimGenerator.generateForTool\n */\n async generateForTool(toolName: string, toolConfig: ToolConfig, options?: IGenerateShimsOptions): Promise<string[]> {\n const logger = this.logger.getSubLogger({ name: 'generateForTool', context: toolName });\n // Create a tool-specific TrackedFileSystem if we have a TrackedFileSystem instance\n const toolFs = this.fs instanceof TrackedFileSystem ? this.fs.withToolName(toolName) : this.fs;\n\n const toolFileSystemName = toolFs.constructor.name;\n logger.debug(messages.generateForTool.started(toolName, toolFileSystemName));\n\n // Resolve platform-specific configurations before processing\n const resolvedConfig = resolvePlatformConfig(toolConfig, this.systemInfo);\n\n const generatedShimPaths: string[] = [];\n const overwrite = options?.overwrite ?? false;\n const overwriteConflicts = options?.overwriteConflicts ?? false;\n\n if (this.isConfigurationOnlyToolConfig(resolvedConfig)) {\n return generatedShimPaths;\n }\n\n // Manual tools without binaryPath can't produce binaries in the staging dir.\n // The command should come from shell functions, not a shim.\n if (resolvedConfig.installationMethod === 'manual' && !resolvedConfig.installParams?.binaryPath) {\n logger.warn(messages.generateForTool.skippedManualNoBinaryPath());\n return generatedShimPaths;\n }\n\n // For externally managed tools (e.g., Homebrew), only generate shims if not yet installed.\n // After installation, the tool's binaries are in their own PATH location (e.g., /opt/homebrew/bin).\n if (this.externallyManagedMethods.has(resolvedConfig.installationMethod)) {\n if (!this.toolInstallationRegistry) {\n logger.debug(messages.generateForTool.skippedExternallyManaged(toolName, resolvedConfig.installationMethod));\n return generatedShimPaths;\n }\n const isInstalled = await this.toolInstallationRegistry.isToolInstalled(toolName);\n if (isInstalled) {\n logger.debug(messages.generateForTool.skippedAlreadyInstalled(toolName));\n return generatedShimPaths;\n }\n }\n\n // Get list of binaries to generate shims for\n // If no binaries are defined (i.e., .bin() was never called), skip shim generation entirely\n const binaries = resolvedConfig.binaries && resolvedConfig.binaries.length > 0\n ? resolvedConfig.binaries\n : [];\n\n if (binaries.length === 0) {\n logger.debug(messages.generateForTool.skippedNoBinaries(toolName));\n return generatedShimPaths;\n }\n\n const binaryNames = binaries.map((binary) => (typeof binary === 'string' ? binary : binary.name));\n\n // Generate a shim for each binary\n for (const binaryName of binaryNames) {\n const shimPath = await this.generateShimForBinary(\n toolFs,\n toolName,\n resolvedConfig,\n binaryName,\n overwrite,\n overwriteConflicts,\n );\n if (shimPath) {\n generatedShimPaths.push(shimPath);\n }\n }\n\n return generatedShimPaths;\n }\n\n /**\n * Generates a shim file for a specific binary.\n *\n * @param toolFs - The file system interface (may be tool-specific tracked FS).\n * @param toolName - The name of the tool.\n * @param _toolConfig - The tool configuration (unused currently).\n * @param binaryName - The name of the binary to generate a shim for.\n * @param overwrite - Whether to overwrite existing shims created by the generator.\n * @param overwriteConflicts - Whether to overwrite conflicting files not created by the generator.\n * @returns The path to the generated shim, or null if generation was skipped.\n */\n private async generateShimForBinary(\n toolFs: IFileSystem,\n toolName: string,\n toolConfig: ToolConfig,\n binaryName: string,\n overwrite: boolean,\n overwriteConflicts: boolean,\n ): Promise<string | null> {\n const logger = this.logger.getSubLogger({ name: 'generateShimForBinary' });\n const shimDir = this.config.paths.targetDir;\n const shimFilePath = path.join(shimDir, binaryName);\n\n logger.debug(messages.generateShim.resolvedShimPath(shimFilePath));\n\n if (await toolFs.exists(shimFilePath)) {\n // Check if the existing file is one of our shims\n const isOurShim = await this.isGeneratedShim(toolFs, shimFilePath);\n\n if (!isOurShim) {\n if (!overwriteConflicts) {\n // Not our shim and overwriteConflicts is false - log error and skip\n logger.error(messages.generateShim.conflictingFile(toolName, shimFilePath));\n return null;\n }\n // overwriteConflicts is true - continue to overwrite the conflicting file\n logger.debug(messages.generateShim.overwritingConflict(shimFilePath));\n } else if (!overwrite) {\n // It's our shim but overwrite is false - skip silently\n logger.debug(messages.generateShim.existingShim(shimFilePath));\n return null;\n }\n\n // It's our shim and overwrite is true - continue to overwrite\n }\n\n // Use the stable current symlink folder for execution\n const toolBinaryPath = path.join(this.config.paths.binariesDir, toolName, 'current', binaryName);\n\n logger.debug(messages.generateShim.resolvedBinaryPath(toolName, binaryName, toolBinaryPath));\n\n const envVarSuffix = toolName.toUpperCase().replace(/[^A-Z0-9_]/g, '_');\n\n const missingBinaryMessage = this.missingBinaryMessagesByMethod.get(toolConfig.installationMethod);\n const missingBinaryMessageCommand = missingBinaryMessage\n ? `echo \"${missingBinaryMessage.replaceAll('\"', '\\\\\"')}\" >&2`\n : 'echo \"Installation completed but binary not found at: $TOOL_EXECUTABLE\" >&2';\n\n const shimContent = dedentString(`\n #!/usr/bin/env bash\n # Shim for ${binaryName}\n # Generated by Dotfiles Management Tool\n\n set -euo pipefail\n\n TOOL_NAME=\"${toolName}\"\n BINARY_NAME=\"${binaryName}\"\n TOOL_EXECUTABLE=\"${toolBinaryPath}\"\n GENERATOR_CLI_EXECUTABLE=\"${getCliBinPath()}\"\n CONFIG_PATH=\"${this.config.configFilePath}\"\n\n # Check for recursion\n RECURSION_ENV_VAR=\"DOTFILES_INSTALLING_${envVarSuffix}\"\n\n if [ -n \"\\${!RECURSION_ENV_VAR:-}\" ]; then\n echo \"Recursive installation detected for $TOOL_NAME. Aborting to prevent infinite loop.\" >&2\n exit 1\n fi\n\n # Record shim usage in the background (non-blocking)\n if [ \"\\${DOTFILES_USAGE_TRACKING:-1}\" != \"0\" ]; then\n # Use eval to properly handle GENERATOR_CLI_EXECUTABLE with spaces\n eval \"$GENERATOR_CLI_EXECUTABLE\" @track-usage '\"$TOOL_NAME\"' '\"$BINARY_NAME\"' --config '\"$CONFIG_PATH\"' >/dev/null 2>&1 &\n fi\n\n # Check if the first argument is @update\n if [ $# -gt 0 ] && [ \"$1\" = \"@update\" ]; then\n echo \"Updating $TOOL_NAME to latest version...\"\n # Use eval to properly handle GENERATOR_CLI_EXECUTABLE with spaces\n eval \"$GENERATOR_CLI_EXECUTABLE\" update --shim-mode --config '\"$CONFIG_PATH\"' '\"$TOOL_NAME\"'\n exit $?\n fi\n\n # Check if tool exists and execute it\n if [ -x \"$TOOL_EXECUTABLE\" ]; then\n exec \"$TOOL_EXECUTABLE\" \"$@\"\n else\n # Tool not found, try to install it\n # Use eval to properly handle GENERATOR_CLI_EXECUTABLE with spaces\n # Let stderr (progress bars) pass through to the user\n # Temporarily disable set -e to handle install failures gracefully\n set +e\n eval \"$GENERATOR_CLI_EXECUTABLE\" install --shim-mode --config '\"$CONFIG_PATH\"' '\"$TOOL_NAME\"'\n install_exit_code=$?\n set -e\n\n if [ $install_exit_code -eq 0 ]; then\n # Installation successful, try to execute binary again\n if [ -x \"$TOOL_EXECUTABLE\" ]; then\n exec \"$TOOL_EXECUTABLE\" \"$@\"\n else\n ${missingBinaryMessageCommand}\n exit 1\n fi\n else\n # Installation failed, exit with the same code\n exit $install_exit_code\n fi\n fi\n `);\n\n logger.debug(messages.generateShim.generatedContent(binaryName));\n\n // Directory creation must be attributed to the system, not the tool.\n // This enables clean undo semantics (a future delete command can replay tool changes separately).\n await this.fs.ensureDir(path.dirname(shimFilePath));\n await toolFs.writeFile(shimFilePath, shimContent);\n\n // Only chmod if file doesn't already have the correct permissions\n const desiredMode = 0o755; // rwxr-xr-x\n try {\n const stats = await toolFs.stat(shimFilePath);\n const currentMode = stats.mode & 0o777; // Extract permission bits\n if (currentMode !== desiredMode) {\n await toolFs.chmod(shimFilePath, desiredMode);\n }\n } catch {\n // If we can't check permissions, try to set them anyway\n await toolFs.chmod(shimFilePath, desiredMode);\n }\n logger.debug(messages.generateShim.success(binaryName, shimFilePath, toolFs.constructor.name));\n return shimFilePath;\n }\n\n /**\n * Checks if a file is a shim generated by the dotfiles management tool.\n *\n * @param fs - The filesystem interface to use.\n * @param filePath - The path to the file to check.\n * @returns True if the file is one of our generated shims, false otherwise.\n */\n private async isGeneratedShim(fs: IFileSystem, filePath: string): Promise<boolean> {\n try {\n // Symlinks created by installer plugins at install time are also \"ours\"\n try {\n const stats = await fs.lstat(filePath);\n if (stats.isSymbolicLink()) {\n return true;\n }\n } catch {\n // lstat failed — fall through to content check\n }\n\n const content = await fs.readFile(filePath, 'utf8');\n // Check for our distinctive header comment\n return content.includes('# Generated by Dotfiles Management Tool');\n } catch {\n // If we can't read the file, assume it's not our shim\n return false;\n }\n }\n}\n",
|
|
217
|
+
"import { createSafeLogMessage, type SafeLogMessageMap } from '@dotfiles/logger';\n\nexport const messages = {\n constructor: {\n initialized: () => createSafeLogMessage('ShimGenerator initialized'),\n } satisfies SafeLogMessageMap,\n generate: {\n missingToolConfig: (toolName: string) =>\n createSafeLogMessage(`Skipping shim generation for ${toolName} because configuration is missing`),\n } satisfies SafeLogMessageMap,\n generateForTool: {\n started: (toolName: string, fileSystemName: string) =>\n createSafeLogMessage(`Generating shims for ${toolName} using ${fileSystemName}`),\n skippedExternallyManaged: (toolName: string, method: string) =>\n createSafeLogMessage(`Skipping shim generation for ${toolName} (externally managed via ${method})`),\n skippedNoBinaries: (toolName: string) =>\n createSafeLogMessage(`Skipping shim generation for ${toolName} (no binaries defined)`),\n skippedAlreadyInstalled: (toolName: string) =>\n createSafeLogMessage(`Skipping shim generation for ${toolName} (already installed)`),\n skippedManualNoBinaryPath: () =>\n createSafeLogMessage(\n 'Skipping shim generation (manual tool has .bin() but no binaryPath — use shell functions instead)',\n ),\n } satisfies SafeLogMessageMap,\n generateShim: {\n resolvedShimPath: (shimPath: string) => createSafeLogMessage(`Resolved shim output path ${shimPath}`),\n existingShim: (shimPath: string) =>\n createSafeLogMessage(`Existing shim found at ${shimPath}; overwrite is disabled`),\n conflictingFile: (toolName: string, shimPath: string) =>\n createSafeLogMessage(\n `Cannot create shim for \"${toolName}\": conflicting file exists at ${shimPath}. Use --overwrite to replace it.`,\n ),\n overwritingConflict: (shimPath: string) => createSafeLogMessage(`Overwriting conflicting file at ${shimPath}`),\n resolvedBinaryPath: (toolName: string, binaryName: string, binaryPath: string) =>\n createSafeLogMessage(`Resolved binary path for ${toolName}/${binaryName} to ${binaryPath}`),\n generatedContent: (binaryName: string) => createSafeLogMessage(`Generated shim content for ${binaryName}`),\n success: (binaryName: string, shimPath: string, fileSystemName: string) =>\n createSafeLogMessage(`Generated shim ${binaryName} at ${shimPath} using ${fileSystemName}`),\n } satisfies SafeLogMessageMap,\n} as const;\n",
|
|
218
|
+
"import type { ProjectConfig } from '@dotfiles/config';\nimport type { ISystemInfo, ToolConfig } from '@dotfiles/core';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { TrackedFileSystem } from '@dotfiles/registry/file';\nimport { expandToolConfigPath, resolvePlatformConfig } from '@dotfiles/utils';\nimport path from 'node:path';\nimport type { CopyOperationResult, ICopyGenerator, IGenerateCopiesOptions } from './ICopyGenerator';\nimport { messages } from './log-messages';\n\n/** Configuration for a single copy mapping from source to target */\ninterface ICopyConfig {\n source: string;\n target: string;\n}\n\n/** Status values for successful copy creation operations */\ntype CopyCreationStatus = 'created' | 'updated_target' | 'backed_up';\n\n/**\n * Service that copies files for dotfiles.\n *\n * This class handles copying files and directories from source locations (in the dotfiles repository)\n * to target locations (typically in the user's home directory). It supports overwriting\n * existing files, creating backups, and tracking which copies belong to which tools.\n */\nexport class CopyGenerator implements ICopyGenerator {\n private readonly fs: IFileSystem;\n private readonly projectConfig: ProjectConfig;\n private readonly systemInfo: ISystemInfo;\n private readonly logger: TsLogger;\n\n constructor(parentLogger: TsLogger, fileSystem: IFileSystem, projectConfig: ProjectConfig, systemInfo: ISystemInfo) {\n this.fs = fileSystem;\n this.projectConfig = projectConfig;\n this.systemInfo = systemInfo;\n this.logger = parentLogger.getSubLogger({ name: 'CopyGenerator' });\n }\n\n async generate(\n toolConfigs: Record<string, ToolConfig>,\n options: IGenerateCopiesOptions = {},\n ): Promise<CopyOperationResult[]> {\n const logger = this.logger.getSubLogger({ name: 'generate' });\n const results: CopyOperationResult[] = [];\n\n for (const toolName in toolConfigs) {\n const toolConfig = toolConfigs[toolName];\n if (!toolConfig) {\n continue;\n }\n\n const resolvedToolConfig = resolvePlatformConfig(toolConfig, this.systemInfo);\n if (!this.shouldProcessTool(resolvedToolConfig, toolName, logger)) {\n continue;\n }\n\n const toolLogger = logger.getSubLogger({ context: toolName });\n const toolFs = this.fs instanceof TrackedFileSystem ? this.fs.withToolName(toolName) : this.fs;\n toolLogger.debug(messages.copy.processingTool(toolName));\n\n for (const copyConfig of resolvedToolConfig.copies) {\n const result = await this.processCopy(resolvedToolConfig, copyConfig, toolFs, options, toolLogger);\n results.push(result);\n }\n }\n\n return results;\n }\n\n private shouldProcessTool(\n toolConfig: ToolConfig | undefined,\n toolName: string,\n logger: TsLogger,\n ): toolConfig is ToolConfig & { copies: NonNullable<ToolConfig['copies']>; } {\n const methodLogger = logger.getSubLogger({ name: 'shouldProcessTool' });\n if (!toolConfig) {\n methodLogger.debug(messages.copy.missingToolConfig(toolName));\n return false;\n }\n if (!toolConfig.copies || toolConfig.copies.length === 0) {\n return false;\n }\n return true;\n }\n\n private async processCopy(\n toolConfig: ToolConfig,\n copyConfig: ICopyConfig,\n toolFs: IFileSystem,\n options: IGenerateCopiesOptions,\n logger: TsLogger,\n ): Promise<CopyOperationResult> {\n const methodLogger = logger.getSubLogger({ name: 'processCopy' });\n const { overwrite = false, backup = false } = options;\n const sourceAbsPath = expandToolConfigPath(\n toolConfig.configFilePath,\n copyConfig.source,\n this.projectConfig,\n this.systemInfo,\n );\n const targetAbsPath = expandToolConfigPath(\n toolConfig.configFilePath,\n copyConfig.target,\n this.projectConfig,\n this.systemInfo,\n );\n\n methodLogger.debug(\n messages.copy.copyDetails(copyConfig.source, sourceAbsPath, copyConfig.target, targetAbsPath),\n );\n\n if (!(await toolFs.exists(sourceAbsPath))) {\n methodLogger.error(messages.copy.sourceMissing(toolConfig.name, sourceAbsPath));\n return {\n success: false,\n sourcePath: sourceAbsPath,\n targetPath: targetAbsPath,\n status: 'failed',\n error: messages.copy.sourceMissing(toolConfig.name, sourceAbsPath),\n };\n }\n\n const targetExists = await toolFs.exists(targetAbsPath);\n if (targetExists) {\n methodLogger.debug(messages.copy.targetExists(targetAbsPath));\n\n if (!overwrite) {\n methodLogger.debug(messages.copy.skipExistingTarget(targetAbsPath));\n return {\n success: true,\n sourcePath: sourceAbsPath,\n targetPath: targetAbsPath,\n status: 'skipped_exists',\n };\n }\n\n // Handle overwrite with optional backup\n const overwriteResult = await this.handleOverwrite(targetAbsPath, toolFs, backup, methodLogger);\n if (overwriteResult.failed) {\n return {\n success: false,\n sourcePath: sourceAbsPath,\n targetPath: targetAbsPath,\n status: 'failed',\n error: overwriteResult.error,\n };\n }\n\n return this.performCopy(sourceAbsPath, targetAbsPath, toolFs, overwriteResult.status, methodLogger);\n }\n\n return this.performCopy(sourceAbsPath, targetAbsPath, toolFs, 'created', methodLogger);\n }\n\n private async handleOverwrite(\n targetAbsPath: string,\n toolFs: IFileSystem,\n shouldBackup: boolean,\n logger: TsLogger,\n ): Promise<{ failed: false; status: CopyCreationStatus; } | { failed: true; error: string; }> {\n if (shouldBackup) {\n const backupPath = `${targetAbsPath}.bak`;\n try {\n if (await toolFs.exists(backupPath)) {\n await toolFs.rm(backupPath, { recursive: true, force: true });\n }\n await toolFs.rename(targetAbsPath, backupPath);\n return { failed: false, status: 'backed_up' };\n } catch {\n const errorMsg = messages.filesystem.backupFailed(targetAbsPath);\n logger.error(errorMsg);\n return { failed: true, error: errorMsg };\n }\n }\n\n try {\n const targetStat = await toolFs.stat(targetAbsPath);\n if (targetStat.isDirectory()) {\n await toolFs.rm(targetAbsPath, { recursive: true, force: true });\n } else {\n await toolFs.rm(targetAbsPath, { force: true });\n }\n return { failed: false, status: 'updated_target' };\n } catch {\n const errorMsg = messages.filesystem.deleteFailed(targetAbsPath);\n logger.error(errorMsg);\n return { failed: true, error: errorMsg };\n }\n }\n\n private async performCopy(\n sourceAbsPath: string,\n targetAbsPath: string,\n toolFs: IFileSystem,\n status: CopyCreationStatus,\n logger: TsLogger,\n ): Promise<CopyOperationResult> {\n const methodLogger = logger.getSubLogger({ name: 'performCopy' });\n const targetDir = path.dirname(targetAbsPath);\n\n try {\n await toolFs.ensureDir(targetDir);\n } catch {\n const errorMsg = messages.filesystem.directoryCreateFailed(targetDir);\n methodLogger.error(errorMsg);\n return {\n success: false,\n sourcePath: sourceAbsPath,\n targetPath: targetAbsPath,\n status: 'failed',\n error: errorMsg,\n };\n }\n\n try {\n const sourceStat = await toolFs.stat(sourceAbsPath);\n if (sourceStat.isDirectory()) {\n await this.copyDirectory(sourceAbsPath, targetAbsPath, toolFs);\n } else {\n await toolFs.copyFile(sourceAbsPath, targetAbsPath);\n }\n\n return {\n success: true,\n sourcePath: sourceAbsPath,\n targetPath: targetAbsPath,\n status,\n };\n } catch {\n const errorMsg = messages.copy.copyFailed(sourceAbsPath, targetAbsPath);\n methodLogger.error(errorMsg);\n return {\n success: false,\n sourcePath: sourceAbsPath,\n targetPath: targetAbsPath,\n status: 'failed',\n error: errorMsg,\n };\n }\n }\n\n private async copyDirectory(sourceDir: string, targetDir: string, toolFs: IFileSystem): Promise<void> {\n await toolFs.ensureDir(targetDir);\n const entries = await toolFs.readdir(sourceDir);\n\n for (const entry of entries) {\n const sourcePath = path.join(sourceDir, entry);\n const targetPath = path.join(targetDir, entry);\n const stat = await toolFs.stat(sourcePath);\n\n if (stat.isDirectory()) {\n await this.copyDirectory(sourcePath, targetPath, toolFs);\n } else {\n await toolFs.copyFile(sourcePath, targetPath);\n }\n }\n }\n}\n",
|
|
219
|
+
"import { createSafeLogMessage, type SafeLogMessageMap } from '@dotfiles/logger';\n\nexport const messages = {\n generate: {\n processingTool: (toolName: string) => createSafeLogMessage(`Processing symlinks for tool \"${toolName}\"`),\n missingToolConfig: (toolName: string) =>\n createSafeLogMessage(`Tool config for \"${toolName}\" is undefined. Skipping.`),\n } satisfies SafeLogMessageMap,\n process: {\n symlinkDetails: (source: string, sourceAbs: string, target: string, targetAbs: string) =>\n createSafeLogMessage(\n `Processing symlink: source=\"${source}\" (abs: \"${sourceAbs}\"), target=\"${target}\" (abs: \"${targetAbs}\")`,\n ),\n sourceMissing: (toolName: string, sourceAbsPath: string) =>\n createSafeLogMessage(`Tool \"${toolName}\" source file not found: ${sourceAbsPath}`),\n targetExists: (targetAbsPath: string) => createSafeLogMessage(`Target path \"${targetAbsPath}\" already exists.`),\n skipExistingTarget: (targetAbsPath: string) =>\n createSafeLogMessage(`Target \"${targetAbsPath}\" exists and overwrite is false. Skipping symlink creation.`),\n } satisfies SafeLogMessageMap,\n filesystem: {\n backupFailed: (targetAbsPath: string) => createSafeLogMessage(`Failed to write backup of ${targetAbsPath}`),\n deleteFailed: (targetAbsPath: string) => createSafeLogMessage(`Failed to delete ${targetAbsPath}`),\n directoryCreateFailed: (directoryPath: string) =>\n createSafeLogMessage(`Failed to create directory ${directoryPath}`),\n symlinkFailed: (source: string, target: string) =>\n createSafeLogMessage(`Failed to create symlink ${source} → ${target}`),\n symlinkAlreadyExists: (symlinkPath: string, target: string) =>\n createSafeLogMessage(`Symlink already exists and is valid: ${symlinkPath} -> ${target}`),\n creatingSymlink: (symlinkPath: string, targetPath: string) =>\n createSafeLogMessage(`Creating symlink: ${symlinkPath} -> ${targetPath}`),\n symlinkCreated: (symlinkPath: string, targetPath: string) =>\n createSafeLogMessage(`Successfully created symlink: ${symlinkPath} -> ${targetPath}`),\n removingBrokenSymlink: (symlinkPath: string) => createSafeLogMessage(`Removing broken symlink: ${symlinkPath}`),\n } satisfies SafeLogMessageMap,\n copy: {\n processingTool: (toolName: string) => createSafeLogMessage(`Processing copies for tool \"${toolName}\"`),\n missingToolConfig: (toolName: string) =>\n createSafeLogMessage(`Tool config for \"${toolName}\" is undefined. Skipping.`),\n copyDetails: (source: string, sourceAbs: string, target: string, targetAbs: string) =>\n createSafeLogMessage(\n `Processing copy: source=\"${source}\" (abs: \"${sourceAbs}\"), target=\"${target}\" (abs: \"${targetAbs}\")`,\n ),\n sourceMissing: (toolName: string, sourceAbsPath: string) =>\n createSafeLogMessage(`Tool \"${toolName}\" source file not found: ${sourceAbsPath}`),\n targetExists: (targetAbsPath: string) => createSafeLogMessage(`Target path \"${targetAbsPath}\" already exists.`),\n skipExistingTarget: (targetAbsPath: string) =>\n createSafeLogMessage(`Target \"${targetAbsPath}\" exists and overwrite is false. Skipping copy.`),\n copyFailed: (source: string, target: string) => createSafeLogMessage(`Failed to copy ${source} → ${target}`),\n } satisfies SafeLogMessageMap,\n} as const;\n",
|
|
220
|
+
"import type { ProjectConfig } from '@dotfiles/config';\nimport type { ISystemInfo, ToolConfig } from '@dotfiles/core';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { TrackedFileSystem } from '@dotfiles/registry/file';\nimport { expandToolConfigPath, resolvePlatformConfig } from '@dotfiles/utils';\nimport path from 'node:path';\nimport type { IGenerateSymlinksOptions, ISymlinkGenerator, SymlinkOperationResult } from './ISymlinkGenerator';\nimport { messages } from './log-messages';\n\n/** Configuration for a single symlink mapping from source to target */\ninterface ISymlinkConfig {\n source: string;\n target: string;\n}\n\n/** Options for handling existing targets during symlink creation */\ninterface IOverwriteOptions {\n overwrite: boolean;\n backup: boolean;\n}\n\n/** Result indicating the symlink is correct */\ninterface ISymlinkCheckResult {\n isCorrect: boolean;\n}\n\n/** Result of a successful operation */\ninterface IOperationSuccess {\n failed: false;\n}\n\n/** Result of a failed operation with error message */\ninterface IOperationFailure {\n failed: true;\n error: string;\n}\n\n/** Union type for operation results */\ntype OperationResult = IOperationSuccess | IOperationFailure;\n\n/** Result when target handling determines symlink should be created */\ninterface ITargetHandlingCreate {\n shouldSkip: false;\n status: SymlinkCreationStatus;\n}\n\n/** Result when target handling determines symlink should be skipped */\ninterface ITargetHandlingSkip {\n shouldSkip: true;\n status: 'skipped_correct' | 'skipped_exists' | 'failed';\n error?: string;\n}\n\n/** Union type for target handling results */\ntype TargetHandlingResult = ITargetHandlingCreate | ITargetHandlingSkip;\n\n/** Status values for overwrite operations (excludes 'created') */\ntype OverwriteStatus = 'updated_target' | 'backed_up';\n\n/** Result when overwrite handling succeeds */\ninterface IOverwriteHandlingSuccess {\n shouldSkip: false;\n status: OverwriteStatus;\n}\n\n/** Result when overwrite handling fails */\ninterface IOverwriteHandlingFailure {\n shouldSkip: true;\n status: 'failed';\n error: string;\n}\n\n/** Union type for overwrite handling results */\ntype OverwriteHandlingResult = IOverwriteHandlingSuccess | IOverwriteHandlingFailure;\n\n/** Status values for successful symlink creation operations */\ntype SymlinkCreationStatus = 'created' | 'updated_target' | 'backed_up';\n\n/**\n * Service that generates symbolic links for dotfiles.\n *\n * This class handles creating symlinks from source files (in the dotfiles repository)\n * to target locations (typically in the user's home directory). It supports overwriting\n * existing files, creating backups, and tracking which symlinks belong to which tools.\n * The generator expands paths using configuration variables and system information.\n */\nexport class SymlinkGenerator implements ISymlinkGenerator {\n private readonly fs: IFileSystem;\n private readonly projectConfig: ProjectConfig;\n private readonly systemInfo: ISystemInfo;\n private readonly logger: TsLogger;\n\n /**\n * Creates a new SymlinkGenerator instance.\n *\n * @param parentLogger - The parent logger for creating sub-loggers.\n * @param fileSystem - The file system interface for file operations.\n * @param projectConfig - The project configuration containing paths and settings.\n * @param systemInfo - System information for path expansion.\n */\n constructor(parentLogger: TsLogger, fileSystem: IFileSystem, projectConfig: ProjectConfig, systemInfo: ISystemInfo) {\n this.fs = fileSystem;\n this.projectConfig = projectConfig;\n this.systemInfo = systemInfo;\n this.logger = parentLogger.getSubLogger({ name: 'SymlinkGenerator' });\n }\n\n /**\n * @inheritdoc ISymlinkGenerator.createBinarySymlink\n */\n async createBinarySymlink(parentLogger: TsLogger, sourcePath: string, targetPath: string): Promise<void> {\n const logger = parentLogger.getSubLogger({ name: 'createBinarySymlink' });\n\n // Check if symlink already exists and is valid\n try {\n const targetStat = await this.fs.lstat(targetPath);\n if (targetStat.isSymbolicLink()) {\n const currentTarget = await this.fs.readlink(targetPath);\n const resolvedCurrentTarget = path.resolve(path.dirname(targetPath), currentTarget);\n const resolvedSourcePath = path.resolve(sourcePath);\n\n if (resolvedCurrentTarget === resolvedSourcePath) {\n // Symlink already exists and points to correct target\n const targetExists = await this.fs.exists(resolvedCurrentTarget);\n if (targetExists) {\n logger.debug(messages.filesystem.symlinkAlreadyExists(targetPath, resolvedCurrentTarget));\n return;\n }\n }\n\n // Symlink exists but points to wrong target, remove it\n await this.fs.rm(targetPath, { force: true });\n } else {\n // Target exists but is not a symlink, remove it\n await this.fs.rm(targetPath, { force: true });\n }\n } catch {\n // Target doesn't exist, proceed with creation\n }\n\n // Validate source binary exists\n const sourceExists = await this.fs.exists(sourcePath);\n if (!sourceExists) {\n throw new Error(`Cannot create symlink: binary does not exist at ${sourcePath}`);\n }\n\n // Create symlink\n logger.debug(messages.filesystem.creatingSymlink(targetPath, sourcePath));\n await this.fs.symlink(sourcePath, targetPath);\n\n // Verify symlink was created successfully\n const verifyTarget = await this.fs.readlink(targetPath);\n const resolvedVerifyTarget = path.resolve(path.dirname(targetPath), verifyTarget);\n const resolvedSourcePath = path.resolve(sourcePath);\n\n if (resolvedVerifyTarget !== resolvedSourcePath) {\n throw new Error(\n `Symlink verification failed: ${targetPath} points to ${resolvedVerifyTarget}, expected ${resolvedSourcePath}`,\n );\n }\n\n logger.debug(messages.filesystem.symlinkCreated(targetPath, sourcePath));\n }\n\n /**\n * @inheritdoc ISymlinkGenerator.generate\n */\n async generate(\n toolConfigs: Record<string, ToolConfig>,\n options: IGenerateSymlinksOptions = {},\n ): Promise<SymlinkOperationResult[]> {\n const logger = this.logger.getSubLogger({ name: 'generate' });\n const results: SymlinkOperationResult[] = [];\n\n for (const toolName in toolConfigs) {\n const toolConfig = toolConfigs[toolName];\n if (!toolConfig) {\n continue;\n }\n // Resolve platform-specific configuration to get symlinks from platformConfigs\n const resolvedToolConfig = resolvePlatformConfig(toolConfig, this.systemInfo);\n if (!this.shouldProcessTool(resolvedToolConfig, toolName, logger)) {\n continue;\n }\n\n const toolLogger = logger.getSubLogger({ context: toolName });\n const toolFs = this.fs instanceof TrackedFileSystem ? this.fs.withToolName(toolName) : this.fs;\n toolLogger.debug(messages.generate.processingTool(toolName));\n\n for (const symlinkConfig of resolvedToolConfig.symlinks) {\n const result = await this.processSymlink(resolvedToolConfig, symlinkConfig, toolFs, options, toolLogger);\n results.push(result);\n }\n }\n\n return results;\n }\n\n /**\n * Determines whether a tool should be processed for symlink generation.\n *\n * @param toolConfig - The tool configuration to check.\n * @param toolName - The name of the tool.\n * @param logger - The logger instance.\n * @returns True if the tool has symlink configurations, false otherwise.\n */\n private shouldProcessTool(\n toolConfig: ToolConfig | undefined,\n toolName: string,\n logger: TsLogger,\n ): toolConfig is ToolConfig & { symlinks: NonNullable<ToolConfig['symlinks']>; } {\n const methodLogger = logger.getSubLogger({ name: 'shouldProcessTool' });\n if (!toolConfig) {\n methodLogger.debug(messages.generate.missingToolConfig(toolName));\n return false;\n }\n if (!toolConfig.symlinks || toolConfig.symlinks.length === 0) {\n return false;\n }\n return true;\n }\n\n /**\n * Processes a single symlink configuration.\n *\n * @param toolConfig - The tool configuration.\n * @param symlinkConfig - The symlink source and target configuration.\n * @param toolFs - The file system interface (may be tool-specific tracked FS).\n * @param options - Options for symlink generation.\n * @param logger - The logger instance.\n * @returns The result of the symlink operation.\n */\n private async processSymlink(\n toolConfig: ToolConfig,\n symlinkConfig: ISymlinkConfig,\n toolFs: IFileSystem,\n options: IGenerateSymlinksOptions,\n logger: TsLogger,\n ): Promise<SymlinkOperationResult> {\n const methodLogger = logger.getSubLogger({ name: 'processSymlink' });\n const { overwrite = false, backup = false } = options;\n const sourceAbsPath = expandToolConfigPath(\n toolConfig.configFilePath,\n symlinkConfig.source,\n this.projectConfig,\n this.systemInfo,\n );\n const targetAbsPath = expandToolConfigPath(\n toolConfig.configFilePath,\n symlinkConfig.target,\n this.projectConfig,\n this.systemInfo,\n );\n\n methodLogger.debug(\n messages.process.symlinkDetails(symlinkConfig.source, sourceAbsPath, symlinkConfig.target, targetAbsPath),\n symlinkConfig.source,\n sourceAbsPath,\n symlinkConfig.target,\n targetAbsPath,\n );\n\n if (!(await toolFs.exists(sourceAbsPath))) {\n methodLogger.error(messages.process.sourceMissing(toolConfig.name, sourceAbsPath));\n const result: SymlinkOperationResult = {\n success: false,\n sourcePath: sourceAbsPath,\n targetPath: targetAbsPath,\n status: 'failed',\n error: messages.process.sourceMissing(toolConfig.name, sourceAbsPath),\n };\n return result;\n }\n\n const targetHandlingResult = await this.handleExistingTarget(\n sourceAbsPath,\n targetAbsPath,\n toolFs,\n { overwrite, backup },\n methodLogger,\n );\n\n if (targetHandlingResult.shouldSkip) {\n if (targetHandlingResult.status === 'failed') {\n const result: SymlinkOperationResult = {\n success: false,\n sourcePath: sourceAbsPath,\n targetPath: targetAbsPath,\n status: 'failed',\n error: targetHandlingResult.error ?? 'Unknown error',\n };\n return result;\n }\n\n // If symlink is correct but was skipped, ensure it's registered in the registry\n if (targetHandlingResult.status === 'skipped_correct' && toolFs instanceof TrackedFileSystem) {\n await toolFs.recordExistingSymlink(sourceAbsPath, targetAbsPath);\n }\n\n const result: SymlinkOperationResult = {\n success: true,\n sourcePath: sourceAbsPath,\n targetPath: targetAbsPath,\n status: targetHandlingResult.status,\n };\n return result;\n }\n\n return await this.createSymlink(sourceAbsPath, targetAbsPath, toolFs, targetHandlingResult.status, methodLogger);\n }\n\n private async handleExistingTarget(\n sourceAbsPath: string,\n targetAbsPath: string,\n toolFs: IFileSystem,\n options: IOverwriteOptions,\n logger: TsLogger,\n ): Promise<TargetHandlingResult> {\n const methodLogger = logger.getSubLogger({ name: 'handleExistingTarget' });\n const targetExists = await toolFs.exists(targetAbsPath);\n\n if (!targetExists) {\n // Check for broken symlink - exists() returns false but the symlink file itself may exist\n const brokenSymlinkRemoved = await this.removeBrokenSymlink(targetAbsPath, toolFs, methodLogger);\n if (brokenSymlinkRemoved.failed) {\n return { shouldSkip: true, status: 'failed', error: brokenSymlinkRemoved.error };\n }\n return { shouldSkip: false, status: 'created' };\n }\n\n methodLogger.debug(messages.process.targetExists(targetAbsPath));\n\n const correctSymlinkResult = await this.checkCorrectSymlink(sourceAbsPath, targetAbsPath, toolFs);\n if (correctSymlinkResult.isCorrect) {\n return { shouldSkip: true, status: 'skipped_correct' };\n }\n\n if (!options.overwrite) {\n methodLogger.debug(messages.process.skipExistingTarget(targetAbsPath));\n return { shouldSkip: true, status: 'skipped_exists' };\n }\n\n return await this.handleOverwrite(targetAbsPath, toolFs, options.backup, methodLogger);\n }\n\n /**\n * Checks if a symlink already points to the correct source.\n *\n * @param sourceAbsPath - The absolute path to the source file.\n * @param targetAbsPath - The absolute path to the symlink.\n * @param toolFs - The file system interface.\n * @returns An object indicating whether the symlink is correct.\n */\n private async checkCorrectSymlink(\n sourceAbsPath: string,\n targetAbsPath: string,\n toolFs: IFileSystem,\n ): Promise<ISymlinkCheckResult> {\n try {\n const targetStat = await toolFs.lstat(targetAbsPath);\n if (targetStat.isSymbolicLink()) {\n const currentTarget = await toolFs.readlink(targetAbsPath);\n const resolvedCurrentTarget = path.resolve(path.dirname(targetAbsPath), currentTarget);\n const resolvedSourcePath = path.resolve(sourceAbsPath);\n return { isCorrect: resolvedCurrentTarget === resolvedSourcePath };\n }\n } catch {\n // If we can't check the symlink, proceed with normal logic\n }\n return { isCorrect: false };\n }\n\n /**\n * Handles overwriting an existing file or symlink at the target path.\n *\n * @param targetAbsPath - The absolute path to the target.\n * @param toolFs - The file system interface.\n * @param backup - Whether to backup the existing file.\n * @param logger - The logger instance.\n * @returns An object indicating whether to skip symlink creation and the status.\n */\n private async handleOverwrite(\n targetAbsPath: string,\n toolFs: IFileSystem,\n backup: boolean,\n logger: TsLogger,\n ): Promise<OverwriteHandlingResult> {\n const methodLogger = logger.getSubLogger({ name: 'handleOverwrite' });\n let status: SymlinkOperationResult['status'] = 'updated_target';\n\n if (backup) {\n const backupResult = await this.createBackup(targetAbsPath, toolFs, methodLogger);\n if (backupResult.failed) {\n return { shouldSkip: true, status: 'failed', error: backupResult.error };\n }\n status = 'backed_up';\n } else {\n const deleteResult = await this.deleteTarget(targetAbsPath, toolFs, methodLogger);\n if (deleteResult.failed) {\n return { shouldSkip: true, status: 'failed', error: deleteResult.error };\n }\n }\n\n return { shouldSkip: false, status };\n }\n\n /**\n * Creates a backup of the target file before overwriting.\n *\n * @param targetAbsPath - The absolute path to the target file.\n * @param toolFs - The file system interface.\n * @param logger - The logger instance.\n * @returns An object indicating success or failure with error message.\n */\n private async createBackup(targetAbsPath: string, toolFs: IFileSystem, logger: TsLogger): Promise<OperationResult> {\n const methodLogger = logger.getSubLogger({ name: 'createBackup' });\n const backupPath = `${targetAbsPath}.bak`;\n try {\n if (await toolFs.exists(backupPath)) {\n await toolFs.rm(backupPath, { recursive: true, force: true });\n }\n await toolFs.rename(targetAbsPath, backupPath);\n return { failed: false };\n } catch {\n const errorMsg = messages.filesystem.backupFailed(targetAbsPath);\n methodLogger.error(errorMsg);\n return { failed: true, error: errorMsg };\n }\n }\n\n /**\n * Deletes the target file or directory before creating symlink.\n *\n * @param targetAbsPath - The absolute path to the target.\n * @param toolFs - The file system interface.\n * @param logger - The logger instance.\n * @returns An object indicating success or failure with error message.\n */\n private async deleteTarget(targetAbsPath: string, toolFs: IFileSystem, logger: TsLogger): Promise<OperationResult> {\n const methodLogger = logger.getSubLogger({ name: 'deleteTarget' });\n try {\n const targetStat = await toolFs.stat(targetAbsPath);\n const isDirectory = targetStat.isDirectory();\n\n if (isDirectory) {\n await toolFs.rm(targetAbsPath, { recursive: true, force: true });\n } else {\n await toolFs.rm(targetAbsPath, { force: true });\n }\n return { failed: false };\n } catch {\n const errorMsg = messages.filesystem.deleteFailed(targetAbsPath);\n methodLogger.error(errorMsg);\n return { failed: true, error: errorMsg };\n }\n }\n\n /**\n * Removes a broken symlink at the target path if one exists.\n *\n * A broken symlink is one where the symlink file exists but its target doesn't.\n * The exists() method returns false for broken symlinks (following Node.js behavior),\n * but the symlink file itself still exists and must be removed before creating a new one.\n *\n * @param targetAbsPath - The absolute path to check for a broken symlink.\n * @param toolFs - The file system interface.\n * @param logger - The logger instance.\n * @returns An object indicating success or failure with error message.\n */\n private async removeBrokenSymlink(\n targetAbsPath: string,\n toolFs: IFileSystem,\n logger: TsLogger,\n ): Promise<OperationResult> {\n const methodLogger = logger.getSubLogger({ name: 'removeBrokenSymlink' });\n try {\n const stats = await toolFs.lstat(targetAbsPath);\n if (stats.isSymbolicLink()) {\n methodLogger.debug(messages.filesystem.removingBrokenSymlink(targetAbsPath));\n await toolFs.rm(targetAbsPath, { force: true });\n }\n return { failed: false };\n } catch {\n // lstat failed - no file/symlink exists at this path, nothing to remove\n return { failed: false };\n }\n }\n\n /**\n * Creates a symbolic link from source to target.\n *\n * @param sourceAbsPath - The absolute path to the source file.\n * @param targetAbsPath - The absolute path where the symlink will be created.\n * @param toolFs - The file system interface.\n * @param status - The status to report in the result.\n * @param logger - The logger instance.\n * @returns The result of the symlink operation.\n */\n private async createSymlink(\n sourceAbsPath: string,\n targetAbsPath: string,\n toolFs: IFileSystem,\n status: SymlinkCreationStatus,\n logger: TsLogger,\n ): Promise<SymlinkOperationResult> {\n const methodLogger = logger.getSubLogger({ name: 'createSymlink' });\n const targetDir = path.dirname(targetAbsPath);\n\n try {\n await toolFs.ensureDir(targetDir);\n } catch {\n const errorMsg = messages.filesystem.directoryCreateFailed(targetDir);\n methodLogger.error(errorMsg);\n const result: SymlinkOperationResult = {\n success: false,\n sourcePath: sourceAbsPath,\n targetPath: targetAbsPath,\n status: 'failed',\n error: errorMsg,\n };\n return result;\n }\n\n try {\n await toolFs.symlink(sourceAbsPath, targetAbsPath);\n const result: SymlinkOperationResult = {\n success: true,\n sourcePath: sourceAbsPath,\n targetPath: targetAbsPath,\n status,\n };\n return result;\n } catch {\n const errorMsg = messages.filesystem.symlinkFailed(sourceAbsPath, targetAbsPath);\n methodLogger.error(errorMsg);\n const result: SymlinkOperationResult = {\n success: false,\n sourcePath: sourceAbsPath,\n targetPath: targetAbsPath,\n status: 'failed',\n error: errorMsg,\n };\n return result;\n }\n }\n}\n",
|
|
221
|
+
"import type { IGitHubApiClient } from '@dotfiles/installer-github';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { eq, gt, valid } from 'semver';\nimport type { IVersionChecker } from './IVersionChecker.ts';\nimport { VersionComparisonStatus } from './IVersionChecker.ts';\nimport { messages } from './log-messages';\n\n/**\n * Service for checking and comparing tool versions from GitHub releases.\n *\n * This class implements version checking functionality that fetches the latest release\n * information from GitHub repositories and compares semantic versions to determine if\n * updates are available. It handles version string normalization (removing 'v' prefix)\n * and validates versions using semver.\n */\nexport class VersionChecker implements IVersionChecker {\n private readonly githubClient: IGitHubApiClient;\n private readonly logger: TsLogger;\n\n /**\n * Creates a new VersionChecker instance.\n *\n * @param parentLogger - The parent logger for creating sub-loggers.\n * @param githubClient - The GitHub API client for fetching release information.\n */\n constructor(parentLogger: TsLogger, githubClient: IGitHubApiClient) {\n this.logger = parentLogger.getSubLogger({ name: 'VersionChecker' });\n this.logger.debug(messages.initializing());\n this.githubClient = githubClient;\n }\n\n /**\n * @inheritdoc IVersionChecker.getLatestToolVersion\n */\n async getLatestToolVersion(owner: string, repo: string): Promise<string | null> {\n const logger = this.logger.getSubLogger({ name: 'getLatestToolVersion' });\n logger.debug(messages.fetchingLatestRelease(owner, repo));\n try {\n const release = await this.githubClient.getLatestRelease(owner, repo);\n if (release?.tag_name) {\n // Remove 'v' prefix if present, common in tags\n const version = release.tag_name.replace(/^v/, '');\n logger.debug(messages.latestReleaseFound(version));\n return version;\n }\n logger.debug(messages.noLatestRelease(owner, repo));\n return null;\n } catch (error) {\n logger.debug(messages.latestReleaseError(owner, repo), error);\n return null;\n }\n }\n\n /**\n * @inheritdoc IVersionChecker.checkVersionStatus\n */\n async checkVersionStatus(currentVersion: string, latestVersion: string): Promise<VersionComparisonStatus> {\n const logger = this.logger.getSubLogger({ name: 'checkVersionStatus' });\n logger.debug(messages.comparingVersions(currentVersion, latestVersion));\n\n const cleanCurrentVersion = currentVersion.replace(/^v/, '');\n const cleanLatestVersion = latestVersion.replace(/^v/, '');\n\n if (!valid(cleanCurrentVersion)) {\n logger.debug(messages.invalidConfiguredVersion(cleanCurrentVersion));\n return VersionComparisonStatus.INVALID_CURRENT_VERSION;\n }\n if (!valid(cleanLatestVersion)) {\n logger.debug(messages.invalidLatestVersion(cleanLatestVersion));\n return VersionComparisonStatus.INVALID_LATEST_VERSION;\n }\n\n if (gt(cleanLatestVersion, cleanCurrentVersion)) {\n logger.debug(messages.versionComparisonResult('NEWER_AVAILABLE'));\n return VersionComparisonStatus.NEWER_AVAILABLE;\n }\n if (eq(cleanLatestVersion, cleanCurrentVersion)) {\n logger.debug(messages.versionComparisonResult('UP_TO_DATE'));\n return VersionComparisonStatus.UP_TO_DATE;\n }\n // This implies lt(cleanLatestVersion, cleanCurrentVersion)\n logger.debug(messages.versionComparisonResult('AHEAD_OF_LATEST'));\n return VersionComparisonStatus.AHEAD_OF_LATEST;\n }\n}\n",
|
|
222
|
+
"import { createSafeLogMessage, type SafeLogMessageMap } from '@dotfiles/logger';\n\nexport const messages = {\n initializing: () => createSafeLogMessage('Initializing VersionChecker with githubClient'),\n fetchingLatestRelease: (owner: string, repo: string) =>\n createSafeLogMessage(`Fetching latest version for ${owner}/${repo}`),\n latestReleaseFound: (version: string) => createSafeLogMessage(`Latest release found ${version}`),\n latestReleaseError: (owner: string, repo: string) =>\n createSafeLogMessage(`Failed to fetch latest release for ${owner}/${repo}`),\n noLatestRelease: (owner: string, repo: string) =>\n createSafeLogMessage(`No latest release found for ${owner}/${repo}`),\n comparingVersions: (configuredVersion: string, latestVersion: string) =>\n createSafeLogMessage(`Comparing versions configured ${configuredVersion} vs latest ${latestVersion}`),\n invalidConfiguredVersion: (configuredVersion: string) =>\n createSafeLogMessage(`Configured version invalid ${configuredVersion}`),\n invalidLatestVersion: (latestVersion: string) => createSafeLogMessage(`Latest version invalid ${latestVersion}`),\n versionComparisonResult: (result: string) => createSafeLogMessage(`Version comparison result ${result}`),\n} satisfies SafeLogMessageMap;\n",
|
|
223
|
+
"#!/usr/bin/env bun\n\nimport { ArchiveExtractor } from '@dotfiles/archive-extractor';\nimport { ConfigService, type ProjectConfig } from '@dotfiles/config';\nimport {\n createShell,\n InstallerPluginRegistry,\n} from '@dotfiles/core';\nimport { Downloader, FileCache, type ICache } from '@dotfiles/downloader';\nimport { ReadmeService } from '@dotfiles/features';\nimport {\n type IFileSystem,\n type IResolvedFileSystem,\n MemFileSystem,\n NodeFileSystem,\n ResolvedFileSystem,\n} from '@dotfiles/file-system';\nimport { GeneratorOrchestrator } from '@dotfiles/generator-orchestrator';\nimport { HookExecutor, Installer } from '@dotfiles/installer';\nimport { BrewInstallerPlugin } from '@dotfiles/installer-brew';\nimport { CargoClient, CargoInstallerPlugin } from '@dotfiles/installer-cargo';\nimport { CurlBinaryInstallerPlugin } from '@dotfiles/installer-curl-binary';\nimport { CurlScriptInstallerPlugin } from '@dotfiles/installer-curl-script';\nimport { CurlTarInstallerPlugin } from '@dotfiles/installer-curl-tar';\nimport { DmgInstallerPlugin } from '@dotfiles/installer-dmg';\nimport { GiteaReleaseInstallerPlugin } from '@dotfiles/installer-gitea';\nimport { GhCliApiClient, GitHubApiClient, GitHubReleaseInstallerPlugin } from '@dotfiles/installer-github';\nimport { ManualInstallerPlugin } from '@dotfiles/installer-manual';\nimport { NpmInstallerPlugin } from '@dotfiles/installer-npm';\nimport { ZshPluginInstallerPlugin } from '@dotfiles/installer-zsh-plugin';\nimport { createTsLogger, getLogLevelFromFlags, type LogLevelValue, type TsLogger } from '@dotfiles/logger';\nimport { type IFileRegistry, TrackedFileSystem } from '@dotfiles/registry/file';\nimport { CompletionCommandExecutor, CompletionGenerator, ShellInitGenerator } from '@dotfiles/shell-init-generator';\nimport { ShimGenerator } from '@dotfiles/shim-generator';\nimport { CopyGenerator, SymlinkGenerator } from '@dotfiles/symlink-generator';\nimport { VersionChecker } from '@dotfiles/version-checker';\nimport net from 'node:net';\nimport path from 'node:path';\n\nimport { registerBinCommand } from './binCommand';\nimport { registerCheckUpdatesCommand } from './checkUpdatesCommand';\nimport { registerCleanupCommand } from './cleanupCommand';\nimport { createProgram } from './createProgram';\nimport { registerDashboardCommand } from './dashboardCommand';\nimport { registerDetectConflictsCommand } from './detectConflictsCommand';\nimport { registerEnvCommand } from './envCommand';\nimport { registerFeaturesCommand } from './featuresCommand';\nimport { registerFilesCommand } from './filesCommand';\nimport { registerGenerateCommand } from './generateCommand';\nimport { registerInstallCommand } from './installCommand';\nimport { runTrackUsageCommand } from './light/runTrackUsageCommand';\nimport { messages } from './log-messages';\nimport { registerLogCommand } from './logCommand';\nimport { populateMemFsForDryRun } from './populateMemFsForDryRun';\nimport { createBaseRuntimeContext } from './runtime/createBaseRuntimeContext';\nimport { registerSkillCommand } from './skillCommand';\nimport type { IGlobalProgram, IGlobalProgramOptions, IServices } from './types';\nimport { registerUpdateCommand } from './updateCommand';\n\n// Re-export public API for library consumers\nexport * from './schema-exports';\n\ntype SetupServicesOptions = IGlobalProgramOptions & {\n cwd: string;\n env: NodeJS.ProcessEnv;\n};\n\n/**\n * Checks if a TCP port is available by attempting to connect.\n * Returns true if connection succeeds, false otherwise.\n */\nasync function isProxyAvailable(port: number, timeoutMs: number = 2000): Promise<boolean> {\n return new Promise((resolve) => {\n const socket = new net.Socket();\n\n const cleanup = (): void => {\n socket.removeAllListeners();\n socket.destroy();\n };\n\n socket.setTimeout(timeoutMs);\n\n socket.on('connect', () => {\n cleanup();\n resolve(true);\n });\n\n socket.on('timeout', () => {\n cleanup();\n resolve(false);\n });\n\n socket.on('error', () => {\n cleanup();\n resolve(false);\n });\n\n socket.connect(port, 'localhost');\n });\n}\n\ninterface IDevProxyValidationResult {\n port: number | undefined;\n invalidValue: string | undefined;\n}\n\nfunction validateDevProxyPort(rawValue: string | undefined): IDevProxyValidationResult {\n if (typeof rawValue === 'undefined') {\n return {\n port: undefined,\n invalidValue: undefined,\n };\n }\n\n const normalizedValue = rawValue.trim();\n const isIntegerString = /^\\d+$/.test(normalizedValue);\n\n if (!isIntegerString) {\n return {\n port: undefined,\n invalidValue: rawValue,\n };\n }\n\n const parsedPort = Number.parseInt(normalizedValue, 10);\n const isValidPortRange = parsedPort >= 1 && parsedPort <= 65535;\n\n if (!isValidPortRange) {\n return {\n port: undefined,\n invalidValue: rawValue,\n };\n }\n\n return {\n port: parsedPort,\n invalidValue: undefined,\n };\n}\n\nfunction initializeFileSystem(logger: TsLogger, dryRun: boolean): IFileSystem {\n let fs: IFileSystem;\n if (dryRun) {\n logger.trace(messages.dryRunEnabled());\n fs = new MemFileSystem({});\n } else {\n fs = new NodeFileSystem();\n }\n logger.trace(messages.componentInitialized('filesystem'), fs.constructor.name);\n return fs;\n}\n\nfunction initializeDownloadCache(\n parentLogger: TsLogger,\n fs: IFileSystem,\n projectConfig: ProjectConfig,\n): ICache | undefined {\n if (!projectConfig.downloader.cache.enabled) {\n parentLogger.info(messages.cachingDisabled());\n return undefined;\n }\n\n const cacheDir = path.join(projectConfig.paths.generatedDir, 'cache', 'downloads');\n const downloadCache = new FileCache(parentLogger, fs, {\n enabled: true,\n defaultTtl: projectConfig.downloader.cache.ttl,\n cacheDir,\n storageStrategy: 'binary',\n });\n\n return downloadCache;\n}\n\nfunction createTrackedFileSystems(\n parentLogger: TsLogger,\n fs: IResolvedFileSystem,\n fileRegistry: IFileRegistry,\n projectConfig: ProjectConfig,\n): {\n shimTrackedFs: TrackedFileSystem;\n shellInitTrackedFs: TrackedFileSystem;\n symlinkTrackedFs: TrackedFileSystem;\n copyTrackedFs: TrackedFileSystem;\n installerTrackedFs: TrackedFileSystem;\n catalogTrackedFs: TrackedFileSystem;\n completionTrackedFs: TrackedFileSystem;\n} {\n const shimTrackedFs = new TrackedFileSystem(\n parentLogger,\n fs,\n fileRegistry,\n TrackedFileSystem.createContext('system', 'shim'),\n projectConfig,\n );\n\n const shellInitTrackedFs = new TrackedFileSystem(\n parentLogger,\n fs,\n fileRegistry,\n TrackedFileSystem.createContext('system', 'init'),\n projectConfig,\n );\n\n const symlinkTrackedFs = new TrackedFileSystem(\n parentLogger,\n fs,\n fileRegistry,\n TrackedFileSystem.createContext('system', 'symlink'),\n projectConfig,\n );\n\n const copyTrackedFs = new TrackedFileSystem(\n parentLogger,\n fs,\n fileRegistry,\n TrackedFileSystem.createContext('system', 'copy'),\n projectConfig,\n );\n\n const installerTrackedFs = new TrackedFileSystem(\n parentLogger,\n fs,\n fileRegistry,\n TrackedFileSystem.createContext('system', 'binary'),\n projectConfig,\n );\n\n const catalogTrackedFs = new TrackedFileSystem(\n parentLogger,\n fs,\n fileRegistry,\n TrackedFileSystem.createContext('system', 'catalog'),\n projectConfig,\n );\n\n const completionTrackedFs = new TrackedFileSystem(\n parentLogger,\n fs,\n fileRegistry,\n TrackedFileSystem.createContext('system', 'completion'),\n projectConfig,\n );\n\n return {\n shimTrackedFs,\n shellInitTrackedFs,\n symlinkTrackedFs,\n copyTrackedFs,\n installerTrackedFs,\n catalogTrackedFs,\n completionTrackedFs,\n };\n}\n\nexport async function setupServices(parentLogger: TsLogger, options: SetupServicesOptions): Promise<IServices> {\n const logger = parentLogger.getSubLogger({ name: 'setupServices' });\n const { dryRun, env, config } = options;\n\n // Initialize filesystem first\n const fs = initializeFileSystem(logger, dryRun);\n\n // For config loading, use NodeFileSystem only in dry-run mode when running the CLI directly\n const isRunningDirectly = process.env.NODE_ENV !== 'test' && !process.env['BUN_TEST'];\n const configFs = dryRun && isRunningDirectly ? new NodeFileSystem() : fs;\n\n const baseContext = await createBaseRuntimeContext(logger, {\n config,\n cwd: options.cwd,\n env,\n platform: options.platform,\n arch: options.arch,\n fileSystem: fs,\n configFileSystem: configFs,\n warnOnPlatformArchOverride: true,\n });\n\n if (!baseContext) {\n logger.error(messages.configNotFound());\n process.exit(1);\n }\n\n const {\n projectConfig,\n systemInfo,\n registryPath,\n fileRegistry,\n toolInstallationRegistry,\n } = baseContext;\n\n const devProxyRawValue = env['DEV_PROXY'];\n const devProxyValidation = validateDevProxyPort(devProxyRawValue);\n const devProxyPort = devProxyValidation.port;\n\n if (typeof devProxyValidation.invalidValue === 'string') {\n logger.error(\n messages.configParameterInvalid('DEV_PROXY', devProxyValidation.invalidValue, 'an integer between 1 and 65535'),\n );\n process.exit(1);\n }\n\n if (typeof devProxyPort === 'number') {\n logger.debug(messages.proxyCheckingAvailability(devProxyPort));\n const proxyAvailable = await isProxyAvailable(devProxyPort);\n if (!proxyAvailable) {\n logger.error(messages.proxyUnavailable(devProxyPort));\n process.exit(1);\n }\n logger.warn(messages.proxyEnabled(devProxyPort));\n }\n\n // Wrap filesystem to resolve tilde paths using configured home\n const resolvedFs = new ResolvedFileSystem(fs, projectConfig.paths.homeDir);\n\n // If dry run, load tool configs into memory filesystem\n if (dryRun) {\n const nodeFs = new NodeFileSystem();\n await populateMemFsForDryRun(logger, {\n sourceFs: nodeFs,\n targetFs: resolvedFs,\n toolConfigsDir: projectConfig.paths.toolConfigsDir,\n homeDir: systemInfo.homeDir,\n });\n }\n\n // Initialize download cache if enabled\n const downloadCache = initializeDownloadCache(parentLogger, resolvedFs, projectConfig);\n\n parentLogger.debug(messages.registryInitialized(registryPath));\n\n // Initialize services with projectConfig\n // Pass proxy config to enable routing requests through HTTP caching proxy\n const proxyConfig = typeof devProxyPort === 'number' ? { enabled: true, port: devProxyPort } : undefined;\n const downloader = new Downloader(parentLogger, resolvedFs, undefined, downloadCache, proxyConfig);\n\n // Create shell instance for all components\n const shell = createShell();\n\n // Initialize GitHub API cache using generic FileCache with JSON strategy\n const githubApiCache = new FileCache(parentLogger, resolvedFs, {\n enabled: projectConfig.github.cache.enabled,\n defaultTtl: projectConfig.github.cache.ttl,\n cacheDir: path.join(projectConfig.paths.generatedDir, 'cache', 'github-api'),\n storageStrategy: 'json',\n });\n const githubApiClient = new GitHubApiClient(parentLogger, projectConfig, downloader, githubApiCache);\n const ghCliApiClient = new GhCliApiClient(parentLogger, projectConfig, shell, githubApiCache);\n\n const giteaApiCache = new FileCache(parentLogger, resolvedFs, {\n enabled: projectConfig.github.cache.enabled,\n defaultTtl: projectConfig.github.cache.ttl,\n cacheDir: path.join(projectConfig.paths.generatedDir, 'cache', 'gitea-api'),\n storageStrategy: 'json',\n });\n\n const cargoCratesIoCache = new FileCache(parentLogger, resolvedFs, {\n enabled: projectConfig.cargo.cratesIo.cache.enabled,\n defaultTtl: projectConfig.cargo.cratesIo.cache.ttl,\n cacheDir: path.join(projectConfig.paths.generatedDir, 'cache', 'cargo', 'crates-io'),\n storageStrategy: 'json',\n });\n const cargoGithubRawCache = new FileCache(parentLogger, resolvedFs, {\n enabled: projectConfig.cargo.githubRaw.cache.enabled,\n defaultTtl: projectConfig.cargo.githubRaw.cache.ttl,\n cacheDir: path.join(projectConfig.paths.generatedDir, 'cache', 'cargo', 'github-raw'),\n storageStrategy: 'json',\n });\n const cargoClient = new CargoClient(parentLogger, projectConfig, downloader, cargoCratesIoCache, cargoGithubRawCache);\n\n // Create tracked filesystem instances for each generator\n const {\n shimTrackedFs,\n shellInitTrackedFs,\n symlinkTrackedFs,\n copyTrackedFs,\n installerTrackedFs,\n catalogTrackedFs,\n completionTrackedFs,\n } = createTrackedFileSystems(parentLogger, resolvedFs, fileRegistry, projectConfig);\n\n // Create system-context logger for generators that operate at system level\n const systemLogger = parentLogger.getSubLogger({ context: 'system' });\n\n const shellInitGenerator = new ShellInitGenerator(systemLogger, shellInitTrackedFs, projectConfig);\n const symlinkGenerator = new SymlinkGenerator(systemLogger, symlinkTrackedFs, projectConfig, systemInfo);\n const copyGenerator = new CopyGenerator(systemLogger, copyTrackedFs, projectConfig, systemInfo);\n const completionCommandExecutor = new CompletionCommandExecutor(systemLogger, shell);\n\n const archiveExtractor = new ArchiveExtractor(parentLogger, resolvedFs, shell);\n\n // Initialize hook executor for plugins\n const hookExecutor = new HookExecutor((chunk: string): void => {\n process.stdout.write(chunk);\n });\n\n // Initialize plugin registry and register all installer plugins\n const pluginRegistry = new InstallerPluginRegistry(parentLogger);\n pluginRegistry.register(\n new GitHubReleaseInstallerPlugin(\n installerTrackedFs,\n downloader,\n githubApiClient,\n ghCliApiClient,\n archiveExtractor,\n projectConfig,\n hookExecutor,\n ),\n );\n pluginRegistry.register(\n new GiteaReleaseInstallerPlugin(\n installerTrackedFs,\n downloader,\n archiveExtractor,\n hookExecutor,\n giteaApiCache,\n ),\n );\n pluginRegistry.register(new BrewInstallerPlugin(shell));\n pluginRegistry.register(\n new CargoInstallerPlugin(\n installerTrackedFs,\n downloader,\n cargoClient,\n archiveExtractor,\n hookExecutor,\n projectConfig.cargo.githubRelease.host,\n ),\n );\n pluginRegistry.register(new CurlScriptInstallerPlugin(installerTrackedFs, downloader, hookExecutor, shell));\n pluginRegistry.register(\n new CurlTarInstallerPlugin(installerTrackedFs, downloader, archiveExtractor, hookExecutor, shell),\n );\n pluginRegistry.register(new CurlBinaryInstallerPlugin(installerTrackedFs, downloader, hookExecutor, shell));\n pluginRegistry.register(\n new DmgInstallerPlugin(\n installerTrackedFs,\n downloader,\n archiveExtractor,\n hookExecutor,\n shell,\n githubApiClient,\n ghCliApiClient,\n ),\n );\n pluginRegistry.register(new ManualInstallerPlugin(installerTrackedFs));\n pluginRegistry.register(new NpmInstallerPlugin(shell));\n pluginRegistry.register(new ZshPluginInstallerPlugin(installerTrackedFs, shell));\n\n // Create shim generator with knowledge of externally managed plugins (e.g., brew)\n // so it can skip shim generation for already-installed externally managed tools\n const externallyManagedMethods = pluginRegistry.getExternallyManagedMethods();\n const missingBinaryMessagesByMethod = pluginRegistry.getMissingBinaryMessagesByMethod();\n const shimGenerator = new ShimGenerator(\n systemLogger,\n shimTrackedFs,\n projectConfig,\n systemInfo,\n externallyManagedMethods,\n missingBinaryMessagesByMethod,\n toolInstallationRegistry,\n );\n\n const completionGenerator = new CompletionGenerator(\n systemLogger,\n completionTrackedFs,\n shell,\n completionCommandExecutor,\n {\n downloader,\n archiveExtractor,\n },\n );\n\n const generatorOrchestrator = new GeneratorOrchestrator(\n systemLogger,\n shimGenerator,\n shellInitGenerator,\n symlinkGenerator,\n copyGenerator,\n completionGenerator,\n systemInfo,\n projectConfig,\n fileRegistry,\n resolvedFs,\n completionTrackedFs,\n );\n\n const installer = new Installer(\n logger,\n installerTrackedFs,\n resolvedFs,\n projectConfig,\n toolInstallationRegistry,\n systemInfo,\n pluginRegistry,\n symlinkGenerator,\n shell, // Don't add logging here - HookExecutor.createEnhancedContext adds logging with tool context\n hookExecutor,\n );\n const versionChecker = new VersionChecker(logger, githubApiClient);\n const configService = new ConfigService();\n const readmeService = new ReadmeService(\n logger,\n downloader,\n toolInstallationRegistry,\n resolvedFs,\n catalogTrackedFs,\n path.join(projectConfig.paths.generatedDir, 'cache', 'readme'),\n pluginRegistry,\n );\n\n return {\n projectConfig,\n fs: resolvedFs,\n configService,\n readmeService,\n fileRegistry,\n toolInstallationRegistry,\n downloadCache,\n downloader,\n githubApiCache,\n cargoCratesIoCache,\n cargoGithubRawCache,\n githubApiClient,\n cargoClient,\n shimGenerator,\n shellInitGenerator,\n symlinkGenerator,\n copyGenerator,\n completionGenerator,\n generatorOrchestrator,\n installer,\n archiveExtractor,\n versionChecker,\n pluginRegistry,\n systemInfo,\n };\n}\n\nexport function registerAllCommands(\n parentLogger: TsLogger,\n program: IGlobalProgram,\n servicesFactory: () => Promise<IServices>,\n) {\n const logger = parentLogger.getSubLogger({ name: 'registerAllCommands' });\n registerBinCommand(logger, program, servicesFactory);\n registerInstallCommand(logger, program, servicesFactory);\n registerGenerateCommand(logger, program, servicesFactory);\n registerFeaturesCommand(logger, program, servicesFactory);\n registerCleanupCommand(logger, program, servicesFactory);\n registerCheckUpdatesCommand(logger, program, servicesFactory);\n registerUpdateCommand(logger, program, servicesFactory);\n registerDetectConflictsCommand(logger, program, servicesFactory);\n registerLogCommand(logger, program, servicesFactory);\n registerFilesCommand(logger, program, servicesFactory);\n registerSkillCommand(logger, program, servicesFactory);\n registerDashboardCommand(logger, program, servicesFactory);\n registerEnvCommand(logger, program);\n}\n\nfunction hasFlag(argv: string[], flag: string): boolean {\n return argv.includes(flag);\n}\n\nconst COMMAND_FLAGS_WITH_VALUES = new Set(['--config', '--log', '--platform', '--arch']);\n\nfunction resolveCommand(argv: string[]): string | undefined {\n for (let i = 2; i < argv.length; i += 1) {\n const token = argv[i];\n if (!token) {\n continue;\n }\n\n if (COMMAND_FLAGS_WITH_VALUES.has(token)) {\n i += 1;\n continue;\n }\n\n if (token.startsWith('-')) {\n continue;\n }\n\n return token;\n }\n\n return undefined;\n}\n\nexport function resolveLogLevel(argv: string[], options: IGlobalProgramOptions): LogLevelValue {\n const isShimMode = hasFlag(argv, '--shim-mode');\n const quiet = options.quiet || isShimMode;\n return getLogLevelFromFlags(options.log, quiet, options.verbose);\n}\n\nexport async function main(argv: string[]) {\n const program = createProgram();\n\n // Parse options first to get quiet/verbose flags\n program.parseOptions(argv);\n const options: IGlobalProgramOptions = program.opts();\n\n // Create logger with appropriate level based on CLI flags\n const logLevel = resolveLogLevel(argv, options);\n const rootLogger = createTsLogger({ name: 'cli', level: logLevel, trace: options.trace });\n const logger = rootLogger.getSubLogger({ name: 'main' });\n\n logger.trace(messages.cliStarted(), argv);\n\n // Create a factory function that will initialize services only when needed\n const servicesFactory = async () => {\n return await setupServices(logger, {\n ...options,\n cwd: process.cwd(),\n env: process.env,\n });\n };\n\n registerAllCommands(logger, program, servicesFactory);\n await program.parseAsync(argv);\n}\n\nexport async function runCliEntrypoint(argv: string[]): Promise<void> {\n const command = resolveCommand(argv);\n\n if (command === '@track-usage') {\n await runTrackUsageCommand(argv);\n return;\n }\n\n await main(argv);\n}\n\n// Only run main if the script is executed directly\nif (import.meta.main) {\n runCliEntrypoint(process.argv).catch((error) => {\n // Create a basic logger for fatal errors only, since we don't have parsed options yet\n const fatalLogger = createTsLogger({ name: 'cli' });\n fatalLogger.fatal(messages.commandExecutionFailed('main', 1), error);\n process.exit(1);\n });\n}\n",
|
|
224
|
+
"import type { IConfigService, ProjectConfig } from '@dotfiles/config';\nimport type { ISystemInfo } from '@dotfiles/core';\nimport type { IResolvedFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { exitCli, ExitCode, resolvePlatformConfig } from '@dotfiles/utils';\nimport { realpath } from 'node:fs/promises';\nimport path from 'node:path';\nimport type { ICommandCompletionMeta, IGlobalProgram, IServices } from './types';\n\nexport const BIN_COMMAND_COMPLETION: ICommandCompletionMeta = {\n name: 'bin',\n description: 'Print the real path to a binary',\n hasPositionalArg: true,\n positionalArgDescription: 'binary name',\n positionalArgType: 'tool',\n};\n\nasync function loadToolConfigByNameOrBinary(\n logger: TsLogger,\n nameOrBinary: string,\n toolConfigsDir: string,\n fs: IResolvedFileSystem,\n projectConfig: ProjectConfig,\n configService: IConfigService,\n systemInfo: ISystemInfo,\n): Promise<{ toolName: string; binaryName: string; } | undefined> {\n const toolConfig = await configService.loadSingleToolConfig(\n logger,\n nameOrBinary,\n toolConfigsDir,\n fs,\n projectConfig,\n systemInfo,\n );\n\n if (toolConfig) {\n const resolved = resolvePlatformConfig(toolConfig, systemInfo);\n const binaries = resolved.binaries ?? [];\n const firstBinary = binaries[0];\n const binaryName = firstBinary\n ? (typeof firstBinary === 'string' ? firstBinary : firstBinary.name)\n : nameOrBinary;\n return { toolName: nameOrBinary, binaryName };\n }\n\n const binaryLookupResult = await configService.loadToolConfigByBinary(\n logger,\n nameOrBinary,\n toolConfigsDir,\n fs,\n projectConfig,\n systemInfo,\n );\n\n if (binaryLookupResult && !('error' in binaryLookupResult)) {\n return { toolName: binaryLookupResult.name, binaryName: nameOrBinary };\n }\n\n return undefined;\n}\n\nasync function executeBinCommandAction(\n logger: TsLogger,\n nameOrBinary: string,\n services: IServices,\n): Promise<ExitCode> {\n const { projectConfig, fs, configService, systemInfo } = services;\n\n // Use a silent logger (minLevel above FATAL=6) to suppress all output\n // from config loading, which may log validation errors for unrelated tools\n const silentLogger = logger.getSubLogger({ minLevel: 7 });\n\n const result = await loadToolConfigByNameOrBinary(\n silentLogger,\n nameOrBinary,\n projectConfig.paths.toolConfigsDir,\n fs,\n projectConfig,\n configService,\n systemInfo,\n );\n\n if (!result) {\n return ExitCode.ERROR;\n }\n\n const { toolName, binaryName } = result;\n const binaryPath = path.join(projectConfig.paths.binariesDir, toolName, 'current', binaryName);\n\n try {\n const resolvedPath = await realpath(binaryPath);\n process.stdout.write(resolvedPath);\n return ExitCode.SUCCESS;\n } catch {\n return ExitCode.ERROR;\n }\n}\n\nexport function registerBinCommand(\n parentLogger: TsLogger,\n program: IGlobalProgram,\n servicesFactory: () => Promise<IServices>,\n): void {\n const logger = parentLogger.getSubLogger({ name: 'registerBinCommand' });\n\n program\n .command('bin <name>')\n .description(\n 'Print the absolute real path to a binary (resolving symlinks). Accepts tool name or binary name (from .bin()).',\n )\n .action(async (name: string) => {\n const services = await servicesFactory();\n const exitCode = await executeBinCommandAction(logger, name, services);\n exitCli(exitCode);\n });\n}\n",
|
|
225
|
+
"import type { IConfigService, ISystemInfo, ProjectConfig, ToolConfig } from '@dotfiles/config';\nimport { createShell, createToolLog, type IInstallContext } from '@dotfiles/core';\nimport type { IResolvedFileSystem } from '@dotfiles/file-system';\nimport { createConfiguredShell } from '@dotfiles/installer';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { exitCli, ExitCode, replaceInFile } from '@dotfiles/utils';\nimport { type IVersionChecker, VersionComparisonStatus } from '@dotfiles/version-checker';\nimport path from 'node:path';\nimport { messages } from './log-messages';\nimport type { ICommandCompletionMeta, IGlobalProgram, IServices } from './types';\n\n/**\n * Completion metadata for the check-updates command.\n */\nexport const CHECK_UPDATES_COMMAND_COMPLETION: ICommandCompletionMeta = {\n name: 'check-updates',\n description: 'Check for available tool updates',\n hasPositionalArg: true,\n positionalArgDescription: 'tool name (optional, checks all if omitted)',\n positionalArgType: 'tool',\n};\n\nasync function loadToolConfigs(\n logger: TsLogger,\n configService: IConfigService,\n toolName: string | undefined,\n projectConfig: ProjectConfig,\n fs: IResolvedFileSystem,\n systemInfo: ISystemInfo,\n): Promise<Record<string, ToolConfig> | null> {\n let toolConfigs: Record<string, ToolConfig> = {};\n if (toolName) {\n logger.debug(messages.commandCheckingUpdatesFor(toolName));\n try {\n const config = await configService.loadSingleToolConfig(\n logger,\n toolName,\n projectConfig.paths.toolConfigsDir,\n fs,\n projectConfig,\n systemInfo,\n );\n if (config) {\n toolConfigs[toolName] = config;\n } else {\n logger.error(messages.toolNotFound(toolName, projectConfig.paths.toolConfigsDir));\n return null;\n }\n } catch (error) {\n logger.error(messages.configLoadFailed(`tool \"${toolName}\"`), error);\n return null;\n }\n } else {\n try {\n logger.debug(messages.commandCheckingUpdatesForAll());\n toolConfigs = await configService.loadToolConfigs(\n logger,\n projectConfig.paths.toolConfigsDir,\n fs,\n projectConfig,\n systemInfo,\n );\n if (Object.keys(toolConfigs).length === 0) {\n logger.error(messages.toolNoConfigurationsFound(projectConfig.paths.toolConfigsDir));\n return null;\n }\n } catch (error) {\n logger.error(messages.configLoadFailed('tool configurations'), error);\n return null;\n }\n }\n return toolConfigs;\n}\n\nfunction createInstallContext(\n config: ToolConfig,\n projectConfig: ProjectConfig,\n systemInfo: ISystemInfo,\n fs: IResolvedFileSystem,\n logger: TsLogger,\n): IInstallContext {\n const timestamp = new Date().toISOString().replace(/:/g, '-').split('.')[0];\n const toolDir: string = config.configFilePath\n ? path.dirname(config.configFilePath)\n : projectConfig.paths.toolConfigsDir;\n\n const currentDir: string = path.join(projectConfig.paths.binariesDir, config.name, 'current');\n\n const context: IInstallContext = {\n toolName: config.name,\n toolDir,\n currentDir,\n stagingDir: '',\n timestamp: timestamp || '',\n systemInfo,\n toolConfig: config,\n projectConfig: projectConfig,\n $: createConfiguredShell(createShell(), process.env),\n fileSystem: fs,\n replaceInFile: (filePath, from, to, options) => replaceInFile(fs, filePath, from, to, options),\n resolve: () => {\n throw new Error('resolve not supported in version check context');\n },\n log: createToolLog(logger, config.name),\n };\n\n return context;\n}\n\nasync function logVersionStatus(\n logger: TsLogger,\n versionChecker: IVersionChecker,\n config: ToolConfig,\n currentVersion: string,\n latestVersion: string,\n hasUpdate: boolean,\n): Promise<void> {\n if (currentVersion === 'latest') {\n logger.info(messages.toolConfiguredToLatest(config.name, latestVersion));\n return;\n }\n\n if (hasUpdate) {\n logger.info(messages.toolUpdateAvailable(config.name, currentVersion, latestVersion));\n return;\n }\n\n const status = await versionChecker.checkVersionStatus(currentVersion, latestVersion);\n\n if (status === VersionComparisonStatus.UP_TO_DATE) {\n logger.info(messages.toolUpToDate(config.name, currentVersion, latestVersion));\n } else if (status === VersionComparisonStatus.AHEAD_OF_LATEST) {\n logger.info(messages.toolAheadOfLatest(config.name, currentVersion, latestVersion));\n } else {\n logger.warn(messages.toolVersionComparisonFailed(config.name, currentVersion, latestVersion));\n }\n}\n\nasync function checkToolUpdate(logger: TsLogger, config: ToolConfig, services: IServices): Promise<void> {\n const { projectConfig, versionChecker, pluginRegistry, systemInfo, fs } = services;\n\n const context = createInstallContext(config, projectConfig, systemInfo, fs, logger);\n const plugin = pluginRegistry.get(config.installationMethod);\n\n if (!plugin) {\n logger.warn(\n messages.commandUnsupportedOperation(\n 'check-updates',\n `installation method: \"${config.installationMethod}\" for tool \"${config.name}\"`,\n ),\n );\n return;\n }\n\n if (!plugin.supportsUpdateCheck || !plugin.supportsUpdateCheck()) {\n logger.info(\n messages.commandUnsupportedOperation(\n 'check-updates',\n `installation method: \"${config.installationMethod}\" for tool \"${config.name}\"`,\n ),\n );\n return;\n }\n\n const updateCheckResult = await plugin.checkUpdate?.(config.name, config, context, logger);\n\n if (!updateCheckResult) {\n logger.warn(messages.commandUnsupportedOperation('check-updates', config.name));\n return;\n }\n\n if (!updateCheckResult.success) {\n logger.error(messages.serviceGithubApiFailed('check update', 0), new Error(updateCheckResult.error));\n return;\n }\n\n const currentVersion = updateCheckResult.currentVersion || config.version || 'unknown';\n const latestVersion = updateCheckResult.latestVersion || 'unknown';\n\n await logVersionStatus(logger, versionChecker, config, currentVersion, latestVersion, updateCheckResult.hasUpdate);\n}\n\nexport async function checkUpdatesActionLogic(\n logger: TsLogger,\n toolName: string | undefined,\n services: IServices,\n): Promise<void> {\n logger.trace(messages.commandActionStarted('check-updates', toolName || 'all'));\n\n const toolConfigs = await loadToolConfigs(\n logger,\n services.configService,\n toolName,\n services.projectConfig,\n services.fs,\n services.systemInfo,\n );\n\n if (!toolConfigs) {\n return;\n }\n\n for (const config of Object.values(toolConfigs)) {\n await checkToolUpdate(logger, config, services);\n }\n}\n\nexport function registerCheckUpdatesCommand(\n parentLogger: TsLogger,\n program: IGlobalProgram,\n servicesFactory: () => Promise<IServices>,\n): void {\n const logger = parentLogger.getSubLogger({ name: 'registerCheckUpdatesCommand' });\n program\n .command('check-updates [toolName]')\n .description('Checks for available updates for configured tools. If [toolName] is provided, checks only that tool.')\n .action(async (toolName: string | undefined) => {\n try {\n const services = await servicesFactory();\n await checkUpdatesActionLogic(logger, toolName, services);\n } catch (error) {\n logger.error(messages.commandExecutionFailed('check-updates', ExitCode.ERROR), error);\n exitCli(ExitCode.ERROR);\n }\n });\n}\n",
|
|
226
|
+
"import { createSafeLogMessage, type SafeLogMessageMap } from '@dotfiles/logger';\n\nexport const messages = {\n proxyUnavailable: (port: number) =>\n createSafeLogMessage(\n `HTTP proxy not available on port ${port}. Start with 'bun proxy' or unset DEV_PROXY env var.`,\n ),\n proxyEnabled: (port: number) => createSafeLogMessage(`Routing requests through HTTP proxy on port ${port}`),\n proxyCheckingAvailability: (port: number) => createSafeLogMessage(`Checking proxy availability on port ${port}`),\n commandActionStarted: (commandName: string, targetTool?: string) =>\n createSafeLogMessage(`${commandName} command action logic started${targetTool ? `. Tool: ${targetTool}` : ''}`),\n commandConfigErrorDetails: () => createSafeLogMessage('Configuration loading error details: %O'),\n dryRunEnabled: () => createSafeLogMessage('Dry run enabled. Initializing MemFileSystem'),\n componentInitialized: (component: string) => createSafeLogMessage(`${component} initialized`),\n toolConfigsLoading: (toolConfigsDir: string) => createSafeLogMessage(`tool config loading: ${toolConfigsDir}`),\n toolConfigsLoaded: (toolConfigsDir: string, toolCount: number) =>\n createSafeLogMessage(`Configuration loaded from ${toolConfigsDir} (${toolCount} tools configured)`),\n toolConfigsForDryRun: () => createSafeLogMessage('tool configs for dry run'),\n commandCompleted: (isDryRun: boolean) => createSafeLogMessage(`DONE${isDryRun ? ' (dry run)' : ''}`),\n commandExecutionFailed: (commandName: string, exitCode: number) =>\n createSafeLogMessage(`Command failed [${commandName}] exit ${exitCode}`),\n commandVersionComparison: (toolName: string, configuredVersion: string, latestVersion: string) =>\n createSafeLogMessage(`Tool: ${toolName}, Configured: ${configuredVersion}, Latest: ${latestVersion}`),\n commandUnsupportedOperation: (operation: string, details: string) =>\n createSafeLogMessage(`${operation} not yet supported (${details})`),\n toolNotFound: (toolName: string, source: string) => createSafeLogMessage(`Tool \"${toolName}\" not found in ${source}`),\n toolInstalled: (toolName: string, version: string, method: string) =>\n createSafeLogMessage(`Tool \"${toolName}\" \\`${version}\\` installed successfully using ${method}`),\n toolAlreadyInstalled: (toolName: string, version: string) =>\n createSafeLogMessage(`Tool \"${toolName}\" \\`${version}\\` is already installed`),\n toolInstallSkippedConfigurationOnly: (toolName: string) =>\n createSafeLogMessage(`Tool \"${toolName}\" has no installation steps`),\n shimDeleted: (binaryName: string, shimPath: string) =>\n createSafeLogMessage(`Deleted temporary shim ${binaryName} at ${shimPath}`),\n toolNoConfigurationsFound: (toolConfigsDir: string) =>\n createSafeLogMessage(`No tool configurations found in ${toolConfigsDir}`),\n toolCheckingUpdates: (toolName: string) => createSafeLogMessage(`updates for ${toolName}`),\n toolUpdateAvailable: (toolName: string, currentVersion: string, latestVersion: string) =>\n createSafeLogMessage(`Update available for ${toolName}: ${currentVersion} -> ${latestVersion}`),\n toolUpToDate: (toolName: string, currentVersion: string, latestVersion: string) =>\n createSafeLogMessage(`${toolName} (${currentVersion}) is up to date. Latest: ${latestVersion}`),\n toolAheadOfLatest: (toolName: string, currentVersion: string, latestVersion: string) =>\n createSafeLogMessage(`${toolName} (${currentVersion}) is ahead of the latest known version (${latestVersion})`),\n toolConfiguredToLatest: (toolName: string, latestVersion: string) =>\n createSafeLogMessage(\n `Tool \"${toolName}\" is configured to 'latest'. The latest available version is ${latestVersion}`,\n ),\n toolVersionComparisonFailed: (toolName: string, currentVersion: string, latestVersion: string) =>\n createSafeLogMessage(\n `Could not determine update status for ${toolName} (${currentVersion}) against latest ${latestVersion}`,\n ),\n toolShimUpToDate: (toolName: string, version: string) =>\n createSafeLogMessage(`${toolName} is already up to date (${version})`),\n toolShimOnLatest: (toolName: string, version: string) =>\n createSafeLogMessage(`${toolName} is already on latest version (${version})`),\n toolShimUpdateStarting: (toolName: string, currentVersion: string, latestVersion: string) =>\n createSafeLogMessage(`Updating ${toolName} from ${currentVersion} to ${latestVersion}...`),\n toolProcessingUpdate: (toolName: string, currentVersion: string, latestVersion: string) =>\n createSafeLogMessage(`${toolName} update from ${currentVersion} to ${latestVersion}`),\n toolShimUpdateSuccess: (toolName: string, version: string) =>\n createSafeLogMessage(`${toolName} successfully updated to ${version}`),\n toolUpdated: (toolName: string, fromVersion: string, toVersion: string) =>\n createSafeLogMessage(`Tool \"${toolName}\" updated from \\`${fromVersion}\\` to \\`${toVersion}\\``),\n toolUpdateFailed: (toolName: string, reason: string) =>\n createSafeLogMessage(`Update failed for tool \"${toolName}\": ${reason}`),\n toolVersionPinned: (toolName: string, version: string) =>\n createSafeLogMessage(\n `Tool \"${toolName}\" is pinned to version \\`${version}\\`. Set version to \"latest\" in the tool config to enable updates`,\n ),\n toolUpdateNotSupported: (toolName: string, method: string) =>\n createSafeLogMessage(\n `Tool \"${toolName}\" uses ${method} which does not support updates. Performing regular install instead`,\n ),\n commandCheckingUpdatesFor: (toolName: string) => createSafeLogMessage(`Checking \"${toolName}\" for updates`),\n commandCheckingUpdatesForAll: () => createSafeLogMessage(`Checking all tools for updates`),\n fsReadFailed: (path: string) => createSafeLogMessage(`Failed to read ${path}`),\n fsAccessDenied: (operation: string, path: string) => createSafeLogMessage(`Access denied ${operation}: ${path}`),\n fsItemNotFound: (itemType: string, path: string) => createSafeLogMessage(`${itemType} not found: ${path}`),\n fsWrite: (toolName: string, path: string) => createSafeLogMessage(`[${toolName}] write ${path}`),\n toolConflictsDetected: (header: string, conflicts: string) => createSafeLogMessage(`${header}\\n${conflicts}`),\n noConflictsDetected: () => createSafeLogMessage('No conflicts detected'),\n cleanupAllTrackedFiles: () => createSafeLogMessage('Registry-based cleanup: Removing all tracked files'),\n cleanupRegistryDatabase: () => createSafeLogMessage('registry database cleanup'),\n cleanupRegistryDryRun: () => createSafeLogMessage('Would clean up registry database (dry run)'),\n cleanupToolFiles: (toolName: string) => createSafeLogMessage(`Registry-based cleanup: files for tool '${toolName}'`),\n cleanupRegistryTool: (toolName: string, dryRun: boolean) =>\n createSafeLogMessage(\n dryRun\n ? `Would remove registry entries for tool: ${toolName} (dry run)`\n : `Removed registry entries for tool: ${toolName}`,\n ),\n cleanupTypeFiles: (fileType: string) => createSafeLogMessage(`Registry-based cleanup: files of type '${fileType}'`),\n cleanupFoundFiles: (count: number, toolName: string, fileType?: string) =>\n createSafeLogMessage(`Found ${count} files for tool '${toolName}'${fileType ? ` of type '${fileType}'` : ''}`),\n cleanupFileRemoved: (path: string) => createSafeLogMessage(`[cleanup] rm ${path}`),\n fileCleanupDryRun: (filePath: string) => createSafeLogMessage(`Would delete: ${filePath}`),\n cleanupFileNotFound: (filePath: string) => createSafeLogMessage(`file not found ${filePath}`),\n cleanupDeleteFailed: (filePath: string) => createSafeLogMessage(`Failed to delete ${filePath}`),\n cleanupProcessStarted: (dryRun: boolean) => createSafeLogMessage(`starting cleanup process, dryRun=${dryRun}: %O`),\n configParameterOverridden: (field: string, value: string) =>\n createSafeLogMessage(`${field.charAt(0).toUpperCase() + field.slice(1)} overridden to: ${value}`),\n cleanupProcessFinished: (dryRun: boolean) => createSafeLogMessage(`cleanup process finished, dryRun=${dryRun}`),\n logCheckingFileStates: () => createSafeLogMessage('Checking file states for all tools'),\n logFileStatesForTool: (toolName: string) => createSafeLogMessage(`${toolName} files`),\n logFileStatus: (statusIcon: string, filePath: string, fileType: string, statusText: string, sizeText: string) =>\n createSafeLogMessage(`${statusIcon} ${filePath} [${fileType}] - ${statusText}${sizeText}`),\n logTargetStatus: (targetIcon: string, targetPath: string) => createSafeLogMessage(`${targetIcon} ${targetPath}`),\n logNoOperationsFound: () => createSafeLogMessage('No file operations found matching criteria'),\n logOperationHistory: (timestamp: string, operationMessage: string, metadata: string) =>\n createSafeLogMessage(`${timestamp} ${operationMessage}${metadata ? ` ${metadata}` : ''}`),\n cachingDisabled: () => createSafeLogMessage('Caching disabled'),\n registryInitialized: (path: string) => createSafeLogMessage(`File tracking initialized: ${path}`),\n cliStarted: () => createSafeLogMessage('CLI starting with arguments'),\n serviceGithubResourceNotFound: (resource: string, identifier: string) =>\n createSafeLogMessage(`GitHub ${resource} not found: ${identifier}`),\n serviceGithubApiFailed: (operation: string, status: number) =>\n createSafeLogMessage(`GitHub API failed [${operation}] ${status}`),\n configLoadFailed: (configPath: string) => createSafeLogMessage(`Failed to load configuration from ${configPath}`),\n configPathResolved: (configPath: string) => createSafeLogMessage(`Using configuration: ${configPath}`),\n configNotFound: () =>\n createSafeLogMessage('No configuration file found. Create dotfiles.config.ts or specify --config <path>'),\n configParameterIgnored: (field: string, reason: string) =>\n createSafeLogMessage(`Configuration field \"${field}\" ignored: ${reason}`),\n configParameterInvalid: (field: string, value: string, expected: string) =>\n createSafeLogMessage(`Invalid ${field}: \"${value}\" (expected ${expected})`),\n updatesCommandCompleted: () => createSafeLogMessage('Check-updates command completed'),\n toolTypesGenerated: (path: string) => createSafeLogMessage(`Generated tool types: ${path}`),\n toolNotInstalled: (toolName: string) => createSafeLogMessage(`Tool \"${toolName}\" is not installed`),\n installPathNotFound: (path: string) => createSafeLogMessage(`Installation path not found: ${path}`),\n filesCommandShowingTree: (path: string) => createSafeLogMessage(path),\n filesCommandEmptyDirectory: () => createSafeLogMessage('(empty directory)'),\n filesCommandTree: (treeOutput: string) => createSafeLogMessage(treeOutput),\n skillAlreadyExists: (destPath: string) => createSafeLogMessage(`Destination already exists, removing: ${destPath}`),\n skillDryRun: (destPath: string, sourcePath: string) =>\n createSafeLogMessage(`Would copy skill: ${sourcePath} -> ${destPath}`),\n skillCopied: (destPath: string, sourcePath: string) =>\n createSafeLogMessage(`Copied skill: ${sourcePath} -> ${destPath}`),\n skillCopyFailed: (destPath: string) => createSafeLogMessage(`Failed to copy skill to: ${destPath}`),\n cliCompletionGenerated: (path: string) => createSafeLogMessage(`CLI completion generated: ${path}`),\n dashboardStopping: () => createSafeLogMessage('Stopping dashboard server'),\n dashboardBrowserOpenFailed: () => createSafeLogMessage('Failed to open browser'),\n toolLookupByBinaryStarted: (binaryName: string) =>\n createSafeLogMessage(`Tool not found by name '${binaryName}', searching by binary name`),\n toolFoundByBinary: (binaryName: string, toolName: string) =>\n createSafeLogMessage(`Binary '${binaryName}' is provided by tool '${toolName}'`),\n toolNotFoundByBinary: (name: string, toolConfigsDir: string) =>\n createSafeLogMessage(`No tool or binary named \"${name}\" found in ${toolConfigsDir}`),\n envCreated: (envDir: string) => createSafeLogMessage(`Environment created at ${envDir}`),\n envDeleted: (envDir: string) => createSafeLogMessage(`Environment deleted at ${envDir}`),\n envNotFound: (envDir: string) => createSafeLogMessage(`Environment not found at ${envDir}`),\n envOperationFailed: (operation: string, error: string) =>\n createSafeLogMessage(`Environment ${operation} failed: ${error}`),\n envDeletionCancelled: () => createSafeLogMessage('Deletion cancelled'),\n envActivationHint: (envName: string) => createSafeLogMessage(`To activate: source ${envName}/source`),\n envConfigPath: (configPath: string) => createSafeLogMessage(`Config file: ${configPath}`),\n envConfigFromEnvVar: (configPath: string) =>\n createSafeLogMessage(`Using config from DOTFILES_ENV_DIR: ${configPath}`),\n} satisfies SafeLogMessageMap;\n",
|
|
227
|
+
"import type { IFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport type { IFileOperation, IFileRegistry } from '@dotfiles/registry/file';\nimport { contractHomePath, exitCli } from '@dotfiles/utils';\nimport { messages } from './log-messages';\nimport type {\n ICleanupCommandSpecificOptions,\n ICommandCompletionMeta,\n IGlobalProgram,\n IGlobalProgramOptions,\n IServices,\n} from './types';\n\n/**\n * Completion metadata for the cleanup command.\n */\nexport const CLEANUP_COMMAND_COMPLETION: ICommandCompletionMeta = {\n name: 'cleanup',\n description: 'Clean up generated files and registry entries',\n options: [\n { flag: '--tool', description: 'Cleanup files for specific tool', hasArg: true, argPlaceholder: '<name>' },\n { flag: '--type', description: 'Cleanup files of specific type', hasArg: true, argPlaceholder: '<type>' },\n { flag: '--all', description: 'Cleanup all tracked files' },\n ],\n};\n\nasync function cleanupAllTrackedFiles(\n logger: TsLogger,\n fs: IFileSystem,\n fileRegistry: IFileRegistry,\n homeDir: string,\n dryRun: boolean,\n): Promise<void> {\n logger.info(messages.cleanupAllTrackedFiles());\n\n const allTools = await fileRegistry.getRegisteredTools();\n for (const toolName of allTools) {\n await cleanupToolFiles(logger, fs, fileRegistry, toolName, homeDir, undefined, dryRun);\n }\n\n if (!dryRun) {\n for (const toolName of allTools) {\n await fileRegistry.removeToolOperations(toolName);\n }\n logger.info(messages.cleanupRegistryDatabase());\n } else {\n logger.info(messages.cleanupRegistryDryRun());\n }\n}\n\nasync function cleanupSpecificTool(\n logger: TsLogger,\n fs: IFileSystem,\n fileRegistry: IFileRegistry,\n toolName: string,\n homeDir: string,\n fileType: string | undefined,\n dryRun: boolean,\n): Promise<void> {\n logger.info(messages.cleanupToolFiles(toolName));\n await cleanupToolFiles(logger, fs, fileRegistry, toolName, homeDir, fileType, dryRun);\n\n if (!dryRun) {\n await fileRegistry.removeToolOperations(toolName);\n logger.info(messages.cleanupRegistryTool(toolName, false));\n } else {\n logger.info(messages.cleanupRegistryTool(toolName, true));\n }\n}\n\nasync function cleanupSpecificType(\n logger: TsLogger,\n fs: IFileSystem,\n fileRegistry: IFileRegistry,\n fileType: string,\n homeDir: string,\n dryRun: boolean,\n): Promise<void> {\n logger.info(messages.cleanupTypeFiles(fileType));\n const operations = await fileRegistry.getOperations({ fileType: fileType as IFileOperation['fileType'] });\n\n for (const operation of operations) {\n const fileState = await fileRegistry.getFileState(operation.filePath);\n if (fileState && fileState.lastOperation !== 'rm') {\n await removeFile(logger, fs, operation.filePath, homeDir, dryRun);\n }\n }\n}\n\nasync function registryBasedCleanup(\n logger: TsLogger,\n services: IServices,\n options: ICleanupCommandSpecificOptions & IGlobalProgramOptions,\n): Promise<void> {\n const { fs, fileRegistry } = services;\n const { dryRun, tool, type, all } = options;\n const homeDir = services.projectConfig.paths.homeDir;\n\n if (all) {\n await cleanupAllTrackedFiles(logger, fs, fileRegistry, homeDir, dryRun);\n } else if (tool) {\n await cleanupSpecificTool(logger, fs, fileRegistry, tool, homeDir, type, dryRun);\n } else if (type) {\n await cleanupSpecificType(logger, fs, fileRegistry, type, homeDir, dryRun);\n } else {\n logger.warn(\n messages.configParameterIgnored(\n 'cleanup options',\n 'Registry-based cleanup requires --all, --tool <name>, or --type <type> option',\n ),\n );\n }\n}\n\nasync function cleanupToolFiles(\n logger: TsLogger,\n fs: IFileSystem,\n fileRegistry: IFileRegistry,\n toolName: string,\n homeDir: string,\n fileType?: string,\n dryRun?: boolean,\n): Promise<void> {\n const fileStates = await fileRegistry.getFileStatesForTool(toolName);\n\n let filteredStates = fileStates;\n if (fileType) {\n filteredStates = fileStates.filter((state) => state.fileType === fileType);\n }\n\n logger.trace(messages.cleanupFoundFiles(filteredStates.length, toolName, fileType));\n\n for (const fileState of filteredStates) {\n if (fileState.lastOperation !== 'rm') {\n // For symlinks, remove the symlink (targetPath), not the original file (filePath)\n const pathToRemove = fileState.fileType === 'symlink' && fileState.targetPath\n ? fileState.targetPath\n : fileState.filePath;\n await removeFile(logger, fs, pathToRemove, homeDir, dryRun || false);\n }\n }\n}\n\nasync function removeFile(\n logger: TsLogger,\n fs: IFileSystem,\n filePath: string,\n homeDir: string,\n dryRun?: boolean,\n): Promise<void> {\n try {\n if (await fs.exists(filePath)) {\n if (!dryRun) {\n await fs.rm(filePath, { force: true });\n logger.info(messages.cleanupFileRemoved(contractHomePath(homeDir, filePath)));\n } else {\n logger.info(messages.fileCleanupDryRun(filePath));\n }\n } else {\n logger.debug(messages.cleanupFileNotFound(filePath));\n }\n } catch (error) {\n logger.error(messages.cleanupDeleteFailed(filePath), error);\n }\n}\n\nasync function cleanupActionLogic(\n logger: TsLogger,\n options: ICleanupCommandSpecificOptions & IGlobalProgramOptions,\n services: IServices,\n): Promise<void> {\n const { dryRun, tool, type, all } = options;\n\n try {\n logger.trace(messages.cleanupProcessStarted(dryRun), options);\n\n // Use registry-based cleanup (default behavior is now --all)\n const cleanupOptions = { ...options, all: all || (!tool && !type) };\n await registryBasedCleanup(logger, services, cleanupOptions);\n\n logger.trace(messages.cleanupProcessFinished(dryRun));\n } catch (error) {\n logger.error(messages.commandExecutionFailed('cleanup', 1), error);\n exitCli(1);\n }\n}\n\nexport function registerCleanupCommand(\n parentLogger: TsLogger,\n program: IGlobalProgram,\n servicesFactory: () => Promise<IServices>,\n): void {\n const logger = parentLogger.getSubLogger({ name: 'registerCleanupCommand' });\n program\n .command('cleanup')\n .description('Remove all generated artifacts, including shims, shell configurations, and the .generated directory.')\n .option('--tool <name>', 'Remove files for specific tool only (registry-based)')\n .option('--type <type>', 'Remove files of specific type only (registry-based)')\n .option('--all', 'Remove all tracked files (registry-based)')\n .action(async (options: ICleanupCommandSpecificOptions) => {\n const combinedOptions: ICleanupCommandSpecificOptions & IGlobalProgramOptions = { ...options, ...program.opts() };\n const services = await servicesFactory();\n await cleanupActionLogic(logger, combinedOptions, services);\n });\n}\n",
|
|
228
|
+
"import { ARCH_VALUES, OS_VALUES } from '@dotfiles/config';\nimport { LOG_LEVEL_NAMES } from '@dotfiles/logger';\nimport { Command } from 'commander';\nimport type { ICommandCompletionMeta, IGlobalProgram } from './types';\n\n/**\n * Completion metadata for global CLI options.\n * These apply to all commands.\n */\nexport const GLOBAL_OPTIONS_COMPLETION: ICommandCompletionMeta = {\n name: 'dotfiles',\n description: 'Dotfiles management CLI',\n options: [\n { flag: '--config', description: 'Path to configuration file', hasArg: true, argPlaceholder: '<path>' },\n { flag: '--dry-run', description: 'Simulate operations without changes' },\n { flag: '--log', description: 'Set log level', hasArg: true, argPlaceholder: '<level>' },\n { flag: '--verbose', description: 'Enable detailed debug messages' },\n { flag: '--quiet', description: 'Suppress informational output' },\n { flag: '--platform', description: 'Override detected platform', hasArg: true, argPlaceholder: '<platform>' },\n { flag: '--arch', description: 'Override detected architecture', hasArg: true, argPlaceholder: '<arch>' },\n ],\n};\n\n/**\n * Creates and configures the main Commander.js program with global options.\n *\n * Sets up the CLI program with global options available to all commands:\n * - Configuration file path\n * - Dry-run mode\n * - Log level control\n * - Platform/architecture overrides\n *\n * @returns The configured Commander.js program instance\n * @example\n * ```typescript\n * const program = createProgram();\n * program\n * .command('install')\n * .action(async (options) => {\n * const globalOpts = program.opts();\n * console.log(globalOpts.verbose); // Access global options\n * });\n * ```\n */\nexport function createProgram(): IGlobalProgram {\n const program: IGlobalProgram = new Command()\n .name('generator')\n .description('CLI tool for managing dotfiles and tool configurations')\n .version(process.env.DOTFILES_VERSION ?? '0.0.0')\n .option('--config <path>', 'Path to a configuration file', '')\n .option('--dry-run', 'Simulate all operations without making changes to the file system', false)\n .option('--trace', 'Show file paths and line numbers in log output', false)\n .option(`--log <level>`, `Set log level (${LOG_LEVEL_NAMES.join(', ')})`, 'default')\n .option('--verbose', 'Enable detailed debug messages (alias for --log=verbose)', false)\n .option(\n '--quiet',\n 'Suppress all informational and debug output. Errors are still displayed (alias for --log=quiet)',\n false,\n )\n .option('--platform <platform>', `Override the detected platform (${OS_VALUES.join(', ')})`)\n .option('--arch <arch>', `Override the detected architecture (${ARCH_VALUES.join(', ')})`);\n\n return program;\n}\n",
|
|
229
|
+
"import fs from 'node:fs';\nimport path from 'node:path';\n\nimport type { TsLogger } from '@dotfiles/logger';\nimport { messages } from './log-messages';\nimport { createApiRoutes } from './routes';\nimport type { IDashboardServer, IDashboardServerOptions, IDashboardServices } from './types';\n\n// Bun HTML import - handles bundling automatically\nimport clientApp from '../client/dashboard.html';\n\n/**\n * The directory containing this module (and the bundled chunk files).\n * Bun's HTML import feature generates chunks with relative paths that are resolved\n * from the current working directory. We need to ensure the CWD is the package\n * directory where the chunks are located.\n */\nconst PACKAGE_DIR = import.meta.dir;\nconst IS_DEV = process.env.NODE_ENV === 'development';\nconst IS_RELOAD = IS_DEV && process.env['DOTFILES_IS_RELOAD'] === '1';\n\n/**\n * WORKAROUND: Bun HTMLBundle bug - certain JS chunks (especially those starting\n * with \"// @bun\" CJS interop helpers) are served with Content-Type: text/html\n * instead of text/javascript. This causes browser errors.\n * Bug: https://github.com/oven-sh/bun/issues/23431\n * TODO: Remove this workaround once the Bun bug is fixed\n *\n * Generate explicit routes for all JS files in the directory to ensure\n * they are served with the correct Content-Type.\n */\nfunction generateJsFileRoutes(dir: string): Record<string, () => Response> {\n const routes: Record<string, () => Response> = {};\n\n try {\n const files = fs.readdirSync(dir);\n for (const file of files) {\n if (file.endsWith('.js')) {\n const filePath = path.join(dir, file);\n routes[`/${file}`] = () => {\n return new Response(Bun.file(filePath), {\n headers: { 'Content-Type': 'text/javascript' },\n });\n };\n }\n }\n } catch {\n // Ignore errors (e.g., in dev mode where chunks might not exist on disk)\n }\n\n return routes;\n}\n\n/**\n * Creates and returns a dashboard server instance.\n */\nexport function createDashboardServer(\n parentLogger: TsLogger,\n services: IDashboardServices,\n options: IDashboardServerOptions,\n): IDashboardServer {\n const logger = parentLogger.getSubLogger({ name: 'DashboardServer' });\n const api = createApiRoutes(logger, services);\n let server: ReturnType<typeof Bun.serve> | null = null;\n\n return {\n async start() {\n process.env['DOTFILES_IS_RELOAD'] = '1';\n\n // IMPORTANT: Change to package directory before starting server.\n // Bun's HTML import generates chunk files (like dashboard-*.js, cli-*.js)\n // that are referenced with relative paths (e.g., \"./dashboard-pks45b1c.js\").\n // These paths are resolved from the CWD, so we must ensure we're in the\n // directory where the chunks are located.\n if (!IS_DEV) {\n process.chdir(PACKAGE_DIR);\n }\n\n // Generate JS file routes AFTER chdir (in production) so we scan the correct directory\n const jsRoutes = IS_DEV ? {} : generateJsFileRoutes(process.cwd());\n\n server = Bun.serve({\n port: options.port,\n hostname: options.host,\n development: IS_DEV,\n routes: {\n // JS file routes first (workaround for Bun HTMLBundle Content-Type bug)\n ...jsRoutes,\n\n '/api/tools': async () => {\n const result = await api.getTools();\n return Response.json(result);\n },\n\n '/api/stats': async () => {\n const result = await api.getStats();\n return Response.json(result);\n },\n\n '/api/health': async () => {\n const result = await api.getHealth();\n return Response.json(result);\n },\n\n '/api/config': async () => {\n const result = await api.getConfig();\n return Response.json(result);\n },\n\n '/api/tool-configs-tree': async () => {\n const result = await api.getToolConfigsTree();\n return Response.json(result);\n },\n\n '/api/shell': async () => {\n const result = await api.getShellIntegration();\n return Response.json(result);\n },\n\n '/api/activity': async (req) => {\n const url = new URL(req.url);\n const limitParam = url.searchParams.get('limit');\n const limit = limitParam ? parseInt(limitParam, 10) : undefined;\n const result = await api.getActivity(limit);\n return Response.json(result);\n },\n\n '/api/tools/:name/history': async (req: Request & { params: { name: string; }; }) => {\n const toolName = decodeURIComponent(req.params.name);\n const result = await api.getToolHistory(toolName);\n return Response.json(result);\n },\n\n '/api/tools/:name/readme': async (req: Request & { params: { name: string; }; }) => {\n const toolName = decodeURIComponent(req.params.name);\n const result = await api.getToolReadme(toolName);\n return Response.json(result);\n },\n\n '/api/tools/:name/source': async (req: Request & { params: { name: string; }; }) => {\n const toolName = decodeURIComponent(req.params.name);\n const result = await api.getToolSource(toolName);\n return Response.json(result);\n },\n\n '/api/tools/:name/install': async (req: Request & { params: { name: string; }; }) => {\n if (req.method !== 'POST') {\n return Response.json({ success: false, error: 'Method not allowed' }, { status: 405 });\n }\n const toolName = decodeURIComponent(req.params.name);\n const body = await req.json().catch(() => ({}));\n const result = await api.installTool(toolName, body);\n return Response.json(result);\n },\n\n '/api/tools/:name/check-update': async (req: Request & { params: { name: string; }; }) => {\n if (req.method !== 'POST') {\n return Response.json({ success: false, error: 'Method not allowed' }, { status: 405 });\n }\n const toolName = decodeURIComponent(req.params.name);\n const result = await api.checkToolUpdate(toolName);\n return Response.json(result);\n },\n\n '/api/tools/:name/update': async (req: Request & { params: { name: string; }; }) => {\n if (req.method !== 'POST') {\n return Response.json({ success: false, error: 'Method not allowed' }, { status: 405 });\n }\n const toolName = decodeURIComponent(req.params.name);\n const result = await api.updateTool(toolName);\n return Response.json(result);\n },\n\n '/api/recent-tools': async (req) => {\n const url = new URL(req.url);\n const limitParam = url.searchParams.get('limit');\n const limit = limitParam ? parseInt(limitParam, 10) : undefined;\n const result = await api.getRecentTools(limit);\n return Response.json(result);\n },\n\n '/api/*': Response.json({ success: false, error: 'Not found' }, { status: 404 }),\n\n // In development mode, Bun generates asset paths like \"/../server/chunk-*.css\"\n // due to HTML being imported from a different directory. This route redirects\n // those requests to the correct root-level paths where Bun actually serves them.\n '/server/*': (req: Request) => {\n const url = new URL(req.url);\n const assetPath = url.pathname.replace(/^\\/server\\//, '/');\n return Response.redirect(new URL(assetPath, url.origin).href, 302);\n },\n\n // Wildcard route for SPA - Bun handles HTMLBundle specially\n '/*': clientApp,\n },\n fetch(request) {\n const url = new URL(request.url);\n logger.debug(messages.requestReceived(request.method, url.pathname));\n return new Response('Not Found', { status: 404 });\n },\n });\n\n logger.info(messages.serverStarted(this.getUrl()));\n return IS_RELOAD;\n },\n\n async stop() {\n logger.info(messages.serverStopping());\n if (server) {\n server.stop();\n server = null;\n }\n logger.info(messages.serverStopped());\n },\n\n getUrl() {\n return `http://${options.host}:${options.port}`;\n },\n };\n}\n\nexport type { IDashboardServer, IDashboardServerOptions, IDashboardServices };\n",
|
|
230
|
+
"import { createSafeLogMessage } from '@dotfiles/logger';\n\nexport const messages = {\n serverStarted: (url: string) => createSafeLogMessage(`Dashboard available at ${url}`),\n serverStopping: () => createSafeLogMessage('Stopping dashboard server'),\n serverStopped: () => createSafeLogMessage('Dashboard server stopped'),\n requestReceived: (method: string, path: string) => createSafeLogMessage(`${method} ${path}`),\n apiError: (endpoint: string) => createSafeLogMessage(`API error in ${endpoint}`),\n installFailed: (error: string) => createSafeLogMessage(`Installation failed: ${error}`),\n installSucceeded: () => createSafeLogMessage('Installation succeeded'),\n checkUpdateCompleted: (hasUpdate: boolean, currentVersion: string, latestVersion: string) =>\n createSafeLogMessage(\n `Update check complete: hasUpdate=${hasUpdate} current=${currentVersion} latest=${latestVersion}`,\n ),\n checkUpdateFailed: (error: string) => createSafeLogMessage(`Update check failed: ${error}`),\n updateNotSupported: (method: string) => createSafeLogMessage(`Update not supported for method ${method}`),\n updateFailed: (error: string) => createSafeLogMessage(`Update failed: ${error}`),\n updateSucceeded: (oldVersion: string, newVersion: string) =>\n createSafeLogMessage(`Updated from ${oldVersion} to ${newVersion}`),\n};\n",
|
|
231
|
+
"import type { IBinaryConfig, ISystemInfo, ToolConfig } from '@dotfiles/core';\nimport { Architecture, Platform } from '@dotfiles/core';\nimport type { IFileOperation, IFileState } from '@dotfiles/registry/file';\nimport type { IToolInstallationRecord } from '@dotfiles/registry/tool';\n\n/**\n * Standard API response wrapper for all dashboard endpoints.\n */\nexport interface IApiResponse<T> {\n success: boolean;\n data?: T;\n error?: string;\n}\n\n/**\n * Serializable binary configuration for API response.\n */\nexport type ISerializableBinary = string | IBinaryConfig;\n\n/**\n * Serializable install params - common fields across all methods.\n * Method-specific fields are included as optional for flexibility.\n */\nexport interface ISerializableInstallParams {\n /** GitHub repository (github-release, zsh-plugin) */\n repo?: string;\n /** Asset pattern (github-release) */\n assetPattern?: string;\n /** Use GitHub CLI for downloads (github-release) */\n ghCli?: boolean;\n /** Crate name (cargo) */\n crate?: string;\n /** Homebrew formula or cask name (brew) */\n formula?: string;\n /** URL (curl-script, curl-tar) */\n url?: string;\n}\n\n/**\n * Serializable symlink configuration.\n */\nexport interface ISerializableSymlink {\n source: string;\n target: string;\n}\n\n/**\n * Serializable platform configuration entry.\n * Represents platform-specific overrides in a JSON-safe format.\n */\nexport interface ISerializablePlatformConfigEntry {\n /** Display names for target platforms (e.g., [\"Linux\", \"macOS\"]) */\n platforms: string[];\n /** Display names for target architectures (e.g., [\"x86_64\", \"arm64\"]) - undefined means all architectures */\n architectures?: string[];\n /** Platform-specific installation method override */\n installationMethod?: string;\n /** Platform-specific install params override */\n installParams?: ISerializableInstallParams;\n /** Platform-specific binaries override */\n binaries?: ISerializableBinary[];\n /** Platform-specific symlinks override */\n symlinks?: ISerializableSymlink[];\n}\n\n/**\n * JSON-serializable tool configuration from .tool.ts files.\n * Contains static configuration, not runtime state.\n */\nexport interface ISerializableToolConfig {\n name: string;\n version: string;\n installationMethod: string;\n installParams: ISerializableInstallParams;\n binaries?: ISerializableBinary[];\n dependencies?: string[];\n symlinks?: ISerializableSymlink[];\n disabled?: boolean;\n hostname?: string;\n configFilePath?: string;\n /** Platform-specific configuration overrides */\n platformConfigs?: ISerializablePlatformConfigEntry[];\n}\n\n/**\n * Runtime state from the installation registry.\n */\nexport interface IToolRuntimeState {\n status: 'installed' | 'not-installed' | 'error';\n installedVersion: string | null;\n installedAt: string | null;\n installPath: string | null;\n binaryPaths: string[];\n hasUpdate: boolean;\n}\n\n/**\n * Complete tool detail combining static config and runtime state.\n */\nexport interface IToolDetail {\n /** Static configuration from .tool.ts */\n config: ISerializableToolConfig;\n /** Runtime state from registry */\n runtime: IToolRuntimeState;\n /** Files tracked by the file registry */\n files: IFileState[];\n /** Binary disk size in bytes for this tool */\n binaryDiskSize: number;\n /** Usage statistics gathered from shim executions */\n usage: IToolUsageSummary;\n}\n\n/**\n * Per-binary usage stats for a tool.\n */\nexport interface IToolBinaryUsage {\n binaryName: string;\n count: number;\n lastUsedAt: string | null;\n}\n\n/**\n * Usage summary for a tool.\n */\nexport interface IToolUsageSummary {\n totalCount: number;\n binaries: IToolBinaryUsage[];\n}\n\n/**\n * Tool summary for catalog listing (subset of detail).\n */\nexport interface IToolSummary {\n name: string;\n version: string;\n installationMethod: string;\n status: 'installed' | 'not-installed' | 'error';\n installedVersion: string | null;\n hasUpdate: boolean;\n binaries?: ISerializableBinary[];\n}\n\n/**\n * File tree entry for displaying tool config directory structure.\n */\nexport interface IFileTreeEntry {\n name: string;\n path: string;\n type: 'file' | 'directory';\n children?: IFileTreeEntry[];\n /** For tool files, the associated tool name */\n toolName?: string;\n}\n\n/**\n * Tool configs file tree response.\n */\nexport interface IToolConfigsTree {\n rootPath: string;\n entries: IFileTreeEntry[];\n}\n\n/**\n * Dashboard statistics for overview page.\n */\nexport interface IDashboardStats {\n toolsInstalled: number;\n updatesAvailable: number;\n filesTracked: number;\n totalOperations: number;\n oldestOperation: string | null;\n newestOperation: string | null;\n}\n\n/**\n * Health check result for a single check.\n */\nexport interface IHealthCheckResult {\n name: string;\n status: 'pass' | 'warn' | 'fail';\n message: string;\n details?: string[];\n}\n\n/**\n * Overall health status.\n */\nexport interface IHealthStatus {\n overall: 'healthy' | 'warning' | 'unhealthy';\n checks: IHealthCheckResult[];\n lastCheck: string;\n}\n\n/**\n * File operation for timeline display.\n */\nexport interface IFileOperationDisplay extends IFileOperation {\n formattedTime: string;\n}\n\n/**\n * Project configuration summary for settings display.\n */\nexport interface IConfigSummary {\n dotfilesDir: string;\n generatedDir: string;\n binariesDir: string;\n targetDir: string;\n toolConfigsDir: string;\n}\n\n/**\n * Convert a Platform bitmask to an array of human-readable platform names.\n */\nexport function platformBitmaskToNames(platforms: Platform): string[] {\n const names: string[] = [];\n if (platforms & Platform.Linux) names.push('Linux');\n if (platforms & Platform.MacOS) names.push('macOS');\n if (platforms & Platform.Windows) names.push('Windows');\n return names;\n}\n\n/**\n * Convert an Architecture bitmask to an array of human-readable architecture names.\n */\nexport function architectureBitmaskToNames(architectures: Architecture): string[] {\n const names: string[] = [];\n if (architectures & Architecture.X86_64) names.push('x86_64');\n if (architectures & Architecture.Arm64) names.push('arm64');\n return names;\n}\n\n/**\n * Extract serializable install params from a config object.\n */\nfunction extractInstallParams(params: Record<string, unknown>): ISerializableInstallParams {\n const installParams: ISerializableInstallParams = {};\n if (typeof params['repo'] === 'string') installParams.repo = params['repo'];\n if (typeof params['assetPattern'] === 'string') installParams.assetPattern = params['assetPattern'];\n if (typeof params['ghCli'] === 'boolean') installParams.ghCli = params['ghCli'];\n // Handle both 'crate' and 'crateName' (cargo uses crateName internally)\n if (typeof params['crate'] === 'string') installParams.crate = params['crate'];\n if (typeof params['crateName'] === 'string') installParams.crate = params['crateName'];\n if (typeof params['formula'] === 'string') installParams.formula = params['formula'];\n if (typeof params['url'] === 'string') installParams.url = params['url'];\n return installParams;\n}\n\n/**\n * Serialize a ToolConfig to a JSON-safe structure.\n * Strips functions and non-serializable fields.\n */\nexport function serializeToolConfig(config: ToolConfig): ISerializableToolConfig {\n // Extract serializable install params (varies by method)\n const installParams: ISerializableInstallParams = {};\n\n if ('installParams' in config && config.installParams) {\n const params = config.installParams as Record<string, unknown>;\n Object.assign(installParams, extractInstallParams(params));\n }\n\n // Serialize platform configs if present\n let platformConfigs: ISerializablePlatformConfigEntry[] | undefined;\n if (config.platformConfigs && config.platformConfigs.length > 0) {\n platformConfigs = config.platformConfigs.map((entry) => {\n const serialized: ISerializablePlatformConfigEntry = {\n platforms: platformBitmaskToNames(entry.platforms),\n };\n\n if (entry.architectures !== undefined) {\n serialized.architectures = architectureBitmaskToNames(entry.architectures);\n }\n\n const platformConfig = entry.config;\n if (platformConfig.installationMethod) {\n serialized.installationMethod = platformConfig.installationMethod;\n }\n if (platformConfig.installParams) {\n serialized.installParams = extractInstallParams(platformConfig.installParams as Record<string, unknown>);\n }\n if (platformConfig.binaries) {\n serialized.binaries = platformConfig.binaries;\n }\n if (platformConfig.symlinks) {\n serialized.symlinks = platformConfig.symlinks;\n }\n\n return serialized;\n });\n }\n\n return {\n name: config.name,\n version: config.version,\n installationMethod: config.installationMethod,\n installParams,\n binaries: config.binaries,\n dependencies: config.dependencies,\n symlinks: config.symlinks,\n disabled: config.disabled,\n hostname: config.hostname,\n configFilePath: config.configFilePath,\n platformConfigs,\n };\n}\n\n/**\n * Get runtime state for a tool from the installation registry.\n */\nexport function getToolRuntimeState(\n toolName: string,\n installations: Map<string, IToolInstallationRecord>,\n): IToolRuntimeState {\n const record = installations.get(toolName);\n\n if (!record) {\n return {\n status: 'not-installed',\n installedVersion: null,\n installedAt: null,\n installPath: null,\n binaryPaths: [],\n hasUpdate: false,\n };\n }\n\n return {\n status: 'installed',\n installedVersion: record.version,\n installedAt: record.installedAt.toISOString(),\n installPath: record.installPath,\n binaryPaths: record.binaryPaths || [],\n hasUpdate: false,\n };\n}\n\n/**\n * Convert a ToolConfig and registry state to a full IToolDetail.\n * Serializes the original config (including platformConfigs) for visualization.\n */\nexport function toToolDetail(\n config: ToolConfig,\n installations: Map<string, IToolInstallationRecord>,\n files: IFileState[],\n _systemInfo: ISystemInfo,\n binaryDiskSize: number,\n usage: IToolUsageSummary,\n): IToolDetail {\n // Serialize the original config (not resolved) to preserve platformConfigs for visualization\n return {\n config: serializeToolConfig(config),\n runtime: getToolRuntimeState(config.name, installations),\n files,\n binaryDiskSize,\n usage,\n };\n}\n\n/**\n * Format a Unix timestamp for display.\n */\nexport function formatTimestamp(timestamp: number): string {\n return new Date(timestamp).toISOString();\n}\n\n/**\n * Shell file info for shell integration view.\n */\nexport interface IShellFile {\n toolName: string;\n filePath: string;\n fileType: 'completion' | 'init';\n lastModified: string;\n}\n\n/**\n * Shell integration summary.\n */\nexport interface IShellIntegration {\n completions: IShellFile[];\n initScripts: IShellFile[];\n totalFiles: number;\n}\n\n/**\n * Activity item for activity feed.\n */\nexport interface IActivityItem {\n id: number;\n toolName: string;\n action: string;\n description: string;\n timestamp: string;\n relativeTime: string;\n}\n\n/**\n * Activity feed response.\n */\nexport interface IActivityFeed {\n activities: IActivityItem[];\n totalCount: number;\n}\n\n/**\n * Format a timestamp as relative time (e.g., \"2 minutes ago\").\n */\nexport function formatRelativeTime(timestamp: number): string {\n const now = Date.now();\n const diff = now - timestamp;\n\n const seconds = Math.floor(diff / 1000);\n if (seconds < 60) {\n return 'just now';\n }\n\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) {\n return `${minutes} minute${minutes === 1 ? '' : 's'} ago`;\n }\n\n const hours = Math.floor(minutes / 60);\n if (hours < 24) {\n return `${hours} hour${hours === 1 ? '' : 's'} ago`;\n }\n\n const days = Math.floor(hours / 24);\n if (days < 30) {\n return `${days} day${days === 1 ? '' : 's'} ago`;\n }\n\n const months = Math.floor(days / 30);\n return `${months} month${months === 1 ? '' : 's'} ago`;\n}\n\n/**\n * Single file entry for the files list.\n */\nexport interface IFileEntry {\n filePath: string;\n fileType: string;\n toolName: string;\n}\n\n/**\n * Files list response (flat list, UI builds tree).\n */\nexport interface IFilesList {\n files: IFileEntry[];\n totalCount: number;\n}\n\n/**\n * File tree node for UI display.\n */\nexport interface IFileTreeNode {\n name: string;\n path: string;\n type: 'directory' | 'file';\n fileType?: string;\n toolName?: string;\n children?: IFileTreeNode[];\n}\n\n/**\n * Tool history entry for timeline display.\n */\nexport interface IToolHistoryEntry {\n id: number;\n operationType: string;\n fileType: string;\n filePath: string;\n timestamp: string;\n relativeTime: string;\n}\n\n/**\n * Tool history response.\n */\nexport interface IToolHistory {\n entries: IToolHistoryEntry[];\n totalCount: number;\n installedAt: string | null;\n dotfilesDir: string;\n}\n\n/**\n * Timestamp source for recent tools.\n */\nexport type TimestampSource = 'git' | 'mtime';\n\n/**\n * Recently added tool file entry.\n */\nexport interface IRecentToolFile {\n name: string;\n configFilePath: string;\n createdAt: string;\n relativeTime: string;\n timestampSource: TimestampSource;\n}\n\n/**\n * Recently added tools response.\n */\nexport interface IRecentTools {\n tools: IRecentToolFile[];\n}\n\n/**\n * Request body for POST /api/tools/:name/install\n */\nexport interface IInstallToolRequest {\n /** Whether to force reinstallation even if already installed */\n force?: boolean;\n}\n\n/**\n * Response for POST /api/tools/:name/install\n */\nexport interface IInstallToolResponse {\n /** Whether the installation was successful */\n installed: boolean;\n /** Installed version (when successful) */\n version?: string;\n /** Whether the tool was already installed (skipped) */\n alreadyInstalled?: boolean;\n /** Error message (when failed) */\n error?: string;\n}\n\n/**\n * Response for POST /api/tools/:name/check-update\n */\nexport interface ICheckUpdateResponse {\n /** Whether an update is available */\n hasUpdate: boolean;\n /** Currently configured or installed version */\n currentVersion: string;\n /** Latest available version */\n latestVersion: string;\n /** Whether the plugin supports update checking */\n supported: boolean;\n /** Error message when check fails */\n error?: string;\n}\n\n/**\n * Response for POST /api/tools/:name/update\n */\nexport interface IUpdateToolResponse {\n /** Whether the update was successful */\n updated: boolean;\n /** The old version before update */\n oldVersion?: string;\n /** The new version after update */\n newVersion?: string;\n /** Whether the plugin supports updating */\n supported: boolean;\n /** Error message when update fails */\n error?: string;\n}\n",
|
|
232
|
+
"import type { TsLogger } from '@dotfiles/logger';\nimport type { IActivityFeed, IApiResponse } from '../../shared/types';\nimport { formatRelativeTime, formatTimestamp } from '../../shared/types';\nimport { messages } from '../log-messages';\nimport type { IDashboardServices } from '../types';\n\n/**\n * GET /api/activity - Get recent activity feed\n */\nexport async function getActivity(\n logger: TsLogger,\n services: IDashboardServices,\n limit: number = 20,\n): Promise<IApiResponse<IActivityFeed>> {\n try {\n // Get all operations, sorted by most recent\n const operations = await services.fileRegistry.getOperations();\n\n // Sort by createdAt descending (most recent first)\n const sorted = operations.toSorted((a, b) => b.createdAt - a.createdAt);\n\n const totalCount = sorted.length;\n\n // Map operations to activity items\n const activities = sorted.slice(0, limit).map((op) => ({\n id: op.id,\n toolName: op.toolName,\n action: op.operationType,\n description: `${op.operationType} ${op.fileType}: ${op.filePath}`,\n timestamp: formatTimestamp(op.createdAt),\n relativeTime: formatRelativeTime(op.createdAt),\n }));\n\n return {\n success: true,\n data: {\n activities,\n totalCount,\n },\n };\n } catch (error) {\n logger.error(messages.apiError('getActivity'), error);\n return { success: false, error: 'Failed to retrieve activity feed' };\n }\n}\n",
|
|
233
|
+
"import type { TsLogger } from '@dotfiles/logger';\nimport type { IApiResponse, IConfigSummary } from '../../shared/types';\nimport { messages } from '../log-messages';\nimport type { IDashboardServices } from '../types';\n\n/**\n * GET /api/config - Get project configuration summary\n */\nexport async function getConfig(\n logger: TsLogger,\n services: IDashboardServices,\n): Promise<IApiResponse<IConfigSummary>> {\n try {\n const paths = services.projectConfig.paths;\n const summary: IConfigSummary = {\n dotfilesDir: paths.dotfilesDir,\n generatedDir: paths.generatedDir,\n binariesDir: paths.binariesDir,\n targetDir: paths.targetDir,\n toolConfigsDir: paths.toolConfigsDir,\n };\n return { success: true, data: summary };\n } catch (error) {\n logger.error(messages.apiError('getConfig'), error);\n return { success: false, error: 'Failed to retrieve configuration' };\n }\n}\n",
|
|
234
|
+
"import type { TsLogger } from '@dotfiles/logger';\nimport path from 'node:path';\nimport type { IApiResponse, IHealthStatus } from '../../shared/types';\nimport { messages } from '../log-messages';\nimport type { IDashboardServices } from '../types';\n\n/**\n * Finds unused binary version directories in the binaries directory.\n *\n * An unused binary is a version directory that is not currently pointed to by the\n * tool's `current` symlink. Each tool directory in binariesDir contains:\n * - Version folders (e.g., `v1.0.0`, `2025-01-01-120000`)\n * - A `current` symlink pointing to the active version folder\n *\n * @returns Array of paths to unused binary directories\n */\nasync function findUnusedBinaries(services: IDashboardServices): Promise<string[]> {\n const { fs, projectConfig } = services;\n const binariesDir = projectConfig.paths.binariesDir;\n const unusedBinaries: string[] = [];\n\n // Check if binaries directory exists\n if (!(await fs.exists(binariesDir))) {\n return [];\n }\n\n // List all tool directories\n const toolDirs = await fs.readdir(binariesDir);\n\n for (const toolName of toolDirs) {\n const toolDir = path.join(binariesDir, toolName);\n\n // Get stats to check if it's a directory\n const toolStat = await fs.stat(toolDir).catch(() => null);\n if (!toolStat?.isDirectory()) {\n continue;\n }\n\n // List contents of tool directory\n const contents = await fs.readdir(toolDir);\n\n // Find the current symlink target\n const currentPath = path.join(toolDir, 'current');\n let currentTarget: string | null = null;\n\n if (contents.includes('current')) {\n const currentStat = await fs.lstat(currentPath).catch(() => null);\n if (currentStat?.isSymbolicLink()) {\n const linkTarget = await fs.readlink(currentPath).catch(() => null);\n if (linkTarget) {\n // Resolve relative symlink to absolute path\n currentTarget = path.isAbsolute(linkTarget) ? linkTarget : path.resolve(toolDir, linkTarget);\n }\n }\n }\n\n // Check each entry in the tool directory\n for (const entry of contents) {\n // Skip the 'current' symlink itself\n if (entry === 'current') {\n continue;\n }\n\n const entryPath = path.join(toolDir, entry);\n const entryStat = await fs.lstat(entryPath).catch(() => null);\n\n // Only consider directories as potential version folders\n if (!entryStat?.isDirectory()) {\n continue;\n }\n\n // If this directory is not the current target, it's unused\n if (currentTarget !== entryPath) {\n unusedBinaries.push(entryPath);\n }\n }\n }\n\n return unusedBinaries;\n}\n\n/**\n * GET /api/health - Get health status\n */\nexport async function getHealth(\n logger: TsLogger,\n services: IDashboardServices,\n): Promise<IApiResponse<IHealthStatus>> {\n try {\n const checks = [];\n\n // Check for unused binaries (first, most actionable)\n const unusedBinaries = await findUnusedBinaries(services);\n const unusedCount = unusedBinaries.length;\n if (unusedCount > 0) {\n checks.push({\n name: 'Unused Binaries',\n status: 'warn' as const,\n message: '',\n details: unusedBinaries,\n });\n }\n\n // Check registry health\n const validation = await services.fileRegistry.validate();\n checks.push({\n name: 'Registry Integrity',\n status: validation.valid ? 'pass' : 'warn',\n message: validation.valid ? 'Registry is healthy' : `Found ${validation.issues.length} issues`,\n details: validation.issues,\n });\n\n // Check tool installations\n const installations = await services.toolInstallationRegistry.getAllToolInstallations();\n const toolCount = installations.length;\n checks.push({\n name: 'Tool Installations',\n status: toolCount > 0 ? 'pass' : 'warn',\n message: `${toolCount} tool${toolCount === 1 ? '' : 's'} installed`,\n });\n\n // Determine overall status\n const hasFailure = checks.some((c) => c.status === 'fail');\n const hasWarning = checks.some((c) => c.status === 'warn');\n const overall = hasFailure ? 'unhealthy' : hasWarning ? 'warning' : 'healthy';\n\n const status: IHealthStatus = {\n overall,\n checks: checks as IHealthStatus['checks'],\n lastCheck: new Date().toISOString(),\n };\n return { success: true, data: status };\n } catch (error) {\n logger.error(messages.apiError('getHealth'), error);\n return { success: false, error: 'Failed to retrieve health status' };\n }\n}\n",
|
|
235
|
+
"import path from 'node:path';\nimport type { IDashboardServices } from '../../types';\n\n/**\n * Calculates the total size of a directory recursively.\n */\nexport async function getDirectorySize(services: IDashboardServices, dirPath: string): Promise<number> {\n const fs = services.fs;\n let totalSize = 0;\n\n try {\n if (!(await fs.exists(dirPath))) {\n return 0;\n }\n\n const entries = await fs.readdir(dirPath);\n for (const entry of entries) {\n const entryPath = path.join(dirPath, entry);\n const stat = await fs.stat(entryPath);\n\n if (stat.isDirectory()) {\n totalSize += await getDirectorySize(services, entryPath);\n } else if (stat.isFile()) {\n totalSize += stat.size;\n }\n }\n } catch {\n // Ignore errors (permission issues, etc.)\n }\n\n return totalSize;\n}\n\n/**\n * Gets the binary disk size for a specific tool.\n */\nexport async function getToolBinaryDiskSize(services: IDashboardServices, toolName: string): Promise<number> {\n const binariesDir = path.join(services.projectConfig.paths.generatedDir, 'binaries');\n const toolBinaryDir = path.join(binariesDir, toolName);\n return getDirectorySize(services, toolBinaryDir);\n}\n",
|
|
236
|
+
"/** Cache for git first commit dates */\nlet gitFirstCommitDatesCache: Map<string, Date> | null = null;\n\n/**\n * Clear the git first commit dates cache. Used for testing.\n */\nexport function clearGitFirstCommitDatesCache(): void {\n gitFirstCommitDatesCache = null;\n}\n\n/**\n * Load all git first commit dates in a single git command.\n * Returns a Map from absolute file path to the Date it was first committed.\n */\nasync function loadGitFirstCommitDates(): Promise<Map<string, Date>> {\n const cache = new Map<string, Date>();\n\n try {\n // Get the repository root to resolve relative paths\n const rootProc = Bun.spawn(['git', 'rev-parse', '--show-toplevel'], {\n stdout: 'pipe',\n stderr: 'pipe',\n });\n const repoRoot = (await new Response(rootProc.stdout).text()).trim();\n const rootExitCode = await rootProc.exited;\n\n if (rootExitCode !== 0 || !repoRoot) {\n return cache;\n }\n\n // Get all file additions with their dates in a single command\n // Output format: date\\n\\nfile1\\nfile2\\n\\ndate2\\n\\nfile3\\n...\n const proc = Bun.spawn(['git', 'log', '--diff-filter=A', '--name-only', '--format=%aI'], {\n stdout: 'pipe',\n stderr: 'pipe',\n });\n const output = await new Response(proc.stdout).text();\n const exitCode = await proc.exited;\n\n if (exitCode !== 0) {\n return cache;\n }\n\n // Parse the output: dates are ISO format, files follow each date\n // Format is: date\\n\\nfile1\\nfile2\\n\\ndate2\\n\\nfile3...\n const lines = output.split('\\n');\n let currentDate: Date | null = null;\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n\n // Check if this line is an ISO date (starts with 4 digits)\n if (/^\\d{4}-\\d{2}-\\d{2}/.test(trimmed)) {\n currentDate = new Date(trimmed);\n } else if (currentDate) {\n // This is a file path, make it absolute\n const absolutePath = `${repoRoot}/${trimmed}`;\n // Only store if we haven't seen this file (first commit = earliest in log)\n if (!cache.has(absolutePath)) {\n cache.set(absolutePath, currentDate);\n }\n }\n }\n } catch {\n // Return empty cache on any error\n }\n\n return cache;\n}\n\n/**\n * Query git for a single file's first commit date.\n * Used for files not found in the batch cache (e.g., newly added files).\n */\nasync function querySingleFileGitDate(filePath: string): Promise<Date | null> {\n try {\n const proc = Bun.spawn(['git', 'log', '--diff-filter=A', '--format=%aI', '--', filePath], {\n stdout: 'pipe',\n stderr: 'pipe',\n });\n const output = await new Response(proc.stdout).text();\n const exitCode = await proc.exited;\n\n if (exitCode !== 0 || !output.trim()) {\n return null;\n }\n\n const dateStr = output.trim().split('\\n')[0];\n if (!dateStr) {\n return null;\n }\n\n return new Date(dateStr);\n } catch {\n return null;\n }\n}\n\n/**\n * Get the date when a file was first committed to git.\n * Uses a cached batch query for performance, with on-demand queries for new files.\n * Returns null if the file is not tracked by git.\n */\nexport async function getGitFirstCommitDate(filePath: string): Promise<Date | null> {\n if (!gitFirstCommitDatesCache) {\n gitFirstCommitDatesCache = await loadGitFirstCommitDates();\n }\n\n // Check cache first\n const cached = gitFirstCommitDatesCache.get(filePath);\n if (cached) {\n return cached;\n }\n\n // File not in cache - might be newly added, query git directly\n const date = await querySingleFileGitDate(filePath);\n if (date) {\n gitFirstCommitDatesCache.set(filePath, date);\n }\n return date;\n}\n",
|
|
237
|
+
"import type { ToolConfig } from '@dotfiles/core';\nimport type { TsLogger } from '@dotfiles/logger';\nimport type { IDashboardServices, ToolConfigsCache } from '../../types';\n\n/** Cache for loaded tool configs to avoid re-parsing on every request */\nlet toolConfigsCache: ToolConfigsCache | null = null;\n\n/**\n * Clear the tool configs cache. Used for testing.\n */\nexport function clearToolConfigsCache(): void {\n toolConfigsCache = null;\n}\n\n/**\n * Load tool configs, using cache if available.\n */\nexport async function getToolConfigs(\n logger: TsLogger,\n services: IDashboardServices,\n): Promise<Record<string, ToolConfig>> {\n if (toolConfigsCache) {\n return toolConfigsCache;\n }\n\n const { projectConfig, fs, configService, systemInfo } = services;\n\n toolConfigsCache = await configService.loadToolConfigs(\n logger,\n projectConfig.paths.toolConfigsDir,\n fs,\n projectConfig,\n systemInfo,\n );\n\n return toolConfigsCache;\n}\n",
|
|
238
|
+
"import type { TsLogger } from '@dotfiles/logger';\nimport type { IApiResponse, IRecentTools } from '../../shared/types';\nimport { formatRelativeTime, formatTimestamp } from '../../shared/types';\nimport { messages } from '../log-messages';\nimport type { IDashboardServices } from '../types';\nimport { getGitFirstCommitDate } from './helpers';\n\n/**\n * GET /api/recent-tools - Get recently added tool config files\n * Returns the 10 most recently created .tool.ts files.\n * Uses git commit date when available, falls back to filesystem mtime.\n */\nexport async function getRecentTools(\n logger: TsLogger,\n services: IDashboardServices,\n limit: number = 10,\n): Promise<IApiResponse<IRecentTools>> {\n try {\n const toolConfigsDir = services.projectConfig.paths.toolConfigsDir;\n\n // Collect all .tool.ts files\n const toolFiles: Array<{ name: string; configFilePath: string; }> = [];\n\n async function collectToolFiles(dirPath: string): Promise<void> {\n const itemNames = await services.fs.readdir(dirPath);\n\n for (const name of itemNames) {\n const fullPath = `${dirPath}/${name}`;\n const stat = await services.fs.stat(fullPath);\n\n if (stat.isDirectory()) {\n await collectToolFiles(fullPath);\n } else if (name.endsWith('.tool.ts')) {\n const toolName = name.replace(/\\.tool\\.ts$/, '');\n toolFiles.push({\n name: toolName,\n configFilePath: fullPath,\n });\n }\n }\n }\n\n await collectToolFiles(toolConfigsDir);\n\n // Get timestamps for all files (git or mtime)\n const toolsWithTimestamps = await Promise.all(\n toolFiles.map(async (file) => {\n const gitDate = await getGitFirstCommitDate(file.configFilePath);\n if (gitDate) {\n return {\n name: file.name,\n configFilePath: file.configFilePath,\n timestamp: gitDate.getTime(),\n source: 'git' as const,\n };\n }\n const stat = await services.fs.stat(file.configFilePath);\n return {\n name: file.name,\n configFilePath: file.configFilePath,\n timestamp: stat.mtimeMs,\n source: 'mtime' as const,\n };\n }),\n );\n\n // Sort by timestamp descending (most recent first) and take top N\n const recentFiles = toolsWithTimestamps\n .toSorted((a, b) => b.timestamp - a.timestamp)\n .slice(0, limit);\n\n const tools = recentFiles.map((file) => ({\n name: file.name,\n configFilePath: file.configFilePath,\n createdAt: formatTimestamp(file.timestamp),\n relativeTime: formatRelativeTime(file.timestamp),\n timestampSource: file.source,\n }));\n\n return {\n success: true,\n data: { tools },\n };\n } catch (error) {\n logger.error(messages.apiError('getRecentTools'), error);\n return { success: false, error: 'Failed to retrieve recent tools' };\n }\n}\n",
|
|
239
|
+
"import type { TsLogger } from '@dotfiles/logger';\nimport type { IApiResponse, IShellIntegration } from '../../shared/types';\nimport { formatTimestamp } from '../../shared/types';\nimport { messages } from '../log-messages';\nimport type { IDashboardServices } from '../types';\n\n/**\n * GET /api/shell - Get shell integration (completions and init scripts)\n */\nexport async function getShellIntegration(\n logger: TsLogger,\n services: IDashboardServices,\n): Promise<IApiResponse<IShellIntegration>> {\n try {\n // Get all file operations for completion and init types\n const completionOps = await services.fileRegistry.getOperations({ fileType: 'completion' });\n const initOps = await services.fileRegistry.getOperations({ fileType: 'init' });\n\n // Group by file path to get latest state\n const completionMap = new Map<string, (typeof completionOps)[0]>();\n for (const op of completionOps) {\n const existing = completionMap.get(op.filePath);\n if (!existing || op.createdAt > existing.createdAt) {\n completionMap.set(op.filePath, op);\n }\n }\n\n const initMap = new Map<string, (typeof initOps)[0]>();\n for (const op of initOps) {\n const existing = initMap.get(op.filePath);\n if (!existing || op.createdAt > existing.createdAt) {\n initMap.set(op.filePath, op);\n }\n }\n\n // Filter out deleted files\n const completions = Array.from(completionMap.values())\n .filter((op) => op.operationType !== 'rm')\n .map((op) => ({\n toolName: op.toolName,\n filePath: op.filePath,\n fileType: 'completion' as const,\n lastModified: formatTimestamp(op.createdAt),\n }));\n\n const initScripts = Array.from(initMap.values())\n .filter((op) => op.operationType !== 'rm')\n .map((op) => ({\n toolName: op.toolName,\n filePath: op.filePath,\n fileType: 'init' as const,\n lastModified: formatTimestamp(op.createdAt),\n }));\n\n const integration: IShellIntegration = {\n completions,\n initScripts,\n totalFiles: completions.length + initScripts.length,\n };\n\n return { success: true, data: integration };\n } catch (error) {\n logger.error(messages.apiError('getShellIntegration'), error);\n return { success: false, error: 'Failed to retrieve shell integration' };\n }\n}\n",
|
|
240
|
+
"import type { TsLogger } from '@dotfiles/logger';\nimport type { IApiResponse, IDashboardStats } from '../../shared/types';\nimport { formatTimestamp } from '../../shared/types';\nimport { messages } from '../log-messages';\nimport type { IDashboardServices } from '../types';\n\n/**\n * GET /api/stats - Get dashboard statistics\n */\nexport async function getStats(\n logger: TsLogger,\n services: IDashboardServices,\n): Promise<IApiResponse<IDashboardStats>> {\n try {\n const stats = await services.fileRegistry.getStats();\n const installations = await services.toolInstallationRegistry.getAllToolInstallations();\n\n const dashboardStats: IDashboardStats = {\n toolsInstalled: installations.length,\n updatesAvailable: 0,\n filesTracked: stats.totalFiles,\n totalOperations: stats.totalOperations,\n oldestOperation: stats.oldestOperation > 0 ? formatTimestamp(stats.oldestOperation) : null,\n newestOperation: stats.newestOperation > 0 ? formatTimestamp(stats.newestOperation) : null,\n };\n return { success: true, data: dashboardStats };\n } catch (error) {\n logger.error(messages.apiError('getStats'), error);\n return { success: false, error: 'Failed to retrieve statistics' };\n }\n}\n",
|
|
241
|
+
"import type { TsLogger } from '@dotfiles/logger';\nimport type { IApiResponse, ICheckUpdateResponse } from '../../shared/types';\nimport { messages } from '../log-messages';\nimport type { IDashboardServices } from '../types';\nimport { getToolConfigs } from './helpers';\n\n/**\n * POST /api/tools/:name/check-update - Check for available updates\n */\nexport async function checkToolUpdate(\n logger: TsLogger,\n services: IDashboardServices,\n toolName: string,\n): Promise<IApiResponse<ICheckUpdateResponse>> {\n const subLogger = logger.getSubLogger({ name: 'checkToolUpdate', context: toolName });\n\n try {\n const toolConfigs = await getToolConfigs(logger, services);\n const toolConfig = toolConfigs[toolName];\n\n if (!toolConfig) {\n return { success: false, error: `Tool \"${toolName}\" not found in configuration` };\n }\n\n const plugin = services.pluginRegistry.get(toolConfig.installationMethod);\n\n if (!plugin || !plugin.supportsUpdateCheck || !plugin.supportsUpdateCheck()) {\n return {\n success: true,\n data: {\n hasUpdate: false,\n currentVersion: toolConfig.version || 'unknown',\n latestVersion: 'unknown',\n supported: false,\n error: `Update checking is not supported for installation method \"${toolConfig.installationMethod}\"`,\n },\n };\n }\n\n const updateCheckResult = await plugin.checkUpdate?.(\n toolName,\n toolConfig,\n {} as Parameters<NonNullable<typeof plugin.checkUpdate>>[2],\n subLogger,\n );\n\n if (!updateCheckResult) {\n return {\n success: true,\n data: {\n hasUpdate: false,\n currentVersion: toolConfig.version || 'unknown',\n latestVersion: 'unknown',\n supported: false,\n error: 'Update check returned no result',\n },\n };\n }\n\n if (!updateCheckResult.success) {\n return {\n success: true,\n data: {\n hasUpdate: false,\n currentVersion: toolConfig.version || 'unknown',\n latestVersion: 'unknown',\n supported: true,\n error: updateCheckResult.error,\n },\n };\n }\n\n subLogger.info(messages.checkUpdateCompleted(\n updateCheckResult.hasUpdate,\n updateCheckResult.currentVersion ?? 'unknown',\n updateCheckResult.latestVersion ?? 'unknown',\n ));\n\n return {\n success: true,\n data: {\n hasUpdate: updateCheckResult.hasUpdate,\n currentVersion: updateCheckResult.currentVersion ?? toolConfig.version ?? 'unknown',\n latestVersion: updateCheckResult.latestVersion ?? 'unknown',\n supported: true,\n },\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n subLogger.error(messages.checkUpdateFailed(errorMessage), error);\n return { success: false, error: `Failed to check for updates: ${errorMessage}` };\n }\n}\n",
|
|
242
|
+
"import type { TsLogger } from '@dotfiles/logger';\nimport type { IApiResponse, IToolConfigsTree } from '../../shared/types';\nimport { messages } from '../log-messages';\nimport type { IDashboardServices } from '../types';\nimport { getToolConfigs } from './helpers';\n\n/**\n * GET /api/tool-configs-tree - Get file tree of tool configs directory\n */\nexport async function getToolConfigsTree(\n logger: TsLogger,\n services: IDashboardServices,\n): Promise<IApiResponse<IToolConfigsTree>> {\n try {\n const toolConfigsDir = services.projectConfig.paths.toolConfigsDir;\n const toolConfigs = await getToolConfigs(logger, services);\n\n // Build a map from config file path to tool name\n const configPathToTool = new Map<string, string>();\n for (const config of Object.values(toolConfigs)) {\n if (config.configFilePath) {\n configPathToTool.set(config.configFilePath, config.name);\n }\n }\n\n // Recursively build file tree\n async function buildTree(dirPath: string) {\n interface IFileTreeEntry {\n name: string;\n path: string;\n type: 'file' | 'directory';\n children?: IFileTreeEntry[];\n toolName?: string;\n }\n\n const entries: IFileTreeEntry[] = [];\n const itemNames = await services.fs.readdir(dirPath);\n\n for (const name of itemNames) {\n const fullPath = `${dirPath}/${name}`;\n const stat = await services.fs.stat(fullPath);\n\n if (stat.isDirectory()) {\n const children = await buildTree(fullPath);\n // Only include non-empty directories\n if (children.length > 0) {\n entries.push({\n name,\n path: fullPath,\n type: 'directory',\n children,\n });\n }\n } else if (name.endsWith('.tool.ts')) {\n entries.push({\n name,\n path: fullPath,\n type: 'file',\n toolName: configPathToTool.get(fullPath),\n });\n }\n }\n\n // Sort: directories first, then files, alphabetically\n return entries.toSorted((a, b) => {\n if (a.type !== b.type) {\n return a.type === 'directory' ? -1 : 1;\n }\n return a.name.localeCompare(b.name);\n });\n }\n\n const entries = await buildTree(toolConfigsDir);\n\n return {\n success: true,\n data: {\n rootPath: toolConfigsDir,\n entries,\n },\n };\n } catch (error) {\n logger.error(messages.apiError('getToolConfigsTree'), error);\n return { success: false, error: 'Failed to retrieve tool configs tree' };\n }\n}\n",
|
|
243
|
+
"import type { TsLogger } from '@dotfiles/logger';\nimport type { IApiResponse, IToolHistory } from '../../shared/types';\nimport { formatRelativeTime, formatTimestamp } from '../../shared/types';\nimport { messages } from '../log-messages';\nimport type { IDashboardServices } from '../types';\n\n/**\n * GET /api/tools/:name/history - Get file operation history for a tool\n */\nexport async function getToolHistory(\n logger: TsLogger,\n services: IDashboardServices,\n toolName: string,\n): Promise<IApiResponse<IToolHistory>> {\n try {\n // Get operations for this tool\n const operations = await services.fileRegistry.getOperations({ toolName });\n\n // Sort by createdAt descending (most recent first)\n const sorted = operations.toSorted((a, b) => b.createdAt - a.createdAt);\n\n // Get installation record for installedAt\n const installation = await services.toolInstallationRegistry.getToolInstallation(toolName);\n\n const entries = sorted.map((op) => ({\n id: op.id,\n operationType: op.operationType,\n fileType: op.fileType,\n filePath: op.filePath,\n timestamp: formatTimestamp(op.createdAt),\n relativeTime: formatRelativeTime(op.createdAt),\n }));\n\n return {\n success: true,\n data: {\n entries,\n totalCount: entries.length,\n installedAt: installation?.installedAt.toISOString() ?? null,\n dotfilesDir: services.projectConfig.paths.dotfilesDir,\n },\n };\n } catch (error) {\n logger.error(messages.apiError('getToolHistory'), error);\n return { success: false, error: 'Failed to retrieve tool history' };\n }\n}\n",
|
|
244
|
+
"import type { TsLogger } from '@dotfiles/logger';\nimport type { IApiResponse, IInstallToolRequest, IInstallToolResponse } from '../../shared/types';\nimport { messages } from '../log-messages';\nimport type { IDashboardServices } from '../types';\nimport { getToolConfigs } from './helpers';\n\n/**\n * POST /api/tools/:name/install - Install or reinstall a tool\n */\nexport async function installTool(\n logger: TsLogger,\n services: IDashboardServices,\n toolName: string,\n request: IInstallToolRequest,\n): Promise<IApiResponse<IInstallToolResponse>> {\n const subLogger = logger.getSubLogger({ name: 'installTool', context: toolName });\n\n try {\n // Load tool config\n const toolConfigs = await getToolConfigs(logger, services);\n const toolConfig = toolConfigs[toolName];\n\n if (!toolConfig) {\n return { success: false, error: `Tool \"${toolName}\" not found in configuration` };\n }\n\n // Install the tool\n const result = await services.installer.install(toolName, toolConfig, {\n force: request.force ?? false,\n });\n\n if (!result.success) {\n subLogger.error(messages.installFailed(result.error ?? 'Unknown error'));\n return {\n success: true,\n data: {\n installed: false,\n error: result.error ?? 'Installation failed',\n },\n };\n }\n\n subLogger.info(messages.installSucceeded());\n return {\n success: true,\n data: {\n installed: true,\n version: result.version,\n alreadyInstalled: result.installationMethod === 'already-installed',\n },\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n subLogger.error(messages.installFailed(errorMessage), error);\n return { success: false, error: `Failed to install tool: ${errorMessage}` };\n }\n}\n",
|
|
245
|
+
"import { Architecture, hasArchitecture, hasPlatform, type ISystemInfo, Platform } from '@dotfiles/core';\nimport { NotFoundError } from '@dotfiles/downloader';\nimport type { TsLogger } from '@dotfiles/logger';\nimport type { IApiResponse } from '../../shared/types';\nimport { messages } from '../log-messages';\nimport type { IDashboardServices } from '../types';\nimport { getToolConfigs } from './helpers';\n\ninterface IPlatformConfigLike {\n platforms: Platform;\n architectures?: Architecture;\n config?: {\n installParams?: Record<string, unknown>;\n };\n}\n\ninterface IToolConfigLike {\n installParams?: Record<string, unknown>;\n platformConfigs?: IPlatformConfigLike[];\n}\n\nfunction getRepoFromInstallParams(installParams: Record<string, unknown> | undefined): string | null {\n if (!installParams) {\n return null;\n }\n\n const repo = installParams['repo'];\n return typeof repo === 'string' ? repo : null;\n}\n\nfunction matchesPlatformConfig(entry: IPlatformConfigLike, systemInfo: ISystemInfo): boolean {\n if (systemInfo.platform === Platform.None) {\n return false;\n }\n\n if (!hasPlatform(entry.platforms, systemInfo.platform)) {\n return false;\n }\n\n if (entry.architectures === undefined) {\n return true;\n }\n\n if (systemInfo.arch === Architecture.None) {\n return false;\n }\n\n return hasArchitecture(entry.architectures, systemInfo.arch);\n}\n\nfunction getRepoFromToolConfig(config: IToolConfigLike, systemInfo: ISystemInfo): string | null {\n const topLevelRepo = getRepoFromInstallParams(config.installParams);\n if (topLevelRepo) {\n return topLevelRepo;\n }\n\n for (const entry of config.platformConfigs ?? []) {\n if (!matchesPlatformConfig(entry, systemInfo)) {\n continue;\n }\n\n const repo = getRepoFromInstallParams(entry.config?.installParams);\n if (repo) {\n return repo;\n }\n }\n\n return null;\n}\n\n/**\n * GET /api/tools/:name/readme - Get README content for a tool\n * Fetches README from GitHub raw URL if tool has a repo configured.\n * Uses the downloader service which has caching enabled.\n */\nexport async function getToolReadme(\n logger: TsLogger,\n services: IDashboardServices,\n toolName: string,\n): Promise<IApiResponse<{ content: string; }>> {\n try {\n const toolConfigs = await getToolConfigs(logger, services);\n const config = toolConfigs[toolName];\n\n if (!config) {\n return { success: false, error: 'Tool not found' };\n }\n\n const repo = getRepoFromToolConfig(config, services.systemInfo);\n\n if (!repo) {\n return { success: false, error: 'Tool does not have a GitHub repository' };\n }\n\n // Try version first if specified, then common default branches\n const branchesToTry = config.version\n ? [config.version, 'main', 'master']\n : ['main', 'master'];\n\n for (const branch of branchesToTry) {\n const url = `https://raw.githubusercontent.com/${repo}/${branch}/README.md`;\n try {\n const response = await services.downloader.download(logger, url);\n\n if (response) {\n const content = response.toString('utf-8');\n return { success: true, data: { content } };\n }\n } catch (error) {\n // NotFoundError means this branch doesn't have a README, try next branch\n if (error instanceof NotFoundError) {\n continue;\n }\n throw error;\n }\n }\n\n return { success: false, error: 'README not found' };\n } catch (error) {\n logger.error(messages.apiError('getToolReadme'), error);\n return { success: false, error: 'Failed to retrieve README' };\n }\n}\n",
|
|
246
|
+
"import type { TsLogger } from '@dotfiles/logger';\nimport type { IApiResponse } from '../../shared/types';\nimport { messages } from '../log-messages';\nimport type { IDashboardServices } from '../types';\nimport { getToolConfigs } from './helpers';\n\n/**\n * GET /api/tools/:name/source - Get tool configuration source code\n * Returns the raw TypeScript source of the .tool.ts file.\n */\nexport async function getToolSource(\n logger: TsLogger,\n services: IDashboardServices,\n toolName: string,\n): Promise<IApiResponse<{ content: string; filePath: string; }>> {\n try {\n const toolConfigs = await getToolConfigs(logger, services);\n const config = toolConfigs[toolName];\n\n if (!config) {\n return { success: false, error: 'Tool not found' };\n }\n\n const configFilePath = config.configFilePath;\n if (!configFilePath) {\n return { success: false, error: 'Tool configuration file path not available' };\n }\n\n const content = await services.fs.readFile(configFilePath, 'utf-8');\n return { success: true, data: { content, filePath: configFilePath } };\n } catch (error) {\n logger.error(messages.apiError('getToolSource'), error);\n return { success: false, error: 'Failed to retrieve tool source' };\n }\n}\n",
|
|
247
|
+
"import type { TsLogger } from '@dotfiles/logger';\nimport type { IApiResponse, IUpdateToolResponse } from '../../shared/types';\nimport { messages } from '../log-messages';\nimport type { IDashboardServices } from '../types';\nimport { getToolConfigs } from './helpers';\n\n/**\n * POST /api/tools/:name/update - Update a tool to the latest version\n */\nexport async function updateTool(\n logger: TsLogger,\n services: IDashboardServices,\n toolName: string,\n): Promise<IApiResponse<IUpdateToolResponse>> {\n const subLogger = logger.getSubLogger({ name: 'updateTool', context: toolName });\n\n try {\n const toolConfigs = await getToolConfigs(logger, services);\n const toolConfig = toolConfigs[toolName];\n\n if (!toolConfig) {\n return { success: false, error: `Tool \"${toolName}\" not found in configuration` };\n }\n\n if (toolConfig.version !== 'latest') {\n return {\n success: true,\n data: {\n updated: false,\n supported: false,\n error: `Tool is pinned to version \"${toolConfig.version}\". Only tools with version \"latest\" can be updated.`,\n },\n };\n }\n\n const plugin = services.pluginRegistry.get(toolConfig.installationMethod);\n\n if (!plugin || !plugin.supportsUpdate()) {\n subLogger.warn(messages.updateNotSupported(toolConfig.installationMethod));\n }\n\n const existingInstallation = await services.toolInstallationRegistry.getToolInstallation(toolName);\n const oldVersion = existingInstallation?.version ?? 'unknown';\n\n const installResult = await services.installer.install(toolName, toolConfig, { force: true });\n\n if (!installResult.success) {\n subLogger.error(messages.updateFailed(installResult.error ?? 'Unknown error'));\n return {\n success: true,\n data: {\n updated: false,\n supported: true,\n error: installResult.error ?? 'Update failed',\n },\n };\n }\n\n const newVersion = 'version' in installResult && typeof installResult.version === 'string'\n ? installResult.version\n : 'unknown';\n\n subLogger.info(messages.updateSucceeded(oldVersion, newVersion));\n\n return {\n success: true,\n data: {\n updated: true,\n oldVersion,\n newVersion,\n supported: true,\n },\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n subLogger.error(messages.updateFailed(errorMessage), error);\n return { success: false, error: `Failed to update tool: ${errorMessage}` };\n }\n}\n",
|
|
248
|
+
"import type { IFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport type { IFileState } from '@dotfiles/registry/file';\nimport type { IToolInstallationRecord } from '@dotfiles/registry/tool';\nimport type { IApiResponse, IToolBinaryUsage, IToolDetail, IToolUsageSummary } from '../../shared/types';\nimport { toToolDetail } from '../../shared/types';\nimport { messages } from '../log-messages';\nimport type { IDashboardServices } from '../types';\nimport { getToolBinaryDiskSize, getToolConfigs } from './helpers';\n\n/**\n * Enriches file states with actual file sizes from disk when sizeBytes is missing.\n * This compensates for file operations that weren't tracked with size information.\n */\nasync function enrichFileSizesFromDisk(files: IFileState[], fs: IFileSystem): Promise<IFileState[]> {\n return Promise.all(\n files.map(async (file) => {\n if (file.sizeBytes !== undefined) {\n return file;\n }\n\n try {\n const stat = await fs.stat(file.filePath);\n if (stat.isFile()) {\n return { ...file, sizeBytes: stat.size };\n }\n } catch {\n // File doesn't exist or can't be read, return as-is\n }\n return file;\n }),\n );\n}\n\nfunction getConfiguredBinaryNames(config: { binaries?: Array<string | { name: string; }>; }): string[] {\n if (!config.binaries || config.binaries.length === 0) {\n return [];\n }\n\n return config.binaries.map((binary) => (typeof binary === 'string' ? binary : binary.name));\n}\n\nasync function getToolUsageSummary(\n services: IDashboardServices,\n toolName: string,\n binaryNames: string[],\n): Promise<IToolUsageSummary> {\n const usageByBinary: IToolBinaryUsage[] = await Promise.all(\n binaryNames.map(async (binaryName) => {\n const usage = await services.toolInstallationRegistry.getToolUsage(toolName, binaryName);\n return {\n binaryName,\n count: usage?.usageCount ?? 0,\n lastUsedAt: usage?.lastUsedAt ? usage.lastUsedAt.toISOString() : null,\n };\n }),\n );\n\n const totalCount = usageByBinary.reduce((sum, item) => sum + item.count, 0);\n\n return {\n totalCount,\n binaries: usageByBinary,\n };\n}\n\n/**\n * GET /api/tools - List all tools with full details\n * Returns tools from tool configs with runtime state from registry\n */\nexport async function getTools(\n logger: TsLogger,\n services: IDashboardServices,\n): Promise<IApiResponse<IToolDetail[]>> {\n try {\n // Load tool configs from .tool.ts files\n const toolConfigs = await getToolConfigs(logger, services);\n\n // Get installation records and create lookup map\n const installations = await services.toolInstallationRegistry.getAllToolInstallations();\n const installationsMap = new Map<string, IToolInstallationRecord>(\n installations.map((i) => [i.toolName, i]),\n );\n\n // Build tool details from configs with runtime state overlay\n const toolDetails = await Promise.all(\n Object.values(toolConfigs).map(async (config) => {\n const files = await services.fileRegistry.getFileStatesForTool(config.name);\n const enrichedFiles = await enrichFileSizesFromDisk(files, services.fs);\n const binaryDiskSize = await getToolBinaryDiskSize(services, config.name);\n const binaryNames = getConfiguredBinaryNames(config);\n const usage = await getToolUsageSummary(services, config.name, binaryNames);\n return toToolDetail(config, installationsMap, enrichedFiles, services.systemInfo, binaryDiskSize, usage);\n }),\n );\n\n // Sort by name\n const sortedDetails = toolDetails.toSorted((a: typeof toolDetails[0], b: typeof toolDetails[0]) =>\n a.config.name.localeCompare(b.config.name)\n );\n\n return { success: true, data: sortedDetails };\n } catch (error) {\n logger.error(messages.apiError('getTools'), error);\n return { success: false, error: 'Failed to retrieve tools' };\n }\n}\n",
|
|
249
|
+
"import type { TsLogger } from '@dotfiles/logger';\nimport type { IInstallToolRequest } from '../../shared/types';\nimport type { IDashboardServices } from '../types';\nimport { getActivity } from './activity';\nimport { getConfig } from './config';\nimport { getHealth } from './health';\nimport { clearGitFirstCommitDatesCache, clearToolConfigsCache } from './helpers';\nimport { getRecentTools } from './recent-tools';\nimport { getShellIntegration } from './shell-integration';\nimport { getStats } from './stats';\nimport { checkToolUpdate } from './tool-check-update';\nimport { getToolConfigsTree } from './tool-configs-tree';\nimport { getToolHistory } from './tool-history';\nimport { installTool } from './tool-install';\nimport { getToolReadme } from './tool-readme';\nimport { getToolSource } from './tool-source';\nimport { updateTool } from './tool-update';\nimport { getTools } from './tools';\n\n/**\n * Creates API route handlers for the dashboard.\n */\nexport function createApiRoutes(parentLogger: TsLogger, services: IDashboardServices) {\n const logger = parentLogger.getSubLogger({ name: 'api' });\n\n return {\n getTools: () => getTools(logger, services),\n getStats: () => getStats(logger, services),\n getHealth: () => getHealth(logger, services),\n getConfig: () => getConfig(logger, services),\n getToolConfigsTree: () => getToolConfigsTree(logger, services),\n getShellIntegration: () => getShellIntegration(logger, services),\n getActivity: (limit?: number) => getActivity(logger, services, limit),\n getToolHistory: (toolName: string) => getToolHistory(logger, services, toolName),\n getToolReadme: (toolName: string) => getToolReadme(logger, services, toolName),\n getToolSource: (toolName: string) => getToolSource(logger, services, toolName),\n getRecentTools: (limit?: number) => getRecentTools(logger, services, limit),\n installTool: (toolName: string, request: IInstallToolRequest) => installTool(logger, services, toolName, request),\n checkToolUpdate: (toolName: string) => checkToolUpdate(logger, services, toolName),\n updateTool: (toolName: string) => updateTool(logger, services, toolName),\n };\n}\n\nexport type ApiRoutes = ReturnType<typeof createApiRoutes>;\n\n// Export cache clearing functions for testing\nexport { clearGitFirstCommitDatesCache, clearToolConfigsCache };\n",
|
|
250
|
+
"import { Platform } from '@dotfiles/core';\nimport { createDashboardServer, type IDashboardServices } from '@dotfiles/dashboard';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { $ } from 'bun';\nimport { messages } from './log-messages';\nimport type { ICommandCompletionMeta, IGlobalProgram, IServices } from './types';\n\n/**\n * Completion metadata for the dashboard command.\n */\nexport const DASHBOARD_COMMAND_COMPLETION: ICommandCompletionMeta = {\n name: 'dashboard',\n description: 'Start the web-based visualization dashboard',\n options: [\n { flag: '--port', description: 'Port to run the server on', hasArg: true, argPlaceholder: '<port>' },\n { flag: '--host', description: 'Host to bind the server to', hasArg: true, argPlaceholder: '<host>' },\n { flag: '--no-open', description: 'Do not open browser when server starts', hasArg: false },\n ],\n};\n\ninterface DashboardCommandOptions {\n port?: string;\n host?: string;\n open?: boolean;\n}\n\nexport type BrowserOpener = (url: string, platform: Platform) => Promise<void>;\nexport type DashboardServerFactory = typeof createDashboardServer;\n\ninterface DashboardCommandDependencies {\n openBrowser?: BrowserOpener;\n createServer?: DashboardServerFactory;\n}\n\n/**\n * Default browser opener implementation.\n * Cross-platform compatible (macOS, Linux, Windows).\n */\nconst defaultOpenBrowser: BrowserOpener = async (url: string, platform: Platform): Promise<void> => {\n const command = platform === Platform.MacOS ? 'open' : platform === Platform.Windows ? 'start' : 'xdg-open';\n await $`${command} ${url}`.nothrow().quiet();\n};\n\n/**\n * Registers the dashboard command with the CLI program.\n */\nexport function registerDashboardCommand(\n parentLogger: TsLogger,\n program: IGlobalProgram,\n servicesFactory: () => Promise<IServices>,\n deps: DashboardCommandDependencies = {},\n) {\n const openBrowser = deps.openBrowser ?? defaultOpenBrowser;\n const createServer = deps.createServer ?? createDashboardServer;\n const logger = parentLogger.getSubLogger({ name: 'dashboard' });\n\n program\n .command('dashboard')\n .description('Start the web-based visualization dashboard')\n .option('--port <port>', 'Port to run the server on', '3000')\n .option('--host <host>', 'Host to bind the server to', 'localhost')\n .option('--no-open', 'Do not open browser when server starts')\n .action(async (options: DashboardCommandOptions) => {\n const port = parseInt(options.port ?? '3000', 10);\n const host = options.host ?? 'localhost';\n const shouldOpen = options.open ?? true;\n const services = await servicesFactory();\n\n const dashboardServices: IDashboardServices = {\n projectConfig: services.projectConfig,\n fs: services.fs,\n configService: services.configService,\n systemInfo: services.systemInfo,\n fileRegistry: services.fileRegistry,\n toolInstallationRegistry: services.toolInstallationRegistry,\n versionChecker: services.versionChecker,\n downloader: services.downloader,\n installer: services.installer,\n pluginRegistry: services.pluginRegistry,\n };\n\n const server = createServer(logger, dashboardServices, { port, host });\n const isRestart = await server.start();\n\n // Skip browser opening on HMR restarts (server was already running)\n if (shouldOpen && !isRestart) {\n const url = `http://${host}:${port}`;\n try {\n await openBrowser(url, services.systemInfo.platform);\n } catch (error) {\n logger.warn(messages.dashboardBrowserOpenFailed(), error);\n }\n }\n });\n}\n",
|
|
251
|
+
"import type { IConfigService, ProjectConfig } from '@dotfiles/config';\nimport type { ISystemInfo, ToolConfig } from '@dotfiles/core';\nimport type { IFileSystem, IResolvedFileSystem, Stats } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { exitCli, ExitCode } from '@dotfiles/utils';\nimport path from 'node:path';\nimport { messages } from './log-messages';\nimport type { ICommandCompletionMeta, IGlobalProgram, IGlobalProgramOptions, IServices } from './types';\n\n/**\n * Completion metadata for the detect-conflicts command.\n */\nexport const DETECT_CONFLICTS_COMMAND_COMPLETION: ICommandCompletionMeta = {\n name: 'detect-conflicts',\n description: 'Detect file conflicts before generating',\n};\n\nasync function loadToolConfigs(\n logger: TsLogger,\n projectConfig: ProjectConfig,\n fs: IResolvedFileSystem,\n configService: IConfigService,\n systemInfo: ISystemInfo,\n): Promise<{ toolConfigs: ToolConfig[]; exitCode: ExitCode; }> {\n try {\n const toolConfigsRecord = await configService.loadToolConfigs(\n logger,\n projectConfig.paths.toolConfigsDir,\n fs,\n projectConfig,\n systemInfo,\n );\n return { toolConfigs: Object.values(toolConfigsRecord), exitCode: ExitCode.SUCCESS };\n } catch (error: unknown) {\n logger.error(messages.configLoadFailed('tool configurations'), error);\n return { toolConfigs: [], exitCode: ExitCode.ERROR };\n }\n}\n\nasync function checkShimConflicts(\n logger: TsLogger,\n fs: IFileSystem,\n toolConfig: ToolConfig,\n targetDir: string,\n conflictMessages: string[],\n): Promise<void> {\n if (!toolConfig.binaries) return;\n\n for (const binary of toolConfig.binaries) {\n const binaryName = typeof binary === 'string' ? binary : binary.name;\n const shimPath = path.join(targetDir, binaryName);\n if (await fs.exists(shimPath)) {\n try {\n const content = await fs.readFile(shimPath);\n if (!content.includes('# Generated by Dotfiles Management Tool')) {\n conflictMessages.push(`[${toolConfig.name}]: ${shimPath} (exists but is not a generator shim)`);\n }\n } catch (readError: unknown) {\n logger.warn(messages.fsReadFailed(shimPath), readError);\n conflictMessages.push(`[${toolConfig.name}]: ${shimPath} (exists but could not be read/verified)`);\n }\n }\n }\n}\n\nasync function checkSymlinkConflicts(\n logger: TsLogger,\n fs: IFileSystem,\n toolConfig: ToolConfig,\n homeDir: string,\n dotfilesDir: string,\n conflictMessages: string[],\n): Promise<void> {\n if (!toolConfig.symlinks) return;\n\n for (const symlink of toolConfig.symlinks) {\n const targetPath = path.join(homeDir, symlink.target);\n const sourcePath = path.join(dotfilesDir, symlink.source);\n\n try {\n const stats: Stats | null = await fs.lstat(targetPath);\n if (stats) {\n if (stats.isSymbolicLink()) {\n const linkString = await fs.readlink(targetPath);\n const resolvedLinkTarget = path.resolve(path.dirname(targetPath), linkString);\n if (resolvedLinkTarget !== sourcePath) {\n conflictMessages.push(\n `[${toolConfig.name}]: ${targetPath} (points to '${linkString}', expected '${sourcePath}')`,\n );\n }\n } else {\n conflictMessages.push(`[${toolConfig.name}]: ${targetPath} (exists but is not a symlink)`);\n }\n }\n } catch (error: unknown) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {\n logger.warn(messages.fsReadFailed(targetPath), error);\n }\n }\n }\n}\n\nfunction reportConflicts(logger: TsLogger, conflictMessages: string[]): ExitCode {\n if (conflictMessages.length > 0) {\n const header = 'Conflicts detected with files not owned by the generator:';\n const formattedConflicts = conflictMessages.map((msg) => ` - ${msg}`).join('\\n');\n logger.warn(messages.toolConflictsDetected(header, formattedConflicts));\n return ExitCode.ERROR;\n } else {\n logger.info(messages.noConflictsDetected());\n return ExitCode.SUCCESS;\n }\n}\n\nexport async function detectConflictsActionLogic(\n logger: TsLogger,\n _options: IGlobalProgramOptions,\n services: IServices,\n): Promise<ExitCode> {\n const { projectConfig, fs, configService, systemInfo } = services;\n const conflictMessages: string[] = [];\n\n const toolConfigsResult = await loadToolConfigs(logger, projectConfig, fs, configService, systemInfo);\n\n if (toolConfigsResult.exitCode !== ExitCode.SUCCESS) {\n return toolConfigsResult.exitCode;\n }\n\n const toolConfigsArray = toolConfigsResult.toolConfigs;\n\n if (toolConfigsArray.length === 0) {\n logger.info(messages.toolNoConfigurationsFound(projectConfig.paths.toolConfigsDir));\n return ExitCode.SUCCESS;\n }\n\n for (const toolConfig of toolConfigsArray) {\n // Check for shim conflicts\n await checkShimConflicts(logger, fs, toolConfig, projectConfig.paths.targetDir, conflictMessages);\n\n // Check for symlink conflicts\n await checkSymlinkConflicts(\n logger,\n fs,\n toolConfig,\n projectConfig.paths.homeDir,\n projectConfig.paths.dotfilesDir,\n conflictMessages,\n );\n }\n\n return reportConflicts(logger, conflictMessages);\n}\n\nexport function registerDetectConflictsCommand(\n parentLogger: TsLogger,\n program: IGlobalProgram,\n servicesFactory: () => Promise<IServices>,\n): void {\n const logger = parentLogger.getSubLogger({ name: 'registerDetectConflictsCommand' });\n program\n .command('detect-conflicts')\n .description('Detects conflicts between potential generated artifacts and existing system files.')\n .action(async () => {\n const combinedOptions = program.opts();\n\n let exitCode: ExitCode;\n try {\n const services = await servicesFactory();\n exitCode = await detectConflictsActionLogic(logger, combinedOptions, services);\n } catch (error) {\n logger.error(messages.commandExecutionFailed('detect-conflicts', ExitCode.ERROR), error);\n exitCode = ExitCode.ERROR;\n }\n\n exitCli(exitCode);\n });\n}\n",
|
|
252
|
+
"/**\n * Environment variable name for the active environment directory.\n */\nexport const ENV_DIR_VAR = 'DOTFILES_ENV_DIR';\n\n/**\n * Environment variable name for the active environment name.\n */\nexport const ENV_NAME_VAR = 'DOTFILES_ENV_NAME';\n\n/**\n * Default environment directory name when none is specified.\n */\nexport const DEFAULT_ENV_NAME = 'env';\n\n/**\n * Name of the source/activation script file (POSIX shells).\n */\nexport const SOURCE_FILE_NAME = 'source';\n\n/**\n * Name of the PowerShell source/activation script file.\n */\nexport const POWERSHELL_SOURCE_FILE_NAME = 'source.ps1';\n\n/**\n * Name of the configuration file.\n */\nexport const CONFIG_FILE_NAME = 'config.ts';\n\n/**\n * Name of the tools configuration directory.\n */\nexport const TOOLS_DIR_NAME = 'tools';\n",
|
|
253
|
+
"import { dedentString, getBuiltPackageName } from '@dotfiles/utils';\n\n/**\n * Generates the default configuration file content for a virtual environment.\n *\n * The configuration uses relative paths based on the config file location\n * to ensure portability.\n *\n * @returns TypeScript configuration file content as a string\n */\nexport function generateDefaultConfig(): string {\n const packageName = getBuiltPackageName();\n\n return dedentString(`\n import { defineConfig } from '${packageName}';\n\n export default defineConfig(({ configFileDir }) => {\n const generatedDir = \\`\\${configFileDir}/.generated\\`;\n\n return {\n paths: {\n generatedDir,\n targetDir: \\`\\${generatedDir}/user-bin\\`,\n toolConfigsDir: \\`\\${configFileDir}/tools\\`,\n binariesDir: \\`\\${generatedDir}/binaries\\`,\n },\n };\n });\n `);\n}\n",
|
|
254
|
+
"import { dedentString } from '@dotfiles/utils';\nimport { ENV_DIR_VAR, ENV_NAME_VAR } from './constants';\n\n/**\n * Generates the PowerShell activation script content for a virtual environment.\n *\n * The script:\n * - Sets DOTFILES_ENV_DIR to the absolute path of the environment\n * - Sets DOTFILES_ENV_NAME to the environment name\n * - Provides a dotfiles-deactivate function to clean up\n * - Handles re-activation by deactivating first\n *\n * @param envDir - Absolute path to the environment directory\n * @param envName - Name of the environment\n * @returns PowerShell script content as a string\n */\nexport function generatePowerShellSourceScript(envDir: string, envName: string): string {\n return dedentString(`\n # ==============================================================================\n # Dotfiles Virtual Environment Activation Script (PowerShell)\n # ==============================================================================\n # This file is automatically generated. Do not edit directly.\n #\n # Usage:\n # . .\\\\${envName}\\\\source.ps1\n # . ${envName}/source.ps1\n #\n # To deactivate:\n # dotfiles-deactivate\n # ==============================================================================\n\n function global:dotfiles-deactivate {\n if ($env:${ENV_DIR_VAR}) {\n # Restore original XDG_CONFIG_HOME\n if ($env:_DOTFILES_OLD_XDG_CONFIG_HOME) {\n $env:XDG_CONFIG_HOME = $env:_DOTFILES_OLD_XDG_CONFIG_HOME\n Remove-Item Env:_DOTFILES_OLD_XDG_CONFIG_HOME -ErrorAction SilentlyContinue\n } else {\n Remove-Item Env:XDG_CONFIG_HOME -ErrorAction SilentlyContinue\n }\n Remove-Item Env:${ENV_DIR_VAR} -ErrorAction SilentlyContinue\n Remove-Item Env:${ENV_NAME_VAR} -ErrorAction SilentlyContinue\n Remove-Item Function:dotfiles-deactivate -ErrorAction SilentlyContinue\n Write-Host \"Deactivated dotfiles environment\"\n }\n }\n\n # If already active, deactivate first\n if ($env:${ENV_DIR_VAR}) {\n dotfiles-deactivate\n }\n\n # Resolve the directory containing this script\n $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path\n if (-not $scriptDir) {\n # Fallback to hardcoded path\n $scriptDir = \"${envDir}\"\n }\n\n # Save old XDG_CONFIG_HOME for restoration on deactivate\n if ($env:XDG_CONFIG_HOME) {\n $env:_DOTFILES_OLD_XDG_CONFIG_HOME = $env:XDG_CONFIG_HOME\n }\n\n # Export environment variables\n $env:${ENV_DIR_VAR} = $scriptDir\n $env:${ENV_NAME_VAR} = \"${envName}\"\n $env:XDG_CONFIG_HOME = \"$scriptDir\\\\.config\"\n\n Write-Host \"Activated dotfiles environment: ${envName}\"\n Write-Host \" Directory: $env:${ENV_DIR_VAR}\"\n Write-Host \" Config: $env:${ENV_DIR_VAR}\\\\config.ts\"\n `);\n}\n",
|
|
255
|
+
"import { dedentString } from '@dotfiles/utils';\nimport { ENV_DIR_VAR, ENV_NAME_VAR } from './constants';\n\n/**\n * Generates the shell activation script content for a virtual environment.\n *\n * The script:\n * - Sets DOTFILES_ENV_DIR to the absolute path of the environment\n * - Sets DOTFILES_ENV_NAME to the environment name\n * - Provides a dotfiles-deactivate function to clean up\n * - Handles re-activation by deactivating first\n *\n * @param envDir - Absolute path to the environment directory\n * @param envName - Name of the environment\n * @returns Shell script content as a string\n */\nexport function generateSourceScript(envDir: string, envName: string): string {\n // Use a heredoc-style approach for better readability\n return dedentString(`\n #!/bin/sh\n # ==============================================================================\n # Dotfiles Virtual Environment Activation Script\n # ==============================================================================\n # This file is automatically generated. Do not edit directly.\n #\n # Usage:\n # source ${envName}/source\n # . ./${envName}/source\n #\n # To deactivate:\n # dotfiles-deactivate\n # ==============================================================================\n\n # Deactivate function to clean up environment\n dotfiles-deactivate() {\n if [ -n \"\\${${ENV_DIR_VAR}:-}\" ]; then\n # Restore original XDG_CONFIG_HOME\n if [ -n \"\\${_DOTFILES_OLD_XDG_CONFIG_HOME:-}\" ]; then\n export XDG_CONFIG_HOME=\"\\${_DOTFILES_OLD_XDG_CONFIG_HOME}\"\n unset _DOTFILES_OLD_XDG_CONFIG_HOME\n else\n unset XDG_CONFIG_HOME\n fi\n unset ${ENV_DIR_VAR}\n unset ${ENV_NAME_VAR}\n unset -f dotfiles-deactivate 2>/dev/null\n echo \"Deactivated dotfiles environment\"\n fi\n }\n\n # If already active, deactivate first\n if [ -n \"\\${${ENV_DIR_VAR}:-}\" ]; then\n dotfiles-deactivate\n fi\n\n # Resolve the directory containing this script\n # Support both sourced and direct execution\n if [ -n \"\\${BASH_SOURCE[0]:-}\" ]; then\n _dotfiles_script_dir=\"$(cd \"$(dirname \"\\${BASH_SOURCE[0]}\")\" && pwd)\"\n elif [ -n \"\\${ZSH_VERSION:-}\" ]; then\n _dotfiles_script_dir=\"$(cd \"$(dirname \"\\${(%):-%x}\")\" && pwd)\"\n else\n # Fallback to the hardcoded path\n _dotfiles_script_dir=\"${envDir}\"\n fi\n\n # Save old XDG_CONFIG_HOME for restoration on deactivate\n if [ -n \"\\${XDG_CONFIG_HOME:-}\" ]; then\n export _DOTFILES_OLD_XDG_CONFIG_HOME=\"\\${XDG_CONFIG_HOME}\"\n fi\n\n # Export environment variables\n export ${ENV_DIR_VAR}=\"\\${_dotfiles_script_dir}\"\n export ${ENV_NAME_VAR}=\"${envName}\"\n export XDG_CONFIG_HOME=\"\\${_dotfiles_script_dir}/.config\"\n\n # Clean up temporary variable\n unset _dotfiles_script_dir\n\n echo \"Activated dotfiles environment: ${envName}\"\n echo \" Directory: \\${${ENV_DIR_VAR}}\"\n echo \" Config: \\${${ENV_DIR_VAR}}/config.ts\"\n `);\n}\n",
|
|
256
|
+
"import { z } from 'zod';\n\n/**\n * Schema for validating create environment options.\n */\nexport const createEnvOptionsSchema = z.object({\n /**\n * Name of the environment directory to create.\n */\n name: z.string().min(1).default('env'),\n\n /**\n * Parent directory where the environment will be created (defaults to cwd).\n */\n parentDir: z.string().min(1),\n\n /**\n * Whether to overwrite an existing environment.\n */\n force: z.boolean().default(false),\n});\n\n/**\n * Schema for environment info.\n */\nexport const envInfoSchema = z.object({\n /**\n * Absolute path to the environment directory.\n */\n envDir: z.string(),\n\n /**\n * Name of the environment.\n */\n name: z.string(),\n\n /**\n * Path to the configuration file.\n */\n configPath: z.string(),\n\n /**\n * Path to the source/activation script.\n */\n sourcePath: z.string(),\n\n /**\n * Path to the tools directory.\n */\n toolsDir: z.string(),\n});\n",
|
|
257
|
+
"import type { IFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport path from 'node:path';\nimport {\n CONFIG_FILE_NAME,\n DEFAULT_ENV_NAME,\n ENV_DIR_VAR,\n ENV_NAME_VAR,\n POWERSHELL_SOURCE_FILE_NAME,\n SOURCE_FILE_NAME,\n TOOLS_DIR_NAME,\n} from './constants';\nimport { generateDefaultConfig } from './generateDefaultConfig';\nimport { generatePowerShellSourceScript } from './generatePowerShellSourceScript';\nimport { generateSourceScript } from './generateSourceScript';\nimport { messages } from './log-messages';\nimport type { ActiveEnvResult, CreateEnvOptions, DetectEnvResult, EnvInfo, VirtualEnvResult } from './types';\n\n/**\n * Interface for the virtual environment manager.\n */\nexport interface IVirtualEnvManager {\n /**\n * Creates a new virtual environment.\n */\n create(options: CreateEnvOptions): Promise<VirtualEnvResult>;\n\n /**\n * Deletes an existing virtual environment.\n */\n delete(envDir: string): Promise<VirtualEnvResult>;\n\n /**\n * Gets information about an existing environment.\n */\n getEnvInfo(envDir: string): Promise<EnvInfo | null>;\n\n /**\n * Checks if a directory is a valid virtual environment.\n */\n isValidEnv(envDir: string): Promise<boolean>;\n\n /**\n * Detects a virtual environment in a directory.\n */\n detectEnv(searchDir: string, envName?: string): Promise<DetectEnvResult>;\n\n /**\n * Gets the currently active environment from environment variables.\n */\n getActiveEnv(): ActiveEnvResult;\n}\n\n/**\n * Manages dotfiles virtual environments.\n *\n * Handles creation, deletion, and detection of virtual environments\n * that provide isolated dotfiles configurations.\n */\nexport class VirtualEnvManager implements IVirtualEnvManager {\n constructor(\n private readonly parentLogger: TsLogger,\n private readonly fs: IFileSystem,\n private readonly env: NodeJS.ProcessEnv = process.env,\n ) {}\n\n private get logger(): TsLogger {\n return this.parentLogger.getSubLogger({ name: 'VirtualEnvManager' });\n }\n\n /**\n * Creates a new virtual environment.\n *\n * @param options - Options for creating the environment\n * @returns Result indicating success or failure\n */\n async create(options: CreateEnvOptions): Promise<VirtualEnvResult> {\n const logger = this.logger.getSubLogger({ name: 'create' });\n const envName = options.name;\n const envDir = path.resolve(options.parentDir, envName);\n\n logger.debug(messages.creatingEnv(envName));\n\n // Check if environment already exists\n if (await this.fs.exists(envDir)) {\n if (!options.force) {\n logger.warn(messages.envAlreadyExists(envDir));\n const result: VirtualEnvResult = { success: false, error: `Environment already exists at ${envDir}` };\n return result;\n }\n // Force mode: delete existing environment first\n await this.fs.rm(envDir, { recursive: true });\n }\n\n // Create environment directory structure\n await this.fs.ensureDir(envDir);\n\n // Create tools directory\n const toolsDir = path.join(envDir, TOOLS_DIR_NAME);\n await this.fs.ensureDir(toolsDir);\n logger.debug(messages.toolsDirCreated(toolsDir));\n\n // Generate and write source script (POSIX)\n const sourcePath = path.join(envDir, SOURCE_FILE_NAME);\n const sourceContent = generateSourceScript(envDir, envName);\n await this.fs.writeFile(sourcePath, sourceContent);\n await this.fs.chmod(sourcePath, 0o755);\n logger.debug(messages.sourceFileGenerated(sourcePath));\n\n // Generate and write PowerShell source script\n const psSourcePath = path.join(envDir, POWERSHELL_SOURCE_FILE_NAME);\n const psSourceContent = generatePowerShellSourceScript(envDir, envName);\n await this.fs.writeFile(psSourcePath, psSourceContent);\n logger.debug(messages.sourceFileGenerated(psSourcePath));\n\n // Generate and write config file\n const configPath = path.join(envDir, CONFIG_FILE_NAME);\n const configContent = generateDefaultConfig();\n await this.fs.writeFile(configPath, configContent);\n logger.debug(messages.configFileGenerated(configPath));\n\n logger.debug(messages.envCreated(envDir));\n const result: VirtualEnvResult = { success: true, envDir, envName };\n return result;\n }\n\n /**\n * Deletes an existing virtual environment.\n *\n * @param envDir - Path to the environment directory to delete\n * @returns Result indicating success or failure\n */\n async delete(envDir: string): Promise<VirtualEnvResult> {\n const logger = this.logger.getSubLogger({ name: 'delete' });\n const envName = path.basename(envDir);\n\n // Check if environment exists\n if (!(await this.isValidEnv(envDir))) {\n logger.warn(messages.envNotFound(envDir));\n const result: VirtualEnvResult = { success: false, error: `Environment not found at ${envDir}` };\n return result;\n }\n\n logger.debug(messages.deletingEnv(envName));\n\n // Remove the entire environment directory\n await this.fs.rm(envDir, { recursive: true });\n\n logger.debug(messages.envDeleted(envDir));\n const result: VirtualEnvResult = { success: true, envDir, envName };\n return result;\n }\n\n /**\n * Gets information about an existing environment.\n *\n * @param envDir - Path to the environment directory\n * @returns Environment info or null if not a valid environment\n */\n async getEnvInfo(envDir: string): Promise<EnvInfo | null> {\n if (!(await this.isValidEnv(envDir))) {\n return null;\n }\n\n const result: EnvInfo = {\n envDir: path.resolve(envDir),\n name: path.basename(envDir),\n configPath: path.join(envDir, CONFIG_FILE_NAME),\n sourcePath: path.join(envDir, SOURCE_FILE_NAME),\n toolsDir: path.join(envDir, TOOLS_DIR_NAME),\n };\n return result;\n }\n\n /**\n * Checks if a directory is a valid virtual environment.\n *\n * A valid environment must have:\n * - A source file\n * - A config.ts file\n *\n * @param envDir - Path to check\n * @returns True if the directory is a valid environment\n */\n async isValidEnv(envDir: string): Promise<boolean> {\n const sourcePath = path.join(envDir, SOURCE_FILE_NAME);\n const configPath = path.join(envDir, CONFIG_FILE_NAME);\n\n const [sourceExists, configExists] = await Promise.all([\n this.fs.exists(sourcePath),\n this.fs.exists(configPath),\n ]);\n\n return sourceExists && configExists;\n }\n\n /**\n * Detects a virtual environment in a directory.\n *\n * Searches for a directory with the given name (or default 'env')\n * that contains a valid environment structure.\n *\n * @param searchDir - Directory to search in\n * @param envName - Name of the environment to look for (defaults to 'env')\n * @returns Detection result\n */\n async detectEnv(searchDir: string, envName?: string): Promise<DetectEnvResult> {\n const logger = this.logger.getSubLogger({ name: 'detectEnv' });\n const targetName = envName ?? DEFAULT_ENV_NAME;\n const envDir = path.resolve(searchDir, targetName);\n\n if (await this.isValidEnv(envDir)) {\n logger.debug(messages.envDetected(envDir));\n const result: DetectEnvResult = {\n found: true,\n envDir,\n envName: targetName,\n configPath: path.join(envDir, CONFIG_FILE_NAME),\n };\n return result;\n }\n\n const result: DetectEnvResult = { found: false };\n return result;\n }\n\n /**\n * Gets the currently active environment from environment variables.\n *\n * @returns Active environment info or inactive result\n */\n getActiveEnv(): ActiveEnvResult {\n const envDir = this.env[ENV_DIR_VAR];\n const envName = this.env[ENV_NAME_VAR];\n\n if (envDir && envName) {\n const result: ActiveEnvResult = { active: true, envDir, envName };\n return result;\n }\n\n const result: ActiveEnvResult = { active: false };\n return result;\n }\n}\n",
|
|
258
|
+
"import { createSafeLogMessage } from '@dotfiles/logger';\n\nexport const messages = {\n creatingEnv: (name: string) => createSafeLogMessage(`Creating environment: ${name}`),\n envCreated: (envDir: string) => createSafeLogMessage(`Environment created at ${envDir}`),\n envAlreadyExists: (envDir: string) => createSafeLogMessage(`Environment already exists at ${envDir}`),\n envNotFound: (envDir: string) => createSafeLogMessage(`Environment not found at ${envDir}`),\n deletingEnv: (name: string) => createSafeLogMessage(`Deleting environment: ${name}`),\n envDeleted: (envDir: string) => createSafeLogMessage(`Environment deleted at ${envDir}`),\n envDetected: (envDir: string) => createSafeLogMessage(`Detected environment at ${envDir}`),\n envActive: (name: string) => createSafeLogMessage(`Active environment: ${name}`),\n sourceFileGenerated: (path: string) => createSafeLogMessage(`Source file generated at ${path}`),\n configFileGenerated: (path: string) => createSafeLogMessage(`Config file generated at ${path}`),\n toolsDirCreated: (path: string) => createSafeLogMessage(`Tools directory created at ${path}`),\n writeFile: (path: string) => createSafeLogMessage(`Writing ${path}`),\n deleteConfirmationRequired: () => createSafeLogMessage('Deletion requires confirmation'),\n};\n",
|
|
259
|
+
"import { NodeFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { exitCli } from '@dotfiles/utils';\nimport {\n CONFIG_FILE_NAME,\n DEFAULT_ENV_NAME,\n VirtualEnvManager,\n} from '@dotfiles/virtual-env';\nimport path from 'node:path';\nimport * as readline from 'node:readline';\nimport { messages } from './log-messages';\nimport type { ICommandCompletionMeta, IGlobalProgram, IGlobalProgramOptions } from './types';\n\n/**\n * Completion metadata for the env command.\n */\nexport const ENV_COMMAND_COMPLETION: ICommandCompletionMeta = {\n name: 'env',\n description: 'Manage virtual environments',\n subcommands: [\n {\n name: 'create',\n description: 'Create a new virtual environment',\n hasPositionalArg: true,\n positionalArgDescription: 'environment name (default: env)',\n },\n {\n name: 'delete',\n description: 'Delete a virtual environment',\n hasPositionalArg: true,\n positionalArgDescription: 'environment name (default: env)',\n options: [\n { flag: '--force', description: 'Skip confirmation prompt' },\n ],\n },\n ],\n};\n\n/**\n * Options for the env create command.\n */\nexport interface IEnvCreateOptions {\n // No command-specific options currently\n}\n\n/**\n * Options for the env delete command.\n */\nexport interface IEnvDeleteOptions {\n force?: boolean;\n}\n\n/**\n * Prompts the user for confirmation.\n */\nasync function confirmDeletion(envDir: string): Promise<boolean> {\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n return new Promise((resolve) => {\n rl.question(`Delete environment at '${envDir}'? [y/N] `, (answer) => {\n rl.close();\n resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');\n });\n });\n}\n\nasync function createActionLogic(\n logger: TsLogger,\n envName: string,\n _options: IEnvCreateOptions & IGlobalProgramOptions,\n): Promise<void> {\n const fs = new NodeFileSystem();\n const manager = new VirtualEnvManager(logger, fs);\n const cwd = process.cwd();\n\n const result = await manager.create({\n name: envName,\n parentDir: cwd,\n force: false,\n });\n\n if (!result.success) {\n logger.error(messages.envOperationFailed('create', result.error));\n exitCli(1);\n return;\n }\n\n const configPath = path.join(result.envDir, CONFIG_FILE_NAME);\n logger.info(messages.envCreated(result.envDir));\n logger.info(messages.envActivationHint(envName));\n logger.info(messages.envConfigPath(configPath));\n}\n\nasync function deleteActionLogic(\n logger: TsLogger,\n envName: string,\n options: IEnvDeleteOptions & IGlobalProgramOptions,\n): Promise<void> {\n const fs = new NodeFileSystem();\n const manager = new VirtualEnvManager(logger, fs);\n const cwd = process.cwd();\n const envDir = path.resolve(cwd, envName);\n\n // Check if environment exists\n if (!(await manager.isValidEnv(envDir))) {\n logger.error(messages.envNotFound(envDir));\n exitCli(1);\n return;\n }\n\n // Ask for confirmation unless --force is provided\n if (!options.force) {\n const confirmed = await confirmDeletion(envDir);\n if (!confirmed) {\n logger.info(messages.envDeletionCancelled());\n return;\n }\n }\n\n const result = await manager.delete(envDir);\n\n if (!result.success) {\n logger.error(messages.envOperationFailed('delete', result.error));\n exitCli(1);\n return;\n }\n\n logger.info(messages.envDeleted(result.envDir));\n}\n\nexport function registerEnvCommand(\n parentLogger: TsLogger,\n program: IGlobalProgram,\n): void {\n const logger = parentLogger.getSubLogger({ name: 'registerEnvCommand' });\n\n const envCmd = program\n .command('env')\n .description('Manage virtual environments for isolated dotfiles configurations.');\n\n // Create subcommand\n envCmd\n .command('create [name]')\n .description('Create a new virtual environment')\n .action(async (name?: string) => {\n const envName = name ?? DEFAULT_ENV_NAME;\n const combinedOptions: IEnvCreateOptions & IGlobalProgramOptions = program.opts();\n await createActionLogic(logger, envName, combinedOptions);\n });\n\n // Delete subcommand\n envCmd\n .command('delete [name]')\n .description('Delete a virtual environment')\n .option('--force', 'Skip confirmation prompt', false)\n .action(async (name: string | undefined, options: IEnvDeleteOptions) => {\n const envName = name ?? DEFAULT_ENV_NAME;\n const combinedOptions: IEnvDeleteOptions & IGlobalProgramOptions = { ...options, ...program.opts() };\n await deleteActionLogic(logger, envName, combinedOptions);\n });\n}\n",
|
|
260
|
+
"import type { TsLogger } from '@dotfiles/logger';\nimport { exitCli } from '@dotfiles/utils';\nimport { messages } from './log-messages';\nimport type { IBaseCommandOptions, ICommandCompletionMeta, IGlobalProgram, IServices } from './types';\n\n/**\n * Completion metadata for the features command.\n */\nexport const FEATURES_COMMAND_COMPLETION: ICommandCompletionMeta = {\n name: 'features',\n description: 'Manage features and generate artifacts',\n subcommands: [\n {\n name: 'catalog',\n description: 'Generate catalog of available features',\n },\n ],\n};\n\nexport interface IFeaturesCommandOptions extends IBaseCommandOptions {\n // No command-specific options for features command\n}\n\nasync function catalogActionLogic(\n logger: TsLogger,\n _options: IFeaturesCommandOptions,\n services: IServices,\n): Promise<void> {\n try {\n const { projectConfig, fs, configService, readmeService, systemInfo } = services;\n\n const toolConfigs = await configService.loadToolConfigs(\n logger,\n projectConfig.paths.toolConfigsDir,\n fs,\n projectConfig,\n systemInfo,\n );\n\n await readmeService.generateCatalogFromConfigs(projectConfig.features.catalog.filePath, toolConfigs);\n } catch (error) {\n logger.error(messages.commandExecutionFailed('features catalog', 1), error);\n exitCli(1);\n }\n}\n\nexport function registerFeaturesCommand(\n parentLogger: TsLogger,\n program: IGlobalProgram,\n servicesFactory: () => Promise<IServices>,\n): void {\n const logger = parentLogger.getSubLogger({ name: 'registerFeaturesCommand' });\n\n const featuresCmd = program\n .command('features')\n .description('Manage features and generate feature-specific artifacts.');\n\n // Catalog subcommand\n featuresCmd\n .command('catalog')\n .description('Catalog of available features documentation')\n .action(async () => {\n const combinedOptions: IFeaturesCommandOptions = program.opts();\n const services = await servicesFactory();\n await catalogActionLogic(logger, combinedOptions, services);\n\n logger.info(messages.commandCompleted(Boolean(combinedOptions.dryRun)));\n });\n}\n",
|
|
261
|
+
"import type { IFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport type { IToolInstallationRegistry } from '@dotfiles/registry/tool';\nimport { exitCli, ExitCode } from '@dotfiles/utils';\nimport { messages } from './log-messages';\nimport type {\n ICommandCompletionMeta,\n IFilesCommandSpecificOptions,\n IGlobalProgram,\n IGlobalProgramOptions,\n IServices,\n} from './types';\n\n/**\n * Completion metadata for the files command.\n */\nexport const FILES_COMMAND_COMPLETION: ICommandCompletionMeta = {\n name: 'files',\n description: 'Show generated files structure',\n hasPositionalArg: true,\n positionalArgDescription: 'tool name (optional)',\n positionalArgType: 'tool',\n};\n\ninterface ITreeNode {\n name: string;\n isDirectory: boolean;\n children?: ITreeNode[];\n}\n\ntype PrintFunction = (message: string) => void;\n\nasync function buildTreeFromDirectory(logger: TsLogger, fs: IFileSystem, dirPath: string): Promise<ITreeNode[]> {\n const nodes: ITreeNode[] = [];\n\n try {\n const entries = await fs.readdir(dirPath);\n\n for (const entry of entries) {\n const fullPath: string = `${dirPath}/${entry}`;\n const stats = await fs.stat(fullPath);\n const isDirectory: boolean = stats.isDirectory();\n\n const node: ITreeNode = {\n name: entry,\n isDirectory,\n };\n\n if (isDirectory) {\n node.children = await buildTreeFromDirectory(logger, fs, fullPath);\n }\n\n nodes.push(node);\n }\n } catch (error) {\n logger.error(messages.commandExecutionFailed('files', ExitCode.ERROR), error);\n }\n\n // Sort: directories first, then files, both alphabetically\n return nodes.toSorted((a, b) => {\n if (a.isDirectory === b.isDirectory) {\n return a.name.localeCompare(b.name);\n }\n return a.isDirectory ? -1 : 1;\n });\n}\n\nfunction formatTree(nodes: ITreeNode[], prefix: string = ''): string {\n const lines: string[] = [];\n\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i];\n if (!node) {\n continue;\n }\n\n const isLastNode: boolean = i === nodes.length - 1;\n const connector: string = isLastNode ? '└─ ' : '├─ ';\n const displayName: string = node.name;\n\n lines.push(`${prefix}${connector}${displayName}`);\n\n if (node.isDirectory && node.children && node.children.length > 0) {\n const childPrefix: string = prefix + (isLastNode ? ' ' : '│ ');\n const childTree = formatTree(node.children, childPrefix);\n lines.push(childTree);\n }\n }\n\n return lines.join('\\n');\n}\n\nasync function displayTreeForTool(\n logger: TsLogger,\n fs: IFileSystem,\n toolInstallationRegistry: IToolInstallationRegistry,\n toolName: string,\n print: PrintFunction,\n): Promise<ExitCode> {\n const installation = await toolInstallationRegistry.getToolInstallation(toolName);\n\n if (!installation) {\n logger.error(messages.toolNotInstalled(toolName));\n return ExitCode.ERROR;\n }\n\n const installPath: string = installation.installPath;\n const exists: boolean = await fs.exists(installPath);\n\n if (!exists) {\n logger.error(messages.installPathNotFound(installPath));\n return ExitCode.ERROR;\n }\n\n // Display the full path first\n print(installPath);\n\n // Build and display the tree\n const tree = await buildTreeFromDirectory(logger, fs, installPath);\n\n if (tree.length === 0) {\n print('(empty directory)');\n return ExitCode.SUCCESS;\n }\n\n const treeOutput = formatTree(tree);\n print(treeOutput);\n\n return ExitCode.SUCCESS;\n}\n\nasync function filesActionLogic(\n logger: TsLogger,\n toolName: string,\n _options: IFilesCommandSpecificOptions & IGlobalProgramOptions,\n services: IServices,\n print: PrintFunction,\n): Promise<void> {\n const { fs, projectConfig, configService, toolInstallationRegistry, systemInfo } = services;\n\n try {\n const toolConfig = await configService.loadSingleToolConfig(\n logger,\n toolName,\n projectConfig.paths.toolConfigsDir,\n fs,\n projectConfig,\n systemInfo,\n );\n\n if (!toolConfig) {\n logger.error(messages.toolNotFound(toolName, projectConfig.paths.toolConfigsDir));\n exitCli(ExitCode.ERROR);\n return;\n }\n\n const exitCode = await displayTreeForTool(logger, fs, toolInstallationRegistry, toolName, print);\n if (exitCode !== ExitCode.SUCCESS) {\n exitCli(exitCode);\n }\n } catch (error) {\n logger.error(messages.commandExecutionFailed('files', ExitCode.ERROR), error);\n exitCli(ExitCode.ERROR);\n }\n}\n\nexport function registerFilesCommand(\n parentLogger: TsLogger,\n program: IGlobalProgram,\n servicesFactory: () => Promise<IServices>,\n // oxlint-disable-next-line no-console: default print function\n print: PrintFunction = console.log,\n): void {\n const logger = parentLogger.getSubLogger({ name: 'registerFilesCommand' });\n\n program\n .command('files <toolName>')\n .description('Display a tree view of files in the tool installation directory')\n .action(async (toolName: string, commandOptions: IFilesCommandSpecificOptions) => {\n const combinedOptions: IFilesCommandSpecificOptions & IGlobalProgramOptions = {\n ...commandOptions,\n ...program.opts(),\n };\n const services = await servicesFactory();\n await filesActionLogic(logger, toolName, combinedOptions, services, print);\n });\n}\n",
|
|
262
|
+
"import type { TsLogger } from '@dotfiles/logger';\nimport { exitCli, generateToolTypes } from '@dotfiles/utils';\nimport path from 'node:path';\nimport { generateZshCompletion } from './generateZshCompletion';\nimport { messages } from './log-messages';\nimport type { IGlobalProgram, IGlobalProgramOptions, IServices } from './types';\n\n// Re-export the completion metadata for external use\nexport * from './generateCommandCompletion';\n\n/**\n * The binary name for the dotfiles CLI.\n */\nconst DOTFILES_CLI_BINARY_NAME = 'dotfiles';\n\n/**\n * Command-specific options for the generate command.\n */\nexport interface IGenerateCommandSpecificOptions {\n overwrite?: boolean;\n}\n\n/**\n * Combined options for the generate command (command-specific + global).\n */\nexport interface IGenerateCommandOptions extends IGenerateCommandSpecificOptions, IGlobalProgramOptions {}\n\n/**\n * Generates the CLI completion file for zsh.\n */\nasync function generateCliCompletions(logger: TsLogger, services: IServices, toolNames: string[]): Promise<void> {\n const subLogger = logger.getSubLogger({ name: 'generateCliCompletions' });\n const { projectConfig, fs } = services;\n\n const completionContent = generateZshCompletion(DOTFILES_CLI_BINARY_NAME, toolNames);\n const completionDir = path.join(projectConfig.paths.shellScriptsDir, 'zsh', 'completions');\n const completionPath = path.join(completionDir, `_${DOTFILES_CLI_BINARY_NAME}`);\n\n await fs.ensureDir(completionDir);\n await fs.writeFile(completionPath, completionContent);\n subLogger.debug(messages.cliCompletionGenerated(completionPath));\n}\n\nexport function registerGenerateCommand(\n parentLogger: TsLogger,\n program: IGlobalProgram,\n servicesFactory: () => Promise<IServices>,\n): void {\n const logger = parentLogger.getSubLogger({ name: 'registerGenerateCommand' });\n program\n .command('generate')\n .description('Generates shims, shell init files, and symlinks based on tool configurations.')\n .option('--overwrite', 'Overwrite conflicting files that were not created by the generator')\n .action(async (options: IGenerateCommandSpecificOptions) => {\n const combinedOptions: IGenerateCommandOptions = { ...options, ...program.opts() };\n const services = await servicesFactory();\n const { projectConfig, fs, generatorOrchestrator, configService, systemInfo, installer } = services;\n\n try {\n logger.debug(messages.toolConfigsLoading(projectConfig.paths.toolConfigsDir), fs.constructor.name);\n const toolConfigs = await configService.loadToolConfigs(\n logger,\n projectConfig.paths.toolConfigsDir,\n fs,\n projectConfig,\n systemInfo,\n );\n logger.debug(messages.toolConfigsLoaded(projectConfig.paths.toolConfigsDir, Object.keys(toolConfigs).length));\n\n const toolTypesPath: string = path.join(projectConfig.paths.generatedDir, 'tool-types.d.ts');\n await generateToolTypes(toolConfigs, toolTypesPath, fs);\n logger.debug(messages.toolTypesGenerated(toolTypesPath));\n\n await generatorOrchestrator.generateAll(toolConfigs, { overwrite: combinedOptions.overwrite, installer });\n\n // Generate CLI completions after tool completions\n const toolNames = Object.keys(toolConfigs).toSorted((a, b) => a.localeCompare(b));\n await generateCliCompletions(logger, services, toolNames);\n\n logger.info(messages.commandCompleted(Boolean(combinedOptions.dryRun)));\n } catch (_error) {\n logger.error(messages.commandExecutionFailed('generate', 1));\n exitCli(1);\n }\n });\n}\n",
|
|
263
|
+
"import type { ICommandCompletionMeta } from './types';\n\n/**\n * Completion metadata for the generate command.\n * Defined separately to avoid circular dependencies with generateZshCompletion.ts.\n */\nexport const GENERATE_COMMAND_COMPLETION: ICommandCompletionMeta = {\n name: 'generate',\n description: 'Generate shims, shell init files, and symlinks',\n options: [{ flag: '--overwrite', description: 'Overwrite conflicting files not created by generator' }],\n};\n",
|
|
264
|
+
"import type { IConfigService, ProjectConfig } from '@dotfiles/config';\nimport type { ISystemInfo, ToolConfig } from '@dotfiles/core';\nimport type { IResolvedFileSystem } from '@dotfiles/file-system';\nimport type { InstallResult } from '@dotfiles/installer';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { exitCli, resolvePlatformConfig } from '@dotfiles/utils';\nimport path from 'node:path';\nimport { messages } from './log-messages';\nimport type {\n ICommandCompletionMeta,\n IGlobalProgram,\n IGlobalProgramOptions,\n InstallCommandSpecificOptions,\n IServices,\n} from './types';\n\n/**\n * Completion metadata for the install command.\n */\nexport const INSTALL_COMMAND_COMPLETION: ICommandCompletionMeta = {\n name: 'install',\n description: 'Install a tool by name or binary',\n hasPositionalArg: true,\n positionalArgDescription: 'tool name or binary name to install',\n positionalArgType: 'tool',\n options: [\n { flag: '--force', description: 'Force installation even if already installed' },\n { flag: '--shim-mode', description: 'Optimized output for shim usage' },\n ],\n};\n\n/**\n * Result of loading a tool configuration.\n * Returns the tool config and actual tool name (which may differ from input if looked up by binary).\n */\ntype LoadToolConfigResult =\n | { success: true; toolConfig: ToolConfig; toolName: string; }\n | { success: false; error: string; };\n\n/**\n * Type guard to check if a result from loadToolConfigByBinary is an error object.\n */\nfunction isConfigByBinaryError(result: unknown): result is { error: string; } {\n return typeof result === 'object' && result !== null && 'error' in result;\n}\n\n/**\n * Loads a tool configuration by name or binary name.\n *\n * First attempts to load by tool name (filename without .tool.ts extension).\n * If not found, attempts to find a tool that provides a binary with the given name.\n *\n * @param logger - Logger instance for logging operations.\n * @param nameOrBinary - The tool name or binary name to search for.\n * @param toolConfigsDir - Directory containing tool configuration files.\n * @param fs - File system interface for reading configuration files.\n * @param projectConfig - Parsed project configuration object.\n * @param configService - Configuration service for loading tool configs.\n * @param systemInfo - System information for context creation.\n * @returns Result object with tool config and actual tool name, or error message.\n */\nasync function loadToolConfigByNameOrBinary(\n logger: TsLogger,\n nameOrBinary: string,\n toolConfigsDir: string,\n fs: IResolvedFileSystem,\n projectConfig: ProjectConfig,\n configService: IConfigService,\n systemInfo: ISystemInfo,\n): Promise<LoadToolConfigResult> {\n // First, try to load by exact tool name\n const toolConfig = await configService.loadSingleToolConfig(\n logger,\n nameOrBinary,\n toolConfigsDir,\n fs,\n projectConfig,\n systemInfo,\n );\n\n if (toolConfig) {\n const result: LoadToolConfigResult = { success: true, toolConfig, toolName: nameOrBinary };\n return result;\n }\n\n // Not found by tool name, try to find by binary name\n logger.debug(messages.toolLookupByBinaryStarted(nameOrBinary));\n const binaryLookupResult = await configService.loadToolConfigByBinary(\n logger,\n nameOrBinary,\n toolConfigsDir,\n fs,\n projectConfig,\n systemInfo,\n );\n\n if (isConfigByBinaryError(binaryLookupResult)) {\n const result: LoadToolConfigResult = { success: false, error: binaryLookupResult.error };\n return result;\n }\n\n if (binaryLookupResult) {\n logger.debug(messages.toolFoundByBinary(nameOrBinary, binaryLookupResult.name));\n const result: LoadToolConfigResult = {\n success: true,\n toolConfig: binaryLookupResult,\n toolName: binaryLookupResult.name,\n };\n return result;\n }\n\n // Not found by either method\n const result: LoadToolConfigResult = {\n success: false,\n error: `No tool or binary named \"${nameOrBinary}\" found in ${toolConfigsDir}`,\n };\n return result;\n}\n\nfunction handleInstallationResult(\n logger: TsLogger,\n result: InstallResult,\n toolName: string,\n shimMode: boolean,\n): number | null {\n if (result.success) {\n if (shimMode) {\n // In shim mode, exit silently on success\n return 0;\n } else {\n // Normal mode: log success message and continue (don't exit)\n const actualMethod = result.installationMethod ?? 'unknown';\n const version = result.version ?? 'unknown';\n if (actualMethod === 'already-installed') {\n logger.info(messages.toolAlreadyInstalled(toolName, version));\n } else {\n logger.info(messages.toolInstalled(toolName, version, actualMethod));\n }\n return null; // Don't exit on success in normal mode\n }\n } else {\n // Error already logged by Installer - just return exit code\n return 1;\n }\n}\n\nfunction handleInstallationError(logger: TsLogger, error: Error, toolName: string, shimMode: boolean): number {\n if (shimMode) {\n // In shim mode, output user-friendly error message to stderr only\n process.stderr.write(`Failed to install '${toolName}': ${error.message}\\n`);\n } else {\n // Normal mode: use logger only\n logger.error(messages.commandExecutionFailed('install', 1), error);\n }\n return 1;\n}\n\nasync function deleteShimsForTool(\n toolConfig: ToolConfig,\n targetDir: string,\n fs: IResolvedFileSystem,\n logger: TsLogger,\n): Promise<void> {\n if (!toolConfig.binaries || toolConfig.binaries.length === 0) {\n return;\n }\n for (const binary of toolConfig.binaries) {\n const binaryName = typeof binary === 'string' ? binary : binary.name;\n const shimPath = path.join(targetDir, binaryName);\n try {\n await fs.lstat(shimPath);\n await fs.rm(shimPath, { force: true });\n logger.debug(messages.shimDeleted(binaryName, shimPath));\n } catch {\n // Shim doesn't exist, nothing to delete\n }\n }\n}\n\nfunction isConfigurationOnlyToolConfig(toolConfig: ToolConfig): boolean {\n const isManual = toolConfig.installationMethod === 'manual';\n const hasNoInstallParams = !toolConfig.installParams || Object.keys(toolConfig.installParams).length === 0;\n const hasNoBinaries = !toolConfig.binaries || toolConfig.binaries.length === 0;\n return isManual && hasNoInstallParams && hasNoBinaries;\n}\n\nfunction toError(value: unknown): Error {\n if (value instanceof Error) {\n return value;\n }\n\n const message = typeof value === 'string' ? value : 'Unknown error';\n const error = new Error(message);\n return error;\n}\n\nasync function executeInstallCommandAction(\n logger: TsLogger,\n nameOrBinary: string,\n combinedOptions: InstallCommandSpecificOptions & IGlobalProgramOptions,\n services: IServices,\n): Promise<number | null> {\n const { projectConfig, fs, installer, configService, generatorOrchestrator, systemInfo } = services;\n\n logger.debug(\n messages.commandActionStarted('install', nameOrBinary),\n projectConfig.paths.toolConfigsDir,\n fs.constructor.name,\n );\n\n const loadResult = await loadToolConfigByNameOrBinary(\n logger,\n nameOrBinary,\n projectConfig.paths.toolConfigsDir,\n fs,\n projectConfig,\n configService,\n systemInfo,\n );\n\n if (!loadResult.success) {\n logger.error(messages.toolNotFoundByBinary(nameOrBinary, projectConfig.paths.toolConfigsDir));\n const result: number = 1;\n return result;\n }\n\n const { toolConfig, toolName } = loadResult;\n\n // Resolve platform-specific configuration before checking if it's configuration-only.\n // Tools like skhd may define installation only in platform-specific configs, so we need\n // to resolve the platform config first to determine if there are actual installation steps.\n const resolvedToolConfig = resolvePlatformConfig(toolConfig, systemInfo);\n\n if (isConfigurationOnlyToolConfig(resolvedToolConfig)) {\n if (!combinedOptions.shimMode) {\n logger.info(messages.toolInstallSkippedConfigurationOnly(toolName));\n }\n\n await generatorOrchestrator.generateCompletionsForTool(toolName, toolConfig);\n\n const result: number | null = combinedOptions.shimMode ? 0 : null;\n return result;\n }\n\n const result = await installer.install(toolName, toolConfig, {\n force: combinedOptions.force,\n verbose: combinedOptions.verbose,\n shimMode: combinedOptions.shimMode,\n });\n\n if (result.success) {\n // Extract binaryPaths from install result if available\n const binaryPaths = 'binaryPaths' in result ? result.binaryPaths : undefined;\n await generatorOrchestrator.generateCompletionsForTool(toolName, toolConfig, result.version, binaryPaths);\n\n // Delete temporary shims for externally managed tools (brew, dmg).\n // After install, the tool's binaries are in their own PATH location.\n const externallyManagedMethods = services.pluginRegistry.getExternallyManagedMethods();\n if (externallyManagedMethods.has(resolvedToolConfig.installationMethod)) {\n await deleteShimsForTool(resolvedToolConfig, projectConfig.paths.targetDir, fs, logger);\n }\n }\n\n const exitCode = handleInstallationResult(logger, result, toolName, combinedOptions.shimMode);\n return exitCode;\n}\n\nexport function registerInstallCommand(\n parentLogger: TsLogger,\n program: IGlobalProgram,\n servicesFactory: () => Promise<IServices>,\n): void {\n const logger = parentLogger.getSubLogger({ name: 'registerInstallCommand' });\n program\n .command('install <nameOrBinary>')\n .description(\n 'Installs a tool by name or binary. Accepts tool name (filename without .tool.ts) or binary name (from .bin()).',\n )\n .option('--force', 'Force installation even if the tool is already installed', false)\n .option('--shim-mode', 'Optimized output for shim usage: shows progress bars but suppresses log messages', false)\n .action(async (nameOrBinary: string, commandOptions: InstallCommandSpecificOptions) => {\n const combinedOptions: InstallCommandSpecificOptions & IGlobalProgramOptions = {\n ...commandOptions,\n ...program.opts(),\n };\n const services = await servicesFactory();\n let shouldExitWithCode: number | null = null;\n\n try {\n shouldExitWithCode = await executeInstallCommandAction(logger, nameOrBinary, combinedOptions, services);\n } catch (error) {\n const finalError = toError(error);\n shouldExitWithCode = handleInstallationError(logger, finalError, nameOrBinary, combinedOptions.shimMode);\n }\n\n if (shouldExitWithCode !== null) {\n exitCli(shouldExitWithCode);\n }\n });\n}\n",
|
|
265
|
+
"import type { ProjectConfig } from '@dotfiles/config';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport type { IFileOperation, IFileRegistry } from '@dotfiles/registry/file';\nimport { contractHomePath, exitCli, ExitCode, formatPermissions } from '@dotfiles/utils';\nimport { messages } from './log-messages';\nimport type {\n ICommandCompletionMeta,\n IGlobalProgram,\n IGlobalProgramOptions,\n ILogCommandSpecificOptions,\n IServices,\n} from './types';\n\n/**\n * Completion metadata for the log command.\n */\nexport const LOG_COMMAND_COMPLETION: ICommandCompletionMeta = {\n name: 'log',\n description: 'Show file operation history',\n hasPositionalArg: true,\n positionalArgDescription: 'tool name (optional)',\n positionalArgType: 'tool',\n options: [\n { flag: '--type', description: 'Filter by file type', hasArg: true, argPlaceholder: '<type>' },\n { flag: '--status', description: 'Show current file states' },\n { flag: '--since', description: 'Show operations since date', hasArg: true, argPlaceholder: '<date>' },\n ],\n};\n\nfunction buildOperationsFilter(\n options: ILogCommandSpecificOptions & IGlobalProgramOptions,\n parentLogger: TsLogger,\n): { filter: Record<string, unknown>; exitCode: ExitCode; } {\n const logger = parentLogger.getSubLogger({ name: 'buildOperationsFilter' });\n const { tool, type, since } = options;\n const filter: Record<string, unknown> = {};\n\n if (tool) {\n filter['toolName'] = tool;\n }\n\n if (type) {\n filter['fileType'] = type;\n }\n\n if (since) {\n const sinceDate = new Date(since);\n if (Number.isNaN(sinceDate.getTime())) {\n logger.error(messages.configParameterInvalid('date format for --since', since, 'ISO format like \"2025-08-01\"'));\n return { filter: {}, exitCode: ExitCode.ERROR };\n }\n filter['createdAfter'] = sinceDate.getTime();\n }\n\n return { filter, exitCode: ExitCode.SUCCESS };\n}\n\nasync function showFileStates(\n parentLogger: TsLogger,\n fileRegistry: IFileRegistry,\n fs: IFileSystem,\n tool?: string,\n): Promise<void> {\n const logger = parentLogger.getSubLogger({ name: 'showFileStates' });\n const allTools = await fileRegistry.getRegisteredTools();\n logger.info(messages.logCheckingFileStates());\n\n for (const toolName of allTools) {\n if (tool && toolName !== tool) continue;\n\n const fileStates = await fileRegistry.getFileStatesForTool(toolName);\n if (fileStates.length === 0) continue;\n\n logger.info(messages.logFileStatesForTool(toolName));\n\n for (const state of fileStates) {\n await logFileState(logger, fs, state);\n }\n }\n}\n\nasync function logFileState(\n parentLogger: TsLogger,\n fs: IFileSystem,\n state: { filePath: string; fileType: string; sizeBytes?: number; targetPath?: string; },\n): Promise<void> {\n const logger = parentLogger.getSubLogger({ name: 'logFileState' });\n const exists = await fs.exists(state.filePath);\n const statusIcon = exists ? '✓' : '✗';\n const statusText = exists ? 'exists' : 'MISSING';\n const sizeText = state.sizeBytes ? ` (${state.sizeBytes} bytes)` : '';\n\n logger.info(messages.logFileStatus(statusIcon, state.filePath, state.fileType, statusText, sizeText));\n\n if (state.targetPath) {\n const targetExists = await fs.exists(state.targetPath);\n const targetIcon = targetExists ? '→' : '✗';\n logger.info(messages.logTargetStatus(targetIcon, state.targetPath));\n }\n}\n\nfunction buildMetadataString(operation: {\n operationType: string;\n sizeBytes?: number;\n permissions?: number;\n targetPath?: string;\n metadata?: Record<string, unknown>;\n}): string {\n const metadataParts: string[] = [];\n\n // Only include size for write operations (not for chmod)\n if (operation.sizeBytes && operation.operationType === 'writeFile') {\n metadataParts.push(`size: ${operation.sizeBytes}`);\n }\n\n // Never include permissions - they're shown in chmod/write commands when relevant\n // Never include targetPath - it's shown in ln/cp/mv commands\n\n // Include custom metadata\n if (operation.metadata && Object.keys(operation.metadata).length > 0) {\n for (const [key, value] of Object.entries(operation.metadata)) {\n if (key === 'newMode') {\n metadataParts.push(`${key}: ${formatPermissions(value as string | number)}`);\n } else {\n metadataParts.push(`${key}: ${value}`);\n }\n }\n }\n\n return metadataParts.length > 0 ? `(${metadataParts.join(', ')})` : '';\n}\n\nfunction formatTimestamp(createdAt: number): string {\n return new Date(createdAt)\n .toLocaleString('en-US', {\n hour12: false,\n year: 'numeric',\n month: 'numeric',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n })\n .replace(',', '');\n}\n\nfunction logOperationByType(\n parentLogger: TsLogger,\n operation: IFileOperation,\n timestamp: string,\n contractedPath: string,\n metadataString: string,\n projectConfig: ProjectConfig,\n): void {\n const logger = parentLogger.getSubLogger({ name: 'logOperationByType' });\n switch (operation.operationType) {\n case 'writeFile': {\n const writeMessage = `[${operation.toolName}] write ${contractedPath}`;\n logger.info(messages.logOperationHistory(timestamp, writeMessage, metadataString));\n break;\n }\n case 'mkdir': {\n const mkdirMessage = `[${operation.toolName}] mkdir ${contractedPath}`;\n logger.info(messages.logOperationHistory(timestamp, mkdirMessage, metadataString));\n break;\n }\n case 'chmod': {\n const modeString = formatPermissions(operation.permissions || 0);\n const chmodMessage = `[${operation.toolName}] chmod ${modeString} ${contractedPath}`;\n logger.info(messages.logOperationHistory(timestamp, chmodMessage, metadataString));\n break;\n }\n case 'rm': {\n const removeMessage = `[${operation.toolName}] rm ${contractedPath}`;\n logger.info(messages.logOperationHistory(timestamp, removeMessage, metadataString));\n break;\n }\n case 'rename': {\n const targetPath = operation.targetPath\n ? contractHomePath(projectConfig.paths.homeDir, operation.targetPath)\n : contractedPath;\n const renameMessage = `[${operation.toolName}] mv ${targetPath} ${contractedPath}`;\n logger.info(messages.logOperationHistory(timestamp, renameMessage, metadataString));\n break;\n }\n case 'cp': {\n const sourcePath = operation.targetPath\n ? contractHomePath(projectConfig.paths.homeDir, operation.targetPath)\n : contractedPath;\n const copyMessage = `[${operation.toolName}] cp ${sourcePath} ${contractedPath}`;\n logger.info(messages.logOperationHistory(timestamp, copyMessage, metadataString));\n break;\n }\n case 'symlink': {\n const symlinkTargetPath = operation.targetPath\n ? contractHomePath(projectConfig.paths.homeDir, operation.targetPath)\n : contractedPath;\n const symlinkMessage = `[${operation.toolName}] ln -s ${symlinkTargetPath} ${contractedPath}`;\n logger.info(messages.logOperationHistory(timestamp, symlinkMessage, metadataString));\n break;\n }\n default: {\n const defaultMessage = `[${operation.toolName}] write ${contractedPath}`;\n logger.info(messages.logOperationHistory(timestamp, defaultMessage, metadataString));\n }\n }\n}\n\nfunction groupOperationsByTool(operations: IFileOperation[]): Record<string, IFileOperation[]> {\n const operationsByTool: Record<string, IFileOperation[]> = {};\n\n for (const operation of operations) {\n if (!operationsByTool[operation.toolName]) {\n operationsByTool[operation.toolName] = [];\n }\n operationsByTool[operation.toolName]?.push(operation);\n }\n\n return operationsByTool;\n}\n\nasync function showOperations(\n parentLogger: TsLogger,\n operations: IFileOperation[],\n projectConfig: ProjectConfig,\n): Promise<void> {\n const logger = parentLogger.getSubLogger({ name: 'showOperations' });\n if (operations.length === 0) {\n logger.info(messages.logNoOperationsFound());\n return;\n }\n\n const operationsByTool = groupOperationsByTool(operations);\n\n for (const [, toolOperations] of Object.entries(operationsByTool)) {\n for (const operation of toolOperations) {\n const timestamp = formatTimestamp(operation.createdAt);\n const metadataString = buildMetadataString(operation);\n const contractedPath = contractHomePath(projectConfig.paths.homeDir, operation.filePath);\n\n logOperationByType(logger, operation, timestamp, contractedPath, metadataString, projectConfig);\n }\n }\n}\n\nasync function logActionLogic(\n parentLogger: TsLogger,\n options: ILogCommandSpecificOptions & IGlobalProgramOptions,\n services: IServices,\n): Promise<void> {\n const logger = parentLogger.getSubLogger({ name: 'logActionLogic' });\n const { fileRegistry, fs, projectConfig } = services;\n\n try {\n if (options.status) {\n await showFileStates(logger, fileRegistry, fs, options.tool);\n return;\n }\n\n const filterResult = buildOperationsFilter(options, logger);\n if (filterResult.exitCode !== ExitCode.SUCCESS) {\n exitCli(filterResult.exitCode);\n return;\n }\n const operations = await fileRegistry.getOperations(filterResult.filter);\n await showOperations(logger, operations, projectConfig);\n } catch (error) {\n logger.error(messages.commandExecutionFailed('log', ExitCode.ERROR), error);\n exitCli(ExitCode.ERROR);\n }\n}\n\nexport function registerLogCommand(\n parentLogger: TsLogger,\n program: IGlobalProgram,\n servicesFactory: () => Promise<IServices>,\n): void {\n const logger = parentLogger.getSubLogger({ name: 'registerLogCommand' });\n\n program\n .command('log [tool]')\n .description('Inspect tracked files in the registry')\n .option('--type <type>', 'Show files of specific type only (shim, binary, symlink, etc.)')\n .option('--status', 'Check file status (missing, broken links, etc.)')\n .option('--since <date>', 'Show files created since date (ISO format: 2025-08-01)')\n .action(async (tool: string | undefined, commandOptions: ILogCommandSpecificOptions) => {\n const combinedOptions: ILogCommandSpecificOptions & IGlobalProgramOptions = {\n ...commandOptions,\n tool,\n ...program.opts(),\n };\n const services = await servicesFactory();\n await logActionLogic(logger, combinedOptions, services);\n });\n}\n",
|
|
266
|
+
"import { NodeFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { exitCli, ExitCode, getCliBinPath } from '@dotfiles/utils';\nimport { cp } from 'node:fs/promises';\nimport path from 'node:path';\nimport { messages } from './log-messages';\nimport type { ICommandCompletionMeta, IGlobalProgram, IGlobalProgramOptions, IServices } from './types';\n\nexport const SKILL_COMMAND_COMPLETION: ICommandCompletionMeta = {\n name: 'skill',\n description: 'Copy the dotfiles skill folder to the target directory',\n hasPositionalArg: true,\n positionalArgDescription: 'target directory for skill folder',\n};\n\nfunction getSkillPath(): string {\n const cliBinPath = getCliBinPath();\n const scriptPath = cliBinPath.split(' ').pop() ?? cliBinPath;\n const scriptDir = path.dirname(scriptPath);\n return path.join(scriptDir, 'skill');\n}\n\nasync function copySkill(parentLogger: TsLogger, targetPath: string, dryRun: boolean): Promise<ExitCode> {\n const logger = parentLogger.getSubLogger({ name: 'copySkill' });\n\n const nodeFs = new NodeFileSystem();\n\n const skillSourcePath = getSkillPath();\n const destinationPath = path.join(targetPath, 'dotfiles');\n\n const skillExists = await nodeFs.exists(skillSourcePath);\n if (!skillExists) {\n logger.error(messages.fsItemNotFound('Skill directory', skillSourcePath));\n return ExitCode.ERROR;\n }\n\n const targetExists = await nodeFs.exists(targetPath);\n if (!targetExists) {\n logger.error(messages.fsItemNotFound('Target directory', targetPath));\n return ExitCode.ERROR;\n }\n\n const destinationExists = await nodeFs.exists(destinationPath);\n if (destinationExists) {\n logger.info(messages.skillAlreadyExists(destinationPath));\n await nodeFs.rmdir(destinationPath, { recursive: true });\n }\n\n if (dryRun) {\n logger.info(messages.skillDryRun(destinationPath, skillSourcePath));\n return ExitCode.SUCCESS;\n }\n\n try {\n await cp(skillSourcePath, destinationPath, { recursive: true });\n logger.info(messages.skillCopied(destinationPath, skillSourcePath));\n return ExitCode.SUCCESS;\n } catch (error) {\n logger.error(messages.skillCopyFailed(destinationPath), error);\n return ExitCode.ERROR;\n }\n}\n\nasync function skillActionLogic(\n parentLogger: TsLogger,\n options: { targetPath: string; } & IGlobalProgramOptions,\n): Promise<void> {\n const logger = parentLogger.getSubLogger({ name: 'skillActionLogic' });\n const { targetPath, dryRun } = options;\n\n logger.debug(messages.commandActionStarted('skill'));\n\n const exitCode = await copySkill(logger, targetPath, dryRun);\n exitCli(exitCode);\n}\n\nexport function registerSkillCommand(\n parentLogger: TsLogger,\n program: IGlobalProgram,\n _servicesFactory: () => Promise<IServices>,\n): void {\n const logger = parentLogger.getSubLogger({ name: 'registerSkillCommand' });\n\n program\n .command('skill <path>')\n .description('Copy the dotfiles skill folder to the target directory')\n .action(async (targetPath: string) => {\n const combinedOptions: { targetPath: string; } & IGlobalProgramOptions = {\n targetPath: path.resolve(targetPath),\n ...program.opts(),\n };\n await skillActionLogic(logger, combinedOptions);\n });\n}\n",
|
|
267
|
+
"import type { IConfigService } from '@dotfiles/config';\nimport type { ISystemInfo, ProjectConfig, ToolConfig } from '@dotfiles/core';\nimport type { IResolvedFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { exitCli, ExitCode } from '@dotfiles/utils';\nimport { messages } from './log-messages';\nimport type {\n ICommandCompletionMeta,\n IGlobalProgram,\n IGlobalProgramOptions,\n IServices,\n IUpdateCommandSpecificOptions,\n} from './types';\n\ninterface ILoadToolConfigSafelyResult {\n toolConfig: ToolConfig | null;\n exitCode: ExitCode;\n}\n\n/**\n * Completion metadata for the update command.\n */\nexport const UPDATE_COMMAND_COMPLETION: ICommandCompletionMeta = {\n name: 'update',\n description: 'Update an installed tool to latest version',\n hasPositionalArg: true,\n positionalArgDescription: 'tool name to update',\n positionalArgType: 'tool',\n options: [{ flag: '--shim-mode', description: 'Optimized output for shim usage' }],\n};\n\nasync function loadToolConfigSafely(\n logger: TsLogger,\n configService: IConfigService,\n toolName: string,\n toolConfigsDir: string,\n fs: IResolvedFileSystem,\n projectConfig: ProjectConfig,\n systemInfo: ISystemInfo,\n): Promise<ILoadToolConfigSafelyResult> {\n try {\n const toolConfig = await configService.loadSingleToolConfig(\n logger,\n toolName,\n toolConfigsDir,\n fs,\n projectConfig,\n systemInfo,\n );\n\n if (!toolConfig) {\n logger.error(messages.toolNotFound(toolName, toolConfigsDir));\n const result: ILoadToolConfigSafelyResult = { toolConfig: null, exitCode: ExitCode.ERROR };\n return result;\n }\n\n const result: ILoadToolConfigSafelyResult = { toolConfig, exitCode: ExitCode.SUCCESS };\n return result;\n } catch (error) {\n logger.error(messages.configLoadFailed(`tool \"${toolName}\"`), error);\n const result: ILoadToolConfigSafelyResult = { toolConfig: null, exitCode: ExitCode.ERROR };\n return result;\n }\n}\n\nasync function handleToolUpdate(\n logger: TsLogger,\n services: IServices,\n toolName: string,\n toolConfig: ToolConfig,\n shimMode: boolean,\n): Promise<void> {\n const { toolInstallationRegistry, installer, pluginRegistry } = services;\n\n if (toolConfig.version !== 'latest') {\n logger.info(messages.toolVersionPinned(toolName, toolConfig.version));\n return;\n }\n\n const plugin = pluginRegistry.get(toolConfig.installationMethod);\n\n if (plugin && !plugin.supportsUpdate()) {\n logger.warn(messages.toolUpdateNotSupported(toolName, toolConfig.installationMethod));\n }\n\n const existingInstallation = await toolInstallationRegistry.getToolInstallation(toolName);\n const oldVersion = existingInstallation?.version || 'unknown';\n\n const installResult = await installer.install(toolName, toolConfig, { force: true, shimMode });\n\n if (!installResult.success) {\n logger.error(messages.toolUpdateFailed(toolName, installResult.error));\n exitCli(ExitCode.ERROR);\n return;\n }\n\n const resolvedNewVersion: string = 'version' in installResult && typeof installResult.version === 'string'\n ? installResult.version\n : 'unknown';\n const isUpToDate = oldVersion === resolvedNewVersion;\n\n if (shimMode) {\n if (isUpToDate) {\n logger.info(messages.toolShimUpToDate(toolName, resolvedNewVersion));\n } else {\n logger.info(messages.toolShimUpdateStarting(toolName, oldVersion, resolvedNewVersion));\n logger.info(messages.toolShimUpdateSuccess(toolName, resolvedNewVersion));\n }\n } else {\n logger.info(messages.toolUpdated(toolName, oldVersion, resolvedNewVersion));\n }\n}\n\nexport function registerUpdateCommand(\n parentLogger: TsLogger,\n program: IGlobalProgram,\n servicesFactory: () => Promise<IServices>,\n): void {\n const logger = parentLogger.getSubLogger({ name: 'registerUpdateCommand' });\n program\n .command('update <toolName>')\n .description('Updates a specified tool to its latest version.')\n .option('--shim-mode', 'Run in shim mode with minimal output', false)\n .action(async (toolName: string, commandOptions: IUpdateCommandSpecificOptions) => {\n const combinedOptions: IUpdateCommandSpecificOptions & IGlobalProgramOptions = {\n ...commandOptions,\n ...program.opts(),\n };\n\n const services = await servicesFactory();\n const { projectConfig, fs, configService, systemInfo } = services;\n\n try {\n const toolConfigResult = await loadToolConfigSafely(\n logger,\n configService,\n toolName,\n projectConfig.paths.toolConfigsDir,\n fs,\n projectConfig,\n systemInfo,\n );\n\n if (toolConfigResult.exitCode !== ExitCode.SUCCESS) {\n exitCli(toolConfigResult.exitCode);\n return;\n }\n\n if (!toolConfigResult.toolConfig) {\n logger.error(messages.toolNotFound(toolName, projectConfig.paths.toolConfigsDir));\n exitCli(ExitCode.ERROR);\n return;\n }\n\n const toolConfig = toolConfigResult.toolConfig;\n\n if (!combinedOptions.shimMode) {\n logger.info(messages.commandCheckingUpdatesFor(toolName));\n }\n\n await handleToolUpdate(logger, services, toolName, toolConfig, combinedOptions.shimMode);\n } catch (error) {\n logger.error(messages.commandExecutionFailed('update', ExitCode.ERROR), error);\n exitCli(ExitCode.ERROR);\n }\n });\n}\n",
|
|
268
|
+
"import { dedentTemplate } from '@dotfiles/utils';\nimport { BIN_COMMAND_COMPLETION } from './binCommand';\nimport { CHECK_UPDATES_COMMAND_COMPLETION } from './checkUpdatesCommand';\nimport { CLEANUP_COMMAND_COMPLETION } from './cleanupCommand';\nimport { GLOBAL_OPTIONS_COMPLETION } from './createProgram';\nimport { DETECT_CONFLICTS_COMMAND_COMPLETION } from './detectConflictsCommand';\nimport { FEATURES_COMMAND_COMPLETION } from './featuresCommand';\nimport { FILES_COMMAND_COMPLETION } from './filesCommand';\nimport { GENERATE_COMMAND_COMPLETION } from './generateCommandCompletion';\nimport { INSTALL_COMMAND_COMPLETION } from './installCommand';\nimport { LOG_COMMAND_COMPLETION } from './logCommand';\nimport { SKILL_COMMAND_COMPLETION } from './skillCommand';\nimport type { CompletionPositionalArgType, ICommandCompletionMeta, ICompletionOption } from './types';\nimport { UPDATE_COMMAND_COMPLETION } from './updateCommand';\n\n/**\n * All command completion metadata collected from individual command files.\n */\nexport const ALL_COMMAND_COMPLETIONS: ICommandCompletionMeta[] = [\n BIN_COMMAND_COMPLETION,\n INSTALL_COMMAND_COMPLETION,\n GENERATE_COMMAND_COMPLETION,\n CLEANUP_COMMAND_COMPLETION,\n CHECK_UPDATES_COMMAND_COMPLETION,\n UPDATE_COMMAND_COMPLETION,\n DETECT_CONFLICTS_COMMAND_COMPLETION,\n LOG_COMMAND_COMPLETION,\n FILES_COMMAND_COMPLETION,\n SKILL_COMMAND_COMPLETION,\n FEATURES_COMMAND_COMPLETION,\n];\n\n/**\n * Formats a single option for zsh completion.\n */\nfunction formatZshOption(option: ICompletionOption): string {\n const flag = option.flag;\n const description = option.description.replace(/'/g, \"\\\\'\");\n\n if (option.hasArg) {\n // Option with argument\n return `'${flag}=[${description}]:${option.argPlaceholder || 'arg'}:'`;\n }\n // Boolean flag\n return `'${flag}[${description}]'`;\n}\n\n/**\n * Formats command arguments specification for _arguments.\n */\nfunction formatCommandArgs(globalOptions: ICompletionOption[]): string {\n const lines: string[] = [];\n\n // Add global options\n for (const option of globalOptions) {\n lines.push(formatZshOption(option));\n }\n\n // Add command selection\n lines.push(\"'1:command:->command'\");\n lines.push(\"'*::arg:->args'\");\n\n return lines.join(' \\\\\\n');\n}\n\nfunction escapeSingleQuotes(value: string): string {\n return value.replace(/'/g, \"\\\\'\");\n}\n\nfunction formatToolNameList(toolNames: string[]): string {\n return toolNames.map((name) => escapeSingleQuotes(name)).join(' ');\n}\n\nfunction formatPositionalArgLine(\n description: string,\n positionalArgType: CompletionPositionalArgType | undefined,\n toolNames: string[],\n): string {\n if (positionalArgType === 'tool' && toolNames.length > 0) {\n const toolList = formatToolNameList(toolNames);\n return `'1:${description}:(${toolList})'`;\n }\n return `'1:${description}:'`;\n}\n\n/**\n * Generates the commands case for the completion function.\n */\nfunction generateCommandsCase(): string {\n return `_describe 'command' commands`;\n}\n\n/**\n * Removes trailing backslash from the last line if present.\n */\nfunction removeTrailingBackslash(lines: string[]): void {\n if (lines.length === 0) return;\n const lastIndex = lines.length - 1;\n const lastLine = lines[lastIndex];\n if (lastLine?.endsWith(' \\\\')) {\n lines[lastIndex] = lastLine.slice(0, -2);\n }\n}\n\n/**\n * Collects all options for a command (command-specific + global).\n */\nfunction collectCommandOptions(cmd: ICommandCompletionMeta, globalOptions: ICompletionOption[]): string[] {\n const allOptions: string[] = [];\n\n if (cmd.options) {\n for (const option of cmd.options) {\n allOptions.push(formatZshOption(option));\n }\n }\n\n for (const option of globalOptions) {\n allOptions.push(formatZshOption(option));\n }\n\n return allOptions;\n}\n\n/**\n * Generates subcommand completion lines.\n */\nfunction generateSubcommandLines(subcommands: ICommandCompletionMeta[]): string[] {\n const lines: string[] = [];\n lines.push(\"'1:subcommand:->subcommand'\");\n const subcommandDescriptions = subcommands\n .map((sub) => `'${sub.name}:${sub.description.replace(/'/g, \"\\\\'\")}'`)\n .join(' ');\n lines.push('case $state in');\n lines.push(' subcommand)');\n lines.push(` local -a subcommands=(${subcommandDescriptions})`);\n lines.push(\" _describe 'subcommand' subcommands\");\n lines.push(' ;;');\n lines.push('esac');\n return lines;\n}\n\n/**\n * Generates case handler for a single command's arguments.\n */\nfunction generateSingleCommandCase(\n cmd: ICommandCompletionMeta,\n globalOptions: ICompletionOption[],\n toolNames: string[],\n): string {\n const caseLines: string[] = [];\n caseLines.push(`${cmd.name})`);\n\n const allOptions = collectCommandOptions(cmd, globalOptions);\n const hasContent = allOptions.length > 0 || cmd.hasPositionalArg || cmd.subcommands;\n\n if (hasContent) {\n caseLines.push(' _arguments \\\\');\n\n for (const opt of allOptions) {\n caseLines.push(` ${opt} \\\\`);\n }\n\n if (cmd.subcommands && cmd.subcommands.length > 0) {\n const subLines = generateSubcommandLines(cmd.subcommands);\n for (const line of subLines) {\n caseLines.push(` ${line}`);\n }\n } else if (cmd.hasPositionalArg) {\n const argDesc = cmd.positionalArgDescription || 'argument';\n const positionalLine = formatPositionalArgLine(argDesc, cmd.positionalArgType, toolNames);\n caseLines.push(` ${positionalLine}`);\n } else {\n removeTrailingBackslash(caseLines);\n }\n }\n\n caseLines.push(' ;;');\n return caseLines.join('\\n');\n}\n\n/**\n * Generates case handlers for each command's arguments.\n */\nfunction generateArgsCases(\n commands: ICommandCompletionMeta[],\n globalOptions: ICompletionOption[],\n toolNames: string[],\n): string {\n const cases: string[] = commands.map((cmd) => generateSingleCommandCase(cmd, globalOptions, toolNames));\n return cases.join('\\n');\n}\n\n/**\n * Generates a native zsh completion script for the dotfiles CLI.\n *\n * @param binaryName - The name of the CLI binary (e.g., 'dotfiles')\n * @returns The complete zsh completion script content\n */\nexport function generateZshCompletion(binaryName: string, toolNames: string[]): string {\n const commands = ALL_COMMAND_COMPLETIONS;\n const globalOptions = GLOBAL_OPTIONS_COMPLETION.options || [];\n const sortedToolNames = [...toolNames].toSorted((a, b) => a.localeCompare(b));\n\n // Build the list of command descriptions for the initial command completion\n const commandDescriptions = commands.map((cmd) => `'${cmd.name}:${cmd.description.replace(/'/g, \"\\\\'\")}'`).join('\\n');\n\n const script = dedentTemplate(\n `\n #compdef {binaryName}\n # Generated by Dotfiles Management Tool\n # Do not edit this file manually - it will be overwritten\n\n _{binaryName}() {\n local curcontext=\"$curcontext\" state line\n typeset -A opt_args\n\n local -a commands=(\n {commandDescriptions}\n )\n\n _arguments -C \\\\\n {globalArgs}\n\n case $state in\n command)\n {commandsCase}\n ;;\n args)\n case $line[1] in\n {argsCases}\n esac\n ;;\n esac\n }\n\n _{binaryName} \"$@\"\n `,\n {\n binaryName,\n commandDescriptions,\n globalArgs: formatCommandArgs(globalOptions),\n commandsCase: generateCommandsCase(),\n argsCases: generateArgsCases(commands, globalOptions, sortedToolNames),\n },\n );\n\n return script;\n}\n",
|
|
269
|
+
"import type { TsLogger } from '@dotfiles/logger';\nimport { Database } from 'bun:sqlite';\nimport { mkdirSync } from 'node:fs';\nimport path from 'node:path';\nimport { messages } from './log-messages';\n\n/**\n * Manages the shared SQLite database connection for the dotfiles project.\n *\n * This class is responsible for initializing and providing access to a centralized\n * SQLite database. It ensures that different parts of the application, such as the\n * file registry and tool installation registry, can share a single database connection.\n *\n * @param parentLogger - The parent logger instance for creating a sub-logger.\n * @param registryDbPath - The file system path to the SQLite database file.\n */\nexport class RegistryDatabase {\n private db: Database;\n private logger: TsLogger;\n\n constructor(parentLogger: TsLogger, registryDbPath: string) {\n this.logger = parentLogger.getSubLogger({ name: 'RegistryDatabase' });\n const dbDir = path.dirname(registryDbPath);\n mkdirSync(dbDir, { recursive: true });\n this.db = new Database(registryDbPath);\n this.configureConnectionPragmas();\n this.logger.debug(messages.initialized(), 'shared connection');\n }\n\n private configureConnectionPragmas(): void {\n // Improve multi-process write behavior for shim usage tracking + foreground commands.\n // busy_timeout prevents immediate SQLITE_BUSY failures when another process briefly holds a lock.\n // WAL allows concurrent readers with a single writer and reduces lock contention.\n // synchronous=NORMAL is a practical durability/performance balance for this metadata DB.\n try {\n this.db.run('PRAGMA busy_timeout = 5000;');\n this.db.run('PRAGMA journal_mode = WAL;');\n this.db.run('PRAGMA synchronous = NORMAL;');\n } catch (error) {\n this.logger.warn(messages.sqlitePragmaConfigFailed(), error);\n }\n }\n\n /**\n * Retrieves the active SQLite database connection.\n *\n * @returns The `Database` instance from `bun:sqlite`.\n */\n getConnection(): Database {\n return this.db;\n }\n\n /**\n * Closes the database connection.\n *\n * This method should be called during application shutdown to ensure a graceful\n * termination of the database connection.\n */\n close(): void {\n this.db.close();\n }\n}\n",
|
|
270
|
+
"import { createSafeLogMessage, type SafeLogMessageMap } from '@dotfiles/logger';\n\nexport const messages = {\n initialized: () => createSafeLogMessage('Initialized SQLite file registry at: %s'),\n sqlitePragmaConfigFailed: () => createSafeLogMessage('Failed to configure SQLite pragmas; continuing with defaults'),\n} satisfies SafeLogMessageMap;\n",
|
|
271
|
+
"import { createSafeLogMessage, type SafeLogMessageMap } from '@dotfiles/logger';\n\nexport const messages = {\n databaseInitialized: () => createSafeLogMessage('Database initialized'),\n toolInstallationRecorded: () => createSafeLogMessage('Tool installation recorded: %s version %s'),\n toolInstallationNotFound: () => createSafeLogMessage('Tool installation not found: %s'),\n toolInstallationsRetrieved: () => createSafeLogMessage('Retrieved %d tool installations'),\n noUpdatesProvided: () => createSafeLogMessage('No updates provided for tool: %s'),\n toolInstallationUpdated: () => createSafeLogMessage('Tool installation updated: %s'),\n toolInstallationRemoved: () => createSafeLogMessage('Tool installation removed: %s'),\n toolInstallationCheckCompleted: () => createSafeLogMessage('Tool installation check: %s version %s - installed: %s'),\n databaseClosed: () => createSafeLogMessage('Database closed'),\n} satisfies SafeLogMessageMap;\n",
|
|
272
|
+
"import type { TsLogger } from '@dotfiles/logger';\nimport type { Database } from 'bun:sqlite';\nimport type { IToolInstallationRegistry } from './IToolInstallationRegistry';\nimport { messages } from './log-messages';\nimport type { IToolInstallationDetails, IToolInstallationRecord, IToolUsageRecord } from './types';\n\ninterface IToolInstallationRow {\n id: number;\n tool_name: string;\n version: string;\n install_path: string;\n timestamp: string;\n installed_at: number;\n binary_paths: string;\n download_url: string | null;\n asset_name: string | null;\n configured_version: string | null;\n original_tag: string | null;\n install_method: string | null;\n}\n\ninterface IToolUsageRow {\n tool_name: string;\n binary_name: string;\n usage_count: number;\n last_used_at: number;\n}\n\n/**\n * SQLite-based implementation of the tool installation registry.\n *\n * This class manages a persistent database of installed tools, tracking metadata such as\n * versions, installation paths, binary locations, and download sources. Each tool can have\n * only one installation record at a time, enforced by a unique constraint on the tool name.\n *\n * The registry serves several critical purposes:\n * - **Version tracking**: Records the actual installed version for update detection\n * - **Path management**: Stores installation paths and binary locations for cleanup\n * - **Source tracking**: Maintains download URLs and asset names for reproducibility\n * - **Installation history**: Records timestamps for tracking when tools were installed\n *\n * When a tool is reinstalled or upgraded, the previous record is automatically replaced\n * using SQLite's INSERT OR REPLACE functionality, ensuring the registry always reflects\n * the current state of installed tools.\n */\nexport class ToolInstallationRegistry implements IToolInstallationRegistry {\n private db: Database;\n private logger: TsLogger;\n\n constructor(parentLogger: TsLogger, db: Database) {\n this.logger = parentLogger.getSubLogger({ name: 'ToolInstallationRegistry' });\n this.db = db;\n this.initializeDatabase();\n }\n\n private initializeDatabase(): void {\n const logger = this.logger.getSubLogger({ name: 'initializeDatabase' });\n this.db.run(`\n CREATE TABLE IF NOT EXISTS tool_installations (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n tool_name TEXT NOT NULL UNIQUE,\n version TEXT NOT NULL,\n install_path TEXT NOT NULL,\n timestamp TEXT NOT NULL,\n installed_at INTEGER NOT NULL,\n binary_paths TEXT NOT NULL,\n download_url TEXT,\n asset_name TEXT,\n configured_version TEXT,\n original_tag TEXT,\n install_method TEXT,\n UNIQUE(tool_name)\n );\n `);\n\n // Migration: Add install_method column if it doesn't exist (for existing databases)\n this.migrateAddInstallMethod();\n\n this.db.run(`\n CREATE TABLE IF NOT EXISTS tool_usage (\n tool_name TEXT NOT NULL,\n binary_name TEXT NOT NULL,\n usage_count INTEGER NOT NULL DEFAULT 0,\n last_used_at INTEGER NOT NULL,\n PRIMARY KEY (tool_name, binary_name)\n );\n `);\n\n this.db.run(`\n CREATE INDEX IF NOT EXISTS idx_tool_usage_tool_name ON tool_usage(tool_name);\n `);\n\n logger.debug(messages.databaseInitialized());\n }\n\n private migrateAddInstallMethod(): void {\n try {\n // Check if column exists by querying table info\n const columns = this.db.prepare('PRAGMA table_info(tool_installations)').all() as Array<{ name: string; }>;\n const hasInstallMethod = columns.some((col) => col.name === 'install_method');\n if (!hasInstallMethod) {\n this.db.run('ALTER TABLE tool_installations ADD COLUMN install_method TEXT');\n }\n } catch {\n // Column might already exist, ignore errors\n }\n }\n\n async recordToolInstallation(installation: IToolInstallationDetails): Promise<void> {\n const logger = this.logger.getSubLogger({ name: 'recordToolInstallation' });\n const stmt = this.db.prepare(`\n INSERT OR REPLACE INTO tool_installations \n (tool_name, version, install_path, timestamp, installed_at, binary_paths, download_url, asset_name, configured_version, original_tag, install_method)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `);\n\n stmt.run(\n installation.toolName,\n installation.version,\n installation.installPath,\n installation.timestamp,\n Date.now(),\n JSON.stringify(installation.binaryPaths),\n installation.downloadUrl || null,\n installation.assetName || null,\n installation.configuredVersion || null,\n installation.originalTag || null,\n installation.installMethod || null,\n );\n logger.debug(messages.toolInstallationRecorded(), installation.toolName, installation.version);\n }\n\n async getToolInstallation(toolName: string): Promise<IToolInstallationRecord | null> {\n const logger = this.logger.getSubLogger({ name: 'getToolInstallation' });\n const stmt = this.db.prepare(`\n SELECT * FROM tool_installations WHERE tool_name = ?\n `);\n\n const row = stmt.get(toolName) as IToolInstallationRow | undefined;\n if (!row) {\n logger.debug(messages.toolInstallationNotFound(), toolName);\n return null;\n }\n\n return {\n id: row.id,\n toolName: row.tool_name,\n version: row.version,\n installPath: row.install_path,\n timestamp: row.timestamp,\n installedAt: new Date(row.installed_at),\n binaryPaths: JSON.parse(row.binary_paths),\n downloadUrl: row.download_url || undefined,\n assetName: row.asset_name || undefined,\n configuredVersion: row.configured_version || undefined,\n originalTag: row.original_tag || undefined,\n installMethod: row.install_method || undefined,\n };\n }\n\n async getAllToolInstallations(): Promise<IToolInstallationRecord[]> {\n const logger = this.logger.getSubLogger({ name: 'getAllToolInstallations' });\n const stmt = this.db.prepare(`\n SELECT * FROM tool_installations ORDER BY tool_name\n `);\n\n const rows = stmt.all() as IToolInstallationRow[];\n logger.debug(messages.toolInstallationsRetrieved(), rows.length);\n return rows.map((row) => ({\n id: row.id,\n toolName: row.tool_name,\n version: row.version,\n installPath: row.install_path,\n timestamp: row.timestamp,\n installedAt: new Date(row.installed_at),\n binaryPaths: JSON.parse(row.binary_paths),\n downloadUrl: row.download_url || undefined,\n assetName: row.asset_name || undefined,\n configuredVersion: row.configured_version || undefined,\n originalTag: row.original_tag || undefined,\n installMethod: row.install_method || undefined,\n }));\n }\n\n async updateToolInstallation(toolName: string, updates: Partial<IToolInstallationRecord>): Promise<void> {\n const logger = this.logger.getSubLogger({ name: 'updateToolInstallation' });\n const fields: string[] = [];\n const values: (string | number | null)[] = [];\n\n if (updates.version !== undefined) {\n fields.push('version = ?');\n values.push(updates.version);\n }\n if (updates.installPath !== undefined) {\n fields.push('install_path = ?');\n values.push(updates.installPath);\n }\n if (updates.timestamp !== undefined) {\n fields.push('timestamp = ?');\n values.push(updates.timestamp);\n }\n if (updates.binaryPaths !== undefined) {\n fields.push('binary_paths = ?');\n values.push(JSON.stringify(updates.binaryPaths));\n }\n if (updates.downloadUrl !== undefined) {\n fields.push('download_url = ?');\n values.push(updates.downloadUrl);\n }\n if (updates.assetName !== undefined) {\n fields.push('asset_name = ?');\n values.push(updates.assetName);\n }\n if (updates.configuredVersion !== undefined) {\n fields.push('configured_version = ?');\n values.push(updates.configuredVersion);\n }\n if (updates.originalTag !== undefined) {\n fields.push('original_tag = ?');\n values.push(updates.originalTag);\n }\n\n if (fields.length === 0) {\n logger.debug(messages.noUpdatesProvided(), toolName);\n return;\n }\n\n values.push(toolName);\n const stmt = this.db.prepare(`\n UPDATE tool_installations SET ${fields.join(', ')} WHERE tool_name = ?\n `);\n\n stmt.run(...values);\n logger.debug(messages.toolInstallationUpdated(), toolName);\n }\n\n async removeToolInstallation(toolName: string): Promise<void> {\n const logger = this.logger.getSubLogger({ name: 'removeToolInstallation' });\n const stmt = this.db.prepare(`\n DELETE FROM tool_installations WHERE tool_name = ?\n `);\n\n stmt.run(toolName);\n logger.debug(messages.toolInstallationRemoved(), toolName);\n }\n\n async isToolInstalled(toolName: string, version?: string): Promise<boolean> {\n const logger = this.logger.getSubLogger({ name: 'isToolInstalled' });\n if (version) {\n const stmt = this.db.prepare(`\n SELECT 1 FROM tool_installations WHERE tool_name = ? AND version = ?\n `);\n const result = stmt.get(toolName, version);\n const isInstalled: boolean = result !== null;\n logger.debug(messages.toolInstallationCheckCompleted(), toolName, version, isInstalled);\n return isInstalled;\n } else {\n const stmt = this.db.prepare(`\n SELECT 1 FROM tool_installations WHERE tool_name = ?\n `);\n const result = stmt.get(toolName);\n const isInstalled: boolean = result !== null;\n logger.debug(messages.toolInstallationCheckCompleted(), toolName, 'any', isInstalled);\n return isInstalled;\n }\n }\n\n async recordToolUsage(toolName: string, binaryName: string): Promise<void> {\n const now = Date.now();\n\n const stmt = this.db.prepare(`\n INSERT INTO tool_usage (tool_name, binary_name, usage_count, last_used_at)\n VALUES (?, ?, 1, ?)\n ON CONFLICT(tool_name, binary_name)\n DO UPDATE SET\n usage_count = usage_count + 1,\n last_used_at = excluded.last_used_at\n `);\n\n stmt.run(toolName, binaryName, now);\n }\n\n async getToolUsage(toolName: string, binaryName: string): Promise<IToolUsageRecord | null> {\n const stmt = this.db.prepare(`\n SELECT tool_name, binary_name, usage_count, last_used_at\n FROM tool_usage\n WHERE tool_name = ? AND binary_name = ?\n `);\n\n const row = stmt.get(toolName, binaryName) as IToolUsageRow | undefined;\n if (!row) {\n return null;\n }\n\n return {\n toolName: row.tool_name,\n binaryName: row.binary_name,\n usageCount: row.usage_count,\n lastUsedAt: new Date(row.last_used_at),\n };\n }\n\n async close(): Promise<void> {\n const logger = this.logger.getSubLogger({ name: 'close' });\n this.db.close();\n logger.debug(messages.databaseClosed());\n }\n}\n",
|
|
273
|
+
"import { loadConfig, type ProjectConfig } from '@dotfiles/config';\nimport { architectureFromNodeJS, type ISystemInfo, platformFromNodeJS } from '@dotfiles/core';\nimport type { IFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { RegistryDatabase } from '@dotfiles/registry-database';\nimport { FileRegistry } from '@dotfiles/registry/file';\nimport { ToolInstallationRegistry } from '@dotfiles/registry/tool';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { messages } from '../log-messages';\nimport { resolveConfigPath } from '../resolveConfigPath';\n\nexport interface IBaseRuntimeOptions {\n config: string;\n cwd: string;\n env: NodeJS.ProcessEnv;\n platform?: string;\n arch?: string;\n fileSystem: IFileSystem;\n configFileSystem?: IFileSystem;\n warnOnPlatformArchOverride?: boolean;\n}\n\nexport interface IBaseRuntimeContext {\n projectConfig: ProjectConfig;\n systemInfo: ISystemInfo;\n registryPath: string;\n registryDatabase: RegistryDatabase;\n fileRegistry: FileRegistry;\n toolInstallationRegistry: ToolInstallationRegistry;\n}\n\nfunction createSystemInfo(parentLogger: TsLogger, options: IBaseRuntimeOptions): ISystemInfo {\n const platformString: NodeJS.Platform = (options.platform as NodeJS.Platform) || process.platform;\n const archString: NodeJS.Architecture = (options.arch as NodeJS.Architecture) || process.arch;\n\n if (options.warnOnPlatformArchOverride) {\n if (options.platform) {\n parentLogger.warn(messages.configParameterOverridden('platform', options.platform));\n }\n if (options.arch) {\n parentLogger.warn(messages.configParameterOverridden('arch', options.arch));\n }\n }\n\n return {\n platform: platformFromNodeJS(platformString),\n arch: architectureFromNodeJS(archString),\n homeDir: os.homedir(),\n hostname: os.hostname(),\n };\n}\n\nexport async function createBaseRuntimeContext(\n parentLogger: TsLogger,\n options: IBaseRuntimeOptions,\n): Promise<IBaseRuntimeContext | null> {\n const logger = parentLogger.getSubLogger({ name: 'createBaseRuntimeContext' });\n const systemInfo = createSystemInfo(parentLogger, options);\n\n const configPath = await resolveConfigPath(logger, options.config, {\n cwd: options.cwd,\n homeDir: systemInfo.homeDir,\n });\n\n if (!configPath) {\n return null;\n }\n\n const configFs = options.configFileSystem ?? options.fileSystem;\n const projectConfig = await loadConfig(logger, configFs, configPath, systemInfo, options.env);\n\n const finalSystemInfo: ISystemInfo = {\n ...systemInfo,\n homeDir: projectConfig.paths.homeDir,\n };\n\n const registryPath = path.join(projectConfig.paths.generatedDir, 'registry.db');\n const registryDatabase = new RegistryDatabase(parentLogger, registryPath);\n const db = registryDatabase.getConnection();\n const registryLogger = parentLogger.getSubLogger({ context: 'system' });\n const fileRegistry = new FileRegistry(registryLogger, db);\n const toolInstallationRegistry = new ToolInstallationRegistry(registryLogger, db);\n\n return {\n projectConfig,\n systemInfo: finalSystemInfo,\n registryPath,\n registryDatabase,\n fileRegistry,\n toolInstallationRegistry,\n };\n}\n",
|
|
274
|
+
"import { NodeFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { CONFIG_FILE_NAME, ENV_DIR_VAR } from '@dotfiles/virtual-env';\nimport path from 'node:path';\nimport { messages } from './log-messages';\n\n/**\n * Default configuration file names to search for when no explicit config is provided.\n * Files are searched in order of priority - the first existing file is used.\n */\nexport const DEFAULT_CONFIG_FILES: string[] = ['dotfiles.config.ts'];\n\nconst BOUNDARY_MARKERS: string[] = ['project.json', '.git'];\n\nexport interface ProcessInfo {\n cwd: string;\n homeDir: string;\n}\n\n/**\n * Resolves the configuration file path.\n *\n * If an explicit config path is provided via CLI, it is resolved relative to cwd.\n * If no config is provided, walks up the directory tree searching for default config files.\n * Stops at project boundaries (project.json, .git) or $HOME.\n *\n * @param parentLogger - Parent logger instance for logging.\n * @param configOption - The --config CLI option value (empty string if not provided).\n * @param processInfo - Process-derived values: cwd and homeDir.\n * @returns Absolute path to the configuration file, or undefined if not found.\n */\nexport async function resolveConfigPath(\n parentLogger: TsLogger,\n configOption: string,\n processInfo: ProcessInfo,\n): Promise<string | undefined> {\n const logger = parentLogger.getSubLogger({ name: 'resolveConfigPath' });\n\n const { cwd, homeDir } = processInfo;\n const nodeFs = new NodeFileSystem();\n\n // If explicit config path provided, resolve it relative to cwd\n if (configOption.length > 0) {\n const expandedConfigOption: string = configOption.startsWith('~')\n ? configOption.replace(/^~(?=$|\\/|\\\\)/, homeDir)\n : configOption;\n\n const resolvedPath = path.resolve(cwd, expandedConfigOption);\n logger.debug(messages.configPathResolved(resolvedPath));\n return resolvedPath;\n }\n\n // Check if DOTFILES_ENV_DIR is set (virtual env is active)\n const envDir = process.env[ENV_DIR_VAR];\n if (envDir) {\n const envConfigPath = path.join(envDir, CONFIG_FILE_NAME);\n if (await nodeFs.exists(envConfigPath)) {\n logger.debug(messages.envConfigFromEnvVar(envConfigPath));\n return envConfigPath;\n }\n }\n\n // Search for default config files, walking up the directory tree\n let currentDir: string = cwd;\n\n while (true) {\n for (const fileName of DEFAULT_CONFIG_FILES) {\n const configPath = path.join(currentDir, fileName);\n if (await nodeFs.exists(configPath)) {\n logger.debug(messages.configPathResolved(configPath));\n return configPath;\n }\n }\n\n // Stop at $HOME (after checking it for config)\n if (currentDir === homeDir) {\n break;\n }\n\n // Stop at boundary markers: project.json or .git\n for (const marker of BOUNDARY_MARKERS) {\n if (await nodeFs.exists(path.join(currentDir, marker))) {\n return undefined;\n }\n }\n\n const parentDir = path.dirname(currentDir);\n if (parentDir === currentDir) {\n break;\n }\n currentDir = parentDir;\n }\n\n return undefined;\n}\n",
|
|
275
|
+
"import type { ProjectConfig } from '@dotfiles/config';\nimport type { ISystemInfo } from '@dotfiles/core';\nimport { NodeFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport type { ToolInstallationRegistry } from '@dotfiles/registry/tool';\nimport { createBaseRuntimeContext } from '../runtime/createBaseRuntimeContext';\n\nexport interface ILightRuntimeOptions {\n config: string;\n cwd: string;\n env: NodeJS.ProcessEnv;\n platform?: string;\n arch?: string;\n}\n\nexport interface ILightRuntimeContext {\n projectConfig: ProjectConfig;\n systemInfo: ISystemInfo;\n toolInstallationRegistry: ToolInstallationRegistry;\n close: () => void;\n}\n\nexport async function createLightRuntimeContext(\n parentLogger: TsLogger,\n options: ILightRuntimeOptions,\n): Promise<ILightRuntimeContext | null> {\n const fs = new NodeFileSystem();\n const baseContext = await createBaseRuntimeContext(parentLogger, {\n config: options.config,\n cwd: options.cwd,\n env: options.env,\n platform: options.platform,\n arch: options.arch,\n fileSystem: fs,\n });\n\n if (!baseContext) {\n return null;\n }\n\n return {\n projectConfig: baseContext.projectConfig,\n systemInfo: baseContext.systemInfo,\n toolInstallationRegistry: baseContext.toolInstallationRegistry,\n close: () => {\n baseContext.registryDatabase.close();\n },\n };\n}\n",
|
|
276
|
+
"import type { TsLogger } from '@dotfiles/logger';\nimport { createLightRuntimeContext } from './createLightRuntimeContext';\n\ninterface ITrackUsageArgs {\n toolName: string;\n binaryName: string;\n config: string;\n platform?: string;\n arch?: string;\n}\n\nfunction parseTrackUsageArgs(argv: string[]): ITrackUsageArgs | null {\n const commandIndex = argv.indexOf('@track-usage');\n if (commandIndex < 0) {\n return null;\n }\n\n const positionals: string[] = [];\n let config = '';\n let platform: string | undefined;\n let arch: string | undefined;\n\n for (let i = 2; i < argv.length; i += 1) {\n const token = argv[i];\n if (!token) {\n continue;\n }\n\n if (token === '@track-usage') {\n continue;\n }\n\n if (token === '--config' || token === '--platform' || token === '--arch') {\n const value = argv[i + 1];\n if (!value) {\n continue;\n }\n\n if (token === '--config') {\n config = value;\n }\n if (token === '--platform') {\n platform = value;\n }\n if (token === '--arch') {\n arch = value;\n }\n\n i += 1;\n continue;\n }\n\n if (i > commandIndex && !token.startsWith('-')) {\n positionals.push(token);\n }\n }\n\n const toolName = positionals[0];\n const binaryName = positionals[1];\n if (!toolName || !binaryName) {\n return null;\n }\n\n return {\n toolName,\n binaryName,\n config,\n platform,\n arch,\n };\n}\n\nexport async function runTrackUsageCommand(argv: string[]): Promise<void> {\n const args = parseTrackUsageArgs(argv);\n if (!args) {\n return;\n }\n\n const logger: TsLogger = {\n getSubLogger: () => logger,\n trace: () => undefined,\n debug: () => undefined,\n info: () => undefined,\n warn: () => undefined,\n error: () => undefined,\n fatal: () => undefined,\n setPrefix: () => logger,\n } as unknown as TsLogger;\n\n const context = await createLightRuntimeContext(logger, {\n config: args.config,\n cwd: process.cwd(),\n env: process.env,\n platform: args.platform,\n arch: args.arch,\n });\n\n if (!context) {\n return;\n }\n\n try {\n await context.toolInstallationRegistry.recordToolUsage(args.toolName, args.binaryName);\n } finally {\n context.close();\n }\n}\n",
|
|
277
|
+
"import type { IFileSystem } from '@dotfiles/file-system';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { contractHomePath } from '@dotfiles/utils';\nimport path from 'node:path';\nimport { messages } from './log-messages';\n\ninterface PopulateMemFsParams {\n /** Source filesystem to read from (typically NodeFileSystem) */\n sourceFs: IFileSystem;\n /** Target filesystem to write to (typically MemFileSystem) */\n targetFs: IFileSystem;\n /** Directory containing tool configurations */\n toolConfigsDir: string;\n /** Home directory for path contraction in logs */\n homeDir: string;\n}\n\n/**\n * Populates an in-memory filesystem with all files from a tool configs directory.\n *\n * Used in dry-run mode to ensure the in-memory filesystem contains all files\n * that tools may reference, including configuration files, keys, and other\n * supporting assets alongside .tool.ts files.\n */\nexport async function populateMemFsForDryRun(\n parentLogger: TsLogger,\n params: PopulateMemFsParams,\n): Promise<void> {\n const logger = parentLogger.getSubLogger({ name: 'populateMemFsForDryRun' });\n const { sourceFs, targetFs, toolConfigsDir, homeDir } = params;\n\n logger.trace(messages.toolConfigsForDryRun());\n\n if (!(await sourceFs.exists(toolConfigsDir))) {\n logger.warn(messages.fsItemNotFound('Tool configs directory', toolConfigsDir));\n return;\n }\n\n const filePaths = await collectFilePaths(logger, sourceFs, toolConfigsDir);\n logger.trace(messages.toolConfigsLoaded(toolConfigsDir, filePaths.length));\n\n for (const filePath of filePaths) {\n await copyFile(logger, sourceFs, targetFs, filePath, homeDir);\n }\n}\n\n/**\n * Recursively collects all file paths from a directory tree.\n */\nasync function collectFilePaths(logger: TsLogger, fs: IFileSystem, dirPath: string): Promise<string[]> {\n const results: string[] = [];\n\n try {\n const entries = await fs.readdir(dirPath);\n\n for (const entry of entries) {\n const entryPath = path.join(dirPath, entry);\n\n try {\n const stat = await fs.stat(entryPath);\n\n if (stat.isDirectory()) {\n const subResults = await collectFilePaths(logger, fs, entryPath);\n results.push(...subResults);\n } else {\n results.push(entryPath);\n }\n } catch (error) {\n logger.debug(messages.fsReadFailed(entryPath), error);\n }\n }\n } catch (error) {\n logger.debug(messages.fsReadFailed(dirPath), error);\n }\n\n return results;\n}\n\n/**\n * Copies a file from source filesystem to target filesystem.\n */\nasync function copyFile(\n logger: TsLogger,\n sourceFs: IFileSystem,\n targetFs: IFileSystem,\n filePath: string,\n homeDir: string,\n): Promise<void> {\n try {\n const content = await sourceFs.readFile(filePath, 'utf8');\n await targetFs.ensureDir(path.dirname(filePath));\n await targetFs.writeFile(filePath, content);\n logger.trace(messages.fsWrite('memfs', contractHomePath(homeDir, filePath)));\n } catch (error) {\n logger.error(messages.fsReadFailed(filePath), error);\n }\n}\n",
|
|
278
|
+
"/**\n * Install-first API implementation for defineTool.\n *\n * Provides type-safe tool configuration through generic mapped types,\n * where the installer method is selected first.\n */\n\nimport type {\n AsyncConfigureTool,\n IInstallParamsRegistry,\n InstallFunction,\n InstallMethod,\n IToolConfigBuilder as ToolConfigBuilderContract,\n IToolConfigContext,\n ToolConfig,\n} from '@dotfiles/core';\nimport type { TsLogger } from '@dotfiles/logger';\nimport { IToolConfigBuilder } from '@dotfiles/tool-config-builder';\n\ntype ConfigureToolFnResult =\n | ToolConfig\n | ToolConfigBuilderContract\n | Omit<ToolConfigBuilderContract, 'bin'>\n | undefined\n | Promise<ToolConfig | ToolConfigBuilderContract | Omit<ToolConfigBuilderContract, 'bin'> | undefined>;\n\n/**\n * Define a tool configuration with type-safe install method selection.\n *\n * The install function is provided as the first parameter, allowing you to\n * select the installer method and provide type-checked parameters upfront.\n *\n * @param fn - Configuration callback that receives:\n * - `install` - Function to select installer method. Call with method name and params,\n * or call with no args for manual tools. Returns a fluent builder.\n * - `ctx` - Context with tool/config info (toolName, projectConfig, systemInfo).\n *\n * @returns Async function compatible with tool loading system\n *\n * @example\n * ```ts\n * export default defineTool((install, ctx) =>\n * install('github-release', { repo: 'BurntSushi/ripgrep' })\n * .bin('rg')\n * .version('14.0.0')\n * );\n * ```\n */\nexport function defineTool(\n fn: (\n /**\n * Function to select the installation method and provide type-checked parameters.\n * Call with a method name and params for installers, or call with no args for manual tools.\n * Returns a fluent builder to configure binaries, versions, hooks, and shell settings.\n *\n * @inheritdoc\n */\n install: InstallFunction,\n /**\n * Context object providing access to paths, configuration, and system information.\n * Use `ctx.projectConfig.paths.*` for configured directory paths.\n */\n ctx: IToolConfigContext,\n ) => ConfigureToolFnResult,\n): AsyncConfigureTool {\n return async (\n install: InstallFunction,\n ctx: IToolConfigContext,\n ): Promise<ToolConfig | ToolConfigBuilderContract | Omit<ToolConfigBuilderContract, 'bin'> | undefined> => {\n const result = fn(install, ctx);\n if (result instanceof Promise) {\n return result;\n }\n return result;\n };\n}\n\n/**\n * Create an InstallFunction bound to a specific logger and tool name.\n *\n * @param logger - Logger instance for the builder\n * @param toolName - Name of the tool being configured\n * @param context - Tool configuration context providing path helpers\n * @returns InstallFunction that creates configured IToolConfigBuilder instances\n */\nexport function createInstallFunction(\n logger: TsLogger,\n toolName: string,\n context?: IToolConfigContext,\n): InstallFunction {\n let builderInstance: IToolConfigBuilder | null = null;\n\n const getOrCreateBuilder = (): IToolConfigBuilder => {\n if (!builderInstance) {\n builderInstance = new IToolConfigBuilder(logger, toolName);\n }\n\n builderInstance.setContext(context);\n return builderInstance;\n };\n\n function install(method?: InstallMethod, params?: IInstallParamsRegistry[InstallMethod]): ToolConfigBuilderContract {\n const builder = getOrCreateBuilder();\n\n if (method) {\n const fallbackParams: Record<string, unknown> = {};\n builder.currentInstallationMethod = method;\n builder.currentInstallParams = params ?? fallbackParams;\n }\n\n return builder;\n }\n\n return install as InstallFunction;\n}\n"
|
|
279
|
+
],
|
|
280
|
+
"mappings": "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",
|
|
281
|
+
"debugId": "E442911435651FF164756E2164756E21",
|
|
282
|
+
"names": []
|
|
283
|
+
}
|