Last active
November 28, 2023 12:03
-
-
Save navin-moorthy/8918004d2b666a44c4f37d31c88d0ced to your computer and use it in GitHub Desktop.
Node Utils
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { type NextFunction, type Request, type Response } from "express"; | |
export type AsyncHandler = ( | |
request: Request, | |
response: Response, | |
next: NextFunction, | |
) => Promise<void>; | |
/** | |
* Wraps an asynchronous route handler function with error handling middleware. | |
* @param {AsyncHandler} asyncFunction - The async route handler function to wrap. | |
* @returns A new function that handles errors thrown by the async function. | |
*/ | |
export function asyncWrapper(asyncFunction: AsyncHandler) { | |
/** | |
* The new function that wraps the async route handler function. | |
* @param {Request} request - The request object. | |
* @param {Response} response - The response object. | |
* @param {NextFunction} next - The next middleware function. | |
*/ | |
return function (request: Request, response: Response, next: NextFunction) { | |
// eslint-disable-next-line promise/prefer-await-to-then | |
asyncFunction(request, response, next).catch(next); | |
}; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import fsSync from "node:fs"; | |
import fs from "node:fs/promises"; | |
import { throwError } from "./throwError.js"; | |
/** | |
* Checks if a directory exists and is readable and writable. If it doesn't exist, it creates it. If it exists but is not readable or writable, it makes it both. | |
* @async | |
* @function | |
* @param {string} directoryPath - The path of the directory to check. | |
* @returns {Promise<void>} A promise that resolves to a void indicating whether the directory exists and is readable and writable. | |
*/ | |
export async function ensureDirectoryAccess( | |
directoryPath: string, | |
): Promise<void> { | |
try { | |
await fs.access( | |
directoryPath, | |
// eslint-disable-next-line no-bitwise | |
fsSync.constants.F_OK | fsSync.constants.W_OK | fsSync.constants.R_OK, | |
); | |
} catch { | |
try { | |
await fs.mkdir(directoryPath, { recursive: true }); | |
await fs.chmod(directoryPath, 0o777); | |
} catch (error) { | |
throwError( | |
error, | |
`${directoryPath} does not exists / writable / readable in ensureDirectoryAccess function`, | |
); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-disable node/no-sync */ | |
import fsSync from "node:fs"; | |
import { throwError } from "./throwError.js"; | |
/** | |
* Checks if a directory exists and is readable and writable. If it doesn't exist, it creates it. If it exists but is not readable or writable, it makes it both. | |
* @function | |
* @param {string} directoryPath - The path of the directory to check. | |
* @returns {void} A void indicating whether the directory exists and is readable and writable. | |
*/ | |
export function ensureDirectoryAccessSync(directoryPath: string): void { | |
try { | |
fsSync.accessSync( | |
directoryPath, | |
// eslint-disable-next-line no-bitwise | |
fsSync.constants.F_OK | fsSync.constants.W_OK | fsSync.constants.R_OK, | |
); | |
} catch { | |
try { | |
fsSync.mkdirSync(directoryPath, { recursive: true }); | |
fsSync.chmodSync(directoryPath, 0o777); | |
} catch (error) { | |
throwError( | |
error, | |
`${directoryPath} does not exists / writable / readable in ensureDirectoryAccess function`, | |
); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import fsSync from "node:fs"; | |
import fs, { type FileHandle } from "node:fs/promises"; | |
import { throwError } from "./throwError.js"; | |
/** | |
* Ensures that the file at the given path exists, is writable and is readable. | |
* @async | |
* @param {string} filePath - The path to the file to check. | |
* @returns {Promise<void>} A promise that resolves to true if the file is accessible, false otherwise. | |
*/ | |
export async function ensureFileAccess(filePath: string): Promise<void> { | |
try { | |
await fs.access( | |
filePath, | |
// eslint-disable-next-line no-bitwise | |
fsSync.constants.F_OK | fsSync.constants.W_OK | fsSync.constants.R_OK, | |
); | |
} catch { | |
let fileHandle: FileHandle | undefined; | |
try { | |
fileHandle = await fs.open(filePath, "w"); | |
} catch (error) { | |
throwError( | |
error, | |
`${filePath} does not exists / writable / readable in ensureFileAccess function`, | |
); | |
} finally { | |
await fileHandle?.close(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import prettier from "prettier"; | |
/** | |
* Resolves the Prettier options by finding and reading the Prettier configuration file. | |
* | |
* @async | |
* @returns {Promise<prettier.Options>} A promise that resolves with the Prettier options. | |
* @throws {Error} If the Prettier options cannot be resolved. | |
*/ | |
async function resolvePrettierOptions(): Promise<prettier.Options> { | |
try { | |
const configFile = await prettier.resolveConfigFile(); | |
if (!configFile) { | |
throw new Error("Could not resolve Prettier config file."); | |
} | |
const options = await prettier.resolveConfig(configFile); | |
if (!options) { | |
throw new Error("Could not resolve Prettier options."); | |
} | |
return options; | |
} catch (error: unknown) { | |
throw new Error("Could not resolve Prettier options.", { | |
cause: error, | |
}); | |
} | |
} | |
const prettierOptions = await resolvePrettierOptions(); | |
/** | |
* Formats JSON data with Prettier. | |
* | |
* @param {*} data - The JSON data to format. | |
* @returns {string} The formatted JSON data. | |
*/ | |
function formatWithPrettier(data: unknown): string { | |
return prettier.format(JSON.stringify(data), { | |
...prettierOptions, | |
parser: "json", | |
}); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://markoskon.com/creating-font-subsets/#minimal-english-subset | |
const unicodeRange = `U+0100-0130,U+0132-0151,U+0154-017F` | |
.replaceAll("U+", "") | |
.split(",") | |
.reduce((response, item) => { | |
if (/-/u.test(item)) { | |
const [first, last] = item | |
.split("-") | |
.map((index) => Number.parseInt(index, 16)); | |
const numbers = []; | |
for (let index = first; index <= last; index++) { | |
numbers.push(String.fromCodePoint(index)); | |
} | |
return [...response, { range: item, characters: numbers.join(" ") }]; | |
} else { | |
return [ | |
...response, | |
{ | |
range: item, | |
characters: String.fromCodePoint(Number.parseInt(item, 16)), | |
}, | |
]; | |
} | |
}, []); | |
console.log(unicodeRange); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import path from "node:path"; | |
import { fileURLToPath } from "node:url"; | |
/** | |
* Get the directory name of a file URL. | |
* | |
* @param {string} url - The file URL. | |
* @returns {string} The directory name. | |
*/ | |
export function getDirname(url: string): string { | |
return path.dirname(fileURLToPath(url)); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import got from "got"; | |
import { throwErrorWithAdditionalMessage } from "./throwErrorWithAdditionalMessage.js"; | |
type GotGetFetcherProps = { | |
searchParameters?: Record<string, string>; | |
url: string; | |
}; | |
export const gotGetFetcher = async <T>( | |
props: GotGetFetcherProps, | |
): Promise<T> => { | |
try { | |
const { searchParameters = {}, url } = props; | |
const urlSearchParameters = new URLSearchParams(); | |
for (const [key, value] of Object.entries(searchParameters)) { | |
urlSearchParameters.append(key, value); | |
} | |
const dataDetailsData = await got | |
.get(url, { | |
searchParams: searchParameters, | |
}) | |
.json<T>(); | |
return dataDetailsData; | |
} catch (error) { | |
return throwErrorWithAdditionalMessage( | |
error, | |
`Fetching ${props.url} in gotGetFetcher function failed`, | |
); | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import got, { type Options } from "got"; | |
import { type z } from "zod"; | |
import { throwErrorWithAdditionalMessage } from "./throwErrorWithAdditionalMessage"; | |
import { validateZodData } from "./validateZodData"; | |
type GotPostZodValidationType< | |
TInput extends z.Schema, | |
TOutput extends z.Schema | |
> = { | |
headers: Options["headers"]; | |
inputData: z.infer<TInput>; | |
inputSchema: TInput; | |
inputSchemaName: string; | |
outputSchema: TOutput; | |
outputSchemaName: string; | |
url: string; | |
}; | |
const map = new Map(); | |
export const gotPostZodValidation = async < | |
TInput extends z.Schema, | |
TOutput extends z.Schema | |
>({ | |
url, | |
inputData, | |
inputSchema, | |
inputSchemaName, | |
outputSchema, | |
outputSchemaName, | |
headers, | |
}: GotPostZodValidationType<TInput, TOutput>): Promise<z.infer<TOutput>> => { | |
try { | |
const parsedInputData = await validateZodData({ | |
data: inputData, | |
schema: inputSchema, | |
schemaName: inputSchemaName, | |
}); | |
const dataDetailsData = await got | |
.post(url, { | |
headers, | |
cache: map, | |
json: parsedInputData, | |
}) | |
.json<z.infer<TOutput>>(); | |
return await validateZodData({ | |
data: dataDetailsData, | |
schema: outputSchema, | |
schemaName: outputSchemaName, | |
}); | |
} catch (error) { | |
return throwErrorWithAdditionalMessage( | |
error, | |
`Fetching ${url} in gotPostZodValidation function failed` | |
); | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Represents an HTTP exception. | |
* @class | |
* @augments Error | |
*/ | |
export class HttpException extends Error { | |
/** | |
* The HTTP status code of the exception. | |
* @type {number} | |
*/ | |
public status: number; | |
/** | |
* The message of the exception. | |
* @type {string} | |
*/ | |
public message: string; | |
/** | |
* Creates a new instance of HttpException. | |
* @class | |
* @param {number} status - The HTTP status code of the exception. | |
* @param {string} message - The message of the exception. | |
*/ | |
public constructor(status: number, message: string) { | |
super(message); | |
this.status = status; | |
this.message = message; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { type NextFunction, type Request, type Response } from "express"; | |
import { verify } from "jsonwebtoken"; | |
import { API_KEY, PIN } from "../config/index.js"; | |
import { HttpException } from "../exceptions/httpException.js"; | |
import { type DataStoredInJwtToken } from "../services/authToken.js"; | |
/** | |
* Gets the authorization token from the request headers. | |
* @param {Request} request - The request object. | |
* @returns {string | null} The authorization token, or null if not found. | |
*/ | |
const getAuthorizationToken = (request: Request): string | null => { | |
const header = request.header("Authorization"); | |
if (header) { | |
const jwt = header.split("JWT ")[1]; | |
if (jwt) return jwt; | |
return null; | |
} | |
return null; | |
}; | |
/** | |
* Express middleware that checks if a request is authenticated. | |
* @param {Request} request - The request object. | |
* @param {Response} _response - The response object. | |
* @param {NextFunction} next - The next function in the middleware chain. | |
* @throws {HttpException} If the authentication token is missing or invalid. | |
*/ | |
export const jwtAuthMiddleware = ( | |
request: Request, | |
_response: Response, | |
next: NextFunction, | |
) => { | |
try { | |
const token = getAuthorizationToken(request); | |
if (token) { | |
const payload = verify(token, API_KEY) as DataStoredInJwtToken; | |
if (payload.pin === PIN) { | |
next(); | |
} else { | |
next(new HttpException(401, "JWT token mismatch")); | |
} | |
} else { | |
next(new HttpException(401, "JWT token missing")); | |
} | |
} catch { | |
next(new HttpException(401, "Wrong JWT token")); | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { type Request, type Response } from "express"; | |
import { logger } from "./logger.js"; | |
/** | |
* Logs the error message and sends a JSON response with the error message and status code. | |
* @function | |
* @param {unknown} error - The error to log. | |
* @param {Request} request - The Express request object. | |
* @param {Response} response - The Express response object. | |
* @param {number} status - The HTTP status code. | |
* @param {string} loggerMessage - The error message for logger. | |
* @param {string} errorMessage - The error message for the response. | |
* @returns {void} | |
*/ | |
export function logErrorResponse( | |
error: unknown, | |
request: Request, | |
response: Response, | |
status: number, | |
loggerMessage: string, | |
errorMessage: string = loggerMessage, | |
): void { | |
logger.error( | |
`[${request.method}] ${request.path} >> StatusCode:: ${status}, Message:: ${loggerMessage} -`, | |
error, | |
); | |
response.status(status).json({ message: errorMessage }); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { consola } from "consola"; | |
import { createLogger, format, transports } from "winston"; | |
import winstonDaily from "winston-daily-rotate-file"; | |
import { LOG_DIR, NODE_ENV } from "../config/index.js"; | |
import { ensureDirectoryAccessSync } from "./ensureDirectoryAccessSync.js"; | |
const logDirectory = LOG_DIR; | |
ensureDirectoryAccessSync(logDirectory); | |
const { combine, timestamp, printf, colorize, errors, prettyPrint } = format; | |
// Define log format | |
const logFormat = printf( | |
({ level, message, timestamp: _timestamp }) => | |
`${_timestamp} ${level}: ${message}`, | |
); | |
const jsonLogFileFormat = combine( | |
// Error stack is not needed for production | |
// errors({ stack: true }), | |
timestamp({ | |
format: "YYYY-MM-DD HH:mm:ss", | |
}), | |
logFormat, | |
prettyPrint(), | |
); | |
const isDevelopment = NODE_ENV === "development"; | |
/* | |
* Log Level | |
* error: 0, warn: 1, info: 2, http: 3, verbose: 4, debug: 5, silly: 6 | |
*/ | |
const winstonLogger = createLogger({ | |
format: jsonLogFileFormat, | |
transports: [ | |
// debug log setting | |
new winstonDaily({ | |
level: "debug", | |
datePattern: "YYYY-MM-DD", | |
// log file /logs/debug/*.log in save | |
dirname: logDirectory + "/debug", | |
filename: `%DATE%.log`, | |
// 30 Days saved | |
maxFiles: 30, | |
json: false, | |
zippedArchive: true, | |
}), | |
// error log setting | |
new winstonDaily({ | |
level: "error", | |
datePattern: "YYYY-MM-DD", | |
// log file /logs/error/*.log in save | |
dirname: logDirectory + "/error", | |
filename: `%DATE%.log`, | |
// 30 Days saved | |
maxFiles: 30, | |
handleExceptions: true, | |
json: false, | |
zippedArchive: true, | |
}), | |
], | |
}); | |
// Add console transport for logging to console | |
winstonLogger.add( | |
new transports.Console({ | |
format: combine( | |
errors({ stack: true }), | |
colorize(), | |
printf(({ level, message, timestamp: _timestamp, stack }) => { | |
if (stack) { | |
// print log trace | |
console.error(stack); | |
} | |
return `${_timestamp} ${level}: ${message}`; | |
}), | |
), | |
}), | |
); | |
// Define a stream for morgan to use for logging HTTP requests | |
const stream = { | |
write: (message: string) => { | |
winstonLogger.info( | |
message.slice(0, Math.max(0, message.lastIndexOf("\n"))), | |
); | |
}, | |
}; | |
const logger = isDevelopment ? consola : winstonLogger; | |
export { logger, stream }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import fs from "node:fs/promises"; | |
import path from "node:path"; | |
import { ensureFileAccess } from "./ensureFileAccess.mjs"; | |
import { isErrorObject } from "./getErrorMessage.mjs"; | |
import { throwError } from "./throwError.mjs"; | |
/** | |
* Reads JSON data from a file. | |
* @async | |
* @template T | |
* @param {string} directoryPath - The path of the directory to write to. | |
* @param {string} fileName - The name of the file to read from. | |
* @returns {Promise<T>} A promise that resolves with the parsed JSON data. | |
* @throws {Error} If the read operation fails or if the file does not exist. | |
*/ | |
export async function readJsonFromFile<T>( | |
directoryPath: string, | |
fileName: string | |
): Promise<T> { | |
try { | |
await ensureDirectoryAccess(directoryPath); | |
const filePath = path.join(directoryPath, fileName); | |
await ensureFileAccess(filePath); | |
const fileData = await fs.readFile(filePath); | |
const stringData = fileData.toString("utf8"); | |
const jsonData = JSON.parse(stringData || "{}"); | |
return jsonData; | |
} catch (error) { | |
if (isErrorObject(error) && "code" in error && error.code === "ENOENT") { | |
return throwError( | |
error, | |
`File does not exist: ${directoryPath}/${fileName} in readJsonFromFile function` | |
); | |
} | |
return throwError( | |
error, | |
`Could not read from file: ${directoryPath}/${fileName} in readJsonFromFile function` | |
); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-disable no-console */ | |
// write a node script to separate an single audio sprite into individual audio files provided associated an json file | |
// load the json file | |
const json = require("./audioSprite.json"); | |
const fs = require("node:fs"); | |
const path = require("node:path"); | |
const exec = require("node:child_process").exec; | |
let count = 0; | |
const file = path.resolve(__dirname, "audioSprite.mp3"); | |
// fix bug that the json is not iterable | |
if (typeof json[Symbol.iterator] !== "function") { | |
json[Symbol.iterator] = function* () { | |
for (const key of Object.keys(this)) { | |
yield this[key]; | |
} | |
}; | |
} | |
for (const item of json) { | |
console.log("🚀 ~ file: sprite.cjs:23 ~ item:", item); | |
const start = item.start; | |
console.log("🚀 ~ file: sprite.cjs:25 ~ start:", start); | |
const end = item.end; | |
console.log("🚀 ~ file: sprite.cjs:27 ~ end:", end); | |
const name = item.name; | |
const cmd = | |
"ffmpeg -i " + | |
file + | |
" -ss " + | |
start + | |
" -to " + | |
end + | |
" -acodec copy " + | |
name + | |
".mp3"; | |
// split the audio file give the start and end time using ffmpeg | |
// eslint-disable-next-line no-loop-func | |
exec(cmd, (error) => { | |
if (error) { | |
console.log("error with " + name + " " + error); | |
} else { | |
console.log(name + " done"); | |
} | |
count++; | |
if (count === json.length) { | |
console.log("all done"); | |
} | |
}); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { addAdditionalErrorMessage } from "./addAdditionalErrorMessage.js"; | |
/** | |
* Throws an error with an additional error message. | |
* @param error - The original error. | |
* @param errorMessage - The additional error message. | |
* @throws {Error} | |
*/ | |
export function throwError(error: unknown, errorMessage: string): never { | |
throw new Error(addAdditionalErrorMessage(error, errorMessage), { | |
cause: error, | |
}); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { z } from "zod"; | |
import { fromZodError } from "zod-validation-error"; | |
import { throwErrorWithAdditionalMessage } from "./throwErrorWithAdditionalMessage.js"; | |
type ValidateZodDataType<T extends z.Schema> = { | |
data: z.infer<T>; | |
schema: T; | |
schemaName: string; | |
}; | |
export const validateZodData = async <T extends z.Schema>({ | |
data, | |
schema, | |
schemaName, | |
}: ValidateZodDataType<T>): Promise<z.infer<T>> => { | |
try { | |
return await schema.parseAsync(data); | |
} catch (error) { | |
if (error instanceof z.ZodError) { | |
const validationError = fromZodError(error, { prefix: schemaName }); | |
return throwErrorWithAdditionalMessage( | |
validationError, | |
`Validating ${schemaName} in validateZodData function failed` | |
); | |
} | |
return throwErrorWithAdditionalMessage( | |
error, | |
`Validating ${schemaName} in validateZodData function failed` | |
); | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { type NextFunction, type Request, type Response } from "express"; | |
import { ZodError, type AnyZodObject } from "zod"; | |
import { fromZodError } from "zod-validation-error"; | |
import { HttpException } from "../exceptions/httpException.js"; | |
import { addAdditionalErrorMessage } from "../utils/addAdditionalErrorMessage.js"; | |
import { type AsyncHandler } from "../utils/asyncWrapper.js"; | |
import { logger } from "../utils/logger.js"; | |
/** | |
* Represents the validation middleware. | |
* @function | |
* @param {object} options - The options for the validation middleware. | |
* @param {AnyZodObject} options.schema - The Zod schema to validate the request body against. | |
* @param {string} options.schemaName - The name of the schema being validated. | |
* @returns {Function} - The validation middleware function. | |
*/ | |
export function validationMiddleware({ | |
schema, | |
schemaName, | |
}: { | |
schema: AnyZodObject; | |
schemaName: string; | |
}): AsyncHandler { | |
/** | |
* The middleware function that validates the request body against the Zod schema. | |
* @function | |
* @param {Request} request - The request object. | |
* @param {Response} _response - The response object. | |
* @param {NextFunction} next - The next middleware function. | |
* @throws {HttpException} If the parsing is invalid. | |
*/ | |
return async function ( | |
request: Request, | |
_response: Response, | |
next: NextFunction, | |
) { | |
try { | |
await schema.parseAsync(request.body); | |
next(); | |
} catch (error) { | |
if (error instanceof ZodError) { | |
const validationError = fromZodError(error, { prefix: schemaName }); | |
logger.error( | |
addAdditionalErrorMessage( | |
validationError, | |
`"ParseAsync request body schema ${schemaName} errored in the validationMiddleware function"`, | |
), | |
); | |
next( | |
new HttpException( | |
400, | |
"Validating request body errored in the zod schema", | |
), | |
); | |
} | |
next(new HttpException(400, "Error validating request body")); | |
} | |
}; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import fs from "node:fs/promises"; | |
import path from "node:path"; | |
import { ensureDirectoryAccess } from "./ensureDirectoryAccess.js"; | |
import { throwError } from "./throwError.js"; | |
/** | |
* Writes JSON data to a file. | |
* @async | |
* @template T | |
* @param {string} directoryPath - The path of the directory to write to. | |
* @param {string} fileName - The name of the file to write to. | |
* @param {T} data - The JSON data to write. | |
* @returns {Promise<void>} A promise that resolves when the data has been written to the file. | |
* @throws {Error} If the write operation fails. | |
*/ | |
export async function writeJsonToFile<T>( | |
directoryPath: string, | |
fileName: string, | |
data: T, | |
): Promise<void> { | |
try { | |
await ensureDirectoryAccess(directoryPath); | |
const filePath = path.join(directoryPath, fileName); | |
await fs.writeFile(filePath, JSON.stringify(data), "utf8"); | |
} catch (error) { | |
throwError( | |
error, | |
`Could not write to file: ${directoryPath}/${fileName} in writeJsonToFile function`, | |
); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Asynchronously resolves Prettier options by attempting to locate and read the Prettier config file. | |
* | |
* @returns A Promise that resolves with the Prettier options object. | |
* @throws An error if the Prettier config file could not be located or read. | |
*/ | |
async function resolvePrettierOptions(): Promise<prettier.Options> { | |
try { | |
const configFile = await prettier.resolveConfigFile(); | |
if (!configFile) { | |
throw new Error("Could not resolve Prettier config file."); | |
} | |
const options = await prettier.resolveConfig(configFile); | |
if (!options) { | |
throw new Error("Could not resolve Prettier options."); | |
} | |
return options; | |
} catch (error: unknown) { | |
throw new Error("Could not resolve Prettier options.", { | |
cause: error, | |
}); | |
} | |
} | |
const prettierOptions = await resolvePrettierOptions(); | |
/** | |
* Formats the given data using Prettier with JSON parser. | |
* | |
* @param data - The data to format. | |
* @returns The formatted data. | |
*/ | |
function formatWithPrettier(data: unknown): string { | |
return prettier.format(JSON.stringify(data), { | |
...prettierOptions, | |
parser: "json", | |
}); | |
} | |
/** | |
* Asynchronously writes the given data to a file. | |
* If the file already exists, its contents are overwritten. | |
* If the file does not exist, it is created and the data is written to it. | |
* | |
* @param file - The file to write to. | |
* @param data - The data to write to the file. | |
* @returns A Promise that resolves when the data has been written to the file. | |
*/ | |
async function writeToFile(file: string, data: unknown): Promise<void> { | |
try { | |
// Check if the file exists | |
await access(file); | |
// If it exists, rewrite the file with the new data | |
await writeFile(file, formatWithPrettier(data)); | |
} catch { | |
// If it doesn't exist, create the file and write the data to it | |
await writeFile(file, formatWithPrettier(data)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment