gutterpress 0.11.4 → 0.11.5
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/api/index.js +2 -2
- package/dist/{audit-hnqcf3w9.js → audit-wxzty30c.js} +4 -4
- package/dist/{build-5apre79d.js → build-m54gr7r3.js} +4 -4
- package/dist/{cli-4mtf9ajw.js → cli-c9h2bk2k.js} +18 -6
- package/dist/{cli-58np1xae.js → cli-d8my7adx.js} +4 -4
- package/dist/{cli-en9tew8h.js → cli-fxk3whwm.js} +1 -1
- package/dist/{cli-r2hsreyr.js → cli-pqe8cahv.js} +1 -1
- package/dist/cli.js +14 -14
- package/dist/{doctor-tj6k990h.js → doctor-3aj8rkjs.js} +2 -2
- package/dist/{ext-jgwqgkrn.js → ext-cwpwca5v.js} +4 -4
- package/dist/{index-xefff326.js → index-771dmhpc.js} +224 -152
- package/dist/{index-y01v2fe0.js → index-c5ev4r1y.js} +67 -9
- package/dist/index.js +2 -2
- package/dist/lib/remote-auth/converge-merge.d.ts +3 -0
- package/dist/lib/remote-auth/operation-log.d.ts +15 -6
- package/dist/lib/remote-auth/sync-types.d.ts +7 -7
- package/dist/{lint-zeyzqkz2.js → lint-s4ydm5zs.js} +4 -4
- package/dist/{new-et0fffvj.js → new-t3mcy3st.js} +4 -4
- package/dist/{preflight-qya0kg4p.js → preflight-phf1d3e0.js} +4 -4
- package/dist/{preview-8ptv04xf.js → preview-aasty7am.js} +4 -4
- package/dist/{publish-sqp59a20.js → publish-9g4dph7e.js} +4 -4
- package/dist/{source-provider-51g6mt6y.js → source-provider-54a52bpd.js} +1 -1
- package/dist/{source-provider-jkppq9fc.js → source-provider-a458mmec.js} +1 -1
- package/dist/{validate-tfk44ey7.js → validate-mrp8dve2.js} +4 -4
- package/package.json +1 -1
package/dist/api/index.js
CHANGED
|
@@ -124,7 +124,7 @@ import {
|
|
|
124
124
|
syncProject,
|
|
125
125
|
testRemoteAccess,
|
|
126
126
|
validateProjectExtensions
|
|
127
|
-
} from "../index-
|
|
127
|
+
} from "../index-771dmhpc.js";
|
|
128
128
|
import {
|
|
129
129
|
BuildError,
|
|
130
130
|
DEFAULT_PRINT_OPTS,
|
|
@@ -141,7 +141,7 @@ import {
|
|
|
141
141
|
resolveLogger,
|
|
142
142
|
restoreVersionWithBackup,
|
|
143
143
|
switchBranch
|
|
144
|
-
} from "../index-
|
|
144
|
+
} from "../index-c5ev4r1y.js";
|
|
145
145
|
import {
|
|
146
146
|
capabilitiesFor,
|
|
147
147
|
detectProjectSource,
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
2
|
executeAndReport
|
|
3
|
-
} from "./cli-
|
|
3
|
+
} from "./cli-d8my7adx.js";
|
|
4
4
|
import {
|
|
5
5
|
log
|
|
6
|
-
} from "./cli-
|
|
6
|
+
} from "./cli-pqe8cahv.js";
|
|
7
7
|
import {
|
|
8
8
|
UsageError,
|
|
9
9
|
rejectExtraPositionals,
|
|
10
10
|
rejectUnknownFlags
|
|
11
|
-
} from "./cli-
|
|
11
|
+
} from "./cli-fxk3whwm.js";
|
|
12
12
|
import"./cli-sekhwex4.js";
|
|
13
13
|
import"./cli-c41yr7he.js";
|
|
14
14
|
import {
|
|
15
15
|
EXIT_CODES
|
|
16
16
|
} from "./cli-46ycxe6r.js";
|
|
17
|
-
import"./cli-
|
|
17
|
+
import"./cli-c9h2bk2k.js";
|
|
18
18
|
import"./cli-v5mp7a6q.js";
|
|
19
19
|
import"./cli-37x76zdn.js";
|
|
20
20
|
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import {
|
|
2
2
|
runBuild,
|
|
3
3
|
splitOutPath
|
|
4
|
-
} from "./cli-
|
|
4
|
+
} from "./cli-d8my7adx.js";
|
|
5
5
|
import {
|
|
6
6
|
log
|
|
7
|
-
} from "./cli-
|
|
7
|
+
} from "./cli-pqe8cahv.js";
|
|
8
8
|
import {
|
|
9
9
|
UsageError,
|
|
10
10
|
parseFormat,
|
|
11
11
|
parsePdfxFlavor,
|
|
12
12
|
rejectExtraPositionals,
|
|
13
13
|
rejectUnknownFlags
|
|
14
|
-
} from "./cli-
|
|
14
|
+
} from "./cli-fxk3whwm.js";
|
|
15
15
|
import"./cli-sekhwex4.js";
|
|
16
16
|
import"./cli-c41yr7he.js";
|
|
17
17
|
import {
|
|
18
18
|
BuildError
|
|
19
19
|
} from "./cli-46ycxe6r.js";
|
|
20
|
-
import"./cli-
|
|
20
|
+
import"./cli-c9h2bk2k.js";
|
|
21
21
|
import"./cli-v5mp7a6q.js";
|
|
22
22
|
import"./cli-37x76zdn.js";
|
|
23
23
|
|
|
@@ -63,6 +63,17 @@ import git from "isomorphic-git";
|
|
|
63
63
|
// src/lib/remote-auth/operation-log.ts
|
|
64
64
|
import { appendFileSync, mkdirSync } from "node:fs";
|
|
65
65
|
import path from "node:path";
|
|
66
|
+
function sanitizeLogText(value, secrets = []) {
|
|
67
|
+
let text = value;
|
|
68
|
+
for (const secret of secrets) {
|
|
69
|
+
if (!secret)
|
|
70
|
+
continue;
|
|
71
|
+
for (const form of [secret, encodeURIComponent(secret)]) {
|
|
72
|
+
text = text.split(form).join("[redacted]");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return text.replace(/\/\/[^/\s]*@/g, "//").replace(/([?&](?:access_token|token|password|secret|key|code)=)[^&#\s]*/gi, "$1[redacted]").replace(/\b(Bearer|Basic)\s+[A-Za-z0-9+/_.=~-]+/gi, "$1 [redacted]").replace(/\b(?:gh[pousr]_[A-Za-z0-9_]+|github_pat_[A-Za-z0-9_]+)/g, "[redacted]").replace(/[\s\x00-\x1f\x7f]+/g, " ").trim().slice(0, 6000);
|
|
76
|
+
}
|
|
66
77
|
var noop = {
|
|
67
78
|
debug: () => {},
|
|
68
79
|
info: () => {},
|
|
@@ -70,7 +81,7 @@ var noop = {
|
|
|
70
81
|
error: () => {}
|
|
71
82
|
};
|
|
72
83
|
var LEVEL_ORDER = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
73
|
-
function createFileLogger(logFile, operation, minLevel = "debug") {
|
|
84
|
+
function createFileLogger(logFile, operation, minLevel = "debug", options = {}) {
|
|
74
85
|
try {
|
|
75
86
|
mkdirSync(path.dirname(logFile), { recursive: true });
|
|
76
87
|
} catch {
|
|
@@ -80,8 +91,9 @@ function createFileLogger(logFile, operation, minLevel = "debug") {
|
|
|
80
91
|
if (LEVEL_ORDER[level] < LEVEL_ORDER[minLevel])
|
|
81
92
|
return;
|
|
82
93
|
const ts = new Date().toISOString();
|
|
83
|
-
const
|
|
84
|
-
const
|
|
94
|
+
const clean = (value) => sanitizeLogText(value, options.secrets);
|
|
95
|
+
const fields = formatData({ ...options.context, ...data }, clean);
|
|
96
|
+
const line = `[${ts}] ${level.toUpperCase().padEnd(5)} ${clean(operation)}: step=${clean(step)}${fields} | ${clean(message)}
|
|
85
97
|
`;
|
|
86
98
|
try {
|
|
87
99
|
appendFileSync(logFile, line, "utf8");
|
|
@@ -94,7 +106,7 @@ function createFileLogger(logFile, operation, minLevel = "debug") {
|
|
|
94
106
|
error: (step, message, data) => write("error", step, message, data)
|
|
95
107
|
};
|
|
96
108
|
}
|
|
97
|
-
function formatData(data) {
|
|
109
|
+
function formatData(data, clean) {
|
|
98
110
|
if (!data)
|
|
99
111
|
return "";
|
|
100
112
|
const parts = [];
|
|
@@ -102,9 +114,9 @@ function formatData(data) {
|
|
|
102
114
|
if (value === undefined)
|
|
103
115
|
continue;
|
|
104
116
|
if (Array.isArray(value)) {
|
|
105
|
-
parts.push(`${key}=${value.join(",")}`);
|
|
117
|
+
parts.push(`${clean(key)}=${clean(value.join(","))}`);
|
|
106
118
|
} else {
|
|
107
|
-
parts.push(`${key}=${String(value)}`);
|
|
119
|
+
parts.push(`${clean(key)}=${clean(String(value))}`);
|
|
108
120
|
}
|
|
109
121
|
}
|
|
110
122
|
return parts.length > 0 ? " " + parts.join(" ") : "";
|
|
@@ -13,12 +13,12 @@ import {
|
|
|
13
13
|
stampCreator,
|
|
14
14
|
stripAnnotations,
|
|
15
15
|
warn
|
|
16
|
-
} from "./cli-
|
|
16
|
+
} from "./cli-pqe8cahv.js";
|
|
17
17
|
import {
|
|
18
18
|
DEBOUNCE,
|
|
19
19
|
UsageError,
|
|
20
20
|
resolvePort
|
|
21
|
-
} from "./cli-
|
|
21
|
+
} from "./cli-fxk3whwm.js";
|
|
22
22
|
import {
|
|
23
23
|
MARGIN_BOX_IGNORED_PROPERTIES,
|
|
24
24
|
RENDER_TIMEOUT_MS,
|
|
@@ -43,7 +43,7 @@ import {
|
|
|
43
43
|
import {
|
|
44
44
|
hasUncommittedChanges,
|
|
45
45
|
listWorkdirChanges
|
|
46
|
-
} from "./cli-
|
|
46
|
+
} from "./cli-c9h2bk2k.js";
|
|
47
47
|
import {
|
|
48
48
|
detectProjectSource,
|
|
49
49
|
repoSubPath
|
|
@@ -10418,7 +10418,7 @@ async function scaffoldProject(options) {
|
|
|
10418
10418
|
let versionHistoryError;
|
|
10419
10419
|
if (requested === "local-git") {
|
|
10420
10420
|
try {
|
|
10421
|
-
const { providerFor } = await import("./source-provider-
|
|
10421
|
+
const { providerFor } = await import("./source-provider-54a52bpd.js");
|
|
10422
10422
|
const provider = providerFor({ type: "local-folder", path: projectDir });
|
|
10423
10423
|
await provider.initVersionHistory({
|
|
10424
10424
|
projectDir,
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
// package.json
|
|
5
5
|
var package_default = {
|
|
6
6
|
name: "gutterpress",
|
|
7
|
-
version: "0.11.
|
|
7
|
+
version: "0.11.5",
|
|
8
8
|
description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
|
|
9
9
|
author: "itlackey",
|
|
10
10
|
license: "MPL-2.0",
|
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
UsageError,
|
|
7
7
|
package_default,
|
|
8
8
|
rejectUnknownFlags
|
|
9
|
-
} from "./cli-
|
|
9
|
+
} from "./cli-fxk3whwm.js";
|
|
10
10
|
import {
|
|
11
11
|
EXIT_CODES
|
|
12
12
|
} from "./cli-46ycxe6r.js";
|
|
@@ -18,16 +18,16 @@ import {
|
|
|
18
18
|
import { defineCommand, parseArgs, runMain } from "citty";
|
|
19
19
|
import { statSync } from "node:fs";
|
|
20
20
|
var SUBCOMMANDS = {
|
|
21
|
-
new: () => import("./new-
|
|
22
|
-
preview: () => import("./preview-
|
|
23
|
-
build: () => import("./build-
|
|
24
|
-
publish: () => import("./publish-
|
|
25
|
-
lint: () => import("./lint-
|
|
26
|
-
validate: () => import("./validate-
|
|
27
|
-
audit: () => import("./audit-
|
|
28
|
-
preflight: () => import("./preflight-
|
|
29
|
-
doctor: () => import("./doctor-
|
|
30
|
-
ext: () => import("./ext-
|
|
21
|
+
new: () => import("./new-t3mcy3st.js").then((m) => m.default),
|
|
22
|
+
preview: () => import("./preview-aasty7am.js").then((m) => m.default),
|
|
23
|
+
build: () => import("./build-m54gr7r3.js").then((m) => m.default),
|
|
24
|
+
publish: () => import("./publish-9g4dph7e.js").then((m) => m.default),
|
|
25
|
+
lint: () => import("./lint-s4ydm5zs.js").then((m) => m.default),
|
|
26
|
+
validate: () => import("./validate-mrp8dve2.js").then((m) => m.default),
|
|
27
|
+
audit: () => import("./audit-wxzty30c.js").then((m) => m.default),
|
|
28
|
+
preflight: () => import("./preflight-phf1d3e0.js").then((m) => m.default),
|
|
29
|
+
doctor: () => import("./doctor-3aj8rkjs.js").then((m) => m.default),
|
|
30
|
+
ext: () => import("./ext-cwpwca5v.js").then((m) => m.default)
|
|
31
31
|
};
|
|
32
32
|
var VERSION = package_default.version;
|
|
33
33
|
var main = defineCommand({
|
|
@@ -52,7 +52,7 @@ async function preflightRequiredInvocations(rawArgs) {
|
|
|
52
52
|
const [command, ...commandArgs] = rawArgs;
|
|
53
53
|
try {
|
|
54
54
|
if (command === "new") {
|
|
55
|
-
const { newArgs } = await import("./new-
|
|
55
|
+
const { newArgs } = await import("./new-t3mcy3st.js");
|
|
56
56
|
rejectUnknownFlags(commandArgs, newArgs, "new");
|
|
57
57
|
const parsed = parseArgs(commandArgs, {
|
|
58
58
|
...newArgs,
|
|
@@ -64,7 +64,7 @@ async function preflightRequiredInvocations(rawArgs) {
|
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
66
66
|
if (command === "preflight") {
|
|
67
|
-
const { preflightArgs } = await import("./preflight-
|
|
67
|
+
const { preflightArgs } = await import("./preflight-phf1d3e0.js");
|
|
68
68
|
rejectUnknownFlags(commandArgs, preflightArgs, "preflight");
|
|
69
69
|
const parsed = parseArgs(commandArgs, {
|
|
70
70
|
...preflightArgs,
|
|
@@ -84,7 +84,7 @@ async function preflightRequiredInvocations(rawArgs) {
|
|
|
84
84
|
}
|
|
85
85
|
if (subcommand.startsWith("-"))
|
|
86
86
|
return;
|
|
87
|
-
const { EXT_SUBCOMMANDS, extAddArgs, extRemoveArgs, extEnableArgs, extDisableArgs } = await import("./ext-
|
|
87
|
+
const { EXT_SUBCOMMANDS, extAddArgs, extRemoveArgs, extEnableArgs, extDisableArgs } = await import("./ext-cwpwca5v.js");
|
|
88
88
|
if (!EXT_SUBCOMMANDS.includes(subcommand)) {
|
|
89
89
|
throw new UsageError(`gutterpress ext: unknown command "${subcommand}"`);
|
|
90
90
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getSystemDiagnostics,
|
|
3
3
|
log
|
|
4
|
-
} from "./cli-
|
|
4
|
+
} from "./cli-pqe8cahv.js";
|
|
5
5
|
import {
|
|
6
6
|
UsageError,
|
|
7
7
|
rejectExtraPositionals,
|
|
8
8
|
rejectUnknownFlags
|
|
9
|
-
} from "./cli-
|
|
9
|
+
} from "./cli-fxk3whwm.js";
|
|
10
10
|
import"./cli-c41yr7he.js";
|
|
11
11
|
import"./cli-46ycxe6r.js";
|
|
12
12
|
import"./cli-37x76zdn.js";
|
|
@@ -10,21 +10,21 @@ import {
|
|
|
10
10
|
removeExtension,
|
|
11
11
|
searchNpmExtensions,
|
|
12
12
|
setExtensionEnabled
|
|
13
|
-
} from "./cli-
|
|
14
|
-
import"./cli-
|
|
13
|
+
} from "./cli-d8my7adx.js";
|
|
14
|
+
import"./cli-pqe8cahv.js";
|
|
15
15
|
import {
|
|
16
16
|
UsageError,
|
|
17
17
|
exitForUsage,
|
|
18
18
|
rejectExtraPositionals,
|
|
19
19
|
rejectUnknownFlags,
|
|
20
20
|
resolveProjectDir
|
|
21
|
-
} from "./cli-
|
|
21
|
+
} from "./cli-fxk3whwm.js";
|
|
22
22
|
import"./cli-sekhwex4.js";
|
|
23
23
|
import"./cli-c41yr7he.js";
|
|
24
24
|
import {
|
|
25
25
|
EXIT_CODES
|
|
26
26
|
} from "./cli-46ycxe6r.js";
|
|
27
|
-
import"./cli-
|
|
27
|
+
import"./cli-c9h2bk2k.js";
|
|
28
28
|
import"./cli-v5mp7a6q.js";
|
|
29
29
|
import"./cli-37x76zdn.js";
|
|
30
30
|
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
spawnCapture
|
|
21
21
|
} from "./index-6n15e1ze.js";
|
|
22
22
|
import {
|
|
23
|
+
errorLogData,
|
|
23
24
|
gitFs,
|
|
24
25
|
gitScopeFor,
|
|
25
26
|
hasPendingChanges,
|
|
@@ -30,7 +31,7 @@ import {
|
|
|
30
31
|
snapshotStagingMarkerPath,
|
|
31
32
|
snapshotWorkingTreeUnlocked,
|
|
32
33
|
withRepoLock
|
|
33
|
-
} from "./index-
|
|
34
|
+
} from "./index-c5ev4r1y.js";
|
|
34
35
|
import {
|
|
35
36
|
detectProjectSource,
|
|
36
37
|
repoSubPath,
|
|
@@ -4874,7 +4875,7 @@ import git from "isomorphic-git";
|
|
|
4874
4875
|
// package.json
|
|
4875
4876
|
var package_default = {
|
|
4876
4877
|
name: "gutterpress",
|
|
4877
|
-
version: "0.11.
|
|
4878
|
+
version: "0.11.5",
|
|
4878
4879
|
description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
|
|
4879
4880
|
author: "itlackey",
|
|
4880
4881
|
license: "MPL-2.0",
|
|
@@ -11159,7 +11160,7 @@ async function scaffoldProject(options) {
|
|
|
11159
11160
|
let versionHistoryError;
|
|
11160
11161
|
if (requested === "local-git") {
|
|
11161
11162
|
try {
|
|
11162
|
-
const { providerFor } = await import("./source-provider-
|
|
11163
|
+
const { providerFor } = await import("./source-provider-a458mmec.js");
|
|
11163
11164
|
const provider = providerFor({ type: "local-folder", path: projectDir });
|
|
11164
11165
|
await provider.initVersionHistory({
|
|
11165
11166
|
projectDir,
|
|
@@ -11251,7 +11252,7 @@ styles:
|
|
|
11251
11252
|
let versionHistoryError;
|
|
11252
11253
|
if (requested === "local-git") {
|
|
11253
11254
|
try {
|
|
11254
|
-
const { providerFor } = await import("./source-provider-
|
|
11255
|
+
const { providerFor } = await import("./source-provider-a458mmec.js");
|
|
11255
11256
|
const provider = providerFor({ type: "local-folder", path: dir });
|
|
11256
11257
|
await provider.initVersionHistory({
|
|
11257
11258
|
projectDir: dir,
|
|
@@ -14428,7 +14429,7 @@ async function fetchRemoteTip(dir, branch, transport, http3, cache2) {
|
|
|
14428
14429
|
if (code === "NoRefspecError")
|
|
14429
14430
|
return null;
|
|
14430
14431
|
if (code === "NotFoundError" || e?.data?.statusCode === 404) {
|
|
14431
|
-
const err = new Error("The online repository couldn't be accessed with the saved connection. Reconnect and try again.");
|
|
14432
|
+
const err = new Error("The online repository couldn't be accessed with the saved connection. Reconnect and try again.", { cause: e });
|
|
14432
14433
|
err.code = "HttpError";
|
|
14433
14434
|
err.data = { statusCode: 401 };
|
|
14434
14435
|
throw err;
|
|
@@ -14787,6 +14788,7 @@ async function diagnoseProjectRemote(projectDir, options = {}) {
|
|
|
14787
14788
|
}
|
|
14788
14789
|
// src/lib/remote-auth/sync.ts
|
|
14789
14790
|
import git6 from "isomorphic-git";
|
|
14791
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
14790
14792
|
|
|
14791
14793
|
// src/lib/remote-auth/converge-merge.ts
|
|
14792
14794
|
import { mkdir as mkdir15, unlink as unlink3, writeFile as writeFile13 } from "node:fs/promises";
|
|
@@ -14828,13 +14830,8 @@ function mergeWithMarkers(baseContent, ourContent, theirContent) {
|
|
|
14828
14830
|
}
|
|
14829
14831
|
return merged;
|
|
14830
14832
|
}
|
|
14831
|
-
async function
|
|
14832
|
-
|
|
14833
|
-
const r = await git5.readBlob({ fs: gitFs, dir, cache: cache2, oid, filepath });
|
|
14834
|
-
return { oid: r.oid, blob: r.blob };
|
|
14835
|
-
} catch {
|
|
14836
|
-
return null;
|
|
14837
|
-
}
|
|
14833
|
+
async function readBlob(dir, cache2, oid, filepath) {
|
|
14834
|
+
return git5.readBlob({ fs: gitFs, dir, cache: cache2, oid, filepath });
|
|
14838
14835
|
}
|
|
14839
14836
|
async function writeTreeFile(dir, filepath, content) {
|
|
14840
14837
|
const abs = path24.join(dir, filepath);
|
|
@@ -14875,6 +14872,8 @@ async function bothAddedPaths(dir, cache2, ourTip, theirTip, allowUnrelated) {
|
|
|
14875
14872
|
}
|
|
14876
14873
|
async function convergeMerge(params) {
|
|
14877
14874
|
const { dir, cache: cache2, branch, theirs, author } = params;
|
|
14875
|
+
const step = params.onStep ?? (() => {});
|
|
14876
|
+
const logger = params.logger ?? resolveLogger(undefined, "sync");
|
|
14878
14877
|
const snapshot = (message) => snapshotWorkingTreeUnlocked({
|
|
14879
14878
|
projectDir: dir,
|
|
14880
14879
|
repoRoot: dir,
|
|
@@ -14889,6 +14888,7 @@ async function convergeMerge(params) {
|
|
|
14889
14888
|
let tipBeforeMerge = ourTip;
|
|
14890
14889
|
const attempt = async () => {
|
|
14891
14890
|
tipBeforeMerge = await git5.resolveRef({ fs: gitFs, dir, ref: branch });
|
|
14891
|
+
step("merge", { local: tipBeforeMerge, fetched: theirs });
|
|
14892
14892
|
return git5.merge({
|
|
14893
14893
|
fs: gitFs,
|
|
14894
14894
|
dir,
|
|
@@ -14913,13 +14913,14 @@ async function convergeMerge(params) {
|
|
|
14913
14913
|
};
|
|
14914
14914
|
const postRestore = [];
|
|
14915
14915
|
const equalize = async (paths) => {
|
|
14916
|
+
step("equalize", { binary: paths.binary, deleteByUs: paths.deleteByUs, deleteByTheirs: paths.deleteByTheirs });
|
|
14916
14917
|
for (const p of paths.binary) {
|
|
14918
|
+
step("read-conflict-blobs", { filepath: p });
|
|
14917
14919
|
const [ourBlob, theirBlob] = await Promise.all([
|
|
14918
|
-
|
|
14919
|
-
|
|
14920
|
+
readBlob(dir, cache2, ourTip, p),
|
|
14921
|
+
readBlob(dir, cache2, theirs, p)
|
|
14920
14922
|
]);
|
|
14921
|
-
|
|
14922
|
-
continue;
|
|
14923
|
+
step("write-conflict-file", { filepath: p });
|
|
14923
14924
|
await writeTreeFile(dir, p, theirBlob.blob);
|
|
14924
14925
|
const onlinePath = onlineSiblingPath(p);
|
|
14925
14926
|
postRestore.push({ path: p, bytes: ourBlob.blob });
|
|
@@ -14927,19 +14928,22 @@ async function convergeMerge(params) {
|
|
|
14927
14928
|
keptBothFiles.push({ path: p, onlinePath });
|
|
14928
14929
|
}
|
|
14929
14930
|
for (const p of paths.deleteByUs) {
|
|
14930
|
-
|
|
14931
|
-
|
|
14932
|
-
|
|
14931
|
+
step("read-conflict-blobs", { filepath: p });
|
|
14932
|
+
const theirBlob = await readBlob(dir, cache2, theirs, p);
|
|
14933
|
+
step("write-conflict-file", { filepath: p });
|
|
14934
|
+
await writeTreeFile(dir, p, theirBlob.blob);
|
|
14933
14935
|
}
|
|
14934
14936
|
for (const p of paths.deleteByTheirs) {
|
|
14935
|
-
|
|
14937
|
+
step("read-conflict-blobs", { filepath: p });
|
|
14938
|
+
const ourBlob = await readBlob(dir, cache2, ourTip, p);
|
|
14939
|
+
step("delete-conflict-file", { filepath: p });
|
|
14936
14940
|
await unlink3(path24.join(dir, p)).catch((e) => {
|
|
14937
14941
|
if (e?.code !== "ENOENT")
|
|
14938
14942
|
throw e;
|
|
14939
14943
|
});
|
|
14940
|
-
|
|
14941
|
-
postRestore.push({ path: p, bytes: ourBlob.blob });
|
|
14944
|
+
postRestore.push({ path: p, bytes: ourBlob.blob });
|
|
14942
14945
|
}
|
|
14946
|
+
step("snapshot-equalized");
|
|
14943
14947
|
if (await hasPendingChanges(dir))
|
|
14944
14948
|
await snapshot(CONVERGE_PREPARE_MESSAGE);
|
|
14945
14949
|
};
|
|
@@ -14947,6 +14951,7 @@ async function convergeMerge(params) {
|
|
|
14947
14951
|
await attempt();
|
|
14948
14952
|
} catch (e) {
|
|
14949
14953
|
if (isMergeConflictError(e)) {
|
|
14954
|
+
logger.info("merge", "resolving conflicts", errorLogData(e));
|
|
14950
14955
|
await equalize({
|
|
14951
14956
|
binary: e.data.bothModified ?? [],
|
|
14952
14957
|
deleteByUs: e.data.deleteByUs ?? [],
|
|
@@ -14954,8 +14959,14 @@ async function convergeMerge(params) {
|
|
|
14954
14959
|
});
|
|
14955
14960
|
await attempt();
|
|
14956
14961
|
} else if (e?.code === "MergeNotSupportedError") {
|
|
14962
|
+
step("merge-base");
|
|
14957
14963
|
const added = await bothAddedPaths(dir, cache2, ourTip, theirs, false);
|
|
14958
|
-
if (added === null
|
|
14964
|
+
if (added === null) {
|
|
14965
|
+
throw Object.assign(new Error("No common commits between local and online histories", { cause: e }), {
|
|
14966
|
+
code: "UnrelatedHistoriesError"
|
|
14967
|
+
});
|
|
14968
|
+
}
|
|
14969
|
+
if (added.length === 0)
|
|
14959
14970
|
throw e;
|
|
14960
14971
|
await equalize({ binary: added, deleteByUs: [], deleteByTheirs: [] });
|
|
14961
14972
|
await attempt();
|
|
@@ -14963,11 +14974,14 @@ async function convergeMerge(params) {
|
|
|
14963
14974
|
throw e;
|
|
14964
14975
|
}
|
|
14965
14976
|
}
|
|
14977
|
+
step("checkout");
|
|
14966
14978
|
try {
|
|
14967
14979
|
await git5.checkout({ fs: gitFs, dir, cache: cache2, ref: branch, force: false });
|
|
14968
14980
|
} catch (e) {
|
|
14969
14981
|
if (!isCheckoutConflict(e))
|
|
14970
14982
|
throw e;
|
|
14983
|
+
logger.warn("checkout", "checkout refused; restoring branch", errorLogData(e));
|
|
14984
|
+
step("rollback", { local: tipBeforeMerge });
|
|
14971
14985
|
await git5.writeRef({
|
|
14972
14986
|
fs: gitFs,
|
|
14973
14987
|
dir,
|
|
@@ -14975,14 +14989,19 @@ async function convergeMerge(params) {
|
|
|
14975
14989
|
value: tipBeforeMerge,
|
|
14976
14990
|
force: true
|
|
14977
14991
|
});
|
|
14992
|
+
step("checkout");
|
|
14978
14993
|
throw e;
|
|
14979
14994
|
}
|
|
14980
14995
|
if (postRestore.length > 0) {
|
|
14981
|
-
for (const r of postRestore)
|
|
14996
|
+
for (const r of postRestore) {
|
|
14997
|
+
step("restore-conflict-file", { filepath: r.path });
|
|
14982
14998
|
await writeTreeFile(dir, r.path, r.bytes);
|
|
14999
|
+
}
|
|
15000
|
+
step("snapshot-restored");
|
|
14983
15001
|
if (await hasPendingChanges(dir))
|
|
14984
15002
|
await snapshot(CONVERGE_RESTORE_MESSAGE);
|
|
14985
15003
|
}
|
|
15004
|
+
step("merged-ref");
|
|
14986
15005
|
return {
|
|
14987
15006
|
oid: await git5.resolveRef({ fs: gitFs, dir, ref: branch }),
|
|
14988
15007
|
combinedFiles: [...combined].sort(),
|
|
@@ -15005,7 +15024,7 @@ function isPushRejected(e) {
|
|
|
15005
15024
|
function isUnrelatedHistories(e) {
|
|
15006
15025
|
const code = e?.code;
|
|
15007
15026
|
const msg = e?.message ?? String(e);
|
|
15008
|
-
return code === "
|
|
15027
|
+
return code === "UnrelatedHistoriesError" || /unrelated histories|no common commits|refusing to merge unrelated/i.test(msg);
|
|
15009
15028
|
}
|
|
15010
15029
|
async function historyUnreadable(dir) {
|
|
15011
15030
|
try {
|
|
@@ -15027,157 +15046,210 @@ function defaultSleep2(ms) {
|
|
|
15027
15046
|
async function syncProject(options) {
|
|
15028
15047
|
const http3 = options.httpClient ?? defaultGitHttp;
|
|
15029
15048
|
const push = options.push ?? true;
|
|
15030
|
-
const
|
|
15031
|
-
const
|
|
15049
|
+
const requestedAttempts = options.retry?.attempts;
|
|
15050
|
+
const attempts = Number.isFinite(requestedAttempts) ? Math.min(10, Math.max(1, Math.floor(requestedAttempts))) : DEFAULT_SYNC_RETRY.attempts;
|
|
15051
|
+
const requestedBackoff = options.retry?.backoffMs;
|
|
15052
|
+
const backoffMs = Number.isFinite(requestedBackoff) ? Math.min(30000, Math.max(0, requestedBackoff)) : DEFAULT_SYNC_RETRY.backoffMs;
|
|
15032
15053
|
const sleep = options.retry?.sleep ?? defaultSleep2;
|
|
15033
|
-
const
|
|
15034
|
-
|
|
15035
|
-
|
|
15036
|
-
|
|
15037
|
-
|
|
15038
|
-
|
|
15039
|
-
|
|
15040
|
-
|
|
15041
|
-
|
|
15042
|
-
|
|
15043
|
-
|
|
15044
|
-
|
|
15045
|
-
|
|
15046
|
-
|
|
15047
|
-
|
|
15048
|
-
|
|
15054
|
+
const secrets = options.credential ? [options.credential.token] : [];
|
|
15055
|
+
const logger = resolveLogger(options.logFile, "sync", {
|
|
15056
|
+
context: { run: randomUUID4(), version: package_default.version, platform: process.platform, node: process.version },
|
|
15057
|
+
secrets
|
|
15058
|
+
});
|
|
15059
|
+
const started = performance.now();
|
|
15060
|
+
let stage = "repo";
|
|
15061
|
+
const step = (next, data) => {
|
|
15062
|
+
stage = next;
|
|
15063
|
+
logger.info(stage, "starting", data);
|
|
15064
|
+
};
|
|
15065
|
+
const finish = (outcome) => {
|
|
15066
|
+
logger.info("result", "sync complete", {
|
|
15067
|
+
status: outcome.status,
|
|
15068
|
+
stage,
|
|
15069
|
+
push,
|
|
15070
|
+
elapsedMs: Math.round(performance.now() - started),
|
|
15071
|
+
snapshot: outcome.snapshotId ?? "none",
|
|
15072
|
+
filesChanged: outcome.filesChanged ?? false
|
|
15049
15073
|
});
|
|
15050
|
-
|
|
15051
|
-
|
|
15052
|
-
|
|
15053
|
-
|
|
15054
|
-
|
|
15055
|
-
|
|
15056
|
-
|
|
15057
|
-
|
|
15058
|
-
|
|
15059
|
-
|
|
15060
|
-
|
|
15074
|
+
return outcome;
|
|
15075
|
+
};
|
|
15076
|
+
logger.info("sync", "starting sync", { push, attempts });
|
|
15077
|
+
const run2 = async () => {
|
|
15078
|
+
step("repo");
|
|
15079
|
+
const dir = await repoDirFor(options.projectDir);
|
|
15080
|
+
step("lock");
|
|
15081
|
+
return withRepoLock(dir, async () => {
|
|
15082
|
+
const cache2 = {};
|
|
15083
|
+
let snapshotId;
|
|
15084
|
+
let pulled = false;
|
|
15085
|
+
let filesChanged = false;
|
|
15086
|
+
const combinedFiles = new Set;
|
|
15087
|
+
const keptBothFiles = [];
|
|
15088
|
+
const base = () => ({
|
|
15089
|
+
...filesChanged ? { filesChanged: true } : {},
|
|
15090
|
+
...snapshotId ? { snapshotId } : {}
|
|
15091
|
+
});
|
|
15092
|
+
const convergeExtras = () => ({
|
|
15093
|
+
...combinedFiles.size > 0 ? { combinedFiles: [...combinedFiles].sort() } : {},
|
|
15094
|
+
...keptBothFiles.length > 0 ? { keptBothFiles } : {}
|
|
15095
|
+
});
|
|
15096
|
+
try {
|
|
15097
|
+
step("branch");
|
|
15098
|
+
const branch = await currentBranchOrThrow(dir);
|
|
15099
|
+
step("transport");
|
|
15100
|
+
const transport = await resolveTransport(dir, options);
|
|
15101
|
+
if (transport.credential) {
|
|
15102
|
+
secrets.push(transport.credential.token);
|
|
15103
|
+
const username = transport.credential.kind === "github-oauth" ? "x-access-token" : transport.credential.username || transport.credential.token;
|
|
15104
|
+
secrets.push(Buffer.from(`${username}:${transport.credential.token}`).toString("base64"));
|
|
15105
|
+
}
|
|
15106
|
+
logger.info("transport", "resolved", {
|
|
15107
|
+
branch,
|
|
15108
|
+
remote: transport.remote,
|
|
15109
|
+
host: transport.host,
|
|
15110
|
+
credential: Boolean(transport.credential)
|
|
15061
15111
|
});
|
|
15062
|
-
|
|
15063
|
-
|
|
15064
|
-
|
|
15065
|
-
const remoteTip = await fetchRemoteTip(dir, branch, transport, http3, cache2);
|
|
15066
|
-
let localTip = await git6.resolveRef({ fs: gitFs, dir, ref: branch });
|
|
15067
|
-
if (push || remoteTip !== null && remoteTip !== localTip) {
|
|
15068
|
-
const lateSnapshot = await snapshotBeforeAction({
|
|
15112
|
+
if (push) {
|
|
15113
|
+
step("snapshot");
|
|
15114
|
+
snapshotId = await snapshotBeforeAction({
|
|
15069
15115
|
projectDir: options.projectDir,
|
|
15070
15116
|
dir,
|
|
15071
|
-
message:
|
|
15117
|
+
message: options.message,
|
|
15072
15118
|
authorName: options.authorName,
|
|
15073
15119
|
authorEmail: options.authorEmail,
|
|
15074
15120
|
cache: cache2
|
|
15075
15121
|
});
|
|
15076
|
-
if (lateSnapshot) {
|
|
15077
|
-
snapshotId = lateSnapshot;
|
|
15078
|
-
localTip = await git6.resolveRef({ fs: gitFs, dir, ref: branch });
|
|
15079
|
-
}
|
|
15080
15122
|
}
|
|
15081
|
-
|
|
15082
|
-
|
|
15083
|
-
|
|
15084
|
-
|
|
15085
|
-
|
|
15086
|
-
|
|
15123
|
+
for (let attempt = 0;attempt < attempts; attempt++) {
|
|
15124
|
+
logger.info("sync", `sync pass ${attempt + 1}/${attempts}`);
|
|
15125
|
+
step("fetch", { attempt: attempt + 1, branch, remote: transport.remote });
|
|
15126
|
+
const remoteTip = await fetchRemoteTip(dir, branch, transport, http3, cache2);
|
|
15127
|
+
step("local-ref", { fetched: remoteTip ?? "none" });
|
|
15128
|
+
let localTip = await git6.resolveRef({ fs: gitFs, dir, ref: branch });
|
|
15129
|
+
if (push || remoteTip !== null && remoteTip !== localTip) {
|
|
15130
|
+
step("snapshot-after-fetch");
|
|
15131
|
+
const lateSnapshot = await snapshotBeforeAction({
|
|
15132
|
+
projectDir: options.projectDir,
|
|
15087
15133
|
dir,
|
|
15088
|
-
|
|
15089
|
-
branch,
|
|
15090
|
-
theirs: remoteTip,
|
|
15091
|
-
author: await resolveGitAuthor(dir, options.authorName, options.authorEmail),
|
|
15134
|
+
message: SYNC_LATE_EDIT_MESSAGE,
|
|
15092
15135
|
authorName: options.authorName,
|
|
15093
|
-
authorEmail: options.authorEmail
|
|
15136
|
+
authorEmail: options.authorEmail,
|
|
15137
|
+
cache: cache2
|
|
15094
15138
|
});
|
|
15095
|
-
|
|
15096
|
-
|
|
15097
|
-
|
|
15098
|
-
return { status: "error", message: MSG_UNRELATED, ...base() };
|
|
15139
|
+
if (lateSnapshot) {
|
|
15140
|
+
snapshotId = lateSnapshot;
|
|
15141
|
+
localTip = await git6.resolveRef({ fs: gitFs, dir, ref: branch });
|
|
15099
15142
|
}
|
|
15100
|
-
throw e;
|
|
15101
15143
|
}
|
|
15102
|
-
|
|
15103
|
-
|
|
15104
|
-
|
|
15105
|
-
|
|
15106
|
-
|
|
15107
|
-
|
|
15108
|
-
|
|
15109
|
-
|
|
15110
|
-
|
|
15111
|
-
|
|
15112
|
-
|
|
15113
|
-
|
|
15114
|
-
|
|
15144
|
+
logger.info("sync", `branch=${branch} local=${short(localTip)} fetched=${short(remoteTip)} snapshot=${snapshotId ? short(snapshotId) : "none"}`);
|
|
15145
|
+
let tip = localTip;
|
|
15146
|
+
if (remoteTip && remoteTip !== localTip) {
|
|
15147
|
+
let converge;
|
|
15148
|
+
step("author");
|
|
15149
|
+
const author = await resolveGitAuthor(dir, options.authorName, options.authorEmail);
|
|
15150
|
+
try {
|
|
15151
|
+
step("merge", { local: localTip, fetched: remoteTip });
|
|
15152
|
+
converge = await convergeMerge({
|
|
15153
|
+
dir,
|
|
15154
|
+
cache: cache2,
|
|
15155
|
+
branch,
|
|
15156
|
+
theirs: remoteTip,
|
|
15157
|
+
author,
|
|
15158
|
+
onStep: step,
|
|
15159
|
+
logger,
|
|
15160
|
+
authorName: options.authorName,
|
|
15161
|
+
authorEmail: options.authorEmail
|
|
15162
|
+
});
|
|
15163
|
+
} catch (e) {
|
|
15164
|
+
if (isUnrelatedHistories(e)) {
|
|
15165
|
+
logger.warn(stage, "unrelated histories — refusing to combine", errorLogData(e));
|
|
15166
|
+
return { status: "error", message: MSG_UNRELATED, ...base() };
|
|
15167
|
+
}
|
|
15168
|
+
throw e;
|
|
15169
|
+
}
|
|
15170
|
+
tip = converge.oid;
|
|
15171
|
+
if (tip !== localTip) {
|
|
15172
|
+
pulled = true;
|
|
15173
|
+
step("compare-trees");
|
|
15174
|
+
const [before, after] = await Promise.all([
|
|
15175
|
+
git6.readCommit({ fs: gitFs, dir, cache: cache2, oid: localTip }),
|
|
15176
|
+
git6.readCommit({ fs: gitFs, dir, cache: cache2, oid: tip })
|
|
15177
|
+
]);
|
|
15178
|
+
filesChanged = filesChanged || before.commit.tree !== after.commit.tree;
|
|
15179
|
+
for (const f of converge.combinedFiles)
|
|
15180
|
+
combinedFiles.add(f);
|
|
15181
|
+
keptBothFiles.push(...converge.keptBothFiles);
|
|
15182
|
+
if (converge.combinedFiles.length > 0) {
|
|
15183
|
+
logger.info("sync", `combined with markers`, { files: converge.combinedFiles });
|
|
15184
|
+
}
|
|
15115
15185
|
}
|
|
15116
15186
|
}
|
|
15117
|
-
|
|
15118
|
-
|
|
15119
|
-
|
|
15120
|
-
|
|
15121
|
-
|
|
15122
|
-
|
|
15123
|
-
|
|
15124
|
-
}
|
|
15125
|
-
|
|
15126
|
-
|
|
15127
|
-
|
|
15187
|
+
if (tip === remoteTip) {
|
|
15188
|
+
logger.info("sync", `up-to-date`, { pulled });
|
|
15189
|
+
return {
|
|
15190
|
+
status: "up-to-date",
|
|
15191
|
+
message: pulled ? MSG_UP_TO_DATE_PULLED : MSG_UP_TO_DATE,
|
|
15192
|
+
...base()
|
|
15193
|
+
};
|
|
15194
|
+
}
|
|
15195
|
+
if (!push) {
|
|
15196
|
+
logger.info("sync", `pull-only pass complete — push deferred`, { pulled });
|
|
15197
|
+
return {
|
|
15198
|
+
status: "up-to-date",
|
|
15199
|
+
message: pulled ? MSG_UP_TO_DATE_PULLED : MSG_UP_TO_DATE,
|
|
15200
|
+
...convergeExtras(),
|
|
15201
|
+
...base()
|
|
15202
|
+
};
|
|
15203
|
+
}
|
|
15204
|
+
step("push", { branch, local: tip, fetched: remoteTip ?? "none" });
|
|
15205
|
+
try {
|
|
15206
|
+
await git6.push({
|
|
15207
|
+
fs: gitFs,
|
|
15208
|
+
http: http3,
|
|
15209
|
+
dir,
|
|
15210
|
+
cache: cache2,
|
|
15211
|
+
remote: transport.remote,
|
|
15212
|
+
ref: branch,
|
|
15213
|
+
...onAuthFor(transport.credential)
|
|
15214
|
+
});
|
|
15215
|
+
} catch (e) {
|
|
15216
|
+
if (!isPushRejected(e))
|
|
15217
|
+
throw e;
|
|
15218
|
+
logger.info("push", `push rejected (non-fast-forward) — retrying`, errorLogData(e));
|
|
15219
|
+
if (attempt < attempts - 1 && backoffMs > 0)
|
|
15220
|
+
await sleep(backoffMs);
|
|
15221
|
+
continue;
|
|
15222
|
+
}
|
|
15223
|
+
logger.info("sync", `synced`, { pulled });
|
|
15128
15224
|
return {
|
|
15129
|
-
status: "
|
|
15130
|
-
message: pulled ?
|
|
15225
|
+
status: "synced",
|
|
15226
|
+
message: pulled ? MSG_SYNCED_MERGED : MSG_SYNCED,
|
|
15227
|
+
mergedRemoteChanges: pulled,
|
|
15131
15228
|
...convergeExtras(),
|
|
15132
15229
|
...base()
|
|
15133
15230
|
};
|
|
15134
15231
|
}
|
|
15135
|
-
|
|
15136
|
-
|
|
15137
|
-
|
|
15138
|
-
|
|
15139
|
-
|
|
15140
|
-
|
|
15141
|
-
|
|
15142
|
-
|
|
15143
|
-
|
|
15144
|
-
}
|
|
15145
|
-
} catch (e) {
|
|
15146
|
-
if (!isPushRejected(e)) {
|
|
15147
|
-
const err = e;
|
|
15148
|
-
logger.warn("sync", "push failed", {
|
|
15149
|
-
code: err?.code ?? err?.name,
|
|
15150
|
-
error: String(err?.message ?? e).replace(/\s+/g, " ").trim().replace(/\/\/[^/\s]*@/g, "//")
|
|
15151
|
-
});
|
|
15152
|
-
throw e;
|
|
15153
|
-
}
|
|
15154
|
-
logger.info("sync", `push rejected (non-fast-forward) — retrying`);
|
|
15155
|
-
if (attempt < attempts - 1 && backoffMs > 0)
|
|
15156
|
-
await sleep(backoffMs);
|
|
15157
|
-
continue;
|
|
15232
|
+
logger.error("sync", `exhausted ${attempts} retry attempts (race)`);
|
|
15233
|
+
return { status: "error", message: MSG_BUSY, ...convergeExtras(), ...base() };
|
|
15234
|
+
} catch (e) {
|
|
15235
|
+
logger.warn(stage, `${stage} failed`, errorLogData(e));
|
|
15236
|
+
const setupMsg = setupErrorMessage(e);
|
|
15237
|
+
if (setupMsg)
|
|
15238
|
+
return { status: "error", message: setupMsg, ...base() };
|
|
15239
|
+
if (await historyUnreadable(dir)) {
|
|
15240
|
+
logger.error("sync", "the project's history could not be read");
|
|
15241
|
+
return { status: "error", message: MSG_HISTORY_UNREADABLE, ...base() };
|
|
15158
15242
|
}
|
|
15159
|
-
|
|
15160
|
-
return {
|
|
15161
|
-
status: "synced",
|
|
15162
|
-
message: pulled ? MSG_SYNCED_MERGED : MSG_SYNCED,
|
|
15163
|
-
mergedRemoteChanges: pulled,
|
|
15164
|
-
...convergeExtras(),
|
|
15165
|
-
...base()
|
|
15166
|
-
};
|
|
15167
|
-
}
|
|
15168
|
-
logger.error("sync", `exhausted ${attempts} retry attempts (race)`);
|
|
15169
|
-
return { status: "error", message: MSG_BUSY, ...convergeExtras(), ...base() };
|
|
15170
|
-
} catch (e) {
|
|
15171
|
-
const setupMsg = setupErrorMessage(e);
|
|
15172
|
-
if (setupMsg)
|
|
15173
|
-
return { status: "error", message: setupMsg, ...base() };
|
|
15174
|
-
if (await historyUnreadable(dir)) {
|
|
15175
|
-
logger.error("sync", "the project's history could not be read");
|
|
15176
|
-
return { status: "error", message: MSG_HISTORY_UNREADABLE, ...base() };
|
|
15243
|
+
return { ...failureOutcome(e, snapshotId), ...filesChanged ? { filesChanged: true } : {} };
|
|
15177
15244
|
}
|
|
15178
|
-
|
|
15179
|
-
|
|
15180
|
-
|
|
15245
|
+
});
|
|
15246
|
+
};
|
|
15247
|
+
try {
|
|
15248
|
+
return finish(await run2());
|
|
15249
|
+
} catch (e) {
|
|
15250
|
+
logger.warn(stage, `${stage} failed`, errorLogData(e));
|
|
15251
|
+
return finish(failureOutcome(e));
|
|
15252
|
+
}
|
|
15181
15253
|
}
|
|
15182
15254
|
async function refreshRemoteCopies(options) {
|
|
15183
15255
|
const http3 = options.httpClient ?? defaultGitHttp;
|
|
@@ -63,6 +63,63 @@ import git from "isomorphic-git";
|
|
|
63
63
|
// src/lib/remote-auth/operation-log.ts
|
|
64
64
|
import { appendFileSync, mkdirSync } from "node:fs";
|
|
65
65
|
import path from "node:path";
|
|
66
|
+
function sanitizeLogText(value, secrets = []) {
|
|
67
|
+
let text = value;
|
|
68
|
+
for (const secret of secrets) {
|
|
69
|
+
if (!secret)
|
|
70
|
+
continue;
|
|
71
|
+
for (const form of [secret, encodeURIComponent(secret)]) {
|
|
72
|
+
text = text.split(form).join("[redacted]");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return text.replace(/\/\/[^/\s]*@/g, "//").replace(/([?&](?:access_token|token|password|secret|key|code)=)[^&#\s]*/gi, "$1[redacted]").replace(/\b(Bearer|Basic)\s+[A-Za-z0-9+/_.=~-]+/gi, "$1 [redacted]").replace(/\b(?:gh[pousr]_[A-Za-z0-9_]+|github_pat_[A-Za-z0-9_]+)/g, "[redacted]").replace(/[\s\x00-\x1f\x7f]+/g, " ").trim().slice(0, 6000);
|
|
76
|
+
}
|
|
77
|
+
function errorLogData(error) {
|
|
78
|
+
const out = { error: error == null ? String(error) : undefined };
|
|
79
|
+
let current = error;
|
|
80
|
+
const seen = new Set;
|
|
81
|
+
for (let depth = 0;depth < 3 && current != null && !seen.has(current); depth++) {
|
|
82
|
+
seen.add(current);
|
|
83
|
+
const prefix = depth === 0 ? "" : `cause${depth}_`;
|
|
84
|
+
if (typeof current !== "object") {
|
|
85
|
+
out[`${prefix}error`] = String(current);
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
const err = current;
|
|
89
|
+
out[`${prefix}error`] = typeof err.message === "string" ? err.message : "Unknown error";
|
|
90
|
+
for (const key of ["code", "name", "caller", "stack", "syscall", "path"]) {
|
|
91
|
+
if (typeof err[key] === "string")
|
|
92
|
+
out[`${prefix}${key}`] = err[key];
|
|
93
|
+
}
|
|
94
|
+
const data = err.data;
|
|
95
|
+
if (data && typeof data === "object") {
|
|
96
|
+
for (const key of [
|
|
97
|
+
"statusCode",
|
|
98
|
+
"reason",
|
|
99
|
+
"prettyDetails",
|
|
100
|
+
"oid",
|
|
101
|
+
"ref",
|
|
102
|
+
"what",
|
|
103
|
+
"filepath",
|
|
104
|
+
"filepaths",
|
|
105
|
+
"bothModified",
|
|
106
|
+
"deleteByUs",
|
|
107
|
+
"deleteByTheirs"
|
|
108
|
+
]) {
|
|
109
|
+
const value = data[key];
|
|
110
|
+
if (typeof value === "string" || typeof value === "number")
|
|
111
|
+
out[`${prefix}${key}`] = value;
|
|
112
|
+
else if (Array.isArray(value)) {
|
|
113
|
+
out[`${prefix}${key}`] = value.filter((v) => typeof v === "string").slice(0, 50);
|
|
114
|
+
if (value.length > 50)
|
|
115
|
+
out[`${prefix}${key}Count`] = value.length;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
current = err.cause;
|
|
120
|
+
}
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
66
123
|
var noop = {
|
|
67
124
|
debug: () => {},
|
|
68
125
|
info: () => {},
|
|
@@ -70,7 +127,7 @@ var noop = {
|
|
|
70
127
|
error: () => {}
|
|
71
128
|
};
|
|
72
129
|
var LEVEL_ORDER = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
73
|
-
function createFileLogger(logFile, operation, minLevel = "debug") {
|
|
130
|
+
function createFileLogger(logFile, operation, minLevel = "debug", options = {}) {
|
|
74
131
|
try {
|
|
75
132
|
mkdirSync(path.dirname(logFile), { recursive: true });
|
|
76
133
|
} catch {
|
|
@@ -80,8 +137,9 @@ function createFileLogger(logFile, operation, minLevel = "debug") {
|
|
|
80
137
|
if (LEVEL_ORDER[level] < LEVEL_ORDER[minLevel])
|
|
81
138
|
return;
|
|
82
139
|
const ts = new Date().toISOString();
|
|
83
|
-
const
|
|
84
|
-
const
|
|
140
|
+
const clean = (value) => sanitizeLogText(value, options.secrets);
|
|
141
|
+
const fields = formatData({ ...options.context, ...data }, clean);
|
|
142
|
+
const line = `[${ts}] ${level.toUpperCase().padEnd(5)} ${clean(operation)}: step=${clean(step)}${fields} | ${clean(message)}
|
|
85
143
|
`;
|
|
86
144
|
try {
|
|
87
145
|
appendFileSync(logFile, line, "utf8");
|
|
@@ -94,12 +152,12 @@ function createFileLogger(logFile, operation, minLevel = "debug") {
|
|
|
94
152
|
error: (step, message, data) => write("error", step, message, data)
|
|
95
153
|
};
|
|
96
154
|
}
|
|
97
|
-
function resolveLogger(logFile, operation) {
|
|
155
|
+
function resolveLogger(logFile, operation, options = {}) {
|
|
98
156
|
if (!logFile)
|
|
99
157
|
return noop;
|
|
100
|
-
return createFileLogger(logFile, operation);
|
|
158
|
+
return createFileLogger(logFile, operation, "debug", options);
|
|
101
159
|
}
|
|
102
|
-
function formatData(data) {
|
|
160
|
+
function formatData(data, clean) {
|
|
103
161
|
if (!data)
|
|
104
162
|
return "";
|
|
105
163
|
const parts = [];
|
|
@@ -107,9 +165,9 @@ function formatData(data) {
|
|
|
107
165
|
if (value === undefined)
|
|
108
166
|
continue;
|
|
109
167
|
if (Array.isArray(value)) {
|
|
110
|
-
parts.push(`${key}=${value.join(",")}`);
|
|
168
|
+
parts.push(`${clean(key)}=${clean(value.join(","))}`);
|
|
111
169
|
} else {
|
|
112
|
-
parts.push(`${key}=${String(value)}`);
|
|
170
|
+
parts.push(`${clean(key)}=${clean(String(value))}`);
|
|
113
171
|
}
|
|
114
172
|
}
|
|
115
173
|
return parts.length > 0 ? " " + parts.join(" ") : "";
|
|
@@ -635,4 +693,4 @@ async function switchBranch(options) {
|
|
|
635
693
|
});
|
|
636
694
|
}
|
|
637
695
|
|
|
638
|
-
export { gitFs, resolveLogger, gitScopeFor, HISTORY_PAGE_LIMIT, withRepoLock, __repoLockQueueSizeForTests, resolveGitAuthor, listWorkdirChanges, stageChanges, hasPendingChanges, hasUncommittedChanges, snapshotWorkingTreeUnlocked, isNoChangesError, providerFor, gitDirFor, snapshotStagingMarkerPath, RESTORE_BACKUP_MESSAGE, AUTO_SNAPSHOT_MESSAGE, restoreVersionWithBackup, SWITCH_BRANCH_SNAPSHOT_MESSAGE, listLocalBranches, switchBranch };
|
|
696
|
+
export { gitFs, errorLogData, resolveLogger, gitScopeFor, HISTORY_PAGE_LIMIT, withRepoLock, __repoLockQueueSizeForTests, resolveGitAuthor, listWorkdirChanges, stageChanges, hasPendingChanges, hasUncommittedChanges, snapshotWorkingTreeUnlocked, isNoChangesError, providerFor, gitDirFor, snapshotStagingMarkerPath, RESTORE_BACKUP_MESSAGE, AUTO_SNAPSHOT_MESSAGE, restoreVersionWithBackup, SWITCH_BRANCH_SNAPSHOT_MESSAGE, listLocalBranches, switchBranch };
|
package/dist/index.js
CHANGED
|
@@ -143,7 +143,7 @@ import {
|
|
|
143
143
|
syncProject,
|
|
144
144
|
testRemoteAccess,
|
|
145
145
|
validateProjectExtensions
|
|
146
|
-
} from "./index-
|
|
146
|
+
} from "./index-771dmhpc.js";
|
|
147
147
|
import {
|
|
148
148
|
BUILD_DIAGNOSTIC_CODES
|
|
149
149
|
} from "./index-12pvhbyz.js";
|
|
@@ -163,7 +163,7 @@ import {
|
|
|
163
163
|
resolveLogger,
|
|
164
164
|
restoreVersionWithBackup,
|
|
165
165
|
switchBranch
|
|
166
|
-
} from "./index-
|
|
166
|
+
} from "./index-c5ev4r1y.js";
|
|
167
167
|
import {
|
|
168
168
|
capabilitiesFor,
|
|
169
169
|
detectProjectSource,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { GitCache, KeptBothFile } from "./sync-types.ts";
|
|
2
|
+
import { type LogData, type OperationLogger } from "./operation-log.ts";
|
|
2
3
|
export type { KeptBothFile };
|
|
3
4
|
/** Type guard exposing MergeConflictError's per-file payload. */
|
|
4
5
|
export declare function isMergeConflictError(e: unknown): e is {
|
|
@@ -60,4 +61,6 @@ export declare function convergeMerge(params: {
|
|
|
60
61
|
};
|
|
61
62
|
authorName?: string;
|
|
62
63
|
authorEmail?: string;
|
|
64
|
+
onStep?: (stage: string, data?: LogData) => void;
|
|
65
|
+
logger?: OperationLogger;
|
|
63
66
|
}): Promise<ConvergeResult>;
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* it survives even if the process crashes mid-operation.
|
|
10
10
|
*
|
|
11
11
|
* SECURITY INVARIANT: this logger NEVER writes secrets, tokens, credentials,
|
|
12
|
-
* or full remote URLs with embedded auth.
|
|
13
|
-
*
|
|
12
|
+
* or full remote URLs with embedded auth. All fields are sanitized at the
|
|
13
|
+
* write boundary; callers must still avoid supplying arbitrary request data.
|
|
14
14
|
*
|
|
15
15
|
* The logger is injectable: callers pass a `logFile` path and get a file
|
|
16
16
|
* logger; omit it and a no-op logger is used (backward compatible — existing
|
|
@@ -27,8 +27,7 @@
|
|
|
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
|
|
30
|
-
* file
|
|
31
|
-
* desktop ensures `userData/logs/` exists before passing the path).
|
|
30
|
+
* file and its parent directory when needed).
|
|
32
31
|
*
|
|
33
32
|
* Compatible with `bun build --compile`: no runtime package.json reads, no
|
|
34
33
|
* computed-path dynamic imports, no native bindings — just `fs.appendFileSync`.
|
|
@@ -42,6 +41,15 @@ export interface OperationLogger {
|
|
|
42
41
|
}
|
|
43
42
|
/** Structured key-value fields appended after the step. No secrets. */
|
|
44
43
|
export type LogData = Record<string, string | number | boolean | string[] | undefined>;
|
|
44
|
+
interface LogOptions {
|
|
45
|
+
context?: LogData;
|
|
46
|
+
/** Mutable per-operation list; credentials may be resolved after logging starts. */
|
|
47
|
+
secrets?: readonly string[];
|
|
48
|
+
}
|
|
49
|
+
/** One line, bounded, with credentials removed BEFORE truncation. */
|
|
50
|
+
export declare function sanitizeLogText(value: string, secrets?: readonly string[]): string;
|
|
51
|
+
/** Allowlist Git/OS diagnostic fields; never serialize request/config/credential objects. */
|
|
52
|
+
export declare function errorLogData(error: unknown): LogData;
|
|
45
53
|
/**
|
|
46
54
|
* Create a file-backed operation logger. Each call appends one line to
|
|
47
55
|
* `logFile` (created if it doesn't exist; the parent directory is created
|
|
@@ -52,10 +60,11 @@ export type LogData = Record<string, string | number | boolean | string[] | unde
|
|
|
52
60
|
* @param minLevel Minimum level to write (default: "debug" — write everything).
|
|
53
61
|
* @param operation Operation name shown in each line (e.g. "sync", "recovery").
|
|
54
62
|
*/
|
|
55
|
-
export declare function createFileLogger(logFile: string, operation: string, minLevel?: LogLevel): OperationLogger;
|
|
63
|
+
export declare function createFileLogger(logFile: string, operation: string, minLevel?: LogLevel, options?: LogOptions): OperationLogger;
|
|
56
64
|
/**
|
|
57
65
|
* Resolve a logger from an optional `logFile` path. Returns a no-op logger
|
|
58
66
|
* when `logFile` is undefined or empty (backward compatible — callers that
|
|
59
67
|
* don't pass `logFile` see zero behavior change and zero file I/O).
|
|
60
68
|
*/
|
|
61
|
-
export declare function resolveLogger(logFile: string | undefined, operation: string): OperationLogger;
|
|
69
|
+
export declare function resolveLogger(logFile: string | undefined, operation: string, options?: LogOptions): OperationLogger;
|
|
70
|
+
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Type/interface declarations for snapshot-first sync
|
|
3
|
-
* (#15, ADR 0006 D5).
|
|
4
|
-
*
|
|
2
|
+
* Type/interface declarations for snapshot-first sync
|
|
3
|
+
* (#15, ADR 0006 D5). Shared by the orchestrator, transport and
|
|
4
|
+
* convergence modules share ONE definition of each result/option shape.
|
|
5
5
|
* Pure type surface — no runtime code.
|
|
6
6
|
*/
|
|
7
7
|
import type httpNode from "isomorphic-git/http/node";
|
|
@@ -102,16 +102,16 @@ export interface SyncProjectOptions {
|
|
|
102
102
|
retry?: SyncRetryOptions;
|
|
103
103
|
/**
|
|
104
104
|
* Optional path to a log file for debugging sync/recovery operations.
|
|
105
|
-
* When set, each
|
|
106
|
-
*
|
|
105
|
+
* When set, each stage and terminal outcome is appended with a run ID,
|
|
106
|
+
* runtime version, timing, and sanitized error details. Never logs credentials.
|
|
107
107
|
*/
|
|
108
108
|
logFile?: string;
|
|
109
109
|
}
|
|
110
110
|
/** Bounded retry policy for the sync race loop (BUG 6). */
|
|
111
111
|
export interface SyncRetryOptions {
|
|
112
|
-
/** Max pull→push passes before giving up.
|
|
112
|
+
/** Max pull→push passes before giving up. Finite integer clamped to 1–10; non-finite values use default 3. */
|
|
113
113
|
attempts?: number;
|
|
114
|
-
/** Delay between passes, in ms. Clamped to
|
|
114
|
+
/** Delay between passes, in ms. Clamped to 0–30,000; non-finite values use default 150. */
|
|
115
115
|
backoffMs?: number;
|
|
116
116
|
/** Injectable delay (tests only); defaults to a real timer. */
|
|
117
117
|
sleep?: (ms: number) => Promise<void>;
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
2
|
MANIFEST_FILENAMES,
|
|
3
3
|
runLint
|
|
4
|
-
} from "./cli-
|
|
4
|
+
} from "./cli-d8my7adx.js";
|
|
5
5
|
import {
|
|
6
6
|
log
|
|
7
|
-
} from "./cli-
|
|
7
|
+
} from "./cli-pqe8cahv.js";
|
|
8
8
|
import {
|
|
9
9
|
UsageError,
|
|
10
10
|
rejectExtraPositionals,
|
|
11
11
|
rejectUnknownFlags
|
|
12
|
-
} from "./cli-
|
|
12
|
+
} from "./cli-fxk3whwm.js";
|
|
13
13
|
import"./cli-sekhwex4.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-c9h2bk2k.js";
|
|
19
19
|
import"./cli-v5mp7a6q.js";
|
|
20
20
|
import"./cli-37x76zdn.js";
|
|
21
21
|
|
|
@@ -7,15 +7,15 @@ import {
|
|
|
7
7
|
TARGET_IDS,
|
|
8
8
|
scaffoldExtension,
|
|
9
9
|
scaffoldProject
|
|
10
|
-
} from "./cli-
|
|
10
|
+
} from "./cli-d8my7adx.js";
|
|
11
11
|
import {
|
|
12
12
|
resolveGhostscript
|
|
13
|
-
} from "./cli-
|
|
13
|
+
} from "./cli-pqe8cahv.js";
|
|
14
14
|
import {
|
|
15
15
|
UsageError,
|
|
16
16
|
rejectExtraPositionals,
|
|
17
17
|
rejectUnknownFlags
|
|
18
|
-
} from "./cli-
|
|
18
|
+
} from "./cli-fxk3whwm.js";
|
|
19
19
|
import"./cli-sekhwex4.js";
|
|
20
20
|
import {
|
|
21
21
|
isToolAvailable
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
import {
|
|
24
24
|
EXIT_CODES
|
|
25
25
|
} from "./cli-46ycxe6r.js";
|
|
26
|
-
import"./cli-
|
|
26
|
+
import"./cli-c9h2bk2k.js";
|
|
27
27
|
import"./cli-v5mp7a6q.js";
|
|
28
28
|
import"./cli-37x76zdn.js";
|
|
29
29
|
|
|
@@ -2,21 +2,21 @@ import {
|
|
|
2
2
|
executeValidation,
|
|
3
3
|
publishTargetFor,
|
|
4
4
|
reportMissingTools
|
|
5
|
-
} from "./cli-
|
|
5
|
+
} from "./cli-d8my7adx.js";
|
|
6
6
|
import {
|
|
7
7
|
log
|
|
8
|
-
} from "./cli-
|
|
8
|
+
} from "./cli-pqe8cahv.js";
|
|
9
9
|
import {
|
|
10
10
|
UsageError,
|
|
11
11
|
rejectExtraPositionals,
|
|
12
12
|
rejectUnknownFlags
|
|
13
|
-
} from "./cli-
|
|
13
|
+
} from "./cli-fxk3whwm.js";
|
|
14
14
|
import"./cli-sekhwex4.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-c9h2bk2k.js";
|
|
20
20
|
import"./cli-v5mp7a6q.js";
|
|
21
21
|
import"./cli-37x76zdn.js";
|
|
22
22
|
|
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
runBuild,
|
|
7
7
|
splitOutPath,
|
|
8
8
|
startPreviewServer
|
|
9
|
-
} from "./cli-
|
|
9
|
+
} from "./cli-d8my7adx.js";
|
|
10
10
|
import {
|
|
11
11
|
log
|
|
12
|
-
} from "./cli-
|
|
12
|
+
} from "./cli-pqe8cahv.js";
|
|
13
13
|
import {
|
|
14
14
|
UsageError,
|
|
15
15
|
parseFormat,
|
|
@@ -17,13 +17,13 @@ import {
|
|
|
17
17
|
rejectExtraPositionals,
|
|
18
18
|
rejectUnknownFlags,
|
|
19
19
|
resolvePort
|
|
20
|
-
} from "./cli-
|
|
20
|
+
} from "./cli-fxk3whwm.js";
|
|
21
21
|
import"./cli-sekhwex4.js";
|
|
22
22
|
import"./cli-c41yr7he.js";
|
|
23
23
|
import {
|
|
24
24
|
BuildError
|
|
25
25
|
} from "./cli-46ycxe6r.js";
|
|
26
|
-
import"./cli-
|
|
26
|
+
import"./cli-c9h2bk2k.js";
|
|
27
27
|
import"./cli-v5mp7a6q.js";
|
|
28
28
|
import"./cli-37x76zdn.js";
|
|
29
29
|
|
|
@@ -11,22 +11,22 @@ import {
|
|
|
11
11
|
publishProviderFor,
|
|
12
12
|
resolvePublishFormat,
|
|
13
13
|
runPublish
|
|
14
|
-
} from "./cli-
|
|
14
|
+
} from "./cli-d8my7adx.js";
|
|
15
15
|
import {
|
|
16
16
|
FileTokenStore,
|
|
17
17
|
log
|
|
18
|
-
} from "./cli-
|
|
18
|
+
} from "./cli-pqe8cahv.js";
|
|
19
19
|
import {
|
|
20
20
|
UsageError,
|
|
21
21
|
rejectExtraPositionals,
|
|
22
22
|
rejectUnknownFlags
|
|
23
|
-
} from "./cli-
|
|
23
|
+
} from "./cli-fxk3whwm.js";
|
|
24
24
|
import"./cli-sekhwex4.js";
|
|
25
25
|
import"./cli-c41yr7he.js";
|
|
26
26
|
import {
|
|
27
27
|
EXIT_CODES
|
|
28
28
|
} from "./cli-46ycxe6r.js";
|
|
29
|
-
import"./cli-
|
|
29
|
+
import"./cli-c9h2bk2k.js";
|
|
30
30
|
import"./cli-v5mp7a6q.js";
|
|
31
31
|
import"./cli-37x76zdn.js";
|
|
32
32
|
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
2
|
executeAndReport
|
|
3
|
-
} from "./cli-
|
|
3
|
+
} from "./cli-d8my7adx.js";
|
|
4
4
|
import {
|
|
5
5
|
log
|
|
6
|
-
} from "./cli-
|
|
6
|
+
} from "./cli-pqe8cahv.js";
|
|
7
7
|
import {
|
|
8
8
|
UsageError,
|
|
9
9
|
rejectExtraPositionals,
|
|
10
10
|
rejectUnknownFlags
|
|
11
|
-
} from "./cli-
|
|
11
|
+
} from "./cli-fxk3whwm.js";
|
|
12
12
|
import"./cli-sekhwex4.js";
|
|
13
13
|
import"./cli-c41yr7he.js";
|
|
14
14
|
import {
|
|
15
15
|
EXIT_CODES
|
|
16
16
|
} from "./cli-46ycxe6r.js";
|
|
17
|
-
import"./cli-
|
|
17
|
+
import"./cli-c9h2bk2k.js";
|
|
18
18
|
import"./cli-v5mp7a6q.js";
|
|
19
19
|
import"./cli-37x76zdn.js";
|
|
20
20
|
|
package/package.json
CHANGED