Skip to content

Commit 6d0d24b

Browse files
committed
Add function to handle glob pattern environments
1 parent faacfda commit 6d0d24b

File tree

2 files changed

+183
-0
lines changed

2 files changed

+183
-0
lines changed

packages/cli-kit/src/public/node/environments.test.ts

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,145 @@ describe('getEnvironmentNames', () => {
176176
})
177177
})
178178
})
179+
180+
describe('expandEnvironmentPatterns', () => {
181+
test('returns dedeuplicated environment names', async () => {
182+
await inTemporaryDirectory(async (tmpDir) => {
183+
// Given
184+
const filePath = joinPath(tmpDir, fileName)
185+
await writeFile(
186+
filePath,
187+
tomlEncode({
188+
environments: {
189+
'us-production': {store: 'us-store'},
190+
'ca-production': {store: 'ca-store'},
191+
},
192+
}),
193+
)
194+
195+
// When
196+
const expanded = await environments.expandEnvironmentPatterns(['us-production', 'us-production'], fileName, {
197+
from: tmpDir,
198+
})
199+
200+
// Then
201+
expect(expanded).toEqual(['us-production'])
202+
})
203+
})
204+
205+
describe('when the pattern contains glob characters', () => {
206+
test('expands glob pattern to return multiple environments', async () => {
207+
await inTemporaryDirectory(async (tmpDir) => {
208+
// Given
209+
const filePath = joinPath(tmpDir, fileName)
210+
await writeFile(
211+
filePath,
212+
tomlEncode({
213+
environments: {
214+
'us-production': {store: 'us-store'},
215+
'ca-production': {store: 'ca-store'},
216+
'de-production': {store: 'de-store'},
217+
'us-staging': {store: 'us-staging-store'},
218+
},
219+
}),
220+
)
221+
222+
// When
223+
const expanded = await environments.expandEnvironmentPatterns(['*-production'], fileName, {from: tmpDir})
224+
225+
// Then
226+
expect(expanded.sort()).toEqual(['ca-production', 'de-production', 'us-production'])
227+
})
228+
})
229+
230+
test('supports question mark glob patterns', async () => {
231+
await inTemporaryDirectory(async (tmpDir) => {
232+
// Given
233+
const filePath = joinPath(tmpDir, fileName)
234+
await writeFile(
235+
filePath,
236+
tomlEncode({
237+
environments: {
238+
'us-production': {store: 'us-store'},
239+
'uk-production': {store: 'uk-store'},
240+
'usa-production': {store: 'usa-store'},
241+
},
242+
}),
243+
)
244+
245+
// When
246+
const expanded = await environments.expandEnvironmentPatterns(['u?-production'], fileName, {from: tmpDir})
247+
248+
// Then
249+
expect(expanded.sort()).toEqual(['uk-production', 'us-production'])
250+
})
251+
})
252+
253+
test('supports square bracket glob patterns', async () => {
254+
await inTemporaryDirectory(async (tmpDir) => {
255+
// Given
256+
const filePath = joinPath(tmpDir, fileName)
257+
await writeFile(
258+
filePath,
259+
tomlEncode({
260+
environments: {
261+
'us-production': {store: 'us-store'},
262+
'uk-production': {store: 'uk-store'},
263+
'usa-production': {store: 'usa-store'},
264+
},
265+
}),
266+
)
267+
268+
// When
269+
const expanded = await environments.expandEnvironmentPatterns(['u[a-z]-production'], fileName, {from: tmpDir})
270+
271+
// Then
272+
expect(expanded.sort()).toEqual(['uk-production', 'us-production'])
273+
})
274+
})
275+
276+
test('supports curly bracket glob patterns', async () => {
277+
await inTemporaryDirectory(async (tmpDir) => {
278+
// Given
279+
const filePath = joinPath(tmpDir, fileName)
280+
await writeFile(
281+
filePath,
282+
tomlEncode({
283+
environments: {
284+
'us-production': {store: 'us-store'},
285+
'uk-production': {store: 'uk-store'},
286+
'usa-production': {store: 'usa-store'},
287+
},
288+
}),
289+
)
290+
291+
// When
292+
const expanded = await environments.expandEnvironmentPatterns(['{uk,us}-production'], fileName, {from: tmpDir})
293+
294+
// Then
295+
expect(expanded.sort()).toEqual(['uk-production', 'us-production'])
296+
})
297+
})
298+
})
299+
300+
test('returns exact pattern name even if no match found', async () => {
301+
await inTemporaryDirectory(async (tmpDir) => {
302+
// Given
303+
const filePath = joinPath(tmpDir, fileName)
304+
await writeFile(
305+
filePath,
306+
tomlEncode({
307+
environments: {
308+
'us-production': {store: 'us-store'},
309+
},
310+
}),
311+
)
312+
313+
// When
314+
const expanded = await environments.expandEnvironmentPatterns(['nonexistent'], fileName, {from: tmpDir})
315+
316+
// Then
317+
expect(expanded).toEqual(['nonexistent'])
318+
})
319+
})
320+
})

packages/cli-kit/src/public/node/environments.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {cwd} from './path.js'
44
import * as metadata from './metadata.js'
55
import {renderWarning} from './ui.js'
66
import {JsonMap} from '../../private/common/json.js'
7+
import {minimatch} from 'minimatch'
78

89
export interface Environments {
910
[name: string]: JsonMap
@@ -111,3 +112,43 @@ export async function getEnvironmentNames(fileName: string, options?: LoadEnviro
111112

112113
return Object.keys(environments)
113114
}
115+
116+
export const ENVIRONMENT_ALLOWED_GLOB_PATTERN = /[*?[\]{}]/
117+
118+
/**
119+
* Expands environment patterns (including globs) to actual environment names.
120+
* @param patterns - Array of environment names or glob patterns.
121+
* @param fileName - The file name to load environments from.
122+
* @param options - Optional configuration for loading.
123+
* @returns Array of matched environment names.
124+
*/
125+
export async function expandEnvironmentPatterns(
126+
patterns: string[],
127+
fileName: string,
128+
options?: LoadEnvironmentOptions,
129+
): Promise<string[]> {
130+
const allEnvironments = await getEnvironmentNames(fileName, options)
131+
const matchedEnvironments = new Set<string>()
132+
133+
for (const pattern of patterns) {
134+
const isGlob = ENVIRONMENT_ALLOWED_GLOB_PATTERN.test(pattern)
135+
136+
if (isGlob) {
137+
const matches = allEnvironments.filter((env) => minimatch(env, pattern))
138+
139+
if (matches.length === 0) {
140+
renderWarningIfNeeded(
141+
{
142+
body: `Could not find any environments matching ${pattern}`,
143+
},
144+
options?.silent,
145+
)
146+
}
147+
matches.forEach((match) => matchedEnvironments.add(match))
148+
} else {
149+
matchedEnvironments.add(pattern)
150+
}
151+
}
152+
153+
return Array.from(matchedEnvironments)
154+
}

0 commit comments

Comments
 (0)