Skip to content

Commit 9ffadaa

Browse files
committed
Implement strategies and unit tests
1 parent b277cef commit 9ffadaa

File tree

5 files changed

+372
-73
lines changed

5 files changed

+372
-73
lines changed

README.md

Lines changed: 71 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Git Json Resolver Semver <img src="https://raw.githubusercontent.com/mayank1513/mayank1513/main/popper.png" style="height: 40px"/>
1+
# git-json-resolver-semver <img src="https://raw.githubusercontent.com/mayank1513/mayank1513/main/popper.png" style="height: 40px"/>
22

33
[![test](https://github.com/react18-tools/git-json-resolver-semver/actions/workflows/test.yml/badge.svg)](https://github.com/react18-tools/git-json-resolver-semver/actions/workflows/test.yml)
44
[![Maintainability](https://qlty.sh/gh/react18-tools/projects/git-json-resolver-semver/maintainability.svg)](https://qlty.sh/gh/react18-tools/projects/git-json-resolver-semver)
@@ -7,25 +7,27 @@
77
[![Downloads](https://img.jsdelivr.com/img.shields.io/npm/d18m/git-json-resolver-semver.svg)](https://www.npmjs.com/package/git-json-resolver-semver)
88
![npm bundle size](https://img.shields.io/bundlephobia/minzip/git-json-resolver-semver)
99

10-
Git Json Resolver Semver is a comprehensive library designed to unlock the full potential of React 18 server components. It provides customizable loading animation components and a fullscreen loader container, seamlessly integrating with React and Next.js.
10+
**Semver-aware plugin for [`git-json-resolver`](https://github.com/react18-tools/git-json-resolver)** — resolve JSON version conflicts (e.g., `package.json`) via semantic-version strategies.
1111

12-
✅ Fully Treeshakable (import from `git-json-resolver-semver/client/loader-container`)
12+
**Strategies (this release):**
1313

14-
✅ Fully TypeScript Supported
14+
- `semver-max` → pick the higher version
15+
- `semver-min` → pick the lower version
16+
- `semver-ours` → prefer ours if valid, else (optionally) prefer valid theirs
17+
- `semver-theirs` → prefer theirs if valid, else (optionally) prefer valid ours
1518

16-
✅ Leverages the power of React 18 Server components
19+
---
1720

18-
✅ Compatible with all React 18 build systems/tools/frameworks
21+
## ✨ Features
1922

20-
✅ Documented with [Typedoc](https://react18-tools.github.io/git-json-resolver-semver) ([Docs](https://react18-tools.github.io/git-json-resolver-semver))
23+
- Avoid manual conflict resolution in `package.json`
24+
- Small & tree-shakable (0 runtime dependencies)
25+
- Works with **direct import** or **dynamic plugin loading**
26+
- TypeScript types included
2127

22-
✅ Examples for Next.js, and Vite
28+
> <img src="https://raw.githubusercontent.com/mayank1513/mayank1513/main/popper.png" style="height: 20px"/> Star the repo if it saved your merge. And and also share it with your friends.
2329
24-
> <img src="https://raw.githubusercontent.com/mayank1513/mayank1513/main/popper.png" style="height: 20px"/> Star [this repository](https://github.com/react18-tools/git-json-resolver-semver) and share it with your friends.
25-
26-
## Getting Started
27-
28-
### Installation
30+
## 📦 Install
2931

3032
```bash
3133
pnpm add git-json-resolver-semver
@@ -43,95 +45,91 @@ npm install git-json-resolver-semver
4345
yarn add git-json-resolver-semver
4446
```
4547

46-
## Want Lite Version? [![npm bundle size](https://img.shields.io/bundlephobia/minzip/git-json-resolver-semver-lite)](https://www.npmjs.com/package/git-json-resolver-semver-lite) [![Version](https://img.shields.io/npm/v/git-json-resolver-semver-lite.svg?colorB=green)](https://www.npmjs.com/package/git-json-resolver-semver-lite) [![Downloads](https://img.jsdelivr.com/img.shields.io/npm/d18m/git-json-resolver-semver-lite.svg)](https://www.npmjs.com/package/git-json-resolver-semver-lite)
48+
Peer dependencies:
4749

4850
```bash
49-
pnpm add git-json-resolver-semver-lite
51+
pnpm install git-json-resolver
5052
```
5153

52-
**or**
54+
---
5355

54-
```bash
55-
npm install git-json-resolver-semver-lite
56-
```
56+
## 🚀 Usage
5757

58-
**or**
58+
### 1. Direct Import
5959

60-
```bash
61-
yarn add git-json-resolver-semver-lite
62-
```
63-
64-
> You need `r18gs` as a peer-dependency
65-
66-
### Import Styles
67-
68-
You can import styles globally or within specific components.
69-
70-
```css
71-
/* globals.css */
72-
@import "git-json-resolver-semver/dist";
73-
```
60+
```ts
61+
import { semverMax } from "git-json-resolver-semver";
62+
import { resolveConflicts } from "git-json-resolver";
7463

75-
```tsx
76-
// layout.tsx
77-
import "git-json-resolver-semver/dist/index.css";
64+
await resolveConflicts({
65+
customStrategies: {
66+
"semver-max": semverMax,
67+
},
68+
rules: {
69+
"dependencies.react": ["semver-max"],
70+
"devDependencies.vitest": ["semver-min"],
71+
},
72+
});
7873
```
7974

80-
For selective imports:
75+
### 2. Dynamic Loading
8176

82-
```css
83-
/* globals.css */
84-
@import "git-json-resolver-semver/dist/client"; /** required if you are using LoaderContainer */
85-
@import "git-json-resolver-semver/dist/server/bars/bars1";
77+
```json
78+
{
79+
"plugins": ["git-json-resolver-semver"],
80+
"rules": {
81+
"dependencies.react": ["semver-max"],
82+
"devDependencies.vitest": ["semver-min"]
83+
}
84+
}
8685
```
8786

88-
### Usage
87+
**_or_** TypeScript Config
8988

90-
Using loaders is straightforward.
89+
```ts
90+
// git-json-resolver.config.ts
91+
import type { Config } from "git-json-resolver";
9192

92-
```tsx
93-
import { Bars1 } from "git-json-resolver-semver/dist/server/bars/bars1";
93+
const config: Config = {
94+
plugins: ["git-json-resolver-semver"],
95+
rules: {
96+
"dependencies.react": ["semver-max"],
97+
"devDependencies.vitest": ["semver-min"],
98+
},
99+
};
94100

95-
export default function MyComponent() {
96-
return someCondition ? <Bars1 /> : <>Something else...</>;
97-
}
101+
export default config;
98102
```
99103

100-
For detailed API and options, refer to [the API documentation](https://react18-tools.github.io/git-json-resolver-semver).
104+
---
101105

102-
**Using LoaderContainer**
106+
## ⚙️ Behavior notes
103107

104-
`LoaderContainer` is a fullscreen component. You can add this component directly in your layout and then use `useLoader` hook to toggle its visibility.
108+
- **strict** mode (default) accepts only `x.y.z`. Set non-strict to allow prereleases/ranges.
109+
- **preferValid** (default) returns the valid side when the other is invalid.
110+
- **fallback** controls behavior when neither side is valid (`ours` | `theirs` | `continue` | `error`).
105111

106-
```tsx
107-
// layout.tsx
108-
<LoaderContainer />
109-
...
110-
```
112+
## ⚙️ Strategies
111113

112-
```tsx
113-
// some other page or component
114-
import { useLoader } from "git-json-resolver-semver/dist/hooks";
115-
116-
export default MyComponent() {
117-
const { setLoading } = useLoader();
118-
useCallback(()=>{
119-
setLoading(true);
120-
...do some work
121-
setLoading(false);
122-
}, [])
123-
...
124-
}
125-
```
114+
| Strategy | Behavior | Example (`ours` vs `theirs`) | Result |
115+
| --------------- | --------------------------------------------------------------------- | ---------------------------- | ------- |
116+
| `semver-max` | Picks the higher valid semver | `1.2.3` vs `1.3.0` | `1.3.0` |
117+
| `semver-min` | Picks the lower valid semver | `2.0.0` vs `2.1.0` | `2.0.0` |
118+
| `semver-ours` | Picks `ours` if valid semver, else apply `preferValid` / `fallback` | `1.2.3` vs `banana` | `1.2.3` |
119+
| `semver-theirs` | Picks `theirs` if valid semver, else apply `preferValid` / `fallback` | `foo` vs `2.0.0` | `2.0.0` |
126120

127-
## License
121+
## 🙏 Acknowledgments
128122

129-
This library is licensed under the MPL-2.0 open-source license.
123+
- [`git-json-resolver`](https://github.com/...) for the plugin system
124+
- [`compare-versions`](https://github.com/omichelsen/compare-versions) for lightweight semver checks
130125

126+
## License
131127

128+
This library is licensed under the MPL-2.0 open-source license.
132129

133130
> <img src="https://raw.githubusercontent.com/mayank1513/mayank1513/main/popper.png" style="height: 20px"/> Please enroll in [our courses](https://mayank-chaudhari.vercel.app/courses) or [sponsor](https://github.com/sponsors/mayank1513) our work.
134131
135132
<hr />
136133

137134
<p align="center" style="text-align:center">with 💖 by <a href="https://mayank-chaudhari.vercel.app" target="_blank">Mayank Kumar Chaudhari</a></p>
135+
```

lib/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"@repo/typescript-config": "workspace:*",
3737
"@types/node": "^24.3.0",
3838
"@vitest/coverage-v8": "^3.2.4",
39+
"compare-versions": "^6.1.1",
3940
"esbuild-plugin-rdi": "^0.0.0",
4041
"git-json-resolver": "^1.2.0",
4142
"tsup": "^8.5.0",

lib/src/index.test.ts

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import { describe, it, expect, beforeEach } from "vitest";
2+
import plugin, { semverMax, semverMin, semverOurs, semverTheirs } from "./index";
3+
import { StrategyStatus } from "git-json-resolver";
4+
5+
const run = (fn: any, ours: any, theirs: any) => fn({ ours, theirs, base: undefined, path: [] });
6+
7+
const reset = (overrides: any = {}) => {
8+
// @ts-ignore -- ts gone mad
9+
plugin.init({
10+
strict: true,
11+
normalize: true,
12+
fallback: "continue",
13+
preferValid: true,
14+
preferRange: false,
15+
workspacePattern: "",
16+
...overrides,
17+
});
18+
};
19+
describe("git-json-resolver-semver", () => {
20+
beforeEach(() => reset());
21+
22+
describe("semver-max", () => {
23+
it("picks higher when both valid", () => {
24+
const r = run(semverMax, "1.2.3", "1.3.0");
25+
expect(r.status).toBe(StrategyStatus.OK);
26+
expect(r.value).toBe("1.3.0");
27+
});
28+
29+
it("prefers valid when only one is valid", () => {
30+
const r = run(semverMax, "1.2.3", "banana");
31+
expect(r.status).toBe(StrategyStatus.OK);
32+
expect(r.value).toBe("1.2.3");
33+
});
34+
35+
it("respects fallback=continue when both invalid", async () => {
36+
await reset({ preferValid: false, fallback: "continue" });
37+
const r = run(semverMax, "foo", "bar");
38+
expect(r.status).toBe(StrategyStatus.CONTINUE);
39+
});
40+
41+
it("respects fallback=ours/theirs/error", async () => {
42+
await reset({ preferValid: false, fallback: "ours" });
43+
let r = run(semverMax, "foo", "bar");
44+
expect(r.status).toBe(StrategyStatus.OK);
45+
expect(r.value).toBe("foo");
46+
47+
await reset({ preferValid: false, fallback: "theirs" });
48+
r = run(semverMax, "foo", "bar");
49+
expect(r.status).toBe(StrategyStatus.OK);
50+
expect(r.value).toBe("bar");
51+
52+
await reset({ preferValid: false, fallback: "error" });
53+
r = run(semverMax, "foo", "bar");
54+
expect(r.status).toBe(StrategyStatus.FAIL);
55+
});
56+
57+
it("strict=false allows prereleases/ranges", async () => {
58+
await reset({ strict: false });
59+
const r1 = run(semverMax, "1.2.3", "1.2.3-beta.1");
60+
expect(r1.status).toBe(StrategyStatus.OK);
61+
expect(r1.value).toBe("1.2.3"); // release > prerelease
62+
63+
const r2 = run(semverMax, "^1.2.3", "1.3.0");
64+
// validate("^1.2.3") is true in non-strict; compareVersions("^1.2.3", "1.3.0")
65+
// is not meaningful, so preferValid should push to 1.3.0 only when one side valid.
66+
// Here both are considered valid, so we trust compareVersions result.
67+
expect([StrategyStatus.OK, StrategyStatus.CONTINUE]).toContain(r2.status);
68+
});
69+
70+
it("strict=true rejects prereleases", async () => {
71+
await reset({ strict: true });
72+
const r = run(semverMax, "1.2.3-beta.1", "1.2.3");
73+
expect(r.status).toBe(StrategyStatus.OK);
74+
expect(r.value).toBe("1.2.3"); // only ours invalid → pick valid theirs via preferValid
75+
});
76+
});
77+
78+
describe("semver-min", () => {
79+
it("picks lower when both valid", () => {
80+
const r = run(semverMin, "2.0.0", "2.1.0");
81+
expect(r.status).toBe(StrategyStatus.OK);
82+
expect(r.value).toBe("2.0.0");
83+
});
84+
85+
it("prefers valid when only one is valid", () => {
86+
const r = run(semverMin, "banana", "2.1.0");
87+
expect(r.status).toBe(StrategyStatus.OK);
88+
expect(r.value).toBe("2.1.0");
89+
});
90+
91+
it("strict=false handles prerelease ordering", async () => {
92+
await reset({ strict: false });
93+
const r = run(semverMin, "1.2.3", "1.2.3-beta.1");
94+
expect(r.status).toBe(StrategyStatus.OK);
95+
expect(r.value).toBe("1.2.3-beta.1"); // prerelease < release
96+
});
97+
});
98+
99+
describe("semver-ours / semver-theirs", () => {
100+
it("semver-ours returns ours when ours is valid", () => {
101+
const r = run(semverOurs, "1.0.0", "2.0.0");
102+
expect(r.status).toBe(StrategyStatus.OK);
103+
expect(r.value).toBe("1.0.0");
104+
});
105+
106+
it("semver-ours falls back to valid theirs when ours invalid", () => {
107+
const r = run(semverOurs, "banana", "1.0.1");
108+
expect(r.status).toBe(StrategyStatus.OK);
109+
expect(r.value).toBe("1.0.1");
110+
});
111+
112+
it("semver-theirs returns theirs when theirs is valid", () => {
113+
const r = run(semverTheirs, "1.2.3", "2.0.0");
114+
expect(r.status).toBe(StrategyStatus.OK);
115+
expect(r.value).toBe("2.0.0");
116+
});
117+
118+
it("semver-theirs falls back to valid ours when theirs invalid", () => {
119+
const r = run(semverTheirs, "1.2.3", "carrot");
120+
expect(r.status).toBe(StrategyStatus.OK);
121+
expect(r.value).toBe("1.2.3");
122+
});
123+
124+
it("falls back per config when neither valid", async () => {
125+
await reset({ preferValid: false, fallback: "theirs" });
126+
const r = run(semverOurs, "foo", "bar");
127+
expect(r.status).toBe(StrategyStatus.OK);
128+
expect(r.value).toBe("bar");
129+
});
130+
});
131+
});

0 commit comments

Comments
 (0)