gutterpress 0.11.6-alpha.3 → 0.11.6
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.d.ts +3 -3
- package/dist/api/index.js +9 -5
- package/dist/{audit-t4r954ff.js → audit-vbyr2yaa.js} +4 -4
- package/dist/{build-8gx90w22.js → build-j0e2hrza.js} +4 -4
- package/dist/{cli-g6vshy3f.js → cli-02sh3r8x.js} +393 -968
- package/dist/{cli-5c2xfsmz.js → cli-1bccnym3.js} +1 -1
- package/dist/cli-4tx0szk7.js +1251 -0
- package/dist/{cli-rra50cp6.js → cli-emp8wz36.js} +1 -1
- package/dist/cli.js +14 -14
- package/dist/{doctor-b1awtn7m.js → doctor-radk1tcg.js} +2 -2
- package/dist/{ext-mxxz3p8t.js → ext-vtnqbct0.js} +4 -4
- package/dist/index-7mqst9tg.js +1302 -0
- package/dist/{index-0h00ve4y.js → index-yzcd0ezr.js} +468 -1042
- package/dist/index.js +9 -5
- package/dist/lib/project-scaffold.d.ts +20 -0
- package/dist/lib/source-provider.d.ts +24 -0
- package/dist/{lint-vyxf5zmw.js → lint-692hba2b.js} +4 -4
- package/dist/{new-r3s74yfv.js → new-431dez0g.js} +4 -4
- package/dist/{preflight-j9e9m4ea.js → preflight-9e0xm760.js} +4 -4
- package/dist/{preview-aprv0c49.js → preview-j429jbx1.js} +4 -4
- package/dist/{publish-4y1kztbd.js → publish-hr60t4j2.js} +4 -4
- package/dist/{source-provider-exy1wakv.js → source-provider-gy3cn7kw.js} +3 -1
- package/dist/{source-provider-y9ktprbx.js → source-provider-m5wes47h.js} +3 -1
- package/dist/{validate-sf6hga9e.js → validate-g1ckwmnz.js} +4 -4
- package/package.json +1 -1
- package/dist/cli-c5360prx.js +0 -645
- package/dist/index-8xkw284t.js +0 -696
|
@@ -20,18 +20,37 @@ import {
|
|
|
20
20
|
spawnCapture
|
|
21
21
|
} from "./index-gf1shk3y.js";
|
|
22
22
|
import {
|
|
23
|
+
PLUGINS_DIR,
|
|
24
|
+
VENDORED_NPM_DIR,
|
|
25
|
+
VENDOR_RECEIPT_FILE,
|
|
26
|
+
VENDOR_RECEIPT_VERSION,
|
|
27
|
+
assertWindowsSafeRelativePath,
|
|
28
|
+
computeVendorTreeDigest,
|
|
23
29
|
errorLogData,
|
|
24
30
|
gitFs,
|
|
25
31
|
gitScopeFor,
|
|
26
32
|
hasPendingChanges,
|
|
27
33
|
hasUncommittedChanges,
|
|
34
|
+
isExactNpmVersion,
|
|
35
|
+
isValidNpmPackageName,
|
|
28
36
|
listWorkdirChanges,
|
|
37
|
+
npmRegistryUrl,
|
|
38
|
+
packageEntryFormat,
|
|
39
|
+
packageResolutionTargets,
|
|
40
|
+
parseNpmPluginSpec,
|
|
29
41
|
resolveGitAuthor,
|
|
30
42
|
resolveLogger,
|
|
43
|
+
resolvePackageEntry,
|
|
44
|
+
resolveVendoredPluginInstallRoot,
|
|
31
45
|
snapshotStagingMarkerPath,
|
|
32
46
|
snapshotWorkingTreeUnlocked,
|
|
47
|
+
toPosixPath,
|
|
48
|
+
vendoredNpmPluginPackageDir,
|
|
49
|
+
vendoredNpmPluginRoot,
|
|
50
|
+
verifyVendoredPlugin,
|
|
51
|
+
windowsPathKey,
|
|
33
52
|
withRepoLock
|
|
34
|
-
} from "./index-
|
|
53
|
+
} from "./index-7mqst9tg.js";
|
|
35
54
|
import {
|
|
36
55
|
detectProjectSource,
|
|
37
56
|
repoSubPath,
|
|
@@ -42,611 +61,18 @@ import {
|
|
|
42
61
|
} from "./index-37x76zdn.js";
|
|
43
62
|
|
|
44
63
|
// src/lib/build-runner.ts
|
|
45
|
-
import
|
|
64
|
+
import path8 from "node:path";
|
|
46
65
|
import fs2 from "node:fs";
|
|
47
66
|
import fsp2 from "node:fs/promises";
|
|
48
67
|
import os2 from "node:os";
|
|
49
68
|
import { randomBytes } from "node:crypto";
|
|
50
69
|
|
|
51
70
|
// src/lib/manifest.ts
|
|
52
|
-
import { readFile
|
|
71
|
+
import { readFile } from "node:fs/promises";
|
|
53
72
|
import { existsSync, statSync } from "node:fs";
|
|
54
73
|
import { resolve, dirname } from "node:path";
|
|
55
74
|
import { parse as parseYaml, YAMLParseError } from "yaml";
|
|
56
75
|
|
|
57
|
-
// src/lib/plugin-vendor.ts
|
|
58
|
-
import { createHash } from "node:crypto";
|
|
59
|
-
import { createReadStream } from "node:fs";
|
|
60
|
-
import { lstat, readFile, readdir, realpath } from "node:fs/promises";
|
|
61
|
-
import path from "node:path";
|
|
62
|
-
import { exports as resolveExports } from "resolve.exports";
|
|
63
|
-
import { satisfies, validRange } from "semver";
|
|
64
|
-
|
|
65
|
-
// src/lib/npm-registry.ts
|
|
66
|
-
var DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org";
|
|
67
|
-
var ENV_OVERRIDE = "GUTTERPRESS_NPM_REGISTRY";
|
|
68
|
-
function npmRegistryUrl() {
|
|
69
|
-
const configured = process.env[ENV_OVERRIDE]?.trim();
|
|
70
|
-
if (!configured)
|
|
71
|
-
return DEFAULT_NPM_REGISTRY;
|
|
72
|
-
let scheme;
|
|
73
|
-
try {
|
|
74
|
-
scheme = new URL(configured).protocol;
|
|
75
|
-
} catch {
|
|
76
|
-
throw new Error(`Invalid ${ENV_OVERRIDE} URL "${configured}".`);
|
|
77
|
-
}
|
|
78
|
-
if (scheme !== "http:" && scheme !== "https:") {
|
|
79
|
-
throw new Error(`${ENV_OVERRIDE} must be an http(s) URL — got "${scheme}".`);
|
|
80
|
-
}
|
|
81
|
-
return configured.replace(/\/+$/, "");
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// src/lib/plugin-vendor.ts
|
|
85
|
-
var PLUGINS_DIR = "plugins";
|
|
86
|
-
var VENDORED_NPM_DIR = "npm";
|
|
87
|
-
var VENDOR_RECEIPT_FILE = ".gutterpress-install.json";
|
|
88
|
-
var VENDOR_RECEIPT_VERSION = 2;
|
|
89
|
-
var NPM_SEGMENT = /^[a-z0-9][a-z0-9._~-]*$/;
|
|
90
|
-
var EXACT_VERSION = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
91
|
-
var WINDOWS_RESERVED = /^(con|prn|aux|nul|clock\$|conin\$|conout\$|com[1-9¹²³]|lpt[1-9¹²³])(?:\..*)?$/i;
|
|
92
|
-
var MAX_VERIFIED_FILES = 50000;
|
|
93
|
-
var MAX_VERIFIED_BYTES = 512 * 1024 * 1024;
|
|
94
|
-
function isValidNpmPackageName(name) {
|
|
95
|
-
if (!name || name.length > 214 || name !== name.toLowerCase())
|
|
96
|
-
return false;
|
|
97
|
-
if (name.startsWith("@")) {
|
|
98
|
-
const parts = name.slice(1).split("/");
|
|
99
|
-
return parts.length === 2 && parts.every((part) => NPM_SEGMENT.test(part));
|
|
100
|
-
}
|
|
101
|
-
return !name.includes("/") && NPM_SEGMENT.test(name);
|
|
102
|
-
}
|
|
103
|
-
function parseNpmPluginSpec(input) {
|
|
104
|
-
const spec = input.trim();
|
|
105
|
-
if (!spec)
|
|
106
|
-
throw new Error("An npm package name is required.");
|
|
107
|
-
let name = spec;
|
|
108
|
-
let selector;
|
|
109
|
-
if (spec.startsWith("@")) {
|
|
110
|
-
const slash = spec.indexOf("/");
|
|
111
|
-
const versionAt = spec.lastIndexOf("@");
|
|
112
|
-
if (slash > 1 && versionAt > slash) {
|
|
113
|
-
name = spec.slice(0, versionAt);
|
|
114
|
-
selector = spec.slice(versionAt + 1);
|
|
115
|
-
}
|
|
116
|
-
} else {
|
|
117
|
-
const versionAt = spec.lastIndexOf("@");
|
|
118
|
-
if (versionAt > 0) {
|
|
119
|
-
name = spec.slice(0, versionAt);
|
|
120
|
-
selector = spec.slice(versionAt + 1);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
if (!isValidNpmPackageName(name)) {
|
|
124
|
-
throw new Error(`"${name}" is not a valid npm package name. Use a name like ` + "markdown-it-highlightjs or @scope/markdown-it-plugin.");
|
|
125
|
-
}
|
|
126
|
-
if (selector !== undefined && !selector) {
|
|
127
|
-
throw new Error(`The package spec "${spec}" is missing a selector after @.`);
|
|
128
|
-
}
|
|
129
|
-
return selector === undefined ? { name } : { name, selector };
|
|
130
|
-
}
|
|
131
|
-
function isExactNpmVersion(version) {
|
|
132
|
-
return EXACT_VERSION.test(version);
|
|
133
|
-
}
|
|
134
|
-
function encodePathPart(value) {
|
|
135
|
-
return encodeURIComponent(value).replace(/[!'()*]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
136
|
-
}
|
|
137
|
-
function vendoredNpmPluginRoot(projectDir, name, version) {
|
|
138
|
-
return path.join(projectDir, PLUGINS_DIR, VENDORED_NPM_DIR, encodePathPart(name), encodePathPart(version));
|
|
139
|
-
}
|
|
140
|
-
function vendoredNpmPluginPackageDir(installRoot, name) {
|
|
141
|
-
return path.join(installRoot, "node_modules", ...name.split("/"));
|
|
142
|
-
}
|
|
143
|
-
function toPosixPath(value) {
|
|
144
|
-
return value.split(path.sep).join("/");
|
|
145
|
-
}
|
|
146
|
-
function packageEntryFormat(manifest, relativeEntry) {
|
|
147
|
-
const extension = path.posix.extname(relativeEntry);
|
|
148
|
-
return extension === ".cjs" || extension === ".json" || extension !== ".mjs" && manifest.type !== "module" ? "commonjs" : "module";
|
|
149
|
-
}
|
|
150
|
-
function assertWindowsSafeRelativePath(relative) {
|
|
151
|
-
if (!relative || relative.includes("\\") || relative.startsWith("/") || /^[a-zA-Z]:/.test(relative)) {
|
|
152
|
-
throw new Error(`Unsafe package path: ${relative || "(empty)"}`);
|
|
153
|
-
}
|
|
154
|
-
const normalized = relative.normalize("NFKC");
|
|
155
|
-
const parts = normalized.split("/");
|
|
156
|
-
if (parts.some((part) => !part || part === "." || part === "..")) {
|
|
157
|
-
throw new Error(`Unsafe package path: ${relative}`);
|
|
158
|
-
}
|
|
159
|
-
for (const part of parts) {
|
|
160
|
-
if (part.length > 255 || /[\u0000-\u001f<>:"|?*]/.test(part) || part.endsWith(".") || part.endsWith(" ") || WINDOWS_RESERVED.test(part)) {
|
|
161
|
-
throw new Error(`Package path is invalid on Windows: ${relative}`);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
function windowsPathKey(relative) {
|
|
166
|
-
return relative.normalize("NFKC").toLowerCase();
|
|
167
|
-
}
|
|
168
|
-
function safeReceiptPath(relative, label) {
|
|
169
|
-
if (typeof relative !== "string")
|
|
170
|
-
throw new Error(`${label} must be a string.`);
|
|
171
|
-
assertWindowsSafeRelativePath(relative);
|
|
172
|
-
return relative;
|
|
173
|
-
}
|
|
174
|
-
function isContained(root, candidate) {
|
|
175
|
-
const relative = path.relative(root, candidate);
|
|
176
|
-
return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
177
|
-
}
|
|
178
|
-
function object(value) {
|
|
179
|
-
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
180
|
-
}
|
|
181
|
-
function stringMap(value) {
|
|
182
|
-
const map = object(value);
|
|
183
|
-
if (!map)
|
|
184
|
-
return {};
|
|
185
|
-
const result = {};
|
|
186
|
-
for (const [name, selector] of Object.entries(map)) {
|
|
187
|
-
if (typeof selector === "string" && selector.trim())
|
|
188
|
-
result[name] = selector.trim();
|
|
189
|
-
}
|
|
190
|
-
return result;
|
|
191
|
-
}
|
|
192
|
-
function dependencyDeclarations(manifest) {
|
|
193
|
-
const required = stringMap(manifest.dependencies);
|
|
194
|
-
const optional = stringMap(manifest.optionalDependencies);
|
|
195
|
-
for (const name of Object.keys(optional))
|
|
196
|
-
delete required[name];
|
|
197
|
-
const peers = stringMap(manifest.peerDependencies);
|
|
198
|
-
const peerMeta = object(manifest.peerDependenciesMeta) ?? {};
|
|
199
|
-
const optionalPeers = {};
|
|
200
|
-
for (const [name, selector] of Object.entries(peers)) {
|
|
201
|
-
if (name in required || name in optional)
|
|
202
|
-
continue;
|
|
203
|
-
if (object(peerMeta[name])?.optional === true)
|
|
204
|
-
optionalPeers[name] = selector;
|
|
205
|
-
else
|
|
206
|
-
required[name] = selector;
|
|
207
|
-
}
|
|
208
|
-
return { required, optional, optionalPeers };
|
|
209
|
-
}
|
|
210
|
-
function unsupportedRegistrySelector(selector) {
|
|
211
|
-
return /^(?:file:|git\+|https?:|ssh:|github:|workspace:|link:|npm:)/i.test(selector);
|
|
212
|
-
}
|
|
213
|
-
async function readPackageJson(packageDir) {
|
|
214
|
-
const packageJsonPath = path.join(packageDir, "package.json");
|
|
215
|
-
const info = await lstat(packageJsonPath);
|
|
216
|
-
if (!info.isFile() || info.isSymbolicLink()) {
|
|
217
|
-
throw new Error(`Package metadata is not a regular file: ${packageJsonPath}`);
|
|
218
|
-
}
|
|
219
|
-
const parsed = object(JSON.parse(await readFile(packageJsonPath, "utf8")));
|
|
220
|
-
if (!parsed)
|
|
221
|
-
throw new Error(`Package metadata is not an object: ${packageJsonPath}`);
|
|
222
|
-
return parsed;
|
|
223
|
-
}
|
|
224
|
-
async function verifyReceiptEntry(installRoot, packagePath, entry, label) {
|
|
225
|
-
if (entry === undefined)
|
|
226
|
-
return;
|
|
227
|
-
const relative = safeReceiptPath(entry, label);
|
|
228
|
-
if (!relative.startsWith(`${packagePath}/`)) {
|
|
229
|
-
throw new Error(`${label} is outside its package.`);
|
|
230
|
-
}
|
|
231
|
-
const packageDir = path.resolve(installRoot, ...packagePath.split("/"));
|
|
232
|
-
const entryPath = path.resolve(installRoot, ...relative.split("/"));
|
|
233
|
-
const [realPackageDir, realEntry] = await Promise.all([
|
|
234
|
-
realpath(packageDir),
|
|
235
|
-
realpath(entryPath)
|
|
236
|
-
]);
|
|
237
|
-
if (!isContained(realPackageDir, realEntry))
|
|
238
|
-
throw new Error(`${label} escapes its package.`);
|
|
239
|
-
const info = await lstat(realEntry);
|
|
240
|
-
if (!info.isFile() || info.isSymbolicLink())
|
|
241
|
-
throw new Error(`${label} is not a regular file.`);
|
|
242
|
-
return realEntry;
|
|
243
|
-
}
|
|
244
|
-
function packageResolutionTargets(manifest, subpath, condition) {
|
|
245
|
-
if (manifest.exports !== undefined) {
|
|
246
|
-
try {
|
|
247
|
-
const targets2 = resolveExports(manifest, subpath ? `./${subpath}` : ".", { require: condition === "require" }) ?? [];
|
|
248
|
-
return targets2.map((target) => ({ target, exact: true }));
|
|
249
|
-
} catch {
|
|
250
|
-
return [];
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
if (subpath)
|
|
254
|
-
return [{ target: subpath, exact: false }];
|
|
255
|
-
const targets = [
|
|
256
|
-
...condition === "import" && typeof manifest.module === "string" ? [manifest.module] : [],
|
|
257
|
-
...typeof manifest.main === "string" ? [manifest.main] : [],
|
|
258
|
-
"index.mjs",
|
|
259
|
-
"index.js",
|
|
260
|
-
"index.cjs"
|
|
261
|
-
];
|
|
262
|
-
return [...new Set(targets)].map((target) => ({ target, exact: false }));
|
|
263
|
-
}
|
|
264
|
-
function safePackageTarget(target) {
|
|
265
|
-
if (!target || target.includes("\\") || target.includes("*") || target.includes("?") || target.includes("#")) {
|
|
266
|
-
return null;
|
|
267
|
-
}
|
|
268
|
-
const relative = target.startsWith("./") ? target.slice(2) : target;
|
|
269
|
-
if (!relative || path.isAbsolute(relative))
|
|
270
|
-
return null;
|
|
271
|
-
assertWindowsSafeRelativePath(relative);
|
|
272
|
-
if (relative.normalize("NFKC").split("/").some((part) => part.toLowerCase() === "node_modules")) {
|
|
273
|
-
return null;
|
|
274
|
-
}
|
|
275
|
-
return relative;
|
|
276
|
-
}
|
|
277
|
-
async function resolveEntryCandidate(packageDir, resolution) {
|
|
278
|
-
const relative = safePackageTarget(resolution.target);
|
|
279
|
-
if (!relative)
|
|
280
|
-
return null;
|
|
281
|
-
const base = path.resolve(packageDir, ...relative.split("/"));
|
|
282
|
-
if (!isContained(packageDir, base))
|
|
283
|
-
return null;
|
|
284
|
-
const candidates = resolution.exact ? [base] : [
|
|
285
|
-
base,
|
|
286
|
-
`${base}.mjs`,
|
|
287
|
-
`${base}.js`,
|
|
288
|
-
`${base}.cjs`,
|
|
289
|
-
`${base}.json`,
|
|
290
|
-
path.join(base, "index.mjs"),
|
|
291
|
-
path.join(base, "index.js"),
|
|
292
|
-
path.join(base, "index.cjs"),
|
|
293
|
-
path.join(base, "index.json")
|
|
294
|
-
];
|
|
295
|
-
for (const candidate of candidates) {
|
|
296
|
-
try {
|
|
297
|
-
const info = await lstat(candidate);
|
|
298
|
-
if (!info.isFile() || info.isSymbolicLink())
|
|
299
|
-
continue;
|
|
300
|
-
const extension = path.extname(candidate);
|
|
301
|
-
if (extension && ![".js", ".mjs", ".cjs", ".json"].includes(extension))
|
|
302
|
-
continue;
|
|
303
|
-
const [realRoot, realCandidate] = await Promise.all([realpath(packageDir), realpath(candidate)]);
|
|
304
|
-
if (!isContained(realRoot, realCandidate))
|
|
305
|
-
continue;
|
|
306
|
-
return toPosixPath(path.relative(packageDir, realCandidate));
|
|
307
|
-
} catch {}
|
|
308
|
-
}
|
|
309
|
-
return null;
|
|
310
|
-
}
|
|
311
|
-
async function resolvePackageEntry(packageDir, packageJson, condition = "import") {
|
|
312
|
-
const manifest = packageJson ?? await readPackageJson(packageDir);
|
|
313
|
-
for (const target of packageResolutionTargets(manifest, "", condition)) {
|
|
314
|
-
const resolved = await resolveEntryCandidate(packageDir, target);
|
|
315
|
-
if (resolved)
|
|
316
|
-
return resolved;
|
|
317
|
-
}
|
|
318
|
-
throw new Error("Package has no contained JavaScript entry point for ESM import.");
|
|
319
|
-
}
|
|
320
|
-
async function listTreeEntries(root) {
|
|
321
|
-
const out = [];
|
|
322
|
-
async function visit(dir) {
|
|
323
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
324
|
-
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
325
|
-
for (const entry of entries) {
|
|
326
|
-
const absolute = path.join(dir, entry.name);
|
|
327
|
-
const relative = toPosixPath(path.relative(root, absolute));
|
|
328
|
-
if (relative === VENDOR_RECEIPT_FILE)
|
|
329
|
-
continue;
|
|
330
|
-
if (entry.isSymbolicLink())
|
|
331
|
-
throw new Error(`Vendor tree contains a symbolic link: ${relative}`);
|
|
332
|
-
if (entry.isDirectory()) {
|
|
333
|
-
out.push({ relative, directory: true });
|
|
334
|
-
await visit(absolute);
|
|
335
|
-
} else if (entry.isFile())
|
|
336
|
-
out.push({ relative, directory: false });
|
|
337
|
-
else
|
|
338
|
-
throw new Error(`Vendor tree contains an unsupported filesystem entry: ${relative}`);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
await visit(root);
|
|
342
|
-
return out;
|
|
343
|
-
}
|
|
344
|
-
async function computeVendorTreeDigest(root) {
|
|
345
|
-
const entries = await listTreeEntries(root);
|
|
346
|
-
const files = entries.filter((entry) => !entry.directory);
|
|
347
|
-
if (files.length > MAX_VERIFIED_FILES) {
|
|
348
|
-
throw new Error(`Vendor tree contains more than ${MAX_VERIFIED_FILES} files.`);
|
|
349
|
-
}
|
|
350
|
-
const seen = new Set;
|
|
351
|
-
const hash = createHash("sha256");
|
|
352
|
-
let bytes = 0;
|
|
353
|
-
for (const entry of entries) {
|
|
354
|
-
const { relative } = entry;
|
|
355
|
-
assertWindowsSafeRelativePath(relative);
|
|
356
|
-
const key = windowsPathKey(relative);
|
|
357
|
-
if (seen.has(key))
|
|
358
|
-
throw new Error(`Vendor tree has a Windows-colliding path: ${relative}`);
|
|
359
|
-
seen.add(key);
|
|
360
|
-
if (entry.directory)
|
|
361
|
-
continue;
|
|
362
|
-
const absolute = path.join(root, ...relative.split("/"));
|
|
363
|
-
const info = await lstat(absolute);
|
|
364
|
-
if (!info.isFile() || info.isSymbolicLink() || info.nlink !== 1) {
|
|
365
|
-
throw new Error(`Vendor tree file is not an independent regular file: ${relative}`);
|
|
366
|
-
}
|
|
367
|
-
bytes += info.size;
|
|
368
|
-
if (bytes > MAX_VERIFIED_BYTES) {
|
|
369
|
-
throw new Error(`Vendor tree exceeds ${Math.round(MAX_VERIFIED_BYTES / 1024 / 1024)}MB.`);
|
|
370
|
-
}
|
|
371
|
-
hash.update(`${Buffer.byteLength(relative)}:${relative}:${info.size}\x00`);
|
|
372
|
-
for await (const chunk of createReadStream(absolute))
|
|
373
|
-
hash.update(chunk);
|
|
374
|
-
}
|
|
375
|
-
return { digest: hash.digest("hex"), files: files.length, bytes };
|
|
376
|
-
}
|
|
377
|
-
async function assertVendorPackageOwnership(root, packagePaths) {
|
|
378
|
-
const packages = packagePaths.map((relative) => ({
|
|
379
|
-
relative,
|
|
380
|
-
key: windowsPathKey(relative),
|
|
381
|
-
parts: relative.split("/").length
|
|
382
|
-
})).sort((a, b) => b.key.length - a.key.length);
|
|
383
|
-
for (const entry of await listTreeEntries(root)) {
|
|
384
|
-
const actualParts = entry.relative.split("/");
|
|
385
|
-
for (const part of actualParts) {
|
|
386
|
-
if (part.normalize("NFKC").toLowerCase() === "node_modules" && part !== "node_modules") {
|
|
387
|
-
throw new Error(`Vendor tree contains a non-canonical node_modules path: ${entry.relative}`);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
const key = windowsPathKey(entry.relative);
|
|
391
|
-
const structural = packages.some((pkg) => pkg.key === key || pkg.key.startsWith(`${key}/`));
|
|
392
|
-
if (entry.directory && structural)
|
|
393
|
-
continue;
|
|
394
|
-
const owner = packages.find((pkg) => key.startsWith(`${pkg.key}/`));
|
|
395
|
-
if (!owner)
|
|
396
|
-
throw new Error(`Vendor tree path is not owned by a receipt package: ${entry.relative}`);
|
|
397
|
-
const packageRelative = actualParts.slice(owner.parts);
|
|
398
|
-
const nodeModulesAt = packageRelative.findIndex((part) => part.normalize("NFKC").toLowerCase() === "node_modules");
|
|
399
|
-
if (nodeModulesAt < 0)
|
|
400
|
-
continue;
|
|
401
|
-
const generatedEmptyDirectory = entry.directory && nodeModulesAt === 0 && (packageRelative.length === 1 || packageRelative.length === 2 && packageRelative[1].startsWith("@"));
|
|
402
|
-
if (!generatedEmptyDirectory) {
|
|
403
|
-
throw new Error(`Package contains bundled node_modules content: ${entry.relative}`);
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
async function recomputeReceiptEntry(packageDir, packagePath, manifest, condition) {
|
|
408
|
-
try {
|
|
409
|
-
const entry = await resolvePackageEntry(packageDir, manifest, condition);
|
|
410
|
-
return `${packagePath}/${entry}`;
|
|
411
|
-
} catch (error) {
|
|
412
|
-
if (error instanceof Error && error.message.includes("no contained JavaScript entry point")) {
|
|
413
|
-
return;
|
|
414
|
-
}
|
|
415
|
-
throw error;
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
async function resolveVendoredPluginInstallRoot(projectDir, expectedName, expectedVersion) {
|
|
419
|
-
const requestedProjectRoot = path.resolve(projectDir);
|
|
420
|
-
const requestedInstallRoot = vendoredNpmPluginRoot(requestedProjectRoot, expectedName, expectedVersion);
|
|
421
|
-
let installInfo;
|
|
422
|
-
try {
|
|
423
|
-
installInfo = await lstat(requestedInstallRoot);
|
|
424
|
-
} catch (error) {
|
|
425
|
-
if (error.code === "ENOENT")
|
|
426
|
-
return null;
|
|
427
|
-
throw error;
|
|
428
|
-
}
|
|
429
|
-
if (!installInfo.isDirectory() || installInfo.isSymbolicLink()) {
|
|
430
|
-
throw new Error("Vendor install root is not a normal directory.");
|
|
431
|
-
}
|
|
432
|
-
const [realProjectRoot, installRoot] = await Promise.all([
|
|
433
|
-
realpath(requestedProjectRoot),
|
|
434
|
-
realpath(requestedInstallRoot)
|
|
435
|
-
]);
|
|
436
|
-
if (!isContained(realProjectRoot, installRoot)) {
|
|
437
|
-
throw new Error("Vendor install root resolves outside the book folder.");
|
|
438
|
-
}
|
|
439
|
-
return installRoot;
|
|
440
|
-
}
|
|
441
|
-
function reconcileReceiptGraph(receipt, packages, manifests) {
|
|
442
|
-
if (!Array.isArray(receipt.skipped))
|
|
443
|
-
throw new Error("Vendor receipt skipped list is invalid.");
|
|
444
|
-
const declarations = new Map;
|
|
445
|
-
for (const [packagePath, manifest] of manifests) {
|
|
446
|
-
declarations.set(packagePath, dependencyDeclarations(manifest));
|
|
447
|
-
}
|
|
448
|
-
const skippedByEdge = new Map;
|
|
449
|
-
for (const skipped of receipt.skipped) {
|
|
450
|
-
if (!packages.has(skipped.from) || !isValidNpmPackageName(skipped.name) || typeof skipped.selector !== "string" || !["optional", "optional-peer"].includes(skipped.kind) || typeof skipped.reason !== "string") {
|
|
451
|
-
throw new Error("Vendor receipt contains an invalid skipped dependency.");
|
|
452
|
-
}
|
|
453
|
-
const declared = declarations.get(skipped.from);
|
|
454
|
-
const expectedSelector = skipped.kind === "optional" ? declared.optional[skipped.name] : declared.optionalPeers[skipped.name];
|
|
455
|
-
if (expectedSelector !== skipped.selector) {
|
|
456
|
-
throw new Error(`Skipped dependency is not declared as ${skipped.kind}: ${skipped.name}`);
|
|
457
|
-
}
|
|
458
|
-
const key = `${skipped.from}\x00${skipped.name}`;
|
|
459
|
-
if (skippedByEdge.has(key))
|
|
460
|
-
throw new Error(`Duplicate skipped dependency: ${skipped.name}`);
|
|
461
|
-
skippedByEdge.set(key, skipped);
|
|
462
|
-
}
|
|
463
|
-
for (const pkg of receipt.packages) {
|
|
464
|
-
if (!object(pkg.dependencies))
|
|
465
|
-
throw new Error(`Invalid dependency map for ${pkg.name}.`);
|
|
466
|
-
const declared = declarations.get(pkg.path);
|
|
467
|
-
for (const [dependency, target] of Object.entries(pkg.dependencies)) {
|
|
468
|
-
const targetPackage = packages.get(target);
|
|
469
|
-
if (!isValidNpmPackageName(dependency) || !targetPackage || targetPackage.name !== dependency) {
|
|
470
|
-
throw new Error(`Broken dependency edge ${pkg.name} -> ${dependency}.`);
|
|
471
|
-
}
|
|
472
|
-
const selector = declared.required[dependency] ?? declared.optional[dependency];
|
|
473
|
-
if (!selector)
|
|
474
|
-
throw new Error(`Undeclared dependency edge ${pkg.name} -> ${dependency}.`);
|
|
475
|
-
if (unsupportedRegistrySelector(selector)) {
|
|
476
|
-
throw new Error(`Unsupported selector on dependency edge ${pkg.name} -> ${dependency}.`);
|
|
477
|
-
}
|
|
478
|
-
const range = validRange(selector);
|
|
479
|
-
if (range && !satisfies(targetPackage.version, range)) {
|
|
480
|
-
throw new Error(`Dependency edge ${pkg.name} -> ${dependency}@${targetPackage.version} does not satisfy ${selector}.`);
|
|
481
|
-
}
|
|
482
|
-
if (skippedByEdge.has(`${pkg.path}\x00${dependency}`)) {
|
|
483
|
-
throw new Error(`Dependency is both installed and skipped: ${pkg.name} -> ${dependency}.`);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
for (const dependency of Object.keys(declared.required)) {
|
|
487
|
-
if (!(dependency in pkg.dependencies)) {
|
|
488
|
-
throw new Error(`Missing required dependency edge ${pkg.name} -> ${dependency}.`);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
for (const dependency of Object.keys(declared.optional)) {
|
|
492
|
-
if (!(dependency in pkg.dependencies) && !skippedByEdge.has(`${pkg.path}\x00${dependency}`)) {
|
|
493
|
-
throw new Error(`Optional dependency is neither installed nor skipped: ${pkg.name} -> ${dependency}.`);
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
for (const dependency of Object.keys(declared.optionalPeers)) {
|
|
497
|
-
if (!skippedByEdge.has(`${pkg.path}\x00${dependency}`)) {
|
|
498
|
-
throw new Error(`Optional peer dependency is not recorded as skipped: ${pkg.name} -> ${dependency}.`);
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
async function verifyVendoredPlugin(projectDir, expectedName, expectedVersion) {
|
|
504
|
-
try {
|
|
505
|
-
const installRoot = await resolveVendoredPluginInstallRoot(projectDir, expectedName, expectedVersion);
|
|
506
|
-
if (!installRoot)
|
|
507
|
-
return null;
|
|
508
|
-
const receiptPath = path.join(installRoot, VENDOR_RECEIPT_FILE);
|
|
509
|
-
let raw;
|
|
510
|
-
try {
|
|
511
|
-
const info = await lstat(receiptPath);
|
|
512
|
-
if (!info.isFile() || info.isSymbolicLink() || info.nlink !== 1 || info.size > 1024 * 1024) {
|
|
513
|
-
throw new Error("Vendor receipt is not a small regular file.");
|
|
514
|
-
}
|
|
515
|
-
raw = await readFile(receiptPath, "utf8");
|
|
516
|
-
} catch (error) {
|
|
517
|
-
if (error.code === "ENOENT")
|
|
518
|
-
return null;
|
|
519
|
-
throw error;
|
|
520
|
-
}
|
|
521
|
-
const receipt = JSON.parse(raw);
|
|
522
|
-
if (receipt.schemaVersion !== VENDOR_RECEIPT_VERSION) {
|
|
523
|
-
throw new Error(`Unsupported vendor receipt schema ${String(receipt.schemaVersion)}.`);
|
|
524
|
-
}
|
|
525
|
-
if (receipt.root?.name !== expectedName || receipt.root?.version !== expectedVersion) {
|
|
526
|
-
throw new Error("Vendor receipt identity does not match the manifest entry.");
|
|
527
|
-
}
|
|
528
|
-
if (!Array.isArray(receipt.packages) || receipt.packages.length === 0) {
|
|
529
|
-
throw new Error("Vendor receipt has no package graph.");
|
|
530
|
-
}
|
|
531
|
-
const tree = await computeVendorTreeDigest(installRoot);
|
|
532
|
-
if (receipt.tree?.algorithm !== "sha256" || receipt.tree.digest !== tree.digest || receipt.tree.files !== tree.files || receipt.tree.bytes !== tree.bytes) {
|
|
533
|
-
throw new Error("Vendor tree hash does not match its receipt.");
|
|
534
|
-
}
|
|
535
|
-
const packages = new Map;
|
|
536
|
-
const manifests = new Map;
|
|
537
|
-
const verifiedPackages = [];
|
|
538
|
-
const pathKeys = new Set;
|
|
539
|
-
for (const pkg of receipt.packages) {
|
|
540
|
-
const relative = safeReceiptPath(pkg.path, "Package receipt path");
|
|
541
|
-
const key = windowsPathKey(relative);
|
|
542
|
-
if (pathKeys.has(key))
|
|
543
|
-
throw new Error(`Duplicate package receipt path: ${relative}`);
|
|
544
|
-
pathKeys.add(key);
|
|
545
|
-
if (!isValidNpmPackageName(pkg.name) || !isExactNpmVersion(pkg.version)) {
|
|
546
|
-
throw new Error(`Invalid package identity in receipt: ${pkg.name}@${pkg.version}`);
|
|
547
|
-
}
|
|
548
|
-
const expectedSuffix = `node_modules/${pkg.name}`;
|
|
549
|
-
if (relative !== expectedSuffix && !relative.endsWith(`/${expectedSuffix}`)) {
|
|
550
|
-
throw new Error(`Package receipt path does not match ${pkg.name}: ${relative}`);
|
|
551
|
-
}
|
|
552
|
-
if (typeof pkg.integrity !== "string" || !/^(?:sha512|sha384|sha256|sha1)-(?:hex-)?[A-Za-z0-9+/=]+$/.test(pkg.integrity) || typeof pkg.legacySha1 !== "boolean" || pkg.legacySha1 !== pkg.integrity.startsWith("sha1-")) {
|
|
553
|
-
throw new Error(`Package receipt is missing provenance: ${pkg.name}@${pkg.version}`);
|
|
554
|
-
}
|
|
555
|
-
const tarball = new URL(pkg.tarball);
|
|
556
|
-
if (tarball.origin !== new URL(npmRegistryUrl()).origin || tarball.username || tarball.password) {
|
|
557
|
-
throw new Error(`Package receipt has an invalid tarball URL: ${pkg.name}@${pkg.version}`);
|
|
558
|
-
}
|
|
559
|
-
const packageDir = path.resolve(installRoot, ...relative.split("/"));
|
|
560
|
-
if (!isContained(installRoot, packageDir))
|
|
561
|
-
throw new Error(`Package escapes vendor root: ${relative}`);
|
|
562
|
-
const realPackageDir2 = await realpath(packageDir);
|
|
563
|
-
if (!isContained(installRoot, realPackageDir2)) {
|
|
564
|
-
throw new Error(`Package resolves outside the vendor root: ${relative}`);
|
|
565
|
-
}
|
|
566
|
-
const manifest = await readPackageJson(packageDir);
|
|
567
|
-
if (manifest.name !== pkg.name || manifest.version !== pkg.version) {
|
|
568
|
-
throw new Error(`Installed package identity changed: ${pkg.name}@${pkg.version}`);
|
|
569
|
-
}
|
|
570
|
-
const [expectedEntry, expectedRequireEntry] = await Promise.all([
|
|
571
|
-
recomputeReceiptEntry(packageDir, relative, manifest, "import"),
|
|
572
|
-
recomputeReceiptEntry(packageDir, relative, manifest, "require")
|
|
573
|
-
]);
|
|
574
|
-
if (pkg.entry !== expectedEntry || pkg.requireEntry !== expectedRequireEntry) {
|
|
575
|
-
throw new Error(`Recorded package entries do not match package.json: ${pkg.name}@${pkg.version}`);
|
|
576
|
-
}
|
|
577
|
-
packages.set(relative, pkg);
|
|
578
|
-
manifests.set(relative, manifest);
|
|
579
|
-
verifiedPackages.push({
|
|
580
|
-
path: relative,
|
|
581
|
-
name: pkg.name,
|
|
582
|
-
packageDir: realPackageDir2,
|
|
583
|
-
manifest,
|
|
584
|
-
entryPath: await verifyReceiptEntry(installRoot, relative, pkg.entry, "Package import entry"),
|
|
585
|
-
requireEntryPath: await verifyReceiptEntry(installRoot, relative, pkg.requireEntry, "Package require entry"),
|
|
586
|
-
dependencies: pkg.dependencies
|
|
587
|
-
});
|
|
588
|
-
}
|
|
589
|
-
await assertVendorPackageOwnership(installRoot, receipt.packages.map((pkg) => pkg.path));
|
|
590
|
-
reconcileReceiptGraph(receipt, packages, manifests);
|
|
591
|
-
const rootPackagePath = safeReceiptPath(receipt.root.packagePath, "Root package path");
|
|
592
|
-
const rootPackage = packages.get(rootPackagePath);
|
|
593
|
-
if (!rootPackage || rootPackage.name !== expectedName || rootPackage.version !== expectedVersion) {
|
|
594
|
-
throw new Error("Vendor receipt root package is missing.");
|
|
595
|
-
}
|
|
596
|
-
const reachablePackages = new Set([rootPackagePath]);
|
|
597
|
-
const pendingPackages = [rootPackagePath];
|
|
598
|
-
while (pendingPackages.length > 0) {
|
|
599
|
-
const current = packages.get(pendingPackages.pop());
|
|
600
|
-
for (const target of Object.values(current.dependencies)) {
|
|
601
|
-
if (reachablePackages.has(target))
|
|
602
|
-
continue;
|
|
603
|
-
reachablePackages.add(target);
|
|
604
|
-
pendingPackages.push(target);
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
if (reachablePackages.size !== receipt.packages.length) {
|
|
608
|
-
throw new Error("Vendor receipt contains a package that is not reachable from its root.");
|
|
609
|
-
}
|
|
610
|
-
const rootManifest = manifests.get(rootPackagePath);
|
|
611
|
-
if (receipt.root.format !== "module" && receipt.root.format !== "commonjs") {
|
|
612
|
-
throw new Error("Vendor receipt root format is invalid.");
|
|
613
|
-
}
|
|
614
|
-
const entryRelative = safeReceiptPath(receipt.root.entry, "Plugin entry path");
|
|
615
|
-
if (!entryRelative.startsWith(`${rootPackagePath}/`)) {
|
|
616
|
-
throw new Error("Plugin entry is outside its package.");
|
|
617
|
-
}
|
|
618
|
-
const expectedRootEntry = receipt.root.format === "commonjs" ? rootPackage.requireEntry ?? rootPackage.entry : rootPackage.entry;
|
|
619
|
-
if (entryRelative !== expectedRootEntry) {
|
|
620
|
-
throw new Error("Plugin entry does not match the package graph entry.");
|
|
621
|
-
}
|
|
622
|
-
if (packageEntryFormat(rootManifest, entryRelative) !== receipt.root.format) {
|
|
623
|
-
throw new Error("Plugin entry format does not match its package metadata.");
|
|
624
|
-
}
|
|
625
|
-
const entryPath = path.resolve(installRoot, ...entryRelative.split("/"));
|
|
626
|
-
const rootPackageDir = path.resolve(installRoot, ...rootPackagePath.split("/"));
|
|
627
|
-
const [realPackageDir, realEntry] = await Promise.all([
|
|
628
|
-
realpath(rootPackageDir),
|
|
629
|
-
realpath(entryPath)
|
|
630
|
-
]);
|
|
631
|
-
if (!isContained(installRoot, realEntry) || !isContained(realPackageDir, realEntry)) {
|
|
632
|
-
throw new Error("Plugin entry resolves outside the verified vendor tree.");
|
|
633
|
-
}
|
|
634
|
-
const entryInfo = await lstat(realEntry);
|
|
635
|
-
if (!entryInfo.isFile() || entryInfo.isSymbolicLink()) {
|
|
636
|
-
throw new Error("Plugin entry is not a regular file.");
|
|
637
|
-
}
|
|
638
|
-
return {
|
|
639
|
-
installRoot,
|
|
640
|
-
entryPath: realEntry,
|
|
641
|
-
format: receipt.root.format,
|
|
642
|
-
packages: verifiedPackages,
|
|
643
|
-
receipt
|
|
644
|
-
};
|
|
645
|
-
} catch (error) {
|
|
646
|
-
throw new Error(`Vendored plugin "${expectedName}@${expectedVersion}" failed verification: ` + `${error instanceof Error ? error.message : String(error)} Reinstall it before building.`, { cause: error });
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
|
|
650
76
|
// src/lib/extension-specifier.ts
|
|
651
77
|
var BUNDLED_EXTENSIONS = [
|
|
652
78
|
"markdown-it-mark",
|
|
@@ -1027,7 +453,7 @@ async function loadManifestWithPath(pathOrDir, opts) {
|
|
|
1027
453
|
const candidates = pathOrDir ? [resolve(pathOrDir), ...MANIFEST_FILENAMES.map((name) => resolve(pathOrDir, name))] : MANIFEST_FILENAMES.map((name) => resolve(name));
|
|
1028
454
|
for (const p of candidates) {
|
|
1029
455
|
if (existsSync(p) && statSync(p).isFile()) {
|
|
1030
|
-
const raw = await
|
|
456
|
+
const raw = await readFile(p, "utf8");
|
|
1031
457
|
try {
|
|
1032
458
|
const manifest = parseYaml(raw) ?? {};
|
|
1033
459
|
return { manifest, manifestDir: dirname(p), manifestPath: p };
|
|
@@ -1215,7 +641,7 @@ function resolveWithPreset(cliOverrides, manifest, preset) {
|
|
|
1215
641
|
}
|
|
1216
642
|
|
|
1217
643
|
// src/lib/markdown/index.ts
|
|
1218
|
-
import { readdir as
|
|
644
|
+
import { readdir as readdir2, readFile as readFile3, writeFile, mkdir } from "node:fs/promises";
|
|
1219
645
|
|
|
1220
646
|
// src/lib/remote-auth/sync-messages.ts
|
|
1221
647
|
var MSG_UP_TO_DATE = "Everything is in sync.";
|
|
@@ -3224,8 +2650,8 @@ ${bodyContent}
|
|
|
3224
2650
|
|
|
3225
2651
|
// src/lib/style-resolver.ts
|
|
3226
2652
|
import { existsSync as existsSync2 } from "node:fs";
|
|
3227
|
-
import { readdir
|
|
3228
|
-
import
|
|
2653
|
+
import { readdir } from "node:fs/promises";
|
|
2654
|
+
import path from "node:path";
|
|
3229
2655
|
var STYLES_SUBDIR = "styles";
|
|
3230
2656
|
var FALLBACK_PRIORITY = [
|
|
3231
2657
|
"styles/book.css",
|
|
@@ -3235,21 +2661,21 @@ var FALLBACK_PRIORITY = [
|
|
|
3235
2661
|
"css/main.css"
|
|
3236
2662
|
];
|
|
3237
2663
|
function relDisplay(projectDir, absPath) {
|
|
3238
|
-
return
|
|
2664
|
+
return path.relative(projectDir, absPath).split(path.sep).join("/");
|
|
3239
2665
|
}
|
|
3240
2666
|
async function dirEntries(dir) {
|
|
3241
2667
|
try {
|
|
3242
|
-
return await
|
|
2668
|
+
return await readdir(dir, { withFileTypes: true });
|
|
3243
2669
|
} catch {
|
|
3244
2670
|
return [];
|
|
3245
2671
|
}
|
|
3246
2672
|
}
|
|
3247
2673
|
function cssFilesFrom(dir, entries) {
|
|
3248
|
-
return entries.filter((e) => e.isFile() && e.name.toLowerCase().endsWith(".css")).map((e) =>
|
|
2674
|
+
return entries.filter((e) => e.isFile() && e.name.toLowerCase().endsWith(".css")).map((e) => path.join(dir, e.name));
|
|
3249
2675
|
}
|
|
3250
2676
|
async function discoverCssFiles(projectDir) {
|
|
3251
|
-
const stylesDir =
|
|
3252
|
-
const cssDir =
|
|
2677
|
+
const stylesDir = path.join(projectDir, STYLES_SUBDIR);
|
|
2678
|
+
const cssDir = path.join(projectDir, "css");
|
|
3253
2679
|
const [rootEntries, stylesEntries, cssEntries] = await Promise.all([
|
|
3254
2680
|
dirEntries(projectDir),
|
|
3255
2681
|
dirEntries(stylesDir),
|
|
@@ -3272,7 +2698,7 @@ async function resolveActiveStyles(projectDir, manifestStyles) {
|
|
|
3272
2698
|
if (manifest.length > 0)
|
|
3273
2699
|
return manifest;
|
|
3274
2700
|
for (const rel of FALLBACK_PRIORITY) {
|
|
3275
|
-
if (existsSync2(
|
|
2701
|
+
if (existsSync2(path.join(projectDir, rel)))
|
|
3276
2702
|
return [rel];
|
|
3277
2703
|
}
|
|
3278
2704
|
const discovered = await discoverCssFiles(projectDir);
|
|
@@ -3286,14 +2712,14 @@ var SHARED_STYLE_ROOTS = ["shared", "."];
|
|
|
3286
2712
|
async function discoverSharedCssFiles(repoRoot) {
|
|
3287
2713
|
const found = new Set;
|
|
3288
2714
|
for (const rel of SHARED_STYLE_ROOTS) {
|
|
3289
|
-
const base =
|
|
2715
|
+
const base = path.resolve(repoRoot, rel);
|
|
3290
2716
|
for (const abs of await discoverCssFiles(base))
|
|
3291
2717
|
found.add(abs);
|
|
3292
2718
|
}
|
|
3293
2719
|
return [...found];
|
|
3294
2720
|
}
|
|
3295
2721
|
async function listProjectStyles(projectDir, opts = {}) {
|
|
3296
|
-
const repoRoot = opts.repoRoot &&
|
|
2722
|
+
const repoRoot = opts.repoRoot && path.resolve(opts.repoRoot) !== path.resolve(projectDir) ? path.resolve(opts.repoRoot) : null;
|
|
3297
2723
|
const [{ manifest }, ownCss, sharedCss] = await Promise.all([
|
|
3298
2724
|
loadManifestWithPath(projectDir),
|
|
3299
2725
|
discoverCssFiles(projectDir),
|
|
@@ -3304,7 +2730,7 @@ async function listProjectStyles(projectDir, opts = {}) {
|
|
|
3304
2730
|
const out = [];
|
|
3305
2731
|
const seen = new Set;
|
|
3306
2732
|
for (const rel of activeRels) {
|
|
3307
|
-
const abs =
|
|
2733
|
+
const abs = path.resolve(projectDir, rel);
|
|
3308
2734
|
if (seen.has(abs))
|
|
3309
2735
|
continue;
|
|
3310
2736
|
seen.add(abs);
|
|
@@ -3316,9 +2742,9 @@ async function listProjectStyles(projectDir, opts = {}) {
|
|
|
3316
2742
|
}
|
|
3317
2743
|
|
|
3318
2744
|
// src/lib/asset-inline.ts
|
|
3319
|
-
import { createHash
|
|
3320
|
-
import { readFile as
|
|
3321
|
-
import
|
|
2745
|
+
import { createHash } from "node:crypto";
|
|
2746
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
2747
|
+
import path2 from "node:path";
|
|
3322
2748
|
import postcss from "postcss";
|
|
3323
2749
|
var FONT_EXTS = new Set([".woff2", ".woff", ".ttf", ".otf"]);
|
|
3324
2750
|
var HASHED_ASSET_DIR = "assets";
|
|
@@ -3359,19 +2785,19 @@ function decodeRef(ref) {
|
|
|
3359
2785
|
}
|
|
3360
2786
|
}
|
|
3361
2787
|
function toPosix(p) {
|
|
3362
|
-
return p.split(
|
|
2788
|
+
return p.split(path2.sep).join("/");
|
|
3363
2789
|
}
|
|
3364
2790
|
function escapesProjectRoot(projectDir, absPath) {
|
|
3365
|
-
const rel =
|
|
3366
|
-
return rel === ".." || rel.startsWith(`..${
|
|
2791
|
+
const rel = path2.relative(projectDir, absPath);
|
|
2792
|
+
return rel === ".." || rel.startsWith(`..${path2.sep}`) || path2.isAbsolute(rel);
|
|
3367
2793
|
}
|
|
3368
2794
|
function proseImageRefError(ref, projectDir) {
|
|
3369
2795
|
const cleaned = stripUrlSuffix(decodeRef(ref));
|
|
3370
|
-
if (/^file:/i.test(cleaned) ||
|
|
2796
|
+
if (/^file:/i.test(cleaned) || path2.isAbsolute(cleaned)) {
|
|
3371
2797
|
return `Image reference must be relative to the book folder: ${ref}
|
|
3372
2798
|
` + ` Copy the file into your book folder and reference it from there.`;
|
|
3373
2799
|
}
|
|
3374
|
-
if (escapesProjectRoot(projectDir,
|
|
2800
|
+
if (escapesProjectRoot(projectDir, path2.resolve(projectDir, cleaned))) {
|
|
3375
2801
|
return `Image reference points outside the book folder: ${ref}
|
|
3376
2802
|
` + ` Copy the file into your book folder and reference it from there.`;
|
|
3377
2803
|
}
|
|
@@ -3382,7 +2808,7 @@ function mimeFor(ext) {
|
|
|
3382
2808
|
}
|
|
3383
2809
|
async function readOrThrow(absPath, kind, referencedFrom) {
|
|
3384
2810
|
try {
|
|
3385
|
-
return await
|
|
2811
|
+
return await readFile2(absPath);
|
|
3386
2812
|
} catch {
|
|
3387
2813
|
throw new BuildError(`Missing ${kind}: ${absPath}
|
|
3388
2814
|
referenced from ${referencedFrom}
|
|
@@ -3393,7 +2819,7 @@ function dataUri(bytes, ext) {
|
|
|
3393
2819
|
return `data:${mimeFor(ext)};base64,${bytes.toString("base64")}`;
|
|
3394
2820
|
}
|
|
3395
2821
|
function contentHash(bytes) {
|
|
3396
|
-
return
|
|
2822
|
+
return createHash("sha256").update(bytes).digest("hex").slice(0, 16);
|
|
3397
2823
|
}
|
|
3398
2824
|
async function rewriteUrls(value, rewrite) {
|
|
3399
2825
|
const matches = [...value.matchAll(URL_TOKEN_RE)];
|
|
@@ -3441,12 +2867,12 @@ function importTarget(atRule) {
|
|
|
3441
2867
|
return target;
|
|
3442
2868
|
}
|
|
3443
2869
|
async function inlineOne(cssPath, copies, warnings, seen) {
|
|
3444
|
-
const abs =
|
|
2870
|
+
const abs = path2.resolve(cssPath);
|
|
3445
2871
|
if (seen.has(abs))
|
|
3446
2872
|
return "";
|
|
3447
2873
|
seen.add(abs);
|
|
3448
2874
|
const source = await readOrThrow(abs, "stylesheet", "the book manifest");
|
|
3449
|
-
const cssDir =
|
|
2875
|
+
const cssDir = path2.dirname(abs);
|
|
3450
2876
|
let root;
|
|
3451
2877
|
try {
|
|
3452
2878
|
root = postcss.parse(source.toString("utf-8"), { from: abs });
|
|
@@ -3468,8 +2894,8 @@ async function inlineOne(cssPath, copies, warnings, seen) {
|
|
|
3468
2894
|
return null;
|
|
3469
2895
|
}
|
|
3470
2896
|
const relPath = stripUrlSuffix(decodeRef(raw));
|
|
3471
|
-
const absAsset =
|
|
3472
|
-
const ext =
|
|
2897
|
+
const absAsset = path2.resolve(cssDir, relPath);
|
|
2898
|
+
const ext = path2.extname(absAsset).toLowerCase();
|
|
3473
2899
|
if (FONT_EXTS.has(ext)) {
|
|
3474
2900
|
const bytes2 = await readOrThrow(absAsset, "font file", abs);
|
|
3475
2901
|
return dataUri(bytes2, ext);
|
|
@@ -3487,7 +2913,7 @@ async function inlineOne(cssPath, copies, warnings, seen) {
|
|
|
3487
2913
|
imports.push({ node, target });
|
|
3488
2914
|
});
|
|
3489
2915
|
for (const { node, target } of imports) {
|
|
3490
|
-
const importedAbs =
|
|
2916
|
+
const importedAbs = path2.resolve(cssDir, stripUrlSuffix(decodeRef(target)));
|
|
3491
2917
|
const inlined = await inlineOne(importedAbs, copies, warnings, seen);
|
|
3492
2918
|
node.replaceWith(wrapImportConditions(node, postcss.parse(inlined, { from: importedAbs })));
|
|
3493
2919
|
}
|
|
@@ -3499,10 +2925,10 @@ async function inlineStyles(projectDir, stylePaths) {
|
|
|
3499
2925
|
const seen = new Set;
|
|
3500
2926
|
const parts = [];
|
|
3501
2927
|
for (const rel of stylePaths) {
|
|
3502
|
-
const abs =
|
|
2928
|
+
const abs = path2.resolve(projectDir, rel);
|
|
3503
2929
|
const css = await inlineOne(abs, copies, warnings, seen);
|
|
3504
2930
|
if (css.trim().length > 0) {
|
|
3505
|
-
parts.push(`/* ${toPosix(
|
|
2931
|
+
parts.push(`/* ${toPosix(path2.relative(projectDir, abs))} */
|
|
3506
2932
|
${css.trim()}`);
|
|
3507
2933
|
}
|
|
3508
2934
|
}
|
|
@@ -3518,14 +2944,14 @@ async function collectStyleDependencies(projectDir, stylePaths) {
|
|
|
3518
2944
|
const found = new Set;
|
|
3519
2945
|
const visited = new Set;
|
|
3520
2946
|
async function walk(cssPath) {
|
|
3521
|
-
const abs =
|
|
2947
|
+
const abs = path2.resolve(cssPath);
|
|
3522
2948
|
if (visited.has(abs))
|
|
3523
2949
|
return;
|
|
3524
2950
|
visited.add(abs);
|
|
3525
2951
|
found.add(abs);
|
|
3526
2952
|
let source;
|
|
3527
2953
|
try {
|
|
3528
|
-
source = await
|
|
2954
|
+
source = await readFile2(abs, "utf-8");
|
|
3529
2955
|
} catch {
|
|
3530
2956
|
return;
|
|
3531
2957
|
}
|
|
@@ -3535,14 +2961,14 @@ async function collectStyleDependencies(projectDir, stylePaths) {
|
|
|
3535
2961
|
} catch {
|
|
3536
2962
|
return;
|
|
3537
2963
|
}
|
|
3538
|
-
const cssDir =
|
|
2964
|
+
const cssDir = path2.dirname(abs);
|
|
3539
2965
|
root.walkDecls((decl) => {
|
|
3540
2966
|
if (!decl.value.includes("url("))
|
|
3541
2967
|
return;
|
|
3542
2968
|
for (const raw of extractCssUrls(decl.value)) {
|
|
3543
2969
|
if (!raw || isNonFilesystemRef(raw))
|
|
3544
2970
|
continue;
|
|
3545
|
-
found.add(
|
|
2971
|
+
found.add(path2.resolve(cssDir, stripUrlSuffix(decodeRef(raw))));
|
|
3546
2972
|
}
|
|
3547
2973
|
});
|
|
3548
2974
|
const imports = [];
|
|
@@ -3552,11 +2978,11 @@ async function collectStyleDependencies(projectDir, stylePaths) {
|
|
|
3552
2978
|
imports.push(target);
|
|
3553
2979
|
});
|
|
3554
2980
|
for (const target of imports) {
|
|
3555
|
-
await walk(
|
|
2981
|
+
await walk(path2.resolve(cssDir, stripUrlSuffix(decodeRef(target))));
|
|
3556
2982
|
}
|
|
3557
2983
|
}
|
|
3558
2984
|
for (const rel of stylePaths) {
|
|
3559
|
-
await walk(
|
|
2985
|
+
await walk(path2.resolve(projectDir, rel));
|
|
3560
2986
|
}
|
|
3561
2987
|
return [...found];
|
|
3562
2988
|
}
|
|
@@ -3573,8 +2999,8 @@ async function planImageCopies(projectDir, refs) {
|
|
|
3573
2999
|
continue;
|
|
3574
3000
|
}
|
|
3575
3001
|
const cleaned = stripUrlSuffix(decodeRef(ref));
|
|
3576
|
-
const abs =
|
|
3577
|
-
const rel =
|
|
3002
|
+
const abs = path2.resolve(projectDir, cleaned);
|
|
3003
|
+
const rel = path2.relative(projectDir, abs);
|
|
3578
3004
|
const dest = toPosix(rel);
|
|
3579
3005
|
copies.set(dest, { from: abs, to: dest });
|
|
3580
3006
|
destinations.set(ref, dest);
|
|
@@ -3595,18 +3021,18 @@ async function inlineShapeUrls(html, baseDir) {
|
|
|
3595
3021
|
if (!raw || isNonFilesystemRef(raw))
|
|
3596
3022
|
continue;
|
|
3597
3023
|
const cleaned = stripUrlSuffix(decodeRef(raw));
|
|
3598
|
-
if (
|
|
3024
|
+
if (path2.isAbsolute(cleaned))
|
|
3599
3025
|
continue;
|
|
3600
|
-
const abs =
|
|
3026
|
+
const abs = path2.resolve(baseDir, cleaned);
|
|
3601
3027
|
if (escapesProjectRoot(baseDir, abs))
|
|
3602
3028
|
continue;
|
|
3603
3029
|
let bytes;
|
|
3604
3030
|
try {
|
|
3605
|
-
bytes = await
|
|
3031
|
+
bytes = await readFile2(abs);
|
|
3606
3032
|
} catch {
|
|
3607
3033
|
continue;
|
|
3608
3034
|
}
|
|
3609
|
-
const mime = mimeFor(
|
|
3035
|
+
const mime = mimeFor(path2.extname(abs).toLowerCase());
|
|
3610
3036
|
const dataUri2 = `data:${mime};base64,${bytes.toString("base64")}`;
|
|
3611
3037
|
replacements.set(m[0], `--gp-shape:url("${dataUri2}")`);
|
|
3612
3038
|
}
|
|
@@ -3623,7 +3049,7 @@ async function inlineShapeUrls(html, baseDir) {
|
|
|
3623
3049
|
async function resolveActiveMarkdownFiles(inputDir, configuredFiles) {
|
|
3624
3050
|
if (configuredFiles && configuredFiles.length > 0)
|
|
3625
3051
|
return configuredFiles;
|
|
3626
|
-
return (await
|
|
3052
|
+
return (await readdir2(inputDir)).filter((f) => f.endsWith(".md") && !isOnlineSibling(f)).sort();
|
|
3627
3053
|
}
|
|
3628
3054
|
async function renderChapters(inputDir, opts = {}) {
|
|
3629
3055
|
const styles = await resolveActiveStyles(inputDir, opts.styles);
|
|
@@ -3662,7 +3088,7 @@ ${css.trim()}
|
|
|
3662
3088
|
}
|
|
3663
3089
|
return assembleBookHtml({
|
|
3664
3090
|
files,
|
|
3665
|
-
readText: (relPath) =>
|
|
3091
|
+
readText: (relPath) => readFile3(join(inputDir, canonicalChapterId(relPath)), "utf-8"),
|
|
3666
3092
|
projectCss: inlined.css,
|
|
3667
3093
|
preloadImages: cssAssetCopies.map((c) => c.to),
|
|
3668
3094
|
title: opts.title,
|
|
@@ -3693,9 +3119,9 @@ async function renderChaptersToFile(inputDir, outDir, opts = {}) {
|
|
|
3693
3119
|
}
|
|
3694
3120
|
|
|
3695
3121
|
// src/lib/markdown/plugins.ts
|
|
3696
|
-
import { createHash as
|
|
3122
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
3697
3123
|
import { existsSync as existsSync4, lstatSync, realpathSync, rmSync, statSync as statSync2, unlinkSync } from "node:fs";
|
|
3698
|
-
import { cp, link, mkdir as mkdir2, mkdtemp, readFile as
|
|
3124
|
+
import { cp, link, mkdir as mkdir2, mkdtemp, readFile as readFile5, rm, unlink, writeFile as writeFile2 } from "node:fs/promises";
|
|
3699
3125
|
import { tmpdir } from "node:os";
|
|
3700
3126
|
import { resolve as resolve3, join as join2, dirname as dirname2, basename, extname, relative, isAbsolute } from "node:path";
|
|
3701
3127
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -3720,24 +3146,24 @@ function resolveDeclaredStyles(rawStyles, baseDir, subject) {
|
|
|
3720
3146
|
}
|
|
3721
3147
|
|
|
3722
3148
|
// src/lib/extension-manifest.ts
|
|
3723
|
-
import { readFile as
|
|
3724
|
-
import
|
|
3149
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
3150
|
+
import path3 from "node:path";
|
|
3725
3151
|
var EXTENSION_MANIFEST_FILENAME = "package.json";
|
|
3726
3152
|
var GUTTERPRESS_KEY = "gutterpress";
|
|
3727
|
-
function
|
|
3153
|
+
function object(value) {
|
|
3728
3154
|
return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
3729
3155
|
}
|
|
3730
3156
|
function str(value) {
|
|
3731
3157
|
return typeof value === "string" ? value : undefined;
|
|
3732
3158
|
}
|
|
3733
3159
|
function authorName(value) {
|
|
3734
|
-
return str(value) ?? str(
|
|
3160
|
+
return str(value) ?? str(object(value)?.name);
|
|
3735
3161
|
}
|
|
3736
3162
|
function metaFromPackageJson(raw) {
|
|
3737
|
-
const pkg =
|
|
3163
|
+
const pkg = object(raw);
|
|
3738
3164
|
if (!pkg)
|
|
3739
3165
|
return {};
|
|
3740
|
-
const gp =
|
|
3166
|
+
const gp = object(pkg[GUTTERPRESS_KEY]) ?? {};
|
|
3741
3167
|
const keywords = Array.isArray(pkg.keywords) ? pkg.keywords.filter((k) => typeof k === "string") : undefined;
|
|
3742
3168
|
return {
|
|
3743
3169
|
...str(pkg.name) ? { name: str(pkg.name) } : {},
|
|
@@ -3756,7 +3182,7 @@ function metaFromPackageJson(raw) {
|
|
|
3756
3182
|
}
|
|
3757
3183
|
async function readExtensionMeta(dir) {
|
|
3758
3184
|
try {
|
|
3759
|
-
return metaFromPackageJson(JSON.parse(await
|
|
3185
|
+
return metaFromPackageJson(JSON.parse(await readFile4(path3.join(dir, EXTENSION_MANIFEST_FILENAME), "utf8")));
|
|
3760
3186
|
} catch {
|
|
3761
3187
|
return {};
|
|
3762
3188
|
}
|
|
@@ -3779,7 +3205,7 @@ function extensionCarries(meta) {
|
|
|
3779
3205
|
};
|
|
3780
3206
|
}
|
|
3781
3207
|
function pathEscapesFolder(rel) {
|
|
3782
|
-
return
|
|
3208
|
+
return path3.isAbsolute(rel) || rel.split(/[\\/]/).includes("..");
|
|
3783
3209
|
}
|
|
3784
3210
|
function assertExtensionContained(meta) {
|
|
3785
3211
|
if (meta.engineStyles !== undefined) {
|
|
@@ -4050,9 +3476,9 @@ function isReceiptBoundRequireCall(node) {
|
|
|
4050
3476
|
return true;
|
|
4051
3477
|
if (callee.type !== "MemberExpression")
|
|
4052
3478
|
return false;
|
|
4053
|
-
const
|
|
3479
|
+
const object2 = callee.object;
|
|
4054
3480
|
const property = memberName(callee);
|
|
4055
|
-
return Boolean(
|
|
3481
|
+
return Boolean(object2 && (object2.type === "Identifier" && object2.name === "module" && property === "require" || object2.type === "Identifier" && object2.name === "require" && property === "resolve"));
|
|
4056
3482
|
}
|
|
4057
3483
|
function rewriteCommonJsRequires(source, absolute, pkg, tree) {
|
|
4058
3484
|
const ast = parseJavaScript(source, {
|
|
@@ -4103,7 +3529,7 @@ async function rewriteReachableModuleImports(entryPath, tree) {
|
|
|
4103
3529
|
const pkg = packageOwningFile(tree, absolute);
|
|
4104
3530
|
if (!pkg)
|
|
4105
3531
|
throw new Error(`Reachable plugin module is outside the verified package tree: ${absolute}`);
|
|
4106
|
-
const source = await
|
|
3532
|
+
const source = await readFile5(absolute, "utf8");
|
|
4107
3533
|
const [imports] = parseModuleImports(source, absolute);
|
|
4108
3534
|
const replacements = [];
|
|
4109
3535
|
const targets = new Set;
|
|
@@ -4210,7 +3636,7 @@ async function createIsolatedVendorTree(verified, address) {
|
|
|
4210
3636
|
}
|
|
4211
3637
|
}
|
|
4212
3638
|
async function isolateVendoredPlugin(verified) {
|
|
4213
|
-
const address =
|
|
3639
|
+
const address = createHash2("sha256").update(verified.receipt.tree.digest).update("\x00").update(JSON.stringify(verified.receipt)).digest("hex");
|
|
4214
3640
|
let pending = isolatedVendorTrees.get(address);
|
|
4215
3641
|
if (!pending) {
|
|
4216
3642
|
pending = createIsolatedVendorTree(verified, address).catch((error2) => {
|
|
@@ -4614,7 +4040,7 @@ async function loadPluginsWithCss(configs, baseDir, onError) {
|
|
|
4614
4040
|
}
|
|
4615
4041
|
|
|
4616
4042
|
// src/lib/output-paths.ts
|
|
4617
|
-
import
|
|
4043
|
+
import path4 from "node:path";
|
|
4618
4044
|
|
|
4619
4045
|
// src/lib/slug.ts
|
|
4620
4046
|
function slugify(name, fallback = "") {
|
|
@@ -4632,7 +4058,7 @@ function bookSlug(title) {
|
|
|
4632
4058
|
return slugify(title ?? "", "book");
|
|
4633
4059
|
}
|
|
4634
4060
|
function resolveOutputDir(manifestDir, title) {
|
|
4635
|
-
return
|
|
4061
|
+
return path4.resolve(manifestDir, DIST_DIRNAME, bookSlug(title));
|
|
4636
4062
|
}
|
|
4637
4063
|
function artifactName(title, format) {
|
|
4638
4064
|
return `${bookSlug(title)}-${format}.pdf`;
|
|
@@ -4640,7 +4066,7 @@ function artifactName(title, format) {
|
|
|
4640
4066
|
|
|
4641
4067
|
// src/lib/ghostscript.ts
|
|
4642
4068
|
import { existsSync as existsSync5 } from "node:fs";
|
|
4643
|
-
import { readFile as
|
|
4069
|
+
import { readFile as readFile6, writeFile as writeFile3, unlink as unlink2, rename, readdir as readdir3 } from "node:fs/promises";
|
|
4644
4070
|
import { join as join3, basename as basename2, resolve as resolve4 } from "node:path";
|
|
4645
4071
|
var WINDOWS_GHOSTSCRIPT_NAMES = ["gswin64c", "gswin32c", "gs"];
|
|
4646
4072
|
var POSIX_GHOSTSCRIPT_NAMES = ["gs"];
|
|
@@ -4667,7 +4093,7 @@ async function findWindowsGhostscriptInstall(env) {
|
|
|
4667
4093
|
const installRoot = join3(root, "gs");
|
|
4668
4094
|
let versions;
|
|
4669
4095
|
try {
|
|
4670
|
-
versions = (await
|
|
4096
|
+
versions = (await readdir3(installRoot, { withFileTypes: true })).filter((entry) => entry.isDirectory() && /^gs\d/i.test(entry.name)).sort((a, b) => b.name.localeCompare(a.name, undefined, { numeric: true }));
|
|
4671
4097
|
} catch {
|
|
4672
4098
|
continue;
|
|
4673
4099
|
}
|
|
@@ -4742,7 +4168,7 @@ async function stripAnnotations(pdfPath, stagingDir) {
|
|
|
4742
4168
|
await unlink2(tmp).catch(() => {});
|
|
4743
4169
|
}
|
|
4744
4170
|
const { PDFDocument, PDFName } = await import("pdf-lib");
|
|
4745
|
-
const bytes = await
|
|
4171
|
+
const bytes = await readFile6(pdfPath);
|
|
4746
4172
|
const doc = await PDFDocument.load(bytes, { updateMetadata: false });
|
|
4747
4173
|
const annots = PDFName.of("Annots");
|
|
4748
4174
|
for (const page of doc.getPages())
|
|
@@ -4817,7 +4243,7 @@ ${docInfoConformance}/DOCINFO pdfmark
|
|
|
4817
4243
|
}
|
|
4818
4244
|
async function stampCreator(pdfPath) {
|
|
4819
4245
|
const { PDFDocument } = await import("pdf-lib");
|
|
4820
|
-
const bytes = await
|
|
4246
|
+
const bytes = await readFile6(pdfPath);
|
|
4821
4247
|
const doc = await PDFDocument.load(bytes, { updateMetadata: false });
|
|
4822
4248
|
doc.setCreator("gutterpress");
|
|
4823
4249
|
const out = await doc.save();
|
|
@@ -4868,14 +4294,14 @@ async function convertToPdfxCmyk(inputPdf, outPdf, config) {
|
|
|
4868
4294
|
|
|
4869
4295
|
// src/lib/build-fingerprint.ts
|
|
4870
4296
|
import * as fs from "node:fs";
|
|
4871
|
-
import { mkdir as mkdir3, readFile as
|
|
4872
|
-
import { createHash as
|
|
4873
|
-
import
|
|
4297
|
+
import { mkdir as mkdir3, readFile as readFile7, writeFile as writeFile4 } from "node:fs/promises";
|
|
4298
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
4299
|
+
import path5 from "node:path";
|
|
4874
4300
|
import git from "isomorphic-git";
|
|
4875
4301
|
// package.json
|
|
4876
4302
|
var package_default = {
|
|
4877
4303
|
name: "gutterpress",
|
|
4878
|
-
version: "0.11.6
|
|
4304
|
+
version: "0.11.6",
|
|
4879
4305
|
description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
|
|
4880
4306
|
author: "itlackey",
|
|
4881
4307
|
license: "MPL-2.0",
|
|
@@ -5032,7 +4458,7 @@ async function getGitRevision(sourceDir) {
|
|
|
5032
4458
|
const candidateDirs = [sourceDir, process.cwd()].filter((v) => Boolean(v));
|
|
5033
4459
|
const seen = new Set;
|
|
5034
4460
|
for (const dir of candidateDirs) {
|
|
5035
|
-
const abs =
|
|
4461
|
+
const abs = path5.resolve(dir);
|
|
5036
4462
|
if (seen.has(abs))
|
|
5037
4463
|
continue;
|
|
5038
4464
|
seen.add(abs);
|
|
@@ -5068,10 +4494,10 @@ async function getEngineBundleHash() {
|
|
|
5068
4494
|
getAssetPath("engine/gutterpress-agent.js")
|
|
5069
4495
|
]);
|
|
5070
4496
|
const [viewerBytes, agentBytes] = await Promise.all([
|
|
5071
|
-
|
|
5072
|
-
|
|
4497
|
+
readFile7(viewerPath),
|
|
4498
|
+
readFile7(agentPath)
|
|
5073
4499
|
]);
|
|
5074
|
-
const hash =
|
|
4500
|
+
const hash = createHash3("sha256");
|
|
5075
4501
|
hash.update(viewerBytes);
|
|
5076
4502
|
hash.update(agentBytes);
|
|
5077
4503
|
return hash.digest("hex").slice(0, 12);
|
|
@@ -5117,8 +4543,8 @@ function sanitizeArgs(args) {
|
|
|
5117
4543
|
return out;
|
|
5118
4544
|
}
|
|
5119
4545
|
async function writeBuildFingerprint(input) {
|
|
5120
|
-
const outputDir =
|
|
5121
|
-
const outPath =
|
|
4546
|
+
const outputDir = path5.resolve(input.outputDir);
|
|
4547
|
+
const outPath = path5.join(outputDir, FINGERPRINT_FILENAME);
|
|
5122
4548
|
const [tools, sourceRevision] = await Promise.all([
|
|
5123
4549
|
getToolVersions(),
|
|
5124
4550
|
getGitRevision(input.sourceDir)
|
|
@@ -5134,7 +4560,7 @@ async function writeBuildFingerprint(input) {
|
|
|
5134
4560
|
iccPath: input.pdfx.iccPath,
|
|
5135
4561
|
stripAnnotations: input.pdfx.stripAnnotations
|
|
5136
4562
|
},
|
|
5137
|
-
outputDir: input.recordedOutputDir ?
|
|
4563
|
+
outputDir: input.recordedOutputDir ? path5.resolve(input.recordedOutputDir) : outputDir
|
|
5138
4564
|
},
|
|
5139
4565
|
sourceRevision,
|
|
5140
4566
|
tools
|
|
@@ -5149,7 +4575,7 @@ import { existsSync as existsSync10 } from "node:fs";
|
|
|
5149
4575
|
import { dirname as dirname4, join as join4, resolve as resolve8 } from "node:path";
|
|
5150
4576
|
|
|
5151
4577
|
// src/lib/markdownlint-fix.ts
|
|
5152
|
-
import { readFile as
|
|
4578
|
+
import { readFile as readFile9, writeFile as writeFile5 } from "node:fs/promises";
|
|
5153
4579
|
import { applyFixes } from "markdownlint";
|
|
5154
4580
|
import { lint as lint2 } from "markdownlint/sync";
|
|
5155
4581
|
|
|
@@ -5170,7 +4596,7 @@ function findConfigFile(inputDir, names, explicit) {
|
|
|
5170
4596
|
}
|
|
5171
4597
|
|
|
5172
4598
|
// src/checks/source/markdownlint.ts
|
|
5173
|
-
import { readFile as
|
|
4599
|
+
import { readFile as readFile8 } from "node:fs/promises";
|
|
5174
4600
|
import { lint } from "markdownlint/sync";
|
|
5175
4601
|
import { parse as parseYaml2 } from "yaml";
|
|
5176
4602
|
|
|
@@ -5252,7 +4678,7 @@ function stripJsonComments(s) {
|
|
|
5252
4678
|
return s.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1");
|
|
5253
4679
|
}
|
|
5254
4680
|
async function loadConfig(configPath) {
|
|
5255
|
-
const raw = await
|
|
4681
|
+
const raw = await readFile8(configPath, "utf8");
|
|
5256
4682
|
let parsed;
|
|
5257
4683
|
if (configPath.endsWith(".json")) {
|
|
5258
4684
|
parsed = JSON.parse(raw);
|
|
@@ -5349,7 +4775,7 @@ async function applyMarkdownlintFixes(ctx) {
|
|
|
5349
4775
|
for (const [file, violations] of Object.entries(results)) {
|
|
5350
4776
|
if (!Array.isArray(violations) || violations.length === 0)
|
|
5351
4777
|
continue;
|
|
5352
|
-
const before = await
|
|
4778
|
+
const before = await readFile9(file, "utf8");
|
|
5353
4779
|
const after = applyFixes(before, violations);
|
|
5354
4780
|
if (after !== before)
|
|
5355
4781
|
pending.push({ file, content: after });
|
|
@@ -5474,7 +4900,7 @@ function inspectionFailed(checkId, message, extra = {}) {
|
|
|
5474
4900
|
}
|
|
5475
4901
|
|
|
5476
4902
|
// src/lib/pdf-inspect.ts
|
|
5477
|
-
import { stat, readFile as
|
|
4903
|
+
import { stat, readFile as readFile10 } from "node:fs/promises";
|
|
5478
4904
|
import { getDocumentProxy } from "unpdf";
|
|
5479
4905
|
import * as pdfjs from "unpdf/pdfjs";
|
|
5480
4906
|
function getOps(mod) {
|
|
@@ -5510,17 +4936,17 @@ function evictLru() {
|
|
|
5510
4936
|
destroyEntryAfterGrace(evicted);
|
|
5511
4937
|
}
|
|
5512
4938
|
}
|
|
5513
|
-
async function loadPdf(
|
|
4939
|
+
async function loadPdf(path6) {
|
|
5514
4940
|
let st;
|
|
5515
4941
|
try {
|
|
5516
|
-
st = await stat(
|
|
4942
|
+
st = await stat(path6);
|
|
5517
4943
|
} catch {
|
|
5518
4944
|
return null;
|
|
5519
4945
|
}
|
|
5520
|
-
const hit = docCache.get(
|
|
4946
|
+
const hit = docCache.get(path6);
|
|
5521
4947
|
if (hit && hit.mtimeMs === st.mtimeMs && hit.size === st.size) {
|
|
5522
|
-
docCache.delete(
|
|
5523
|
-
docCache.set(
|
|
4948
|
+
docCache.delete(path6);
|
|
4949
|
+
docCache.set(path6, hit);
|
|
5524
4950
|
try {
|
|
5525
4951
|
return await hit.doc;
|
|
5526
4952
|
} catch {
|
|
@@ -5529,15 +4955,15 @@ async function loadPdf(path7) {
|
|
|
5529
4955
|
}
|
|
5530
4956
|
if (hit)
|
|
5531
4957
|
destroyEntry(hit);
|
|
5532
|
-
const docPromise =
|
|
5533
|
-
docCache.delete(
|
|
5534
|
-
docCache.set(
|
|
4958
|
+
const docPromise = readFile10(path6).then((buf) => getDocumentProxy(new Uint8Array(buf)));
|
|
4959
|
+
docCache.delete(path6);
|
|
4960
|
+
docCache.set(path6, { mtimeMs: st.mtimeMs, size: st.size, doc: docPromise });
|
|
5535
4961
|
evictLru();
|
|
5536
4962
|
try {
|
|
5537
4963
|
return await docPromise;
|
|
5538
4964
|
} catch {
|
|
5539
|
-
if (docCache.get(
|
|
5540
|
-
docCache.delete(
|
|
4965
|
+
if (docCache.get(path6)?.doc === docPromise)
|
|
4966
|
+
docCache.delete(path6);
|
|
5541
4967
|
return null;
|
|
5542
4968
|
}
|
|
5543
4969
|
}
|
|
@@ -5988,9 +5414,9 @@ var check4 = {
|
|
|
5988
5414
|
registerCheck(check4);
|
|
5989
5415
|
|
|
5990
5416
|
// src/lib/pdf-parse.ts
|
|
5991
|
-
import { readFile as
|
|
5417
|
+
import { readFile as readFile11 } from "node:fs/promises";
|
|
5992
5418
|
async function readPdfBytes(pdfPath) {
|
|
5993
|
-
return
|
|
5419
|
+
return readFile11(pdfPath, "latin1");
|
|
5994
5420
|
}
|
|
5995
5421
|
function parseInkCov(out) {
|
|
5996
5422
|
const lines = out.split(/\r?\n/).filter((l) => l.includes("CMYK"));
|
|
@@ -6562,7 +5988,7 @@ registerCheck(check16);
|
|
|
6562
5988
|
|
|
6563
5989
|
// src/checks/source/htmlhint.ts
|
|
6564
5990
|
import { existsSync as existsSync7 } from "node:fs";
|
|
6565
|
-
import { readFile as
|
|
5991
|
+
import { readFile as readFile12 } from "node:fs/promises";
|
|
6566
5992
|
import htmlhint from "htmlhint";
|
|
6567
5993
|
var { HTMLHint } = htmlhint;
|
|
6568
5994
|
var CONFIG_NAMES2 = [".htmlhintrc"];
|
|
@@ -6582,8 +6008,8 @@ var check17 = {
|
|
|
6582
6008
|
const resolvedConfig = findConfigFile(ctx.inputDir, CONFIG_NAMES2, configPath);
|
|
6583
6009
|
if (!resolvedConfig && !configPath)
|
|
6584
6010
|
return [];
|
|
6585
|
-
const ruleset = resolvedConfig ? JSON.parse(await
|
|
6586
|
-
const html = await
|
|
6011
|
+
const ruleset = resolvedConfig ? JSON.parse(await readFile12(resolvedConfig, "utf8")) : HTMLHint.defaultRuleset;
|
|
6012
|
+
const html = await readFile12(ctx.htmlPath, "utf8");
|
|
6587
6013
|
const messages = HTMLHint.verify(html, ruleset);
|
|
6588
6014
|
return messages.map((m) => ({
|
|
6589
6015
|
checkId: check17.id,
|
|
@@ -6598,7 +6024,7 @@ var check17 = {
|
|
|
6598
6024
|
registerCheck(check17);
|
|
6599
6025
|
|
|
6600
6026
|
// src/checks/source/stylelint.ts
|
|
6601
|
-
import { readFile as
|
|
6027
|
+
import { readFile as readFile13 } from "node:fs/promises";
|
|
6602
6028
|
|
|
6603
6029
|
// src/lib/printsafe.ts
|
|
6604
6030
|
import postcss2 from "postcss";
|
|
@@ -6874,7 +6300,7 @@ var check18 = {
|
|
|
6874
6300
|
for (const file of files) {
|
|
6875
6301
|
let css;
|
|
6876
6302
|
try {
|
|
6877
|
-
css = await
|
|
6303
|
+
css = await readFile13(file, "utf8");
|
|
6878
6304
|
} catch (err) {
|
|
6879
6305
|
results.push({
|
|
6880
6306
|
checkId: check18.id,
|
|
@@ -6901,12 +6327,12 @@ var check18 = {
|
|
|
6901
6327
|
registerCheck(check18);
|
|
6902
6328
|
|
|
6903
6329
|
// src/checks/source/css-ownership.ts
|
|
6904
|
-
import { readFile as
|
|
6330
|
+
import { readFile as readFile14 } from "node:fs/promises";
|
|
6905
6331
|
import postcss3 from "postcss";
|
|
6906
6332
|
import { parse as parseYaml3 } from "yaml";
|
|
6907
6333
|
var CONFIG_NAMES3 = [".gutterpress/css-contract.yaml", ".gutterpress/css-contract.yml"];
|
|
6908
6334
|
async function loadContract(configPath) {
|
|
6909
|
-
const raw = await
|
|
6335
|
+
const raw = await readFile14(configPath, "utf8");
|
|
6910
6336
|
const parsed = parseYaml3(raw);
|
|
6911
6337
|
if (parsed && typeof parsed === "object")
|
|
6912
6338
|
return parsed;
|
|
@@ -7057,7 +6483,7 @@ var check19 = {
|
|
|
7057
6483
|
continue;
|
|
7058
6484
|
let css;
|
|
7059
6485
|
try {
|
|
7060
|
-
css = await
|
|
6486
|
+
css = await readFile14(absFile, "utf8");
|
|
7061
6487
|
} catch {
|
|
7062
6488
|
continue;
|
|
7063
6489
|
}
|
|
@@ -7103,7 +6529,7 @@ import { existsSync as existsSync8 } from "node:fs";
|
|
|
7103
6529
|
import { dirname as dirname3, resolve as resolve6 } from "node:path";
|
|
7104
6530
|
|
|
7105
6531
|
// src/checks/source/local-ref-parser.ts
|
|
7106
|
-
import { readFile as
|
|
6532
|
+
import { readFile as readFile15 } from "node:fs/promises";
|
|
7107
6533
|
function newlineOffsets(content) {
|
|
7108
6534
|
const offsets = [];
|
|
7109
6535
|
for (let i = 0;i < content.length; i++)
|
|
@@ -7172,7 +6598,7 @@ async function* renderedLocalRefs(ctx, checkId, results) {
|
|
|
7172
6598
|
for (const file of files) {
|
|
7173
6599
|
let refs;
|
|
7174
6600
|
try {
|
|
7175
|
-
refs = collectRenderedLocalRefs(await
|
|
6601
|
+
refs = collectRenderedLocalRefs(await readFile15(file, "utf8"));
|
|
7176
6602
|
} catch {
|
|
7177
6603
|
results.push(inspectionFailed(checkId, `Could not read source file: ${file}`, { file }));
|
|
7178
6604
|
continue;
|
|
@@ -7234,15 +6660,15 @@ function localRefExists(ref, kind, sourceFile, projectRoot) {
|
|
|
7234
6660
|
registerCheck(check20);
|
|
7235
6661
|
|
|
7236
6662
|
// src/lib/build-staging.ts
|
|
7237
|
-
import
|
|
6663
|
+
import path6 from "node:path";
|
|
7238
6664
|
import os from "node:os";
|
|
7239
6665
|
import fsp from "node:fs/promises";
|
|
7240
6666
|
|
|
7241
6667
|
// src/lib/missing-asset-placeholder.ts
|
|
7242
|
-
import { createHash as
|
|
6668
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
7243
6669
|
import { deflateSync } from "node:zlib";
|
|
7244
6670
|
function placeholderOutputPath(missingOutputPath) {
|
|
7245
|
-
const hash =
|
|
6671
|
+
const hash = createHash4("sha256").update(missingOutputPath).digest("hex").slice(0, 16);
|
|
7246
6672
|
return `assets/gutterpress-missing/${hash}.png`;
|
|
7247
6673
|
}
|
|
7248
6674
|
function decodeHtmlAttribute(value) {
|
|
@@ -7419,8 +6845,8 @@ function placeholderPng(width = 640, height = 480, cell = 32) {
|
|
|
7419
6845
|
|
|
7420
6846
|
// src/lib/build-staging.ts
|
|
7421
6847
|
async function shipViewerHtml(htmlFile, outDir) {
|
|
7422
|
-
await fsp.mkdir(
|
|
7423
|
-
await fsp.copyFile(await getAssetPath("engine/gutterpress-viewer.js"),
|
|
6848
|
+
await fsp.mkdir(path6.join(outDir, "engine"), { recursive: true });
|
|
6849
|
+
await fsp.copyFile(await getAssetPath("engine/gutterpress-viewer.js"), path6.join(outDir, "engine/gutterpress-viewer.js"));
|
|
7424
6850
|
const tag = ` <script src="engine/gutterpress-viewer.js"></script>
|
|
7425
6851
|
`;
|
|
7426
6852
|
const html = await fsp.readFile(htmlFile, "utf-8");
|
|
@@ -7452,18 +6878,18 @@ async function stageBookAssets(options) {
|
|
|
7452
6878
|
return { missing: [...missingPlaceholders.keys()].sort() };
|
|
7453
6879
|
}
|
|
7454
6880
|
async function copyReferencedAssets(copies, outDir) {
|
|
7455
|
-
const dirs = new Set(copies.map((c) =>
|
|
6881
|
+
const dirs = new Set(copies.map((c) => path6.dirname(path6.resolve(outDir, c.to))));
|
|
7456
6882
|
await Promise.all([...dirs].map((d) => fsp.mkdir(d, { recursive: true })));
|
|
7457
6883
|
const missing = new Map;
|
|
7458
6884
|
await Promise.all(copies.map(async (c) => {
|
|
7459
|
-
const dest =
|
|
6885
|
+
const dest = path6.resolve(outDir, c.to);
|
|
7460
6886
|
try {
|
|
7461
6887
|
await fsp.copyFile(c.from, dest);
|
|
7462
6888
|
} catch (err) {
|
|
7463
6889
|
if (err?.code === "ENOENT") {
|
|
7464
6890
|
const placeholder = placeholderOutputPath(c.to);
|
|
7465
|
-
const placeholderDest =
|
|
7466
|
-
await fsp.mkdir(
|
|
6891
|
+
const placeholderDest = path6.resolve(outDir, placeholder);
|
|
6892
|
+
await fsp.mkdir(path6.dirname(placeholderDest), { recursive: true });
|
|
7467
6893
|
await fsp.writeFile(placeholderDest, placeholderPng());
|
|
7468
6894
|
missing.set(c.to, placeholder);
|
|
7469
6895
|
return;
|
|
@@ -7493,7 +6919,7 @@ function dropRelativeLinkHrefs(html) {
|
|
|
7493
6919
|
});
|
|
7494
6920
|
}
|
|
7495
6921
|
async function createStageRoot() {
|
|
7496
|
-
return fsp.mkdtemp(
|
|
6922
|
+
return fsp.mkdtemp(path6.join(os.tmpdir(), "gutterpress-stage-"));
|
|
7497
6923
|
}
|
|
7498
6924
|
|
|
7499
6925
|
// src/checks/source/dangling-links.ts
|
|
@@ -7523,7 +6949,7 @@ var check21 = {
|
|
|
7523
6949
|
registerCheck(check21);
|
|
7524
6950
|
|
|
7525
6951
|
// src/checks/source/accessibility-alt-text.ts
|
|
7526
|
-
import { readFile as
|
|
6952
|
+
import { readFile as readFile16 } from "node:fs/promises";
|
|
7527
6953
|
var check22 = {
|
|
7528
6954
|
id: "source.accessibility.alt-text",
|
|
7529
6955
|
name: "Image Alt Text",
|
|
@@ -7537,7 +6963,7 @@ var check22 = {
|
|
|
7537
6963
|
const results = [];
|
|
7538
6964
|
for (const file of files) {
|
|
7539
6965
|
try {
|
|
7540
|
-
const content = await
|
|
6966
|
+
const content = await readFile16(file, "utf8");
|
|
7541
6967
|
const lines = content.split(`
|
|
7542
6968
|
`);
|
|
7543
6969
|
let inFence = false;
|
|
@@ -7581,7 +7007,7 @@ function extractImageAlts(line) {
|
|
|
7581
7007
|
registerCheck(check22);
|
|
7582
7008
|
|
|
7583
7009
|
// src/checks/source/accessibility-heading-order.ts
|
|
7584
|
-
import { readFile as
|
|
7010
|
+
import { readFile as readFile17 } from "node:fs/promises";
|
|
7585
7011
|
var check23 = {
|
|
7586
7012
|
id: "source.accessibility.heading-order",
|
|
7587
7013
|
name: "Heading Order",
|
|
@@ -7595,7 +7021,7 @@ var check23 = {
|
|
|
7595
7021
|
const results = [];
|
|
7596
7022
|
for (const file of files) {
|
|
7597
7023
|
try {
|
|
7598
|
-
const content = await
|
|
7024
|
+
const content = await readFile17(file, "utf8");
|
|
7599
7025
|
const lines = content.split(`
|
|
7600
7026
|
`);
|
|
7601
7027
|
let inFence = false;
|
|
@@ -7631,7 +7057,7 @@ var check23 = {
|
|
|
7631
7057
|
registerCheck(check23);
|
|
7632
7058
|
|
|
7633
7059
|
// src/checks/source/layout-markers.ts
|
|
7634
|
-
import { readFile as
|
|
7060
|
+
import { readFile as readFile18 } from "node:fs/promises";
|
|
7635
7061
|
var check24 = {
|
|
7636
7062
|
id: "source.markdown.layout-markers",
|
|
7637
7063
|
name: "Layout Markers",
|
|
@@ -7649,7 +7075,7 @@ var check24 = {
|
|
|
7649
7075
|
const md = createMarkdownRenderer(plugins);
|
|
7650
7076
|
for (const file of files) {
|
|
7651
7077
|
try {
|
|
7652
|
-
const content = await
|
|
7078
|
+
const content = await readFile18(file, "utf8");
|
|
7653
7079
|
const env = {};
|
|
7654
7080
|
md.render(content, env);
|
|
7655
7081
|
for (const w of env.layoutWarnings ?? []) {
|
|
@@ -7671,8 +7097,8 @@ var check24 = {
|
|
|
7671
7097
|
registerCheck(check24);
|
|
7672
7098
|
|
|
7673
7099
|
// src/checks/source/merge-markers.ts
|
|
7674
|
-
import { readFile as
|
|
7675
|
-
import
|
|
7100
|
+
import { readFile as readFile19 } from "node:fs/promises";
|
|
7101
|
+
import path7 from "node:path";
|
|
7676
7102
|
|
|
7677
7103
|
// src/checks/asset/extensions.ts
|
|
7678
7104
|
var RASTER_INSPECTABLE_EXTS = [
|
|
@@ -7713,7 +7139,7 @@ var check25 = {
|
|
|
7713
7139
|
for (const file of textFiles) {
|
|
7714
7140
|
let content;
|
|
7715
7141
|
try {
|
|
7716
|
-
content = await
|
|
7142
|
+
content = await readFile19(file, "utf8");
|
|
7717
7143
|
} catch {
|
|
7718
7144
|
results.push(inspectionFailed(check25.id, `Could not read source file: ${file}`, { file }));
|
|
7719
7145
|
continue;
|
|
@@ -7756,7 +7182,7 @@ var check25 = {
|
|
|
7756
7182
|
ignore: [...ASSET_SCAN_IGNORE_GLOBS]
|
|
7757
7183
|
});
|
|
7758
7184
|
for (const sibling of siblings.sort()) {
|
|
7759
|
-
const rel =
|
|
7185
|
+
const rel = path7.relative(ctx.inputDir, sibling).split(path7.sep).join("/");
|
|
7760
7186
|
results.push(finding(check25.id, {
|
|
7761
7187
|
severity: "warning",
|
|
7762
7188
|
code: "kept-both-versions",
|
|
@@ -7776,27 +7202,27 @@ registerCheck(check25);
|
|
|
7776
7202
|
import { stat as stat3 } from "node:fs/promises";
|
|
7777
7203
|
|
|
7778
7204
|
// src/lib/image-inspect.ts
|
|
7779
|
-
import { stat as stat2, readFile as
|
|
7205
|
+
import { stat as stat2, readFile as readFile20 } from "node:fs/promises";
|
|
7780
7206
|
var DEFAULT_DPI = 72;
|
|
7781
7207
|
var cache = new Map;
|
|
7782
|
-
async function inspectImage(
|
|
7208
|
+
async function inspectImage(path8) {
|
|
7783
7209
|
let st;
|
|
7784
7210
|
try {
|
|
7785
|
-
st = await stat2(
|
|
7211
|
+
st = await stat2(path8);
|
|
7786
7212
|
} catch {
|
|
7787
7213
|
return null;
|
|
7788
7214
|
}
|
|
7789
|
-
const hit = cache.get(
|
|
7215
|
+
const hit = cache.get(path8);
|
|
7790
7216
|
if (hit && hit.mtimeMs === st.mtimeMs && hit.size === st.size)
|
|
7791
7217
|
return hit.info;
|
|
7792
7218
|
let info2 = null;
|
|
7793
7219
|
try {
|
|
7794
|
-
const buf = await
|
|
7220
|
+
const buf = await readFile20(path8);
|
|
7795
7221
|
info2 = parseImage(buf);
|
|
7796
7222
|
} catch {
|
|
7797
7223
|
info2 = null;
|
|
7798
7224
|
}
|
|
7799
|
-
cache.set(
|
|
7225
|
+
cache.set(path8, { mtimeMs: st.mtimeMs, size: st.size, info: info2 });
|
|
7800
7226
|
return info2;
|
|
7801
7227
|
}
|
|
7802
7228
|
function parseImage(b) {
|
|
@@ -8351,7 +7777,7 @@ var check33 = {
|
|
|
8351
7777
|
registerCheck(check33);
|
|
8352
7778
|
|
|
8353
7779
|
// src/checks/heuristic/section-density.ts
|
|
8354
|
-
import { readFile as
|
|
7780
|
+
import { readFile as readFile21 } from "node:fs/promises";
|
|
8355
7781
|
var check34 = {
|
|
8356
7782
|
id: "heuristic.chunking.section-density",
|
|
8357
7783
|
name: "Section Density",
|
|
@@ -8368,7 +7794,7 @@ var check34 = {
|
|
|
8368
7794
|
const results = [];
|
|
8369
7795
|
for (const file of files) {
|
|
8370
7796
|
try {
|
|
8371
|
-
const content = await
|
|
7797
|
+
const content = await readFile21(file, "utf8");
|
|
8372
7798
|
const lines = content.split(`
|
|
8373
7799
|
`);
|
|
8374
7800
|
let currentSectionStart = 0;
|
|
@@ -9017,18 +8443,18 @@ function splitOutPath(outArg, format) {
|
|
|
9017
8443
|
if (typeof outArg !== "string" || outArg.length === 0) {
|
|
9018
8444
|
return { outDir: undefined, pdfFileOverride: null };
|
|
9019
8445
|
}
|
|
9020
|
-
const resolved =
|
|
8446
|
+
const resolved = path8.resolve(outArg);
|
|
9021
8447
|
if ((format === "pdf" || format === "pdfx") && resolved.toLowerCase().endsWith(".pdf")) {
|
|
9022
|
-
return { outDir:
|
|
8448
|
+
return { outDir: path8.dirname(resolved), pdfFileOverride: resolved };
|
|
9023
8449
|
}
|
|
9024
8450
|
return { outDir: resolved, pdfFileOverride: null };
|
|
9025
8451
|
}
|
|
9026
8452
|
function layoutWarningKey(file, line, message) {
|
|
9027
|
-
return `${
|
|
8453
|
+
return `${path8.resolve(file)}\x00${line ?? 0}\x00${message}`;
|
|
9028
8454
|
}
|
|
9029
8455
|
async function resolveBuildContext(opts) {
|
|
9030
8456
|
const { format } = opts;
|
|
9031
|
-
const inputDir =
|
|
8457
|
+
const inputDir = path8.resolve(opts.inputDir);
|
|
9032
8458
|
const { manifest, manifestDir, manifestPath } = await loadManifestWithPath(opts.manifestPath ?? inputDir, { explicit: opts.manifestPath !== undefined });
|
|
9033
8459
|
if (manifestPath === null) {
|
|
9034
8460
|
throw new UsageError(`No book manifest found in ${inputDir}. Looked for ${MANIFEST_FILENAMES.join(" or ")}. ` + "Run from your book folder or pass that folder with `gutterpress build <book-dir>`. " + "For a custom manifest filename, pass `--manifest <path>`.");
|
|
@@ -9042,10 +8468,10 @@ async function resolveBuildContext(opts) {
|
|
|
9042
8468
|
title: opts.title,
|
|
9043
8469
|
pdfx: pdfxConfigOverride
|
|
9044
8470
|
}, manifest);
|
|
9045
|
-
const target = opts.pdfFileOverride ? { kind: "file", file:
|
|
9046
|
-
const outDir = target.kind === "file" ?
|
|
8471
|
+
const target = opts.pdfFileOverride ? { kind: "file", file: path8.resolve(opts.pdfFileOverride) } : opts.outDir ? { kind: "directory", dir: opts.outDir } : { kind: "project", dir: resolveOutputDir(manifestDir, config.title) };
|
|
8472
|
+
const outDir = target.kind === "file" ? path8.dirname(target.file) : target.dir;
|
|
9047
8473
|
const gates = computeGates(format, opts, config);
|
|
9048
|
-
const workDir = target.kind === "project" ?
|
|
8474
|
+
const workDir = target.kind === "project" ? path8.join(path8.dirname(target.dir), `.${path8.basename(target.dir)}-build-${randomBytes(6).toString("hex")}`) : path8.join(os2.tmpdir(), `gutterpress-build-${randomBytes(6).toString("hex")}`);
|
|
9049
8475
|
return {
|
|
9050
8476
|
opts,
|
|
9051
8477
|
format,
|
|
@@ -9115,7 +8541,7 @@ async function renderBook(ctx) {
|
|
|
9115
8541
|
pluginStyles,
|
|
9116
8542
|
onChapterWarnings: (file, warnings) => {
|
|
9117
8543
|
for (const w of warnings) {
|
|
9118
|
-
const key = layoutWarningKey(
|
|
8544
|
+
const key = layoutWarningKey(path8.resolve(renderDir, file), w.line, w.message);
|
|
9119
8545
|
if (!ctx.prevalidatedLayoutWarningKeys.has(key)) {
|
|
9120
8546
|
log.warn(` ${file}, line ${w.line}: ${w.message}`);
|
|
9121
8547
|
}
|
|
@@ -9154,9 +8580,9 @@ async function renderBook(ctx) {
|
|
|
9154
8580
|
return htmlFile;
|
|
9155
8581
|
}
|
|
9156
8582
|
async function resolveIccProfile(icc, manifestDir, explicitIccPath) {
|
|
9157
|
-
const iccCandidates =
|
|
8583
|
+
const iccCandidates = path8.isAbsolute(icc) ? [icc] : [path8.resolve(manifestDir, icc), path8.resolve(icc)];
|
|
9158
8584
|
let effectiveIccPath = iccCandidates.find((candidate) => fs2.existsSync(candidate)) ?? null;
|
|
9159
|
-
if (!effectiveIccPath && !explicitIccPath &&
|
|
8585
|
+
if (!effectiveIccPath && !explicitIccPath && path8.basename(icc) === "CGATS21_CRPC1.icc") {
|
|
9160
8586
|
effectiveIccPath = await getAssetPath("profiles/CGATS21_CRPC1.icc");
|
|
9161
8587
|
}
|
|
9162
8588
|
if (!effectiveIccPath) {
|
|
@@ -9172,13 +8598,13 @@ async function finalizeBuild(ctx, fingerprint, wroteMessage, paths, artifactName
|
|
|
9172
8598
|
});
|
|
9173
8599
|
await publishBuild(ctx, artifactName2);
|
|
9174
8600
|
const delivered = ctx.target.kind === "project" || ctx.target.kind === "directory" && ctx.format === "html";
|
|
9175
|
-
const fingerprintPath = delivered ?
|
|
8601
|
+
const fingerprintPath = delivered ? path8.join(ctx.outDir, path8.basename(workFingerprint)) : null;
|
|
9176
8602
|
log.success(wroteMessage);
|
|
9177
8603
|
if (fingerprintPath)
|
|
9178
8604
|
log.info(`Fingerprint: ${fingerprintPath}`);
|
|
9179
8605
|
return {
|
|
9180
8606
|
outDir: ctx.outDir,
|
|
9181
|
-
htmlPath: delivered ?
|
|
8607
|
+
htmlPath: delivered ? path8.join(ctx.outDir, path8.relative(ctx.workDir, paths.htmlPath)) : null,
|
|
9182
8608
|
pdfPath: paths.pdfPath,
|
|
9183
8609
|
fingerprintPath,
|
|
9184
8610
|
diagnostics
|
|
@@ -9189,7 +8615,7 @@ async function publishToDirectory(workDir, targetDir, format, artifactName2) {
|
|
|
9189
8615
|
if (format !== "html") {
|
|
9190
8616
|
if (!artifactName2)
|
|
9191
8617
|
throw new BuildError("No artifact to write for this format", 1);
|
|
9192
|
-
await fsp2.copyFile(
|
|
8618
|
+
await fsp2.copyFile(path8.join(workDir, artifactName2), path8.join(targetDir, artifactName2));
|
|
9193
8619
|
return;
|
|
9194
8620
|
}
|
|
9195
8621
|
await fsp2.cp(workDir, targetDir, { recursive: true, force: true });
|
|
@@ -9199,8 +8625,8 @@ async function publishBuild(ctx, artifactName2) {
|
|
|
9199
8625
|
if (target.kind === "file") {
|
|
9200
8626
|
if (!artifactName2)
|
|
9201
8627
|
throw new BuildError("No artifact to write for this format", 1);
|
|
9202
|
-
await fsp2.mkdir(
|
|
9203
|
-
await fsp2.copyFile(
|
|
8628
|
+
await fsp2.mkdir(path8.dirname(target.file), { recursive: true });
|
|
8629
|
+
await fsp2.copyFile(path8.join(workDir, artifactName2), target.file);
|
|
9204
8630
|
return;
|
|
9205
8631
|
}
|
|
9206
8632
|
if (target.kind === "directory") {
|
|
@@ -9208,12 +8634,12 @@ async function publishBuild(ctx, artifactName2) {
|
|
|
9208
8634
|
return;
|
|
9209
8635
|
}
|
|
9210
8636
|
for (const name of await listPdfs(target.dir)) {
|
|
9211
|
-
if (!fs2.existsSync(
|
|
9212
|
-
await fsp2.rename(
|
|
8637
|
+
if (!fs2.existsSync(path8.join(workDir, name))) {
|
|
8638
|
+
await fsp2.rename(path8.join(target.dir, name), path8.join(workDir, name));
|
|
9213
8639
|
}
|
|
9214
8640
|
}
|
|
9215
8641
|
await fsp2.rm(target.dir, { recursive: true, force: true });
|
|
9216
|
-
await fsp2.mkdir(
|
|
8642
|
+
await fsp2.mkdir(path8.dirname(target.dir), { recursive: true });
|
|
9217
8643
|
await fsp2.rename(workDir, target.dir);
|
|
9218
8644
|
}
|
|
9219
8645
|
async function listPdfs(dir) {
|
|
@@ -9229,7 +8655,7 @@ class HtmlOutput {
|
|
|
9229
8655
|
const { workDir, outDir, inputDir, config, opts } = ctx;
|
|
9230
8656
|
log.info("Shipping self-contained HTML + viewer bundle (native engine)");
|
|
9231
8657
|
await shipViewerHtml(htmlFile, workDir);
|
|
9232
|
-
await fsp2.writeFile(
|
|
8658
|
+
await fsp2.writeFile(path8.join(workDir, "index.html"), `<!DOCTYPE html><html><head><meta charset="utf-8"><meta http-equiv="refresh" content="0;url=book.html"><title>Gutterpress</title></head><body></body></html>
|
|
9233
8659
|
`, "utf-8");
|
|
9234
8660
|
return finalizeBuild(ctx, {
|
|
9235
8661
|
command: "build",
|
|
@@ -9242,7 +8668,7 @@ class HtmlOutput {
|
|
|
9242
8668
|
iccPath: null,
|
|
9243
8669
|
stripAnnotations: null
|
|
9244
8670
|
}
|
|
9245
|
-
}, `Wrote: ${
|
|
8671
|
+
}, `Wrote: ${path8.join(outDir, BOOK_HTML)}`, { htmlPath: htmlFile, pdfPath: null });
|
|
9246
8672
|
}
|
|
9247
8673
|
}
|
|
9248
8674
|
|
|
@@ -9251,12 +8677,12 @@ class PdfOutput {
|
|
|
9251
8677
|
const { workDir, outDir, inputDir, config, opts, format, manifestDir, gates } = ctx;
|
|
9252
8678
|
const pdfxMode = format === "pdfx" ? opts.pdfxFlavor ?? config.pdfx.flavor : undefined;
|
|
9253
8679
|
const pdfName = artifactName(config.title, pdfxMode ? "pdfx" : "pdf");
|
|
9254
|
-
const pdfFile =
|
|
9255
|
-
const reportedPdf = ctx.target.kind === "file" ? ctx.target.file :
|
|
8680
|
+
const pdfFile = path8.join(workDir, pdfName);
|
|
8681
|
+
const reportedPdf = ctx.target.kind === "file" ? ctx.target.file : path8.join(outDir, pdfName);
|
|
9256
8682
|
const stage = await createStageRoot();
|
|
9257
8683
|
try {
|
|
9258
|
-
const rawPdf = pdfxMode ?
|
|
9259
|
-
await fsp2.mkdir(
|
|
8684
|
+
const rawPdf = pdfxMode ? path8.join(stage, "raw.pdf") : path8.resolve(pdfFile);
|
|
8685
|
+
await fsp2.mkdir(path8.dirname(path8.resolve(pdfFile)), { recursive: true });
|
|
9260
8686
|
log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
|
|
9261
8687
|
const { buildNativePdf } = await import("./engine-15dfcy85.js");
|
|
9262
8688
|
const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
|
|
@@ -9289,7 +8715,7 @@ class PdfOutput {
|
|
|
9289
8715
|
await stripAnnotations(rawPdf, stage);
|
|
9290
8716
|
}
|
|
9291
8717
|
log.info(`Converting to CMYK PDF/X (${pdfxMode})`);
|
|
9292
|
-
await convertToPdfxCmyk(rawPdf,
|
|
8718
|
+
await convertToPdfxCmyk(rawPdf, path8.resolve(pdfFile), {
|
|
9293
8719
|
iccPath: effectiveIccPath,
|
|
9294
8720
|
pdfx: pdfxMode,
|
|
9295
8721
|
title: config.title,
|
|
@@ -9349,10 +8775,10 @@ async function runBuild(opts) {
|
|
|
9349
8775
|
}
|
|
9350
8776
|
}
|
|
9351
8777
|
// src/preview/lifecycle.ts
|
|
9352
|
-
import
|
|
8778
|
+
import path10 from "path";
|
|
9353
8779
|
import { tmpdir as tmpdir2 } from "os";
|
|
9354
8780
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
9355
|
-
import { readdir as
|
|
8781
|
+
import { readdir as readdir4, readFile as readFile22, writeFile as writeFile6 } from "node:fs/promises";
|
|
9356
8782
|
|
|
9357
8783
|
// src/utils/file-utils.ts
|
|
9358
8784
|
import { promises as fs3 } from "fs";
|
|
@@ -9374,7 +8800,7 @@ async function mkdir4(dirPath) {
|
|
|
9374
8800
|
// src/preview/file-watcher.ts
|
|
9375
8801
|
import { watch } from "chokidar";
|
|
9376
8802
|
import fsp3 from "node:fs/promises";
|
|
9377
|
-
import
|
|
8803
|
+
import path9 from "path";
|
|
9378
8804
|
var EMPTY_BOOK_HTML = `<!doctype html>
|
|
9379
8805
|
<html lang="en">
|
|
9380
8806
|
<head>
|
|
@@ -9427,7 +8853,7 @@ function injectPreviewScripts(html, pageIsolateChapters) {
|
|
|
9427
8853
|
async function generateAndWriteHtml(inputPath, tempDir, config, cssAssets) {
|
|
9428
8854
|
if (!inputPath) {
|
|
9429
8855
|
cssAssets.clear();
|
|
9430
|
-
await fsp3.writeFile(
|
|
8856
|
+
await fsp3.writeFile(path9.join(tempDir, BOOK_HTML_FILENAME), EMPTY_BOOK_HTML, "utf-8");
|
|
9431
8857
|
return;
|
|
9432
8858
|
}
|
|
9433
8859
|
const nextAssets = new Map;
|
|
@@ -9442,7 +8868,7 @@ async function generateAndWriteHtml(inputPath, tempDir, config, cssAssets) {
|
|
|
9442
8868
|
cssAssets.clear();
|
|
9443
8869
|
for (const [to, from] of nextAssets)
|
|
9444
8870
|
cssAssets.set(to, from);
|
|
9445
|
-
await fsp3.writeFile(
|
|
8871
|
+
await fsp3.writeFile(path9.join(tempDir, BOOK_HTML_FILENAME), injectPreviewScripts(html, false), "utf-8");
|
|
9446
8872
|
}
|
|
9447
8873
|
async function renderChapterPreviewHtml(inputPath, file, config) {
|
|
9448
8874
|
const html = await renderPreviewBook(inputPath, config, {
|
|
@@ -9454,12 +8880,12 @@ async function renderChapterPreviewHtml(inputPath, file, config) {
|
|
|
9454
8880
|
function describeChanges(changes, inputResolved) {
|
|
9455
8881
|
const files = [];
|
|
9456
8882
|
for (const [changedPath, event] of changes) {
|
|
9457
|
-
const relative2 =
|
|
9458
|
-
if (relative2 === "" || relative2 === ".." || relative2.startsWith(`..${
|
|
8883
|
+
const relative2 = path9.relative(inputResolved, path9.resolve(changedPath));
|
|
8884
|
+
if (relative2 === "" || relative2 === ".." || relative2.startsWith(`..${path9.sep}`) || path9.isAbsolute(relative2))
|
|
9459
8885
|
continue;
|
|
9460
8886
|
files.push({
|
|
9461
8887
|
relativePath: canonicalChapterId(relative2),
|
|
9462
|
-
ext:
|
|
8888
|
+
ext: path9.extname(changedPath).toLowerCase(),
|
|
9463
8889
|
event
|
|
9464
8890
|
});
|
|
9465
8891
|
}
|
|
@@ -9511,23 +8937,23 @@ function isIgnoredWatchPath(candidatePath, projectRoot) {
|
|
|
9511
8937
|
return isDotPathUnderRoot(candidatePath, projectRoot) || isGeneratedProjectPath(relative2);
|
|
9512
8938
|
}
|
|
9513
8939
|
function isExternalWatchCandidate(candidatePath, targets) {
|
|
9514
|
-
const candidate =
|
|
8940
|
+
const candidate = path9.resolve(candidatePath);
|
|
9515
8941
|
for (const rawTarget of targets) {
|
|
9516
|
-
const target =
|
|
9517
|
-
const rel =
|
|
9518
|
-
if (rel === "" || rel !== ".." && !rel.startsWith(`..${
|
|
8942
|
+
const target = path9.resolve(rawTarget);
|
|
8943
|
+
const rel = path9.relative(candidate, target);
|
|
8944
|
+
if (rel === "" || rel !== ".." && !rel.startsWith(`..${path9.sep}`) && !path9.isAbsolute(rel))
|
|
9519
8945
|
return true;
|
|
9520
8946
|
}
|
|
9521
8947
|
return false;
|
|
9522
8948
|
}
|
|
9523
8949
|
async function nearestExistingDirectory(start) {
|
|
9524
|
-
let current =
|
|
8950
|
+
let current = path9.resolve(start);
|
|
9525
8951
|
while (true) {
|
|
9526
8952
|
try {
|
|
9527
8953
|
if ((await fsp3.stat(current)).isDirectory())
|
|
9528
8954
|
return current;
|
|
9529
8955
|
} catch {}
|
|
9530
|
-
const parent =
|
|
8956
|
+
const parent = path9.dirname(current);
|
|
9531
8957
|
if (parent === current)
|
|
9532
8958
|
return current;
|
|
9533
8959
|
current = parent;
|
|
@@ -9536,7 +8962,7 @@ async function nearestExistingDirectory(start) {
|
|
|
9536
8962
|
async function externalWatchRoots(targets) {
|
|
9537
8963
|
const roots = new Set;
|
|
9538
8964
|
for (const target of targets) {
|
|
9539
|
-
roots.add(await nearestExistingDirectory(
|
|
8965
|
+
roots.add(await nearestExistingDirectory(path9.dirname(path9.resolve(target))));
|
|
9540
8966
|
}
|
|
9541
8967
|
return [...roots];
|
|
9542
8968
|
}
|
|
@@ -9550,19 +8976,19 @@ async function extensionFolderFiles(dir) {
|
|
|
9550
8976
|
const meta = await readExtensionMeta(dir);
|
|
9551
8977
|
const entry = extensionEntry(meta);
|
|
9552
8978
|
return [
|
|
9553
|
-
|
|
9554
|
-
...entry ? [
|
|
8979
|
+
path9.join(dir, EXTENSION_MANIFEST_FILENAME),
|
|
8980
|
+
...entry ? [path9.resolve(dir, entry)] : [],
|
|
9555
8981
|
...await collectStyleDependencies(dir, extensionStyleList(meta))
|
|
9556
8982
|
];
|
|
9557
8983
|
}
|
|
9558
8984
|
async function externalWatchTargets(projectDir, config) {
|
|
9559
|
-
const root =
|
|
8985
|
+
const root = path9.resolve(projectDir);
|
|
9560
8986
|
let canonicalRoot = root;
|
|
9561
8987
|
try {
|
|
9562
8988
|
canonicalRoot = await fsp3.realpath(root);
|
|
9563
8989
|
} catch {}
|
|
9564
8990
|
const styles = await resolveActiveStyles(root, config.styles);
|
|
9565
|
-
const extensionPaths = (config.extensions ?? []).map((p) => p.path).filter((p) => !!p).map((p) =>
|
|
8991
|
+
const extensionPaths = (config.extensions ?? []).map((p) => p.path).filter((p) => !!p).map((p) => path9.resolve(root, p));
|
|
9566
8992
|
const candidates = [
|
|
9567
8993
|
...await collectStyleDependencies(root, styles),
|
|
9568
8994
|
...extensionPaths,
|
|
@@ -9570,17 +8996,17 @@ async function externalWatchTargets(projectDir, config) {
|
|
|
9570
8996
|
];
|
|
9571
8997
|
const external = new Set;
|
|
9572
8998
|
for (const abs of candidates) {
|
|
9573
|
-
const authoredPath =
|
|
9574
|
-
const authoredRel =
|
|
9575
|
-
if (authoredRel !== "" && (authoredRel === ".." || authoredRel.startsWith(`..${
|
|
8999
|
+
const authoredPath = path9.resolve(abs);
|
|
9000
|
+
const authoredRel = path9.relative(root, authoredPath);
|
|
9001
|
+
if (authoredRel !== "" && (authoredRel === ".." || authoredRel.startsWith(`..${path9.sep}`) || path9.isAbsolute(authoredRel))) {
|
|
9576
9002
|
external.add(authoredPath);
|
|
9577
9003
|
}
|
|
9578
9004
|
let canonicalPath = authoredPath;
|
|
9579
9005
|
try {
|
|
9580
9006
|
canonicalPath = await fsp3.realpath(canonicalPath);
|
|
9581
9007
|
} catch {}
|
|
9582
|
-
const rel =
|
|
9583
|
-
if (rel === "" || rel !== ".." && !rel.startsWith(`..${
|
|
9008
|
+
const rel = path9.relative(canonicalRoot, canonicalPath);
|
|
9009
|
+
if (rel === "" || rel !== ".." && !rel.startsWith(`..${path9.sep}`) && !path9.isAbsolute(rel))
|
|
9584
9010
|
continue;
|
|
9585
9011
|
external.add(canonicalPath);
|
|
9586
9012
|
}
|
|
@@ -9607,7 +9033,7 @@ async function addAndAwaitWatch(watcher, roots) {
|
|
|
9607
9033
|
}
|
|
9608
9034
|
}
|
|
9609
9035
|
function createFileWatcher(state) {
|
|
9610
|
-
const inputResolved =
|
|
9036
|
+
const inputResolved = path9.resolve(state.currentInputPath);
|
|
9611
9037
|
const watcher = watch(inputResolved, {
|
|
9612
9038
|
persistent: true,
|
|
9613
9039
|
ignoreInitial: true,
|
|
@@ -9647,7 +9073,7 @@ function createFileWatcher(state) {
|
|
|
9647
9073
|
async function recordChange(event, filePath) {
|
|
9648
9074
|
if (closed)
|
|
9649
9075
|
return;
|
|
9650
|
-
const target =
|
|
9076
|
+
const target = path9.resolve(filePath);
|
|
9651
9077
|
const settled = settledWrites.get(target);
|
|
9652
9078
|
if (settled && event !== "unlink" && Date.now() <= settled.expiresAt) {
|
|
9653
9079
|
const diskContent = await fsp3.readFile(target, "utf8").catch(() => null);
|
|
@@ -9668,7 +9094,7 @@ function createFileWatcher(state) {
|
|
|
9668
9094
|
recordChange(event, filePath);
|
|
9669
9095
|
});
|
|
9670
9096
|
externalWatcher.on("all", (event, filePath) => {
|
|
9671
|
-
const target =
|
|
9097
|
+
const target = path9.resolve(filePath);
|
|
9672
9098
|
if (!desiredExternals.has(target))
|
|
9673
9099
|
return;
|
|
9674
9100
|
if (event === "add" && suppressInitialExternalAdds.delete(target))
|
|
@@ -9676,8 +9102,8 @@ function createFileWatcher(state) {
|
|
|
9676
9102
|
recordChange(event, target);
|
|
9677
9103
|
});
|
|
9678
9104
|
function isWatchedSource(target) {
|
|
9679
|
-
const rel =
|
|
9680
|
-
const inProject = rel === "" || rel !== ".." && !rel.startsWith(`..${
|
|
9105
|
+
const rel = path9.relative(inputResolved, target);
|
|
9106
|
+
const inProject = rel === "" || rel !== ".." && !rel.startsWith(`..${path9.sep}`) && !path9.isAbsolute(rel);
|
|
9681
9107
|
if (inProject)
|
|
9682
9108
|
return !isIgnoredWatchPath(target, inputResolved);
|
|
9683
9109
|
return desiredExternals.has(target);
|
|
@@ -9702,7 +9128,7 @@ function createFileWatcher(state) {
|
|
|
9702
9128
|
state.notifySettledWrite = (filePath, writtenContent) => {
|
|
9703
9129
|
if (closed)
|
|
9704
9130
|
return;
|
|
9705
|
-
const target =
|
|
9131
|
+
const target = path9.resolve(filePath);
|
|
9706
9132
|
if (isWatchedSource(target)) {
|
|
9707
9133
|
acceptSettledWrite(target, writtenContent);
|
|
9708
9134
|
return;
|
|
@@ -9723,7 +9149,7 @@ function createFileWatcher(state) {
|
|
|
9723
9149
|
return;
|
|
9724
9150
|
let nextTargets;
|
|
9725
9151
|
try {
|
|
9726
|
-
nextTargets = new Set((await externalWatchTargets(inputResolved, state.config)).map((target) =>
|
|
9152
|
+
nextTargets = new Set((await externalWatchTargets(inputResolved, state.config)).map((target) => path9.resolve(target)));
|
|
9727
9153
|
} catch (err) {
|
|
9728
9154
|
debug(`Could not resolve external watch targets: ${err}`);
|
|
9729
9155
|
return;
|
|
@@ -9855,7 +9281,7 @@ async function stopFileWatcher(state) {
|
|
|
9855
9281
|
}
|
|
9856
9282
|
|
|
9857
9283
|
// src/preview/lifecycle.ts
|
|
9858
|
-
var TEMP_DIR_BASE =
|
|
9284
|
+
var TEMP_DIR_BASE = path10.join(tmpdir2(), "gutterpress-preview");
|
|
9859
9285
|
var PID_FILE_NAME = ".gutterpress.pid";
|
|
9860
9286
|
function isProcessAlive(pid) {
|
|
9861
9287
|
if (!Number.isFinite(pid) || pid <= 0)
|
|
@@ -9870,15 +9296,15 @@ function isProcessAlive(pid) {
|
|
|
9870
9296
|
async function cleanupOrphanTempDirs() {
|
|
9871
9297
|
let entries;
|
|
9872
9298
|
try {
|
|
9873
|
-
entries = await
|
|
9299
|
+
entries = await readdir4(TEMP_DIR_BASE);
|
|
9874
9300
|
} catch {
|
|
9875
9301
|
return;
|
|
9876
9302
|
}
|
|
9877
9303
|
for (const entry of entries) {
|
|
9878
|
-
const dir =
|
|
9879
|
-
const pidFile =
|
|
9304
|
+
const dir = path10.join(TEMP_DIR_BASE, entry);
|
|
9305
|
+
const pidFile = path10.join(dir, PID_FILE_NAME);
|
|
9880
9306
|
try {
|
|
9881
|
-
const raw = await
|
|
9307
|
+
const raw = await readFile22(pidFile, "utf8");
|
|
9882
9308
|
const pid = parseInt(raw.trim(), 10);
|
|
9883
9309
|
if (!isProcessAlive(pid)) {
|
|
9884
9310
|
debug(`Removing orphan temp dir ${dir} (pid ${pid} not alive)`);
|
|
@@ -9890,10 +9316,10 @@ async function cleanupOrphanTempDirs() {
|
|
|
9890
9316
|
async function initializePreviewDirectories() {
|
|
9891
9317
|
await cleanupOrphanTempDirs();
|
|
9892
9318
|
const tempDirSuffix = randomBytes2(8).toString("hex");
|
|
9893
|
-
const tempDir =
|
|
9319
|
+
const tempDir = path10.join(TEMP_DIR_BASE, tempDirSuffix);
|
|
9894
9320
|
await mkdir4(tempDir);
|
|
9895
9321
|
debug(`Created temporary directory: ${tempDir}`);
|
|
9896
|
-
await writeFile6(
|
|
9322
|
+
await writeFile6(path10.join(tempDir, PID_FILE_NAME), `${process.pid}
|
|
9897
9323
|
`, "utf8");
|
|
9898
9324
|
return tempDir;
|
|
9899
9325
|
}
|
|
@@ -9983,9 +9409,9 @@ function createServerState(inputPath, tempDir, config, options) {
|
|
|
9983
9409
|
// src/preview/http-server.ts
|
|
9984
9410
|
import http from "node:http";
|
|
9985
9411
|
import net from "node:net";
|
|
9986
|
-
import { createHash as
|
|
9987
|
-
import { readFile as
|
|
9988
|
-
import
|
|
9412
|
+
import { createHash as createHash5, randomUUID } from "node:crypto";
|
|
9413
|
+
import { readFile as readFile23, stat as stat4 } from "node:fs/promises";
|
|
9414
|
+
import path12 from "path";
|
|
9989
9415
|
import { WebSocket, WebSocketServer } from "ws";
|
|
9990
9416
|
|
|
9991
9417
|
// src/lib/open-path.ts
|
|
@@ -10017,7 +9443,7 @@ function openPath(filePath) {
|
|
|
10017
9443
|
}
|
|
10018
9444
|
|
|
10019
9445
|
// src/lib/static-serve.ts
|
|
10020
|
-
import
|
|
9446
|
+
import path11 from "node:path";
|
|
10021
9447
|
var STATIC_MIME = {
|
|
10022
9448
|
".html": "text/html; charset=utf-8",
|
|
10023
9449
|
".htm": "text/html; charset=utf-8",
|
|
@@ -10039,9 +9465,9 @@ var STATIC_MIME = {
|
|
|
10039
9465
|
".otf": "font/otf"
|
|
10040
9466
|
};
|
|
10041
9467
|
function resolveWithinRoot(relPath, root) {
|
|
10042
|
-
const resolvedRoot =
|
|
10043
|
-
const candidate =
|
|
10044
|
-
if (candidate !== resolvedRoot && !candidate.startsWith(resolvedRoot +
|
|
9468
|
+
const resolvedRoot = path11.resolve(root);
|
|
9469
|
+
const candidate = path11.resolve(resolvedRoot, "." + (relPath.startsWith("/") ? relPath : "/" + relPath));
|
|
9470
|
+
if (candidate !== resolvedRoot && !candidate.startsWith(resolvedRoot + path11.sep)) {
|
|
10045
9471
|
return null;
|
|
10046
9472
|
}
|
|
10047
9473
|
return candidate;
|
|
@@ -10294,7 +9720,7 @@ async function serveStatic(absPath, res, cacheControl = "no-store", extraHeaders
|
|
|
10294
9720
|
try {
|
|
10295
9721
|
const s = await stat4(filePath);
|
|
10296
9722
|
if (s.isDirectory())
|
|
10297
|
-
filePath =
|
|
9723
|
+
filePath = path12.join(filePath, "index.html");
|
|
10298
9724
|
} catch {
|
|
10299
9725
|
res.writeHead(404);
|
|
10300
9726
|
res.end("Not Found");
|
|
@@ -10302,7 +9728,7 @@ async function serveStatic(absPath, res, cacheControl = "no-store", extraHeaders
|
|
|
10302
9728
|
}
|
|
10303
9729
|
let data;
|
|
10304
9730
|
try {
|
|
10305
|
-
data = await
|
|
9731
|
+
data = await readFile23(filePath);
|
|
10306
9732
|
} catch {
|
|
10307
9733
|
res.writeHead(404);
|
|
10308
9734
|
res.end("Not Found");
|
|
@@ -10315,7 +9741,7 @@ async function serveStatic(absPath, res, cacheControl = "no-store", extraHeaders
|
|
|
10315
9741
|
res.end(withHmr);
|
|
10316
9742
|
return;
|
|
10317
9743
|
}
|
|
10318
|
-
const ct = STATIC_MIME[
|
|
9744
|
+
const ct = STATIC_MIME[path12.extname(filePath).toLowerCase()] ?? "application/octet-stream";
|
|
10319
9745
|
res.writeHead(200, { "Content-Type": ct, "Cache-Control": cacheControl, ...extraHeaders });
|
|
10320
9746
|
res.end(data);
|
|
10321
9747
|
}
|
|
@@ -10325,7 +9751,7 @@ var EMBEDDED_CACHE_CONTROL = "public, no-cache";
|
|
|
10325
9751
|
var EMBEDDED_ETAG = `"gutterpress-${PACKAGE_VERSION}"`;
|
|
10326
9752
|
var PROJECT_ASSET_CACHE_CONTROL = "private, no-cache";
|
|
10327
9753
|
async function serveRevalidatedStatic(req, absPath, res, validators) {
|
|
10328
|
-
const extension =
|
|
9754
|
+
const extension = path12.extname(absPath).toLowerCase();
|
|
10329
9755
|
if (extension === ".html" || extension === ".htm") {
|
|
10330
9756
|
await serveStatic(absPath, res);
|
|
10331
9757
|
return;
|
|
@@ -10352,12 +9778,12 @@ async function serveRevalidatedStatic(req, absPath, res, validators) {
|
|
|
10352
9778
|
let data;
|
|
10353
9779
|
if (!validator || validator.stamp !== stamp) {
|
|
10354
9780
|
try {
|
|
10355
|
-
data = await
|
|
9781
|
+
data = await readFile23(absPath);
|
|
10356
9782
|
} catch {
|
|
10357
9783
|
await serveStatic(absPath, res);
|
|
10358
9784
|
return;
|
|
10359
9785
|
}
|
|
10360
|
-
const digest =
|
|
9786
|
+
const digest = createHash5("sha256").update(data).digest("base64url");
|
|
10361
9787
|
validator = { stamp, etag: `"gutterpress-file-${digest}"` };
|
|
10362
9788
|
validators.set(absPath, validator);
|
|
10363
9789
|
}
|
|
@@ -10491,7 +9917,7 @@ async function createPreviewServer(state, port) {
|
|
|
10491
9917
|
const file = rawFile ? canonicalChapterId(rawFile) : "";
|
|
10492
9918
|
const revision = Number(url.searchParams.get("revision"));
|
|
10493
9919
|
const configuredFiles = state.config.source?.files;
|
|
10494
|
-
if (!file || !state.currentInputPath || !incrementalPreviewEnabled() || !Number.isSafeInteger(revision) || revision !== reloadRevision ||
|
|
9920
|
+
if (!file || !state.currentInputPath || !incrementalPreviewEnabled() || !Number.isSafeInteger(revision) || revision !== reloadRevision || path12.extname(file).toLowerCase() !== ".md" || hasDotSegment(file) || !resolveWithinRoot(file, state.currentInputPath) || Array.isArray(configuredFiles) && configuredFiles.length > 0 && !configuredFiles.some((configured) => canonicalChapterId(configured) === file)) {
|
|
10495
9921
|
res.writeHead(400);
|
|
10496
9922
|
res.end("Bad Request");
|
|
10497
9923
|
return;
|
|
@@ -10693,9 +10119,9 @@ async function startPreviewServer(options) {
|
|
|
10693
10119
|
import { platform, arch, release } from "node:os";
|
|
10694
10120
|
|
|
10695
10121
|
// src/lib/remote-auth/token-store.ts
|
|
10696
|
-
import { chmod, mkdir as mkdir5, readFile as
|
|
10122
|
+
import { chmod, mkdir as mkdir5, readFile as readFile24, rename as rename2, writeFile as writeFile7 } from "node:fs/promises";
|
|
10697
10123
|
import os3 from "node:os";
|
|
10698
|
-
import
|
|
10124
|
+
import path13 from "node:path";
|
|
10699
10125
|
function credentialHostKey(hostOrUrl) {
|
|
10700
10126
|
const trimmed = String(hostOrUrl ?? "").trim();
|
|
10701
10127
|
if (!trimmed)
|
|
@@ -10722,22 +10148,22 @@ function defaultConfigDir() {
|
|
|
10722
10148
|
if (override)
|
|
10723
10149
|
return override;
|
|
10724
10150
|
if (process.platform === "win32" && process.env.APPDATA) {
|
|
10725
|
-
return
|
|
10151
|
+
return path13.join(process.env.APPDATA, "gutterpress");
|
|
10726
10152
|
}
|
|
10727
10153
|
const xdg = process.env.XDG_CONFIG_HOME?.trim();
|
|
10728
|
-
return
|
|
10154
|
+
return path13.join(xdg || path13.join(os3.homedir(), ".config"), "gutterpress");
|
|
10729
10155
|
}
|
|
10730
10156
|
|
|
10731
10157
|
class FileTokenStore {
|
|
10732
10158
|
filePath;
|
|
10733
10159
|
queue = Promise.resolve();
|
|
10734
10160
|
constructor(filePath) {
|
|
10735
|
-
this.filePath = filePath ??
|
|
10161
|
+
this.filePath = filePath ?? path13.join(defaultConfigDir(), "credentials.json");
|
|
10736
10162
|
}
|
|
10737
10163
|
async read() {
|
|
10738
10164
|
let raw;
|
|
10739
10165
|
try {
|
|
10740
|
-
raw = await
|
|
10166
|
+
raw = await readFile24(this.filePath, "utf8");
|
|
10741
10167
|
} catch (e) {
|
|
10742
10168
|
if (e?.code === "ENOENT") {
|
|
10743
10169
|
return { version: 1, credentials: {} };
|
|
@@ -10757,7 +10183,7 @@ class FileTokenStore {
|
|
|
10757
10183
|
return { version: 1, credentials: {} };
|
|
10758
10184
|
}
|
|
10759
10185
|
async write(data) {
|
|
10760
|
-
await mkdir5(
|
|
10186
|
+
await mkdir5(path13.dirname(this.filePath), { recursive: true });
|
|
10761
10187
|
const tmpPath = `${this.filePath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
10762
10188
|
await writeFile7(tmpPath, JSON.stringify(data, null, 2), {
|
|
10763
10189
|
encoding: "utf8",
|
|
@@ -10906,18 +10332,18 @@ async function getSystemDiagnostics() {
|
|
|
10906
10332
|
installHint: INSTALL_HINTS3[t.hintKey]
|
|
10907
10333
|
};
|
|
10908
10334
|
}
|
|
10909
|
-
const [
|
|
10335
|
+
const [path14, available] = await Promise.all([
|
|
10910
10336
|
findTool(t.bin),
|
|
10911
10337
|
isToolAvailable(t.bin)
|
|
10912
10338
|
]);
|
|
10913
|
-
const found = available || !!
|
|
10339
|
+
const found = available || !!path14;
|
|
10914
10340
|
const version = found ? await getVersion(t.bin) : undefined;
|
|
10915
10341
|
return {
|
|
10916
10342
|
id: t.id,
|
|
10917
10343
|
name: t.name,
|
|
10918
10344
|
bin: t.bin,
|
|
10919
10345
|
found,
|
|
10920
|
-
path:
|
|
10346
|
+
path: path14,
|
|
10921
10347
|
version,
|
|
10922
10348
|
usedBy: t.usedBy,
|
|
10923
10349
|
installHint: INSTALL_HINTS3[t.hintKey]
|
|
@@ -10937,25 +10363,25 @@ async function getSystemDiagnostics() {
|
|
|
10937
10363
|
};
|
|
10938
10364
|
}
|
|
10939
10365
|
// src/lib/project-scaffold.ts
|
|
10940
|
-
import { access, copyFile, cp as cp2, mkdir as mkdir7, readdir as
|
|
10366
|
+
import { access, copyFile, cp as cp2, mkdir as mkdir7, readdir as readdir5, readFile as readFile26, stat as stat5, writeFile as writeFile8 } from "node:fs/promises";
|
|
10941
10367
|
import { constants as FS, existsSync as existsSync12 } from "node:fs";
|
|
10942
|
-
import
|
|
10368
|
+
import path15 from "node:path";
|
|
10943
10369
|
|
|
10944
10370
|
// src/lib/manifest-doc.ts
|
|
10945
10371
|
import { existsSync as existsSync11 } from "node:fs";
|
|
10946
10372
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
10947
|
-
import { mkdir as mkdir6, open, readFile as
|
|
10948
|
-
import
|
|
10373
|
+
import { mkdir as mkdir6, open, readFile as readFile25, rename as rename3, rm as rm2 } from "node:fs/promises";
|
|
10374
|
+
import path14 from "node:path";
|
|
10949
10375
|
import { parseDocument, isSeq, YAMLSeq } from "yaml";
|
|
10950
10376
|
function resolveManifestPath(projectDir) {
|
|
10951
|
-
const existing = MANIFEST_FILENAMES.find((name) => existsSync11(
|
|
10952
|
-
return
|
|
10377
|
+
const existing = MANIFEST_FILENAMES.find((name) => existsSync11(path14.join(projectDir, name)));
|
|
10378
|
+
return path14.join(projectDir, existing ?? MANIFEST_FILENAMES[0]);
|
|
10953
10379
|
}
|
|
10954
10380
|
async function loadManifestDoc(projectDir) {
|
|
10955
10381
|
const file = resolveManifestPath(projectDir);
|
|
10956
10382
|
let text = "";
|
|
10957
10383
|
try {
|
|
10958
|
-
text = await
|
|
10384
|
+
text = await readFile25(file, "utf8");
|
|
10959
10385
|
} catch (error2) {
|
|
10960
10386
|
if (error2.code !== "ENOENT")
|
|
10961
10387
|
throw error2;
|
|
@@ -10963,9 +10389,9 @@ async function loadManifestDoc(projectDir) {
|
|
|
10963
10389
|
return { doc: parseDocument(text), file };
|
|
10964
10390
|
}
|
|
10965
10391
|
async function writeManifestDoc(file, doc) {
|
|
10966
|
-
const dir =
|
|
10392
|
+
const dir = path14.dirname(file);
|
|
10967
10393
|
await mkdir6(dir, { recursive: true });
|
|
10968
|
-
const temporary =
|
|
10394
|
+
const temporary = path14.join(dir, `.${path14.basename(file)}.tmp-${process.pid}-${randomUUID2()}`);
|
|
10969
10395
|
try {
|
|
10970
10396
|
const handle = await open(temporary, "wx", 438);
|
|
10971
10397
|
try {
|
|
@@ -11074,7 +10500,7 @@ async function scaffoldProject(options) {
|
|
|
11074
10500
|
throw new CreateProjectErrorImpl("invalid-name", `Could not derive a valid folder name from "${name}".`);
|
|
11075
10501
|
}
|
|
11076
10502
|
const parentDir = options.parentDir;
|
|
11077
|
-
if (!parentDir || !
|
|
10503
|
+
if (!parentDir || !path15.isAbsolute(parentDir)) {
|
|
11078
10504
|
throw new CreateProjectErrorImpl("parent-not-writable", "The save location must be an absolute path.");
|
|
11079
10505
|
}
|
|
11080
10506
|
try {
|
|
@@ -11082,7 +10508,7 @@ async function scaffoldProject(options) {
|
|
|
11082
10508
|
} catch {
|
|
11083
10509
|
throw new CreateProjectErrorImpl("parent-not-writable", `The chosen save location can't be written to: ${parentDir}`);
|
|
11084
10510
|
}
|
|
11085
|
-
const projectDir =
|
|
10511
|
+
const projectDir = path15.join(parentDir, folderName);
|
|
11086
10512
|
let targetExists = true;
|
|
11087
10513
|
try {
|
|
11088
10514
|
await access(projectDir, FS.F_OK);
|
|
@@ -11099,21 +10525,21 @@ async function scaffoldProject(options) {
|
|
|
11099
10525
|
try {
|
|
11100
10526
|
await mkdir7(projectDir, { recursive: true });
|
|
11101
10527
|
if (customTemplateDir) {
|
|
11102
|
-
const entries = await
|
|
10528
|
+
const entries = await readdir5(customTemplateDir, { withFileTypes: true });
|
|
11103
10529
|
for (const entry of entries) {
|
|
11104
10530
|
if (entry.name === ".gutterpress-template.json")
|
|
11105
10531
|
continue;
|
|
11106
|
-
await cp2(
|
|
10532
|
+
await cp2(path15.join(customTemplateDir, entry.name), path15.join(projectDir, entry.name), { recursive: true });
|
|
11107
10533
|
}
|
|
11108
|
-
await mkdir7(
|
|
10534
|
+
await mkdir7(path15.join(projectDir, "assets"), { recursive: true });
|
|
11109
10535
|
} else {
|
|
11110
10536
|
const tplManifest = await getAssetPath(`templates/${template}/manifest.yaml`);
|
|
11111
10537
|
const tplChapter = await getAssetPath(`templates/${template}/chapter-01.md`);
|
|
11112
|
-
await mkdir7(
|
|
11113
|
-
await copyFile(tplManifest,
|
|
11114
|
-
await copyFile(tplChapter,
|
|
11115
|
-
await mkdir7(
|
|
11116
|
-
await writeFile8(
|
|
10538
|
+
await mkdir7(path15.join(projectDir, "assets"), { recursive: true });
|
|
10539
|
+
await copyFile(tplManifest, path15.join(projectDir, MANIFEST_FILENAMES[0]));
|
|
10540
|
+
await copyFile(tplChapter, path15.join(projectDir, "chapter-01.md"));
|
|
10541
|
+
await mkdir7(path15.join(projectDir, "styles"), { recursive: true });
|
|
10542
|
+
await writeFile8(path15.join(projectDir, "styles", "book.css"), starterOverridesCss(), "utf8");
|
|
11117
10543
|
}
|
|
11118
10544
|
} catch (e) {
|
|
11119
10545
|
throw new CreateProjectErrorImpl("scaffold-io", `Could not create the book files: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -11123,13 +10549,13 @@ async function scaffoldProject(options) {
|
|
|
11123
10549
|
} catch (e) {
|
|
11124
10550
|
throw new CreateProjectErrorImpl("scaffold-io", `Could not write .gitignore: ${e instanceof Error ? e.message : String(e)}`);
|
|
11125
10551
|
}
|
|
11126
|
-
const manifestPath = MANIFEST_FILENAMES.map((name2) =>
|
|
10552
|
+
const manifestPath = MANIFEST_FILENAMES.map((name2) => path15.join(projectDir, name2)).find(existsSync12) ?? path15.join(projectDir, MANIFEST_FILENAMES[0]);
|
|
11127
10553
|
const author = (options.author ?? "").trim() || DEFAULT_AUTHOR;
|
|
11128
10554
|
const substitutions = {
|
|
11129
10555
|
"{{TITLE}}": escapeYamlScalar(name),
|
|
11130
10556
|
"{{AUTHOR}}": escapeYamlScalar(author)
|
|
11131
10557
|
};
|
|
11132
|
-
let openFile =
|
|
10558
|
+
let openFile = path15.join(projectDir, "chapter-01.md");
|
|
11133
10559
|
try {
|
|
11134
10560
|
await fillTemplateFile(manifestPath, substitutions);
|
|
11135
10561
|
if (preset) {
|
|
@@ -11150,7 +10576,7 @@ async function scaffoldProject(options) {
|
|
|
11150
10576
|
}
|
|
11151
10577
|
const firstSource = await firstSourceFile(manifestPath);
|
|
11152
10578
|
if (firstSource)
|
|
11153
|
-
openFile =
|
|
10579
|
+
openFile = path15.join(projectDir, firstSource);
|
|
11154
10580
|
await fillTemplateFile(openFile, { "{{TITLE}}": name }).catch(() => {});
|
|
11155
10581
|
} catch (e) {
|
|
11156
10582
|
throw new CreateProjectErrorImpl("scaffold-io", `Could not finalize the book files: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -11160,7 +10586,7 @@ async function scaffoldProject(options) {
|
|
|
11160
10586
|
let versionHistoryError;
|
|
11161
10587
|
if (requested === "local-git") {
|
|
11162
10588
|
try {
|
|
11163
|
-
const { providerFor } = await import("./source-provider-
|
|
10589
|
+
const { providerFor } = await import("./source-provider-m5wes47h.js");
|
|
11164
10590
|
const provider = providerFor({ type: "local-folder", path: projectDir });
|
|
11165
10591
|
await provider.initVersionHistory({
|
|
11166
10592
|
projectDir,
|
|
@@ -11172,7 +10598,7 @@ async function scaffoldProject(options) {
|
|
|
11172
10598
|
versionHistory = "none";
|
|
11173
10599
|
const { findEnclosingRepoDir } = await import("./project-source-ekcyp63q.js");
|
|
11174
10600
|
const enclosingRepoDir = await findEnclosingRepoDir(projectDir);
|
|
11175
|
-
versionHistoryError = enclosingRepoDir ? `This book was added to ${
|
|
10601
|
+
versionHistoryError = enclosingRepoDir ? `This book was added to ${path15.basename(enclosingRepoDir)}'s shared version history; save a snapshot to record it.` : e instanceof Error ? e.message : String(e);
|
|
11176
10602
|
}
|
|
11177
10603
|
}
|
|
11178
10604
|
const result = {
|
|
@@ -11192,7 +10618,7 @@ function prettifyFolderName(base) {
|
|
|
11192
10618
|
}
|
|
11193
10619
|
async function adoptFolder(options) {
|
|
11194
10620
|
const dir = options.dir;
|
|
11195
|
-
if (typeof dir !== "string" || !
|
|
10621
|
+
if (typeof dir !== "string" || !path15.isAbsolute(dir)) {
|
|
11196
10622
|
throw new CreateProjectErrorImpl("scaffold-io", "An absolute folder path is required.");
|
|
11197
10623
|
}
|
|
11198
10624
|
let st;
|
|
@@ -11204,24 +10630,24 @@ async function adoptFolder(options) {
|
|
|
11204
10630
|
if (!st.isDirectory()) {
|
|
11205
10631
|
throw new CreateProjectErrorImpl("scaffold-io", `Not a folder: ${dir}`);
|
|
11206
10632
|
}
|
|
11207
|
-
if (MANIFEST_FILENAMES.some((name) => existsSync12(
|
|
10633
|
+
if (MANIFEST_FILENAMES.some((name) => existsSync12(path15.join(dir, name)))) {
|
|
11208
10634
|
throw new CreateProjectErrorImpl("target-exists", "This folder is already a Gutterpress book.");
|
|
11209
10635
|
}
|
|
11210
10636
|
const template = options.template ?? "book";
|
|
11211
|
-
const title = (options.title ?? "").trim() || prettifyFolderName(
|
|
10637
|
+
const title = (options.title ?? "").trim() || prettifyFolderName(path15.basename(dir));
|
|
11212
10638
|
const author = (options.author ?? "").trim() || DEFAULT_AUTHOR;
|
|
11213
10639
|
let mdFiles;
|
|
11214
10640
|
try {
|
|
11215
|
-
const entries = await
|
|
10641
|
+
const entries = await readdir5(dir, { withFileTypes: true });
|
|
11216
10642
|
mdFiles = entries.filter((e) => e.isFile() && /\.(md|markdown)$/i.test(e.name)).map((e) => e.name).sort((a, b) => a.localeCompare(b));
|
|
11217
10643
|
if (mdFiles.length === 0) {
|
|
11218
10644
|
const tplChapter = await getAssetPath(`templates/${template}/chapter-01.md`);
|
|
11219
|
-
await copyFile(tplChapter,
|
|
11220
|
-
await fillTemplateFile(
|
|
10645
|
+
await copyFile(tplChapter, path15.join(dir, "chapter-01.md"));
|
|
10646
|
+
await fillTemplateFile(path15.join(dir, "chapter-01.md"), { "{{TITLE}}": title }).catch(() => {});
|
|
11221
10647
|
mdFiles = ["chapter-01.md"];
|
|
11222
10648
|
}
|
|
11223
|
-
await mkdir7(
|
|
11224
|
-
const bookCssPath =
|
|
10649
|
+
await mkdir7(path15.join(dir, "styles"), { recursive: true });
|
|
10650
|
+
const bookCssPath = path15.join(dir, "styles", "book.css");
|
|
11225
10651
|
if (!existsSync12(bookCssPath)) {
|
|
11226
10652
|
await writeFile8(bookCssPath, starterOverridesCss(), "utf8");
|
|
11227
10653
|
}
|
|
@@ -11239,8 +10665,8 @@ ${filesYaml}
|
|
|
11239
10665
|
styles:
|
|
11240
10666
|
- styles/book.css
|
|
11241
10667
|
`;
|
|
11242
|
-
await writeFile8(
|
|
11243
|
-
await mkdir7(
|
|
10668
|
+
await writeFile8(path15.join(dir, MANIFEST_FILENAMES[0]), manifest, "utf8");
|
|
10669
|
+
await mkdir7(path15.join(dir, "assets"), { recursive: true });
|
|
11244
10670
|
await ensureGitignoreHasDist(dir);
|
|
11245
10671
|
} catch (e) {
|
|
11246
10672
|
if (e instanceof CreateProjectErrorImpl)
|
|
@@ -11252,7 +10678,7 @@ styles:
|
|
|
11252
10678
|
let versionHistoryError;
|
|
11253
10679
|
if (requested === "local-git") {
|
|
11254
10680
|
try {
|
|
11255
|
-
const { providerFor } = await import("./source-provider-
|
|
10681
|
+
const { providerFor } = await import("./source-provider-m5wes47h.js");
|
|
11256
10682
|
const provider = providerFor({ type: "local-folder", path: dir });
|
|
11257
10683
|
await provider.initVersionHistory({
|
|
11258
10684
|
projectDir: dir,
|
|
@@ -11267,8 +10693,8 @@ styles:
|
|
|
11267
10693
|
}
|
|
11268
10694
|
const result = {
|
|
11269
10695
|
projectDir: dir,
|
|
11270
|
-
manifestPath:
|
|
11271
|
-
openFile:
|
|
10696
|
+
manifestPath: path15.join(dir, MANIFEST_FILENAMES[0]),
|
|
10697
|
+
openFile: path15.join(dir, mdFiles[0]),
|
|
11272
10698
|
versionHistory
|
|
11273
10699
|
};
|
|
11274
10700
|
if (versionHistoryError !== undefined)
|
|
@@ -11276,10 +10702,10 @@ styles:
|
|
|
11276
10702
|
return result;
|
|
11277
10703
|
}
|
|
11278
10704
|
async function ensureGitignoreHasDist(projectDir) {
|
|
11279
|
-
const gitignorePath =
|
|
10705
|
+
const gitignorePath = path15.join(projectDir, ".gitignore");
|
|
11280
10706
|
let existing = null;
|
|
11281
10707
|
try {
|
|
11282
|
-
existing = await
|
|
10708
|
+
existing = await readFile26(gitignorePath, "utf8");
|
|
11283
10709
|
} catch {}
|
|
11284
10710
|
if (existing === null) {
|
|
11285
10711
|
await writeFile8(gitignorePath, `dist/
|
|
@@ -11299,7 +10725,7 @@ async function ensureGitignoreHasDist(projectDir) {
|
|
|
11299
10725
|
async function firstSourceFile(manifestPath) {
|
|
11300
10726
|
let text;
|
|
11301
10727
|
try {
|
|
11302
|
-
text = await
|
|
10728
|
+
text = await readFile26(manifestPath, "utf8");
|
|
11303
10729
|
} catch {
|
|
11304
10730
|
return;
|
|
11305
10731
|
}
|
|
@@ -11311,16 +10737,16 @@ async function firstSourceFile(manifestPath) {
|
|
|
11311
10737
|
return m ? m[1].trim() : undefined;
|
|
11312
10738
|
}
|
|
11313
10739
|
async function fillTemplateFile(filePath, substitutions) {
|
|
11314
|
-
let text = await
|
|
10740
|
+
let text = await readFile26(filePath, "utf8");
|
|
11315
10741
|
for (const [token, value] of Object.entries(substitutions)) {
|
|
11316
10742
|
text = text.split(token).join(value);
|
|
11317
10743
|
}
|
|
11318
10744
|
await writeFile8(filePath, text, "utf8");
|
|
11319
10745
|
}
|
|
11320
10746
|
// src/lib/extension-scaffold.ts
|
|
11321
|
-
import { access as access2, mkdir as mkdir8, readFile as
|
|
10747
|
+
import { access as access2, mkdir as mkdir8, readFile as readFile27, writeFile as writeFile9 } from "node:fs/promises";
|
|
11322
10748
|
import { constants as FS2 } from "node:fs";
|
|
11323
|
-
import
|
|
10749
|
+
import path16 from "node:path";
|
|
11324
10750
|
var EXTENSION_KINDS = ["plugin", "theme"];
|
|
11325
10751
|
var RESERVED_PREFIX = "gp-";
|
|
11326
10752
|
var TEMPLATE_FILES = {
|
|
@@ -11398,7 +10824,7 @@ async function scaffoldExtension(options) {
|
|
|
11398
10824
|
}
|
|
11399
10825
|
const prefix = resolveExtensionPrefix(slug, options.prefix);
|
|
11400
10826
|
const parentDir = options.parentDir;
|
|
11401
|
-
if (!parentDir || !
|
|
10827
|
+
if (!parentDir || !path16.isAbsolute(parentDir)) {
|
|
11402
10828
|
throw new ExtensionScaffoldError("parent-not-writable", "The save location must be an absolute path.");
|
|
11403
10829
|
}
|
|
11404
10830
|
try {
|
|
@@ -11406,7 +10832,7 @@ async function scaffoldExtension(options) {
|
|
|
11406
10832
|
} catch {
|
|
11407
10833
|
throw new ExtensionScaffoldError("parent-not-writable", `The chosen save location can't be written to: ${parentDir}`);
|
|
11408
10834
|
}
|
|
11409
|
-
const extensionDir =
|
|
10835
|
+
const extensionDir = path16.join(parentDir, slug);
|
|
11410
10836
|
let targetExists = true;
|
|
11411
10837
|
try {
|
|
11412
10838
|
await access2(extensionDir, FS2.F_OK);
|
|
@@ -11433,9 +10859,9 @@ async function scaffoldExtension(options) {
|
|
|
11433
10859
|
await mkdir8(extensionDir, { recursive: true });
|
|
11434
10860
|
for (const [assetName, relDest] of entries) {
|
|
11435
10861
|
const src = await getAssetPath(`extension-templates/${kind}/${assetName}`);
|
|
11436
|
-
const dest =
|
|
11437
|
-
await mkdir8(
|
|
11438
|
-
const text = await
|
|
10862
|
+
const dest = path16.join(extensionDir, relDest);
|
|
10863
|
+
await mkdir8(path16.dirname(dest), { recursive: true });
|
|
10864
|
+
const text = await readFile27(src, "utf8");
|
|
11439
10865
|
const table = relDest.endsWith(".json") ? jsonSafe : plain;
|
|
11440
10866
|
await writeFile9(dest, substitute(text, table), "utf8");
|
|
11441
10867
|
written.push(relDest);
|
|
@@ -11445,11 +10871,11 @@ async function scaffoldExtension(options) {
|
|
|
11445
10871
|
}
|
|
11446
10872
|
return {
|
|
11447
10873
|
extensionDir,
|
|
11448
|
-
manifestPath:
|
|
10874
|
+
manifestPath: path16.join(extensionDir, "package.json"),
|
|
11449
10875
|
kind,
|
|
11450
10876
|
slug,
|
|
11451
10877
|
prefix,
|
|
11452
|
-
openFile:
|
|
10878
|
+
openFile: path16.join(extensionDir, OPEN_FILE[kind]),
|
|
11453
10879
|
files: written
|
|
11454
10880
|
};
|
|
11455
10881
|
}
|
|
@@ -11461,10 +10887,10 @@ function substitute(text, table) {
|
|
|
11461
10887
|
return out;
|
|
11462
10888
|
}
|
|
11463
10889
|
// src/lib/project-templates.ts
|
|
11464
|
-
import { cp as cp3, mkdir as mkdir9, readFile as
|
|
10890
|
+
import { cp as cp3, mkdir as mkdir9, readFile as readFile28, readdir as readdir6, stat as stat6, writeFile as writeFile10 } from "node:fs/promises";
|
|
11465
10891
|
import { constants as FS3, existsSync as existsSync13 } from "node:fs";
|
|
11466
10892
|
import { access as access3 } from "node:fs/promises";
|
|
11467
|
-
import
|
|
10893
|
+
import path17 from "node:path";
|
|
11468
10894
|
import { isMap, isScalar, isSeq as isSeq2, parseDocument as parseDocument2 } from "yaml";
|
|
11469
10895
|
var BUILT_IN_TEMPLATE_IDS = [
|
|
11470
10896
|
"book",
|
|
@@ -11473,7 +10899,7 @@ var BUILT_IN_TEMPLATE_IDS = [
|
|
|
11473
10899
|
];
|
|
11474
10900
|
async function readTemplateChoices(manifestPath) {
|
|
11475
10901
|
try {
|
|
11476
|
-
const text = await
|
|
10902
|
+
const text = await readFile28(manifestPath, "utf8");
|
|
11477
10903
|
const doc = parseDocument2(text);
|
|
11478
10904
|
const out = {};
|
|
11479
10905
|
const preset = doc.get("preset");
|
|
@@ -11549,7 +10975,7 @@ function rewriteEntry(node, newPath) {
|
|
|
11549
10975
|
node.value = newPath;
|
|
11550
10976
|
}
|
|
11551
10977
|
function commonAncestorDir(absFiles) {
|
|
11552
|
-
const dirSegs = absFiles.map((f) =>
|
|
10978
|
+
const dirSegs = absFiles.map((f) => path17.resolve(f).split(path17.sep).slice(0, -1));
|
|
11553
10979
|
if (dirSegs.length === 0)
|
|
11554
10980
|
return "";
|
|
11555
10981
|
let common = dirSegs[0];
|
|
@@ -11559,22 +10985,22 @@ function commonAncestorDir(absFiles) {
|
|
|
11559
10985
|
i++;
|
|
11560
10986
|
common = common.slice(0, i);
|
|
11561
10987
|
}
|
|
11562
|
-
return common.join(
|
|
10988
|
+
return common.join(path17.sep) || path17.sep;
|
|
11563
10989
|
}
|
|
11564
10990
|
function pickVendorDir(templateDir, base) {
|
|
11565
|
-
const preferred =
|
|
11566
|
-
if (!existsSync13(
|
|
10991
|
+
const preferred = path17.basename(base) || VENDOR_DIR;
|
|
10992
|
+
if (!existsSync13(path17.join(templateDir, preferred)))
|
|
11567
10993
|
return preferred;
|
|
11568
10994
|
for (let n = 1;; n++) {
|
|
11569
10995
|
const candidate = `${preferred}-${n}`;
|
|
11570
|
-
if (!existsSync13(
|
|
10996
|
+
if (!existsSync13(path17.join(templateDir, candidate)))
|
|
11571
10997
|
return candidate;
|
|
11572
10998
|
}
|
|
11573
10999
|
}
|
|
11574
11000
|
var TEMPLATE_META_FILE = ".gutterpress-template.json";
|
|
11575
11001
|
async function reconcileSharedRefs(templateDir, sourceProjectDir, mode) {
|
|
11576
11002
|
const { doc, file } = await loadManifestDoc(templateDir);
|
|
11577
|
-
const escapes = (p) => escapesProjectRoot(sourceProjectDir,
|
|
11003
|
+
const escapes = (p) => escapesProjectRoot(sourceProjectDir, path17.resolve(sourceProjectDir, p));
|
|
11578
11004
|
const stylesSeq = doc.get("styles", true);
|
|
11579
11005
|
const extensionsSeq = doc.get("extensions", true);
|
|
11580
11006
|
const escapingStyles = collectEscaping(isSeq2(stylesSeq) ? stylesSeq.items : [], scalarString, escapes);
|
|
@@ -11590,16 +11016,16 @@ async function reconcileSharedRefs(templateDir, sourceProjectDir, mode) {
|
|
|
11590
11016
|
return { vendoredRefs: [], excludedRefs };
|
|
11591
11017
|
}
|
|
11592
11018
|
const styleClosure = await collectStyleDependencies(sourceProjectDir, escapingStyles.map((s) => s.value));
|
|
11593
|
-
const extensionFiles = escapingExtensions.map((p) =>
|
|
11019
|
+
const extensionFiles = escapingExtensions.map((p) => path17.resolve(sourceProjectDir, p.value));
|
|
11594
11020
|
const externalFiles = [...new Set([...styleClosure, ...extensionFiles])].filter((f) => escapesProjectRoot(sourceProjectDir, f));
|
|
11595
11021
|
const base = commonAncestorDir(externalFiles);
|
|
11596
11022
|
const vendorDir = pickVendorDir(templateDir, base);
|
|
11597
11023
|
await Promise.all(externalFiles.filter((abs) => existsSync13(abs)).map(async (abs) => {
|
|
11598
|
-
const dest =
|
|
11599
|
-
await mkdir9(
|
|
11024
|
+
const dest = path17.join(templateDir, vendorDir, path17.relative(base, abs));
|
|
11025
|
+
await mkdir9(path17.dirname(dest), { recursive: true });
|
|
11600
11026
|
await cp3(abs, dest, { recursive: true });
|
|
11601
11027
|
}));
|
|
11602
|
-
const bookLocal = (value) => toPosixPath(
|
|
11028
|
+
const bookLocal = (value) => toPosixPath(path17.join(vendorDir, path17.relative(base, path17.resolve(sourceProjectDir, value))));
|
|
11603
11029
|
const vendoredRefs = [];
|
|
11604
11030
|
for (const { node, value } of escapingStyles) {
|
|
11605
11031
|
const rewritten = bookLocal(value);
|
|
@@ -11619,7 +11045,7 @@ async function saveProjectAsTemplate(options) {
|
|
|
11619
11045
|
const id = slugify(name);
|
|
11620
11046
|
if (!id)
|
|
11621
11047
|
throw new Error(`Could not derive a template id from "${name}".`);
|
|
11622
|
-
const dir =
|
|
11048
|
+
const dir = path17.join(templatesRoot, id);
|
|
11623
11049
|
let exists = true;
|
|
11624
11050
|
try {
|
|
11625
11051
|
await access3(dir, FS3.F_OK);
|
|
@@ -11630,19 +11056,19 @@ async function saveProjectAsTemplate(options) {
|
|
|
11630
11056
|
throw new Error(`A template named "${name}" already exists. Choose a different name.`);
|
|
11631
11057
|
}
|
|
11632
11058
|
await mkdir9(dir, { recursive: true });
|
|
11633
|
-
const entries = await
|
|
11059
|
+
const entries = await readdir6(projectDir, { withFileTypes: true });
|
|
11634
11060
|
for (const entry of entries) {
|
|
11635
11061
|
if (SKIP_ENTRIES.has(entry.name))
|
|
11636
11062
|
continue;
|
|
11637
11063
|
if (entry.name === TEMPLATE_META_FILE)
|
|
11638
11064
|
continue;
|
|
11639
|
-
await cp3(
|
|
11065
|
+
await cp3(path17.join(projectDir, entry.name), path17.join(dir, entry.name), { recursive: true });
|
|
11640
11066
|
}
|
|
11641
11067
|
const manifestName = MANIFEST_FILENAMES.find((name2) => entries.some((entry) => entry.name === name2));
|
|
11642
11068
|
const shared = manifestName ? await reconcileSharedRefs(dir, projectDir, options.sharedRefs ?? "vendor") : { vendoredRefs: [], excludedRefs: [] };
|
|
11643
11069
|
if (manifestName)
|
|
11644
|
-
await retokeniseManifest(
|
|
11645
|
-
await writeFile10(
|
|
11070
|
+
await retokeniseManifest(path17.join(dir, manifestName));
|
|
11071
|
+
await writeFile10(path17.join(dir, TEMPLATE_META_FILE), JSON.stringify({ label: name }, null, 2), "utf8");
|
|
11646
11072
|
return {
|
|
11647
11073
|
id,
|
|
11648
11074
|
label: name,
|
|
@@ -11655,7 +11081,7 @@ async function saveProjectAsTemplate(options) {
|
|
|
11655
11081
|
async function retokeniseManifest(manifestPath) {
|
|
11656
11082
|
let text;
|
|
11657
11083
|
try {
|
|
11658
|
-
text = await
|
|
11084
|
+
text = await readFile28(manifestPath, "utf8");
|
|
11659
11085
|
} catch {
|
|
11660
11086
|
return;
|
|
11661
11087
|
}
|
|
@@ -11665,7 +11091,7 @@ async function retokeniseManifest(manifestPath) {
|
|
|
11665
11091
|
async function listCustomTemplates(templatesRoot) {
|
|
11666
11092
|
let entries;
|
|
11667
11093
|
try {
|
|
11668
|
-
entries = await
|
|
11094
|
+
entries = await readdir6(templatesRoot, { withFileTypes: true });
|
|
11669
11095
|
} catch {
|
|
11670
11096
|
return [];
|
|
11671
11097
|
}
|
|
@@ -11673,10 +11099,10 @@ async function listCustomTemplates(templatesRoot) {
|
|
|
11673
11099
|
for (const entry of entries) {
|
|
11674
11100
|
if (!entry.isDirectory())
|
|
11675
11101
|
continue;
|
|
11676
|
-
const dir =
|
|
11102
|
+
const dir = path17.join(templatesRoot, entry.name);
|
|
11677
11103
|
let label = prettify(entry.name);
|
|
11678
11104
|
try {
|
|
11679
|
-
const meta = JSON.parse(await
|
|
11105
|
+
const meta = JSON.parse(await readFile28(path17.join(dir, TEMPLATE_META_FILE), "utf8"));
|
|
11680
11106
|
if (typeof meta.label === "string" && meta.label)
|
|
11681
11107
|
label = meta.label;
|
|
11682
11108
|
} catch {}
|
|
@@ -11686,7 +11112,7 @@ async function listCustomTemplates(templatesRoot) {
|
|
|
11686
11112
|
description: "Your saved template.",
|
|
11687
11113
|
kind: "custom",
|
|
11688
11114
|
dir,
|
|
11689
|
-
...await readTemplateChoices(
|
|
11115
|
+
...await readTemplateChoices(path17.join(dir, MANIFEST_FILENAMES[0]))
|
|
11690
11116
|
});
|
|
11691
11117
|
}
|
|
11692
11118
|
out.sort((a, b) => a.label.localeCompare(b.label));
|
|
@@ -11698,37 +11124,37 @@ async function importTemplateFromFolder(options) {
|
|
|
11698
11124
|
if (!srcStat.isDirectory()) {
|
|
11699
11125
|
throw new Error("The chosen path is not a folder.");
|
|
11700
11126
|
}
|
|
11701
|
-
const name = options.name?.trim() || prettify(
|
|
11127
|
+
const name = options.name?.trim() || prettify(path17.basename(sourceDir));
|
|
11702
11128
|
return saveProjectAsTemplate({ projectDir: sourceDir, name, templatesRoot });
|
|
11703
11129
|
}
|
|
11704
11130
|
// src/lib/snippets.ts
|
|
11705
|
-
import { readdir as
|
|
11706
|
-
import
|
|
11131
|
+
import { readdir as readdir7, readFile as readFile31, writeFile as writeFile11, mkdir as mkdir12, rm as rm5 } from "node:fs/promises";
|
|
11132
|
+
import path20 from "node:path";
|
|
11707
11133
|
|
|
11708
11134
|
// src/lib/extension-manager.ts
|
|
11709
|
-
import { cp as cp4, mkdir as mkdir11, readFile as
|
|
11135
|
+
import { cp as cp4, mkdir as mkdir11, readFile as readFile30, rm as rm4, stat as stat7 } from "node:fs/promises";
|
|
11710
11136
|
import { existsSync as existsSync14 } from "node:fs";
|
|
11711
|
-
import
|
|
11137
|
+
import path19 from "node:path";
|
|
11712
11138
|
import { isSeq as isSeq3, isMap as isMap2, isScalar as isScalar2, YAMLMap, Scalar } from "yaml";
|
|
11713
11139
|
|
|
11714
11140
|
// src/lib/npm-plugin-installer.ts
|
|
11715
|
-
import { createHash as
|
|
11716
|
-
import { createReadStream
|
|
11141
|
+
import { createHash as createHash6, randomUUID as randomUUID3, timingSafeEqual } from "node:crypto";
|
|
11142
|
+
import { createReadStream } from "node:fs";
|
|
11717
11143
|
import {
|
|
11718
|
-
lstat
|
|
11144
|
+
lstat,
|
|
11719
11145
|
mkdir as mkdir10,
|
|
11720
11146
|
mkdtemp as mkdtemp2,
|
|
11721
11147
|
open as open2,
|
|
11722
|
-
readFile as
|
|
11723
|
-
realpath
|
|
11148
|
+
readFile as readFile29,
|
|
11149
|
+
realpath,
|
|
11724
11150
|
rename as rename4,
|
|
11725
11151
|
rm as rm3
|
|
11726
11152
|
} from "node:fs/promises";
|
|
11727
|
-
import
|
|
11153
|
+
import path18 from "node:path";
|
|
11728
11154
|
import { Transform } from "node:stream";
|
|
11729
11155
|
import { pipeline } from "node:stream/promises";
|
|
11730
11156
|
import { createGunzip } from "node:zlib";
|
|
11731
|
-
import { maxSatisfying, satisfies
|
|
11157
|
+
import { maxSatisfying, satisfies, validRange } from "semver";
|
|
11732
11158
|
|
|
11733
11159
|
// src/lib/fetch-timeout.ts
|
|
11734
11160
|
class FetchPassthroughError extends Error {
|
|
@@ -11785,11 +11211,11 @@ class PackageUnavailableError extends Error {
|
|
|
11785
11211
|
function isOptionalAvailabilityError(error2) {
|
|
11786
11212
|
return error2 instanceof PackageUnavailableError || error2 instanceof FriendlyHttpError || error2 instanceof FetchUnavailableError;
|
|
11787
11213
|
}
|
|
11788
|
-
function
|
|
11214
|
+
function object2(value) {
|
|
11789
11215
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
11790
11216
|
}
|
|
11791
|
-
function
|
|
11792
|
-
const map =
|
|
11217
|
+
function stringMap(value) {
|
|
11218
|
+
const map = object2(value);
|
|
11793
11219
|
if (!map)
|
|
11794
11220
|
return {};
|
|
11795
11221
|
const result = {};
|
|
@@ -11802,9 +11228,9 @@ function stringMap2(value) {
|
|
|
11802
11228
|
function mb(bytes) {
|
|
11803
11229
|
return `${Math.ceil(bytes / 1024 / 1024)}MB`;
|
|
11804
11230
|
}
|
|
11805
|
-
function
|
|
11806
|
-
const relative2 =
|
|
11807
|
-
return relative2 === "" || !relative2.startsWith("..") && !
|
|
11231
|
+
function isContained(root, candidate) {
|
|
11232
|
+
const relative2 = path18.relative(root, candidate);
|
|
11233
|
+
return relative2 === "" || !relative2.startsWith("..") && !path18.isAbsolute(relative2);
|
|
11808
11234
|
}
|
|
11809
11235
|
function mergeLimits(overrides) {
|
|
11810
11236
|
const limits = { ...DEFAULT_LIMITS, ...overrides };
|
|
@@ -11882,7 +11308,7 @@ async function fetchPackument(ctx, name) {
|
|
|
11882
11308
|
}
|
|
11883
11309
|
let metadata = null;
|
|
11884
11310
|
try {
|
|
11885
|
-
metadata =
|
|
11311
|
+
metadata = object2(JSON.parse(new TextDecoder().decode(bytes)));
|
|
11886
11312
|
} catch {}
|
|
11887
11313
|
if (!metadata || metadata.name !== name) {
|
|
11888
11314
|
throw new FriendlyHttpError(`npm returned invalid metadata for "${name}".`);
|
|
@@ -11938,29 +11364,29 @@ async function selectPackage(ctx, name, selector) {
|
|
|
11938
11364
|
throw new PackageUnavailableError(`Dependency selector "${selector}" for ${name} is not a registry semver range or dist-tag.`);
|
|
11939
11365
|
}
|
|
11940
11366
|
const metadata = await fetchPackument(ctx, name);
|
|
11941
|
-
const versions =
|
|
11942
|
-
const tags =
|
|
11367
|
+
const versions = object2(metadata.versions);
|
|
11368
|
+
const tags = object2(metadata["dist-tags"]);
|
|
11943
11369
|
if (!versions || !tags)
|
|
11944
11370
|
throw new PackageUnavailableError(`npm metadata for "${name}" has no versions.`);
|
|
11945
11371
|
let version;
|
|
11946
11372
|
if (!selector) {
|
|
11947
11373
|
if (typeof tags.latest === "string")
|
|
11948
11374
|
version = tags.latest;
|
|
11949
|
-
} else if (
|
|
11375
|
+
} else if (object2(versions[selector])) {
|
|
11950
11376
|
version = selector;
|
|
11951
11377
|
} else if (typeof tags[selector] === "string") {
|
|
11952
11378
|
version = tags[selector];
|
|
11953
|
-
} else if (
|
|
11379
|
+
} else if (validRange(selector)) {
|
|
11954
11380
|
version = maxSatisfying(Object.keys(versions), selector) ?? undefined;
|
|
11955
11381
|
}
|
|
11956
11382
|
if (!version || !isExactNpmVersion(version)) {
|
|
11957
11383
|
throw new PackageUnavailableError(`No exact npm version of "${name}" satisfies "${selector ?? "latest"}".`);
|
|
11958
11384
|
}
|
|
11959
|
-
const selected =
|
|
11385
|
+
const selected = object2(versions[version]);
|
|
11960
11386
|
if (!selected || selected.name !== name || selected.version !== version) {
|
|
11961
11387
|
throw new SecurityInstallError(`npm metadata identity failed for "${name}@${version}".`);
|
|
11962
11388
|
}
|
|
11963
|
-
const dist =
|
|
11389
|
+
const dist = object2(selected.dist);
|
|
11964
11390
|
if (!dist || typeof dist.tarball !== "string") {
|
|
11965
11391
|
throw new PackageUnavailableError(`npm metadata for "${name}@${version}" has no tarball.`);
|
|
11966
11392
|
}
|
|
@@ -11977,7 +11403,7 @@ async function selectPackage(ctx, name, selector) {
|
|
|
11977
11403
|
}
|
|
11978
11404
|
async function downloadTarball(ctx, pkg, archivePath) {
|
|
11979
11405
|
const file = await open2(archivePath, "wx");
|
|
11980
|
-
const hash =
|
|
11406
|
+
const hash = createHash6(pkg.integrity.algorithm);
|
|
11981
11407
|
try {
|
|
11982
11408
|
await withFetchTimeout({
|
|
11983
11409
|
timeoutMs: TARBALL_TIMEOUT_MS,
|
|
@@ -12099,7 +11525,7 @@ async function extractTarball(ctx, archivePath, packageDir, packageName) {
|
|
|
12099
11525
|
}
|
|
12100
11526
|
});
|
|
12101
11527
|
try {
|
|
12102
|
-
await pipeline(
|
|
11528
|
+
await pipeline(createReadStream(archivePath), createGunzip(), limiter, unpack);
|
|
12103
11529
|
} catch (error2) {
|
|
12104
11530
|
if (rejected)
|
|
12105
11531
|
throw rejected;
|
|
@@ -12111,13 +11537,13 @@ async function extractTarball(ctx, archivePath, packageDir, packageName) {
|
|
|
12111
11537
|
throw rejected;
|
|
12112
11538
|
}
|
|
12113
11539
|
async function readInstalledManifest(packageDir, selected) {
|
|
12114
|
-
const packageJsonPath =
|
|
11540
|
+
const packageJsonPath = path18.join(packageDir, "package.json");
|
|
12115
11541
|
let manifest = null;
|
|
12116
11542
|
try {
|
|
12117
|
-
const info2 = await
|
|
11543
|
+
const info2 = await lstat(packageJsonPath);
|
|
12118
11544
|
if (!info2.isFile() || info2.isSymbolicLink())
|
|
12119
11545
|
throw new Error("not a regular file");
|
|
12120
|
-
manifest =
|
|
11546
|
+
manifest = object2(JSON.parse(await readFile29(packageJsonPath, "utf8")));
|
|
12121
11547
|
} catch (error2) {
|
|
12122
11548
|
throw new SecurityInstallError(`Downloaded ${selected.name}@${selected.version} has no valid package.json (${error2 instanceof Error ? error2.message : String(error2)}).`);
|
|
12123
11549
|
}
|
|
@@ -12180,17 +11606,17 @@ async function installPackage(ctx, name, selector, parentPackagePath, ancestors,
|
|
|
12180
11606
|
}
|
|
12181
11607
|
throw error2;
|
|
12182
11608
|
}
|
|
12183
|
-
const ancestor = ancestors.find((candidate) => candidate.name === name && (candidate.version === selected.version || (selector ?
|
|
11609
|
+
const ancestor = ancestors.find((candidate) => candidate.name === name && (candidate.version === selected.version || (selector ? satisfies(candidate.version, selector) : false)));
|
|
12184
11610
|
if (ancestor)
|
|
12185
11611
|
return { path: ancestor.path };
|
|
12186
11612
|
if (ctx.records.length >= ctx.limits.totalPackages) {
|
|
12187
11613
|
throw new SecurityInstallError(`Dependency graph exceeds ${ctx.limits.totalPackages} packages.`);
|
|
12188
11614
|
}
|
|
12189
11615
|
const relativePath = relativePackagePath(parentPackagePath, name);
|
|
12190
|
-
const packageDir =
|
|
12191
|
-
const archivePath =
|
|
11616
|
+
const packageDir = path18.join(ctx.stageRoot, ...relativePath.split("/"));
|
|
11617
|
+
const archivePath = path18.join(ctx.downloadsDir, `${ctx.archiveCounter++}.tgz`);
|
|
12192
11618
|
try {
|
|
12193
|
-
await mkdir10(
|
|
11619
|
+
await mkdir10(path18.dirname(packageDir), { recursive: true });
|
|
12194
11620
|
await downloadTarball(ctx, selected, archivePath);
|
|
12195
11621
|
await extractTarball(ctx, archivePath, packageDir, selected.name);
|
|
12196
11622
|
const manifest = await readInstalledManifest(packageDir, selected);
|
|
@@ -12224,19 +11650,19 @@ async function installPackage(ctx, name, selector, parentPackagePath, ancestors,
|
|
|
12224
11650
|
if (selected.integrity.legacySha1) {
|
|
12225
11651
|
ctx.warnings.add(`${name}@${selected.version} is an old npm release with only SHA-1 registry integrity. It was accepted for compatibility and recorded as legacy SHA-1 in the vendor receipt.`);
|
|
12226
11652
|
}
|
|
12227
|
-
const dependencies =
|
|
12228
|
-
const optionalDependencies =
|
|
11653
|
+
const dependencies = stringMap(manifest.dependencies);
|
|
11654
|
+
const optionalDependencies = stringMap(manifest.optionalDependencies);
|
|
12229
11655
|
for (const optionalName of Object.keys(optionalDependencies))
|
|
12230
11656
|
delete dependencies[optionalName];
|
|
12231
|
-
const peerDependencies =
|
|
12232
|
-
const peerMeta =
|
|
11657
|
+
const peerDependencies = stringMap(manifest.peerDependencies);
|
|
11658
|
+
const peerMeta = object2(manifest.peerDependenciesMeta) ?? {};
|
|
12233
11659
|
for (const existing of [...Object.keys(dependencies), ...Object.keys(optionalDependencies)]) {
|
|
12234
11660
|
delete peerDependencies[existing];
|
|
12235
11661
|
}
|
|
12236
11662
|
const nextAncestors = [...ancestors, record];
|
|
12237
11663
|
const requiredEntries = [
|
|
12238
11664
|
...Object.entries(dependencies).map(([dependency, range]) => ({ dependency, range, kind: "required" })),
|
|
12239
|
-
...Object.entries(peerDependencies).filter(([dependency]) =>
|
|
11665
|
+
...Object.entries(peerDependencies).filter(([dependency]) => object2(peerMeta[dependency])?.optional !== true).map(([dependency, range]) => ({ dependency, range, kind: "peer" }))
|
|
12240
11666
|
].sort((a, b) => a.dependency.localeCompare(b.dependency));
|
|
12241
11667
|
for (const { dependency, range } of requiredEntries) {
|
|
12242
11668
|
const child = await installPackage(ctx, dependency, range, relativePath, nextAncestors, depth + 1, false);
|
|
@@ -12254,7 +11680,7 @@ async function installPackage(ctx, name, selector, parentPackagePath, ancestors,
|
|
|
12254
11680
|
}
|
|
12255
11681
|
}
|
|
12256
11682
|
for (const [dependency, range] of Object.entries(peerDependencies)) {
|
|
12257
|
-
if (
|
|
11683
|
+
if (object2(peerMeta[dependency])?.optional === true) {
|
|
12258
11684
|
ctx.skipped.push({
|
|
12259
11685
|
from: relativePath,
|
|
12260
11686
|
name: dependency,
|
|
@@ -12278,7 +11704,7 @@ async function installPackage(ctx, name, selector, parentPackagePath, ancestors,
|
|
|
12278
11704
|
}
|
|
12279
11705
|
async function ensureDirectory(pathname) {
|
|
12280
11706
|
try {
|
|
12281
|
-
const info2 = await
|
|
11707
|
+
const info2 = await lstat(pathname);
|
|
12282
11708
|
if (info2.isSymbolicLink() || !info2.isDirectory()) {
|
|
12283
11709
|
throw new Error(`Plugin install path is not a normal directory: ${pathname}`);
|
|
12284
11710
|
}
|
|
@@ -12289,18 +11715,18 @@ async function ensureDirectory(pathname) {
|
|
|
12289
11715
|
}
|
|
12290
11716
|
}
|
|
12291
11717
|
async function prepareVendorParent(projectDir, name) {
|
|
12292
|
-
const plugins =
|
|
12293
|
-
const npm =
|
|
12294
|
-
const packageParent =
|
|
11718
|
+
const plugins = path18.join(projectDir, PLUGINS_DIR);
|
|
11719
|
+
const npm = path18.join(plugins, VENDORED_NPM_DIR);
|
|
11720
|
+
const packageParent = path18.dirname(vendoredNpmPluginRoot(projectDir, name, "0.0.0"));
|
|
12295
11721
|
await mkdir10(projectDir, { recursive: true });
|
|
12296
11722
|
await ensureDirectory(plugins);
|
|
12297
11723
|
await ensureDirectory(npm);
|
|
12298
11724
|
await ensureDirectory(packageParent);
|
|
12299
11725
|
const [realNpm, realPackageParent] = await Promise.all([
|
|
12300
|
-
|
|
12301
|
-
|
|
11726
|
+
realpath(npm),
|
|
11727
|
+
realpath(packageParent)
|
|
12302
11728
|
]);
|
|
12303
|
-
if (!
|
|
11729
|
+
if (!isContained(projectDir, realNpm) || !isContained(projectDir, realPackageParent)) {
|
|
12304
11730
|
throw new Error("Plugin install path resolves outside the book folder.");
|
|
12305
11731
|
}
|
|
12306
11732
|
return realNpm;
|
|
@@ -12319,14 +11745,14 @@ function publicReceipt(record) {
|
|
|
12319
11745
|
};
|
|
12320
11746
|
}
|
|
12321
11747
|
async function installNpmPlugin(projectDirInput, packageSpec, options = {}) {
|
|
12322
|
-
const requestedProjectDir =
|
|
11748
|
+
const requestedProjectDir = path18.resolve(projectDirInput);
|
|
12323
11749
|
await mkdir10(requestedProjectDir, { recursive: true });
|
|
12324
|
-
const projectDir = await
|
|
11750
|
+
const projectDir = await realpath(requestedProjectDir);
|
|
12325
11751
|
const requested = parseNpmPluginSpec(packageSpec);
|
|
12326
11752
|
const npmRoot = await prepareVendorParent(projectDir, requested.name);
|
|
12327
|
-
const container = await mkdtemp2(
|
|
12328
|
-
const stageRoot =
|
|
12329
|
-
const downloadsDir =
|
|
11753
|
+
const container = await mkdtemp2(path18.join(npmRoot, ".install-"));
|
|
11754
|
+
const stageRoot = path18.join(container, "root");
|
|
11755
|
+
const downloadsDir = path18.join(container, "downloads");
|
|
12330
11756
|
await Promise.all([mkdir10(stageRoot), mkdir10(downloadsDir)]);
|
|
12331
11757
|
const ctx = {
|
|
12332
11758
|
fetch: options.fetch ?? globalThis.fetch,
|
|
@@ -12372,7 +11798,7 @@ async function installNpmPlugin(projectDirInput, packageSpec, options = {}) {
|
|
|
12372
11798
|
skipped: ctx.skipped,
|
|
12373
11799
|
tree: { algorithm: "sha256", ...tree }
|
|
12374
11800
|
};
|
|
12375
|
-
const receiptFile = await open2(
|
|
11801
|
+
const receiptFile = await open2(path18.join(stageRoot, VENDOR_RECEIPT_FILE), "wx");
|
|
12376
11802
|
try {
|
|
12377
11803
|
await receiptFile.writeFile(`${JSON.stringify(receipt, null, 2)}
|
|
12378
11804
|
`, "utf8");
|
|
@@ -12381,12 +11807,12 @@ async function installNpmPlugin(projectDirInput, packageSpec, options = {}) {
|
|
|
12381
11807
|
await receiptFile.close();
|
|
12382
11808
|
}
|
|
12383
11809
|
finalRoot = vendoredNpmPluginRoot(projectDir, rootRecord.name, rootRecord.version);
|
|
12384
|
-
const realFinalParent = await
|
|
12385
|
-
if (!
|
|
11810
|
+
const realFinalParent = await realpath(path18.dirname(finalRoot));
|
|
11811
|
+
if (!isContained(projectDir, realFinalParent)) {
|
|
12386
11812
|
throw new Error("Plugin install destination resolves outside the book folder.");
|
|
12387
11813
|
}
|
|
12388
11814
|
try {
|
|
12389
|
-
await
|
|
11815
|
+
await lstat(finalRoot);
|
|
12390
11816
|
backupRoot = `${finalRoot}.backup-${randomUUID3()}`;
|
|
12391
11817
|
await rename4(finalRoot, backupRoot);
|
|
12392
11818
|
} catch (error2) {
|
|
@@ -12459,7 +11885,7 @@ var RECOMMENDED_EXTENSIONS = [
|
|
|
12459
11885
|
var BUILT_IN_STYLE_SET_IDS = ["clean-book", "zine", "technical-doc"];
|
|
12460
11886
|
var mutationQueues = new Map;
|
|
12461
11887
|
function withMutationLock(projectDir, mutation) {
|
|
12462
|
-
const resolved =
|
|
11888
|
+
const resolved = path19.resolve(projectDir);
|
|
12463
11889
|
const key = process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
12464
11890
|
const previous = mutationQueues.get(key) ?? Promise.resolve();
|
|
12465
11891
|
const run2 = previous.then(mutation, mutation);
|
|
@@ -12521,7 +11947,7 @@ function indexOfUse(seq, use) {
|
|
|
12521
11947
|
});
|
|
12522
11948
|
}
|
|
12523
11949
|
function manifestPathFor(projectDir, abs) {
|
|
12524
|
-
const rel =
|
|
11950
|
+
const rel = path19.relative(path19.resolve(projectDir), abs).split(path19.sep).join("/");
|
|
12525
11951
|
if (rel === "") {
|
|
12526
11952
|
throw new Error("An extension cannot be the book folder itself.");
|
|
12527
11953
|
}
|
|
@@ -12580,21 +12006,21 @@ async function describeEntry(projectDir, raw) {
|
|
|
12580
12006
|
});
|
|
12581
12007
|
}
|
|
12582
12008
|
if (parsed.kind === "path") {
|
|
12583
|
-
const abs =
|
|
12009
|
+
const abs = path19.resolve(projectDir, parsed.path);
|
|
12584
12010
|
const base2 = { ...common, kind: "path", name: parsed.path };
|
|
12585
12011
|
let info2;
|
|
12586
12012
|
try {
|
|
12587
12013
|
info2 = await stat7(abs);
|
|
12588
12014
|
} catch {
|
|
12589
12015
|
warnings.push(`Not found: ${abs}`);
|
|
12590
|
-
return withWarnings({ ...base2, label:
|
|
12016
|
+
return withWarnings({ ...base2, label: path19.basename(abs), carries: NO_CARRIES });
|
|
12591
12017
|
}
|
|
12592
12018
|
if (info2.isDirectory()) {
|
|
12593
|
-
return withWarnings(await describeFolder(abs, base2, prettify(
|
|
12019
|
+
return withWarnings(await describeFolder(abs, base2, prettify(path19.basename(abs))));
|
|
12594
12020
|
}
|
|
12595
12021
|
return withWarnings({
|
|
12596
12022
|
...base2,
|
|
12597
|
-
label:
|
|
12023
|
+
label: path19.basename(abs),
|
|
12598
12024
|
carries: { ...NO_CARRIES, markdown: true }
|
|
12599
12025
|
});
|
|
12600
12026
|
}
|
|
@@ -12619,7 +12045,7 @@ async function describeEntry(projectDir, raw) {
|
|
|
12619
12045
|
return withWarnings({ ...base, label: parsed.name, carries: { ...NO_CARRIES, markdown: true } });
|
|
12620
12046
|
}
|
|
12621
12047
|
const pkgDir = vendoredNpmPluginPackageDir(installRoot, parsed.name);
|
|
12622
|
-
if (!existsSync14(
|
|
12048
|
+
if (!existsSync14(path19.join(pkgDir, EXTENSION_MANIFEST_FILENAME))) {
|
|
12623
12049
|
return withWarnings({ ...base, label: parsed.name, carries: { ...NO_CARRIES, markdown: true } });
|
|
12624
12050
|
}
|
|
12625
12051
|
const described = await describeFolder(pkgDir, base, parsed.name, (name) => name);
|
|
@@ -12684,7 +12110,7 @@ async function reorderExtensions(projectDir, order) {
|
|
|
12684
12110
|
const { doc, file } = await loadManifestDoc(projectDir);
|
|
12685
12111
|
const seq = doc.get("extensions", true);
|
|
12686
12112
|
if (!isSeq3(seq))
|
|
12687
|
-
throw new Error(`No extensions are configured in ${
|
|
12113
|
+
throw new Error(`No extensions are configured in ${path19.basename(file)}.`);
|
|
12688
12114
|
const items = seq.items;
|
|
12689
12115
|
const current = items.map((item) => entryUse(item));
|
|
12690
12116
|
const wanted = order.map((u) => u.trim());
|
|
@@ -12778,11 +12204,11 @@ async function addExtension(projectDir, specifier, options = {}) {
|
|
|
12778
12204
|
async function addExtensionUnlocked(projectDir, specifier, options) {
|
|
12779
12205
|
const input = specifier.trim();
|
|
12780
12206
|
const exportName = options.exportName?.trim() || undefined;
|
|
12781
|
-
if (
|
|
12207
|
+
if (path19.isAbsolute(input))
|
|
12782
12208
|
return addPathExtension(projectDir, input, exportName);
|
|
12783
12209
|
const parsed = parseExtensionSpecifier(input);
|
|
12784
12210
|
if (parsed.kind === "path") {
|
|
12785
|
-
return addPathExtension(projectDir,
|
|
12211
|
+
return addPathExtension(projectDir, path19.resolve(projectDir, parsed.path), exportName);
|
|
12786
12212
|
}
|
|
12787
12213
|
if (parsed.kind === "bundled") {
|
|
12788
12214
|
if (exportName)
|
|
@@ -12824,7 +12250,7 @@ async function listBuiltInStyleSets() {
|
|
|
12824
12250
|
const assets = await getAssetsDir();
|
|
12825
12251
|
const out = [];
|
|
12826
12252
|
for (const id of BUILT_IN_STYLE_SET_IDS) {
|
|
12827
|
-
const meta = await readExtensionMeta(
|
|
12253
|
+
const meta = await readExtensionMeta(path19.join(assets, "themes", id));
|
|
12828
12254
|
out.push({
|
|
12829
12255
|
id,
|
|
12830
12256
|
name: prettify(meta.name?.trim() || id),
|
|
@@ -12836,7 +12262,7 @@ async function listBuiltInStyleSets() {
|
|
|
12836
12262
|
async function uniqueExtensionId(projectDir, base) {
|
|
12837
12263
|
const slug = slugify(base) || "extension";
|
|
12838
12264
|
let candidate = slug;
|
|
12839
|
-
for (let n = 2;existsSync14(
|
|
12265
|
+
for (let n = 2;existsSync14(path19.join(projectDir, EXTENSIONS_DIR, candidate)); n++) {
|
|
12840
12266
|
candidate = `${slug}-${n}`;
|
|
12841
12267
|
}
|
|
12842
12268
|
return candidate;
|
|
@@ -12846,10 +12272,10 @@ async function addBuiltInStyleSet(projectDir, id) {
|
|
|
12846
12272
|
throw new Error(`Unknown built-in look "${id}" (one of: ${BUILT_IN_STYLE_SET_IDS.join(", ")}).`);
|
|
12847
12273
|
}
|
|
12848
12274
|
return withMutationLock(projectDir, async () => {
|
|
12849
|
-
const dest =
|
|
12275
|
+
const dest = path19.join(projectDir, EXTENSIONS_DIR, id);
|
|
12850
12276
|
if (!existsSync14(dest)) {
|
|
12851
|
-
const src =
|
|
12852
|
-
await mkdir11(
|
|
12277
|
+
const src = path19.join(await getAssetsDir(), "themes", id);
|
|
12278
|
+
await mkdir11(path19.dirname(dest), { recursive: true });
|
|
12853
12279
|
await cp4(src, dest, { recursive: true });
|
|
12854
12280
|
}
|
|
12855
12281
|
return addExtensionUnlocked(projectDir, `./${EXTENSIONS_DIR}/${id}`, {});
|
|
@@ -12889,7 +12315,7 @@ async function readExtensionCss(projectDir, use) {
|
|
|
12889
12315
|
throw new Error(`Extension "${use}" declares no stylesheets.`);
|
|
12890
12316
|
const parts = [];
|
|
12891
12317
|
for (const sheet of sheets)
|
|
12892
|
-
parts.push(await
|
|
12318
|
+
parts.push(await readFile30(sheet, "utf8"));
|
|
12893
12319
|
return parts.join(`
|
|
12894
12320
|
|
|
12895
12321
|
`);
|
|
@@ -12914,19 +12340,19 @@ function substituteVariables(template, values) {
|
|
|
12914
12340
|
return template.replace(PLACEHOLDER_RE, (_full, name) => Object.prototype.hasOwnProperty.call(values, name) ? values[name] : "");
|
|
12915
12341
|
}
|
|
12916
12342
|
function resolveSafeChildFile(dir, fileName) {
|
|
12917
|
-
const full =
|
|
12918
|
-
if (full !==
|
|
12343
|
+
const full = path20.resolve(dir, fileName);
|
|
12344
|
+
if (full !== path20.join(dir, path20.basename(fileName)) || path20.dirname(full) !== dir) {
|
|
12919
12345
|
throw new Error(`Unsafe snippet filename: ${fileName}`);
|
|
12920
12346
|
}
|
|
12921
12347
|
return full;
|
|
12922
12348
|
}
|
|
12923
12349
|
function resolveSnippetPath(projectDir, fileName) {
|
|
12924
|
-
return resolveSafeChildFile(
|
|
12350
|
+
return resolveSafeChildFile(path20.resolve(projectDir, SNIPPETS_DIR), fileName);
|
|
12925
12351
|
}
|
|
12926
12352
|
async function scanSnippetFiles(dir) {
|
|
12927
12353
|
let names;
|
|
12928
12354
|
try {
|
|
12929
|
-
names = await
|
|
12355
|
+
names = await readdir7(dir);
|
|
12930
12356
|
} catch {
|
|
12931
12357
|
return [];
|
|
12932
12358
|
}
|
|
@@ -12936,7 +12362,7 @@ async function scanSnippetFiles(dir) {
|
|
|
12936
12362
|
continue;
|
|
12937
12363
|
let body = "";
|
|
12938
12364
|
try {
|
|
12939
|
-
body = await
|
|
12365
|
+
body = await readFile31(path20.join(dir, fileName), "utf8");
|
|
12940
12366
|
} catch {
|
|
12941
12367
|
continue;
|
|
12942
12368
|
}
|
|
@@ -12950,18 +12376,18 @@ async function scanSnippetFiles(dir) {
|
|
|
12950
12376
|
return entries;
|
|
12951
12377
|
}
|
|
12952
12378
|
async function listSnippets(projectDir) {
|
|
12953
|
-
const files = await scanSnippetFiles(
|
|
12379
|
+
const files = await scanSnippetFiles(path20.join(projectDir, SNIPPETS_DIR));
|
|
12954
12380
|
return files.map((file) => ({ ...file, source: { kind: "project" } }));
|
|
12955
12381
|
}
|
|
12956
12382
|
async function readSnippet(projectDir, fileName) {
|
|
12957
|
-
return
|
|
12383
|
+
return readFile31(resolveSnippetPath(projectDir, fileName), "utf8");
|
|
12958
12384
|
}
|
|
12959
12385
|
async function saveSnippet(projectDir, name, body) {
|
|
12960
12386
|
const stem = slugify(name);
|
|
12961
12387
|
if (!stem)
|
|
12962
12388
|
throw new Error(`Could not derive a filename from "${name}".`);
|
|
12963
12389
|
const fileName = `${stem}.md`;
|
|
12964
|
-
const dir =
|
|
12390
|
+
const dir = path20.join(projectDir, SNIPPETS_DIR);
|
|
12965
12391
|
await mkdir12(dir, { recursive: true });
|
|
12966
12392
|
await writeFile11(resolveSnippetPath(projectDir, fileName), body, "utf8");
|
|
12967
12393
|
return { name, fileName, variables: extractVariables(body), source: { kind: "project" } };
|
|
@@ -12985,7 +12411,7 @@ async function listInstalledExtensions(projectDir) {
|
|
|
12985
12411
|
function extensionSnippetsDir(ext) {
|
|
12986
12412
|
if (pathEscapesFolder(ext.snippetsRel))
|
|
12987
12413
|
return null;
|
|
12988
|
-
return
|
|
12414
|
+
return path20.join(ext.dir, ext.snippetsRel);
|
|
12989
12415
|
}
|
|
12990
12416
|
async function listMergedSnippets(projectDir) {
|
|
12991
12417
|
const project = await listSnippets(projectDir);
|
|
@@ -13013,13 +12439,13 @@ async function readExtensionSnippet(projectDir, source, fileName) {
|
|
|
13013
12439
|
if (!dir) {
|
|
13014
12440
|
throw new Error(`Extension "${source.ref}" does not declare a usable snippets folder.`);
|
|
13015
12441
|
}
|
|
13016
|
-
return
|
|
12442
|
+
return readFile31(resolveSafeChildFile(dir, fileName), "utf8");
|
|
13017
12443
|
}
|
|
13018
12444
|
// src/lib/extension-import.ts
|
|
13019
12445
|
import { existsSync as existsSync15 } from "node:fs";
|
|
13020
|
-
import { cp as cp5, mkdir as mkdir13, mkdtemp as mkdtemp3, readFile as
|
|
12446
|
+
import { cp as cp5, mkdir as mkdir13, mkdtemp as mkdtemp3, readFile as readFile32, rm as rm6, writeFile as writeFile12 } from "node:fs/promises";
|
|
13021
12447
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
13022
|
-
import
|
|
12448
|
+
import path21 from "node:path";
|
|
13023
12449
|
import { unzipSync } from "fflate";
|
|
13024
12450
|
var MAX_THEME_ARCHIVE_BYTES = 25 * 1024 * 1024;
|
|
13025
12451
|
var MAX_THEME_UNZIPPED_BYTES = 25 * 1024 * 1024;
|
|
@@ -13086,11 +12512,11 @@ function mb2(n) {
|
|
|
13086
12512
|
return `${Math.round(n / (1024 * 1024))}MB`;
|
|
13087
12513
|
}
|
|
13088
12514
|
async function finalizeExtensionImport(projectDir, sourceDir, relPaths, fallbackName) {
|
|
13089
|
-
const cssPath =
|
|
12515
|
+
const cssPath = path21.join(sourceDir, "theme.css");
|
|
13090
12516
|
if (!existsSync15(cssPath)) {
|
|
13091
12517
|
throw new Error("A theme must contain a theme.css file.");
|
|
13092
12518
|
}
|
|
13093
|
-
const css = await
|
|
12519
|
+
const css = await readFile32(cssPath, "utf8");
|
|
13094
12520
|
const { reject, warnings: printFindings } = classifyThemeCssFindings(checkCss(css, "theme.css"));
|
|
13095
12521
|
if (reject) {
|
|
13096
12522
|
throw new Error(`theme.css could not be parsed — ${reject.message}`);
|
|
@@ -13099,7 +12525,7 @@ async function finalizeExtensionImport(projectDir, sourceDir, relPaths, fallback
|
|
|
13099
12525
|
for (const w of printFindings) {
|
|
13100
12526
|
warnings.push({ code: "print-safety", message: w.message });
|
|
13101
12527
|
}
|
|
13102
|
-
const metaExists = existsSync15(
|
|
12528
|
+
const metaExists = existsSync15(path21.join(sourceDir, EXTENSION_MANIFEST_FILENAME));
|
|
13103
12529
|
const meta = metaExists ? await readExtensionMeta(sourceDir) : {};
|
|
13104
12530
|
if (!metaExists) {
|
|
13105
12531
|
warnings.push({
|
|
@@ -13118,7 +12544,7 @@ async function finalizeExtensionImport(projectDir, sourceDir, relPaths, fallback
|
|
|
13118
12544
|
for (let i = 0;i < extraSheets.length; i++) {
|
|
13119
12545
|
const rel = extraSheets[i];
|
|
13120
12546
|
const sheetPath = extraSheetPaths[i];
|
|
13121
|
-
const sheetCss = await
|
|
12547
|
+
const sheetCss = await readFile32(sheetPath, "utf8");
|
|
13122
12548
|
const { reject: sheetReject, warnings: sheetFindings } = classifyThemeCssFindings(checkCss(sheetCss, rel));
|
|
13123
12549
|
if (sheetReject) {
|
|
13124
12550
|
throw new Error(`${rel} could not be parsed — ${sheetReject.message}`);
|
|
@@ -13137,8 +12563,8 @@ async function finalizeExtensionImport(projectDir, sourceDir, relPaths, fallback
|
|
|
13137
12563
|
});
|
|
13138
12564
|
}
|
|
13139
12565
|
const id = await uniqueExtensionId(projectDir, meta.name?.trim() || fallbackName);
|
|
13140
|
-
const dest =
|
|
13141
|
-
await mkdir13(
|
|
12566
|
+
const dest = path21.join(projectDir, EXTENSIONS_DIR, id);
|
|
12567
|
+
await mkdir13(path21.dirname(dest), { recursive: true });
|
|
13142
12568
|
await cp5(sourceDir, dest, { recursive: true });
|
|
13143
12569
|
await completeLandedManifest(dest, id, meta, metaExists);
|
|
13144
12570
|
const entry = await addExtension(projectDir, `./${EXTENSIONS_DIR}/${id}`);
|
|
@@ -13147,11 +12573,11 @@ async function finalizeExtensionImport(projectDir, sourceDir, relPaths, fallback
|
|
|
13147
12573
|
async function completeLandedManifest(dest, id, meta, metaExists) {
|
|
13148
12574
|
if (metaExists && extensionStyleList(meta).length > 0)
|
|
13149
12575
|
return;
|
|
13150
|
-
const file =
|
|
12576
|
+
const file = path21.join(dest, EXTENSION_MANIFEST_FILENAME);
|
|
13151
12577
|
let pkg = {};
|
|
13152
12578
|
if (metaExists) {
|
|
13153
12579
|
try {
|
|
13154
|
-
const parsed = JSON.parse(await
|
|
12580
|
+
const parsed = JSON.parse(await readFile32(file, "utf8"));
|
|
13155
12581
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
13156
12582
|
pkg = parsed;
|
|
13157
12583
|
}
|
|
@@ -13205,7 +12631,7 @@ async function importExtensionFromZip(projectDir, archive) {
|
|
|
13205
12631
|
throw new Error("No theme.css found in the package. A theme needs a theme.css at the package root or inside a single folder.");
|
|
13206
12632
|
}
|
|
13207
12633
|
const prefix = root === "" ? "" : `${root}/`;
|
|
13208
|
-
const tmp = await mkdtemp3(
|
|
12634
|
+
const tmp = await mkdtemp3(path21.join(tmpdir3(), "gutterpress-theme-"));
|
|
13209
12635
|
try {
|
|
13210
12636
|
const relPaths = [];
|
|
13211
12637
|
for (const [name, data] of Object.entries(files)) {
|
|
@@ -13217,12 +12643,12 @@ async function importExtensionFromZip(projectDir, archive) {
|
|
|
13217
12643
|
const rel = prefix ? norm.slice(prefix.length) : norm;
|
|
13218
12644
|
if (!rel || rel.split("/").some((seg) => seg === ".."))
|
|
13219
12645
|
continue;
|
|
13220
|
-
const dest =
|
|
13221
|
-
if (dest !== tmp && !dest.startsWith(tmp +
|
|
12646
|
+
const dest = path21.join(tmp, rel);
|
|
12647
|
+
if (dest !== tmp && !dest.startsWith(tmp + path21.sep)) {
|
|
13222
12648
|
throw new Error(`The package contains an unsafe path and was rejected: ${name}`);
|
|
13223
12649
|
}
|
|
13224
12650
|
relPaths.push(rel);
|
|
13225
|
-
await mkdir13(
|
|
12651
|
+
await mkdir13(path21.dirname(dest), { recursive: true });
|
|
13226
12652
|
await writeFile12(dest, data);
|
|
13227
12653
|
}
|
|
13228
12654
|
return await finalizeExtensionImport(projectDir, tmp, relPaths, root || "imported-look");
|
|
@@ -13236,10 +12662,10 @@ async function importExtensionFromCssText(projectDir, css, name) {
|
|
|
13236
12662
|
throw new Error(`The CSS could not be parsed — ${reject.message}`);
|
|
13237
12663
|
}
|
|
13238
12664
|
const displayName = name.trim() || "Imported theme";
|
|
13239
|
-
const tmp = await mkdtemp3(
|
|
12665
|
+
const tmp = await mkdtemp3(path21.join(tmpdir3(), "gutterpress-theme-"));
|
|
13240
12666
|
try {
|
|
13241
|
-
await writeFile12(
|
|
13242
|
-
await writeFile12(
|
|
12667
|
+
await writeFile12(path21.join(tmp, "theme.css"), css, "utf8");
|
|
12668
|
+
await writeFile12(path21.join(tmp, EXTENSION_MANIFEST_FILENAME), JSON.stringify({ name: slugify(displayName, "look"), gutterpress: { styles: ["theme.css"] } }, null, 2), "utf8");
|
|
13243
12669
|
const result = await finalizeExtensionImport(projectDir, tmp, ["theme.css", EXTENSION_MANIFEST_FILENAME], displayName);
|
|
13244
12670
|
result.warnings.unshift({
|
|
13245
12671
|
code: "no-theme-json",
|
|
@@ -13251,14 +12677,14 @@ async function importExtensionFromCssText(projectDir, css, name) {
|
|
|
13251
12677
|
}
|
|
13252
12678
|
}
|
|
13253
12679
|
async function importExtensionFromFile(projectDir, filePath) {
|
|
13254
|
-
const ext =
|
|
12680
|
+
const ext = path21.extname(filePath).toLowerCase();
|
|
13255
12681
|
if (ext === ".zip") {
|
|
13256
|
-
const buf = await
|
|
12682
|
+
const buf = await readFile32(filePath);
|
|
13257
12683
|
return importExtensionFromZip(projectDir, new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength));
|
|
13258
12684
|
}
|
|
13259
12685
|
if (ext === ".css") {
|
|
13260
|
-
const css = await
|
|
13261
|
-
return importExtensionFromCssText(projectDir, css, prettify(
|
|
12686
|
+
const css = await readFile32(filePath, "utf8");
|
|
12687
|
+
return importExtensionFromCssText(projectDir, css, prettify(path21.basename(filePath, ".css")));
|
|
13262
12688
|
}
|
|
13263
12689
|
throw new Error("Choose a .zip extension package or a .css stylesheet.");
|
|
13264
12690
|
}
|
|
@@ -13306,7 +12732,7 @@ async function importExtensionFromUrl(projectDir, url) {
|
|
|
13306
12732
|
let baseName;
|
|
13307
12733
|
if (looksLikeCssUrl(trimmed)) {
|
|
13308
12734
|
css = await fetchText(trimmed);
|
|
13309
|
-
baseName =
|
|
12735
|
+
baseName = path21.basename(new URL(trimmed, "https://x/").pathname, ".css") || "look";
|
|
13310
12736
|
} else {
|
|
13311
12737
|
const base = trimmed.endsWith("/") ? trimmed : `${trimmed}/`;
|
|
13312
12738
|
css = await fetchText(`${base}theme.css`);
|
|
@@ -13315,14 +12741,14 @@ async function importExtensionFromUrl(projectDir, url) {
|
|
|
13315
12741
|
if (parsed && typeof parsed === "object")
|
|
13316
12742
|
meta = parsed;
|
|
13317
12743
|
} catch {}
|
|
13318
|
-
baseName = typeof meta.name === "string" && meta.name ||
|
|
12744
|
+
baseName = typeof meta.name === "string" && meta.name || path21.basename(new URL(base, "https://x/").pathname.replace(/\/$/, "")) || "look";
|
|
13319
12745
|
}
|
|
13320
12746
|
if (!css.trim())
|
|
13321
12747
|
throw new Error(`Fetched CSS from ${trimmed} was empty.`);
|
|
13322
12748
|
assertLooksLikeCss(css, trimmed);
|
|
13323
|
-
const tmp = await mkdtemp3(
|
|
12749
|
+
const tmp = await mkdtemp3(path21.join(tmpdir3(), "gutterpress-ext-"));
|
|
13324
12750
|
try {
|
|
13325
|
-
await writeFile12(
|
|
12751
|
+
await writeFile12(path21.join(tmp, "theme.css"), css, "utf8");
|
|
13326
12752
|
const gp = meta.gutterpress && typeof meta.gutterpress === "object" && !Array.isArray(meta.gutterpress) ? meta.gutterpress : {};
|
|
13327
12753
|
const finalMeta = {
|
|
13328
12754
|
name: typeof meta.name === "string" && meta.name || slugify(baseName, "look"),
|
|
@@ -13334,7 +12760,7 @@ async function importExtensionFromUrl(projectDir, url) {
|
|
|
13334
12760
|
preview: typeof gp.preview === "string" ? gp.preview : null
|
|
13335
12761
|
}
|
|
13336
12762
|
};
|
|
13337
|
-
await writeFile12(
|
|
12763
|
+
await writeFile12(path21.join(tmp, EXTENSION_MANIFEST_FILENAME), JSON.stringify(finalMeta, null, 2), "utf8");
|
|
13338
12764
|
return await finalizeExtensionImport(projectDir, tmp, ["theme.css", EXTENSION_MANIFEST_FILENAME], baseName);
|
|
13339
12765
|
} finally {
|
|
13340
12766
|
await rm6(tmp, { recursive: true, force: true });
|
|
@@ -13351,7 +12777,7 @@ var KIND_FOR_KEYWORD = {
|
|
|
13351
12777
|
function invalid(reason) {
|
|
13352
12778
|
throw new Error(`Malformed npm search response: ${reason}.`);
|
|
13353
12779
|
}
|
|
13354
|
-
function
|
|
12780
|
+
function object3(value) {
|
|
13355
12781
|
return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
13356
12782
|
}
|
|
13357
12783
|
function str2(value) {
|
|
@@ -13364,7 +12790,7 @@ function parseSearchPage(text, keyword) {
|
|
|
13364
12790
|
} catch {
|
|
13365
12791
|
invalid("not valid JSON");
|
|
13366
12792
|
}
|
|
13367
|
-
const root =
|
|
12793
|
+
const root = object3(parsed);
|
|
13368
12794
|
if (!root)
|
|
13369
12795
|
invalid("root is not an object");
|
|
13370
12796
|
if (!Array.isArray(root.objects))
|
|
@@ -13374,10 +12800,10 @@ function parseSearchPage(text, keyword) {
|
|
|
13374
12800
|
const packages = [];
|
|
13375
12801
|
const scores = [];
|
|
13376
12802
|
root.objects.forEach((raw, i) => {
|
|
13377
|
-
const entry =
|
|
12803
|
+
const entry = object3(raw);
|
|
13378
12804
|
if (!entry)
|
|
13379
12805
|
invalid(`objects[${i}] is not an object`);
|
|
13380
|
-
const pkg =
|
|
12806
|
+
const pkg = object3(entry.package);
|
|
13381
12807
|
if (!pkg)
|
|
13382
12808
|
invalid(`objects[${i}].package is not an object`);
|
|
13383
12809
|
const name = str2(pkg.name);
|
|
@@ -13391,7 +12817,7 @@ function parseSearchPage(text, keyword) {
|
|
|
13391
12817
|
return;
|
|
13392
12818
|
if (name === "markdown-it" || name.startsWith("@types/"))
|
|
13393
12819
|
return;
|
|
13394
|
-
const links =
|
|
12820
|
+
const links = object3(pkg.links) ?? {};
|
|
13395
12821
|
packages.push({
|
|
13396
12822
|
name,
|
|
13397
12823
|
version,
|
|
@@ -14146,8 +13572,8 @@ async function listGitHubBranches(credential, owner, repo, options = {}) {
|
|
|
14146
13572
|
return branches;
|
|
14147
13573
|
}
|
|
14148
13574
|
// src/lib/remote-auth/clone.ts
|
|
14149
|
-
import { mkdir as mkdir14, readdir as
|
|
14150
|
-
import
|
|
13575
|
+
import { mkdir as mkdir14, readdir as readdir8, rm as rm7 } from "node:fs/promises";
|
|
13576
|
+
import path22 from "node:path";
|
|
14151
13577
|
import git3 from "isomorphic-git";
|
|
14152
13578
|
|
|
14153
13579
|
// src/lib/remote-auth/git-http.ts
|
|
@@ -14447,7 +13873,7 @@ function sanitizeCloneFolderName(name) {
|
|
|
14447
13873
|
}
|
|
14448
13874
|
async function assertCloneTarget(dir) {
|
|
14449
13875
|
try {
|
|
14450
|
-
const entries = await
|
|
13876
|
+
const entries = await readdir8(dir);
|
|
14451
13877
|
if (entries.length > 0) {
|
|
14452
13878
|
throw new Error("That folder already has files in it. Choose an empty folder for the book.");
|
|
14453
13879
|
}
|
|
@@ -14478,7 +13904,7 @@ function friendlyCloneError(e) {
|
|
|
14478
13904
|
}
|
|
14479
13905
|
async function cloneRepository(options) {
|
|
14480
13906
|
const { dir, onProgress, tokenStore } = options;
|
|
14481
|
-
if (!
|
|
13907
|
+
if (!path22.isAbsolute(dir)) {
|
|
14482
13908
|
throw new Error("cloneRepository requires an absolute destination path.");
|
|
14483
13909
|
}
|
|
14484
13910
|
let parsed;
|
|
@@ -14792,7 +14218,7 @@ import { randomUUID as randomUUID4 } from "node:crypto";
|
|
|
14792
14218
|
|
|
14793
14219
|
// src/lib/remote-auth/converge-merge.ts
|
|
14794
14220
|
import { mkdir as mkdir15, unlink as unlink3, writeFile as writeFile13 } from "node:fs/promises";
|
|
14795
|
-
import
|
|
14221
|
+
import path23 from "node:path";
|
|
14796
14222
|
import git5 from "isomorphic-git";
|
|
14797
14223
|
import diff3Merge from "diff3";
|
|
14798
14224
|
function isMergeConflictError(e) {
|
|
@@ -14834,8 +14260,8 @@ async function readBlob(dir, cache2, oid, filepath) {
|
|
|
14834
14260
|
return git5.readBlob({ fs: gitFs, dir, cache: cache2, oid, filepath });
|
|
14835
14261
|
}
|
|
14836
14262
|
async function writeTreeFile(dir, filepath, content) {
|
|
14837
|
-
const abs =
|
|
14838
|
-
await mkdir15(
|
|
14263
|
+
const abs = path23.join(dir, filepath);
|
|
14264
|
+
await mkdir15(path23.dirname(abs), { recursive: true });
|
|
14839
14265
|
await writeFile13(abs, content);
|
|
14840
14266
|
}
|
|
14841
14267
|
async function bothAddedPaths(dir, cache2, ourTip, theirTip, allowUnrelated) {
|
|
@@ -14937,7 +14363,7 @@ async function convergeMerge(params) {
|
|
|
14937
14363
|
step("read-conflict-blobs", { filepath: p });
|
|
14938
14364
|
const ourBlob = await readBlob(dir, cache2, ourTip, p);
|
|
14939
14365
|
step("delete-conflict-file", { filepath: p });
|
|
14940
|
-
await unlink3(
|
|
14366
|
+
await unlink3(path23.join(dir, p)).catch((e) => {
|
|
14941
14367
|
if (e?.code !== "ENOENT")
|
|
14942
14368
|
throw e;
|
|
14943
14369
|
});
|
|
@@ -15283,8 +14709,8 @@ async function refreshRemoteCopies(options) {
|
|
|
15283
14709
|
}
|
|
15284
14710
|
}
|
|
15285
14711
|
// src/lib/publish/run-publish.ts
|
|
15286
|
-
import { readdir as
|
|
15287
|
-
import
|
|
14712
|
+
import { readdir as readdir10, stat as stat11 } from "node:fs/promises";
|
|
14713
|
+
import path27 from "node:path";
|
|
15288
14714
|
|
|
15289
14715
|
// src/lib/publish/command-runner.ts
|
|
15290
14716
|
var CAPTURE_LIMIT = 64 * 1024;
|
|
@@ -15434,24 +14860,24 @@ ${tail}` : ""}`);
|
|
|
15434
14860
|
|
|
15435
14861
|
// src/lib/publish/providers/guided.ts
|
|
15436
14862
|
import { copyFile as copyFile2, mkdir as mkdir16, writeFile as writeFile14 } from "node:fs/promises";
|
|
15437
|
-
import
|
|
14863
|
+
import path24 from "node:path";
|
|
15438
14864
|
async function stageGuidedPackage(req, providerId, listingLines) {
|
|
15439
|
-
const packageDir =
|
|
14865
|
+
const packageDir = path24.join(path24.dirname(req.artifact.path), "publish", providerId);
|
|
15440
14866
|
await mkdir16(packageDir, { recursive: true });
|
|
15441
|
-
await copyFile2(req.artifact.path,
|
|
14867
|
+
await copyFile2(req.artifact.path, path24.join(packageDir, path24.basename(req.artifact.path)));
|
|
15442
14868
|
const { title, authors } = req.project;
|
|
15443
14869
|
const sheet = [
|
|
15444
14870
|
`# ${title || "Untitled"}`,
|
|
15445
14871
|
"",
|
|
15446
14872
|
`- **Authors:** ${authors.length ? authors.join(", ") : "(not set)"}`,
|
|
15447
|
-
`- **File:** ${
|
|
14873
|
+
`- **File:** ${path24.basename(req.artifact.path)}`,
|
|
15448
14874
|
...listingLines,
|
|
15449
14875
|
"",
|
|
15450
14876
|
"Copy these details into the platform's listing form when uploading.",
|
|
15451
14877
|
""
|
|
15452
14878
|
].join(`
|
|
15453
14879
|
`);
|
|
15454
|
-
await writeFile14(
|
|
14880
|
+
await writeFile14(path24.join(packageDir, "LISTING.md"), sheet, "utf8");
|
|
15455
14881
|
return packageDir;
|
|
15456
14882
|
}
|
|
15457
14883
|
|
|
@@ -15514,9 +14940,9 @@ var drivethrurpgProvider = {
|
|
|
15514
14940
|
};
|
|
15515
14941
|
|
|
15516
14942
|
// src/lib/publish/providers/gdrive.ts
|
|
15517
|
-
import { mkdtemp as mkdtemp4, readFile as
|
|
14943
|
+
import { mkdtemp as mkdtemp4, readFile as readFile33, readdir as readdir9, rm as rm8, stat as stat9, writeFile as writeFile15 } from "node:fs/promises";
|
|
15518
14944
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
15519
|
-
import
|
|
14945
|
+
import path25 from "node:path";
|
|
15520
14946
|
import { Zip, ZipDeflate } from "fflate";
|
|
15521
14947
|
|
|
15522
14948
|
// src/lib/publish/google-drive.ts
|
|
@@ -15899,26 +15325,26 @@ function zipEntriesNonBlocking(entries, opts) {
|
|
|
15899
15325
|
});
|
|
15900
15326
|
}
|
|
15901
15327
|
async function collectZipEntries(root, dir, out, onWarn) {
|
|
15902
|
-
const entries = await
|
|
15328
|
+
const entries = await readdir9(dir, { withFileTypes: true });
|
|
15903
15329
|
for (const entry of entries) {
|
|
15904
|
-
const full =
|
|
15330
|
+
const full = path25.join(dir, entry.name);
|
|
15905
15331
|
if (entry.isDirectory()) {
|
|
15906
15332
|
await collectZipEntries(root, full, out, onWarn);
|
|
15907
15333
|
} else if (entry.isFile()) {
|
|
15908
|
-
const rel =
|
|
15909
|
-
out[rel] = await
|
|
15334
|
+
const rel = path25.relative(root, full).split(path25.sep).join("/");
|
|
15335
|
+
out[rel] = await readFile33(full);
|
|
15910
15336
|
} else if (entry.isSymbolicLink()) {
|
|
15911
15337
|
const resolved = await stat9(full).catch(() => null);
|
|
15912
15338
|
if (resolved?.isFile()) {
|
|
15913
|
-
const rel =
|
|
15914
|
-
out[rel] = await
|
|
15339
|
+
const rel = path25.relative(root, full).split(path25.sep).join("/");
|
|
15340
|
+
out[rel] = await readFile33(full);
|
|
15915
15341
|
} else if (resolved?.isDirectory()) {
|
|
15916
15342
|
await collectZipEntries(root, full, out, onWarn);
|
|
15917
15343
|
} else {
|
|
15918
|
-
onWarn?.(`Skipped "${
|
|
15344
|
+
onWarn?.(`Skipped "${path25.relative(root, full)}" in the website export — it's a broken symlink.`);
|
|
15919
15345
|
}
|
|
15920
15346
|
} else {
|
|
15921
|
-
onWarn?.(`Skipped "${
|
|
15347
|
+
onWarn?.(`Skipped "${path25.relative(root, full)}" in the website export — not a file, folder, or symlink.`);
|
|
15922
15348
|
}
|
|
15923
15349
|
}
|
|
15924
15350
|
}
|
|
@@ -15926,9 +15352,9 @@ async function zipHtmlExport(exportDir, title, onWarn, writeArchiveImpl = writeF
|
|
|
15926
15352
|
const entries = {};
|
|
15927
15353
|
await collectZipEntries(exportDir, exportDir, entries, onWarn);
|
|
15928
15354
|
const fileName = `${bookSlug(title)}-website.zip`;
|
|
15929
|
-
const tmpRoot = await mkdtemp4(
|
|
15355
|
+
const tmpRoot = await mkdtemp4(path25.join(tmpdir4(), "gutterpress-gdrive-"));
|
|
15930
15356
|
try {
|
|
15931
|
-
const filePath =
|
|
15357
|
+
const filePath = path25.join(tmpRoot, fileName);
|
|
15932
15358
|
const zipped = await zipEntriesNonBlocking(entries, { level: 6 });
|
|
15933
15359
|
await writeArchiveImpl(filePath, zipped);
|
|
15934
15360
|
return {
|
|
@@ -15976,7 +15402,7 @@ var gdriveProvider = {
|
|
|
15976
15402
|
const isHtml = req.artifact.format === "html";
|
|
15977
15403
|
const source = isHtml ? await zipHtmlExport(req.artifact.path, req.project.title, (msg) => req.deps.onProgress?.(msg)) : {
|
|
15978
15404
|
filePath: req.artifact.path,
|
|
15979
|
-
fileName:
|
|
15405
|
+
fileName: path25.basename(req.artifact.path),
|
|
15980
15406
|
mimeType: "application/pdf",
|
|
15981
15407
|
cleanup: async () => {}
|
|
15982
15408
|
};
|
|
@@ -16027,7 +15453,7 @@ var gdriveProvider = {
|
|
|
16027
15453
|
|
|
16028
15454
|
// src/lib/publish/butler.ts
|
|
16029
15455
|
import { chmod as chmod2, mkdir as mkdir17, stat as stat10, writeFile as writeFile16 } from "node:fs/promises";
|
|
16030
|
-
import
|
|
15456
|
+
import path26 from "node:path";
|
|
16031
15457
|
import { unzipSync as unzipSync2 } from "fflate";
|
|
16032
15458
|
var BUTLER_DOWNLOAD_TIMEOUT_MS = 300000;
|
|
16033
15459
|
function butlerBrothChannel(platform2 = process.platform, arch2 = process.arch) {
|
|
@@ -16064,8 +15490,8 @@ async function ensureButler(deps) {
|
|
|
16064
15490
|
}
|
|
16065
15491
|
if (await commandExists("butler", runCommand, deps.env))
|
|
16066
15492
|
return "butler";
|
|
16067
|
-
const cacheDir =
|
|
16068
|
-
const cached =
|
|
15493
|
+
const cacheDir = path26.join(deps.configDir ?? defaultConfigDir(), "tools", "butler");
|
|
15494
|
+
const cached = path26.join(cacheDir, butlerBinaryName());
|
|
16069
15495
|
if (await fileExists2(cached))
|
|
16070
15496
|
return cached;
|
|
16071
15497
|
const channel = butlerBrothChannel();
|
|
@@ -16092,9 +15518,9 @@ async function ensureButler(deps) {
|
|
|
16092
15518
|
for (const [name, data] of Object.entries(files)) {
|
|
16093
15519
|
if (name.endsWith("/"))
|
|
16094
15520
|
continue;
|
|
16095
|
-
const target =
|
|
15521
|
+
const target = path26.join(cacheDir, path26.basename(name));
|
|
16096
15522
|
await writeFile16(target, data);
|
|
16097
|
-
if (
|
|
15523
|
+
if (path26.basename(name) === butlerBinaryName()) {
|
|
16098
15524
|
await chmod2(target, 493).catch(() => {});
|
|
16099
15525
|
foundBinary = true;
|
|
16100
15526
|
}
|
|
@@ -16543,9 +15969,9 @@ async function resolvePublishRequest(options, deps) {
|
|
|
16543
15969
|
const effectiveDeps = effectiveAccount ? { ...deps, credentialAccount: effectiveAccount } : deps;
|
|
16544
15970
|
const outDir = resolveOutputDir(manifestDir, config.title);
|
|
16545
15971
|
const effectiveFormat = resolvePublishFormat(provider.info, providerConfig);
|
|
16546
|
-
const defaultArtifact = effectiveFormat === "pdf" ?
|
|
15972
|
+
const defaultArtifact = effectiveFormat === "pdf" ? path27.join(outDir, artifactName(config.title, "pdf")) : outDir;
|
|
16547
15973
|
const artifact = {
|
|
16548
|
-
path: options.artifactPath ?
|
|
15974
|
+
path: options.artifactPath ? path27.resolve(options.projectDir, options.artifactPath) : defaultArtifact,
|
|
16549
15975
|
format: effectiveFormat
|
|
16550
15976
|
};
|
|
16551
15977
|
return {
|
|
@@ -16609,7 +16035,7 @@ async function artifactIssues(artifact) {
|
|
|
16609
16035
|
var BUILD_FINGERPRINT_FILENAME = "build-fingerprint.json";
|
|
16610
16036
|
async function htmlDirIssues(dir) {
|
|
16611
16037
|
const issues = [];
|
|
16612
|
-
const isThere = async (rel) => stat11(
|
|
16038
|
+
const isThere = async (rel) => stat11(path27.join(dir, rel)).then(() => true, () => false);
|
|
16613
16039
|
if (!await isThere(BOOK_HTML)) {
|
|
16614
16040
|
issues.push({
|
|
16615
16041
|
severity: "error",
|
|
@@ -16617,7 +16043,7 @@ async function htmlDirIssues(dir) {
|
|
|
16617
16043
|
message: `${dir} has no ${BOOK_HTML} — it isn't an HTML export. ${HTML_HINT}`
|
|
16618
16044
|
});
|
|
16619
16045
|
}
|
|
16620
|
-
const entries = await
|
|
16046
|
+
const entries = await readdir10(dir).catch(() => []);
|
|
16621
16047
|
const extras = entries.filter((name) => name.toLowerCase().endsWith(".pdf") || name === BUILD_FINGERPRINT_FILENAME);
|
|
16622
16048
|
if (extras.length > 0) {
|
|
16623
16049
|
issues.push({
|
|
@@ -16728,17 +16154,17 @@ async function disconnectPublishCredential(key, deps, options = {}) {
|
|
|
16728
16154
|
}
|
|
16729
16155
|
}
|
|
16730
16156
|
// src/lib/publish/selections.ts
|
|
16731
|
-
import { chmod as chmod3, mkdir as mkdir18, readFile as
|
|
16732
|
-
import
|
|
16157
|
+
import { chmod as chmod3, mkdir as mkdir18, readFile as readFile34, writeFile as writeFile17 } from "node:fs/promises";
|
|
16158
|
+
import path28 from "node:path";
|
|
16733
16159
|
class PublishSelectionsStore {
|
|
16734
16160
|
filePath;
|
|
16735
16161
|
queue = Promise.resolve();
|
|
16736
16162
|
constructor(filePath) {
|
|
16737
|
-
this.filePath = filePath ??
|
|
16163
|
+
this.filePath = filePath ?? path28.join(defaultConfigDir(), "publish-selections.json");
|
|
16738
16164
|
}
|
|
16739
16165
|
async read() {
|
|
16740
16166
|
try {
|
|
16741
|
-
const raw = await
|
|
16167
|
+
const raw = await readFile34(this.filePath, "utf8");
|
|
16742
16168
|
const parsed = JSON.parse(raw);
|
|
16743
16169
|
if (parsed && typeof parsed === "object") {
|
|
16744
16170
|
return {
|
|
@@ -16751,7 +16177,7 @@ class PublishSelectionsStore {
|
|
|
16751
16177
|
return { version: 1, global: {}, projects: {} };
|
|
16752
16178
|
}
|
|
16753
16179
|
async write(data) {
|
|
16754
|
-
await mkdir18(
|
|
16180
|
+
await mkdir18(path28.dirname(this.filePath), { recursive: true });
|
|
16755
16181
|
await writeFile17(this.filePath, JSON.stringify(data, null, 2), {
|
|
16756
16182
|
encoding: "utf8",
|
|
16757
16183
|
mode: 384
|
|
@@ -16854,4 +16280,4 @@ async function setPublishProviderConfig(projectDir, providerId, values) {
|
|
|
16854
16280
|
await writeManifestDoc(file, doc);
|
|
16855
16281
|
return settingsFromDoc(doc);
|
|
16856
16282
|
}
|
|
16857
|
-
export {
|
|
16283
|
+
export { BUNDLED_EXTENSIONS, isBundledExtension, isPathSpecifier, parseExtensionSpecifier, PRESET_IDS, PRESETS, TARGETS, TARGET_IDS, publishTargetFor, MANIFEST_FILENAMES, hasProjectManifest, loadManifest, loadManifestWithPath, resolveConfig, SYNC_SNAPSHOT_MESSAGE, log, resolveActiveStyles, listProjectStyles, EXTENSION_MANIFEST_FILENAME, readExtensionMeta, extensionStyleList, extensionEntry, extensionCarries, pathEscapesFolder, assertExtensionContained, resolveExtension, loadPluginsWithCss, getChecks, getCheckById, resolveCheckSelectors, formatReport, ruleRemoteUrls, ruleRiskyProps, ruleSyntax, rulePageContainment, checkCss, inspectImage, runChecks, checkToolAvailability, reportMissingTools, executeValidation, executeAndReport, splitOutPath, runBuild, openPath, startPreviewServer, defaultConfigDir, FileTokenStore, extractUrlCredential, getSystemDiagnostics, slugifyProjectName, escapeYamlScalar, scaffoldProject, adoptFolder, ensureGitignoreHasDist, EXTENSION_KINDS, RESERVED_PREFIX, resolveExtensionPrefix, scaffoldExtension, BUILT_IN_TEMPLATE_IDS, listBuiltInTemplates, saveProjectAsTemplate, listCustomTemplates, importTemplateFromFolder, EXTENSIONS_DIR, RECOMMENDED_EXTENSIONS, BUILT_IN_STYLE_SET_IDS, sameExtension, listProjectExtensions, describeExtension, setExtensionEnabled, reorderExtensions, removeExtension, addExtension, listBuiltInStyleSets, addBuiltInStyleSet, validateProjectExtensions, readExtensionCss, SNIPPETS_DIR, extractVariables, substituteVariables, listSnippets, readSnippet, saveSnippet, deleteSnippet, listMergedSnippets, readExtensionSnippet, importExtensionFromFile, importExtensionFromUrl, MAX_SEARCH_BYTES, SEARCH_KEYWORDS, searchNpmExtensions, setManifestFields, readManifestFields, setActiveStyles, AUTO_SNAPSHOT_DEFAULT_MINUTES, AUTO_SYNC_MIN_MINUTES, AUTO_SYNC_MAX_MINUTES, AUTO_SYNC_DEFAULT_MINUTES, AUTO_SYNC_PUSH_INTERVAL_MINUTES, autoSnapshotDelayMs, autoSyncDelayMs, isGitInternalPath, resolveGitHubClientId, GITHUB_HOST, GitHubAuthProvider, GDRIVE_HOST, resolveGoogleClientId, resolveGoogleClientSecret, GOOGLE_NOT_CONFIGURED_MESSAGE, requireGoogleClientCredentials, pkceChallengeFromVerifier, GoogleAuthProvider, revokeGoogleCredential, publishCredentialKey, publishConnectionStatus, listPublishAccounts, connectGoogleDrive, listGitHubRepositories, listRepoBooks, listGitHubBranches, sanitizeCloneFolderName, cloneRepository, isSshRemoteUrl, testRemoteAccess, knownForgeTokenUrl, connectGenericHost, parseRemoteOrigin, forgeKindForHost, diagnoseProjectRemote, syncProject, refreshRemoteCopies, listPublishProviders, publishProviderFor, resolvePublishFormat, resolvePublishRequest, runPublish, connectPublishProvider, disconnectPublishCredential, PublishSelectionsStore, readPublishSettings, setPublishProviderConfig };
|