Skip to content

Commit e80977f

Browse files
committed
Reject empty or whitespace-only configKey values in include_assets builds
1 parent 7078668 commit e80977f

2 files changed

Lines changed: 47 additions & 0 deletions

File tree

packages/app/src/cli/services/build/steps/include-assets/copy-config-key-entry.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,4 +316,44 @@ describe('copyConfigKeyEntry', () => {
316316
await expect(fileExists(joinPath(outDir, 'tools.json'))).resolves.toBe(true)
317317
})
318318
})
319+
320+
describe('value guard', () => {
321+
test('throws when value is an empty string', async () => {
322+
await inTemporaryDirectory(async (tmpDir) => {
323+
const outDir = joinPath(tmpDir, 'out')
324+
await mkdir(outDir)
325+
const context = makeContext({assets: ''})
326+
const promise = copyConfigKeyEntry({key: 'assets', baseDir: tmpDir, outputDir: outDir, context})
327+
await expect(promise).rejects.toThrow(AbortError)
328+
await expect(promise).rejects.toThrow(`'assets' can't be empty.`)
329+
})
330+
})
331+
332+
test('throws when value is whitespace-only', async () => {
333+
await inTemporaryDirectory(async (tmpDir) => {
334+
const outDir = joinPath(tmpDir, 'out')
335+
await mkdir(outDir)
336+
const context = makeContext({assets: ' '})
337+
const promise = copyConfigKeyEntry({key: 'assets', baseDir: tmpDir, outputDir: outDir, context})
338+
await expect(promise).rejects.toThrow(AbortError)
339+
await expect(promise).rejects.toThrow(`'assets' can't be empty.`)
340+
})
341+
})
342+
343+
test('throws with the field name only, not the full configKey, when the key is nested', async () => {
344+
await inTemporaryDirectory(async (tmpDir) => {
345+
const outDir = joinPath(tmpDir, 'out')
346+
await mkdir(outDir)
347+
const context = makeContext({extension_points: [{assets: ''}]})
348+
const promise = copyConfigKeyEntry({
349+
key: 'extension_points[].assets',
350+
baseDir: tmpDir,
351+
outputDir: outDir,
352+
context,
353+
})
354+
await expect(promise).rejects.toThrow(AbortError)
355+
await expect(promise).rejects.toThrow(`'assets' can't be empty.`)
356+
})
357+
})
358+
})
319359
})

packages/app/src/cli/services/build/steps/include-assets/copy-config-key-entry.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,13 @@ export async function copyConfigKeyEntry(config: {
5959
// should only be copied once; the pathMap entry is reused for all references.
6060
const uniquePaths = [...new Set(paths)]
6161

62+
const fieldName = key.split('.').pop()?.replace(/\[\]$/, '') ?? key
63+
for (const sourcePath of uniquePaths) {
64+
if (sourcePath.trim() === '') {
65+
throw new AbortError(`'${fieldName}' can't be empty.`)
66+
}
67+
}
68+
6269
// Process sequentially to avoid filesystem race conditions on shared output paths.
6370
const pathMap = new Map<string, string | string[]>()
6471
let filesCopied = 0

0 commit comments

Comments
 (0)