| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- #!/usr/bin/env bun
- /**
- * Lock files transform - handles lock file conflicts by accepting ours and regenerating
- *
- * Lock files (bun.lock, package-lock.json, yarn.lock, Cargo.lock, etc.) should not be
- * manually merged. Instead, we accept our version to resolve the conflict, then regenerate
- * the lock file fresh after the merge is complete.
- */
- import { $ } from "bun"
- import { info, success, warn, debug } from "../utils/logger"
- import { defaultConfig } from "../utils/config"
- import { checkoutOurs, stageFiles, getConflictedFiles } from "../utils/git"
- export interface LockFileResult {
- file: string
- action: "resolved" | "skipped" | "regenerated" | "failed"
- dryRun: boolean
- }
- export interface LockFileOptions {
- dryRun?: boolean
- verbose?: boolean
- patterns?: string[]
- }
- /**
- * Check if a file is a lock file based on patterns
- */
- export function isLockFile(path: string, patterns?: string[]): boolean {
- const lockPatterns = patterns || defaultConfig.lockFiles
- return lockPatterns.some((pattern) => {
- // Exact match
- if (path === pattern) return true
- // Glob pattern with **
- if (pattern.includes("**")) {
- const regex = new RegExp("^" + pattern.replace(/\*\*/g, ".*").replace(/\./g, "\\.") + "$")
- return regex.test(path)
- }
- // Simple glob pattern
- if (pattern.includes("*")) {
- const regex = new RegExp("^" + pattern.replace(/\./g, "\\.").replace(/\*/g, "[^/]*") + "$")
- return regex.test(path)
- }
- // Basename match (e.g., "bun.lock" matches "packages/foo/bun.lock")
- const basename = path.split("/").pop()
- return basename === pattern
- })
- }
- /**
- * Resolve lock file conflicts by accepting our version
- */
- export async function resolveLockFileConflicts(options: LockFileOptions = {}): Promise<LockFileResult[]> {
- const results: LockFileResult[] = []
- const patterns = options.patterns || defaultConfig.lockFiles
- const conflicted = await getConflictedFiles()
- if (conflicted.length === 0) {
- debug("No conflicted files found")
- return results
- }
- const lockFiles = conflicted.filter((file) => isLockFile(file, patterns))
- if (lockFiles.length === 0) {
- debug("No lock file conflicts found")
- return results
- }
- info(`Found ${lockFiles.length} conflicted lock file(s)`)
- for (const file of lockFiles) {
- if (options.dryRun) {
- info(`[DRY-RUN] Would resolve conflict (accept ours): ${file}`)
- results.push({ file, action: "resolved", dryRun: true })
- continue
- }
- try {
- await checkoutOurs([file])
- await stageFiles([file])
- success(`Resolved lock file conflict (accepted ours): ${file}`)
- results.push({ file, action: "resolved", dryRun: false })
- } catch (err) {
- warn(`Failed to resolve lock file conflict: ${file} - ${err}`)
- results.push({ file, action: "failed", dryRun: false })
- }
- }
- return results
- }
- /**
- * Regenerate lock files after merge
- */
- export async function regenerateLockFiles(options: LockFileOptions = {}): Promise<LockFileResult[]> {
- const results: LockFileResult[] = []
- // Check if bun.lock exists or was part of the merge
- const hasBunLock = await Bun.file("bun.lock").exists()
- if (hasBunLock) {
- if (options.dryRun) {
- info("[DRY-RUN] Would regenerate bun.lock via 'bun install'")
- results.push({ file: "bun.lock", action: "regenerated", dryRun: true })
- } else {
- info("Regenerating bun.lock...")
- const result = await $`bun install`.quiet().nothrow()
- if (result.exitCode === 0) {
- success("Regenerated bun.lock")
- results.push({ file: "bun.lock", action: "regenerated", dryRun: false })
- } else {
- warn(`Failed to regenerate bun.lock: ${result.stderr.toString()}`)
- results.push({ file: "bun.lock", action: "failed", dryRun: false })
- }
- }
- }
- // Check for Cargo.lock in Tauri package
- const cargoLockPath = "packages/desktop/src-tauri/Cargo.lock"
- const hasCargoLock = await Bun.file(cargoLockPath).exists()
- if (hasCargoLock) {
- if (options.dryRun) {
- info("[DRY-RUN] Would regenerate Cargo.lock via 'cargo generate-lockfile'")
- results.push({ file: cargoLockPath, action: "regenerated", dryRun: true })
- } else {
- info("Regenerating Cargo.lock...")
- const result = await $`cargo generate-lockfile`.cwd("packages/desktop/src-tauri").quiet().nothrow()
- if (result.exitCode === 0) {
- success("Regenerated Cargo.lock")
- results.push({ file: cargoLockPath, action: "regenerated", dryRun: false })
- } else {
- // Cargo might not be installed, just warn
- warn(`Could not regenerate Cargo.lock (cargo may not be installed): ${result.stderr.toString()}`)
- results.push({ file: cargoLockPath, action: "skipped", dryRun: false })
- }
- }
- }
- // Note about nix/hashes.json - regenerated by CI, not locally
- const nixHashesPath = "nix/hashes.json"
- const hasNixHashes = await Bun.file(nixHashesPath).exists()
- if (hasNixHashes) {
- info("Note: nix/hashes.json will be regenerated by CI (update-nix-hashes.yml) after PR is created")
- results.push({ file: nixHashesPath, action: "skipped", dryRun: options.dryRun ?? false })
- }
- return results
- }
- // CLI entry point
- if (import.meta.main) {
- const args = process.argv.slice(2)
- const dryRun = args.includes("--dry-run")
- const verbose = args.includes("--verbose")
- const regenerate = args.includes("--regenerate")
- if (dryRun) {
- info("Running in dry-run mode (no files will be modified)")
- }
- if (regenerate) {
- const results = await regenerateLockFiles({ dryRun, verbose })
- const regenerated = results.filter((r) => r.action === "regenerated")
- console.log()
- success(`Regenerated ${regenerated.length} lock file(s)`)
- } else {
- const results = await resolveLockFileConflicts({ dryRun, verbose })
- const resolved = results.filter((r) => r.action === "resolved")
- console.log()
- success(`Resolved ${resolved.length} lock file conflict(s)`)
- }
- if (dryRun) {
- info("Run without --dry-run to apply changes")
- }
- }
|