| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751 |
- #!/usr/bin/env bun
- /**
- * Enhanced package.json transform with Kilo dependency injection
- *
- * This script handles package.json conflicts by:
- * 1. Taking upstream's version (to get new dependencies)
- * 2. Transforming package names (opencode -> kilo)
- * 3. Injecting Kilo-specific dependencies
- * 4. Preserving Kilo's version number
- * 5. Preserving overrides and patchedDependencies
- * 6. Preserving Kilo's repository configuration
- * 7. Using "newest wins" strategy for dependency versions
- */
- import { $ } from "bun"
- import { info, success, warn, debug } from "../utils/logger"
- import { getCurrentVersion } from "./preserve-versions"
- import { oursHasKilocodeChanges } from "../utils/git"
- /**
- * Extract clean version string from a version specifier
- * Removes ^, ~, >=, etc. prefixes
- */
- function extractVersion(version: string): string | null {
- // Handle special formats that can't be compared
- if (
- version.startsWith("workspace:") ||
- version.startsWith("catalog:") ||
- version.startsWith("http://") ||
- version.startsWith("https://") ||
- version.startsWith("git://") ||
- version.startsWith("git+") ||
- version.startsWith("file:") ||
- version.startsWith("link:") ||
- version.startsWith("npm:")
- ) {
- return null
- }
- // Remove common prefixes: ^, ~, >=, >, <=, <, =
- const cleaned = version.replace(/^[\^~>=<]+/, "").trim()
- // Basic semver validation (x.y.z with optional pre-release/build)
- if (/^\d+\.\d+\.\d+/.test(cleaned)) {
- return cleaned
- }
- // Handle x.y format
- if (/^\d+\.\d+$/.test(cleaned)) {
- return cleaned + ".0"
- }
- // Handle single number
- if (/^\d+$/.test(cleaned)) {
- return cleaned + ".0.0"
- }
- return null
- }
- /**
- * Parse a semver string into components
- */
- function parseSemver(version: string): { major: number; minor: number; patch: number; prerelease: string } | null {
- const match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:-(.+))?/)
- if (!match) return null
- return {
- major: parseInt(match[1], 10),
- minor: parseInt(match[2], 10),
- patch: parseInt(match[3], 10),
- prerelease: match[4] || "",
- }
- }
- /**
- * Compare two version strings
- * Returns: 1 if a > b, -1 if a < b, 0 if equal
- * For special formats (URLs, catalog:, workspace:*), returns null (can't compare)
- */
- function compareVersions(a: string, b: string): number | null {
- const cleanA = extractVersion(a)
- const cleanB = extractVersion(b)
- // If either can't be parsed, return null (can't compare)
- if (!cleanA || !cleanB) return null
- const semverA = parseSemver(cleanA)
- const semverB = parseSemver(cleanB)
- if (!semverA || !semverB) return null
- // Compare major.minor.patch
- if (semverA.major !== semverB.major) return semverA.major > semverB.major ? 1 : -1
- if (semverA.minor !== semverB.minor) return semverA.minor > semverB.minor ? 1 : -1
- if (semverA.patch !== semverB.patch) return semverA.patch > semverB.patch ? 1 : -1
- // Handle prerelease (no prerelease > prerelease)
- if (!semverA.prerelease && semverB.prerelease) return 1
- if (semverA.prerelease && !semverB.prerelease) return -1
- if (semverA.prerelease && semverB.prerelease) {
- return semverA.prerelease.localeCompare(semverB.prerelease)
- }
- return 0
- }
- /**
- * Merge two dependency objects using "newest wins" strategy
- * For non-comparable versions (URLs, catalog:, workspace:*), upstream (theirs) wins
- */
- function mergeWithNewestVersions(
- ours: Record<string, string> | undefined,
- theirs: Record<string, string> | undefined,
- changes: string[],
- section: string,
- ): Record<string, string> {
- const result: Record<string, string> = {}
- // Start with all of theirs
- if (theirs) {
- for (const [name, version] of Object.entries(theirs)) {
- result[name] = version
- }
- }
- // Merge in ours, keeping newer versions
- if (ours) {
- for (const [name, ourVersion] of Object.entries(ours)) {
- const theirVersion = result[name]
- if (!theirVersion) {
- // Dependency only exists in ours - keep it
- result[name] = ourVersion
- changes.push(`${section}: preserved ${name}@${ourVersion} (kilo-only)`)
- } else if (ourVersion !== theirVersion) {
- // Both have it with different versions - compare
- const comparison = compareVersions(ourVersion, theirVersion)
- if (comparison === null) {
- // Can't compare (special format) - upstream wins per user preference
- changes.push(`${section}: ${name} kept upstream ${theirVersion} (special format)`)
- } else if (comparison > 0) {
- // Ours is newer
- result[name] = ourVersion
- changes.push(`${section}: ${name} ${theirVersion} -> ${ourVersion} (kilo newer)`)
- } else if (comparison < 0) {
- // Theirs is newer - already in result
- changes.push(`${section}: ${name} kept upstream ${theirVersion} (upstream newer)`)
- }
- // If equal, keep theirs (already in result)
- }
- }
- }
- return result
- }
- export interface PackageJsonResult {
- file: string
- action: "transformed" | "skipped" | "failed" | "flagged"
- changes: string[]
- dryRun: boolean
- }
- export interface PackageJsonOptions {
- dryRun?: boolean
- verbose?: boolean
- preserveVersion?: boolean
- }
- // Package name mappings
- const PACKAGE_NAME_MAP: Record<string, string> = {
- "opencode-ai": "@kilocode/cli",
- "@opencode-ai/cli": "@kilocode/cli",
- "@opencode-ai/sdk": "@kilocode/sdk",
- "@opencode-ai/plugin": "@kilocode/plugin",
- }
- // Kilo-specific dependencies to inject into specific packages
- // NOTE: When adding new Kilo-specific workspace dependencies (packages starting with @kilocode/kilo-*),
- // add them here to prevent them from being removed during upstream merges
- const KILO_DEPENDENCIES: Record<string, Record<string, string>> = {
- // packages/opencode/package.json needs these
- "packages/opencode/package.json": {
- "@kilocode/kilo-gateway": "workspace:*",
- "@kilocode/kilo-telemetry": "workspace:*",
- },
- // packages/app/package.json needs these
- "packages/app/package.json": {
- "@kilocode/kilo-i18n": "workspace:*",
- },
- }
- // Kilo-specific bin entries to set on specific packages
- const KILO_BIN: Record<string, Record<string, string>> = {
- "packages/opencode/package.json": {
- kilo: "./bin/kilo",
- kilocode: "./bin/kilo",
- },
- }
- // Packages that should have their name transformed
- const TRANSFORM_PACKAGE_NAMES: Record<string, string> = {
- "package.json": "@kilocode/kilo",
- "packages/opencode/package.json": "@kilocode/cli",
- "packages/plugin/package.json": "@kilocode/plugin",
- "packages/sdk/js/package.json": "@kilocode/sdk",
- }
- /**
- * Check if file is a package.json
- */
- export function isPackageJson(file: string): boolean {
- return file.endsWith("package.json")
- }
- /**
- * Transform dependencies in package.json
- */
- function transformDependencies(deps: Record<string, string> | undefined): {
- result: Record<string, string>
- changes: string[]
- } {
- if (!deps) return { result: {}, changes: [] }
- const result: Record<string, string> = {}
- const changes: string[] = []
- for (const [name, version] of Object.entries(deps)) {
- const newName = PACKAGE_NAME_MAP[name]
- if (newName) {
- result[newName] = version
- changes.push(`${name} -> ${newName}`)
- } else {
- result[name] = version
- }
- }
- return { result, changes }
- }
- /**
- * Transform a package.json file
- */
- export async function transformPackageJson(file: string, options: PackageJsonOptions = {}): Promise<PackageJsonResult> {
- const changes: string[] = []
- if (options.dryRun) {
- info(`[DRY-RUN] Would transform package.json: ${file}`)
- return { file, action: "transformed", changes: [], dryRun: true }
- }
- // If our version has kilocode_change markers, flag for manual resolution
- if (await oursHasKilocodeChanges(file)) {
- warn(`${file} has kilocode_change markers — skipping auto-transform, needs manual resolution`)
- return { file, action: "flagged", changes: [], dryRun: false }
- }
- try {
- // Save Kilo's version BEFORE taking theirs
- let ourPkg: Record<string, unknown> | null = null
- try {
- const ourContent = await $`git show :2:${file}`.text() // :2: is "ours" in merge
- ourPkg = JSON.parse(ourContent)
- } catch {
- // File might not exist in ours (new file from upstream)
- // or we're not in a merge conflict - try reading current file
- try {
- const currentContent = await Bun.file(file).text()
- if (!currentContent.includes("<<<<<<<")) {
- // Not a conflict, read as-is
- ourPkg = JSON.parse(currentContent)
- }
- } catch {
- // File doesn't exist yet
- }
- }
- // Take upstream's version
- await $`git checkout --theirs ${file}`.quiet().nothrow()
- await $`git add ${file}`.quiet().nothrow()
- // Read and parse upstream's version
- const content = await Bun.file(file).text()
- const pkg = JSON.parse(content)
- // 1. Transform package name if needed
- const relativePath = file.replace(process.cwd() + "/", "")
- const newName = TRANSFORM_PACKAGE_NAMES[relativePath]
- if (newName && pkg.name !== newName) {
- changes.push(`name: ${pkg.name} -> ${newName}`)
- pkg.name = newName
- }
- // 2. Preserve Kilo version if requested
- if (options.preserveVersion !== false) {
- const kiloVersion = await getCurrentVersion()
- if (pkg.version !== kiloVersion) {
- changes.push(`version: ${pkg.version} -> ${kiloVersion}`)
- pkg.version = kiloVersion
- }
- }
- // 3. Merge dependencies with "newest wins" strategy
- if (ourPkg) {
- pkg.dependencies = mergeWithNewestVersions(
- ourPkg.dependencies as Record<string, string> | undefined,
- pkg.dependencies,
- changes,
- "dependencies",
- )
- pkg.devDependencies = mergeWithNewestVersions(
- ourPkg.devDependencies as Record<string, string> | undefined,
- pkg.devDependencies,
- changes,
- "devDependencies",
- )
- pkg.peerDependencies = mergeWithNewestVersions(
- ourPkg.peerDependencies as Record<string, string> | undefined,
- pkg.peerDependencies,
- changes,
- "peerDependencies",
- )
- // 4. Preserve/merge overrides
- const ourOverrides = ourPkg.overrides as Record<string, string> | undefined
- if (ourOverrides || pkg.overrides) {
- pkg.overrides = mergeWithNewestVersions(ourOverrides, pkg.overrides, changes, "overrides")
- }
- // 5. Preserve patchedDependencies (Kilo-specific, upstream won't have these)
- const ourPatchedDeps = ourPkg.patchedDependencies as Record<string, string> | undefined
- if (ourPatchedDeps) {
- pkg.patchedDependencies = pkg.patchedDependencies || {}
- for (const [name, patch] of Object.entries(ourPatchedDeps)) {
- if (!pkg.patchedDependencies[name]) {
- pkg.patchedDependencies[name] = patch
- changes.push(`patchedDependencies: preserved ${name}`)
- }
- }
- }
- // 6. Preserve repository (Kilo-specific, upstream doesn't have this)
- const ourRepo = ourPkg.repository
- if (ourRepo && JSON.stringify(pkg.repository) !== JSON.stringify(ourRepo)) {
- pkg.repository = ourRepo
- changes.push(`repository: preserved Kilo's repository configuration`)
- }
- // 7. Handle workspaces for root package.json
- // Kilo has removed hosted platform packages (console/*, slack, etc.)
- // so we need to preserve Kilo's workspace configuration instead of taking upstream's
- const ourWorkspaces = ourPkg.workspaces as { packages?: string[]; catalog?: Record<string, string> } | undefined
- const theirWorkspaces = pkg.workspaces as { packages?: string[]; catalog?: Record<string, string> } | undefined
- if (relativePath === "package.json" && ourWorkspaces?.packages) {
- pkg.workspaces = pkg.workspaces || {}
- pkg.workspaces.packages = ourWorkspaces.packages
- changes.push(`workspaces.packages: preserved Kilo's workspace configuration`)
- }
- const ourScripts = ourPkg.scripts as Record<string, string> | undefined
- if (relativePath === "package.json" && ourScripts?.extension && pkg.scripts?.extension !== ourScripts.extension) {
- pkg.scripts = pkg.scripts || {}
- pkg.scripts.extension = ourScripts.extension
- changes.push(`scripts.extension: preserved Kilo's extension script`)
- }
- if (relativePath === "package.json" && ourScripts?.changeset && pkg.scripts?.changeset !== ourScripts.changeset) {
- pkg.scripts = pkg.scripts || {}
- pkg.scripts.changeset = ourScripts.changeset
- changes.push(`scripts.changeset: preserved Kilo's changeset script`)
- }
- if (
- relativePath === "package.json" &&
- ourScripts?.["changeset:version"] &&
- pkg.scripts?.["changeset:version"] !== ourScripts["changeset:version"]
- ) {
- pkg.scripts = pkg.scripts || {}
- pkg.scripts["changeset:version"] = ourScripts["changeset:version"]
- changes.push(`scripts.changeset:version: preserved Kilo's changeset:version script`)
- }
- // Preserve Kilo's test runner scripts for packages/opencode
- if (
- relativePath === "packages/opencode/package.json" &&
- ourScripts?.test &&
- pkg.scripts?.test !== ourScripts.test
- ) {
- pkg.scripts = pkg.scripts || {}
- pkg.scripts.test = ourScripts.test
- changes.push(`scripts.test: preserved Kilo's test runner script`)
- }
- if (
- relativePath === "packages/opencode/package.json" &&
- ourScripts?.["test:ci"] &&
- pkg.scripts?.["test:ci"] !== ourScripts["test:ci"]
- ) {
- pkg.scripts = pkg.scripts || {}
- pkg.scripts["test:ci"] = ourScripts["test:ci"]
- changes.push(`scripts.test:ci: preserved Kilo's CI test runner script`)
- }
- // Merge catalog with "newest wins" strategy
- if (ourWorkspaces?.catalog || theirWorkspaces?.catalog) {
- pkg.workspaces = pkg.workspaces || {}
- pkg.workspaces.catalog = mergeWithNewestVersions(
- ourWorkspaces?.catalog,
- theirWorkspaces?.catalog,
- changes,
- "workspaces.catalog",
- )
- }
- }
- // 7. Transform dependency names (opencode -> kilo)
- if (pkg.dependencies) {
- const { result, changes: depChanges } = transformDependencies(pkg.dependencies)
- pkg.dependencies = result
- changes.push(...depChanges.map((c) => `dependencies: ${c}`))
- }
- if (pkg.devDependencies) {
- const { result, changes: devChanges } = transformDependencies(pkg.devDependencies)
- if (devChanges.length > 0) {
- pkg.devDependencies = result
- changes.push(...devChanges.map((c) => `devDependencies: ${c}`))
- }
- }
- if (pkg.peerDependencies) {
- const { result, changes: peerChanges } = transformDependencies(pkg.peerDependencies)
- if (peerChanges.length > 0) {
- pkg.peerDependencies = result
- changes.push(...peerChanges.map((c) => `peerDependencies: ${c}`))
- }
- }
- // 8. Inject Kilo-specific dependencies
- const kiloDeps = KILO_DEPENDENCIES[relativePath]
- if (kiloDeps) {
- pkg.dependencies = pkg.dependencies || {}
- for (const [name, version] of Object.entries(kiloDeps)) {
- if (!pkg.dependencies[name]) {
- pkg.dependencies[name] = version
- changes.push(`injected: ${name}`)
- }
- }
- }
- // 9. Set Kilo-specific bin entries
- const kiloBin = KILO_BIN[relativePath]
- if (kiloBin) {
- pkg.bin = kiloBin
- changes.push(`bin: set Kilo bin entries`)
- }
- // Write back with proper formatting
- const newContent = JSON.stringify(pkg, null, 2) + "\n"
- await Bun.write(file, newContent)
- await $`git add ${file}`.quiet().nothrow()
- if (changes.length > 0) {
- success(`Transformed ${file}: ${changes.length} changes`)
- if (options.verbose) {
- for (const change of changes) {
- debug(` - ${change}`)
- }
- }
- }
- return { file, action: "transformed", changes, dryRun: false }
- } catch (err) {
- warn(`Failed to transform ${file}: ${err}`)
- return { file, action: "failed", changes: [], dryRun: false }
- }
- }
- /**
- * Transform conflicted package.json files
- */
- export async function transformConflictedPackageJson(
- files: string[],
- options: PackageJsonOptions = {},
- ): Promise<PackageJsonResult[]> {
- const results: PackageJsonResult[] = []
- for (const file of files) {
- if (!isPackageJson(file)) {
- results.push({ file, action: "skipped", changes: [], dryRun: options.dryRun ?? false })
- continue
- }
- const result = await transformPackageJson(file, options)
- results.push(result)
- }
- return results
- }
- /**
- * Get Kilo's package.json from the base branch (main) for comparison
- * Used during pre-merge to compare upstream versions against Kilo's versions
- */
- async function getKiloPackageJson(path: string, baseBranch = "main"): Promise<Record<string, unknown> | null> {
- try {
- // Try to get the file from origin/main (or whatever base branch)
- const content = await $`git show origin/${baseBranch}:${path}`.text()
- return JSON.parse(content)
- } catch {
- // File might not exist in Kilo
- return null
- }
- }
- /**
- * Transform all package.json files (pre-merge, on opencode branch)
- * This function merges Kilo's versions with upstream, using "newest wins" strategy
- */
- export async function transformAllPackageJson(options: PackageJsonOptions = {}): Promise<PackageJsonResult[]> {
- const { Glob } = await import("bun")
- const results: PackageJsonResult[] = []
- // Find all package.json files
- const glob = new Glob("**/package.json")
- for await (const path of glob.scan({ absolute: false })) {
- // Skip node_modules
- if (path.includes("node_modules")) continue
- const file = Bun.file(path)
- if (!(await file.exists())) continue
- try {
- const content = await file.text()
- const pkg = JSON.parse(content) // This is upstream's version
- const changes: string[] = []
- // Get Kilo's version from base branch for comparison
- const kiloPkg = await getKiloPackageJson(path)
- // 1. Transform package name if needed
- const newName = TRANSFORM_PACKAGE_NAMES[path]
- if (newName && pkg.name !== newName) {
- changes.push(`name: ${pkg.name} -> ${newName}`)
- pkg.name = newName
- }
- // 2. Preserve Kilo version if requested
- if (options.preserveVersion !== false) {
- const kiloVersion = await getCurrentVersion()
- if (pkg.version !== kiloVersion) {
- changes.push(`version: ${pkg.version} -> ${kiloVersion}`)
- pkg.version = kiloVersion
- }
- }
- // 3. Merge dependencies with "newest wins" strategy (if Kilo has this file)
- if (kiloPkg) {
- pkg.dependencies = mergeWithNewestVersions(
- kiloPkg.dependencies as Record<string, string> | undefined,
- pkg.dependencies,
- changes,
- "dependencies",
- )
- pkg.devDependencies = mergeWithNewestVersions(
- kiloPkg.devDependencies as Record<string, string> | undefined,
- pkg.devDependencies,
- changes,
- "devDependencies",
- )
- pkg.peerDependencies = mergeWithNewestVersions(
- kiloPkg.peerDependencies as Record<string, string> | undefined,
- pkg.peerDependencies,
- changes,
- "peerDependencies",
- )
- // 4. Preserve/merge overrides
- const kiloOverrides = kiloPkg.overrides as Record<string, string> | undefined
- if (kiloOverrides || pkg.overrides) {
- pkg.overrides = mergeWithNewestVersions(kiloOverrides, pkg.overrides, changes, "overrides")
- }
- // 5. Preserve patchedDependencies (Kilo-specific, upstream won't have these)
- const kiloPatchedDeps = kiloPkg.patchedDependencies as Record<string, string> | undefined
- if (kiloPatchedDeps) {
- pkg.patchedDependencies = pkg.patchedDependencies || {}
- for (const [name, patch] of Object.entries(kiloPatchedDeps)) {
- if (!pkg.patchedDependencies[name]) {
- pkg.patchedDependencies[name] = patch
- changes.push(`patchedDependencies: preserved ${name}`)
- }
- }
- }
- // 6. Preserve repository (Kilo-specific, upstream doesn't have this)
- const kiloRepo = kiloPkg.repository
- if (kiloRepo && JSON.stringify(pkg.repository) !== JSON.stringify(kiloRepo)) {
- pkg.repository = kiloRepo
- changes.push(`repository: preserved Kilo's repository configuration`)
- }
- // 7. Handle workspaces for root package.json
- // Kilo has removed hosted platform packages (console/*, slack, etc.)
- // so we need to preserve Kilo's workspace configuration instead of taking upstream's
- const kiloWorkspaces = kiloPkg.workspaces as
- | { packages?: string[]; catalog?: Record<string, string> }
- | undefined
- const upstreamWorkspaces = pkg.workspaces as
- | { packages?: string[]; catalog?: Record<string, string> }
- | undefined
- if (path === "package.json" && kiloWorkspaces?.packages) {
- pkg.workspaces = pkg.workspaces || {}
- pkg.workspaces.packages = kiloWorkspaces.packages
- changes.push(`workspaces.packages: preserved Kilo's workspace configuration`)
- }
- const kiloScripts = kiloPkg.scripts as Record<string, string> | undefined
- if (path === "package.json" && kiloScripts?.extension && pkg.scripts?.extension !== kiloScripts.extension) {
- pkg.scripts = pkg.scripts || {}
- pkg.scripts.extension = kiloScripts.extension
- changes.push(`scripts.extension: preserved Kilo's extension script`)
- }
- // Preserve Kilo's test runner scripts for packages/opencode
- if (path === "packages/opencode/package.json" && kiloScripts?.test && pkg.scripts?.test !== kiloScripts.test) {
- pkg.scripts = pkg.scripts || {}
- pkg.scripts.test = kiloScripts.test
- changes.push(`scripts.test: preserved Kilo's test runner script`)
- }
- if (
- path === "packages/opencode/package.json" &&
- kiloScripts?.["test:ci"] &&
- pkg.scripts?.["test:ci"] !== kiloScripts["test:ci"]
- ) {
- pkg.scripts = pkg.scripts || {}
- pkg.scripts["test:ci"] = kiloScripts["test:ci"]
- changes.push(`scripts.test:ci: preserved Kilo's CI test runner script`)
- }
- // Merge catalog with "newest wins" strategy
- if (kiloWorkspaces?.catalog || upstreamWorkspaces?.catalog) {
- pkg.workspaces = pkg.workspaces || {}
- pkg.workspaces.catalog = mergeWithNewestVersions(
- kiloWorkspaces?.catalog,
- upstreamWorkspaces?.catalog,
- changes,
- "workspaces.catalog",
- )
- }
- }
- // 7. Transform dependency names (opencode -> kilo)
- if (pkg.dependencies) {
- const { result, changes: depChanges } = transformDependencies(pkg.dependencies)
- if (depChanges.length > 0) {
- pkg.dependencies = result
- changes.push(...depChanges.map((c) => `dependencies: ${c}`))
- }
- }
- if (pkg.devDependencies) {
- const { result, changes: devChanges } = transformDependencies(pkg.devDependencies)
- if (devChanges.length > 0) {
- pkg.devDependencies = result
- changes.push(...devChanges.map((c) => `devDependencies: ${c}`))
- }
- }
- if (pkg.peerDependencies) {
- const { result, changes: peerChanges } = transformDependencies(pkg.peerDependencies)
- if (peerChanges.length > 0) {
- pkg.peerDependencies = result
- changes.push(...peerChanges.map((c) => `peerDependencies: ${c}`))
- }
- }
- // 8. Inject Kilo-specific dependencies
- const kiloDeps = KILO_DEPENDENCIES[path]
- if (kiloDeps) {
- pkg.dependencies = pkg.dependencies || {}
- for (const [name, version] of Object.entries(kiloDeps)) {
- if (!pkg.dependencies[name]) {
- pkg.dependencies[name] = version
- changes.push(`injected: ${name}`)
- }
- }
- }
- // 9. Set Kilo-specific bin entries
- const kiloBin = KILO_BIN[path]
- if (kiloBin) {
- pkg.bin = kiloBin
- changes.push(`bin: set Kilo bin entries`)
- }
- if (changes.length > 0) {
- if (!options.dryRun) {
- const newContent = JSON.stringify(pkg, null, 2) + "\n"
- await Bun.write(path, newContent)
- success(`Transformed ${path}: ${changes.length} changes`)
- } else {
- info(`[DRY-RUN] Would transform ${path}: ${changes.length} changes`)
- }
- }
- results.push({ file: path, action: "transformed", changes, dryRun: options.dryRun ?? false })
- } catch (err) {
- warn(`Failed to transform ${path}: ${err}`)
- results.push({ file: path, action: "failed", changes: [], 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 files = args.filter((a) => !a.startsWith("--"))
- if (files.length === 0) {
- info("Usage: transform-package-json.ts [--dry-run] [--verbose] <file1> <file2> ...")
- process.exit(1)
- }
- if (dryRun) {
- info("Running in dry-run mode")
- }
- const results = await transformConflictedPackageJson(files, { dryRun, verbose })
- const transformed = results.filter((r) => r.action === "transformed")
- const totalChanges = results.reduce((sum, r) => sum + r.changes.length, 0)
- console.log()
- success(`Transformed ${transformed.length} package.json files with ${totalChanges} changes`)
- if (dryRun) {
- info("Run without --dry-run to apply changes")
- }
- }
|