@sleeperhq/mini-core
Version:
Core library frameworks for developing Sleeper Mini Apps.
190 lines (159 loc) • 6.13 kB
JavaScript
const { spawn } = require('child_process');
const os = require('os');
const path = require('path');
const readLine = require('readline');
const fs = require('fs');
const isWindows = os.platform() === 'win32';
const getCommands = (projectName) => {
// Pathing
const distPath = path.join('dist', projectName);
const zipFilePath = `${distPath}.zip`;
const assetsDestPath = {
ios: path.join(distPath, 'ios'),
android: path.join(distPath, 'android'),
};
const bundleOutputPath = {
ios: path.join(assetsDestPath["ios"], 'index.bundle'),
android: path.join(assetsDestPath["android"], 'index.bundle'),
};
const sourcemapOutputPath = {
ios: path.join(assetsDestPath["ios"], 'index.bundle.map'),
android: path.join(assetsDestPath["android"], 'index.bundle.map'),
};
const reactNativeCliPath = path.join('node_modules', 'react-native', 'cli.js');
// Commands
const removeDir = (isWindows ? 'rmdir /s /q ' : 'rm -rf ');
// TODO (Windows): double check this zip command.
const zip = isWindows ?
`powershell Compress-Archive -Path "${distPath}" -DestinationPath "${zipFilePath}"` :
`cd dist && zip -r "${projectName}.zip" * && cd -`;
const cleanIndex = `${removeDir} "${bundleOutputPath["ios"]}" "${bundleOutputPath["android"]}" "${sourcemapOutputPath["ios"]}" "${sourcemapOutputPath["android"]}"`;
const cleanAll = `${removeDir} dist node_modules`;
const cleanBuild = `${removeDir} "${distPath}" "${assetsDestPath["ios"]}" "${assetsDestPath["android"]}"`;
const install = `yarn install`;
const getBundleCommand = (platform) => {
return `node "${reactNativeCliPath}" webpack-bundle \
--entry-file ./node_modules/@sleeperhq/mini-core/start.tsx \
--platform ${platform} \
--dev false \
--reset-cache \
--bundle-output "${bundleOutputPath[platform]}" \
--sourcemap-output "${sourcemapOutputPath[platform]}" \
--minify true \
--assets-dest "${assetsDestPath[platform]}" \
--webpackConfig ./node_modules/@sleeperhq/mini-core/webpack.config.js`;
};
// Exposed
return {
bundleIOS: getBundleCommand('ios'),
bundleAndroid: getBundleCommand('android'),
zip,
cleanIndex,
cleanAll,
cleanBuild,
zipFilePath,
install,
}
}
const spawnProcess = (command, errorMessage) => {
return new Promise((resolve) => {
const child = isWindows
? spawn('cmd.exe', ['/c', command])
: spawn(command, { env: process.env, shell: process.env.SHELL || true });
child.stdout.on('data', (data) => {
process.stdout.write(data); // output the data to the console
});
child.stderr.on('data', (data) => {
process.stderr.write(data); // output the error to the console
});
child.on('close', (code) => {
if (code !== 0) {
printError(errorMessage || `command exited with non-zero code: ${code}`);
process.exit(1);
}
resolve();
});
});
};
const printError = (error) => {
console.error("\n\033[91m" + error + "\033[0m");
};
const printInfo = (message) => {
console.log("\n\033[96m" + message + "\033[0m");
};
const printComplete = (message) => {
console.log("\033[92m" + message + "\033[0m");
};
const getInput = (message, fallback) => {
const interface = readLine.createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve) => {
interface.question("\n\033[96m[current: " + fallback + "]\033[0m " + message, (name) => {
const result = name.trim();
if (!result) {
resolve(fallback);
} else {
resolve(name.trim());
}
interface.close();
});
});
};
const getProjectName = () => {
const appJsonPath = 'app.json';
const appJson = JSON.parse(fs.readFileSync(appJsonPath));
return appJson.name;
}
const setProjectName = (name) => {
const podfilePath = path.join('ios', 'Podfile');
const podfileString = fs.readFileSync(podfilePath).toString();
const newPodfileString = podfileString.replace(/target '.*'/, `target '${name}'`);
fs.writeFileSync(podfilePath, newPodfileString);
}
const validateProjectName = (name) => {
const regex = /^[a-zA-Z0-9_]+$/;
return regex.test(name);
}
const main = async () => {
// Enter project name.
const fallback = getProjectName();
const projectName = await getInput("Enter project name (return to skip): ", fallback);
if (!validateProjectName(projectName)) {
printError("Invalid project name. Only alphanumeric characters and underscores are allowed.");
process.exit(1);
}
if (projectName !== fallback) {
await spawnProcess(`yarn react-native-rename "${projectName}" --skipAllGitChecks`, "rename command exited with non-zero code");
setProjectName(projectName);
}
const commands = getCommands(projectName);
const shouldClean = await getInput("Clean and rebuild project? (y/n): ", 'y');
if (shouldClean === 'y') {
// Clean build folders
await spawnProcess(commands.cleanAll, "clean command exited with non-zero code");
// Run yarn
await spawnProcess(commands.install, "install command exited with non-zero code");
}
// Build iOS
printInfo("Building iOS...");
await spawnProcess(commands.bundleIOS, "webpack-bundle ios command exited with non-zero code");
printComplete("iOS build complete.");
// Build Android
printInfo("Building Android...");
await spawnProcess(commands.bundleAndroid, "webpack-bundle android command exited with non-zero code");
printComplete("Android build complete.");
// Copy the app.json file to the dist folder
fs.copyFileSync('app.json', path.join('dist', projectName, 'app.json'));
// Create Zip Archive
printInfo("Creating zip archive...");
await spawnProcess(commands.cleanIndex, "clean index command exited with non-zero code");
await spawnProcess(commands.zip, "zip command exited with non-zero code");
printComplete(`Zip archive created successfully at ${commands.zipFilePath}`);
// Clean build folders
await spawnProcess(commands.cleanBuild, "clean build command exited with non-zero code");
process.exit(0);
};
main();