| | 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); |
| | } |
| |
|