gutterpress 0.11.23-alpha.7 → 0.11.23-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +3 -1
  2. package/dist/{README-syg2hn83.md → README-8rct3axy.md} +2 -2
  3. package/dist/api/index.d.ts +3 -3
  4. package/dist/api/index.js +8 -6
  5. package/dist/{build-w572wq10.js → build-3e18wnqj.js} +9 -8
  6. package/dist/{cli-mmgtqnmr.js → cli-0ep11b0q.js} +1 -1
  7. package/dist/cli-65gy8ask.js +82 -0
  8. package/dist/{cli-6pv84kf3.js → cli-88arpdke.js} +6 -84
  9. package/dist/{cli-eatdqx3z.js → cli-9n9kg58f.js} +1 -1
  10. package/dist/{cli-58fsk6d7.js → cli-pd2cje5b.js} +187 -44
  11. package/dist/{cli-3cvq3n1c.js → cli-pnrararp.js} +189 -85
  12. package/dist/{cli-wyg7qstd.js → cli-tgnyvra3.js} +15 -5
  13. package/dist/{cli-31anr0r9.js → cli-v0evdpdf.js} +68 -28
  14. package/dist/{cli-5ba6bm6z.js → cli-x7gcdg6w.js} +1 -1
  15. package/dist/cli.js +12 -12
  16. package/dist/{doctor-2f3zqmza.js → doctor-2f4s9dyn.js} +3 -3
  17. package/dist/engine/compiler/postprocess.d.ts +3 -0
  18. package/dist/engine/shared/page-vars.d.ts +32 -0
  19. package/dist/engine/shared/pdf-inspect.d.ts +33 -0
  20. package/dist/{engine-4rabsgwq.js → engine-c4pxz79g.js} +2 -1
  21. package/dist/{engine-wm4fggac.js → engine-cs5g2ez0.js} +3 -3
  22. package/dist/{ext-0cgky8n3.js → ext-70gv86v5.js} +9 -8
  23. package/dist/{extension-restore-vghc58vx.js → extension-restore-chj5nx3t.js} +3 -3
  24. package/dist/{extension-restore-e7je32pr.js → extension-restore-pd87p8r2.js} +5 -4
  25. package/dist/{gutterpress-agent-d29ry3ak.js → gutterpress-agent-wyryqehx.js} +13 -4
  26. package/dist/{gutterpress-viewer-mafsby5k.js → gutterpress-viewer-ywkf49q5.js} +90 -166
  27. package/dist/{index-nfysshcc.js → index-0k43evt0.js} +309 -155
  28. package/dist/{index-wm4x7xbf.js → index-2t1gdma7.js} +178 -35
  29. package/dist/{index-cw03e9p4.js → index-7j3hkwya.js} +68 -28
  30. package/dist/{index-fp7xwvpr.js → index-80vg3ed6.js} +15 -5
  31. package/dist/{index-2vxzxyyj.js → index-swfdgn2n.js} +10 -10
  32. package/dist/index-tayzfr9c.js +99 -0
  33. package/dist/{index-hphcn7th.js → index-ytjkvwy5.js} +5 -85
  34. package/dist/index.d.ts +2 -0
  35. package/dist/index.js +234 -7
  36. package/dist/lib/book-css.d.ts +56 -0
  37. package/dist/lib/markdown/assemble.d.ts +58 -0
  38. package/dist/lib/markdown/gutterpress-css.d.ts +1 -1
  39. package/dist/lib/publish/google-drive.d.ts +12 -4
  40. package/dist/lib/publish/manifest-publish.d.ts +6 -0
  41. package/dist/lib/publish/providers/gdrive-folder.d.ts +30 -0
  42. package/dist/lib/publish/run-publish.d.ts +6 -1
  43. package/dist/lib/publish/types.d.ts +52 -5
  44. package/dist/lib/remote-auth/converge-merge.d.ts +3 -3
  45. package/dist/lib/remote-auth/sync-messages.d.ts +2 -2
  46. package/dist/lib/root-tokens.d.ts +76 -0
  47. package/dist/lib/source-provider.d.ts +27 -0
  48. package/dist/lib/version-changes.d.ts +26 -16
  49. package/dist/{manifest-79zsgkpn.yaml → manifest-0vwaskj2.yaml} +1 -1
  50. package/dist/{manifest-2kc56wvw.yaml → manifest-aya31jwt.yaml} +1 -1
  51. package/dist/{manifest-41ddzm13.yaml → manifest-mffah3hr.yaml} +1 -1
  52. package/dist/{manifest-sqzm4681.yaml → manifest-wbcv8cbp.yaml} +2 -2
  53. package/dist/{manifest.schema-93sd7zje.json → manifest.schema-0jgj0jt1.json} +1 -1
  54. package/dist/{new-88rkrv71.js → new-d61n5cn0.js} +9 -8
  55. package/dist/{page-rules-ytj9p04v.css → page-rules-vnja6grt.css} +2 -2
  56. package/dist/{plugins-3aj0wc5h.js → plugins-0hhw6s17.js} +1 -1
  57. package/dist/{plugins-sprxsz5d.js → plugins-e0n46s6m.js} +3 -3
  58. package/dist/{preflight-11dg1qkk.js → preflight-fjayhjr5.js} +9 -8
  59. package/dist/{preview-nftp782j.js → preview-h815jrfb.js} +9 -8
  60. package/dist/{publish-6tgxfck9.js → publish-ft39zgma.js} +25 -9
  61. package/dist/render.js +77 -26
  62. package/dist/schema/manifest.types.d.ts +2 -1
  63. package/dist/{source-provider-n7vehh6t.js → source-provider-j1w7byeq.js} +5 -1
  64. package/dist/{source-provider-tgzet3j5.js → source-provider-tjaf303m.js} +5 -1
  65. package/dist/test-helpers/vocabulary.d.ts +12 -0
  66. package/dist/{tokens-n82t4k9h.css → tokens-a7grv940.css} +4 -5
  67. package/dist/{validate-pktqrye5.js → validate-b201dbrb.js} +9 -8
  68. package/package.json +1 -1
  69. package/dist/index-46ycxe6r.js +0 -18
@@ -10,20 +10,13 @@ import {
10
10
  UsageError,
11
11
  addExtension,
12
12
  applyDefaultPdfStrictChecks,
13
- cssLength,
14
13
  ensureProjectGitignore,
15
14
  extensionConfigFor,
16
- formatPageSize,
17
- isNonNegativePoints,
18
- isPositivePoints,
19
- judgeRect,
20
15
  listProjectExtensions,
21
16
  loadManifest,
22
17
  loadManifestDoc,
23
18
  loadManifestWithPath,
24
19
  npmRegistryUrl,
25
- pageBoxes,
26
- printAreaMeta,
27
20
  publishTargetFor,
28
21
  readEntryCss,
29
22
  resolveConfig,
@@ -31,13 +24,12 @@ import {
31
24
  resolvePort,
32
25
  resolveTargets,
33
26
  restoreForCommand,
34
- round3,
35
27
  scalarString,
36
28
  uniqueExtensionId,
37
29
  validatePageMargin,
38
30
  withFetchTimeout,
39
31
  writeManifestDoc
40
- } from "./index-hphcn7th.js";
32
+ } from "./index-ytjkvwy5.js";
41
33
  import {
42
34
  EXTENSION_MANIFEST_FILENAME,
43
35
  GUTTERPRESS_CSS,
@@ -73,7 +65,7 @@ import {
73
65
  setLogLevel,
74
66
  slugify,
75
67
  warn
76
- } from "./index-fp7xwvpr.js";
68
+ } from "./index-80vg3ed6.js";
77
69
  import {
78
70
  blackTextToGray
79
71
  } from "./index-ht1bcqdd.js";
@@ -84,10 +76,18 @@ import {
84
76
  getAssetPath,
85
77
  lastPageMode,
86
78
  resolvePage
87
- } from "./index-2vxzxyyj.js";
79
+ } from "./index-swfdgn2n.js";
88
80
  import {
89
- BuildError
90
- } from "./index-46ycxe6r.js";
81
+ BuildError,
82
+ cssLength,
83
+ formatPageSize,
84
+ isNonNegativePoints,
85
+ isPositivePoints,
86
+ judgeRect,
87
+ pageBoxes,
88
+ printAreaMeta,
89
+ round3
90
+ } from "./index-tayzfr9c.js";
91
91
  import {
92
92
  countLinkAnnotations,
93
93
  getImageResolutions,
@@ -124,14 +124,16 @@ import {
124
124
  hasUncommittedChanges,
125
125
  isContained,
126
126
  listWorkdirChanges,
127
+ nextInHistory,
127
128
  readRevisionRefs,
128
129
  resolveGitAuthor,
129
130
  resolveLogger,
130
131
  revisionNameFrom,
131
132
  snapshotStagingMarkerPath,
132
133
  snapshotWorkingTreeUnlocked,
134
+ startHistoryWalk,
133
135
  withRepoLock
134
- } from "./index-cw03e9p4.js";
136
+ } from "./index-7j3hkwya.js";
135
137
  import {
136
138
  EXTENSIONS_DIR,
137
139
  resolveVendoredPluginInstallRoot,
@@ -166,8 +168,8 @@ var MSG_OFFLINE = "Your changes are saved on this computer. Gutterpress couldn't
166
168
  var MSG_AUTH = "The online copy didn't accept the saved sign-in. Sign in again and try again.";
167
169
  var MSG_INSECURE_TRANSPORT = "This book's online copy is at an address that isn't secure, so the saved sign-in wasn't sent — sign-ins are never sent over an insecure address. Switch the address to a secure one (starting with https), or to a local loopback address for a server on this computer, to back up.";
168
170
  var MSG_BUSY = "The online copy is changing very quickly right now. Your work is saved on this computer — try again in a moment.";
169
- var MSG_HISTORY_UNREADABLE = `This book's versions can't be read, so backing up can't continue. Your writing is safe — every file is still here on this computer. Use "Repair the online copy" (in Book setup → Online copy) to get versions and backing up working again.`;
170
- var MSG_UNRELATED = `This book and the online copy have no versions in common, so they can't be combined. Either the online copy's address points at a different book, or this book's own versions were lost and started over. Your writing is safe — every file is still here on this computer. Check the online copy's address; if it is correct, use "Repair the online copy" (in Book setup → Online copy).`;
171
+ var MSG_HISTORY_UNREADABLE = `This book's versions can't be read, so backing up can't continue. Your writing is safe — every file is still here on this computer. Use "Repair the online copy" in Book setup to get versions and backing up working again.`;
172
+ var MSG_UNRELATED = `This book and the online copy have no versions in common, so they can't be combined. Either the online copy's address points at a different book, or this book's own versions were lost and started over. Your writing is safe — every file is still here on this computer. Check the online copy's address; if it is correct, use "Repair the online copy" in Book setup.`;
171
173
  var MSG_NO_REMOTE = "This book doesn't have an online copy yet.";
172
174
  var MSG_SSH_REMOTE = "This book's online copy uses an SSH address (git@…), which Gutterpress can't back up to. Switch it to the web (HTTPS) address to back up from here.";
173
175
  var MSG_NO_BRANCH = "This book isn't on a named revision, so it can't be backed up right now.";
@@ -202,6 +204,7 @@ function canonicalChapterId(p) {
202
204
  }
203
205
 
204
206
  // src/lib/markdown/assemble.ts
207
+ import postcss from "postcss";
205
208
  function pageGeometryCss(page) {
206
209
  if (!page || !isPositivePoints(page.width) || !isPositivePoints(page.height))
207
210
  return null;
@@ -222,6 +225,65 @@ function defaultPageMarginCss(page) {
222
225
  @page { margin: ${cssLength(DEFAULT_PAGE_MARGIN_PT)}; }
223
226
  }`;
224
227
  }
228
+ function liftPageRules(css) {
229
+ if (!/@page\b/i.test(css))
230
+ return { layered: css, pages: "" };
231
+ let root;
232
+ try {
233
+ root = postcss.parse(css);
234
+ } catch {
235
+ return { layered: css, pages: "" };
236
+ }
237
+ const found = [];
238
+ root.walkAtRules(/^page$/i, (rule) => {
239
+ found.push(rule);
240
+ });
241
+ const pages = found.map((rule) => {
242
+ let lifted = rule.clone();
243
+ for (let p = rule.parent;p instanceof postcss.AtRule || p instanceof postcss.Rule; p = p.parent) {
244
+ if (p instanceof postcss.AtRule && p.name.toLowerCase() === "layer")
245
+ continue;
246
+ lifted = p.clone({ nodes: [] }).append(lifted);
247
+ }
248
+ rule.remove();
249
+ return lifted.toString();
250
+ });
251
+ return { layered: root.toString(), pages: pages.join(`
252
+ `) };
253
+ }
254
+ var CORE_MARKERS = liftPageRules(`${MARKER_CSS.trim()}
255
+ ${TWO_VERSIONS_CSS.trim()}`);
256
+ var CORE_VOCAB = liftPageRules(GUTTERPRESS_CSS.trim());
257
+ function assembleCssChunks(opts) {
258
+ const core = (css) => css ? [{ origin: { kind: "core" }, css }] : [];
259
+ const headed = (title, chunks = []) => chunks.map((chunk, i) => i === 0 ? { ...chunk, css: `/* ${title} */
260
+ ${chunk.css.trim()}` } : chunk);
261
+ const pageGeometry = pageGeometryCss(opts.printArea);
262
+ const defaultMargin = defaultPageMarginCss(opts.printArea);
263
+ return [
264
+ ...core("@layer gp.marker, gp.vocab;"),
265
+ ...core(`/* gutterpress markers */
266
+ @layer gp.marker {
267
+ ${CORE_MARKERS.layered.trim()}
268
+ }`),
269
+ ...core(CORE_MARKERS.pages && `/* gutterpress markers: page rules */
270
+ ${CORE_MARKERS.pages}`),
271
+ ...core(`/* gutterpress */
272
+ @layer gp.vocab {
273
+ ${CORE_VOCAB.layered.trim()}
274
+ }`),
275
+ ...core(CORE_VOCAB.pages && `/* gutterpress: page rules */
276
+ ${CORE_VOCAB.pages}`),
277
+ ...core(defaultMargin && `/* page margins (default) */
278
+ ${defaultMargin}`),
279
+ ...headed("extension css", opts.extensions),
280
+ ...core(`/* column alignment */
281
+ ${TABLE_ALIGN_CSS.trim()}`),
282
+ ...headed("project css", opts.book),
283
+ ...core(pageGeometry && `/* page geometry (manifest) */
284
+ ${pageGeometry}`)
285
+ ];
286
+ }
225
287
  async function assembleBookHtml(opts) {
226
288
  const title = opts.title ?? "Document";
227
289
  const projectCss = opts.projectCss ?? "";
@@ -260,30 +322,11 @@ async function assembleBookHtml(opts) {
260
322
  imageRefs.add(ref);
261
323
  opts.onImageRefs([...imageRefs]);
262
324
  }
263
- const pageGeometry = pageGeometryCss(opts.printArea);
264
- const defaultMargin = defaultPageMarginCss(opts.printArea);
265
- const inlineCss = [
266
- "@layer gp.marker, gp.vocab;",
267
- `/* gutterpress markers */
268
- @layer gp.marker {
269
- ${MARKER_CSS.trim()}
270
- ${TWO_VERSIONS_CSS.trim()}
271
- }`,
272
- `/* gutterpress */
273
- @layer gp.vocab {
274
- ${GUTTERPRESS_CSS.trim()}
275
- }`,
276
- defaultMargin ? `/* page margins (default) */
277
- ${defaultMargin}` : null,
278
- pluginCss ? `/* extension css */
279
- ${pluginCss.trim()}` : null,
280
- `/* column alignment */
281
- ${TABLE_ALIGN_CSS.trim()}`,
282
- projectCss ? `/* project css */
283
- ${projectCss.trim()}` : null,
284
- pageGeometry ? `/* page geometry (manifest) */
285
- ${pageGeometry}` : null
286
- ].filter(Boolean).join(`
325
+ const inlineCss = assembleCssChunks({
326
+ printArea: opts.printArea,
327
+ extensions: pluginCss ? [{ origin: { kind: "extension" }, css: pluginCss }] : [],
328
+ book: projectCss ? [{ origin: { kind: "book" }, css: projectCss }] : []
329
+ }).map((chunk) => chunk.css).join(`
287
330
 
288
331
  `);
289
332
  const preloadTags = (opts.preloadImages ?? []).map((href) => `
@@ -396,7 +439,7 @@ async function listProjectStyles(projectDir, opts = {}) {
396
439
  import { createHash } from "node:crypto";
397
440
  import { readFile } from "node:fs/promises";
398
441
  import path2 from "node:path";
399
- import postcss from "postcss";
442
+ import postcss2 from "postcss";
400
443
  var FONT_EXTS = new Set([".woff2", ".woff", ".ttf", ".otf"]);
401
444
  var HASHED_ASSET_DIR = "assets";
402
445
  var MIME_BY_EXT = {
@@ -496,15 +539,15 @@ function wrapImportConditions(node, imported) {
496
539
  const layer = media.match(/\blayer\(([^)]*)\)|\blayer\b/i);
497
540
  if (layer) {
498
541
  media = media.replace(layer[0], "").trim();
499
- inner = postcss.atRule({ name: "layer", params: (layer[1] ?? "").trim(), nodes: [inner] });
542
+ inner = postcss2.atRule({ name: "layer", params: (layer[1] ?? "").trim(), nodes: [inner] });
500
543
  }
501
544
  const supports = media.match(/\bsupports\(([\s\S]*)\)/i);
502
545
  if (supports) {
503
546
  media = media.replace(supports[0], "").trim();
504
- inner = postcss.atRule({ name: "supports", params: (supports[1] ?? "").trim(), nodes: [inner] });
547
+ inner = postcss2.atRule({ name: "supports", params: (supports[1] ?? "").trim(), nodes: [inner] });
505
548
  }
506
549
  if (media) {
507
- inner = postcss.atRule({ name: "media", params: media, nodes: [inner] });
550
+ inner = postcss2.atRule({ name: "media", params: media, nodes: [inner] });
508
551
  }
509
552
  return inner;
510
553
  }
@@ -526,7 +569,7 @@ async function inlineOne(cssPath, copies, warnings, seen) {
526
569
  const cssDir = path2.dirname(abs);
527
570
  let root;
528
571
  try {
529
- root = postcss.parse(source.toString("utf-8"), { from: abs });
572
+ root = postcss2.parse(source.toString("utf-8"), { from: abs });
530
573
  } catch (err) {
531
574
  throw new BuildError(`Could not parse CSS: ${abs}
532
575
  ${err instanceof Error ? err.message : String(err)}`, 1);
@@ -566,7 +609,7 @@ async function inlineOne(cssPath, copies, warnings, seen) {
566
609
  for (const { node, target } of imports) {
567
610
  const importedAbs = path2.resolve(cssDir, stripUrlSuffix(decodeRef(target)));
568
611
  const inlined = await inlineOne(importedAbs, copies, warnings, seen);
569
- node.replaceWith(wrapImportConditions(node, postcss.parse(inlined, { from: importedAbs })));
612
+ node.replaceWith(wrapImportConditions(node, postcss2.parse(inlined, { from: importedAbs })));
570
613
  }
571
614
  return root.toString();
572
615
  }
@@ -608,7 +651,7 @@ async function collectStyleDependencies(projectDir, stylePaths) {
608
651
  }
609
652
  let root;
610
653
  try {
611
- root = postcss.parse(source, { from: abs });
654
+ root = postcss2.parse(source, { from: abs });
612
655
  } catch {
613
656
  return;
614
657
  }
@@ -705,6 +748,7 @@ async function resolveActiveMarkdownFiles(inputDir, configuredFiles) {
705
748
  async function renderChapters(inputDir, opts = {}) {
706
749
  const styles = await resolveActiveStyles(inputDir, opts.styles);
707
750
  const inlined = await inlineStyles(inputDir, styles);
751
+ const book = liftPageRules(inlined.css);
708
752
  const blocks = [];
709
753
  const layers = [];
710
754
  const styleWarnings = [];
@@ -719,10 +763,13 @@ async function renderChapters(inputDir, opts = {}) {
719
763
  if (!css)
720
764
  continue;
721
765
  layers.push(group.layer);
766
+ const { layered, pages } = liftPageRules(css);
722
767
  blocks.push(`/* ${group.name} */
723
768
  @layer ${group.layer} {
724
- ${css.trim()}
725
- }`);
769
+ ${layered.trim()}
770
+ }` + (pages ? `
771
+ /* ${group.name}: page rules */
772
+ ${pages}` : ""));
726
773
  }
727
774
  styleWarnings.push(...inlined.warnings);
728
775
  if (styleWarnings.length > 0)
@@ -740,7 +787,8 @@ ${css.trim()}
740
787
  return assembleBookHtml({
741
788
  files,
742
789
  readText: (relPath) => readFile2(join(inputDir, canonicalChapterId(relPath)), "utf-8"),
743
- projectCss: inlined.css,
790
+ projectCss: book.pages ? `${book.layered.trim()}
791
+ ${book.pages}` : inlined.css,
744
792
  preloadImages: cssAssetCopies.map((c) => c.to),
745
793
  title: opts.title,
746
794
  language: opts.language,
@@ -2737,7 +2785,7 @@ registerCheck(check19);
2737
2785
  import { readFile as readFile11 } from "node:fs/promises";
2738
2786
 
2739
2787
  // src/lib/printsafe.ts
2740
- import postcss2 from "postcss";
2788
+ import postcss3 from "postcss";
2741
2789
 
2742
2790
  // src/lib/lint-rules.ts
2743
2791
  function ruleLevel(rules, id) {
@@ -3029,7 +3077,7 @@ function filterFindings(findings, css, rules) {
3029
3077
  function checkCssRaw(css, from) {
3030
3078
  let root;
3031
3079
  try {
3032
- root = postcss2.parse(css, from ? { from } : undefined);
3080
+ root = postcss3.parse(css, from ? { from } : undefined);
3033
3081
  } catch (err) {
3034
3082
  const e = err;
3035
3083
  return [
@@ -3063,7 +3111,7 @@ function checkCssRaw(css, from) {
3063
3111
  warnings.push({
3064
3112
  rule: rulePageSizeInStylesheet,
3065
3113
  severity: "warning",
3066
- message: `"size" in a stylesheet's @page rule is ignored: the page size comes from the manifest (preset:, or page: width and height — Setup > Book in the app), which is written after every stylesheet. Remove this declaration; to print at another size, change the manifest.`,
3114
+ message: `"size" in a stylesheet's @page rule is ignored: the page size comes from the manifest (preset:, or page: width and height — set them in Book setup), which is written after every stylesheet. Remove this declaration; to print at another size, change the manifest.`,
3067
3115
  ...nodeLoc(decl)
3068
3116
  });
3069
3117
  return;
@@ -3199,7 +3247,7 @@ registerCheck(check20);
3199
3247
 
3200
3248
  // src/checks/source/css-ownership.ts
3201
3249
  import { readFile as readFile12 } from "node:fs/promises";
3202
- import postcss3 from "postcss";
3250
+ import postcss4 from "postcss";
3203
3251
  import { parse as parseYaml2 } from "yaml";
3204
3252
  var CONFIG_NAMES3 = [".gutterpress/css-contract.yaml", ".gutterpress/css-contract.yml"];
3205
3253
  async function loadContract(configPath) {
@@ -3356,7 +3404,7 @@ var check21 = {
3356
3404
  }
3357
3405
  let root;
3358
3406
  try {
3359
- root = postcss3.parse(css, { from: absFile });
3407
+ root = postcss4.parse(css, { from: absFile });
3360
3408
  } catch {
3361
3409
  continue;
3362
3410
  }
@@ -3851,7 +3899,7 @@ registerCheck(check25);
3851
3899
  import { readFile as readFile15 } from "node:fs/promises";
3852
3900
 
3853
3901
  // src/lib/unknown-classes.ts
3854
- import postcss4 from "postcss";
3902
+ import postcss5 from "postcss";
3855
3903
  var EMITTED = new Set([
3856
3904
  "chapter",
3857
3905
  "spread",
@@ -3899,7 +3947,7 @@ function addSelectorClasses(selector, into) {
3899
3947
  }
3900
3948
  function addCssClasses(css, into) {
3901
3949
  try {
3902
- postcss4.parse(css).walk((node) => {
3950
+ postcss5.parse(css).walk((node) => {
3903
3951
  if (node.type === "rule")
3904
3952
  addSelectorClasses(node.selector, into);
3905
3953
  else if (node.type === "atrule") {
@@ -4097,7 +4145,7 @@ registerCheck(check27);
4097
4145
  import { readFile as readFile17 } from "node:fs/promises";
4098
4146
 
4099
4147
  // src/checks/source/character-table.ts
4100
- var CHARACTER_PATTERN = new RegExp("[\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F-\\u00A0\\u00AD\\u00B4\\u02BC" + "\\u061C\\u2000-\\u2015\\u2018\\u201B-\\u201D\\u201F\\u2022\\u2026" + "\\u2028-\\u202F\\u2032\\u2033\\u205F\\u2060\\u2066-\\u2069\\u2190\\u2192" + "\\u2194\\u21D2\\u2212\\u25AA\\u25E6\\u2605\\u2713\\u2714\\u2717\\u3000" + "\\uE000-\\uF8FF\\uFEFF\\uFF02\\uFF07\\uFFFD]" + "|\\p{Emoji_Presentation}|\\p{Extended_Pictographic}(?=\\uFE0F)" + "|(?<=[\\p{L}\\p{N}])\\u2019|\\u2019(?![\\p{L}\\p{N}])", "gu");
4148
+ var CHARACTER_PATTERN = new RegExp("[\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F-\\u00A0\\u00AD\\u00B4\\u02BC" + "\\u061C\\u2000-\\u2015\\u2018\\u201B-\\u201D\\u201F\\u2022\\u2026" + "\\u2028-\\u202F\\u2032\\u2033\\u205F\\u2060\\u2066-\\u2069\\u2190\\u2192" + "\\u2194\\u21D2\\u25AA\\u25E6\\u2605\\u2713\\u2714\\u2717\\u3000" + "\\uE000-\\uF8FF\\uFEFF\\uFF02\\uFF07\\uFFFD]" + "|\\p{Emoji_Presentation}|\\p{Extended_Pictographic}(?=\\uFE0F)" + "|(?<=[\\p{L}\\p{N}])\\u2019|\\u2019(?![\\p{L}\\p{N}])", "gu");
4101
4149
  var NAMED = {
4102
4150
  8212: ["Em dash", "typographer", "---"],
4103
4151
  8211: ["En dash", "typographer", "--"],
@@ -4118,7 +4166,6 @@ var NAMED = {
4118
4166
  8209: ["Non-breaking hyphen", "lookalike", "-"],
4119
4167
  8210: ["Figure dash", "lookalike", "-"],
4120
4168
  8213: ["Horizontal bar", "lookalike", "---"],
4121
- 8722: ["Minus sign", "lookalike", "-"],
4122
4169
  8226: ["Bullet", "symbol", "&bull;"],
4123
4170
  9702: ["White bullet", "symbol", "&#x25E6;"],
4124
4171
  9642: ["Small square", "symbol", "&squf;"],
@@ -5532,7 +5579,7 @@ class PdfOutput {
5532
5579
  const rawPdf = pdfxMode ? path7.join(stage, "raw.pdf") : path7.resolve(pdfFile);
5533
5580
  await fsp2.mkdir(path7.dirname(path7.resolve(pdfFile)), { recursive: true });
5534
5581
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
5535
- const { buildNativePdf } = await import("./engine-wm4fggac.js");
5582
+ const { buildNativePdf } = await import("./engine-cs5g2ez0.js");
5536
5583
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
5537
5584
  title: config.title,
5538
5585
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
@@ -7216,10 +7263,10 @@ function starterOverridesCss() {
7216
7263
  return `/**
7217
7264
  * Your book's own styles. Rules here win over your look's.
7218
7265
  *
7219
- * In the app you rarely need this file: Setup > Look chooses a look and
7220
- * changes its fonts and colors (those changes are saved here as :root
7221
- * custom properties), Setup > Advanced lists every setting, and Setup > Book
7222
- * sets the page size and margins (in the manifest).
7266
+ * In the app you rarely need this file: Book setup chooses a look, changes
7267
+ * its fonts and colors (those changes are saved here as :root custom
7268
+ * properties), lists every setting, and sets the page size and margins (in
7269
+ * the manifest).
7223
7270
  *
7224
7271
  * To change the look from the command line: gutterpress ext remove <old>
7225
7272
  * <book>, then gutterpress ext add <look> <book>.
@@ -7361,7 +7408,7 @@ async function scaffoldProject(options) {
7361
7408
  let versionHistoryError;
7362
7409
  if (requested === "local-git") {
7363
7410
  try {
7364
- const { providerFor } = await import("./source-provider-tgzet3j5.js");
7411
+ const { providerFor } = await import("./source-provider-tjaf303m.js");
7365
7412
  const provider = providerFor({ type: "local-folder", path: projectDir });
7366
7413
  await provider.initVersionHistory({
7367
7414
  projectDir,
@@ -7452,7 +7499,7 @@ styles:
7452
7499
  let versionHistoryError;
7453
7500
  if (requested === "local-git") {
7454
7501
  try {
7455
- const { providerFor } = await import("./source-provider-tgzet3j5.js");
7502
+ const { providerFor } = await import("./source-provider-tjaf303m.js");
7456
7503
  const provider = providerFor({ type: "local-folder", path: dir });
7457
7504
  await provider.initVersionHistory({
7458
7505
  projectDir: dir,
@@ -8621,7 +8668,7 @@ async function setActiveStyles(projectDir, paths) {
8621
8668
  import path19 from "node:path";
8622
8669
  import { mkdir as mkdir10, readFile as readFile30, writeFile as writeFile12 } from "node:fs/promises";
8623
8670
  import git3 from "isomorphic-git";
8624
- import postcss5 from "postcss";
8671
+ import postcss6 from "postcss";
8625
8672
 
8626
8673
  // src/lib/remote-auth/transport.ts
8627
8674
  import git2 from "isomorphic-git";
@@ -9087,7 +9134,7 @@ function paragraphs(text) {
9087
9134
  }
9088
9135
  function cssRules(text) {
9089
9136
  try {
9090
- return postcss5.parse(text).nodes.map((node) => node.toString().trim()).filter((rule) => rule.length > 0);
9137
+ return postcss6.parse(text).nodes.map((node) => node.toString().trim()).filter((rule) => rule.length > 0);
9091
9138
  } catch {
9092
9139
  return textLines(text);
9093
9140
  }
@@ -9108,13 +9155,10 @@ function changeHint(filepath, before, after) {
9108
9155
  }
9109
9156
  return count > 0 ? `${plural(count, unit)} changed` : "changed";
9110
9157
  }
9111
- async function firstParent(dir, cache2, oid) {
9112
- const { commit } = await git3.readCommit({ fs: gitFs, dir, cache: cache2, oid });
9113
- return commit.parent[0] ?? null;
9114
- }
9115
- async function listVersionChanges(dir, oid, options = {}) {
9116
- const cache2 = options.cache ?? {};
9117
- const parent = await firstParent(dir, cache2, oid);
9158
+ async function listVersionChanges(projectDir, oid) {
9159
+ const dir = await repoDirFor(projectDir);
9160
+ const cache2 = {};
9161
+ const parent = (await git3.readCommit({ fs: gitFs, dir, cache: cache2, oid })).commit.parent[0] ?? null;
9118
9162
  const found = [];
9119
9163
  await git3.walk({
9120
9164
  fs: gitFs,
@@ -9185,52 +9229,105 @@ async function trackingTip(dir) {
9185
9229
  }
9186
9230
  return resolveOrNull(dir, `refs/remotes/${remote.remote}/${branch}`);
9187
9231
  }
9188
- async function isAncestor(dir, cache2, oid, ancestor) {
9189
- if (oid === ancestor)
9190
- return true;
9191
- try {
9192
- return await git3.isDescendent({ fs: gitFs, dir, cache: cache2, oid, ancestor });
9193
- } catch {
9194
- return false;
9195
- }
9196
- }
9197
9232
  async function describeVersions(projectDir, entries) {
9198
9233
  const dir = await repoDirFor(projectDir);
9199
- const cache2 = {};
9200
9234
  const tip = await trackingTip(dir);
9235
+ if (!tip)
9236
+ return entries.map((entry) => ({ ...entry, backedUp: null }));
9237
+ const cache2 = {};
9238
+ const shallow = await readFile30(path19.join(dir, ".git", "shallow"), "utf8").catch(() => "");
9239
+ const reached = new Set([tip]);
9240
+ const queued = new Set;
9241
+ const queue = [tip];
9201
9242
  const out = [];
9202
9243
  let olderAreBackedUp = false;
9203
9244
  for (const entry of entries) {
9204
- const { files, more } = await listVersionChanges(dir, entry.id, { cache: cache2 });
9205
- let backedUp = null;
9206
- if (tip) {
9207
- backedUp = olderAreBackedUp || await isAncestor(dir, cache2, tip, entry.id);
9208
- if (backedUp)
9209
- olderAreBackedUp = true;
9245
+ try {
9246
+ while (!olderAreBackedUp && !reached.has(entry.id) && queue.length > 0) {
9247
+ const oid = queue.shift();
9248
+ const { commit } = await git3.readCommit({ fs: gitFs, dir, cache: cache2, oid });
9249
+ for (const parent of commit.parent)
9250
+ reached.add(parent);
9251
+ if (shallow.includes(oid))
9252
+ continue;
9253
+ for (const parent of commit.parent) {
9254
+ if (queued.has(parent))
9255
+ continue;
9256
+ queued.add(parent);
9257
+ queue.push(parent);
9258
+ }
9259
+ }
9260
+ } catch {
9261
+ queue.length = 0;
9210
9262
  }
9211
- out.push({ ...entry, changes: files, moreChanges: more, backedUp });
9263
+ const backedUp = olderAreBackedUp || reached.has(entry.id);
9264
+ if (backedUp)
9265
+ olderAreBackedUp = true;
9266
+ out.push({ ...entry, backedUp });
9212
9267
  }
9213
9268
  return out;
9214
9269
  }
9270
+ var CLOCK_SKEW_SECONDS = 10 * 60;
9215
9271
  async function commitsAhead(dir, fromOid, baseOid, cache2 = {}, limit = 100) {
9216
9272
  if (fromOid === baseOid)
9217
9273
  return [];
9218
- const bases = await git3.findMergeBase({ fs: gitFs, dir, cache: cache2, oids: [fromOid, baseOid] });
9219
- const stopAt = new Set(bases);
9220
- if (stopAt.size === 0)
9221
- return [];
9222
- const commits = await git3.log({ fs: gitFs, dir, cache: cache2, ref: fromOid, depth: limit });
9223
- const seen = new Set;
9224
- const out = [];
9225
- for (const c of commits) {
9226
- if (stopAt.has(c.oid))
9274
+ const walk = await startHistoryWalk(dir, cache2, [baseOid, fromOid]);
9275
+ const local = new Set([baseOid]);
9276
+ const online = new Set([fromOid]);
9277
+ const parentsOf = new Map;
9278
+ const listed = [];
9279
+ let shared = false;
9280
+ const mark = (oid) => {
9281
+ const stack = [oid];
9282
+ for (let next = stack.pop();next !== undefined; next = stack.pop()) {
9283
+ if (local.has(next))
9284
+ continue;
9285
+ local.add(next);
9286
+ if (online.has(next))
9287
+ shared = true;
9288
+ stack.push(...parentsOf.get(next) ?? []);
9289
+ }
9290
+ };
9291
+ const firstAhead = () => {
9292
+ const out = [];
9293
+ for (const c of listed) {
9294
+ if (out.length === limit)
9295
+ break;
9296
+ if (!local.has(c.oid))
9297
+ out.push(c);
9298
+ }
9299
+ return out;
9300
+ };
9301
+ const time = (c) => c.commit.committer.timestamp;
9302
+ for (;; ) {
9303
+ const searching = walk.tips.some((c2) => !local.has(c2.oid));
9304
+ if (!searching || shared && listed.length >= limit) {
9305
+ const shown = firstAhead();
9306
+ if (!searching || shown.length === limit) {
9307
+ const oldest = Math.min(...shown.map(time));
9308
+ const newestMarked = walk.tips.findLast((c2) => local.has(c2.oid));
9309
+ if (!newestMarked || time(newestMarked) < oldest - CLOCK_SKEW_SECONDS)
9310
+ break;
9311
+ }
9312
+ }
9313
+ const c = await nextInHistory(dir, walk, cache2, 1);
9314
+ if (!c)
9227
9315
  break;
9228
- if (seen.has(c.oid))
9316
+ const parents = walk.shallow.has(c.oid) ? [] : c.commit.parent;
9317
+ parentsOf.set(c.oid, parents);
9318
+ if (local.has(c.oid)) {
9319
+ for (const p of parents)
9320
+ mark(p);
9229
9321
  continue;
9230
- seen.add(c.oid);
9231
- out.push(toEntry(c));
9322
+ }
9323
+ listed.push(c);
9324
+ for (const p of parents) {
9325
+ online.add(p);
9326
+ if (local.has(p))
9327
+ shared = true;
9328
+ }
9232
9329
  }
9233
- return out;
9330
+ return shared ? firstAhead().map(toEntry) : [];
9234
9331
  }
9235
9332
  async function listOnlineVersions(options) {
9236
9333
  const http2 = options.httpClient ?? defaultGitHttp;
@@ -9258,12 +9355,8 @@ async function listOnlineVersions(options) {
9258
9355
  const local = await resolveOrNull(dir, branch);
9259
9356
  if (!tip || !local)
9260
9357
  return { entries: [] };
9261
- const entries = [];
9262
- for (const entry of await commitsAhead(dir, tip, local, cache2)) {
9263
- const { files, more } = await listVersionChanges(dir, entry.id, { cache: cache2 });
9264
- entries.push({ ...entry, changes: files, moreChanges: more, backedUp: true });
9265
- }
9266
- return { entries };
9358
+ const entries = await commitsAhead(dir, tip, local, cache2);
9359
+ return { entries: entries.map((entry) => ({ ...entry, backedUp: true })) };
9267
9360
  } catch (e) {
9268
9361
  const message = e?.message ?? "";
9269
9362
  if (message === MSG_NO_REMOTE || message === MSG_SSH_REMOTE)
@@ -9953,9 +10046,9 @@ function isMergeConflictError(e) {
9953
10046
  function isCheckoutConflict(e) {
9954
10047
  return e?.code === "CheckoutConflictError";
9955
10048
  }
9956
- var CONVERGE_PREPARE_MESSAGE = "Getting your changes ready to combine with the online version";
9957
- var CONVERGE_RESTORE_MESSAGE = "Kept both versions of the files that can't be combined";
9958
- var CONVERGE_MERGE_MESSAGE = "Combined your changes with the online version";
10049
+ var CONVERGE_PREPARE_MESSAGE = "Getting your changes ready to combine with the online copy's changes";
10050
+ var CONVERGE_RESTORE_MESSAGE = "Kept both takes of the files that can't be combined";
10051
+ var CONVERGE_MERGE_MESSAGE = "Combined your changes with the online copy's";
9959
10052
  var MARKER_UNSAFE_EXT = /\.svg$/i;
9960
10053
  var OUR_LABEL = YOUR_VERSION_LABEL;
9961
10054
  var THEIR_LABEL = ONLINE_VERSION_LABEL;
@@ -10485,8 +10578,8 @@ var API_BASE3 = "https://api.github.com";
10485
10578
  var REQUEST_TIMEOUT_MS4 = 15000;
10486
10579
  var MAX_NAME_LENGTH = 100;
10487
10580
  var MSG = {
10488
- notConnected: "Connect your GitHub account first (Settings > Accounts), then set up the online copy.",
10489
- auth: "GitHub didn't accept the saved sign-in. Sign in to GitHub again in Settings > Accounts and try again.",
10581
+ notConnected: "Connect your GitHub account first, then set up the online copy.",
10582
+ auth: "GitHub didn't accept the saved sign-in. Sign in to GitHub again and try again.",
10490
10583
  offline: "Gutterpress couldn't reach GitHub. Check your internet connection and try again. Nothing was changed.",
10491
10584
  noHistory: "Turn on version history for this book first. The online copy holds the versions Gutterpress keeps on this computer.",
10492
10585
  alreadyHasRemote: "This book already has an online copy.",
@@ -11659,13 +11752,13 @@ async function createFolder(fetchImpl, accessToken, name) {
11659
11752
  throw await driveFailure(res, `Couldn't create the Google Drive folder "${name}"`);
11660
11753
  return await res.json();
11661
11754
  }
11662
- async function findFolderByName(fetchImpl, accessToken, name) {
11755
+ async function findFoldersByName(fetchImpl, accessToken, name, limit = 1) {
11663
11756
  const q = `name='${escapeDriveQueryValue(name)}' and mimeType='${FOLDER_MIME}' and trashed=false`;
11664
11757
  const params = new URLSearchParams({
11665
11758
  q,
11666
11759
  fields: "files(id,name)",
11667
11760
  orderBy: "modifiedTime desc",
11668
- pageSize: "1"
11761
+ pageSize: String(limit)
11669
11762
  });
11670
11763
  const res = await driveFetch(fetchImpl, `${DRIVE_API_BASE}/files?${params.toString()}`, {
11671
11764
  method: "GET",
@@ -11674,10 +11767,10 @@ async function findFolderByName(fetchImpl, accessToken, name) {
11674
11767
  if (!res.ok)
11675
11768
  throw await driveFailure(res, `Couldn't look up the Google Drive folder "${name}"`);
11676
11769
  const body = await res.json();
11677
- return body.files?.[0] ?? null;
11770
+ return body.files ?? [];
11678
11771
  }
11679
11772
  async function ensureFolder(fetchImpl, accessToken, name) {
11680
- const found = await findFolderByName(fetchImpl, accessToken, name);
11773
+ const [found] = await findFoldersByName(fetchImpl, accessToken, name);
11681
11774
  if (found)
11682
11775
  return found;
11683
11776
  return createFolder(fetchImpl, accessToken, name);
@@ -11810,8 +11903,80 @@ async function resumableUpload(fetchImpl, accessToken, opts) {
11810
11903
  }
11811
11904
  }
11812
11905
 
11813
- // src/lib/publish/providers/gdrive.ts
11906
+ // src/lib/publish/providers/gdrive-folder.ts
11814
11907
  var DEFAULT_FOLDER_NAME = "Gutterpress";
11908
+ var NO_OWN_FOLDER = { myFolderId: "", myFolder: "", myFolderFor: "", myFolderAccount: "" };
11909
+ function readConfig(req) {
11910
+ const text = (key) => {
11911
+ const value = req.config[key];
11912
+ return (typeof value === "string" ? value.trim() : "") || undefined;
11913
+ };
11914
+ return {
11915
+ folder: text("folder"),
11916
+ folderId: text("folderId"),
11917
+ myFolderId: text("myFolderId"),
11918
+ myFolder: text("myFolder"),
11919
+ myFolderFor: text("myFolderFor"),
11920
+ myFolderAccount: text("myFolderAccount"),
11921
+ account: req.deps.credentialAccount?.trim() ?? ""
11922
+ };
11923
+ }
11924
+ async function resolveFolder(req, accessToken) {
11925
+ const fetchImpl = req.deps.fetch ?? globalThis.fetch;
11926
+ const cfg = readConfig(req);
11927
+ let found = [];
11928
+ if (cfg.folderId) {
11929
+ const folder = await getFolderById(fetchImpl, accessToken, cfg.folderId);
11930
+ if (folder)
11931
+ found = [folder];
11932
+ } else if (cfg.folder) {
11933
+ found = await findFoldersByName(fetchImpl, accessToken, cfg.folder, 2);
11934
+ }
11935
+ const first = found[0];
11936
+ const book = { title: cfg.folder || cfg.folderId || "", reachable: !cfg.folderId || !!first };
11937
+ if (cfg.myFolderId) {
11938
+ const bookKey = cfg.folderId || cfg.folder;
11939
+ if (bookKey && cfg.myFolderFor === bookKey && (cfg.myFolderAccount ?? "") === cfg.account) {
11940
+ const mine = await getFolderById(fetchImpl, accessToken, cfg.myFolderId);
11941
+ return mine ? { state: "mine", destination: toDestination(mine), book } : { state: "unreachable", book, mine: cfg.myFolder || cfg.myFolderId };
11942
+ }
11943
+ if (book.reachable) {
11944
+ await req.deps.selections?.setSettings(req.project.projectDir, "gdrive", NO_OWN_FOLDER);
11945
+ }
11946
+ }
11947
+ if (cfg.folderId) {
11948
+ return first ? { state: "book", destination: toDestination(first) } : { state: "unreachable", book };
11949
+ }
11950
+ if (cfg.folder) {
11951
+ return {
11952
+ state: "by-name",
11953
+ name: cfg.folder,
11954
+ ...first ? { destination: toDestination(first) } : {},
11955
+ several: found.length > 1
11956
+ };
11957
+ }
11958
+ return { state: "unset" };
11959
+ }
11960
+ async function uploadFolder(where, fetchImpl, accessToken) {
11961
+ switch (where.state) {
11962
+ case "unreachable":
11963
+ throw new Error(where.mine ? `Your own Drive folder "${where.mine}" for this book can't be reached any more: it was trashed or deleted, or belongs to another Google account. Choose a folder you can reach on the Google Drive step (it is saved for you only).` : `This book publishes to the Drive folder "${where.book.title}", which this Google account can't reach: it isn't shared with Gutterpress under this account, or it was trashed or deleted. The book keeps its folder for everyone else. To publish to a folder you can reach, choose one for yourself on the Google Drive step in the desktop app (saved for you only), or pass --folder-id <id> or --folder <name> to "gutterpress publish" for one run.`);
11964
+ case "unset":
11965
+ return toDestination(await ensureFolder(fetchImpl, accessToken, DEFAULT_FOLDER_NAME));
11966
+ case "by-name":
11967
+ return where.destination ?? toDestination(await createFolder(fetchImpl, accessToken, where.name));
11968
+ default:
11969
+ return where.destination;
11970
+ }
11971
+ }
11972
+ function folderUrl(id) {
11973
+ return `https://drive.google.com/drive/folders/${id}`;
11974
+ }
11975
+ function toDestination(folder) {
11976
+ return { id: folder.id, title: folder.name, url: folderUrl(folder.id) };
11977
+ }
11978
+
11979
+ // src/lib/publish/providers/gdrive.ts
11815
11980
  var NOT_CONNECTED_MESSAGE = "Google Drive isn't connected. Run `gutterpress publish --provider gdrive --connect` (or set GDRIVE_REFRESH_TOKEN) first.";
11816
11981
  var info5 = {
11817
11982
  id: "gdrive",
@@ -11830,13 +11995,6 @@ var info5 = {
11830
11995
  },
11831
11996
  destinations: { label: "Folder", canCreate: true }
11832
11997
  };
11833
- function readConfig(req) {
11834
- const cfg = req.config;
11835
- return {
11836
- folder: cfg.folder?.trim() || undefined,
11837
- folderId: cfg.folderId?.trim() || undefined
11838
- };
11839
- }
11840
11998
  function formatBytes(bytes) {
11841
11999
  if (bytes < 1024 * 1024)
11842
12000
  return `${(bytes / 1024).toFixed(0)} KB`;
@@ -11858,22 +12016,6 @@ async function getAccessToken(req) {
11858
12016
  const accessToken = await mintAccessToken(resolved, req.deps.fetch ?? globalThis.fetch);
11859
12017
  return { accessToken, source: resolved.source };
11860
12018
  }
11861
- async function resolveFolder(fetchImpl, accessToken, cfg) {
11862
- if (cfg.folderId) {
11863
- const folder = await getFolderById(fetchImpl, accessToken, cfg.folderId);
11864
- if (!folder) {
11865
- throw new Error(`The Drive folder recorded in publish.gdrive.folderId ("${cfg.folderId}") can't be found — it may have been trashed or the id copied wrong. Pick the folder again.`);
11866
- }
11867
- return folder;
11868
- }
11869
- return ensureFolder(fetchImpl, accessToken, cfg.folder || DEFAULT_FOLDER_NAME);
11870
- }
11871
- function folderUrl(id) {
11872
- return `https://drive.google.com/drive/folders/${id}`;
11873
- }
11874
- function toDestination(folder) {
11875
- return { id: folder.id, title: folder.name, url: folderUrl(folder.id) };
11876
- }
11877
12019
  var ZIP_CHUNK_BYTES = 64 * 1024;
11878
12020
  function yieldToEventLoop() {
11879
12021
  return new Promise((resolve6) => setImmediate(resolve6));
@@ -12000,9 +12142,12 @@ var gdriveProvider = {
12000
12142
  const folder = await createFolder(req.deps.fetch ?? globalThis.fetch, accessToken, name);
12001
12143
  return toDestination(folder);
12002
12144
  },
12145
+ async destinationStatus(req) {
12146
+ const { accessToken } = await getAccessToken(req);
12147
+ return resolveFolder(req, accessToken);
12148
+ },
12003
12149
  async upload(req) {
12004
12150
  const fetchImpl = req.deps.fetch ?? globalThis.fetch;
12005
- const cfg = readConfig(req);
12006
12151
  const { accessToken } = await getAccessToken(req);
12007
12152
  const isHtml = req.artifact.format === "html";
12008
12153
  const source = isHtml ? await zipHtmlExport(req.artifact.path, req.project.title, (msg) => req.deps.onProgress?.(msg)) : {
@@ -12020,10 +12165,11 @@ var gdriveProvider = {
12020
12165
  }
12021
12166
  }
12022
12167
  req.deps.onProgress?.("Resolving the Drive folder…");
12023
- const folder = await resolveFolder(fetchImpl, accessToken, cfg);
12168
+ const where = await resolveFolder(req, accessToken);
12169
+ const folder = await uploadFolder(where, fetchImpl, accessToken);
12024
12170
  const fileName = source.fileName;
12025
12171
  const existing = await findFileInFolder(fetchImpl, accessToken, folder.id, fileName);
12026
- req.deps.onProgress?.(existing ? `Updating "${fileName}" in "${folder.name}"…` : `Uploading "${fileName}" to "${folder.name}"…`);
12172
+ req.deps.onProgress?.(existing ? `Updating "${fileName}" in "${folder.title}"…` : `Uploading "${fileName}" to "${folder.title}"…`);
12027
12173
  let lastReported = -1;
12028
12174
  const file = await resumableUpload(fetchImpl, accessToken, {
12029
12175
  ...existing ? { fileId: existing.id } : {},
@@ -12043,11 +12189,11 @@ var gdriveProvider = {
12043
12189
  return {
12044
12190
  kind: "published",
12045
12191
  url: file.webViewLink,
12046
- detail: `Uploaded "${fileName}" to the "${folder.name}" folder in your Google Drive${existing ? " (updated the existing file)" : ""}.`,
12192
+ detail: `Uploaded "${fileName}" to the "${folder.title}" folder in your Google Drive${existing ? " (updated the existing file)" : ""}.` + (where.state === "by-name" && where.several ? ` Several of your folders are named "${where.name}"; this is the most recently changed one: ${folder.url}` : ""),
12047
12193
  followUp: [
12048
12194
  "To share it, open it in Drive and use the Share button — Gutterpress never changes who can see your files.",
12049
12195
  isHtml ? "Google Drive stores files, not live websites — download and unzip it to view the export, or publish it as a real site with the Azure Static Web Apps provider." : undefined,
12050
- cfg.folderId ? undefined : `Tip (CLI): record the folder id in the manifest (publish.gdrive.folderId: "${folder.id}") so renaming the folder in Drive can never break publishing.`
12196
+ where.state === "unset" ? `Tip (CLI): record the folder id in the manifest (publish.gdrive.folderId: "${folder.id}") so renaming the folder in Drive can never break publishing.` : undefined
12051
12197
  ].filter((s) => !!s)
12052
12198
  };
12053
12199
  } finally {
@@ -12659,7 +12805,7 @@ async function resolvePublishProvider(id, book) {
12659
12805
  const ext = await bookExtensions(book);
12660
12806
  const npmEntry = ext.extensions.some((c) => !c.path && c.name === id);
12661
12807
  if (npmEntry) {
12662
- const { restoreForCommand: restoreForCommand2 } = await import("./extension-restore-vghc58vx.js");
12808
+ const { restoreForCommand: restoreForCommand2 } = await import("./extension-restore-chj5nx3t.js");
12663
12809
  await restoreForCommand2(book.projectDir, { manifestPath: book.manifestPath, failFast: true });
12664
12810
  }
12665
12811
  const publishers = await discover(ext);
@@ -12697,7 +12843,7 @@ function infoFor(p) {
12697
12843
  }
12698
12844
  function pluginProvider(p) {
12699
12845
  let loading;
12700
- const load = () => loading ??= import("./plugins-3aj0wc5h.js").then((m) => m.loadPublishFunction(p));
12846
+ const load = () => loading ??= import("./plugins-0hhw6s17.js").then((m) => m.loadPublishFunction(p));
12701
12847
  return {
12702
12848
  info: infoFor(p),
12703
12849
  async authenticate() {
@@ -12766,7 +12912,8 @@ async function requestFor(provider, options, deps) {
12766
12912
  const publishSettings = manifest.publish ?? {};
12767
12913
  const providerConfig = {
12768
12914
  ...publishSettings[provider.info.id] ?? {},
12769
- ...(await deps.selections?.settings(options.projectDir))?.[provider.info.id] ?? {}
12915
+ ...(await deps.selections?.settings(options.projectDir))?.[provider.info.id] ?? {},
12916
+ ...options.settings
12770
12917
  };
12771
12918
  const bookAccount = typeof providerConfig.credential === "string" ? providerConfig.credential.trim() : "";
12772
12919
  const effectiveAccount = bookAccount || deps.credentialAccount;
@@ -13145,19 +13292,26 @@ async function savePublishSettings(projectDir, providerId, values, selections) {
13145
13292
  if (inside !== null) {
13146
13293
  shared[key] = inside;
13147
13294
  mine[key] = "";
13148
- } else if (isDir || key === "credential") {
13295
+ } else if (isDir || key === "credential" || providerId === "gdrive" && key.startsWith("myFolder")) {
13149
13296
  mine[key] = isDir && text ? path31.resolve(projectDir, text) : text;
13150
13297
  shared[key] = "";
13151
13298
  } else {
13152
13299
  shared[key] = value;
13153
13300
  }
13154
13301
  }
13302
+ const inManifest = (await readPublishSettings(projectDir))[providerId] ?? {};
13303
+ if ("myFolderId" in mine) {
13304
+ const own = !!mine.myFolderId;
13305
+ const account = mine.credential ?? (await selections.settings(projectDir))[providerId]?.credential;
13306
+ mine.myFolder = own ? mine.myFolder ?? "" : "";
13307
+ mine.myFolderFor = own ? String(inManifest.folderId || inManifest.folder || "") : "";
13308
+ mine.myFolderAccount = own ? account ?? String(inManifest.credential ?? "") : "";
13309
+ }
13155
13310
  if (Object.keys(mine).length > 0)
13156
13311
  await selections.setSettings(projectDir, providerId, mine);
13157
- const inManifest = (await readPublishSettings(projectDir))[providerId] ?? {};
13158
13312
  const changes = Object.fromEntries(Object.entries(shared).filter(([key, value]) => value !== "" || (key in inManifest)));
13159
13313
  if (Object.keys(changes).length > 0)
13160
13314
  await setPublishProviderConfig(projectDir, providerId, changes);
13161
13315
  return readPublishSettings(projectDir, selections);
13162
13316
  }
13163
- export { SYNC_SNAPSHOT_MESSAGE, resolveActiveStyles, listProjectStyles, getChecks, getCheckById, resolveCheckSelectors, formatReport, ruleRemoteUrls, ruleRiskyProps, ruleSyntax, checkCss, inspectImage, runChecks, checkToolAvailability, reportMissingTools, executeValidation, executeAndReport, splitOutPath, runBuild, openPath, startPreviewServer, defaultConfigDir, FileTokenStore, extractUrlCredential, getSystemDiagnostics, readPageSetup, setPageSetup, slugifyProjectName, escapeYamlScalar, scaffoldProject, adoptFolder, EXTENSION_KINDS, RESERVED_PREFIX, resolveExtensionPrefix, scaffoldExtension, BUILT_IN_TEMPLATE_IDS, listBuiltInTemplates, saveProjectAsTemplate, listCustomTemplates, importTemplateFromFolder, SNIPPETS_DIR, extractVariables, substituteVariables, listSnippets, saveSnippet, deleteSnippet, listMergedSnippets, listMarkerComponents, listLocationVariants, importExtensionFromFile, importExtensionFromUrl, MAX_SEARCH_BYTES, SEARCH_KEYWORDS, searchNpmExtensions, setManifestFields, readManifestFields, setActiveStyles, VERSION_CHANGES_SHOWN, describeVersions, listOnlineVersions, compareVersionFile, exportVersionTree, AUTO_SNAPSHOT_DEFAULT_MINUTES, AUTO_SYNC_MIN_MINUTES, AUTO_SYNC_MAX_MINUTES, AUTO_SYNC_DEFAULT_MINUTES, AUTO_SYNC_PUSH_INTERVAL_MINUTES, autoSnapshotDelayMs, autoSyncDelayMs, isGitInternalPath, resolveGitHubClientId, GITHUB_HOST, GitHubAuthProvider, GDRIVE_HOST, resolveGoogleClientId, resolveGoogleClientSecret, GOOGLE_NOT_CONFIGURED_MESSAGE, requireGoogleClientCredentials, pkceChallengeFromVerifier, GoogleAuthProvider, revokeGoogleCredential, publishCredentialKey, publishConnectionStatus, listPublishAccounts, connectGoogleDrive, listGitHubRepositories, listRepoBooks, listGitHubBranches, syncProject, refreshRemoteCopies, checkRemoteUpdates, isValidRepositoryName, setUpOnlineBackup, sanitizeCloneFolderName, cloneRepository, isSshRemoteUrl, testRemoteAccess, knownForgeTokenUrl, connectGenericHost, parseRemoteOrigin, forgeKindForHost, diagnoseProjectRemote, renameRevision, deleteRevision, REPAIR_SNAPSHOT_MESSAGE, repairOnlineBackup, scorchedEarth, listPublishProviders, publishProviderFor, listBookPublishProviders, resolvePublishProvider, resolvePublishFormat, resolvePublishRequest, runPublish, connectPublishProvider, disconnectPublishCredential, PublishSelectionsStore, readPublishSettings, setPublishProviderConfig, savePublishSettings };
13317
+ export { SYNC_SNAPSHOT_MESSAGE, assembleCssChunks, relDisplay, resolveActiveStyles, listProjectStyles, getChecks, getCheckById, resolveCheckSelectors, formatReport, ruleRemoteUrls, ruleRiskyProps, ruleSyntax, checkCss, inspectImage, runChecks, checkToolAvailability, reportMissingTools, executeValidation, executeAndReport, splitOutPath, runBuild, openPath, startPreviewServer, defaultConfigDir, FileTokenStore, extractUrlCredential, getSystemDiagnostics, readPageSetup, setPageSetup, slugifyProjectName, escapeYamlScalar, scaffoldProject, adoptFolder, EXTENSION_KINDS, RESERVED_PREFIX, resolveExtensionPrefix, scaffoldExtension, BUILT_IN_TEMPLATE_IDS, listBuiltInTemplates, saveProjectAsTemplate, listCustomTemplates, importTemplateFromFolder, SNIPPETS_DIR, extractVariables, substituteVariables, listSnippets, saveSnippet, deleteSnippet, listMergedSnippets, listMarkerComponents, listLocationVariants, importExtensionFromFile, importExtensionFromUrl, MAX_SEARCH_BYTES, SEARCH_KEYWORDS, searchNpmExtensions, setManifestFields, readManifestFields, setActiveStyles, VERSION_CHANGES_SHOWN, listVersionChanges, describeVersions, listOnlineVersions, compareVersionFile, exportVersionTree, AUTO_SNAPSHOT_DEFAULT_MINUTES, AUTO_SYNC_MIN_MINUTES, AUTO_SYNC_MAX_MINUTES, AUTO_SYNC_DEFAULT_MINUTES, AUTO_SYNC_PUSH_INTERVAL_MINUTES, autoSnapshotDelayMs, autoSyncDelayMs, isGitInternalPath, resolveGitHubClientId, GITHUB_HOST, GitHubAuthProvider, GDRIVE_HOST, resolveGoogleClientId, resolveGoogleClientSecret, GOOGLE_NOT_CONFIGURED_MESSAGE, requireGoogleClientCredentials, pkceChallengeFromVerifier, GoogleAuthProvider, revokeGoogleCredential, publishCredentialKey, publishConnectionStatus, listPublishAccounts, connectGoogleDrive, listGitHubRepositories, listRepoBooks, listGitHubBranches, syncProject, refreshRemoteCopies, checkRemoteUpdates, isValidRepositoryName, setUpOnlineBackup, sanitizeCloneFolderName, cloneRepository, isSshRemoteUrl, testRemoteAccess, knownForgeTokenUrl, connectGenericHost, parseRemoteOrigin, forgeKindForHost, diagnoseProjectRemote, renameRevision, deleteRevision, REPAIR_SNAPSHOT_MESSAGE, repairOnlineBackup, scorchedEarth, listPublishProviders, publishProviderFor, listBookPublishProviders, resolvePublishProvider, resolvePublishFormat, resolvePublishRequest, runPublish, connectPublishProvider, disconnectPublishCredential, PublishSelectionsStore, readPublishSettings, setPublishProviderConfig, savePublishSettings };