Skip to content

Commit 1b62e00

Browse files
authored
S3 list folders before files (#367)
1 parent 47bc183 commit 1b62e00

File tree

2 files changed

+30
-29
lines changed

2 files changed

+30
-29
lines changed

package.json

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,32 +59,32 @@
5959
"hyparquet": "1.24.0",
6060
"hyparquet-compressors": "1.1.1",
6161
"icebird": "0.3.1",
62-
"squirreling": "0.7.8"
62+
"squirreling": "0.7.9"
6363
},
6464
"devDependencies": {
65-
"@storybook/react-vite": "10.1.11",
66-
"@testing-library/react": "16.3.1",
67-
"@types/node": "25.0.9",
68-
"@types/react": "19.2.8",
65+
"@storybook/react-vite": "10.2.0",
66+
"@testing-library/react": "16.3.2",
67+
"@types/node": "25.0.10",
68+
"@types/react": "19.2.9",
6969
"@types/react-dom": "19.2.3",
7070
"@vitejs/plugin-react": "5.1.2",
71-
"@vitest/coverage-v8": "4.0.17",
71+
"@vitest/coverage-v8": "4.0.18",
7272
"eslint": "9.39.2",
7373
"eslint-plugin-react": "7.37.5",
7474
"eslint-plugin-react-hooks": "7.0.1",
7575
"eslint-plugin-react-refresh": "0.4.26",
76-
"eslint-plugin-storybook": "10.1.11",
77-
"globals": "17.0.0",
76+
"eslint-plugin-storybook": "10.2.0",
77+
"globals": "17.1.0",
7878
"jsdom": "27.4.0",
7979
"nodemon": "3.1.11",
8080
"npm-run-all": "4.1.5",
8181
"react": "19.2.3",
8282
"react-dom": "19.2.3",
83-
"storybook": "10.1.11",
83+
"storybook": "10.2.0",
8484
"typescript": "5.9.3",
85-
"typescript-eslint": "8.53.0",
85+
"typescript-eslint": "8.53.1",
8686
"vite": "7.3.1",
87-
"vitest": "4.0.17"
87+
"vitest": "4.0.18"
8888
},
8989
"peerDependencies": {
9090
"react": "^18.3.1 || ^19",

src/lib/sources/httpSource.ts

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,23 @@ async function s3list(bucket: string, prefix: string): Promise<S3ListItem[]> {
1818
const text = await result.text()
1919
const results: S3ListItem[] = []
2020

21+
// Parse CommonPrefixes (virtual directories)
22+
const prefixRegex = /<CommonPrefixes>(.*?)<\/CommonPrefixes>/gs
23+
const prefixMatches = text.match(prefixRegex) ?? []
24+
25+
for (const match of prefixMatches) {
26+
const prefixMatch = /<Prefix>(.*?)<\/Prefix>/.exec(match)
27+
if (!prefixMatch) continue
28+
29+
const key = prefixMatch[1]
30+
results.push({
31+
key,
32+
lastModified: new Date().toISOString(), // No lastModified for CommonPrefixes
33+
size: 0,
34+
isCommonPrefix: true,
35+
})
36+
}
37+
2138
// Parse regular objects (files and explicit directories)
2239
const contentsRegex = /<Contents>(.*?)<\/Contents>/gs
2340
const contentsMatches = text.match(contentsRegex) ?? []
@@ -38,23 +55,6 @@ async function s3list(bucket: string, prefix: string): Promise<S3ListItem[]> {
3855
results.push({ key, lastModified, size, eTag })
3956
}
4057

41-
// Parse CommonPrefixes (virtual directories)
42-
const prefixRegex = /<CommonPrefixes>(.*?)<\/CommonPrefixes>/gs
43-
const prefixMatches = text.match(prefixRegex) ?? []
44-
45-
for (const match of prefixMatches) {
46-
const prefixMatch = /<Prefix>(.*?)<\/Prefix>/.exec(match)
47-
if (!prefixMatch) continue
48-
49-
const key = prefixMatch[1]
50-
results.push({
51-
key,
52-
lastModified: new Date().toISOString(), // No lastModified for CommonPrefixes
53-
size: 0,
54-
isCommonPrefix: true,
55-
})
56-
}
57-
5858
return results
5959
}
6060

@@ -101,7 +101,8 @@ export function getHttpSource(sourceId: string, options?: {requestInit?: Request
101101
prefix,
102102
listFiles: () => s3list(bucket, prefix).then(items =>
103103
items
104-
.filter(item => item.key !== undefined)
104+
// skip s3 directory placeholder
105+
.filter(item => item.key !== undefined && item.key !== prefix)
105106
.map(item => {
106107
if (!item.key) {
107108
throw new Error('Key is undefined')

0 commit comments

Comments
 (0)