Skip to content

Commit 858468c

Browse files
committed
feat: introduce general project zipping interface
1 parent 7d73cd4 commit 858468c

File tree

2 files changed

+106
-50
lines changed

2 files changed

+106
-50
lines changed

packages/core/src/amazonq/util/files.ts

Lines changed: 24 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import * as vscode from 'vscode'
77
import * as path from 'path'
88
import {
9-
collectFiles,
109
CollectFilesFilter,
1110
defaultExcludePatterns,
1211
getWorkspaceFoldersByPrefixes,
@@ -28,6 +27,7 @@ import { ZipStream } from '../../shared/utilities/zipStream'
2827
import { isPresent } from '../../shared/utilities/collectionUtils'
2928
import { AuthUtil } from '../../codewhisperer/util/authUtil'
3029
import { TelemetryHelper } from '../util/telemetryHelper'
30+
import { zipProject } from './zipProjectUtil'
3131

3232
export const SvgFileExtension = '.svg'
3333

@@ -102,86 +102,60 @@ export async function prepareRepoData(
102102
const fileSizeByteLimit = options?.fileSizeByteLimit
103103
? Math.min(options.fileSizeByteLimit, maxFileSizeBytes)
104104
: maxFileSizeBytes
105-
const zip = options?.zip ?? new ZipStream()
106105

107106
const autoBuildSetting = CodeWhispererSettings.instance.getAutoBuildSetting()
108107
const useAutoBuildFeature = autoBuildSetting[repoRootPaths[0]] ?? false
109108
const { excludePatterns, filterFn } = getFilterAndExcludePattern(useAutoBuildFeature, includeInfraDiagram)
110109

111-
const files = await collectFiles(repoRootPaths, workspaceFolders, {
112-
maxTotalSizeBytes: maxRepoSizeBytes,
113-
excludeByGitIgnore: true,
114-
excludePatterns: excludePatterns,
115-
filterFn: filterFn,
116-
})
117-
118-
let totalBytes = 0
119110
const ignoredExtensionMap = new Map<string, number>()
120-
const addedFilePaths = new Set()
121-
122-
for (const file of files) {
123-
if (addedFilePaths.has(file.zipFilePath) || !(await fs.exists(file.fileUri.fsPath))) {
124-
continue
125-
}
126-
addedFilePaths.add(file.zipFilePath)
127-
128-
const fileSize = (await fs.stat(file.fileUri.fsPath)).size
129-
130-
const isCodeFile_ = isCodeFile(file.relativeFilePath)
131-
const isDevFile = file.relativeFilePath === 'devfile.yaml'
132-
const isInfraDiagramFileExt = isInfraDiagramFile(file.relativeFilePath)
111+
const isExcluded = (relativeFilePath: string, fileSize: number) => {
112+
const isCodeFile_ = isCodeFile(relativeFilePath)
113+
const isDevFile = relativeFilePath === 'devfile.yaml'
114+
const isInfraDiagramFileExt = isInfraDiagramFile(relativeFilePath)
133115

134116
let isExcludeFile = fileSize >= fileSizeByteLimit
135117
// When useAutoBuildFeature is on, only respect the gitignore rules filtered earlier and apply the size limit
136118
if (!isExcludeFile && !useAutoBuildFeature) {
137119
isExcludeFile = isDevFile || (!isCodeFile_ && (!includeInfraDiagram || !isInfraDiagramFileExt))
138120
}
139-
121+
// Side-effect of isExcluded
140122
if (isExcludeFile) {
141123
if (!isCodeFile_) {
142124
const re = /(?:\.([^.]+))?$/
143-
const extensionArray = re.exec(file.relativeFilePath)
125+
const extensionArray = re.exec(relativeFilePath)
144126
const extension = extensionArray?.length ? extensionArray[1] : undefined
145127
if (extension) {
146128
const currentCount = ignoredExtensionMap.get(extension)
147129

148130
ignoredExtensionMap.set(extension, (currentCount ?? 0) + 1)
149131
}
150132
}
151-
continue
152133
}
153134

154-
totalBytes += fileSize
155-
// Paths in zip should be POSIX compliant regardless of OS
156-
// Reference: https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
157-
const posixPath = file.zipFilePath.split(path.sep).join(path.posix.sep)
158-
159-
try {
160-
zip.writeFile(file.fileUri.fsPath, posixPath)
161-
} catch (error) {
162-
if (error instanceof Error && error.message.includes('File not found')) {
163-
// No-op: Skip if file was deleted or does not exist
164-
// Reference: https://github.com/cthackers/adm-zip/blob/1cd32f7e0ad3c540142a76609bb538a5cda2292f/adm-zip.js#L296-L321
165-
continue
166-
}
167-
throw error
168-
}
135+
return isExcludeFile
169136
}
170137

138+
const zipResult = await zipProject(
139+
repoRootPaths,
140+
workspaceFolders,
141+
{
142+
maxTotalSizeBytes: maxRepoSizeBytes,
143+
excludeByGitIgnore: true,
144+
excludePatterns: excludePatterns,
145+
filterFn: filterFn,
146+
},
147+
isExcluded,
148+
{ zip: options?.zip ?? new ZipStream() }
149+
)
150+
171151
await emitIgnoredExtensionTelemetry(ignoredExtensionMap)
172152

173153
if (telemetry) {
174-
telemetry.setRepositorySize(totalBytes)
154+
telemetry.setRepositorySize(zipResult.totalFileBytes)
175155
}
176156

177-
span.record({ amazonqRepositorySize: totalBytes })
178-
const zipResult = await zip.finalize()
179-
180-
const zipFileBuffer = zipResult.streamBuffer.getContents() || Buffer.from('')
181-
return {
182-
zipFileBuffer,
183-
zipFileChecksum: zipResult.hash,
184-
}
157+
span.record({ amazonqRepositorySize: zipResult.totalFileBytes })
158+
return zipResult
185159
} catch (error) {
186160
getLogger().debug(`Failed to prepare repo: ${error}`)
187161
if (error instanceof ToolkitError && error.code === 'ContentLengthError') {
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import path from 'path'
7+
import { fs } from '../../shared/fs/fs'
8+
import { collectFiles, CollectFilesOptions } from '../../shared/utilities/workspaceUtils'
9+
import { CurrentWsFolders } from '../commons/types'
10+
import { ZipStream } from '../../shared/utilities/zipStream'
11+
import { hasCode } from '../../shared/errors'
12+
13+
export interface ZippedResult {
14+
zipFileBuffer: Buffer
15+
zipFileChecksum: string
16+
totalFileBytes: number
17+
}
18+
19+
interface ZipProjectOptions {
20+
zip?: ZipStream
21+
}
22+
23+
export async function zipProject(
24+
repoRootPaths: string[],
25+
workspaceFolders: CurrentWsFolders,
26+
collectFilesOptions: CollectFilesOptions,
27+
isExcluded: (relativePath: string, fileSize: number) => boolean,
28+
options?: ZipProjectOptions
29+
): Promise<ZippedResult> {
30+
const zip = options?.zip ?? new ZipStream()
31+
const files = await collectFiles(repoRootPaths, workspaceFolders, collectFilesOptions)
32+
const zippedFiles = new Set()
33+
let totalBytes: number = 0
34+
for (const file of files) {
35+
if (zippedFiles.has(file.zipFilePath)) {
36+
continue
37+
}
38+
zippedFiles.add(file.zipFilePath)
39+
40+
const fileSize = await fs
41+
.stat(file.fileUri.fsPath)
42+
.then((r) => r.size)
43+
.catch((e) => {
44+
if (hasCode(e) && e.code === 'ENOENT') {
45+
// No-op: Skip if file does not exist
46+
return
47+
}
48+
throw e
49+
})
50+
if (!fileSize) {
51+
continue
52+
}
53+
54+
if (isExcluded(file.relativeFilePath, fileSize)) {
55+
continue
56+
}
57+
58+
totalBytes += fileSize
59+
// Paths in zip should be POSIX compliant regardless of OS
60+
// Reference: https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
61+
const posixPath = file.zipFilePath.split(path.sep).join(path.posix.sep)
62+
63+
try {
64+
zip.writeFile(file.fileUri.fsPath, posixPath)
65+
} catch (error) {
66+
if (error instanceof Error && error.message.includes('File not found')) {
67+
// No-op: Skip if file was deleted or does not exist
68+
// Reference: https://github.com/cthackers/adm-zip/blob/1cd32f7e0ad3c540142a76609bb538a5cda2292f/adm-zip.js#L296-L321
69+
continue
70+
}
71+
throw error
72+
}
73+
}
74+
75+
const zipResult = await zip.finalize()
76+
const zipFileBuffer = zipResult.streamBuffer.getContents() || Buffer.from('')
77+
return {
78+
zipFileBuffer,
79+
zipFileChecksum: zipResult.hash,
80+
totalFileBytes: totalBytes,
81+
}
82+
}

0 commit comments

Comments
 (0)