Skip to content

Commit e3cfbbf

Browse files
authored
Temp Fix: Fallback to npm install when npm ci fails (#900)
1 parent 6dfd9f1 commit e3cfbbf

2 files changed

Lines changed: 45 additions & 1 deletion

File tree

src/commands/app/install.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,17 @@ class InstallCommand extends BaseCommand {
5454

5555
const packageLockPath = path.join(outputPath, PACKAGE_LOCK_FILE)
5656
if (fs.existsSync(packageLockPath)) {
57-
await this.npmCI(flags.verbose, flags['allow-scripts'])
57+
try {
58+
await this.npmCI(flags.verbose, flags['allow-scripts'])
59+
} catch (npmCIError) {
60+
// Log the npm ci failure for monitoring
61+
aioLogger.warn(`npm ci failed: ${npmCIError.message}`)
62+
aioLogger.debug('npm ci error details:', npmCIError)
63+
64+
// Fallback to npm install with a warning message
65+
this.warn('npm ci failed (lockfile out of sync). Falling back to npm install.')
66+
await this.npmInstall(flags.verbose, flags['allow-scripts'])
67+
}
5868
} else {
5969
this.warn('No lockfile found, running standard npm install. It is recommended that you include a lockfile with your app bundle.')
6070
await this.npmInstall(flags.verbose, flags['allow-scripts'])

test/commands/app/install.test.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,40 @@ describe('run', () => {
413413
expect(command.error).toHaveBeenCalledTimes(0)
414414
})
415415

416+
test('no flags, has lockfile, npm ci fails and falls back to npm install', async () => {
417+
const command = new TheCommand()
418+
command.argv = ['my-app.zip']
419+
420+
const npmCIError = new Error('npm ci can only install packages when your package.json and package-lock.json are in sync')
421+
422+
// we simulate npm ci failing and verify it falls back to npm install
423+
command.validateZipDirectoryStructure = jest.fn()
424+
command.unzipFile = jest.fn()
425+
command.addCodeDownloadAnnotation = jest.fn()
426+
command.validateDeployConfig = jest.fn()
427+
command.runTests = jest.fn()
428+
command.npmInstall = jest.fn()
429+
command.npmCI = jest.fn().mockRejectedValue(npmCIError)
430+
command.warn = jest.fn()
431+
command.error = jest.fn()
432+
433+
fs.existsSync.mockImplementation((filePath) => filePath === PACKAGE_LOCK_FILE)
434+
435+
await command.run()
436+
437+
expect(command.validateZipDirectoryStructure).toHaveBeenCalledTimes(1)
438+
expect(command.unzipFile).toHaveBeenCalledTimes(1)
439+
expect(libConfig.coalesce).toHaveBeenCalledTimes(1)
440+
expect(libConfig.validate).toHaveBeenCalledTimes(1)
441+
expect(command.validateDeployConfig).toHaveBeenCalledTimes(1)
442+
expect(command.runTests).toHaveBeenCalledTimes(1)
443+
expect(command.npmCI).toHaveBeenCalledTimes(1)
444+
expect(command.npmInstall).toHaveBeenCalledTimes(1)
445+
expect(command.warn).toHaveBeenCalledWith(expect.stringContaining('npm ci failed'))
446+
expect(command.warn).toHaveBeenCalledWith(expect.stringContaining('Falling back to npm install'))
447+
expect(command.error).toHaveBeenCalledTimes(0)
448+
})
449+
416450
test('subcommand throws error (--verbose)', async () => {
417451
const command = new TheCommand()
418452
command.argv = ['my-app.zip', '--verbose']

0 commit comments

Comments
 (0)