@react-native-community/bob
Version:
CLI to build JavaScript files for React Native libraries
343 lines (291 loc) • 10.2 kB
JavaScript
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = create;
exports.args = void 0;
var _path = _interopRequireDefault(require("path"));
var _fsExtra = _interopRequireDefault(require("fs-extra"));
var _ejs = _interopRequireDefault(require("ejs"));
var _dedent = _interopRequireDefault(require("dedent"));
var _chalk = _interopRequireDefault(require("chalk"));
var _crossSpawn = _interopRequireDefault(require("cross-spawn"));
var _validateNpmPackageName = _interopRequireDefault(require("validate-npm-package-name"));
var _githubUsername = _interopRequireDefault(require("github-username"));
var _prompts = _interopRequireDefault(require("./utils/prompts"));
var _package = _interopRequireDefault(require("../package.json"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const BINARIES = /(gradlew|\.(jar|keystore|png|jpg|gif))$/;
const COMMON_FILES = _path.default.resolve(__dirname, '../templates/common');
const JS_FILES = _path.default.resolve(__dirname, '../templates/js-library');
const EXPO_FILES = _path.default.resolve(__dirname, '../templates/expo-library');
const CPP_FILES = _path.default.resolve(__dirname, '../templates/cpp-library');
const EXAMPLE_FILES = _path.default.resolve(__dirname, '../templates/example'); // Android
const NATIVE_FILES = moduleType => {
switch (moduleType) {
case 'module':
return _path.default.resolve(__dirname, '../templates/native-library');
case 'view':
return _path.default.resolve(__dirname, '../templates/native-view-library');
}
}; // Objc
const OBJC_FILES = moduleType => {
switch (moduleType) {
case 'module':
return _path.default.resolve(__dirname, '../templates/objc-library');
case 'view':
return _path.default.resolve(__dirname, '../templates/objc-view-library');
}
}; // Swift
const SWIFT_FILES = moduleType => {
switch (moduleType) {
case 'module':
return _path.default.resolve(__dirname, '../templates/swift-library');
case 'view':
return _path.default.resolve(__dirname, '../templates/swift-view-library');
}
};
const args = {
'slug': {
description: 'Name of the npm package',
type: 'string'
},
'description': {
description: 'Description of the npm package',
type: 'string'
},
'author-name': {
description: 'Name of the package author',
type: 'string'
},
'author-email': {
description: 'Email address of the package author',
type: 'string'
},
'author-url': {
description: 'URL for the package author',
type: 'string'
},
'repo-url': {
description: 'URL for the repository',
type: 'string'
},
'type': {
description: 'Type package do you want to develop',
choices: ['native', 'native-swift', 'js', 'cpp', 'expo']
}
};
exports.args = args;
async function create(argv) {
const folder = _path.default.join(process.cwd(), argv.name);
if (await _fsExtra.default.pathExists(folder)) {
console.log(`A folder already exists at ${_chalk.default.blue(folder)}! Please specify another folder name or delete the existing one.`);
process.exit(1);
}
let name, email;
try {
name = _crossSpawn.default.sync('git', ['config', '--get', 'user.name']).stdout.toString().trim();
email = _crossSpawn.default.sync('git', ['config', '--get', 'user.email']).stdout.toString().trim();
} catch (e) {// Ignore error
}
const basename = _path.default.basename(argv.name);
const questions = {
'slug': {
type: 'text',
name: 'slug',
message: 'What is the name of the npm package?',
initial: (0, _validateNpmPackageName.default)(basename).validForNewPackages ? /^(@|react-native)/.test(basename) ? basename : `react-native-${basename}` : undefined,
validate: input => (0, _validateNpmPackageName.default)(input).validForNewPackages || 'Must be a valid npm package name'
},
'description': {
type: 'text',
name: 'description',
message: 'What is the description for the package?',
validate: input => Boolean(input) || 'Cannot be empty'
},
'author-name': {
type: 'text',
name: 'authorName',
message: 'What is the name of package author?',
initial: name,
validate: input => Boolean(input) || 'Cannot be empty'
},
'author-email': {
type: 'text',
name: 'authorEmail',
message: 'What is the email address for the package author?',
initial: email,
validate: input => /^\S+@\S+$/.test(input) || 'Must be a valid email address'
},
'author-url': {
type: 'text',
name: 'authorUrl',
message: 'What is the URL for the package author?',
// @ts-ignore: this is supported, but types are wrong
initial: async previous => {
try {
const username = await (0, _githubUsername.default)(previous);
return `https://github.com/${username}`;
} catch (e) {// Ignore error
}
return undefined;
},
validate: input => /^https?:\/\//.test(input) || 'Must be a valid URL'
},
'repo-url': {
type: 'text',
name: 'repoUrl',
message: 'What is the URL for the repository?',
// @ts-ignore: this is supported, but types are wrong
initial: (_, answers) => {
if (/^https?:\/\/github.com\/[^/]+/.test(answers.authorUrl)) {
return `${answers.authorUrl}/${answers.slug.replace(/^@/, '').replace(/\//g, '-')}`;
}
return '';
},
validate: input => /^https?:\/\//.test(input) || 'Must be a valid URL'
},
'type': {
type: 'select',
name: 'type',
message: 'What type of package do you want to develop?',
choices: [{
title: 'Native module in Kotlin and Objective-C',
value: 'native'
}, {
title: 'Native module in Kotlin and Swift',
value: 'native-swift'
}, {
title: 'Native module with C++ code',
value: 'cpp'
}, {
title: 'Native view in Kotlin and Objective-C',
value: 'native-view'
}, {
title: 'Native view in Kotlin and Swift',
value: 'native-view-swift'
}, {
title: 'JavaScript library with native example',
value: 'js'
}, {
title: 'JavaScript library with Expo example and Web support',
value: 'expo'
}]
}
};
const {
slug,
description,
authorName,
authorEmail,
authorUrl,
repoUrl,
type
} = { ...argv,
...(await (0, _prompts.default)(Object.entries(questions).filter(([k, v]) => !(argv[k] && v.validate ? v.validate(argv[k]) === true : Boolean(argv[k]))).map(([, v]) => v)))
};
const project = slug.replace(/^(react-native-|@[^/]+\/)/, '');
const moduleType = type === 'native-view' || type === 'native-view-swift' ? 'view' : 'module';
const options = {
bob: {
version: _package.default.version
},
project: {
slug,
description,
name: `${project.charAt(0).toUpperCase()}${project.replace(/[^a-z0-9](\w)/g, (_, $1) => $1.toUpperCase()).slice(1)}`,
package: slug.replace(/[^a-z0-9]/g, '').toLowerCase(),
podspec: slug.replace(/[^a-z0-9]+/g, '-').replace(/^-/, ''),
native: type === 'native' || type === 'cpp' || 'native-swift' || 'native-view' || 'native-view-swift',
cpp: type === 'cpp',
swift: type === 'native-swift' || 'native-view-swift',
module: type !== 'js',
moduleType
},
author: {
name: authorName,
email: authorEmail,
url: authorUrl
},
repo: repoUrl
};
const copyDir = async (source, dest) => {
await _fsExtra.default.mkdirp(dest);
const files = await _fsExtra.default.readdir(source);
for (const f of files) {
const target = _path.default.join(dest, _ejs.default.render(f.replace(/^\$/, ''), options, {
openDelimiter: '{',
closeDelimiter: '}'
}));
const file = _path.default.join(source, f);
const stats = await _fsExtra.default.stat(file);
if (stats.isDirectory()) {
await copyDir(file, target);
} else if (!file.match(BINARIES)) {
const content = await _fsExtra.default.readFile(file, 'utf8');
await _fsExtra.default.writeFile(target, _ejs.default.render(content, options));
} else {
await _fsExtra.default.copyFile(file, target);
}
}
};
await copyDir(COMMON_FILES, folder);
if (type === 'expo') {
await copyDir(JS_FILES, folder);
await copyDir(EXPO_FILES, folder);
} else if (type === 'js') {
await copyDir(JS_FILES, folder);
await copyDir(_path.default.join(EXAMPLE_FILES, 'example'), _path.default.join(folder, 'example'));
} else {
await copyDir(_path.default.join(EXAMPLE_FILES, 'example'), _path.default.join(folder, 'example'));
await copyDir(NATIVE_FILES(moduleType), folder);
if (type === 'cpp') {
await copyDir(CPP_FILES, folder);
} else if (type === 'native-swift') {
await copyDir(SWIFT_FILES(moduleType), folder);
} else {
await copyDir(OBJC_FILES(moduleType), folder);
}
}
try {
_crossSpawn.default.sync('git', ['init'], {
cwd: folder
});
_crossSpawn.default.sync('git', ['add', '.'], {
cwd: folder
});
_crossSpawn.default.sync('git', ['commit', '-m', 'chore: initial commit'], {
cwd: folder
});
} catch (e) {// Ignore error
}
const platforms = {
ios: {
name: 'iOS',
color: 'cyan'
},
android: {
name: 'Android',
color: 'green'
},
...(type === 'expo' ? {
web: {
name: 'Web',
color: 'blue'
}
} : null)
};
console.log((0, _dedent.default)((0, _chalk.default)`
Project created successfully at {yellow ${argv.name}}!
{magenta {bold Get started} with the project}{gray :}
{gray $} yarn
${Object.entries(platforms).map(([script, {
name,
color
}]) => (0, _chalk.default)`
{${color} Run the example app on {bold ${name}}}{gray :}
{gray $} yarn example ${script}`).join('\n')}
{yellow Good luck!}
`));
}
//# sourceMappingURL=create.js.map
;