| import path from 'path'; |
| import crypto from 'node:crypto'; |
| import { createReadStream } from 'fs'; |
| import { readFile, stat } from 'fs/promises'; |
|
|
| |
| |
| |
| |
| export function sanitizeFilename(inputName: string): string { |
| |
| let name = path.basename(inputName); |
|
|
| |
| name = name.replace(/[^a-zA-Z0-9.-]/g, '_'); |
|
|
| |
| if (name.startsWith('.') || name === '') { |
| name = '_' + name; |
| } |
|
|
| |
| const MAX_LENGTH = 255; |
| if (name.length > MAX_LENGTH) { |
| const ext = path.extname(name); |
| const nameWithoutExt = path.basename(name, ext); |
| name = |
| nameWithoutExt.slice(0, MAX_LENGTH - ext.length - 7) + |
| '-' + |
| crypto.randomBytes(3).toString('hex') + |
| ext; |
| } |
|
|
| return name; |
| } |
|
|
| |
| |
| |
| export interface ReadFileOptions { |
| encoding?: BufferEncoding; |
| |
| streamThreshold?: number; |
| |
| highWaterMark?: number; |
| |
| fileSize?: number; |
| } |
|
|
| |
| |
| |
| export interface ReadFileResult<T> { |
| content: T; |
| bytes: number; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| export async function readFileAsString( |
| filePath: string, |
| options: ReadFileOptions = {}, |
| ): Promise<ReadFileResult<string>> { |
| const { |
| encoding = 'utf8', |
| streamThreshold = 10 * 1024 * 1024, |
| highWaterMark = 64 * 1024, |
| fileSize, |
| } = options; |
|
|
| |
| const bytes = fileSize ?? (await stat(filePath)).size; |
|
|
| |
| if (bytes > streamThreshold) { |
| const chunks: string[] = []; |
| const stream = createReadStream(filePath, { |
| encoding, |
| highWaterMark, |
| }); |
|
|
| for await (const chunk of stream) { |
| chunks.push(chunk as string); |
| } |
|
|
| return { content: chunks.join(''), bytes }; |
| } |
|
|
| |
| const content = await readFile(filePath, encoding); |
| return { content, bytes }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| export async function readFileAsBuffer( |
| filePath: string, |
| options: Omit<ReadFileOptions, 'encoding'> = {}, |
| ): Promise<ReadFileResult<Buffer>> { |
| const { |
| streamThreshold = 10 * 1024 * 1024, |
| highWaterMark = 64 * 1024, |
| fileSize, |
| } = options; |
|
|
| |
| const bytes = fileSize ?? (await stat(filePath)).size; |
|
|
| |
| if (bytes > streamThreshold) { |
| const chunks: Buffer[] = []; |
| const stream = createReadStream(filePath, { |
| highWaterMark, |
| }); |
|
|
| for await (const chunk of stream) { |
| chunks.push(chunk as Buffer); |
| } |
|
|
| return { content: Buffer.concat(chunks), bytes }; |
| } |
|
|
| |
| const content = await readFile(filePath); |
| return { content, bytes }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| export async function readJsonFile<T = unknown>( |
| filePath: string, |
| options: Omit<ReadFileOptions, 'encoding'> = {}, |
| ): Promise<T> { |
| const { content } = await readFileAsString(filePath, { ...options, encoding: 'utf8' }); |
| return JSON.parse(content); |
| } |
|
|