mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-30 17:14:40 +08:00
refactor: improve premerge transforms
This commit is contained in:
@@ -57,7 +57,7 @@ bun run merge.ts --version v1.1.49 --dry-run
|
||||
|
||||
## Merge Process
|
||||
|
||||
The merge automation follows this process:
|
||||
The merge automation follows this process, applying **all transformations BEFORE the merge** to minimize conflicts:
|
||||
|
||||
1. **Validate environment**
|
||||
- Check for upstream remote
|
||||
@@ -72,14 +72,26 @@ The merge automation follows this process:
|
||||
- `<author>/kilo-opencode-<version>` - Merge target branch
|
||||
- `<author>/opencode-<version>` - Transformed upstream branch
|
||||
|
||||
5. **Apply transformations** to upstream branch:
|
||||
5. **Apply ALL transformations to upstream branch (PRE-MERGE)**:
|
||||
- Transform package names (opencode-ai -> @kilocode/cli)
|
||||
- Preserve Kilo's versions
|
||||
- Transform i18n files with Kilo branding
|
||||
- Transform branding-only files (UI components, configs)
|
||||
- Transform Tauri/Desktop config files
|
||||
- Transform package.json files (names, deps, Kilo injections)
|
||||
- Transform script files (GitHub API references)
|
||||
- Transform extension files (Zed, etc.)
|
||||
- Transform web/docs files
|
||||
- Reset Kilo-specific files
|
||||
|
||||
6. **Merge** transformed upstream into Kilo branch
|
||||
- Since all branding transforms are applied pre-merge, conflicts should be minimal
|
||||
- Remaining conflicts are files with actual code differences (kilocode_change markers)
|
||||
|
||||
7. **Auto-resolve** known conflicts (markdown, Kilo-specific files)
|
||||
7. **Auto-resolve** any remaining conflicts
|
||||
- Skip files that shouldn't exist in Kilo
|
||||
- Keep Kilo's version of specific files
|
||||
- Fallback transforms for edge cases
|
||||
|
||||
8. **Push** and generate final report
|
||||
|
||||
@@ -138,9 +150,27 @@ Configuration is defined in `utils/config.ts`:
|
||||
}
|
||||
```
|
||||
|
||||
## Auto-Resolution Strategies
|
||||
## Pre-Merge Transformation Strategy
|
||||
|
||||
The merge tool uses different strategies based on file type:
|
||||
**Key insight**: By applying all branding transforms to the upstream branch BEFORE merging, we eliminate most conflicts that would otherwise occur due to branding differences (OpenCode -> Kilo).
|
||||
|
||||
### Transform Order (Pre-Merge)
|
||||
|
||||
The following transforms are applied to the opencode branch before merging:
|
||||
|
||||
1. **Package names** - `opencode-ai` -> `@kilocode/cli`, etc.
|
||||
2. **Versions** - Preserve Kilo's version numbers
|
||||
3. **i18n files** - OpenCode -> Kilo in user-visible strings
|
||||
4. **Branding files** - UI components, configs with branding only
|
||||
5. **Tauri configs** - Desktop app identifiers, names
|
||||
6. **package.json** - Names, dependencies, Kilo injections
|
||||
7. **Scripts** - GitHub API references
|
||||
8. **Extensions** - Zed, etc.
|
||||
9. **Web/docs** - Documentation files
|
||||
|
||||
### Post-Merge Strategies
|
||||
|
||||
After merging, any remaining conflicts are handled based on file type:
|
||||
|
||||
| File Type | Strategy | Description |
|
||||
| ----------------- | ----------------------- | ------------------------------------------------ |
|
||||
@@ -155,6 +185,22 @@ The merge tool uses different strategies based on file type:
|
||||
| GitHub workflows | `keep-ours` | Keep Kilo's version (manual review) |
|
||||
| Code with markers | `manual` | Has `kilocode_change` markers, needs review |
|
||||
|
||||
### Why This Reduces Conflicts
|
||||
|
||||
Previously, conflicts occurred because:
|
||||
|
||||
- Upstream had `OpenCode` branding
|
||||
- Kilo had `Kilo` branding
|
||||
- Git saw these as conflicting changes
|
||||
|
||||
Now:
|
||||
|
||||
- We transform upstream to `Kilo` branding BEFORE merge
|
||||
- Both branches have the same branding
|
||||
- Git sees no conflict for branding-only files
|
||||
|
||||
The only remaining conflicts are files with **actual code differences** - files with `kilocode_change` markers that contain Kilo-specific logic.
|
||||
|
||||
## CLI Options
|
||||
|
||||
### merge.ts
|
||||
|
||||
@@ -29,6 +29,7 @@ export {
|
||||
export {
|
||||
transformConflictedTakeTheirs,
|
||||
transformTakeTheirs,
|
||||
transformAllTakeTheirs,
|
||||
shouldTakeTheirs,
|
||||
applyBrandingTransforms,
|
||||
matchesPattern,
|
||||
@@ -37,6 +38,7 @@ export {
|
||||
export {
|
||||
transformConflictedTauri,
|
||||
transformTauriFile,
|
||||
transformAllTauri,
|
||||
isTauriFile,
|
||||
applyTauriTransforms,
|
||||
} from "./transforms/transform-tauri"
|
||||
@@ -44,12 +46,14 @@ export {
|
||||
export {
|
||||
transformConflictedPackageJson,
|
||||
transformPackageJson,
|
||||
transformAllPackageJson,
|
||||
isPackageJson,
|
||||
} from "./transforms/transform-package-json"
|
||||
|
||||
export {
|
||||
transformConflictedScripts,
|
||||
transformScriptFile,
|
||||
transformAllScripts,
|
||||
isScriptFile,
|
||||
applyScriptTransforms,
|
||||
} from "./transforms/transform-scripts"
|
||||
@@ -57,8 +61,15 @@ export {
|
||||
export {
|
||||
transformConflictedExtensions,
|
||||
transformExtensionFile,
|
||||
transformAllExtensions,
|
||||
isExtensionFile,
|
||||
applyExtensionTransforms,
|
||||
} from "./transforms/transform-extensions"
|
||||
|
||||
export { transformConflictedWeb, transformWebFile, isWebFile, applyWebTransforms } from "./transforms/transform-web"
|
||||
export {
|
||||
transformConflictedWeb,
|
||||
transformWebFile,
|
||||
transformAllWeb,
|
||||
isWebFile,
|
||||
applyWebTransforms,
|
||||
} from "./transforms/transform-web"
|
||||
|
||||
+175
-108
@@ -29,12 +29,24 @@ import { keepOursFiles, resetToOurs } from "./transforms/keep-ours"
|
||||
import { skipFiles, skipSpecificFiles } from "./transforms/skip-files"
|
||||
import { transformConflictedI18n, transformAllI18n } from "./transforms/transform-i18n"
|
||||
// New transforms for auto-resolving more conflict types
|
||||
import { transformConflictedTakeTheirs, shouldTakeTheirs } from "./transforms/transform-take-theirs"
|
||||
import { transformConflictedTauri, isTauriFile } from "./transforms/transform-tauri"
|
||||
import { transformConflictedPackageJson, isPackageJson } from "./transforms/transform-package-json"
|
||||
import { transformConflictedScripts, isScriptFile } from "./transforms/transform-scripts"
|
||||
import { transformConflictedExtensions, isExtensionFile } from "./transforms/transform-extensions"
|
||||
import { transformConflictedWeb, isWebFile } from "./transforms/transform-web"
|
||||
import {
|
||||
transformConflictedTakeTheirs,
|
||||
shouldTakeTheirs,
|
||||
transformAllTakeTheirs,
|
||||
} from "./transforms/transform-take-theirs"
|
||||
import { transformConflictedTauri, isTauriFile, transformAllTauri } from "./transforms/transform-tauri"
|
||||
import {
|
||||
transformConflictedPackageJson,
|
||||
isPackageJson,
|
||||
transformAllPackageJson,
|
||||
} from "./transforms/transform-package-json"
|
||||
import { transformConflictedScripts, isScriptFile, transformAllScripts } from "./transforms/transform-scripts"
|
||||
import {
|
||||
transformConflictedExtensions,
|
||||
isExtensionFile,
|
||||
transformAllExtensions,
|
||||
} from "./transforms/transform-extensions"
|
||||
import { transformConflictedWeb, isWebFile, transformAllWeb } from "./transforms/transform-web"
|
||||
|
||||
interface MergeOptions {
|
||||
version?: string
|
||||
@@ -250,15 +262,16 @@ async function main() {
|
||||
await git.createBranch(opencodeBranch)
|
||||
logger.info(`Created opencode branch: ${opencodeBranch}`)
|
||||
|
||||
// Step 6: Apply transformations to opencode branch
|
||||
logger.step(6, 8, "Applying transformations to opencode branch...")
|
||||
// Step 6: Apply ALL transformations to opencode branch (pre-merge)
|
||||
// This reduces conflicts by transforming upstream code to Kilo conventions BEFORE merging
|
||||
logger.step(6, 8, "Applying transformations to opencode branch (pre-merge)...")
|
||||
|
||||
// Transform package names
|
||||
// 6a. Transform package names (opencode-ai -> @kilocode/cli)
|
||||
logger.info("Transforming package names...")
|
||||
const nameResults = await transformPackageNames({ dryRun: false, verbose: options.verbose })
|
||||
logger.success(`Transformed ${nameResults.length} files`)
|
||||
|
||||
// Preserve Kilo versions
|
||||
// 6b. Preserve Kilo versions
|
||||
logger.info("Preserving Kilo versions...")
|
||||
const versionResults = await preserveAllVersions({
|
||||
dryRun: false,
|
||||
@@ -267,15 +280,71 @@ async function main() {
|
||||
})
|
||||
logger.success(`Preserved versions in ${versionResults.length} files`)
|
||||
|
||||
// Reset keep-ours files to Kilo's version
|
||||
// 6c. Transform i18n files (OpenCode -> Kilo branding)
|
||||
logger.info("Transforming i18n files...")
|
||||
const i18nPreResults = await transformAllI18n({ dryRun: false, verbose: options.verbose })
|
||||
const i18nPreCount = i18nPreResults.filter((r) => r.replacements > 0).length
|
||||
if (i18nPreCount > 0) {
|
||||
logger.success(`Transformed ${i18nPreCount} i18n files with Kilo branding`)
|
||||
}
|
||||
|
||||
// 6d. Transform branding-only files (take-theirs patterns)
|
||||
logger.info("Transforming branding-only files...")
|
||||
const brandingResults = await transformAllTakeTheirs({ dryRun: false, verbose: options.verbose })
|
||||
const brandingCount = brandingResults.filter((r) => r.action === "transformed" && r.replacements > 0).length
|
||||
if (brandingCount > 0) {
|
||||
logger.success(`Transformed ${brandingCount} files with Kilo branding`)
|
||||
}
|
||||
|
||||
// 6e. Transform Tauri/Desktop config files
|
||||
logger.info("Transforming Tauri/Desktop config files...")
|
||||
const tauriPreResults = await transformAllTauri({ dryRun: false, verbose: options.verbose })
|
||||
const tauriPreCount = tauriPreResults.filter((r) => r.action === "transformed" && r.replacements > 0).length
|
||||
if (tauriPreCount > 0) {
|
||||
logger.success(`Transformed ${tauriPreCount} Tauri config files`)
|
||||
}
|
||||
|
||||
// 6f. Transform package.json files (names, deps, Kilo injections)
|
||||
logger.info("Transforming package.json files...")
|
||||
const pkgPreResults = await transformAllPackageJson({ dryRun: false, verbose: options.verbose })
|
||||
const pkgPreCount = pkgPreResults.filter((r) => r.action === "transformed" && r.changes.length > 0).length
|
||||
if (pkgPreCount > 0) {
|
||||
logger.success(`Transformed ${pkgPreCount} package.json files`)
|
||||
}
|
||||
|
||||
// 6g. Transform script files (GitHub API references)
|
||||
logger.info("Transforming script files...")
|
||||
const scriptPreResults = await transformAllScripts({ dryRun: false, verbose: options.verbose })
|
||||
const scriptPreCount = scriptPreResults.filter((r) => r.action === "transformed" && r.replacements > 0).length
|
||||
if (scriptPreCount > 0) {
|
||||
logger.success(`Transformed ${scriptPreCount} script files`)
|
||||
}
|
||||
|
||||
// 6h. Transform extension files (Zed, etc.)
|
||||
logger.info("Transforming extension files...")
|
||||
const extPreResults = await transformAllExtensions({ dryRun: false, verbose: options.verbose })
|
||||
const extPreCount = extPreResults.filter((r) => r.action === "transformed" && r.replacements > 0).length
|
||||
if (extPreCount > 0) {
|
||||
logger.success(`Transformed ${extPreCount} extension files`)
|
||||
}
|
||||
|
||||
// 6i. Transform web/docs files
|
||||
logger.info("Transforming web/docs files...")
|
||||
const webPreResults = await transformAllWeb({ dryRun: false, verbose: options.verbose })
|
||||
const webPreCount = webPreResults.filter((r) => r.action === "transformed" && r.replacements > 0).length
|
||||
if (webPreCount > 0) {
|
||||
logger.success(`Transformed ${webPreCount} web/docs files`)
|
||||
}
|
||||
|
||||
// 6j. Reset keep-ours files to Kilo's version
|
||||
logger.info("Resetting Kilo-specific files...")
|
||||
const keepOursResults = await resetToOurs(config.keepOurs, { dryRun: false, verbose: options.verbose })
|
||||
logger.success(`Reset ${keepOursResults.length} files to Kilo's version`)
|
||||
|
||||
// Commit transformations
|
||||
// Commit all transformations
|
||||
await git.stageAll()
|
||||
await git.commit(`refactor: kilo compat for ${targetVersion.tag}`)
|
||||
logger.success("Committed transformations")
|
||||
logger.success("Committed pre-merge transformations")
|
||||
|
||||
// Step 7: Merge into Kilo branch
|
||||
logger.step(7, 8, "Merging into Kilo branch...")
|
||||
@@ -284,10 +353,13 @@ async function main() {
|
||||
const mergeResult = await git.merge(opencodeBranch)
|
||||
|
||||
if (!mergeResult.success) {
|
||||
logger.warn("Merge has conflicts")
|
||||
logger.warn("Merge has conflicts (these should only be files with actual code differences)")
|
||||
logger.info("Conflicted files:")
|
||||
logger.list(mergeResult.conflicts)
|
||||
|
||||
// Since we applied all branding transforms pre-merge, remaining conflicts should be minimal.
|
||||
// These are likely files with kilocode_change markers or actual logic differences.
|
||||
|
||||
// Step 7a: Skip files that shouldn't exist in Kilo
|
||||
logger.info("Removing files that shouldn't exist in Kilo...")
|
||||
const skipResults = await skipFiles({ dryRun: false, verbose: options.verbose })
|
||||
@@ -296,105 +368,104 @@ async function main() {
|
||||
logger.success(`Skipped ${skippedCount} files (removed from merge)`)
|
||||
}
|
||||
|
||||
// Step 7b: Transform i18n files (take upstream + apply Kilo branding)
|
||||
logger.info("Transforming i18n files...")
|
||||
let conflictedFiles = await git.getConflictedFiles()
|
||||
const i18nResults = await transformConflictedI18n(conflictedFiles, { dryRun: false, verbose: options.verbose })
|
||||
const i18nTransformed = i18nResults.filter((r) => r.replacements > 0).length
|
||||
if (i18nTransformed > 0) {
|
||||
logger.success(`Transformed ${i18nTransformed} i18n files with Kilo branding`)
|
||||
}
|
||||
|
||||
// Step 7c: Auto-resolve keep-ours conflicts
|
||||
// Step 7b: Auto-resolve keep-ours conflicts
|
||||
logger.info("Keeping Kilo-specific files...")
|
||||
const resolved = await keepOursFiles({ dryRun: false, verbose: options.verbose })
|
||||
const autoResolved = resolved.filter((r) => r.action === "kept")
|
||||
|
||||
if (autoResolved.length > 0) {
|
||||
logger.success(`Auto-resolved ${autoResolved.length} conflicts (kept Kilo's version)`)
|
||||
}
|
||||
|
||||
// Step 7d: Transform branding-only files (take theirs + apply Kilo branding)
|
||||
conflictedFiles = await git.getConflictedFiles()
|
||||
if (conflictedFiles.length > 0) {
|
||||
logger.info("Transforming branding-only files...")
|
||||
const takeTheirsResults = await transformConflictedTakeTheirs(conflictedFiles, {
|
||||
dryRun: false,
|
||||
verbose: options.verbose,
|
||||
})
|
||||
const takeTheirsCount = takeTheirsResults.filter((r) => r.action === "transformed").length
|
||||
if (takeTheirsCount > 0) {
|
||||
logger.success(`Transformed ${takeTheirsCount} files (take upstream + Kilo branding)`)
|
||||
}
|
||||
}
|
||||
// Step 7c: Try to auto-resolve remaining conflicts with post-merge transforms
|
||||
// These handle edge cases where pre-merge transforms might have missed something
|
||||
let conflictedFiles = await git.getConflictedFiles()
|
||||
|
||||
// Step 7e: Transform Tauri/Desktop config files
|
||||
conflictedFiles = await git.getConflictedFiles()
|
||||
if (conflictedFiles.length > 0) {
|
||||
logger.info("Transforming Tauri/Desktop config files...")
|
||||
const tauriResults = await transformConflictedTauri(conflictedFiles, {
|
||||
dryRun: false,
|
||||
verbose: options.verbose,
|
||||
})
|
||||
const tauriCount = tauriResults.filter((r) => r.action === "transformed").length
|
||||
if (tauriCount > 0) {
|
||||
logger.success(`Transformed ${tauriCount} Tauri config files`)
|
||||
}
|
||||
}
|
||||
logger.info("Attempting to auto-resolve remaining conflicts...")
|
||||
|
||||
// Step 7f: Transform package.json files
|
||||
conflictedFiles = await git.getConflictedFiles()
|
||||
if (conflictedFiles.length > 0) {
|
||||
logger.info("Transforming package.json files...")
|
||||
const pkgResults = await transformConflictedPackageJson(conflictedFiles, {
|
||||
dryRun: false,
|
||||
verbose: options.verbose,
|
||||
})
|
||||
const pkgCount = pkgResults.filter((r) => r.action === "transformed").length
|
||||
if (pkgCount > 0) {
|
||||
logger.success(`Transformed ${pkgCount} package.json files`)
|
||||
// Transform i18n files
|
||||
const i18nResults = await transformConflictedI18n(conflictedFiles, { dryRun: false, verbose: options.verbose })
|
||||
const i18nTransformed = i18nResults.filter((r) => r.replacements > 0).length
|
||||
if (i18nTransformed > 0) {
|
||||
logger.success(`Auto-resolved ${i18nTransformed} i18n conflicts`)
|
||||
}
|
||||
}
|
||||
|
||||
// Step 7g: Transform script files
|
||||
conflictedFiles = await git.getConflictedFiles()
|
||||
if (conflictedFiles.length > 0) {
|
||||
logger.info("Transforming script files...")
|
||||
const scriptResults = await transformConflictedScripts(conflictedFiles, {
|
||||
dryRun: false,
|
||||
verbose: options.verbose,
|
||||
})
|
||||
const scriptCount = scriptResults.filter((r) => r.action === "transformed").length
|
||||
if (scriptCount > 0) {
|
||||
logger.success(`Transformed ${scriptCount} script files`)
|
||||
// Transform branding-only files
|
||||
conflictedFiles = await git.getConflictedFiles()
|
||||
if (conflictedFiles.length > 0) {
|
||||
const takeTheirsResults = await transformConflictedTakeTheirs(conflictedFiles, {
|
||||
dryRun: false,
|
||||
verbose: options.verbose,
|
||||
})
|
||||
const takeTheirsCount = takeTheirsResults.filter((r) => r.action === "transformed").length
|
||||
if (takeTheirsCount > 0) {
|
||||
logger.success(`Auto-resolved ${takeTheirsCount} branding conflicts`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 7h: Transform extension files
|
||||
conflictedFiles = await git.getConflictedFiles()
|
||||
if (conflictedFiles.length > 0) {
|
||||
logger.info("Transforming extension files...")
|
||||
const extResults = await transformConflictedExtensions(conflictedFiles, {
|
||||
dryRun: false,
|
||||
verbose: options.verbose,
|
||||
})
|
||||
const extCount = extResults.filter((r) => r.action === "transformed").length
|
||||
if (extCount > 0) {
|
||||
logger.success(`Transformed ${extCount} extension files`)
|
||||
// Transform Tauri files
|
||||
conflictedFiles = await git.getConflictedFiles()
|
||||
if (conflictedFiles.length > 0) {
|
||||
const tauriResults = await transformConflictedTauri(conflictedFiles, {
|
||||
dryRun: false,
|
||||
verbose: options.verbose,
|
||||
})
|
||||
const tauriCount = tauriResults.filter((r) => r.action === "transformed").length
|
||||
if (tauriCount > 0) {
|
||||
logger.success(`Auto-resolved ${tauriCount} Tauri conflicts`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 7i: Transform web/docs files
|
||||
conflictedFiles = await git.getConflictedFiles()
|
||||
if (conflictedFiles.length > 0) {
|
||||
logger.info("Transforming web/docs files...")
|
||||
const webResults = await transformConflictedWeb(conflictedFiles, {
|
||||
dryRun: false,
|
||||
verbose: options.verbose,
|
||||
})
|
||||
const webCount = webResults.filter((r) => r.action === "transformed").length
|
||||
if (webCount > 0) {
|
||||
logger.success(`Transformed ${webCount} web/docs files`)
|
||||
// Transform package.json files
|
||||
conflictedFiles = await git.getConflictedFiles()
|
||||
if (conflictedFiles.length > 0) {
|
||||
const pkgResults = await transformConflictedPackageJson(conflictedFiles, {
|
||||
dryRun: false,
|
||||
verbose: options.verbose,
|
||||
})
|
||||
const pkgCount = pkgResults.filter((r) => r.action === "transformed").length
|
||||
if (pkgCount > 0) {
|
||||
logger.success(`Auto-resolved ${pkgCount} package.json conflicts`)
|
||||
}
|
||||
}
|
||||
|
||||
// Transform script files
|
||||
conflictedFiles = await git.getConflictedFiles()
|
||||
if (conflictedFiles.length > 0) {
|
||||
const scriptResults = await transformConflictedScripts(conflictedFiles, {
|
||||
dryRun: false,
|
||||
verbose: options.verbose,
|
||||
})
|
||||
const scriptCount = scriptResults.filter((r) => r.action === "transformed").length
|
||||
if (scriptCount > 0) {
|
||||
logger.success(`Auto-resolved ${scriptCount} script conflicts`)
|
||||
}
|
||||
}
|
||||
|
||||
// Transform extension files
|
||||
conflictedFiles = await git.getConflictedFiles()
|
||||
if (conflictedFiles.length > 0) {
|
||||
const extResults = await transformConflictedExtensions(conflictedFiles, {
|
||||
dryRun: false,
|
||||
verbose: options.verbose,
|
||||
})
|
||||
const extCount = extResults.filter((r) => r.action === "transformed").length
|
||||
if (extCount > 0) {
|
||||
logger.success(`Auto-resolved ${extCount} extension conflicts`)
|
||||
}
|
||||
}
|
||||
|
||||
// Transform web/docs files
|
||||
conflictedFiles = await git.getConflictedFiles()
|
||||
if (conflictedFiles.length > 0) {
|
||||
const webResults = await transformConflictedWeb(conflictedFiles, {
|
||||
dryRun: false,
|
||||
verbose: options.verbose,
|
||||
})
|
||||
const webCount = webResults.filter((r) => r.action === "transformed").length
|
||||
if (webCount > 0) {
|
||||
logger.success(`Auto-resolved ${webCount} web/docs conflicts`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,24 +475,20 @@ async function main() {
|
||||
logger.warn(`${remaining.length} conflicts require manual resolution:`)
|
||||
logger.list(remaining)
|
||||
logger.info("")
|
||||
logger.info("These conflicts likely contain kilocode_change markers or have actual code differences.")
|
||||
logger.info("After resolving conflicts, run:")
|
||||
logger.info(" git add -A && git commit -m 'resolve merge conflicts'")
|
||||
} else {
|
||||
await git.stageAll()
|
||||
await git.commit(`merge: upstream ${targetVersion.tag}`)
|
||||
logger.success("Merge completed")
|
||||
logger.success("Merge completed - all conflicts auto-resolved!")
|
||||
}
|
||||
} else {
|
||||
logger.success("Merge completed without conflicts")
|
||||
|
||||
// Even without conflicts, transform i18n files to ensure Kilo branding
|
||||
logger.info("Transforming i18n files for Kilo branding...")
|
||||
const i18nResults = await transformAllI18n({ dryRun: false, verbose: options.verbose })
|
||||
const i18nTransformed = i18nResults.filter((r) => r.replacements > 0).length
|
||||
if (i18nTransformed > 0) {
|
||||
await git.stageAll()
|
||||
await git.commit(`chore: apply Kilo branding to i18n files`)
|
||||
logger.success(`Transformed ${i18nTransformed} i18n files with Kilo branding`)
|
||||
logger.success("Merge completed without conflicts!")
|
||||
await git.stageAll()
|
||||
const hasChanges = await git.hasUncommittedChanges()
|
||||
if (hasChanges) {
|
||||
await git.commit(`merge: upstream ${targetVersion.tag}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -205,6 +205,50 @@ export async function transformConflictedExtensions(
|
||||
return results
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform all extension files (pre-merge, on opencode branch)
|
||||
*/
|
||||
export async function transformAllExtensions(
|
||||
options: ExtensionTransformOptions = {},
|
||||
): Promise<ExtensionTransformResult[]> {
|
||||
const { Glob } = await import("bun")
|
||||
const results: ExtensionTransformResult[] = []
|
||||
const patterns = defaultConfig.extensionFiles
|
||||
|
||||
for (const pattern of patterns) {
|
||||
const glob = new Glob(pattern)
|
||||
|
||||
for await (const path of glob.scan({ absolute: false })) {
|
||||
const file = Bun.file(path)
|
||||
if (!(await file.exists())) continue
|
||||
|
||||
// Skip non-text files
|
||||
if (!path.endsWith(".toml") && !path.endsWith(".json") && !path.endsWith(".ts") && !path.endsWith(".js")) {
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
const content = await file.text()
|
||||
const { result, replacements } = applyExtensionTransforms(content, path, options.verbose)
|
||||
|
||||
if (replacements > 0 && !options.dryRun) {
|
||||
await Bun.write(path, result)
|
||||
success(`Transformed extension ${path}: ${replacements} replacements`)
|
||||
} else if (options.dryRun && replacements > 0) {
|
||||
info(`[DRY-RUN] Would transform extension ${path}: ${replacements} replacements`)
|
||||
}
|
||||
|
||||
results.push({ file: path, action: "transformed", replacements, dryRun: options.dryRun ?? false })
|
||||
} catch (err) {
|
||||
warn(`Failed to transform extension ${path}: ${err}`)
|
||||
results.push({ file: path, action: "failed", replacements: 0, dryRun: options.dryRun ?? false })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// CLI entry point
|
||||
if (import.meta.main) {
|
||||
const args = process.argv.slice(2)
|
||||
|
||||
@@ -195,6 +195,103 @@ export async function transformConflictedPackageJson(
|
||||
return results
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform all package.json files (pre-merge, on opencode branch)
|
||||
*/
|
||||
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)
|
||||
const changes: string[] = []
|
||||
|
||||
// 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. Transform dependencies
|
||||
if (pkg.dependencies) {
|
||||
const { result, changes: depChanges } = transformDependencies(pkg.dependencies)
|
||||
if (depChanges.length > 0) {
|
||||
pkg.dependencies = result
|
||||
changes.push(...depChanges.map((c) => `dependencies: ${c}`))
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Transform devDependencies
|
||||
if (pkg.devDependencies) {
|
||||
const { result, changes: devChanges } = transformDependencies(pkg.devDependencies)
|
||||
if (devChanges.length > 0) {
|
||||
pkg.devDependencies = result
|
||||
changes.push(...devChanges.map((c) => `devDependencies: ${c}`))
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Transform peerDependencies
|
||||
if (pkg.peerDependencies) {
|
||||
const { result, changes: peerChanges } = transformDependencies(pkg.peerDependencies)
|
||||
if (peerChanges.length > 0) {
|
||||
pkg.peerDependencies = result
|
||||
changes.push(...peerChanges.map((c) => `peerDependencies: ${c}`))
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 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}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
@@ -171,6 +171,43 @@ export async function transformConflictedScripts(
|
||||
return results
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform all script files (pre-merge, on opencode branch)
|
||||
*/
|
||||
export async function transformAllScripts(options: ScriptTransformOptions = {}): Promise<ScriptTransformResult[]> {
|
||||
const { Glob } = await import("bun")
|
||||
const results: ScriptTransformResult[] = []
|
||||
const patterns = defaultConfig.scriptFiles
|
||||
|
||||
for (const pattern of patterns) {
|
||||
const glob = new Glob(pattern)
|
||||
|
||||
for await (const path of glob.scan({ absolute: false })) {
|
||||
const file = Bun.file(path)
|
||||
if (!(await file.exists())) continue
|
||||
|
||||
try {
|
||||
const content = await file.text()
|
||||
const { result, replacements } = applyScriptTransforms(content, options.verbose)
|
||||
|
||||
if (replacements > 0 && !options.dryRun) {
|
||||
await Bun.write(path, result)
|
||||
success(`Transformed script ${path}: ${replacements} replacements`)
|
||||
} else if (options.dryRun && replacements > 0) {
|
||||
info(`[DRY-RUN] Would transform script ${path}: ${replacements} replacements`)
|
||||
}
|
||||
|
||||
results.push({ file: path, action: "transformed", replacements, dryRun: options.dryRun ?? false })
|
||||
} catch (err) {
|
||||
warn(`Failed to transform script ${path}: ${err}`)
|
||||
results.push({ file: path, action: "failed", replacements: 0, dryRun: options.dryRun ?? false })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// CLI entry point
|
||||
if (import.meta.main) {
|
||||
const args = process.argv.slice(2)
|
||||
|
||||
@@ -241,6 +241,45 @@ export function shouldTakeTheirs(file: string, patterns?: string[]): boolean {
|
||||
return matchesPattern(file, p)
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform all files matching take-theirs patterns (pre-merge, on opencode branch)
|
||||
* This applies branding transforms to files that exist on the current branch
|
||||
*/
|
||||
export async function transformAllTakeTheirs(options: TakeTheirsOptions = {}): Promise<TakeTheirsResult[]> {
|
||||
const { Glob } = await import("bun")
|
||||
const results: TakeTheirsResult[] = []
|
||||
const patterns = options.patterns || defaultConfig.takeTheirsAndTransform
|
||||
|
||||
for (const pattern of patterns) {
|
||||
const glob = new Glob(pattern)
|
||||
|
||||
for await (const path of glob.scan({ absolute: false })) {
|
||||
// Skip if file doesn't exist
|
||||
const file = Bun.file(path)
|
||||
if (!(await file.exists())) continue
|
||||
|
||||
try {
|
||||
const content = await file.text()
|
||||
const { result, replacements } = applyBrandingTransforms(content, options.verbose)
|
||||
|
||||
if (replacements > 0 && !options.dryRun) {
|
||||
await Bun.write(path, result)
|
||||
success(`Transformed ${path}: ${replacements} branding replacements`)
|
||||
} else if (options.dryRun && replacements > 0) {
|
||||
info(`[DRY-RUN] Would transform ${path}: ${replacements} branding replacements`)
|
||||
}
|
||||
|
||||
results.push({ file: path, action: "transformed", replacements, dryRun: options.dryRun ?? false })
|
||||
} catch (err) {
|
||||
warn(`Failed to transform ${path}: ${err}`)
|
||||
results.push({ file: path, action: "failed", replacements: 0, dryRun: options.dryRun ?? false })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// CLI entry point
|
||||
if (import.meta.main) {
|
||||
const args = process.argv.slice(2)
|
||||
|
||||
@@ -263,6 +263,43 @@ export async function transformConflictedTauri(
|
||||
return results
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform all Tauri files (pre-merge, on opencode branch)
|
||||
*/
|
||||
export async function transformAllTauri(options: TauriTransformOptions = {}): Promise<TauriTransformResult[]> {
|
||||
const { Glob } = await import("bun")
|
||||
const results: TauriTransformResult[] = []
|
||||
const patterns = defaultConfig.tauriFiles
|
||||
|
||||
for (const pattern of patterns) {
|
||||
const glob = new Glob(pattern)
|
||||
|
||||
for await (const path of glob.scan({ absolute: false })) {
|
||||
const file = Bun.file(path)
|
||||
if (!(await file.exists())) continue
|
||||
|
||||
try {
|
||||
const content = await file.text()
|
||||
const { result, replacements } = applyTauriTransforms(content, path, options.verbose)
|
||||
|
||||
if (replacements > 0 && !options.dryRun) {
|
||||
await Bun.write(path, result)
|
||||
success(`Transformed Tauri ${path}: ${replacements} replacements`)
|
||||
} else if (options.dryRun && replacements > 0) {
|
||||
info(`[DRY-RUN] Would transform Tauri ${path}: ${replacements} replacements`)
|
||||
}
|
||||
|
||||
results.push({ file: path, action: "transformed", replacements, dryRun: options.dryRun ?? false })
|
||||
} catch (err) {
|
||||
warn(`Failed to transform Tauri ${path}: ${err}`)
|
||||
results.push({ file: path, action: "failed", replacements: 0, dryRun: options.dryRun ?? false })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// CLI entry point
|
||||
if (import.meta.main) {
|
||||
const args = process.argv.slice(2)
|
||||
|
||||
@@ -231,6 +231,43 @@ export async function transformConflictedWeb(
|
||||
return results
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform all web/docs files (pre-merge, on opencode branch)
|
||||
*/
|
||||
export async function transformAllWeb(options: WebTransformOptions = {}): Promise<WebTransformResult[]> {
|
||||
const { Glob } = await import("bun")
|
||||
const results: WebTransformResult[] = []
|
||||
const patterns = defaultConfig.webFiles
|
||||
|
||||
for (const pattern of patterns) {
|
||||
const glob = new Glob(pattern)
|
||||
|
||||
for await (const path of glob.scan({ absolute: false })) {
|
||||
const file = Bun.file(path)
|
||||
if (!(await file.exists())) continue
|
||||
|
||||
try {
|
||||
const content = await file.text()
|
||||
const { result, replacements } = applyWebTransforms(content, options.verbose)
|
||||
|
||||
if (replacements > 0 && !options.dryRun) {
|
||||
await Bun.write(path, result)
|
||||
success(`Transformed web ${path}: ${replacements} replacements`)
|
||||
} else if (options.dryRun && replacements > 0) {
|
||||
info(`[DRY-RUN] Would transform web ${path}: ${replacements} replacements`)
|
||||
}
|
||||
|
||||
results.push({ file: path, action: "transformed", replacements, dryRun: options.dryRun ?? false })
|
||||
} catch (err) {
|
||||
warn(`Failed to transform web ${path}: ${err}`)
|
||||
results.push({ file: path, action: "failed", replacements: 0, dryRun: options.dryRun ?? false })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// CLI entry point
|
||||
if (import.meta.main) {
|
||||
const args = process.argv.slice(2)
|
||||
|
||||
Reference in New Issue
Block a user