mirror of
https://github.com/stefanpejcic/openpanel
synced 2025-06-26 18:28:26 +00:00
back
This commit is contained in:
81
packages/cli/src/utils/announcement/index.tsx
Normal file
81
packages/cli/src/utils/announcement/index.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import React from "react";
|
||||
import matter from "gray-matter";
|
||||
import boxen from "boxen";
|
||||
|
||||
import { Announcement } from "@definitions/announcement";
|
||||
import { markedTerminalRenderer } from "@utils/marked-terminal-renderer";
|
||||
|
||||
const ANNOUNCEMENT_URL =
|
||||
"https://raw.githubusercontent.com/refinedev/refine/master/packages/cli/ANNOUNCEMENTS.md";
|
||||
|
||||
const ANNOUNCEMENT_DELIMITER = "---announcement";
|
||||
|
||||
const splitAnnouncements = (feed: string) => {
|
||||
const sections = feed.split(ANNOUNCEMENT_DELIMITER);
|
||||
|
||||
return sections
|
||||
.slice(1)
|
||||
.map((section) => `${ANNOUNCEMENT_DELIMITER}${section}`);
|
||||
};
|
||||
|
||||
const parseAnnouncement = (raw: string): Announcement => {
|
||||
const fixed = raw.replace(ANNOUNCEMENT_DELIMITER, "---");
|
||||
const parsed = matter(fixed);
|
||||
const content = (
|
||||
parsed.content.length === 0
|
||||
? fixed.replace(/---/g, "")
|
||||
: parsed.content.replace(/---/g, "")
|
||||
).trim();
|
||||
|
||||
return {
|
||||
...parsed.data,
|
||||
content,
|
||||
} as Announcement;
|
||||
};
|
||||
|
||||
export const getAnnouncements = async () => {
|
||||
try {
|
||||
const response = await fetch(ANNOUNCEMENT_URL)
|
||||
.then((res) => res.text())
|
||||
.catch(() => "");
|
||||
|
||||
const announcements = splitAnnouncements(response).map((section) =>
|
||||
parseAnnouncement(section),
|
||||
);
|
||||
|
||||
return announcements;
|
||||
} catch (_) {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const printAnnouncements = async () => {
|
||||
const announcements = await getAnnouncements();
|
||||
|
||||
const visibleAnnouncements = announcements.filter(
|
||||
(a) => Boolean(a.hidden) === false,
|
||||
);
|
||||
|
||||
if (visibleAnnouncements.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const stringAnnouncements = visibleAnnouncements
|
||||
.map((a) => {
|
||||
const dash = visibleAnnouncements.length > 1 ? "— " : "";
|
||||
const content = markedTerminalRenderer(a.content);
|
||||
return `${dash}${content}`;
|
||||
})
|
||||
.join("")
|
||||
.trim();
|
||||
|
||||
console.log(
|
||||
boxen(stringAnnouncements, {
|
||||
padding: 1,
|
||||
margin: 0,
|
||||
borderStyle: "round",
|
||||
borderColor: "blueBright",
|
||||
titleAlignment: "left",
|
||||
}),
|
||||
);
|
||||
};
|
||||
24
packages/cli/src/utils/array/index.test.ts
Normal file
24
packages/cli/src/utils/array/index.test.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { findDuplicates } from "@utils/array";
|
||||
|
||||
test("Find duplicates from array", () => {
|
||||
const testCases = [
|
||||
{
|
||||
input: [],
|
||||
output: [],
|
||||
},
|
||||
|
||||
{
|
||||
input: [1, 2, 3, 3, "3", "3"],
|
||||
output: [3, "3"],
|
||||
},
|
||||
{
|
||||
input: [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5],
|
||||
output: [1, 2, 3, 4, 5],
|
||||
},
|
||||
];
|
||||
|
||||
testCases.forEach((testCase) => {
|
||||
const result = findDuplicates(testCase.input);
|
||||
expect(result).toEqual(testCase.output);
|
||||
});
|
||||
});
|
||||
5
packages/cli/src/utils/array/index.ts
Normal file
5
packages/cli/src/utils/array/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export const findDuplicates = (arr: (string | number)[]) => {
|
||||
const duplicates = arr.filter((item, index) => arr.indexOf(item) !== index);
|
||||
const unique = new Set(duplicates);
|
||||
return Array.from(unique);
|
||||
};
|
||||
61
packages/cli/src/utils/compile/index.ts
Normal file
61
packages/cli/src/utils/compile/index.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import Handlebars from "handlebars";
|
||||
import {
|
||||
readFileSync,
|
||||
readdirSync,
|
||||
createFileSync,
|
||||
writeFileSync,
|
||||
unlinkSync,
|
||||
} from "fs-extra";
|
||||
|
||||
export const compile = (filePath: string, params: any): string => {
|
||||
const content = readFileSync(filePath);
|
||||
|
||||
Handlebars.registerHelper("ifIn", function (elem, list, options) {
|
||||
if (elem.includes(list)) {
|
||||
return options.fn(Handlebars);
|
||||
}
|
||||
return options.inverse(Handlebars);
|
||||
});
|
||||
|
||||
Handlebars.registerHelper("formatInferencerComponent", function (string) {
|
||||
if (!string) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (string) {
|
||||
case "chakra-ui":
|
||||
return "ChakraUI";
|
||||
|
||||
default:
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
});
|
||||
|
||||
Handlebars.registerHelper("capitalize", function (string) {
|
||||
if (!string) {
|
||||
return;
|
||||
}
|
||||
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
});
|
||||
|
||||
const template = Handlebars.compile(content.toString());
|
||||
return template(params);
|
||||
};
|
||||
|
||||
export const compileDir = (dirPath: string, params: any) => {
|
||||
const files = readdirSync(dirPath);
|
||||
|
||||
files.forEach((file: string) => {
|
||||
const templateFilePath = `${dirPath}/${file}`;
|
||||
// create file
|
||||
const compiledFilePath = `${dirPath}/${file.replace(".hbs", "")}`;
|
||||
createFileSync(compiledFilePath);
|
||||
|
||||
// write compiled file
|
||||
writeFileSync(compiledFilePath, compile(templateFilePath, params));
|
||||
|
||||
// delete template file (*.hbs)
|
||||
unlinkSync(templateFilePath);
|
||||
});
|
||||
};
|
||||
14
packages/cli/src/utils/encode/index.ts
Normal file
14
packages/cli/src/utils/encode/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export const stringToBase64 = (str: string) => {
|
||||
if (typeof btoa !== "undefined") {
|
||||
return btoa(str);
|
||||
}
|
||||
|
||||
return Buffer.from(str).toString("base64");
|
||||
};
|
||||
|
||||
export const base64ToString = (base64: string) => {
|
||||
if (typeof atob !== "undefined") {
|
||||
return atob(base64);
|
||||
}
|
||||
return Buffer.from(base64, "base64").toString();
|
||||
};
|
||||
55
packages/cli/src/utils/env/index.test.tsx
vendored
Normal file
55
packages/cli/src/utils/env/index.test.tsx
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
import { getNodeEnv } from ".";
|
||||
|
||||
test("Get NODE_ENV", async () => {
|
||||
const testCases = [
|
||||
{
|
||||
input: "development",
|
||||
expected: "development",
|
||||
},
|
||||
{
|
||||
input: "dev",
|
||||
expected: "development",
|
||||
},
|
||||
{
|
||||
input: "Production",
|
||||
expected: "production",
|
||||
},
|
||||
{
|
||||
input: "prod",
|
||||
expected: "production",
|
||||
},
|
||||
{
|
||||
input: "test",
|
||||
expected: "test",
|
||||
},
|
||||
{
|
||||
input: "TESTING",
|
||||
expected: "test",
|
||||
},
|
||||
{
|
||||
input: "ci",
|
||||
expected: "continuous-integration",
|
||||
},
|
||||
{
|
||||
input: "UAT",
|
||||
expected: "user-acceptance-testing",
|
||||
},
|
||||
{
|
||||
input: "SIT",
|
||||
expected: "system-integration-testing",
|
||||
},
|
||||
{
|
||||
input: "another-node-env",
|
||||
expected: "custom",
|
||||
},
|
||||
{
|
||||
input: "",
|
||||
expected: "development",
|
||||
},
|
||||
];
|
||||
|
||||
for (const testCase of testCases) {
|
||||
process.env.NODE_ENV = testCase.input;
|
||||
expect(getNodeEnv()).toEqual(testCase.expected);
|
||||
}
|
||||
});
|
||||
47
packages/cli/src/utils/env/index.ts
vendored
Normal file
47
packages/cli/src/utils/env/index.ts
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
import { NODE_ENV } from "@definitions/node";
|
||||
import * as dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
const envSearchMap: Record<Exclude<NODE_ENV, "custom">, RegExp> = {
|
||||
development: /dev/i,
|
||||
production: /prod/i,
|
||||
test: /test|tst/i,
|
||||
"continuous-integration": /ci/i,
|
||||
"user-acceptance-testing": /uat/i,
|
||||
"system-integration-testing": /sit/i,
|
||||
};
|
||||
|
||||
export const getNodeEnv = (): NODE_ENV => {
|
||||
const nodeEnv = process.env.NODE_ENV;
|
||||
|
||||
if (!nodeEnv) {
|
||||
return "development";
|
||||
}
|
||||
|
||||
let env: NODE_ENV = "custom";
|
||||
|
||||
for (const [key, value] of Object.entries(envSearchMap)) {
|
||||
if (value.test(nodeEnv)) {
|
||||
env = key as NODE_ENV;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return env;
|
||||
};
|
||||
|
||||
export const ENV = {
|
||||
NODE_ENV: getNodeEnv(),
|
||||
REFINE_NO_TELEMETRY: process.env.REFINE_NO_TELEMETRY || "false",
|
||||
UPDATE_NOTIFIER_IS_DISABLED:
|
||||
process.env.UPDATE_NOTIFIER_IS_DISABLED || "false",
|
||||
UPDATE_NOTIFIER_CACHE_TTL:
|
||||
process.env.UPDATE_NOTIFIER_CACHE_TTL || 1000 * 60 * 60 * 24, // 24 hours,
|
||||
REFINE_PROXY_DOMAIN:
|
||||
process.env.REFINE_PROXY_DOMAIN || "https://refine.dev",
|
||||
REFINE_PROXY_TARGET:
|
||||
process.env.REFINE_PROXY_TARGET || "http://localhost:3000",
|
||||
REFINE_PROXY_PORT: process.env.REFINE_PROXY_PORT || "7313",
|
||||
REFINE_PROXY_REWRITE_URL:
|
||||
process.env.REFINE_REWRITE_URL || "http://localhost:7313",
|
||||
};
|
||||
9
packages/cli/src/utils/marked-terminal-renderer/index.ts
Normal file
9
packages/cli/src/utils/marked-terminal-renderer/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { renderCodeMarkdown } from "@utils/swizzle/renderCodeMarkdown";
|
||||
import { marked } from "marked";
|
||||
import TerminalRenderer from "marked-terminal";
|
||||
|
||||
export const markedTerminalRenderer = (markdown: string) => {
|
||||
return marked(markdown, {
|
||||
renderer: new TerminalRenderer({ code: renderCodeMarkdown }) as any,
|
||||
});
|
||||
};
|
||||
25
packages/cli/src/utils/os/index.ts
Normal file
25
packages/cli/src/utils/os/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import envinfo from "envinfo";
|
||||
import os from "os";
|
||||
|
||||
export const getOSType = () => {
|
||||
const osPlatform = os.type();
|
||||
|
||||
const types: Record<string, "macOS" | "Linux" | "Windows"> = {
|
||||
Darwin: "macOS",
|
||||
Linux: "Linux",
|
||||
Windows_NT: "Windows",
|
||||
};
|
||||
|
||||
return types[osPlatform];
|
||||
};
|
||||
|
||||
export const getOS = async () => {
|
||||
// returns as a ['OS', 'macOS Mojave 10.14.5']
|
||||
const [_, OSInfo] =
|
||||
(await envinfo.helpers.getOSInfo()) as unknown as string[];
|
||||
|
||||
return {
|
||||
name: getOSType(),
|
||||
version: OSInfo,
|
||||
};
|
||||
};
|
||||
46
packages/cli/src/utils/package/index.test.tsx
Normal file
46
packages/cli/src/utils/package/index.test.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { parsePackageNameAndVersion } from "@utils/package";
|
||||
|
||||
test("Get package name and version from string", () => {
|
||||
const testCases = [
|
||||
{
|
||||
input: "@refinedev/antd@2.36.2",
|
||||
output: {
|
||||
name: "@refinedev/antd",
|
||||
version: "2.36.2",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "@owner/package_name@2.36.2_beta.1",
|
||||
output: {
|
||||
name: "@owner/package_name",
|
||||
version: "2.36.2_beta.1",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "@owner/package-name",
|
||||
output: {
|
||||
name: "@owner/package-name",
|
||||
version: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "owner/package-name",
|
||||
output: {
|
||||
name: "owner/package-name",
|
||||
version: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "owner/package-name@3.2.1",
|
||||
output: {
|
||||
name: "owner/package-name",
|
||||
version: "3.2.1",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
testCases.forEach((testCase) => {
|
||||
const result = parsePackageNameAndVersion(testCase.input);
|
||||
expect(result).toEqual(testCase.output);
|
||||
});
|
||||
});
|
||||
248
packages/cli/src/utils/package/index.ts
Normal file
248
packages/cli/src/utils/package/index.ts
Normal file
@@ -0,0 +1,248 @@
|
||||
import spinner from "@utils/spinner";
|
||||
import execa from "execa";
|
||||
import { existsSync, pathExists, readFileSync, readJSON } from "fs-extra";
|
||||
import globby from "globby";
|
||||
import path from "path";
|
||||
import preferredPM from "preferred-pm";
|
||||
|
||||
// TODO: Add package.json type
|
||||
export const getPackageJson = (): any => {
|
||||
if (!existsSync("package.json")) {
|
||||
throw new Error("./package.json not found");
|
||||
}
|
||||
|
||||
return JSON.parse(readFileSync("package.json", "utf8"));
|
||||
};
|
||||
|
||||
export const getDependencies = (): string[] => {
|
||||
const packageJson = getPackageJson();
|
||||
return Object.keys(packageJson.dependencies || {});
|
||||
};
|
||||
|
||||
export const getDependenciesWithVersion = (): string[] => {
|
||||
const packageJson = getPackageJson();
|
||||
return packageJson.dependencies;
|
||||
};
|
||||
|
||||
export const getDevDependencies = (): string[] => {
|
||||
const packageJson = getPackageJson();
|
||||
return Object.keys(packageJson.devDependencies || {});
|
||||
};
|
||||
|
||||
export const getScripts = (): Record<string, string> => {
|
||||
const packageJson = getPackageJson();
|
||||
return packageJson.scripts;
|
||||
};
|
||||
|
||||
export const getInstalledRefinePackages = async () => {
|
||||
try {
|
||||
const execution = await execa("npm", ["ls", "--depth=0", "--json"], {
|
||||
reject: false,
|
||||
});
|
||||
|
||||
const dependencies = JSON.parse(execution.stdout)?.dependencies || {};
|
||||
const refineDependencies = Object.keys(dependencies).filter(
|
||||
(dependency) =>
|
||||
dependency.startsWith("@refinedev") ||
|
||||
dependency.startsWith("@pankod/refine-"),
|
||||
);
|
||||
|
||||
const normalize: {
|
||||
name: string;
|
||||
version: string;
|
||||
}[] = [];
|
||||
|
||||
for (const dependency of refineDependencies) {
|
||||
const version = dependencies[dependency].version;
|
||||
normalize.push({
|
||||
name: dependency,
|
||||
version,
|
||||
});
|
||||
}
|
||||
|
||||
return normalize;
|
||||
} catch (error) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
};
|
||||
|
||||
export const getInstalledRefinePackagesFromNodeModules = async () => {
|
||||
const REFINE_PACKAGES = ["core"];
|
||||
|
||||
try {
|
||||
const packagesFromGlobbySearch = await globby(
|
||||
"node_modules/@refinedev/*",
|
||||
{
|
||||
onlyDirectories: true,
|
||||
},
|
||||
);
|
||||
|
||||
const packageDirsFromModules = REFINE_PACKAGES.flatMap((pkg) => {
|
||||
try {
|
||||
const pkgPath = require.resolve(
|
||||
path.join("@refinedev", pkg, "package.json"),
|
||||
);
|
||||
|
||||
return [path.dirname(pkgPath)];
|
||||
} catch (err) {
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
const refinePackages: Array<{
|
||||
name: string;
|
||||
path: string;
|
||||
version: string;
|
||||
}> = [];
|
||||
|
||||
await Promise.all(
|
||||
[...packageDirsFromModules, ...packagesFromGlobbySearch].map(
|
||||
async (packageDir) => {
|
||||
const hasPackageJson = await pathExists(
|
||||
`${packageDir}/package.json`,
|
||||
);
|
||||
if (hasPackageJson) {
|
||||
const packageJson = await readJSON(
|
||||
`${packageDir}/package.json`,
|
||||
);
|
||||
|
||||
refinePackages.push({
|
||||
name: packageJson.name,
|
||||
version: packageJson.version,
|
||||
path: packageDir,
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
return refinePackages;
|
||||
} catch (err) {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const isPackageHaveRefineConfig = async (packagePath: string) => {
|
||||
return await pathExists(`${packagePath}/refine.config.js`);
|
||||
};
|
||||
|
||||
export const pmCommands = {
|
||||
npm: {
|
||||
install: ["install", "--save"],
|
||||
installDev: ["install", "--save-dev"],
|
||||
outdatedJson: ["outdated", "--json"],
|
||||
},
|
||||
yarn: {
|
||||
install: ["add"],
|
||||
installDev: ["add", "-D"],
|
||||
outdatedJson: ["outdated", "--json"],
|
||||
},
|
||||
pnpm: {
|
||||
install: ["add"],
|
||||
installDev: ["add", "-D"],
|
||||
outdatedJson: ["outdated", "--format", "json"],
|
||||
},
|
||||
};
|
||||
|
||||
export const getPreferedPM = async () => {
|
||||
const pm = await spinner(
|
||||
() => preferredPM(process.cwd()),
|
||||
"Getting package manager...",
|
||||
);
|
||||
|
||||
if (!pm) {
|
||||
throw new Error("Package manager not found.");
|
||||
}
|
||||
|
||||
return pm;
|
||||
};
|
||||
|
||||
export const installPackages = async (packages: string[]) => {
|
||||
const pm = await getPreferedPM();
|
||||
|
||||
try {
|
||||
const installCommand = pmCommands[pm.name].install;
|
||||
|
||||
const execution = execa(pm.name, [...installCommand, ...packages], {
|
||||
stdio: "inherit",
|
||||
});
|
||||
|
||||
execution.on("message", (message) => {
|
||||
console.log(message);
|
||||
});
|
||||
|
||||
execution.on("error", (error) => {
|
||||
console.log(error);
|
||||
});
|
||||
|
||||
execution.on("exit", (exitCode) => {
|
||||
if (exitCode === 0) {
|
||||
console.log("All `refine` packages updated 🎉");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Application exited with code ${exitCode}`);
|
||||
});
|
||||
} catch (error: any) {
|
||||
throw new Error(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const installPackagesSync = async (packages: string[]) => {
|
||||
const pm = await getPreferedPM();
|
||||
|
||||
try {
|
||||
const installCommand = pmCommands[pm.name].install;
|
||||
|
||||
const execution = execa.sync(
|
||||
pm.name,
|
||||
[...installCommand, ...packages],
|
||||
{
|
||||
stdio: "inherit",
|
||||
},
|
||||
);
|
||||
|
||||
if (execution.failed || execution.exitCode !== 0) {
|
||||
throw new Error(execution.stderr);
|
||||
}
|
||||
|
||||
return execution;
|
||||
} catch (error: any) {
|
||||
throw new Error(error);
|
||||
}
|
||||
};
|
||||
|
||||
export interface PackageNameAndVersion {
|
||||
name: string;
|
||||
version: string | null;
|
||||
}
|
||||
|
||||
export const parsePackageNameAndVersion = (
|
||||
str: string,
|
||||
): PackageNameAndVersion => {
|
||||
const versionStartIndex = str.lastIndexOf("@");
|
||||
|
||||
if (versionStartIndex <= 0) {
|
||||
return {
|
||||
name: str,
|
||||
version: null,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
name: str.slice(0, versionStartIndex),
|
||||
version: str.slice(versionStartIndex + 1),
|
||||
};
|
||||
};
|
||||
|
||||
export const getRefineProjectId = () => {
|
||||
const packageJson = getPackageJson();
|
||||
|
||||
return packageJson?.refine?.projectId;
|
||||
};
|
||||
|
||||
export const isDevtoolsInstalled = async () => {
|
||||
const installedPackages = await getInstalledRefinePackagesFromNodeModules();
|
||||
|
||||
return installedPackages.some((pkg) => pkg.name === "@refinedev/devtools");
|
||||
};
|
||||
79
packages/cli/src/utils/project/index.ts
Normal file
79
packages/cli/src/utils/project/index.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { ProjectTypes, UIFrameworks } from "@definitions";
|
||||
import { getDependencies, getDevDependencies } from "@utils/package";
|
||||
|
||||
export const getProjectType = (platform?: ProjectTypes): ProjectTypes => {
|
||||
if (platform) {
|
||||
return platform;
|
||||
}
|
||||
// read dependencies from package.json
|
||||
const dependencies = getDependencies();
|
||||
const devDependencies = getDevDependencies();
|
||||
|
||||
// check for craco
|
||||
// craco and react-scripts installs together. We need to check for craco first
|
||||
if (
|
||||
dependencies.includes("@craco/craco") ||
|
||||
devDependencies.includes("@craco/craco")
|
||||
) {
|
||||
return ProjectTypes.CRACO;
|
||||
}
|
||||
|
||||
// check for react-scripts
|
||||
if (
|
||||
dependencies.includes("react-scripts") ||
|
||||
devDependencies.includes("react-scripts")
|
||||
) {
|
||||
return ProjectTypes.REACT_SCRIPT;
|
||||
}
|
||||
|
||||
// check for next
|
||||
if (dependencies.includes("next") || devDependencies.includes("next")) {
|
||||
return ProjectTypes.NEXTJS;
|
||||
}
|
||||
|
||||
// check for remix
|
||||
if (
|
||||
dependencies.includes("@remix-run/react") ||
|
||||
devDependencies.includes("@remix-run/react")
|
||||
) {
|
||||
return ProjectTypes.REMIX;
|
||||
}
|
||||
|
||||
// check for vite
|
||||
if (dependencies.includes("vite") || devDependencies.includes("vite")) {
|
||||
return ProjectTypes.VITE;
|
||||
}
|
||||
|
||||
if (dependencies.includes("parcel") || devDependencies.includes("parcel")) {
|
||||
return ProjectTypes.PARCEL;
|
||||
}
|
||||
|
||||
return ProjectTypes.UNKNOWN;
|
||||
};
|
||||
|
||||
export const getUIFramework = (): UIFrameworks | undefined => {
|
||||
// read dependencies from package.json
|
||||
const dependencies = getDependencies();
|
||||
|
||||
// check for antd
|
||||
if (dependencies.includes("@refinedev/antd")) {
|
||||
return UIFrameworks.ANTD;
|
||||
}
|
||||
|
||||
// check for mui
|
||||
if (dependencies.includes("@refinedev/mui")) {
|
||||
return UIFrameworks.MUI;
|
||||
}
|
||||
|
||||
// check for chakra
|
||||
if (dependencies.includes("@refinedev/chakra-ui")) {
|
||||
return UIFrameworks.CHAKRA;
|
||||
}
|
||||
|
||||
// check for mantine
|
||||
if (dependencies.includes("@refinedev/mantine")) {
|
||||
return UIFrameworks.MANTINE;
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
67
packages/cli/src/utils/refine/index.test.tsx
Normal file
67
packages/cli/src/utils/refine/index.test.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import * as utilsPackage from "@utils/package";
|
||||
import { hasDefaultScript } from ".";
|
||||
|
||||
test("Has default script", () => {
|
||||
const testCases = [
|
||||
{
|
||||
input: {
|
||||
scripts: {
|
||||
dev: "refine dev",
|
||||
},
|
||||
},
|
||||
output: {
|
||||
dev: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: {
|
||||
scripts: {
|
||||
dev: "PORT=5252 refine dev --force",
|
||||
},
|
||||
},
|
||||
output: {
|
||||
dev: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: {
|
||||
scripts: {
|
||||
dev: "refine dev",
|
||||
},
|
||||
},
|
||||
output: {
|
||||
dev: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: {
|
||||
scripts: {
|
||||
dev: "refine dev2",
|
||||
},
|
||||
},
|
||||
output: {
|
||||
dev: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: {
|
||||
scripts: {
|
||||
dev: "refine dev;echo '1'",
|
||||
},
|
||||
},
|
||||
output: {
|
||||
dev: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
testCases.forEach((testCase) => {
|
||||
jest.spyOn(utilsPackage, "getPackageJson").mockReturnValueOnce(
|
||||
testCase.input,
|
||||
);
|
||||
|
||||
const result = hasDefaultScript();
|
||||
|
||||
expect(result).toEqual(testCase.output);
|
||||
});
|
||||
});
|
||||
22
packages/cli/src/utils/refine/index.ts
Normal file
22
packages/cli/src/utils/refine/index.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { getPackageJson } from "@utils/package";
|
||||
|
||||
type ReturnType = {
|
||||
dev: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the project has a refine script in package.json
|
||||
*/
|
||||
export const hasDefaultScript = (): ReturnType => {
|
||||
const packageJson = getPackageJson();
|
||||
|
||||
const scripts = packageJson.scripts || {};
|
||||
|
||||
const isDefault =
|
||||
((scripts?.dev || "") as string).match(/refine dev(\s|$|;){1}/) !==
|
||||
null;
|
||||
|
||||
return {
|
||||
dev: isDefault,
|
||||
};
|
||||
};
|
||||
39
packages/cli/src/utils/resource/index.spec.ts
Normal file
39
packages/cli/src/utils/resource/index.spec.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { ProjectTypes } from "@definitions/projectTypes";
|
||||
import { getProviderPath } from ".";
|
||||
|
||||
it("should get provider path", () => {
|
||||
expect(getProviderPath(ProjectTypes.NEXTJS)).toEqual({
|
||||
path: "src/providers",
|
||||
alias: "../src/providers",
|
||||
});
|
||||
|
||||
expect(getProviderPath(ProjectTypes.REMIX)).toEqual({
|
||||
path: "app/providers",
|
||||
alias: "~/providers",
|
||||
});
|
||||
|
||||
expect(getProviderPath(ProjectTypes.VITE)).toEqual({
|
||||
path: "src/providers",
|
||||
alias: "providers",
|
||||
});
|
||||
|
||||
expect(getProviderPath(ProjectTypes.REACT_SCRIPT)).toEqual({
|
||||
path: "src/providers",
|
||||
alias: "providers",
|
||||
});
|
||||
|
||||
expect(getProviderPath(ProjectTypes.CRACO)).toEqual({
|
||||
path: "src/providers",
|
||||
alias: "providers",
|
||||
});
|
||||
|
||||
expect(getProviderPath(ProjectTypes.PARCEL)).toEqual({
|
||||
path: "src/providers",
|
||||
alias: "providers",
|
||||
});
|
||||
|
||||
expect(getProviderPath(ProjectTypes.UNKNOWN)).toEqual({
|
||||
path: "src/providers",
|
||||
alias: "providers",
|
||||
});
|
||||
});
|
||||
62
packages/cli/src/utils/resource/index.ts
Normal file
62
packages/cli/src/utils/resource/index.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { ProjectTypes } from "@definitions/projectTypes";
|
||||
|
||||
export const getResourcePath = (
|
||||
projectType: ProjectTypes,
|
||||
): { path: string; alias: string } => {
|
||||
switch (projectType) {
|
||||
case ProjectTypes.NEXTJS:
|
||||
return {
|
||||
path: "src/components",
|
||||
alias: "../src/components",
|
||||
};
|
||||
case ProjectTypes.REMIX:
|
||||
return {
|
||||
path: "app/components",
|
||||
alias: "~/components",
|
||||
};
|
||||
}
|
||||
|
||||
// vite and react
|
||||
return {
|
||||
path: "src/pages",
|
||||
alias: "pages",
|
||||
};
|
||||
};
|
||||
|
||||
export const getProviderPath = (
|
||||
projectType: ProjectTypes,
|
||||
): { path: string; alias: string } => {
|
||||
switch (projectType) {
|
||||
case ProjectTypes.NEXTJS:
|
||||
return {
|
||||
path: "src/providers",
|
||||
alias: "../src/providers",
|
||||
};
|
||||
case ProjectTypes.REMIX:
|
||||
return {
|
||||
path: "app/providers",
|
||||
alias: "~/providers",
|
||||
};
|
||||
}
|
||||
|
||||
// vite and react
|
||||
return {
|
||||
path: "src/providers",
|
||||
alias: "providers",
|
||||
};
|
||||
};
|
||||
|
||||
export const getFilesPathByProject = (projectType?: ProjectTypes) => {
|
||||
switch (projectType) {
|
||||
case ProjectTypes.REMIX:
|
||||
return "./app";
|
||||
case ProjectTypes.NEXTJS:
|
||||
case ProjectTypes.REACT_SCRIPT:
|
||||
case ProjectTypes.VITE:
|
||||
case ProjectTypes.CRACO:
|
||||
case ProjectTypes.PARCEL:
|
||||
case ProjectTypes.UNKNOWN:
|
||||
default:
|
||||
return "./src";
|
||||
}
|
||||
};
|
||||
13
packages/cli/src/utils/spinner/index.ts
Normal file
13
packages/cli/src/utils/spinner/index.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import ora from "ora";
|
||||
|
||||
const spinner = async <T>(fn: () => Promise<T>, message: string) => {
|
||||
const spinner = ora({
|
||||
color: "cyan",
|
||||
text: message,
|
||||
}).start();
|
||||
const result = await fn();
|
||||
spinner.stop();
|
||||
return result;
|
||||
};
|
||||
|
||||
export default spinner;
|
||||
23
packages/cli/src/utils/swizzle/appendAfterImports.test.ts
Normal file
23
packages/cli/src/utils/swizzle/appendAfterImports.test.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { appendAfterImports } from "./appendAfterImports";
|
||||
|
||||
describe("appendAfterImports", () => {
|
||||
it("should append after imports", () => {
|
||||
const content = `
|
||||
import { foo } from "bar";
|
||||
import { bar } from "foo";
|
||||
import { baz } from "baz";
|
||||
`;
|
||||
|
||||
const append = `console.log("hello world");`;
|
||||
|
||||
const result = appendAfterImports(content, append);
|
||||
|
||||
expect(result).toEqual(`
|
||||
import { foo } from "bar";
|
||||
import { bar } from "foo";
|
||||
import { baz } from "baz";
|
||||
console.log("hello world");
|
||||
|
||||
`);
|
||||
});
|
||||
});
|
||||
18
packages/cli/src/utils/swizzle/appendAfterImports.ts
Normal file
18
packages/cli/src/utils/swizzle/appendAfterImports.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { getImports } from "./import";
|
||||
|
||||
export const appendAfterImports = (content: string, append: string): string => {
|
||||
const imports = getImports(content);
|
||||
const lastImport = imports[imports.length - 1];
|
||||
|
||||
const lastImportIndex = lastImport
|
||||
? content.indexOf(lastImport.statement)
|
||||
: content.length - 1;
|
||||
|
||||
return (
|
||||
content.slice(0, lastImportIndex + lastImport?.statement.length) +
|
||||
"\n" +
|
||||
append +
|
||||
"\n" +
|
||||
content.slice(lastImportIndex + lastImport?.statement.length)
|
||||
);
|
||||
};
|
||||
8
packages/cli/src/utils/swizzle/codes.ts
Normal file
8
packages/cli/src/utils/swizzle/codes.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export const SWIZZLE_CODES = {
|
||||
SUCCESS: "SUCCESS",
|
||||
UNKNOWN_ERROR: "UNKNOWN_ERROR",
|
||||
SOURCE_PATH_NOT_FOUND: "SOURCE_PATH_NOT_FOUND",
|
||||
TARGET_PATH_NOT_FOUND: "TARGET_PATH_NOT_FOUND",
|
||||
SOURCE_PATH_NOT_A_FILE: "SOURCE_PATH_NOT_A_FILE",
|
||||
TARGET_ALREADY_EXISTS: "TARGET_ALREADY_EXISTS",
|
||||
};
|
||||
16
packages/cli/src/utils/swizzle/getFileContent.ts
Normal file
16
packages/cli/src/utils/swizzle/getFileContent.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { readFileSync } from "fs-extra";
|
||||
import { join } from "path";
|
||||
|
||||
export function getFileContent(
|
||||
this: undefined | { absolutePackageDir?: string },
|
||||
path: string,
|
||||
): string | undefined {
|
||||
if (!this?.absolutePackageDir) {
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
return readFileSync(join(this.absolutePackageDir, path)).toString();
|
||||
} catch (err) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
17
packages/cli/src/utils/swizzle/getPathPrefix.ts
Normal file
17
packages/cli/src/utils/swizzle/getPathPrefix.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { ProjectTypes } from "@definitions/projectTypes";
|
||||
import { getProjectType } from "@utils/project";
|
||||
import { getFilesPathByProject } from "@utils/resource";
|
||||
|
||||
export const getPathPrefix = () => {
|
||||
let projectType: ProjectTypes | undefined = undefined;
|
||||
|
||||
try {
|
||||
projectType = getProjectType();
|
||||
} catch (error) {
|
||||
projectType = undefined;
|
||||
}
|
||||
|
||||
const pathPrefix = getFilesPathByProject(projectType);
|
||||
|
||||
return pathPrefix;
|
||||
};
|
||||
205
packages/cli/src/utils/swizzle/import.test.ts
Normal file
205
packages/cli/src/utils/swizzle/import.test.ts
Normal file
@@ -0,0 +1,205 @@
|
||||
import { getImports, getNameChangeInImport, reorderImports } from "./import";
|
||||
|
||||
describe("getImports", () => {
|
||||
it("should get all imports", () => {
|
||||
const content = `
|
||||
import React from "react";
|
||||
import { Button } from "antd";
|
||||
import { TextInput as AntTextInput } from "antd";
|
||||
import * as Antd from "antd";
|
||||
import { Button as AntButton, TextInput } from "antd";
|
||||
import { Button as AntButton, TextInput as AntTextInput } from "antd";
|
||||
`;
|
||||
|
||||
const expected = [
|
||||
{
|
||||
statement: 'import React from "react";',
|
||||
importPath: "react",
|
||||
defaultImport: "React",
|
||||
},
|
||||
{
|
||||
statement: 'import { Button } from "antd";',
|
||||
importPath: "antd",
|
||||
namedImports: " { Button }",
|
||||
},
|
||||
{
|
||||
statement: 'import { TextInput as AntTextInput } from "antd";',
|
||||
importPath: "antd",
|
||||
namedImports: " { TextInput as AntTextInput }",
|
||||
},
|
||||
{
|
||||
statement: 'import * as Antd from "antd";',
|
||||
importPath: "antd",
|
||||
namespaceImport: "Antd",
|
||||
},
|
||||
{
|
||||
statement:
|
||||
'import { Button as AntButton, TextInput } from "antd";',
|
||||
importPath: "antd",
|
||||
namedImports: " { Button as AntButton, TextInput }",
|
||||
},
|
||||
{
|
||||
statement:
|
||||
'import { Button as AntButton, TextInput as AntTextInput } from "antd";',
|
||||
importPath: "antd",
|
||||
namedImports:
|
||||
" { Button as AntButton, TextInput as AntTextInput }",
|
||||
},
|
||||
];
|
||||
|
||||
expect(getImports(content)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getNameChangeInImport", () => {
|
||||
it("should get all name changes", () => {
|
||||
const statement = `
|
||||
{ Button as AntButton, TextInput as AntTextInput }
|
||||
`;
|
||||
|
||||
const expected = [
|
||||
{
|
||||
statement: " Button as AntButton,",
|
||||
fromName: "Button",
|
||||
toName: "AntButton",
|
||||
afterCharacter: ",",
|
||||
},
|
||||
{
|
||||
statement: " TextInput as AntTextInput ",
|
||||
fromName: "TextInput",
|
||||
toName: "AntTextInput",
|
||||
afterCharacter: undefined,
|
||||
},
|
||||
];
|
||||
|
||||
expect(getNameChangeInImport(statement)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("reorderImports", () => {
|
||||
it("should reorder named imports", () => {
|
||||
const content = `
|
||||
import { Button, TextInput } from "zantd";
|
||||
import { useEffect } from "react";
|
||||
import { useList } from "@refinedev/core";
|
||||
`;
|
||||
|
||||
const expected = `
|
||||
import { useEffect } from "react";
|
||||
import { useList } from "@refinedev/core";
|
||||
import { Button, TextInput } from "zantd";
|
||||
`;
|
||||
|
||||
expect(reorderImports(content).trim()).toEqual(expected.trim());
|
||||
});
|
||||
|
||||
it("should merge the same module imports", () => {
|
||||
const content = `
|
||||
import { Button, TextInput } from "antd";
|
||||
import { useEffect } from "react";
|
||||
import { useList, useOtherList, } from "@refinedev/core";
|
||||
import { useOne, useOtherOne } from "@refinedev/core";
|
||||
`;
|
||||
|
||||
const expected = `
|
||||
import { useEffect } from "react";
|
||||
import { useList, useOtherList, useOne, useOtherOne } from "@refinedev/core";
|
||||
import { Button, TextInput } from "antd";
|
||||
`;
|
||||
|
||||
expect(reorderImports(content).trim()).toEqual(expected.trim());
|
||||
});
|
||||
|
||||
it("should merge default imports with named imports", () => {
|
||||
const content = `
|
||||
import { Button, TextInput } from "antd";
|
||||
import { useEffect } from "react";
|
||||
import React from "react";
|
||||
`;
|
||||
|
||||
const expected = `
|
||||
import React, { useEffect } from "react";
|
||||
import { Button, TextInput } from "antd";
|
||||
`;
|
||||
|
||||
expect(reorderImports(content).trim()).toEqual(expected.trim());
|
||||
});
|
||||
|
||||
it("should not merge namespace imports with named imports", () => {
|
||||
const content = `
|
||||
import { Button, TextInput } from "antd";
|
||||
import { useEffect } from "react";
|
||||
import * as Antd from "antd";
|
||||
`;
|
||||
|
||||
const expected = `
|
||||
import { useEffect } from "react";
|
||||
import * as Antd from "antd";
|
||||
import { Button, TextInput } from "antd";
|
||||
`;
|
||||
|
||||
expect(reorderImports(content).trim()).toEqual(expected.trim());
|
||||
});
|
||||
|
||||
it("should not merge namespace imports with default imports", () => {
|
||||
const content = `
|
||||
import React from "react";
|
||||
import * as ReactPackage from "react";
|
||||
`;
|
||||
|
||||
const expected = `
|
||||
import * as ReactPackage from "react";
|
||||
import React from "react";
|
||||
`;
|
||||
|
||||
expect(reorderImports(content).trim()).toEqual(expected.trim());
|
||||
});
|
||||
|
||||
it("should keep name changes in named imports", () => {
|
||||
const content = `
|
||||
import { Button, TextInput } from "antd";
|
||||
import { Layout as AntLayout } from "antd";
|
||||
`;
|
||||
|
||||
const expected = `
|
||||
import { Button, TextInput, Layout as AntLayout } from "antd";
|
||||
`;
|
||||
|
||||
expect(reorderImports(content).trim()).toEqual(expected.trim());
|
||||
});
|
||||
|
||||
it("should keep the imports with comments before", () => {
|
||||
const content = `
|
||||
import React from "react";
|
||||
// comment
|
||||
import { Button, TextInput } from "antd";
|
||||
import { Layout } from "antd";
|
||||
`;
|
||||
|
||||
const expected = `
|
||||
import React from "react";
|
||||
import { Layout } from "antd";
|
||||
|
||||
// comment
|
||||
import { Button, TextInput } from "antd";
|
||||
`;
|
||||
|
||||
expect(reorderImports(content).trim()).toEqual(expected.trim());
|
||||
});
|
||||
|
||||
it("should keep type imports and add them to the end", () => {
|
||||
const content = `
|
||||
import type { Layout } from "antd";
|
||||
import React from "react";
|
||||
import { Button, TextInput } from "antd";
|
||||
`;
|
||||
|
||||
const expected = `
|
||||
import React from "react";
|
||||
import { Button, TextInput } from "antd";
|
||||
import type { Layout } from "antd";
|
||||
`;
|
||||
|
||||
expect(reorderImports(content).trim()).toEqual(expected.trim());
|
||||
});
|
||||
});
|
||||
264
packages/cli/src/utils/swizzle/import.ts
Normal file
264
packages/cli/src/utils/swizzle/import.ts
Normal file
@@ -0,0 +1,264 @@
|
||||
const packageRegex =
|
||||
/import(?:(?:(?:[ \n\t]+([^ *\n\t\{\},]+)[ \n\t]*(?:,|[ \n\t]+))?([ \n\t]*\{(?:[ \n\t]*[^ \n\t"'\{\}]+[ \n\t]*,?)+\})?[ \n\t]*)|[ \n\t]*\*[ \n\t]*as[ \n\t]+([^ \n\t\{\}]+)[ \n\t]+)from[ \n\t]*(?:['"])([^'"\n]+)(?:['"])(?:;?)/g;
|
||||
|
||||
const nameChangeRegex = /((?:\w|\s|_)*)( as )((?:\w|\s|_)*)( |,)?/g;
|
||||
|
||||
export type ImportMatch = {
|
||||
statement: string;
|
||||
importPath: string;
|
||||
defaultImport?: string;
|
||||
namedImports?: string;
|
||||
namespaceImport?: string;
|
||||
};
|
||||
|
||||
export type NameChangeMatch = {
|
||||
statement: string;
|
||||
fromName: string;
|
||||
toName: string;
|
||||
afterCharacter?: string;
|
||||
};
|
||||
|
||||
export const getImports = (content: string): Array<ImportMatch> => {
|
||||
const matches = content.matchAll(packageRegex);
|
||||
|
||||
const imports: Array<ImportMatch> = [];
|
||||
|
||||
for (const match of matches) {
|
||||
const [
|
||||
statement,
|
||||
defaultImport,
|
||||
namedImports,
|
||||
namespaceImport,
|
||||
importPath,
|
||||
] = match;
|
||||
|
||||
imports.push({
|
||||
statement,
|
||||
importPath,
|
||||
...(defaultImport && { defaultImport }),
|
||||
...(namedImports && { namedImports }),
|
||||
...(namespaceImport && { namespaceImport }),
|
||||
});
|
||||
}
|
||||
|
||||
return imports?.filter(Boolean);
|
||||
};
|
||||
|
||||
export const getNameChangeInImport = (
|
||||
namedImportString: string,
|
||||
): Array<NameChangeMatch> => {
|
||||
const matches = namedImportString.matchAll(nameChangeRegex);
|
||||
|
||||
const nameChanges: Array<NameChangeMatch> = [];
|
||||
|
||||
for (const match of matches) {
|
||||
const [statement, fromName, _as, toName, afterCharacter] = match;
|
||||
|
||||
nameChanges.push({
|
||||
statement,
|
||||
fromName: fromName.trim(),
|
||||
toName: toName.trim(),
|
||||
afterCharacter,
|
||||
});
|
||||
}
|
||||
|
||||
return nameChanges;
|
||||
};
|
||||
|
||||
/** @internal */
|
||||
export const getContentBeforeImport = (
|
||||
content: string,
|
||||
importMatch: ImportMatch,
|
||||
): string => {
|
||||
// get the content before the import statement and between the last import statement and the current one
|
||||
const contentBeforeImport = content.substring(
|
||||
0,
|
||||
content.indexOf(importMatch.statement),
|
||||
);
|
||||
// get the last import statement
|
||||
const lastImportStatement = getImports(contentBeforeImport).pop();
|
||||
|
||||
// if there is no last import statement, return the content before the current import statement
|
||||
if (!lastImportStatement) {
|
||||
return contentBeforeImport;
|
||||
}
|
||||
|
||||
// get the content between the last import statement and the current one
|
||||
const contentBetweenImports = contentBeforeImport.substring(
|
||||
contentBeforeImport.indexOf(lastImportStatement?.statement) +
|
||||
lastImportStatement?.statement?.length,
|
||||
);
|
||||
|
||||
// return the content before the current import statement and between the last import statement and the current one
|
||||
return contentBetweenImports;
|
||||
};
|
||||
|
||||
/** @internal */
|
||||
export const isImportHasBeforeContent = (
|
||||
content: string,
|
||||
importMatch: ImportMatch,
|
||||
): boolean => {
|
||||
const contentBeforeImport = importMatch
|
||||
? getContentBeforeImport(content, importMatch)
|
||||
: "";
|
||||
|
||||
return !!contentBeforeImport.trim();
|
||||
};
|
||||
|
||||
const IMPORT_ORDER = ["react", "@refinedev/core", "@refinedev/"];
|
||||
|
||||
export const reorderImports = (content: string): string => {
|
||||
let newContent = content;
|
||||
// imports can have comments before them, we need to preserve those comments and import statements.
|
||||
// so we need to filter out the imports with comments before.
|
||||
const allImports = getImports(content);
|
||||
// remove `import type` imports
|
||||
const allModuleImports = allImports.filter(
|
||||
(importMatch) => !importMatch.statement.includes("import type "),
|
||||
);
|
||||
const typeImports = allImports.filter((importMatch) =>
|
||||
importMatch.statement.includes("import type"),
|
||||
);
|
||||
|
||||
const importsWithBeforeContent: ImportMatch[] = [];
|
||||
const importsWithoutBeforeContent: ImportMatch[] = [];
|
||||
|
||||
allModuleImports.forEach((importMatch) => {
|
||||
if (isImportHasBeforeContent(content, importMatch)) {
|
||||
importsWithBeforeContent.push(importMatch);
|
||||
} else {
|
||||
importsWithoutBeforeContent.push(importMatch);
|
||||
}
|
||||
});
|
||||
|
||||
// insertion point is the first import statement, others will be replaced to empty string and added to the first import line
|
||||
const insertionPoint = newContent.indexOf(
|
||||
importsWithoutBeforeContent?.[0]?.statement,
|
||||
);
|
||||
|
||||
// remove all the imports without comments before
|
||||
importsWithoutBeforeContent.forEach((importMatch) => {
|
||||
newContent = newContent.replace(importMatch.statement, "");
|
||||
});
|
||||
|
||||
// remove all type imports
|
||||
typeImports.forEach((importMatch) => {
|
||||
newContent = newContent.replace(importMatch.statement, "");
|
||||
});
|
||||
|
||||
// we need to merge the imports from the same package unless one of them is a namespace import]
|
||||
const importsByPackage = importsWithoutBeforeContent.reduce(
|
||||
(acc, importMatch) => {
|
||||
const { importPath } = importMatch;
|
||||
|
||||
if (acc[importPath]) {
|
||||
acc[importPath].push(importMatch);
|
||||
} else {
|
||||
acc[importPath] = [importMatch];
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, ImportMatch[]>,
|
||||
);
|
||||
|
||||
// merge the imports from the same package
|
||||
const mergedImports = Object.entries(importsByPackage).map(
|
||||
([importPath, importMatches]) => {
|
||||
// example: A
|
||||
const defaultImport = importMatches.find(
|
||||
(importMatch) => importMatch.defaultImport,
|
||||
);
|
||||
|
||||
// example: * as A
|
||||
const namespaceImport = importMatches.find(
|
||||
(importMatch) => importMatch.namespaceImport,
|
||||
);
|
||||
|
||||
// example: { A, B }
|
||||
// example: { A as C, B }
|
||||
// content inside the curly braces should be merged
|
||||
const namedImports = importMatches
|
||||
.filter((importMatch) => importMatch.namedImports)
|
||||
.map((importMatch) => {
|
||||
// remove curly braces and trim then split by comma (can be multiline)
|
||||
const namedImports = (importMatch.namedImports ?? "")
|
||||
.replace(/{|}/g, "")
|
||||
.trim()
|
||||
.split(",")
|
||||
.map((namedImport) => namedImport.trim());
|
||||
|
||||
return namedImports.filter(Boolean).join(", ");
|
||||
})
|
||||
.join(", ");
|
||||
|
||||
let importLine = "";
|
||||
|
||||
// default import and namespace import can not be used together
|
||||
// but we can use default import and named imports together
|
||||
// so we need to merge them
|
||||
if (namespaceImport) {
|
||||
importLine += namespaceImport.statement + "\n";
|
||||
}
|
||||
if (defaultImport || namedImports) {
|
||||
if (defaultImport && namedImports) {
|
||||
importLine += `import ${defaultImport.defaultImport}, { ${namedImports} } from "${importMatches[0].importPath}";\n`;
|
||||
} else if (defaultImport) {
|
||||
importLine += `import ${defaultImport.defaultImport} from "${importMatches[0].importPath}";\n`;
|
||||
} else {
|
||||
importLine += `import { ${namedImports} } from "${importMatches[0].importPath}";\n`;
|
||||
}
|
||||
}
|
||||
|
||||
return [importPath, importLine] as [
|
||||
importPath: string,
|
||||
importLine: string,
|
||||
];
|
||||
},
|
||||
);
|
||||
|
||||
// sort the imports without comments before
|
||||
// sort should be done by IMPORT_ORDER and alphabetically
|
||||
// priority is exact match in IMPORT_ORDER, then includes match in IMPORT_ORDER, then alphabetically
|
||||
const sortedImports = [...mergedImports].sort(
|
||||
([aImportPath], [bImportPath]) => {
|
||||
const aImportOrderIndex = IMPORT_ORDER.findIndex((order) =>
|
||||
aImportPath.includes(order),
|
||||
);
|
||||
const bImportOrderIndex = IMPORT_ORDER.findIndex((order) =>
|
||||
bImportPath.includes(order),
|
||||
);
|
||||
|
||||
if (aImportOrderIndex === bImportOrderIndex) {
|
||||
return aImportPath.localeCompare(bImportPath);
|
||||
}
|
||||
|
||||
if (aImportOrderIndex === -1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (bImportOrderIndex === -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return aImportOrderIndex - bImportOrderIndex;
|
||||
},
|
||||
);
|
||||
|
||||
// add the sorted imports to the insertion point keep the before and after content
|
||||
// add the type imports after the sorted imports
|
||||
const joinedModuleImports = sortedImports
|
||||
.map(([, importLine]) => importLine)
|
||||
.join("");
|
||||
const joinedTypeImports = typeImports
|
||||
.map((importMatch) => importMatch.statement)
|
||||
.join("\n");
|
||||
|
||||
newContent =
|
||||
newContent.substring(0, insertionPoint) +
|
||||
joinedModuleImports +
|
||||
joinedTypeImports +
|
||||
newContent.substring(insertionPoint);
|
||||
|
||||
return newContent;
|
||||
};
|
||||
22
packages/cli/src/utils/swizzle/index.ts
Normal file
22
packages/cli/src/utils/swizzle/index.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import path from "path";
|
||||
import { RefineConfig } from "@definitions";
|
||||
import { provideCliHelpers } from "./provideCliHelpers";
|
||||
|
||||
export const getRefineConfig = async (
|
||||
packagePath: string,
|
||||
isAbsolute?: boolean,
|
||||
) => {
|
||||
try {
|
||||
provideCliHelpers(packagePath, isAbsolute);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const config = require(path.join(
|
||||
isAbsolute ? packagePath : path.join(process.cwd(), packagePath),
|
||||
"refine.config.js",
|
||||
)) as RefineConfig;
|
||||
|
||||
return config;
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
25
packages/cli/src/utils/swizzle/parseSwizzleBlocks.test.ts
Normal file
25
packages/cli/src/utils/swizzle/parseSwizzleBlocks.test.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { parseSwizzleBlocks } from "./parseSwizzleBlocks";
|
||||
|
||||
describe("parseSwizzleBlocks", () => {
|
||||
it("should remove swizzle blocks", () => {
|
||||
const content = `
|
||||
1
|
||||
// swizzle-remove-start
|
||||
remove-this
|
||||
//swizzle-remove-end
|
||||
2
|
||||
/* swizzle-remove-start */
|
||||
remove-this-too
|
||||
/* swizzle-remove-end */
|
||||
`;
|
||||
|
||||
const result = parseSwizzleBlocks(content);
|
||||
|
||||
expect(result).not.toContain("remove-this");
|
||||
expect(result).not.toContain("remove-this-too");
|
||||
expect(result).toContain("1");
|
||||
expect(result).toContain("2");
|
||||
expect(result).not.toContain("swizzle-remove-start");
|
||||
expect(result).not.toContain("swizzle-remove-end");
|
||||
});
|
||||
});
|
||||
6
packages/cli/src/utils/swizzle/parseSwizzleBlocks.ts
Normal file
6
packages/cli/src/utils/swizzle/parseSwizzleBlocks.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export const parseSwizzleBlocks = (content: string) => {
|
||||
const regex =
|
||||
/(\/\/|\/\*)(\s?)swizzle-remove-start([\s\S]*?)(\/\/|\/\*)(\s?)swizzle-remove-end(\s*)(\*\/)?/g;
|
||||
|
||||
return content.replace(regex, "");
|
||||
};
|
||||
14
packages/cli/src/utils/swizzle/prettierFormat.ts
Normal file
14
packages/cli/src/utils/swizzle/prettierFormat.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { format, resolveConfig } from "prettier";
|
||||
|
||||
export const prettierFormat = async (code: string) => {
|
||||
try {
|
||||
const prettierConfig = await resolveConfig(process.cwd());
|
||||
|
||||
return format(code, {
|
||||
...(prettierConfig ?? {}),
|
||||
parser: "typescript",
|
||||
});
|
||||
} catch (err) {
|
||||
return code;
|
||||
}
|
||||
};
|
||||
33
packages/cli/src/utils/swizzle/provideCliHelpers.ts
Normal file
33
packages/cli/src/utils/swizzle/provideCliHelpers.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import path from "path";
|
||||
import * as RefineCLI from "../../index";
|
||||
import { getFileContent } from "./getFileContent";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const Module = require("module");
|
||||
const originalRequire = Module.prototype.require;
|
||||
|
||||
export const provideCliHelpers = (
|
||||
packagePath: string,
|
||||
isAbsolute?: boolean,
|
||||
) => {
|
||||
Module.prototype.require = function (...args: Parameters<NodeRequire>) {
|
||||
if ((args[0] as unknown as string) === "@refinedev/cli") {
|
||||
return {
|
||||
...RefineCLI,
|
||||
getFileContent: (filePath: string) => {
|
||||
return getFileContent.call(
|
||||
{
|
||||
absolutePackageDir: isAbsolute
|
||||
? packagePath
|
||||
: path.join(process.cwd(), packagePath),
|
||||
},
|
||||
filePath,
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
//do your thing here
|
||||
return originalRequire.apply(this, args);
|
||||
};
|
||||
};
|
||||
46
packages/cli/src/utils/swizzle/renderCodeMarkdown.ts
Normal file
46
packages/cli/src/utils/swizzle/renderCodeMarkdown.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import chalk from "chalk";
|
||||
import cardinal from "cardinal";
|
||||
import boxen from "boxen";
|
||||
|
||||
const getCodeData = (content: string): { title?: string; code: string } => {
|
||||
const titleRegexp = /^(?:\/\/\s?title:\s?)(.*?)\n/g;
|
||||
|
||||
const [commentLine, titleMatch] = titleRegexp.exec(content) ?? [];
|
||||
|
||||
if (titleMatch) {
|
||||
const title = titleMatch.trim();
|
||||
const code = content.replace(commentLine || "", "");
|
||||
|
||||
return { title, code };
|
||||
}
|
||||
|
||||
return { code: content };
|
||||
};
|
||||
|
||||
export const renderCodeMarkdown = (content: string) => {
|
||||
const { title, code: rawCode } = getCodeData(content);
|
||||
|
||||
let highlighted = "";
|
||||
|
||||
// run cardinal on codeContent
|
||||
try {
|
||||
const code = cardinal.highlight(rawCode, {
|
||||
jsx: true,
|
||||
});
|
||||
highlighted = code;
|
||||
} catch (err) {
|
||||
highlighted = rawCode;
|
||||
}
|
||||
|
||||
// wrap to boxen
|
||||
const boxed = boxen(highlighted, {
|
||||
padding: 1,
|
||||
margin: 0,
|
||||
borderStyle: "round",
|
||||
borderColor: "gray",
|
||||
titleAlignment: "left",
|
||||
title: title ? chalk.bold(title) : undefined,
|
||||
});
|
||||
|
||||
return boxed;
|
||||
};
|
||||
29
packages/cli/src/utils/text/index.test.tsx
Normal file
29
packages/cli/src/utils/text/index.test.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { removeANSIColors, uppercaseFirstChar } from ".";
|
||||
|
||||
describe("uppercaseFirstChar", () => {
|
||||
it("should return the string with the first character capitalized", () => {
|
||||
const str = "hello world";
|
||||
const result = uppercaseFirstChar(str);
|
||||
expect(result).toEqual("Hello world");
|
||||
});
|
||||
|
||||
it("should return an empty string if the input is empty", () => {
|
||||
const str = "";
|
||||
const result = uppercaseFirstChar(str);
|
||||
expect(result).toEqual("");
|
||||
});
|
||||
});
|
||||
|
||||
describe("removeANSIColors", () => {
|
||||
it("should remove ANSI color codes from the string", () => {
|
||||
const str = "\u001b[31mHello \u001b[32mworld\u001b[0m";
|
||||
const result = removeANSIColors(str);
|
||||
expect(result).toEqual("Hello world");
|
||||
});
|
||||
|
||||
it("should return the original string if it does not contain any ANSI color codes", () => {
|
||||
const str = "Hello world";
|
||||
const result = removeANSIColors(str);
|
||||
expect(result).toEqual(str);
|
||||
});
|
||||
});
|
||||
10
packages/cli/src/utils/text/index.ts
Normal file
10
packages/cli/src/utils/text/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export const uppercaseFirstChar = (str: string): string => {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
};
|
||||
|
||||
export const removeANSIColors = (str: string): string => {
|
||||
return str.replace(
|
||||
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
|
||||
"",
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user