react-native-builder-bob
Version:
CLI to build JavaScript files for React Native libraries
371 lines (365 loc) • 12.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.init = init;
var _path = _interopRequireDefault(require("path"));
var _fsExtra = _interopRequireDefault(require("fs-extra"));
var _kleur = _interopRequireDefault(require("kleur"));
var _dedent = _interopRequireDefault(require("dedent"));
var _isGitDirty = _interopRequireDefault(require("is-git-dirty"));
var _prompts = _interopRequireDefault(require("./utils/prompts"));
var _loadConfig = require("./utils/loadConfig");
var _package = _interopRequireDefault(require("../package.json"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
const FLOW_PRGAMA_REGEX = /\*?\s*@(flow)\b/m;
async function init() {
const root = process.cwd();
const projectPackagePath = _path.default.resolve(root, 'package.json');
if ((0, _isGitDirty.default)()) {
const {
shouldContinue
} = await (0, _prompts.default)({
type: 'confirm',
name: 'shouldContinue',
message: `The working directory is not clean.\n You should commit or stash your changes before configuring bob.\n Continue anyway?`,
initial: false
});
if (!shouldContinue) {
process.exit(0);
}
}
if (!(await _fsExtra.default.pathExists(projectPackagePath))) {
throw new Error(`Couldn't find a 'package.json' file in '${root}'.\n Are you in a project folder?`);
}
const pkg = JSON.parse(await _fsExtra.default.readFile(projectPackagePath, 'utf-8'));
const result = (0, _loadConfig.loadConfig)(root);
if (result?.config && pkg.devDependencies && _package.default.name in pkg.devDependencies) {
const {
shouldContinue
} = await (0, _prompts.default)({
type: 'confirm',
name: 'shouldContinue',
message: `The project seems to be already configured with bob.\n Do you want to overwrite the existing configuration?`,
initial: false
});
if (!shouldContinue) {
process.exit(0);
}
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const {
source
} = await (0, _prompts.default)({
type: 'text',
name: 'source',
message: 'Where are your source files?',
initial: 'src',
validate: input => Boolean(input)
});
let entryFile;
if (await _fsExtra.default.pathExists(_path.default.join(root, source, 'index.js'))) {
entryFile = 'index.js';
} else if (await _fsExtra.default.pathExists(_path.default.join(root, source, 'index.ts'))) {
entryFile = 'index.ts';
} else if (await _fsExtra.default.pathExists(_path.default.join(root, source, 'index.tsx'))) {
entryFile = 'index.tsx';
}
if (!entryFile) {
throw new Error(`Couldn't find a 'index.js'. 'index.ts' or 'index.tsx' file under '${source}'.\n Please re-run the CLI after creating it.`);
}
pkg.devDependencies = Object.fromEntries([...Object.entries(pkg.devDependencies || {}), [_package.default.name, `^${_package.default.version}`]].sort(([a], [b]) => a.localeCompare(b)));
const questions = [{
type: 'text',
name: 'output',
message: 'Where do you want to generate the output files?',
initial: 'lib',
validate: input => Boolean(input)
}, {
type: 'multiselect',
name: 'targets',
message: 'Which targets do you want to build?',
choices: [{
title: 'module - for modern setups',
value: 'module',
selected: true
}, {
title: 'commonjs - for legacy setups (Node.js < 20)',
value: 'commonjs',
selected: false
}, {
title: 'typescript - declaration files for typechecking',
value: 'typescript',
selected: /\.tsx?$/.test(entryFile)
}],
validate: input => Boolean(input.length)
}];
if (entryFile.endsWith('.js') && FLOW_PRGAMA_REGEX.test(await _fsExtra.default.readFile(_path.default.join(root, source, entryFile), 'utf-8'))) {
questions.push({
type: 'confirm',
name: 'flow',
message: 'Do you want to publish definitions for flow?',
initial: Object.keys(pkg.devDependencies || {}).includes('flow-bin')
});
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const {
output,
targets,
flow
} = await (0, _prompts.default)(questions);
const target = targets[0] === 'commonjs' || targets[0] === 'module' ? targets[0] : undefined;
const entries = {
source: `./${_path.default.join(source, entryFile)}`
};
let esm = false;
if (targets.includes('module')) {
esm = true;
entries.module = `./${_path.default.join(output, 'module', 'index.js')}`;
}
if (targets.includes('commonjs')) {
entries.commonjs = `./${_path.default.join(output, 'commonjs', 'index.js')}`;
}
const types = {};
if (targets.includes('typescript')) {
if (targets.includes('commonjs') && targets.includes('module')) {
types.require = `./${_path.default.join(output, 'typescript', 'commonjs', source, 'index.d.ts')}`;
types.import = `./${_path.default.join(output, 'typescript', 'module', source, 'index.d.ts')}`;
} else {
types.require = `./${_path.default.join(output, 'typescript', source, 'index.d.ts')}`;
types.import = types.require;
}
if (!(await _fsExtra.default.pathExists(_path.default.join(root, 'tsconfig.json')))) {
const {
tsconfig
} = await (0, _prompts.default)({
type: 'confirm',
name: 'tsconfig',
message: `You have enabled 'typescript' compilation, but we couldn't find a 'tsconfig.json' in project root.\n Generate one?`,
initial: true
});
if (tsconfig) {
await _fsExtra.default.writeJSON(_path.default.join(root, 'tsconfig.json'), {
compilerOptions: {
rootDir: '.',
allowUnreachableCode: false,
allowUnusedLabels: false,
esModuleInterop: true,
forceConsistentCasingInFileNames: true,
jsx: 'react-jsx',
lib: ['ESNext'],
module: 'ESNext',
moduleResolution: 'bundler',
noFallthroughCasesInSwitch: true,
noImplicitReturns: true,
noImplicitUseStrict: false,
noStrictGenericChecks: false,
noUncheckedIndexedAccess: true,
noUnusedLocals: true,
noUnusedParameters: true,
resolveJsonModule: true,
skipLibCheck: true,
strict: true,
target: 'ESNext',
verbatimModuleSyntax: true
}
}, {
spaces: 2
});
}
}
}
const prepare = 'bob build';
const files = [source, output, '!**/__tests__', '!**/__fixtures__', '!**/__mocks__'];
if (esm) {
let replace = false;
const exportsField = {
'.': {},
'./package.json': './package.json'
};
const importField = {
...(types.import ? {
types: types.import
} : null),
...(entries.module ? {
default: entries.module
} : null)
};
const requireField = {
...(types.require ? {
types: types.require
} : null),
...(entries.commonjs ? {
default: entries.commonjs
} : null)
};
if (targets.includes('commonjs') && targets.includes('module')) {
exportsField['.'] = {
source: entries.source,
import: importField,
require: requireField
};
} else if (targets.includes('commonjs')) {
exportsField['.'] = {
source: entries.source,
...requireField
};
} else if (targets.includes('module')) {
exportsField['.'] = {
source: entries.source,
...importField
};
}
if (pkg.exports && JSON.stringify(pkg.exports) !== JSON.stringify(exportsField)) {
replace = (await (0, _prompts.default)({
type: 'confirm',
name: 'replace',
message: `Your package.json has 'exports' field set.\n Do you want to replace it?`,
initial: true
})).replace;
} else {
replace = true;
}
if (replace) {
pkg.exports = exportsField;
}
}
const entryFields = {};
if (targets.includes('commonjs') && targets.includes('module')) {
entryFields.main = entries.commonjs;
entryFields.module = entries.module;
if (targets.includes('typescript')) {
entryFields.types = types.require;
}
} else if (targets.includes('commonjs')) {
entryFields.main = entries.commonjs;
if (targets.includes('typescript')) {
entryFields.types = types.require;
}
} else if (targets.includes('module')) {
entryFields.main = entries.module;
if (targets.includes('typescript')) {
entryFields.types = types.import;
}
}
for (const key in entryFields) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const entry = entryFields[key];
if (pkg[key] && pkg[key] !== entry) {
const {
replace
} = await (0, _prompts.default)({
type: 'confirm',
name: 'replace',
message: `Your package.json has the '${key}' field set to '${String(pkg[key])}'.\n Do you want to replace it with '${String(entry)}'?`,
initial: true
});
if (replace) {
pkg[key] = entry;
}
} else {
pkg[key] = entry;
}
}
if (pkg['react-native'] && (pkg['react-native'].startsWith(source) || pkg['react-native'].startsWith(`./${source}`))) {
const {
remove
} = await (0, _prompts.default)({
type: 'confirm',
name: 'remove',
message: `Your package.json has the 'react-native' field pointing to source code.\n This can cause problems when customizing babel configuration.\n Do you want to remove it?`,
initial: true
});
if (remove) {
delete pkg['react-native'];
}
}
if (pkg.scripts?.prepare && pkg.scripts.prepare !== prepare) {
const {
replace
} = await (0, _prompts.default)({
type: 'confirm',
name: 'replace',
message: `Your package.json has the 'scripts.prepare' field set to '${String(pkg.scripts.prepare)}'.\n Do you want to replace it with '${prepare}'?`,
initial: true
});
if (replace) {
pkg.scripts.prepare = prepare;
}
} else {
pkg.scripts = pkg.scripts || {};
pkg.scripts.prepare = prepare;
}
if (pkg.files) {
const pkgFiles = pkg.files;
if (files?.some(file => !pkgFiles.includes(file))) {
const {
update
} = await (0, _prompts.default)({
type: 'confirm',
name: 'update',
message: `Your package.json already has a 'files' field.\n Do you want to update it?`,
initial: true
});
if (update) {
pkg.files = [...files, ...pkg.files.filter(file => !files.includes(file))];
}
}
} else {
pkg.files = files;
}
pkg[_package.default.name] = {
source,
output,
targets: targets.map(t => {
if (t === target && flow) {
return [t, {
copyFlow: true
}];
}
if (t === 'commonjs' || t === 'module') {
return [t, {
esm
}];
}
return t;
})
};
if (pkg.jest) {
const entry = `<rootDir>/${output}/`;
if (pkg.jest.modulePathIgnorePatterns) {
const {
modulePathIgnorePatterns
} = pkg.jest;
if (!modulePathIgnorePatterns.includes(entry)) {
modulePathIgnorePatterns.push(entry);
}
} else {
pkg.jest.modulePathIgnorePatterns = [entry];
}
}
pkg.eslintIgnore = pkg.eslintIgnore || ['node_modules/'];
if (!pkg.eslintIgnore.includes(`${output}/`)) {
pkg.eslintIgnore.push(`${output}/`);
}
await _fsExtra.default.writeJSON(projectPackagePath, pkg, {
spaces: 2
});
const ignorefiles = [_path.default.join(root, '.gitignore'), _path.default.join(root, '.eslintignore')];
for (const ignorefile of ignorefiles) {
if (await _fsExtra.default.pathExists(ignorefile)) {
const content = await _fsExtra.default.readFile(ignorefile, 'utf-8');
if (!content.split('\n').includes(`${output}/`)) {
await _fsExtra.default.writeFile(ignorefile, `${content}\n# generated by bob\n${output}/\n`);
}
}
}
const packageManager = (await _fsExtra.default.pathExists(_path.default.join(root, 'yarn.lock'))) ? 'yarn' : 'npm';
process.stdout.write((0, _dedent.default)(`
Project ${_kleur.default.yellow(pkg.name)} configured successfully!
${_kleur.default.magenta(`${_kleur.default.bold('Perform last steps')} by running`)}${_kleur.default.gray(':')}
${_kleur.default.gray('$')} ${packageManager} install
${_kleur.default.yellow('Good luck!')}
`));
}
//# sourceMappingURL=init.js.map