@james-pre/config 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,25 @@
1
+ export declare function setCommandTimeout(value: number): void;
2
+ /**
3
+ * Run a system command with fancy I/O like "Example... done."
4
+ * @deprecated Use {@link trackCommand} instead, this function is unsafe.
5
+ */
6
+ export declare function runShell(message: string, command: string): Promise<string>;
7
+ export interface TrackCommandOptions {
8
+ text: string;
9
+ /** Whether to ignore the command's exit code (or which codes to ignore) */
10
+ ignoreCode?: boolean | number[];
11
+ }
12
+ /**
13
+ * Run a system command with fancy I/O like "Example... done."
14
+ */
15
+ export declare function trackCommand(message: string, command: string, ...args: string[]): string;
16
+ export declare function trackCommand(options: TrackCommandOptions, command: string, ...args: string[]): string;
17
+ export {
18
+ /**
19
+ * @deprecated use {@link runShell}
20
+ */
21
+ runShell as run, };
22
+ /** Yet another convenience function */
23
+ export declare function exit(message: unknown, code?: number): never;
24
+ /** Use a different working directory for the rest of a block */
25
+ export declare function withCWD(path: string): string & Disposable;
@@ -0,0 +1,78 @@
1
+ // SPDX-License-Identifier: LGPL-3.0-or-later
2
+ /* eslint-disable @typescript-eslint/only-throw-error */
3
+ import { exec, spawnSync } from 'node:child_process';
4
+ import * as io from '../core.js';
5
+ let timeout = 1000;
6
+ export function setCommandTimeout(value) {
7
+ timeout = value;
8
+ }
9
+ /**
10
+ * Run a system command with fancy I/O like "Example... done."
11
+ * @deprecated Use {@link trackCommand} instead, this function is unsafe.
12
+ */
13
+ export async function runShell(message, command) {
14
+ let stderr;
15
+ try {
16
+ io.start(message);
17
+ const { promise, resolve, reject } = Promise.withResolvers();
18
+ exec(command, { timeout }, (err, stdout, _stderr) => {
19
+ stderr = _stderr.startsWith('ERROR:') ? _stderr.slice(6).trim() : _stderr;
20
+ if (err)
21
+ reject('[command]');
22
+ else
23
+ resolve(stdout);
24
+ });
25
+ const value = await promise;
26
+ io.done();
27
+ return value;
28
+ }
29
+ catch (error) {
30
+ io.done(true);
31
+ throw error == '[command]'
32
+ ? stderr?.slice(0, 100) || 'failed.'
33
+ : typeof error == 'object' && 'message' in error
34
+ ? error.message
35
+ : error;
36
+ }
37
+ }
38
+ export function trackCommand(message, command, ...args) {
39
+ const { text, ignoreCode } = typeof message === 'string' ? { text: message, ignoreCode: false } : message;
40
+ io.start(text);
41
+ const result = spawnSync(command, args, { encoding: 'utf-8', timeout });
42
+ if (result.error) {
43
+ io.done(true);
44
+ throw result.error;
45
+ }
46
+ const stderr = result.stderr.startsWith('ERROR:') ? result.stderr.slice(6).trim() : result.stderr.trim();
47
+ if (result.status && (!ignoreCode || (Array.isArray(ignoreCode) && !ignoreCode.includes(result.status)))) {
48
+ io.done(true);
49
+ throw stderr.slice(0, 100) || 'failed.';
50
+ }
51
+ io.done();
52
+ return result.stdout;
53
+ }
54
+ export {
55
+ /**
56
+ * @deprecated use {@link runShell}
57
+ */
58
+ runShell as run, };
59
+ /** Yet another convenience function */
60
+ export function exit(message, code = 1) {
61
+ if (typeof message == 'number') {
62
+ code = message;
63
+ message = 'Unknown error!';
64
+ }
65
+ io.done(true);
66
+ io.error(io.errorText(message));
67
+ process.exit(code);
68
+ }
69
+ /** Use a different working directory for the rest of a block */
70
+ export function withCWD(path) {
71
+ const original = process.cwd();
72
+ process.chdir(path);
73
+ return Object.assign(original, {
74
+ [Symbol.dispose]() {
75
+ process.chdir(original);
76
+ },
77
+ });
78
+ }
@@ -0,0 +1,13 @@
1
+ import { type Interface } from 'node:readline/promises';
2
+ /**
3
+ * Get a readline reference. While held, this means process.stdin is ref'ed if it's a TTY.
4
+ */
5
+ export declare function getReadline(): Interface;
6
+ /**
7
+ * Confirm a yes/no question
8
+ */
9
+ export declare function confirm(question?: string | null, defaultAnswer?: boolean): Promise<boolean>;
10
+ /**
11
+ * Assert that the answer to a question is yes, otherwise fail and exit
12
+ */
13
+ export declare function assertYes(question?: string | null, failureMessage?: string): Promise<void>;
@@ -0,0 +1,97 @@
1
+ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
2
+ if (value !== null && value !== void 0) {
3
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
4
+ var dispose, inner;
5
+ if (async) {
6
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
7
+ dispose = value[Symbol.asyncDispose];
8
+ }
9
+ if (dispose === void 0) {
10
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
11
+ dispose = value[Symbol.dispose];
12
+ if (async) inner = dispose;
13
+ }
14
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
15
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
16
+ env.stack.push({ value: value, dispose: dispose, async: async });
17
+ }
18
+ else if (async) {
19
+ env.stack.push({ async: true });
20
+ }
21
+ return value;
22
+ };
23
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
24
+ return function (env) {
25
+ function fail(e) {
26
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
27
+ env.hasError = true;
28
+ }
29
+ var r, s = 0;
30
+ function next() {
31
+ while (r = env.stack.pop()) {
32
+ try {
33
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
34
+ if (r.dispose) {
35
+ var result = r.dispose.call(r.value);
36
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
37
+ }
38
+ else s |= 1;
39
+ }
40
+ catch (e) {
41
+ fail(e);
42
+ }
43
+ }
44
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
45
+ if (env.hasError) throw env.error;
46
+ }
47
+ return next();
48
+ };
49
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
50
+ var e = new Error(message);
51
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
+ });
53
+ import { createInterface } from 'node:readline/promises';
54
+ import * as z from 'zod';
55
+ import { exit } from './process.js';
56
+ const _rl = createInterface({
57
+ input: process.stdin,
58
+ output: process.stdout,
59
+ });
60
+ _rl[Symbol.dispose] = () => process.stdin.unref?.();
61
+ process.stdin.unref?.();
62
+ /**
63
+ * Get a readline reference. While held, this means process.stdin is ref'ed if it's a TTY.
64
+ */
65
+ export function getReadline() {
66
+ process.stdin.ref?.();
67
+ return _rl;
68
+ }
69
+ /**
70
+ * Confirm a yes/no question
71
+ */
72
+ export async function confirm(question, defaultAnswer = false) {
73
+ const env_1 = { stack: [], error: void 0, hasError: false };
74
+ try {
75
+ const rl = __addDisposableResource(env_1, getReadline(), false);
76
+ const raw = await rl.question(`${question || 'Is this ok'} [${defaultAnswer ? 'Y/n' : 'y/N'}]: `).catch(() => null);
77
+ if (raw === null || raw === '')
78
+ return defaultAnswer;
79
+ const { data, error } = z.stringbool().default(defaultAnswer).safeParse(raw);
80
+ return !!error || data;
81
+ }
82
+ catch (e_1) {
83
+ env_1.error = e_1;
84
+ env_1.hasError = true;
85
+ }
86
+ finally {
87
+ __disposeResources(env_1);
88
+ }
89
+ }
90
+ /**
91
+ * Assert that the answer to a question is yes, otherwise fail and exit
92
+ */
93
+ export async function assertYes(question, failureMessage = 'Aborted.') {
94
+ const yes = await confirm(question);
95
+ if (!yes)
96
+ exit(failureMessage);
97
+ }
@@ -0,0 +1,26 @@
1
+ export interface TableColumn<T> {
2
+ name?: string;
3
+ size?: number;
4
+ text(row: T): unknown;
5
+ length?(row: T): number;
6
+ /** @deprecated Include the formatting in `text` instead */
7
+ format?(text: string, row: T): string;
8
+ /** If set pad the start instead of the end */
9
+ padStart?: boolean;
10
+ /** Growth factor when there is extra space */
11
+ grow?: number;
12
+ }
13
+ export interface TableOptions {
14
+ /** If specified, indent by this about of spaces before each row */
15
+ indent?: number;
16
+ /** Customize the header formatting */
17
+ formatHead?(text: string): string;
18
+ /** If set do not output anything. Useful for when you want interleaved output */
19
+ noOutput?: boolean;
20
+ }
21
+ export declare function setTableTargetWidth(width: number): void;
22
+ export declare function setANSIStripper(fn: (text: string) => string): void;
23
+ /**
24
+ * Output a table, or prepare to output a table
25
+ */
26
+ export declare function table<T>(columns: TableColumn<T>[], options?: TableOptions, data?: T[]): (row: T) => void;
package/dist/table.js ADDED
@@ -0,0 +1,65 @@
1
+ let targetWidth;
2
+ export function setTableTargetWidth(width) {
3
+ targetWidth = width;
4
+ }
5
+ /**
6
+ * Matches CSI/OSC sequences: an introducer (ESC or 8-bit CSI) followed by either
7
+ * an OSC-style payload terminated by BEL, or parameter bytes plus a final byte.
8
+ */
9
+ const ansiRegex =
10
+ // eslint-disable-next-line no-control-regex
11
+ /[\u001B\u009B][[\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\d/#&.:=?%@~_]+)*|[a-zA-Z\d]+(?:;[-a-zA-Z\d/#&.:=?%@~_]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))/g;
12
+ let stripANSI = (text) => text.replaceAll(ansiRegex, '');
13
+ export function setANSIStripper(fn) {
14
+ stripANSI = fn;
15
+ }
16
+ /**
17
+ * Output a table, or prepare to output a table
18
+ */
19
+ export function table(columns, options = {}, data = []) {
20
+ const nColumns = columns.length;
21
+ for (const col of columns) {
22
+ let max = Math.max(col.size || 0, col.name?.length || 0);
23
+ for (const row of data)
24
+ max = Math.max(max, col.length ? col.length(row) : stripANSI(String(col.text(row))).length);
25
+ col.size = max;
26
+ }
27
+ let currentWidth = columns.reduce((sum, col) => sum + col.size, nColumns - 1);
28
+ const canGrow = columns.some(col => !!(col.grow ?? 1));
29
+ while (canGrow && targetWidth && currentWidth < targetWidth) {
30
+ for (const col of columns) {
31
+ const extra = Math.max(0, Math.min(targetWidth - currentWidth, col.grow ?? 1));
32
+ col.size += extra;
33
+ currentWidth += extra;
34
+ }
35
+ }
36
+ function output(row) {
37
+ const out = [];
38
+ for (const col of columns) {
39
+ const text = String(col.text(row));
40
+ const padding = ' '.repeat(Math.max(0, col.size - stripANSI(text).length));
41
+ let formatted = col.padStart ? padding + text : text + padding;
42
+ if (col.format)
43
+ formatted = col.format(formatted, row);
44
+ out.push(formatted);
45
+ }
46
+ console.log(...out);
47
+ }
48
+ function outputHead() {
49
+ const out = [];
50
+ for (const col of columns) {
51
+ const text = String(col.name ?? '');
52
+ let formatted = col.padStart ? text.padStart(col.size) : text.padEnd(col.size);
53
+ if (options.formatHead)
54
+ formatted = options.formatHead(formatted);
55
+ out.push(formatted);
56
+ }
57
+ console.log(...out);
58
+ }
59
+ if (!options.noOutput) {
60
+ outputHead();
61
+ for (const row of data)
62
+ output(row);
63
+ }
64
+ return output;
65
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Track the execution of a thunk with appropriate I/O handling.
3
+ * Errors thrown by the thunk are re-thrown, if you want them logged see {@link tryTrack}.
4
+ */
5
+ export declare function track<T>(label: string, thunk: () => T): T;
6
+ /**
7
+ * Track the settlement of a promise with appropriate I/O handling.
8
+ * Rejections are re-thrown, if you want them logged see {@link tryTrack}.
9
+ */
10
+ export declare function track<T>(label: string, promise: Promise<T>): Promise<T>;
11
+ /**
12
+ * Track the execution of a thunk with appropriate I/O handling.
13
+ * Errors thrown by the thunk are logged, if you want them rethrown see {@link track}.
14
+ */
15
+ export declare function tryTrack<T>(label: string, thunk: () => T): T | null;
16
+ /**
17
+ * Track the settlement of a promise with appropriate I/O handling.
18
+ * Rejections are logged, if you want them thrown see {@link track}.
19
+ */
20
+ export declare function tryTrack<T>(label: string, promise: Promise<T>): Promise<T | null>;
@@ -0,0 +1,48 @@
1
+ // SPDX-License-Identifier: LGPL-3.0-or-later
2
+ import { done, error, errorText, start } from './core.js';
3
+ export function track(label, value) {
4
+ start(label);
5
+ if (typeof value == 'function')
6
+ try {
7
+ const result = value();
8
+ done();
9
+ return result;
10
+ }
11
+ catch (e) {
12
+ done(true);
13
+ throw e;
14
+ }
15
+ return value
16
+ .then(v => {
17
+ done();
18
+ return v;
19
+ })
20
+ .catch(e => {
21
+ done(true);
22
+ throw e;
23
+ });
24
+ }
25
+ export function tryTrack(label, value) {
26
+ start(label);
27
+ if (typeof value == 'function')
28
+ try {
29
+ const result = value();
30
+ done();
31
+ return result;
32
+ }
33
+ catch (e) {
34
+ done(true);
35
+ error(errorText(e));
36
+ return null;
37
+ }
38
+ return value
39
+ .then(v => {
40
+ done();
41
+ return v;
42
+ })
43
+ .catch(e => {
44
+ done(true);
45
+ error(errorText(e));
46
+ return null;
47
+ });
48
+ }
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@james-pre/config",
3
+ "version": "0.0.1",
4
+ "description": "Manage configuration files",
5
+ "funding": {
6
+ "type": "individual",
7
+ "url": "https://github.com/sponsors/james-pre"
8
+ },
9
+ "main": "dist/index.js",
10
+ "types": "dist/index.d.ts",
11
+ "type": "module",
12
+ "exports": {
13
+ ".": "./dist/index.js",
14
+ "./*": "./dist/*.js"
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "format:check": "prettier --check .",
21
+ "format": "prettier --write .",
22
+ "build": "tsc",
23
+ "lint": "tsc --noEmit && eslint src",
24
+ "prepublishOnly": "npx tsc"
25
+ },
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/james-pre/@james-pre/config.git"
29
+ },
30
+ "author": "James Prevett <jp@jamespre.dev> (https://jamespre.dev)",
31
+ "license": "LGPL-3.0-or-later",
32
+ "bugs": {
33
+ "url": "https://github.com/james-pre/@james-pre/config/issues"
34
+ },
35
+ "homepage": "https://github.com/james-pre/@james-pre/config#readme",
36
+ "engines": {
37
+ "node": ">=22.0.0"
38
+ },
39
+ "peerDependencies": {
40
+ "utilium": "^3.7.0",
41
+ "zod": "^4.5.0"
42
+ },
43
+ "publishConfig": {
44
+ "provenance": true,
45
+ "access": "public"
46
+ },
47
+ "devDependencies": {
48
+ "@eslint/js": "^10.0.1",
49
+ "@types/node": "^24.7.0",
50
+ "eslint": "^10.1.0",
51
+ "globals": "^17.4.0",
52
+ "prettier": "^3.2.5",
53
+ "typedoc": "^0.28.18",
54
+ "typescript": "^6.0.2",
55
+ "typescript-eslint": "^8.58.0"
56
+ },
57
+ "prettier": {
58
+ "singleQuote": true,
59
+ "useTabs": true,
60
+ "trailingComma": "es5",
61
+ "tabWidth": 4,
62
+ "printWidth": 120,
63
+ "arrowParens": "avoid",
64
+ "experimentalOperatorPosition": "start"
65
+ }
66
+ }