mirror of
https://github.com/stefanpejcic/openpanel
synced 2025-06-26 18:28:26 +00:00
packages
This commit is contained in:
239
packages/cli/src/commands/check-updates/index.test.tsx
Normal file
239
packages/cli/src/commands/check-updates/index.test.tsx
Normal 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);
|
||||
});
|
||||
});
|
||||
177
packages/cli/src/commands/check-updates/index.tsx
Normal file
177
packages/cli/src/commands/check-updates/index.tsx
Normal 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;
|
||||
Reference in New Issue
Block a user