diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ab743ff04..8877aa2f3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,6 +28,8 @@ jobs: - run: yarn - run: yarn lint - run: yarn build + - if: ${{ startsWith(github.ref, 'refs/tags/') }} + run: yarn build-icons - run: yarn test - name: Coveralls uses: coverallsapp/github-action@v2 diff --git a/README.md b/README.md index 6dbed62cb..2e69c9589 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,8 @@ yarn build-icons This command launches a script that copies a set of icon packs from the dist of the [react-icons](https://github.com/react-icons/react-icons) dependency and build the set of Mia-Platform icons from the files contained in `/src/assets/icons`. To add a new icon you just need to place a new `.svg` file here (and don't forget to update the `/src/assets/icons/icons.stories.mdx` story file). +Given the high number of files, the script is pretty slow: Therefore, in CI it is run only on tags. + ## License All files under the src folder must have the license boilerplate attached to files. This is checked by CI. diff --git a/package.json b/package.json index 0c1329f4e..257743ead 100644 --- a/package.json +++ b/package.json @@ -32,8 +32,7 @@ "dev": "vite", "preview": "vite preview", "build-icons": "node --no-warnings --loader=ts-node/esm scripts/build-icons.ts", - "build-lib": "rm -rf ./dist -rf && tsc && vite build", - "build": "yarn build-icons && yarn build-lib", + "build": "rm -rf ./dist -rf && tsc && vite build", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives", "lint:fix": "yarn lint --fix", "checkonly": "find ./src -name '*.test.tsx' | xargs grep '\\.only'; test $? -eq 123", @@ -102,6 +101,7 @@ "react-dom": "^18.3.1", "rollup-plugin-visualizer": "^5.12.0", "storybook": "^7.6.17", + "tar": "^7.4.0", "traverse": "^0.6.8", "ts-jest": "^29.1.5", "ts-node": "^10.9.2", diff --git a/scripts/build-icons.ts b/scripts/build-icons.ts index ab43b20b9..70fa86cd5 100644 --- a/scripts/build-icons.ts +++ b/scripts/build-icons.ts @@ -19,24 +19,23 @@ /* eslint-disable no-console */ import { Cheerio, Element, load } from 'cheerio' +import { type TarOptionsWithAliasesAsyncNoFile, extract } from 'tar' import { IconTree } from 'react-icons' import camelcase from 'camelcase' -import { createRequire } from 'module' import fs from 'fs/promises' import { glob } from 'glob' +import https from 'https' import path from 'path' import url from 'url' -const require = createRequire(import.meta.url) -const reactIconsPath = path.dirname(require.resolve('react-icons/package.json')) +const REACT_ICONS_VERSION = '5.2.1' +const REACT_ICONS_PACKAGES = ['lib', 'ai', 'fi', 'pi'] const dirname = path.dirname(url.fileURLToPath(import.meta.url)) const rootDir = path.resolve(dirname, '..') const outDir = path.resolve(rootDir, 'icons') -const reactIconsPackages = ['ai', 'fi', 'pi'] - -async function initialize(): Promise { +async function setup(): Promise { const outDirContent = await fs.readdir(outDir) const promises = outDirContent.reduce[]>((acc, cur) => { @@ -51,17 +50,36 @@ async function initialize(): Promise { await Promise.all(promises) } -async function copyReactIconsPackages(): Promise { - const packagesToCopy = ['lib', ...reactIconsPackages] +async function downloadReactIconsPackage(): Promise { + const pkgUrl = `https://github.com/react-icons/react-icons/releases/download/v${REACT_ICONS_VERSION}/react-icons-all-files-${REACT_ICONS_VERSION}.tgz` + + const tarOptions: TarOptionsWithAliasesAsyncNoFile = { + cwd: outDir, + filter: (entryPath) => REACT_ICONS_PACKAGES.some((pkgName) => entryPath.startsWith(`package/${pkgName}`)), + strip: 1, + } + + const urlToCall = await new Promise((resolve, reject) => { + const reqUrl = new url.URL(pkgUrl) - const promises = packagesToCopy.map((packageName) => { - const source = path.resolve(reactIconsPath, packageName) - const dest = path.resolve(outDir, packageName) + const request = https.get(reqUrl, (res) => resolve(res.headers.location ?? '')) - return fs.cp(source, dest, { recursive: true }) + request.on('error', reject) }) - await Promise.all(promises) + await new Promise((resolve, reject) => { + const request = https.get(urlToCall) + + request.on('response', (res) => { + res.pipe(extract(tarOptions), { end: true }) + + res.on('error', reject) + + res.on('end', resolve) + }) + + request.on('error', reject) + }) } async function svgStringToTree(svg: string): Promise { @@ -105,35 +123,6 @@ async function buildMiaIcons(): Promise { const miaIconsPackageDir = path.resolve(outDir, 'mi') await fs.mkdir(miaIconsPackageDir) - const packageJsonPath = path.resolve(miaIconsPackageDir, 'package.json') - const esmIndexPath = path.resolve(miaIconsPackageDir, 'index.mjs') - const cjsIndexPath = path.resolve(miaIconsPackageDir, 'index.js') - const declarationsPath = path.resolve(miaIconsPackageDir, 'index.d.ts') - - await fs.appendFile( - packageJsonPath, - '{\n "sideEffects": false,\n "module": "./index.esm.js"\n}', - 'utf-8' - ) - - await fs.appendFile( - esmIndexPath, - '// THIS FILE IS AUTO GENERATED\nimport { GenIcon } from \'../lib\';\n', - 'utf-8' - ) - - await fs.appendFile( - cjsIndexPath, - '// THIS FILE IS AUTO GENERATED\nvar GenIcon = require(\'../lib\').GenIcon\n', - 'utf-8' - ) - - await fs.appendFile( - declarationsPath, - '// THIS FILE IS AUTO GENERATED\nimport { IconType } from \'../lib\'\n', - 'utf-8' - ) - const svgFiles = await glob(path.resolve(rootDir, 'src/assets/icons/*.svg')) const promises = svgFiles.map(async(filePath) => { @@ -145,28 +134,44 @@ async function buildMiaIcons(): Promise { const iconTree = await svgStringToTree(svgStr) const iconTreeStr = JSON.stringify(iconTree) - const esmTemplate = `export function ${iconName} (props) {\n` + const esmFilePath = path.resolve(miaIconsPackageDir, `${iconName}.mjs`) + const esmTemplate = `// THIS FILE IS AUTO GENERATED\n` + + `import { GenIcon } from '../lib/index.mjs';\n` + + `export function ${iconName} (props) {\n` + ` return GenIcon(${iconTreeStr})(props);\n` + `};\n` - const cjsTemplate = `module.exports.${iconName} = function ${iconName} (props) {\n` + const cjsFilePath = path.resolve(miaIconsPackageDir, `${iconName}.js`) + const cjsTemplate = `// THIS FILE IS AUTO GENERATED\n` + + `var GenIcon = require('../lib').GenIcon\n` + + `module.exports.${iconName} = function ${iconName} (props) {\n` + ` return GenIcon(${iconTreeStr})(props);\n` + `};\n` - const declarationsTemplate = `export declare const ${iconName}: IconType;\n` + const declarationsFilePath = path.resolve(miaIconsPackageDir, `${iconName}.d.ts`) + const declarationsTemplate = `// THIS FILE IS AUTO GENERATED\n` + + `import { IconType } from '../lib/index.mjs'\n` + + `export declare const ${iconName}: IconType;\n` - await fs.appendFile(esmIndexPath, esmTemplate, 'utf-8') - await fs.appendFile(cjsIndexPath, cjsTemplate, 'utf-8') - await fs.appendFile(declarationsPath, declarationsTemplate, 'utf-8') + await fs.writeFile(esmFilePath, esmTemplate, 'utf-8') + await fs.writeFile(cjsFilePath, cjsTemplate, 'utf-8') + await fs.writeFile(declarationsFilePath, declarationsTemplate, 'utf-8') }) await Promise.all(promises) } async function main(): Promise { - await initialize() - await copyReactIconsPackages() + console.info(`» Start icons building process`) + + await setup() + + console.debug(`» Downloading icons from react-icons v${REACT_ICONS_VERSION} archive...`) + await downloadReactIconsPackage() + console.debug('» Icon files downloaded and extracted') + await buildMiaIcons() + console.debug('» Mia-Platform icons built') } main() diff --git a/src/assets/icons/icons.stories.mdx b/src/assets/icons/icons.stories.mdx index 3967f976e..b865f05a6 100644 --- a/src/assets/icons/icons.stories.mdx +++ b/src/assets/icons/icons.stories.mdx @@ -30,7 +30,7 @@ You can use these icons with the [`` component](/docs/components-icon--d ## Import ```tsx -import { IconName } from "@mia-platform-internal/console-design-system-react/icons/mi" +import { IconName } from "@mia-platform-internal/console-design-system-react/icons/mi/IconName" ``` ## Icons diff --git a/src/components/Icon/Icon.tsx b/src/components/Icon/Icon.tsx index 6ca335711..bfc390ad7 100644 --- a/src/components/Icon/Icon.tsx +++ b/src/components/Icon/Icon.tsx @@ -29,16 +29,16 @@ import { IconProps } from './Icon.props' * pack such as [React Icons](https://react-icons.github.io/react-icons/). * * For convenience, the design system itself ships several icon packs ready to use with this components, namely: - * - [Ant Design Icons](https://react-icons.github.io/react-icons/icons/ai/) importable from `@mia-platform-internal/console-design-system-react/icons/ai` - * - [Phosphor Icons](https://react-icons.github.io/react-icons/icons/pi/) importable from `@mia-platform-internal/console-design-system-react/icons/pi` - * - [Feather Icons](https://react-icons.github.io/react-icons/icons/fi/) importable from `@mia-platform-internal/console-design-system-react/icons/fi` + * - [Ant Design Icons](https://react-icons.github.io/react-icons/icons/ai/) importable from `@mia-platform-internal/console-design-system-react/icons/ai/` + * - [Phosphor Icons](https://react-icons.github.io/react-icons/icons/pi/) importable from `@mia-platform-internal/console-design-system-react/icons/pi/` + * - [Feather Icons](https://react-icons.github.io/react-icons/icons/fi/) importable from `@mia-platform-internal/console-design-system-react/icons/fi/` * - [Mia-Platform Icons](/docs/icons-mia-platform--docs) importable from - * `@mia-platform-internal/console-design-system-react/icons/mi` + * `@mia-platform-internal/console-design-system-react/icons/mi/` * * To use one of the aforementioned icons, just import the component and pass it to ``: * * ```tsx - * import { PiAddressBook } from "@mia-platform-internal/console-design-system-react/icons/pi" + * import { PiAddressBook } from "@mia-platform-internal/console-design-system-react/icons/pi/PiAddressBook" * * const App = () => * ``` diff --git a/yarn.lock b/yarn.lock index 398836bd8..c62694671 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2727,6 +2727,15 @@ __metadata: languageName: node linkType: hard +"@isaacs/fs-minipass@npm:^4.0.0": + version: 4.0.1 + resolution: "@isaacs/fs-minipass@npm:4.0.1" + dependencies: + minipass: "npm:^7.0.4" + checksum: 10/4412e9e6713c89c1e66d80bb0bb5a2a93192f10477623a27d08f228ba0316bb880affabc5bfe7f838f58a34d26c2c190da726e576cdfc18c49a72e89adabdcf5 + languageName: node + linkType: hard + "@istanbuljs/load-nyc-config@npm:^1.0.0": version: 1.1.0 resolution: "@istanbuljs/load-nyc-config@npm:1.1.0" @@ -3198,6 +3207,7 @@ __metadata: react-icons: "npm:^4.11.0" rollup-plugin-visualizer: "npm:^5.12.0" storybook: "npm:^7.6.17" + tar: "npm:^7.4.0" traverse: "npm:^0.6.8" ts-jest: "npm:^29.1.5" ts-node: "npm:^10.9.2" @@ -7740,6 +7750,13 @@ __metadata: languageName: node linkType: hard +"chownr@npm:^3.0.0": + version: 3.0.0 + resolution: "chownr@npm:3.0.0" + checksum: 10/b63cb1f73d171d140a2ed8154ee6566c8ab775d3196b0e03a2a94b5f6a0ce7777ee5685ca56849403c8d17bd457a6540672f9a60696a6137c7a409097495b82c + languageName: node + linkType: hard + "ci-info@npm:^3.2.0": version: 3.9.0 resolution: "ci-info@npm:3.9.0" @@ -10228,6 +10245,22 @@ __metadata: languageName: node linkType: hard +"glob@npm:^10.3.7": + version: 10.4.5 + resolution: "glob@npm:10.4.5" + dependencies: + foreground-child: "npm:^3.1.0" + jackspeak: "npm:^3.1.2" + minimatch: "npm:^9.0.4" + minipass: "npm:^7.1.2" + package-json-from-dist: "npm:^1.0.0" + path-scurry: "npm:^1.11.1" + bin: + glob: dist/esm/bin.mjs + checksum: 10/698dfe11828b7efd0514cd11e573eaed26b2dff611f0400907281ce3eab0c1e56143ef9b35adc7c77ecc71fba74717b510c7c223d34ca8a98ec81777b293d4ac + languageName: node + linkType: hard + "glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6, glob@npm:^7.2.0": version: 7.2.3 resolution: "glob@npm:7.2.3" @@ -12800,7 +12833,7 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^7.0.2, minipass@npm:^7.1.2": +"minipass@npm:^7.0.2, minipass@npm:^7.0.4, minipass@npm:^7.1.2": version: 7.1.2 resolution: "minipass@npm:7.1.2" checksum: 10/c25f0ee8196d8e6036661104bacd743785b2599a21de5c516b32b3fa2b83113ac89a2358465bc04956baab37ffb956ae43be679b2262bf7be15fce467ccd7950 @@ -12817,6 +12850,16 @@ __metadata: languageName: node linkType: hard +"minizlib@npm:^3.0.1": + version: 3.0.1 + resolution: "minizlib@npm:3.0.1" + dependencies: + minipass: "npm:^7.0.4" + rimraf: "npm:^5.0.5" + checksum: 10/622cb85f51e5c206a080a62d20db0d7b4066f308cb6ce82a9644da112367c3416ae7062017e631eb7ac8588191cfa4a9a279b8651c399265202b298e98c4acef + languageName: node + linkType: hard + "mkdirp-classic@npm:^0.5.2": version: 0.5.3 resolution: "mkdirp-classic@npm:0.5.3" @@ -12844,6 +12887,15 @@ __metadata: languageName: node linkType: hard +"mkdirp@npm:^3.0.1": + version: 3.0.1 + resolution: "mkdirp@npm:3.0.1" + bin: + mkdirp: dist/cjs/src/bin.js + checksum: 10/16fd79c28645759505914561e249b9a1f5fe3362279ad95487a4501e4467abeb714fd35b95307326b8fd03f3c7719065ef11a6f97b7285d7888306d1bd2232ba + languageName: node + linkType: hard + "ms@npm:2.0.0": version: 2.0.0 resolution: "ms@npm:2.0.0" @@ -13400,6 +13452,13 @@ __metadata: languageName: node linkType: hard +"package-json-from-dist@npm:^1.0.0": + version: 1.0.0 + resolution: "package-json-from-dist@npm:1.0.0" + checksum: 10/ac706ec856a5a03f5261e4e48fa974f24feb044d51f84f8332e2af0af04fbdbdd5bbbfb9cbbe354190409bc8307c83a9e38c6672c3c8855f709afb0006a009ea + languageName: node + linkType: hard + "pako@npm:~0.2.0": version: 0.2.9 resolution: "pako@npm:0.2.9" @@ -15193,6 +15252,17 @@ __metadata: languageName: node linkType: hard +"rimraf@npm:^5.0.5": + version: 5.0.9 + resolution: "rimraf@npm:5.0.9" + dependencies: + glob: "npm:^10.3.7" + bin: + rimraf: dist/esm/bin.mjs + checksum: 10/443669809ca3d402ca7565fd9f5b994b5669d8f8b33a23e3a00a66c3a2e4c529d8a5a47c9e7c42f2c7a0c70d21ff8bb1c86493b12027139a3de47fc33fe60084 + languageName: node + linkType: hard + "rimraf@npm:~2.6.2": version: 2.6.3 resolution: "rimraf@npm:2.6.3" @@ -16134,6 +16204,20 @@ __metadata: languageName: node linkType: hard +"tar@npm:^7.4.0": + version: 7.4.0 + resolution: "tar@npm:7.4.0" + dependencies: + "@isaacs/fs-minipass": "npm:^4.0.0" + chownr: "npm:^3.0.0" + minipass: "npm:^7.1.2" + minizlib: "npm:^3.0.1" + mkdirp: "npm:^3.0.1" + yallist: "npm:^5.0.0" + checksum: 10/fd0e366c0da823ac66cd0d2f52ff2b7963945a4252db88b5887053b6ab54a489380dfad64b2b26ae4a08bb2f5d66de04432d45e039a37bc0d442546ef29c6ee6 + languageName: node + linkType: hard + "telejson@npm:^7.2.0": version: 7.2.0 resolution: "telejson@npm:7.2.0" @@ -17528,6 +17612,13 @@ __metadata: languageName: node linkType: hard +"yallist@npm:^5.0.0": + version: 5.0.0 + resolution: "yallist@npm:5.0.0" + checksum: 10/1884d272d485845ad04759a255c71775db0fac56308764b4c77ea56a20d56679fad340213054c8c9c9c26fcfd4c4b2a90df993b7e0aaf3cdb73c618d1d1a802a + languageName: node + linkType: hard + "yaml@npm:^1.10.2": version: 1.10.2 resolution: "yaml@npm:1.10.2"