Skip to content

Commit 07e0ea8

Browse files
authored
Merge branch 'feature/q-dev-execution' into autoMerge/feature/q-dev-execution
2 parents bdbd8e8 + e06906f commit 07e0ea8

File tree

16 files changed

+302
-80
lines changed

16 files changed

+302
-80
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Feature",
3+
"description": "Add setting to allow Q /dev to run code and test commands"
4+
}

packages/amazonq/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,11 @@
127127
"markdownDescription": "%AWS.configuration.description.amazonq%",
128128
"default": true
129129
},
130+
"amazonQ.allowFeatureDevelopmentToRunCodeAndTests": {
131+
"markdownDescription": "%AWS.configuration.description.featureDevelopment.allowRunningCodeAndTests%",
132+
"type": "object",
133+
"default": {}
134+
},
130135
"amazonQ.importRecommendationForInlineCodeSuggestions": {
131136
"type": "boolean",
132137
"description": "%AWS.configuration.description.amazonq.importRecommendation%",

packages/amazonq/test/unit/amazonqFeatureDev/session/session.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ describe('session', () => {
3838
describe('preloader', () => {
3939
it('emits start chat telemetry', async () => {
4040
const session = await createSession({ messenger, conversationID, scheme: featureDevScheme })
41+
session.latestMessage = 'implement twosum in typescript'
4142

42-
await session.preloader('implement twosum in typescript')
43+
await session.preloader()
4344

4445
assertTelemetry('amazonq_startConversationInvoke', {
4546
amazonqConversationId: conversationID,

packages/amazonq/test/unit/amazonqFeatureDev/util/files.test.ts

Lines changed: 92 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,55 +10,113 @@ import {
1010
ContentLengthError,
1111
maxRepoSizeBytes,
1212
} from 'aws-core-vscode/amazonqFeatureDev'
13-
import { assertTelemetry, createTestWorkspace } from 'aws-core-vscode/test'
13+
import { assertTelemetry, getWorkspaceFolder, TestFolder } from 'aws-core-vscode/test'
1414
import { fs, AmazonqCreateUpload } from 'aws-core-vscode/shared'
15-
import { Span } from 'aws-core-vscode/telemetry'
15+
import { MetricName, Span } from 'aws-core-vscode/telemetry'
1616
import sinon from 'sinon'
17+
import { CodeWhispererSettings } from 'aws-core-vscode/codewhisperer'
18+
19+
import AdmZip from 'adm-zip'
20+
21+
const testDevfilePrepareRepo = async (devfileEnabled: boolean) => {
22+
const files: Record<string, string> = {
23+
'file.md': 'test content',
24+
// only include when execution is enabled
25+
'devfile.yaml': 'test',
26+
// .git folder is always dropped (because of vscode global exclude rules)
27+
'.git/ref': '####',
28+
// .gitignore should always be included
29+
'.gitignore': 'node_models/*',
30+
// non code files only when dev execution is enabled
31+
'abc.jar': 'jar-content',
32+
'data/logo.ico': 'binary-content',
33+
}
34+
const folder = await TestFolder.create()
35+
36+
for (const [fileName, content] of Object.entries(files)) {
37+
await folder.write(fileName, content)
38+
}
39+
40+
const expectedFiles = !devfileEnabled
41+
? ['./file.md', './.gitignore']
42+
: ['./devfile.yaml', './file.md', './.gitignore', './abc.jar', 'data/logo.ico']
43+
44+
const workspace = getWorkspaceFolder(folder.path)
45+
sinon
46+
.stub(CodeWhispererSettings.instance, 'getAutoBuildSetting')
47+
.returns(devfileEnabled ? { [workspace.uri.fsPath]: true } : {})
48+
49+
await testPrepareRepoData(workspace, expectedFiles)
50+
}
51+
52+
const testPrepareRepoData = async (
53+
workspace: vscode.WorkspaceFolder,
54+
expectedFiles: string[],
55+
expectedTelemetryMetrics?: Array<{ metricName: MetricName; value: any }>
56+
) => {
57+
expectedFiles.sort((a, b) => a.localeCompare(b))
58+
const telemetry = new TelemetryHelper()
59+
const result = await prepareRepoData([workspace.uri.fsPath], [workspace], telemetry, {
60+
record: () => {},
61+
} as unknown as Span<AmazonqCreateUpload>)
62+
63+
assert.strictEqual(Buffer.isBuffer(result.zipFileBuffer), true)
64+
// checksum is not the same across different test executions because some unique random folder names are generated
65+
assert.strictEqual(result.zipFileChecksum.length, 44)
66+
67+
if (expectedTelemetryMetrics) {
68+
for (const metric of expectedTelemetryMetrics) {
69+
assertTelemetry(metric.metricName, metric.value)
70+
}
71+
}
72+
73+
// Unzip the buffer and compare the entry names
74+
const zip = new AdmZip(result.zipFileBuffer)
75+
const actualZipEntries = zip.getEntries().map((entry) => entry.entryName)
76+
actualZipEntries.sort((a, b) => a.localeCompare(b))
77+
assert.deepStrictEqual(actualZipEntries, expectedFiles)
78+
}
1779

1880
describe('file utils', () => {
1981
describe('prepareRepoData', function () {
20-
it('returns files in the workspace as a zip', async function () {
21-
// these variables are a manual selection of settings for the test in order to test the collectFiles function
22-
const fileAmount = 2
23-
const fileNamePrefix = 'file'
24-
const fileNameSuffix = '.md'
25-
const fileContent = 'test content'
82+
afterEach(() => {
83+
sinon.restore()
84+
})
2685

27-
const workspace = await createTestWorkspace(fileAmount, { fileNamePrefix, fileContent, fileNameSuffix })
86+
it('returns files in the workspace as a zip', async function () {
87+
const folder = await TestFolder.create()
88+
await folder.write('file1.md', 'test content')
89+
await folder.write('file2.md', 'test content')
90+
const workspace = getWorkspaceFolder(folder.path)
2891

29-
const telemetry = new TelemetryHelper()
30-
const result = await prepareRepoData([workspace.uri.fsPath], [workspace], telemetry, {
31-
record: () => {},
32-
} as unknown as Span<AmazonqCreateUpload>)
33-
assert.strictEqual(Buffer.isBuffer(result.zipFileBuffer), true)
34-
// checksum is not the same across different test executions because some unique random folder names are generated
35-
assert.strictEqual(result.zipFileChecksum.length, 44)
36-
assert.strictEqual(telemetry.repositorySize, 24)
92+
await testPrepareRepoData(workspace, ['./file1.md', './file2.md'])
3793
})
3894

3995
it('prepareRepoData ignores denied file extensions', async function () {
40-
// these variables are a manual selection of settings for the test in order to test the collectFiles function
41-
const fileAmount = 1
42-
const fileNamePrefix = 'file'
43-
const fileNameSuffix = '.mp4'
44-
const fileContent = 'test content'
96+
const folder = await TestFolder.create()
97+
await folder.write('file.mp4', 'test content')
98+
const workspace = getWorkspaceFolder(folder.path)
4599

46-
const workspace = await createTestWorkspace(fileAmount, { fileNamePrefix, fileContent, fileNameSuffix })
47-
const telemetry = new TelemetryHelper()
48-
const result = await prepareRepoData([workspace.uri.fsPath], [workspace], telemetry, {
49-
record: () => {},
50-
} as unknown as Span<AmazonqCreateUpload>)
51-
52-
assert.strictEqual(Buffer.isBuffer(result.zipFileBuffer), true)
53-
// checksum is not the same across different test executions because some unique random folder names are generated
54-
assert.strictEqual(result.zipFileChecksum.length, 44)
55-
assert.strictEqual(telemetry.repositorySize, 0)
56-
assertTelemetry('amazonq_bundleExtensionIgnored', { filenameExt: 'mp4', count: 1 })
100+
await testPrepareRepoData(
101+
workspace,
102+
[],
103+
[{ metricName: 'amazonq_bundleExtensionIgnored', value: { filenameExt: 'mp4', count: 1 } }]
104+
)
105+
})
106+
107+
it('should ignore devfile.yaml when setting is disabled', async function () {
108+
await testDevfilePrepareRepo(false)
109+
})
110+
111+
it('should include devfile.yaml when setting is enabled', async function () {
112+
await testDevfilePrepareRepo(true)
57113
})
58114

59115
// Test the logic that allows the customer to modify root source folder
60116
it('prepareRepoData throws a ContentLengthError code when repo is too big', async function () {
61-
const workspace = await createTestWorkspace(1, {})
117+
const folder = await TestFolder.create()
118+
await folder.write('file.md', 'test content')
119+
const workspace = getWorkspaceFolder(folder.path)
62120
const telemetry = new TelemetryHelper()
63121

64122
sinon.stub(fs, 'stat').resolves({ size: 2 * maxRepoSizeBytes } as vscode.FileStat)

packages/core/package.nls.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"AWS.configuration.description.suppressPrompts": "Prompts which ask for confirmation. Checking an item suppresses the prompt.",
2121
"AWS.configuration.enableCodeLenses": "Enable SAM hints in source code and template.yaml files",
2222
"AWS.configuration.description.resources.enabledResources": "AWS resources to display in the 'Resources' portion of the explorer.",
23+
"AWS.configuration.description.featureDevelopment.allowRunningCodeAndTests": "Allow /dev to run code and test commands",
2324
"AWS.configuration.description.experiments": "Try experimental features and give feedback. Note that experimental features may be removed at any time.\n * `jsonResourceModification` - Enables basic create, update, and delete support for cloud resources via the JSON Resources explorer component.",
2425
"AWS.stepFunctions.asl.format.enable.desc": "Enables the default formatter used with Amazon States Language files",
2526
"AWS.stepFunctions.asl.maxItemsComputed.desc": "The maximum number of outline symbols and folding regions computed (limited for performance reasons).",
@@ -361,12 +362,18 @@
361362
"AWS.amazonq.featureDev.pillText.selectOption": "Choose an option to proceed",
362363
"AWS.amazonq.featureDev.pillText.unableGenerateChanges": "Unable to generate any file changes",
363364
"AWS.amazonq.featureDev.pillText.provideFeedback": "Provide feedback & regenerate",
364-
"AWS.amazonq.featureDev.answer.generateSuggestion": "Would you like to generate a suggestion for this? You'll review a file diff before inserting into your project.",
365+
"AWS.amazonq.featureDev.pillText.generateDevFile": "Generate devfile to build code",
366+
"AWS.amazonq.featureDev.pillText.acceptForProject": "Yes, use my devfile for this project",
367+
"AWS.amazonq.featureDev.pillText.declineForProject": "No, thanks",
368+
"AWS.amazonq.featureDev.answer.generateSuggestion": "Would you like to generate a suggestion for this? You’ll review a file diff before inserting into your project.",
365369
"AWS.amazonq.featureDev.answer.qGeneratedCode": "The Amazon Q Developer Agent for software development has generated code for you to review",
366370
"AWS.amazonq.featureDev.answer.howCodeCanBeImproved": "How can I improve the code for your use case?",
367371
"AWS.amazonq.featureDev.answer.updateCode": "Okay, I updated your code files. Would you like to work on another task?",
368372
"AWS.amazonq.featureDev.answer.sessionClosed": "Okay, I've ended this chat session. You can open a new tab to chat or start another workflow.",
369373
"AWS.amazonq.featureDev.answer.newTaskChanges": "What new task would you like to work on?",
374+
"AWS.amazonq.featureDev.answer.devFileSuggestion": "For future tasks in this project, I can create a devfile to build and test code as I generate it. This can improve the quality of generated code. To allow me to create a devfile, choose **Generate devfile to build code**.",
375+
"AWS.amazonq.featureDev.answer.settingUpdated": "I've updated your settings so I can run code and test commands based on your devfile for this project. You can update this setting under **Amazon Q: Allow Q /dev to run code and test commands**.",
376+
"AWS.amazonq.featureDev.answer.devFileInRepository": "I noticed that your repository has a `devfile.yaml`. Would you like me to use the devfile to build and test your project as I generate code? \n\nFor more information on using devfiles to improve code generation, see the <a href=\"https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/software-dev.html\" target=\"_blank\">Amazon Q Developer documentation</a>.",
370377
"AWS.amazonq.featureDev.placeholder.chatInputDisabled": "Chat input is disabled",
371378
"AWS.amazonq.featureDev.placeholder.additionalImprovements": "Describe your task or issue in detail",
372379
"AWS.amazonq.featureDev.placeholder.feedback": "Provide feedback or comments",

packages/core/src/amazonq/commons/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ export enum FollowUpTypes {
2424
NewTask = 'NewTask',
2525
CloseSession = 'CloseSession',
2626
SendFeedback = 'SendFeedback',
27+
AcceptAutoBuild = 'AcceptAutoBuild',
28+
DenyAutoBuild = 'DenyAutoBuild',
29+
GenerateDevFile = 'GenerateDevFile',
2730
// Doc
2831
CreateDocumentation = 'CreateDocumentation',
2932
ChooseFolder = 'ChooseFolder',

packages/core/src/amazonqFeatureDev/constants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ export const featureDevChat = 'featureDevChat'
1414

1515
export const featureName = 'Amazon Q Developer Agent for software development'
1616

17+
export const generateDevFilePrompt =
18+
"generate a devfile in my repository. Note that you should only use devfile version 2.0.0 and the only supported commands are install, build and test (are all optional). so you may have to bundle some commands together using '&&'. also you can use ”public.ecr.aws/aws-mde/universal-image:latest” as universal image if you aren’t sure which image to use. here is an example for a node repository (but don't assume it's always a node project. look at the existing repository structure before generating the devfile): schemaVersion: 2.0.0 components: - name: dev container: image: public.ecr.aws/aws-mde/universal-image:latest commands: - id: install exec: component: dev commandLine: ”npm install” - id: build exec: component: dev commandLine: ”npm run build” - id: test exec: component: dev commandLine: ”npm run test”"
19+
1720
// Max allowed size for file collection
1821
export const maxRepoSizeBytes = 200 * 1024 * 1024
1922

0 commit comments

Comments
 (0)