Skip to content

Commit 7d3f014

Browse files
authored
Merge pull request #1 from react18-tools/patch/fix-generic-types
Patch/fix-generic-types
2 parents 526a999 + 60fb92f commit 7d3f014

File tree

8 files changed

+160
-10
lines changed

8 files changed

+160
-10
lines changed

lib/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# git-json-resolver
2+
3+
## 0.0.1
4+
5+
### Patch Changes
6+
7+
- Wire generic types through resolveConflicts function for improved type safety with custom strategies

lib/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "git-json-resolver",
33
"author": "Mayank Kumar Chaudhari (https://mayank-chaudhari.vercel.app)",
44
"private": false,
5-
"version": "0.0.0",
5+
"version": "0.0.1",
66
"description": "A rules-based JSON conflict resolver that parses Git conflict markers, reconstructs ours/theirs, and merges with deterministic strategies — beyond line-based merges.",
77
"license": "MPL-2.0",
88
"main": "./dist/index.js",

lib/src/cli.test.ts

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// cli.test.ts
2+
import { describe, it, expect, vi, beforeEach } from "vitest";
3+
import fs from "node:fs";
4+
import path from "node:path";
5+
import * as child_process from "node:child_process";
6+
7+
vi.mock("node:fs");
8+
vi.mock("node:child_process");
9+
vi.mock("./index", () => ({
10+
resolveConflicts: vi.fn(),
11+
}));
12+
vi.mock("./normalizer", () => ({
13+
DEFAULT_CONFIG: { defaultStrategy: "merge" },
14+
}));
15+
16+
// Import after mocks
17+
import type { Config } from "./types";
18+
19+
// Re-import CLI helpers (not the top-level IIFE)
20+
import * as cli from "./cli";
21+
22+
describe("cli helpers", () => {
23+
beforeEach(() => {
24+
vi.clearAllMocks();
25+
});
26+
27+
describe("findGitRoot", () => {
28+
it("returns git root from execSync", () => {
29+
vi.spyOn(child_process, "execSync").mockReturnValue("/git/root\n" as any);
30+
const root = (cli as any).findGitRoot();
31+
expect(root).toBe("/git/root");
32+
});
33+
34+
it("falls back to process.cwd() on error", () => {
35+
vi.spyOn(child_process, "execSync").mockImplementation(() => {
36+
throw new Error("no git");
37+
});
38+
const root = (cli as any).findGitRoot();
39+
expect(root).toBe(process.cwd());
40+
});
41+
});
42+
43+
describe("parseArgs", () => {
44+
it("parses include/exclude/matcher/debug/strict-arrays/sidecar", () => {
45+
const argv = [
46+
"node",
47+
"cli",
48+
"--include",
49+
"a.json,b.json",
50+
"--exclude",
51+
"c.json",
52+
"--matcher",
53+
"micromatch",
54+
"--debug",
55+
"--strict-arrays",
56+
"--sidecar",
57+
];
58+
const result = (cli as any).parseArgs(argv);
59+
expect(result.overrides).toEqual({
60+
include: ["a.json", "b.json"],
61+
exclude: ["c.json"],
62+
matcher: "micromatch",
63+
debug: true,
64+
strictArrays: true,
65+
writeConflictSidecar: true,
66+
});
67+
expect(result.init).toBe(false);
68+
});
69+
70+
it("sets init flag", () => {
71+
const result = (cli as any).parseArgs(["node", "cli", "--init"]);
72+
expect(result.init).toBe(true);
73+
});
74+
75+
it("warns on unknown option", () => {
76+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
77+
(cli as any).parseArgs(["node", "cli", "--unknown"]);
78+
expect(warn).toHaveBeenCalledWith("[git-json-resolver] Unknown option: --unknown");
79+
});
80+
});
81+
82+
describe("initConfig", () => {
83+
const tmpDir = "/tmp/test-cli";
84+
const configPath = path.join(tmpDir, "git-json-resolver.config.js");
85+
86+
it("writes starter config if none exists", () => {
87+
(fs.existsSync as any).mockReturnValue(false);
88+
const writeFileSync = vi.spyOn(fs, "writeFileSync").mockImplementation(() => {});
89+
const log = vi.spyOn(console, "log").mockImplementation(() => {});
90+
91+
(cli as any).initConfig(tmpDir);
92+
93+
expect(writeFileSync).toHaveBeenCalled();
94+
expect(log).toHaveBeenCalledWith(
95+
`[git-json-resolver] Created starter config at ${configPath}`,
96+
);
97+
});
98+
99+
it("exits if config already exists", () => {
100+
(fs.existsSync as any).mockReturnValue(true);
101+
const exit = vi.spyOn(process, "exit").mockImplementation(() => {
102+
throw new Error("exit");
103+
});
104+
const error = vi.spyOn(console, "error").mockImplementation(() => {});
105+
106+
expect(() => (cli as any).initConfig(tmpDir)).toThrow("exit");
107+
expect(error).toHaveBeenCalledWith(
108+
`[git-json-resolver] Config file already exists: ${configPath}`,
109+
);
110+
expect(exit).toHaveBeenCalledWith(1);
111+
});
112+
});
113+
114+
describe("loadConfigFile", () => {
115+
it("returns {} if no config found", async () => {
116+
(fs.existsSync as any).mockReturnValue(false);
117+
const result = await (cli as any).loadConfigFile();
118+
expect(result).toEqual({});
119+
});
120+
121+
it.skip("loads config from js file", async () => {
122+
const fakeConfig: Partial<Config> = { debug: true };
123+
(fs.existsSync as any).mockReturnValue(true);
124+
vi.doMock("/git/root/git-json-resolver.config.js", () => ({
125+
default: fakeConfig,
126+
}));
127+
vi.spyOn(child_process, "execSync").mockReturnValue("/git/root\n" as any);
128+
129+
const mod = await (cli as any).loadConfigFile();
130+
expect(mod).toEqual(fakeConfig);
131+
});
132+
});
133+
});

lib/src/cli.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const CONFIG_FILENAME = "git-json-resolver.config.js";
1212
/**
1313
* Find Git root directory
1414
*/
15-
const findGitRoot = (): string => {
15+
export const findGitRoot = (): string => {
1616
try {
1717
return execSync("git rev-parse --show-toplevel", { encoding: "utf8" }).trim();
1818
} catch {
@@ -23,7 +23,7 @@ const findGitRoot = (): string => {
2323
/**
2424
* Load configuration file (js/ts) from current dir or Git root.
2525
*/
26-
const loadConfigFile = async (): Promise<Partial<Config>> => {
26+
export const loadConfigFile = async (): Promise<Partial<Config>> => {
2727
const searchDirs = [process.cwd(), findGitRoot()];
2828
const configNames = [CONFIG_FILENAME, "git-json-resolver.config.ts"];
2929

@@ -42,7 +42,7 @@ const loadConfigFile = async (): Promise<Partial<Config>> => {
4242
/**
4343
* Write a starter config file
4444
*/
45-
const initConfig = (targetDir: string) => {
45+
export const initConfig = (targetDir: string) => {
4646
const targetPath = path.join(targetDir, CONFIG_FILENAME);
4747
if (fs.existsSync(targetPath)) {
4848
console.error(`[git-json-resolver] Config file already exists: ${targetPath}`);
@@ -63,7 +63,7 @@ module.exports = ${JSON.stringify(DEFAULT_CONFIG, null, 2)};
6363
/**
6464
* CLI argument parser (minimal, no external deps).
6565
*/
66-
const parseArgs = (argv: string[]): { overrides: Partial<Config>; init?: boolean } => {
66+
export const parseArgs = (argv: string[]): { overrides: Partial<Config>; init?: boolean } => {
6767
const overrides: Partial<Config> = {};
6868
let init = false;
6969

lib/src/index.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { parseConflictContent } from "./file-parser";
33
import { serialize } from "./file-serializer";
44
import { Conflict, mergeObject } from "./merger";
55
import { normalizeConfig, NormalizedConfig } from "./normalizer";
6-
import { Config } from "./types";
6+
import { Config, InbuiltMergeStrategies } from "./types";
77
import { backupFile, listMatchingFiles } from "./utils";
88
import fs from "node:fs/promises";
99
import { reconstructConflict } from "./conflict-helper";
@@ -12,8 +12,10 @@ export * from "./types";
1212

1313
const _strategyCache = new Map<string, string[]>();
1414

15-
export const resolveConflicts = async (config: Config) => {
16-
const normalizedConfig: NormalizedConfig = await normalizeConfig(config);
15+
export const resolveConflicts = async <T extends string = InbuiltMergeStrategies>(
16+
config: Config<T>,
17+
) => {
18+
const normalizedConfig: NormalizedConfig = await normalizeConfig<T>(config);
1719
const filesEntries = await listMatchingFiles(normalizedConfig);
1820
await Promise.all(
1921
filesEntries.map(async ({ filePath, content }) => {

packages/shared/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# @repo/shared
2+
3+
## 0.0.1
4+
5+
### Patch Changes
6+
7+
- Updated dependencies
8+

packages/shared/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@repo/shared",
3-
"version": "0.0.0",
3+
"version": "0.0.1",
44
"private": true,
55
"sideEffects": false,
66
"main": "./dist/index.js",

scripts/publish-canonical.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const { execSync } = require("child_process");
22

33
// Publish canonical packages
4-
[].forEach(pkg => {
4+
["json-merge-resolver", "json-conflict-resolver"].forEach(pkg => {
55
execSync(`sed -i -e "s/name.*/name\\": \\"${pkg.replace(/\//g, "\\\\/")}\\",/" lib/package.json`);
66
execSync("cd lib && npm publish --provenance --access public");
77
});

0 commit comments

Comments
 (0)