gutterpress 0.10.0 → 0.10.1-beta.2
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/README.md +3 -16
- package/dist/api/index.d.ts +10 -19
- package/dist/api/index.js +8 -64
- package/dist/{audit-cvarpa72.js → audit-dce99css.js} +5 -5
- package/dist/{build-6r502chw.js → build-bj4x0727.js} +5 -5
- package/dist/checks/source/index.d.ts +1 -0
- package/dist/checks/source/merge-markers.d.ts +3 -0
- package/dist/{cli-cqtggsng.js → cli-0z6zf98d.js} +2 -47
- package/dist/{cli-eq5naw4m.js → cli-7n848af2.js} +370 -1249
- package/dist/{cli-zfcryxg8.js → cli-eewynen9.js} +97 -60
- package/dist/{cli-k1065rkg.js → cli-w692g6dx.js} +75 -29
- package/dist/{cli-149edp6b.js → cli-ydpf09d9.js} +1 -1
- package/dist/cli.js +14 -15
- package/dist/{doctor-akvxbtjb.js → doctor-7nd6n2aa.js} +2 -2
- package/dist/{engine-ft4cr3ep.js → engine-57vw8zpd.js} +1 -1
- package/dist/{engine-b159tbns.js → engine-8kdwy0jz.js} +2 -2
- package/dist/{gutterpress-viewer-te8g5grx.js → gutterpress-viewer-tma1qwz2.js} +57 -15
- package/dist/{index-ycpvr0am.js → index-7hrvksrb.js} +4 -4
- package/dist/{index-mdefp0y5.js → index-dtrftayr.js} +75 -27
- package/dist/{index-ge7q9xj3.js → index-h1x7krkg.js} +771 -1482
- package/dist/{index-wq3r5pj7.js → index-p0ebr7dh.js} +94 -57
- package/dist/index.js +9 -65
- package/dist/lib/git-fs.d.ts +43 -0
- package/dist/lib/host-policy.d.ts +14 -2
- package/dist/lib/remote-auth/clone.d.ts +0 -21
- package/dist/lib/remote-auth/converge-merge.d.ts +24 -7
- package/dist/lib/remote-auth/generic-auth.d.ts +0 -22
- package/dist/lib/remote-auth/git-http.d.ts +1 -1
- package/dist/lib/remote-auth/github-auth.d.ts +2 -22
- package/dist/lib/remote-auth/operation-log.d.ts +4 -9
- package/dist/lib/remote-auth/sync-messages.d.ts +35 -12
- package/dist/lib/remote-auth/sync-types.d.ts +30 -101
- package/dist/lib/remote-auth/sync.d.ts +19 -21
- package/dist/lib/remote-auth/token-store.d.ts +0 -10
- package/dist/lib/remote-auth/transport.d.ts +42 -40
- package/dist/lib/source-provider.d.ts +12 -0
- package/dist/{lint-p2sw53d9.js → lint-c8ze8sqw.js} +5 -5
- package/dist/{new-hvq0x91q.js → new-xy2qc300.js} +5 -5
- package/dist/{plugin-pssmk0dx.js → plugin-g9hwzjsm.js} +5 -5
- package/dist/{preflight-4j00yd0g.js → preflight-prcvdvmk.js} +5 -5
- package/dist/{preview-bridge-fz7vpk8m.js → preview-bridge-2nnrs2hy.js} +15 -0
- package/dist/{preview-d4s6gk6p.js → preview-h479apj9.js} +5 -5
- package/dist/{preview-interface-0ssk8bmm.js → preview-interface-2f5v96hd.js} +411 -88
- package/dist/{preview-shell-c5mfa3q0.js → preview-shell-kyjq59d9.js} +80 -25
- package/dist/{publish-tkc26en7.js → publish-emk644a8.js} +5 -5
- package/dist/{source-provider-3tcj6qg2.js → source-provider-2j5x2hyw.js} +1 -1
- package/dist/{source-provider-vanafrt9.js → source-provider-kn41jmbs.js} +1 -1
- package/dist/{validate-w9vfefrw.js → validate-be54bdc6.js} +5 -5
- package/package.json +1 -1
- package/dist/lib/app-heartbeat.d.ts +0 -102
- package/dist/lib/remote-auth/image-clash.d.ts +0 -17
- package/dist/lib/remote-auth/recovery/classify.d.ts +0 -97
- package/dist/lib/remote-auth/recovery/inspect.d.ts +0 -75
- package/dist/lib/remote-auth/recovery/locks.d.ts +0 -20
- package/dist/lib/remote-auth/recovery/repair.d.ts +0 -29
- package/dist/lib/remote-auth/recovery/types.d.ts +0 -39
- package/dist/repair-2va4w12t.js +0 -137
|
@@ -17,19 +17,6 @@ export interface HostCallbacks {
|
|
|
17
17
|
/** Optional cancellation (user closed the dialog). */
|
|
18
18
|
signal?: AbortSignal;
|
|
19
19
|
}
|
|
20
|
-
/**
|
|
21
|
-
* Per-host auth-acquisition plugin contract (ADR 0006 D3 layer 3). github.com
|
|
22
|
-
* gets the device flow below; every other host gets the generic token flow
|
|
23
|
-
* (#14, not in this change).
|
|
24
|
-
*/
|
|
25
|
-
export interface RemoteAuthProvider {
|
|
26
|
-
/** Does this provider handle the given remote host? */
|
|
27
|
-
matches(origin: URL): boolean;
|
|
28
|
-
/** Interactive flow producing a credential for the host. */
|
|
29
|
-
connect(callbacks: HostCallbacks): Promise<HostCredential>;
|
|
30
|
-
/** Cheap revalidation for stored credentials. */
|
|
31
|
-
validate(credential: HostCredential): Promise<boolean>;
|
|
32
|
-
}
|
|
33
20
|
/**
|
|
34
21
|
* Client id resolution: explicit option → env var → registered default.
|
|
35
22
|
* Empty/whitespace-only values at any layer are treated as unset (the packaged
|
|
@@ -48,20 +35,13 @@ export interface GitHubAuthProviderOptions {
|
|
|
48
35
|
/** Injectable sleep for tests (so polling tests run instantly). */
|
|
49
36
|
sleepImpl?: (ms: number) => Promise<void>;
|
|
50
37
|
}
|
|
51
|
-
/** GitHub device-flow
|
|
52
|
-
export declare class GitHubAuthProvider
|
|
38
|
+
/** GitHub device-flow credential acquisition (ADR 0006 D1). */
|
|
39
|
+
export declare class GitHubAuthProvider {
|
|
53
40
|
private readonly clientId;
|
|
54
41
|
private readonly fetchImpl;
|
|
55
42
|
private readonly sleep;
|
|
56
43
|
constructor(options?: GitHubAuthProviderOptions);
|
|
57
|
-
matches(origin: URL): boolean;
|
|
58
44
|
connect(callbacks: HostCallbacks): Promise<HostCredential>;
|
|
59
|
-
/**
|
|
60
|
-
* Cheap credential revalidation against `GET /user`. Returns `false` on any
|
|
61
|
-
* non-200 (401 revoked/expired, 403 forbidden/rate-limit-blocked, …);
|
|
62
|
-
* network failures are treated as "can't tell" → `true`.
|
|
63
|
-
*/
|
|
64
|
-
validate(credential: HostCredential): Promise<boolean>;
|
|
65
45
|
/** Best-effort login lookup after auth — failure is non-fatal. */
|
|
66
46
|
private fetchUsername;
|
|
67
47
|
}
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
* [ISO-timestamp] LEVEL operation: step=<step> key=value ... | <message>
|
|
21
21
|
*
|
|
22
22
|
* Example:
|
|
23
|
-
* [2026-06-19T12:34:56.789Z] INFO
|
|
24
|
-
* [2026-06-19T12:34:56.890Z] INFO
|
|
25
|
-
* [2026-06-19T12:34:57.789Z] WARN
|
|
26
|
-
* [2026-06-19T12:34:57.890Z] INFO
|
|
23
|
+
* [2026-06-19T12:34:56.789Z] INFO sync: repo=my-book branch=main | starting sync
|
|
24
|
+
* [2026-06-19T12:34:56.890Z] INFO sync: step=snapshot | committed 3 changed files
|
|
25
|
+
* [2026-06-19T12:34:57.789Z] WARN sync: step=merge | combined files=manifest.yaml,notes.md
|
|
26
|
+
* [2026-06-19T12:34:57.890Z] INFO sync: result=synced | pushed to the online copy
|
|
27
27
|
*
|
|
28
28
|
* Cross-platform: uses `node:fs` appendFileSync + `node:path`. The caller
|
|
29
29
|
* is responsible for providing a valid directory (the logger creates the
|
|
@@ -59,8 +59,3 @@ export declare function createFileLogger(logFile: string, operation: string, min
|
|
|
59
59
|
* don't pass `logFile` see zero behavior change and zero file I/O).
|
|
60
60
|
*/
|
|
61
61
|
export declare function resolveLogger(logFile: string | undefined, operation: string): OperationLogger;
|
|
62
|
-
/**
|
|
63
|
-
* Shorten a 40-char OID to the first 7 characters for compact log lines.
|
|
64
|
-
* Same convention as `git log --oneline`.
|
|
65
|
-
*/
|
|
66
|
-
export declare function shortOid(oid: string): string;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Author-language copy for sync
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Author-language copy for sync (never raw git words). Extracted from sync.ts
|
|
3
|
+
* so the transport and orchestrator modules share ONE source of these
|
|
4
|
+
* strings — their exact wording is part of the observable contract (the host
|
|
5
|
+
* renders them verbatim).
|
|
6
6
|
*/
|
|
7
7
|
export declare const MSG_UP_TO_DATE = "Everything is in sync.";
|
|
8
8
|
export declare const MSG_UP_TO_DATE_PULLED = "Everything is in sync. The latest online changes were downloaded to this computer.";
|
|
@@ -12,14 +12,37 @@ export declare const MSG_OFFLINE = "Your changes are saved on this computer. gut
|
|
|
12
12
|
export declare const MSG_AUTH = "The online repository didn't accept the saved connection. Reconnect and try again.";
|
|
13
13
|
export declare const MSG_INSECURE_TRANSPORT = "This project's online address isn't secure, so the saved connection wasn't sent \u2014 connections are never sent over an insecure address. Switch the address to a secure one (starting with https), or to a local loopback address for a server on this computer, to sync.";
|
|
14
14
|
export declare const MSG_BUSY = "The online copy is changing very quickly right now. Your work is saved on this computer \u2014 try Sync again in a moment.";
|
|
15
|
-
|
|
15
|
+
/**
|
|
16
|
+
* The project's own version history is unreadable — a damaged or missing
|
|
17
|
+
* `.git`. Says the three things that are actually true and useful: the writing
|
|
18
|
+
* is safe, the HISTORY is what broke, and (sync always has a remote, so this
|
|
19
|
+
* always applies here) a fresh copy from online is the way back.
|
|
20
|
+
*/
|
|
21
|
+
export declare const MSG_HISTORY_UNREADABLE = "This project's version history can't be read, so syncing can't continue. Your writing is safe \u2014 every file is still here on this computer. To get history and syncing working again, download a fresh copy of the project from online, then move any recent changes into it.";
|
|
22
|
+
export declare const MSG_UNRELATED = "This project and the online copy have no history in common, so they can't be combined. Either the online address points at a different project, or this project's own history was lost and started over. Your writing is safe \u2014 every file is still here on this computer. Check the online address; if it is correct, download a fresh copy from online and move any recent changes into it.";
|
|
16
23
|
export declare const MSG_NO_REMOTE = "This project isn't connected to an online repository yet.";
|
|
17
24
|
export declare const MSG_SSH_REMOTE = "This project's online address uses SSH (git@\u2026), which gutterpress can't sync to. Switch it to the web (HTTPS) address to sync from here.";
|
|
18
25
|
export declare const MSG_NO_BRANCH = "This project's version history isn't on a named branch, so it can't be synced right now.";
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
export declare const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
/** Message recorded on the automatic pre-sync snapshot (D5 invariant).
|
|
27
|
+
* Pre-0.10.1 history carries the old spelling "Snapshot before syncing". */
|
|
28
|
+
export declare const SYNC_SNAPSHOT_MESSAGE = "Automatic backup of your work";
|
|
29
|
+
/**
|
|
30
|
+
* Message recorded when an edit reached disk while the fetch was in flight —
|
|
31
|
+
* after the pre-sync snapshot, before the merge (see the re-snapshot in
|
|
32
|
+
* `syncProject`). Distinct wording so the race is legible in history; the
|
|
33
|
+
* copy is already writer-voiced, so it deliberately stays unchanged (every
|
|
34
|
+
* rename permanently grows the timeline's superseded-spelling list).
|
|
35
|
+
*/
|
|
36
|
+
export declare const SYNC_LATE_EDIT_MESSAGE = "Saved the edit you made while syncing";
|
|
37
|
+
/**
|
|
38
|
+
* The keep-both sibling contract, kept HERE rather than in converge-merge.ts
|
|
39
|
+
* because every consumer needs it and converge-merge pulls in isomorphic-git:
|
|
40
|
+
* the markdown file resolver must not render a sibling as a chapter, and the
|
|
41
|
+
* merge-marker check must report it. This file imports nothing, so any of
|
|
42
|
+
* them can depend on it.
|
|
43
|
+
*
|
|
44
|
+
* `art/cover.png` -> `art/cover.online.png`; extensionless `NOTES` -> `NOTES.online`.
|
|
45
|
+
*/
|
|
46
|
+
export declare function onlineSiblingPath(filepath: string): string;
|
|
47
|
+
/** True for a path `onlineSiblingPath` produced — Gutterpress's artifact, not the author's. */
|
|
48
|
+
export declare function isOnlineSibling(filepath: string): boolean;
|
|
@@ -16,26 +16,21 @@ import type { HostCredential, TokenStore } from "./token-store.ts";
|
|
|
16
16
|
*/
|
|
17
17
|
export type GitCache = Record<string, unknown>;
|
|
18
18
|
/**
|
|
19
|
-
* One
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* can never go stale.
|
|
19
|
+
* One file that changed on both sides and cannot carry conflict markers (a
|
|
20
|
+
* binary, or an SVG whose XML markers would break). Both versions are on
|
|
21
|
+
* disk and committed: ours stayed at `path`, the online one was written
|
|
22
|
+
* beside it. The host names the pair so the writer can fix it by hand.
|
|
24
23
|
*/
|
|
25
|
-
export interface
|
|
26
|
-
/** Repo-relative path
|
|
24
|
+
export interface KeptBothFile {
|
|
25
|
+
/** Repo-relative path holding OUR version (unchanged). */
|
|
27
26
|
path: string;
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
-
/** Blob oid of the online version. */
|
|
31
|
-
remoteOid: string;
|
|
32
|
-
/** Which side the automatic newer-wins policy kept on disk. */
|
|
33
|
-
kept: "local" | "online";
|
|
27
|
+
/** Repo-relative path holding the ONLINE version (`name.online.ext`). */
|
|
28
|
+
onlinePath: string;
|
|
34
29
|
}
|
|
35
30
|
/** Outcome of a sync attempt. Sync always converges — there is no "conflict"
|
|
36
31
|
* arm: overlapping text edits land in the file inside standard git conflict
|
|
37
|
-
* markers (`combinedFiles`), clashing binaries keep
|
|
38
|
-
* (`
|
|
32
|
+
* markers (`combinedFiles`), clashing binaries keep BOTH versions as two
|
|
33
|
+
* files (`keptBothFiles`), and every version is always reachable in
|
|
39
34
|
* history. Owner ruling 2026-08-14. */
|
|
40
35
|
export type SyncOutcome = {
|
|
41
36
|
status: "synced";
|
|
@@ -48,13 +43,17 @@ export type SyncOutcome = {
|
|
|
48
43
|
filesChanged?: boolean;
|
|
49
44
|
/** Files whose text now holds BOTH versions inside git conflict markers. */
|
|
50
45
|
combinedFiles?: string[];
|
|
51
|
-
/**
|
|
52
|
-
|
|
46
|
+
/** Files kept as a pair (ours at `path`, theirs at `onlinePath`). */
|
|
47
|
+
keptBothFiles?: KeptBothFile[];
|
|
53
48
|
} | {
|
|
54
49
|
status: "up-to-date";
|
|
55
50
|
message: string;
|
|
56
51
|
snapshotId?: string;
|
|
57
52
|
filesChanged?: boolean;
|
|
53
|
+
/** A pull-merge-only pass (`push: false`) can combine overlapping edits
|
|
54
|
+
* and then hold the push — the converge report still surfaces here. */
|
|
55
|
+
combinedFiles?: string[];
|
|
56
|
+
keptBothFiles?: KeptBothFile[];
|
|
58
57
|
} | {
|
|
59
58
|
status: "auth";
|
|
60
59
|
message: string;
|
|
@@ -68,90 +67,9 @@ export type SyncOutcome = {
|
|
|
68
67
|
} | {
|
|
69
68
|
status: "error";
|
|
70
69
|
message: string;
|
|
71
|
-
/**
|
|
72
|
-
* Stable machine-readable signal so a host UI can route without
|
|
73
|
-
* string-matching `message` (which stays free to reword):
|
|
74
|
-
* "needs-connection-setup" — the project isn't set up right (no
|
|
75
|
-
* remote / SSH remote / no named branch); route to the connect/setup
|
|
76
|
-
* surface.
|
|
77
|
-
*/
|
|
78
|
-
code?: "needs-connection-setup";
|
|
79
70
|
snapshotId?: string;
|
|
80
71
|
filesChanged?: boolean;
|
|
81
72
|
};
|
|
82
|
-
/**
|
|
83
|
-
* Outcome of a pull-only attempt ({@link pullChanges}): fetch + fast-forward/
|
|
84
|
-
* converge-merge of the online changes, NEVER a push. The merge always lands
|
|
85
|
-
* (see converge-merge.ts) — there is no "conflict" arm.
|
|
86
|
-
*/
|
|
87
|
-
export type PullOutcome = {
|
|
88
|
-
status: "pulled";
|
|
89
|
-
message: string;
|
|
90
|
-
/** Snapshot taken of unsaved work before pulling, if any. */
|
|
91
|
-
snapshotId?: string;
|
|
92
|
-
/**
|
|
93
|
-
* True when the pull created a combine (merge) commit — local commits
|
|
94
|
-
* existed alongside the online ones. False for a plain fast-forward.
|
|
95
|
-
*/
|
|
96
|
-
merged: boolean;
|
|
97
|
-
/**
|
|
98
|
-
* True when the working tree CONTENT changed (the tip's tree differs
|
|
99
|
-
* from before) — the host should refresh its preview.
|
|
100
|
-
*/
|
|
101
|
-
filesChanged: boolean;
|
|
102
|
-
/** Files whose text now holds BOTH versions inside git conflict markers. */
|
|
103
|
-
combinedFiles?: string[];
|
|
104
|
-
/** Clashing images (newer kept) for the host's non-blocking picker. */
|
|
105
|
-
imageClashes?: ImageClash[];
|
|
106
|
-
} | {
|
|
107
|
-
status: "up-to-date";
|
|
108
|
-
message: string;
|
|
109
|
-
snapshotId?: string;
|
|
110
|
-
} | {
|
|
111
|
-
status: "auth";
|
|
112
|
-
message: string;
|
|
113
|
-
snapshotId?: string;
|
|
114
|
-
} | {
|
|
115
|
-
status: "offline";
|
|
116
|
-
message: string;
|
|
117
|
-
snapshotId?: string;
|
|
118
|
-
} | {
|
|
119
|
-
status: "error";
|
|
120
|
-
message: string;
|
|
121
|
-
snapshotId?: string;
|
|
122
|
-
};
|
|
123
|
-
/**
|
|
124
|
-
* Outcome of a push-only attempt ({@link pushChanges}): snapshot-if-needed,
|
|
125
|
-
* then push — NEVER a merge. When the online copy has commits this computer
|
|
126
|
-
* doesn't have, the result is the typed `"pull-first"` status (the host shows
|
|
127
|
-
* a plain-language "get the latest changes first" message) — pushChanges
|
|
128
|
-
* never auto-merges.
|
|
129
|
-
*/
|
|
130
|
-
export type PushOutcome = {
|
|
131
|
-
status: "pushed";
|
|
132
|
-
message: string;
|
|
133
|
-
snapshotId?: string;
|
|
134
|
-
} | {
|
|
135
|
-
status: "up-to-date";
|
|
136
|
-
message: string;
|
|
137
|
-
snapshotId?: string;
|
|
138
|
-
} | {
|
|
139
|
-
status: "pull-first";
|
|
140
|
-
message: string;
|
|
141
|
-
snapshotId?: string;
|
|
142
|
-
} | {
|
|
143
|
-
status: "auth";
|
|
144
|
-
message: string;
|
|
145
|
-
snapshotId?: string;
|
|
146
|
-
} | {
|
|
147
|
-
status: "offline";
|
|
148
|
-
message: string;
|
|
149
|
-
snapshotId?: string;
|
|
150
|
-
} | {
|
|
151
|
-
status: "error";
|
|
152
|
-
message: string;
|
|
153
|
-
snapshotId?: string;
|
|
154
|
-
};
|
|
155
73
|
export interface SyncProjectOptions {
|
|
156
74
|
projectDir: string;
|
|
157
75
|
/** Explicit credential; wins over the token store. */
|
|
@@ -162,12 +80,23 @@ export interface SyncProjectOptions {
|
|
|
162
80
|
message?: string;
|
|
163
81
|
authorName?: string;
|
|
164
82
|
authorEmail?: string;
|
|
83
|
+
/**
|
|
84
|
+
* When `false`, run a pull-merge-only pass: fetch + converge-merge exactly
|
|
85
|
+
* as always (remote work still arrives promptly), but hold the network push
|
|
86
|
+
* — and mint no snapshot unless the remote moved and the merge needs one
|
|
87
|
+
* (D5), so a quiet pass creates no commit at all. Local work stays safely
|
|
88
|
+
* committed/on disk for the next push-enabled pass to send. Default `true`:
|
|
89
|
+
* the full snapshot-first → fetch → merge → push pass. Owner decision
|
|
90
|
+
* 2026-08-23 (push cadence): the desktop's 2-minute tick pulls every time
|
|
91
|
+
* and enables the push only every ~15 minutes and on app exit.
|
|
92
|
+
*/
|
|
93
|
+
push?: boolean;
|
|
165
94
|
/** Injectable git HTTP transport for tests. */
|
|
166
95
|
httpClient?: typeof httpNode;
|
|
167
96
|
/**
|
|
168
|
-
* Bounded retry policy for {@link syncProject}'s
|
|
169
|
-
* loop is ALWAYS bounded (never infinite) and the snapshot-first
|
|
170
|
-
* holds on every path. Defaults to {@link DEFAULT_SYNC_RETRY}. `sleep` is
|
|
97
|
+
* Bounded retry policy for {@link syncProject}'s fetch→merge→push race
|
|
98
|
+
* loop. The loop is ALWAYS bounded (never infinite) and the snapshot-first
|
|
99
|
+
* guarantee holds on every path. Defaults to {@link DEFAULT_SYNC_RETRY}. `sleep` is
|
|
171
100
|
* injectable so tests can drive backoff deterministically.
|
|
172
101
|
*/
|
|
173
102
|
retry?: SyncRetryOptions;
|
|
@@ -1,30 +1,28 @@
|
|
|
1
1
|
import { onAuthFor } from "./transport.ts";
|
|
2
|
-
import type {
|
|
2
|
+
import type { SyncOutcome, SyncProjectOptions } from "./sync-types.ts";
|
|
3
3
|
export { onAuthFor };
|
|
4
4
|
export { SYNC_SNAPSHOT_MESSAGE } from "./sync-messages.ts";
|
|
5
|
-
export type {
|
|
5
|
+
export type { KeptBothFile, SyncOutcome, SyncProjectOptions, } from "./sync-types.ts";
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* the push reports pull-first and the pair re-runs (their commits converge in
|
|
10
|
-
* on the next pass), with a short backoff between passes. The loop is ALWAYS
|
|
11
|
-
* bounded by `retry.attempts`; a remote that races every pass surfaces a
|
|
12
|
-
* friendly "try again in a moment" rather than looping forever. Never throws
|
|
13
|
-
* for expected outcomes — everything is reported through {@link SyncOutcome}.
|
|
7
|
+
* A push the remote refused because our branch is behind — the pull-first
|
|
8
|
+
* situation the retry loop exists for.
|
|
14
9
|
*/
|
|
15
|
-
export declare function
|
|
10
|
+
export declare function isPushRejected(e: unknown): boolean;
|
|
16
11
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* function-scoped object cache, released on return.
|
|
12
|
+
* Unrelated histories — the local project and the configured online project
|
|
13
|
+
* share no common starting point. Sync surfaces this as a plain setup error
|
|
14
|
+
* (a wrong online address must never be silently spliced into the book).
|
|
21
15
|
*/
|
|
22
|
-
export declare function
|
|
16
|
+
export declare function isUnrelatedHistories(e: unknown): boolean;
|
|
23
17
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* the
|
|
18
|
+
* Snapshot-first sync (ADR 0006 D5). Serialized on the per-repo lock; one
|
|
19
|
+
* function-scoped object cache, released on return.
|
|
20
|
+
*
|
|
21
|
+
* If someone pushes between our fetch and our push, the push is rejected and
|
|
22
|
+
* the fetch→merge→push pass re-runs (their commits converge in on the next
|
|
23
|
+
* fetch), with a short backoff between passes. The loop is ALWAYS bounded by
|
|
24
|
+
* `retry.attempts`; a remote that races every pass surfaces a friendly "try
|
|
25
|
+
* again in a moment" rather than looping forever. Never throws for expected
|
|
26
|
+
* outcomes — everything is reported through {@link SyncOutcome}.
|
|
29
27
|
*/
|
|
30
|
-
export declare function
|
|
28
|
+
export declare function syncProject(options: SyncProjectOptions): Promise<SyncOutcome>;
|
|
@@ -25,10 +25,6 @@ export interface TokenStore {
|
|
|
25
25
|
/** All stored credentials (used by "connected accounts" UIs). */
|
|
26
26
|
list(): Promise<HostCredential[]>;
|
|
27
27
|
}
|
|
28
|
-
/** A credential with the token value masked — safe for logs/diagnostics. */
|
|
29
|
-
export declare function redactCredential(cred: HostCredential): Omit<HostCredential, "token"> & {
|
|
30
|
-
token: string;
|
|
31
|
-
};
|
|
32
28
|
/**
|
|
33
29
|
* THE canonical credential-store key for a remote host — the ONE derivation
|
|
34
30
|
* every writer (GitHub device flow, generic connect, embedded-URL migration)
|
|
@@ -87,9 +83,3 @@ export interface UrlCredentialExtraction {
|
|
|
87
83
|
* display. Non-HTTP(S) or unparseable URLs pass through unchanged.
|
|
88
84
|
*/
|
|
89
85
|
export declare function extractUrlCredential(url: string): UrlCredentialExtraction;
|
|
90
|
-
/**
|
|
91
|
-
* Migrate any credential embedded in `url` into `store` (only when the store
|
|
92
|
-
* has no existing credential for that host — a stored credential is fresher
|
|
93
|
-
* than one fossilized in a clone URL) and return the sanitized URL.
|
|
94
|
-
*/
|
|
95
|
-
export declare function migrateUrlCredential(url: string, store: TokenStore): Promise<string>;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import httpNode from "isomorphic-git/http/node";
|
|
2
2
|
import { type HostCredential, type TokenStore } from "./token-store.ts";
|
|
3
|
-
import type { OperationLogger } from "./operation-log.ts";
|
|
4
3
|
import type { GitCache, RemoteTransport } from "./sync-types.ts";
|
|
5
4
|
/**
|
|
6
5
|
* The git repo directory for a project dir. A project IS its git repo, so this
|
|
@@ -17,6 +16,19 @@ export declare function repoDirFor(projectDir: string): Promise<string>;
|
|
|
17
16
|
* http carries no network exposure, so it stays allowed.
|
|
18
17
|
*/
|
|
19
18
|
export declare function isCredentialTransmissionSafe(url: string): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Thrown by {@link onAuthFor} when a stored credential EXISTS but the remote
|
|
21
|
+
* URL fails {@link isCredentialTransmissionSafe} (non-loopback http). Loud and
|
|
22
|
+
* typed on purpose: the old behavior (silently withholding the credential)
|
|
23
|
+
* surfaced as a 401 → "auth" → "reconnect" loop. The `code` string is the
|
|
24
|
+
* STABLE contract (matchable across dynamic-import boundaries).
|
|
25
|
+
*/
|
|
26
|
+
export declare class InsecureTransportError extends Error {
|
|
27
|
+
readonly code = "InsecureTransport";
|
|
28
|
+
constructor();
|
|
29
|
+
}
|
|
30
|
+
/** Type guard for {@link InsecureTransportError} (matches on the stable code). */
|
|
31
|
+
export declare function isInsecureTransportError(e: unknown): e is InsecureTransportError;
|
|
20
32
|
export declare function onAuthFor(credential: HostCredential | undefined): {
|
|
21
33
|
onAuth?: undefined;
|
|
22
34
|
} | {
|
|
@@ -35,13 +47,19 @@ export declare function resolveTransport(dir: string, options: {
|
|
|
35
47
|
tokenStore?: TokenStore;
|
|
36
48
|
}): Promise<RemoteTransport>;
|
|
37
49
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
50
|
+
* Decode a thrown transport error into the outcome it maps to, or null when it
|
|
51
|
+
* is not a transport failure at all. The single source of truth for transport
|
|
52
|
+
* error decoding — sync.ts and clone.ts consume it rather than keeping
|
|
53
|
+
* parallel copies.
|
|
54
|
+
*/
|
|
55
|
+
export declare function classifyTransportFailure(e: unknown): "auth_required" | "network_unavailable" | "insecure_transport" | null;
|
|
56
|
+
/**
|
|
57
|
+
* The failure arms of {@link SyncOutcome}. Decoding delegates to
|
|
58
|
+
* {@link classifyTransportFailure}: auth_required → "auth",
|
|
59
|
+
* network_unavailable → "offline", insecure_transport → "error" with its
|
|
60
|
+
* dedicated message (NEVER "auth" — reconnecting can't fix an http:// address,
|
|
61
|
+
* and the auth recovery path deletes the stored credential), anything else →
|
|
62
|
+
* the generic "error" arm.
|
|
45
63
|
*/
|
|
46
64
|
export declare function failureOutcome(e: unknown, snapshotId?: string): {
|
|
47
65
|
status: "auth" | "offline" | "error";
|
|
@@ -51,7 +69,7 @@ export declare function failureOutcome(e: unknown, snapshotId?: string): {
|
|
|
51
69
|
/** Friendly setup-problem message for the expected gate errors, else null. */
|
|
52
70
|
export declare function setupErrorMessage(e: unknown): string | null;
|
|
53
71
|
/**
|
|
54
|
-
* Snapshot-first step
|
|
72
|
+
* Snapshot-first step used by syncProject
|
|
55
73
|
* (ADR 0006 D5): commit any unsaved work in the WHOLE repo BEFORE any network
|
|
56
74
|
* or merge step, so a forced post-merge checkout can never discard it. The
|
|
57
75
|
* working-tree check runs lazily at action time on the caller's function-scoped
|
|
@@ -65,44 +83,28 @@ export declare function snapshotBeforeAction(args: {
|
|
|
65
83
|
authorEmail?: string;
|
|
66
84
|
cache: GitCache;
|
|
67
85
|
}): Promise<string | undefined>;
|
|
68
|
-
/**
|
|
69
|
-
* Structural preflight — never touch the tree of a damaged repo. An interrupted
|
|
70
|
-
* merge/rebase/cherry-pick, detached HEAD, stale lock, or missing `.git` must be
|
|
71
|
-
* REPAIRED before any sync work: snapshot-first would otherwise commit whatever
|
|
72
|
-
* is on disk (e.g. the literal conflict markers a half-done native-git merge
|
|
73
|
-
* leaves in tracked files) and push it to every collaborator. Throwing the
|
|
74
|
-
* typed error routes the caller through the recover() path.
|
|
75
|
-
* `checkLocalChanges: false` — only the structural flags matter here.
|
|
76
|
-
*
|
|
77
|
-
* Runs INSIDE the caller's repo lock. Shared by pullChanges and pushChanges.
|
|
78
|
-
*/
|
|
79
|
-
export declare function assertNoStructuralDamage(projectDir: string, logger: OperationLogger): Promise<void>;
|
|
80
86
|
export declare function currentBranchOrThrow(dir: string): Promise<string>;
|
|
81
87
|
/**
|
|
82
|
-
* Run `fn` (a fetch that moves remote-tracking
|
|
83
|
-
* (deep-analysis R15): isomorphic-git updates refs/remotes/<remote
|
|
84
|
-
* the ref advertisement BEFORE collecting the packfile, so an abort
|
|
85
|
-
* mid-transfer (e.g. the defaultGitHttp idle timeout) leaves
|
|
86
|
-
* at
|
|
88
|
+
* Run `fn` (a fetch that moves ONE remote-tracking ref) with a rollback guard
|
|
89
|
+
* (deep-analysis R15): isomorphic-git updates `refs/remotes/<remote>/<branch>`
|
|
90
|
+
* from the ref advertisement BEFORE collecting the packfile, so an abort
|
|
91
|
+
* mid-transfer (e.g. the defaultGitHttp idle timeout) leaves the ref pointing
|
|
92
|
+
* at an oid with no local object. Such a dangling ref poisons the next fetch —
|
|
87
93
|
* zero `have`s → the server streams the ENTIRE repository (the OOM
|
|
88
|
-
* fetchRemoteTip's `ref` choice exists to prevent) — and resolving it reports
|
|
94
|
+
* `fetchRemoteTip`'s `ref` choice exists to prevent) — and resolving it reports
|
|
89
95
|
* missing-object "corruption" on a never-corrupt repo.
|
|
90
96
|
*
|
|
91
|
-
* `
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
+
* If `fn` throws and the ref moved to an oid whose object is MISSING locally,
|
|
98
|
+
* it is restored to its previous oid (or DELETED if it did not exist before).
|
|
99
|
+
* A ref whose object DID land is kept — the pack made it. On success no ref is
|
|
100
|
+
* touched.
|
|
101
|
+
*
|
|
102
|
+
* Every read here is best-effort and never masks `fn`'s error: a damaged ref
|
|
103
|
+
* store must not block the guarded fetch, because the recovery handlers run on
|
|
104
|
+
* exactly such repos and skipping `fn` would skip the repair itself. An
|
|
105
|
+
* unreadable pre-scan simply degrades to the delete-if-dangling arm.
|
|
97
106
|
*/
|
|
98
|
-
export declare function guardRefs<T>(dir: string, listRefs: () => Promise<string[]>, cache: GitCache, fn: () => Promise<T>): Promise<T>;
|
|
99
|
-
/** Single-ref form of {@link guardRefs} — guards one remote-tracking ref. */
|
|
100
107
|
export declare function guardTrackingRef<T>(dir: string, ref: string, cache: GitCache, fn: () => Promise<T>): Promise<T>;
|
|
101
|
-
/**
|
|
102
|
-
* Remote-wide form of {@link guardRefs} for a fetch that may move or create
|
|
103
|
-
* ANY refs/remotes/<remote>/* ref (e.g. `singleBranch: false`).
|
|
104
|
-
*/
|
|
105
|
-
export declare function guardRemoteRefs<T>(dir: string, remote: string, cache: GitCache, fn: () => Promise<T>): Promise<T>;
|
|
106
108
|
/**
|
|
107
109
|
* Fetch the tracked branch's online tip. Returns `null` when the online
|
|
108
110
|
* repository has no such branch yet (a freshly created empty repo).
|
|
@@ -38,6 +38,18 @@ export interface SnapshotOptions {
|
|
|
38
38
|
repoRoot?: string;
|
|
39
39
|
/** Optional log file for debugging snapshot operations. */
|
|
40
40
|
logFile?: string;
|
|
41
|
+
/**
|
|
42
|
+
* The CALLER's object cache, when the snapshot is one step of a longer
|
|
43
|
+
* locked operation (sync). Committing rewrites `.git/index`, and
|
|
44
|
+
* isomorphic-git's index cache invalidates on a stat comparison that can
|
|
45
|
+
* miss a same-second rewrite of the same size — so a caller that keeps
|
|
46
|
+
* using its own cache afterwards would keep reading the PRE-snapshot index.
|
|
47
|
+
* That is not cosmetic: `git.checkout` derives STAGE from it, and a stale
|
|
48
|
+
* STAGE makes an unmodified file look locally modified. Pass the cache and
|
|
49
|
+
* the snapshot's own `git.add`/`git.commit` refresh it in place. Omit it
|
|
50
|
+
* for standalone snapshots — they get a private cache, released on return.
|
|
51
|
+
*/
|
|
52
|
+
cache?: GitCache;
|
|
41
53
|
}
|
|
42
54
|
/**
|
|
43
55
|
* The git repo directory a project's operations run against. A project IS its
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
2
|
MANIFEST_FILENAMES,
|
|
3
3
|
runLint
|
|
4
|
-
} from "./cli-
|
|
4
|
+
} from "./cli-7n848af2.js";
|
|
5
5
|
import {
|
|
6
6
|
log
|
|
7
|
-
} from "./cli-
|
|
7
|
+
} from "./cli-0z6zf98d.js";
|
|
8
8
|
import {
|
|
9
9
|
UsageError,
|
|
10
10
|
rejectExtraPositionals,
|
|
11
11
|
rejectUnknownFlags
|
|
12
|
-
} from "./cli-
|
|
13
|
-
import"./cli-
|
|
12
|
+
} from "./cli-ydpf09d9.js";
|
|
13
|
+
import"./cli-eewynen9.js";
|
|
14
14
|
import"./cli-c41yr7he.js";
|
|
15
15
|
import {
|
|
16
16
|
EXIT_CODES
|
|
17
17
|
} from "./cli-46ycxe6r.js";
|
|
18
|
-
import"./cli-
|
|
18
|
+
import"./cli-w692g6dx.js";
|
|
19
19
|
import"./cli-v5mp7a6q.js";
|
|
20
20
|
import"./cli-37x76zdn.js";
|
|
21
21
|
|
|
@@ -5,23 +5,23 @@ import {
|
|
|
5
5
|
TARGETS,
|
|
6
6
|
TARGET_IDS,
|
|
7
7
|
scaffoldProject
|
|
8
|
-
} from "./cli-
|
|
8
|
+
} from "./cli-7n848af2.js";
|
|
9
9
|
import {
|
|
10
10
|
resolveGhostscript
|
|
11
|
-
} from "./cli-
|
|
11
|
+
} from "./cli-0z6zf98d.js";
|
|
12
12
|
import {
|
|
13
13
|
UsageError,
|
|
14
14
|
rejectExtraPositionals,
|
|
15
15
|
rejectUnknownFlags
|
|
16
|
-
} from "./cli-
|
|
17
|
-
import"./cli-
|
|
16
|
+
} from "./cli-ydpf09d9.js";
|
|
17
|
+
import"./cli-eewynen9.js";
|
|
18
18
|
import {
|
|
19
19
|
isToolAvailable
|
|
20
20
|
} from "./cli-c41yr7he.js";
|
|
21
21
|
import {
|
|
22
22
|
EXIT_CODES
|
|
23
23
|
} from "./cli-46ycxe6r.js";
|
|
24
|
-
import"./cli-
|
|
24
|
+
import"./cli-w692g6dx.js";
|
|
25
25
|
import"./cli-v5mp7a6q.js";
|
|
26
26
|
import"./cli-37x76zdn.js";
|
|
27
27
|
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
addNpmPlugin,
|
|
3
3
|
parseNpmPluginSpec
|
|
4
|
-
} from "./cli-
|
|
5
|
-
import"./cli-
|
|
4
|
+
} from "./cli-7n848af2.js";
|
|
5
|
+
import"./cli-0z6zf98d.js";
|
|
6
6
|
import {
|
|
7
7
|
UsageError,
|
|
8
8
|
rejectExtraPositionals,
|
|
9
9
|
rejectUnknownFlags
|
|
10
|
-
} from "./cli-
|
|
11
|
-
import"./cli-
|
|
10
|
+
} from "./cli-ydpf09d9.js";
|
|
11
|
+
import"./cli-eewynen9.js";
|
|
12
12
|
import"./cli-c41yr7he.js";
|
|
13
13
|
import {
|
|
14
14
|
EXIT_CODES
|
|
15
15
|
} from "./cli-46ycxe6r.js";
|
|
16
|
-
import"./cli-
|
|
16
|
+
import"./cli-w692g6dx.js";
|
|
17
17
|
import"./cli-v5mp7a6q.js";
|
|
18
18
|
import"./cli-37x76zdn.js";
|
|
19
19
|
|
|
@@ -2,21 +2,21 @@ import {
|
|
|
2
2
|
executeValidation,
|
|
3
3
|
publishTargetFor,
|
|
4
4
|
reportMissingTools
|
|
5
|
-
} from "./cli-
|
|
5
|
+
} from "./cli-7n848af2.js";
|
|
6
6
|
import {
|
|
7
7
|
log
|
|
8
|
-
} from "./cli-
|
|
8
|
+
} from "./cli-0z6zf98d.js";
|
|
9
9
|
import {
|
|
10
10
|
UsageError,
|
|
11
11
|
rejectExtraPositionals,
|
|
12
12
|
rejectUnknownFlags
|
|
13
|
-
} from "./cli-
|
|
14
|
-
import"./cli-
|
|
13
|
+
} from "./cli-ydpf09d9.js";
|
|
14
|
+
import"./cli-eewynen9.js";
|
|
15
15
|
import"./cli-c41yr7he.js";
|
|
16
16
|
import {
|
|
17
17
|
EXIT_CODES
|
|
18
18
|
} from "./cli-46ycxe6r.js";
|
|
19
|
-
import"./cli-
|
|
19
|
+
import"./cli-w692g6dx.js";
|
|
20
20
|
import"./cli-v5mp7a6q.js";
|
|
21
21
|
import"./cli-37x76zdn.js";
|
|
22
22
|
|