yamlite 0.0.0-stage → 0.35.0
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.
- package/LICENSE +21 -0
- package/README.md +746 -2
- package/dist/browser/aliases.d.mts +6 -0
- package/dist/browser/aliases.mjs +42 -0
- package/dist/browser/chokidar.d.mts +17 -0
- package/dist/browser/chokidar.mjs +64 -0
- package/dist/browser/crypto.d.mts +11 -0
- package/dist/browser/crypto.mjs +27 -0
- package/dist/browser/expose.d.mts +3 -0
- package/dist/browser/expose.mjs +80 -0
- package/dist/browser/fs.d.mts +85 -0
- package/dist/browser/fs.mjs +1 -0
- package/dist/browser/globals.d.mts +1 -0
- package/dist/browser/globals.mjs +21 -0
- package/dist/browser/os.d.mts +9 -0
- package/dist/browser/os.mjs +9 -0
- package/dist/browser/path.d.mts +28 -0
- package/dist/browser/path.mjs +15 -0
- package/dist/browser/sqlite.d.mts +48 -0
- package/dist/browser/sqlite.mjs +219 -0
- package/dist/browser/unavailable.d.mts +15 -0
- package/dist/browser/unavailable.mjs +21 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +433 -0
- package/dist/core.d.mts +527 -0
- package/dist/core.mjs +3 -0
- package/dist/fs-DZ4QHM93.mjs +251 -0
- package/dist/guide.md +171 -0
- package/dist/index-BBHZ8yok.d.mts +316 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +1 -0
- package/dist/src-DKu7thkC.mjs +8030 -0
- package/dist/ui/assets/ErdView-Bpo3_oCp.js +8 -0
- package/dist/ui/assets/ErdView-DLioOiRN.css +1 -0
- package/dist/ui/assets/MarkdownField-CRtDmSzB.js +32 -0
- package/dist/ui/assets/PageView-6iig-DcM.js +1 -0
- package/dist/ui/assets/index-BIkG4xcY.css +2 -0
- package/dist/ui/assets/index-CqIdjmI5.js +125 -0
- package/dist/ui/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/dist/ui/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/dist/ui/assets/jetbrains-mono-latin-700-normal-BYuf6tUa.woff2 +0 -0
- package/dist/ui/assets/jetbrains-mono-latin-700-normal-D3wTyLJW.woff +0 -0
- package/dist/ui/assets/load-kYvvjpD1.js +2 -0
- package/dist/ui/assets/outfit-latin-400-normal-BGsTXAXT.woff2 +0 -0
- package/dist/ui/assets/outfit-latin-400-normal-DMwTpYkH.woff +0 -0
- package/dist/ui/assets/outfit-latin-500-normal-ClnHRwRh.woff +0 -0
- package/dist/ui/assets/outfit-latin-500-normal-DKnIMDSk.woff2 +0 -0
- package/dist/ui/assets/outfit-latin-600-normal-B7SfZ07L.woff2 +0 -0
- package/dist/ui/assets/outfit-latin-600-normal-BEfTtDA7.woff +0 -0
- package/dist/ui/assets/outfit-latin-700-normal-Cu9v6i1X.woff2 +0 -0
- package/dist/ui/assets/outfit-latin-700-normal-D4itBLBr.woff +0 -0
- package/dist/ui/assets/rolldown-runtime-XKyaHtl9.js +1 -0
- package/dist/ui/assets/sql-wasm-C1D_RJuy.wasm +0 -0
- package/dist/ui/assets/sql-wasm-Dnr4gnQb.js +1 -0
- package/dist/ui/assets/sql-wasm-browser-CNwHQfY0.js +2 -0
- package/dist/ui/assets/theme-CMaLONsO.js +1 -0
- package/dist/ui/favicon.svg +6 -0
- package/dist/ui/index.html +26 -0
- package/dist/ui-embed/ErdView-Dk-iexgy.js +10362 -0
- package/dist/ui-embed/HintPopover-D5P2UuTa.js +8257 -0
- package/dist/ui-embed/MarkdownField-DTAEh2oz.js +5211 -0
- package/dist/ui-embed/PageView-Cpq5JtTW.js +818 -0
- package/dist/ui-embed/activity-letLdXN4.js +190 -0
- package/dist/ui-embed/dist-CkcIwL4M.js +8142 -0
- package/dist/ui-embed/embed.js +18684 -0
- package/dist/ui-embed/favicon.svg +6 -0
- package/dist/ui-embed/page-frame.js +7 -0
- package/dist/ui-embed/pageFrame-Dm62WvvT.js +2556 -0
- package/dist/ui-embed/style.css +3 -0
- package/dist/ui-embed/tabs-CLBiDAgo.js +17095 -0
- package/dist/ui-embed/theme-BwJfOY1V.js +2951 -0
- package/dist/workspace-BHg_GGkH.mjs +896 -0
- package/package.json +171 -3
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import sqlite3InitModule from "@sqlite.org/sqlite-wasm";
|
|
2
|
+
//#region src/browser/sqlite.ts
|
|
3
|
+
const sqlite3 = await sqlite3InitModule();
|
|
4
|
+
const opfs = typeof globalThis.FileSystemHandle !== "undefined" ? await sqlite3.installOpfsSAHPoolVfs({ name: "yamlite" }) : null;
|
|
5
|
+
let VFS = opfs?.vfsName;
|
|
6
|
+
function registerVfs(name) {
|
|
7
|
+
VFS = name;
|
|
8
|
+
}
|
|
9
|
+
const { capi, oo1 } = sqlite3;
|
|
10
|
+
const constants = Object.fromEntries(Object.entries(capi).filter(([k, v]) => k.startsWith("SQLITE_") && typeof v === "number"));
|
|
11
|
+
var SqliteError = class extends Error {
|
|
12
|
+
errcode;
|
|
13
|
+
errstr;
|
|
14
|
+
code = "ERR_SQLITE_ERROR";
|
|
15
|
+
constructor(message, errcode, errstr) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.errcode = errcode;
|
|
18
|
+
this.errstr = errstr;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
function sqliteError(db, e) {
|
|
22
|
+
if (e instanceof SqliteError || !(e instanceof Error) || !("resultCode" in e)) return e;
|
|
23
|
+
const ptr = db.pointer;
|
|
24
|
+
const code = ptr ? capi.sqlite3_extended_errcode(ptr) : Number(e.resultCode);
|
|
25
|
+
return new SqliteError(ptr ? capi.sqlite3_errmsg(ptr) : e.message, code, capi.sqlite3_errstr(code));
|
|
26
|
+
}
|
|
27
|
+
const isNamed = (v) => typeof v === "object" && v !== null && !(v instanceof Uint8Array) && !ArrayBuffer.isView(v);
|
|
28
|
+
const finalizer = new FinalizationRegistry((fn) => fn());
|
|
29
|
+
function finalize(stmt) {
|
|
30
|
+
try {
|
|
31
|
+
if (stmt.pointer) stmt.finalize();
|
|
32
|
+
} catch {}
|
|
33
|
+
}
|
|
34
|
+
const finalized = () => Object.assign(/* @__PURE__ */ new Error("statement has been finalized"), { code: "ERR_INVALID_STATE" });
|
|
35
|
+
var StatementSync = class {
|
|
36
|
+
db;
|
|
37
|
+
stmt;
|
|
38
|
+
bigInts = false;
|
|
39
|
+
arrays = false;
|
|
40
|
+
constructor(db, stmt, open) {
|
|
41
|
+
this.db = db;
|
|
42
|
+
this.stmt = stmt;
|
|
43
|
+
open.add(stmt);
|
|
44
|
+
finalizer.register(this, () => {
|
|
45
|
+
finalize(stmt);
|
|
46
|
+
open.delete(stmt);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
setReadBigInts(on) {
|
|
50
|
+
this.bigInts = on;
|
|
51
|
+
}
|
|
52
|
+
setReturnArrays(on) {
|
|
53
|
+
this.arrays = on;
|
|
54
|
+
}
|
|
55
|
+
columns() {
|
|
56
|
+
const p = this.stmt.pointer;
|
|
57
|
+
if (!p) throw finalized();
|
|
58
|
+
const n = capi.sqlite3_column_count(p);
|
|
59
|
+
return Array.from({ length: n }, (_, i) => Object.assign(Object.create(null), {
|
|
60
|
+
column: null,
|
|
61
|
+
database: null,
|
|
62
|
+
name: capi.sqlite3_column_name(p, i),
|
|
63
|
+
table: null,
|
|
64
|
+
type: capi.sqlite3_column_decltype(p, i) ?? null
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
bind(params) {
|
|
68
|
+
const s = this.stmt;
|
|
69
|
+
const p = s.pointer;
|
|
70
|
+
if (!p) throw finalized();
|
|
71
|
+
s.reset();
|
|
72
|
+
s.clearBindings();
|
|
73
|
+
const count = capi.sqlite3_bind_parameter_count(p);
|
|
74
|
+
let named;
|
|
75
|
+
let rest = params;
|
|
76
|
+
if (isNamed(params[0])) {
|
|
77
|
+
named = params[0];
|
|
78
|
+
rest = params.slice(1);
|
|
79
|
+
}
|
|
80
|
+
let next = 0;
|
|
81
|
+
for (let i = 1; i <= count; i++) {
|
|
82
|
+
const name = capi.sqlite3_bind_parameter_name(p, i);
|
|
83
|
+
let v;
|
|
84
|
+
if (name && named) v = name in named ? named[name] : named[name.slice(1)];
|
|
85
|
+
else if (!name || /^\?\d*$/.test(name)) v = rest[name && name.length > 1 ? Number(name.slice(1)) - 1 : next++];
|
|
86
|
+
else continue;
|
|
87
|
+
if (v === void 0) continue;
|
|
88
|
+
if (v === null) capi.sqlite3_bind_null(p, i);
|
|
89
|
+
else if (typeof v === "number") capi.sqlite3_bind_double(p, i, v);
|
|
90
|
+
else if (typeof v === "bigint") capi.sqlite3_bind_int64(p, i, v);
|
|
91
|
+
else if (typeof v === "string" || v instanceof Uint8Array) s.bind(i, v);
|
|
92
|
+
else throw new TypeError(`Provided value cannot be bound to SQLite parameter ${i}.`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
row() {
|
|
96
|
+
const p = this.stmt.pointer;
|
|
97
|
+
const n = capi.sqlite3_column_count(p);
|
|
98
|
+
const values = [];
|
|
99
|
+
for (let i = 0; i < n; i++) switch (capi.sqlite3_column_type(p, i)) {
|
|
100
|
+
case capi.SQLITE_INTEGER: {
|
|
101
|
+
const v = BigInt(capi.sqlite3_column_int64(p, i));
|
|
102
|
+
if (this.bigInts) values.push(v);
|
|
103
|
+
else if (v > BigInt(Number.MAX_SAFE_INTEGER) || v < BigInt(Number.MIN_SAFE_INTEGER)) {
|
|
104
|
+
const e = /* @__PURE__ */ new RangeError(`Value is too large to be represented as a JavaScript number: ${v}`);
|
|
105
|
+
throw Object.assign(e, { code: "ERR_OUT_OF_RANGE" });
|
|
106
|
+
} else values.push(Number(v));
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
case capi.SQLITE_FLOAT:
|
|
110
|
+
values.push(capi.sqlite3_column_double(p, i));
|
|
111
|
+
break;
|
|
112
|
+
case capi.SQLITE_TEXT:
|
|
113
|
+
values.push(capi.sqlite3_column_text(p, i));
|
|
114
|
+
break;
|
|
115
|
+
case capi.SQLITE_BLOB:
|
|
116
|
+
values.push(this.stmt.get(i, capi.SQLITE_BLOB));
|
|
117
|
+
break;
|
|
118
|
+
default: values.push(null);
|
|
119
|
+
}
|
|
120
|
+
if (this.arrays) return values;
|
|
121
|
+
const row = Object.create(null);
|
|
122
|
+
for (let i = 0; i < n; i++) row[capi.sqlite3_column_name(p, i)] = values[i];
|
|
123
|
+
return row;
|
|
124
|
+
}
|
|
125
|
+
step() {
|
|
126
|
+
try {
|
|
127
|
+
return this.stmt.step();
|
|
128
|
+
} catch (e) {
|
|
129
|
+
const err = sqliteError(this.db, e);
|
|
130
|
+
try {
|
|
131
|
+
this.stmt.reset();
|
|
132
|
+
} catch {}
|
|
133
|
+
throw err;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
all(...params) {
|
|
137
|
+
this.bind(params);
|
|
138
|
+
const rows = [];
|
|
139
|
+
while (this.step()) rows.push(this.row());
|
|
140
|
+
this.stmt.reset();
|
|
141
|
+
return rows;
|
|
142
|
+
}
|
|
143
|
+
get(...params) {
|
|
144
|
+
this.bind(params);
|
|
145
|
+
const row = this.step() ? this.row() : void 0;
|
|
146
|
+
this.stmt.reset();
|
|
147
|
+
return row;
|
|
148
|
+
}
|
|
149
|
+
*iterate(...params) {
|
|
150
|
+
this.bind(params);
|
|
151
|
+
try {
|
|
152
|
+
while (this.step()) yield this.row();
|
|
153
|
+
} finally {
|
|
154
|
+
this.stmt.reset();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
run(...params) {
|
|
158
|
+
this.bind(params);
|
|
159
|
+
while (this.step());
|
|
160
|
+
this.stmt.reset();
|
|
161
|
+
const changes = capi.sqlite3_changes(this.db.pointer);
|
|
162
|
+
const last = BigInt(capi.sqlite3_last_insert_rowid(this.db.pointer));
|
|
163
|
+
return this.bigInts ? {
|
|
164
|
+
changes: BigInt(changes),
|
|
165
|
+
lastInsertRowid: last
|
|
166
|
+
} : {
|
|
167
|
+
changes: Number(changes),
|
|
168
|
+
lastInsertRowid: Number(last)
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
var DatabaseSync = class {
|
|
173
|
+
db;
|
|
174
|
+
stmts = /* @__PURE__ */ new Set();
|
|
175
|
+
constructor(path, opts = {}) {
|
|
176
|
+
try {
|
|
177
|
+
this.db = new oo1.DB({
|
|
178
|
+
filename: path,
|
|
179
|
+
flags: opts.readOnly ? "r" : "c",
|
|
180
|
+
...VFS && path !== ":memory:" ? { vfs: VFS } : {}
|
|
181
|
+
});
|
|
182
|
+
} catch (e) {
|
|
183
|
+
throw new SqliteError(e instanceof Error ? e.message : String(e), capi.SQLITE_CANTOPEN, "unable to open database file");
|
|
184
|
+
}
|
|
185
|
+
capi.sqlite3_extended_result_codes(this.db.pointer, 1);
|
|
186
|
+
}
|
|
187
|
+
get isOpen() {
|
|
188
|
+
return !!this.db.pointer;
|
|
189
|
+
}
|
|
190
|
+
get isTransaction() {
|
|
191
|
+
return capi.sqlite3_get_autocommit(this.db.pointer) === 0;
|
|
192
|
+
}
|
|
193
|
+
exec(sql) {
|
|
194
|
+
try {
|
|
195
|
+
this.db.exec(sql);
|
|
196
|
+
} catch (e) {
|
|
197
|
+
throw sqliteError(this.db, e);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
prepare(sql) {
|
|
201
|
+
try {
|
|
202
|
+
return new StatementSync(this.db, this.db.prepare(sql), this.stmts);
|
|
203
|
+
} catch (e) {
|
|
204
|
+
throw sqliteError(this.db, e);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
setAuthorizer(fn) {
|
|
208
|
+
const cb = fn ? (_p, action, ...args) => fn(action, ...args.map((a) => a === 0 ? null : a)) : 0;
|
|
209
|
+
capi.sqlite3_set_authorizer(this.db.pointer, cb, 0);
|
|
210
|
+
}
|
|
211
|
+
close() {
|
|
212
|
+
if (!this.db.pointer) throw Object.assign(/* @__PURE__ */ new Error("database is not open"), { code: "ERR_INVALID_STATE" });
|
|
213
|
+
for (const s of this.stmts) finalize(s);
|
|
214
|
+
this.stmts.clear();
|
|
215
|
+
this.db.close();
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
//#endregion
|
|
219
|
+
export { DatabaseSync, StatementSync, constants, opfs, registerVfs, sqlite3 };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region src/browser/unavailable.d.ts
|
|
2
|
+
export declare const spawn: () => never;
|
|
3
|
+
export declare const pipeline: () => never;
|
|
4
|
+
export declare class Readable {}
|
|
5
|
+
export declare class Writable {}
|
|
6
|
+
export declare const fileURLToPath: (u: string | URL) => string;
|
|
7
|
+
export declare const watch: () => {
|
|
8
|
+
on: () => /*elided*/ any;
|
|
9
|
+
close: () => Promise<void>;
|
|
10
|
+
add: () => /*elided*/ any;
|
|
11
|
+
unwatch: () => /*elided*/ any;
|
|
12
|
+
};
|
|
13
|
+
declare const _default: {};
|
|
14
|
+
//#endregion
|
|
15
|
+
export { _default as default };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//#region src/browser/unavailable.ts
|
|
2
|
+
const no = (what) => () => {
|
|
3
|
+
throw new Error(`${what} is not available in the browser build`);
|
|
4
|
+
};
|
|
5
|
+
const spawn = no("child_process.spawn");
|
|
6
|
+
const pipeline = no("stream.pipeline");
|
|
7
|
+
var Readable = class {};
|
|
8
|
+
var Writable = class {};
|
|
9
|
+
const fileURLToPath = (u) => new URL(u).pathname;
|
|
10
|
+
const watch = () => {
|
|
11
|
+
const w = {
|
|
12
|
+
on: () => w,
|
|
13
|
+
close: async () => {},
|
|
14
|
+
add: () => w,
|
|
15
|
+
unwatch: () => w
|
|
16
|
+
};
|
|
17
|
+
return w;
|
|
18
|
+
};
|
|
19
|
+
var unavailable_default = {};
|
|
20
|
+
//#endregion
|
|
21
|
+
export { Readable, Writable, unavailable_default as default, fileURLToPath, pipeline, spawn, watch };
|
package/dist/cli.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { I as isLoopback, L as loopbackHosts, P as createHandler, R as check, a as query, c as exportSite, i as formatTable, n as formatCsv, o as generateConfig, r as formatJson, s as init, t as open } from "./src-DKu7thkC.mjs";
|
|
3
|
+
import { n as guide, r as envKeys, t as createWorkspace } from "./workspace-BHg_GGkH.mjs";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
import { relative, resolve } from "node:path";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import { randomBytes } from "node:crypto";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { spawn } from "node:child_process";
|
|
10
|
+
import { Command } from "commander";
|
|
11
|
+
import { styleText } from "node:util";
|
|
12
|
+
import { createServer } from "node:http";
|
|
13
|
+
//#region src/format.ts
|
|
14
|
+
const painter = (stream) => (style, text) => styleText(style, text, { stream });
|
|
15
|
+
const MIN_SUMMARY_WIDTH = 38;
|
|
16
|
+
const WATCH_LABEL_WIDTH = 10;
|
|
17
|
+
const plural = (n, word, many = `${word}s`) => `${n} ${n === 1 ? word : many}`;
|
|
18
|
+
function displayPath(path, home = homedir()) {
|
|
19
|
+
return path === home || path.startsWith(`${home}/`) ? `~${path.slice(home.length)}` : path;
|
|
20
|
+
}
|
|
21
|
+
const changeCount = (r) => r.toDb + r.toFile + r.deletedDb + r.deletedFile;
|
|
22
|
+
function schemaCounts(schema) {
|
|
23
|
+
const columns = schema.filter((c) => c.op === "alterColumn" || c.op === "dropColumn").length;
|
|
24
|
+
const views = schema.filter((c) => c.op === "createView" || c.op === "dropView").length;
|
|
25
|
+
const indexes = schema.length - columns - views;
|
|
26
|
+
return [
|
|
27
|
+
columns > 0 ? plural(columns, "column change") : "",
|
|
28
|
+
indexes > 0 ? plural(indexes, "index change") : "",
|
|
29
|
+
views > 0 ? plural(views, "view change") : ""
|
|
30
|
+
].filter(Boolean);
|
|
31
|
+
}
|
|
32
|
+
function schemaDetail(c, paint, mode) {
|
|
33
|
+
if (c.op === "dropColumn") return `${paint("red", "−")} column ${c.name} ${paint("dim", "(removed from yamlite.yaml)")}`;
|
|
34
|
+
if (c.op === "alterColumn") {
|
|
35
|
+
const note = mode === "status" ? "table will be rebuilt" : "table rebuilt";
|
|
36
|
+
return `${paint("cyan", "↻")} column ${c.name} ${c.definition} ${paint("dim", `(${note})`)}`;
|
|
37
|
+
}
|
|
38
|
+
if (c.op === "createView") return `${paint("green", "+")} view ${c.definition}`;
|
|
39
|
+
if (c.op === "dropView") return `${paint("red", "−")} view ${c.definition}`;
|
|
40
|
+
return c.op === "createIndex" ? `${paint("green", "+")} index ${c.definition}` : `${paint("red", "−")} index ${c.definition}`;
|
|
41
|
+
}
|
|
42
|
+
function summary(r) {
|
|
43
|
+
const parts = [
|
|
44
|
+
r.toDb > 0 ? `${r.toDb} → db` : "",
|
|
45
|
+
r.toFile > 0 ? `${r.toFile} → file` : "",
|
|
46
|
+
r.deletedDb > 0 ? `${plural(r.deletedDb, "row")} deleted` : "",
|
|
47
|
+
r.deletedFile > 0 ? `${plural(r.deletedFile, "file")} deleted` : "",
|
|
48
|
+
r.conflicts.length > 0 ? plural(r.conflicts.length, "conflict") : "",
|
|
49
|
+
...schemaCounts(r.schema),
|
|
50
|
+
r.registered.length > 0 ? plural(r.registered.length, "new column") : ""
|
|
51
|
+
].filter(Boolean);
|
|
52
|
+
return parts.length > 0 ? parts.join(" · ") : "in sync";
|
|
53
|
+
}
|
|
54
|
+
function resolution(c, root) {
|
|
55
|
+
const loser = c.winner === "db" ? "file" : "db";
|
|
56
|
+
const saved = c.savedTo ? ` · ${loser} version saved to ${relative(root, c.savedTo)}` : "";
|
|
57
|
+
return `kept ${c.winner}${saved}`;
|
|
58
|
+
}
|
|
59
|
+
function conflictText(c, root, done) {
|
|
60
|
+
return `changed on both sides · ${done ? resolution(c, root) : `${c.winner} will win`}`;
|
|
61
|
+
}
|
|
62
|
+
function report(results, { mode, root, paint, elapsedMs = 0 }) {
|
|
63
|
+
const nameWidth = Math.max(0, ...results.map((r) => r.table.length));
|
|
64
|
+
const summaryWidth = Math.max(MIN_SUMMARY_WIDTH, ...results.map((r) => summary(r).length + 2));
|
|
65
|
+
const indent = " ".repeat(4 + nameWidth + 3);
|
|
66
|
+
const lines = [`${paint("bold", "YAMLite")} ${paint("dim", `· ${displayPath(root)}`)}`, ""];
|
|
67
|
+
for (const r of results) {
|
|
68
|
+
const name = r.table.padEnd(nameWidth);
|
|
69
|
+
if (!r.ok) {
|
|
70
|
+
lines.push(` ${paint("red", "✗")} ${name} ${paint("red", r.error ?? "failed")}`);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const icon = mode === "status" && (changeCount(r) > 0 || r.schema.length > 0 || r.registered.length > 0) ? paint("yellow", "●") : paint("green", "✓");
|
|
74
|
+
const records = paint("dim", plural(r.records, "record"));
|
|
75
|
+
lines.push(` ${icon} ${name} ${summary(r).padEnd(summaryWidth)}${records}`);
|
|
76
|
+
for (const c of r.schema) lines.push(`${indent}${schemaDetail(c, paint, mode)}`);
|
|
77
|
+
const note = mode === "status" ? "will be added to yamlite.yaml" : "added to yamlite.yaml";
|
|
78
|
+
for (const c of r.registered) lines.push(`${indent}${paint("green", "+")} column ${c.column} ${c.type} ${paint("dim", `(${note})`)}`);
|
|
79
|
+
for (const w of r.warnings) lines.push(`${indent}${paint("yellow", "!")} ${w}`);
|
|
80
|
+
for (const c of r.conflicts) lines.push(`${indent}${paint("yellow", "⚠")} ${c.key}: ${conflictText(c, root, mode === "sync")}`);
|
|
81
|
+
}
|
|
82
|
+
const changes = results.reduce((n, r) => n + changeCount(r), 0);
|
|
83
|
+
const newColumns = results.reduce((n, r) => n + r.registered.length, 0);
|
|
84
|
+
const work = [
|
|
85
|
+
changes > 0 ? plural(changes, "change") : "",
|
|
86
|
+
...schemaCounts(results.flatMap((r) => r.schema)),
|
|
87
|
+
newColumns > 0 ? plural(newColumns, "new column") : ""
|
|
88
|
+
].filter(Boolean);
|
|
89
|
+
const conflicts = results.reduce((n, r) => n + r.conflicts.length, 0);
|
|
90
|
+
const errors = results.filter((r) => !r.ok).length;
|
|
91
|
+
const extras = [conflicts > 0 ? plural(conflicts, "conflict") : "", errors > 0 ? paint("red", plural(errors, "error")) : ""].filter(Boolean);
|
|
92
|
+
let footer;
|
|
93
|
+
if (mode === "status") {
|
|
94
|
+
if (work.length === 0 && errors === 0) footer = paint("green", "Everything is in sync");
|
|
95
|
+
else {
|
|
96
|
+
const head = work.length > 0 ? `${work.join(" · ")} pending` : "no changes pending";
|
|
97
|
+
const hint = work.length > 0 ? [paint("dim", "run yamlite sync to apply")] : [];
|
|
98
|
+
footer = [
|
|
99
|
+
head,
|
|
100
|
+
...extras,
|
|
101
|
+
...hint
|
|
102
|
+
].join(" · ");
|
|
103
|
+
}
|
|
104
|
+
} else footer = [
|
|
105
|
+
`Synced ${plural(results.length, "table")} in ${Math.round(elapsedMs)}ms`,
|
|
106
|
+
...work.length > 0 ? work : ["no changes"],
|
|
107
|
+
...extras
|
|
108
|
+
].join(" · ");
|
|
109
|
+
lines.push("", ` ${footer}`);
|
|
110
|
+
return lines.join("\n");
|
|
111
|
+
}
|
|
112
|
+
function checkReport(result, { root, paint }) {
|
|
113
|
+
const nameWidth = Math.max(0, ...result.tables.map((t) => t.table.length));
|
|
114
|
+
const indent = " ".repeat(4 + nameWidth + 3);
|
|
115
|
+
const lines = [`${paint("bold", "YAMLite")} ${paint("dim", `· ${displayPath(root)}`)}`, ""];
|
|
116
|
+
const unlisted = new Set(result.unregisteredTables);
|
|
117
|
+
let problems = 0;
|
|
118
|
+
for (const t of result.tables) {
|
|
119
|
+
const name = t.table.padEnd(nameWidth);
|
|
120
|
+
if (!t.ok) {
|
|
121
|
+
problems++;
|
|
122
|
+
lines.push(` ${paint("red", "✗")} ${name} ${paint("red", t.error ?? "failed")}`);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const details = [
|
|
126
|
+
...unlisted.has(t.table) ? [`${paint("yellow", "+")} table ${paint("dim", "(not in yamlite.yaml)")}`] : [],
|
|
127
|
+
...t.unregistered.map((c) => `${paint("yellow", "+")} column ${c.column} ${c.type} ${paint("dim", "(not in yamlite.yaml)")}`),
|
|
128
|
+
...t.warnings.map((w) => `${paint("yellow", "!")} ${w}`)
|
|
129
|
+
];
|
|
130
|
+
problems += details.length;
|
|
131
|
+
lines.push(` ${details.length > 0 ? paint("red", "✗") : paint("green", "✓")} ${t.table}`);
|
|
132
|
+
for (const d of details) lines.push(`${indent}${d}`);
|
|
133
|
+
}
|
|
134
|
+
lines.push("", problems === 0 ? paint("green", "✓ no problems") : paint("red", `✗ ${plural(problems, "problem")}`));
|
|
135
|
+
return lines.join("\n");
|
|
136
|
+
}
|
|
137
|
+
function watchHeader(tables, root, paint) {
|
|
138
|
+
const names = tables.length > 0 ? tables.join(", ") : "(no tables)";
|
|
139
|
+
return `${paint("bold", "YAMLite")} · watching ${names} in ${displayPath(root)} ${paint("dim", "· Ctrl-C to stop")}`;
|
|
140
|
+
}
|
|
141
|
+
const clock = (now) => [
|
|
142
|
+
now.getHours(),
|
|
143
|
+
now.getMinutes(),
|
|
144
|
+
now.getSeconds()
|
|
145
|
+
].map((n) => String(n).padStart(2, "0")).join(":");
|
|
146
|
+
function reloadLine(tables, now, paint) {
|
|
147
|
+
const names = tables.length > 0 ? tables.join(", ") : "(no tables)";
|
|
148
|
+
return ` ${paint("dim", clock(now))} ${paint("cyan", "↻ reloaded")} config · watching ${names}`;
|
|
149
|
+
}
|
|
150
|
+
const OP_LABEL = {
|
|
151
|
+
toDb: ["cyan", "→ db"],
|
|
152
|
+
toFile: ["cyan", "→ file"],
|
|
153
|
+
deleteDb: ["red", "− row"],
|
|
154
|
+
deleteFile: ["red", "− file"]
|
|
155
|
+
};
|
|
156
|
+
function watchEvents(r, { root, now, paint, quiet, nameWidth = 0 }) {
|
|
157
|
+
const prefix = ` ${paint("dim", clock(now))} ${r.table.padEnd(nameWidth)} `;
|
|
158
|
+
const line = (style, label, text) => `${prefix}${paint(style, label.padEnd(WATCH_LABEL_WIDTH))} ${text}`;
|
|
159
|
+
if (!r.ok) return [line("red", "✗ error", r.error ?? "failed")];
|
|
160
|
+
const lines = [];
|
|
161
|
+
if (!quiet) {
|
|
162
|
+
for (const c of r.changes) lines.push(line(OP_LABEL[c.op][0], OP_LABEL[c.op][1], c.key));
|
|
163
|
+
for (const c of r.schema) if (c.op === "alterColumn") lines.push(line("cyan", "↻ column", `${c.name} ${c.definition}`));
|
|
164
|
+
else if (c.op === "dropColumn") lines.push(line("red", "− column", c.name));
|
|
165
|
+
else if (c.op === "createIndex") lines.push(line("green", "+ index", c.definition));
|
|
166
|
+
else if (c.op === "createView") lines.push(line("green", "+ view", c.definition));
|
|
167
|
+
else if (c.op === "dropView") lines.push(line("red", "− view", c.definition));
|
|
168
|
+
else lines.push(line("red", "− index", c.definition));
|
|
169
|
+
for (const c of r.registered) lines.push(line("green", "+ column", `${c.column} ${c.type}`));
|
|
170
|
+
}
|
|
171
|
+
for (const c of r.conflicts) lines.push(line("yellow", "⚠ conflict", `${c.key} · ${resolution(c, root)}`));
|
|
172
|
+
if (!quiet) for (const w of r.warnings) lines.push(line("yellow", "! warning", w));
|
|
173
|
+
return lines;
|
|
174
|
+
}
|
|
175
|
+
//#endregion
|
|
176
|
+
//#region src/serve/index.ts
|
|
177
|
+
const DEFAULT_UI = fileURLToPath(new URL("./ui/", import.meta.url));
|
|
178
|
+
const urlHostOf = (h) => h.includes(":") ? `[${h}]` : h;
|
|
179
|
+
function listen(server, port, host) {
|
|
180
|
+
return new Promise((done, fail) => {
|
|
181
|
+
server.once("error", (e) => {
|
|
182
|
+
fail(e.code === "EADDRINUSE" ? /* @__PURE__ */ new Error(`port ${port} is already in use; pick another one with --port`) : e);
|
|
183
|
+
});
|
|
184
|
+
server.listen(port, host, () => done(server.address().port));
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
async function serve(opts) {
|
|
188
|
+
const host = opts.host ?? "127.0.0.1";
|
|
189
|
+
let port = 0;
|
|
190
|
+
const w = await createWorkspace({
|
|
191
|
+
root: opts.root,
|
|
192
|
+
db: opts.db,
|
|
193
|
+
watch: opts.watch,
|
|
194
|
+
ai: opts.agent === false ? null : opts.ai ?? {
|
|
195
|
+
keys: envKeys(process.env),
|
|
196
|
+
webSearch: opts.webSearch
|
|
197
|
+
},
|
|
198
|
+
gh: opts.gh,
|
|
199
|
+
git: opts.git
|
|
200
|
+
});
|
|
201
|
+
try {
|
|
202
|
+
const token = randomBytes(24).toString("hex");
|
|
203
|
+
let policy = {
|
|
204
|
+
token,
|
|
205
|
+
allowedHosts: /* @__PURE__ */ new Set()
|
|
206
|
+
};
|
|
207
|
+
const http = createServer(createHandler(w.router, () => policy, opts.uiDir ?? DEFAULT_UI));
|
|
208
|
+
port = await listen(http, opts.port ?? 4610, host);
|
|
209
|
+
policy = {
|
|
210
|
+
token,
|
|
211
|
+
allowedHosts: isLoopback(host) ? loopbackHosts(port) : null
|
|
212
|
+
};
|
|
213
|
+
let closing;
|
|
214
|
+
return {
|
|
215
|
+
url: `http://${urlHostOf(host)}:${port}/?token=${token}`,
|
|
216
|
+
port,
|
|
217
|
+
token,
|
|
218
|
+
context: w.context,
|
|
219
|
+
close() {
|
|
220
|
+
closing ??= (async () => {
|
|
221
|
+
try {
|
|
222
|
+
http.closeAllConnections();
|
|
223
|
+
await new Promise((done) => http.close(() => done()));
|
|
224
|
+
} finally {
|
|
225
|
+
await w.close();
|
|
226
|
+
}
|
|
227
|
+
})();
|
|
228
|
+
return closing;
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
} catch (e) {
|
|
232
|
+
await w.close();
|
|
233
|
+
throw e;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
//#endregion
|
|
237
|
+
//#region src/cli.ts
|
|
238
|
+
const { version } = createRequire(import.meta.url)("../package.json");
|
|
239
|
+
const collect = (value, previous) => [...previous, value];
|
|
240
|
+
const out = painter(process.stdout);
|
|
241
|
+
const err = painter(process.stderr);
|
|
242
|
+
function withRoot(cmd) {
|
|
243
|
+
return cmd.argument("[root]", "data directory", ".").option("--db <path>", "SQLite database (default: <root>/.yamlite/db.sqlite)");
|
|
244
|
+
}
|
|
245
|
+
const program = new Command().name("yamlite").description("Sync YAML files and SQLite in both directions").version(version);
|
|
246
|
+
withRoot(program.command("init").description("create yamlite.yaml from the YAML files and the existing database")).option("--force", "overwrite an existing yamlite.yaml").option("--print", "print the generated yamlite.yaml instead of writing it").action((root, o) => {
|
|
247
|
+
if (o.print) {
|
|
248
|
+
process.stdout.write(generateConfig({
|
|
249
|
+
root,
|
|
250
|
+
db: o.db,
|
|
251
|
+
force: true
|
|
252
|
+
}));
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const result = init({
|
|
256
|
+
root,
|
|
257
|
+
db: o.db,
|
|
258
|
+
force: o.force
|
|
259
|
+
});
|
|
260
|
+
const tables = result.tables.length > 0 ? result.tables.join(", ") : "no tables yet";
|
|
261
|
+
console.log(`${out("green", "✓")} wrote ${displayPath(result.path)} ${out("dim", `· ${tables}`)}`);
|
|
262
|
+
console.log(` next: ${out("bold", "yamlite status")} to preview, ${out("bold", "yamlite sync")} to apply`);
|
|
263
|
+
});
|
|
264
|
+
program.command("guide").description("print the guide to writing yamlite.yaml, for people and AI agents").action(() => {
|
|
265
|
+
process.stdout.write(guide());
|
|
266
|
+
});
|
|
267
|
+
program.command("check").description("check the YAML files against yamlite.yaml without writing anything; exits 1 on any problem").argument("[root]", "data directory", ".").option("--table <name>", "only this table (repeatable)", collect, []).option("--json", "print JSON").action(async (root, o) => {
|
|
268
|
+
const result = await check({
|
|
269
|
+
root,
|
|
270
|
+
tables: o.table
|
|
271
|
+
});
|
|
272
|
+
if (o.json) console.log(JSON.stringify(result, null, 2));
|
|
273
|
+
else console.log(checkReport(result, {
|
|
274
|
+
root: resolve(root),
|
|
275
|
+
paint: out
|
|
276
|
+
}));
|
|
277
|
+
if (!result.ok) process.exitCode = 1;
|
|
278
|
+
});
|
|
279
|
+
const FORMATS = {
|
|
280
|
+
table: formatTable,
|
|
281
|
+
json: formatJson,
|
|
282
|
+
csv: formatCsv
|
|
283
|
+
};
|
|
284
|
+
program.command("query").description("run one read-only SQL statement against the YAML files (or --db) and print the rows").argument("<sql>", "the statement").argument("[root]", "data directory", ".").option("--db <path>", "read this database as it is instead of the YAML files").option("--format <format>", "table, json or csv", "table").option("--limit <n>", "at most this many rows; 0 for no limit", "1000").action(async (sql, root, o) => {
|
|
285
|
+
if (!Object.hasOwn(FORMATS, o.format)) throw new Error(`unknown format ${o.format}: use table, json or csv`);
|
|
286
|
+
const format = FORMATS[o.format];
|
|
287
|
+
const limit = Number(o.limit);
|
|
288
|
+
if (!Number.isInteger(limit) || limit < 0) throw new Error(`--limit must be a whole number, not ${o.limit}`);
|
|
289
|
+
const r = await query({
|
|
290
|
+
root,
|
|
291
|
+
db: o.db,
|
|
292
|
+
sql,
|
|
293
|
+
limit
|
|
294
|
+
});
|
|
295
|
+
for (const w of r.warnings) console.error(`${err("yellow", "!")} ${w}`);
|
|
296
|
+
process.stdout.write(o.format === "csv" ? format(r) : `${format(r)}\n`);
|
|
297
|
+
if (o.format === "table") console.error(err("dim", `${r.rows.length} ${r.rows.length === 1 ? "row" : "rows"}`));
|
|
298
|
+
if (r.truncated) console.error(err("yellow", `… truncated at ${limit} rows (use --limit)`));
|
|
299
|
+
});
|
|
300
|
+
withRoot(program.command("status").description("show what sync would change, without writing")).option("--table <name>", "only this table (repeatable)", collect, []).option("--json", "print JSON").action(async (root, o) => {
|
|
301
|
+
const y = await open({
|
|
302
|
+
root,
|
|
303
|
+
db: o.db
|
|
304
|
+
});
|
|
305
|
+
try {
|
|
306
|
+
const results = await y.status({ tables: o.table });
|
|
307
|
+
if (o.json) console.log(JSON.stringify(results, null, 2));
|
|
308
|
+
else console.log(report(results, {
|
|
309
|
+
mode: "status",
|
|
310
|
+
root: resolve(root),
|
|
311
|
+
paint: out
|
|
312
|
+
}));
|
|
313
|
+
} finally {
|
|
314
|
+
await y.close();
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
withRoot(program.command("sync").description("sync once")).option("--table <name>", "only this table (repeatable)", collect, []).option("--force", "allow mass deletions").option("--force-convert", "clear values that cannot be converted when a column type changes").option("--json", "print JSON").action(async (root, o) => {
|
|
318
|
+
const y = await open({
|
|
319
|
+
root,
|
|
320
|
+
db: o.db
|
|
321
|
+
});
|
|
322
|
+
try {
|
|
323
|
+
const started = performance.now();
|
|
324
|
+
const results = await y.sync({
|
|
325
|
+
tables: o.table,
|
|
326
|
+
force: o.force,
|
|
327
|
+
forceConvert: o.forceConvert
|
|
328
|
+
});
|
|
329
|
+
const elapsedMs = performance.now() - started;
|
|
330
|
+
if (o.json) console.log(JSON.stringify(results, null, 2));
|
|
331
|
+
else console.log(report(results, {
|
|
332
|
+
mode: "sync",
|
|
333
|
+
root: resolve(root),
|
|
334
|
+
paint: out,
|
|
335
|
+
elapsedMs
|
|
336
|
+
}));
|
|
337
|
+
if (results.some((r) => !r.ok)) process.exitCode = 1;
|
|
338
|
+
} finally {
|
|
339
|
+
await y.close();
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
withRoot(program.command("watch").description("sync continuously")).option("--quiet", "only print conflicts and errors").action(async (root, o) => {
|
|
343
|
+
const y = await open({
|
|
344
|
+
root,
|
|
345
|
+
db: o.db
|
|
346
|
+
});
|
|
347
|
+
const nameWidth = () => Math.max(0, ...y.tables.map((t) => t.name.length));
|
|
348
|
+
await y.watch({
|
|
349
|
+
onSync: (r) => {
|
|
350
|
+
const opts = {
|
|
351
|
+
root: resolve(root),
|
|
352
|
+
now: /* @__PURE__ */ new Date(),
|
|
353
|
+
paint: out,
|
|
354
|
+
quiet: o.quiet ?? false,
|
|
355
|
+
nameWidth: nameWidth()
|
|
356
|
+
};
|
|
357
|
+
for (const line of watchEvents(r, opts)) console.log(line);
|
|
358
|
+
},
|
|
359
|
+
onReload: (tables) => {
|
|
360
|
+
if (!o.quiet) console.log(reloadLine(tables, /* @__PURE__ */ new Date(), out));
|
|
361
|
+
},
|
|
362
|
+
onError: (e, table) => {
|
|
363
|
+
if (table === void 0) console.error(painter(process.stderr)("red", `error: ${e.message}`));
|
|
364
|
+
}
|
|
365
|
+
}).ready;
|
|
366
|
+
if (!o.quiet) console.log(`${watchHeader(y.tables.map((t) => t.name), resolve(root), out)}\n`);
|
|
367
|
+
const stop = () => {
|
|
368
|
+
y.close().then(() => process.exit(0));
|
|
369
|
+
};
|
|
370
|
+
process.once("SIGINT", stop);
|
|
371
|
+
process.once("SIGTERM", stop);
|
|
372
|
+
});
|
|
373
|
+
function openBrowser(url) {
|
|
374
|
+
const [cmd, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", [
|
|
375
|
+
"/c",
|
|
376
|
+
"start",
|
|
377
|
+
"",
|
|
378
|
+
url
|
|
379
|
+
]] : ["xdg-open", [url]];
|
|
380
|
+
spawn(cmd, args, {
|
|
381
|
+
stdio: "ignore",
|
|
382
|
+
detached: true
|
|
383
|
+
}).on("error", () => {}).unref();
|
|
384
|
+
}
|
|
385
|
+
withRoot(program.command("serve").description("open the web UI and sync continuously")).option("--port <number>", "port to listen on", "4610").option("--host <address>", "address to listen on", "127.0.0.1").option("--open", "open the UI in the browser").option("--no-agent", "do not offer Ask AI in the web UI").option("--web-search", "let Ask AI search the web; each search is billed to the API key").action(async (root, o) => {
|
|
386
|
+
const port = Number(o.port);
|
|
387
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) throw new Error(`invalid port: ${o.port}`);
|
|
388
|
+
const s = await serve({
|
|
389
|
+
root,
|
|
390
|
+
db: o.db,
|
|
391
|
+
port,
|
|
392
|
+
host: o.host,
|
|
393
|
+
agent: o.agent,
|
|
394
|
+
webSearch: o.webSearch
|
|
395
|
+
});
|
|
396
|
+
if (!isLoopback(o.host)) {
|
|
397
|
+
const askAi = o.agent && Object.keys(envKeys(process.env)).length > 0;
|
|
398
|
+
console.error(painter(process.stderr)("yellow", `warning: listening on ${o.host}; anyone who can reach it and has the token can read and change the data${askAi ? " and use Ask AI with the API keys from the environment" : ""}`));
|
|
399
|
+
}
|
|
400
|
+
console.log(`${out("green", "✓")} YAMLite UI · ${out("bold", s.url)}`);
|
|
401
|
+
console.log(out("dim", ` watching ${displayPath(resolve(root))} · Ctrl+C to stop`));
|
|
402
|
+
if (o.open) openBrowser(s.url);
|
|
403
|
+
let stopping = false;
|
|
404
|
+
const stop = () => {
|
|
405
|
+
if (stopping) process.exit(130);
|
|
406
|
+
stopping = true;
|
|
407
|
+
s.close().then(() => process.exit(0), (e) => {
|
|
408
|
+
console.error(`error: ${e instanceof Error ? e.message : String(e)}`);
|
|
409
|
+
process.exit(1);
|
|
410
|
+
});
|
|
411
|
+
};
|
|
412
|
+
process.on("SIGINT", stop);
|
|
413
|
+
process.on("SIGTERM", stop);
|
|
414
|
+
});
|
|
415
|
+
program.command("export").description("write the web UI as a read-only static site").argument("[root]", "data directory", ".").option("--out <dir>", "output folder", ".yamlite-export").option("--table <name>", "only this table (repeatable)", collect, []).option("--force", "replace an output folder that is not a previous export").action(async (root, o) => {
|
|
416
|
+
const r = await exportSite({
|
|
417
|
+
root,
|
|
418
|
+
out: o.out,
|
|
419
|
+
tables: o.table,
|
|
420
|
+
force: o.force
|
|
421
|
+
});
|
|
422
|
+
const count = `${r.tables.length} ${r.tables.length === 1 ? "table" : "tables"}`;
|
|
423
|
+
console.log(`${out("green", "✓")} exported ${count} to ${displayPath(r.out)}`);
|
|
424
|
+
for (const [table, warnings] of Object.entries(r.warnings)) for (const w of warnings) console.log(` ${out("yellow", "!")} ${table}: ${w}`);
|
|
425
|
+
for (const [page, reason] of Object.entries(r.skippedPages)) console.log(` ${out("yellow", "!")} page ${page} not exported: ${reason}`);
|
|
426
|
+
console.log(out("dim", " serve the folder over HTTP to open it; file:// does not work"));
|
|
427
|
+
});
|
|
428
|
+
program.parseAsync().catch((e) => {
|
|
429
|
+
console.error(`error: ${e instanceof Error ? e.message : String(e)}`);
|
|
430
|
+
process.exitCode = 1;
|
|
431
|
+
});
|
|
432
|
+
//#endregion
|
|
433
|
+
export {};
|