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/dist/gitroll.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- /*! GitRoll 0.1.2 · MIT License · Includes third-party software; see THIRD_PARTY_NOTICES.txt */
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 date2 = Date.UTC(year, month - 1, day, hour || 0, minute || 0, second || 0, millisec);
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
- date2 -= 6e4 * d;
3138
+ date -= 6e4 * d;
3139
3139
  }
3140
- return new Date(date2);
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, date2, subject] = (nl < 0 ? chunk : chunk.slice(0, nl)).split("");
9060
- return { commit, author, date: date2, subject, patch: nl < 0 ? "" : chunk.slice(nl + 1).trim() };
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 = { repo, webDir, token: opts.token ?? randomBytes(32).toString("base64url"), cookie: "gitroll", ai: opts.ai ?? null };
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 repo.sync());
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 date2 = d.toLocaleDateString("en-US", { month: "short", day: "numeric" }).padEnd(7);
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 - date2.length - (labels ? [...labels].length + 2 : 0);
9973
- const plain = `${date2} ${fit(first, Math.max(8, room))}`;
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))}`);