You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
166 lines
4.7 KiB
166 lines
4.7 KiB
/*
|
|
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. I 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
|
|
}
|
|
|