parent
98f11eca8f
commit
d0a63a7acf
@ -0,0 +1,62 @@ |
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import { Command } from "commander"; |
||||||
|
// NOTE: this changed in node 18 to be node:url for...stupid
|
||||||
|
import { fileURLToPath } from "url"; |
||||||
|
import { glob } from "./lib/builderator.js"; |
||||||
|
import path from "path"; |
||||||
|
import assert from "assert"; |
||||||
|
import fs from "fs"; |
||||||
|
|
||||||
|
// BUG: this will load the commands but most of them assume the root
|
||||||
|
const __filename = fileURLToPath(import.meta.url); |
||||||
|
const { dir } = path.parse(__filename); |
||||||
|
let __dirname = dir; |
||||||
|
|
||||||
|
// now even exec commands will know where the project root is
|
||||||
|
process.env['PROJECT_ROOT'] = __dirname; |
||||||
|
const program = new Command(); |
||||||
|
// remember that glob doesn't like windows \
|
||||||
|
const command_dir = `${__dirname}/commands`; |
||||||
|
|
||||||
|
if(!fs.existsSync(command_dir)) { |
||||||
|
console.error("ERROR: there is no command directory at", command_dir); |
||||||
|
process.exit(1); |
||||||
|
} |
||||||
|
|
||||||
|
program |
||||||
|
.name("bando") |
||||||
|
.description("Command runner for the bando project.") |
||||||
|
.version("0.1.0"); |
||||||
|
|
||||||
|
for(let command of glob(`${command_dir}/*.js`)) { |
||||||
|
const { name } = path.parse(command); |
||||||
|
const mod = await import(command); |
||||||
|
|
||||||
|
assert(mod.description, `export const description missing in ${command}`); |
||||||
|
assert(mod.main, `export const main missing in ${command}`); |
||||||
|
|
||||||
|
const build = program.command(name) |
||||||
|
.description(mod.description) |
||||||
|
.action(mod.main); |
||||||
|
|
||||||
|
if(mod.options) { |
||||||
|
mod.options.forEach(opt => build.option(...opt)); |
||||||
|
} |
||||||
|
|
||||||
|
if(mod.required) { |
||||||
|
mod.required.forEach(opt => build.requiredOption(...opt)); |
||||||
|
} |
||||||
|
|
||||||
|
if(mod.argument) { |
||||||
|
build.argument(...mod.argument); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
program.parse(); |
||||||
|
} catch(error) { |
||||||
|
console.log("------------- ERROR, here's the stack trace."); |
||||||
|
console.error(error.stack); |
||||||
|
process.exit(1); |
||||||
|
} |
@ -1,5 +0,0 @@ |
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
import { writeFile } from "node:fs/promises"; |
|
||||||
|
|
||||||
await writeFile("test.txt", "whatever"); |
|
@ -0,0 +1,99 @@ |
|||||||
|
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"; |
||||||
|
|
||||||
|
// fast-glob is old and can't do import as
|
||||||
|
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); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
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; |
||||||
|
} |
||||||
|
|
||||||
|
export const mkdir = (dir) => { |
||||||
|
/* Makes a directory recursively. */ |
||||||
|
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. |
||||||
|
* |
||||||
|
* @param { string } -- path to file |
||||||
|
*/ |
||||||
|
export const mkdir_to = (target) => { |
||||||
|
let dirs = Path.parse(target); |
||||||
|
mkdir(dirs.dir); |
||||||
|
} |
||||||
|
|
||||||
|
export const write = (target, data) => { |
||||||
|
/* Convenience function that makes sure the dir is there then writes the data. */ |
||||||
|
mkdir_to(target); |
||||||
|
fs.writeFileSync(target, data); |
||||||
|
} |
||||||
|
|
||||||
|
export const copy = (src, dest, filter=null) => { |
||||||
|
/** 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. |
||||||
|
*/ |
||||||
|
mkdir_to(dest); |
||||||
|
|
||||||
|
if(filter) { |
||||||
|
try { |
||||||
|
let raw_data = fs.readFileSync(src); |
||||||
|
let data = filter(src, dest, raw_data); |
||||||
|
write(dest, data); |
||||||
|
} catch (error) { |
||||||
|
log.error(error, "Problem with filter write in copy"); |
||||||
|
} |
||||||
|
} else { |
||||||
|
fs.copyFileSync(src, dest); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
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; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export const exec = (cmd, opts) => { |
||||||
|
log.info(`EXEC ${cmd}`); |
||||||
|
return execSync(cmd, opts); |
||||||
|
} |
||||||
|
|
||||||
|
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 |
||||||
|
} |
@ -0,0 +1,32 @@ |
|||||||
|
import pino from 'pino'; |
||||||
|
import pinoPretty from 'pino-pretty'; |
||||||
|
import path from 'path'; |
||||||
|
|
||||||
|
const log_level = process.env.PROD === undefined ? "debug" : "info"; |
||||||
|
|
||||||
|
const pino_config = { |
||||||
|
level: log_level, |
||||||
|
} |
||||||
|
|
||||||
|
if(!process.env.PROD) { |
||||||
|
pino_config.transport = { |
||||||
|
target: 'pino-pretty', |
||||||
|
options: { |
||||||
|
levelFirst: true, |
||||||
|
colorize: true, |
||||||
|
singleLine: true, |
||||||
|
ignore: "module", |
||||||
|
messageFormat: "{levelLabel}[{pid}] {module}: {msg}" |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export const log = pino(pino_config); |
||||||
|
|
||||||
|
export const create = (import_url) => { |
||||||
|
const pd = path.parse(import_url); |
||||||
|
const log_line = `${path.basename(pd.dir)}/${pd.base}`; |
||||||
|
return log.child({module: log_line}); |
||||||
|
} |
||||||
|
|
||||||
|
export default { create, log }; |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,6 @@ |
|||||||
|
import test from "ava"; |
||||||
|
|
||||||
|
|
||||||
|
test("base test setup", (t) => { |
||||||
|
t.pass(); |
||||||
|
}); |
Loading…
Reference in new issue