/* Useful system level functions for use in build commands. It's mostly things like detecting if two files changed, doing globs on Windows and Unix, and various file operations that you need. */ import fg from "fast-glob"; import fs from "fs"; import assert from "assert"; import { log } from "./logging.js"; import Path from "path"; import { execSync } from "child_process"; /* Fixes some common problems with `fast-glob` on windows. It removes leading "C:\" from paths and replaces all of the `\\` with `/`. __BUG__: This obviously won't work if you're on a different drive than C:. + `path string` -- The path/glob pattern to use. */ export const glob = (path) => { if(process.platform === "win32") { // fast-glob doesn't understand \\ or C:\ const fixed_path = path.replace("C:","").replaceAll('\\', '/') return fg.sync(fixed_path); } else { return fg.sync(path); } } /* Detects if two files have changed. This is used to avoid writing or working on files that don't need it, similar to the Unix make utility. ___FOOTGUN___: It uses the stat.mtimeMs which may or may not be the most reliable way to detect differences. + `source string` -- The source path. + `target string` -- The target path. */ export const changed = (source, target) => { // we want the source to error if it doesn't exist const s_stat = fs.statSync(source); // but target might not exist, which would mean it's a change const t_stat = fs.statSync(target, {throwIfNoEntry: false}); return !t_stat || s_stat.mtimeMs > t_stat.mtimeMs; } /* Makes a directory recursively, kind of like `mkdir -p`. + `dir string` -- The full path to the final directory to create. */ export const mkdir = (dir) => { if(!fs.existsSync(dir)) { log.debug(`making dir ${dir}`); fs.mkdirSync(dir, { recursive: true }); } } /* Given any path to a file, makes sure all its directories are in place. This will only the directory to the file, and then calls `mkdir` on it to create the path. - `param string` -- path to file */ export const mkdir_to = (target) => { let dirs = Path.parse(target); mkdir(dirs.dir); } /* Convenience function that makes sure the dir is there then writes the data. It's a combination of `mkdir_to` and `fs.writeFileSync` and exists because these two operations are so common. + `target string` -- Path to the file to write. + `data string|buffer|etc.` -- Anything that `fs.writeFileSync` can take as file contents. */ export const write = (target, data) => { mkdir_to(target); fs.writeFileSync(target, data); } /* Like write but does an OS copy after making the target dir. When given a filter it will give the filter function the arguments, let it return contents, then write those instead of a copy. This will create the target file's path with `mkdir_to`. + `src string` -- Source file path. + `dest string` -- Destination path. + `filter cb(src, dest, raw_data)` -- A function that can modify the data before it's written. */ export const copy = (src, dest, filter=null) => { mkdir_to(dest); if(filter) { try { let raw_data = fs.readFileSync(src); let data = filter(src, dest, raw_data); fs.writeFileSync(dest, data); } catch (error) { log.error(error, "Problem with filter write in copy"); } } else { fs.copyFileSync(src, dest); } } /* Removes a file, optionally ignoring any errors. + `file_name string` -- The path to the file to remove. + `ignore boolean (false)` -- Whether to ignore errors removing the file. */ export const rm = (file_name, ignore=false) => { log.warn("Deleting file", file_name); try { fs.unlinkSync(file_name); } catch(error) { if(ignore) { log.debug(`Requested delete file ${file_name} doesn't exist, but ignored.`); } else { log.error(error, file_name); throw error; } } } /* Executes a command, and mostly just logs the command then run execSync with the same options. + `cmd string` -- Command to run. + `opts Object` -- Options for `execSync`. */ export const exec = (cmd, opts) => { log.info(`EXEC ${cmd}`); return execSync(cmd, opts); } /* Similar to `exec` but it is _interactive_ because it reroutes this process's stderr, stdout, and stdin to the process. If you find you can't ctrl-c then use this. The options set are: ``` {stdio: [process.stdout, process.stderr, process.stdin]} ``` + `cmd string` -- The command to run. + `opts Object` -- Additional options to set for the exec command. */ export const exec_i = (cmd, opts={}) => { return exec(cmd, {stdio: [process.stdout, process.stderr, process.stdin], ...opts}); } export default { rm, exec, changed, mkdir, mkdir_to, write, copy, exec_i, glob }