Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion src/commands/app/install.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'])
Expand Down
34 changes: 34 additions & 0 deletions test/commands/app/install.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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']
Expand Down