gutterpress 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +422 -0
- package/dist/CGATS21_CRPC1-g0e3k7kr.icc +0 -0
- package/dist/api/index.d.ts +76 -0
- package/dist/api/index.js +272 -0
- package/dist/assets/preview/scripts/pagedjs-bridge.d.ts +1 -0
- package/dist/assets/preview/scripts/pagedjs-interface.d.ts +1 -0
- package/dist/assets/preview/scripts/preview-shell.d.ts +1 -0
- package/dist/audit-1jrwwacz.js +66 -0
- package/dist/build-5s92kgpc.js +77 -0
- package/dist/chapter-01-6fn9pvd0.md +31 -0
- package/dist/chapter-01-7jwqg1ct.md +20 -0
- package/dist/chapter-01-jj8ftn4c.md +29 -0
- package/dist/chapter-01-rkpyvjna.md +26 -0
- package/dist/checks/asset/approved-fonts.d.ts +3 -0
- package/dist/checks/asset/extensions.d.ts +41 -0
- package/dist/checks/asset/font-license.d.ts +3 -0
- package/dist/checks/asset/image-alpha.d.ts +3 -0
- package/dist/checks/asset/image-color-space.d.ts +3 -0
- package/dist/checks/asset/image-file-size.d.ts +3 -0
- package/dist/checks/asset/image-resolution.d.ts +3 -0
- package/dist/checks/asset/image-tac.d.ts +3 -0
- package/dist/checks/asset/index.d.ts +7 -0
- package/dist/checks/formatter.d.ts +3 -0
- package/dist/checks/heuristic/index.d.ts +4 -0
- package/dist/checks/heuristic/layer-count.d.ts +3 -0
- package/dist/checks/heuristic/placement-variance.d.ts +3 -0
- package/dist/checks/heuristic/section-density.d.ts +3 -0
- package/dist/checks/heuristic/text-density.d.ts +3 -0
- package/dist/checks/pdf/bleed.d.ts +3 -0
- package/dist/checks/pdf/bookmarks.d.ts +3 -0
- package/dist/checks/pdf/color-spaces.d.ts +3 -0
- package/dist/checks/pdf/cross-refs.d.ts +3 -0
- package/dist/checks/pdf/embedded-fonts.d.ts +3 -0
- package/dist/checks/pdf/image-resolution.d.ts +3 -0
- package/dist/checks/pdf/index.d.ts +15 -0
- package/dist/checks/pdf/ink-coverage.d.ts +3 -0
- package/dist/checks/pdf/page-labels.d.ts +3 -0
- package/dist/checks/pdf/page-size.d.ts +3 -0
- package/dist/checks/pdf/pdfx-markers.d.ts +3 -0
- package/dist/checks/pdf/pdfx-metadata.d.ts +3 -0
- package/dist/checks/pdf/pdfx-structure.d.ts +6 -0
- package/dist/checks/pdf/qpdf-structure.d.ts +3 -0
- package/dist/checks/pdf/rasterized-pages.d.ts +3 -0
- package/dist/checks/pdf/toc-links.d.ts +3 -0
- package/dist/checks/pdf/transparency.d.ts +3 -0
- package/dist/checks/policy.d.ts +61 -0
- package/dist/checks/register-builtins.d.ts +17 -0
- package/dist/checks/registry.d.ts +40 -0
- package/dist/checks/runner.d.ts +25 -0
- package/dist/checks/source/accessibility-alt-text.d.ts +3 -0
- package/dist/checks/source/accessibility-heading-order.d.ts +3 -0
- package/dist/checks/source/config-file.d.ts +8 -0
- package/dist/checks/source/htmlhint.d.ts +3 -0
- package/dist/checks/source/index.d.ts +6 -0
- package/dist/checks/source/local-refs.d.ts +3 -0
- package/dist/checks/source/markdownlint.d.ts +3 -0
- package/dist/checks/source/stylelint.d.ts +3 -0
- package/dist/checks/tool-check.d.ts +23 -0
- package/dist/checks/types.d.ts +52 -0
- package/dist/cli-1tyj4bk3.js +124 -0
- package/dist/cli-37x76zdn.js +4 -0
- package/dist/cli-506tg37g.js +26 -0
- package/dist/cli-5bxx0c4y.js +464 -0
- package/dist/cli-7ykx7pp0.js +12378 -0
- package/dist/cli-h7phg3ka.js +746 -0
- package/dist/cli-hvqjs342.js +284 -0
- package/dist/cli-yja077f6.js +92 -0
- package/dist/cli.js +157 -0
- package/dist/constants.d.ts +17 -0
- package/dist/doctor-25zjt8dz.js +57 -0
- package/dist/favicon-wkbm9cjn.ico +0 -0
- package/dist/git-http-yrb4ag6z.js +17 -0
- package/dist/index-1tyj4bk3.js +124 -0
- package/dist/index-37x76zdn.js +4 -0
- package/dist/index-3h3fef7r.js +15218 -0
- package/dist/index-5bxx0c4y.js +464 -0
- package/dist/index-yja077f6.js +92 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +310 -0
- package/dist/lib/app-heartbeat.d.ts +102 -0
- package/dist/lib/asset-inline.d.ts +73 -0
- package/dist/lib/browser-pool.d.ts +17 -0
- package/dist/lib/build-error.d.ts +52 -0
- package/dist/lib/build-fingerprint.d.ts +15 -0
- package/dist/lib/build-preflight.d.ts +43 -0
- package/dist/lib/build-runner.d.ts +161 -0
- package/dist/lib/build-staging.d.ts +64 -0
- package/dist/lib/chromium.d.ts +20 -0
- package/dist/lib/cli-args.d.ts +45 -0
- package/dist/lib/desktop.d.ts +14 -0
- package/dist/lib/diagnostics.d.ts +64 -0
- package/dist/lib/embedded-assets.d.ts +29 -0
- package/dist/lib/exec.d.ts +84 -0
- package/dist/lib/fetch-timeout.d.ts +41 -0
- package/dist/lib/ghostscript.d.ts +48 -0
- package/dist/lib/host-policy.d.ts +75 -0
- package/dist/lib/image-inspect.d.ts +32 -0
- package/dist/lib/install-hints.d.ts +21 -0
- package/dist/lib/lint-runner.d.ts +10 -0
- package/dist/lib/manifest-config.d.ts +31 -0
- package/dist/lib/manifest-doc.d.ts +25 -0
- package/dist/lib/manifest.d.ts +41 -0
- package/dist/lib/markdown/assemble.d.ts +78 -0
- package/dist/lib/markdown/chapter-id.d.ts +24 -0
- package/dist/lib/markdown/images.d.ts +34 -0
- package/dist/lib/markdown/index.d.ts +78 -0
- package/dist/lib/markdown/markdown-it-paged.d.ts +30 -0
- package/dist/lib/markdown/plugins.d.ts +69 -0
- package/dist/lib/markdown/renderer.d.ts +111 -0
- package/dist/lib/npm-plugin-installer.d.ts +33 -0
- package/dist/lib/open-path.d.ts +5 -0
- package/dist/lib/output-paths.d.ts +23 -0
- package/dist/lib/pagedjs-marker.d.ts +42 -0
- package/dist/lib/pagedjs.d.ts +26 -0
- package/dist/lib/pagination.d.ts +169 -0
- package/dist/lib/pdf-inspect.d.ts +123 -0
- package/dist/lib/pdf-parse.d.ts +57 -0
- package/dist/lib/plugin-manager.d.ts +108 -0
- package/dist/lib/plugin-vendor.d.ts +107 -0
- package/dist/lib/presets.d.ts +45 -0
- package/dist/lib/printsafe.d.ts +17 -0
- package/dist/lib/project-scaffold.d.ts +116 -0
- package/dist/lib/project-source.d.ts +140 -0
- package/dist/lib/project-templates.d.ts +49 -0
- package/dist/lib/publish/butler.d.ts +10 -0
- package/dist/lib/publish/command-runner.d.ts +18 -0
- package/dist/lib/publish/connect.d.ts +23 -0
- package/dist/lib/publish/manifest-publish.d.ts +9 -0
- package/dist/lib/publish/providers/azure-swa.d.ts +3 -0
- package/dist/lib/publish/providers/drivethrurpg.d.ts +10 -0
- package/dist/lib/publish/providers/guided.d.ts +3 -0
- package/dist/lib/publish/providers/itch.d.ts +5 -0
- package/dist/lib/publish/providers/kdp.d.ts +9 -0
- package/dist/lib/publish/providers/shopify.d.ts +5 -0
- package/dist/lib/publish/registry.d.ts +5 -0
- package/dist/lib/publish/run-publish.d.ts +30 -0
- package/dist/lib/publish/selections.d.ts +25 -0
- package/dist/lib/publish/types.d.ts +258 -0
- package/dist/lib/remote-auth/clone.d.ts +87 -0
- package/dist/lib/remote-auth/conflict-resolution.d.ts +29 -0
- package/dist/lib/remote-auth/diagnose.d.ts +77 -0
- package/dist/lib/remote-auth/generic-auth.d.ts +97 -0
- package/dist/lib/remote-auth/git-http.d.ts +85 -0
- package/dist/lib/remote-auth/github-auth.d.ts +69 -0
- package/dist/lib/remote-auth/github-repos.d.ts +64 -0
- package/dist/lib/remote-auth/operation-log.d.ts +66 -0
- package/dist/lib/remote-auth/recovery/abort-interrupted-operation.d.ts +103 -0
- package/dist/lib/remote-auth/recovery/backup.d.ts +113 -0
- package/dist/lib/remote-auth/recovery/classify.d.ts +109 -0
- package/dist/lib/remote-auth/recovery/context.d.ts +47 -0
- package/dist/lib/remote-auth/recovery/dispatch.d.ts +33 -0
- package/dist/lib/remote-auth/recovery/failsafe.d.ts +33 -0
- package/dist/lib/remote-auth/recovery/inspect.d.ts +74 -0
- package/dist/lib/remote-auth/recovery/manual-guidance.d.ts +28 -0
- package/dist/lib/remote-auth/recovery/outcome-mapping.d.ts +58 -0
- package/dist/lib/remote-auth/recovery/policy.d.ts +47 -0
- package/dist/lib/remote-auth/recovery/recover-auth.d.ts +42 -0
- package/dist/lib/remote-auth/recovery/recover-binary-conflict.d.ts +37 -0
- package/dist/lib/remote-auth/recovery/recover-corrupt-index.d.ts +40 -0
- package/dist/lib/remote-auth/recovery/recover-detached-head.d.ts +70 -0
- package/dist/lib/remote-auth/recovery/recover-interrupted-cherry-pick.d.ts +23 -0
- package/dist/lib/remote-auth/recovery/recover-interrupted-merge.d.ts +28 -0
- package/dist/lib/remote-auth/recovery/recover-interrupted-rebase.d.ts +39 -0
- package/dist/lib/remote-auth/recovery/recover-merge-conflict.d.ts +34 -0
- package/dist/lib/remote-auth/recovery/recover-missing-git-dir.d.ts +37 -0
- package/dist/lib/remote-auth/recovery/recover-missing-objects.d.ts +56 -0
- package/dist/lib/remote-auth/recovery/recover-network.d.ts +34 -0
- package/dist/lib/remote-auth/recovery/recover-non-fast-forward.d.ts +27 -0
- package/dist/lib/remote-auth/recovery/recover-stale-lock.d.ts +76 -0
- package/dist/lib/remote-auth/recovery/recover-unrelated-histories.d.ts +44 -0
- package/dist/lib/remote-auth/recovery/recover-wrong-remote.d.ts +35 -0
- package/dist/lib/remote-auth/recovery/types.d.ts +235 -0
- package/dist/lib/remote-auth/resolution-plan.d.ts +64 -0
- package/dist/lib/remote-auth/sync-messages.d.ts +26 -0
- package/dist/lib/remote-auth/sync-types.d.ts +225 -0
- package/dist/lib/remote-auth/sync.d.ts +45 -0
- package/dist/lib/remote-auth/test-access.d.ts +47 -0
- package/dist/lib/remote-auth/test-support/git-http-server.d.ts +71 -0
- package/dist/lib/remote-auth/token-store.d.ts +95 -0
- package/dist/lib/remote-auth/transport.d.ts +105 -0
- package/dist/lib/slug.d.ts +28 -0
- package/dist/lib/snippets.d.ts +39 -0
- package/dist/lib/source-provider.d.ts +244 -0
- package/dist/lib/static-serve.d.ts +54 -0
- package/dist/lib/style-resolver.d.ts +30 -0
- package/dist/lib/theme-import.d.ts +70 -0
- package/dist/lib/theme-manager.d.ts +128 -0
- package/dist/lib/tool-probe.d.ts +37 -0
- package/dist/lib/validation-exec.d.ts +47 -0
- package/dist/lib/validation-profile.d.ts +18 -0
- package/dist/lib/version.d.ts +9 -0
- package/dist/lint-f63nxjfj.js +65 -0
- package/dist/manifest-n1gh3qw5.yaml +9 -0
- package/dist/manifest-ryyg71nf.yaml +9 -0
- package/dist/manifest.schema-rn1dpdpn.json +564 -0
- package/dist/new-7b08a42h.js +105 -0
- package/dist/paged.polyfill-n95pbxfn.js +33288 -0
- package/dist/pagedjs-bridge-vn4hk9fx.js +111 -0
- package/dist/pagedjs-interface-80g3xa8p.js +498 -0
- package/dist/platform.d.ts +100 -0
- package/dist/plugin-wf2cbmwc.js +129 -0
- package/dist/preflight-tg4mpz1h.js +222 -0
- package/dist/preview/file-watcher.d.ts +174 -0
- package/dist/preview/http-server.d.ts +45 -0
- package/dist/preview/lifecycle.d.ts +67 -0
- package/dist/preview/server-context.d.ts +36 -0
- package/dist/preview-h2bwbb2x.js +102 -0
- package/dist/preview-shell-rghtfmc8.js +155 -0
- package/dist/project-source-0tg0r6vm.js +17 -0
- package/dist/publish-ztaet53r.js +233 -0
- package/dist/render.d.ts +20 -0
- package/dist/render.js +682 -0
- package/dist/repair-jkp4n3j8.js +171 -0
- package/dist/schema/manifest.types.d.ts +218 -0
- package/dist/server.d.ts +34 -0
- package/dist/source-provider-hp96b5jf.js +46 -0
- package/dist/test-helpers/testkit.d.ts +91 -0
- package/dist/theme-0yzvthe8.css +65 -0
- package/dist/theme-570zmh2t.css +58 -0
- package/dist/theme-6a2fngc8.json +6 -0
- package/dist/theme-cd6efzjw.json +6 -0
- package/dist/theme-nya4nqh6.css +70 -0
- package/dist/theme-tqfypc1w.json +6 -0
- package/dist/theme-xt0hjb1b.json +6 -0
- package/dist/theme-zz2ktzqs.css +60 -0
- package/dist/types.d.ts +31 -0
- package/dist/utils/file-utils.d.ts +53 -0
- package/dist/utils/logger.d.ts +59 -0
- package/dist/validate-k2drs19j.js +99 -0
- package/package.json +102 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
import {
|
|
2
|
+
capabilitiesFor,
|
|
3
|
+
detectProjectSource,
|
|
4
|
+
findEnclosingRepoDir
|
|
5
|
+
} from "./cli-1tyj4bk3.js";
|
|
6
|
+
|
|
7
|
+
// src/lib/source-provider.ts
|
|
8
|
+
import * as fs from "node:fs";
|
|
9
|
+
import path2 from "node:path";
|
|
10
|
+
import git from "isomorphic-git";
|
|
11
|
+
|
|
12
|
+
// src/lib/remote-auth/operation-log.ts
|
|
13
|
+
import { appendFileSync, mkdirSync } from "node:fs";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
var noop = {
|
|
16
|
+
debug: () => {},
|
|
17
|
+
info: () => {},
|
|
18
|
+
warn: () => {},
|
|
19
|
+
error: () => {}
|
|
20
|
+
};
|
|
21
|
+
var LEVEL_ORDER = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
22
|
+
function createFileLogger(logFile, operation, minLevel = "debug") {
|
|
23
|
+
try {
|
|
24
|
+
mkdirSync(path.dirname(logFile), { recursive: true });
|
|
25
|
+
} catch {
|
|
26
|
+
return noop;
|
|
27
|
+
}
|
|
28
|
+
function write(level, step, message, data) {
|
|
29
|
+
if (LEVEL_ORDER[level] < LEVEL_ORDER[minLevel])
|
|
30
|
+
return;
|
|
31
|
+
const ts = new Date().toISOString();
|
|
32
|
+
const fields = formatData(data);
|
|
33
|
+
const line = `[${ts}] ${level.toUpperCase().padEnd(5)} ${operation}: step=${step}${fields} | ${message}
|
|
34
|
+
`;
|
|
35
|
+
try {
|
|
36
|
+
appendFileSync(logFile, line, "utf8");
|
|
37
|
+
} catch {}
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
debug: (step, message, data) => write("debug", step, message, data),
|
|
41
|
+
info: (step, message, data) => write("info", step, message, data),
|
|
42
|
+
warn: (step, message, data) => write("warn", step, message, data),
|
|
43
|
+
error: (step, message, data) => write("error", step, message, data)
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function resolveLogger(logFile, operation) {
|
|
47
|
+
if (!logFile)
|
|
48
|
+
return noop;
|
|
49
|
+
return createFileLogger(logFile, operation);
|
|
50
|
+
}
|
|
51
|
+
function formatData(data) {
|
|
52
|
+
if (!data)
|
|
53
|
+
return "";
|
|
54
|
+
const parts = [];
|
|
55
|
+
for (const [key, value] of Object.entries(data)) {
|
|
56
|
+
if (value === undefined)
|
|
57
|
+
continue;
|
|
58
|
+
if (Array.isArray(value)) {
|
|
59
|
+
parts.push(`${key}=${value.join(",")}`);
|
|
60
|
+
} else {
|
|
61
|
+
parts.push(`${key}=${String(value)}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return parts.length > 0 ? " " + parts.join(" ") : "";
|
|
65
|
+
}
|
|
66
|
+
function shortOid(oid) {
|
|
67
|
+
return oid.length >= 7 ? oid.slice(0, 7) : oid;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/lib/source-provider.ts
|
|
71
|
+
var noopLogger = {
|
|
72
|
+
debug: () => {},
|
|
73
|
+
info: () => {},
|
|
74
|
+
warn: () => {},
|
|
75
|
+
error: () => {}
|
|
76
|
+
};
|
|
77
|
+
function gitScopeFor(source) {
|
|
78
|
+
return source.repoRoot || source.path;
|
|
79
|
+
}
|
|
80
|
+
var HISTORY_PAGE_LIMIT = 100;
|
|
81
|
+
var DEFAULT_AUTHOR = "gutterpress";
|
|
82
|
+
var DEFAULT_EMAIL = "noreply@gutterpress.local";
|
|
83
|
+
var DEFAULT_BRANCH = "main";
|
|
84
|
+
var SNAPSHOT_STAGING_MARKER = "gutterpress-snapshot-staging";
|
|
85
|
+
var repoQueues = new Map;
|
|
86
|
+
function repoLockKey(projectDir) {
|
|
87
|
+
const resolved = path2.resolve(projectDir);
|
|
88
|
+
try {
|
|
89
|
+
return fs.realpathSync(resolved);
|
|
90
|
+
} catch {
|
|
91
|
+
return resolved;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function withRepoLock(projectDir, fn) {
|
|
95
|
+
const key = repoLockKey(projectDir);
|
|
96
|
+
const prev = repoQueues.get(key) ?? Promise.resolve();
|
|
97
|
+
const run = prev.then(fn, fn);
|
|
98
|
+
const tail = run.then(() => {
|
|
99
|
+
return;
|
|
100
|
+
}, () => {
|
|
101
|
+
return;
|
|
102
|
+
});
|
|
103
|
+
repoQueues.set(key, tail);
|
|
104
|
+
tail.then(() => {
|
|
105
|
+
if (repoQueues.get(key) === tail)
|
|
106
|
+
repoQueues.delete(key);
|
|
107
|
+
});
|
|
108
|
+
return run;
|
|
109
|
+
}
|
|
110
|
+
function __repoLockQueueSizeForTests() {
|
|
111
|
+
return repoQueues.size;
|
|
112
|
+
}
|
|
113
|
+
function gitAuthor(name, email) {
|
|
114
|
+
const n = (name ?? "").trim();
|
|
115
|
+
const e = (email ?? "").trim();
|
|
116
|
+
return { name: n || DEFAULT_AUTHOR, email: e || DEFAULT_EMAIL };
|
|
117
|
+
}
|
|
118
|
+
async function readGitAuthor(dir) {
|
|
119
|
+
const [name, email] = await Promise.all([
|
|
120
|
+
git.getConfig({ fs, dir, path: "user.name" }).catch(() => {
|
|
121
|
+
return;
|
|
122
|
+
}),
|
|
123
|
+
git.getConfig({ fs, dir, path: "user.email" }).catch(() => {
|
|
124
|
+
return;
|
|
125
|
+
})
|
|
126
|
+
]);
|
|
127
|
+
return {
|
|
128
|
+
name: typeof name === "string" ? name : undefined,
|
|
129
|
+
email: typeof email === "string" ? email : undefined
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
async function resolveGitAuthor(dir, name, email) {
|
|
133
|
+
const existing = await readGitAuthor(dir);
|
|
134
|
+
return gitAuthor(name || existing.name, email || existing.email);
|
|
135
|
+
}
|
|
136
|
+
async function listWorkdirChanges(dir, cache = {}) {
|
|
137
|
+
const adds = [];
|
|
138
|
+
const removes = [];
|
|
139
|
+
await git.walk({
|
|
140
|
+
fs,
|
|
141
|
+
dir,
|
|
142
|
+
cache,
|
|
143
|
+
trees: [git.WORKDIR(), git.STAGE()],
|
|
144
|
+
map: async (filepath, [workdir, stage]) => {
|
|
145
|
+
if (filepath === ".")
|
|
146
|
+
return;
|
|
147
|
+
if (!stage && workdir && await git.isIgnored({ fs, dir, filepath })) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
const [wType, sType] = await Promise.all([
|
|
151
|
+
workdir ? workdir.type() : Promise.resolve(undefined),
|
|
152
|
+
stage ? stage.type() : Promise.resolve(undefined)
|
|
153
|
+
]);
|
|
154
|
+
if (wType === "tree" || sType === "tree") {
|
|
155
|
+
if (sType === "blob")
|
|
156
|
+
removes.push(filepath);
|
|
157
|
+
else if (wType === "blob")
|
|
158
|
+
adds.push(filepath);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (wType === "blob" && !sType) {
|
|
162
|
+
adds.push(filepath);
|
|
163
|
+
} else if (!wType && sType === "blob") {
|
|
164
|
+
removes.push(filepath);
|
|
165
|
+
} else if (wType === "blob" && sType === "blob") {
|
|
166
|
+
const workdirOid = await workdir.oid();
|
|
167
|
+
const stageOid = await stage.oid();
|
|
168
|
+
if (workdirOid !== stageOid) {
|
|
169
|
+
adds.push(filepath);
|
|
170
|
+
} else {
|
|
171
|
+
const fileBytes = await fs.promises.readFile(path2.join(dir, filepath));
|
|
172
|
+
const { oid: actualOid } = await git.hashBlob({ object: fileBytes });
|
|
173
|
+
if (actualOid !== stageOid)
|
|
174
|
+
adds.push(filepath);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
return { adds, removes };
|
|
181
|
+
}
|
|
182
|
+
async function stageChanges(dir, changes, cache) {
|
|
183
|
+
if (changes.adds.length > 0) {
|
|
184
|
+
await git.add({ fs, dir, cache, filepath: changes.adds });
|
|
185
|
+
}
|
|
186
|
+
for (const filepath of changes.removes) {
|
|
187
|
+
await git.remove({ fs, dir, cache, filepath });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
async function hasPendingChanges(dir, cache = {}) {
|
|
191
|
+
const { adds, removes } = await listWorkdirChanges(dir, cache);
|
|
192
|
+
return adds.length > 0 || removes.length > 0;
|
|
193
|
+
}
|
|
194
|
+
async function hasUncommittedChanges(dir, cache = {}) {
|
|
195
|
+
if (await hasPendingChanges(dir, cache))
|
|
196
|
+
return true;
|
|
197
|
+
return !await stageMatchesHead(dir, cache);
|
|
198
|
+
}
|
|
199
|
+
async function stageMatchesHead(dir, cache) {
|
|
200
|
+
let headOid;
|
|
201
|
+
try {
|
|
202
|
+
headOid = await git.resolveRef({ fs, dir, ref: "HEAD" });
|
|
203
|
+
} catch {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
let differs = false;
|
|
207
|
+
await git.walk({
|
|
208
|
+
fs,
|
|
209
|
+
dir,
|
|
210
|
+
cache,
|
|
211
|
+
trees: [git.TREE({ ref: headOid }), git.STAGE()],
|
|
212
|
+
map: async (filepath, [tree, stage]) => {
|
|
213
|
+
if (differs || filepath === ".")
|
|
214
|
+
return differs ? null : undefined;
|
|
215
|
+
const [tType, sType] = await Promise.all([
|
|
216
|
+
tree ? tree.type() : Promise.resolve(undefined),
|
|
217
|
+
stage ? stage.type() : Promise.resolve(undefined)
|
|
218
|
+
]);
|
|
219
|
+
if (tType === "tree" || sType === "tree")
|
|
220
|
+
return;
|
|
221
|
+
if (!tType !== !sType) {
|
|
222
|
+
differs = true;
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
if (!tType || !sType)
|
|
226
|
+
return;
|
|
227
|
+
const [tOid, sOid] = await Promise.all([tree.oid(), stage.oid()]);
|
|
228
|
+
if (tOid !== sOid)
|
|
229
|
+
differs = true;
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
return !differs;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
class LocalFolderSourceProvider {
|
|
237
|
+
source;
|
|
238
|
+
capabilities;
|
|
239
|
+
constructor(source) {
|
|
240
|
+
this.source = source;
|
|
241
|
+
this.capabilities = capabilitiesFor(source);
|
|
242
|
+
}
|
|
243
|
+
async initVersionHistory(options) {
|
|
244
|
+
const dir = options.projectDir;
|
|
245
|
+
if (await findEnclosingRepoDir(dir) !== undefined) {
|
|
246
|
+
throw new Error("This folder is already inside a versioned project, so gutterpress " + "won't create a separate history here.");
|
|
247
|
+
}
|
|
248
|
+
return withRepoLock(dir, async () => {
|
|
249
|
+
const cache = {};
|
|
250
|
+
await git.init({ fs, dir, defaultBranch: DEFAULT_BRANCH });
|
|
251
|
+
await stageChanges(dir, await listWorkdirChanges(dir, cache), cache);
|
|
252
|
+
await git.commit({
|
|
253
|
+
fs,
|
|
254
|
+
dir,
|
|
255
|
+
cache,
|
|
256
|
+
message: options.initialMessage?.trim() || "Created project",
|
|
257
|
+
author: await resolveGitAuthor(dir, options.authorName, options.authorEmail)
|
|
258
|
+
});
|
|
259
|
+
return {
|
|
260
|
+
type: "local-git-folder",
|
|
261
|
+
path: dir,
|
|
262
|
+
repoRoot: dir,
|
|
263
|
+
subPath: "",
|
|
264
|
+
hasRemote: false,
|
|
265
|
+
branch: DEFAULT_BRANCH
|
|
266
|
+
};
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
snapshot() {
|
|
270
|
+
return Promise.reject(new Error("This project has no version history yet. Enable version history first."));
|
|
271
|
+
}
|
|
272
|
+
listHistory() {
|
|
273
|
+
return Promise.resolve([]);
|
|
274
|
+
}
|
|
275
|
+
listHistoryPage() {
|
|
276
|
+
return Promise.resolve({ entries: [], hasMore: false });
|
|
277
|
+
}
|
|
278
|
+
restore() {
|
|
279
|
+
return Promise.reject(new Error("This project has no version history yet. Enable version history first."));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
class LocalGitSourceProvider {
|
|
284
|
+
source;
|
|
285
|
+
capabilities;
|
|
286
|
+
dir;
|
|
287
|
+
constructor(source) {
|
|
288
|
+
this.source = source;
|
|
289
|
+
this.capabilities = capabilitiesFor(source);
|
|
290
|
+
this.dir = gitScopeFor(source);
|
|
291
|
+
}
|
|
292
|
+
async initVersionHistory(options) {
|
|
293
|
+
try {
|
|
294
|
+
await this.snapshot({
|
|
295
|
+
projectDir: options.projectDir,
|
|
296
|
+
message: options.initialMessage?.trim() || "Created project",
|
|
297
|
+
authorName: options.authorName,
|
|
298
|
+
authorEmail: options.authorEmail
|
|
299
|
+
});
|
|
300
|
+
} catch (e) {
|
|
301
|
+
if (!isNoChangesError(e))
|
|
302
|
+
throw e;
|
|
303
|
+
}
|
|
304
|
+
return this.source;
|
|
305
|
+
}
|
|
306
|
+
snapshot(options) {
|
|
307
|
+
return withRepoLock(this.dir, () => this.snapshotUnlocked(options));
|
|
308
|
+
}
|
|
309
|
+
snapshotUnlocked(options) {
|
|
310
|
+
return snapshotWorkingTreeUnlocked({ ...options, repoRoot: this.dir });
|
|
311
|
+
}
|
|
312
|
+
async listHistory(projectDir) {
|
|
313
|
+
return (await this.listHistoryPage(projectDir)).entries;
|
|
314
|
+
}
|
|
315
|
+
async listHistoryPage(_projectDir, options = {}) {
|
|
316
|
+
const dir = this.dir;
|
|
317
|
+
const limit = Math.min(500, Math.max(1, options.limit ?? HISTORY_PAGE_LIMIT));
|
|
318
|
+
const before = options.before;
|
|
319
|
+
let commits;
|
|
320
|
+
try {
|
|
321
|
+
commits = await git.log({
|
|
322
|
+
fs,
|
|
323
|
+
dir,
|
|
324
|
+
cache: {},
|
|
325
|
+
depth: limit + 2,
|
|
326
|
+
...before ? { ref: before } : {}
|
|
327
|
+
});
|
|
328
|
+
} catch (e) {
|
|
329
|
+
if (before && e?.code === "NotFoundError") {
|
|
330
|
+
return { entries: [], hasMore: false };
|
|
331
|
+
}
|
|
332
|
+
throw e;
|
|
333
|
+
}
|
|
334
|
+
const seen = new Set;
|
|
335
|
+
let filtered = commits.filter((c) => {
|
|
336
|
+
if (c.oid === before || seen.has(c.oid))
|
|
337
|
+
return false;
|
|
338
|
+
seen.add(c.oid);
|
|
339
|
+
return true;
|
|
340
|
+
});
|
|
341
|
+
const hasMore = filtered.length > limit;
|
|
342
|
+
if (hasMore)
|
|
343
|
+
filtered = filtered.slice(0, limit);
|
|
344
|
+
return {
|
|
345
|
+
entries: filtered.map((c) => ({
|
|
346
|
+
id: c.oid,
|
|
347
|
+
message: c.commit.message.trim(),
|
|
348
|
+
timestamp: c.commit.author.timestamp * 1000,
|
|
349
|
+
author: c.commit.author.name
|
|
350
|
+
})),
|
|
351
|
+
hasMore
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
restore(options) {
|
|
355
|
+
return withRepoLock(this.dir, () => this.restoreUnlocked(options));
|
|
356
|
+
}
|
|
357
|
+
async restoreUnlocked(options) {
|
|
358
|
+
await git.checkout({
|
|
359
|
+
fs,
|
|
360
|
+
dir: this.dir,
|
|
361
|
+
cache: {},
|
|
362
|
+
ref: options.id,
|
|
363
|
+
force: true,
|
|
364
|
+
noUpdateHead: true
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
async function snapshotWorkingTreeUnlocked(options) {
|
|
369
|
+
const dir = options.repoRoot ?? options.projectDir;
|
|
370
|
+
const logger = options.logFile ? createFileLogger(options.logFile, "snapshot") : noopLogger;
|
|
371
|
+
const cache = {};
|
|
372
|
+
const stagingMarker = snapshotStagingMarkerPath(dir);
|
|
373
|
+
const staleStaging = fs.existsSync(stagingMarker);
|
|
374
|
+
const changes = await listWorkdirChanges(dir, cache);
|
|
375
|
+
const workdirClean = changes.adds.length === 0 && changes.removes.length === 0;
|
|
376
|
+
if (workdirClean && !staleStaging) {
|
|
377
|
+
logger.debug("snapshot", "no changes — skipping");
|
|
378
|
+
throw new Error("No changes since the last snapshot — there is nothing new to save.");
|
|
379
|
+
}
|
|
380
|
+
if (workdirClean && staleStaging && await stageMatchesHead(dir, cache)) {
|
|
381
|
+
logger.debug("snapshot", "stale marker outlived a completed commit — clearing, not recommitting");
|
|
382
|
+
fs.rmSync(stagingMarker, { force: true });
|
|
383
|
+
const [head2] = await git.log({ fs, dir, cache, depth: 1 });
|
|
384
|
+
return {
|
|
385
|
+
id: head2.oid,
|
|
386
|
+
message: head2.commit.message.trim(),
|
|
387
|
+
timestamp: head2.commit.author.timestamp * 1000,
|
|
388
|
+
author: head2.commit.author.name
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
logger.info("snapshot", "committing", {
|
|
392
|
+
adds: changes.adds.length,
|
|
393
|
+
removes: changes.removes.length,
|
|
394
|
+
...staleStaging ? { recoveredStaleStaging: true } : {}
|
|
395
|
+
});
|
|
396
|
+
fs.writeFileSync(stagingMarker, "");
|
|
397
|
+
await stageChanges(dir, changes, cache);
|
|
398
|
+
const author = await resolveGitAuthor(dir, options.authorName, options.authorEmail);
|
|
399
|
+
const id = await git.commit({
|
|
400
|
+
fs,
|
|
401
|
+
dir,
|
|
402
|
+
cache,
|
|
403
|
+
message: options.message,
|
|
404
|
+
author
|
|
405
|
+
});
|
|
406
|
+
fs.rmSync(stagingMarker, { force: true });
|
|
407
|
+
const [head] = await git.log({ fs, dir, cache, depth: 1 });
|
|
408
|
+
return {
|
|
409
|
+
id,
|
|
410
|
+
message: options.message,
|
|
411
|
+
timestamp: head ? head.commit.author.timestamp * 1000 : Date.now(),
|
|
412
|
+
author: author.name
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
function isNoChangesError(e) {
|
|
416
|
+
return e instanceof Error && /no changes since the last snapshot/i.test(e.message);
|
|
417
|
+
}
|
|
418
|
+
function providerFor(source) {
|
|
419
|
+
switch (source.type) {
|
|
420
|
+
case "local-folder":
|
|
421
|
+
return new LocalFolderSourceProvider(source);
|
|
422
|
+
case "local-git-folder":
|
|
423
|
+
return new LocalGitSourceProvider(source);
|
|
424
|
+
case "managed-github":
|
|
425
|
+
throw new Error("Managed GitHub projects are not supported yet (#15/#16).");
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
function gitDirFor(projectDir) {
|
|
429
|
+
return path2.join(projectDir, ".git");
|
|
430
|
+
}
|
|
431
|
+
function snapshotStagingMarkerPath(projectDir) {
|
|
432
|
+
return path2.join(gitDirFor(projectDir), SNAPSHOT_STAGING_MARKER);
|
|
433
|
+
}
|
|
434
|
+
var RESTORE_BACKUP_MESSAGE = "Automatic backup before restoring an earlier version";
|
|
435
|
+
var AUTO_SNAPSHOT_MESSAGE = "Automatic snapshot";
|
|
436
|
+
async function restoreVersionWithBackup(options) {
|
|
437
|
+
const { projectDir, id, authorName, authorEmail } = options;
|
|
438
|
+
const source = await detectProjectSource(projectDir);
|
|
439
|
+
if (source.type !== "local-git-folder") {
|
|
440
|
+
throw new Error("This project has no version history yet. Enable version history first.");
|
|
441
|
+
}
|
|
442
|
+
const provider = new LocalGitSourceProvider(source);
|
|
443
|
+
const repoDir = gitScopeFor(source);
|
|
444
|
+
return withRepoLock(repoDir, async () => {
|
|
445
|
+
let backupId;
|
|
446
|
+
if (await hasPendingChanges(repoDir)) {
|
|
447
|
+
const backup = await provider.snapshotUnlocked({
|
|
448
|
+
projectDir,
|
|
449
|
+
message: RESTORE_BACKUP_MESSAGE,
|
|
450
|
+
authorName,
|
|
451
|
+
authorEmail
|
|
452
|
+
});
|
|
453
|
+
backupId = backup.id;
|
|
454
|
+
}
|
|
455
|
+
try {
|
|
456
|
+
await provider.restoreUnlocked({ projectDir, id });
|
|
457
|
+
} catch (cause) {
|
|
458
|
+
throw new Error(backupId ? "The restore could not be completed, but your work is safe — it was " + `automatically saved as a backup snapshot (${backupId.slice(0, 7)}) ` + "and appears in your version history." : "The restore could not be completed. Your project files were not changed.", { cause });
|
|
459
|
+
}
|
|
460
|
+
return { restoredId: id, backupId };
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
export { resolveLogger, shortOid, gitScopeFor, HISTORY_PAGE_LIMIT, SNAPSHOT_STAGING_MARKER, withRepoLock, __repoLockQueueSizeForTests, gitAuthor, readGitAuthor, resolveGitAuthor, listWorkdirChanges, stageChanges, hasPendingChanges, hasUncommittedChanges, snapshotWorkingTreeUnlocked, isNoChangesError, providerFor, gitDirFor, snapshotStagingMarkerPath, RESTORE_BACKUP_MESSAGE, AUTO_SNAPSHOT_MESSAGE, restoreVersionWithBackup };
|