Skip to content

Commit 408f360

Browse files
Merge master into feature/ui-e2e-tests
2 parents 7e76506 + 14242d6 commit 408f360

File tree

4 files changed

+101
-45
lines changed

4 files changed

+101
-45
lines changed

packages/amazonq/src/lsp/client.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ export async function startLanguageServer(
164164
developerProfiles: true,
165165
pinnedContextEnabled: true,
166166
mcp: true,
167+
modelSelection: true,
167168
workspaceFilePath: vscode.workspace.workspaceFile?.fsPath,
168169
},
169170
window: {

packages/core/src/awsService/appBuilder/serverlessLand/main.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { ExtContext } from '../../../shared/extensions'
1515
import { addFolderToWorkspace } from '../../../shared/utilities/workspaceUtils'
1616
import { ToolkitError } from '../../../shared/errors'
1717
import { fs } from '../../../shared/fs/fs'
18+
import { handleOverwriteConflict } from '../../../shared/utilities/messages'
1819
import { getPattern } from '../../../shared/utilities/downloadPatterns'
1920
import { MetadataManager } from './metadataManager'
2021

@@ -89,6 +90,9 @@ export async function launchProjectCreationWizard(
8990
export async function downloadPatternCode(config: CreateServerlessLandWizardForm, assetName: string): Promise<void> {
9091
const fullAssetName = assetName + '.zip'
9192
const location = vscode.Uri.joinPath(config.location, config.name)
93+
94+
await handleOverwriteConflict(location)
95+
9296
try {
9397
await getPattern(serverlessLandOwner, serverlessLandRepo, fullAssetName, location, true)
9498
} catch (error) {

packages/core/src/shared/utilities/messages.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import * as vscode from 'vscode'
77
import * as nls from 'vscode-nls'
8+
import * as path from 'path'
89
import * as localizedText from '../localizedText'
910
import { getLogger } from '../../shared/logger/logger'
1011
import { ProgressEntry } from '../../shared/vscode/window'
@@ -14,6 +15,8 @@ import { Timeout } from './timeoutUtils'
1415
import { addCodiconToString } from './textUtilities'
1516
import { getIcon, codicon } from '../icons'
1617
import globals from '../extensionGlobals'
18+
import { ToolkitError } from '../../shared/errors'
19+
import { fs } from '../../shared/fs/fs'
1720
import { openUrl } from './vsCodeUtils'
1821
import { AmazonQPromptSettings, ToolkitPromptSettings } from '../../shared/settings'
1922
import { telemetry, ToolkitShowNotification } from '../telemetry/telemetry'
@@ -140,6 +143,41 @@ export async function showViewLogsMessage(
140143
})
141144
}
142145

146+
/**
147+
* Checks if a path exists and prompts user for overwrite confirmation if it does.
148+
* @param path The file or directory path to check
149+
* @param itemName The name of the item for display in the message
150+
* @returns Promise<boolean> - true if should proceed (path doesn't exist or user confirmed overwrite)
151+
*/
152+
export async function handleOverwriteConflict(location: vscode.Uri): Promise<boolean> {
153+
if (!(await fs.exists(location))) {
154+
return true
155+
}
156+
157+
const choice = showConfirmationMessage({
158+
prompt: localize(
159+
'AWS.toolkit.confirmOverwrite',
160+
'{0} already exists in the selected directory, overwrite?',
161+
location.fsPath
162+
),
163+
confirm: localize('AWS.generic.overwrite', 'Yes'),
164+
cancel: localize('AWS.generic.cancel', 'No'),
165+
type: 'warning',
166+
})
167+
168+
if (!choice) {
169+
throw new ToolkitError(`Folder already exists: ${path.basename(location.fsPath)}`)
170+
}
171+
172+
try {
173+
await fs.delete(location, { recursive: true, force: true })
174+
} catch (error) {
175+
throw ToolkitError.chain(error, `Failed to delete existing folder: ${path.basename(location.fsPath)}`)
176+
}
177+
178+
return true
179+
}
180+
143181
/**
144182
* Shows a modal confirmation (warning) message with buttons to confirm or cancel.
145183
*

packages/core/src/test/awsService/appBuilder/serverlessLand/main.test.ts

Lines changed: 58 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { fs } from '../../../../shared/fs/fs'
2323
import * as downloadPatterns from '../../../../shared/utilities/downloadPatterns'
2424
import { ExtContext } from '../../../../shared/extensions'
2525
import { workspaceUtils } from '../../../../shared'
26+
import * as messages from '../../../../shared/utilities/messages'
2627
import * as downloadPattern from '../../../../shared/utilities/downloadPatterns'
2728
import * as wizardModule from '../../../../awsService/appBuilder/serverlessLand/wizard'
2829

@@ -80,63 +81,75 @@ describe('createNewServerlessLandProject', () => {
8081
})
8182
})
8283

84+
function assertDownloadPatternCall(getPatternStub: sinon.SinonStub, mockConfig: any) {
85+
const mockAssetName = 'test-project-sam-python.zip'
86+
const serverlessLandOwner = 'aws-samples'
87+
const serverlessLandRepo = 'serverless-patterns'
88+
const mockLocation = vscode.Uri.joinPath(mockConfig.location, mockConfig.name)
89+
90+
assert(getPatternStub.calledOnce)
91+
assert(getPatternStub.firstCall.args[0] === serverlessLandOwner)
92+
assert(getPatternStub.firstCall.args[1] === serverlessLandRepo)
93+
assert(getPatternStub.firstCall.args[2] === mockAssetName)
94+
assert(getPatternStub.firstCall.args[3].toString() === mockLocation.toString())
95+
assert(getPatternStub.firstCall.args[4] === true)
96+
}
97+
8398
describe('downloadPatternCode', () => {
8499
let sandbox: sinon.SinonSandbox
85100
let getPatternStub: sinon.SinonStub
101+
let mockConfig: any
86102

87103
beforeEach(function () {
88104
sandbox = sinon.createSandbox()
89105
getPatternStub = sandbox.stub(downloadPatterns, 'getPattern')
106+
mockConfig = {
107+
name: 'test-project',
108+
location: vscode.Uri.file('/test'),
109+
pattern: 'test-project-sam-python',
110+
runtime: 'python',
111+
iac: 'sam',
112+
assetName: 'test-project-sam-python',
113+
}
90114
})
91115
afterEach(function () {
92116
sandbox.restore()
117+
getPatternStub.restore()
93118
})
94-
const mockConfig = {
95-
name: 'test-project',
96-
location: vscode.Uri.file('/test'),
97-
pattern: 'test-project-sam-python',
98-
runtime: 'python',
99-
iac: 'sam',
100-
assetName: 'test-project-sam-python',
101-
}
102119
it('successfully downloads pattern code', async () => {
103-
const mockAssetName = 'test-project-sam-python.zip'
104-
const serverlessLandOwner = 'aws-samples'
105-
const serverlessLandRepo = 'serverless-patterns'
106-
const mockLocation = vscode.Uri.joinPath(mockConfig.location, mockConfig.name)
107-
108-
await downloadPatternCode(mockConfig, 'test-project-sam-python')
109-
assert(getPatternStub.calledOnce)
110-
assert(getPatternStub.firstCall.args[0] === serverlessLandOwner)
111-
assert(getPatternStub.firstCall.args[1] === serverlessLandRepo)
112-
assert(getPatternStub.firstCall.args[2] === mockAssetName)
113-
assert(getPatternStub.firstCall.args[3].toString() === mockLocation.toString())
114-
assert(getPatternStub.firstCall.args[4] === true)
115-
})
116-
it('handles download failure', async () => {
117-
const mockAssetName = 'test-project-sam-python.zip'
118-
const error = new Error('Download failed')
119-
getPatternStub.rejects(error)
120-
try {
121-
await downloadPatternCode(mockConfig, mockAssetName)
122-
assert.fail('Expected an error to be thrown')
123-
} catch (err: any) {
124-
assert.strictEqual(err.message, 'Failed to download pattern: Error: Download failed')
125-
}
120+
sandbox.stub(messages, 'handleOverwriteConflict').resolves(true)
121+
122+
await downloadPatternCode(mockConfig, mockConfig.assetName)
123+
assertDownloadPatternCall(getPatternStub, mockConfig)
124+
})
125+
it('downloads pattern when directory exists and user confirms overwrite', async function () {
126+
sandbox.stub(messages, 'handleOverwriteConflict').resolves(true)
127+
128+
await downloadPatternCode(mockConfig, mockConfig.assetName)
129+
assertDownloadPatternCall(getPatternStub, mockConfig)
130+
})
131+
it('throws error when directory exists and user cancels overwrite', async function () {
132+
const handleOverwriteStub = sandbox.stub(messages, 'handleOverwriteConflict')
133+
handleOverwriteStub.rejects(new Error('Folder already exists: test-project'))
134+
135+
await assert.rejects(
136+
() => downloadPatternCode(mockConfig, mockConfig.assetName),
137+
/Folder already exists: test-project/
138+
)
126139
})
127140
})
128141

129142
describe('openReadmeFile', () => {
130-
let sandbox: sinon.SinonSandbox
143+
let testsandbox: sinon.SinonSandbox
131144
let spyExecuteCommand: sinon.SinonSpy
132145

133146
beforeEach(function () {
134-
sandbox = sinon.createSandbox()
135-
spyExecuteCommand = sandbox.spy(vscode.commands, 'executeCommand')
147+
testsandbox = sinon.createSandbox()
148+
spyExecuteCommand = testsandbox.spy(vscode.commands, 'executeCommand')
136149
})
137150

138151
afterEach(function () {
139-
sandbox.restore()
152+
testsandbox.restore()
140153
})
141154
const mockConfig = {
142155
name: 'test-project',
@@ -148,43 +161,43 @@ describe('openReadmeFile', () => {
148161
}
149162
it('successfully opens README file', async () => {
150163
const mockReadmeUri = vscode.Uri.file('/test/README.md')
151-
sandbox.stub(main, 'getProjectUri').resolves(mockReadmeUri)
164+
testsandbox.stub(main, 'getProjectUri').resolves(mockReadmeUri)
152165

153-
sandbox.stub(fs, 'exists').resolves(true)
166+
testsandbox.stub(fs, 'exists').resolves(true)
154167

155168
// When
156169
await openReadmeFile(mockConfig)
157170
// Then
158-
sandbox.assert.calledWith(spyExecuteCommand, 'workbench.action.focusFirstEditorGroup')
159-
sandbox.assert.calledWith(spyExecuteCommand, 'markdown.showPreview')
171+
testsandbox.assert.calledWith(spyExecuteCommand, 'workbench.action.focusFirstEditorGroup')
172+
testsandbox.assert.calledWith(spyExecuteCommand, 'markdown.showPreview')
160173
})
161174

162175
it('handles missing README file', async () => {
163176
const mockReadmeUri = vscode.Uri.file('/test/file.md')
164-
sandbox.stub(main, 'getProjectUri').resolves(mockReadmeUri)
177+
testsandbox.stub(main, 'getProjectUri').resolves(mockReadmeUri)
165178

166-
sandbox.stub(fs, 'exists').resolves(false)
179+
testsandbox.stub(fs, 'exists').resolves(false)
167180

168181
// When
169182
await openReadmeFile(mockConfig)
170183
// Then
171-
sandbox.assert.neverCalledWith(spyExecuteCommand, 'markdown.showPreview')
184+
testsandbox.assert.neverCalledWith(spyExecuteCommand, 'markdown.showPreview')
172185
assert.ok(true, 'Function should return without throwing error when README is not found')
173186
})
174187

175188
it('handles error with opening README file', async () => {
176189
const mockReadmeUri = vscode.Uri.file('/test/README.md')
177-
sandbox.stub(main, 'getProjectUri').resolves(mockReadmeUri)
190+
testsandbox.stub(main, 'getProjectUri').resolves(mockReadmeUri)
178191

179-
sandbox.stub(fs, 'exists').rejects(new Error('File system error'))
192+
testsandbox.stub(fs, 'exists').rejects(new Error('File system error'))
180193

181194
// When
182195
await assert.rejects(() => openReadmeFile(mockConfig), {
183196
name: 'Error',
184197
message: 'Error processing README file',
185198
})
186199
// Then
187-
sandbox.assert.neverCalledWith(spyExecuteCommand, 'markdown.showPreview')
200+
testsandbox.assert.neverCalledWith(spyExecuteCommand, 'markdown.showPreview')
188201
})
189202
})
190203

0 commit comments

Comments
 (0)