UNPKG

generator-react-native-vector-icons

Version:

Generates React Native vector icons font library

302 lines (301 loc) 11.5 kB
/* eslint-disable no-underscore-dangle,import/no-unresolved */ import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import npmFetch from 'npm-registry-fetch'; import getAuthToken from 'registry-auth-token'; import semver from 'semver'; import Generator, {} from 'yeoman-generator'; import { generateGlyphmap } from './generateGlyphmap.js'; const { uid, gid } = os.userInfo(); export default class extends Generator { data; constructor(args, opts) { super(args, opts); this.option('current-version', { type: String, description: 'Current package version' }); this.data = this._data(); } writing() { this._writeTemplates(); } install() { return this._writePackageJSON(); } end() { this._buildSteps(); } _docker(image, args, options = []) { const { exitCode } = this.spawnSync('docker', [ 'run', '--rm', `--volume=${process.cwd()}:/usr/src/app`, `--volume=${process.cwd()}/../../node_modules:/usr/src/app/node_modules`, `--user=${uid}:${gid}`, '--env=SOURCE_DATE_EPOCH=1702622477', // TODO: Should we use something more sensible as the date for the fonts ...options, image, ...args, ], { stdio: 'inherit' }); if (exitCode !== 0) { throw new Error(`${image} exited with exitCode ${exitCode}`); } } _writeTemplates() { const { data } = this; const files = [ 'package.json', 'tsconfig.json', 'tsconfig.build.json', 'babel.config.js', ]; if (data.customSrc === true) { // Do nothing } else if (data.customSrc) { files.push([data.customSrc, data.customSrc.endsWith('.tsx') ? 'src/index.tsx' : 'src/index.ts']); } else { files.push('src/index.ts'); } if (!data.customReadme) { files.push('README.md'); } files.forEach((file) => { if (typeof file === 'string') { this.fs.copyTpl(this.templatePath(file), this.destinationPath(file), data); } else { const [from, to] = file; this.fs.copyTpl(this.templatePath(from), this.destinationPath(to), data); } }); } async _writePackageJSON() { const { data } = this; const packageFile = this.destinationPath('package.json'); const packageJSON = JSON.parse(fs.readFileSync(packageFile, 'utf8')); let packageName = ''; let version; let versionOnly = false; if (typeof data.upstreamFont === 'object') { const registry = data.upstreamFont.registry ?? 'https://registry.npmjs.org'; packageName = data.upstreamFont.packageName; const versionRange = data.upstreamFont.versionRange || '*'; versionOnly = data.upstreamFont.versionOnly || false; const authToken = getAuthToken(registry.replace(/^https?:/, '')); const packageInfo = await npmFetch.json(`${registry}/${packageName}`, { forceAuth: { _authToken: authToken?.token }, }); const versions = Object.keys(packageInfo.versions); const possibleVersion = semver.maxSatisfying(versions, versionRange); if (!possibleVersion) { throw new Error(`Invalid upstreamFont ${data.upstreamFont}: no matching version`); } version = possibleVersion; } else if (typeof data.upstreamFont === 'string') { const packageInfo = await npmFetch.json(`https://registry.npmjs.org/${data.upstreamFont}/latest`); version = packageInfo.version; packageName = data.upstreamFont; } else { version = '0.0.1'; } const { currentVersion } = this.options; let versionSuffix = ''; if (currentVersion && data.versionSuffix && currentVersion.match(data.versionSuffix)) { const preRelease = currentVersion.split(data.versionSuffix)[1]; versionSuffix = `${data.versionSuffix}${preRelease}`; } else { versionSuffix = data.versionSuffix ? `${data.versionSuffix}.1` : ''; } packageJSON.version = `${version}${versionSuffix}`; const commonPackageFile = this.destinationPath('../common/package.json'); const commonPackageJSON = JSON.parse(fs.readFileSync(commonPackageFile, 'utf8')); packageJSON.dependencies['@react-native-vector-icons/common'] = `^${commonPackageJSON.version}`; if (data.dependencies) { Object.entries(data.dependencies).forEach(([depName, depVersion]) => { if (!depName.startsWith('@react-native-vector-icons')) { packageJSON.dependencies[depName] = depVersion; return; } const dep = depName.split('/')[1]; const depFile = this.destinationPath(`../${dep}/package.json`); const depJSON = JSON.parse(fs.readFileSync(depFile, 'utf8')); packageJSON.dependencies[depName] = `^${depJSON.version}`; }); } if (!versionOnly && packageName) { packageJSON.devDependencies[packageName] = version; } fs.writeFileSync(packageFile, JSON.stringify(packageJSON, null, 2)); } _buildSteps() { this._preScript(); this._fixSVGPaths(); this._buildFontCustom(); this._buildGlyphmap(); this._copyFont(); this._fontForgeScript(); this._postScript(); } _preScript() { const { preScript } = this.data.buildSteps; if (!preScript) { return; } const { exitCode } = this.spawnSync('bash', ['-c', preScript.script], { stdio: 'inherit' }); if (exitCode !== 0) { throw new Error(`preScript exited with exitCode ${exitCode}`); } } _fixSVGPaths() { const { fixSVGPaths } = this.data.buildSteps; if (!fixSVGPaths) { return; } fs.mkdirSync('fixedSvg'); const { exitCode } = this.spawnSync('../../node_modules/.bin/oslllo-svg-fixer', ['-s', fixSVGPaths.location, '-d', 'fixedSvg'], { stdio: 'inherit' }); if (exitCode !== 0) { throw new Error(`oslllo-svg-fixer exited with exitCode ${exitCode}`); } const { keepPostfix } = fixSVGPaths; if (keepPostfix) { const files = fs.readdirSync('fixedSvg'); files.forEach((file) => { if (!file.endsWith(`${keepPostfix}.svg`)) { // Delete files that do not end with -16.svg fs.unlinkSync(path.join('fixedSvg', file)); return; } const newName = file.replace(keepPostfix, ''); fs.renameSync(path.join('fixedSvg', file), path.join('fixedSvg', newName)); }); } } _buildFontCustom() { const { data } = this; const { fontCustom } = this.data.buildSteps; if (!fontCustom) { return; } const args = [ 'compile', fontCustom.location, '--templates', 'css', '--name', data.className, '--force', '--no-hash', ]; this._docker('johnf/fontcustom', args); if (!fs.existsSync('fonts')) { fs.mkdirSync('fonts'); } fs.renameSync(`${data.className}/${data.className}.ttf`, `fonts/${data.className}.ttf`); fs.renameSync(`${data.className}/${data.className}.css`, `${data.className}.css`); fs.rmSync(data.className, { recursive: true }); if (fontCustom.cleanup) { fs.rmSync(fontCustom.location, { recursive: true }); } } _fontForgeScript() { const { data } = this; const { fontforgeScript } = this.data.buildSteps; if (!fontforgeScript) { return; } const options = [ '--entrypoint=/usr/local/bin/fontforge', `--volume=${process.cwd()}/../../node_modules/generator-react-native-vector-icons/generators/app/fontforge/${fontforgeScript.script}:/script.py`, ]; const args = ['-script', '/script.py', `fonts/${data.className}.ttf`]; this._docker('johnf/fontcustom', args, options); } _buildGlyphmap() { const { data } = this; const { glyphmap } = this.data.buildSteps; if (!glyphmap) { return; } let locations = []; if (!glyphmap.location) { locations.push([`${data.className}.css`, data.fontFileName]); } else if (typeof glyphmap.location === 'string') { locations.push([glyphmap.location, data.className]); } else { locations = glyphmap.location; } if (!fs.existsSync('glyphmaps')) { fs.mkdirSync('glyphmaps'); } locations.forEach(([from, to]) => { const json = generateGlyphmap(glyphmap.mode, from, glyphmap.prefix); fs.writeFileSync(`glyphmaps/${to}.json`, json); if (glyphmap.cleanup) { fs.rmSync(from); } }); } _copyFont() { const { data } = this; const { copyFont } = this.data.buildSteps; if (!copyFont) { return; } let locations = []; if (typeof copyFont.location === 'string') { locations.push([copyFont.location, data.fontFileName]); } else { locations = copyFont.location; } locations.forEach(([from, to]) => fs.cpSync(from, `fonts/${to}.ttf`)); } _postScript() { const { postScript } = this.data.buildSteps; if (!postScript) { return; } const { exitCode } = this.spawnSync('bash', ['-c', postScript.script], { stdio: 'inherit' }); if (exitCode !== 0) { throw new Error(`postScript exited with exitCode ${exitCode}`); } } _data() { // TODO: Use zod to vaidate the .yo-rc.json data const data = this.config.getAll(); if (!data.packageName) { throw new Error('packageName is required'); } data.name ||= data.packageName .split('-') .map((x) => x.charAt(0).toUpperCase() + x.slice(1)) .join(' '); data.buildSteps ||= {}; data.className ||= data.packageName .split('-') .map((x) => x.charAt(0).toUpperCase() + x.slice(1)) .join(''); data.postScriptName ||= data.packageName .split('-') .map((x) => x.charAt(0).toUpperCase() + x.slice(1)) .join(''); data.fontFileName ||= data.packageName .split('-') .map((x) => x.charAt(0).toUpperCase() + x.slice(1)) .join(''); data.customReadme ||= false; data.customAssets ||= false; data.commonPackage ||= 'common'; data.source = './src/index.ts'; if (typeof data.customSrc === 'string') { data.source = data.customSrc.endsWith('.tsx') ? './src/index.tsx' : './src/index.ts'; } return data; } }