body.ts81 lines · main
1import { createReadStream } from 'node:fs'
2import { pipeline } from 'node:stream/promises'
3import { type NextApiRequest, type NextApiResponse } from 'next'
4
5import apiWrapper from '@/lib/api/apiWrapper'
6import { getFunctionsArtifactStore } from '@/lib/api/self-hosted/functions'
7import { uuidv4 } from '@/lib/helpers'
8
9export default function handlerWithErrorCatching(req: NextApiRequest, res: NextApiResponse) {
10 return apiWrapper(req, res, handler, { withAuth: true })
11}
12
13async function handler(req: NextApiRequest, res: NextApiResponse) {
14 const { method } = req
15
16 switch (method) {
17 case 'GET':
18 return handleGet(req, res)
19 default:
20 res.setHeader('Allow', ['GET'])
21 res.status(405).json({ data: null, error: { message: `Method ${method} Not Allowed` } })
22 }
23}
24
25async function handleGet(req: NextApiRequest, res: NextApiResponse) {
26 const slugParam = req.query.slug
27 const slug = Array.isArray(slugParam) ? slugParam[0] : slugParam
28 if (!slug) {
29 res.status(404).json({ error: { message: `Missing function 'slug' parameter` } })
30 return
31 }
32
33 const store = getFunctionsArtifactStore()
34 const fileEntries = await store.getFileEntriesBySlug(slug)
35
36 const boundary = `----FormBoundary${uuidv4().replace(/-/g, '')}`
37 const totalSize = fileEntries.reduce((sum, entry) => sum + entry.size, 0)
38
39 const metadata = {
40 // mock id, should be "<project_id>_<function_id>_<version>"
41 deployment_id: uuidv4(),
42 original_size: totalSize,
43 compressed_size: totalSize,
44 module_count: fileEntries.length,
45 }
46
47 res.setHeader('Content-Type', `multipart/form-data; boundary=${boundary}`)
48 res.status(200)
49
50 // Write metadata part
51 const metadataJson = JSON.stringify(metadata)
52 res.write(
53 `--${boundary}\r\n` +
54 `Content-Disposition: form-data; name="metadata"\r\n` +
55 `Content-Type: application/json\r\n` +
56 `\r\n` +
57 metadataJson +
58 `\r\n`
59 )
60
61 // Stream each file part
62 for (const entry of fileEntries) {
63 const safeName = entry.relativePath
64 .replace(/[\r\n]/g, '')
65 .replace(/\\/g, '\\\\')
66 .replace(/"/g, '\\"')
67 const encodedName = encodeURIComponent(entry.relativePath)
68 res.write(
69 `--${boundary}\r\n` +
70 `Content-Disposition: form-data; name="file"; filename="${safeName}"; filename*=UTF-8''${encodedName}\r\n` +
71 `Content-Type: text/plain\r\n` +
72 `\r\n`
73 )
74 await pipeline(createReadStream(entry.absolutePath), res, { end: false })
75 res.write(`\r\n`)
76 }
77
78 // Write closing boundary
79 res.write(`--${boundary}--\r\n`)
80 res.end()
81}