This commit is contained in:
Stefan Pejcic
2024-11-07 19:03:37 +01:00
parent c6df945ed5
commit 09f9f9502d
2472 changed files with 620417 additions and 0 deletions

View File

@@ -0,0 +1,239 @@
import type {
NpmOutdatedResponse,
RefinePackageInstalledVersionData,
} from "@definitions/package";
import * as checkUpdates from "./index";
import * as packageUtils from "@utils/package";
const { getOutdatedRefinePackages } = checkUpdates;
test("Get outdated refine packages", async () => {
const testCases: {
input: NpmOutdatedResponse;
output: RefinePackageInstalledVersionData[];
}[] = [
{
input: {
"@refinedev/core": {
current: "1.0.0",
wanted: "1.0.1",
latest: "2.0.0",
dependent: "",
location: "",
},
"@refinedev/cli": {
current: "1.1.1",
wanted: "1.1.1",
latest: "1.1.0",
dependent: "",
location: "",
},
"@pankod/canvas2video": {
current: "1.1.1",
wanted: "1.1.1",
latest: "1.1.1",
dependent: "",
location: "",
},
"@owner/package-name": {
current: "1.1.1",
wanted: "1.1.1",
latest: "1.1.0",
dependent: "",
location: "",
},
"@owner/package-name1": {
current: "N/A",
wanted: "undefined",
latest: "NaN",
dependent: "",
location: "",
},
"@owner/refine-react": {
current: "1.0.0",
wanted: "1.0.1",
latest: "2.0.0",
dependent: "",
location: "",
},
},
output: [
{
name: "@refinedev/core",
current: "1.0.0",
wanted: "1.0.1",
latest: "2.0.0",
changelog: "https://c.refine.dev/core",
dependent: "",
location: "",
},
],
},
];
for (const testCase of testCases) {
jest
.spyOn(checkUpdates, "getOutdatedPackageList")
.mockResolvedValue(testCase.input);
const result = await getOutdatedRefinePackages();
expect(result).toEqual(testCase.output);
}
});
describe("getWantedWithPreferredWildcard", () => {
it("package not found in package.json", () => {
jest.spyOn(packageUtils, "getDependenciesWithVersion").mockReturnValue({});
const result = checkUpdates.getWantedWithPreferredWildcard(
"@refinedev/core",
"1.0.1",
);
expect(result).toEqual("^1.0.1");
});
it("with carret", () => {
jest.spyOn(packageUtils, "getDependenciesWithVersion").mockReturnValue({
"@refinedev/core": "^1.0.0",
});
const result = checkUpdates.getWantedWithPreferredWildcard(
"@refinedev/core",
"1.0.1",
);
expect(result).toEqual("^1.0.1");
});
it("with tilda", () => {
jest.spyOn(packageUtils, "getDependenciesWithVersion").mockReturnValue({
"@refinedev/core": "~1.0.0",
});
const result = checkUpdates.getWantedWithPreferredWildcard(
"@refinedev/core",
"1.0.1",
);
expect(result).toEqual("~1.0.1");
});
it("without caret and tilda", () => {
jest.spyOn(packageUtils, "getDependenciesWithVersion").mockReturnValue({
"@refinedev/core": "1.0.0",
});
const result = checkUpdates.getWantedWithPreferredWildcard(
"@refinedev/core",
"1.0.1",
);
expect(result).toEqual("1.0.1");
});
it("with `.x.x`", () => {
jest.spyOn(packageUtils, "getDependenciesWithVersion").mockReturnValue({
"@refinedev/core": "1.x.x",
});
const result = checkUpdates.getWantedWithPreferredWildcard(
"@refinedev/core",
"1.10.1",
);
expect(result).toEqual("1.x.x");
});
it("with `.x`", () => {
jest.spyOn(packageUtils, "getDependenciesWithVersion").mockReturnValue({
"@refinedev/core": "1.1.x",
});
const result = checkUpdates.getWantedWithPreferredWildcard(
"@refinedev/core",
"1.1.10",
);
expect(result).toEqual("1.1.x");
});
it("with `latest`", () => {
jest.spyOn(packageUtils, "getDependenciesWithVersion").mockReturnValue({
"@refinedev/core": "latest",
});
const result = checkUpdates.getWantedWithPreferredWildcard(
"@refinedev/core",
"3.1.1",
);
expect(result).toEqual("latest");
});
it("with range", () => {
jest.spyOn(packageUtils, "getDependenciesWithVersion").mockReturnValue({
"@refinedev/core": ">=1.0.0 <=1.1.9",
});
const result = checkUpdates.getWantedWithPreferredWildcard(
"@refinedev/core",
"1.0.0-rc.10",
);
expect(result).toEqual(">=1.0.0 <=1.1.9");
});
it("multiple sets", () => {
jest.spyOn(packageUtils, "getDependenciesWithVersion").mockReturnValue({
"@refinedev/core": "^2 <2.2 || > 2.3",
});
const result = checkUpdates.getWantedWithPreferredWildcard(
"@refinedev/core",
"2.3.1",
);
expect(result).toEqual("^2 <2.2 || > 2.3");
});
it("with `*`", () => {
jest.spyOn(packageUtils, "getDependenciesWithVersion").mockReturnValue({
"@refinedev/core": "*",
});
const result = checkUpdates.getWantedWithPreferredWildcard(
"@refinedev/core",
"3.1.1",
);
expect(result).toEqual("*");
});
});
describe("getLatestMinorVersionOfPackage", () => {
it.each([
{
versionList: [
"1.0.0",
"1.0.1",
"1.0.2",
"1.1.0",
"1.1.1",
"1.1.2",
"2.0.0",
],
currentVersion: "1.1.0",
expected: "1.1.2",
},
{
versionList: ["1.0.0", "1.0.1"],
currentVersion: "1.0.1",
expected: "1.0.1",
},
{
versionList: [],
currentVersion: "1.0.1",
expected: "1.0.1",
},
])("should return %p", async ({ versionList, currentVersion, expected }) => {
jest
.spyOn(packageUtils, "getAllVersionsOfPackage")
.mockResolvedValueOnce(versionList);
const result = await checkUpdates.getLatestMinorVersionOfPackage(
"@refinedev/core",
currentVersion,
);
expect(result).toEqual(expected);
});
});

View File

@@ -0,0 +1,177 @@
import type { Command } from "commander";
import { printUpdateWarningTable } from "@components/update-warning-table";
import {
getAllVersionsOfPackage,
getDependenciesWithVersion,
pmCommands,
} from "@utils/package";
import execa from "execa";
import spinner from "@utils/spinner";
import type {
NpmOutdatedResponse,
RefinePackageInstalledVersionData,
} from "@definitions/package";
import semverDiff from "semver-diff";
import { maxSatisfying } from "semver";
const load = (program: Command) => {
return program
.command("check-updates")
.description("Check all installed `Refine` packages are up to date")
.action(action);
};
const action = async () => {
const packages = await spinner(isRefineUptoDate, "Checking for updates...");
if (!packages.length) {
console.log("All `Refine` packages are up to date 🎉\n");
return;
}
await printUpdateWarningTable({ data: packages });
};
/**
*
* @returns `Refine` packages that have updates.
* @returns `[]` if no Refine package found.
* @returns `[]` if all `Refine` packages are up to date.
*/
export const isRefineUptoDate = async () => {
const refinePackages = await getOutdatedRefinePackages();
return refinePackages;
};
/**
* Uses `npm outdated` command to get the list of outdated packages.
* @returns `[]` if no Refine package found.
* @returns `Refine` packages that have updates.
*/
export const getOutdatedRefinePackages = async () => {
const packages = await getOutdatedPackageList();
if (!packages) return [];
const list: RefinePackageInstalledVersionData[] = [];
Object.keys(packages).forEach((packageName) => {
const dependency = packages[packageName];
if (packageName.includes("@refinedev")) {
list.push({
...dependency,
name: packageName,
changelog: packageName.replace(/@refinedev\//, "https://c.refine.dev/"),
});
}
});
// When user has installed `next` version, it will be ahead of the `latest` version. But `npm outdated` command still returns as an outdated package.
// So we need to filter out the if `current` version is ahead of `latest` version.
// ex: in the npm registry `next` version is 1.1.1 -> [current:1.1.1, wanted:1.1.1, latest:1.1.0]
const filteredList = list.filter((item) => {
const diff = semverDiff(item.current, item.latest);
return !!diff;
});
return filteredList;
};
/**
* @returns `npm outdated` command response
*/
export const getOutdatedPackageList = async () => {
const pm = "npm";
const { stdout, timedOut } = await execa(pm, pmCommands[pm].outdatedJson, {
reject: false,
timeout: 25 * 1000,
});
if (timedOut) {
throw new Error("Timed out while checking for updates.");
}
if (!stdout) return null;
return JSON.parse(stdout) as NpmOutdatedResponse | null;
};
/**
* The `npm outdated` command's `wanted` field shows the desired update version (e.g., `^1.2.0` in `package.json` resolves to `1.2.1`).
* This function returns the version that matches the semver range in `package.json` (e.g., `^1.2.0` resolves to `^1.2.1`).
*
* @param packageName The name of the package.
* @param versionWanted The version that the user wants to update to. Wihtout semver range.
* @returns The version that satisfies the semver range in `package.json` with the preferred wildcard.
*/
export const getWantedWithPreferredWildcard = (
packageName: RefinePackageInstalledVersionData["name"],
versionWanted: RefinePackageInstalledVersionData["wanted"],
): string => {
const dependencies = getDependenciesWithVersion();
const versionInPackageJson = dependencies[packageName];
if (!versionInPackageJson) {
return `^${versionWanted}`;
}
if (versionInPackageJson === "latest") {
return "latest";
}
if (versionInPackageJson === "*") {
return "*";
}
// has range
// if the version in the package.json has a range, it means the user has installed the package with a range.
// in that case, we should not change the version. package manager will install the latest version that satisfies the semver range.
if (
[">", "<", ">=", "<=", "||"].some((char) =>
versionInPackageJson.includes(char),
)
) {
return versionInPackageJson;
}
// has `x`
// if the version in the package.json has `x` in it, it means the user has installed the package with a wildcard.
// in that case, we should not change the version. package manager will install the latest version that satisfies the semver range.
if (versionInPackageJson?.includes("x")) {
return `${versionInPackageJson}`;
}
// has tilda
if (versionInPackageJson?.startsWith("~")) {
return `~${versionWanted}`;
}
// has caret
if (versionInPackageJson?.startsWith("^")) {
return `^${versionWanted}`;
}
return versionWanted;
};
/**
*
* @param packageName to get the latest minor version of the package available on npm.
* @param version current installed version of the package. This will be used to calculate the latest minor version.
* @returns The latest minor version of the package available on npm.
*/
export const getLatestMinorVersionOfPackage = async (
packageName: RefinePackageInstalledVersionData["name"],
version: RefinePackageInstalledVersionData["wanted"],
) => {
const versionAll = await getAllVersionsOfPackage(packageName);
/**
* The `semver` package's `maxSatisfying` function returns the highest version in the list that satisfies the range.
*/
const versionLatest = maxSatisfying(versionAll, `^${version}`);
return versionLatest ?? version;
};
export default load;