gitroll 0.1.2 → 0.2.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/CHANGELOG.md +24 -0
- package/README.md +5 -4
- package/SPEC.md +3 -1
- package/dist/THIRD_PARTY_NOTICES.txt +1671 -0
- package/dist/gitroll.mjs +54 -16
- package/dist/web/THIRD_PARTY_NOTICES.txt +1671 -0
- package/dist/web/app.js +275 -112
- package/dist/web/index.html +3 -52
- package/dist/web/style.css +2 -224
- package/package.json +25 -3
package/dist/gitroll.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/*! GitRoll 0.
|
|
2
|
+
/*! GitRoll 0.2.0 · MIT License · Includes third-party software; see THIRD_PARTY_NOTICES.txt */
|
|
3
3
|
import { createRequire as __gitrollRequire } from "node:module"; const require = __gitrollRequire(import.meta.url);
|
|
4
4
|
var __create = Object.create;
|
|
5
5
|
var __defProp = Object.defineProperty;
|
|
@@ -3129,15 +3129,15 @@ var require_timestamp = __commonJS({
|
|
|
3129
3129
|
throw new Error("!!timestamp expects a date, starting with yyyy-mm-dd");
|
|
3130
3130
|
const [, year, month, day, hour, minute, second] = match.map(Number);
|
|
3131
3131
|
const millisec = match[7] ? Number((match[7] + "00").substr(1, 3)) : 0;
|
|
3132
|
-
let
|
|
3132
|
+
let date = Date.UTC(year, month - 1, day, hour || 0, minute || 0, second || 0, millisec);
|
|
3133
3133
|
const tz = match[8];
|
|
3134
3134
|
if (tz && tz !== "Z") {
|
|
3135
3135
|
let d = parseSexagesimal(tz, false);
|
|
3136
3136
|
if (Math.abs(d) < 30)
|
|
3137
3137
|
d *= 60;
|
|
3138
|
-
|
|
3138
|
+
date -= 6e4 * d;
|
|
3139
3139
|
}
|
|
3140
|
-
return new Date(
|
|
3140
|
+
return new Date(date);
|
|
3141
3141
|
},
|
|
3142
3142
|
stringify: ({ value }) => value?.toISOString().replace(/(T00:00:00)?\.000Z$/, "") ?? ""
|
|
3143
3143
|
};
|
|
@@ -7844,6 +7844,10 @@ var ALIASES = {
|
|
|
7844
7844
|
p: "project",
|
|
7845
7845
|
project: "project",
|
|
7846
7846
|
projects: "project",
|
|
7847
|
+
// "Topic" is what the interface calls a project. The stored field stays
|
|
7848
|
+
// `projects` (SPEC.md, format version 1), so these are aliases, not a rename.
|
|
7849
|
+
topic: "project",
|
|
7850
|
+
topics: "project",
|
|
7847
7851
|
t: "tag",
|
|
7848
7852
|
tag: "tag",
|
|
7849
7853
|
tags: "tag",
|
|
@@ -8049,12 +8053,10 @@ var t = (id, label, icon, amount2, fields, description) => ({
|
|
|
8049
8053
|
builtin: true
|
|
8050
8054
|
});
|
|
8051
8055
|
var text2 = (key, label, extra = {}) => ({ key, label, kind: "text", ...extra });
|
|
8052
|
-
var date = (key, label) => ({ key, label, kind: "date" });
|
|
8053
8056
|
var select = (key, label, options) => ({ key, label, kind: "select", options });
|
|
8054
8057
|
var BUILTIN_TYPES = [
|
|
8055
8058
|
t(DEFAULT_TYPE, "Log", "\u{1F4DD}", "optional", [], "Something happened."),
|
|
8056
8059
|
t("expense", "Expense", "\u{1F9FE}", "expected", [text2("vendor", "Paid to"), text2("category", "Category"), select("method", "Method", ["card", "cash", "check", "transfer", "other"])], "Money spent, usually with a receipt."),
|
|
8057
|
-
t("maintenance", "Maintenance", "\u{1F527}", "optional", [text2("asset", "Asset"), text2("vendor", "Done by"), date("next_due", "Next due"), date("warranty_until", "Warranty until")], "Service, repair or upkeep."),
|
|
8058
8060
|
t("decision", "Decision", "\u2696\uFE0F", "none", [select("status", "Status", ["decided", "proposed", "reversed"]), { key: "alternatives", label: "Alternatives considered", kind: "longtext" }], "A choice that was made, and why."),
|
|
8059
8061
|
t("milestone", "Milestone", "\u{1F3C1}", "none", [], "A notable moment."),
|
|
8060
8062
|
t("issue", "Issue", "\u26A0\uFE0F", "optional", [select("severity", "Severity", ["low", "medium", "high"]), select("status", "Status", ["open", "resolved"])], "A problem that was noticed.")
|
|
@@ -9056,8 +9058,8 @@ var GitRoll = class _GitRoll {
|
|
|
9056
9058
|
const out = tryRun(this.root, ["log", "--follow", "-p", "--format=%x1e%H%x1f%an%x1f%aI%x1f%s", "--", cur.path]) ?? "";
|
|
9057
9059
|
return out.split("").filter((c) => c.trim()).map((chunk) => {
|
|
9058
9060
|
const nl = chunk.indexOf("\n");
|
|
9059
|
-
const [commit, author,
|
|
9060
|
-
return { commit, author, date
|
|
9061
|
+
const [commit, author, date, subject] = (nl < 0 ? chunk : chunk.slice(0, nl)).split("");
|
|
9062
|
+
return { commit, author, date, subject, patch: nl < 0 ? "" : chunk.slice(nl + 1).trim() };
|
|
9061
9063
|
});
|
|
9062
9064
|
}
|
|
9063
9065
|
/** Validates the Roll against the GitRoll Format, on this computer. */
|
|
@@ -9119,10 +9121,13 @@ ${JSON.stringify(e.data)}`).map((kind) => ({ path: e.path, error: `may contain a
|
|
|
9119
9121
|
* user hasn't explicitly trusted all stop the sync before anything is uploaded.
|
|
9120
9122
|
*/
|
|
9121
9123
|
async sync(options = {}) {
|
|
9124
|
+
const stage = options.onStage ?? (() => {
|
|
9125
|
+
});
|
|
9122
9126
|
const { remote } = this.status();
|
|
9123
9127
|
if (!remote) {
|
|
9124
9128
|
return { ok: false, code: "no-remote", message: "This Roll isn't backed up yet. Your events are saved on this computer. Run: gitroll backup" };
|
|
9125
9129
|
}
|
|
9130
|
+
stage("checking");
|
|
9126
9131
|
const destinations = this.pushDestinations();
|
|
9127
9132
|
if (!destinations.length) return { ok: false, code: "error", message: `Couldn't read where "${remote}" uploads to. Nothing was uploaded.` };
|
|
9128
9133
|
const trusted = new Set((loadUserConfig().trustedRemotes ?? []).map((t2) => t2.toLowerCase()));
|
|
@@ -9154,7 +9159,7 @@ ${JSON.stringify(e.data)}`).map((kind) => ({ path: e.path, error: `may contain a
|
|
|
9154
9159
|
};
|
|
9155
9160
|
}
|
|
9156
9161
|
}
|
|
9157
|
-
return this.#transfer();
|
|
9162
|
+
return this.#transfer(stage);
|
|
9158
9163
|
}
|
|
9159
9164
|
/**
|
|
9160
9165
|
* Download, combine and upload, using the user's own Git credentials. Never
|
|
@@ -9162,22 +9167,26 @@ ${JSON.stringify(e.data)}`).map((kind) => ({ path: e.path, error: `may contain a
|
|
|
9162
9167
|
* automatically without losing either version. On any other failure local
|
|
9163
9168
|
* commits stay exactly as they were.
|
|
9164
9169
|
*/
|
|
9165
|
-
#transfer() {
|
|
9170
|
+
#transfer(stage = () => {
|
|
9171
|
+
}) {
|
|
9166
9172
|
const { remote, branch } = this.status();
|
|
9167
9173
|
if (!remote) return { ok: false, code: "no-remote", message: "This Roll isn't backed up yet." };
|
|
9168
9174
|
const merged = /* @__PURE__ */ new Set();
|
|
9169
9175
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
9170
9176
|
try {
|
|
9177
|
+
stage("downloading");
|
|
9171
9178
|
this.git(["fetch", "-q", remote], { network: true });
|
|
9172
9179
|
} catch (e) {
|
|
9173
9180
|
return this.#syncFailure(e, remote);
|
|
9174
9181
|
}
|
|
9175
9182
|
const upstream = `refs/remotes/${remote}/${branch}`;
|
|
9176
9183
|
if (tryRun(this.root, ["rev-parse", "--verify", "-q", upstream]) !== null) {
|
|
9184
|
+
stage("combining");
|
|
9177
9185
|
const failed = this.#combine(upstream, merged);
|
|
9178
9186
|
if (failed) return failed;
|
|
9179
9187
|
}
|
|
9180
9188
|
try {
|
|
9189
|
+
stage("uploading");
|
|
9181
9190
|
this.git(["push", "-q", "-u", remote, `HEAD:refs/heads/${branch}`], { network: true });
|
|
9182
9191
|
} catch (e) {
|
|
9183
9192
|
if (/\[rejected\]|non-fast-forward|fetch first/i.test(e.message) && attempt < 2) continue;
|
|
@@ -9293,7 +9302,7 @@ import path5 from "node:path";
|
|
|
9293
9302
|
var WEB_DIR = assetDir("index.html", "./web/", "../../dist/web/");
|
|
9294
9303
|
var MAX_BODY = HARD_MAX_ATTACHMENT_MB * 4 * 1024 * 1024;
|
|
9295
9304
|
var LOOPBACK = ["127.0.0.1", "localhost", "::1"];
|
|
9296
|
-
var CSP = "default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self' blob: data:; media-src 'self'; connect-src 'self'; form-action 'none'; base-uri 'none'; object-src 'none'; frame-ancestors 'none'";
|
|
9305
|
+
var CSP = "default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' blob: data:; media-src 'self'; connect-src 'self'; form-action 'none'; base-uri 'none'; object-src 'none'; frame-ancestors 'none'";
|
|
9297
9306
|
var SECURITY_HEADERS = {
|
|
9298
9307
|
"X-Content-Type-Options": "nosniff",
|
|
9299
9308
|
"Referrer-Policy": "no-referrer",
|
|
@@ -9318,7 +9327,14 @@ async function serve(repo, opts) {
|
|
|
9318
9327
|
if (!fs5.existsSync(path5.join(webDir, "index.html"))) {
|
|
9319
9328
|
throw new UserError("GitRoll's app files are missing. Reinstall GitRoll (or run `make build` from source).");
|
|
9320
9329
|
}
|
|
9321
|
-
const ctx = {
|
|
9330
|
+
const ctx = {
|
|
9331
|
+
repo,
|
|
9332
|
+
webDir,
|
|
9333
|
+
token: opts.token ?? randomBytes(32).toString("base64url"),
|
|
9334
|
+
cookie: "gitroll",
|
|
9335
|
+
ai: opts.ai ?? null,
|
|
9336
|
+
sync: { stage: null, running: null, startedAt: 0, last: null }
|
|
9337
|
+
};
|
|
9322
9338
|
const server = http.createServer((req, res) => {
|
|
9323
9339
|
handle(ctx, req, res).catch((err) => {
|
|
9324
9340
|
const status = err instanceof HttpError ? err.status : err instanceof NotFoundError ? 404 : err instanceof UserError ? 400 : 500;
|
|
@@ -9427,11 +9443,33 @@ async function api(ctx, method, [resource, id, sub], req, res) {
|
|
|
9427
9443
|
return sendJson(res, 200, { answer, sources: sources.map((e) => ({ id: e.id, short: shortId(e.id) })) });
|
|
9428
9444
|
}
|
|
9429
9445
|
case "POST sync":
|
|
9430
|
-
return sendJson(res, 200, await
|
|
9446
|
+
return sendJson(res, 200, await runSync(ctx));
|
|
9447
|
+
case "GET sync":
|
|
9448
|
+
return sendJson(res, 200, {
|
|
9449
|
+
running: ctx.sync.running !== null,
|
|
9450
|
+
stage: ctx.sync.stage,
|
|
9451
|
+
startedAt: ctx.sync.startedAt || null,
|
|
9452
|
+
last: ctx.sync.last,
|
|
9453
|
+
status: repo.status()
|
|
9454
|
+
});
|
|
9431
9455
|
default:
|
|
9432
9456
|
throw new HttpError(404, "Not found");
|
|
9433
9457
|
}
|
|
9434
9458
|
}
|
|
9459
|
+
function runSync(ctx) {
|
|
9460
|
+
if (ctx.sync.running) return ctx.sync.running;
|
|
9461
|
+
ctx.sync.stage = "checking";
|
|
9462
|
+
ctx.sync.startedAt = Date.now();
|
|
9463
|
+
const run3 = ctx.repo.sync({ onStage: (stage) => ctx.sync.stage = stage }).then((result) => {
|
|
9464
|
+
ctx.sync.last = { at: Date.now(), result };
|
|
9465
|
+
return result;
|
|
9466
|
+
}).finally(() => {
|
|
9467
|
+
ctx.sync.running = null;
|
|
9468
|
+
ctx.sync.stage = null;
|
|
9469
|
+
});
|
|
9470
|
+
ctx.sync.running = run3;
|
|
9471
|
+
return run3;
|
|
9472
|
+
}
|
|
9435
9473
|
async function readJson(req) {
|
|
9436
9474
|
const chunks = [];
|
|
9437
9475
|
let size = 0;
|
|
@@ -9966,11 +10004,11 @@ var Tui = class {
|
|
|
9966
10004
|
for (let i = this.scroll; i < Math.min(list3.length, this.scroll + rows); i++) {
|
|
9967
10005
|
const e = list3[i];
|
|
9968
10006
|
const d = new Date(e.occurred);
|
|
9969
|
-
const
|
|
10007
|
+
const date = d.toLocaleDateString("en-US", { month: "short", day: "numeric" }).padEnd(7);
|
|
9970
10008
|
const labels = [e.type !== "log" ? e.type : "", ...e.projects.map((p) => this.#names.get(p) ?? p), e.attachments.length ? `${e.attachments.length} file${e.attachments.length === 1 ? "" : "s"}` : ""].filter(Boolean).join(" \xB7 ");
|
|
9971
10009
|
const first = (e.body || "(no text)").split("\n")[0];
|
|
9972
|
-
const room = w - 4 -
|
|
9973
|
-
const plain = `${
|
|
10010
|
+
const room = w - 4 - date.length - (labels ? [...labels].length + 2 : 0);
|
|
10011
|
+
const plain = `${date} ${fit(first, Math.max(8, room))}`;
|
|
9974
10012
|
const pad2 = " ".repeat(Math.max(1, w - 2 - [...plain].length - [...labels].length));
|
|
9975
10013
|
const row = `${plain}${pad2}${labels}`;
|
|
9976
10014
|
out.push(i === this.selected && !this.finding ? inverse(fit(`\u25B8 ${row}`, w)) : ` ${fit(row, w - 2).replace(labels, dim(labels))}`);
|