Skip to content

Commit ff349f3

Browse files
committed
Sync alphalib 2025 11 26
1 parent 72f7070 commit ff349f3

File tree

91 files changed

+586
-5
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

91 files changed

+586
-5
lines changed

.cursor/rules/coding-style.mdc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,4 @@ Coding style:
2929
- Use descriptive names: PascalCase for components/types, camelCase for variables/methods/schemas
3030
- Alphabetize imports, group by source type (built-in/external/internal)
3131
- Favor US English over UK English, so `summarizeError` over `summarise Error`
32+
- Favor `.replaceAll('a', 'b)` over `.replace(/a/g, 'b')` or `.replace(new RegExp('a', 'g'), 'b')` when the only need for regeses was replacing all strings. That's usually both easier to read and more performant.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"dependencies": {
2020
"@aws-sdk/client-s3": "^3.891.0",
2121
"@aws-sdk/s3-request-presigner": "^3.891.0",
22+
"@transloadit/sev-logger": "^0.0.15",
2223
"debug": "^4.4.3",
2324
"form-data": "^4.0.4",
2425
"got": "14.4.9",

src/alphalib/lib/nativeGlobby.ts

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
import type { GlobOptionsWithoutFileTypes } from 'node:fs'
2+
import * as fs from 'node:fs'
3+
import { glob as fsGlob, stat as statAsync } from 'node:fs/promises'
4+
import path from 'node:path'
5+
6+
type PatternInput = string | readonly string[]
7+
const { globSync: fsGlobSync } = fs
8+
9+
export interface NativeGlobbyOptions {
10+
cwd?: string
11+
absolute?: boolean
12+
onlyFiles?: boolean
13+
ignore?: readonly string[]
14+
}
15+
16+
interface NormalizedOptions {
17+
cwd?: string
18+
absolute: boolean
19+
onlyFiles: boolean
20+
ignore?: readonly string[]
21+
}
22+
23+
interface Candidate {
24+
rawPath: string
25+
absolutePath: string
26+
}
27+
28+
const normalizeSlashes = (value: string) => value.replace(/\\/g, '/')
29+
30+
const toAbsolutePath = (rawPath: string, cwd?: string): string => {
31+
if (path.isAbsolute(rawPath)) {
32+
return rawPath
33+
}
34+
if (cwd) {
35+
return path.join(cwd, rawPath)
36+
}
37+
return path.resolve(rawPath)
38+
}
39+
40+
const normalizeOptions = (options: NativeGlobbyOptions = {}): NormalizedOptions => ({
41+
cwd: options.cwd ? path.resolve(options.cwd) : undefined,
42+
absolute: options.absolute ?? false,
43+
onlyFiles: options.onlyFiles ?? true,
44+
ignore: options.ignore && options.ignore.length > 0 ? options.ignore : undefined,
45+
})
46+
47+
const hasGlobMagic = (pattern: string) => /[*?[\]{}()!]/.test(pattern)
48+
49+
const expandPatternAsync = async (pattern: string, options: NormalizedOptions) => {
50+
if (hasGlobMagic(pattern)) {
51+
return [pattern]
52+
}
53+
54+
try {
55+
const absolute = toAbsolutePath(pattern, options.cwd)
56+
const stats = await statAsync(absolute)
57+
if (stats.isDirectory()) {
58+
const expanded = normalizeSlashes(path.join(pattern, '**/*'))
59+
return [expanded]
60+
}
61+
} catch {
62+
// ignore missing paths; fall back to original pattern
63+
}
64+
65+
return [pattern]
66+
}
67+
68+
const expandPatternSync = (pattern: string, options: NormalizedOptions) => {
69+
if (hasGlobMagic(pattern)) {
70+
return [pattern]
71+
}
72+
73+
try {
74+
const absolute = toAbsolutePath(pattern, options.cwd)
75+
const stats = fs.statSync(absolute)
76+
if (stats.isDirectory()) {
77+
const expanded = normalizeSlashes(path.join(pattern, '**/*'))
78+
return [expanded]
79+
}
80+
} catch {
81+
// ignore missing paths; fall back to original pattern
82+
}
83+
84+
return [pattern]
85+
}
86+
87+
const splitPatterns = (patterns: PatternInput) => {
88+
const list = Array.isArray(patterns) ? patterns : [patterns]
89+
const positive: string[] = []
90+
const negative: string[] = []
91+
92+
for (const pattern of list) {
93+
if (pattern.startsWith('!')) {
94+
const negated = pattern.slice(1)
95+
if (negated) {
96+
negative.push(negated)
97+
}
98+
} else {
99+
positive.push(pattern)
100+
}
101+
}
102+
103+
return { positive, negative }
104+
}
105+
106+
const toGlobOptions = (options: NormalizedOptions): GlobOptionsWithoutFileTypes => {
107+
const globOptions: GlobOptionsWithoutFileTypes = { withFileTypes: false }
108+
if (options.cwd) {
109+
globOptions.cwd = options.cwd
110+
}
111+
if (options.ignore) {
112+
// Node's glob implementation uses `exclude` for ignore patterns
113+
globOptions.exclude = options.ignore
114+
}
115+
return globOptions
116+
}
117+
118+
const filterFilesAsync = async (candidates: Candidate[], requireFiles: boolean) => {
119+
if (!requireFiles) {
120+
return candidates
121+
}
122+
123+
const filtered = await Promise.all(
124+
candidates.map(async (candidate) => {
125+
try {
126+
const stats = await statAsync(candidate.absolutePath)
127+
return stats.isFile() ? candidate : null
128+
} catch {
129+
return null
130+
}
131+
}),
132+
)
133+
134+
return filtered.filter(Boolean) as Candidate[]
135+
}
136+
137+
const filterFilesSync = (candidates: Candidate[], requireFiles: boolean) => {
138+
if (!requireFiles) {
139+
return candidates
140+
}
141+
142+
const filtered: Candidate[] = []
143+
for (const candidate of candidates) {
144+
try {
145+
const stats = fs.statSync(candidate.absolutePath)
146+
if (stats.isFile()) {
147+
filtered.push(candidate)
148+
}
149+
} catch {
150+
// Ignore files that cannot be stat'ed
151+
}
152+
}
153+
return filtered
154+
}
155+
156+
const formatResult = (candidate: Candidate, options: NormalizedOptions) => {
157+
const output = options.absolute ? candidate.absolutePath : candidate.rawPath
158+
return normalizeSlashes(output)
159+
}
160+
161+
const collectMatchesAsync = async (pattern: string, options: NormalizedOptions) => {
162+
const matches: Candidate[] = []
163+
for await (const match of fsGlob(pattern, toGlobOptions(options))) {
164+
matches.push({
165+
rawPath: match as string,
166+
absolutePath: toAbsolutePath(match as string, options.cwd),
167+
})
168+
}
169+
const filtered = await filterFilesAsync(matches, options.onlyFiles)
170+
return filtered.map((candidate) => formatResult(candidate, options))
171+
}
172+
173+
const collectMatchesSync = (pattern: string, options: NormalizedOptions) => {
174+
const matches = (fsGlobSync(pattern, toGlobOptions(options)) as string[]).map((match) => ({
175+
rawPath: match,
176+
absolutePath: toAbsolutePath(match, options.cwd),
177+
}))
178+
const filtered = filterFilesSync(matches, options.onlyFiles)
179+
return filtered.map((candidate) => formatResult(candidate, options))
180+
}
181+
182+
type GlobbyLike = {
183+
(patterns: PatternInput, options?: NativeGlobbyOptions): Promise<string[]>
184+
sync(patterns: PatternInput, options?: NativeGlobbyOptions): string[]
185+
}
186+
187+
export const nativeGlobby: GlobbyLike = Object.assign(
188+
async (patterns: PatternInput, options?: NativeGlobbyOptions) => {
189+
const normalized = normalizeOptions(options)
190+
const { positive, negative } = splitPatterns(patterns)
191+
const expandedPositives = (
192+
await Promise.all(positive.map((pattern) => expandPatternAsync(pattern, normalized)))
193+
).flat()
194+
const expandedNegatives = (
195+
await Promise.all(negative.map((pattern) => expandPatternAsync(pattern, normalized)))
196+
).flat()
197+
const results = new Set<string>()
198+
199+
for (const pattern of expandedPositives) {
200+
const matches = await collectMatchesAsync(pattern, normalized)
201+
for (const match of matches) {
202+
results.add(match)
203+
}
204+
}
205+
206+
for (const pattern of expandedNegatives) {
207+
const matches = await collectMatchesAsync(pattern, normalized)
208+
for (const match of matches) {
209+
results.delete(match)
210+
}
211+
}
212+
213+
return Array.from(results)
214+
},
215+
{
216+
sync(patterns: PatternInput, options?: NativeGlobbyOptions) {
217+
const normalized = normalizeOptions(options)
218+
const { positive, negative } = splitPatterns(patterns)
219+
const expandedPositives = positive.flatMap((pattern) =>
220+
expandPatternSync(pattern, normalized),
221+
)
222+
const expandedNegatives = negative.flatMap((pattern) =>
223+
expandPatternSync(pattern, normalized),
224+
)
225+
const results = new Set<string>()
226+
227+
for (const pattern of expandedPositives) {
228+
const matches = collectMatchesSync(pattern, normalized)
229+
for (const match of matches) {
230+
results.add(match)
231+
}
232+
}
233+
234+
for (const pattern of expandedNegatives) {
235+
const matches = collectMatchesSync(pattern, normalized)
236+
for (const match of matches) {
237+
results.delete(match)
238+
}
239+
}
240+
241+
return Array.from(results)
242+
},
243+
},
244+
)

0 commit comments

Comments
 (0)