import * as chalk from 'chalk'; import * as child_process from 'child_process'; 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'); import {DependencyStage} from './DependencyStage'; export namespace AppUpdater { /** * Assumes working dir is app dir */ export async function ensureDependencies( requireUpToDate: boolean, prompt: boolean, runtime: string, ignoreDevUpdates: boolean = true, stage: DependencyStage = 'stable' ) { ensurePublicPackageUsage(); const response = await fetch(getDependencyFileUrl(runtime)); const zipModules = (await response.json()) as any; 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 ocpVersion = zipModules[module][majorVersion][stage]; if (!projectModuleVersion || !isCurrent(ocpVersion, projectModuleVersion)) { prodUpdates.push(`${module}@${ocpVersion}`); updates.push([ module, projectModuleVersion ? chalk.yellow(projectModuleVersion) : chalk.red('missing'), '=>', chalk.green(ocpVersion) ]); } } 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 (await TerminalConfirm.ask('Do you want to update now?', prompt, true)) { const updateCommand = `yarn add ${prodUpdates.join(' ')}`; if (prodUpdates.length > 0 && !TerminalPassthru.exec(updateCommand)) { console.log(chalk.red( `Failed to update dependencies. Run \`${chalk.cyan(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.cyan(updateDev)}\` to update manually.` )); process.exit(1); } console.log(''); } else { console.log(chalk.yellow('OCP 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/ --non-interactive`, {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(ocpVersion: string, projectModuleversion: string) { return compare(ocpVersion, projectModuleversion) === 0; } function ensurePublicPackageUsage() { try { const result = child_process.spawnSync( 'yarn --no-progress --prod -s list --json --pattern="@zaius/app-sdk|@zaius/node-sdk" --non-interactive', {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); } 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); } }