diff --git a/frontend/packages/icons/README.md b/frontend/packages/icons/README.md index 5ae31e2eba..310c0f14c2 100644 --- a/frontend/packages/icons/README.md +++ b/frontend/packages/icons/README.md @@ -15,12 +15,23 @@ This package includes icons from Font Awesome Pro, which are licensed to Rivet for use in Rivet products only. If you wish to use Font Awesome Pro icons in your own projects, you must obtain your own license from [Font Awesome](https://fontawesome.com/plans). -## Motivation +## Overview -Rivet Icons are a set of SVG icons that are used in Rivet products. This package is built on top of great Font Awesome icons.Β Some icons used in our products are from the premium Font Awesome icon set. We've created a package that lets you use premium icons without having to buy a Font Awesome license. +Rivet Icons is an icon library built on Font Awesome that provides pre-generated SVG icons for use in Rivet products. All icons (including Pro icons) are committed to the repository, making this package work out-of-the-box with **no Font Awesome token required** for end users. -All icons (including Pro icons) are pre-generated and committed to the repository, so this package works out-of-the-box with **no Font Awesome token required** for end users. +## Installation +```bash +pnpm add @rivet-gg/icons +``` + +## Usage + +```tsx +import { Icon, faCheckCircle } from "@rivet-gg/icons"; + + +``` ## Contributing @@ -31,8 +42,8 @@ All icons (including Pro icons) are pre-generated and committed to the repositor ### Adding new icons 1. Ensure you have a `FONTAWESOME_PACKAGE_TOKEN` environment variable set -2. Modify [scripts/generateManifest.js](scripts/generateManifest.js) to include new icons -3. Run `./scripts/generateManifest.js` to generate a new `manifest.json` file +2. Modify [scripts/generate-manifest.js](scripts/generate-manifest.js) to include new icons +3. Run `pnpm manifest` to generate a new `manifest.json` file - If you're getting an error about missing packages, run `pnpm install` in the `src` folder first 4. Run `pnpm vendor` to generate icon files: - `src/index.gen.js` diff --git a/frontend/packages/icons/package.json b/frontend/packages/icons/package.json index dd406685b8..08501abf71 100644 --- a/frontend/packages/icons/package.json +++ b/frontend/packages/icons/package.json @@ -2,7 +2,7 @@ "name": "@rivet-gg/icons", "version": "2.0.33", "description": "Icon library for Rivet products. Licensed exclusively for use in Rivet products and services only.", - "license": "UNLICENSED", + "license": "SEE LICENSE IN LICENSE", "private": false, "sideEffects": false, "files": [ diff --git a/frontend/packages/icons/scripts/generate-manifest.js b/frontend/packages/icons/scripts/generate-manifest.js index a092da20b0..3f57c2e229 100755 --- a/frontend/packages/icons/scripts/generate-manifest.js +++ b/frontend/packages/icons/scripts/generate-manifest.js @@ -9,43 +9,26 @@ // Using this in any other product or project is strictly prohibited. // -const fs = require("node:fs"); -const { join } = require("node:path"); -const { spawnSync } = require("node:child_process"); -const { getPackageInfo, importModule, resolveModule } = require("local-pkg"); -const dedentModule = require("dedent"); -const dedent = dedentModule.default || dedentModule; +import fs from "node:fs"; +import { getPackageInfo, importModule, resolveModule } from "local-pkg"; +import { + PATHS, + log, + error, + exitWithError, + faCamelCase, + checkEnvironment, + setupSourceDirectory, + configureFontAwesomeRegistry, + installFontAwesomePackages, +} from "./shared-utils.js"; // ============================================================================ // CONFIGURATION // ============================================================================ -const PATHS = { - root: join(__dirname, ".."), - get src() { - return join(this.root, "src"); - }, - get srcNodeModules() { - return join(this.src, "node_modules"); - }, - get rootNodeModules() { - return join(this.root, "..", "..", "..", "node_modules"); - }, - get manifest() { - return join(this.root, "manifest.json"); - }, -}; - const SEARCH_PATHS = [PATHS.srcNodeModules, PATHS.rootNodeModules]; -const FA_PACKAGES_CONFIG = { - "@awesome.me/kit-63db24046b": "1.0.27", - "@fortawesome/pro-regular-svg-icons": "6.6.0", - "@fortawesome/pro-solid-svg-icons": "6.6.0", - "@fortawesome/free-solid-svg-icons": "6.6.0", - "@fortawesome/free-brands-svg-icons": "6.6.0", -}; - const FA_PACKAGES = [ "@fortawesome/free-solid-svg-icons", "@fortawesome/free-brands-svg-icons", @@ -57,113 +40,6 @@ const CUSTOM_KITS = ["@awesome.me/kit-63db24046b/icons/kit/custom"]; // Track globally registered icons to avoid duplicates const registeredIcons = new Set(); -// ============================================================================ -// UTILITIES -// ============================================================================ - -/** - * @param {string} emoji - * @param {string} message - */ -function log(emoji, message) { - console.log(`${emoji} ${message}`); -} - -/** - * @param {string} message - */ -function error(message) { - console.error(`❌ ${message}`); -} - -/** - * @param {string} message - */ -function exitWithError(message) { - error(message); - process.exit(1); -} - -/** - * Converts kebab-case to faCamelCase - * @param {string} str - Icon name in kebab-case - * @returns {string} Icon name in faCamelCase - * @example - * faCamelCase("arrow-left") // => "faArrowLeft" - */ -function faCamelCase(str) { - const camelCase = str.replace(/-./g, (g) => g[1].toUpperCase()); - const [firstLetter, ...restLetters] = camelCase; - return `fa${firstLetter.toUpperCase()}${restLetters.join("")}`; -} - -// ============================================================================ -// SETUP PHASE -// ============================================================================ - -function checkEnvironment() { - log("πŸ”", "Checking environment..."); - if (!process.env.FONTAWESOME_PACKAGE_TOKEN) { - exitWithError( - "FONTAWESOME_PACKAGE_TOKEN environment variable is required.\n" + - "This script should only be run by maintainers with a Font Awesome Pro license.", - ); - } - log("πŸ”‘", "Font Awesome token found"); -} - -function setupSourceDirectory() { - log("πŸ“", "Setting up source directory..."); - if (!fs.existsSync(PATHS.src)) { - fs.mkdirSync(PATHS.src, { recursive: true }); - log("✨", `Created directory: ${PATHS.src}`); - } -} - -function configureFontAwesomeRegistry() { - log("πŸ“", "Configuring Font Awesome registry..."); - - // Create .npmrc for Font Awesome Pro authentication - const npmrcContent = dedent` - @fortawesome:registry=https://npm.fontawesome.com/ - @awesome.me:registry=https://npm.fontawesome.com/ - //npm.fontawesome.com/:_authToken=\${FONTAWESOME_PACKAGE_TOKEN} - //npm.fontawesome.com/:always-auth=true - `; - fs.writeFileSync(join(PATHS.src, ".npmrc"), npmrcContent); - - // Create temporary package.json - const packageJson = { - name: "@rivet-gg/internal-icons", - private: true, - sideEffects: false, - dependencies: FA_PACKAGES_CONFIG, - }; - fs.writeFileSync( - join(PATHS.src, "package.json"), - JSON.stringify(packageJson, null, 2), - ); - - log("βœ…", "Registry configured"); -} - -function installFontAwesomePackages() { - log("πŸ“¦", "Installing Font Awesome Pro packages..."); - log("⏳", "This may take a minute..."); - - const result = spawnSync("npm", ["install", "--no-package-lock", "--silent"], { - stdio: "inherit", - cwd: PATHS.src, - env: { ...process.env, CI: "0" }, - }); - - if (result.status !== 0) { - exitWithError("Failed to install Font Awesome packages"); - } - - log("βœ…", "Packages installed"); -} - // ============================================================================ // FONT AWESOME ICON REGISTRATION // ============================================================================ @@ -272,18 +148,18 @@ async function registerFontAwesomePackage(packageName) { * @param {string} kitPath - Path to the custom kit * @returns {Record} */ -function registerCustomKit(kitPath) { +async function registerCustomKit(kitPath) { log("πŸ“¦", `Processing custom kit: ${kitPath}...`); // Resolve the custom kit module - const modulePath = require.resolve(kitPath, { paths: SEARCH_PATHS }); + const modulePath = resolveModule(kitPath, { paths: SEARCH_PATHS }); if (!modulePath) { throw new Error(`Could not resolve custom kit: ${kitPath}`); } // Load the custom icons - const customIcons = require(modulePath); + const customIcons = await importModule(modulePath); const foundIcons = []; let skippedCount = 0; @@ -384,7 +260,7 @@ async function main() { log("🎨", "Registering custom kits..."); for (const kitPath of CUSTOM_KITS) { try { - const kitManifest = registerCustomKit(kitPath); + const kitManifest = await registerCustomKit(kitPath); Object.assign(manifest, kitManifest); totalIcons += kitManifest[kitPath].icons.length; } catch (err) { @@ -415,11 +291,7 @@ async function main() { // Run the script main().catch((err) => { - const message = err instanceof Error ? err.message : String(err); console.error("\n❌ Manifest generation failed:"); - error(message); - if (err instanceof Error && err.stack) { - console.error(err.stack); - } + error(err); process.exit(1); }); diff --git a/frontend/packages/icons/scripts/shared-utils.js b/frontend/packages/icons/scripts/shared-utils.js new file mode 100644 index 0000000000..8c584d61a4 --- /dev/null +++ b/frontend/packages/icons/scripts/shared-utils.js @@ -0,0 +1,171 @@ +#!/usr/bin/env node +// @ts-nocheck +// +// RIVET ICONS SHARED UTILITIES +// Common utilities used by icon generation scripts +// + +import fs from "node:fs"; +import { join } from "node:path"; +import { spawnSync } from "node:child_process"; +import dedent from "dedent"; + +// ============================================================================ +// CONSTANTS +// ============================================================================ + +const PATHS = { + get root() { + return join(import.meta.dirname, ".."); + }, + get src() { + return join(this.root, "src"); + }, + get srcNodeModules() { + return join(this.src, "node_modules"); + }, + get rootNodeModules() { + return join(this.root, "..", "..", "..", "node_modules"); + }, + get manifest() { + return join(this.root, "manifest.json"); + }, + get dist() { + return join(this.root, "dist"); + }, +}; + +const FA_PACKAGES_CONFIG = { + // Custom kit with Rivet-specific icons + "@awesome.me/kit-63db24046b": "1.0.27", + // Pro packages (regular and solid styles) + "@fortawesome/pro-regular-svg-icons": "6.6.0", + "@fortawesome/pro-solid-svg-icons": "6.6.0", + // Free packages (used as fallbacks) + "@fortawesome/free-solid-svg-icons": "6.6.0", + "@fortawesome/free-brands-svg-icons": "6.6.0", +}; + +// ============================================================================ +// UTILITIES +// ============================================================================ + +/** + * @param {string} emoji + * @param {string} message + */ +function log(emoji, message) { + console.log(`${emoji} ${message}`); +} + +/** + * @param {string} message + */ +function error(message) { + console.error(`❌`, message); +} + +/** + * @param {string} message + */ +function exitWithError(message) { + error(message); + process.exit(1); +} + +/** + * Converts kebab-case to faCamelCase + * @param {string} str - Icon name in kebab-case + * @returns {string} Icon name in faCamelCase + * @example + * faCamelCase("arrow-left") // => "faArrowLeft" + */ +function faCamelCase(str) { + const camelCase = str.replace(/-./g, (g) => g[1].toUpperCase()); + const [firstLetter, ...restLetters] = camelCase; + return `fa${firstLetter.toUpperCase()}${restLetters.join("")}`; +} + +// ============================================================================ +// SETUP FUNCTIONS +// ============================================================================ + +function checkEnvironment() { + log("πŸ”", "Checking environment..."); + if (!process.env.FONTAWESOME_PACKAGE_TOKEN) { + exitWithError( + "FONTAWESOME_PACKAGE_TOKEN environment variable is required.\n" + + "This script should only be run by maintainers with a Font Awesome Pro license.", + ); + } + log("πŸ”‘", "Font Awesome token found"); +} + +function setupSourceDirectory() { + log("πŸ“", "Setting up source directory..."); + if (!fs.existsSync(PATHS.src)) { + fs.mkdirSync(PATHS.src, { recursive: true }); + log("✨", `Created directory: ${PATHS.src}`); + } +} + +function configureFontAwesomeRegistry() { + log("πŸ“", "Configuring Font Awesome registry..."); + + // Create .npmrc for Font Awesome Pro authentication + const npmrcContent = dedent` + @fortawesome:registry=https://npm.fontawesome.com/ + @awesome.me:registry=https://npm.fontawesome.com/ + //npm.fontawesome.com/:_authToken=\${FONTAWESOME_PACKAGE_TOKEN} + //npm.fontawesome.com/:always-auth=true + `; + fs.writeFileSync(join(PATHS.src, ".npmrc"), npmrcContent); + + // Create temporary package.json + const packageJson = { + name: "@rivet-gg/internal-icons", + private: true, + sideEffects: false, + dependencies: FA_PACKAGES_CONFIG, + }; + fs.writeFileSync( + join(PATHS.src, "package.json"), + JSON.stringify(packageJson, null, 2), + ); + + log("βœ…", "Registry configured"); +} + +function installFontAwesomePackages() { + log("πŸ“¦", "Installing Font Awesome Pro packages..."); + log("⏳", "This may take a minute..."); + + const result = spawnSync("npm", ["install", "--no-package-lock", "--silent"], { + stdio: "inherit", + cwd: PATHS.src, + env: { ...process.env, CI: "0" }, + }); + + if (result.status !== 0) { + exitWithError("Failed to install Font Awesome packages"); + } + + log("βœ…", "Packages installed"); +} + +// ============================================================================ +// EXPORTS +// ============================================================================ + +export { + PATHS, + FA_PACKAGES_CONFIG, + log, + error, + exitWithError, + faCamelCase, + checkEnvironment, + setupSourceDirectory, + configureFontAwesomeRegistry, + installFontAwesomePackages, +}; diff --git a/frontend/packages/icons/scripts/vendor-icons.js b/frontend/packages/icons/scripts/vendor-icons.js index b3103b3953..fec1888a8b 100755 --- a/frontend/packages/icons/scripts/vendor-icons.js +++ b/frontend/packages/icons/scripts/vendor-icons.js @@ -10,39 +10,25 @@ // strictly prohibited and may violate Font Awesome's Terms of Service. // -const fs = require("node:fs"); -const { join, resolve } = require("node:path"); -const { spawnSync } = require("node:child_process"); -const dedentModule = require("dedent"); -const dedent = dedentModule.default || dedentModule; -const esbuild = require("esbuild"); +import fs from "node:fs"; +import { join, resolve } from "node:path"; +import dedent from "dedent"; +import esbuild from "esbuild"; +import { + PATHS, + log, + error, + exitWithError, + checkEnvironment, + setupSourceDirectory, + configureFontAwesomeRegistry, + installFontAwesomePackages, +} from "./shared-utils.js"; // ============================================================================ // CONFIGURATION // ============================================================================ -const PATHS = { - root: join(__dirname, ".."), - get src() { - return join(this.root, "src"); - }, - get srcNodeModules() { - return join(this.src, "node_modules"); - }, - get manifest() { - return join(this.root, "manifest.json"); - }, - get dist() { - return join(this.root, "dist"); - }, -}; - -const FA_PACKAGES = { - "@awesome.me/kit-63db24046b": "1.0.27", - "@fortawesome/pro-regular-svg-icons": "6.6.0", - "@fortawesome/pro-solid-svg-icons": "6.6.0", -}; - const LEGAL_BANNER = dedent` // This file is generated by scripts/vendor-icons.js // Do not modify this file directly @@ -55,100 +41,6 @@ const LEGAL_BANNER = dedent` `; -// ============================================================================ -// UTILITIES -// ============================================================================ - -/** - * @param {string} emoji - * @param {string} message - */ -function log(emoji, message) { - console.log(`${emoji} ${message}`); -} - -/** - * @param {string} message - */ -function error(message) { - console.error(`❌ ${message}`); -} - -/** - * @param {string} message - */ -function exitWithError(message) { - error(message); - process.exit(1); -} - -// ============================================================================ -// SETUP PHASE -// ============================================================================ - -function checkEnvironment() { - log("πŸ”", "Checking environment..."); - if (!process.env.FONTAWESOME_PACKAGE_TOKEN) { - exitWithError( - "FONTAWESOME_PACKAGE_TOKEN environment variable is required.\n" + - "This script should only be run by maintainers with a Font Awesome Pro license.", - ); - } - log("πŸ”‘", "Font Awesome token found"); -} - -function setupSourceDirectory() { - log("πŸ“", "Setting up source directory..."); - if (!fs.existsSync(PATHS.src)) { - fs.mkdirSync(PATHS.src, { recursive: true }); - log("✨", `Created directory: ${PATHS.src}`); - } -} - -function configureFontAwesomeRegistry() { - log("πŸ“", "Configuring Font Awesome registry..."); - - // Create .npmrc for Font Awesome Pro authentication - const npmrcContent = dedent` - @fortawesome:registry=https://npm.fontawesome.com/ - @awesome.me:registry=https://npm.fontawesome.com/ - //npm.fontawesome.com/:_authToken=\${FONTAWESOME_PACKAGE_TOKEN} - //npm.fontawesome.com/:always-auth=true - `; - fs.writeFileSync(join(PATHS.src, ".npmrc"), npmrcContent); - - // Create temporary package.json - const packageJson = { - name: "@rivet-gg/internal-icons", - private: true, - sideEffects: false, - dependencies: FA_PACKAGES, - }; - fs.writeFileSync( - join(PATHS.src, "package.json"), - JSON.stringify(packageJson, null, 2), - ); - - log("βœ…", "Registry configured"); -} - -function installFontAwesomePackages() { - log("πŸ“¦", "Installing Font Awesome Pro packages..."); - log("⏳", "This may take a minute..."); - - const result = spawnSync("npm", ["install", "--no-package-lock", "--silent"], { - stdio: "inherit", - cwd: PATHS.src, - env: { ...process.env, CI: "0" }, - }); - - if (result.status !== 0) { - exitWithError("Failed to install Font Awesome packages"); - } - - log("βœ…", "Packages installed"); -} - // ============================================================================ // ICON GENERATION PHASE // ============================================================================ @@ -156,7 +48,7 @@ function installFontAwesomePackages() { function loadManifest() { log("πŸ“‹", "Loading icon manifest..."); try { - const manifest = require(PATHS.manifest); + const manifest = JSON.parse(fs.readFileSync(PATHS.manifest, "utf-8")); const packageCount = Object.keys(manifest).length; const iconCount = Object.values(manifest).reduce( (sum, pkg) => sum + pkg.icons.length,