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