mainflow 0.30.0-alpha.1 → 0.31.0-alpha.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.
- package/dist/cli.js +112 -9
- package/dist/facts.d.ts +8 -1
- package/dist/facts.js +8 -1
- package/dist/hooks.js +2 -2
- package/dist/{host-DmVNYRql.js → host-CdB_vDY_.js} +43 -6
- package/dist/host.js +1 -1
- package/dist/{launch-DHug6eli.js → launch-D1zaGo7W.js} +2 -2
- package/dist/launch.js +1 -1
- package/dist/native/mainflow-swap-darwin-arm64 +0 -0
- package/dist/native/mainflow-swap-darwin-x64 +0 -0
- package/dist/{operation-BQiUL0eq.js → operation-BmysMg4R.js} +897 -175
- package/dist/{ui-BqRmaHXn.js → ui-DlOY5qEU.js} +4 -4
- package/dist/web/assets/{index-DLLPsiVq.js → index-B-_uZYXu.js} +1 -1
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { B as parseRef, H as refExample, V as parseUntil, Y as stampMs, k as UNTIL_FORMS } from "./dist-IdbnCDqM.js";
|
|
3
|
-
import { B as checkHubName, H as loadHub, J as TASK_FILE, Q as nodeStorage, S as readSession, V as HubError, X as nowMinute, Y as requireSlug, _ as untilState, d as retroReportFromReceiver, f as receiverFromEnv, g as initHub, l as migrateHub,
|
|
3
|
+
import { B as checkHubName, H as loadHub, J as TASK_FILE, Q as nodeStorage, S as readSession, V as HubError, X as nowMinute, Y as requireSlug, _ as untilState, at as findHubRoot, ct as DATA_DIR, d as retroReportFromReceiver, f as receiverFromEnv, g as initHub, l as migrateHub, lt as WriteError, p as RETRY_RULES, q as MAINLINE_FILE, u as retroReport, v as duplicateClaimLines, x as loadSessions, y as sessionBriefing, z as applyOperation } from "./host-CdB_vDY_.js";
|
|
4
4
|
import { t as listProgress } from "./progress-1BfPg2zN.js";
|
|
5
|
-
import { C as
|
|
5
|
+
import { C as syncSettings, D as syncPaths, S as hubName, _ as readRemote, a as remoteMode, b as ENV, c as planFetch, d as DaemonRunning, f as DaemonUnavailable, g as silentHint, h as readDaemon, i as queuedCount, l as planFlush, m as probeDaemon, n as hubWriter, o as fetchDenial, p as callDaemon, r as writtenLines, s as hookAnswer, t as CONFIRM_MS, u as startDaemon, v as ServerApi, w as SETTINGS_FILE, x as earlierToken, y as ServerError } from "./operation-BmysMg4R.js";
|
|
6
6
|
import { COMMAND_SPECS, OPTIONS } from "./facts.js";
|
|
7
7
|
import { hookFallback, parsePayload, runHook } from "./hooks.js";
|
|
8
|
-
import { c as watchHubGone, l as CLI_VERSION, r as launchUi, s as startUiInBackground } from "./launch-
|
|
8
|
+
import { c as watchHubGone, l as CLI_VERSION, r as launchUi, s as startUiInBackground } from "./launch-D1zaGo7W.js";
|
|
9
9
|
import { randomBytes } from "node:crypto";
|
|
10
|
-
import { existsSync, readFileSync, realpathSync, rmSync, statSync } from "node:fs";
|
|
11
|
-
import { basename, join, relative, resolve } from "node:path";
|
|
10
|
+
import { existsSync, readFileSync, readdirSync, realpathSync, rmSync, statSync } from "node:fs";
|
|
11
|
+
import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
12
12
|
import { fileURLToPath } from "node:url";
|
|
13
13
|
import { parseArgs } from "node:util";
|
|
14
14
|
//#region src/remote/sync.ts
|
|
@@ -20,7 +20,8 @@ import { parseArgs } from "node:util";
|
|
|
20
20
|
async function syncRun(root) {
|
|
21
21
|
const settings = syncSettings(root);
|
|
22
22
|
if (!settings) {
|
|
23
|
-
|
|
23
|
+
const earlier = earlierToken(root);
|
|
24
|
+
console.error(earlier ? `✗ ${earlier}` : `✗ no sync server: set ${ENV.url}, ${ENV.clientId} and ${ENV.clientSecret}, or url, clientId and clientSecret in .mainflow/${SETTINGS_FILE}`);
|
|
24
25
|
return 1;
|
|
25
26
|
}
|
|
26
27
|
const running = await probeDaemon(root);
|
|
@@ -28,13 +29,16 @@ async function syncRun(root) {
|
|
|
28
29
|
console.error(`✗ mainflow sync already runs for this hub (pid ${running.daemon.pid})${running.state === "silent" ? `; ${silentHint(running.daemon)}` : ""}`);
|
|
29
30
|
return 1;
|
|
30
31
|
}
|
|
32
|
+
const api = new ServerApi(settings);
|
|
31
33
|
try {
|
|
32
|
-
await
|
|
34
|
+
await api.check();
|
|
33
35
|
} catch (error) {
|
|
34
36
|
if (error instanceof ServerError) {
|
|
35
37
|
console.error(`✗ ${error.message} (hub ${settings.hub} at ${settings.url})`);
|
|
36
38
|
return 1;
|
|
37
39
|
}
|
|
40
|
+
} finally {
|
|
41
|
+
api.close();
|
|
38
42
|
}
|
|
39
43
|
let daemon;
|
|
40
44
|
try {
|
|
@@ -104,6 +108,96 @@ async function syncFlush(root, hook) {
|
|
|
104
108
|
console.log(`${result.problems.length} problems, ${result.queued} queued, ${result.online ? "online" : "offline"}`);
|
|
105
109
|
return result.problems.length > 0 ? 1 : 0;
|
|
106
110
|
}
|
|
111
|
+
/** How long a download may take before the command or the hook gives up on the sync client's answer. */
|
|
112
|
+
const FETCH_MS = 3e4;
|
|
113
|
+
/**
|
|
114
|
+
* What `target` names, as the sync client takes it: a mainline (m001) or a task (m001/t002), as its directory with every
|
|
115
|
+
* file under it, or a path, from the current directory or from .mainflow/. Null when it names nothing of the hub.
|
|
116
|
+
*/
|
|
117
|
+
function fetchSpec(root, target) {
|
|
118
|
+
const ref = /^(m\d{3})(?:\/(t\d{3}))?$/.exec(target);
|
|
119
|
+
if (ref) {
|
|
120
|
+
const dirOf = (parent, id) => existsSync(join(root, ".mainflow", parent)) ? readdirSync(join(root, DATA_DIR, parent)).find((name) => name.startsWith(`${id}-`)) : void 0;
|
|
121
|
+
const mainline = dirOf("mainlines", ref[1]);
|
|
122
|
+
if (!mainline) return null;
|
|
123
|
+
if (!ref[2]) return `mainlines/${mainline}/`;
|
|
124
|
+
const task = dirOf(`mainlines/${mainline}/tasks`, ref[2]);
|
|
125
|
+
return task ? `mainlines/${mainline}/tasks/${task}/` : null;
|
|
126
|
+
}
|
|
127
|
+
const inHub = (file) => {
|
|
128
|
+
const path = relative(join(root, DATA_DIR), file).split(sep).join("/");
|
|
129
|
+
return path === "" || path.startsWith("../") || path === ".." || isAbsolute(path) ? null : path;
|
|
130
|
+
};
|
|
131
|
+
return inHub(resolve(process.cwd(), target)) ?? (isAbsolute(target) ? null : inHub(join(root, ".mainflow", target)));
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Download files only on the sync server, the artifacts of closed mainlines an environment with on-demand downloads
|
|
135
|
+
* left there: one file, a directory, or a mainline's or task's, by its ID. With `hook`, read the PreToolUse payload from
|
|
136
|
+
* stdin instead: a Read, Edit, Write or MultiEdit of an artifact that is not here downloads it first, when the host runs
|
|
137
|
+
* the hook for it (Claude Code answers an Edit of a missing file before its hooks). A Read then goes ahead; a write is
|
|
138
|
+
* stopped with the reason, so the agent reads the file before writing over it. When it cannot be
|
|
139
|
+
* downloaded, or no sync client runs, the tool is stopped with the reason, so the agent does not take the file for one
|
|
140
|
+
* that does not exist. Anything else answers nothing.
|
|
141
|
+
*/
|
|
142
|
+
async function syncFetch(root, target, hook) {
|
|
143
|
+
if (hook) return syncFetchHook(root, readPayload());
|
|
144
|
+
if (target === void 0) {
|
|
145
|
+
console.error("✗ Usage: mainflow sync fetch <path|m|m/t> (or --hook for a PreToolUse hook)");
|
|
146
|
+
return 1;
|
|
147
|
+
}
|
|
148
|
+
const spec = fetchSpec(root, target);
|
|
149
|
+
if (spec === null) {
|
|
150
|
+
console.error(`✗ ${target} names no mainline, task or path in .mainflow/`);
|
|
151
|
+
return 1;
|
|
152
|
+
}
|
|
153
|
+
const daemon = readDaemon(root);
|
|
154
|
+
if (!daemon) {
|
|
155
|
+
console.error("✗ mainflow sync is not running for this hub; start it with mainflow sync, then fetch again");
|
|
156
|
+
return 1;
|
|
157
|
+
}
|
|
158
|
+
let result;
|
|
159
|
+
try {
|
|
160
|
+
result = await callDaemon(daemon, "/fetch", { paths: [spec] }, FETCH_MS);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
if (!(error instanceof DaemonUnavailable)) throw error;
|
|
163
|
+
console.error(error.silent ? `✗ mainflow sync (pid ${daemon.pid}) did not answer within ${FETCH_MS / 1e3} s: it is busy, as with a large snapshot or a slow download; try again shortly` : `✗ ${error.message}; start mainflow sync, then fetch again`);
|
|
164
|
+
return 1;
|
|
165
|
+
}
|
|
166
|
+
if (result.error) {
|
|
167
|
+
console.error(`✗ could not download ${spec}: ${result.error}`);
|
|
168
|
+
return 1;
|
|
169
|
+
}
|
|
170
|
+
for (const path of result.fetched) console.log(` ${path}`);
|
|
171
|
+
for (const path of result.gone) console.log(`! ${path} is no longer on the sync server`);
|
|
172
|
+
if (result.fetched.length === 0 && result.gone.length === 0) console.log(`✓ nothing to download: no file under ${spec} is only on the sync server`);
|
|
173
|
+
else console.log(`✓ downloaded ${result.fetched.length} file${result.fetched.length === 1 ? "" : "s"}`);
|
|
174
|
+
return 0;
|
|
175
|
+
}
|
|
176
|
+
async function syncFetchHook(root, payload) {
|
|
177
|
+
const plan = planFetch(root, payload);
|
|
178
|
+
if (!plan) return 0;
|
|
179
|
+
const deny = (reason) => {
|
|
180
|
+
console.log(fetchDenial(reason));
|
|
181
|
+
return 0;
|
|
182
|
+
};
|
|
183
|
+
const daemon = readDaemon(root);
|
|
184
|
+
let result = null;
|
|
185
|
+
let busy = false;
|
|
186
|
+
if (daemon) try {
|
|
187
|
+
result = await callDaemon(daemon, "/fetch", { paths: [plan.path] }, FETCH_MS);
|
|
188
|
+
} catch (error) {
|
|
189
|
+
busy = error instanceof DaemonUnavailable && error.silent;
|
|
190
|
+
}
|
|
191
|
+
if (!result) {
|
|
192
|
+
if (!(plan.path in readRemote(root))) return 0;
|
|
193
|
+
if (busy) return deny(`.mainflow/${plan.path} is only on the sync server and is not downloaded yet: mainflow sync did not answer within ${FETCH_MS / 1e3} s, as it is busy, such as with a large snapshot. Try again shortly, or run mainflow sync fetch ${plan.path}; the file exists, do not take it for missing.`);
|
|
194
|
+
return deny(`.mainflow/${plan.path} is only on the sync server and is not downloaded here, and mainflow sync is not running, so it cannot be downloaded now. Start mainflow sync and run mainflow sync fetch ${plan.path}; the file exists, do not take it for missing.`);
|
|
195
|
+
}
|
|
196
|
+
if (result.error) return deny(`.mainflow/${plan.path} is only on the sync server and could not be downloaded (${result.error}). Try again once mainflow sync status says online; the file exists, do not take it for missing.`);
|
|
197
|
+
if (!result.fetched.includes(plan.path)) return 0;
|
|
198
|
+
if (plan.write) return deny(`.mainflow/${plan.path} was only on the sync server and is downloaded now. Read it before writing to it, so the write starts from what it holds.`);
|
|
199
|
+
return 0;
|
|
200
|
+
}
|
|
107
201
|
async function syncStatus(root) {
|
|
108
202
|
const probe = await probeDaemon(root);
|
|
109
203
|
if (probe?.state === "silent") {
|
|
@@ -113,11 +207,14 @@ async function syncStatus(root) {
|
|
|
113
207
|
if (probe?.state === "running") {
|
|
114
208
|
const { status, daemon } = probe;
|
|
115
209
|
if (!status) console.log(`mainflow sync runs for this hub (pid ${daemon.pid}); it did not give its status`);
|
|
116
|
-
else console.log(`mainflow sync runs (pid ${daemon.pid}): ${status.online ? status.caughtUp ? "online, caught up" : "online, catching up" : "offline"}, ${status.queued} queued, atomic exchange ${status.swapping ? "on" : "off"}, ${status.problems} problems so far`);
|
|
210
|
+
else console.log(`mainflow sync runs (pid ${daemon.pid}): ${status.online ? status.caughtUp ? "online, caught up" : "online, catching up" : "offline"}, ${status.queued} queued, atomic exchange ${status.swapping ? "on" : "off"}, ${status.problems} problems so far${status.remote ? `, ${status.remote} file${status.remote === 1 ? "" : "s"} only on the server (mainflow sync fetch)` : ""}`);
|
|
211
|
+
if (status?.waitingForUpload) console.log("! the sync server has no copy of this hub, and this environment downloads on demand, so it does not upload its copy; run mainflow sync with on-demand downloads off in an environment that keeps every file of the hub to upload it");
|
|
117
212
|
return 0;
|
|
118
213
|
}
|
|
119
214
|
const queue = queuedCount(root);
|
|
120
215
|
console.log(`mainflow sync is not running; ${remoteMode(root) ? `${queue} write(s) wait in the outbox until it runs` : "writes go to the local files"}`);
|
|
216
|
+
const earlier = remoteMode(root) ? null : earlierToken(root);
|
|
217
|
+
if (earlier) console.error(`! ${earlier}`);
|
|
121
218
|
return 0;
|
|
122
219
|
}
|
|
123
220
|
//#endregion
|
|
@@ -801,7 +898,12 @@ const WRITES = {
|
|
|
801
898
|
function runWrite(ctx, spec) {
|
|
802
899
|
spec.check?.(ctx);
|
|
803
900
|
const input = spec.input(ctx);
|
|
804
|
-
if (!remoteMode(ctx.root))
|
|
901
|
+
if (!remoteMode(ctx.root)) {
|
|
902
|
+
const code = spec.print(ctx, applyOperation(nodeStorage(ctx.root), spec.op, input, { now: nowMinute() }).value);
|
|
903
|
+
const earlier = earlierToken(ctx.root);
|
|
904
|
+
if (earlier) console.error(`! ${earlier}`);
|
|
905
|
+
return code;
|
|
906
|
+
}
|
|
805
907
|
return hubWriter(ctx.root, process.env, { confirmMs: CONFIRM_MS })(spec.op, input).then((written) => {
|
|
806
908
|
const code = spec.print(ctx, written.value);
|
|
807
909
|
for (const line of writtenLines(written)) (line.startsWith("!") ? console.error : console.log)(line);
|
|
@@ -920,6 +1022,7 @@ const RUNS = {
|
|
|
920
1022
|
},
|
|
921
1023
|
sync: ({ root }) => syncRun(root),
|
|
922
1024
|
"sync flush": ({ root, opts }) => syncFlush(root, opts.hook ?? false),
|
|
1025
|
+
"sync fetch": ({ root, args, opts }) => syncFetch(root, args[0], opts.hook ?? false),
|
|
923
1026
|
"sync status": ({ root }) => syncStatus(root)
|
|
924
1027
|
};
|
|
925
1028
|
const COMMANDS = Object.fromEntries(Object.keys(COMMAND_SPECS).map((name) => [name, {
|
package/dist/facts.d.ts
CHANGED
|
@@ -342,7 +342,7 @@ export declare const COMMAND_SPECS: {
|
|
|
342
342
|
};
|
|
343
343
|
readonly sync: {
|
|
344
344
|
readonly usage: 'sync';
|
|
345
|
-
readonly summary: `Keep .mainflow/ in step with the hub on the sync server until stopped: pull its changes, send every local change. The server and token come from MAINFLOW_SYNC_URL and
|
|
345
|
+
readonly summary: `Keep .mainflow/ in step with the hub on the sync server until stopped: pull its changes, send every local change. The server and its Cloudflare Access service token come from MAINFLOW_SYNC_URL, MAINFLOW_SYNC_CLIENT_ID and MAINFLOW_SYNC_CLIENT_SECRET, or url, clientId and clientSecret in .mainflow/local/sync.json. On connecting it downloads only the files that differ from the versions it last synced; with MAINFLOW_SYNC_ON_DEMAND=1, or onDemand: true in sync.json, it leaves the artifacts of done and dropped mainlines on the server until they are used (sync fetch). While it runs, or with a server and token set, every mainflow write is applied here and put in the outbox, which it sends in order; a writing command waits up to 15 s for the server's answer`;
|
|
346
346
|
readonly args: 0;
|
|
347
347
|
readonly needsHub: true;
|
|
348
348
|
};
|
|
@@ -352,6 +352,13 @@ export declare const COMMAND_SPECS: {
|
|
|
352
352
|
readonly args: 0;
|
|
353
353
|
readonly needsHub: true;
|
|
354
354
|
};
|
|
355
|
+
readonly 'sync fetch': {
|
|
356
|
+
readonly usage: 'sync fetch [<path|m|m/t>] [--hook]';
|
|
357
|
+
readonly summary: 'Download files only on the sync server: with MAINFLOW_SYNC_ON_DEMAND, or onDemand in .mainflow/local/sync.json, the artifacts of done and dropped mainlines stay there until used. Give a file or directory (from here or from .mainflow/), or a mainline or task ID for all of its artifacts; mainflow sync must be running. Grep and Glob do not see a file that is not downloaded, so fetch the mainline before searching it. --hook reads a PreToolUse payload from stdin: a Read or Write of such a file downloads it first (Claude Code 2.1.285 answers an Edit of a missing file before any hook runs, so Read it or fetch it before editing); a Write, a failed download, or a mainflow sync that does not run or does not answer within 30 s answers deny with the reason';
|
|
358
|
+
readonly args: 0;
|
|
359
|
+
readonly optionalArgs: 1;
|
|
360
|
+
readonly needsHub: true;
|
|
361
|
+
};
|
|
355
362
|
readonly 'sync status': {
|
|
356
363
|
readonly usage: 'sync status';
|
|
357
364
|
readonly summary: 'Say whether mainflow sync runs for this hub, whether it is online and caught up with the server, and how many writes wait to be sent';
|
package/dist/facts.js
CHANGED
|
@@ -332,7 +332,7 @@ const COMMAND_SPECS = {
|
|
|
332
332
|
},
|
|
333
333
|
sync: {
|
|
334
334
|
usage: "sync",
|
|
335
|
-
summary: `Keep .mainflow/ in step with the hub on the sync server until stopped: pull its changes, send every local change. The server and token come from MAINFLOW_SYNC_URL and
|
|
335
|
+
summary: `Keep .mainflow/ in step with the hub on the sync server until stopped: pull its changes, send every local change. The server and its Cloudflare Access service token come from MAINFLOW_SYNC_URL, MAINFLOW_SYNC_CLIENT_ID and MAINFLOW_SYNC_CLIENT_SECRET, or url, clientId and clientSecret in .mainflow/local/sync.json. On connecting it downloads only the files that differ from the versions it last synced; with MAINFLOW_SYNC_ON_DEMAND=1, or onDemand: true in sync.json, it leaves the artifacts of done and dropped mainlines on the server until they are used (sync fetch). While it runs, or with a server and token set, every mainflow write is applied here and put in the outbox, which it sends in order; a writing command waits up to 15 s for the server's answer`,
|
|
336
336
|
args: 0,
|
|
337
337
|
needsHub: true
|
|
338
338
|
},
|
|
@@ -342,6 +342,13 @@ const COMMAND_SPECS = {
|
|
|
342
342
|
args: 0,
|
|
343
343
|
needsHub: true
|
|
344
344
|
},
|
|
345
|
+
"sync fetch": {
|
|
346
|
+
usage: "sync fetch [<path|m|m/t>] [--hook]",
|
|
347
|
+
summary: "Download files only on the sync server: with MAINFLOW_SYNC_ON_DEMAND, or onDemand in .mainflow/local/sync.json, the artifacts of done and dropped mainlines stay there until used. Give a file or directory (from here or from .mainflow/), or a mainline or task ID for all of its artifacts; mainflow sync must be running. Grep and Glob do not see a file that is not downloaded, so fetch the mainline before searching it. --hook reads a PreToolUse payload from stdin: a Read or Write of such a file downloads it first (Claude Code 2.1.285 answers an Edit of a missing file before any hook runs, so Read it or fetch it before editing); a Write, a failed download, or a mainflow sync that does not run or does not answer within 30 s answers deny with the reason",
|
|
348
|
+
args: 0,
|
|
349
|
+
optionalArgs: 1,
|
|
350
|
+
needsHub: true
|
|
351
|
+
},
|
|
345
352
|
"sync status": {
|
|
346
353
|
usage: "sync status",
|
|
347
354
|
summary: "Say whether mainflow sync runs for this hub, whether it is online and caught up with the server, and how many writes wait to be sent",
|
package/dist/hooks.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { $ as describeRequest, j as configSchema } from "./dist-IdbnCDqM.js";
|
|
2
|
-
import { D as endsWithMarker, E as decorateReply, H as loadHub, O as headerLine, S as readSession, T as matchGrants, V as HubError, Z as readYaml,
|
|
3
|
-
import { n as hubWriter, r as writtenLines } from "./operation-
|
|
2
|
+
import { D as endsWithMarker, E as decorateReply, H as loadHub, O as headerLine, S as readSession, T as matchGrants, V as HubError, Z as readYaml, at as findHubRoot, b as turnReminder, ct as DATA_DIR, i as hostName, it as currentBranch, k as mayDecorate, ot as worktreesOf, w as grantScope, x as loadSessions, y as sessionBriefing } from "./host-CdB_vDY_.js";
|
|
3
|
+
import { n as hubWriter, r as writtenLines } from "./operation-BmysMg4R.js";
|
|
4
4
|
import { resolveFixed, scanCommand, scopedCwd } from "./grant-commands.js";
|
|
5
5
|
import { appendFileSync, existsSync, readFileSync } from "node:fs";
|
|
6
6
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
@@ -328,17 +328,54 @@ function requestTime(instant = /* @__PURE__ */ new Date()) {
|
|
|
328
328
|
}
|
|
329
329
|
/** The routes, below the server's origin. `{hub}` is the hub's name, like carlos-space. */
|
|
330
330
|
const ROUTES = {
|
|
331
|
-
/** GET
|
|
331
|
+
/** GET: the protocol version this server speaks, `{ protocol }` */
|
|
332
332
|
version: "/version",
|
|
333
333
|
/** POST one request (SyncRequest), answered with its response and broadcast to the hub's connections when applied */
|
|
334
334
|
request: (hub) => `/hub/${hub}/request`,
|
|
335
|
-
/**
|
|
335
|
+
/** POST: one batch of the hub's snapshot (SnapshotRequest, answered with SnapshotBatch). PUT: upload a whole hub (UploadRequest) */
|
|
336
336
|
snapshot: (hub) => `/hub/${hub}/snapshot`,
|
|
337
|
-
/** GET `?protocol=
|
|
337
|
+
/** GET `?protocol=3` with `Upgrade: websocket`: receive every broadcast of the hub (ServerBroadcast) */
|
|
338
338
|
socket: (hub) => `/hub/${hub}/ws`
|
|
339
339
|
};
|
|
340
|
-
/**
|
|
341
|
-
|
|
340
|
+
/**
|
|
341
|
+
* The service token of Cloudflare Access, which stands in front of the sync server and lets through only the requests
|
|
342
|
+
* that carry a valid one: every request, the WebSocket's upgrade included, sends both headers. The sync server itself
|
|
343
|
+
* checks no credentials, so one deployed without Access in front serves anyone who knows its address.
|
|
344
|
+
*/
|
|
345
|
+
const ACCESS_HEADERS = {
|
|
346
|
+
clientId: "CF-Access-Client-Id",
|
|
347
|
+
clientSecret: "CF-Access-Client-Secret"
|
|
348
|
+
};
|
|
349
|
+
/**
|
|
350
|
+
* The order of paths in a snapshot: by Unicode code point, which is the order of their UTF-8 bytes and so the order
|
|
351
|
+
* SQLite gives TEXT. JavaScript compares strings by UTF-16 code unit, which differs only where a character beyond
|
|
352
|
+
* U+FFFF meets one from U+E000 to U+FFFF.
|
|
353
|
+
*/
|
|
354
|
+
function comparePaths(a, b) {
|
|
355
|
+
const length = Math.min(a.length, b.length);
|
|
356
|
+
for (let i = 0; i < length; i++) {
|
|
357
|
+
let x = a.charCodeAt(i);
|
|
358
|
+
let y = b.charCodeAt(i);
|
|
359
|
+
if (x === y) continue;
|
|
360
|
+
if (x >= 55296 && y >= 55296) {
|
|
361
|
+
x += x < 57344 ? 8192 : -2048;
|
|
362
|
+
y += y < 57344 ? 8192 : -2048;
|
|
363
|
+
}
|
|
364
|
+
return x - y;
|
|
365
|
+
}
|
|
366
|
+
return a.length - b.length;
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* The close the sync server ends a WebSocket with once the connection is older than the server lets one live, instead
|
|
370
|
+
* of sending it a broadcast. Cloudflare Access checks the service token only on the upgrade, so a connection opened
|
|
371
|
+
* before its token was revoked would otherwise go on receiving the hub; the client reconnects at once, through Access
|
|
372
|
+
* again, and the snapshot on the new connection brings what changed meanwhile. 4000 is the first of the codes RFC 6455
|
|
373
|
+
* leaves to applications.
|
|
374
|
+
*/
|
|
375
|
+
const SOCKET_MAX_AGE_CLOSE = {
|
|
376
|
+
code: 4e3,
|
|
377
|
+
reason: "the connection reached its maximum age; reconnect"
|
|
378
|
+
};
|
|
342
379
|
//#endregion
|
|
343
380
|
//#region ../core/dist/storage/node.js
|
|
344
381
|
const LOCK_WAIT_MS = 5e3;
|
|
@@ -7297,4 +7334,4 @@ function openInApp(hub, target, closeAfterMs = 150) {
|
|
|
7297
7334
|
return true;
|
|
7298
7335
|
}
|
|
7299
7336
|
//#endregion
|
|
7300
|
-
export {
|
|
7337
|
+
export { ACCESS_HEADERS as $, attributeTrace as A, checkHubName as B, transcriptPath as C, endsWithMarker as D, decorateReply as E, renumberFiles as F, linksInto as G, loadHub as H, renumberPath as I, TASK_FILE as J, refIndex as K, renumberText as L, transcriptTitle as M, applyOffline as N, headerLine as O, renumberRequest as P, nodeStorage as Q, scopeOfPath as R, readSession as S, matchGrants as T, dependencyGraph as U, HubError as V, mainlineEdges as W, nowMinute as X, requireSlug as Y, readYaml as Z, untilState as _, openInApp as a, findHubRoot as at, turnReminder as b, syncHost as c, DATA_DIR as ct, retroReportFromReceiver as d, splitFrontMatter as dt, ROUTES as et, receiverFromEnv as f, initHub as g, setArchivedMany as h, hostName as i, currentBranch as it, traceTranscript as j, mayDecorate as k, migrateHub as l, WriteError as lt, ARCHIVE_KINDS as m, DEFAULT_UI_PORT as n, comparePaths as nt, removeHostLink as o, worktreesOf as ot, RETRY_RULES as p, MAINLINE_FILE as q, caddySites as r, requestTime as rt, siteBlock as s, ConflictError as st, APP_BUNDLE_ID as t, SOCKET_MAX_AGE_CLOSE as tt, retroReport as u, contentHash as ut, duplicateClaimLines as v, grantScope as w, loadSessions as x, sessionBriefing as y, applyOperation as z };
|
package/dist/host.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as openInApp, c as syncHost, i as hostName, n as DEFAULT_UI_PORT, o as removeHostLink, r as caddySites, s as siteBlock, t as APP_BUNDLE_ID } from "./host-
|
|
1
|
+
import { a as openInApp, c as syncHost, i as hostName, n as DEFAULT_UI_PORT, o as removeHostLink, r as caddySites, s as siteBlock, t as APP_BUNDLE_ID } from "./host-CdB_vDY_.js";
|
|
2
2
|
export { APP_BUNDLE_ID, DEFAULT_UI_PORT, caddySites, hostName, openInApp, removeHostLink, siteBlock, syncHost };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as syncHost,
|
|
1
|
+
import { c as syncHost, ct as DATA_DIR, i as hostName, lt as WriteError, n as DEFAULT_UI_PORT, o as removeHostLink } from "./host-CdB_vDY_.js";
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { spawn, spawnSync } from "node:child_process";
|
|
@@ -176,7 +176,7 @@ async function launchUi(root, hub, configured, options) {
|
|
|
176
176
|
}
|
|
177
177
|
let server;
|
|
178
178
|
if (!reuse) {
|
|
179
|
-
const { serveUi } = await import("./ui-
|
|
179
|
+
const { serveUi } = await import("./ui-DlOY5qEU.js");
|
|
180
180
|
server = await serveUi(root, choice.port);
|
|
181
181
|
writeUiRecord(root, {
|
|
182
182
|
port: choice.port,
|
package/dist/launch.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as portCandidates, c as watchHubGone, i as networkProbe, n as choosePort, o as readUiRecord, r as launchUi, s as startUiInBackground, t as PORT_SPAN } from "./launch-
|
|
1
|
+
import { a as portCandidates, c as watchHubGone, i as networkProbe, n as choosePort, o as readUiRecord, r as launchUi, s as startUiInBackground, t as PORT_SPAN } from "./launch-D1zaGo7W.js";
|
|
2
2
|
export { PORT_SPAN, choosePort, launchUi, networkProbe, portCandidates, readUiRecord, startUiInBackground, watchHubGone };
|
|
Binary file
|
|
Binary file
|