gutterpress 0.0.0 → 0.9.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -16
- package/dist/api/index.d.ts +6 -2
- package/dist/api/index.js +15 -3
- package/dist/{audit-1jrwwacz.js → audit-k16v3n98.js} +5 -5
- package/dist/{build-5s92kgpc.js → build-8tqr4gbb.js} +5 -5
- package/dist/checks/types.d.ts +6 -0
- package/dist/{cli-hvqjs342.js → cli-rkqxbnkq.js} +2 -2
- package/dist/{cli-h7phg3ka.js → cli-v2dze8ja.js} +258 -258
- package/dist/{index-1tyj4bk3.js → cli-wchtvxvw.js} +4 -1
- package/dist/{cli-7ykx7pp0.js → cli-yp0d2hp8.js} +611 -259
- package/dist/{cli-5bxx0c4y.js → cli-yzrh708h.js} +2 -2
- package/dist/cli.js +15 -15
- package/dist/{doctor-25zjt8dz.js → doctor-57czxy3q.js} +2 -2
- package/dist/{index-3h3fef7r.js → index-m3pa4wyf.js} +826 -335
- package/dist/{cli-1tyj4bk3.js → index-wchtvxvw.js} +4 -1
- package/dist/{index-5bxx0c4y.js → index-yzrh708h.js} +2 -2
- package/dist/index.js +15 -3
- package/dist/lib/asset-inline.d.ts +19 -0
- package/dist/lib/build-error.d.ts +0 -1
- package/dist/lib/build-fingerprint.d.ts +14 -0
- package/dist/lib/build-runner.d.ts +21 -1
- package/dist/lib/ghostscript.d.ts +0 -9
- package/dist/lib/manifest-config.d.ts +7 -0
- package/dist/lib/manifest.d.ts +8 -0
- package/dist/lib/markdown/index.d.ts +0 -1
- package/dist/lib/npm-plugin-installer.d.ts +1 -1
- package/dist/lib/output-paths.d.ts +0 -1
- package/dist/lib/pagination.d.ts +0 -20
- package/dist/lib/pdf-inspect.d.ts +2 -2
- package/dist/lib/pdf-parse.d.ts +2 -1
- package/dist/lib/plugin-manager.d.ts +0 -2
- package/dist/lib/presets.d.ts +36 -7
- package/dist/lib/project-scaffold.d.ts +39 -2
- package/dist/lib/project-source.d.ts +11 -9
- package/dist/lib/project-templates.d.ts +45 -7
- package/dist/lib/publish/providers/azure-swa.d.ts +0 -1
- package/dist/lib/publish/providers/itch.d.ts +0 -1
- package/dist/lib/publish/providers/shopify.d.ts +0 -1
- package/dist/lib/remote-auth/github-repos.d.ts +0 -2
- package/dist/lib/remote-auth/recovery/dispatch.d.ts +0 -5
- package/dist/lib/remote-auth/recovery/outcome-mapping.d.ts +3 -2
- package/dist/lib/remote-auth/recovery/recover-stale-lock.d.ts +0 -8
- package/dist/lib/remote-auth/sync.d.ts +2 -10
- package/dist/lib/remote-auth/test-access.d.ts +0 -2
- package/dist/lib/remote-auth/test-support/git-http-server.d.ts +0 -37
- package/dist/lib/source-provider.d.ts +0 -14
- package/dist/lib/static-serve.d.ts +39 -0
- package/dist/lib/style-resolver.d.ts +10 -1
- package/dist/lib/targets.d.ts +84 -0
- package/dist/lib/theme-import.d.ts +0 -2
- package/dist/lib/theme-manager.d.ts +1 -1
- package/dist/lib/validation-exec.d.ts +4 -3
- package/dist/{lint-f63nxjfj.js → lint-4sq0dvf6.js} +5 -5
- package/dist/{manifest.schema-rn1dpdpn.json → manifest.schema-z61rzw44.json} +12 -6
- package/dist/new-f2nmyr5b.js +185 -0
- package/dist/{plugin-wf2cbmwc.js → plugin-xskq3dtx.js} +5 -5
- package/dist/{preflight-tg4mpz1h.js → preflight-epcffags.js} +24 -29
- package/dist/preview/file-watcher.d.ts +9 -22
- package/dist/preview/server-context.d.ts +22 -0
- package/dist/{preview-h2bwbb2x.js → preview-xf5dktz2.js} +5 -5
- package/dist/{project-source-0tg0r6vm.js → project-source-p0gn1wd5.js} +3 -3
- package/dist/{publish-ztaet53r.js → publish-97vdwq31.js} +5 -5
- package/dist/{repair-jkp4n3j8.js → repair-cz4z4hkg.js} +5 -5
- package/dist/schema/manifest.types.d.ts +14 -3
- package/dist/{source-provider-hp96b5jf.js → source-provider-c1rjm2c0.js} +2 -8
- package/dist/types.d.ts +0 -1
- package/dist/{validate-k2drs19j.js → validate-swzejnd8.js} +8 -8
- package/package.json +2 -2
- package/dist/chapter-01-jj8ftn4c.md +0 -29
- package/dist/lib/validation-profile.d.ts +0 -18
- package/dist/manifest-ryyg71nf.yaml +0 -9
- package/dist/new-7b08a42h.js +0 -105
- package/dist/theme-0yzvthe8.css +0 -65
- package/dist/theme-6a2fngc8.json +0 -6
|
@@ -11,11 +11,11 @@ import {
|
|
|
11
11
|
snapshotWorkingTreeUnlocked,
|
|
12
12
|
stageChanges,
|
|
13
13
|
withRepoLock
|
|
14
|
-
} from "./index-
|
|
14
|
+
} from "./index-yzrh708h.js";
|
|
15
15
|
import {
|
|
16
16
|
detectProjectSource,
|
|
17
17
|
syncRemoteFor
|
|
18
|
-
} from "./index-
|
|
18
|
+
} from "./index-wchtvxvw.js";
|
|
19
19
|
import {
|
|
20
20
|
defaultGitHttp
|
|
21
21
|
} from "./index-yja077f6.js";
|
|
@@ -84,7 +84,9 @@ function resolvePort(raw) {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
// src/lib/presets.ts
|
|
87
|
+
var PRESET_IDS = ["dtrpg", "book", "custom"];
|
|
87
88
|
var DTRPG_PRESET = {
|
|
89
|
+
defaultTargets: ["dtrpg"],
|
|
88
90
|
plugins: [],
|
|
89
91
|
source: {
|
|
90
92
|
files: null
|
|
@@ -156,6 +158,7 @@ var DTRPG_PRESET = {
|
|
|
156
158
|
}
|
|
157
159
|
};
|
|
158
160
|
var BOOK_PRESET = {
|
|
161
|
+
defaultTargets: [],
|
|
159
162
|
plugins: [],
|
|
160
163
|
source: {
|
|
161
164
|
files: null
|
|
@@ -220,9 +223,15 @@ var BOOK_PRESET = {
|
|
|
220
223
|
}
|
|
221
224
|
}
|
|
222
225
|
};
|
|
226
|
+
var CUSTOM_PRESET = {
|
|
227
|
+
...BOOK_PRESET,
|
|
228
|
+
page: null,
|
|
229
|
+
defaultTargets: []
|
|
230
|
+
};
|
|
223
231
|
var PRESETS = {
|
|
224
232
|
dtrpg: DTRPG_PRESET,
|
|
225
|
-
book: BOOK_PRESET
|
|
233
|
+
book: BOOK_PRESET,
|
|
234
|
+
custom: CUSTOM_PRESET
|
|
226
235
|
};
|
|
227
236
|
var warnedIds = new Set;
|
|
228
237
|
function warnOnce(id, message, sink = defaultWarnSink) {
|
|
@@ -236,7 +245,7 @@ function defaultWarnSink(message) {
|
|
|
236
245
|
}
|
|
237
246
|
function resolvePreset(presetName) {
|
|
238
247
|
if (presetName === undefined) {
|
|
239
|
-
warnOnce("no-preset-set", '[gutterpress] No `preset`
|
|
248
|
+
warnOnce("no-preset-set", '[gutterpress] No `preset` in manifest.yaml — using "dtrpg" ' + "(DriveThruRPG print-ready defaults). Set `preset: dtrpg`, `book`, " + "or `custom` to choose explicitly.");
|
|
240
249
|
return DTRPG_PRESET;
|
|
241
250
|
}
|
|
242
251
|
const preset = PRESETS[presetName];
|
|
@@ -246,6 +255,121 @@ function resolvePreset(presetName) {
|
|
|
246
255
|
return preset;
|
|
247
256
|
}
|
|
248
257
|
|
|
258
|
+
// src/lib/targets.ts
|
|
259
|
+
var DTRPG_TARGET = {
|
|
260
|
+
id: "dtrpg",
|
|
261
|
+
label: "DriveThruRPG",
|
|
262
|
+
overlay: {
|
|
263
|
+
pdfx: { flavor: DTRPG_PRESET.pdfx.flavor },
|
|
264
|
+
ink: {
|
|
265
|
+
maxTac: DTRPG_PRESET.ink.maxTac,
|
|
266
|
+
tacTolerance: DTRPG_PRESET.ink.tacTolerance
|
|
267
|
+
},
|
|
268
|
+
validate: {
|
|
269
|
+
checks: {
|
|
270
|
+
"pdf.structure.qpdf": { enabled: true, severity: "error" },
|
|
271
|
+
"pdf.print.pdfx-markers": { enabled: true, severity: "error" },
|
|
272
|
+
"pdf.print.pdfx-metadata": { enabled: true, severity: "error" },
|
|
273
|
+
"pdf.print.embedded-fonts": { enabled: true, severity: "error" }
|
|
274
|
+
},
|
|
275
|
+
assets: {
|
|
276
|
+
allowedColorSpaces: [...DTRPG_PRESET.validate.assets.allowedColorSpaces]
|
|
277
|
+
},
|
|
278
|
+
pdf: {
|
|
279
|
+
forbidTransparency: DTRPG_PRESET.validate.pdf.forbidTransparency,
|
|
280
|
+
minImageResolution: DTRPG_PRESET.validate.pdf.minImageResolution
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
requiredChecks: [
|
|
285
|
+
"pdf.structure.qpdf",
|
|
286
|
+
"pdf.print.pdfx-markers",
|
|
287
|
+
"pdf.print.pdfx-metadata",
|
|
288
|
+
"pdf.print.embedded-fonts"
|
|
289
|
+
],
|
|
290
|
+
requiredTools: ["qpdf", "gs"]
|
|
291
|
+
};
|
|
292
|
+
var ITCH_TARGET = {
|
|
293
|
+
id: "itch",
|
|
294
|
+
label: "itch.io",
|
|
295
|
+
overlay: {
|
|
296
|
+
validate: {
|
|
297
|
+
checks: {
|
|
298
|
+
"pdf.structure.qpdf": { enabled: true, severity: "error" },
|
|
299
|
+
"pdf.print.embedded-fonts": { enabled: true, severity: "error" },
|
|
300
|
+
"pdf.print.pdfx-markers": false,
|
|
301
|
+
"pdf.print.pdfx-metadata": false,
|
|
302
|
+
"pdf.print.ink-coverage": false,
|
|
303
|
+
"asset.image.tac-raster": false
|
|
304
|
+
},
|
|
305
|
+
assets: {
|
|
306
|
+
allowedColorSpaces: ["RGB", "CMYK", "Grayscale"],
|
|
307
|
+
allowAlpha: true
|
|
308
|
+
},
|
|
309
|
+
pdf: {
|
|
310
|
+
forbidTransparency: false,
|
|
311
|
+
requireBleed: false
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
requiredChecks: ["pdf.structure.qpdf", "pdf.print.embedded-fonts"],
|
|
316
|
+
requiredTools: []
|
|
317
|
+
};
|
|
318
|
+
var TARGETS = {
|
|
319
|
+
dtrpg: DTRPG_TARGET,
|
|
320
|
+
itch: ITCH_TARGET
|
|
321
|
+
};
|
|
322
|
+
var TARGET_IDS = Object.keys(TARGETS);
|
|
323
|
+
function publishTargetFor(id) {
|
|
324
|
+
const target = TARGETS[id];
|
|
325
|
+
if (!target) {
|
|
326
|
+
throw new UsageError(`Unknown publish target "${id}". Known targets: ${TARGET_IDS.join(", ")}.`);
|
|
327
|
+
}
|
|
328
|
+
return target;
|
|
329
|
+
}
|
|
330
|
+
function resolveTargets(requested, fallback) {
|
|
331
|
+
const ids = requested ?? fallback;
|
|
332
|
+
const seen = new Set;
|
|
333
|
+
const out = [];
|
|
334
|
+
for (const id of ids) {
|
|
335
|
+
publishTargetFor(id);
|
|
336
|
+
if (!seen.has(id)) {
|
|
337
|
+
seen.add(id);
|
|
338
|
+
out.push(id);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return out;
|
|
342
|
+
}
|
|
343
|
+
function overlayPreset(preset, target) {
|
|
344
|
+
const o = target.overlay;
|
|
345
|
+
return {
|
|
346
|
+
...preset,
|
|
347
|
+
pdfx: { ...preset.pdfx, ...o.pdfx },
|
|
348
|
+
ink: { ...preset.ink, ...o.ink },
|
|
349
|
+
validate: {
|
|
350
|
+
...preset.validate,
|
|
351
|
+
checks: { ...preset.validate.checks, ...o.validate?.checks },
|
|
352
|
+
assets: { ...preset.validate.assets, ...o.validate?.assets },
|
|
353
|
+
pdf: { ...preset.validate.pdf, ...o.validate?.pdf }
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
var DEFAULT_STRICT_PDF_CHECKS = [
|
|
358
|
+
"pdf.structure.qpdf",
|
|
359
|
+
"pdf.print.pdfx-markers",
|
|
360
|
+
"pdf.print.pdfx-metadata",
|
|
361
|
+
"pdf.print.embedded-fonts"
|
|
362
|
+
];
|
|
363
|
+
function applyDefaultPdfStrictChecks(config) {
|
|
364
|
+
const next = structuredClone(config);
|
|
365
|
+
for (const checkId of DEFAULT_STRICT_PDF_CHECKS) {
|
|
366
|
+
if (next.validate.checks[checkId] === undefined) {
|
|
367
|
+
next.validate.checks[checkId] = { enabled: true, severity: "error" };
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return next;
|
|
371
|
+
}
|
|
372
|
+
|
|
249
373
|
// src/lib/manifest.ts
|
|
250
374
|
var MANIFEST_FILENAMES = [
|
|
251
375
|
"manifest.yaml"
|
|
@@ -333,8 +457,34 @@ function mergeShape(cli, manifest, preset) {
|
|
|
333
457
|
return out;
|
|
334
458
|
}
|
|
335
459
|
function resolveConfig(cliOverrides, manifest) {
|
|
460
|
+
const presetName = cliOverrides.preset ?? manifest.preset;
|
|
461
|
+
return resolveWithPreset(cliOverrides, manifest, resolvePreset(presetName));
|
|
462
|
+
}
|
|
463
|
+
function resolveConfigForTarget(cliOverrides, manifest, targetId) {
|
|
336
464
|
const presetName = cliOverrides.preset ?? manifest.preset;
|
|
337
465
|
const preset = resolvePreset(presetName);
|
|
466
|
+
return resolveWithPreset(cliOverrides, manifest, overlayPreset(preset, publishTargetFor(targetId)));
|
|
467
|
+
}
|
|
468
|
+
function resolveCustomPage(c, m) {
|
|
469
|
+
const width = c.page?.width ?? m.page?.width;
|
|
470
|
+
const height = c.page?.height ?? m.page?.height;
|
|
471
|
+
const missing = [
|
|
472
|
+
width === undefined ? "page.width" : null,
|
|
473
|
+
height === undefined ? "page.height" : null
|
|
474
|
+
].filter((f) => f !== null);
|
|
475
|
+
if (missing.length > 0) {
|
|
476
|
+
throw new UsageError(`preset: custom requires ${missing.join(" and ")} in manifest.yaml ` + `(points; 72pt = 1in). Example for US Letter:
|
|
477
|
+
` + ` page:
|
|
478
|
+
width: 612
|
|
479
|
+
height: 792`);
|
|
480
|
+
}
|
|
481
|
+
return {
|
|
482
|
+
width,
|
|
483
|
+
height,
|
|
484
|
+
tolerance: c.page?.tolerance ?? m.page?.tolerance ?? 0.5
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
function resolveWithPreset(cliOverrides, manifest, preset) {
|
|
338
488
|
const m = manifest;
|
|
339
489
|
const c = cliOverrides;
|
|
340
490
|
const legacy = m;
|
|
@@ -356,9 +506,10 @@ function resolveConfig(cliOverrides, manifest) {
|
|
|
356
506
|
authors: c.authors ?? m.authors ?? [],
|
|
357
507
|
styles: c.styles ?? m.styles,
|
|
358
508
|
plugins,
|
|
509
|
+
targets: resolveTargets(c.targets ?? m.targets, preset.defaultTargets),
|
|
359
510
|
source: mergeShape(c.source, m.source, preset.source),
|
|
360
511
|
pdfx: mergeShape(c.pdfx, m.pdfx, preset.pdfx),
|
|
361
|
-
page: mergeShape(c.page, m.page, preset.page),
|
|
512
|
+
page: preset.page ? mergeShape(c.page, m.page, preset.page) : resolveCustomPage(c, m),
|
|
362
513
|
ink: mergeShape(c.ink, m.ink, preset.ink),
|
|
363
514
|
lint: mergeShape(c.lint, m.lint, preset.lint),
|
|
364
515
|
validate: {
|
|
@@ -1153,11 +1304,24 @@ async function resolveActiveStyles(projectDir, manifestStyles) {
|
|
|
1153
1304
|
}
|
|
1154
1305
|
return [];
|
|
1155
1306
|
}
|
|
1156
|
-
|
|
1157
|
-
|
|
1307
|
+
var SHARED_STYLE_ROOTS = ["shared", "."];
|
|
1308
|
+
async function discoverSharedCssFiles(repoRoot) {
|
|
1309
|
+
const found = new Set;
|
|
1310
|
+
for (const rel of SHARED_STYLE_ROOTS) {
|
|
1311
|
+
const base = path.resolve(repoRoot, rel);
|
|
1312
|
+
for (const abs of await discoverCssFiles(base))
|
|
1313
|
+
found.add(abs);
|
|
1314
|
+
}
|
|
1315
|
+
return [...found];
|
|
1316
|
+
}
|
|
1317
|
+
async function listProjectStyles(projectDir, opts = {}) {
|
|
1318
|
+
const repoRoot = opts.repoRoot && path.resolve(opts.repoRoot) !== path.resolve(projectDir) ? path.resolve(opts.repoRoot) : null;
|
|
1319
|
+
const [{ manifest }, ownCss, sharedCss] = await Promise.all([
|
|
1158
1320
|
loadManifestWithPath(projectDir),
|
|
1159
|
-
discoverCssFiles(projectDir)
|
|
1321
|
+
discoverCssFiles(projectDir),
|
|
1322
|
+
repoRoot ? discoverSharedCssFiles(repoRoot) : Promise.resolve([])
|
|
1160
1323
|
]);
|
|
1324
|
+
const discoveredAbs = [...new Set([...ownCss, ...sharedCss])];
|
|
1161
1325
|
const activeRels = await resolveActiveStyles(projectDir, Array.isArray(manifest.styles) ? manifest.styles : undefined);
|
|
1162
1326
|
const out = [];
|
|
1163
1327
|
const seen = new Set;
|
|
@@ -1216,6 +1380,22 @@ function decodeRef(ref) {
|
|
|
1216
1380
|
function toPosix(p) {
|
|
1217
1381
|
return p.split(path2.sep).join("/");
|
|
1218
1382
|
}
|
|
1383
|
+
function escapesProjectRoot(projectDir, absPath) {
|
|
1384
|
+
const rel = path2.relative(projectDir, absPath);
|
|
1385
|
+
return rel === ".." || rel.startsWith(`..${path2.sep}`) || path2.isAbsolute(rel);
|
|
1386
|
+
}
|
|
1387
|
+
function proseImageRefError(ref, projectDir) {
|
|
1388
|
+
const cleaned = stripUrlSuffix(decodeRef(ref));
|
|
1389
|
+
if (path2.isAbsolute(cleaned)) {
|
|
1390
|
+
return `Image reference must be relative to the project: ${ref}
|
|
1391
|
+
` + ` Copy the file into your project folder and reference it from there.`;
|
|
1392
|
+
}
|
|
1393
|
+
if (escapesProjectRoot(projectDir, path2.resolve(projectDir, cleaned))) {
|
|
1394
|
+
return `Image reference points outside the project: ${ref}
|
|
1395
|
+
` + ` Copy the file into your project folder and reference it from there.`;
|
|
1396
|
+
}
|
|
1397
|
+
return null;
|
|
1398
|
+
}
|
|
1219
1399
|
function mimeFor(ext) {
|
|
1220
1400
|
return MIME_BY_EXT[ext] ?? "application/octet-stream";
|
|
1221
1401
|
}
|
|
@@ -1318,7 +1498,7 @@ async function inlineOne(cssPath, projectDir, copies, warnings, seen) {
|
|
|
1318
1498
|
return dataUri(bytes, ext);
|
|
1319
1499
|
}
|
|
1320
1500
|
const projectRel = path2.relative(projectDir, absAsset);
|
|
1321
|
-
const dest = projectRel && !
|
|
1501
|
+
const dest = projectRel && !escapesProjectRoot(projectDir, absAsset) ? toPosix(projectRel) : `${HASHED_ASSET_DIR}/${contentHash(bytes)}${ext}`;
|
|
1322
1502
|
copies.set(dest, { from: absAsset, to: dest });
|
|
1323
1503
|
return dest;
|
|
1324
1504
|
});
|
|
@@ -1409,19 +1589,14 @@ async function planImageCopies(projectDir, refs) {
|
|
|
1409
1589
|
for (const ref of refs) {
|
|
1410
1590
|
if (isNonFileUrl(ref))
|
|
1411
1591
|
continue;
|
|
1412
|
-
const
|
|
1413
|
-
if (
|
|
1414
|
-
errors.push(
|
|
1415
|
-
` + ` Copy the file into your project folder and reference it from there.`);
|
|
1592
|
+
const refError = proseImageRefError(ref, projectDir);
|
|
1593
|
+
if (refError) {
|
|
1594
|
+
errors.push(refError);
|
|
1416
1595
|
continue;
|
|
1417
1596
|
}
|
|
1597
|
+
const cleaned = stripUrlSuffix(decodeRef(ref));
|
|
1418
1598
|
const abs = path2.resolve(projectDir, cleaned);
|
|
1419
1599
|
const rel = path2.relative(projectDir, abs);
|
|
1420
|
-
if (rel.startsWith("..")) {
|
|
1421
|
-
errors.push(`Image reference points outside the project: ${ref}
|
|
1422
|
-
` + ` Copy the file into your project folder and reference it from there.`);
|
|
1423
|
-
continue;
|
|
1424
|
-
}
|
|
1425
1600
|
const dest = toPosix(rel);
|
|
1426
1601
|
copies.set(dest, { from: abs, to: dest });
|
|
1427
1602
|
}
|
|
@@ -2616,7 +2791,7 @@ function shadowPathFor(pluginPath, mtimeMs) {
|
|
|
2616
2791
|
const ext = extname(pluginPath);
|
|
2617
2792
|
const stem = basename(pluginPath, ext);
|
|
2618
2793
|
const token = String(mtimeMs).replace(/\./g, "-");
|
|
2619
|
-
return join2(dirname2(pluginPath), `.${stem}.gutterpress-reload-${token}${ext}`);
|
|
2794
|
+
return join2(dirname2(pluginPath), `.${stem}.gutterpress-reload-${process.pid}-${token}${ext}`);
|
|
2620
2795
|
}
|
|
2621
2796
|
async function loadCachedPathPluginModule(pluginPath) {
|
|
2622
2797
|
const mtimeMs = statSync2(pluginPath).mtimeMs;
|
|
@@ -3212,7 +3387,7 @@ import git from "isomorphic-git";
|
|
|
3212
3387
|
// package.json
|
|
3213
3388
|
var package_default = {
|
|
3214
3389
|
name: "gutterpress",
|
|
3215
|
-
version: "0.0.
|
|
3390
|
+
version: "0.9.0-alpha.1",
|
|
3216
3391
|
description: "Markdown-to-PDF converter for professional print layout using Paged.js and Ghostscript.",
|
|
3217
3392
|
author: "itlackey",
|
|
3218
3393
|
license: "MPL-2.0",
|
|
@@ -3224,7 +3399,7 @@ var package_default = {
|
|
|
3224
3399
|
"ghostscript",
|
|
3225
3400
|
"book",
|
|
3226
3401
|
"publishing",
|
|
3227
|
-
"
|
|
3402
|
+
"zine"
|
|
3228
3403
|
],
|
|
3229
3404
|
repository: {
|
|
3230
3405
|
type: "git",
|
|
@@ -3452,7 +3627,7 @@ async function writeBuildFingerprint(input) {
|
|
|
3452
3627
|
iccPath: input.pdfx.iccPath,
|
|
3453
3628
|
stripAnnotations: input.pdfx.stripAnnotations
|
|
3454
3629
|
},
|
|
3455
|
-
outputDir
|
|
3630
|
+
outputDir: input.recordedOutputDir ? path5.resolve(input.recordedOutputDir) : outputDir
|
|
3456
3631
|
},
|
|
3457
3632
|
sourceRevision,
|
|
3458
3633
|
tools
|
|
@@ -3473,7 +3648,7 @@ import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
|
3473
3648
|
var favicon_default = "./favicon-wkbm9cjn.ico";
|
|
3474
3649
|
|
|
3475
3650
|
// src/assets/manifest.schema.json
|
|
3476
|
-
var manifest_schema_default = "./manifest.schema-
|
|
3651
|
+
var manifest_schema_default = "./manifest.schema-z61rzw44.json";
|
|
3477
3652
|
|
|
3478
3653
|
// src/assets/preview/scripts/pagedjs-interface.js
|
|
3479
3654
|
var pagedjs_interface_default = "./pagedjs-interface-80g3xa8p.js";
|
|
@@ -3496,23 +3671,17 @@ var manifest_default = "./manifest-n1gh3qw5.yaml";
|
|
|
3496
3671
|
// src/assets/templates/book/chapter-01.md
|
|
3497
3672
|
var chapter_01_default = "./chapter-01-rkpyvjna.md";
|
|
3498
3673
|
|
|
3499
|
-
// src/assets/templates/ttrpg/manifest.yaml
|
|
3500
|
-
var manifest_default2 = "./manifest-ryyg71nf.yaml";
|
|
3501
|
-
|
|
3502
|
-
// src/assets/templates/ttrpg/chapter-01.md
|
|
3503
|
-
var chapter_01_default2 = "./chapter-01-jj8ftn4c.md";
|
|
3504
|
-
|
|
3505
3674
|
// src/assets/templates/zine/manifest.yaml
|
|
3506
|
-
var
|
|
3675
|
+
var manifest_default2 = "./manifest-n1gh3qw5.yaml";
|
|
3507
3676
|
|
|
3508
3677
|
// src/assets/templates/zine/chapter-01.md
|
|
3509
|
-
var
|
|
3678
|
+
var chapter_01_default2 = "./chapter-01-7jwqg1ct.md";
|
|
3510
3679
|
|
|
3511
3680
|
// src/assets/templates/technical/manifest.yaml
|
|
3512
|
-
var
|
|
3681
|
+
var manifest_default3 = "./manifest-n1gh3qw5.yaml";
|
|
3513
3682
|
|
|
3514
3683
|
// src/assets/templates/technical/chapter-01.md
|
|
3515
|
-
var
|
|
3684
|
+
var chapter_01_default3 = "./chapter-01-6fn9pvd0.md";
|
|
3516
3685
|
|
|
3517
3686
|
// src/assets/themes/clean-book/theme.css
|
|
3518
3687
|
var theme_default = "./theme-570zmh2t.css";
|
|
@@ -3520,23 +3689,17 @@ var theme_default = "./theme-570zmh2t.css";
|
|
|
3520
3689
|
// src/assets/themes/clean-book/theme.json
|
|
3521
3690
|
var theme_default2 = "./theme-xt0hjb1b.json";
|
|
3522
3691
|
|
|
3523
|
-
// src/assets/themes/ttrpg-supplement/theme.css
|
|
3524
|
-
var theme_default3 = "./theme-0yzvthe8.css";
|
|
3525
|
-
|
|
3526
|
-
// src/assets/themes/ttrpg-supplement/theme.json
|
|
3527
|
-
var theme_default4 = "./theme-6a2fngc8.json";
|
|
3528
|
-
|
|
3529
3692
|
// src/assets/themes/zine/theme.css
|
|
3530
|
-
var
|
|
3693
|
+
var theme_default3 = "./theme-zz2ktzqs.css";
|
|
3531
3694
|
|
|
3532
3695
|
// src/assets/themes/zine/theme.json
|
|
3533
|
-
var
|
|
3696
|
+
var theme_default4 = "./theme-cd6efzjw.json";
|
|
3534
3697
|
|
|
3535
3698
|
// src/assets/themes/technical-doc/theme.css
|
|
3536
|
-
var
|
|
3699
|
+
var theme_default5 = "./theme-nya4nqh6.css";
|
|
3537
3700
|
|
|
3538
3701
|
// src/assets/themes/technical-doc/theme.json
|
|
3539
|
-
var
|
|
3702
|
+
var theme_default6 = "./theme-tqfypc1w.json";
|
|
3540
3703
|
|
|
3541
3704
|
// src/lib/embedded-assets.ts
|
|
3542
3705
|
var __libdir = dirname3(fileURLToPath2(import.meta.url));
|
|
@@ -3552,20 +3715,16 @@ var EMBEDDED_ASSETS = {
|
|
|
3552
3715
|
"profiles/CGATS21_CRPC1.icc": abs(filePath(CGATS21_CRPC1_default)),
|
|
3553
3716
|
"templates/book/manifest.yaml": abs(filePath(manifest_default)),
|
|
3554
3717
|
"templates/book/chapter-01.md": abs(filePath(chapter_01_default)),
|
|
3555
|
-
"templates/
|
|
3556
|
-
"templates/
|
|
3557
|
-
"templates/
|
|
3558
|
-
"templates/
|
|
3559
|
-
"templates/technical/manifest.yaml": abs(filePath(manifest_default4)),
|
|
3560
|
-
"templates/technical/chapter-01.md": abs(filePath(chapter_01_default4)),
|
|
3718
|
+
"templates/zine/manifest.yaml": abs(filePath(manifest_default2)),
|
|
3719
|
+
"templates/zine/chapter-01.md": abs(filePath(chapter_01_default2)),
|
|
3720
|
+
"templates/technical/manifest.yaml": abs(filePath(manifest_default3)),
|
|
3721
|
+
"templates/technical/chapter-01.md": abs(filePath(chapter_01_default3)),
|
|
3561
3722
|
"themes/clean-book/theme.css": abs(filePath(theme_default)),
|
|
3562
3723
|
"themes/clean-book/theme.json": abs(filePath(theme_default2)),
|
|
3563
|
-
"themes/
|
|
3564
|
-
"themes/
|
|
3565
|
-
"themes/
|
|
3566
|
-
"themes/
|
|
3567
|
-
"themes/technical-doc/theme.css": abs(filePath(theme_default7)),
|
|
3568
|
-
"themes/technical-doc/theme.json": abs(filePath(theme_default8))
|
|
3724
|
+
"themes/zine/theme.css": abs(filePath(theme_default3)),
|
|
3725
|
+
"themes/zine/theme.json": abs(filePath(theme_default4)),
|
|
3726
|
+
"themes/technical-doc/theme.css": abs(filePath(theme_default5)),
|
|
3727
|
+
"themes/technical-doc/theme.json": abs(filePath(theme_default6))
|
|
3569
3728
|
};
|
|
3570
3729
|
var extractPromise = null;
|
|
3571
3730
|
async function extractAssets() {
|
|
@@ -3797,7 +3956,7 @@ async function runLint(opts = {}) {
|
|
|
3797
3956
|
|
|
3798
3957
|
// src/lib/validation-exec.ts
|
|
3799
3958
|
import { existsSync as existsSync11 } from "node:fs";
|
|
3800
|
-
import { join as join6, resolve as resolve9 } from "node:path";
|
|
3959
|
+
import { dirname as dirname5, join as join6, resolve as resolve9 } from "node:path";
|
|
3801
3960
|
|
|
3802
3961
|
// src/checks/formatter.ts
|
|
3803
3962
|
function formatReport(report, format = "text") {
|
|
@@ -3809,19 +3968,19 @@ function formatReport(report, format = "text") {
|
|
|
3809
3968
|
}
|
|
3810
3969
|
function formatText(report) {
|
|
3811
3970
|
for (const r of report.infos) {
|
|
3812
|
-
const loc = formatLocation(r.file, r.line, r.column);
|
|
3971
|
+
const loc = targetPrefix(r.target) + formatLocation(r.file, r.line, r.column);
|
|
3813
3972
|
log.info(`${loc}${r.message}`);
|
|
3814
3973
|
if (r.detail)
|
|
3815
3974
|
log.info(` ${r.detail}`);
|
|
3816
3975
|
}
|
|
3817
3976
|
for (const r of report.warnings) {
|
|
3818
|
-
const loc = formatLocation(r.file, r.line, r.column);
|
|
3977
|
+
const loc = targetPrefix(r.target) + formatLocation(r.file, r.line, r.column);
|
|
3819
3978
|
log.warn(`${loc}${r.message}`);
|
|
3820
3979
|
if (r.detail)
|
|
3821
3980
|
log.warn(` ${r.detail}`);
|
|
3822
3981
|
}
|
|
3823
3982
|
for (const r of report.errors) {
|
|
3824
|
-
const loc = formatLocation(r.file, r.line, r.column);
|
|
3983
|
+
const loc = targetPrefix(r.target) + formatLocation(r.file, r.line, r.column);
|
|
3825
3984
|
log.error(`${loc}${r.message}`);
|
|
3826
3985
|
if (r.detail)
|
|
3827
3986
|
log.error(` ${r.detail}`);
|
|
@@ -3835,6 +3994,9 @@ function formatText(report) {
|
|
|
3835
3994
|
log.success("VALIDATION PASSED");
|
|
3836
3995
|
}
|
|
3837
3996
|
}
|
|
3997
|
+
function targetPrefix(target) {
|
|
3998
|
+
return target ? `[${target}] ` : "";
|
|
3999
|
+
}
|
|
3838
4000
|
function formatJson(report) {
|
|
3839
4001
|
console.log(JSON.stringify({
|
|
3840
4002
|
results: report.results,
|
|
@@ -5239,6 +5401,16 @@ var check19 = {
|
|
|
5239
5401
|
if (inFence)
|
|
5240
5402
|
continue;
|
|
5241
5403
|
for (const { ref, kind } of extractLocalRefs(line)) {
|
|
5404
|
+
const escape = kind === "image" ? proseImageRefError(ref, ctx.inputDir) : null;
|
|
5405
|
+
if (escape) {
|
|
5406
|
+
results.push(finding(check19.id, {
|
|
5407
|
+
severity: "error",
|
|
5408
|
+
message: escape,
|
|
5409
|
+
file,
|
|
5410
|
+
line: i + 1
|
|
5411
|
+
}));
|
|
5412
|
+
continue;
|
|
5413
|
+
}
|
|
5242
5414
|
if (localRefExists(ref, kind, file, ctx.inputDir))
|
|
5243
5415
|
continue;
|
|
5244
5416
|
results.push(finding(check19.id, {
|
|
@@ -6301,58 +6473,46 @@ function reportMissingTools(result) {
|
|
|
6301
6473
|
}
|
|
6302
6474
|
}
|
|
6303
6475
|
|
|
6304
|
-
// src/lib/validation-
|
|
6305
|
-
var
|
|
6306
|
-
"
|
|
6307
|
-
"
|
|
6308
|
-
"
|
|
6309
|
-
"
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
|
|
6476
|
+
// src/lib/validation-exec.ts
|
|
6477
|
+
var SHIPPED_ASSET_EXTS = new Set([
|
|
6478
|
+
".woff2",
|
|
6479
|
+
".woff",
|
|
6480
|
+
".ttf",
|
|
6481
|
+
".otf",
|
|
6482
|
+
".png",
|
|
6483
|
+
".jpg",
|
|
6484
|
+
".jpeg",
|
|
6485
|
+
".gif",
|
|
6486
|
+
".webp",
|
|
6487
|
+
".avif",
|
|
6488
|
+
".svg",
|
|
6489
|
+
".tif",
|
|
6490
|
+
".tiff"
|
|
6491
|
+
]);
|
|
6492
|
+
async function sharedAssetDirs(manifestDir, relStyles, inputDir) {
|
|
6493
|
+
let closure;
|
|
6494
|
+
try {
|
|
6495
|
+
closure = await collectStyleDependencies(manifestDir, relStyles);
|
|
6496
|
+
} catch {
|
|
6497
|
+
return [];
|
|
6319
6498
|
}
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
const
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
if (profile === "dtrpg") {
|
|
6330
|
-
next.pdfx.flavor = DTRPG_PRESET.pdfx.flavor;
|
|
6331
|
-
next.ink.maxTac = DTRPG_PRESET.ink.maxTac;
|
|
6332
|
-
next.ink.tacTolerance = DTRPG_PRESET.ink.tacTolerance;
|
|
6333
|
-
next.validate.pdf.forbidTransparency = DTRPG_PRESET.validate.pdf.forbidTransparency;
|
|
6334
|
-
next.validate.pdf.minImageResolution = DTRPG_PRESET.validate.pdf.minImageResolution;
|
|
6335
|
-
next.validate.assets.allowedColorSpaces = [
|
|
6336
|
-
...DTRPG_PRESET.validate.assets.allowedColorSpaces
|
|
6337
|
-
];
|
|
6338
|
-
enforceStrictPdfChecks(next, true);
|
|
6499
|
+
const projectRoot = resolve9(inputDir);
|
|
6500
|
+
const dirs = new Set;
|
|
6501
|
+
for (const file of closure) {
|
|
6502
|
+
const ext = file.slice(file.lastIndexOf(".")).toLowerCase();
|
|
6503
|
+
if (!SHIPPED_ASSET_EXTS.has(ext))
|
|
6504
|
+
continue;
|
|
6505
|
+
const dir = dirname5(resolve9(file));
|
|
6506
|
+
if (escapesProjectRoot(projectRoot, dir))
|
|
6507
|
+
dirs.add(dir);
|
|
6339
6508
|
}
|
|
6340
|
-
return
|
|
6509
|
+
return [...dirs];
|
|
6341
6510
|
}
|
|
6342
|
-
|
|
6343
|
-
// src/lib/validation-exec.ts
|
|
6344
6511
|
function parseCsv(value) {
|
|
6345
6512
|
if (!value)
|
|
6346
6513
|
return;
|
|
6347
6514
|
return value.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
6348
6515
|
}
|
|
6349
|
-
function parseProfile(raw) {
|
|
6350
|
-
if (!raw)
|
|
6351
|
-
return;
|
|
6352
|
-
if (raw === "dtrpg")
|
|
6353
|
-
return raw;
|
|
6354
|
-
throw new Error(`Unsupported profile: ${raw}. Supported profiles: dtrpg`);
|
|
6355
|
-
}
|
|
6356
6516
|
function resolvePhaseArg(raw) {
|
|
6357
6517
|
const normalized = raw.trim().toLowerCase();
|
|
6358
6518
|
if (normalized === "pre" || normalized === "pre-build")
|
|
@@ -6375,20 +6535,16 @@ function resolveCategoryArg(raw) {
|
|
|
6375
6535
|
}
|
|
6376
6536
|
return parsed;
|
|
6377
6537
|
}
|
|
6378
|
-
function
|
|
6379
|
-
if (!profile)
|
|
6380
|
-
return report;
|
|
6381
|
-
const required = profile === "dtrpg" ? DTRPG_STRICT_PDF_CHECKS : [];
|
|
6382
|
-
if (required.length === 0)
|
|
6383
|
-
return report;
|
|
6538
|
+
function withTargetRequiredCheckErrors(report, tools, target) {
|
|
6384
6539
|
const skippedSet = new Set(tools.skippedChecks);
|
|
6385
|
-
const missingRequired =
|
|
6540
|
+
const missingRequired = target.requiredChecks.filter((checkId) => skippedSet.has(checkId));
|
|
6386
6541
|
if (missingRequired.length === 0)
|
|
6387
6542
|
return report;
|
|
6388
6543
|
const syntheticErrors = missingRequired.map((checkId) => ({
|
|
6389
6544
|
checkId,
|
|
6545
|
+
target: target.id,
|
|
6390
6546
|
severity: "error",
|
|
6391
|
-
message: `
|
|
6547
|
+
message: `Target ${target.id} requires check ${checkId}, but it was skipped because required tools are not available.`
|
|
6392
6548
|
}));
|
|
6393
6549
|
const results = [...report.results, ...syntheticErrors];
|
|
6394
6550
|
const errors = [...report.errors, ...syntheticErrors];
|
|
@@ -6402,19 +6558,115 @@ function withProfileRequiredCheckErrors(report, tools, profile) {
|
|
|
6402
6558
|
}
|
|
6403
6559
|
};
|
|
6404
6560
|
}
|
|
6561
|
+
var TARGET_INDEPENDENT_CATEGORIES = ["source", "heuristic"];
|
|
6562
|
+
var TARGET_DEPENDENT_CATEGORIES = ["asset", "pdf"];
|
|
6563
|
+
function tagReport(report, targetId) {
|
|
6564
|
+
const tag = (r) => ({ ...r, target: targetId });
|
|
6565
|
+
return {
|
|
6566
|
+
results: report.results.map(tag),
|
|
6567
|
+
errors: report.errors.map(tag),
|
|
6568
|
+
warnings: report.warnings.map(tag),
|
|
6569
|
+
infos: report.infos.map(tag),
|
|
6570
|
+
passed: report.passed,
|
|
6571
|
+
summary: report.summary
|
|
6572
|
+
};
|
|
6573
|
+
}
|
|
6574
|
+
function mergeReports(reports) {
|
|
6575
|
+
const merged = {
|
|
6576
|
+
results: [],
|
|
6577
|
+
errors: [],
|
|
6578
|
+
warnings: [],
|
|
6579
|
+
infos: [],
|
|
6580
|
+
passed: [],
|
|
6581
|
+
summary: { total: 0, errors: 0, warnings: 0, infos: 0, passed: 0 }
|
|
6582
|
+
};
|
|
6583
|
+
for (const r of reports) {
|
|
6584
|
+
merged.results.push(...r.results);
|
|
6585
|
+
merged.errors.push(...r.errors);
|
|
6586
|
+
merged.warnings.push(...r.warnings);
|
|
6587
|
+
merged.infos.push(...r.infos);
|
|
6588
|
+
merged.passed.push(...r.passed);
|
|
6589
|
+
merged.summary.total += r.summary.total;
|
|
6590
|
+
merged.summary.errors += r.summary.errors;
|
|
6591
|
+
merged.summary.warnings += r.summary.warnings;
|
|
6592
|
+
merged.summary.infos += r.summary.infos;
|
|
6593
|
+
merged.summary.passed += r.summary.passed;
|
|
6594
|
+
}
|
|
6595
|
+
return merged;
|
|
6596
|
+
}
|
|
6597
|
+
function mergeTools(results) {
|
|
6598
|
+
const available = new Set;
|
|
6599
|
+
const missing = new Set;
|
|
6600
|
+
const skippedChecks = new Set;
|
|
6601
|
+
const toolToChecks = new Map;
|
|
6602
|
+
for (const r of results) {
|
|
6603
|
+
for (const t of r.available)
|
|
6604
|
+
available.add(t);
|
|
6605
|
+
for (const t of r.missing)
|
|
6606
|
+
missing.add(t);
|
|
6607
|
+
for (const id of r.skippedChecks)
|
|
6608
|
+
skippedChecks.add(id);
|
|
6609
|
+
for (const [tool, ids] of r.toolToChecks) {
|
|
6610
|
+
const merged = new Set([...toolToChecks.get(tool) ?? [], ...ids]);
|
|
6611
|
+
toolToChecks.set(tool, [...merged]);
|
|
6612
|
+
}
|
|
6613
|
+
}
|
|
6614
|
+
return {
|
|
6615
|
+
available: [...available],
|
|
6616
|
+
missing: [...missing],
|
|
6617
|
+
skippedChecks: [...skippedChecks],
|
|
6618
|
+
toolToChecks
|
|
6619
|
+
};
|
|
6620
|
+
}
|
|
6621
|
+
function planRuns(opts) {
|
|
6622
|
+
const { manifest, baseConfig, targetIds, pdfPath, categories, only } = opts;
|
|
6623
|
+
const finalize = (config) => pdfPath ? applyDefaultPdfStrictChecks(config) : config;
|
|
6624
|
+
if (targetIds.length === 0) {
|
|
6625
|
+
return [{ targetId: null, config: finalize(baseConfig), category: categories, only }];
|
|
6626
|
+
}
|
|
6627
|
+
const requested = categories ?? [...TARGET_INDEPENDENT_CATEGORIES, ...TARGET_DEPENDENT_CATEGORIES];
|
|
6628
|
+
const independentCats = requested.filter((c) => TARGET_INDEPENDENT_CATEGORIES.includes(c));
|
|
6629
|
+
const dependentCats = requested.filter((c) => TARGET_DEPENDENT_CATEGORIES.includes(c));
|
|
6630
|
+
let baseOnly;
|
|
6631
|
+
let dependentOnly;
|
|
6632
|
+
if (only && only.length > 0) {
|
|
6633
|
+
const { resolved, unmatched } = resolveCheckSelectors(only);
|
|
6634
|
+
const categoryById = new Map(getChecks({}).map((c) => [c.id, c.category]));
|
|
6635
|
+
const independentIds = resolved.filter((id) => TARGET_INDEPENDENT_CATEGORIES.includes(categoryById.get(id) ?? ""));
|
|
6636
|
+
const dependentIds = resolved.filter((id) => TARGET_DEPENDENT_CATEGORIES.includes(categoryById.get(id) ?? ""));
|
|
6637
|
+
baseOnly = [...independentIds, ...unmatched];
|
|
6638
|
+
dependentOnly = dependentIds;
|
|
6639
|
+
}
|
|
6640
|
+
const runs = [];
|
|
6641
|
+
const baseWanted = only ? (baseOnly?.length ?? 0) > 0 : independentCats.length > 0;
|
|
6642
|
+
if (baseWanted) {
|
|
6643
|
+
runs.push({
|
|
6644
|
+
targetId: null,
|
|
6645
|
+
config: baseConfig,
|
|
6646
|
+
category: only ? undefined : independentCats,
|
|
6647
|
+
only: baseOnly
|
|
6648
|
+
});
|
|
6649
|
+
}
|
|
6650
|
+
const dependentWanted = only ? (dependentOnly?.length ?? 0) > 0 : dependentCats.length > 0;
|
|
6651
|
+
if (dependentWanted) {
|
|
6652
|
+
for (const targetId of targetIds) {
|
|
6653
|
+
runs.push({
|
|
6654
|
+
targetId,
|
|
6655
|
+
config: finalize(resolveConfigForTarget({}, manifest, targetId)),
|
|
6656
|
+
category: only ? undefined : dependentCats,
|
|
6657
|
+
only: dependentOnly
|
|
6658
|
+
});
|
|
6659
|
+
}
|
|
6660
|
+
}
|
|
6661
|
+
return runs;
|
|
6662
|
+
}
|
|
6405
6663
|
async function executeValidation(args) {
|
|
6406
6664
|
const manifestPath = typeof args.manifest === "string" ? args.manifest : undefined;
|
|
6407
6665
|
const { manifest, manifestDir } = await loadManifestWithPath(manifestPath ?? args.input ?? undefined, { explicit: manifestPath !== undefined });
|
|
6408
|
-
const
|
|
6409
|
-
|
|
6666
|
+
const config = resolveConfig({}, manifest);
|
|
6667
|
+
const targetIds = resolveTargets(parseCsv(typeof args.target === "string" ? args.target : undefined), config.targets);
|
|
6410
6668
|
const pdfPath = typeof args.pdf === "string" ? resolve9(args.pdf) : undefined;
|
|
6411
6669
|
const inputDir = typeof args.input === "string" ? resolve9(args.input) : undefined;
|
|
6412
|
-
if (pdfPath) {
|
|
6413
|
-
config = applyDtrpgPdfDefaults(config);
|
|
6414
|
-
}
|
|
6415
|
-
if (profile) {
|
|
6416
|
-
config = applyValidationProfile(config, profile);
|
|
6417
|
-
}
|
|
6418
6670
|
if (pdfPath && !existsSync11(pdfPath)) {
|
|
6419
6671
|
throw new Error(`File not found: ${pdfPath}`);
|
|
6420
6672
|
}
|
|
@@ -6443,11 +6695,11 @@ async function executeValidation(args) {
|
|
|
6443
6695
|
let htmlPath;
|
|
6444
6696
|
const outDir = resolveOutputDir(manifestDir, config.title);
|
|
6445
6697
|
if (inputDir) {
|
|
6446
|
-
const relMarkdown = await resolveActiveMarkdownFiles(
|
|
6447
|
-
markdownFiles = relMarkdown.map((f) => join6(
|
|
6448
|
-
const relStyles = await resolveActiveStyles(
|
|
6449
|
-
cssFiles = relStyles.map((rel) => resolve9(
|
|
6450
|
-
assetDirs = [inputDir];
|
|
6698
|
+
const relMarkdown = await resolveActiveMarkdownFiles(manifestDir, config.source.files);
|
|
6699
|
+
markdownFiles = relMarkdown.map((f) => join6(manifestDir, canonicalChapterId(f)));
|
|
6700
|
+
const relStyles = await resolveActiveStyles(manifestDir, config.styles);
|
|
6701
|
+
cssFiles = relStyles.map((rel) => resolve9(manifestDir, rel)).filter((f) => !f.endsWith(".min.css"));
|
|
6702
|
+
assetDirs = [inputDir, ...await sharedAssetDirs(manifestDir, relStyles, inputDir)];
|
|
6451
6703
|
const possibleHtml = join6(outDir, BOOK_HTML_FILENAME);
|
|
6452
6704
|
if (existsSync11(possibleHtml)) {
|
|
6453
6705
|
htmlPath = possibleHtml;
|
|
@@ -6469,15 +6721,30 @@ async function executeValidation(args) {
|
|
|
6469
6721
|
only,
|
|
6470
6722
|
skip
|
|
6471
6723
|
};
|
|
6472
|
-
const
|
|
6473
|
-
const
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
|
|
6724
|
+
const runs = planRuns({ manifest, baseConfig: config, targetIds, pdfPath, categories, only });
|
|
6725
|
+
const reports = [];
|
|
6726
|
+
const toolResults = [];
|
|
6727
|
+
for (const run2 of runs) {
|
|
6728
|
+
const runOptions = {
|
|
6729
|
+
category: run2.category,
|
|
6730
|
+
phase,
|
|
6731
|
+
only: run2.only,
|
|
6732
|
+
skip
|
|
6733
|
+
};
|
|
6734
|
+
const runTools = await checkToolAvailability(run2.config, runOptions);
|
|
6735
|
+
toolResults.push(runTools);
|
|
6736
|
+
let runReport = await runChecks({ ...context, config: run2.config }, { ...runOptions, skipMissingTools: runTools.skippedChecks });
|
|
6737
|
+
if (run2.targetId !== null) {
|
|
6738
|
+
runReport = withTargetRequiredCheckErrors(runReport, runTools, publishTargetFor(run2.targetId));
|
|
6739
|
+
runReport = tagReport(runReport, run2.targetId);
|
|
6740
|
+
}
|
|
6741
|
+
reports.push(runReport);
|
|
6742
|
+
}
|
|
6743
|
+
const tools = mergeTools(toolResults);
|
|
6744
|
+
const report = mergeReports(reports);
|
|
6478
6745
|
return {
|
|
6479
6746
|
config,
|
|
6480
|
-
|
|
6747
|
+
targets: targetIds,
|
|
6481
6748
|
context,
|
|
6482
6749
|
runnerOptions,
|
|
6483
6750
|
tools,
|
|
@@ -6555,7 +6822,7 @@ ${m.installHint}`).join(`
|
|
|
6555
6822
|
|
|
6556
6823
|
${list}
|
|
6557
6824
|
|
|
6558
|
-
Install the missing tools and re-run, or set GHOSTSCRIPT_PATH, CHROMIUM_PATH, or system PATH so gutterpress can find them. See the User Guide Chapter
|
|
6825
|
+
Install the missing tools and re-run, or set GHOSTSCRIPT_PATH, CHROMIUM_PATH, or system PATH so gutterpress can find them. See the User Guide Chapter 7 (System Setup) at examples/gutterpress-user-guide/07-system-setup.md for the full per-feature matrix.`, 2);
|
|
6559
6826
|
}
|
|
6560
6827
|
function computeGates(format, opts, config) {
|
|
6561
6828
|
if (format === "html") {
|
|
@@ -6649,6 +6916,15 @@ function resolveWithinRoot(relPath, root) {
|
|
|
6649
6916
|
}
|
|
6650
6917
|
return candidate;
|
|
6651
6918
|
}
|
|
6919
|
+
function hasDotSegment(urlPathname) {
|
|
6920
|
+
let decoded;
|
|
6921
|
+
try {
|
|
6922
|
+
decoded = decodeURIComponent(urlPathname);
|
|
6923
|
+
} catch {
|
|
6924
|
+
return true;
|
|
6925
|
+
}
|
|
6926
|
+
return decoded.split(/[/\\]/).some((segment) => segment.startsWith("."));
|
|
6927
|
+
}
|
|
6652
6928
|
function resolveStaticPath(urlPathname, root) {
|
|
6653
6929
|
let decoded;
|
|
6654
6930
|
try {
|
|
@@ -7047,14 +7323,25 @@ async function resolveBuildContext(opts) {
|
|
|
7047
7323
|
const outDir = target.kind === "file" ? path9.dirname(target.file) : target.dir;
|
|
7048
7324
|
const gates = computeGates(format, opts, config);
|
|
7049
7325
|
const workDir = target.kind === "project" ? path9.join(path9.dirname(target.dir), `.${path9.basename(target.dir)}-build-${randomBytes(6).toString("hex")}`) : path9.join(os2.tmpdir(), `gutterpress-build-${randomBytes(6).toString("hex")}`);
|
|
7050
|
-
return {
|
|
7326
|
+
return {
|
|
7327
|
+
opts,
|
|
7328
|
+
format,
|
|
7329
|
+
inputDir,
|
|
7330
|
+
renderDir: manifestDir,
|
|
7331
|
+
outDir,
|
|
7332
|
+
workDir,
|
|
7333
|
+
target,
|
|
7334
|
+
manifestDir,
|
|
7335
|
+
config,
|
|
7336
|
+
gates
|
|
7337
|
+
};
|
|
7051
7338
|
}
|
|
7052
7339
|
async function runQualityGates(ctx) {
|
|
7053
|
-
const { gates, opts,
|
|
7340
|
+
const { gates, opts, renderDir } = ctx;
|
|
7054
7341
|
if (gates.lint) {
|
|
7055
7342
|
log.info("Lint: CSS print-safety");
|
|
7056
7343
|
const lintResult = await runLint({
|
|
7057
|
-
manifest: opts.manifestPath ??
|
|
7344
|
+
manifest: opts.manifestPath ?? renderDir
|
|
7058
7345
|
});
|
|
7059
7346
|
if (!lintResult.ok) {
|
|
7060
7347
|
throw new BuildError("CSS lint failed", 2);
|
|
@@ -7063,7 +7350,7 @@ async function runQualityGates(ctx) {
|
|
|
7063
7350
|
if (gates.preValidate) {
|
|
7064
7351
|
log.info("Pre-build validation");
|
|
7065
7352
|
const result = await executeAndReport({
|
|
7066
|
-
input:
|
|
7353
|
+
input: renderDir,
|
|
7067
7354
|
phase: "pre-build",
|
|
7068
7355
|
manifest: opts.manifestPath
|
|
7069
7356
|
}, "text");
|
|
@@ -7073,7 +7360,7 @@ async function runQualityGates(ctx) {
|
|
|
7073
7360
|
}
|
|
7074
7361
|
}
|
|
7075
7362
|
async function renderBook(ctx) {
|
|
7076
|
-
const { config,
|
|
7363
|
+
const { config, renderDir, workDir } = ctx;
|
|
7077
7364
|
if (config.source.files && config.source.files.length > 0) {
|
|
7078
7365
|
log.info(`Using specified files (${config.source.files.length} total)`);
|
|
7079
7366
|
} else {
|
|
@@ -7082,13 +7369,13 @@ async function renderBook(ctx) {
|
|
|
7082
7369
|
if (config.plugins.length > 0) {
|
|
7083
7370
|
log.info(`Loading ${config.plugins.length} plugin(s)...`);
|
|
7084
7371
|
}
|
|
7085
|
-
const { plugins, pluginCss } = await loadPluginsWithCss(config.plugins,
|
|
7372
|
+
const { plugins, pluginCss } = await loadPluginsWithCss(config.plugins, renderDir);
|
|
7086
7373
|
if (plugins && plugins.length > 0) {
|
|
7087
7374
|
log.success(`Loaded ${plugins.length} plugin(s)`);
|
|
7088
7375
|
}
|
|
7089
7376
|
const imageRefs = [];
|
|
7090
7377
|
const cssAssets = [];
|
|
7091
|
-
const htmlFile = await renderChaptersToFile(
|
|
7378
|
+
const htmlFile = await renderChaptersToFile(renderDir, workDir, {
|
|
7092
7379
|
title: config.title,
|
|
7093
7380
|
styles: config.styles,
|
|
7094
7381
|
files: config.source.files,
|
|
@@ -7107,7 +7394,7 @@ async function renderBook(ctx) {
|
|
|
7107
7394
|
}
|
|
7108
7395
|
});
|
|
7109
7396
|
log.success(`Wrote ${htmlFile}`);
|
|
7110
|
-
const { copies: imageCopies, errors } = await planImageCopies(
|
|
7397
|
+
const { copies: imageCopies, errors } = await planImageCopies(renderDir, imageRefs);
|
|
7111
7398
|
if (errors.length > 0) {
|
|
7112
7399
|
throw new BuildError(`Cannot resolve ${errors.length} image reference(s):
|
|
7113
7400
|
` + errors.map((e) => ` - ${e}`).join(`
|
|
@@ -7146,7 +7433,8 @@ async function resolveIccProfile(icc, manifestDir, explicitIccPath) {
|
|
|
7146
7433
|
async function finalizeBuild(ctx, fingerprint, wroteMessage, paths, artifactName2 = null) {
|
|
7147
7434
|
const workFingerprint = await writeBuildFingerprint({
|
|
7148
7435
|
...fingerprint,
|
|
7149
|
-
outputDir: ctx.workDir
|
|
7436
|
+
outputDir: ctx.workDir,
|
|
7437
|
+
recordedOutputDir: fingerprint.outputDir
|
|
7150
7438
|
});
|
|
7151
7439
|
await publishBuild(ctx, artifactName2);
|
|
7152
7440
|
const delivered = ctx.target.kind !== "file";
|
|
@@ -7237,7 +7525,9 @@ class PdfOutput {
|
|
|
7237
7525
|
await renderHtmlToPdf(htmlFile, rawPdf, opts.pdfRenderer, staticHtmlRaw);
|
|
7238
7526
|
if (staticHtmlRaw && fs2.existsSync(staticHtmlRaw)) {
|
|
7239
7527
|
await finalizeStaticBook(await fsp3.readFile(staticHtmlRaw, "utf-8"), htmlFile, workDir);
|
|
7240
|
-
|
|
7528
|
+
if (ctx.target.kind !== "file") {
|
|
7529
|
+
log.success(`Wrote static desktop: ${path9.join(outDir, BOOK_HTML)}`);
|
|
7530
|
+
}
|
|
7241
7531
|
}
|
|
7242
7532
|
if (!pdfxMode) {
|
|
7243
7533
|
try {
|
|
@@ -7370,6 +7660,7 @@ async function renderPreviewBook(inputPath, config, opts) {
|
|
|
7370
7660
|
plugins,
|
|
7371
7661
|
pluginCss,
|
|
7372
7662
|
wrapChapters: opts.wrapChapters,
|
|
7663
|
+
...opts.onCssAssets ? { onCssAssets: opts.onCssAssets } : {},
|
|
7373
7664
|
onChapterWarnings: (file, warnings) => {
|
|
7374
7665
|
for (const w of warnings) {
|
|
7375
7666
|
warn2(` ${file}, line ${w.line}: ${w.message}`);
|
|
@@ -7384,15 +7675,24 @@ function injectPreviewScripts(html) {
|
|
|
7384
7675
|
return html.replace(pagedjsPolyfillTagRegex(), iface + BREAK_INSIDE_HANDLER + `
|
|
7385
7676
|
<script src="/vendor/paged.polyfill.js"></script>`);
|
|
7386
7677
|
}
|
|
7387
|
-
async function generateAndWriteHtml(inputPath, tempDir, config) {
|
|
7678
|
+
async function generateAndWriteHtml(inputPath, tempDir, config, cssAssets) {
|
|
7388
7679
|
if (!inputPath) {
|
|
7680
|
+
cssAssets.clear();
|
|
7389
7681
|
await fsp4.writeFile(path10.join(tempDir, BOOK_HTML_FILENAME), EMPTY_BOOK_HTML, "utf-8");
|
|
7390
7682
|
return;
|
|
7391
7683
|
}
|
|
7684
|
+
const nextAssets = new Map;
|
|
7392
7685
|
const html = await renderPreviewBook(inputPath, config, {
|
|
7393
7686
|
files: config.source?.files ?? null,
|
|
7394
|
-
wrapChapters: true
|
|
7687
|
+
wrapChapters: true,
|
|
7688
|
+
onCssAssets: (copies) => {
|
|
7689
|
+
for (const copy of copies)
|
|
7690
|
+
nextAssets.set(copy.to, copy.from);
|
|
7691
|
+
}
|
|
7395
7692
|
});
|
|
7693
|
+
cssAssets.clear();
|
|
7694
|
+
for (const [to, from] of nextAssets)
|
|
7695
|
+
cssAssets.set(to, from);
|
|
7396
7696
|
await fsp4.writeFile(path10.join(tempDir, BOOK_HTML_FILENAME), injectPreviewScripts(html), "utf-8");
|
|
7397
7697
|
}
|
|
7398
7698
|
function isDotPathUnderRoot(candidatePath, root) {
|
|
@@ -7410,11 +7710,24 @@ function isDotPathUnderRoot(candidatePath, root) {
|
|
|
7410
7710
|
return false;
|
|
7411
7711
|
return rel.split("/").some((segment) => segment.startsWith("."));
|
|
7412
7712
|
}
|
|
7713
|
+
function isGeneratedProjectPath(relative2) {
|
|
7714
|
+
const segments = relative2.split("/");
|
|
7715
|
+
for (let i = 0;i < segments.length; i++) {
|
|
7716
|
+
const segment = segments[i];
|
|
7717
|
+
if (segment === "dist" || segment === "node_modules")
|
|
7718
|
+
return true;
|
|
7719
|
+
if (segment === "plugins" && segments[i + 1] === "npm")
|
|
7720
|
+
return true;
|
|
7721
|
+
}
|
|
7722
|
+
return false;
|
|
7723
|
+
}
|
|
7413
7724
|
function isIgnoredWatchPath(candidatePath, projectRoot) {
|
|
7414
7725
|
const normalizedRoot = projectRoot.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
7415
7726
|
const normalizedPath = candidatePath.replace(/\\/g, "/");
|
|
7416
|
-
|
|
7417
|
-
|
|
7727
|
+
if (!normalizedPath.startsWith(normalizedRoot + "/"))
|
|
7728
|
+
return false;
|
|
7729
|
+
const relative2 = normalizedPath.slice(normalizedRoot.length + 1);
|
|
7730
|
+
return isDotPathUnderRoot(candidatePath, projectRoot) || isGeneratedProjectPath(relative2);
|
|
7418
7731
|
}
|
|
7419
7732
|
function isExternalWatchCandidate(candidatePath, targets) {
|
|
7420
7733
|
const candidate = path10.resolve(candidatePath);
|
|
@@ -7593,7 +7906,7 @@ function createFileWatcher(state) {
|
|
|
7593
7906
|
await syncExternalWatches();
|
|
7594
7907
|
if (closed)
|
|
7595
7908
|
return;
|
|
7596
|
-
await generateAndWriteHtml(inputResolved, state.tempDir, updatedConfig);
|
|
7909
|
+
await generateAndWriteHtml(inputResolved, state.tempDir, updatedConfig, state.cssAssets);
|
|
7597
7910
|
if (closed)
|
|
7598
7911
|
return;
|
|
7599
7912
|
state.previewServer?.broadcastReload();
|
|
@@ -7710,7 +8023,7 @@ async function restartPreview(newInputPath, state) {
|
|
|
7710
8023
|
state.currentInputPath = newInputPath;
|
|
7711
8024
|
const manifest = await loadManifest(newInputPath);
|
|
7712
8025
|
state.config = resolveConfig({}, manifest);
|
|
7713
|
-
await generateAndWriteHtml(newInputPath, state.tempDir, state.config);
|
|
8026
|
+
await generateAndWriteHtml(newInputPath, state.tempDir, state.config, state.cssAssets);
|
|
7714
8027
|
startFileWatcher(state);
|
|
7715
8028
|
state.previewServer?.broadcastReload();
|
|
7716
8029
|
info("Preview restarted successfully");
|
|
@@ -7768,7 +8081,8 @@ function createServerState(inputPath, tempDir, config, options) {
|
|
|
7768
8081
|
isShuttingDown: false,
|
|
7769
8082
|
tempDir,
|
|
7770
8083
|
config,
|
|
7771
|
-
options
|
|
8084
|
+
options,
|
|
8085
|
+
cssAssets: new Map
|
|
7772
8086
|
};
|
|
7773
8087
|
}
|
|
7774
8088
|
|
|
@@ -8001,15 +8315,6 @@ function matchesEmbedded(urlPathname) {
|
|
|
8001
8315
|
return true;
|
|
8002
8316
|
return EMBEDDED_PREFIXES.some((p) => urlPathname.startsWith(p));
|
|
8003
8317
|
}
|
|
8004
|
-
function isDotfileRequest(urlPathname) {
|
|
8005
|
-
let decoded;
|
|
8006
|
-
try {
|
|
8007
|
-
decoded = decodeURIComponent(urlPathname);
|
|
8008
|
-
} catch {
|
|
8009
|
-
return true;
|
|
8010
|
-
}
|
|
8011
|
-
return decoded.split("/").some((segment) => segment.startsWith("."));
|
|
8012
|
-
}
|
|
8013
8318
|
async function createPreviewServer(state, port) {
|
|
8014
8319
|
const clients = new Set;
|
|
8015
8320
|
const wss = new WebSocketServer({ noServer: true });
|
|
@@ -8060,8 +8365,18 @@ async function createPreviewServer(state, port) {
|
|
|
8060
8365
|
return;
|
|
8061
8366
|
}
|
|
8062
8367
|
const pathname = url.pathname === "/" ? "/book.html" : url.pathname;
|
|
8368
|
+
const cssAssetSource = state.cssAssets.get(pathname.replace(/^\/+/, ""));
|
|
8369
|
+
if (cssAssetSource) {
|
|
8370
|
+
await serveStatic(cssAssetSource, res).catch((err) => {
|
|
8371
|
+
if (!res.headersSent) {
|
|
8372
|
+
res.writeHead(500);
|
|
8373
|
+
res.end(`Internal Server Error: ${err.message}`);
|
|
8374
|
+
}
|
|
8375
|
+
});
|
|
8376
|
+
return;
|
|
8377
|
+
}
|
|
8063
8378
|
const servingProjectRoot = pathname !== "/book.html";
|
|
8064
|
-
if (servingProjectRoot && (!state.currentInputPath ||
|
|
8379
|
+
if (servingProjectRoot && (!state.currentInputPath || hasDotSegment(pathname))) {
|
|
8065
8380
|
res.writeHead(404);
|
|
8066
8381
|
res.end("Not Found");
|
|
8067
8382
|
return;
|
|
@@ -8146,8 +8461,8 @@ async function startPreviewServer(options) {
|
|
|
8146
8461
|
}
|
|
8147
8462
|
const config = await initializeConfiguration(inputPath);
|
|
8148
8463
|
const tempDir = await initializePreviewDirectories();
|
|
8149
|
-
await generateAndWriteHtml(inputPath, tempDir, config);
|
|
8150
8464
|
const state = createServerState(inputPath, tempDir, config, options);
|
|
8465
|
+
await generateAndWriteHtml(inputPath, tempDir, config, state.cssAssets);
|
|
8151
8466
|
const restartPreview2 = async (newInputPath) => {
|
|
8152
8467
|
await restartPreview(newInputPath, state);
|
|
8153
8468
|
};
|
|
@@ -8437,16 +8752,77 @@ async function getSystemDiagnostics() {
|
|
|
8437
8752
|
},
|
|
8438
8753
|
tools: [chromium, ...tools],
|
|
8439
8754
|
configDir: defaultConfigDir(),
|
|
8440
|
-
docsUrl: "https://github.com/dimm-city/gutterpress/blob/main/examples/gutterpress-user-guide/
|
|
8755
|
+
docsUrl: "https://github.com/dimm-city/gutterpress/blob/main/examples/gutterpress-user-guide/07-system-setup.md"
|
|
8441
8756
|
};
|
|
8442
8757
|
}
|
|
8443
8758
|
// src/lib/project-scaffold.ts
|
|
8444
|
-
import { access, copyFile, cp as cp2, mkdir as
|
|
8445
|
-
import { constants as FS, existsSync as
|
|
8759
|
+
import { access, copyFile, cp as cp2, mkdir as mkdir8, readdir as readdir6, readFile as readFile24, stat as stat5, writeFile as writeFile8 } from "node:fs/promises";
|
|
8760
|
+
import { constants as FS, existsSync as existsSync13 } from "node:fs";
|
|
8761
|
+
import path15 from "node:path";
|
|
8762
|
+
|
|
8763
|
+
// src/lib/manifest-doc.ts
|
|
8764
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
8765
|
+
import { randomUUID } from "node:crypto";
|
|
8766
|
+
import { mkdir as mkdir7, open, readFile as readFile23, rename as rename3, rm as rm2 } from "node:fs/promises";
|
|
8446
8767
|
import path14 from "node:path";
|
|
8768
|
+
import { parseDocument, isSeq, YAMLSeq } from "yaml";
|
|
8769
|
+
function resolveManifestPath(projectDir) {
|
|
8770
|
+
const existing = MANIFEST_FILENAMES.find((name) => existsSync12(path14.join(projectDir, name)));
|
|
8771
|
+
return path14.join(projectDir, existing ?? MANIFEST_FILENAMES[0]);
|
|
8772
|
+
}
|
|
8773
|
+
async function loadManifestDoc(projectDir) {
|
|
8774
|
+
const file = resolveManifestPath(projectDir);
|
|
8775
|
+
let text = "";
|
|
8776
|
+
try {
|
|
8777
|
+
text = await readFile23(file, "utf8");
|
|
8778
|
+
} catch (error2) {
|
|
8779
|
+
if (error2.code !== "ENOENT")
|
|
8780
|
+
throw error2;
|
|
8781
|
+
}
|
|
8782
|
+
return { doc: parseDocument(text), file };
|
|
8783
|
+
}
|
|
8784
|
+
async function writeManifestDoc(file, doc) {
|
|
8785
|
+
const dir = path14.dirname(file);
|
|
8786
|
+
await mkdir7(dir, { recursive: true });
|
|
8787
|
+
const temporary = path14.join(dir, `.${path14.basename(file)}.tmp-${process.pid}-${randomUUID()}`);
|
|
8788
|
+
try {
|
|
8789
|
+
const handle = await open(temporary, "wx", 438);
|
|
8790
|
+
try {
|
|
8791
|
+
await handle.writeFile(doc.toString(), "utf8");
|
|
8792
|
+
await handle.sync();
|
|
8793
|
+
} finally {
|
|
8794
|
+
await handle.close();
|
|
8795
|
+
}
|
|
8796
|
+
await rename3(temporary, file);
|
|
8797
|
+
const directory = await open(dir, "r").catch(() => null);
|
|
8798
|
+
if (directory) {
|
|
8799
|
+
await directory.sync().catch(() => {});
|
|
8800
|
+
await directory.close();
|
|
8801
|
+
}
|
|
8802
|
+
} finally {
|
|
8803
|
+
await rm2(temporary, { force: true });
|
|
8804
|
+
}
|
|
8805
|
+
}
|
|
8806
|
+
function ensureSeq(doc, key) {
|
|
8807
|
+
let seq = doc.get(key, true);
|
|
8808
|
+
if (!isSeq(seq)) {
|
|
8809
|
+
const fresh = new YAMLSeq(doc.schema);
|
|
8810
|
+
doc.set(key, fresh);
|
|
8811
|
+
seq = fresh;
|
|
8812
|
+
}
|
|
8813
|
+
return seq;
|
|
8814
|
+
}
|
|
8815
|
+
function scalarString(item) {
|
|
8816
|
+
if (item && typeof item === "object" && "value" in item) {
|
|
8817
|
+
const v = item.value;
|
|
8818
|
+
return typeof v === "string" ? v : null;
|
|
8819
|
+
}
|
|
8820
|
+
return typeof item === "string" ? item : null;
|
|
8821
|
+
}
|
|
8822
|
+
|
|
8823
|
+
// src/lib/project-scaffold.ts
|
|
8447
8824
|
var STARTER_THEME_FOR_TEMPLATE = {
|
|
8448
8825
|
book: "clean-book",
|
|
8449
|
-
ttrpg: "ttrpg-supplement",
|
|
8450
8826
|
zine: "zine",
|
|
8451
8827
|
technical: "technical-doc"
|
|
8452
8828
|
};
|
|
@@ -8466,6 +8842,39 @@ function escapeYamlScalar(value) {
|
|
|
8466
8842
|
return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
8467
8843
|
}
|
|
8468
8844
|
var DEFAULT_AUTHOR = "Anonymous";
|
|
8845
|
+
function isPositivePoints(value) {
|
|
8846
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
8847
|
+
}
|
|
8848
|
+
function requirePreset(preset, customPage) {
|
|
8849
|
+
if (!preset || !PRESET_IDS.includes(preset)) {
|
|
8850
|
+
const got = preset ? ` (got "${preset}")` : "";
|
|
8851
|
+
throw new CreateProjectErrorImpl("preset-required", `A preset is required to create a book${got}. Choose one of: ${PRESET_IDS.join(", ")}.`);
|
|
8852
|
+
}
|
|
8853
|
+
if (preset === "custom") {
|
|
8854
|
+
const missing = [];
|
|
8855
|
+
if (!customPage || !isPositivePoints(customPage.width))
|
|
8856
|
+
missing.push("page width");
|
|
8857
|
+
if (!customPage || !isPositivePoints(customPage.height))
|
|
8858
|
+
missing.push("page height");
|
|
8859
|
+
if (missing.length > 0) {
|
|
8860
|
+
throw new CreateProjectErrorImpl("custom-page-required", `The custom preset needs a trim size: ${missing.join(" and ")} in points ` + `(72pt = 1in — e.g. US Letter is 612 x 792).`);
|
|
8861
|
+
}
|
|
8862
|
+
} else if (customPage && (!isPositivePoints(customPage.width) || !isPositivePoints(customPage.height))) {
|
|
8863
|
+
throw new CreateProjectErrorImpl("custom-page-required", "Page overrides need a positive width and height in points (72pt = 1in).");
|
|
8864
|
+
}
|
|
8865
|
+
if (customPage?.tolerance !== undefined && !isPositivePoints(customPage.tolerance)) {
|
|
8866
|
+
throw new CreateProjectErrorImpl("custom-page-required", "Page tolerance must be a positive number of points.");
|
|
8867
|
+
}
|
|
8868
|
+
return preset;
|
|
8869
|
+
}
|
|
8870
|
+
function resolveScaffoldTargets(requested, preset) {
|
|
8871
|
+
const ids = requested ?? [...PRESETS[preset].defaultTargets];
|
|
8872
|
+
const unknown = ids.filter((id) => !TARGETS[id]);
|
|
8873
|
+
if (unknown.length > 0) {
|
|
8874
|
+
throw new CreateProjectErrorImpl("invalid-targets", `Unknown publish target${unknown.length > 1 ? "s" : ""} "${unknown.join('", "')}". ` + `Known targets: ${TARGET_IDS.join(", ")}.`);
|
|
8875
|
+
}
|
|
8876
|
+
return [...new Set(ids)];
|
|
8877
|
+
}
|
|
8469
8878
|
async function scaffoldProject(options) {
|
|
8470
8879
|
const name = (options.name ?? "").trim();
|
|
8471
8880
|
if (!name) {
|
|
@@ -8477,7 +8886,7 @@ async function scaffoldProject(options) {
|
|
|
8477
8886
|
throw new CreateProjectErrorImpl("invalid-name", `Could not derive a valid folder name from "${name}".`);
|
|
8478
8887
|
}
|
|
8479
8888
|
const parentDir = options.parentDir;
|
|
8480
|
-
if (!parentDir || !
|
|
8889
|
+
if (!parentDir || !path15.isAbsolute(parentDir)) {
|
|
8481
8890
|
throw new CreateProjectErrorImpl("parent-not-writable", "The save location must be an absolute path.");
|
|
8482
8891
|
}
|
|
8483
8892
|
try {
|
|
@@ -8485,7 +8894,7 @@ async function scaffoldProject(options) {
|
|
|
8485
8894
|
} catch {
|
|
8486
8895
|
throw new CreateProjectErrorImpl("parent-not-writable", `The chosen save location can't be written to: ${parentDir}`);
|
|
8487
8896
|
}
|
|
8488
|
-
const projectDir =
|
|
8897
|
+
const projectDir = path15.join(parentDir, folderName);
|
|
8489
8898
|
let targetExists = true;
|
|
8490
8899
|
try {
|
|
8491
8900
|
await access(projectDir, FS.F_OK);
|
|
@@ -8497,26 +8906,28 @@ async function scaffoldProject(options) {
|
|
|
8497
8906
|
}
|
|
8498
8907
|
const template = options.template ?? "book";
|
|
8499
8908
|
const customTemplateDir = options.templateDir;
|
|
8909
|
+
const preset = customTemplateDir && options.preset === undefined ? undefined : requirePreset(options.preset, options.customPage);
|
|
8910
|
+
const targets = preset ? resolveScaffoldTargets(options.targets, preset) : undefined;
|
|
8500
8911
|
try {
|
|
8501
|
-
await
|
|
8912
|
+
await mkdir8(projectDir, { recursive: true });
|
|
8502
8913
|
if (customTemplateDir) {
|
|
8503
8914
|
const entries = await readdir6(customTemplateDir, { withFileTypes: true });
|
|
8504
8915
|
for (const entry of entries) {
|
|
8505
8916
|
if (entry.name === ".gutterpress-template.json")
|
|
8506
8917
|
continue;
|
|
8507
|
-
await cp2(
|
|
8918
|
+
await cp2(path15.join(customTemplateDir, entry.name), path15.join(projectDir, entry.name), { recursive: true });
|
|
8508
8919
|
}
|
|
8509
|
-
await
|
|
8920
|
+
await mkdir8(path15.join(projectDir, "assets"), { recursive: true });
|
|
8510
8921
|
} else {
|
|
8511
8922
|
const tplManifest = await getAssetPath(`templates/${template}/manifest.yaml`);
|
|
8512
8923
|
const tplChapter = await getAssetPath(`templates/${template}/chapter-01.md`);
|
|
8513
|
-
await
|
|
8514
|
-
await copyFile(tplManifest,
|
|
8515
|
-
await copyFile(tplChapter,
|
|
8924
|
+
await mkdir8(path15.join(projectDir, "assets"), { recursive: true });
|
|
8925
|
+
await copyFile(tplManifest, path15.join(projectDir, MANIFEST_FILENAMES[0]));
|
|
8926
|
+
await copyFile(tplChapter, path15.join(projectDir, "chapter-01.md"));
|
|
8516
8927
|
const starterThemeId = STARTER_THEME_FOR_TEMPLATE[template] ?? "clean-book";
|
|
8517
8928
|
const starterCss = await getAssetPath(`themes/${starterThemeId}/theme.css`);
|
|
8518
|
-
await
|
|
8519
|
-
await copyFile(starterCss,
|
|
8929
|
+
await mkdir8(path15.join(projectDir, "styles"), { recursive: true });
|
|
8930
|
+
await copyFile(starterCss, path15.join(projectDir, "styles", "book.css"));
|
|
8520
8931
|
}
|
|
8521
8932
|
} catch (e) {
|
|
8522
8933
|
throw new CreateProjectErrorImpl("scaffold-io", `Could not create the project files: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -8526,18 +8937,34 @@ async function scaffoldProject(options) {
|
|
|
8526
8937
|
} catch (e) {
|
|
8527
8938
|
throw new CreateProjectErrorImpl("scaffold-io", `Could not write .gitignore: ${e instanceof Error ? e.message : String(e)}`);
|
|
8528
8939
|
}
|
|
8529
|
-
const manifestPath = MANIFEST_FILENAMES.map((name2) =>
|
|
8940
|
+
const manifestPath = MANIFEST_FILENAMES.map((name2) => path15.join(projectDir, name2)).find(existsSync13) ?? path15.join(projectDir, MANIFEST_FILENAMES[0]);
|
|
8530
8941
|
const author = (options.author ?? "").trim() || DEFAULT_AUTHOR;
|
|
8531
8942
|
const substitutions = {
|
|
8532
8943
|
"{{TITLE}}": escapeYamlScalar(name),
|
|
8533
8944
|
"{{AUTHOR}}": escapeYamlScalar(author)
|
|
8534
8945
|
};
|
|
8535
|
-
let openFile =
|
|
8946
|
+
let openFile = path15.join(projectDir, "chapter-01.md");
|
|
8536
8947
|
try {
|
|
8537
8948
|
await fillTemplateFile(manifestPath, substitutions);
|
|
8949
|
+
if (preset) {
|
|
8950
|
+
const { doc, file } = await loadManifestDoc(projectDir);
|
|
8951
|
+
doc.set("preset", preset);
|
|
8952
|
+
doc.set("targets", doc.createNode(targets ?? []));
|
|
8953
|
+
if (options.customPage) {
|
|
8954
|
+
const page = {
|
|
8955
|
+
width: options.customPage.width,
|
|
8956
|
+
height: options.customPage.height
|
|
8957
|
+
};
|
|
8958
|
+
if (options.customPage.tolerance !== undefined) {
|
|
8959
|
+
page.tolerance = options.customPage.tolerance;
|
|
8960
|
+
}
|
|
8961
|
+
doc.set("page", doc.createNode(page));
|
|
8962
|
+
}
|
|
8963
|
+
await writeManifestDoc(file, doc);
|
|
8964
|
+
}
|
|
8538
8965
|
const firstSource = await firstSourceFile(manifestPath);
|
|
8539
8966
|
if (firstSource)
|
|
8540
|
-
openFile =
|
|
8967
|
+
openFile = path15.join(projectDir, firstSource);
|
|
8541
8968
|
await fillTemplateFile(openFile, { "{{TITLE}}": name }).catch(() => {});
|
|
8542
8969
|
} catch (e) {
|
|
8543
8970
|
throw new CreateProjectErrorImpl("scaffold-io", `Could not finalise the project files: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -8547,7 +8974,7 @@ async function scaffoldProject(options) {
|
|
|
8547
8974
|
let versionHistoryError;
|
|
8548
8975
|
if (requested === "local-git") {
|
|
8549
8976
|
try {
|
|
8550
|
-
const { providerFor } = await import("./source-provider-
|
|
8977
|
+
const { providerFor } = await import("./source-provider-c1rjm2c0.js");
|
|
8551
8978
|
const provider = providerFor({ type: "local-folder", path: projectDir });
|
|
8552
8979
|
await provider.initVersionHistory({
|
|
8553
8980
|
projectDir,
|
|
@@ -8557,9 +8984,9 @@ async function scaffoldProject(options) {
|
|
|
8557
8984
|
versionHistory = "local-git";
|
|
8558
8985
|
} catch (e) {
|
|
8559
8986
|
versionHistory = "none";
|
|
8560
|
-
const { findEnclosingRepoDir } = await import("./project-source-
|
|
8987
|
+
const { findEnclosingRepoDir } = await import("./project-source-p0gn1wd5.js");
|
|
8561
8988
|
const enclosingRepoDir = await findEnclosingRepoDir(projectDir);
|
|
8562
|
-
versionHistoryError = enclosingRepoDir ? `This book was added to ${
|
|
8989
|
+
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);
|
|
8563
8990
|
}
|
|
8564
8991
|
}
|
|
8565
8992
|
const result = {
|
|
@@ -8579,7 +9006,7 @@ function prettifyFolderName(base) {
|
|
|
8579
9006
|
}
|
|
8580
9007
|
async function adoptFolder(options) {
|
|
8581
9008
|
const dir = options.dir;
|
|
8582
|
-
if (typeof dir !== "string" || !
|
|
9009
|
+
if (typeof dir !== "string" || !path15.isAbsolute(dir)) {
|
|
8583
9010
|
throw new CreateProjectErrorImpl("scaffold-io", "An absolute folder path is required.");
|
|
8584
9011
|
}
|
|
8585
9012
|
let st;
|
|
@@ -8591,11 +9018,11 @@ async function adoptFolder(options) {
|
|
|
8591
9018
|
if (!st.isDirectory()) {
|
|
8592
9019
|
throw new CreateProjectErrorImpl("scaffold-io", `Not a folder: ${dir}`);
|
|
8593
9020
|
}
|
|
8594
|
-
if (MANIFEST_FILENAMES.some((name) =>
|
|
9021
|
+
if (MANIFEST_FILENAMES.some((name) => existsSync13(path15.join(dir, name)))) {
|
|
8595
9022
|
throw new CreateProjectErrorImpl("target-exists", "This folder is already a Gutterpress project.");
|
|
8596
9023
|
}
|
|
8597
9024
|
const template = options.template ?? "book";
|
|
8598
|
-
const title = (options.title ?? "").trim() || prettifyFolderName(
|
|
9025
|
+
const title = (options.title ?? "").trim() || prettifyFolderName(path15.basename(dir));
|
|
8599
9026
|
const author = (options.author ?? "").trim() || DEFAULT_AUTHOR;
|
|
8600
9027
|
let mdFiles;
|
|
8601
9028
|
try {
|
|
@@ -8603,14 +9030,14 @@ async function adoptFolder(options) {
|
|
|
8603
9030
|
mdFiles = entries.filter((e) => e.isFile() && /\.(md|markdown)$/i.test(e.name)).map((e) => e.name).sort((a, b) => a.localeCompare(b));
|
|
8604
9031
|
if (mdFiles.length === 0) {
|
|
8605
9032
|
const tplChapter = await getAssetPath(`templates/${template}/chapter-01.md`);
|
|
8606
|
-
await copyFile(tplChapter,
|
|
8607
|
-
await fillTemplateFile(
|
|
9033
|
+
await copyFile(tplChapter, path15.join(dir, "chapter-01.md"));
|
|
9034
|
+
await fillTemplateFile(path15.join(dir, "chapter-01.md"), { "{{TITLE}}": title }).catch(() => {});
|
|
8608
9035
|
mdFiles = ["chapter-01.md"];
|
|
8609
9036
|
}
|
|
8610
9037
|
const themeId = STARTER_THEME_FOR_TEMPLATE[template] ?? "clean-book";
|
|
8611
|
-
await
|
|
8612
|
-
const bookCssPath =
|
|
8613
|
-
if (!
|
|
9038
|
+
await mkdir8(path15.join(dir, "styles"), { recursive: true });
|
|
9039
|
+
const bookCssPath = path15.join(dir, "styles", "book.css");
|
|
9040
|
+
if (!existsSync13(bookCssPath)) {
|
|
8614
9041
|
await copyFile(await getAssetPath(`themes/${themeId}/theme.css`), bookCssPath);
|
|
8615
9042
|
}
|
|
8616
9043
|
const filesYaml = mdFiles.map((f) => ` - "${escapeYamlScalar(f)}"`).join(`
|
|
@@ -8618,14 +9045,17 @@ async function adoptFolder(options) {
|
|
|
8618
9045
|
const manifest = `title: "${escapeYamlScalar(title)}"
|
|
8619
9046
|
authors:
|
|
8620
9047
|
- "${escapeYamlScalar(author)}"
|
|
9048
|
+
preset: dtrpg
|
|
9049
|
+
targets:
|
|
9050
|
+
- dtrpg
|
|
8621
9051
|
source:
|
|
8622
9052
|
files:
|
|
8623
9053
|
${filesYaml}
|
|
8624
9054
|
styles:
|
|
8625
9055
|
- styles/book.css
|
|
8626
9056
|
`;
|
|
8627
|
-
await writeFile8(
|
|
8628
|
-
await
|
|
9057
|
+
await writeFile8(path15.join(dir, MANIFEST_FILENAMES[0]), manifest, "utf8");
|
|
9058
|
+
await mkdir8(path15.join(dir, "assets"), { recursive: true });
|
|
8629
9059
|
await ensureGitignoreHasDist(dir);
|
|
8630
9060
|
} catch (e) {
|
|
8631
9061
|
if (e instanceof CreateProjectErrorImpl)
|
|
@@ -8637,7 +9067,7 @@ styles:
|
|
|
8637
9067
|
let versionHistoryError;
|
|
8638
9068
|
if (requested === "local-git") {
|
|
8639
9069
|
try {
|
|
8640
|
-
const { providerFor } = await import("./source-provider-
|
|
9070
|
+
const { providerFor } = await import("./source-provider-c1rjm2c0.js");
|
|
8641
9071
|
const provider = providerFor({ type: "local-folder", path: dir });
|
|
8642
9072
|
await provider.initVersionHistory({
|
|
8643
9073
|
projectDir: dir,
|
|
@@ -8652,8 +9082,8 @@ styles:
|
|
|
8652
9082
|
}
|
|
8653
9083
|
const result = {
|
|
8654
9084
|
projectDir: dir,
|
|
8655
|
-
manifestPath:
|
|
8656
|
-
openFile:
|
|
9085
|
+
manifestPath: path15.join(dir, MANIFEST_FILENAMES[0]),
|
|
9086
|
+
openFile: path15.join(dir, mdFiles[0]),
|
|
8657
9087
|
versionHistory
|
|
8658
9088
|
};
|
|
8659
9089
|
if (versionHistoryError !== undefined)
|
|
@@ -8661,10 +9091,10 @@ styles:
|
|
|
8661
9091
|
return result;
|
|
8662
9092
|
}
|
|
8663
9093
|
async function ensureGitignoreHasDist(projectDir) {
|
|
8664
|
-
const gitignorePath =
|
|
9094
|
+
const gitignorePath = path15.join(projectDir, ".gitignore");
|
|
8665
9095
|
let existing = null;
|
|
8666
9096
|
try {
|
|
8667
|
-
existing = await
|
|
9097
|
+
existing = await readFile24(gitignorePath, "utf8");
|
|
8668
9098
|
} catch {}
|
|
8669
9099
|
if (existing === null) {
|
|
8670
9100
|
await writeFile8(gitignorePath, `dist/
|
|
@@ -8684,7 +9114,7 @@ async function ensureGitignoreHasDist(projectDir) {
|
|
|
8684
9114
|
async function firstSourceFile(manifestPath) {
|
|
8685
9115
|
let text;
|
|
8686
9116
|
try {
|
|
8687
|
-
text = await
|
|
9117
|
+
text = await readFile24(manifestPath, "utf8");
|
|
8688
9118
|
} catch {
|
|
8689
9119
|
return;
|
|
8690
9120
|
}
|
|
@@ -8696,32 +9126,45 @@ async function firstSourceFile(manifestPath) {
|
|
|
8696
9126
|
return m ? m[1].trim() : undefined;
|
|
8697
9127
|
}
|
|
8698
9128
|
async function fillTemplateFile(filePath2, substitutions) {
|
|
8699
|
-
let text = await
|
|
9129
|
+
let text = await readFile24(filePath2, "utf8");
|
|
8700
9130
|
for (const [token, value] of Object.entries(substitutions)) {
|
|
8701
9131
|
text = text.split(token).join(value);
|
|
8702
9132
|
}
|
|
8703
9133
|
await writeFile8(filePath2, text, "utf8");
|
|
8704
9134
|
}
|
|
8705
9135
|
// src/lib/project-templates.ts
|
|
8706
|
-
import { cp as cp3, mkdir as
|
|
8707
|
-
import { constants as FS2 } from "node:fs";
|
|
9136
|
+
import { cp as cp3, mkdir as mkdir9, readFile as readFile25, readdir as readdir7, stat as stat6, writeFile as writeFile9 } from "node:fs/promises";
|
|
9137
|
+
import { constants as FS2, existsSync as existsSync14 } from "node:fs";
|
|
8708
9138
|
import { access as access2 } from "node:fs/promises";
|
|
8709
|
-
import
|
|
9139
|
+
import path16 from "node:path";
|
|
9140
|
+
import { isMap, isScalar, isSeq as isSeq2, parseDocument as parseDocument2 } from "yaml";
|
|
8710
9141
|
var BUILT_IN_TEMPLATE_IDS = [
|
|
8711
9142
|
"book",
|
|
8712
|
-
"ttrpg",
|
|
8713
9143
|
"zine",
|
|
8714
9144
|
"technical"
|
|
8715
9145
|
];
|
|
9146
|
+
async function readTemplateChoices(manifestPath) {
|
|
9147
|
+
try {
|
|
9148
|
+
const text = await readFile25(manifestPath, "utf8");
|
|
9149
|
+
const doc = parseDocument2(text);
|
|
9150
|
+
const out = {};
|
|
9151
|
+
const preset = doc.get("preset");
|
|
9152
|
+
if (typeof preset === "string" && preset)
|
|
9153
|
+
out.preset = preset;
|
|
9154
|
+
const targets = doc.get("targets", true);
|
|
9155
|
+
if (isSeq2(targets)) {
|
|
9156
|
+
out.targets = targets.items.map((i) => scalarString(i)).filter((s) => s !== null);
|
|
9157
|
+
}
|
|
9158
|
+
return out;
|
|
9159
|
+
} catch {
|
|
9160
|
+
return {};
|
|
9161
|
+
}
|
|
9162
|
+
}
|
|
8716
9163
|
var BUILT_IN_META = {
|
|
8717
9164
|
book: {
|
|
8718
9165
|
label: "Book",
|
|
8719
9166
|
description: "A clean starting point for a novel, memoir, or any long-form book."
|
|
8720
9167
|
},
|
|
8721
|
-
ttrpg: {
|
|
8722
|
-
label: "TTRPG supplement",
|
|
8723
|
-
description: "Rules, stat blocks, and tables for a tabletop roleplaying supplement."
|
|
8724
|
-
},
|
|
8725
9168
|
zine: {
|
|
8726
9169
|
label: "Zine",
|
|
8727
9170
|
description: "A short, personal zine made to be printed, folded, and shared."
|
|
@@ -8732,12 +9175,13 @@ var BUILT_IN_META = {
|
|
|
8732
9175
|
}
|
|
8733
9176
|
};
|
|
8734
9177
|
async function listBuiltInTemplates() {
|
|
8735
|
-
return BUILT_IN_TEMPLATE_IDS.map((id) => ({
|
|
9178
|
+
return Promise.all(BUILT_IN_TEMPLATE_IDS.map(async (id) => ({
|
|
8736
9179
|
id,
|
|
8737
9180
|
label: BUILT_IN_META[id].label,
|
|
8738
9181
|
description: BUILT_IN_META[id].description,
|
|
8739
|
-
kind: "builtin"
|
|
8740
|
-
|
|
9182
|
+
kind: "builtin",
|
|
9183
|
+
...await readTemplateChoices(await getAssetPath(`templates/${id}/manifest.yaml`))
|
|
9184
|
+
})));
|
|
8741
9185
|
}
|
|
8742
9186
|
var SKIP_ENTRIES = new Set([
|
|
8743
9187
|
".git",
|
|
@@ -8748,13 +9192,100 @@ var SKIP_ENTRIES = new Set([
|
|
|
8748
9192
|
".gutterpress",
|
|
8749
9193
|
".DS_Store"
|
|
8750
9194
|
]);
|
|
9195
|
+
var VENDOR_DIR = "shared";
|
|
9196
|
+
function collectEscaping(nodes, getValue, escapes) {
|
|
9197
|
+
const out = [];
|
|
9198
|
+
for (const node of nodes) {
|
|
9199
|
+
const value = getValue(node);
|
|
9200
|
+
if (value !== null && escapes(value))
|
|
9201
|
+
out.push({ node, value });
|
|
9202
|
+
}
|
|
9203
|
+
return out;
|
|
9204
|
+
}
|
|
9205
|
+
function pluginEntryPath(node) {
|
|
9206
|
+
return isMap(node) ? scalarString(node.get("path", true)) : scalarString(node);
|
|
9207
|
+
}
|
|
9208
|
+
function dropEscaping(doc, seq, key, escaping) {
|
|
9209
|
+
if (!isSeq2(seq))
|
|
9210
|
+
return;
|
|
9211
|
+
const drop = new Set(escaping.map((e) => e.node));
|
|
9212
|
+
seq.items = seq.items.filter((node) => !drop.has(node));
|
|
9213
|
+
if (seq.items.length === 0)
|
|
9214
|
+
doc.delete(key);
|
|
9215
|
+
}
|
|
9216
|
+
function rewriteEntry(node, newPath) {
|
|
9217
|
+
if (isMap(node))
|
|
9218
|
+
node.set("path", newPath);
|
|
9219
|
+
else if (isScalar(node))
|
|
9220
|
+
node.value = newPath;
|
|
9221
|
+
}
|
|
9222
|
+
function commonAncestorDir(absFiles) {
|
|
9223
|
+
const dirSegs = absFiles.map((f) => path16.resolve(f).split(path16.sep).slice(0, -1));
|
|
9224
|
+
if (dirSegs.length === 0)
|
|
9225
|
+
return "";
|
|
9226
|
+
let common = dirSegs[0];
|
|
9227
|
+
for (const segs of dirSegs.slice(1)) {
|
|
9228
|
+
let i = 0;
|
|
9229
|
+
while (i < common.length && i < segs.length && common[i] === segs[i])
|
|
9230
|
+
i++;
|
|
9231
|
+
common = common.slice(0, i);
|
|
9232
|
+
}
|
|
9233
|
+
return common.join(path16.sep) || path16.sep;
|
|
9234
|
+
}
|
|
9235
|
+
function pickVendorDir(templateDir, base) {
|
|
9236
|
+
const preferred = path16.basename(base) || VENDOR_DIR;
|
|
9237
|
+
if (!existsSync14(path16.join(templateDir, preferred)))
|
|
9238
|
+
return preferred;
|
|
9239
|
+
for (let n = 1;; n++) {
|
|
9240
|
+
const candidate = `${preferred}-${n}`;
|
|
9241
|
+
if (!existsSync14(path16.join(templateDir, candidate)))
|
|
9242
|
+
return candidate;
|
|
9243
|
+
}
|
|
9244
|
+
}
|
|
8751
9245
|
var TEMPLATE_META_FILE = ".gutterpress-template.json";
|
|
9246
|
+
async function reconcileSharedRefs(templateDir, sourceProjectDir, mode) {
|
|
9247
|
+
const { doc, file } = await loadManifestDoc(templateDir);
|
|
9248
|
+
const escapes = (p) => escapesProjectRoot(sourceProjectDir, path16.resolve(sourceProjectDir, p));
|
|
9249
|
+
const stylesSeq = doc.get("styles", true);
|
|
9250
|
+
const pluginsSeq = doc.get("plugins", true);
|
|
9251
|
+
const escapingStyles = collectEscaping(isSeq2(stylesSeq) ? stylesSeq.items : [], scalarString, escapes);
|
|
9252
|
+
const escapingPlugins = collectEscaping(isSeq2(pluginsSeq) ? pluginsSeq.items : [], pluginEntryPath, escapes);
|
|
9253
|
+
if (escapingStyles.length === 0 && escapingPlugins.length === 0) {
|
|
9254
|
+
return { vendoredRefs: [], excludedRefs: [] };
|
|
9255
|
+
}
|
|
9256
|
+
if (mode === "exclude") {
|
|
9257
|
+
const excludedRefs = [...escapingStyles, ...escapingPlugins].map((e) => e.value);
|
|
9258
|
+
dropEscaping(doc, stylesSeq, "styles", escapingStyles);
|
|
9259
|
+
dropEscaping(doc, pluginsSeq, "plugins", escapingPlugins);
|
|
9260
|
+
await writeManifestDoc(file, doc);
|
|
9261
|
+
return { vendoredRefs: [], excludedRefs };
|
|
9262
|
+
}
|
|
9263
|
+
const styleClosure = await collectStyleDependencies(sourceProjectDir, escapingStyles.map((s) => s.value));
|
|
9264
|
+
const pluginFiles = escapingPlugins.map((p) => path16.resolve(sourceProjectDir, p.value));
|
|
9265
|
+
const externalFiles = [...new Set([...styleClosure, ...pluginFiles])].filter((f) => escapesProjectRoot(sourceProjectDir, f));
|
|
9266
|
+
const base = commonAncestorDir(externalFiles);
|
|
9267
|
+
const vendorDir = pickVendorDir(templateDir, base);
|
|
9268
|
+
await Promise.all(externalFiles.filter((abs2) => existsSync14(abs2)).map(async (abs2) => {
|
|
9269
|
+
const dest = path16.join(templateDir, vendorDir, path16.relative(base, abs2));
|
|
9270
|
+
await mkdir9(path16.dirname(dest), { recursive: true });
|
|
9271
|
+
await cp3(abs2, dest);
|
|
9272
|
+
}));
|
|
9273
|
+
const bookLocal = (value) => toPosixPath(path16.join(vendorDir, path16.relative(base, path16.resolve(sourceProjectDir, value))));
|
|
9274
|
+
const vendoredRefs = [];
|
|
9275
|
+
for (const { node, value } of [...escapingStyles, ...escapingPlugins]) {
|
|
9276
|
+
const rewritten = bookLocal(value);
|
|
9277
|
+
rewriteEntry(node, rewritten);
|
|
9278
|
+
vendoredRefs.push(rewritten);
|
|
9279
|
+
}
|
|
9280
|
+
await writeManifestDoc(file, doc);
|
|
9281
|
+
return { vendoredRefs, excludedRefs: [] };
|
|
9282
|
+
}
|
|
8752
9283
|
async function saveProjectAsTemplate(options) {
|
|
8753
9284
|
const { projectDir, name, templatesRoot } = options;
|
|
8754
9285
|
const id = slugify(name);
|
|
8755
9286
|
if (!id)
|
|
8756
9287
|
throw new Error(`Could not derive a template id from "${name}".`);
|
|
8757
|
-
const dir =
|
|
9288
|
+
const dir = path16.join(templatesRoot, id);
|
|
8758
9289
|
let exists = true;
|
|
8759
9290
|
try {
|
|
8760
9291
|
await access2(dir, FS2.F_OK);
|
|
@@ -8764,31 +9295,33 @@ async function saveProjectAsTemplate(options) {
|
|
|
8764
9295
|
if (exists) {
|
|
8765
9296
|
throw new Error(`A template named "${name}" already exists. Choose a different name.`);
|
|
8766
9297
|
}
|
|
8767
|
-
await
|
|
9298
|
+
await mkdir9(dir, { recursive: true });
|
|
8768
9299
|
const entries = await readdir7(projectDir, { withFileTypes: true });
|
|
8769
9300
|
for (const entry of entries) {
|
|
8770
9301
|
if (SKIP_ENTRIES.has(entry.name))
|
|
8771
9302
|
continue;
|
|
8772
9303
|
if (entry.name === TEMPLATE_META_FILE)
|
|
8773
9304
|
continue;
|
|
8774
|
-
await cp3(
|
|
9305
|
+
await cp3(path16.join(projectDir, entry.name), path16.join(dir, entry.name), { recursive: true });
|
|
8775
9306
|
}
|
|
8776
9307
|
const manifestName = MANIFEST_FILENAMES.find((name2) => entries.some((entry) => entry.name === name2));
|
|
9308
|
+
const shared = manifestName ? await reconcileSharedRefs(dir, projectDir, options.sharedRefs ?? "vendor") : { vendoredRefs: [], excludedRefs: [] };
|
|
8777
9309
|
if (manifestName)
|
|
8778
|
-
await retokeniseManifest(
|
|
8779
|
-
await writeFile9(
|
|
9310
|
+
await retokeniseManifest(path16.join(dir, manifestName));
|
|
9311
|
+
await writeFile9(path16.join(dir, TEMPLATE_META_FILE), JSON.stringify({ label: name }, null, 2), "utf8");
|
|
8780
9312
|
return {
|
|
8781
9313
|
id,
|
|
8782
9314
|
label: name,
|
|
8783
9315
|
description: "Your saved template.",
|
|
8784
9316
|
kind: "custom",
|
|
8785
|
-
dir
|
|
9317
|
+
dir,
|
|
9318
|
+
...shared
|
|
8786
9319
|
};
|
|
8787
9320
|
}
|
|
8788
9321
|
async function retokeniseManifest(manifestPath) {
|
|
8789
9322
|
let text;
|
|
8790
9323
|
try {
|
|
8791
|
-
text = await
|
|
9324
|
+
text = await readFile25(manifestPath, "utf8");
|
|
8792
9325
|
} catch {
|
|
8793
9326
|
return;
|
|
8794
9327
|
}
|
|
@@ -8806,14 +9339,21 @@ async function listCustomTemplates(templatesRoot) {
|
|
|
8806
9339
|
for (const entry of entries) {
|
|
8807
9340
|
if (!entry.isDirectory())
|
|
8808
9341
|
continue;
|
|
8809
|
-
const dir =
|
|
9342
|
+
const dir = path16.join(templatesRoot, entry.name);
|
|
8810
9343
|
let label = prettify(entry.name);
|
|
8811
9344
|
try {
|
|
8812
|
-
const meta = JSON.parse(await
|
|
9345
|
+
const meta = JSON.parse(await readFile25(path16.join(dir, TEMPLATE_META_FILE), "utf8"));
|
|
8813
9346
|
if (typeof meta.label === "string" && meta.label)
|
|
8814
9347
|
label = meta.label;
|
|
8815
9348
|
} catch {}
|
|
8816
|
-
out.push({
|
|
9349
|
+
out.push({
|
|
9350
|
+
id: entry.name,
|
|
9351
|
+
label,
|
|
9352
|
+
description: "Your saved template.",
|
|
9353
|
+
kind: "custom",
|
|
9354
|
+
dir,
|
|
9355
|
+
...await readTemplateChoices(path16.join(dir, MANIFEST_FILENAMES[0]))
|
|
9356
|
+
});
|
|
8817
9357
|
}
|
|
8818
9358
|
out.sort((a, b) => a.label.localeCompare(b.label));
|
|
8819
9359
|
return out;
|
|
@@ -8824,12 +9364,12 @@ async function importTemplateFromFolder(options) {
|
|
|
8824
9364
|
if (!srcStat.isDirectory()) {
|
|
8825
9365
|
throw new Error("The chosen path is not a folder.");
|
|
8826
9366
|
}
|
|
8827
|
-
const name = options.name?.trim() || prettify(
|
|
9367
|
+
const name = options.name?.trim() || prettify(path16.basename(sourceDir));
|
|
8828
9368
|
return saveProjectAsTemplate({ projectDir: sourceDir, name, templatesRoot });
|
|
8829
9369
|
}
|
|
8830
9370
|
// src/lib/snippets.ts
|
|
8831
|
-
import { readdir as readdir8, readFile as
|
|
8832
|
-
import
|
|
9371
|
+
import { readdir as readdir8, readFile as readFile26, writeFile as writeFile10, mkdir as mkdir10, rm as rm3 } from "node:fs/promises";
|
|
9372
|
+
import path17 from "node:path";
|
|
8833
9373
|
var SNIPPETS_DIR = "snippets";
|
|
8834
9374
|
var PLACEHOLDER_RE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
|
|
8835
9375
|
function extractVariables(template) {
|
|
@@ -8848,15 +9388,15 @@ function substituteVariables(template, values) {
|
|
|
8848
9388
|
return template.replace(PLACEHOLDER_RE, (_full, name) => Object.prototype.hasOwnProperty.call(values, name) ? values[name] : "");
|
|
8849
9389
|
}
|
|
8850
9390
|
function resolveSnippetPath(projectDir, fileName) {
|
|
8851
|
-
const dir =
|
|
8852
|
-
const full =
|
|
8853
|
-
if (full !==
|
|
9391
|
+
const dir = path17.resolve(projectDir, SNIPPETS_DIR);
|
|
9392
|
+
const full = path17.resolve(dir, fileName);
|
|
9393
|
+
if (full !== path17.join(dir, path17.basename(fileName)) || path17.dirname(full) !== dir) {
|
|
8854
9394
|
throw new Error(`Unsafe snippet filename: ${fileName}`);
|
|
8855
9395
|
}
|
|
8856
9396
|
return full;
|
|
8857
9397
|
}
|
|
8858
9398
|
async function listSnippets(projectDir) {
|
|
8859
|
-
const dir =
|
|
9399
|
+
const dir = path17.join(projectDir, SNIPPETS_DIR);
|
|
8860
9400
|
let names;
|
|
8861
9401
|
try {
|
|
8862
9402
|
names = await readdir8(dir);
|
|
@@ -8869,7 +9409,7 @@ async function listSnippets(projectDir) {
|
|
|
8869
9409
|
continue;
|
|
8870
9410
|
let body = "";
|
|
8871
9411
|
try {
|
|
8872
|
-
body = await
|
|
9412
|
+
body = await readFile26(path17.join(dir, fileName), "utf8");
|
|
8873
9413
|
} catch {
|
|
8874
9414
|
continue;
|
|
8875
9415
|
}
|
|
@@ -8883,86 +9423,26 @@ async function listSnippets(projectDir) {
|
|
|
8883
9423
|
return entries;
|
|
8884
9424
|
}
|
|
8885
9425
|
async function readSnippet(projectDir, fileName) {
|
|
8886
|
-
return
|
|
9426
|
+
return readFile26(resolveSnippetPath(projectDir, fileName), "utf8");
|
|
8887
9427
|
}
|
|
8888
9428
|
async function saveSnippet(projectDir, name, body) {
|
|
8889
9429
|
const stem = slugify(name);
|
|
8890
9430
|
if (!stem)
|
|
8891
9431
|
throw new Error(`Could not derive a filename from "${name}".`);
|
|
8892
9432
|
const fileName = `${stem}.md`;
|
|
8893
|
-
const dir =
|
|
8894
|
-
await
|
|
9433
|
+
const dir = path17.join(projectDir, SNIPPETS_DIR);
|
|
9434
|
+
await mkdir10(dir, { recursive: true });
|
|
8895
9435
|
await writeFile10(resolveSnippetPath(projectDir, fileName), body, "utf8");
|
|
8896
9436
|
return { name, fileName, variables: extractVariables(body) };
|
|
8897
9437
|
}
|
|
8898
9438
|
async function deleteSnippet(projectDir, fileName) {
|
|
8899
|
-
await
|
|
9439
|
+
await rm3(resolveSnippetPath(projectDir, fileName), { force: true });
|
|
8900
9440
|
}
|
|
8901
9441
|
// src/lib/plugin-manager.ts
|
|
8902
9442
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
8903
9443
|
import { cp as cp4, lstat as lstat3, mkdir as mkdir12, rename as rename5, rm as rm5, stat as stat7 } from "node:fs/promises";
|
|
8904
9444
|
import path19 from "node:path";
|
|
8905
|
-
import { isSeq as
|
|
8906
|
-
|
|
8907
|
-
// src/lib/manifest-doc.ts
|
|
8908
|
-
import { existsSync as existsSync13 } from "node:fs";
|
|
8909
|
-
import { randomUUID } from "node:crypto";
|
|
8910
|
-
import { mkdir as mkdir10, open, readFile as readFile26, rename as rename3, rm as rm3 } from "node:fs/promises";
|
|
8911
|
-
import path17 from "node:path";
|
|
8912
|
-
import { parseDocument, isSeq, YAMLSeq } from "yaml";
|
|
8913
|
-
function resolveManifestPath(projectDir) {
|
|
8914
|
-
const existing = MANIFEST_FILENAMES.find((name) => existsSync13(path17.join(projectDir, name)));
|
|
8915
|
-
return path17.join(projectDir, existing ?? MANIFEST_FILENAMES[0]);
|
|
8916
|
-
}
|
|
8917
|
-
async function loadManifestDoc(projectDir) {
|
|
8918
|
-
const file = resolveManifestPath(projectDir);
|
|
8919
|
-
let text = "";
|
|
8920
|
-
try {
|
|
8921
|
-
text = await readFile26(file, "utf8");
|
|
8922
|
-
} catch (error2) {
|
|
8923
|
-
if (error2.code !== "ENOENT")
|
|
8924
|
-
throw error2;
|
|
8925
|
-
}
|
|
8926
|
-
return { doc: parseDocument(text), file };
|
|
8927
|
-
}
|
|
8928
|
-
async function writeManifestDoc(file, doc) {
|
|
8929
|
-
const dir = path17.dirname(file);
|
|
8930
|
-
await mkdir10(dir, { recursive: true });
|
|
8931
|
-
const temporary = path17.join(dir, `.${path17.basename(file)}.tmp-${process.pid}-${randomUUID()}`);
|
|
8932
|
-
try {
|
|
8933
|
-
const handle = await open(temporary, "wx", 438);
|
|
8934
|
-
try {
|
|
8935
|
-
await handle.writeFile(doc.toString(), "utf8");
|
|
8936
|
-
await handle.sync();
|
|
8937
|
-
} finally {
|
|
8938
|
-
await handle.close();
|
|
8939
|
-
}
|
|
8940
|
-
await rename3(temporary, file);
|
|
8941
|
-
const directory = await open(dir, "r").catch(() => null);
|
|
8942
|
-
if (directory) {
|
|
8943
|
-
await directory.sync().catch(() => {});
|
|
8944
|
-
await directory.close();
|
|
8945
|
-
}
|
|
8946
|
-
} finally {
|
|
8947
|
-
await rm3(temporary, { force: true });
|
|
8948
|
-
}
|
|
8949
|
-
}
|
|
8950
|
-
function ensureSeq(doc, key) {
|
|
8951
|
-
let seq = doc.get(key, true);
|
|
8952
|
-
if (!isSeq(seq)) {
|
|
8953
|
-
const fresh = new YAMLSeq(doc.schema);
|
|
8954
|
-
doc.set(key, fresh);
|
|
8955
|
-
seq = fresh;
|
|
8956
|
-
}
|
|
8957
|
-
return seq;
|
|
8958
|
-
}
|
|
8959
|
-
function scalarString(item) {
|
|
8960
|
-
if (item && typeof item === "object" && "value" in item) {
|
|
8961
|
-
const v = item.value;
|
|
8962
|
-
return typeof v === "string" ? v : null;
|
|
8963
|
-
}
|
|
8964
|
-
return typeof item === "string" ? item : null;
|
|
8965
|
-
}
|
|
9445
|
+
import { isSeq as isSeq3, isMap as isMap2, isScalar as isScalar2, YAMLMap, Scalar } from "yaml";
|
|
8966
9446
|
|
|
8967
9447
|
// src/lib/npm-plugin-installer.ts
|
|
8968
9448
|
import { createHash as createHash4, randomUUID as randomUUID2, timingSafeEqual } from "node:crypto";
|
|
@@ -9730,9 +10210,9 @@ function isLocalRef2(ref) {
|
|
|
9730
10210
|
return ref.startsWith("./") || ref.startsWith("../") || ref.startsWith("/") || /^[a-zA-Z]:[\\/]/.test(ref) || /\.(m?js|cjs|ts)$/i.test(ref) || !ref.startsWith("@") && ref.includes("/");
|
|
9731
10211
|
}
|
|
9732
10212
|
function itemRef(item) {
|
|
9733
|
-
if (
|
|
10213
|
+
if (isScalar2(item))
|
|
9734
10214
|
return typeof item.value === "string" ? item.value : null;
|
|
9735
|
-
if (
|
|
10215
|
+
if (isMap2(item)) {
|
|
9736
10216
|
const p = item.get("path");
|
|
9737
10217
|
const n = item.get("name");
|
|
9738
10218
|
if (typeof p === "string")
|
|
@@ -9748,7 +10228,7 @@ function refKind(ref) {
|
|
|
9748
10228
|
async function listProjectPlugins(projectDir) {
|
|
9749
10229
|
const { doc } = await loadManifestDoc(projectDir);
|
|
9750
10230
|
const seq = doc.get("plugins", true);
|
|
9751
|
-
if (!
|
|
10231
|
+
if (!isSeq3(seq))
|
|
9752
10232
|
return [];
|
|
9753
10233
|
const out = [];
|
|
9754
10234
|
for (const item of seq.items) {
|
|
@@ -9759,7 +10239,7 @@ async function listProjectPlugins(projectDir) {
|
|
|
9759
10239
|
let kind;
|
|
9760
10240
|
let version;
|
|
9761
10241
|
let exportName;
|
|
9762
|
-
if (
|
|
10242
|
+
if (isMap2(item)) {
|
|
9763
10243
|
const e = item.get("enabled");
|
|
9764
10244
|
if (e === false)
|
|
9765
10245
|
enabled = false;
|
|
@@ -9797,7 +10277,7 @@ async function setPluginEnabled(projectDir, ref, enabled) {
|
|
|
9797
10277
|
async function setPluginEnabledUnlocked(projectDir, ref, enabled) {
|
|
9798
10278
|
const { doc, file } = await loadManifestDoc(projectDir);
|
|
9799
10279
|
const seq = doc.get("plugins", true);
|
|
9800
|
-
if (!
|
|
10280
|
+
if (!isSeq3(seq)) {
|
|
9801
10281
|
throw new Error(`No plugins are configured in ${path19.basename(file)}.`);
|
|
9802
10282
|
}
|
|
9803
10283
|
const idx = indexOfRef(seq, ref);
|
|
@@ -9806,7 +10286,7 @@ async function setPluginEnabledUnlocked(projectDir, ref, enabled) {
|
|
|
9806
10286
|
}
|
|
9807
10287
|
const item = seq.items[idx];
|
|
9808
10288
|
let map;
|
|
9809
|
-
if (
|
|
10289
|
+
if (isMap2(item)) {
|
|
9810
10290
|
map = item;
|
|
9811
10291
|
} else {
|
|
9812
10292
|
map = new YAMLMap(doc.schema);
|
|
@@ -9834,7 +10314,7 @@ async function recordInstalledNpmPlugin(projectDir, name, version, exportName) {
|
|
|
9834
10314
|
const seq = ensureSeq(doc, "plugins");
|
|
9835
10315
|
const idx = indexOfRef(seq, name);
|
|
9836
10316
|
let map;
|
|
9837
|
-
if (idx >= 0 &&
|
|
10317
|
+
if (idx >= 0 && isMap2(seq.items[idx])) {
|
|
9838
10318
|
map = seq.items[idx];
|
|
9839
10319
|
} else {
|
|
9840
10320
|
map = new YAMLMap(doc.schema);
|
|
@@ -9997,13 +10477,12 @@ async function validateProjectPlugins(projectDir) {
|
|
|
9997
10477
|
}
|
|
9998
10478
|
// src/lib/theme-manager.ts
|
|
9999
10479
|
import { cp as cp5, mkdir as mkdir13, readFile as readFile28, readdir as readdir9, stat as stat8, writeFile as writeFile11, rm as rm6 } from "node:fs/promises";
|
|
10000
|
-
import { existsSync as
|
|
10480
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
10001
10481
|
import path20 from "node:path";
|
|
10002
|
-
import { isSeq as
|
|
10482
|
+
import { isSeq as isSeq4, Scalar as Scalar2 } from "yaml";
|
|
10003
10483
|
var THEMES_DIR = "themes";
|
|
10004
10484
|
var BUILT_IN_THEME_IDS = [
|
|
10005
10485
|
"clean-book",
|
|
10006
|
-
"ttrpg-supplement",
|
|
10007
10486
|
"zine",
|
|
10008
10487
|
"technical-doc"
|
|
10009
10488
|
];
|
|
@@ -10012,10 +10491,6 @@ var BUILT_IN_FALLBACK_META = {
|
|
|
10012
10491
|
name: "Clean Book",
|
|
10013
10492
|
description: "A calm, classic book look: serif body, generous margins."
|
|
10014
10493
|
},
|
|
10015
|
-
"ttrpg-supplement": {
|
|
10016
|
-
name: "TTRPG Supplement",
|
|
10017
|
-
description: "Bold display headings, parchment fills, boxed stat blocks."
|
|
10018
|
-
},
|
|
10019
10494
|
zine: {
|
|
10020
10495
|
name: "Zine",
|
|
10021
10496
|
description: "High-contrast, punchy sans-serif for short printed zines."
|
|
@@ -10077,7 +10552,7 @@ async function resolveBuiltInTheme(id) {
|
|
|
10077
10552
|
}
|
|
10078
10553
|
const cssPath = await getAssetPath(`${THEMES_DIR}/${id}/theme.css`);
|
|
10079
10554
|
const jsonPath = await getAssetPath(`${THEMES_DIR}/${id}/theme.json`);
|
|
10080
|
-
if (!
|
|
10555
|
+
if (!existsSync15(cssPath)) {
|
|
10081
10556
|
throw new Error(`Built-in theme "${id}" is missing its theme.css.`);
|
|
10082
10557
|
}
|
|
10083
10558
|
const meta = await readThemeMeta(jsonPath);
|
|
@@ -10101,7 +10576,7 @@ async function listProjectThemes(projectDir) {
|
|
|
10101
10576
|
if (!entry.isDirectory())
|
|
10102
10577
|
continue;
|
|
10103
10578
|
const dir = path20.join(root, entry.name);
|
|
10104
|
-
if (!
|
|
10579
|
+
if (!existsSync15(path20.join(dir, "theme.css")))
|
|
10105
10580
|
continue;
|
|
10106
10581
|
const meta = await readThemeMeta(path20.join(dir, "theme.json"));
|
|
10107
10582
|
out.push(themeInfo(entry.name, "project", meta));
|
|
@@ -10126,7 +10601,7 @@ function activeThemeIdInSeq(seq) {
|
|
|
10126
10601
|
async function getActiveTheme(projectDir) {
|
|
10127
10602
|
const { doc } = await loadManifestDoc(projectDir);
|
|
10128
10603
|
const seq = doc.get("styles", true);
|
|
10129
|
-
if (!
|
|
10604
|
+
if (!isSeq4(seq))
|
|
10130
10605
|
return null;
|
|
10131
10606
|
for (const item of seq.items) {
|
|
10132
10607
|
const href = scalarString(item);
|
|
@@ -10137,7 +10612,7 @@ async function getActiveTheme(projectDir) {
|
|
|
10137
10612
|
continue;
|
|
10138
10613
|
const id = m[1];
|
|
10139
10614
|
const dir = path20.join(projectDir, THEMES_DIR, id);
|
|
10140
|
-
if (!
|
|
10615
|
+
if (!existsSync15(path20.join(dir, "theme.css")))
|
|
10141
10616
|
continue;
|
|
10142
10617
|
const meta = await readThemeMeta(path20.join(dir, "theme.json"));
|
|
10143
10618
|
return themeInfo(id, "project", meta);
|
|
@@ -10174,7 +10649,7 @@ async function getPreviousTheme(projectDir) {
|
|
|
10174
10649
|
if (!id)
|
|
10175
10650
|
return null;
|
|
10176
10651
|
const dir = themeDirFor(projectDir, id);
|
|
10177
|
-
if (!
|
|
10652
|
+
if (!existsSync15(path20.join(dir, "theme.css")))
|
|
10178
10653
|
return null;
|
|
10179
10654
|
const active = await getActiveTheme(projectDir);
|
|
10180
10655
|
if (active && active.id === id)
|
|
@@ -10194,7 +10669,7 @@ async function applyTheme(projectDir, target) {
|
|
|
10194
10669
|
if (target.kind === "builtin") {
|
|
10195
10670
|
const resolved = await resolveBuiltInTheme(target.id);
|
|
10196
10671
|
let destId = target.id;
|
|
10197
|
-
if (
|
|
10672
|
+
if (existsSync15(path20.join(projectDir, THEMES_DIR, destId, "theme.css"))) {
|
|
10198
10673
|
destId = await uniqueThemeId(projectDir, target.id);
|
|
10199
10674
|
}
|
|
10200
10675
|
const destDir = path20.join(projectDir, THEMES_DIR, destId);
|
|
@@ -10203,7 +10678,7 @@ async function applyTheme(projectDir, target) {
|
|
|
10203
10678
|
info2 = themeInfo(destId, "project", await readThemeMeta(path20.join(destDir, "theme.json")));
|
|
10204
10679
|
} else {
|
|
10205
10680
|
const dir = themeDirFor(projectDir, target.id);
|
|
10206
|
-
if (!
|
|
10681
|
+
if (!existsSync15(path20.join(dir, "theme.css"))) {
|
|
10207
10682
|
throw new Error(`Theme "${target.id}" is not present in this project.`);
|
|
10208
10683
|
}
|
|
10209
10684
|
info2 = themeInfo(target.id, "project", await readThemeMeta(path20.join(dir, "theme.json")));
|
|
@@ -10215,7 +10690,7 @@ async function uniqueThemeId(projectDir, base) {
|
|
|
10215
10690
|
const root = path20.join(projectDir, THEMES_DIR);
|
|
10216
10691
|
let id = themeSlug(base);
|
|
10217
10692
|
let n = 2;
|
|
10218
|
-
while (
|
|
10693
|
+
while (existsSync15(path20.join(root, id))) {
|
|
10219
10694
|
id = `${themeSlug(base)}-${n++}`;
|
|
10220
10695
|
}
|
|
10221
10696
|
return id;
|
|
@@ -10230,7 +10705,7 @@ async function importThemeFromFolder(projectDir, sourceDir) {
|
|
|
10230
10705
|
if (!info2.isDirectory()) {
|
|
10231
10706
|
throw new Error("The chosen path is not a folder.");
|
|
10232
10707
|
}
|
|
10233
|
-
if (!
|
|
10708
|
+
if (!existsSync15(path20.join(sourceDir, "theme.css"))) {
|
|
10234
10709
|
throw new Error("A theme folder must contain a theme.css file.");
|
|
10235
10710
|
}
|
|
10236
10711
|
const meta = await readThemeMeta(path20.join(sourceDir, "theme.json"));
|
|
@@ -10323,20 +10798,20 @@ async function readThemeCss(projectDir, source) {
|
|
|
10323
10798
|
if (!projectDir)
|
|
10324
10799
|
throw new Error("A project is required to read a project theme.");
|
|
10325
10800
|
const cssPath = path20.join(themeDirFor(projectDir, source.id), "theme.css");
|
|
10326
|
-
if (!
|
|
10801
|
+
if (!existsSync15(cssPath)) {
|
|
10327
10802
|
throw new Error(`Theme "${source.id}" has no theme.css in this project.`);
|
|
10328
10803
|
}
|
|
10329
10804
|
return readFile28(cssPath, "utf8");
|
|
10330
10805
|
}
|
|
10331
10806
|
async function removeProjectTheme(projectDir, id) {
|
|
10332
10807
|
const dir = themeDirFor(projectDir, id);
|
|
10333
|
-
if (
|
|
10808
|
+
if (existsSync15(dir)) {
|
|
10334
10809
|
await rm6(dir, { recursive: true, force: true });
|
|
10335
10810
|
}
|
|
10336
10811
|
const { doc, file } = await loadManifestDoc(projectDir);
|
|
10337
10812
|
let changed = false;
|
|
10338
10813
|
const seq = doc.get("styles", true);
|
|
10339
|
-
if (
|
|
10814
|
+
if (isSeq4(seq)) {
|
|
10340
10815
|
const href = themeStyleHref(id);
|
|
10341
10816
|
const before = seq.items.length;
|
|
10342
10817
|
seq.items = seq.items.filter((item) => scalarString(item) !== href);
|
|
@@ -10352,7 +10827,7 @@ async function removeProjectTheme(projectDir, id) {
|
|
|
10352
10827
|
}
|
|
10353
10828
|
}
|
|
10354
10829
|
// src/lib/theme-import.ts
|
|
10355
|
-
import { existsSync as
|
|
10830
|
+
import { existsSync as existsSync16 } from "node:fs";
|
|
10356
10831
|
import { mkdir as mkdir14, mkdtemp as mkdtemp4, readFile as readFile29, rm as rm7, writeFile as writeFile12 } from "node:fs/promises";
|
|
10357
10832
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
10358
10833
|
import path21 from "node:path";
|
|
@@ -10427,7 +10902,7 @@ async function readThemeJson(p) {
|
|
|
10427
10902
|
}
|
|
10428
10903
|
async function finalizeThemeImport(projectDir, sourceDir, relPaths) {
|
|
10429
10904
|
const cssPath = path21.join(sourceDir, "theme.css");
|
|
10430
|
-
if (!
|
|
10905
|
+
if (!existsSync16(cssPath)) {
|
|
10431
10906
|
throw new Error("A theme must contain a theme.css file.");
|
|
10432
10907
|
}
|
|
10433
10908
|
const css = await readFile29(cssPath, "utf8");
|
|
@@ -10440,7 +10915,7 @@ async function finalizeThemeImport(projectDir, sourceDir, relPaths) {
|
|
|
10440
10915
|
warnings.push({ code: "print-safety", message: w.message });
|
|
10441
10916
|
}
|
|
10442
10917
|
const jsonPath = path21.join(sourceDir, "theme.json");
|
|
10443
|
-
if (!
|
|
10918
|
+
if (!existsSync16(jsonPath)) {
|
|
10444
10919
|
warnings.push({
|
|
10445
10920
|
code: "no-theme-json",
|
|
10446
10921
|
message: "No theme.json found — the theme's name was taken from the file/folder name."
|
|
@@ -10564,7 +11039,7 @@ async function importThemeFromFile(projectDir, filePath2) {
|
|
|
10564
11039
|
throw new Error("Choose a .zip theme package or a .css stylesheet.");
|
|
10565
11040
|
}
|
|
10566
11041
|
// src/lib/manifest-config.ts
|
|
10567
|
-
import { isSeq as
|
|
11042
|
+
import { isSeq as isSeq5, isMap as isMap3, YAMLSeq as YAMLSeq3, Scalar as Scalar3 } from "yaml";
|
|
10568
11043
|
function hasKey(obj, key) {
|
|
10569
11044
|
return Object.prototype.hasOwnProperty.call(obj, key);
|
|
10570
11045
|
}
|
|
@@ -10585,12 +11060,24 @@ async function setManifestFields(projectDir, updates) {
|
|
|
10585
11060
|
doc.set("authors", a);
|
|
10586
11061
|
}
|
|
10587
11062
|
}
|
|
11063
|
+
if (hasKey(updates, "targets")) {
|
|
11064
|
+
const t = updates.targets;
|
|
11065
|
+
if (t === undefined) {
|
|
11066
|
+
doc.delete("targets");
|
|
11067
|
+
} else {
|
|
11068
|
+
const unknown = t.filter((id) => !TARGETS[id]);
|
|
11069
|
+
if (unknown.length > 0) {
|
|
11070
|
+
throw new UsageError(`Unknown publish target${unknown.length > 1 ? "s" : ""} "${unknown.join('", "')}". ` + `Known targets: ${TARGET_IDS.join(", ")}.`);
|
|
11071
|
+
}
|
|
11072
|
+
doc.set("targets", [...new Set(t)]);
|
|
11073
|
+
}
|
|
11074
|
+
}
|
|
10588
11075
|
if (hasKey(updates, "sourceFiles")) {
|
|
10589
11076
|
const f = updates.sourceFiles;
|
|
10590
11077
|
if (f === null || Array.isArray(f) && f.length === 0) {
|
|
10591
11078
|
doc.deleteIn(["source", "files"]);
|
|
10592
11079
|
const source = doc.get("source", true);
|
|
10593
|
-
if (
|
|
11080
|
+
if (isMap3(source) && source.items.length === 0) {
|
|
10594
11081
|
doc.delete("source");
|
|
10595
11082
|
}
|
|
10596
11083
|
} else {
|
|
@@ -10608,15 +11095,19 @@ async function readManifestFields(projectDir) {
|
|
|
10608
11095
|
if (typeof title === "string")
|
|
10609
11096
|
out.title = title;
|
|
10610
11097
|
const authors = doc.get("authors", true);
|
|
10611
|
-
if (
|
|
11098
|
+
if (isSeq5(authors)) {
|
|
10612
11099
|
out.authors = authors.items.map((i) => scalarString(i) ?? "");
|
|
10613
11100
|
}
|
|
11101
|
+
const targetsNode = doc.get("targets", true);
|
|
11102
|
+
if (isSeq5(targetsNode)) {
|
|
11103
|
+
out.targets = targetsNode.items.map((i) => scalarString(i)).filter((s) => s !== null);
|
|
11104
|
+
}
|
|
10614
11105
|
const filesNode = doc.getIn(["source", "files"], true);
|
|
10615
|
-
if (
|
|
11106
|
+
if (isSeq5(filesNode)) {
|
|
10616
11107
|
out.sourceFiles = filesNode.items.map((i) => scalarString(i) ?? "");
|
|
10617
11108
|
} else {
|
|
10618
11109
|
const sourceNode = doc.get("source", true);
|
|
10619
|
-
if (
|
|
11110
|
+
if (isMap3(sourceNode) && sourceNode.has("files"))
|
|
10620
11111
|
out.sourceFiles = null;
|
|
10621
11112
|
}
|
|
10622
11113
|
return out;
|
|
@@ -12643,12 +13134,12 @@ async function buildResolutionPlan(resolutions, localTip, remoteId, deps) {
|
|
|
12643
13134
|
|
|
12644
13135
|
// src/lib/remote-auth/conflict-resolution.ts
|
|
12645
13136
|
function onlineCopyPath(filepath, counter) {
|
|
12646
|
-
const
|
|
13137
|
+
const dirname6 = path27.posix.dirname(filepath);
|
|
12647
13138
|
const ext = path27.posix.extname(filepath);
|
|
12648
13139
|
const stem = path27.posix.basename(filepath, ext);
|
|
12649
13140
|
const label = counter && counter >= 2 ? `online copy ${counter}` : "online copy";
|
|
12650
13141
|
const renamed = `${stem} (${label})${ext}`;
|
|
12651
|
-
return
|
|
13142
|
+
return dirname6 === "." ? renamed : `${dirname6}/${renamed}`;
|
|
12652
13143
|
}
|
|
12653
13144
|
async function uniqueOnlineCopyPath(dir, filepath, oids, cache2) {
|
|
12654
13145
|
const taken = async (candidate2) => {
|
|
@@ -13384,8 +13875,8 @@ var drivethrurpgProvider = {
|
|
|
13384
13875
|
}
|
|
13385
13876
|
issues.push({
|
|
13386
13877
|
severity: "info",
|
|
13387
|
-
id: "drivethrurpg/validate-
|
|
13388
|
-
message: "Tip: run `gutterpress validate --
|
|
13878
|
+
id: "drivethrurpg/validate-target",
|
|
13879
|
+
message: "Tip: run `gutterpress validate --target dtrpg` for DriveThruRPG's print-compliance checks before uploading."
|
|
13389
13880
|
});
|
|
13390
13881
|
return issues;
|
|
13391
13882
|
},
|
|
@@ -13499,7 +13990,7 @@ var info4 = {
|
|
|
13499
13990
|
label: "itch.io",
|
|
13500
13991
|
kind: "api",
|
|
13501
13992
|
format: "pdf",
|
|
13502
|
-
description: "Upload the PDF to your itch.io project page (games,
|
|
13993
|
+
description: "Upload the PDF to your itch.io project page (games, zines, and other printable projects).",
|
|
13503
13994
|
configFields: [
|
|
13504
13995
|
{ key: "target", label: "Project (user/game)", placeholder: "you/your-book" },
|
|
13505
13996
|
{ key: "channel", label: "Channel", placeholder: "pdf" }
|
|
@@ -14177,10 +14668,10 @@ function normalizeProjectKey(projectDir) {
|
|
|
14177
14668
|
return projectDir.replace(/[\\/]+$/, "");
|
|
14178
14669
|
}
|
|
14179
14670
|
// src/lib/publish/manifest-publish.ts
|
|
14180
|
-
import { isMap as
|
|
14671
|
+
import { isMap as isMap4 } from "yaml";
|
|
14181
14672
|
function settingsFromDoc(doc) {
|
|
14182
14673
|
const node = doc.get("publish", true);
|
|
14183
|
-
if (!
|
|
14674
|
+
if (!isMap4(node))
|
|
14184
14675
|
return {};
|
|
14185
14676
|
const json = node.toJSON();
|
|
14186
14677
|
const out = {};
|
|
@@ -14205,11 +14696,11 @@ async function setPublishProviderConfig(projectDir, providerId, values) {
|
|
|
14205
14696
|
}
|
|
14206
14697
|
}
|
|
14207
14698
|
const section = doc.getIn(["publish", providerId], true);
|
|
14208
|
-
if (
|
|
14699
|
+
if (isMap4(section) && section.items.length === 0) {
|
|
14209
14700
|
doc.deleteIn(["publish", providerId]);
|
|
14210
14701
|
}
|
|
14211
14702
|
const publish = doc.get("publish", true);
|
|
14212
|
-
if (
|
|
14703
|
+
if (isMap4(publish) && publish.items.length === 0) {
|
|
14213
14704
|
doc.delete("publish");
|
|
14214
14705
|
}
|
|
14215
14706
|
await writeManifestDoc(file, doc);
|
|
@@ -15215,4 +15706,4 @@ async function dispatchToHandler(kind, ctx, error2) {
|
|
|
15215
15706
|
}
|
|
15216
15707
|
return result;
|
|
15217
15708
|
}
|
|
15218
|
-
export { BuildError, MANIFEST_FILENAMES, hasProjectManifest, loadManifest, loadManifestWithPath, resolveConfig, log, resolveActiveStyles, listProjectStyles, PLUGINS_DIR, ruleRemoteUrls, ruleRiskyProps, rulePagedjsCrashSelectors, ruleSyntax, checkCss, runLint, formatReport, getChecks, getCheckById, resolveCheckSelectors, inspectImage, runChecks, checkToolAvailability, reportMissingTools, executeValidation, executeAndReport, splitOutPath, runBuild, openPath, startPreviewServer, redactCredential, defaultConfigDir, FileTokenStore, extractUrlCredential, migrateUrlCredential, getSystemDiagnostics, slugifyProjectName, escapeYamlScalar, scaffoldProject, adoptFolder, BUILT_IN_TEMPLATE_IDS, listBuiltInTemplates, saveProjectAsTemplate, listCustomTemplates, importTemplateFromFolder, SNIPPETS_DIR, extractVariables, substituteVariables, listSnippets, readSnippet, saveSnippet, deleteSnippet, RECOMMENDED_PLUGINS, listProjectPlugins, setPluginEnabled, addNpmPlugin, addLocalPlugin, validateProjectPlugins, THEMES_DIR, BUILT_IN_THEME_IDS, listBuiltInThemes, resolveBuiltInTheme, listProjectThemes, getActiveTheme, getPreviousTheme, revertTheme, applyTheme, importThemeFromFolder, importThemeFromUrl, readThemeCss, removeProjectTheme, importThemeFromFile, setManifestFields, readManifestFields, setActiveStyles, APP_HEARTBEAT_FRESH_MS, heartbeatTtlMs, appHeartbeatPath, writeAppHeartbeat, removeAppHeartbeat, readAppHeartbeat, isAppHeartbeatFresh, AUTO_SNAPSHOT_MIN_MINUTES, AUTO_SNAPSHOT_MAX_MINUTES, AUTO_SNAPSHOT_DEFAULT_MINUTES, AUTO_SYNC_MIN_MINUTES, AUTO_SYNC_MAX_MINUTES, AUTO_SYNC_DEFAULT_MINUTES, autoSnapshotDelayMs, autoSyncDelayMs, isGitInternalPath, resolveGitHubClientId, GITHUB_HOST, GitHubAuthProvider, listGitHubRepositories, listRepoBooks, listGitHubBranches, RepoNeedsRecoveryError, isRepoNeedsRecoveryError, classifyFromHealth, classifyGitError, SYNC_SNAPSHOT_MESSAGE, provenancePath, readProjectProvenance, sanitizeCloneFolderName, cloneRepository, isSshRemoteUrl, testRemoteAccess, normalizeForgeHost, knownForgeTokenUrl, connectGenericHost, GenericTokenAuthProvider, parseRemoteOrigin, forgeKindForHost, diagnoseProjectRemote, inspectRepo, verifyRepoReadable, isUnbornRepo, preflightStructuralReason, buildPreflightDiagnostics, onlineCopyPath, resolveConflicts, syncProject, pullChanges, pushChanges, publishCredentialKey, publishConnectionStatus, listPublishAccounts, listPublishProviders, publishProviderFor, resolvePublishRequest, runPublish, connectPublishProvider, PublishSelectionsStore, readPublishSettings, setPublishProviderConfig, buildRecoveryContext, recover16 as recover };
|
|
15709
|
+
export { BuildError, PRESET_IDS, PRESETS, TARGETS, TARGET_IDS, publishTargetFor, MANIFEST_FILENAMES, hasProjectManifest, loadManifest, loadManifestWithPath, resolveConfig, log, resolveActiveStyles, listProjectStyles, PLUGINS_DIR, ruleRemoteUrls, ruleRiskyProps, rulePagedjsCrashSelectors, ruleSyntax, checkCss, runLint, formatReport, getChecks, getCheckById, resolveCheckSelectors, inspectImage, runChecks, checkToolAvailability, reportMissingTools, executeValidation, executeAndReport, splitOutPath, runBuild, openPath, startPreviewServer, redactCredential, defaultConfigDir, FileTokenStore, extractUrlCredential, migrateUrlCredential, getSystemDiagnostics, slugifyProjectName, escapeYamlScalar, scaffoldProject, adoptFolder, BUILT_IN_TEMPLATE_IDS, listBuiltInTemplates, saveProjectAsTemplate, listCustomTemplates, importTemplateFromFolder, SNIPPETS_DIR, extractVariables, substituteVariables, listSnippets, readSnippet, saveSnippet, deleteSnippet, RECOMMENDED_PLUGINS, listProjectPlugins, setPluginEnabled, addNpmPlugin, addLocalPlugin, validateProjectPlugins, THEMES_DIR, BUILT_IN_THEME_IDS, listBuiltInThemes, resolveBuiltInTheme, listProjectThemes, getActiveTheme, getPreviousTheme, revertTheme, applyTheme, importThemeFromFolder, importThemeFromUrl, readThemeCss, removeProjectTheme, importThemeFromFile, setManifestFields, readManifestFields, setActiveStyles, APP_HEARTBEAT_FRESH_MS, heartbeatTtlMs, appHeartbeatPath, writeAppHeartbeat, removeAppHeartbeat, readAppHeartbeat, isAppHeartbeatFresh, AUTO_SNAPSHOT_MIN_MINUTES, AUTO_SNAPSHOT_MAX_MINUTES, AUTO_SNAPSHOT_DEFAULT_MINUTES, AUTO_SYNC_MIN_MINUTES, AUTO_SYNC_MAX_MINUTES, AUTO_SYNC_DEFAULT_MINUTES, autoSnapshotDelayMs, autoSyncDelayMs, isGitInternalPath, resolveGitHubClientId, GITHUB_HOST, GitHubAuthProvider, listGitHubRepositories, listRepoBooks, listGitHubBranches, RepoNeedsRecoveryError, isRepoNeedsRecoveryError, classifyFromHealth, classifyGitError, SYNC_SNAPSHOT_MESSAGE, provenancePath, readProjectProvenance, sanitizeCloneFolderName, cloneRepository, isSshRemoteUrl, testRemoteAccess, normalizeForgeHost, knownForgeTokenUrl, connectGenericHost, GenericTokenAuthProvider, parseRemoteOrigin, forgeKindForHost, diagnoseProjectRemote, inspectRepo, verifyRepoReadable, isUnbornRepo, preflightStructuralReason, buildPreflightDiagnostics, onlineCopyPath, resolveConflicts, syncProject, pullChanges, pushChanges, publishCredentialKey, publishConnectionStatus, listPublishAccounts, listPublishProviders, publishProviderFor, resolvePublishRequest, runPublish, connectPublishProvider, PublishSelectionsStore, readPublishSettings, setPublishProviderConfig, buildRecoveryContext, recover16 as recover };
|