generator-react-native-vector-icons
Version:
Generates React Native vector icons font library
311 lines (310 loc) • 11.5 kB
JavaScript
/* 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, { customInstallTask: true });
this.option('current-version', { type: String, description: 'Current package version' });
this.data = this._data();
}
async writing() {
this._writeTemplates();
await this._fixPackageVersion();
await this._addFontDependencies();
await this._addDependencies();
}
async install() {
this.spawnSync('pnpm', ['install', '--filter', '.', '--ignore-scripts']);
}
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',
'android/build.gradle',
'android/src/main/AndroidManifestNew.xml',
'android/src/main/AndroidManifest.xml',
];
files.push(['android/src/main/java/Package.kt', `android/src/main/java/VectorIcons${data.className}Package.kt`]);
files.push(['font.podspec', `react-native-vector-icons-${data.packageName}.podspec`]);
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 _fixPackageVersion() {
this.fs.extendJSON(this.destinationPath('package.json'), { version: this.options.currentVersion });
}
async _addFontDependencies() {
const { data } = this;
if (!data.upstreamFont) {
return;
}
if (typeof data.upstreamFont === 'string') {
const packageInfo = (await npmFetch.json(`https://registry.npmjs.org/${data.upstreamFont}/latest`));
await this.addDevDependencies({ [data.upstreamFont]: packageInfo.version });
return;
}
if (data.upstreamFont?.versionOnly) {
return;
}
const packageName = typeof data.upstreamFont === 'object' ? data.upstreamFont.packageName : data.upstreamFont;
let version = this.options.currentVersion;
const versionOnly = data.upstreamFont.versionOnly || false;
if (versionOnly) {
return;
}
const registry = data.upstreamFont.registry ?? 'https://registry.npmjs.org';
const versionRange = data.upstreamFont.versionRange || '*';
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;
await this.addDevDependencies({ [packageName]: version });
}
async _addDependencies() {
const { data } = this;
if (!data.dependencies) {
return;
}
Object.entries(data.dependencies).forEach(async ([depName, depVersion]) => {
await this.addDependencies({ [depName]: depVersion });
});
}
_buildSteps() {
this._preScript();
this._renameSVGs();
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}`);
}
}
_renameSVGs() {
const { renameSVGs } = this.data.buildSteps;
if (!renameSVGs) {
return;
}
const { keepPostfix, location } = renameSVGs;
fs.mkdirSync('renamedSVGs');
if (keepPostfix) {
const files = fs.readdirSync(location);
files.forEach((file) => {
if (file.endsWith(`${keepPostfix}.svg`)) {
// Delete files that do not end with -16.svg
fs.copyFileSync(path.join(location, file), path.join('renamedSVGs', file.replace(keepPostfix, '')));
}
});
}
}
_fixSVGPaths() {
const { fixSVGPaths } = this.data.buildSteps;
if (!fixSVGPaths) {
return;
}
fs.mkdirSync('fixedSvg');
const location = fixSVGPaths.cleanup ? 'renamedSVGs' : fixSVGPaths.location;
const { exitCode } = this.spawnSync('../../node_modules/.bin/oslllo-svg-fixer', ['-s', location, '-d', 'fixedSvg'], { stdio: 'inherit' });
if (exitCode !== 0) {
throw new Error(`oslllo-svg-fixer exited with exitCode ${exitCode}`);
}
if (fixSVGPaths.cleanup) {
fs.rmSync('renamedSVgs', { recursive: true });
}
}
_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();
// ant-design
if (!data.packageName) {
throw new Error('packageName is required');
}
// Ant Design
data.name ||= data.packageName
.split('-')
.map((x) => x.charAt(0).toUpperCase() + x.slice(1))
.join(' ');
// AntDesign
data.className ||= data.packageName
.split('-')
.map((x) => x.charAt(0).toUpperCase() + x.slice(1))
.join('');
// AntDesign
data.postScriptName ||= data.className;
data.fontFileName ||= data.className;
// ant_design
data.androidName = data.packageName.replaceAll('-', '_');
data.buildSteps ||= {};
data.customReadme ||= false;
data.customAssets ||= false;
data.copyCustomFonts ||= 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';
}
if (data.versions) {
const versionTable = [];
data.versions.forEach((version) => {
versionTable.push(`| > ${version.rnvi} | ${version.upstream} |`);
});
data.versionTable = versionTable.join('\n');
}
return data;
}
}