diff --git a/.gitignore b/.gitignore index 55964d8e227..2dfa77403f6 100644 --- a/.gitignore +++ b/.gitignore @@ -122,6 +122,7 @@ packages/ #Other files *.DotSettings .vs/ +.vscode/ *project.lock.json jsconfig.json package-lock.json diff --git a/change/react-native-windows-2020-04-08-19-30-11-fork-localCLI.json b/change/react-native-windows-2020-04-08-19-30-11-fork-localCLI.json new file mode 100644 index 00000000000..4ca1e6568e3 --- /dev/null +++ b/change/react-native-windows-2020-04-08-19-30-11-fork-localCLI.json @@ -0,0 +1,8 @@ +{ + "type": "prerelease", + "comment": "Improve inner loop and error reporting (local CLI)", + "packageName": "react-native-windows", + "email": "asklar@microsoft.com", + "dependentChangeType": "patch", + "date": "2020-04-09T02:30:11.182Z" +} \ No newline at end of file diff --git a/vnext/local-cli/runWindows/runWindows.js b/vnext/local-cli/runWindows/runWindows.js index 3a4c9e40bea..8b951b663be 100644 --- a/vnext/local-cli/runWindows/runWindows.js +++ b/vnext/local-cli/runWindows/runWindows.js @@ -12,6 +12,7 @@ const {newError, newInfo} = require('./utils/commandWithProgress'); const info = require('./utils/info'); const msbuildtools = require('./utils/msbuildtools'); const autolink = require('./utils/autolink'); +const chalk = require('chalk'); async function runWindows(config, args, options) { const verbose = options.logging; @@ -71,8 +72,13 @@ async function runWindows(config, args, options) { ); } catch (e) { newError( - `Build failed with message ${e}. Check your build configuration.`, + `Build failed with message ${ + e.message + }. Check your build configuration.`, ); + if (e.logfile) { + console.log('See', chalk.bold(e.logfile)); + } process.exit(1); } } else { @@ -89,7 +95,7 @@ async function runWindows(config, args, options) { await deploy.deployToDesktop(options, verbose); } } catch (e) { - newError(`Failed to deploy: ${e.message}`); + newError(`Failed to deploy${e ? `: ${e.message}` : ''}`); process.exit(1); } } else { diff --git a/vnext/local-cli/runWindows/utils/WindowsStoreAppUtils.ps1 b/vnext/local-cli/runWindows/utils/WindowsStoreAppUtils.ps1 index 68a76ce1874..2a9ac5f0038 100644 --- a/vnext/local-cli/runWindows/utils/WindowsStoreAppUtils.ps1 +++ b/vnext/local-cli/runWindows/utils/WindowsStoreAppUtils.ps1 @@ -88,7 +88,7 @@ function EnableDevmode { New-Item -Path $RegistryKeyPath -ItemType Directory -Force } - Set-ItemProperty -Path $RegistryKeyPath -Name AllowDevelopmentWithoutDevLicense -Value 1 + Set-ItemProperty -Path $RegistryKeyPath -Name AllowDevelopmentWithoutDevLicense -Value 1 -ErrorAction Stop } # diff --git a/vnext/local-cli/runWindows/utils/build.js b/vnext/local-cli/runWindows/utils/build.js index 4d4ad38e431..620bd2aaaa5 100644 --- a/vnext/local-cli/runWindows/utils/build.js +++ b/vnext/local-cli/runWindows/utils/build.js @@ -42,7 +42,7 @@ async function buildSolution( } async function nugetRestore(nugetPath, slnFile, verbose, msbuildVersion) { - const text = 'Restoring NuGets'; + const text = 'Restoring NuGet packages '; const spinner = newSpinner(text); console.log(nugetPath); await commandWithProgress( @@ -74,7 +74,7 @@ async function restoreNuGetPackages(options, slnFile, verbose) { ensureNugetSpinner, dlNugetText, 'powershell', - `Invoke-WebRequest https://dist.nuget.org/win-x86-commandline/v4.9.2/nuget.exe -outfile ${nugetPath}`.split( + `$progressPreference = [System.Management.Automation.ActionPreference]::SilentlyContinue; Invoke-WebRequest https://dist.nuget.org/win-x86-commandline/v4.9.2/nuget.exe -outfile ${nugetPath}`.split( ' ', ), verbose, @@ -83,12 +83,21 @@ async function restoreNuGetPackages(options, slnFile, verbose) { ensureNugetSpinner.succeed('Found NuGet Binary'); const msbuildTools = MSBuildTools.findAvailableVersion('x86', verbose); - await nugetRestore( - nugetPath, - slnFile, - verbose, - msbuildTools.installationVersion, - ); + try { + await nugetRestore( + nugetPath, + slnFile, + verbose, + msbuildTools.installationVersion, + ); + } catch (e) { + if (!options.isRetryingNuget) { + const retryOptions = Object.assign({isRetryingNuget: true}, options); + fs.unlinkSync(nugetPath); + return restoreNuGetPackages(retryOptions, slnFile, verbose); + } + throw e; + } } function getSolutionFile(options) { diff --git a/vnext/local-cli/runWindows/utils/commandWithProgress.js b/vnext/local-cli/runWindows/utils/commandWithProgress.js index 75f43714ef3..be19f0ad790 100644 --- a/vnext/local-cli/runWindows/utils/commandWithProgress.js +++ b/vnext/local-cli/runWindows/utils/commandWithProgress.js @@ -41,6 +41,32 @@ function newSpinner(text) { return ora(options).start(); } +async function runPowerShellScriptFunction( + taskDescription, + script, + funcName, + verbose, +) { + try { + await commandWithProgress( + newSpinner(taskDescription), + taskDescription, + 'powershell', + [ + '-NoProfile', + '-ExecutionPolicy', + 'RemoteSigned', + `Import-Module "${script}"; ${funcName} -ErrorAction Stop; exit $LASTEXITCODE`, + ], + verbose, + ); + } catch { + // The error output from the process will be shown if verbose is set. + // We don't capture the process output if verbose is set, but at least we have the task name in text, so throw that. + throw new Error(taskDescription); + } +} + function commandWithProgress(spinner, taskDoingName, command, args, verbose) { return new Promise(function(resolve, reject) { const spawnOptions = verbose ? {stdio: 'inherit'} : {}; @@ -50,7 +76,7 @@ function commandWithProgress(spinner, taskDoingName, command, args, verbose) { } const cp = child_process.spawn(command, args, spawnOptions); - + let firstErrorLine = null; if (!verbose) { cp.stdout.on('data', chunk => { const text = chunk.toString(); @@ -58,10 +84,15 @@ function commandWithProgress(spinner, taskDoingName, command, args, verbose) { }); cp.stderr.on('data', chunk => { const text = chunk.toString(); + if (!firstErrorLine) { + firstErrorLine = text; + } if (verbose) { console.error(chalk.red(text)); } - setSpinnerText(spinner, taskDoingName + ': ERROR: ', text); + if (text) { + setSpinnerText(spinner, taskDoingName + ': ERROR: ', firstErrorLine); + } }); } cp.on('error', e => { @@ -105,4 +136,5 @@ module.exports = { newSpinner, newSuccess, newWarn, + runPowerShellScriptFunction, }; diff --git a/vnext/local-cli/runWindows/utils/deploy.js b/vnext/local-cli/runWindows/utils/deploy.js index 5bddc239f59..3edfeac27ba 100644 --- a/vnext/local-cli/runWindows/utils/deploy.js +++ b/vnext/local-cli/runWindows/utils/deploy.js @@ -20,6 +20,7 @@ const { newWarn, newSpinner, commandWithProgress, + runPowerShellScriptFunction, } = require('./commandWithProgress'); function pushd(pathArg) { @@ -166,36 +167,24 @@ async function deployToDesktop(options, verbose) { const popd = pushd(options.root); - const removingText = 'Removing old version of the app'; - await commandWithProgress( - newSpinner(removingText), - removingText, - 'powershell', - `-NoProfile -ExecutionPolicy RemoteSigned Import-Module "${windowsStoreAppUtils}" ; Uninstall-App ${appName}`.split( - ' ', - ), + await runPowerShellScriptFunction( + 'Removing old version of the app', + windowsStoreAppUtils, + `Uninstall-App ${appName}`, verbose, ); - const devmodeText = 'Enabling Developer Mode'; - const devmodeEnable = `-NoProfile -ExecutionPolicy RemoteSigned Import-Module "${windowsStoreAppUtils}"; EnableDevmode "${script}"`; - - await commandWithProgress( - newSpinner(devmodeText), - devmodeText, - 'powershell', - devmodeEnable.split(' '), + await runPowerShellScriptFunction( + 'Enabling Developer Mode', + windowsStoreAppUtils, + `EnableDevMode "${script}"`, verbose, ); - const installingText = 'Installing new version of the app'; - const installApp = `-NoProfile -ExecutionPolicy RemoteSigned Import-Module "${windowsStoreAppUtils}"; Install-App "${script}" -Force`; - - await commandWithProgress( - newSpinner(installingText), - installingText, - 'powershell', - installApp.split(' '), + await runPowerShellScriptFunction( + 'Installing new version of the app', + windowsStoreAppUtils, + `Install-App "${script}" -Force`, verbose, ); @@ -223,14 +212,10 @@ async function deployToDesktop(options, verbose) { ); if (shouldLaunchApp(options)) { - const startingText = 'Starting the app'; - await commandWithProgress( - newSpinner(startingText), - startingText, - 'powershell', - `-ExecutionPolicy RemoteSigned Import-Module "${windowsStoreAppUtils}"; Start-Locally ${appName} ${args}`.split( - ' ', - ), + await runPowerShellScriptFunction( + 'Starting the app', + windowsStoreAppUtils, + `Start-Locally ${appName} ${args}`, verbose, ); } else { diff --git a/vnext/local-cli/runWindows/utils/msbuildtools.js b/vnext/local-cli/runWindows/utils/msbuildtools.js index 00e8d616b5e..a6ad327a507 100644 --- a/vnext/local-cli/runWindows/utils/msbuildtools.js +++ b/vnext/local-cli/runWindows/utils/msbuildtools.js @@ -51,18 +51,18 @@ class MSBuildTools { newInfo(`Build configuration: ${buildType}`); newInfo(`Build platform: ${buildArch}`); - //const verbosityOption = verbose ? 'normal' : 'quiet'; - const verbosityOption = 'normal'; + const verbosityOption = verbose ? 'normal' : 'minimal'; + const errorLog = path.join(process.env.temp, `msbuild_${process.pid}.err`); const args = [ `/clp:NoSummary;NoItemAndPropertyList;Verbosity=${verbosityOption}`, '/nologo', `/p:Configuration=${buildType}`, `/p:Platform=${buildArch}`, '/p:AppxBundle=Never', + '/bl', + `/flp1:errorsonly;logfile=${errorLog}`, ]; - args.push('/bl'); - if (msBuildProps) { Object.keys(msBuildProps).forEach(function(key) { args.push(`/p:${key}=${msBuildProps[key]}`); @@ -73,20 +73,38 @@ class MSBuildTools { checkRequirements.isWinSdkPresent('10.0'); } catch (e) { newError(e.message); - return; + throw e; } - console.log(`Running MSBuild with args ${args.join(' ')}`); + if (verbose) { + console.log(`Running MSBuild with args ${args.join(' ')}`); + } const progressName = 'Building Solution'; const spinner = newSpinner(progressName); - await commandWithProgress( - spinner, - progressName, - path.join(this.path, 'msbuild.exe'), - [slnFile].concat(args), - verbose, - ); + try { + await commandWithProgress( + spinner, + progressName, + path.join(this.path, 'msbuild.exe'), + [slnFile].concat(args), + verbose, + ); + } catch (e) { + let error = e; + if (!e) { + const firstMessage = (await fs.promises.readFile(errorLog)) + .toString() + .split(EOL)[0]; + error = new Error(firstMessage); + error.logfile = errorLog; + } + throw error; + } + // If we have no errors, delete the error log when we're done + if ((await fs.promises.stat(errorLog)).size === 0) { + await fs.promises.unlink(errorLog); + } } }