From d4bdc80411374a6feb8860b85c55d8d3210d948b Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Sat, 5 Nov 2022 17:49:00 -0700 Subject: [PATCH] refactor: migrate import command to listr2 (#3048) * refactor: migrate import command to listr2 * refactor: clean up unused dependencies * Update packages/api/core/src/api/import.ts Co-authored-by: Erick Zhao Co-authored-by: Erick Zhao --- package.json | 4 - packages/api/cli/package.json | 1 - packages/api/core/package.json | 1 - packages/api/core/src/api/import.ts | 372 ++++++++++-------- packages/plugin/webpack/package.json | 1 - packages/utils/async-ora/package.json | 29 -- packages/utils/async-ora/src/index.ts | 4 - packages/utils/async-ora/src/ora-handler.ts | 87 ---- packages/utils/async-ora/src/ora.ts | 55 --- .../utils/async-ora/test/ora-handler_spec.ts | 170 -------- yarn.lock | 90 +---- 11 files changed, 210 insertions(+), 604 deletions(-) delete mode 100644 packages/utils/async-ora/package.json delete mode 100644 packages/utils/async-ora/src/index.ts delete mode 100644 packages/utils/async-ora/src/ora-handler.ts delete mode 100644 packages/utils/async-ora/src/ora.ts delete mode 100644 packages/utils/async-ora/test/ora-handler_spec.ts diff --git a/package.json b/package.json index fbf0b1c8e4..ff7dfc285f 100644 --- a/package.json +++ b/package.json @@ -65,16 +65,13 @@ "find-up": "^5.0.0", "form-data": "^4.0.0", "fs-extra": "^10.0.0", - "global": "^4.3.2", "got": "^11.8.5", "html-webpack-plugin": "^5.3.1", - "inquirer": "^8.0.0", "interpret": "^3.1.1", "lodash": "^4.17.20", "log-symbols": "^4.0.0", "mime-types": "^2.1.25", "node-fetch": "^2.6.7", - "ora": "^5.0.0", "parse-author": "^2.0.0", "pretty-ms": "^7.0.0", "progress": "^2.0.3", @@ -108,7 +105,6 @@ "@types/express-ws": "^3.0.0", "@types/fetch-mock": "^7.3.1", "@types/fs-extra": "^9.0.6", - "@types/inquirer": "^8.1.1", "@types/interpret": "^1.1.1", "@types/listr": "^0.14.2", "@types/lodash": "^4.14.166", diff --git a/packages/api/cli/package.json b/packages/api/cli/package.json index d67a939d33..d42a971d3b 100644 --- a/packages/api/cli/package.json +++ b/packages/api/cli/package.json @@ -24,7 +24,6 @@ "commander": "^4.1.1", "debug": "^4.3.1", "fs-extra": "^10.0.0", - "inquirer": "^8.0.0", "listr2": "^5.0.3", "semver": "^7.2.1" }, diff --git a/packages/api/core/package.json b/packages/api/core/package.json index 477945634e..24d1bc90ea 100644 --- a/packages/api/core/package.json +++ b/packages/api/core/package.json @@ -39,7 +39,6 @@ "sinon-chai": "^3.6.0" }, "dependencies": { - "@electron-forge/async-ora": "6.0.0", "@electron-forge/core-utils": "6.0.0", "@electron-forge/maker-base": "6.0.0", "@electron-forge/plugin-base": "6.0.0", diff --git a/packages/api/core/src/api/import.ts b/packages/api/core/src/api/import.ts index 6f733d8c65..d1db4e98b3 100644 --- a/packages/api/core/src/api/import.ts +++ b/packages/api/core/src/api/import.ts @@ -1,15 +1,14 @@ import path from 'path'; -import { asyncOra } from '@electron-forge/async-ora'; -import { updateElectronDependency } from '@electron-forge/core-utils'; +import { safeYarnOrNpm, updateElectronDependency } from '@electron-forge/core-utils'; import baseTemplate from '@electron-forge/template-base'; import chalk from 'chalk'; import debug from 'debug'; import fs from 'fs-extra'; +import { Listr } from 'listr2'; import { merge } from 'lodash'; import installDepList, { DepType, DepVersionRestriction } from '../util/install-dependencies'; -import { info, warn } from '../util/messages'; import { readRawPackageJson } from '../util/read-package-json'; import upgradeForgeConfig, { updateUpgradedForgeDevDeps } from '../util/upgrade-forge-config'; @@ -60,169 +59,212 @@ export default async ({ shouldUpdateScript, outDir, }: ImportOptions): Promise => { - const calculatedOutDir = outDir || 'out'; - asyncOra.interactive = interactive; - - d(`Attempting to import project in: ${dir}`); - if (!(await fs.pathExists(dir)) || !(await fs.pathExists(path.resolve(dir, 'package.json')))) { - throw new Error(`We couldn't find a project in: ${dir}`); - } - - if (typeof confirmImport === 'function') { - if (!(await confirmImport())) { - // TODO: figure out if we can just return early here - // eslint-disable-next-line no-process-exit - process.exit(0); - } - } - - await initGit(dir); - - const importDeps = ([] as string[]).concat(deps); - let importDevDeps = ([] as string[]).concat(devDeps); - let importExactDevDeps = ([] as string[]).concat(exactDevDeps); - - let packageJSON = await readRawPackageJson(dir); - if (!packageJSON.version) { - warn(interactive, chalk.yellow(`Please set the ${chalk.green('"version"')} in your application's package.json`)); - } - if (packageJSON.config && packageJSON.config.forge) { - if (packageJSON.config.forge.makers) { - warn(interactive, chalk.green('Existing Electron Forge configuration detected')); - if (typeof shouldContinueOnExisting === 'function') { - if (!(await shouldContinueOnExisting())) { - // TODO: figure out if we can just return early here - // eslint-disable-next-line no-process-exit - process.exit(0); - } - } - } else if (!(typeof packageJSON.config.forge === 'object')) { - warn( - interactive, - chalk.yellow( - "We can't tell if the Electron Forge config is compatible because it's in an external JavaScript file, not trying to convert it and continuing anyway" - ) - ); - } else { - d('Upgrading an Electron Forge < 6 project'); - packageJSON.config.forge = upgradeForgeConfig(packageJSON.config.forge); - importDevDeps = updateUpgradedForgeDevDeps(packageJSON, importDevDeps); - } - } - - packageJSON.dependencies = packageJSON.dependencies || {}; - packageJSON.devDependencies = packageJSON.devDependencies || {}; - - [importDevDeps, importExactDevDeps] = updateElectronDependency(packageJSON, importDevDeps, importExactDevDeps); - - const keys = Object.keys(packageJSON.dependencies).concat(Object.keys(packageJSON.devDependencies)); - const buildToolPackages: Record = { - '@electron/get': 'already uses this module as a transitive dependency', - '@electron/osx-sign': 'already uses this module as a transitive dependency', - 'electron-builder': 'provides mostly equivalent functionality', - 'electron-download': 'already uses this module as a transitive dependency', - 'electron-forge': 'replaced with @electron-forge/cli', - 'electron-installer-debian': 'already uses this module as a transitive dependency', - 'electron-installer-dmg': 'already uses this module as a transitive dependency', - 'electron-installer-flatpak': 'already uses this module as a transitive dependency', - 'electron-installer-redhat': 'already uses this module as a transitive dependency', - 'electron-packager': 'already uses this module as a transitive dependency', - 'electron-winstaller': 'already uses this module as a transitive dependency', + const listrOptions = { + concurrent: false, + rendererOptions: { + collapse: false, + collapseErrors: false, + }, + rendererSilent: !interactive, + rendererFallback: Boolean(process.env.DEBUG && process.env.DEBUG.includes('electron-forge')), }; - for (const key of keys) { - if (buildToolPackages[key]) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const explanation = buildToolPackages[key]!; - let remove = true; - if (typeof shouldRemoveDependency === 'function') { - remove = await shouldRemoveDependency(key, explanation); - } - - if (remove) { - delete packageJSON.dependencies[key]; - delete packageJSON.devDependencies[key]; - } - } - } - - packageJSON.scripts = packageJSON.scripts || {}; - d('reading current scripts object:', packageJSON.scripts); - - const updatePackageScript = async (scriptName: string, newValue: string) => { - if (packageJSON.scripts[scriptName] !== newValue) { - let update = true; - if (typeof shouldUpdateScript === 'function') { - update = await shouldUpdateScript(scriptName, newValue); - } - if (update) { - packageJSON.scripts[scriptName] = newValue; - } - } - }; - - await updatePackageScript('start', 'electron-forge start'); - await updatePackageScript('package', 'electron-forge package'); - await updatePackageScript('make', 'electron-forge make'); - - d('forgified scripts object:', packageJSON.scripts); - - const writeChanges = async () => { - await asyncOra('Writing modified package.json file', async () => { - await fs.writeJson(path.resolve(dir, 'package.json'), packageJSON, { spaces: 2 }); - }); - }; - - await writeChanges(); - - await asyncOra('Installing dependencies', async () => { - d('deleting old dependencies forcefully'); - await fs.remove(path.resolve(dir, 'node_modules/.bin/electron')); - await fs.remove(path.resolve(dir, 'node_modules/.bin/electron.cmd')); - - d('installing dependencies'); - await installDepList(dir, importDeps); - - d('installing devDependencies'); - await installDepList(dir, importDevDeps, DepType.DEV); - - d('installing exactDevDependencies'); - await installDepList(dir, importExactDevDeps, DepType.DEV, DepVersionRestriction.EXACT); - }); - - await asyncOra('Copying base template Forge configuration', async () => { - const pathToTemplateConfig = path.resolve(baseTemplate.templateDir, 'forge.config.js'); - - // if there's an existing config.forge object in package.json - if (packageJSON?.config?.forge && typeof packageJSON.config.forge === 'object') { - d('detected existing Forge config in package.json, merging with base template Forge config'); - // eslint-disable-next-line @typescript-eslint/no-var-requires - const templateConfig = require(path.resolve(baseTemplate.templateDir, 'forge.config.js')); - packageJSON = await readRawPackageJson(dir); - merge(templateConfig, packageJSON.config.forge); // mutates the templateConfig object - await writeChanges(); - // otherwise, write to forge.config.js - } else { - d('writing new forge.config.js'); - await fs.copyFile(pathToTemplateConfig, path.resolve(dir, 'forge.config.js')); - } - }); - - await asyncOra('Fixing .gitignore', async () => { - if (await fs.pathExists(path.resolve(dir, '.gitignore'))) { - const gitignore = await fs.readFile(path.resolve(dir, '.gitignore')); - if (!gitignore.includes(calculatedOutDir)) { - await fs.writeFile(path.resolve(dir, '.gitignore'), `${gitignore}\n${calculatedOutDir}/`); - } - } - }); - - info( - interactive, - ` - -We have attempted to convert your app to be in a format that Electron Forge understands. - -Thanks for using ${chalk.green('Electron Forge')}!` + const runner = new Listr( + [ + { + title: 'Locating importable project', + task: async () => { + d(`Attempting to import project in: ${dir}`); + if (!(await fs.pathExists(dir)) || !(await fs.pathExists(path.resolve(dir, 'package.json')))) { + throw new Error(`We couldn't find a project with a package.json file in: ${dir}`); + } + + if (typeof confirmImport === 'function') { + if (!(await confirmImport())) { + // TODO: figure out if we can just return early here + // eslint-disable-next-line no-process-exit + process.exit(0); + } + } + + await initGit(dir); + }, + }, + { + title: 'Processing configuration and dependencies', + options: { + persistentOutput: true, + bottomBar: Infinity, + }, + task: async (ctx, task) => { + const calculatedOutDir = outDir || 'out'; + + const importDeps = ([] as string[]).concat(deps); + let importDevDeps = ([] as string[]).concat(devDeps); + let importExactDevDeps = ([] as string[]).concat(exactDevDeps); + + let packageJSON = await readRawPackageJson(dir); + if (!packageJSON.version) { + task.output = chalk.yellow(`Please set the ${chalk.green('"version"')} in your application's package.json`); + } + if (packageJSON.config && packageJSON.config.forge) { + if (packageJSON.config.forge.makers) { + task.output = chalk.green('Existing Electron Forge configuration detected'); + if (typeof shouldContinueOnExisting === 'function') { + if (!(await shouldContinueOnExisting())) { + // TODO: figure out if we can just return early here + // eslint-disable-next-line no-process-exit + process.exit(0); + } + } + } else if (!(typeof packageJSON.config.forge === 'object')) { + task.output = chalk.yellow( + "We can't tell if the Electron Forge config is compatible because it's in an external JavaScript file, not trying to convert it and continuing anyway" + ); + } else { + d('Upgrading an Electron Forge < 6 project'); + packageJSON.config.forge = upgradeForgeConfig(packageJSON.config.forge); + importDevDeps = updateUpgradedForgeDevDeps(packageJSON, importDevDeps); + } + } + + packageJSON.dependencies = packageJSON.dependencies || {}; + packageJSON.devDependencies = packageJSON.devDependencies || {}; + + [importDevDeps, importExactDevDeps] = updateElectronDependency(packageJSON, importDevDeps, importExactDevDeps); + + const keys = Object.keys(packageJSON.dependencies).concat(Object.keys(packageJSON.devDependencies)); + const buildToolPackages: Record = { + '@electron/get': 'already uses this module as a transitive dependency', + '@electron/osx-sign': 'already uses this module as a transitive dependency', + 'electron-builder': 'provides mostly equivalent functionality', + 'electron-download': 'already uses this module as a transitive dependency', + 'electron-forge': 'replaced with @electron-forge/cli', + 'electron-installer-debian': 'already uses this module as a transitive dependency', + 'electron-installer-dmg': 'already uses this module as a transitive dependency', + 'electron-installer-flatpak': 'already uses this module as a transitive dependency', + 'electron-installer-redhat': 'already uses this module as a transitive dependency', + 'electron-packager': 'already uses this module as a transitive dependency', + 'electron-winstaller': 'already uses this module as a transitive dependency', + }; + + for (const key of keys) { + if (buildToolPackages[key]) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const explanation = buildToolPackages[key]!; + let remove = true; + if (typeof shouldRemoveDependency === 'function') { + remove = await shouldRemoveDependency(key, explanation); + } + + if (remove) { + delete packageJSON.dependencies[key]; + delete packageJSON.devDependencies[key]; + } + } + } + + packageJSON.scripts = packageJSON.scripts || {}; + d('reading current scripts object:', packageJSON.scripts); + + const updatePackageScript = async (scriptName: string, newValue: string) => { + if (packageJSON.scripts[scriptName] !== newValue) { + let update = true; + if (typeof shouldUpdateScript === 'function') { + update = await shouldUpdateScript(scriptName, newValue); + } + if (update) { + packageJSON.scripts[scriptName] = newValue; + } + } + }; + + await updatePackageScript('start', 'electron-forge start'); + await updatePackageScript('package', 'electron-forge package'); + await updatePackageScript('make', 'electron-forge make'); + + d('forgified scripts object:', packageJSON.scripts); + + const writeChanges = async () => { + await fs.writeJson(path.resolve(dir, 'package.json'), packageJSON, { spaces: 2 }); + }; + + return task.newListr( + [ + { + title: 'Installing dependencies', + task: async (_, task) => { + const packageManager = safeYarnOrNpm(); + await writeChanges(); + + d('deleting old dependencies forcefully'); + await fs.remove(path.resolve(dir, 'node_modules/.bin/electron')); + await fs.remove(path.resolve(dir, 'node_modules/.bin/electron.cmd')); + + d('installing dependencies'); + task.output = `${packageManager} install ${importDeps.join(' ')}`; + await installDepList(dir, importDeps); + + d('installing devDependencies'); + task.output = `${packageManager} install --dev ${importDevDeps.join(' ')}`; + await installDepList(dir, importDevDeps, DepType.DEV); + + d('installing exactDevDependencies'); + task.output = `${packageManager} install --dev --exact ${importExactDevDeps.join(' ')}`; + await installDepList(dir, importExactDevDeps, DepType.DEV, DepVersionRestriction.EXACT); + }, + }, + { + title: 'Copying base template Forge configuration', + task: async () => { + const pathToTemplateConfig = path.resolve(baseTemplate.templateDir, 'forge.config.js'); + + // if there's an existing config.forge object in package.json + if (packageJSON?.config?.forge && typeof packageJSON.config.forge === 'object') { + d('detected existing Forge config in package.json, merging with base template Forge config'); + // eslint-disable-next-line @typescript-eslint/no-var-requires + const templateConfig = require(path.resolve(baseTemplate.templateDir, 'forge.config.js')); + packageJSON = await readRawPackageJson(dir); + merge(templateConfig, packageJSON.config.forge); // mutates the templateConfig object + await writeChanges(); + // otherwise, write to forge.config.js + } else { + d('writing new forge.config.js'); + await fs.copyFile(pathToTemplateConfig, path.resolve(dir, 'forge.config.js')); + } + }, + }, + { + title: 'Fixing .gitignore', + task: async () => { + if (await fs.pathExists(path.resolve(dir, '.gitignore'))) { + const gitignore = await fs.readFile(path.resolve(dir, '.gitignore')); + if (!gitignore.includes(calculatedOutDir)) { + await fs.writeFile(path.resolve(dir, '.gitignore'), `${gitignore}\n${calculatedOutDir}/`); + } + } + }, + }, + ], + listrOptions + ); + }, + }, + { + title: 'Finalizing import', + options: { + persistentOutput: true, + bottomBar: Infinity, + }, + task: (_, task) => { + task.output = `We have attempted to convert your app to be in a format that Electron Forge understands. + + Thanks for using ${chalk.green('Electron Forge')}!`; + }, + }, + ], + listrOptions ); + + await runner.run(); }; diff --git a/packages/plugin/webpack/package.json b/packages/plugin/webpack/package.json index 82729a81ae..ae154dffb6 100644 --- a/packages/plugin/webpack/package.json +++ b/packages/plugin/webpack/package.json @@ -31,7 +31,6 @@ "chalk": "^4.0.0", "debug": "^4.3.1", "fs-extra": "^10.0.0", - "global": "^4.3.2", "html-webpack-plugin": "^5.3.1", "webpack": "^5.69.1", "webpack-dev-server": "^4.0.0", diff --git a/packages/utils/async-ora/package.json b/packages/utils/async-ora/package.json deleted file mode 100644 index cde3066bac..0000000000 --- a/packages/utils/async-ora/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "@electron-forge/async-ora", - "version": "6.0.0", - "description": "A helper utility for wrapping async functions in an ora", - "repository": "https://github.com/electron/forge", - "author": "Samuel Attard", - "license": "MIT", - "main": "dist/index.js", - "typings": "dist/index.d.ts", - "scripts": { - "test": "mocha --config ../../../.mocharc.js test/**/*_spec.ts" - }, - "devDependencies": { - "chai": "^4.3.3", - "mocha": "^9.0.1", - "proxyquire": "^2.1.3", - "sinon": "^13.0.1" - }, - "dependencies": { - "chalk": "^4.0.0", - "debug": "^4.3.1", - "log-symbols": "^4.0.0", - "ora": "^5.0.0", - "pretty-ms": "^7.0.0" - }, - "engines": { - "node": ">= 14.17.5" - } -} diff --git a/packages/utils/async-ora/src/index.ts b/packages/utils/async-ora/src/index.ts deleted file mode 100644 index d5d432e110..0000000000 --- a/packages/utils/async-ora/src/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import ora, { fakeOra } from './ora'; -import asyncOra, { OraImpl } from './ora-handler'; - -export { ora, fakeOra, asyncOra, OraImpl }; diff --git a/packages/utils/async-ora/src/ora-handler.ts b/packages/utils/async-ora/src/ora-handler.ts deleted file mode 100644 index 629af9626c..0000000000 --- a/packages/utils/async-ora/src/ora-handler.ts +++ /dev/null @@ -1,87 +0,0 @@ -import chalk from 'chalk'; - -import ora from './ora'; - -export class OraImpl { - constructor(public text: string = '') {} - - succeed(_symbol?: string): OraImpl { - return this; - } - - fail(_symbol?: string): OraImpl { - return this; - } - - start(): OraImpl { - return this; - } - - stop(_symbol?: string): OraImpl { - return this; - } - - warn(_message: string): OraImpl { - return this; - } -} - -export interface AsyncOraMethod { - (initialOraValue: string, asyncFn: (oraImpl: OraImpl) => Promise, processExitFn?: (code: number) => void): Promise; - interactive?: boolean; - - /** - * This will keep stdin unpaused if ora inadvertently pauses it. Beware that - * enabling this may keep the node process alive even when there is no more - * work to be done, as it will forever be waiting for input on stdin. - * - * More context: - * https://github.com/electron/forge/issues/2319 - */ - keepStdinFlowing?: boolean; -} - -const asyncOra: AsyncOraMethod = (initialOraValue, asyncFn, processExitFn = process.exit) => { - let fnOra = new OraImpl(initialOraValue); - if (asyncOra.interactive) { - fnOra = ora(initialOraValue).start(); - } - return new Promise((resolve, reject) => { - asyncFn(fnOra) - .then(() => { - const wasPaused = process.stdin.isPaused(); - - // Note: this may pause stdin as a side-effect in certain cases - fnOra.succeed(); - - if (asyncOra.keepStdinFlowing && !wasPaused && process.stdin.isPaused()) { - process.stdin.resume(); - } - - return resolve(); - }) - .catch((err) => { - fnOra.fail(); - if (asyncOra.interactive) { - if (err && err.message && err.stack) { - console.error(chalk.red('\nAn unhandled error has occurred inside Forge:')); - console.error(chalk.red(err.message)); - console.error(chalk.red(err.stack)); - } else { - console.error(chalk.red('\nElectron Forge was terminated:')); - console.error(chalk.red(typeof err === 'string' ? err : JSON.stringify(err))); - } - processExitFn(1); - // If the process is still alive we should continue because either - // something went really wrong or we are testing this function - setTimeout(() => resolve(), 500); - } else { - reject(err); - } - }); - }); -}; - -asyncOra.interactive = true; - -export default asyncOra; diff --git a/packages/utils/async-ora/src/ora.ts b/packages/utils/async-ora/src/ora.ts deleted file mode 100644 index 454ebdeb53..0000000000 --- a/packages/utils/async-ora/src/ora.ts +++ /dev/null @@ -1,55 +0,0 @@ -import chalk from 'chalk'; -import debug from 'debug'; -import 'log-symbols'; -import realOra from 'ora'; -import prettyMs from 'pretty-ms'; - -import { OraImpl } from './ora-handler'; - -const d = debug('electron-forge:async-ora'); - -const useFakeOra = Boolean(process.env.DEBUG && process.env.DEBUG.includes('electron-forge')); - -if (useFakeOra) { - console.warn(chalk.red('WARNING: DEBUG environment variable detected. Append the electron-forge:lifecycle namespace')); - console.warn(chalk.red('to the value of DEBUG in order to view progress indicators.')); -} - -export const fakeOra = (name: string): OraImpl => { - let oraName = name; - let startTime: number | null = null; - const timing = () => (startTime ? `-- after ${chalk.cyan(`${prettyMs(Date.now() - startTime)}`)}` : null); - const fake: OraImpl = { - start: () => { - startTime = Date.now(); - d('Process Started:', fake.text); - return fake; - }, - fail: () => { - d(chalk.red(`Process Failed: ${fake.text}`), timing()); - return fake; - }, - succeed: () => { - d('Process Succeeded:', fake.text, timing()); - return fake; - }, - stop: () => { - d('Process Stopped:', fake.text, timing()); - return fake; - }, - warn: (warning: string) => { - d('Process Warned:', warning, timing()); - return fake; - }, - get text() { - return oraName; - }, - set text(newName: string) { - d('Process Renamed:', oraName, ' --> ', newName); - oraName = newName; - }, - }; - return fake; -}; - -export default useFakeOra ? fakeOra : realOra; diff --git a/packages/utils/async-ora/test/ora-handler_spec.ts b/packages/utils/async-ora/test/ora-handler_spec.ts deleted file mode 100644 index 9b82367e62..0000000000 --- a/packages/utils/async-ora/test/ora-handler_spec.ts +++ /dev/null @@ -1,170 +0,0 @@ -/* eslint "no-underscore-dangle": "off" */ -import { expect } from 'chai'; -import proxyquire from 'proxyquire'; -import { spy } from 'sinon'; - -import { asyncOra as ora, OraImpl } from '../src/index'; - -type MockOra = OraImpl & { - _text: string; - failed: boolean; - started: boolean; - succeeded: boolean; -}; - -describe('asyncOra', () => { - let asyncOra: typeof ora; - let mockOra: (text: string) => MockOra | undefined; - let currentOra: MockOra | undefined; - - beforeEach(() => { - currentOra = undefined; - mockOra = (text) => { - currentOra = { - _text: '', - failed: false, - started: false, - succeeded: false, - warn() { - return currentOra; - }, - start() { - this.started = true; - return currentOra; - }, - succeed() { - this.succeeded = true; - return currentOra; - }, - fail() { - this.failed = true; - return currentOra; - }, - stop() { - this.failed = true; - return currentOra; - }, - get text(): string { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return currentOra!._text; - }, - set text(newText) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - currentOra!._text = newText; - }, - } as MockOra; - currentOra.succeeded = false; - currentOra.failed = false; - currentOra._text = text; - return currentOra; - }; - asyncOra = proxyquire.noCallThru().load('../src/ora-handler', { - './ora': mockOra, - }).default; - }); - - it('should create an ora with an initial value', () => { - asyncOra('say this first', async () => { - /* no-op async function */ - }); - expect(currentOra).to.not.equal(undefined); - // Why: We checked for undefined in the line above - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - expect(currentOra!.text).to.equal('say this first'); - }); - - it('should not create an ora when in non-interactive mode', () => { - asyncOra.interactive = false; - asyncOra('say this again', async () => { - /* no-op async function */ - }); - expect(currentOra).to.equal(undefined); - }); - - it('should call the provided async function', async () => { - const oraSpy = spy(); - await asyncOra('random text', async () => { - oraSpy(); - }); - expect(oraSpy.callCount).to.equal(1); - }); - - it('should succeed the ora if the async fn passes', async () => { - await asyncOra('random text', async () => { - // eslint-disable-next-line no-constant-condition - if (2 + 2 === 5) console.error('Big brother is at it again'); - }); - expect(currentOra).to.not.equal(undefined); - // Why: We checked for undefined in the line above - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - expect(currentOra!.succeeded).to.equal(true); - // Why: We checked for undefined in the line above - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - expect(currentOra!.failed).to.equal(false); - }); - - it('should fail the ora if the async fn throws', async () => { - await asyncOra( - 'this is gonna end badly', - async () => { - throw { message: 'Not an error', stack: 'No Stack - Not an error' }; - }, - () => { - /* no-op exit function */ - } - ); - expect(currentOra).to.not.equal(undefined); - // Why: We checked for undefined in the line above - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - expect(currentOra!.succeeded).to.equal(false); - // Why: We checked for undefined in the line above - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - expect(currentOra!.failed).to.equal(true); - }); - - it('should exit the process with status 1 if the async fn throws', async () => { - const processExitSpy = spy(); - await asyncOra( - 'this is dodge', - async () => { - throw new Error('woops'); - }, - processExitSpy - ); - expect(processExitSpy.callCount).to.equal(1); - expect(processExitSpy.firstCall.args).to.deep.equal([1]); - }); - - it('should exit the process with status 1 if the async fn throws a number', async () => { - const processExitSpy = spy(); - await asyncOra( - 'this is dodge', - async () => { - throw 42; - }, - processExitSpy - ); - expect(processExitSpy.callCount).to.equal(1); - expect(processExitSpy.firstCall.args).to.deep.equal([1]); - }); - - it('should just reject the promise in non-interactive mode if the fn throws', async () => { - asyncOra.interactive = false; - expect( - asyncOra('doo-wop', async () => { - throw new Error('uh oh'); - }) - ).to.eventually.be.rejectedWith('uh oh'); - }); - - it('should provide a fully functioning mock ora in non-interactive mode', async () => { - asyncOra.interactive = false; - await asyncOra('ora-magic', async (spinner) => { - expect(spinner).to.have.property('start'); - expect(spinner).to.have.property('stop'); - expect(spinner).to.have.property('succeed'); - expect(spinner).to.have.property('fail'); - expect(spinner.start().stop().fail().succeed()).to.equal(spinner); - }); - }); -}); diff --git a/yarn.lock b/yarn.lock index f57d324ae3..d0021ca8bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1895,14 +1895,6 @@ dependencies: "@types/node" "*" -"@types/inquirer@^8.1.1": - version "8.2.0" - resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-8.2.0.tgz#b9566d048f5ff65159f2ed97aff45fe0f00b35ec" - integrity sha512-BNoMetRf3gmkpAlV5we+kxyZTle7YibdOntIZbU5pyIfMdcwy784KfeZDAcuyMznkh5OLa17RVXZOGA5LTlkgQ== - dependencies: - "@types/through" "*" - rxjs "^7.2.0" - "@types/interpret@*", "@types/interpret@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@types/interpret/-/interpret-1.1.1.tgz#b1bf85b0420e2414b989ce237658ad20dc03719b" @@ -2086,13 +2078,6 @@ dependencies: "@types/node" "*" -"@types/through@*": - version "0.0.30" - resolved "https://registry.yarnpkg.com/@types/through/-/through-0.0.30.tgz#e0e42ce77e897bd6aead6f6ea62aeb135b8a3895" - integrity sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg== - dependencies: - "@types/node" "*" - "@types/which@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/which/-/which-2.0.1.tgz#27ecd67f915b7c3d6ba552135bb1eecd66e63501" @@ -2482,7 +2467,7 @@ ansi-escapes@^3.2.0: resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== -ansi-escapes@^4.2.1, ansi-escapes@^4.3.0: +ansi-escapes@^4.3.0: version "4.3.2" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== @@ -3208,11 +3193,6 @@ cli-width@^2.0.0: resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw== -cli-width@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" - integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== - cliui@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" @@ -3788,11 +3768,6 @@ dom-serializer@^2.0.0: domhandler "^5.0.2" entities "^4.2.0" -dom-walk@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84" - integrity sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w== - domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" @@ -4778,13 +4753,6 @@ figures@^2.0.0: dependencies: escape-string-regexp "^1.0.5" -figures@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" - integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== - dependencies: - escape-string-regexp "^1.0.5" - file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -5358,14 +5326,6 @@ global-tunnel-ng@^2.7.1: npm-conf "^1.1.3" tunnel "^0.0.6" -global@^4.3.2: - version "4.4.0" - resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406" - integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w== - dependencies: - min-document "^2.19.0" - process "^0.11.10" - globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" @@ -5792,26 +5752,6 @@ inquirer@^6.2.0: strip-ansi "^5.1.0" through "^2.3.6" -inquirer@^8.0.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.0.tgz#f44f008dd344bbfc4b30031f45d984e034a3ac3a" - integrity sha512-0crLweprevJ02tTuA6ThpoAERAGyVILC4sS74uib58Xf/zSr1/ZWtmm7D5CI+bSQEaA04f0K7idaHpQbSWgiVQ== - dependencies: - ansi-escapes "^4.2.1" - chalk "^4.1.1" - cli-cursor "^3.1.0" - cli-width "^3.0.0" - external-editor "^3.0.3" - figures "^3.0.0" - lodash "^4.17.21" - mute-stream "0.0.8" - ora "^5.4.1" - run-async "^2.4.0" - rxjs "^7.2.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - through "^2.3.6" - internal-slot@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" @@ -6731,13 +6671,6 @@ mimic-response@^3.1.0: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== -min-document@^2.19.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" - integrity sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU= - dependencies: - dom-walk "^0.1.0" - minimalistic-assert@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" @@ -6969,11 +6902,6 @@ mute-stream@0.0.7: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= -mute-stream@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" - integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== - nan@^2.4.0: version "2.15.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" @@ -7354,7 +7282,7 @@ optionator@^0.9.1: type-check "^0.4.0" word-wrap "^1.2.3" -ora@^5.0.0, ora@^5.1.0, ora@^5.4.1: +ora@^5.1.0: version "5.4.1" resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== @@ -7747,11 +7675,6 @@ process-on-spawn@^1.0.0: dependencies: fromentries "^1.2.0" -process@^0.11.10: - version "0.11.10" - resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= - progress@^2.0.0, progress@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" @@ -8177,7 +8100,7 @@ roarr@^2.15.3: semver-compare "^1.0.0" sprintf-js "^1.1.2" -run-async@^2.2.0, run-async@^2.4.0: +run-async@^2.2.0: version "2.4.1" resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== @@ -8196,13 +8119,6 @@ rxjs@^6.4.0, rxjs@^6.5.1: dependencies: tslib "^1.9.0" -rxjs@^7.2.0: - version "7.5.4" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.4.tgz#3d6bd407e6b7ce9a123e76b1e770dc5761aa368d" - integrity sha512-h5M3Hk78r6wAheJF0a5YahB1yRQKCsZ4MsGdZ5O9ETbVtjPcScGfrMmoOq7EBsCRzd4BDkvDJ7ogP8Sz5tTFiQ== - dependencies: - tslib "^2.1.0" - rxjs@^7.5.5, rxjs@^7.5.6: version "7.5.7" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.7.tgz#2ec0d57fdc89ece220d2e702730ae8f1e49def39"