Skip to content

Commit e70418a

Browse files
JoshuaWhite1elanalynn
authored andcommitted
address png encoding bug, path changes
1 parent 8d94171 commit e70418a

2 files changed

Lines changed: 79 additions & 17 deletions

File tree

packages/app/src/cli/services/dev/extension/server/middlewares.test.ts

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ describe('fileServerMiddleware()', async () => {
133133
})
134134

135135
expect(event.node.res.setHeader).toHaveBeenCalledWith('Content-Type', 'text/html')
136-
expect(result).toBe('<html></html>')
136+
expect(result?.toString()).toBe('<html></html>')
137137
})
138138
})
139139

@@ -166,7 +166,7 @@ describe('fileServerMiddleware()', async () => {
166166
})
167167

168168
expect(event.node.res.setHeader).toHaveBeenCalledWith('Content-Type', contentType)
169-
expect(result).toBe(fileContent)
169+
expect(result?.toString()).toBe(fileContent)
170170
})
171171
})
172172

@@ -183,7 +183,7 @@ describe('fileServerMiddleware()', async () => {
183183
})
184184

185185
expect(event.node.res.setHeader).toHaveBeenCalledWith('Content-Type', 'text/plain')
186-
expect(result).toBe('Content for bar.foo')
186+
expect(result?.toString()).toBe('Content for bar.foo')
187187
})
188188
})
189189
})
@@ -244,7 +244,7 @@ describe('getExtensionAssetMiddleware()', () => {
244244
const result = await getExtensionAssetMiddleware(options)(event)
245245

246246
expect(event.node.res.setHeader).toHaveBeenCalledWith('Content-Type', 'application/json')
247-
expect(result).toBe('{"tools": []}')
247+
expect(result?.toString()).toBe('{"tools": []}')
248248
})
249249
})
250250

@@ -275,7 +275,7 @@ describe('getExtensionAssetMiddleware()', () => {
275275
const result = await getExtensionAssetMiddleware(options)(event)
276276

277277
expect(event.node.res.setHeader).toHaveBeenCalledWith('Content-Type', 'text/javascript')
278-
expect(result).toBe('compiled bundle content')
278+
expect(result?.toString()).toBe('compiled bundle content')
279279
})
280280
})
281281

@@ -306,7 +306,50 @@ describe('getExtensionAssetMiddleware()', () => {
306306
const result = await getExtensionAssetMiddleware(options)(event)
307307

308308
// Built asset takes priority
309-
expect(result).toBe('built content')
309+
expect(result?.toString()).toBe('built content')
310+
})
311+
})
312+
313+
test('falls back to the extension point assets folder for static assets', async () => {
314+
await inTemporaryDirectory(async (tmpDir: string) => {
315+
const extension = await testUIExtension({
316+
directory: tmpDir,
317+
configuration: {
318+
name: 'test-ui-extension',
319+
type: 'ui_extension',
320+
handle: 'test-ui-extension',
321+
extension_points: [
322+
{
323+
target: 'admin.app.home.render',
324+
module: './src/index.tsx',
325+
assets: './assets',
326+
},
327+
],
328+
} as any,
329+
})
330+
331+
const options = getOptions({
332+
devOptions: {
333+
extensions: [extension],
334+
},
335+
})
336+
337+
// Static asset lives under the extension-declared assets folder.
338+
const assetsDir = joinPath(tmpDir, 'assets')
339+
await mkdir(assetsDir)
340+
await writeFile(joinPath(assetsDir, 'logo.png'), 'png-bytes')
341+
342+
const event = getMockEvent({
343+
params: {
344+
extensionId: extension.devUUID,
345+
assetPath: 'logo.png',
346+
},
347+
})
348+
349+
const result = await getExtensionAssetMiddleware(options)(event)
350+
351+
expect(event.node.res.setHeader).toHaveBeenCalledWith('Content-Type', 'image/png')
352+
expect(result?.toString()).toBe('png-bytes')
310353
})
311354
})
312355
})

packages/app/src/cli/services/dev/extension/server/middlewares.ts

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export async function fileServerMiddleware(event: H3Event, options: {filePath: s
4141
return sendError(event, {statusCode: 404, statusMessage: `Not Found: ${filePath}`})
4242
}
4343

44-
const fileContent = await readFile(filePath)
44+
const fileContent = await readFile(filePath, {})
4545
const extensionToContent = {
4646
'.ico': 'image/x-icon',
4747
'.html': 'text/html',
@@ -79,16 +79,35 @@ export function getExtensionAssetMiddleware({getExtensions}: GetExtensionsMiddle
7979
}
8080

8181
const resolvedExtensionDirectory = resolvePath(extension.directory)
82-
const builtAssetPath = joinPath(
83-
dirname(joinPath(resolvedExtensionDirectory, extension.outputRelativePath)),
84-
assetPath,
85-
)
86-
87-
// Try the build output directory first (for compiled assets like dist/handle.js),
88-
// then fall back to the extension's source directory (for static assets like tools, instructions).
89-
const filePath = (await fileExists(builtAssetPath))
90-
? builtAssetPath
91-
: joinPath(resolvedExtensionDirectory, assetPath)
82+
const sourceAssetPath = joinPath(resolvedExtensionDirectory, assetPath)
83+
84+
// Candidate locations to try, in order:
85+
// 1. Build output directory (for compiled assets like dist/handle.js)
86+
// 2. Extension source directory (for assets at the root like tools, instructions)
87+
// 3. Per-extension-point static asset folders (declared via the `assets` config key).
88+
// Mirrors how the admin spec's `static_root` serves a whole directory.
89+
const candidates: string[] = [
90+
joinPath(dirname(joinPath(resolvedExtensionDirectory, extension.outputRelativePath)), assetPath),
91+
sourceAssetPath,
92+
]
93+
94+
const config = extension.configuration as {extension_points?: unknown}
95+
const extensionPoints = Array.isArray(config.extension_points) ? config.extension_points : []
96+
for (const ep of extensionPoints) {
97+
const assetsDir = (ep as {assets?: unknown} | null)?.assets
98+
if (typeof assetsDir === 'string' && assetsDir !== '') {
99+
candidates.push(joinPath(resolvedExtensionDirectory, assetsDir, assetPath))
100+
}
101+
}
102+
103+
let filePath: string = sourceAssetPath
104+
for (const candidate of candidates) {
105+
// eslint-disable-next-line no-await-in-loop
106+
if (await fileExists(candidate)) {
107+
filePath = candidate
108+
break
109+
}
110+
}
92111

93112
if (!filePath.startsWith(resolvedExtensionDirectory)) {
94113
return sendError(event, {statusCode: 403, statusMessage: 'Path traversal is not allowed'})

0 commit comments

Comments
 (0)