import chalk from 'chalk'; import * as child_process from 'child_process'; import fetch from 'node-fetch'; import {compare, major} from 'semver'; import {getDependencyFileUrl} from './Config'; import {styledStringLength} from './StringUtils'; import {TerminalPassthru} from './TeminalPassthru'; import {TerminalConfirm} from './TerminalConfirm'; import table = require('text-table'); export namespace AppUpdater { /** * Assumes working dir is app dir */ export async function ensureDependencies( requireUpToDate: boolean, upgradeDeps: boolean, runtime: string, ignoreDevUpdates: boolean = true ) { ensurePublicPackageUsage(); const response = await fetch(getDependencyFileUrl(runtime)); const zipModules = await response.json(); const projectProdDeps = AppUpdater.getPackageDependencies(true); // build a table of updates if any dependencies are missing or outdated const updates: string[][] = []; const prodUpdates: string[] = []; for (const module of Object.keys(zipModules)) { const moduleMajorVersion = Object.keys(module).map((i) => parseInt(i, 10)).sort().reverse()[0]; const projectModuleVersion = projectProdDeps[module]; const majorVersion = (projectModuleVersion ? major(projectModuleVersion) : moduleMajorVersion).toString(); if (!(majorVersion in zipModules[module])) { console.log(chalk.red( `Major version of ${module} not supported. Please use one of supported major versions.` )); process.exit(1); } const stable = zipModules[module][majorVersion]['stable']; const rc = zipModules[module][majorVersion]['rc']; if (!projectModuleVersion || !isCurrent(stable, rc, projectModuleVersion)) { prodUpdates.push(`${module}@${stable}`); updates.push([ module, projectModuleVersion ? chalk.yellow(projectModuleVersion) : chalk.red('missing'), '=>', chalk.green(stable) ]); } } const devDependencies: any = { '@zaiusinc/app-dev-runtime': 'latest' }; const projectDevDeps = AppUpdater.getPackageDependencies(); const devUpdates: string[] = []; if (!ignoreDevUpdates) { for (const name of Object.keys(devDependencies)) { const currentVersion = devDependencies[name]; let updated = false; if (projectDevDeps[name] && currentVersion === 'latest') { // skip it continue; } else if (!projectDevDeps[name] && currentVersion === 'latest') { devUpdates.push(`${name}`); updated = true; } else if (!projectDevDeps[name] || compare(zipModules[name], projectDevDeps[name]) !== 0) { devUpdates.push(`${name}@${currentVersion}`); updated = true; } if (updated) { updates.push([ name, projectDevDeps[name] ? chalk.yellow(projectDevDeps[name]) : chalk.red('missing'), '=>', chalk.green(devDependencies[name]) ]); } } } if (updates.length > 0) { console.log(chalk.yellow('\nThe following dependencies are out of date:')); const t = table( [ ['Module', 'Your Version', '', 'Current Version'], ...updates ], { hsep: ' ', stringLength: styledStringLength } ); console.log(`\n${t}\n`); if (upgradeDeps || await TerminalConfirm.ask('Do you want to update now?')) { const updateCommand = `yarn add ${prodUpdates.join(' ')}`; if (prodUpdates.length > 0 && !TerminalPassthru.exec(updateCommand)) { console.log(chalk.red( `Failed to update dependencies. Run \`${chalk.white(updateCommand)}\` to update manually.` )); process.exit(1); } console.log(''); const updateDev = `yarn add --dev ${devUpdates.join(' ')}`; if (devUpdates.length > 0 && !TerminalPassthru.exec(updateDev)) { console.log(chalk.red( `Failed to update dev dependencies. Run \`${chalk.white(updateDev)}\` to update manually.` )); process.exit(1); } console.log(''); } else { console.log(chalk.yellow('Opti apps always build and run against the current versions of the SDKs.')); console.log(chalk.yellow('You must update your dependencies before uploading or remote validating your app.')); if (requireUpToDate) { process.exit(1); } else { console.log(''); } } } } export function getPackageDependencies(prod: boolean = false): {[name: string]: string} { try { const result = child_process.spawnSync( `yarn --no-progress ${prod ? '--prod ' : ''}-s list --json --pattern=@zaiusinc/`, {cwd: process.cwd(), shell: true} ); if (result.status === 0) { const tree = JSON.parse(result.stdout.toString()); const output: {[name: string]: string} = {}; for (const item of tree.data.trees) { const [name, version] = item.name.split(/(?!^)@/); output[name] = version; } return output; } else { console.error(result.stderr.toString()); } } catch (e) { console.error(e); } console.log(chalk.red("Unable to get package dependencies. Are you in your app's working directory?")); process.exit(1); } function isCurrent(stable: string, rc: string, version: string) { return compare(stable, version) === 0 || (rc && compare(rc, version) === 0); } function ensurePublicPackageUsage() { const result = child_process.spawnSync( 'yarn --no-progress --prod -s list --json --pattern="@zaius/app-sdk|@zaius/node-sdk"', {cwd: process.cwd(), shell: true} ); if (result.status === 0) { const tree = JSON.parse(result.stdout.toString()); if (!tree.data.trees.length) { return; } console.log( chalk.red( '@zaius/app-sdk & @zaius/node-sdk are no longer supported. ' + 'Please update your package.json to use the @zaiusinc scope for these packages.' ) ); process.exit(1); } console.log(chalk.red("Unable to get package dependencies. Are you in your app's working directory?")); process.exit(1); } }