Skip to content

Commit

Permalink
Merge pull request #2 from gronxb/fix/resolution
Browse files Browse the repository at this point in the history
fix: resolution multiple prefix
  • Loading branch information
gronxb authored Sep 16, 2024
2 parents 984942e + 65e2a7e commit 4516540
Show file tree
Hide file tree
Showing 18 changed files with 284 additions and 124 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"react-native-svg": "^15.6.0",
"tsup": "^8.2.4",
"typescript": "^5.6.2",
"vite": "^5.4.5",
"vitest": "^2.1.1"
},
"dependencies": {
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions react-native-icons-builder.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"typescript": true,
"outputPath": "assets/icons",
"icons": [
"bs/Bs0Circle",
"fa/Fa500Px",
"fa6/Fa500Px"
]
}
65 changes: 50 additions & 15 deletions src/commands/add.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,64 @@
import { syncIcons } from "../utils/generateIcon";
import { addIcons, checkIfConfigExists } from "../utils//config";
import { checkIfConfigExists, loadConfig } from "../utils//config";
import { generateBaseCode } from "../utils/generateBaseCode";
import { init } from "./init";
import { checkAvailableIcons } from "../utils/checkAvailableIcons";
import { groupIconsByPrefix } from "../utils/groupIconsByPrefix";
import { log } from "../utils/console";
import { isCancel, select } from "@clack/prompts";
import { IconManager } from "src/iconManager";

export const add = async (iconNames: string[]) => {
export const add = async (iconName: string) => {
try {
if (!checkIfConfigExists()) {
await init();
}
const config = await loadConfig();
const iconManager = new IconManager(config);

const groupedIcons = groupIconsByPrefix(iconNames);
await Promise.all(
groupedIcons.map(async ([prefix, icons]) => {
if (!(await checkAvailableIcons(prefix, icons))) {
throw new Error(`Not found ${icons.join(", ")}`);
}
})
);
const splitIconName = iconName.split("/");
if (splitIconName.length >= 3) {
log.error("Invalid icon name");
return;
}
const [prefixOverride, iconNameToUse] =
splitIconName.length === 2 ? splitIconName : [null, iconName];

async function checkPrefixExists(prefix: string, icon: string) {
const exists = await iconManager.checkIfIconExists(prefix, icon);
return exists ? [prefix] : [];
}

async function getDefaultPrefixes(icon: string) {
return await iconManager.getResolutionPrefixes(icon);
}

const undecidedPrefixes = prefixOverride
? await checkPrefixExists(prefixOverride, iconNameToUse)
: await getDefaultPrefixes(iconNameToUse);

if (undecidedPrefixes.length === 0) {
log.notFound(iconNameToUse);
return;
}

const prefix =
undecidedPrefixes.length === 1
? undecidedPrefixes[0]
: await select({
message: "Pick a prefix.",
options: undecidedPrefixes.map((prefix) => ({
value: prefix,
label: prefix,
})),
});

if (isCancel(prefix)) {
log.error("Operation cancelled");
return;
}

const config = await addIcons(iconNames);
await iconManager.addIcon(prefix as string, iconNameToUse);
await generateBaseCode(config);
await syncIcons(config);
await iconManager.sync();
log.success("Icon added successfully");
} catch (error) {
if (error instanceof Error) {
log.error(error.message);
Expand Down
5 changes: 3 additions & 2 deletions src/commands/sync.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { syncIcons } from "../utils/generateIcon";
import { IconManager } from "../iconManager";
import { loadConfig } from "../utils/config";
import { generateBaseCode } from "../utils/generateBaseCode";

export const sync = async () => {
try {
const config = await loadConfig();
const iconManager = new IconManager(config);
await generateBaseCode(config);
await syncIcons(config);
await iconManager.sync();
} catch (error) {
if (error instanceof Error) {
console.error(error.message);
Expand Down
39 changes: 39 additions & 0 deletions src/iconManager.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { expect, describe, it, vi } from "vitest";
import { IconManager } from "./iconManager";

vi.mock("./utils/generateIcon", () => ({
generateIcon: vi.fn(),
}));

describe("groupIconsByPrefix", () => {
it("should group icons by name", () => {
const iconManager = new IconManager({
icons: [
"ai/AiFillAccountBook",
"ai/AiFillAlert",
"cg/CgAddR",
"fa/FaApple",
"fa6/FaApple",
],
outputPath: "",
typescript: false,
});

expect(iconManager.groupedIcons).toEqual([
["ai", ["AiFillAccountBook", "AiFillAlert"]],
["cg", ["CgAddR"]],
["fa", ["FaApple"]],
["fa6", ["FaApple"]],
]);
});

it("should handle empty array", () => {
const iconManager = new IconManager({
icons: [],
outputPath: "",
typescript: false,
});

expect(iconManager.groupedIcons).toEqual([]);
});
});
122 changes: 122 additions & 0 deletions src/iconManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { initConfig, type Config } from "./utils/config";
import fs from "fs/promises";
import { separateCamelCase } from "./utils/separateCamelCase";
import { uniq } from "./utils/uniq";
import { log } from "./utils/console";
import { generateIconCode } from "./utils/generateIcon";
import { saveIconCode } from "./utils/saveIconCode";
import { searchFunction } from "./utils/searchFunction";

export class IconManager {
private config: Config;

private _groupedIcons: [string, string[]][] = [];

private _prefixCodeMap: Map<string, string> = new Map();

private _resolutionTable: Record<string, string[]> = {
fa: ["fa", "fa6"],
hi: ["hi", "hi2"],
io: ["io", "io5"],
};

constructor(config: Config) {
this.config = config;
}

private get icons() {
return this.config.icons;
}

public async checkIfIconExists(prefix: string, iconName: string) {
const iconCode = await this.getIconCodeByPrefix(prefix);
if (!iconCode) {
return false;
}

return searchFunction(iconCode, iconName);
}

public async getResolutionPrefixes(iconName: string) {
const [prefix] = await separateCamelCase(iconName);
const lowerCasePrefix = prefix.toLowerCase();

const result: string[] = [];
const resolutionPrefixes = this._resolutionTable[lowerCasePrefix] ?? [
lowerCasePrefix,
];

for (const prefix of resolutionPrefixes) {
try {
if (await this.checkIfIconExists(prefix, iconName)) {
result.push(prefix);
}
} catch {}
}

return result;
}

public async getIconCodeByPrefix(prefix: string) {
if (!this._prefixCodeMap.has(prefix)) {
try {
const prefixPath = await import.meta
.resolve(`react-icons/${prefix}`)
.replace("file://", "");

const prefixCode = await fs.readFile(prefixPath, "utf8");
this._prefixCodeMap.set(prefix, prefixCode);
} catch {
return null;
}
}

return this._prefixCodeMap.get(prefix);
}

private _groupIconsByPrefix() {
const groupedIcons: { [key: string]: string[] } = {};

for (const icon of this.icons) {
const [prefix, name] = icon.split("/");
if (!groupedIcons[prefix]) {
groupedIcons[prefix] = [];
}
groupedIcons[prefix].push(name);
}
return Object.entries(groupedIcons);
}

get groupedIcons() {
if (!this._groupedIcons.length) {
this._groupedIcons = this._groupIconsByPrefix();
}

return this._groupedIcons;
}

public async addIcon(prefix: string, iconName: string) {
const updatedIcons = uniq([...this.icons, `${prefix}/${iconName}`]);
this.config.icons = updatedIcons;
return initConfig(this.config);
}

public async sync() {
const groupedIcons = this.groupedIcons;

await Promise.allSettled(
groupedIcons.map(async ([prefix, icons]) => {
try {
const data = await generateIconCode(
prefix,
icons,
this.config.typescript
);
await saveIconCode(this.config.outputPath, data.filename, data.code);
} catch (error) {
log.notFound(prefix);
}
})
);
}
}
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ program
program
.command("add")
.description("Add icons to the project")
.argument("<icon-names...>", "The names of the icons to add")
.argument("<icon-name>", "The names of the icon to add")
.action(add);

program
Expand Down
17 changes: 0 additions & 17 deletions src/utils/checkAvailableIcons.ts

This file was deleted.

8 changes: 0 additions & 8 deletions src/utils/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,6 @@ export const loadConfig = async () => {
return config;
};

export const addIcons = async (iconNames: string[]) => {
const config = await loadConfig();
const updatedIcons = uniq([...config.icons, ...iconNames]);
config.icons = updatedIcons;

return initConfig(config);
};

export const initConfig = async (config: Config) => {
const cwd = getCwd();

Expand Down
3 changes: 3 additions & 0 deletions src/utils/console.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@ export const log = {
error: (message: string) => {
console.log(pc.red(message));
},
notFound: (message: string) => {
console.log(`${pc.red("Not Found:")} ${pc.bgRed(message)}`);
},
};
33 changes: 0 additions & 33 deletions src/utils/generateIcon.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import path from "path";
import fs from "fs/promises";
import { type ModuleItem, parse, print } from "@swc/core";
import { groupIconsByPrefix } from "./groupIconsByPrefix";
import type { Config } from "./config";
import { getCwd } from "./cwd";
import { log } from "./console";
import packageJson from "@/package.json";

export const generateIconCode = async (
Expand Down Expand Up @@ -103,31 +98,3 @@ ${code}
code,
};
};

const saveIcons = async (
outputPath: string,
filename: string,
code: string
) => {
const cwd = getCwd();
const $outputPath = path.join(cwd, outputPath);
await fs.mkdir($outputPath, { recursive: true });

await fs.writeFile(path.join($outputPath, filename), code, "utf8");
log.save(path.join(outputPath, filename));
};

export const syncIcons = async (config: Config) => {
const groupedIcons = groupIconsByPrefix(config.icons);

await Promise.allSettled(
groupedIcons.map(async ([prefix, icons]) => {
try {
const data = await generateIconCode(prefix, icons, config.typescript);
await saveIcons(config.outputPath, data.filename, data.code);
} catch (error) {
log.error(`Not found ${prefix}`);
}
})
);
};
Loading

0 comments on commit 4516540

Please sign in to comment.