Skip to content

Commit ef5e7f6

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

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
@@ -354,4 +354,44 @@ describe('copyConfigKeyEntry', () => {
354354
await expect(fileExists(joinPath(outDir, 'tools.json'))).resolves.toBe(true)
355355
})
356356
})
357+
358+
describe('value guard', () => {
359+
test('throws when value is an empty string', async () => {
360+
await inTemporaryDirectory(async (tmpDir) => {
361+
const outDir = joinPath(tmpDir, 'out')
362+
await mkdir(outDir)
363+
const context = makeContext({assets: ''})
364+
const promise = copyConfigKeyEntry({key: 'assets', baseDir: tmpDir, outputDir: outDir, context})
365+
await expect(promise).rejects.toThrow(AbortError)
366+
await expect(promise).rejects.toThrow(`'assets' can't be empty.`)
367+
})
368+
})
369+
370+
test('throws when value is whitespace-only', async () => {
371+
await inTemporaryDirectory(async (tmpDir) => {
372+
const outDir = joinPath(tmpDir, 'out')
373+
await mkdir(outDir)
374+
const context = makeContext({assets: ' '})
375+
const promise = copyConfigKeyEntry({key: 'assets', baseDir: tmpDir, outputDir: outDir, context})
376+
await expect(promise).rejects.toThrow(AbortError)
377+
await expect(promise).rejects.toThrow(`'assets' can't be empty.`)
378+
})
379+
})
380+
381+
test('throws with the field name only, not the full configKey, when the key is nested', async () => {
382+
await inTemporaryDirectory(async (tmpDir) => {
383+
const outDir = joinPath(tmpDir, 'out')
384+
await mkdir(outDir)
385+
const context = makeContext({extension_points: [{assets: ''}]})
386+
const promise = copyConfigKeyEntry({
387+
key: 'extension_points[].assets',
388+
baseDir: tmpDir,
389+
outputDir: outDir,
390+
context,
391+
})
392+
await expect(promise).rejects.toThrow(AbortError)
393+
await expect(promise).rejects.toThrow(`'assets' can't be empty.`)
394+
})
395+
})
396+
})
357397
})

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
@@ -62,6 +62,13 @@ export async function copyConfigKeyEntry(config: {
6262
// should only be copied once; the pathMap entry is reused for all references.
6363
const uniquePaths = [...new Set(paths)]
6464

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

0 commit comments

Comments
 (0)