browser-extension-manager
Version:
Browser Extension Manager dependency manager
485 lines (399 loc) • 13.7 kB
JavaScript
// Libraries
const Manager = new (require('../build.js'));
const logger = Manager.logger('setup');
const argv = Manager.getArguments();
const path = require('path');
const jetpack = require('fs-jetpack');
const version = require('wonderful-version');
const { execute, template, force } = require('node-powertools');
const NPM = require('npm-api');
const glob = require('glob').globSync;
const { minimatch } = require('minimatch');
// Load package
const package = Manager.getPackage('main');
const project = Manager.getPackage('project');
const manifest = Manager.getManifest();
// Load config
const templating = {
nodeVersion: version.major(package.engines.node),
};
// Dependency MAP
const DEPENDENCY_MAP = {
'gulp': 'dev',
}
// File MAP
const FILE_MAP = {
// Files to skip overwrite
'hooks/**/*': {
overwrite: false,
},
'src/**/*': {
overwrite: false,
},
'src/**/*.{html,md}': {
skip: (file) => {
// Get the name
const name = path.basename(file.name, path.extname(file.name));
const htmlFilePath = path.join(file.destination, `${name}.html`);
const mdFilePath = path.join(file.destination, `${name}.md`);
const htmlFileExists = jetpack.exists(htmlFilePath);
const mdFileExists = jetpack.exists(mdFilePath);
const eitherExists = htmlFileExists || mdFileExists;
// Skip if both files exist
return eitherExists;
},
},
// Files to rewrite path
// Removed because getting too confusing
// 'dist/pages/**/*': {
// path: (file) => file.source.replace('dist/pages', 'dist'),
// },
'_.gitignore': {
name: (file) => file.name.replace('_.gitignore', '.gitignore'),
},
// Files to run templating on
'.github/workflows/build.yml': {
template: templating,
},
'.nvmrc': {
template: templating,
},
}
module.exports = async function (options) {
// Fix options
options = options || {};
options.checkManager = force(options.checkManager || true, 'boolean');
options.checkNode = force(options.checkNode || true, 'boolean');
options.checkPeerDependencies = force(options.checkPeerDependencies || true, 'boolean');
options.setupScripts = force(options.setupScripts || true, 'boolean');
options.buildSiteFiles = force(options.buildSiteFiles || true, 'boolean');
options.buildSiteFilesInput = force(options.buildSiteFilesInput || ['**/*'], 'array');
options.checkLocality = force(options.checkLocality || true, 'boolean');
// Log
logger.log(`Welcome to ${package.name} v${package.version}!`);
logger.log(`options`, options);
// Prefix project
project.dependencies = project.dependencies || {};
project.devDependencies = project.devDependencies || {};
try {
// Log current working directory
await logCWD();
// Ensure this package is up-to-date
if (options.checkManager) {
await updateManager();
}
// Ensure proper node version
if (options.checkNode) {
await ensureNodeVersion();
}
// Run the setup
if (options.checkPeerDependencies) {
await ensurePeerDependencies();
}
// Setup scripts
if (options.setupScripts) {
await setupScripts();
}
// Build files
if (options.buildSiteFiles) {
await buildSiteFiles({ input: options.buildSiteFilesInput });
}
// Copy all files from src/defaults/dist on first run
// await copyDefaultDistFiles();
// Check which locality we are using
if (options.checkLocality) {
await checkLocality();
}
} catch (e) {
// Throw error
throw e;
}
};
async function logCWD() {
logger.log('Current working directory:', process.cwd());
// logger.log('Current working directory 2:', await execute('pwd'));
// logger.log('Current working directory 3:', await execute('ls -al'));
}
async function updateManager() {
const npm = new NPM();
// Get the latest version
const installedVersion = project.devDependencies[package.name];
const latestVersion = await npm.repo(package.name)
.package()
.then((pkg) => {
return pkg.version;
}, (e) => {
return '0.0.0';
});
const isUpToDate = version.is(installedVersion, '>=', latestVersion);
const levelDifference = version.levelDifference(installedVersion, latestVersion);
// Log
logVersionCheck(package.name, installedVersion, latestVersion, isUpToDate);
// Quit if local
if (installedVersion.startsWith('file:')) {
return;
}
// Check if we need to update
if (!isUpToDate) {
// Quit if major version difference
if (levelDifference === 'major') {
return logger.error(`Major version difference detected. Please update to ${latestVersion} manually.`);
}
// Install the latest version
await install(package.name, latestVersion);
}
}
async function ensureNodeVersion() {
const installedVersion = version.clean(process.version);
const requiredVersion = version.clean(package.engines.node);
const isUpToDate = version.is(installedVersion, '>=', requiredVersion);
// Log
logVersionCheck('Node.js', installedVersion, requiredVersion, isUpToDate);
// Check if we need to update
if (!isUpToDate) {
throw new Error(`Node version is out-of-date. Required version is ${requiredVersion}.`);
}
}
async function ensurePeerDependencies() {
const requiredPeerDependencies = package.peerDependencies || {};
// Loop through and make sure project has AT LEAST the required version
for (let [dependency, ver] of Object.entries(requiredPeerDependencies)) {
const projectDependencyVersion = version.clean(project.dependencies[dependency] || project.devDependencies[dependency]);
const location = DEPENDENCY_MAP[dependency] === 'dev' ? '--save-dev' : '';
const isUpToDate = version.is(projectDependencyVersion, '>=', ver);
// Clean version if needed
ver = version.clean(ver);
// Log
// logger.log('Checking peer dep:', dependency, '-->', projectDependencyVersion, '>=', ver);
logVersionCheck(dependency, projectDependencyVersion, ver, isUpToDate);
// Install if not found
if (
// Not found
!projectDependencyVersion
// Not the right version
|| !isUpToDate
) {
await install(dependency, ver, location);
}
}
}
function setupScripts() {
// Setup the scripts
project.scripts = project.scripts || {};
// Setup the scripts
Object.keys(package.projectScripts).forEach((key) => {
project.scripts[key] = package.projectScripts[key];
});
// Save the project
jetpack.write(path.join(process.cwd(), 'package.json'), project);
}
function checkLocality() {
const installedVersion = project.devDependencies[package.name];
if (installedVersion.startsWith('file:')) {
logger.warn(`⚠️⚠️⚠️ You are using the local version of ${package.name}. This WILL NOT WORK when published. ⚠️⚠️⚠️`);
}
}
function install(package, ver, location) {
// Default to latest
ver || 'latest';
// Clean version if needed
ver = ver === 'latest' ? ver : version.clean(ver);
// Build the command
let command = `npm install ${package}@${ver} ${location || '--save'}`;
// Log
logger.log('Installing:', command);
// Execute
return execute(command, { log: true })
.then(async () => {
// Read new project
const projectUpdated = jetpack.read(path.join(process.cwd(), 'package.json'), 'json');
// Log
logger.log('Installed:', package, ver);
// Update package object
project.dependencies = projectUpdated.dependencies;
project.devDependencies = projectUpdated.devDependencies;
});
}
function buildSiteFiles(options) {
options = options || {};
options.input = options.input || ['**/*'];
// Loop through all files in /defaults directory
const dir = path.join(__dirname, '..', 'defaults');
const input = [
// Files to include
// '**/*',
...options.input,
// Files to exclude
// Dist files
// '!dist/**',
'!**/.DS_Store', // TODO: NOT WORKING
]
// Get all files
const files = glob(input, {
cwd: dir,
dot: true,
nodir: true,
});
const debug = argv.debug
|| true;
// Log
logger.log(`Preparing to copy ${files.length} default files...`);
// Loop
for (const file of files) {
// Build item
const item = {
source: null,
destination: null,
name: null,
}
// Get the destination
// const source = path.join(dir, file);
// let destination = path.join(process.cwd(), file.replace('defaults/', ''));
// const filename = path.basename(destination);
// Set the item properties
item.source = path.dirname(path.join(dir, file));
item.name = path.basename(file);
item.destination = path.dirname(path.join(process.cwd(), file.replace('defaults/', '')));
// Get options
const options = getFileOptions(file);
const ogName = item.name;
// Quit if file is '_'
// We have files like this to trigger the creation of directories without them being ignored by git
if (item.name === '_') {
// First, create the directory around the file
jetpack.dir(item.destination);
// Skip
continue;
}
// Resolve name
if (typeof options.name === 'function') {
item.name = options.name(item);
}
// Resolve path
if (typeof options.path === 'function') {
item.destination = options.path(item);
}
// Resolve overwrite
if (typeof options.overwrite === 'function') {
options.overwrite = options.overwrite(item);
}
// Resolve skip
if (typeof options.skip === 'function') {
options.skip = options.skip(item);
}
// Log
if (debug) {
logger.log(`Copying defaults...`);
logger.log(` name: ${item.name}`);
logger.log(` from: ${item.source}`);
logger.log(` to: ${item.destination}`);
logger.log(` overwrite: ${options.overwrite}`);
logger.log(` skip: ${options.skip}`);
// logger.log('File:', file);
// logger.log('filename:', filename);
// logger.log('options:', options);
// // logger.log('contents:', jetpack.read(source));
// logger.log('source:', source);
// logger.log('Destination:', destination);
console.log('\n');
}
// Skip if needed
if (options.skip) {
continue;
}
// Get final paths
const finalSource = path.join(item.source, ogName);
const finalDestination = path.join(item.destination, item.name);
// console.log('---finalSource', finalSource);
// console.log('---finalDestination', finalDestination);
// Check if the file exists
const exists = jetpack.exists(finalDestination);
// Skip if exists and we don't want to overwrite
if (!options.overwrite && exists) {
continue;
}
// Run templating if needed
if (options.template) {
// Log
// logger.log('Running templating on:', destination);
// Run the templating
const contents = jetpack.read(finalSource);
const templated = template(contents, options.template);
// Write the file
jetpack.write(finalDestination, templated);
} else {
// Copy the file
jetpack.copy(finalSource, finalDestination, { overwrite: true });
}
}
}
// Copy default dist files
// async function copyDefaultDistFiles() {
// // Get the directory
// const dir = path.join(__dirname, '..', '..', 'defaults', 'dist');
// // Log
// logger.log(`Copying default dist files from ${dir}`);
// // Copy the files
// jetpack.copy(dir, 'dist', { overwrite: true });
// }
// async function copyDefaultDistFiles() {
// // Loop through all files in /defaults directory
// const dir = path.join(__dirname, '..', '..', 'defaults', 'dist');
// const input = [
// // Files to include
// '**/*',
// // Files to exclude
// '!**/.DS_Store', // TODO: NOT WORKING
// ]
// // Get all files
// const files = glob(input, {
// cwd: dir,
// dot: true,
// nodir: true,
// });
// // Log
// logger.log(`Preparing to copy ${files.length} default dist files...`);
// for (const file of files) {
// // const relativePath = path.relative(path.join(__dirname, '..', '..', 'defaults', 'dist'), file);
// // const destination = path.join(__dirname, '..', '..', 'dist', relativePath.replace('pages', ''));
// const source = path.join(dir, file);
// const destination = path.join(process.cwd(), file.replace('dist/pages', 'dist'));
// // Log
// if (true) {
// logger.log(`Copying default dist file...`);
// logger.log(` file: ${file}`);
// logger.log(` from: ${source}`);
// logger.log(` to: ${destination}`);
// console.log('\n');
// }
// // Ensure the directory exists
// // await jetpack.dir(path.dirname(destination));
// // Copy the file
// // await jetpack.copy(file, destination, { overwrite: true });
// }
// }
function getFileOptions(filePath) {
const defaults = {
overwrite: true,
name: null,
path: null,
template: null,
skip: false,
};
let options = { ...defaults };
// Loop through all patterns
for (const pattern in FILE_MAP) {
if (minimatch(filePath, pattern)) {
options = { ...options, ...FILE_MAP[pattern] };
}
}
return options;
}
function logVersionCheck(name, installedVersion, latestVersion, isUpToDate) {
// Quit if local
if (installedVersion.startsWith('file:')) {
isUpToDate = true;
}
// Log
logger.log(`Checking if ${name} is up to date (${logger.format.bold(installedVersion)} >= ${logger.format.bold(latestVersion)}): ${isUpToDate ? logger.format.green('Yes') : logger.format.red('No')}`);
}