From 5cfd1d2134a93a692bf9bfde837bd50fc0d729bf Mon Sep 17 00:00:00 2001 From: riddhi2910 Date: Wed, 4 Feb 2026 16:55:30 -0800 Subject: [PATCH] Temp Fix: Fallback to npm install when npm ci fails --- src/commands/app/install.js | 12 ++++++++++- test/commands/app/install.test.js | 34 +++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/commands/app/install.js b/src/commands/app/install.js index 8a1a331e..7cba0f87 100644 --- a/src/commands/app/install.js +++ b/src/commands/app/install.js @@ -54,7 +54,17 @@ class InstallCommand extends BaseCommand { const packageLockPath = path.join(outputPath, PACKAGE_LOCK_FILE) if (fs.existsSync(packageLockPath)) { - await this.npmCI(flags.verbose, flags['allow-scripts']) + try { + await this.npmCI(flags.verbose, flags['allow-scripts']) + } catch (npmCIError) { + // Log the npm ci failure for monitoring + aioLogger.warn(`npm ci failed: ${npmCIError.message}`) + aioLogger.debug('npm ci error details:', npmCIError) + + // Fallback to npm install with a warning message + this.warn('npm ci failed (lockfile out of sync). Falling back to npm install.') + await this.npmInstall(flags.verbose, flags['allow-scripts']) + } } else { this.warn('No lockfile found, running standard npm install. It is recommended that you include a lockfile with your app bundle.') await this.npmInstall(flags.verbose, flags['allow-scripts']) diff --git a/test/commands/app/install.test.js b/test/commands/app/install.test.js index 1f0f0b5b..1f76a39e 100644 --- a/test/commands/app/install.test.js +++ b/test/commands/app/install.test.js @@ -413,6 +413,40 @@ describe('run', () => { expect(command.error).toHaveBeenCalledTimes(0) }) + test('no flags, has lockfile, npm ci fails and falls back to npm install', async () => { + const command = new TheCommand() + command.argv = ['my-app.zip'] + + const npmCIError = new Error('npm ci can only install packages when your package.json and package-lock.json are in sync') + + // we simulate npm ci failing and verify it falls back to npm install + command.validateZipDirectoryStructure = jest.fn() + command.unzipFile = jest.fn() + command.addCodeDownloadAnnotation = jest.fn() + command.validateDeployConfig = jest.fn() + command.runTests = jest.fn() + command.npmInstall = jest.fn() + command.npmCI = jest.fn().mockRejectedValue(npmCIError) + command.warn = jest.fn() + command.error = jest.fn() + + fs.existsSync.mockImplementation((filePath) => filePath === PACKAGE_LOCK_FILE) + + await command.run() + + expect(command.validateZipDirectoryStructure).toHaveBeenCalledTimes(1) + expect(command.unzipFile).toHaveBeenCalledTimes(1) + expect(libConfig.coalesce).toHaveBeenCalledTimes(1) + expect(libConfig.validate).toHaveBeenCalledTimes(1) + expect(command.validateDeployConfig).toHaveBeenCalledTimes(1) + expect(command.runTests).toHaveBeenCalledTimes(1) + expect(command.npmCI).toHaveBeenCalledTimes(1) + expect(command.npmInstall).toHaveBeenCalledTimes(1) + expect(command.warn).toHaveBeenCalledWith(expect.stringContaining('npm ci failed')) + expect(command.warn).toHaveBeenCalledWith(expect.stringContaining('Falling back to npm install')) + expect(command.error).toHaveBeenCalledTimes(0) + }) + test('subcommand throws error (--verbose)', async () => { const command = new TheCommand() command.argv = ['my-app.zip', '--verbose']