gutterpress 0.11.13-alpha.1 → 0.11.14-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.
@@ -33,8 +33,8 @@ export { listBuiltInTemplates, listCustomTemplates, saveProjectAsTemplate, impor
33
33
  export type { TemplateInfo, SaveProjectAsTemplateOptions, } from "../lib/project-templates.ts";
34
34
  export { extractVariables, substituteVariables, listSnippets, readSnippet, saveSnippet, deleteSnippet, listMergedSnippets, readExtensionSnippet, SNIPPETS_DIR, } from "../lib/snippets.ts";
35
35
  export type { SnippetEntry, SnippetSource } from "../lib/snippets.ts";
36
- export { listProjectExtensions, describeExtension, addExtension, removeExtension, setExtensionEnabled, reorderExtensions, validateProjectExtensions, readExtensionCss, listBuiltInStyleSets, addBuiltInStyleSet, sameExtension, RECOMMENDED_EXTENSIONS, BUILT_IN_STYLE_SET_IDS, EXTENSIONS_DIR, PLUGINS_DIR, } from "../lib/extension-manager.ts";
37
- export type { ProjectExtensionEntry, ExtensionValidationResult, RecommendedExtension, ExtensionSourceKind, BuiltInStyleSet, BuiltInStyleSetId, AddExtensionOptions, } from "../lib/extension-manager.ts";
36
+ export { listProjectExtensions, describeExtension, addExtension, checkExtensionUpdates, updateExtensions, removeExtension, setExtensionEnabled, reorderExtensions, validateProjectExtensions, readExtensionCss, listBuiltInStyleSets, addBuiltInStyleSet, sameExtension, RECOMMENDED_EXTENSIONS, BUILT_IN_STYLE_SET_IDS, EXTENSIONS_DIR, PLUGINS_DIR, } from "../lib/extension-manager.ts";
37
+ export type { ProjectExtensionEntry, ExtensionValidationResult, RecommendedExtension, ExtensionSourceKind, BuiltInStyleSet, BuiltInStyleSetId, AddExtensionOptions, ExtensionUpdateCheck, ExtensionUpdateResult, CheckExtensionUpdatesOptions, UpdateExtensionsOptions, } from "../lib/extension-manager.ts";
38
38
  export { BUNDLED_EXTENSIONS, isBundledExtension, isPathSpecifier, parseExtensionSpecifier, } from "../lib/extension-specifier.ts";
39
39
  export type { ParsedExtensionSpecifier } from "../lib/extension-specifier.ts";
40
40
  export { importExtensionFromFile, importExtensionFromUrl } from "../lib/extension-import.ts";
package/dist/api/index.js CHANGED
@@ -36,6 +36,7 @@ import {
36
36
  assertExtensionContained,
37
37
  autoSnapshotDelayMs,
38
38
  autoSyncDelayMs,
39
+ checkExtensionUpdates,
39
40
  cloneRepository,
40
41
  connectGenericHost,
41
42
  connectGoogleDrive,
@@ -126,8 +127,9 @@ import {
126
127
  substituteVariables,
127
128
  syncProject,
128
129
  testRemoteAccess,
130
+ updateExtensions,
129
131
  validateProjectExtensions
130
- } from "../index-3gxxb075.js";
132
+ } from "../index-ch9k0kam.js";
131
133
  import {
132
134
  BuildError
133
135
  } from "../index-mw8b25yx.js";
@@ -162,6 +164,7 @@ import"../index-37x76zdn.js";
162
164
  export {
163
165
  whenGitIdle,
164
166
  validateProjectExtensions,
167
+ updateExtensions,
165
168
  testRemoteAccess,
166
169
  syncProject,
167
170
  switchBranch,
@@ -264,6 +267,7 @@ export {
264
267
  connectGoogleDrive,
265
268
  connectGenericHost,
266
269
  cloneRepository,
270
+ checkExtensionUpdates,
267
271
  capabilitiesFor,
268
272
  autoSyncDelayMs,
269
273
  autoSnapshotDelayMs,
@@ -1,17 +1,17 @@
1
1
  import {
2
2
  runBuild,
3
3
  splitOutPath
4
- } from "./cli-ahhc9qhq.js";
4
+ } from "./cli-s38v88rv.js";
5
5
  import {
6
6
  log
7
- } from "./cli-2r5m5wyx.js";
7
+ } from "./cli-cmyfeqg6.js";
8
8
  import {
9
9
  UsageError,
10
10
  parseFormat,
11
11
  parsePdfxFlavor,
12
12
  rejectExtraPositionals,
13
13
  rejectUnknownFlags
14
- } from "./cli-e9matqtf.js";
14
+ } from "./cli-tctw7rcm.js";
15
15
  import"./cli-tjhn2mdn.js";
16
16
  import {
17
17
  BuildError
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  package_default
3
- } from "./cli-e9matqtf.js";
3
+ } from "./cli-tctw7rcm.js";
4
4
  import {
5
5
  execCapture,
6
6
  findTool,
@@ -13,12 +13,12 @@ import {
13
13
  stampCreator,
14
14
  stripAnnotations,
15
15
  warn
16
- } from "./cli-2r5m5wyx.js";
16
+ } from "./cli-cmyfeqg6.js";
17
17
  import {
18
18
  DEBOUNCE,
19
19
  UsageError,
20
20
  resolvePort
21
- } from "./cli-e9matqtf.js";
21
+ } from "./cli-tctw7rcm.js";
22
22
  import {
23
23
  MARGIN_BOX_IGNORED_PROPERTIES,
24
24
  getAssetPath,
@@ -9347,7 +9347,7 @@ var SKIP_ENTRIES = new Set([
9347
9347
  ]);
9348
9348
 
9349
9349
  // src/lib/extension-manager.ts
9350
- import { cp as cp2, mkdir as mkdir8, readFile as readFile26, rm as rm3, stat as stat6 } from "node:fs/promises";
9350
+ import { cp as cp2, mkdir as mkdir8, readdir as readdir5, readFile as readFile26, rm as rm3, stat as stat6 } from "node:fs/promises";
9351
9351
  import { existsSync as existsSync12 } from "node:fs";
9352
9352
  import path17 from "node:path";
9353
9353
  import { isSeq as isSeq3, isMap as isMap2, isScalar as isScalar2, YAMLMap, Scalar } from "yaml";
@@ -10013,6 +10013,7 @@ async function rollbackNpmPluginInstall(installed) {
10013
10013
  }
10014
10014
 
10015
10015
  // src/lib/extension-manager.ts
10016
+ import { gt as semverGt } from "semver";
10016
10017
  var EXTENSIONS_DIR = "extensions";
10017
10018
  var RECOMMENDED_EXTENSIONS = [
10018
10019
  {
@@ -10377,9 +10378,91 @@ async function addExtensionUnlocked(projectDir, specifier, options) {
10377
10378
  await finalizeNpmPluginInstall(installed).catch((error2) => {
10378
10379
  warnings.push(`The extension was installed, but an old backup could not be removed: ` + `${error2 instanceof Error ? error2.message : String(error2)}`);
10379
10380
  });
10381
+ await pruneOtherVersions(projectDir, installed.name, installed.version).catch((error2) => {
10382
+ warnings.push(`The extension was installed, but a previous version's vendored copy could not be removed: ` + `${error2 instanceof Error ? error2.message : String(error2)}`);
10383
+ });
10380
10384
  const entry = await describeExtension(projectDir, use);
10381
10385
  return warnings.length > 0 ? { ...entry, warnings: [...entry.warnings ?? [], ...warnings] } : entry;
10382
10386
  }
10387
+ async function pruneOtherVersions(projectDir, name, keep) {
10388
+ const parent = path17.dirname(vendoredNpmPluginRoot(projectDir, name, keep));
10389
+ let entries;
10390
+ try {
10391
+ entries = await readdir5(parent, { withFileTypes: true });
10392
+ } catch {
10393
+ return;
10394
+ }
10395
+ for (const entry of entries) {
10396
+ if (!entry.isDirectory() || entry.name === keep || !isExactNpmVersion(entry.name))
10397
+ continue;
10398
+ await rm3(path17.join(parent, entry.name), { recursive: true, force: true });
10399
+ }
10400
+ }
10401
+ var LATEST_LOOKUP_TIMEOUT_MS = 30000;
10402
+ var MAX_PACKUMENT_BYTES = 15 * 1024 * 1024;
10403
+ async function latestNpmVersion(name, options) {
10404
+ const fetchImpl = options.fetch ?? globalThis.fetch;
10405
+ return withFetchTimeout({
10406
+ timeoutMs: LATEST_LOOKUP_TIMEOUT_MS,
10407
+ signal: options.signal,
10408
+ timeoutMessage: `Looking up ${name} on npm timed out. Check your connection and try again.`,
10409
+ offlineMessage: (cause) => `Looking up ${name} on npm failed (${cause instanceof Error ? cause.message : String(cause)}).`
10410
+ }, async (signal) => {
10411
+ const response = await fetchImpl(`${npmRegistryUrl()}/${encodeURIComponent(name)}`, {
10412
+ signal,
10413
+ redirect: "error",
10414
+ headers: { accept: "application/vnd.npm.install-v1+json" }
10415
+ });
10416
+ if (response.status === 404)
10417
+ throw new FriendlyHttpError(`npm package "${name}" was not found.`);
10418
+ if (!response.ok)
10419
+ throw new FriendlyHttpError(`Looking up ${name} on npm failed (HTTP ${response.status}).`);
10420
+ const bytes = await response.arrayBuffer();
10421
+ if (bytes.byteLength > MAX_PACKUMENT_BYTES) {
10422
+ throw new FriendlyHttpError(`npm metadata for "${name}" is too large to read.`);
10423
+ }
10424
+ let latest;
10425
+ try {
10426
+ latest = JSON.parse(Buffer.from(bytes).toString("utf8"))["dist-tags"]?.latest;
10427
+ } catch {
10428
+ throw new FriendlyHttpError(`npm metadata for "${name}" is not valid JSON.`);
10429
+ }
10430
+ if (typeof latest !== "string" || !isExactNpmVersion(latest)) {
10431
+ throw new FriendlyHttpError(`npm metadata for "${name}" has no latest version.`);
10432
+ }
10433
+ return latest;
10434
+ });
10435
+ }
10436
+ async function checkExtensionUpdates(projectDir, options = {}) {
10437
+ const entries = await listProjectExtensions(projectDir);
10438
+ const pinned = entries.filter((e) => e.kind === "npm" && e.version);
10439
+ return Promise.all(pinned.map(async (e) => {
10440
+ const current = e.version;
10441
+ const latest = await latestNpmVersion(e.name, options);
10442
+ return { use: e.use, name: e.name, current, latest, outdated: semverGt(latest, current) };
10443
+ }));
10444
+ }
10445
+ async function updateExtensions(projectDir, options = {}) {
10446
+ let checks2 = await checkExtensionUpdates(projectDir, options);
10447
+ if (options.only) {
10448
+ checks2 = checks2.filter((c) => c.name === options.only);
10449
+ if (checks2.length === 0) {
10450
+ throw new Error(`"${options.only}" is not a pinned npm extension of this book.`);
10451
+ }
10452
+ }
10453
+ const updated = [];
10454
+ for (const check37 of checks2) {
10455
+ if (!check37.outdated)
10456
+ continue;
10457
+ await addExtension(projectDir, pinnedNpmSpecifier(check37.name, check37.latest), {
10458
+ fetch: options.fetch,
10459
+ signal: options.signal,
10460
+ limits: options.limits
10461
+ });
10462
+ updated.push({ name: check37.name, from: check37.current, to: check37.latest });
10463
+ }
10464
+ return updated;
10465
+ }
10383
10466
  async function uniqueExtensionId(projectDir, base) {
10384
10467
  const slug = slugify(base) || "extension";
10385
10468
  let candidate = slug;
@@ -11429,7 +11512,7 @@ var drivethrurpgProvider = {
11429
11512
  };
11430
11513
 
11431
11514
  // src/lib/publish/providers/gdrive.ts
11432
- import { mkdtemp as mkdtemp3, readFile as readFile28, readdir as readdir5, rm as rm5, stat as stat7, writeFile as writeFile9 } from "node:fs/promises";
11515
+ import { mkdtemp as mkdtemp3, readFile as readFile28, readdir as readdir6, rm as rm5, stat as stat7, writeFile as writeFile9 } from "node:fs/promises";
11433
11516
  import { tmpdir as tmpdir3 } from "node:os";
11434
11517
  import path20 from "node:path";
11435
11518
  import { Zip, ZipDeflate } from "fflate";
@@ -11814,7 +11897,7 @@ function zipEntriesNonBlocking(entries, opts) {
11814
11897
  });
11815
11898
  }
11816
11899
  async function collectZipEntries(root, dir, out, onWarn) {
11817
- const entries = await readdir5(dir, { withFileTypes: true });
11900
+ const entries = await readdir6(dir, { withFileTypes: true });
11818
11901
  for (const entry of entries) {
11819
11902
  const full = path20.join(dir, entry.name);
11820
11903
  if (entry.isDirectory()) {
@@ -12487,7 +12570,7 @@ function publishProviderFor(id) {
12487
12570
  }
12488
12571
 
12489
12572
  // src/lib/publish/run-publish.ts
12490
- import { readdir as readdir6, stat as stat10 } from "node:fs/promises";
12573
+ import { readdir as readdir7, stat as stat10 } from "node:fs/promises";
12491
12574
  import path23 from "node:path";
12492
12575
  function resolvePublishFormat(info9, providerConfig) {
12493
12576
  if (info9.formats && info9.formats.length > 0) {
@@ -12583,7 +12666,7 @@ async function htmlDirIssues(dir) {
12583
12666
  message: `${dir} has no ${BOOK_HTML} — it isn't an HTML export. ${HTML_HINT}`
12584
12667
  });
12585
12668
  }
12586
- const entries = await readdir6(dir).catch(() => []);
12669
+ const entries = await readdir7(dir).catch(() => []);
12587
12670
  const extras = entries.filter((name) => name.toLowerCase().endsWith(".pdf") || name === BUILD_FINGERPRINT_FILENAME);
12588
12671
  if (extras.length > 0) {
12589
12672
  issues.push({
@@ -12899,4 +12982,4 @@ function normalizeProjectKey(projectDir) {
12899
12982
  }
12900
12983
  // src/lib/publish/manifest-publish.ts
12901
12984
  import { isMap as isMap4 } from "yaml";
12902
- export { isPathSpecifier, parseExtensionSpecifier, PRESET_IDS, PRESETS, TARGETS, TARGET_IDS, publishTargetFor, loadManifestWithPath, reportMissingTools, executeValidation, executeAndReport, splitOutPath, runBuild, openPath, startPreviewServer, scaffoldProject, EXTENSION_KINDS, scaffoldExtension, BUILT_IN_TEMPLATE_IDS, BUILT_IN_STYLE_SET_IDS, listProjectExtensions, setExtensionEnabled, removeExtension, addExtension, addBuiltInStyleSet, importExtensionFromFile, importExtensionFromUrl, searchNpmExtensions, publishCredentialKey, publishConnectionStatus, listPublishAccounts, connectGoogleDrive, listPublishProviders, publishProviderFor, resolvePublishFormat, runPublish, connectPublishProvider, disconnectPublishCredential };
12985
+ export { isPathSpecifier, parseExtensionSpecifier, PRESET_IDS, PRESETS, TARGETS, TARGET_IDS, publishTargetFor, loadManifestWithPath, reportMissingTools, executeValidation, executeAndReport, splitOutPath, runBuild, openPath, startPreviewServer, scaffoldProject, EXTENSION_KINDS, scaffoldExtension, BUILT_IN_TEMPLATE_IDS, BUILT_IN_STYLE_SET_IDS, listProjectExtensions, setExtensionEnabled, removeExtension, addExtension, checkExtensionUpdates, updateExtensions, addBuiltInStyleSet, importExtensionFromFile, importExtensionFromUrl, searchNpmExtensions, publishCredentialKey, publishConnectionStatus, listPublishAccounts, connectGoogleDrive, listPublishProviders, publishProviderFor, resolvePublishFormat, runPublish, connectPublishProvider, disconnectPublishCredential };
@@ -4,7 +4,7 @@ import {
4
4
  // package.json
5
5
  var package_default = {
6
6
  name: "gutterpress",
7
- version: "0.11.13-alpha.1",
7
+ version: "0.11.14-alpha.1",
8
8
  description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
9
9
  author: "itlackey",
10
10
  license: "MPL-2.0",
package/dist/cli.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  UsageError,
7
7
  package_default,
8
8
  rejectUnknownFlags
9
- } from "./cli-e9matqtf.js";
9
+ } from "./cli-tctw7rcm.js";
10
10
  import {
11
11
  EXIT_CODES
12
12
  } from "./cli-46ycxe6r.js";
@@ -18,14 +18,14 @@ import {
18
18
  import { defineCommand, parseArgs, runMain } from "citty";
19
19
  import { statSync } from "node:fs";
20
20
  var SUBCOMMANDS = {
21
- new: () => import("./new-f7b04mv4.js").then((m) => m.default),
22
- preview: () => import("./preview-5b8vk6vc.js").then((m) => m.default),
23
- build: () => import("./build-ncxz7s6y.js").then((m) => m.default),
24
- publish: () => import("./publish-0zrtaezn.js").then((m) => m.default),
25
- validate: () => import("./validate-yagx07vv.js").then((m) => m.default),
26
- preflight: () => import("./preflight-kpnbhs0r.js").then((m) => m.default),
27
- doctor: () => import("./doctor-94fx1qh5.js").then((m) => m.default),
28
- ext: () => import("./ext-4trpk0nf.js").then((m) => m.default)
21
+ new: () => import("./new-pa0pgcat.js").then((m) => m.default),
22
+ preview: () => import("./preview-msq4mz3a.js").then((m) => m.default),
23
+ build: () => import("./build-9ccmg4jw.js").then((m) => m.default),
24
+ publish: () => import("./publish-2rgbacn1.js").then((m) => m.default),
25
+ validate: () => import("./validate-ekbmn8wh.js").then((m) => m.default),
26
+ preflight: () => import("./preflight-6v0j7ae8.js").then((m) => m.default),
27
+ doctor: () => import("./doctor-2kp6wsnx.js").then((m) => m.default),
28
+ ext: () => import("./ext-kct0k4ne.js").then((m) => m.default)
29
29
  };
30
30
  var VERSION = package_default.version;
31
31
  var main = defineCommand({
@@ -50,7 +50,7 @@ async function preflightRequiredInvocations(rawArgs) {
50
50
  const [command, ...commandArgs] = rawArgs;
51
51
  try {
52
52
  if (command === "new") {
53
- const { newArgs } = await import("./new-f7b04mv4.js");
53
+ const { newArgs } = await import("./new-pa0pgcat.js");
54
54
  rejectUnknownFlags(commandArgs, newArgs, "new");
55
55
  const parsed = parseArgs(commandArgs, {
56
56
  ...newArgs,
@@ -62,7 +62,7 @@ async function preflightRequiredInvocations(rawArgs) {
62
62
  return;
63
63
  }
64
64
  if (command === "preflight") {
65
- const { preflightArgs } = await import("./preflight-kpnbhs0r.js");
65
+ const { preflightArgs } = await import("./preflight-6v0j7ae8.js");
66
66
  rejectUnknownFlags(commandArgs, preflightArgs, "preflight");
67
67
  const parsed = parseArgs(commandArgs, {
68
68
  ...preflightArgs,
@@ -82,7 +82,7 @@ async function preflightRequiredInvocations(rawArgs) {
82
82
  }
83
83
  if (subcommand.startsWith("-"))
84
84
  return;
85
- const { EXT_SUBCOMMANDS, extAddArgs, extRemoveArgs, extEnableArgs, extDisableArgs } = await import("./ext-4trpk0nf.js");
85
+ const { EXT_SUBCOMMANDS, extAddArgs, extRemoveArgs, extEnableArgs, extDisableArgs } = await import("./ext-kct0k4ne.js");
86
86
  if (!EXT_SUBCOMMANDS.includes(subcommand)) {
87
87
  throw new UsageError(`gutterpress ext: unknown command "${subcommand}"`);
88
88
  }
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  getSystemDiagnostics,
3
3
  log
4
- } from "./cli-2r5m5wyx.js";
4
+ } from "./cli-cmyfeqg6.js";
5
5
  import {
6
6
  UsageError,
7
7
  rejectExtraPositionals,
8
8
  rejectUnknownFlags
9
- } from "./cli-e9matqtf.js";
9
+ } from "./cli-tctw7rcm.js";
10
10
  import"./cli-46ycxe6r.js";
11
11
  import"./cli-7fkhgtfn.js";
12
12
  import"./cli-37x76zdn.js";
@@ -2,6 +2,7 @@ import {
2
2
  BUILT_IN_STYLE_SET_IDS,
3
3
  addBuiltInStyleSet,
4
4
  addExtension,
5
+ checkExtensionUpdates,
5
6
  importExtensionFromFile,
6
7
  importExtensionFromUrl,
7
8
  isPathSpecifier,
@@ -9,16 +10,17 @@ import {
9
10
  parseExtensionSpecifier,
10
11
  removeExtension,
11
12
  searchNpmExtensions,
12
- setExtensionEnabled
13
- } from "./cli-ahhc9qhq.js";
14
- import"./cli-2r5m5wyx.js";
13
+ setExtensionEnabled,
14
+ updateExtensions
15
+ } from "./cli-s38v88rv.js";
16
+ import"./cli-cmyfeqg6.js";
15
17
  import {
16
18
  UsageError,
17
19
  exitForUsage,
18
20
  rejectExtraPositionals,
19
21
  rejectUnknownFlags,
20
22
  resolveProjectDir
21
- } from "./cli-e9matqtf.js";
23
+ } from "./cli-tctw7rcm.js";
22
24
  import"./cli-tjhn2mdn.js";
23
25
  import {
24
26
  EXIT_CODES
@@ -72,7 +74,16 @@ var extSearchArgs = {
72
74
  required: false
73
75
  }
74
76
  };
75
- var EXT_SUBCOMMANDS = ["list", "add", "remove", "enable", "disable", "search"];
77
+ var extOutdatedArgs = { dir: dirArg };
78
+ var extUpdateArgs = {
79
+ name: {
80
+ type: "positional",
81
+ description: "The npm package to update; omit to update every pinned npm extension",
82
+ required: false
83
+ },
84
+ dir: dirArg
85
+ };
86
+ var EXT_SUBCOMMANDS = ["list", "add", "remove", "enable", "disable", "search", "outdated", "update"];
76
87
  var parentArgs = {};
77
88
  function rejectParentFlags(rawArgs) {
78
89
  const first = rawArgs[0];
@@ -290,20 +301,104 @@ var search = defineCommand({
290
301
  }
291
302
  }
292
303
  });
304
+ var outdated = defineCommand({
305
+ meta: {
306
+ name: "outdated",
307
+ description: "Compare each pinned npm extension with npm's latest (network); exit 1 when one is behind"
308
+ },
309
+ args: extOutdatedArgs,
310
+ async run({ args, rawArgs }) {
311
+ let projectDir = "";
312
+ try {
313
+ rejectUnknownFlags(rawArgs, extOutdatedArgs, "ext outdated");
314
+ rejectExtraPositionals(args._, 1, "ext outdated");
315
+ projectDir = await requireProjectDir(args.dir, "outdated");
316
+ } catch (error) {
317
+ exitForUsage(error);
318
+ }
319
+ try {
320
+ const checks = await checkExtensionUpdates(projectDir);
321
+ if (checks.length === 0) {
322
+ console.log("No pinned npm extensions to check.");
323
+ return;
324
+ }
325
+ const behind = checks.filter((c) => c.outdated);
326
+ if (behind.length === 0) {
327
+ console.log(`Every pinned npm extension is at its latest version (${checks.map((c) => c.use).join(", ")}).`);
328
+ return;
329
+ }
330
+ console.log("Extensions with a newer version on npm:");
331
+ for (const c of behind)
332
+ console.log(` ${c.name} ${c.current} -> ${c.latest}`);
333
+ for (const c of checks)
334
+ if (!c.outdated)
335
+ console.log(` ${c.name} ${c.current} (latest)`);
336
+ console.log("Update with: gutterpress ext update [name] [dir]");
337
+ process.exit(EXIT_CODES.FINDINGS);
338
+ } catch (error) {
339
+ failPipeline("Could not check npm for updates", error);
340
+ }
341
+ }
342
+ });
343
+ var update = defineCommand({
344
+ meta: {
345
+ name: "update",
346
+ description: "Re-pin every outdated npm extension (or one, by name) to npm's latest: download, verify, vendor, load-test"
347
+ },
348
+ args: extUpdateArgs,
349
+ async run({ args, rawArgs }) {
350
+ let projectDir = "";
351
+ let only;
352
+ try {
353
+ rejectUnknownFlags(rawArgs, extUpdateArgs, "ext update");
354
+ rejectExtraPositionals(args._, 2, "ext update");
355
+ let name = typeof args.name === "string" ? args.name.trim() : "";
356
+ let dir = args.dir;
357
+ if (name && dir === undefined) {
358
+ const looksLikePath = path.isAbsolute(name) || isPathSpecifier(name);
359
+ const isDir = await stat(path.resolve(process.cwd(), name)).then((s) => s.isDirectory()).catch(() => false);
360
+ if (looksLikePath || isDir) {
361
+ dir = name;
362
+ name = "";
363
+ }
364
+ }
365
+ projectDir = await requireProjectDir(dir, "update");
366
+ only = name || undefined;
367
+ } catch (error) {
368
+ exitForUsage(error);
369
+ }
370
+ try {
371
+ const updated = await updateExtensions(projectDir, { only });
372
+ if (updated.length === 0) {
373
+ console.log(only ? `${only} is already at its latest version.` : "Every pinned npm extension is already at its latest version.");
374
+ return;
375
+ }
376
+ for (const u of updated)
377
+ console.log(`Updated ${u.name} ${u.from} -> ${u.to}`);
378
+ console.log(` project: ${projectDir}`);
379
+ console.log(" vendored under: plugins/npm (previous version removed)");
380
+ console.log(" manifest: exact version re-pinned");
381
+ } catch (error) {
382
+ failPipeline("Could not update extensions", error);
383
+ }
384
+ }
385
+ });
293
386
  var ext_default = defineCommand({
294
387
  meta: {
295
388
  name: "ext",
296
- description: "List, add, remove, enable, disable the project's extensions, or search npm"
389
+ description: "List, add, remove, enable, disable or update the project's extensions, or search npm"
297
390
  },
298
391
  args: parentArgs,
299
392
  setup({ rawArgs }) {
300
393
  rejectParentFlags(rawArgs);
301
394
  },
302
- subCommands: { list, add, remove, enable, disable, search }
395
+ subCommands: { list, add, remove, enable, disable, search, outdated, update }
303
396
  });
304
397
  export {
398
+ extUpdateArgs,
305
399
  extSearchArgs,
306
400
  extRemoveArgs,
401
+ extOutdatedArgs,
307
402
  extListArgs,
308
403
  extEnableArgs,
309
404
  extDisableArgs,
@@ -3858,7 +3858,7 @@ import git from "isomorphic-git";
3858
3858
  // package.json
3859
3859
  var package_default = {
3860
3860
  name: "gutterpress",
3861
- version: "0.11.13-alpha.1",
3861
+ version: "0.11.14-alpha.1",
3862
3862
  description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
3863
3863
  author: "itlackey",
3864
3864
  license: "MPL-2.0",
@@ -10400,11 +10400,11 @@ async function importTemplateFromFolder(options) {
10400
10400
  return saveProjectAsTemplate({ projectDir: sourceDir, name, templatesRoot });
10401
10401
  }
10402
10402
  // src/lib/snippets.ts
10403
- import { readdir as readdir7, readFile as readFile30, writeFile as writeFile10, mkdir as mkdir11, rm as rm4 } from "node:fs/promises";
10403
+ import { readdir as readdir8, readFile as readFile30, writeFile as writeFile10, mkdir as mkdir11, rm as rm4 } from "node:fs/promises";
10404
10404
  import path20 from "node:path";
10405
10405
 
10406
10406
  // src/lib/extension-manager.ts
10407
- import { cp as cp3, mkdir as mkdir10, readFile as readFile29, rm as rm3, stat as stat7 } from "node:fs/promises";
10407
+ import { cp as cp3, mkdir as mkdir10, readdir as readdir7, readFile as readFile29, rm as rm3, stat as stat7 } from "node:fs/promises";
10408
10408
  import { existsSync as existsSync14 } from "node:fs";
10409
10409
  import path19 from "node:path";
10410
10410
  import { isSeq as isSeq3, isMap as isMap2, isScalar as isScalar2, YAMLMap, Scalar } from "yaml";
@@ -11070,6 +11070,7 @@ async function rollbackNpmPluginInstall(installed) {
11070
11070
  }
11071
11071
 
11072
11072
  // src/lib/extension-manager.ts
11073
+ import { gt as semverGt } from "semver";
11073
11074
  var EXTENSIONS_DIR = "extensions";
11074
11075
  var RECOMMENDED_EXTENSIONS = [
11075
11076
  {
@@ -11457,9 +11458,91 @@ async function addExtensionUnlocked(projectDir, specifier, options) {
11457
11458
  await finalizeNpmPluginInstall(installed).catch((error2) => {
11458
11459
  warnings.push(`The extension was installed, but an old backup could not be removed: ` + `${error2 instanceof Error ? error2.message : String(error2)}`);
11459
11460
  });
11461
+ await pruneOtherVersions(projectDir, installed.name, installed.version).catch((error2) => {
11462
+ warnings.push(`The extension was installed, but a previous version's vendored copy could not be removed: ` + `${error2 instanceof Error ? error2.message : String(error2)}`);
11463
+ });
11460
11464
  const entry = await describeExtension(projectDir, use);
11461
11465
  return warnings.length > 0 ? { ...entry, warnings: [...entry.warnings ?? [], ...warnings] } : entry;
11462
11466
  }
11467
+ async function pruneOtherVersions(projectDir, name, keep) {
11468
+ const parent = path19.dirname(vendoredNpmPluginRoot(projectDir, name, keep));
11469
+ let entries;
11470
+ try {
11471
+ entries = await readdir7(parent, { withFileTypes: true });
11472
+ } catch {
11473
+ return;
11474
+ }
11475
+ for (const entry of entries) {
11476
+ if (!entry.isDirectory() || entry.name === keep || !isExactNpmVersion(entry.name))
11477
+ continue;
11478
+ await rm3(path19.join(parent, entry.name), { recursive: true, force: true });
11479
+ }
11480
+ }
11481
+ var LATEST_LOOKUP_TIMEOUT_MS = 30000;
11482
+ var MAX_PACKUMENT_BYTES = 15 * 1024 * 1024;
11483
+ async function latestNpmVersion(name, options) {
11484
+ const fetchImpl = options.fetch ?? globalThis.fetch;
11485
+ return withFetchTimeout({
11486
+ timeoutMs: LATEST_LOOKUP_TIMEOUT_MS,
11487
+ signal: options.signal,
11488
+ timeoutMessage: `Looking up ${name} on npm timed out. Check your connection and try again.`,
11489
+ offlineMessage: (cause) => `Looking up ${name} on npm failed (${cause instanceof Error ? cause.message : String(cause)}).`
11490
+ }, async (signal) => {
11491
+ const response = await fetchImpl(`${npmRegistryUrl()}/${encodeURIComponent(name)}`, {
11492
+ signal,
11493
+ redirect: "error",
11494
+ headers: { accept: "application/vnd.npm.install-v1+json" }
11495
+ });
11496
+ if (response.status === 404)
11497
+ throw new FriendlyHttpError(`npm package "${name}" was not found.`);
11498
+ if (!response.ok)
11499
+ throw new FriendlyHttpError(`Looking up ${name} on npm failed (HTTP ${response.status}).`);
11500
+ const bytes = await response.arrayBuffer();
11501
+ if (bytes.byteLength > MAX_PACKUMENT_BYTES) {
11502
+ throw new FriendlyHttpError(`npm metadata for "${name}" is too large to read.`);
11503
+ }
11504
+ let latest;
11505
+ try {
11506
+ latest = JSON.parse(Buffer.from(bytes).toString("utf8"))["dist-tags"]?.latest;
11507
+ } catch {
11508
+ throw new FriendlyHttpError(`npm metadata for "${name}" is not valid JSON.`);
11509
+ }
11510
+ if (typeof latest !== "string" || !isExactNpmVersion(latest)) {
11511
+ throw new FriendlyHttpError(`npm metadata for "${name}" has no latest version.`);
11512
+ }
11513
+ return latest;
11514
+ });
11515
+ }
11516
+ async function checkExtensionUpdates(projectDir, options = {}) {
11517
+ const entries = await listProjectExtensions(projectDir);
11518
+ const pinned = entries.filter((e) => e.kind === "npm" && e.version);
11519
+ return Promise.all(pinned.map(async (e) => {
11520
+ const current = e.version;
11521
+ const latest = await latestNpmVersion(e.name, options);
11522
+ return { use: e.use, name: e.name, current, latest, outdated: semverGt(latest, current) };
11523
+ }));
11524
+ }
11525
+ async function updateExtensions(projectDir, options = {}) {
11526
+ let checks2 = await checkExtensionUpdates(projectDir, options);
11527
+ if (options.only) {
11528
+ checks2 = checks2.filter((c) => c.name === options.only);
11529
+ if (checks2.length === 0) {
11530
+ throw new Error(`"${options.only}" is not a pinned npm extension of this book.`);
11531
+ }
11532
+ }
11533
+ const updated = [];
11534
+ for (const check37 of checks2) {
11535
+ if (!check37.outdated)
11536
+ continue;
11537
+ await addExtension(projectDir, pinnedNpmSpecifier(check37.name, check37.latest), {
11538
+ fetch: options.fetch,
11539
+ signal: options.signal,
11540
+ limits: options.limits
11541
+ });
11542
+ updated.push({ name: check37.name, from: check37.current, to: check37.latest });
11543
+ }
11544
+ return updated;
11545
+ }
11463
11546
  async function listBuiltInStyleSets() {
11464
11547
  const assets = await getAssetsDir();
11465
11548
  const out = [];
@@ -11566,7 +11649,7 @@ function resolveSnippetPath(projectDir, fileName) {
11566
11649
  async function scanSnippetFiles(dir) {
11567
11650
  let names;
11568
11651
  try {
11569
- names = await readdir7(dir);
11652
+ names = await readdir8(dir);
11570
11653
  } catch {
11571
11654
  return [];
11572
11655
  }
@@ -12786,7 +12869,7 @@ async function listGitHubBranches(credential, owner, repo, options = {}) {
12786
12869
  return branches;
12787
12870
  }
12788
12871
  // src/lib/remote-auth/clone.ts
12789
- import { mkdir as mkdir13, readdir as readdir8, rm as rm6 } from "node:fs/promises";
12872
+ import { mkdir as mkdir13, readdir as readdir9, rm as rm6 } from "node:fs/promises";
12790
12873
  import path22 from "node:path";
12791
12874
  import git3 from "isomorphic-git";
12792
12875
 
@@ -13087,7 +13170,7 @@ function sanitizeCloneFolderName(name) {
13087
13170
  }
13088
13171
  async function assertCloneTarget(dir) {
13089
13172
  try {
13090
- const entries = await readdir8(dir);
13173
+ const entries = await readdir9(dir);
13091
13174
  if (entries.length > 0) {
13092
13175
  throw new Error("That folder already has files in it. Choose an empty folder for the book.");
13093
13176
  }
@@ -14009,16 +14092,16 @@ async function repairOnlineBackup(options) {
14009
14092
  }
14010
14093
  // src/lib/remote-auth/scorched-earth.ts
14011
14094
  import git8 from "isomorphic-git";
14012
- import { cp as cp6, mkdir as mkdir16, readdir as readdir9, rm as rm8 } from "node:fs/promises";
14095
+ import { cp as cp6, mkdir as mkdir16, readdir as readdir10, rm as rm8 } from "node:fs/promises";
14013
14096
  import path25 from "node:path";
14014
14097
  async function emptyDir(dir) {
14015
- for (const entry of await readdir9(dir)) {
14098
+ for (const entry of await readdir10(dir)) {
14016
14099
  await rm8(path25.join(dir, entry), { recursive: true, force: true });
14017
14100
  }
14018
14101
  }
14019
14102
  async function copyFilesBack(from, to) {
14020
14103
  const skip = path25.join(from, ".git");
14021
- for (const entry of await readdir9(from)) {
14104
+ for (const entry of await readdir10(from)) {
14022
14105
  const src = path25.join(from, entry);
14023
14106
  if (src === skip)
14024
14107
  continue;
@@ -14071,7 +14154,7 @@ async function scorchedEarth(options) {
14071
14154
  }
14072
14155
  }
14073
14156
  // src/lib/publish/run-publish.ts
14074
- import { readdir as readdir11, stat as stat12 } from "node:fs/promises";
14157
+ import { readdir as readdir12, stat as stat12 } from "node:fs/promises";
14075
14158
  import path30 from "node:path";
14076
14159
 
14077
14160
  // src/lib/publish/command-runner.ts
@@ -14302,7 +14385,7 @@ var drivethrurpgProvider = {
14302
14385
  };
14303
14386
 
14304
14387
  // src/lib/publish/providers/gdrive.ts
14305
- import { mkdtemp as mkdtemp3, readFile as readFile32, readdir as readdir10, rm as rm9, stat as stat9, writeFile as writeFile14 } from "node:fs/promises";
14388
+ import { mkdtemp as mkdtemp3, readFile as readFile32, readdir as readdir11, rm as rm9, stat as stat9, writeFile as writeFile14 } from "node:fs/promises";
14306
14389
  import { tmpdir as tmpdir3 } from "node:os";
14307
14390
  import path27 from "node:path";
14308
14391
  import { Zip, ZipDeflate } from "fflate";
@@ -14687,7 +14770,7 @@ function zipEntriesNonBlocking(entries, opts) {
14687
14770
  });
14688
14771
  }
14689
14772
  async function collectZipEntries(root, dir, out, onWarn) {
14690
- const entries = await readdir10(dir, { withFileTypes: true });
14773
+ const entries = await readdir11(dir, { withFileTypes: true });
14691
14774
  for (const entry of entries) {
14692
14775
  const full = path27.join(dir, entry.name);
14693
14776
  if (entry.isDirectory()) {
@@ -15454,7 +15537,7 @@ async function htmlDirIssues(dir) {
15454
15537
  message: `${dir} has no ${BOOK_HTML} — it isn't an HTML export. ${HTML_HINT}`
15455
15538
  });
15456
15539
  }
15457
- const entries = await readdir11(dir).catch(() => []);
15540
+ const entries = await readdir12(dir).catch(() => []);
15458
15541
  const extras = entries.filter((name) => name.toLowerCase().endsWith(".pdf") || name === BUILD_FINGERPRINT_FILENAME);
15459
15542
  if (extras.length > 0) {
15460
15543
  issues.push({
@@ -15691,4 +15774,4 @@ async function setPublishProviderConfig(projectDir, providerId, values) {
15691
15774
  await writeManifestDoc(file, doc);
15692
15775
  return settingsFromDoc(doc);
15693
15776
  }
15694
- export { BUNDLED_EXTENSIONS, isBundledExtension, isPathSpecifier, parseExtensionSpecifier, PRESET_IDS, PRESETS, TARGETS, TARGET_IDS, publishTargetFor, MANIFEST_FILENAMES, hasProjectManifest, loadManifest, loadManifestWithPath, resolveConfig, SYNC_SNAPSHOT_MESSAGE, log, resolveActiveStyles, listProjectStyles, EXTENSION_MANIFEST_FILENAME, readExtensionMeta, extensionStyleList, extensionEntry, extensionCarries, pathEscapesFolder, assertExtensionContained, resolveExtension, getChecks, getCheckById, resolveCheckSelectors, formatReport, ruleRemoteUrls, ruleRiskyProps, ruleSyntax, checkCss, inspectImage, runChecks, checkToolAvailability, reportMissingTools, executeValidation, executeAndReport, splitOutPath, runBuild, openPath, startPreviewServer, defaultConfigDir, FileTokenStore, extractUrlCredential, getSystemDiagnostics, slugifyProjectName, escapeYamlScalar, scaffoldProject, adoptFolder, ensureGitignoreHasDist, EXTENSION_KINDS, RESERVED_PREFIX, resolveExtensionPrefix, scaffoldExtension, BUILT_IN_TEMPLATE_IDS, listBuiltInTemplates, saveProjectAsTemplate, listCustomTemplates, importTemplateFromFolder, DEFAULT_NPM_REGISTRY, npmRegistryUrl, EXTENSIONS_DIR, RECOMMENDED_EXTENSIONS, BUILT_IN_STYLE_SET_IDS, sameExtension, listProjectExtensions, describeExtension, setExtensionEnabled, reorderExtensions, removeExtension, addExtension, listBuiltInStyleSets, addBuiltInStyleSet, validateProjectExtensions, readExtensionCss, SNIPPETS_DIR, extractVariables, substituteVariables, listSnippets, readSnippet, saveSnippet, deleteSnippet, listMergedSnippets, readExtensionSnippet, importExtensionFromFile, importExtensionFromUrl, MAX_SEARCH_BYTES, SEARCH_KEYWORDS, searchNpmExtensions, setManifestFields, readManifestFields, setActiveStyles, AUTO_SNAPSHOT_DEFAULT_MINUTES, AUTO_SYNC_MIN_MINUTES, AUTO_SYNC_MAX_MINUTES, AUTO_SYNC_DEFAULT_MINUTES, AUTO_SYNC_PUSH_INTERVAL_MINUTES, autoSnapshotDelayMs, autoSyncDelayMs, isGitInternalPath, resolveGitHubClientId, GITHUB_HOST, GitHubAuthProvider, GDRIVE_HOST, resolveGoogleClientId, resolveGoogleClientSecret, GOOGLE_NOT_CONFIGURED_MESSAGE, requireGoogleClientCredentials, pkceChallengeFromVerifier, GoogleAuthProvider, revokeGoogleCredential, publishCredentialKey, publishConnectionStatus, listPublishAccounts, connectGoogleDrive, listGitHubRepositories, listRepoBooks, listGitHubBranches, sanitizeCloneFolderName, cloneRepository, isSshRemoteUrl, testRemoteAccess, knownForgeTokenUrl, connectGenericHost, parseRemoteOrigin, forgeKindForHost, diagnoseProjectRemote, syncProject, refreshRemoteCopies, REPAIR_SNAPSHOT_MESSAGE, repairOnlineBackup, scorchedEarth, listPublishProviders, publishProviderFor, resolvePublishFormat, resolvePublishRequest, runPublish, connectPublishProvider, disconnectPublishCredential, PublishSelectionsStore, readPublishSettings, setPublishProviderConfig };
15777
+ export { BUNDLED_EXTENSIONS, isBundledExtension, isPathSpecifier, parseExtensionSpecifier, PRESET_IDS, PRESETS, TARGETS, TARGET_IDS, publishTargetFor, MANIFEST_FILENAMES, hasProjectManifest, loadManifest, loadManifestWithPath, resolveConfig, SYNC_SNAPSHOT_MESSAGE, log, resolveActiveStyles, listProjectStyles, EXTENSION_MANIFEST_FILENAME, readExtensionMeta, extensionStyleList, extensionEntry, extensionCarries, pathEscapesFolder, assertExtensionContained, resolveExtension, getChecks, getCheckById, resolveCheckSelectors, formatReport, ruleRemoteUrls, ruleRiskyProps, ruleSyntax, checkCss, inspectImage, runChecks, checkToolAvailability, reportMissingTools, executeValidation, executeAndReport, splitOutPath, runBuild, openPath, startPreviewServer, defaultConfigDir, FileTokenStore, extractUrlCredential, getSystemDiagnostics, slugifyProjectName, escapeYamlScalar, scaffoldProject, adoptFolder, ensureGitignoreHasDist, EXTENSION_KINDS, RESERVED_PREFIX, resolveExtensionPrefix, scaffoldExtension, BUILT_IN_TEMPLATE_IDS, listBuiltInTemplates, saveProjectAsTemplate, listCustomTemplates, importTemplateFromFolder, DEFAULT_NPM_REGISTRY, npmRegistryUrl, EXTENSIONS_DIR, RECOMMENDED_EXTENSIONS, BUILT_IN_STYLE_SET_IDS, sameExtension, listProjectExtensions, describeExtension, setExtensionEnabled, reorderExtensions, removeExtension, addExtension, checkExtensionUpdates, updateExtensions, listBuiltInStyleSets, addBuiltInStyleSet, validateProjectExtensions, readExtensionCss, SNIPPETS_DIR, extractVariables, substituteVariables, listSnippets, readSnippet, saveSnippet, deleteSnippet, listMergedSnippets, readExtensionSnippet, importExtensionFromFile, importExtensionFromUrl, MAX_SEARCH_BYTES, SEARCH_KEYWORDS, searchNpmExtensions, setManifestFields, readManifestFields, setActiveStyles, AUTO_SNAPSHOT_DEFAULT_MINUTES, AUTO_SYNC_MIN_MINUTES, AUTO_SYNC_MAX_MINUTES, AUTO_SYNC_DEFAULT_MINUTES, AUTO_SYNC_PUSH_INTERVAL_MINUTES, autoSnapshotDelayMs, autoSyncDelayMs, isGitInternalPath, resolveGitHubClientId, GITHUB_HOST, GitHubAuthProvider, GDRIVE_HOST, resolveGoogleClientId, resolveGoogleClientSecret, GOOGLE_NOT_CONFIGURED_MESSAGE, requireGoogleClientCredentials, pkceChallengeFromVerifier, GoogleAuthProvider, revokeGoogleCredential, publishCredentialKey, publishConnectionStatus, listPublishAccounts, connectGoogleDrive, listGitHubRepositories, listRepoBooks, listGitHubBranches, sanitizeCloneFolderName, cloneRepository, isSshRemoteUrl, testRemoteAccess, knownForgeTokenUrl, connectGenericHost, parseRemoteOrigin, forgeKindForHost, diagnoseProjectRemote, syncProject, refreshRemoteCopies, REPAIR_SNAPSHOT_MESSAGE, repairOnlineBackup, scorchedEarth, listPublishProviders, publishProviderFor, resolvePublishFormat, resolvePublishRequest, runPublish, connectPublishProvider, disconnectPublishCredential, PublishSelectionsStore, readPublishSettings, setPublishProviderConfig };
package/dist/index.js CHANGED
@@ -37,6 +37,7 @@ import {
37
37
  autoSnapshotDelayMs,
38
38
  autoSyncDelayMs,
39
39
  checkCss,
40
+ checkExtensionUpdates,
40
41
  checkToolAvailability,
41
42
  cloneRepository,
42
43
  connectGenericHost,
@@ -143,8 +144,9 @@ import {
143
144
  substituteVariables,
144
145
  syncProject,
145
146
  testRemoteAccess,
147
+ updateExtensions,
146
148
  validateProjectExtensions
147
- } from "./index-3gxxb075.js";
149
+ } from "./index-ch9k0kam.js";
148
150
  import {
149
151
  BUILD_DIAGNOSTIC_CODES
150
152
  } from "./index-b6p1fh61.js";
@@ -182,6 +184,7 @@ import"./index-37x76zdn.js";
182
184
  export {
183
185
  whenGitIdle,
184
186
  validateProjectExtensions,
187
+ updateExtensions,
185
188
  testRemoteAccess,
186
189
  syncProject,
187
190
  switchBranch,
@@ -300,6 +303,7 @@ export {
300
303
  connectGenericHost,
301
304
  cloneRepository,
302
305
  checkToolAvailability,
306
+ checkExtensionUpdates,
303
307
  checkCss,
304
308
  capabilitiesFor,
305
309
  autoSyncDelayMs,
@@ -146,6 +146,50 @@ export interface AddExtensionOptions extends NpmPluginInstallOptions {
146
146
  * Returns the described entry.
147
147
  */
148
148
  export declare function addExtension(projectDir: string, specifier: string, options?: AddExtensionOptions): Promise<ProjectExtensionEntry>;
149
+ /** One pinned npm extension against npm's `latest` tag. */
150
+ export interface ExtensionUpdateCheck {
151
+ /** The specifier as written in the manifest (`name@current`). */
152
+ use: string;
153
+ name: string;
154
+ /** The pinned version. */
155
+ current: string;
156
+ /** npm's `latest` dist-tag. */
157
+ latest: string;
158
+ /** `latest` is newer than `current` (semver). */
159
+ outdated: boolean;
160
+ }
161
+ export interface CheckExtensionUpdatesOptions {
162
+ /** Dependency injection for tests; production uses global fetch. */
163
+ fetch?: typeof globalThis.fetch;
164
+ signal?: AbortSignal;
165
+ }
166
+ /**
167
+ * Every pinned npm extension of the book against npm's `latest`, in manifest
168
+ * order. Bundled, path and unpinned entries are not npm's to answer for and
169
+ * are skipped. One lookup per package, in parallel; a failed lookup throws
170
+ * with the package named.
171
+ */
172
+ export declare function checkExtensionUpdates(projectDir: string, options?: CheckExtensionUpdatesOptions): Promise<ExtensionUpdateCheck[]>;
173
+ /** One extension `updateExtensions` moved. */
174
+ export interface ExtensionUpdateResult {
175
+ name: string;
176
+ from: string;
177
+ to: string;
178
+ }
179
+ export interface UpdateExtensionsOptions extends CheckExtensionUpdatesOptions {
180
+ /** Update only this package (its npm name); default every pinned npm entry. */
181
+ only?: string;
182
+ /** Test/specialized-host install limits — see `NpmPluginInstallOptions`. */
183
+ limits?: NpmPluginInstallOptions["limits"];
184
+ }
185
+ /**
186
+ * Re-pin every outdated npm extension (or just `only`) to npm's `latest`,
187
+ * one `addExtension(name@latest)` each — the same download, verification,
188
+ * vendoring, load test and in-place re-pin as `ext add`, keeping the entry's
189
+ * `export:` and `enabled:` as written and removing the old version's folder.
190
+ * Returns what moved; an up-to-date book returns `[]`.
191
+ */
192
+ export declare function updateExtensions(projectDir: string, options?: UpdateExtensionsOptions): Promise<ExtensionUpdateResult[]>;
149
193
  /** List the built-in looks (metadata read from the embedded assets). */
150
194
  export declare function listBuiltInStyleSets(): Promise<BuiltInStyleSet[]>;
151
195
  /** A folder name under `extensions/` not yet taken: `<slug>`, `<slug>-2`, … */
@@ -7,15 +7,15 @@ import {
7
7
  TARGET_IDS,
8
8
  scaffoldExtension,
9
9
  scaffoldProject
10
- } from "./cli-ahhc9qhq.js";
10
+ } from "./cli-s38v88rv.js";
11
11
  import {
12
12
  resolveGhostscript
13
- } from "./cli-2r5m5wyx.js";
13
+ } from "./cli-cmyfeqg6.js";
14
14
  import {
15
15
  UsageError,
16
16
  rejectExtraPositionals,
17
17
  rejectUnknownFlags
18
- } from "./cli-e9matqtf.js";
18
+ } from "./cli-tctw7rcm.js";
19
19
  import"./cli-tjhn2mdn.js";
20
20
  import {
21
21
  EXIT_CODES
@@ -2,15 +2,15 @@ import {
2
2
  executeValidation,
3
3
  publishTargetFor,
4
4
  reportMissingTools
5
- } from "./cli-ahhc9qhq.js";
5
+ } from "./cli-s38v88rv.js";
6
6
  import {
7
7
  log
8
- } from "./cli-2r5m5wyx.js";
8
+ } from "./cli-cmyfeqg6.js";
9
9
  import {
10
10
  UsageError,
11
11
  rejectExtraPositionals,
12
12
  rejectUnknownFlags
13
- } from "./cli-e9matqtf.js";
13
+ } from "./cli-tctw7rcm.js";
14
14
  import"./cli-tjhn2mdn.js";
15
15
  import {
16
16
  EXIT_CODES
@@ -6,10 +6,10 @@ import {
6
6
  runBuild,
7
7
  splitOutPath,
8
8
  startPreviewServer
9
- } from "./cli-ahhc9qhq.js";
9
+ } from "./cli-s38v88rv.js";
10
10
  import {
11
11
  log
12
- } from "./cli-2r5m5wyx.js";
12
+ } from "./cli-cmyfeqg6.js";
13
13
  import {
14
14
  UsageError,
15
15
  parseFormat,
@@ -17,7 +17,7 @@ import {
17
17
  rejectExtraPositionals,
18
18
  rejectUnknownFlags,
19
19
  resolvePort
20
- } from "./cli-e9matqtf.js";
20
+ } from "./cli-tctw7rcm.js";
21
21
  import"./cli-tjhn2mdn.js";
22
22
  import {
23
23
  BuildError
@@ -11,16 +11,16 @@ import {
11
11
  publishProviderFor,
12
12
  resolvePublishFormat,
13
13
  runPublish
14
- } from "./cli-ahhc9qhq.js";
14
+ } from "./cli-s38v88rv.js";
15
15
  import {
16
16
  FileTokenStore,
17
17
  log
18
- } from "./cli-2r5m5wyx.js";
18
+ } from "./cli-cmyfeqg6.js";
19
19
  import {
20
20
  UsageError,
21
21
  rejectExtraPositionals,
22
22
  rejectUnknownFlags
23
- } from "./cli-e9matqtf.js";
23
+ } from "./cli-tctw7rcm.js";
24
24
  import"./cli-tjhn2mdn.js";
25
25
  import {
26
26
  EXIT_CODES
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  executeAndReport
3
- } from "./cli-ahhc9qhq.js";
3
+ } from "./cli-s38v88rv.js";
4
4
  import {
5
5
  log
6
- } from "./cli-2r5m5wyx.js";
6
+ } from "./cli-cmyfeqg6.js";
7
7
  import {
8
8
  UsageError,
9
9
  rejectExtraPositionals,
10
10
  rejectUnknownFlags
11
- } from "./cli-e9matqtf.js";
11
+ } from "./cli-tctw7rcm.js";
12
12
  import"./cli-tjhn2mdn.js";
13
13
  import {
14
14
  EXIT_CODES
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gutterpress",
3
- "version": "0.11.13-alpha.1",
3
+ "version": "0.11.14-alpha.1",
4
4
  "description": "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
5
5
  "author": "itlackey",
6
6
  "license": "MPL-2.0",