import nodeFS from 'node:fs/promises' import path from 'node:path' import nodeFSCallBack, { PathLike } from 'node:fs' import readline from 'node:readline' import glob from 'glob' export namespace PlataFs { export const constants = nodeFS.constants export const fs = nodeFS /** * Tests a user's permissions for the file or directory specified by `path`. * The `mode` argument is an optional integer that specifies the accessibility * checks to be performed. `mode` should be either the value `fs.constants.F_OK`or a mask consisting of the bitwise OR of any of `fs.constants.R_OK`,`fs.constants.W_OK`, and `fs.constants.X_OK` * (e.g.`fs.constants.W_OK | fs.constants.R_OK`). Check `File access constants` for * possible values of `mode`. * * If the accessibility check is successful, the promise is resolved with no * value. If any of the accessibility checks fail, the promise is rejected * with an [Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) object. The following example checks if the file`/etc/passwd` can be read and * written by the current process. * * ```js * import { access } from 'fs/promises'; * import { constants } from 'fs'; * * try { * await access('/etc/passwd', constants.R_OK | constants.W_OK); * console.log('can access'); * } catch { * console.error('cannot access'); * } * ``` * * Using `fsPromises.access()` to check for the accessibility of a file before * calling `fsPromises.open()` is not recommended. Doing so introduces a race * condition, since other processes may change the file's state between the two * calls. Instead, user code should open/read/write the file directly and handle * the error raised if the file is not accessible. * @since v10.0.0 * @param [mode=fs.constants.F_OK] * @return Fulfills with `undefined` upon success. */ export function access(path: PathLike, mode?: number): Promise { return fs.access(path, mode).then( () => true, () => false ) } /** * `options` may also include a `start` option to allow writing data at some * position past the beginning of the file, allowed values are in the * \[0, [`Number.MAX_SAFE_INTEGER`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER)\] range. Modifying a file rather than * replacing it may require the `flags` option to be set to `r+` rather than the * default `w`. The `encoding` can be any one of those accepted by `Buffer`. * * If `autoClose` is set to true (default behavior) on `'error'` or `'finish'`the file descriptor will be closed automatically. If `autoClose` is false, * then the file descriptor won't be closed, even if there's an error. * It is the application's responsibility to close it and make sure there's no * file descriptor leak. * * By default, the stream will emit a `'close'` event after it has been * destroyed. Set the `emitClose` option to `false` to change this behavior. * * By providing the `fs` option it is possible to override the corresponding `fs`implementations for `open`, `write`, `writev` and `close`. Overriding `write()`without `writev()` can reduce * performance as some optimizations (`_writev()`) * will be disabled. When providing the `fs` option, overrides for at least one of`write` and `writev` are required. If no `fd` option is supplied, an override * for `open` is also required. If `autoClose` is `true`, an override for `close`is also required. * * Like `fs.ReadStream`, if `fd` is specified, `fs.WriteStream` will ignore the`path` argument and will use the specified file descriptor. This means that no`'open'` event will be * emitted. `fd` should be blocking; non-blocking `fd`s * should be passed to `net.Socket`. * * If `options` is a string, then it specifies the encoding. * @since v0.1.31 */ export const createWriteStream: typeof nodeFSCallBack.createWriteStream = nodeFSCallBack.createWriteStream /** * Unlike the 16 kb default `highWaterMark` for a `stream.Readable`, the stream * returned by this method has a default `highWaterMark` of 64 kb. * * `options` can include `start` and `end` values to read a range of bytes from * the file instead of the entire file. Both `start` and `end` are inclusive and * start counting at 0, allowed values are in the * \[0, [`Number.MAX_SAFE_INTEGER`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER)\] range. If `fd` is specified and `start` is * omitted or `undefined`, `fs.createReadStream()` reads sequentially from the * current file position. The `encoding` can be any one of those accepted by `Buffer`. * * If `fd` is specified, `ReadStream` will ignore the `path` argument and will use * the specified file descriptor. This means that no `'open'` event will be * emitted. `fd` should be blocking; non-blocking `fd`s should be passed to `net.Socket`. * * If `fd` points to a character device that only supports blocking reads * (such as keyboard or sound card), read operations do not finish until data is * available. This can prevent the process from exiting and the stream from * closing naturally. * * By default, the stream will emit a `'close'` event after it has been * destroyed. Set the `emitClose` option to `false` to change this behavior. * * By providing the `fs` option, it is possible to override the corresponding `fs`implementations for `open`, `read`, and `close`. When providing the `fs` option, * an override for `read` is required. If no `fd` is provided, an override for`open` is also required. If `autoClose` is `true`, an override for `close` is * also required. * * ```js * import { createReadStream } from 'fs'; * * // Create a stream from some character device. * const stream = createReadStream('/dev/input/event0'); * setTimeout(() => { * stream.close(); // This may not close the stream. * // Artificially marking end-of-stream, as if the underlying resource had * // indicated end-of-file by itself, allows the stream to close. * // This does not cancel pending read operations, and if there is such an * // operation, the process may still not be able to exit successfully * // until it finishes. * stream.push(null); * stream.read(0); * }, 100); * ``` * * If `autoClose` is false, then the file descriptor won't be closed, even if * there's an error. It is the application's responsibility to close it and make * sure there's no file descriptor leak. If `autoClose` is set to true (default * behavior), on `'error'` or `'end'` the file descriptor will be closed * automatically. * * `mode` sets the file mode (permission and sticky bits), but only if the * file was created. * * An example to read the last 10 bytes of a file which is 100 bytes long: * * ```js * import { createReadStream } from 'fs'; * * createReadStream('sample.txt', { start: 90, end: 99 }); * ``` * * If `options` is a string, then it specifies the encoding. * @since v0.1.31 */ export const createReadStream: typeof nodeFSCallBack.createReadStream = nodeFSCallBack.createReadStream export async function ReadEnvFileToPromise(p: string): PlataPromise> { const envFile = Object.create(null) const result = await ReadFileAsync(p, async line => { if (line[0] === '#') return if (line !== '') { const env = line.split('=') if (env.length >= 2) { const [ key, ...v ] = env let value = v.join('=') if (value[0] === '{' && value.slice(-1) === '}') value = process.env[value.slice(1, -1)] ?? '' ; if (value !== '') envFile[key] = value ; } } }) if (result !== null) { return result } return envFile as any } export const cp: PlataFunctionWrapperReturn> = (source, destination, opts) => { return nodeFS.cp(source, destination, opts).then( () => null, err => { return { errorID: 'PLFS001', msg: err.message, error: { message: err?.toString(), stack: err?.stack ?? new Error().stack } } } ) } export function ReadFileSync(file: string, callback: (line: string) => string): PlataPromise { return new Promise(resolve => { try { const content: string[] = [] const stream = readline.createInterface({ input: createReadStream(file), crlfDelay: Infinity }) stream.on('close', () => { resolve(content.join('\n')) }) stream.on('line', (line) => { content.push(callback(line)) }) } catch(err) { resolve({ errorID: 'PLTRF0004', msg: `Error while reading file: ${file}`, error: { message: err?.toString(), stack: err?.stack ?? new Error().stack } }) } }) } export function ReadFileAsync(file: string, callback: (line: string) => Promise): Promise { return new Promise(resolve => { try { const promises: any = [] const stream = readline.createInterface({ input: createReadStream(file), crlfDelay: Infinity }) stream.on('close', () => { Promise.all(promises).then( () => resolve(null), err => resolve({ errorID: 'PPFRFA0002', msg: `Unexpected error while reading file: ${file}`, error: { message: err?.toString(), stack: err?.stack ?? new Error().stack } }), ) }) stream.on('line', line => { promises.push(Plata.FastPromise(() => callback(line))) }) } catch (err) { return resolve({ errorID: 'PPFRFA0001', msg: `Unexpected error while reading file: ${file}`, error: { message: err?.toString(), stack: err?.stack ?? new Error().stack } }) } }) } export async function FindFiles(folders: string[], file: string): Promise { const files: string[] = [] for (const folder of folders) { files.push(...glob.sync(`${folder.replace(/\\/g, '/')}/**/${file}`)) } return files } export class CliTools { public readonly sourcePath: PathLike public readonly destPath: PathLike constructor(sourcePath: PathLike, destPath: PathLike) { this.destPath = destPath this.sourcePath = sourcePath } public async mkdirIfNotExists(dir: PathLike) { try { const p = path.resolve(dir.toString()) if (!(await access(p))) { await nodeFS.mkdir(p, { recursive: true }) } } catch (err) { console.error(err) process.exit(1) } } public async createFolderToDest(folder: string) { const destinationPath = path.join(this.destPath.toString(), folder) await this.mkdirIfNotExists(destinationPath) } public async forEachFileInFolder(folder: PathLike, callback: (file: string, filePath: PathLike) => Promise) { try { const folderPath = path.resolve(folder.toString()) const files = await nodeFS.readdir(folder) const promises: Promise[] = [] files.forEach(file => { const filePath = path.join(folderPath, file) promises.push( Promise.resolve().then(() => callback(file, filePath)) ) }) await Promise.all(promises) } catch (err) { console.error(err) process.exit(1) } } public async copyFile(sourcePath: PathLike, destinationPath: PathLike) { try { const s = path.resolve(sourcePath.toString()) const d = path.resolve(destinationPath.toString()) if (!(await access(d))) { await nodeFS.copyFile(s, d) } } catch (err) { console.error(err) process.exit(1) } } public async copyFileIfNotExists(sourcePath: PathLike, destinationPath: PathLike) { try { const s = path.resolve(sourcePath.toString()) const d = path.resolve(destinationPath.toString()) if (!(await access(d))) { await this.copyFile(s, d) } } catch (err) { console.error(err) process.exit(1) } } public async copyFolderToDest(folder: string) { const sourcePath = path.join(this.sourcePath.toString(), folder) const destinationPath = path.join(this.destPath.toString(), folder) await this.mkdirIfNotExists(destinationPath) await this.forEachFileInFolder(sourcePath, (file, filePath) => this.copyFileIfNotExists( filePath, path.join(destinationPath, file) ) ) } public async syncFolder(folder: string) { const sourcePath = path.join(this.sourcePath.toString(), folder) const destinationPath = path.join(this.destPath.toString(), folder) await this.mkdirIfNotExists(destinationPath) await this.forEachFileInFolder(sourcePath, (file, filePath) => this.copyFile( filePath, path.join(destinationPath, file) ) ) } public async copyFileToDest(file: string) { const sourcePath = path.join(this.sourcePath.toString(), file) const destinationPath = path.join(this.destPath.toString(), file) await this.copyFileIfNotExists(sourcePath, destinationPath) } public async syncFileToDest(file: string) { const sourcePath = path.join(this.sourcePath.toString(), file) const destinationPath = path.join(this.destPath.toString(), file) await this.copyFile(sourcePath, destinationPath) } public async createFileToDest(file: string, content: Parameters[1]) { const destinationPath = path.join(this.destPath.toString(), file) await nodeFS.writeFile(destinationPath, content) } } }