jg-fs 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API.md +1716 -0
- package/CHANGELOG.md +23 -13
- package/README.md +6 -21
- package/dist/async/directories/directories.d.ts +14 -0
- package/dist/async/directories/directories.js +20 -2
- package/dist/async/directories/directories.js.map +1 -1
- package/dist/async/files/files.d.ts +46 -8
- package/dist/async/files/files.js +48 -10
- package/dist/async/files/files.js.map +1 -1
- package/dist/async/files/index.d.ts +1 -1
- package/dist/async/files/index.js +1 -1
- package/dist/async/files/index.js.map +1 -1
- package/dist/async/general/general.d.ts +4 -0
- package/dist/async/general/general.js +4 -0
- package/dist/async/general/general.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/shared/index.d.ts +2 -2
- package/dist/shared/index.js +1 -1
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/paths.d.ts +2 -0
- package/dist/shared/paths.js +2 -0
- package/dist/shared/paths.js.map +1 -1
- package/dist/shared/types.d.ts +6 -0
- package/dist/shared/types.js.map +1 -1
- package/dist/shared/validations.d.ts +5 -3
- package/dist/shared/validations.js +30 -14
- package/dist/shared/validations.js.map +1 -1
- package/dist/sync/directories/directories.d.ts +14 -0
- package/dist/sync/directories/directories.js +20 -2
- package/dist/sync/directories/directories.js.map +1 -1
- package/dist/sync/files/files.d.ts +46 -8
- package/dist/sync/files/files.js +48 -10
- package/dist/sync/files/files.js.map +1 -1
- package/dist/sync/files/index.d.ts +1 -1
- package/dist/sync/files/index.js +1 -1
- package/dist/sync/files/index.js.map +1 -1
- package/dist/sync/general/general.d.ts +4 -0
- package/dist/sync/general/general.js +4 -0
- package/dist/sync/general/general.js.map +1 -1
- package/dist/sync/index.d.ts +2 -2
- package/dist/sync/index.js +1 -1
- package/dist/sync/index.js.map +1 -1
- package/package.json +10 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"files.js","sourceRoot":"","sources":["../../../src/async/files/files.ts"],"names":[],"mappings":"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","sourcesContent":["import { Buffer } from 'node:buffer';\nimport { randomUUID } from 'node:crypto';\nimport type { WriteFileOptions } from 'node:fs';\nimport {\n copyFile as copyFileNode,\n mkdir,\n open,\n readFile as readFileNode,\n rename,\n rm,\n symlink,\n unlink,\n writeFile as writeFileNode,\n} from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { Exception } from 'jg-exception';\n\nimport {\n assertBufferData,\n assertTextData,\n BOUNDED_READ_CHUNK_SIZE,\n ERRORS,\n fileTooLarge,\n notAFile,\n parseJSONContent,\n serializeJSONData,\n type IReadBufferFileOptions,\n type IReadFileOptions,\n type IReadStringFileOptions,\n} from '../../shared/index.js';\nimport { getPathElement } from '../general/index.js';\n\nimport { openWithoutFollowing, readPermissionBits, syncDirectory } from './utilities.js';\n\n/**\n * Checks whether a path exists and is a regular file.\n * @param path The path.\n * @returns A promise that resolves to whether the path is a file.\n * @example\n * await isFile('package.json'); // true\n */\nexport const isFile = async (path: string): Promise<boolean> =>\n (await getPathElement(path))?.isFile ?? false;\n\n/**\n * Writes a file, creating its directory first when it doesn't exist.\n * @param path The file's path.\n * @param data The content.\n * @param options Node.js's writeFile options, such as the encoding or mode.\n * @returns A promise that resolves once the file is written.\n * @example\n * await writeFile('out/report.csv', csv, { encoding: 'utf-8' });\n */\nexport const writeFile = async (\n path: string,\n data: string | NodeJS.ArrayBufferView,\n options?: WriteFileOptions,\n): Promise<void> => {\n // a recursive mkdir succeeds when a concurrent call already created the directory\n await mkdir(dirname(path), { recursive: true });\n await writeFileNode(path, data, options);\n};\n\n/**\n * Writes a UTF-8 text file. An empty string writes an empty file.\n * @param path The file's path.\n * @param data The text.\n * @returns A promise that resolves once the file is written.\n * @example\n * await writeTextFile('notes/today.md', '# Today');\n * @throws\n * - FILE_CONTENT_IS_EMPTY_OR_INVALID: if the data isn't a string\n */\nexport const writeTextFile = async (path: string, data: string): Promise<void> => {\n assertTextData(path, data);\n await writeFile(path, data, { encoding: 'utf-8' });\n};\n\n/**\n * Writes a JSON file. A string is written as given; anything else is stringified.\n * @param path The file's path.\n * @param data The value, or its JSON text.\n * @param space The indentation for stringified values.\n * @returns A promise that resolves once the file is written.\n * @example\n * await writeJSONFile('config.json', { port: 8080 });\n * @throws\n * - FILE_CONTENT_IS_EMPTY_OR_INVALID: if the JSON content cannot be stringified or is empty\n */\nexport const writeJSONFile = async <T = Record<string, unknown>>(\n path: string,\n data: T | string,\n space: number = 2,\n): Promise<void> => {\n await writeFile(path, serializeJSONData(path, data, space), { encoding: 'utf-8' });\n};\n\n/**\n * Writes a binary file. An empty Buffer writes an empty file.\n * @param path The file's path.\n * @param data The content.\n * @returns A promise that resolves once the file is written.\n * @example\n * await writeBufferFile('avatar.png', imageBuffer);\n * @throws\n * - FILE_CONTENT_IS_EMPTY_OR_INVALID: if the data isn't a Buffer\n */\nexport const writeBufferFile = async (path: string, data: Buffer): Promise<void> => {\n assertBufferData(path, data);\n await writeFile(path, data);\n};\n\n/**\n * Reads a file as a string when an encoding is given, or as a Buffer otherwise.\n * @param path The file's path.\n * @param options Node.js's readFile options, such as the encoding.\n * @returns A promise that resolves to the content.\n * @example\n * await readFile('notes.md', 'utf-8'); // a string\n * await readFile('avatar.png'); // a Buffer\n * @throws\n * - NOT_A_FILE: if the path doesn't exist or is not a file\n */\nexport async function readFile(path: string, options: IReadStringFileOptions): Promise<string>;\nexport async function readFile(path: string, options?: IReadBufferFileOptions): Promise<Buffer>;\nexport async function readFile(path: string, options?: IReadFileOptions): Promise<string | Buffer>;\nexport async function readFile(\n path: string,\n options: IReadFileOptions = null,\n): Promise<string | Buffer> {\n if (!(await isFile(path))) {\n throw notAFile(path);\n }\n return readFileNode(path, options);\n}\n\n/**\n * Reads a UTF-8 text file. An empty file reads as an empty string.\n * @param path The file's path.\n * @returns A promise that resolves to the text.\n * @example\n * const markdown = await readTextFile('README.md');\n * @throws\n * - NOT_A_FILE: if the path doesn't exist or is not a file\n */\nexport const readTextFile = (path: string): Promise<string> =>\n readFile(path, { encoding: 'utf-8' });\n\n/**\n * Reads and parses a JSON file.\n * @param path The file's path.\n * @returns A promise that resolves to the parsed value.\n * @example\n * const config = await readJSONFile<{ port: number }>('config.json');\n * @throws\n * - NOT_A_FILE: if the path doesn't exist or is not a file\n * - FILE_CONTENT_IS_EMPTY_OR_INVALID: if the file's JSON content cannot be parsed\n */\nexport const readJSONFile = async <T = Record<string, unknown>>(path: string): Promise<T> =>\n parseJSONContent<T>(path, await readTextFile(path));\n\n/**\n * Reads a binary file. An empty file reads as an empty Buffer.\n * @param path The file's path.\n * @returns A promise that resolves to the content.\n * @example\n * const image = await readBufferFile('avatar.png');\n * @throws\n * - NOT_A_FILE: if the path doesn't exist or is not a file\n */\nexport const readBufferFile = (path: string): Promise<Buffer> => readFile(path, null);\n\n/**\n * Copies a file, replacing the destination when it exists.\n * @param srcPath The file to copy.\n * @param destPath Where to copy it.\n * @returns A promise that resolves once the copy exists.\n * @example\n * await copyFile('config.example.json', 'config.json');\n * @throws\n * - NOT_A_FILE: if the srcPath doesn't exist or is not a file\n */\nexport const copyFile = async (srcPath: string, destPath: string): Promise<void> => {\n if (!(await isFile(srcPath))) {\n throw notAFile(srcPath);\n }\n await copyFileNode(srcPath, destPath);\n};\n\n/**\n * Deletes a file.\n * @param path The file's path.\n * @returns A promise that resolves once the file is gone.\n * @example\n * await deleteFile('cache.json');\n * @throws\n * - NOT_A_FILE: if the path doesn't exist or is not a file\n */\nexport const deleteFile = async (path: string): Promise<void> => {\n if (!(await isFile(path))) {\n throw notAFile(path);\n }\n await unlink(path);\n};\n\n/**\n * Creates a symbolic link to a file.\n * @param target The file the link points to.\n * @param path Where to create the link.\n * @returns A promise that resolves once the link exists.\n * @example\n * await createFileSymLink('config.production.json', 'config.json');\n * @throws\n * - NON_EXISTENT_FILE: if the target file does not exist\n * - NOT_A_FILE: if the target is not a file\n */\nexport const createFileSymLink = async (target: string, path: string): Promise<void> => {\n const element = await getPathElement(target);\n if (element === null) {\n throw new Exception(`The target file '${target}' does not exist.`, ERRORS.NON_EXISTENT_FILE);\n }\n if (!element.isFile) {\n throw notAFile(target);\n }\n await symlink(target, path, 'file');\n};\n\n/**\n * Writes a file atomically: readers see either the previous content or the complete new content,\n * never a partial write. The data goes to a temporary file in the same directory, which is flushed\n * to disk and renamed over the path; on POSIX, the directory is flushed too. An explicit mode wins;\n * without one, a replaced file keeps its permission bits and a new file gets the platform default.\n * The temporary file is never more widely readable than the final mode. When writing fails before\n * the rename, the temporary file is removed and the destination is untouched.\n * @param path The file's path.\n * @param data The content.\n * @param mode The permission bits of the written file, such as `0o600`.\n * @returns A promise that resolves once the file is written and flushed.\n * @example\n * await writeFileAtomic('state.json', JSON.stringify(state)); // readers never see a partial file\n * await writeFileAtomic('secrets.env', contents, 0o600);\n */\nexport const writeFileAtomic = async (\n path: string,\n data: string | NodeJS.ArrayBufferView,\n mode?: number,\n): Promise<void> => {\n const directory = dirname(path);\n await mkdir(directory, { recursive: true });\n const finalMode = mode ?? (await readPermissionBits(path));\n const tempPath = join(directory, `.${randomUUID()}.tmp`);\n let isRenamed = false;\n try {\n // a known final mode is applied with chmod, so the umask can't change it\n const handle = await open(tempPath, 'wx', finalMode === undefined ? 0o666 : 0o600);\n try {\n if (finalMode !== undefined) {\n await handle.chmod(finalMode);\n }\n await handle.writeFile(data);\n await handle.sync();\n } finally {\n await handle.close();\n }\n await rename(tempPath, path);\n isRenamed = true;\n } finally {\n if (!isRenamed) {\n await rm(tempPath, { force: true });\n }\n }\n if (process.platform !== 'win32') {\n await syncDirectory(directory);\n }\n};\n\n/**\n * Reads a regular file of at most `maxBytes` bytes. It never follows a symbolic link in the\n * path's last component, though links in parent directories are followed, so callers that need\n * containment combine it with `isPathWithin`. It reads at most `maxBytes + 1` bytes, even when the\n * file grows while it's read.\n * @param path The file's path.\n * @param maxBytes The largest size accepted, in bytes.\n * @returns A promise that resolves to the content.\n * @example\n * const upload = await readBoundedFile(join(uploadsDirectory, name), 5 * 1024 * 1024); // at most 5 MB\n * @throws\n * - NOT_A_FILE: if the path doesn't exist, its final component is a symbolic link, or it isn't a regular file\n * - FILE_TOO_LARGE: if the file holds more than maxBytes bytes\n */\nexport const readBoundedFile = async (path: string, maxBytes: number): Promise<Buffer> => {\n const handle = await openWithoutFollowing(path);\n try {\n const stats = await handle.stat();\n if (!stats.isFile()) {\n throw notAFile(path);\n }\n if (stats.size > maxBytes) {\n throw fileTooLarge(path, maxBytes);\n }\n const chunks: Buffer[] = [];\n let totalBytes = 0;\n while (totalBytes <= maxBytes) {\n const chunk = Buffer.allocUnsafe(\n Math.min(BOUNDED_READ_CHUNK_SIZE, maxBytes + 1 - totalBytes),\n );\n const { bytesRead } = await handle.read(chunk, 0, chunk.length, totalBytes);\n if (bytesRead === 0) {\n break;\n }\n chunks.push(chunk.subarray(0, bytesRead));\n totalBytes += bytesRead;\n }\n if (totalBytes > maxBytes) {\n throw fileTooLarge(path, maxBytes);\n }\n return Buffer.concat(chunks, totalBytes);\n } finally {\n await handle.close();\n }\n};\n"]}
|
|
1
|
+
{"version":3,"file":"files.js","sourceRoot":"","sources":["../../../src/async/files/files.ts"],"names":[],"mappings":"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","sourcesContent":["import { Buffer } from 'node:buffer';\nimport { randomUUID } from 'node:crypto';\nimport type { WriteFileOptions } from 'node:fs';\nimport {\n copyFile as copyFileNode,\n mkdir,\n open,\n readFile as readFileNode,\n rename,\n rm,\n symlink,\n unlink,\n writeFile as writeFileNode,\n} from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { Exception } from 'jg-exception';\n\nimport {\n assertBufferData,\n assertTextData,\n BOUNDED_READ_CHUNK_SIZE,\n ERRORS,\n fileTooLarge,\n notAFile,\n parseJsonContent,\n serializeJsonData,\n type IReadBufferFileOptions,\n type IReadFileOptions,\n type IReadJsonFileOptions,\n type IReadStringFileOptions,\n} from '../../shared/index.js';\nimport { getPathElement } from '../general/index.js';\n\nimport { openWithoutFollowing, readPermissionBits, syncDirectory } from './utilities.js';\n\n/**\n * Checks whether a path exists and is a regular file.\n * @param path The path.\n * @returns A promise that resolves to whether the path is a file.\n * @example\n * import { isFile } from 'jg-fs';\n *\n * await isFile('package.json'); // true\n */\nexport const isFile = async (path: string): Promise<boolean> =>\n (await getPathElement(path))?.isFile ?? false;\n\n/**\n * Writes a file, creating its directory first when it doesn't exist.\n * @param path The file's path.\n * @param data The content.\n * @param options Node.js's writeFile options, such as the encoding or mode.\n * @returns A promise that resolves once the file is written.\n * @example\n * import { writeFile } from 'jg-fs';\n *\n * const csv = 'id,name\\n1,Ada\\n';\n * await writeFile('out/report.csv', csv, { encoding: 'utf-8' });\n */\nexport const writeFile = async (\n path: string,\n data: string | NodeJS.ArrayBufferView,\n options?: WriteFileOptions,\n): Promise<void> => {\n // a recursive mkdir succeeds when a concurrent call already created the directory\n await mkdir(dirname(path), { recursive: true });\n await writeFileNode(path, data, options);\n};\n\n/**\n * Writes a UTF-8 text file. An empty string writes an empty file.\n * @param path The file's path.\n * @param data The text.\n * @returns A promise that resolves once the file is written.\n * @example\n * import { writeTextFile } from 'jg-fs';\n *\n * await writeTextFile('notes/today.md', '# Today');\n * @throws\n * - FILE_CONTENT_IS_EMPTY_OR_INVALID: if the data isn't a string\n */\nexport const writeTextFile = async (path: string, data: string): Promise<void> => {\n assertTextData(path, data);\n await writeFile(path, data, { encoding: 'utf-8' });\n};\n\n/**\n * Writes a JSON file. A string is written as given; anything else is stringified.\n * @param path The file's path.\n * @param data The value, or its JSON text.\n * @param space The indentation for stringified values.\n * @returns A promise that resolves once the file is written.\n * @example\n * import { writeJsonFile } from 'jg-fs';\n *\n * await writeJsonFile('config.json', { port: 8080 });\n * @throws\n * - FILE_CONTENT_IS_EMPTY_OR_INVALID: if the JSON content cannot be stringified or is empty\n */\nexport const writeJsonFile = async <T = Record<string, unknown>>(\n path: string,\n data: T | string,\n space: number = 2,\n): Promise<void> => {\n await writeFile(path, serializeJsonData(path, data, space), { encoding: 'utf-8' });\n};\n\n/**\n * Writes a binary file. An empty Buffer writes an empty file.\n * @param path The file's path.\n * @param data The content.\n * @returns A promise that resolves once the file is written.\n * @example\n * import { writeBufferFile } from 'jg-fs';\n *\n * const imageBuffer = Buffer.from('iVBORw0KGgo=', 'base64');\n * await writeBufferFile('avatar.png', imageBuffer);\n * @throws\n * - FILE_CONTENT_IS_EMPTY_OR_INVALID: if the data isn't a Buffer\n */\nexport const writeBufferFile = async (path: string, data: Buffer): Promise<void> => {\n assertBufferData(path, data);\n await writeFile(path, data);\n};\n\n/**\n * Reads a file as a string when an encoding is given, or as a Buffer otherwise.\n * @param path The file's path.\n * @param options Node.js's readFile options, such as the encoding.\n * @returns A promise that resolves to the content.\n * @example\n * import { readFile } from 'jg-fs';\n *\n * await readFile('notes.md', 'utf-8'); // a string\n * await readFile('avatar.png'); // a Buffer\n * @throws\n * - NOT_A_FILE: if the path doesn't exist or is not a file\n */\nexport async function readFile(path: string, options: IReadStringFileOptions): Promise<string>;\nexport async function readFile(path: string, options?: IReadBufferFileOptions): Promise<Buffer>;\nexport async function readFile(path: string, options?: IReadFileOptions): Promise<string | Buffer>;\nexport async function readFile(\n path: string,\n options: IReadFileOptions = null,\n): Promise<string | Buffer> {\n if (!(await isFile(path))) {\n throw notAFile(path);\n }\n return readFileNode(path, options);\n}\n\n/**\n * Reads a UTF-8 text file. An empty file reads as an empty string.\n * @param path The file's path.\n * @returns A promise that resolves to the text.\n * @example\n * import { readTextFile } from 'jg-fs';\n *\n * const markdown = await readTextFile('README.md');\n * @throws\n * - NOT_A_FILE: if the path doesn't exist or is not a file\n */\nexport const readTextFile = (path: string): Promise<string> =>\n readFile(path, { encoding: 'utf-8' });\n\n/**\n * Reads and parses a JSON file, and validates it with the schema when there is one. Without a\n * schema, the value is `unknown`, or the type you pass.\n * @param path The file's path.\n * @param options The schema, such as a Zod schema, whose errors propagate unchanged.\n * @returns A promise that resolves to the parsed value.\n * @example\n * import { readJsonFile } from 'jg-fs';\n *\n * const config = await readJsonFile('config.json', { schema: Config }); // Config's type\n * @example\n * import { readJsonFile } from 'jg-fs';\n *\n * const value = await readJsonFile('data.json'); // unknown\n * @throws\n * - NOT_A_FILE: if the path doesn't exist or is not a file\n * - FILE_CONTENT_IS_EMPTY_OR_INVALID: if the file's JSON content cannot be parsed\n */\nexport const readJsonFile = async <T = unknown>(\n path: string,\n options: IReadJsonFileOptions<T> = {},\n): Promise<T> => parseJsonContent(path, await readTextFile(path), options.schema);\n\n/**\n * Reads a binary file. An empty file reads as an empty Buffer.\n * @param path The file's path.\n * @returns A promise that resolves to the content.\n * @example\n * import { readBufferFile } from 'jg-fs';\n *\n * const image = await readBufferFile('avatar.png');\n * @throws\n * - NOT_A_FILE: if the path doesn't exist or is not a file\n */\nexport const readBufferFile = (path: string): Promise<Buffer> => readFile(path, null);\n\n/**\n * Copies a file, replacing the destination when it exists.\n * @param srcPath The file to copy.\n * @param destPath Where to copy it.\n * @returns A promise that resolves once the copy exists.\n * @example\n * import { copyFile } from 'jg-fs';\n *\n * await copyFile('config.example.json', 'config.json');\n * @throws\n * - NOT_A_FILE: if the srcPath doesn't exist or is not a file\n */\nexport const copyFile = async (srcPath: string, destPath: string): Promise<void> => {\n if (!(await isFile(srcPath))) {\n throw notAFile(srcPath);\n }\n await copyFileNode(srcPath, destPath);\n};\n\n/**\n * Deletes a file.\n * @param path The file's path.\n * @returns A promise that resolves once the file is gone.\n * @example\n * import { deleteFile } from 'jg-fs';\n *\n * await deleteFile('cache.json');\n * @throws\n * - NOT_A_FILE: if the path doesn't exist or is not a file\n */\nexport const deleteFile = async (path: string): Promise<void> => {\n if (!(await isFile(path))) {\n throw notAFile(path);\n }\n await unlink(path);\n};\n\n/**\n * Creates a symbolic link to a file.\n * @param target The file the link points to.\n * @param path Where to create the link.\n * @returns A promise that resolves once the link exists.\n * @example\n * import { createFileSymLink } from 'jg-fs';\n *\n * await createFileSymLink('config.production.json', 'config.json');\n * @throws\n * - NON_EXISTENT_FILE: if the target file does not exist\n * - NOT_A_FILE: if the target is not a file\n */\nexport const createFileSymLink = async (target: string, path: string): Promise<void> => {\n const element = await getPathElement(target);\n if (element === null) {\n throw new Exception(`The target file '${target}' does not exist.`, {\n code: ERRORS.NON_EXISTENT_FILE,\n });\n }\n if (!element.isFile) {\n throw notAFile(target);\n }\n await symlink(target, path, 'file');\n};\n\n/**\n * Writes a file atomically: readers see either the previous content or the complete new content,\n * never a partial write. The data goes to a temporary file in the same directory, which is flushed\n * to disk and renamed over the path; on POSIX, the directory is flushed too. An explicit mode wins;\n * without one, a replaced file keeps its permission bits and a new file gets the platform default.\n * The temporary file is never more widely readable than the final mode. When writing fails before\n * the rename, the temporary file is removed and the destination is untouched.\n * @param path The file's path.\n * @param data The content.\n * @param mode The permission bits of the written file, such as `0o600`.\n * @returns A promise that resolves once the file is written and flushed.\n * @example\n * import { writeFileAtomic } from 'jg-fs';\n *\n * const state = { step: 2 };\n * await writeFileAtomic('state.json', JSON.stringify(state)); // readers never see a partial file\n * await writeFileAtomic('secrets.env', 'TOKEN=abc\\n', 0o600); // only the owner can read it\n */\nexport const writeFileAtomic = async (\n path: string,\n data: string | NodeJS.ArrayBufferView,\n mode?: number,\n): Promise<void> => {\n const directory = dirname(path);\n await mkdir(directory, { recursive: true });\n const finalMode = mode ?? (await readPermissionBits(path));\n const tempPath = join(directory, `.${randomUUID()}.tmp`);\n let isRenamed = false;\n try {\n // a known final mode is applied with chmod, so the umask can't change it\n const handle = await open(tempPath, 'wx', finalMode === undefined ? 0o666 : 0o600);\n try {\n if (finalMode !== undefined) {\n await handle.chmod(finalMode);\n }\n await handle.writeFile(data);\n await handle.sync();\n } finally {\n await handle.close();\n }\n await rename(tempPath, path);\n isRenamed = true;\n } finally {\n if (!isRenamed) {\n await rm(tempPath, { force: true });\n }\n }\n if (process.platform !== 'win32') {\n await syncDirectory(directory);\n }\n};\n\n/**\n * Reads a regular file of at most `maxBytes` bytes. It never follows a symbolic link in the\n * path's last component, though links in parent directories are followed, so callers that need\n * containment combine it with `isPathWithin`. It reads at most `maxBytes + 1` bytes, even when the\n * file grows while it's read.\n * @param path The file's path.\n * @param maxBytes The largest size accepted, in bytes.\n * @returns A promise that resolves to the content.\n * @example\n * import { join } from 'node:path';\n * import { readBoundedFile } from 'jg-fs';\n *\n * const upload = await readBoundedFile(join('uploads', 'avatar.png'), 5 * 1024 * 1024); // at most 5 MB\n * @throws\n * - NOT_A_FILE: if the path doesn't exist, its final component is a symbolic link, or it isn't a regular file\n * - FILE_TOO_LARGE: if the file holds more than maxBytes bytes\n */\nexport const readBoundedFile = async (path: string, maxBytes: number): Promise<Buffer> => {\n const handle = await openWithoutFollowing(path);\n try {\n const stats = await handle.stat();\n if (!stats.isFile()) {\n throw notAFile(path);\n }\n if (stats.size > maxBytes) {\n throw fileTooLarge(path, maxBytes);\n }\n const chunks: Buffer[] = [];\n let totalBytes = 0;\n while (totalBytes <= maxBytes) {\n const chunk = Buffer.allocUnsafe(\n Math.min(BOUNDED_READ_CHUNK_SIZE, maxBytes + 1 - totalBytes),\n );\n const { bytesRead } = await handle.read(chunk, 0, chunk.length, totalBytes);\n if (bytesRead === 0) {\n break;\n }\n chunks.push(chunk.subarray(0, bytesRead));\n totalBytes += bytesRead;\n }\n if (totalBytes > maxBytes) {\n throw fileTooLarge(path, maxBytes);\n }\n return Buffer.concat(chunks, totalBytes);\n } finally {\n await handle.close();\n }\n};\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { copyFile, createFileSymLink, deleteFile, isFile, readBoundedFile, readBufferFile, readFile,
|
|
1
|
+
export { copyFile, createFileSymLink, deleteFile, isFile, readBoundedFile, readBufferFile, readFile, readJsonFile, readTextFile, writeBufferFile, writeFile, writeFileAtomic, writeJsonFile, writeTextFile, } from './files.js';
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// file actions
|
|
2
|
-
export { copyFile, createFileSymLink, deleteFile, isFile, readBoundedFile, readBufferFile, readFile,
|
|
2
|
+
export { copyFile, createFileSymLink, deleteFile, isFile, readBoundedFile, readBufferFile, readFile, readJsonFile, readTextFile, writeBufferFile, writeFile, writeFileAtomic, writeJsonFile, writeTextFile, } from './files.js';
|
|
3
3
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/async/files/index.ts"],"names":[],"mappings":"AAAA,eAAe;AACf,OAAO,EACL,QAAQ,EACR,iBAAiB,EACjB,UAAU,EACV,MAAM,EACN,eAAe,EACf,cAAc,EACd,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,SAAS,EACT,eAAe,EACf,aAAa,EACb,aAAa,GACd,MAAM,YAAY,CAAC","sourcesContent":["// file actions\nexport {\n copyFile,\n createFileSymLink,\n deleteFile,\n isFile,\n readBoundedFile,\n readBufferFile,\n readFile,\n
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/async/files/index.ts"],"names":[],"mappings":"AAAA,eAAe;AACf,OAAO,EACL,QAAQ,EACR,iBAAiB,EACjB,UAAU,EACV,MAAM,EACN,eAAe,EACf,cAAc,EACd,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,SAAS,EACT,eAAe,EACf,aAAa,EACb,aAAa,GACd,MAAM,YAAY,CAAC","sourcesContent":["// file actions\nexport {\n copyFile,\n createFileSymLink,\n deleteFile,\n isFile,\n readBoundedFile,\n readBufferFile,\n readFile,\n readJsonFile,\n readTextFile,\n writeBufferFile,\n writeFile,\n writeFileAtomic,\n writeJsonFile,\n writeTextFile,\n} from './files.js';\n"]}
|
|
@@ -4,6 +4,8 @@ import { type IPathElement } from '../../shared/index.js';
|
|
|
4
4
|
* @param path The path.
|
|
5
5
|
* @returns A promise that resolves to whether the path exists.
|
|
6
6
|
* @example
|
|
7
|
+
* import { pathExists } from 'jg-fs';
|
|
8
|
+
*
|
|
7
9
|
* await pathExists('config.json'); // true when the file exists
|
|
8
10
|
*/
|
|
9
11
|
export declare const pathExists: (path: string) => Promise<boolean>;
|
|
@@ -12,6 +14,8 @@ export declare const pathExists: (path: string) => Promise<boolean>;
|
|
|
12
14
|
* @param path The path.
|
|
13
15
|
* @returns A promise that resolves to the path's details, or `null` when it doesn't exist.
|
|
14
16
|
* @example
|
|
17
|
+
* import { getPathElement } from 'jg-fs';
|
|
18
|
+
*
|
|
15
19
|
* const element = await getPathElement('logo.png');
|
|
16
20
|
* element?.size; // the size in bytes, or undefined when the path doesn't exist
|
|
17
21
|
*/
|
|
@@ -5,6 +5,8 @@ import { toPathElement } from '../../shared/index.js';
|
|
|
5
5
|
* @param path The path.
|
|
6
6
|
* @returns A promise that resolves to whether the path exists.
|
|
7
7
|
* @example
|
|
8
|
+
* import { pathExists } from 'jg-fs';
|
|
9
|
+
*
|
|
8
10
|
* await pathExists('config.json'); // true when the file exists
|
|
9
11
|
*/
|
|
10
12
|
export const pathExists = async (path) => {
|
|
@@ -21,6 +23,8 @@ export const pathExists = async (path) => {
|
|
|
21
23
|
* @param path The path.
|
|
22
24
|
* @returns A promise that resolves to the path's details, or `null` when it doesn't exist.
|
|
23
25
|
* @example
|
|
26
|
+
* import { getPathElement } from 'jg-fs';
|
|
27
|
+
*
|
|
24
28
|
* const element = await getPathElement('logo.png');
|
|
25
29
|
* element?.size; // the size in bytes, or undefined when the path doesn't exist
|
|
26
30
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"general.js","sourceRoot":"","sources":["../../../src/async/general/general.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAEjD,OAAO,EAAE,aAAa,EAAqB,MAAM,uBAAuB,CAAC;AAEzE
|
|
1
|
+
{"version":3,"file":"general.js","sourceRoot":"","sources":["../../../src/async/general/general.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAEjD,OAAO,EAAE,aAAa,EAAqB,MAAM,uBAAuB,CAAC;AAEzE;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,KAAK,EAAE,IAAY,EAAoB,EAAE;IACjE,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,KAAK,EAAE,IAAY,EAAgC,EAAE;IACjF,IAAI,CAAC;QACH,OAAO,aAAa,CAAC,IAAI,EAAE,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC,CAAC","sourcesContent":["import { access, lstat } from 'node:fs/promises';\n\nimport { toPathElement, type IPathElement } from '../../shared/index.js';\n\n/**\n * Checks whether a path exists, whether it's a file, a directory, or anything else.\n * @param path The path.\n * @returns A promise that resolves to whether the path exists.\n * @example\n * import { pathExists } from 'jg-fs';\n *\n * await pathExists('config.json'); // true when the file exists\n */\nexport const pathExists = async (path: string): Promise<boolean> => {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n};\n\n/**\n * Reads a path's details without following a symbolic link at the path.\n * @param path The path.\n * @returns A promise that resolves to the path's details, or `null` when it doesn't exist.\n * @example\n * import { getPathElement } from 'jg-fs';\n *\n * const element = await getPathElement('logo.png');\n * element?.size; // the size in bytes, or undefined when the path doesn't exist\n */\nexport const getPathElement = async (path: string): Promise<IPathElement | null> => {\n try {\n return toPathElement(path, await lstat(path));\n } catch {\n return null;\n }\n};\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { ERRORS } from './shared/index.js';
|
|
2
|
-
export type { IDirectoryElementsKeySort, IDirectoryElementsOptions, IDirectoryPathElements, IPathElement, } from './shared/index.js';
|
|
2
|
+
export type { IDirectoryElementsKeySort, IDirectoryElementsOptions, IDirectoryPathElements, IPathElement, IReadJsonFileOptions, ISchema, } from './shared/index.js';
|
|
3
3
|
export { isPathWithin } from './shared/index.js';
|
|
4
4
|
export { getPathElement, pathExists } from './async/general/index.js';
|
|
5
5
|
export { copyDirectory, createDirectory, createDirectorySymLink, deleteDirectory, getDirectoryElements, isDirectory, readDirectory, } from './async/directories/index.js';
|
|
6
|
-
export { copyFile, createFileSymLink, deleteFile, isFile, readBoundedFile, readBufferFile, readFile,
|
|
6
|
+
export { copyFile, createFileSymLink, deleteFile, isFile, readBoundedFile, readBufferFile, readFile, readJsonFile, readTextFile, writeBufferFile, writeFile, writeFileAtomic, writeJsonFile, writeTextFile, } from './async/files/index.js';
|
package/dist/index.js
CHANGED
|
@@ -6,5 +6,5 @@ export { getPathElement, pathExists } from './async/general/index.js';
|
|
|
6
6
|
// directory actions
|
|
7
7
|
export { copyDirectory, createDirectory, createDirectorySymLink, deleteDirectory, getDirectoryElements, isDirectory, readDirectory, } from './async/directories/index.js';
|
|
8
8
|
// file actions
|
|
9
|
-
export { copyFile, createFileSymLink, deleteFile, isFile, readBoundedFile, readBufferFile, readFile,
|
|
9
|
+
export { copyFile, createFileSymLink, deleteFile, isFile, readBoundedFile, readBufferFile, readFile, readJsonFile, readTextFile, writeBufferFile, writeFile, writeFileAtomic, writeJsonFile, writeTextFile, } from './async/files/index.js';
|
|
10
10
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,SAAS;AACT,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,SAAS;AACT,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAY3C,kBAAkB;AAClB,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAEtE,oBAAoB;AACpB,OAAO,EACL,aAAa,EACb,eAAe,EACf,sBAAsB,EACtB,eAAe,EACf,oBAAoB,EACpB,WAAW,EACX,aAAa,GACd,MAAM,8BAA8B,CAAC;AAEtC,eAAe;AACf,OAAO,EACL,QAAQ,EACR,iBAAiB,EACjB,UAAU,EACV,MAAM,EACN,eAAe,EACf,cAAc,EACd,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,SAAS,EACT,eAAe,EACf,aAAa,EACb,aAAa,GACd,MAAM,wBAAwB,CAAC","sourcesContent":["// errors\nexport { ERRORS } from './shared/index.js';\n\n// types\nexport type {\n IDirectoryElementsKeySort,\n IDirectoryElementsOptions,\n IDirectoryPathElements,\n IPathElement,\n IReadJsonFileOptions,\n ISchema,\n} from './shared/index.js';\n\n// general actions\nexport { isPathWithin } from './shared/index.js';\nexport { getPathElement, pathExists } from './async/general/index.js';\n\n// directory actions\nexport {\n copyDirectory,\n createDirectory,\n createDirectorySymLink,\n deleteDirectory,\n getDirectoryElements,\n isDirectory,\n readDirectory,\n} from './async/directories/index.js';\n\n// file actions\nexport {\n copyFile,\n createFileSymLink,\n deleteFile,\n isFile,\n readBoundedFile,\n readBufferFile,\n readFile,\n readJsonFile,\n readTextFile,\n writeBufferFile,\n writeFile,\n writeFileAtomic,\n writeJsonFile,\n writeTextFile,\n} from './async/files/index.js';\n"]}
|
package/dist/shared/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { ERRORS } from './errors.js';
|
|
2
|
-
export type { IDirectoryElementsKeySort, IDirectoryElementsOptions, IDirectoryPathElements, IPathElement, IReadBufferFileOptions, IReadFileOptions, IReadStringFileOptions, } from './types.js';
|
|
2
|
+
export type { IDirectoryElementsKeySort, IDirectoryElementsOptions, IDirectoryPathElements, IPathElement, IReadBufferFileOptions, IReadFileOptions, IReadJsonFileOptions, IReadStringFileOptions, ISchema, } from './types.js';
|
|
3
3
|
export { BOUNDED_READ_CHUNK_SIZE, DIRECTORY_ELEMENTS_CONCURRENCY, DIRECTORY_ELEMENTS_DEFAULT_OPTIONS, } from './constants.js';
|
|
4
4
|
export { isPathWithin, toPathElement } from './paths.js';
|
|
5
5
|
export { buildDirectoryElementsOptions, groupDirectoryElements } from './directory-elements.js';
|
|
6
|
-
export { assertBufferData, assertTextData, fileTooLarge, isNoFollowError, isNotFoundError, notADirectory, notAFile, notARegularFile,
|
|
6
|
+
export { assertBufferData, assertTextData, fileTooLarge, isNoFollowError, isNotFoundError, notADirectory, notAFile, notARegularFile, parseJsonContent, serializeJsonData, } from './validations.js';
|
package/dist/shared/index.js
CHANGED
|
@@ -7,5 +7,5 @@ export { isPathWithin, toPathElement } from './paths.js';
|
|
|
7
7
|
// directory elements
|
|
8
8
|
export { buildDirectoryElementsOptions, groupDirectoryElements } from './directory-elements.js';
|
|
9
9
|
// validations and exceptions
|
|
10
|
-
export { assertBufferData, assertTextData, fileTooLarge, isNoFollowError, isNotFoundError, notADirectory, notAFile, notARegularFile,
|
|
10
|
+
export { assertBufferData, assertTextData, fileTooLarge, isNoFollowError, isNotFoundError, notADirectory, notAFile, notARegularFile, parseJsonContent, serializeJsonData, } from './validations.js';
|
|
11
11
|
//# sourceMappingURL=index.js.map
|
package/dist/shared/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/shared/index.ts"],"names":[],"mappings":"AAAA,SAAS;AACT,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/shared/index.ts"],"names":[],"mappings":"AAAA,SAAS;AACT,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAerC,YAAY;AACZ,OAAO,EACL,uBAAuB,EACvB,8BAA8B,EAC9B,kCAAkC,GACnC,MAAM,gBAAgB,CAAC;AAExB,QAAQ;AACR,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEzD,qBAAqB;AACrB,OAAO,EAAE,6BAA6B,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAEhG,6BAA6B;AAC7B,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,eAAe,EACf,aAAa,EACb,QAAQ,EACR,eAAe,EACf,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC","sourcesContent":["// errors\nexport { ERRORS } from './errors.js';\n\n// types\nexport type {\n IDirectoryElementsKeySort,\n IDirectoryElementsOptions,\n IDirectoryPathElements,\n IPathElement,\n IReadBufferFileOptions,\n IReadFileOptions,\n IReadJsonFileOptions,\n IReadStringFileOptions,\n ISchema,\n} from './types.js';\n\n// constants\nexport {\n BOUNDED_READ_CHUNK_SIZE,\n DIRECTORY_ELEMENTS_CONCURRENCY,\n DIRECTORY_ELEMENTS_DEFAULT_OPTIONS,\n} from './constants.js';\n\n// paths\nexport { isPathWithin, toPathElement } from './paths.js';\n\n// directory elements\nexport { buildDirectoryElementsOptions, groupDirectoryElements } from './directory-elements.js';\n\n// validations and exceptions\nexport {\n assertBufferData,\n assertTextData,\n fileTooLarge,\n isNoFollowError,\n isNotFoundError,\n notADirectory,\n notAFile,\n notARegularFile,\n parseJsonContent,\n serializeJsonData,\n} from './validations.js';\n"]}
|
package/dist/shared/paths.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ import type { IPathElement } from './types.js';
|
|
|
9
9
|
* @param targetPath The path to check.
|
|
10
10
|
* @returns Whether the target is the root or inside it.
|
|
11
11
|
* @example
|
|
12
|
+
* import { isPathWithin } from 'jg-fs';
|
|
13
|
+
*
|
|
12
14
|
* isPathWithin('/data/app', '/data/app/logs/today.log'); // true
|
|
13
15
|
* isPathWithin('/data/app', '/data/app/../secrets'); // false
|
|
14
16
|
* isPathWithin('/data/app', '/data/application'); // false
|
package/dist/shared/paths.js
CHANGED
|
@@ -8,6 +8,8 @@ import { basename, extname, isAbsolute, relative, resolve, sep } from 'node:path
|
|
|
8
8
|
* @param targetPath The path to check.
|
|
9
9
|
* @returns Whether the target is the root or inside it.
|
|
10
10
|
* @example
|
|
11
|
+
* import { isPathWithin } from 'jg-fs';
|
|
12
|
+
*
|
|
11
13
|
* isPathWithin('/data/app', '/data/app/logs/today.log'); // true
|
|
12
14
|
* isPathWithin('/data/app', '/data/app/../secrets'); // false
|
|
13
15
|
* isPathWithin('/data/app', '/data/application'); // false
|
package/dist/shared/paths.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/shared/paths.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAIlF
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/shared/paths.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAIlF;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,QAAgB,EAAE,UAAkB,EAAW,EAAE;IAC5E,MAAM,YAAY,GAAG,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;IACtE,OAAO,CACL,YAAY,KAAK,EAAE;QACnB,CAAC,YAAY,KAAK,IAAI,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,KAAK,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,CAC7F,CAAC;AACJ,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,IAAY,EAAE,KAAY,EAAgB,EAAE,CAAC,CAAC;IAC1E,IAAI;IACJ,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC;IACtB,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE;IACtB,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE;IAChC,cAAc,EAAE,KAAK,CAAC,cAAc,EAAE;IACtC,IAAI,EAAE,KAAK,CAAC,IAAI;IAChB,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC;CACxC,CAAC,CAAC","sourcesContent":["import type { Stats } from 'node:fs';\nimport { basename, extname, isAbsolute, relative, resolve, sep } from 'node:path';\n\nimport type { IPathElement } from './types.js';\n\n/**\n * Checks whether a path is a root path or nested inside it. The check is lexical: both paths are\n * resolved without touching the filesystem, and symbolic links aren't followed. Sibling paths\n * that share a prefix, such as `/data/app` and `/data/application`, aren't nested, and neither is a\n * path on another drive on Windows.\n * @param rootPath The root path.\n * @param targetPath The path to check.\n * @returns Whether the target is the root or inside it.\n * @example\n * import { isPathWithin } from 'jg-fs';\n *\n * isPathWithin('/data/app', '/data/app/logs/today.log'); // true\n * isPathWithin('/data/app', '/data/app/../secrets'); // false\n * isPathWithin('/data/app', '/data/application'); // false\n */\nexport const isPathWithin = (rootPath: string, targetPath: string): boolean => {\n const relativePath = relative(resolve(rootPath), resolve(targetPath));\n return (\n relativePath === '' ||\n (relativePath !== '..' && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath))\n );\n};\n\n/**\n * Describes a path from its lstat result.\n * @param path The path.\n * @param stats Its lstat result.\n * @returns The path element.\n */\nexport const toPathElement = (path: string, stats: Stats): IPathElement => ({\n path,\n baseName: basename(path),\n extName: extname(path),\n isFile: stats.isFile(),\n isDirectory: stats.isDirectory(),\n isSymbolicLink: stats.isSymbolicLink(),\n size: stats.size,\n creation: Math.round(stats.birthtimeMs),\n});\n"]}
|
package/dist/shared/types.d.ts
CHANGED
|
@@ -29,3 +29,9 @@ export type IReadBufferFileOptions = {
|
|
|
29
29
|
flag?: string | undefined;
|
|
30
30
|
} | null;
|
|
31
31
|
export type IReadFileOptions = IReadBufferFileOptions | IReadStringFileOptions;
|
|
32
|
+
export type ISchema<T> = {
|
|
33
|
+
parse(value: unknown): T;
|
|
34
|
+
};
|
|
35
|
+
export type IReadJsonFileOptions<T> = {
|
|
36
|
+
schema?: ISchema<T>;
|
|
37
|
+
};
|
package/dist/shared/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/shared/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ISortDirection } from 'jg-utilities';\n\n// the details of a path, read with lstat, so a symbolic link is described rather than followed\nexport type IPathElement = {\n // the path, as given\n path: string;\n\n // the last segment of the path\n baseName: string;\n\n // the extension, such as '.json', or an empty string when there is none\n extName: string;\n\n // whether it's a regular file\n isFile: boolean;\n\n // whether it's a directory\n isDirectory: boolean;\n\n // whether it's a symbolic link, in which case isFile and isDirectory are false\n isSymbolicLink: boolean;\n\n // the size in bytes\n size: number;\n\n // when it was created, in milliseconds since the epoch\n creation: number;\n};\n\n// the keys a directory's elements can be sorted by\nexport type IDirectoryElementsKeySort = 'baseName' | 'size' | 'creation';\n\n// how getDirectoryElements filters and sorts a directory's elements\nexport type IDirectoryElementsOptions = {\n // the key the elements are sorted by. Defaults to 'baseName'\n sortByKey: IDirectoryElementsKeySort;\n\n // the sort direction. Defaults to 'asc'\n sortOrder: ISortDirection;\n\n // the extensions of the files to include, such as ['.json'], matched case-insensitively. Defaults to all\n includeExts: string[];\n};\n\n// a directory's elements, grouped by kind\nexport type IDirectoryPathElements = {\n directories: IPathElement[];\n files: IPathElement[];\n symbolicLinks: IPathElement[];\n};\n\n// the readFile options that make it return a string\nexport type IReadStringFileOptions =\n | {\n encoding: BufferEncoding;\n flag?: string | undefined;\n }\n | BufferEncoding;\n\n// the readFile options that make it return a Buffer\nexport type IReadBufferFileOptions = {\n encoding?: null | undefined;\n flag?: string | undefined;\n} | null;\n\n// the options readFile accepts\nexport type IReadFileOptions = IReadBufferFileOptions | IReadStringFileOptions;\n"]}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/shared/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ISortDirection } from 'jg-utilities';\n\n// the details of a path, read with lstat, so a symbolic link is described rather than followed\nexport type IPathElement = {\n // the path, as given\n path: string;\n\n // the last segment of the path\n baseName: string;\n\n // the extension, such as '.json', or an empty string when there is none\n extName: string;\n\n // whether it's a regular file\n isFile: boolean;\n\n // whether it's a directory\n isDirectory: boolean;\n\n // whether it's a symbolic link, in which case isFile and isDirectory are false\n isSymbolicLink: boolean;\n\n // the size in bytes\n size: number;\n\n // when it was created, in milliseconds since the epoch\n creation: number;\n};\n\n// the keys a directory's elements can be sorted by\nexport type IDirectoryElementsKeySort = 'baseName' | 'size' | 'creation';\n\n// how getDirectoryElements filters and sorts a directory's elements\nexport type IDirectoryElementsOptions = {\n // the key the elements are sorted by. Defaults to 'baseName'\n sortByKey: IDirectoryElementsKeySort;\n\n // the sort direction. Defaults to 'asc'\n sortOrder: ISortDirection;\n\n // the extensions of the files to include, such as ['.json'], matched case-insensitively. Defaults to all\n includeExts: string[];\n};\n\n// a directory's elements, grouped by kind\nexport type IDirectoryPathElements = {\n directories: IPathElement[];\n files: IPathElement[];\n symbolicLinks: IPathElement[];\n};\n\n// the readFile options that make it return a string\nexport type IReadStringFileOptions =\n | {\n encoding: BufferEncoding;\n flag?: string | undefined;\n }\n | BufferEncoding;\n\n// the readFile options that make it return a Buffer\nexport type IReadBufferFileOptions = {\n encoding?: null | undefined;\n flag?: string | undefined;\n} | null;\n\n// the options readFile accepts\nexport type IReadFileOptions = IReadBufferFileOptions | IReadStringFileOptions;\n\n// validates parsed data and returns it typed, which a Zod schema does\nexport type ISchema<T> = {\n parse(value: unknown): T;\n};\n\n// what readJsonFile accepts\nexport type IReadJsonFileOptions<T> = {\n // validates the parsed content, such as a Zod schema. Its errors propagate unchanged\n schema?: ISchema<T>;\n};\n"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Exception } from 'jg-exception';
|
|
2
|
+
import type { ISchema } from './types.js';
|
|
2
3
|
/**
|
|
3
4
|
* Builds the exception for a path that isn't a file.
|
|
4
5
|
* @param path The path.
|
|
@@ -44,16 +45,17 @@ export declare const assertBufferData: (path: string, data: unknown) => void;
|
|
|
44
45
|
* @throws
|
|
45
46
|
* - FILE_CONTENT_IS_EMPTY_OR_INVALID: if the JSON content cannot be stringified or is empty
|
|
46
47
|
*/
|
|
47
|
-
export declare const
|
|
48
|
+
export declare const serializeJsonData: (path: string, data: unknown, space: number) => string;
|
|
48
49
|
/**
|
|
49
|
-
* Parses a JSON file's text.
|
|
50
|
+
* Parses a JSON file's text, and validates it with the schema when there is one.
|
|
50
51
|
* @param path The file's path.
|
|
51
52
|
* @param content The file's text.
|
|
53
|
+
* @param schema The schema, whose errors propagate unchanged.
|
|
52
54
|
* @returns The parsed value.
|
|
53
55
|
* @throws
|
|
54
56
|
* - FILE_CONTENT_IS_EMPTY_OR_INVALID: if the file's JSON content cannot be parsed
|
|
55
57
|
*/
|
|
56
|
-
export declare const
|
|
58
|
+
export declare const parseJsonContent: <T>(path: string, content: string, schema?: ISchema<T>) => T;
|
|
57
59
|
/**
|
|
58
60
|
* Checks whether an error is a missing-path error from Node.js.
|
|
59
61
|
* @param error The error.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer';
|
|
2
|
-
import { Exception
|
|
2
|
+
import { Exception } from 'jg-exception';
|
|
3
3
|
import { ERRORS } from './errors.js';
|
|
4
4
|
/*
|
|
5
5
|
* The checks and exceptions both entries share, so the async and sync functions validate data and
|
|
@@ -10,20 +10,22 @@ import { ERRORS } from './errors.js';
|
|
|
10
10
|
* @param path The path.
|
|
11
11
|
* @returns The NOT_A_FILE exception.
|
|
12
12
|
*/
|
|
13
|
-
export const notAFile = (path) => new Exception(`The path '${path}' is not a file.`, ERRORS.NOT_A_FILE);
|
|
13
|
+
export const notAFile = (path) => new Exception(`The path '${path}' is not a file.`, { code: ERRORS.NOT_A_FILE });
|
|
14
14
|
/**
|
|
15
15
|
* Builds the exception for a path that isn't a directory.
|
|
16
16
|
* @param path The path.
|
|
17
17
|
* @returns The NOT_A_DIRECTORY exception.
|
|
18
18
|
*/
|
|
19
|
-
export const notADirectory = (path) => new Exception(`The path '${path}' is not a directory.`, ERRORS.NOT_A_DIRECTORY);
|
|
19
|
+
export const notADirectory = (path) => new Exception(`The path '${path}' is not a directory.`, { code: ERRORS.NOT_A_DIRECTORY });
|
|
20
20
|
/**
|
|
21
21
|
* Builds the exception for a file larger than a bounded read allows.
|
|
22
22
|
* @param path The path.
|
|
23
23
|
* @param maxBytes The limit.
|
|
24
24
|
* @returns The FILE_TOO_LARGE exception.
|
|
25
25
|
*/
|
|
26
|
-
export const fileTooLarge = (path, maxBytes) => new Exception(`The file '${path}' is larger than ${maxBytes} bytes.`,
|
|
26
|
+
export const fileTooLarge = (path, maxBytes) => new Exception(`The file '${path}' is larger than ${maxBytes} bytes.`, {
|
|
27
|
+
code: ERRORS.FILE_TOO_LARGE,
|
|
28
|
+
});
|
|
27
29
|
/**
|
|
28
30
|
* Ensures text written to a file is a string. An empty string is valid and writes an empty file.
|
|
29
31
|
* @param path The file's path.
|
|
@@ -33,7 +35,7 @@ export const fileTooLarge = (path, maxBytes) => new Exception(`The file '${path}
|
|
|
33
35
|
*/
|
|
34
36
|
export const assertTextData = (path, data) => {
|
|
35
37
|
if (typeof data !== 'string') {
|
|
36
|
-
throw new Exception(`The data for the file '${path}' must be a string. Received: ${typeof data}`, ERRORS.FILE_CONTENT_IS_EMPTY_OR_INVALID);
|
|
38
|
+
throw new Exception(`The data for the file '${path}' must be a string. Received: ${typeof data}`, { code: ERRORS.FILE_CONTENT_IS_EMPTY_OR_INVALID });
|
|
37
39
|
}
|
|
38
40
|
};
|
|
39
41
|
/**
|
|
@@ -45,7 +47,7 @@ export const assertTextData = (path, data) => {
|
|
|
45
47
|
*/
|
|
46
48
|
export const assertBufferData = (path, data) => {
|
|
47
49
|
if (!Buffer.isBuffer(data)) {
|
|
48
|
-
throw new Exception(`The data for the file '${path}' must be a Buffer. Received: ${typeof data}`, ERRORS.FILE_CONTENT_IS_EMPTY_OR_INVALID);
|
|
50
|
+
throw new Exception(`The data for the file '${path}' must be a Buffer. Received: ${typeof data}`, { code: ERRORS.FILE_CONTENT_IS_EMPTY_OR_INVALID });
|
|
49
51
|
}
|
|
50
52
|
};
|
|
51
53
|
/**
|
|
@@ -58,34 +60,45 @@ export const assertBufferData = (path, data) => {
|
|
|
58
60
|
* @throws
|
|
59
61
|
* - FILE_CONTENT_IS_EMPTY_OR_INVALID: if the JSON content cannot be stringified or is empty
|
|
60
62
|
*/
|
|
61
|
-
export const
|
|
63
|
+
export const serializeJsonData = (path, data, space) => {
|
|
62
64
|
let text;
|
|
63
65
|
try {
|
|
64
66
|
text = typeof data === 'string' ? data : JSON.stringify(data, undefined, space);
|
|
65
67
|
}
|
|
66
68
|
catch (error) {
|
|
67
|
-
throw new Exception(`The JSON data for the file '${path}' could not be stringified
|
|
69
|
+
throw new Exception(`The JSON data for the file '${path}' could not be stringified.`, {
|
|
70
|
+
code: ERRORS.FILE_CONTENT_IS_EMPTY_OR_INVALID,
|
|
71
|
+
cause: error,
|
|
72
|
+
});
|
|
68
73
|
}
|
|
69
74
|
if (typeof text !== 'string' || text === '') {
|
|
70
|
-
throw new Exception(`The JSON data for the file '${path}' is empty.`,
|
|
75
|
+
throw new Exception(`The JSON data for the file '${path}' is empty.`, {
|
|
76
|
+
code: ERRORS.FILE_CONTENT_IS_EMPTY_OR_INVALID,
|
|
77
|
+
});
|
|
71
78
|
}
|
|
72
79
|
return text;
|
|
73
80
|
};
|
|
74
81
|
/**
|
|
75
|
-
* Parses a JSON file's text.
|
|
82
|
+
* Parses a JSON file's text, and validates it with the schema when there is one.
|
|
76
83
|
* @param path The file's path.
|
|
77
84
|
* @param content The file's text.
|
|
85
|
+
* @param schema The schema, whose errors propagate unchanged.
|
|
78
86
|
* @returns The parsed value.
|
|
79
87
|
* @throws
|
|
80
88
|
* - FILE_CONTENT_IS_EMPTY_OR_INVALID: if the file's JSON content cannot be parsed
|
|
81
89
|
*/
|
|
82
|
-
export const
|
|
90
|
+
export const parseJsonContent = (path, content, schema) => {
|
|
91
|
+
let value;
|
|
83
92
|
try {
|
|
84
|
-
|
|
93
|
+
value = JSON.parse(content);
|
|
85
94
|
}
|
|
86
95
|
catch (error) {
|
|
87
|
-
throw new Exception(`The JSON file '${path}' could not be parsed
|
|
96
|
+
throw new Exception(`The JSON file '${path}' could not be parsed.`, {
|
|
97
|
+
code: ERRORS.FILE_CONTENT_IS_EMPTY_OR_INVALID,
|
|
98
|
+
cause: error,
|
|
99
|
+
});
|
|
88
100
|
}
|
|
101
|
+
return schema === undefined ? value : schema.parse(value);
|
|
89
102
|
};
|
|
90
103
|
/**
|
|
91
104
|
* Checks whether an error is a missing-path error from Node.js.
|
|
@@ -99,7 +112,10 @@ export const isNotFoundError = (error) => error instanceof Error && 'code' in er
|
|
|
99
112
|
* @param error The error opening it.
|
|
100
113
|
* @returns The NOT_A_FILE exception.
|
|
101
114
|
*/
|
|
102
|
-
export const notARegularFile = (path, error) => new Exception(`The file '${path}' doesn't exist or is a symbolic link
|
|
115
|
+
export const notARegularFile = (path, error) => new Exception(`The file '${path}' doesn't exist or is a symbolic link.`, {
|
|
116
|
+
code: ERRORS.NOT_A_FILE,
|
|
117
|
+
cause: error,
|
|
118
|
+
});
|
|
103
119
|
/**
|
|
104
120
|
* Checks whether opening a path without following links failed because it doesn't exist or its
|
|
105
121
|
* last component is a link (ELOOP, or EMLINK on some BSDs).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validations.js","sourceRoot":"","sources":["../../src/shared/validations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,
|
|
1
|
+
{"version":3,"file":"validations.js","sourceRoot":"","sources":["../../src/shared/validations.ts"],"names":[],"mappings":"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","sourcesContent":["import { Buffer } from 'node:buffer';\nimport { Exception } from 'jg-exception';\n\nimport { ERRORS } from './errors.js';\nimport type { ISchema } from './types.js';\n\n/*\n * The checks and exceptions both entries share, so the async and sync functions validate data and\n * report errors identically.\n */\n\n/**\n * Builds the exception for a path that isn't a file.\n * @param path The path.\n * @returns The NOT_A_FILE exception.\n */\nexport const notAFile = (path: string): Exception =>\n new Exception(`The path '${path}' is not a file.`, { code: ERRORS.NOT_A_FILE });\n\n/**\n * Builds the exception for a path that isn't a directory.\n * @param path The path.\n * @returns The NOT_A_DIRECTORY exception.\n */\nexport const notADirectory = (path: string): Exception =>\n new Exception(`The path '${path}' is not a directory.`, { code: ERRORS.NOT_A_DIRECTORY });\n\n/**\n * Builds the exception for a file larger than a bounded read allows.\n * @param path The path.\n * @param maxBytes The limit.\n * @returns The FILE_TOO_LARGE exception.\n */\nexport const fileTooLarge = (path: string, maxBytes: number): Exception =>\n new Exception(`The file '${path}' is larger than ${maxBytes} bytes.`, {\n code: ERRORS.FILE_TOO_LARGE,\n });\n\n/**\n * Ensures text written to a file is a string. An empty string is valid and writes an empty file.\n * @param path The file's path.\n * @param data The text.\n * @throws\n * - FILE_CONTENT_IS_EMPTY_OR_INVALID: if the data isn't a string\n */\nexport const assertTextData = (path: string, data: unknown): void => {\n if (typeof data !== 'string') {\n throw new Exception(\n `The data for the file '${path}' must be a string. Received: ${typeof data}`,\n { code: ERRORS.FILE_CONTENT_IS_EMPTY_OR_INVALID },\n );\n }\n};\n\n/**\n * Ensures data written to a binary file is a Buffer. An empty Buffer is valid.\n * @param path The file's path.\n * @param data The data.\n * @throws\n * - FILE_CONTENT_IS_EMPTY_OR_INVALID: if the data isn't a Buffer\n */\nexport const assertBufferData = (path: string, data: unknown): void => {\n if (!Buffer.isBuffer(data)) {\n throw new Exception(\n `The data for the file '${path}' must be a Buffer. Received: ${typeof data}`,\n { code: ERRORS.FILE_CONTENT_IS_EMPTY_OR_INVALID },\n );\n }\n};\n\n/**\n * Turns data into the JSON text of a file: a string is written as given, and anything else is\n * stringified. JSON can't be empty, so an empty result is rejected.\n * @param path The file's path.\n * @param data The data, or its JSON text.\n * @param space The indentation for stringified data.\n * @returns The JSON text.\n * @throws\n * - FILE_CONTENT_IS_EMPTY_OR_INVALID: if the JSON content cannot be stringified or is empty\n */\nexport const serializeJsonData = (path: string, data: unknown, space: number): string => {\n let text: unknown;\n try {\n text = typeof data === 'string' ? data : JSON.stringify(data, undefined, space);\n } catch (error) {\n throw new Exception(`The JSON data for the file '${path}' could not be stringified.`, {\n code: ERRORS.FILE_CONTENT_IS_EMPTY_OR_INVALID,\n cause: error,\n });\n }\n if (typeof text !== 'string' || text === '') {\n throw new Exception(`The JSON data for the file '${path}' is empty.`, {\n code: ERRORS.FILE_CONTENT_IS_EMPTY_OR_INVALID,\n });\n }\n return text;\n};\n\n/**\n * Parses a JSON file's text, and validates it with the schema when there is one.\n * @param path The file's path.\n * @param content The file's text.\n * @param schema The schema, whose errors propagate unchanged.\n * @returns The parsed value.\n * @throws\n * - FILE_CONTENT_IS_EMPTY_OR_INVALID: if the file's JSON content cannot be parsed\n */\nexport const parseJsonContent = <T>(path: string, content: string, schema?: ISchema<T>): T => {\n let value: unknown;\n try {\n value = JSON.parse(content);\n } catch (error) {\n throw new Exception(`The JSON file '${path}' could not be parsed.`, {\n code: ERRORS.FILE_CONTENT_IS_EMPTY_OR_INVALID,\n cause: error,\n });\n }\n return schema === undefined ? (value as T) : schema.parse(value);\n};\n\n/**\n * Checks whether an error is a missing-path error from Node.js.\n * @param error The error.\n * @returns Whether its code is ENOENT.\n */\nexport const isNotFoundError = (error: unknown): boolean =>\n error instanceof Error && 'code' in error && error.code === 'ENOENT';\n\n/**\n * Builds the exception for a bounded read whose path doesn't exist or ends in a symbolic link.\n * @param path The path.\n * @param error The error opening it.\n * @returns The NOT_A_FILE exception.\n */\nexport const notARegularFile = (path: string, error: unknown): Exception =>\n new Exception(`The file '${path}' doesn't exist or is a symbolic link.`, {\n code: ERRORS.NOT_A_FILE,\n cause: error,\n });\n\n/**\n * Checks whether opening a path without following links failed because it doesn't exist or its\n * last component is a link (ELOOP, or EMLINK on some BSDs).\n * @param error The error.\n * @returns Whether it means the path isn't a regular file to read.\n */\nexport const isNoFollowError = (error: unknown): boolean =>\n error instanceof Error &&\n 'code' in error &&\n (error.code === 'ENOENT' || error.code === 'ELOOP' || error.code === 'EMLINK');\n"]}
|
|
@@ -4,6 +4,8 @@ import { type IDirectoryElementsOptions, type IDirectoryPathElements } from '../
|
|
|
4
4
|
* @param path The path.
|
|
5
5
|
* @returns Whether the path is a directory.
|
|
6
6
|
* @example
|
|
7
|
+
* import { isDirectory } from 'jg-fs/sync';
|
|
8
|
+
*
|
|
7
9
|
* isDirectory('dist'); // true
|
|
8
10
|
*/
|
|
9
11
|
export declare const isDirectory: (path: string) => boolean;
|
|
@@ -11,6 +13,8 @@ export declare const isDirectory: (path: string) => boolean;
|
|
|
11
13
|
* Deletes a directory and everything in it. A missing directory is not an error.
|
|
12
14
|
* @param path The directory's path.
|
|
13
15
|
* @example
|
|
16
|
+
* import { deleteDirectory } from 'jg-fs/sync';
|
|
17
|
+
*
|
|
14
18
|
* deleteDirectory('dist');
|
|
15
19
|
*/
|
|
16
20
|
export declare const deleteDirectory: (path: string) => void;
|
|
@@ -20,6 +24,8 @@ export declare const deleteDirectory: (path: string) => void;
|
|
|
20
24
|
* @param path The directory's path.
|
|
21
25
|
* @param deleteIfExists Whether to delete an existing directory and its contents first.
|
|
22
26
|
* @example
|
|
27
|
+
* import { createDirectory } from 'jg-fs/sync';
|
|
28
|
+
*
|
|
23
29
|
* createDirectory('dist/assets'); // creates missing parents too
|
|
24
30
|
* createDirectory('dist', true); // replaces it with an empty directory
|
|
25
31
|
* @throws
|
|
@@ -31,6 +37,8 @@ export declare const createDirectory: (path: string, deleteIfExists?: boolean) =
|
|
|
31
37
|
* @param srcPath The directory to copy.
|
|
32
38
|
* @param destPath Where to copy it.
|
|
33
39
|
* @example
|
|
40
|
+
* import { copyDirectory } from 'jg-fs/sync';
|
|
41
|
+
*
|
|
34
42
|
* copyDirectory('templates', 'dist/templates');
|
|
35
43
|
* @throws
|
|
36
44
|
* - NOT_A_DIRECTORY: if the srcPath is not a directory
|
|
@@ -41,6 +49,8 @@ export declare const copyDirectory: (srcPath: string, destPath: string) => void;
|
|
|
41
49
|
* @param target The directory the link points to.
|
|
42
50
|
* @param path Where to create the link.
|
|
43
51
|
* @example
|
|
52
|
+
* import { createDirectorySymLink } from 'jg-fs/sync';
|
|
53
|
+
*
|
|
44
54
|
* createDirectorySymLink('releases/1.4.0', 'current');
|
|
45
55
|
* @throws
|
|
46
56
|
* - NON_EXISTENT_DIRECTORY: if the target directory doesn't exist
|
|
@@ -53,6 +63,8 @@ export declare const createDirectorySymLink: (target: string, path: string) => v
|
|
|
53
63
|
* @param recursive Whether to include the contents of subdirectories.
|
|
54
64
|
* @returns The content paths.
|
|
55
65
|
* @example
|
|
66
|
+
* import { readDirectory } from 'jg-fs/sync';
|
|
67
|
+
*
|
|
56
68
|
* readDirectory('dist'); // ['dist/index.js', 'dist/assets']
|
|
57
69
|
* readDirectory('dist', true); // includes the contents of dist/assets
|
|
58
70
|
* @throws
|
|
@@ -66,6 +78,8 @@ export declare const readDirectory: (path: string, recursive?: boolean) => strin
|
|
|
66
78
|
* @param options The extensions to include and how to sort.
|
|
67
79
|
* @returns The grouped elements.
|
|
68
80
|
* @example
|
|
81
|
+
* import { getDirectoryElements } from 'jg-fs/sync';
|
|
82
|
+
*
|
|
69
83
|
* const { files } = getDirectoryElements('content', { includeExts: ['.md'], sortByKey: 'creation', sortOrder: 'desc' });
|
|
70
84
|
* @throws
|
|
71
85
|
* - NOT_A_DIRECTORY: if the path doesn't exist or is not a directory
|
|
@@ -8,6 +8,8 @@ import { getPathElement } from '../general/index.js';
|
|
|
8
8
|
* @param path The path.
|
|
9
9
|
* @returns Whether the path is a directory.
|
|
10
10
|
* @example
|
|
11
|
+
* import { isDirectory } from 'jg-fs/sync';
|
|
12
|
+
*
|
|
11
13
|
* isDirectory('dist'); // true
|
|
12
14
|
*/
|
|
13
15
|
export const isDirectory = (path) => getPathElement(path)?.isDirectory ?? false;
|
|
@@ -15,6 +17,8 @@ export const isDirectory = (path) => getPathElement(path)?.isDirectory ?? false;
|
|
|
15
17
|
* Deletes a directory and everything in it. A missing directory is not an error.
|
|
16
18
|
* @param path The directory's path.
|
|
17
19
|
* @example
|
|
20
|
+
* import { deleteDirectory } from 'jg-fs/sync';
|
|
21
|
+
*
|
|
18
22
|
* deleteDirectory('dist');
|
|
19
23
|
*/
|
|
20
24
|
export const deleteDirectory = (path) => rmSync(path, { recursive: true, force: true });
|
|
@@ -24,6 +28,8 @@ export const deleteDirectory = (path) => rmSync(path, { recursive: true, force:
|
|
|
24
28
|
* @param path The directory's path.
|
|
25
29
|
* @param deleteIfExists Whether to delete an existing directory and its contents first.
|
|
26
30
|
* @example
|
|
31
|
+
* import { createDirectory } from 'jg-fs/sync';
|
|
32
|
+
*
|
|
27
33
|
* createDirectory('dist/assets'); // creates missing parents too
|
|
28
34
|
* createDirectory('dist', true); // replaces it with an empty directory
|
|
29
35
|
* @throws
|
|
@@ -32,7 +38,9 @@ export const deleteDirectory = (path) => rmSync(path, { recursive: true, force:
|
|
|
32
38
|
export const createDirectory = (path, deleteIfExists) => {
|
|
33
39
|
const element = getPathElement(path);
|
|
34
40
|
if (element !== null && !element.isDirectory) {
|
|
35
|
-
throw new Exception(`The path '${path}' already exists and is not a directory.`,
|
|
41
|
+
throw new Exception(`The path '${path}' already exists and is not a directory.`, {
|
|
42
|
+
code: ERRORS.DIRECTORY_ALREADY_EXISTS,
|
|
43
|
+
});
|
|
36
44
|
}
|
|
37
45
|
if (element !== null && deleteIfExists) {
|
|
38
46
|
deleteDirectory(path);
|
|
@@ -44,6 +52,8 @@ export const createDirectory = (path, deleteIfExists) => {
|
|
|
44
52
|
* @param srcPath The directory to copy.
|
|
45
53
|
* @param destPath Where to copy it.
|
|
46
54
|
* @example
|
|
55
|
+
* import { copyDirectory } from 'jg-fs/sync';
|
|
56
|
+
*
|
|
47
57
|
* copyDirectory('templates', 'dist/templates');
|
|
48
58
|
* @throws
|
|
49
59
|
* - NOT_A_DIRECTORY: if the srcPath is not a directory
|
|
@@ -60,6 +70,8 @@ export const copyDirectory = (srcPath, destPath) => {
|
|
|
60
70
|
* @param target The directory the link points to.
|
|
61
71
|
* @param path Where to create the link.
|
|
62
72
|
* @example
|
|
73
|
+
* import { createDirectorySymLink } from 'jg-fs/sync';
|
|
74
|
+
*
|
|
63
75
|
* createDirectorySymLink('releases/1.4.0', 'current');
|
|
64
76
|
* @throws
|
|
65
77
|
* - NON_EXISTENT_DIRECTORY: if the target directory doesn't exist
|
|
@@ -68,7 +80,9 @@ export const copyDirectory = (srcPath, destPath) => {
|
|
|
68
80
|
export const createDirectorySymLink = (target, path) => {
|
|
69
81
|
const element = getPathElement(target);
|
|
70
82
|
if (element === null) {
|
|
71
|
-
throw new Exception(`The target directory '${target}' does not exist.`,
|
|
83
|
+
throw new Exception(`The target directory '${target}' does not exist.`, {
|
|
84
|
+
code: ERRORS.NON_EXISTENT_DIRECTORY,
|
|
85
|
+
});
|
|
72
86
|
}
|
|
73
87
|
if (!element.isDirectory) {
|
|
74
88
|
throw notADirectory(target);
|
|
@@ -81,6 +95,8 @@ export const createDirectorySymLink = (target, path) => {
|
|
|
81
95
|
* @param recursive Whether to include the contents of subdirectories.
|
|
82
96
|
* @returns The content paths.
|
|
83
97
|
* @example
|
|
98
|
+
* import { readDirectory } from 'jg-fs/sync';
|
|
99
|
+
*
|
|
84
100
|
* readDirectory('dist'); // ['dist/index.js', 'dist/assets']
|
|
85
101
|
* readDirectory('dist', true); // includes the contents of dist/assets
|
|
86
102
|
* @throws
|
|
@@ -99,6 +115,8 @@ export const readDirectory = (path, recursive = false) => {
|
|
|
99
115
|
* @param options The extensions to include and how to sort.
|
|
100
116
|
* @returns The grouped elements.
|
|
101
117
|
* @example
|
|
118
|
+
* import { getDirectoryElements } from 'jg-fs/sync';
|
|
119
|
+
*
|
|
102
120
|
* const { files } = getDirectoryElements('content', { includeExts: ['.md'], sortByKey: 'creation', sortOrder: 'desc' });
|
|
103
121
|
* @throws
|
|
104
122
|
* - NOT_A_DIRECTORY: if the path doesn't exist or is not a directory
|