@bendyline/docblocks 2.6.1 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/THIRD_PARTY_NOTICES.txt +33 -33
  2. package/dist/capabilities-CdQAc745.d.ts +1233 -0
  3. package/dist/catalog-BQK05tne.d.ts +122 -0
  4. package/dist/chunk-245W57QP.js +31 -0
  5. package/dist/{chunk-2WDJ27EH.js → chunk-46AGYHDY.js} +8 -0
  6. package/dist/{chunk-4437YWLE.js → chunk-6UPO2GE6.js} +4 -2
  7. package/dist/{chunk-UMWCTEXW.js → chunk-7SN7YQL4.js} +211 -40
  8. package/dist/{chunk-OQCPBOQA.js → chunk-DAPFSEKY.js} +35 -0
  9. package/dist/{chunk-M62ZGJ5I.js → chunk-HABG2JM3.js} +0 -17
  10. package/dist/{chunk-YM7O3PWO.js → chunk-HKOOHM67.js} +20 -12
  11. package/dist/chunk-IDYE73TU.js +77 -0
  12. package/dist/{chunk-KLQKU74Q.js → chunk-IGB2VSRE.js} +124 -330
  13. package/dist/{chunk-BVY7GGNV.js → chunk-L6PRMR7T.js} +137 -95
  14. package/dist/chunk-LPIJ6HKL.js +19 -0
  15. package/dist/{chunk-SJBB5OD7.js → chunk-QS5WTLUZ.js} +10 -8
  16. package/dist/chunk-R2JX5N3F.js +382 -0
  17. package/dist/{chunk-ZO7G53QP.js → chunk-UDEWLHQI.js} +4 -2
  18. package/dist/chunk-UK7R3RC7.js +514 -0
  19. package/dist/chunk-XTNWCNSK.js +464 -0
  20. package/dist/chunk-YHSFRPBS.js +42 -0
  21. package/dist/chunk-YNUQG4GF.js +1173 -0
  22. package/dist/document/index.d.ts +31 -1
  23. package/dist/document/index.js +7 -6
  24. package/dist/filesystem/electron.d.ts +8 -34
  25. package/dist/filesystem/electron.js +7 -4
  26. package/dist/filesystem/host.d.ts +28 -0
  27. package/dist/filesystem/host.js +103 -0
  28. package/dist/filesystem/index.d.ts +52 -6
  29. package/dist/filesystem/index.js +36 -15
  30. package/dist/filesystem/indexeddb.d.ts +8 -1
  31. package/dist/filesystem/indexeddb.js +7 -6
  32. package/dist/filesystem/memory.d.ts +2 -1
  33. package/dist/filesystem/memory.js +3 -2
  34. package/dist/filesystem/native.d.ts +2 -1
  35. package/dist/filesystem/native.js +3 -2
  36. package/dist/filesystem-v2-C8AdSXUN.d.ts +74 -0
  37. package/dist/host/index.d.ts +127 -5
  38. package/dist/host/index.js +100 -4
  39. package/dist/host/mobile.d.ts +389 -0
  40. package/dist/host/mobile.js +315 -0
  41. package/dist/host-provider-v2-CyLTRKJF.d.ts +51 -0
  42. package/dist/index.d.ts +8 -5
  43. package/dist/index.js +153 -34
  44. package/dist/mcp/index.d.ts +2 -2
  45. package/dist/mcp/zod.d.ts +23 -1
  46. package/dist/mcp/zod.js +1 -0
  47. package/dist/settings-HrrCikVX.d.ts +170 -0
  48. package/dist/types-BvDnr3YU.d.ts +88 -0
  49. package/dist/{types-Oy9U5FST.d.ts → types-d0yvHUJz.d.ts} +6 -0
  50. package/dist/{types-Bb3yccTH.d.ts → v2-BVnZZyd4.d.ts} +1 -86
  51. package/dist/vscode/index.d.ts +43 -1
  52. package/dist/vscode/index.js +67 -1
  53. package/dist/workspace/index.d.ts +10 -6
  54. package/dist/workspace/index.js +4 -2
  55. package/dist/workspace-index/index.d.ts +240 -0
  56. package/dist/workspace-index/index.js +1287 -0
  57. package/dist/workspace-settings/index.d.ts +301 -0
  58. package/dist/workspace-settings/index.js +820 -0
  59. package/package.json +22 -2
  60. package/dist/chunk-6XHOOBYF.js +0 -36
  61. package/dist/chunk-QI6YKTQV.js +0 -110
  62. package/dist/types-DBYraXxE.d.ts +0 -557
@@ -0,0 +1,820 @@
1
+ import {
2
+ CATALOG_FORMAT_VERSION,
3
+ CATALOG_GENERATOR,
4
+ CATALOG_LIMITS,
5
+ OUTSIDE_IN_FORMAT_IDS,
6
+ buildCatalogMarkdownDocument,
7
+ catalogEntryForUnreadable,
8
+ catalogEntryFromDocument,
9
+ catalogEntryFromMarkdown,
10
+ chooseOutsideInMarkdownPath,
11
+ createWorkspaceCatalog,
12
+ isGeneratedCatalogHtml,
13
+ isGeneratedCatalogJson,
14
+ readCatalogHtmlDigest,
15
+ relativeCatalogLink,
16
+ relocateOutsideInLayout,
17
+ resolveOutsideInLayout,
18
+ serializeCatalogJson,
19
+ sha256Hex,
20
+ sortCatalogEntries,
21
+ stampCatalogHtml
22
+ } from "../chunk-R2JX5N3F.js";
23
+ import {
24
+ CATALOG_JSON_CONTENT_MODES,
25
+ CATALOG_SORT_ORDERS,
26
+ DEFAULT_CATALOG_HTML_PATH,
27
+ DEFAULT_CATALOG_JSON_PATH,
28
+ DEFAULT_CATALOG_TITLE,
29
+ EMPTY_WORKSPACE_SETTINGS,
30
+ UnsupportedWorkspaceSettingsVersionError,
31
+ WORKSPACE_SETTINGS_DIRECTORY,
32
+ WORKSPACE_SETTINGS_LIMITS,
33
+ WORKSPACE_SETTINGS_PATH,
34
+ WORKSPACE_SETTINGS_VERSION,
35
+ WORKSPACE_THEME_ID_PATTERN,
36
+ applyWorkspaceSettingsPatch,
37
+ checkCatalogOutputPath,
38
+ diffWorkspaceSettings,
39
+ isDefaultWorkspaceSettings,
40
+ isEmptyWorkspaceSettingsPatch,
41
+ parseWorkspaceSettings,
42
+ parseWorkspaceSettingsPatch,
43
+ parseWorkspaceSettingsText,
44
+ resolveCatalogOutputs,
45
+ resolveFallbackThemeId,
46
+ resolveWorkspaceDefaultThemeId,
47
+ resolveWorkspaceVersioningEnabled,
48
+ serializeWorkspaceSettings,
49
+ tryApplyWorkspaceSettingsPatch,
50
+ validateCatalogOutputPath
51
+ } from "../chunk-UK7R3RC7.js";
52
+ import {
53
+ SQUISQ_RUNTIME_DIRECTORY_NAME,
54
+ isDiscoverableWorkspaceEntry,
55
+ isIgnoredWorkspaceEntryName,
56
+ isMarkdownFileName
57
+ } from "../chunk-YHSFRPBS.js";
58
+ import {
59
+ decodeUtf8Text
60
+ } from "../chunk-M5ZNQGR5.js";
61
+ import {
62
+ FsError,
63
+ WORKSPACE_ROOT,
64
+ parseWorkspacePath
65
+ } from "../chunk-AYWKIZLD.js";
66
+
67
+ // src/workspace-settings/schema.ts
68
+ var themeId = {
69
+ type: "string",
70
+ pattern: WORKSPACE_THEME_ID_PATTERN.source,
71
+ description: 'A DocBlocks document theme id, such as "warm-earth".'
72
+ };
73
+ var relativePath = {
74
+ type: "string",
75
+ minLength: 1,
76
+ maxLength: WORKSPACE_SETTINGS_LIMITS.maxPathCharacters
77
+ };
78
+ var WORKSPACE_SETTINGS_JSON_SCHEMA = {
79
+ $schema: "http://json-schema.org/draft-07/schema#",
80
+ title: "DocBlocks workspace settings",
81
+ description: "Settings shared by everyone who opens this folder in DocBlocks. Every section is optional.",
82
+ type: "object",
83
+ additionalProperties: false,
84
+ required: ["version"],
85
+ properties: {
86
+ $schema: { type: "string", maxLength: WORKSPACE_SETTINGS_LIMITS.maxSchemaCharacters },
87
+ version: { const: 1, description: "Settings format version." },
88
+ documents: {
89
+ type: "object",
90
+ additionalProperties: false,
91
+ properties: {
92
+ defaultTheme: {
93
+ ...themeId,
94
+ description: "Theme for documents whose frontmatter does not name one. A document\u2019s own theme always wins."
95
+ }
96
+ }
97
+ },
98
+ versionHistory: {
99
+ type: "object",
100
+ additionalProperties: false,
101
+ properties: {
102
+ enabled: {
103
+ type: "boolean",
104
+ description: "Keep prior revisions for documents in this folder. Omit to use the app default."
105
+ },
106
+ keep: {
107
+ type: "integer",
108
+ minimum: WORKSPACE_SETTINGS_LIMITS.minKeep,
109
+ maximum: WORKSPACE_SETTINGS_LIMITS.maxKeep,
110
+ description: "Revisions kept per document."
111
+ }
112
+ }
113
+ },
114
+ catalog: {
115
+ type: "object",
116
+ additionalProperties: false,
117
+ properties: {
118
+ title: {
119
+ type: "string",
120
+ minLength: 1,
121
+ maxLength: WORKSPACE_SETTINGS_LIMITS.maxTitleCharacters,
122
+ description: `Catalog heading. Defaults to "${DEFAULT_CATALOG_TITLE}".`
123
+ },
124
+ sort: {
125
+ enum: [...CATALOG_SORT_ORDERS],
126
+ description: 'Catalog order. Defaults to "title".'
127
+ },
128
+ exclude: {
129
+ type: "array",
130
+ maxItems: WORKSPACE_SETTINGS_LIMITS.maxExcludes,
131
+ items: relativePath,
132
+ description: "Workspace-relative folders or files left out of the catalog."
133
+ },
134
+ html: {
135
+ type: "object",
136
+ additionalProperties: false,
137
+ properties: {
138
+ enabled: { type: "boolean", description: "Regenerate the catalog page on every save." },
139
+ path: {
140
+ ...relativePath,
141
+ pattern: "\\.html?$",
142
+ description: `Catalog page path. Defaults to "${DEFAULT_CATALOG_HTML_PATH}".`
143
+ },
144
+ theme: { ...themeId, description: "Theme for the catalog page only." }
145
+ }
146
+ },
147
+ json: {
148
+ type: "object",
149
+ additionalProperties: false,
150
+ properties: {
151
+ enabled: { type: "boolean", description: "Regenerate the catalog data on every save." },
152
+ path: {
153
+ ...relativePath,
154
+ pattern: "\\.json$",
155
+ description: `Catalog data path. Defaults to "${DEFAULT_CATALOG_JSON_PATH}".`
156
+ },
157
+ content: {
158
+ enum: [...CATALOG_JSON_CONTENT_MODES],
159
+ description: '"metadata" (default) or "markdown" to include each document\u2019s source.'
160
+ }
161
+ }
162
+ }
163
+ }
164
+ }
165
+ }
166
+ };
167
+
168
+ // src/workspace-settings/outputs.ts
169
+ var WORKSPACE_OUTPUT_LIMITS = {
170
+ maxDocuments: 5e3,
171
+ maxDirectories: 2e4,
172
+ maxDepth: 32,
173
+ maxDocumentBytes: 4 * 1024 * 1024,
174
+ maxTotalBytes: 256 * 1024 * 1024,
175
+ maxOutputBytes: 32 * 1024 * 1024
176
+ };
177
+ var WorkspaceOutputsLimitError = class extends Error {
178
+ constructor(message) {
179
+ super(message);
180
+ this.name = "WorkspaceOutputsLimitError";
181
+ }
182
+ };
183
+ function createWorkspaceCatalogCache() {
184
+ return /* @__PURE__ */ new Map();
185
+ }
186
+ function throwIfAborted(signal) {
187
+ if (signal?.aborted) {
188
+ throw new FsError("aborted", "Workspace catalog refresh was cancelled.", {
189
+ operation: "read",
190
+ retryable: false
191
+ });
192
+ }
193
+ }
194
+ function isNotFound(error) {
195
+ return error instanceof FsError && (error.code === "not-found" || error.code === "type-mismatch");
196
+ }
197
+ function hasUndiscoverableSegment(path) {
198
+ return path.split("/").some(
199
+ (segment) => isIgnoredWorkspaceEntryName(segment) || segment === SQUISQ_RUNTIME_DIRECTORY_NAME
200
+ );
201
+ }
202
+ function isExcluded(path, exclude) {
203
+ return exclude.some((prefix) => path === prefix || path.startsWith(`${prefix}/`));
204
+ }
205
+ function isCatalogRelevantPath(path, settings) {
206
+ const normalized = path.replace(/^\/+/, "");
207
+ if (!normalized || hasUndiscoverableSegment(normalized)) return false;
208
+ let resolved;
209
+ try {
210
+ resolved = resolveCatalogOutputs(settings);
211
+ } catch {
212
+ return false;
213
+ }
214
+ if (!resolved) return false;
215
+ const lower = normalized.toLowerCase();
216
+ return lower !== resolved.html?.path.toLowerCase() && lower !== resolved.json?.path.toLowerCase();
217
+ }
218
+ function isWorkspaceSettingsPath(path) {
219
+ const normalized = path.replace(/^\/+/, "");
220
+ return normalized === WORKSPACE_SETTINGS_DIRECTORY || normalized.startsWith(`${WORKSPACE_SETTINGS_DIRECTORY}/`);
221
+ }
222
+ async function collectCandidates(io, resolved, limits, signal) {
223
+ const outputPaths = new Set(
224
+ [resolved.html?.path, resolved.json?.path].filter(Boolean).map((path) => path.toLowerCase())
225
+ );
226
+ const exclude = resolved.exclude;
227
+ const candidates = [];
228
+ const queue = [{ path: WORKSPACE_ROOT, depth: 0 }];
229
+ let directories = 0;
230
+ while (queue.length > 0) {
231
+ throwIfAborted(signal);
232
+ const { path, depth } = queue.shift();
233
+ directories += 1;
234
+ if (directories > limits.maxDirectories) {
235
+ throw new WorkspaceOutputsLimitError(
236
+ `This workspace has more than ${limits.maxDirectories} folders; the catalog was not updated.`
237
+ );
238
+ }
239
+ let entries;
240
+ try {
241
+ entries = await io.readDirectory(path);
242
+ } catch (error) {
243
+ if (path !== WORKSPACE_ROOT && isNotFound(error)) continue;
244
+ throw error;
245
+ }
246
+ for (const entry of entries) {
247
+ if (!isDiscoverableWorkspaceEntry(entry.name, entry.kind)) continue;
248
+ if (isExcluded(entry.path, exclude)) continue;
249
+ if (entry.kind === "directory") {
250
+ if (depth + 1 > limits.maxDepth) {
251
+ throw new WorkspaceOutputsLimitError(
252
+ `Folders nest deeper than ${limits.maxDepth} levels; the catalog was not updated.`
253
+ );
254
+ }
255
+ queue.push({ path: entry.path, depth: depth + 1 });
256
+ continue;
257
+ }
258
+ if (outputPaths.has(entry.path.toLowerCase())) continue;
259
+ if (isMarkdownFileName(entry.name)) {
260
+ candidates.push({ kind: "markdown", entry });
261
+ } else {
262
+ const layout = resolveOutsideInLayout(entry.path);
263
+ if (layout) candidates.push({ kind: "outside-in", entry, layout });
264
+ }
265
+ if (candidates.length > limits.maxDocuments) {
266
+ throw new WorkspaceOutputsLimitError(
267
+ `This workspace has more than ${limits.maxDocuments} documents; the catalog was not updated.`
268
+ );
269
+ }
270
+ }
271
+ }
272
+ return candidates;
273
+ }
274
+ var HTML_TITLE = /<title[^>]*>([\s\S]*?)<\/title>/i;
275
+ var HTML_DESCRIPTION = /<meta\b[^>]*\bname\s*=\s*["']description["'][^>]*\bcontent\s*=\s*["']([^"']*)["']/i;
276
+ function decodeEntities(text) {
277
+ return text.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;|&apos;/g, "'").replace(/&amp;/g, "&");
278
+ }
279
+ async function resolveReadTarget(io, candidate) {
280
+ if (candidate.kind === "markdown") {
281
+ return {
282
+ source: { path: candidate.entry.path, format: "markdown" },
283
+ readPath: candidate.entry.path,
284
+ version: candidate.entry.version,
285
+ size: candidate.entry.size,
286
+ mode: "markdown"
287
+ };
288
+ }
289
+ const { layout, entry } = candidate;
290
+ let companion = [];
291
+ try {
292
+ companion = await io.readDirectory(parseWorkspacePath(layout.companionDirectory));
293
+ } catch (error) {
294
+ if (!isNotFound(error)) throw error;
295
+ }
296
+ const markdownPath = chooseOutsideInMarkdownPath(
297
+ layout,
298
+ companion.filter((item) => item.kind === "file").map((item) => item.path)
299
+ );
300
+ const markdownEntry = markdownPath ? companion.find((item) => item.path === markdownPath) : void 0;
301
+ if (markdownEntry) {
302
+ return {
303
+ source: { path: entry.path, source: markdownEntry.path, format: layout.format },
304
+ readPath: markdownEntry.path,
305
+ version: markdownEntry.version,
306
+ size: markdownEntry.size,
307
+ mode: "markdown"
308
+ };
309
+ }
310
+ return {
311
+ source: { path: entry.path, format: layout.format },
312
+ readPath: layout.format === "html" ? entry.path : null,
313
+ version: layout.format === "html" ? entry.version : null,
314
+ size: entry.size,
315
+ mode: layout.format === "html" ? "html" : "unreadable"
316
+ };
317
+ }
318
+ async function collectWorkspaceCatalog(io, resolved, renderer, options = {}) {
319
+ const limits = { ...WORKSPACE_OUTPUT_LIMITS, ...options.limits };
320
+ const cache = options.cache;
321
+ const includeMarkdown = resolved.json?.content === "markdown";
322
+ const candidates = await collectCandidates(io, resolved, limits, options.signal);
323
+ const entries = [];
324
+ const seen = /* @__PURE__ */ new Set();
325
+ let totalBytes = 0;
326
+ for (const candidate of candidates) {
327
+ throwIfAborted(options.signal);
328
+ const target = await resolveReadTarget(io, candidate);
329
+ seen.add(target.source.path);
330
+ if (target.mode === "unreadable" || !target.readPath || target.size !== null && target.size > limits.maxDocumentBytes) {
331
+ entries.push(catalogEntryForUnreadable(target.source));
332
+ continue;
333
+ }
334
+ const cached = cache?.get(target.source.path);
335
+ if (cached && cached.readPath === target.readPath && cached.version === target.version && cached.includeMarkdown === includeMarkdown) {
336
+ if (cached.entry) entries.push(cached.entry);
337
+ continue;
338
+ }
339
+ const read = await io.readFile(parseWorkspacePath(target.readPath));
340
+ if (!read) continue;
341
+ let entry;
342
+ if (read.entry.size > limits.maxDocumentBytes) {
343
+ entry = catalogEntryForUnreadable(target.source);
344
+ } else {
345
+ totalBytes += read.entry.size;
346
+ if (totalBytes > limits.maxTotalBytes) {
347
+ throw new WorkspaceOutputsLimitError(
348
+ `Documents in this workspace exceed ${Math.round(limits.maxTotalBytes / 1024 / 1024)} MiB; the catalog was not updated.`
349
+ );
350
+ }
351
+ const text = new TextDecoder("utf-8").decode(read.data);
352
+ if (target.mode === "markdown") {
353
+ entry = await catalogEntryFromMarkdown(target.source, text, { includeMarkdown });
354
+ } else if (isGeneratedCatalogHtml(text)) {
355
+ entry = null;
356
+ } else if (renderer.importHtml) {
357
+ const head = text.slice(0, 64 * 1024);
358
+ entry = await catalogEntryFromDocument(target.source, await renderer.importHtml(text), {
359
+ fallbackTitle: decodeEntities(HTML_TITLE.exec(head)?.[1] ?? ""),
360
+ description: decodeEntities(HTML_DESCRIPTION.exec(head)?.[1] ?? "")
361
+ });
362
+ } else {
363
+ entry = catalogEntryForUnreadable(target.source);
364
+ }
365
+ }
366
+ cache?.set(target.source.path, {
367
+ readPath: target.readPath,
368
+ version: read.entry.version,
369
+ includeMarkdown,
370
+ entry
371
+ });
372
+ if (entry) entries.push(entry);
373
+ }
374
+ if (cache) {
375
+ for (const key of [...cache.keys()]) if (!seen.has(key)) cache.delete(key);
376
+ }
377
+ return createWorkspaceCatalog(resolved.title, entries, resolved.sort);
378
+ }
379
+ function withoutMarkdown(catalog) {
380
+ if (!catalog.documents.some((entry) => entry.markdown !== void 0)) return catalog;
381
+ return {
382
+ ...catalog,
383
+ documents: catalog.documents.map(({ markdown: _markdown, ...entry }) => entry)
384
+ };
385
+ }
386
+ function sameBytes(left, right) {
387
+ if (left.byteLength !== right.byteLength) return false;
388
+ const view = new Uint8Array(left);
389
+ for (let index = 0; index < view.length; index += 1) {
390
+ if (view[index] !== right[index]) return false;
391
+ }
392
+ return true;
393
+ }
394
+ async function writeOutput(io, request, limits, options) {
395
+ const { kind, path } = request;
396
+ const blocked = (message) => ({
397
+ kind,
398
+ path,
399
+ status: "blocked",
400
+ message
401
+ });
402
+ let existing;
403
+ try {
404
+ existing = await io.readFile(path);
405
+ } catch (error) {
406
+ if (error instanceof FsError && error.code === "type-mismatch") {
407
+ return blocked(`${path} is a folder. Choose another catalog path.`);
408
+ }
409
+ throw error;
410
+ }
411
+ if (existing) {
412
+ const head = new TextDecoder("utf-8").decode(existing.data.slice(0, 4096));
413
+ const generated = kind === "html" ? isGeneratedCatalogHtml(head) : isGeneratedCatalogJson(head);
414
+ if (!generated) {
415
+ return blocked(
416
+ `${path} already exists and was not created by DocBlocks. Choose another catalog path or remove that file.`
417
+ );
418
+ }
419
+ if (kind === "html" && !options.force && readCatalogHtmlDigest(head) === request.digest) {
420
+ return { kind, path, status: "unchanged" };
421
+ }
422
+ }
423
+ if (kind === "html") {
424
+ const layout = resolveOutsideInLayout(path);
425
+ if (layout && await io.stat(parseWorkspacePath(layout.companionDirectory))) {
426
+ return blocked(`${path} is an edited document. Choose another catalog path.`);
427
+ }
428
+ }
429
+ const bytes = new TextEncoder().encode(await request.produce());
430
+ if (bytes.byteLength > limits.maxOutputBytes) {
431
+ return blocked(
432
+ `The catalog would exceed ${Math.round(limits.maxOutputBytes / 1024 / 1024)} MiB and was not written.`
433
+ );
434
+ }
435
+ if (existing && sameBytes(existing.data, bytes)) return { kind, path, status: "unchanged" };
436
+ if (options.dryRun) return { kind, path, status: "would-write" };
437
+ await io.writeFile(path, bytes, {
438
+ mode: existing ? "replace" : "create",
439
+ createParents: true,
440
+ expectedVersion: existing ? existing.entry.version : null
441
+ });
442
+ return { kind, path, status: "written", ...existing ? {} : { created: true } };
443
+ }
444
+ async function refreshWorkspaceOutputs(io, settings, renderer, options = {}) {
445
+ const resolved = resolveCatalogOutputs(settings);
446
+ if (!resolved) return { documentCount: 0, outputs: [] };
447
+ const limits = { ...WORKSPACE_OUTPUT_LIMITS, ...options.limits };
448
+ const catalog = await collectWorkspaceCatalog(io, resolved, renderer, options);
449
+ const metadataCatalog = withoutMarkdown(catalog);
450
+ const outputs = [];
451
+ if (resolved.json) {
452
+ throwIfAborted(options.signal);
453
+ const content = resolved.json.content;
454
+ outputs.push(
455
+ await writeOutput(
456
+ io,
457
+ {
458
+ kind: "json",
459
+ path: resolved.json.path,
460
+ produce: async () => serializeCatalogJson(content === "markdown" ? catalog : metadataCatalog)
461
+ },
462
+ limits,
463
+ options
464
+ )
465
+ );
466
+ }
467
+ if (resolved.html) {
468
+ throwIfAborted(options.signal);
469
+ const html = resolved.html;
470
+ const digest = await sha256Hex(
471
+ JSON.stringify({
472
+ marker: 1,
473
+ path: html.path,
474
+ themeId: html.themeId ?? null,
475
+ catalog: serializeCatalogJson(metadataCatalog)
476
+ })
477
+ );
478
+ outputs.push(
479
+ await writeOutput(
480
+ io,
481
+ {
482
+ kind: "html",
483
+ path: html.path,
484
+ digest,
485
+ produce: async () => stampCatalogHtml(
486
+ await renderer.renderCatalogHtml(
487
+ buildCatalogMarkdownDocument(metadataCatalog, html.path),
488
+ { title: catalog.title, themeId: html.themeId }
489
+ ),
490
+ digest
491
+ )
492
+ },
493
+ limits,
494
+ options
495
+ )
496
+ );
497
+ }
498
+ return { documentCount: catalog.documents.length, outputs };
499
+ }
500
+
501
+ // src/workspace-settings/store.ts
502
+ var UNAVAILABLE_WORKSPACE_SETTINGS = Object.freeze({
503
+ status: "unavailable",
504
+ settings: null,
505
+ version: null,
506
+ message: "Workspace settings need a folder workspace."
507
+ });
508
+ var LOADING_WORKSPACE_SETTINGS = Object.freeze({
509
+ status: "loading",
510
+ settings: null,
511
+ version: null,
512
+ message: null
513
+ });
514
+ var SETTINGS_PATH = parseWorkspacePath(WORKSPACE_SETTINGS_PATH);
515
+ function isWorkspaceSettingsWritable(state) {
516
+ return state.status === "missing" || state.status === "ready";
517
+ }
518
+ async function readWorkspaceSettings(provider) {
519
+ let file;
520
+ try {
521
+ file = await provider.readFile(SETTINGS_PATH);
522
+ } catch (error) {
523
+ return {
524
+ status: "error",
525
+ settings: null,
526
+ version: null,
527
+ message: `Couldn't read ${WORKSPACE_SETTINGS_PATH}: ${errorMessage(error)}`
528
+ };
529
+ }
530
+ if (!file) return { status: "missing", settings: null, version: null, message: null };
531
+ let text;
532
+ try {
533
+ text = decodeUtf8Text(file.data, { label: WORKSPACE_SETTINGS_PATH, path: SETTINGS_PATH });
534
+ } catch (error) {
535
+ return {
536
+ status: "invalid",
537
+ settings: null,
538
+ version: file.entry.version,
539
+ message: errorMessage(error)
540
+ };
541
+ }
542
+ const parsed = parseWorkspaceSettingsText(text);
543
+ if (parsed.status === "ok") {
544
+ return {
545
+ status: "ready",
546
+ settings: parsed.settings,
547
+ version: file.entry.version,
548
+ message: null
549
+ };
550
+ }
551
+ return {
552
+ status: parsed.status,
553
+ settings: null,
554
+ version: file.entry.version,
555
+ message: parsed.message
556
+ };
557
+ }
558
+ function isConcurrentChange(error) {
559
+ return error instanceof FsError && (error.code === "conflict" || error.code === "already-exists" || error.code === "not-found");
560
+ }
561
+ async function writeSettings(provider, base, version, patch) {
562
+ const next = applyWorkspaceSettingsPatch(base, patch);
563
+ if (version === null && isDefaultWorkspaceSettings(next)) {
564
+ return { status: "missing", settings: null, version: null, message: null };
565
+ }
566
+ const snapshot = await provider.writeFile(
567
+ SETTINGS_PATH,
568
+ new TextEncoder().encode(serializeWorkspaceSettings(next)),
569
+ { mode: version ? "replace" : "create", createParents: true, expectedVersion: version }
570
+ );
571
+ return { status: "ready", settings: next, version: snapshot.version, message: null };
572
+ }
573
+ async function saveWorkspaceSettingsPatch(provider, observed, patch) {
574
+ if (!isWorkspaceSettingsWritable(observed)) {
575
+ throw new Error(observed.message ?? "These workspace settings cannot be changed.");
576
+ }
577
+ try {
578
+ return await writeSettings(provider, observed.settings, observed.version, patch);
579
+ } catch (error) {
580
+ if (!isConcurrentChange(error)) throw error;
581
+ }
582
+ const fresh = await readWorkspaceSettings(provider);
583
+ if (!isWorkspaceSettingsWritable(fresh)) {
584
+ throw new Error(fresh.message ?? "The workspace settings file changed and cannot be updated.");
585
+ }
586
+ return writeSettings(provider, fresh.settings, fresh.version, patch);
587
+ }
588
+ function errorMessage(error) {
589
+ return error instanceof Error ? error.message : String(error);
590
+ }
591
+
592
+ // src/workspace-settings/scheduler.ts
593
+ var IDLE_WORKSPACE_OUTPUTS_STATUS = Object.freeze({
594
+ state: "idle",
595
+ result: null,
596
+ message: null
597
+ });
598
+ var WORKSPACE_OUTPUTS_DEBOUNCE_MS = 2500;
599
+ var WORKSPACE_OUTPUTS_MAX_WAIT_MS = 2e4;
600
+ function outputsSignature(settings) {
601
+ try {
602
+ return JSON.stringify(resolveCatalogOutputs(settings));
603
+ } catch {
604
+ return "invalid";
605
+ }
606
+ }
607
+ function describeFailure(error) {
608
+ if (error instanceof WorkspaceOutputsLimitError) return error.message;
609
+ if (error instanceof FsError) return `Couldn't update the catalog: ${error.message}`;
610
+ return `Couldn't update the catalog: ${error instanceof Error ? error.message : String(error)}`;
611
+ }
612
+ function firstBlocked(result) {
613
+ return result.outputs.find((output) => output.status === "blocked");
614
+ }
615
+ function createWorkspaceOutputsScheduler(options) {
616
+ const debounceMs = options.debounceMs ?? WORKSPACE_OUTPUTS_DEBOUNCE_MS;
617
+ const maxWaitMs = options.maxWaitMs ?? WORKSPACE_OUTPUTS_MAX_WAIT_MS;
618
+ const cache = createWorkspaceCatalogCache();
619
+ const listeners = /* @__PURE__ */ new Set();
620
+ let settings = options.settings;
621
+ let signature = outputsSignature(settings);
622
+ let status = IDLE_WORKSPACE_OUTPUTS_STATUS;
623
+ let debounceTimer = null;
624
+ let maxWaitTimer = null;
625
+ let running = null;
626
+ let controller = null;
627
+ let dirty = false;
628
+ let forceNext = false;
629
+ let disposed = false;
630
+ const setStatus = (next) => {
631
+ status = next;
632
+ for (const listener of [...listeners]) listener();
633
+ };
634
+ const clearTimers = () => {
635
+ if (debounceTimer !== null) clearTimeout(debounceTimer);
636
+ if (maxWaitTimer !== null) clearTimeout(maxWaitTimer);
637
+ debounceTimer = null;
638
+ maxWaitTimer = null;
639
+ };
640
+ const enabled = () => resolveCatalogOutputsSafe(settings) !== null;
641
+ const run = () => {
642
+ clearTimers();
643
+ if (disposed || !enabled()) {
644
+ dirty = false;
645
+ return Promise.resolve(status);
646
+ }
647
+ if (running) {
648
+ dirty = true;
649
+ return running;
650
+ }
651
+ dirty = false;
652
+ const force = forceNext;
653
+ forceNext = false;
654
+ const runSettings = settings;
655
+ controller = new AbortController();
656
+ const signal = controller.signal;
657
+ setStatus({ ...status, state: "running" });
658
+ running = (async () => {
659
+ try {
660
+ const renderer = typeof options.renderer === "function" ? await options.renderer() : options.renderer;
661
+ let result;
662
+ try {
663
+ result = await refreshWorkspaceOutputs(options.provider, runSettings, renderer, {
664
+ cache,
665
+ signal,
666
+ force
667
+ });
668
+ } catch (error) {
669
+ if (!(error instanceof FsError && error.code === "conflict")) throw error;
670
+ result = await refreshWorkspaceOutputs(options.provider, runSettings, renderer, {
671
+ cache,
672
+ signal,
673
+ force
674
+ });
675
+ }
676
+ const blocked = firstBlocked(result);
677
+ return { state: "done", result, message: blocked?.message ?? null };
678
+ } catch (error) {
679
+ if (error instanceof FsError && error.code === "aborted") {
680
+ return status.state === "running" ? { ...status, state: "idle" } : status;
681
+ }
682
+ return { state: "error", result: null, message: describeFailure(error) };
683
+ }
684
+ })().then((next) => {
685
+ running = null;
686
+ controller = null;
687
+ if (!disposed) {
688
+ setStatus(next);
689
+ if (dirty) schedule();
690
+ }
691
+ return next;
692
+ });
693
+ return running;
694
+ };
695
+ const schedule = () => {
696
+ if (disposed || !enabled()) return;
697
+ if (status.state !== "running") setStatus({ ...status, state: "pending" });
698
+ if (debounceTimer !== null) clearTimeout(debounceTimer);
699
+ debounceTimer = setTimeout(() => void run(), debounceMs);
700
+ maxWaitTimer ?? (maxWaitTimer = setTimeout(() => void run(), maxWaitMs));
701
+ };
702
+ const scheduler = {
703
+ workspaceId: options.workspaceId,
704
+ notify(path) {
705
+ if (disposed) return;
706
+ if (path !== void 0 && !isCatalogRelevantPath(path, settings)) return;
707
+ if (running) {
708
+ dirty = true;
709
+ return;
710
+ }
711
+ schedule();
712
+ },
713
+ flush() {
714
+ if (debounceTimer !== null || maxWaitTimer !== null) void run();
715
+ },
716
+ regenerate(regenerateOptions) {
717
+ if (regenerateOptions?.force) forceNext = true;
718
+ if (running) dirty = true;
719
+ return running ?? run();
720
+ },
721
+ updateSettings(next) {
722
+ settings = next;
723
+ const nextSignature = outputsSignature(next);
724
+ if (nextSignature === signature) return;
725
+ signature = nextSignature;
726
+ if (!enabled()) {
727
+ clearTimers();
728
+ controller?.abort();
729
+ setStatus(IDLE_WORKSPACE_OUTPUTS_STATUS);
730
+ return;
731
+ }
732
+ scheduler.notify();
733
+ },
734
+ getStatus: () => status,
735
+ subscribe(listener) {
736
+ listeners.add(listener);
737
+ return () => listeners.delete(listener);
738
+ },
739
+ dispose() {
740
+ disposed = true;
741
+ clearTimers();
742
+ controller?.abort();
743
+ listeners.clear();
744
+ }
745
+ };
746
+ if (enabled()) scheduler.notify();
747
+ return scheduler;
748
+ }
749
+ function resolveCatalogOutputsSafe(settings) {
750
+ try {
751
+ return resolveCatalogOutputs(settings);
752
+ } catch {
753
+ return null;
754
+ }
755
+ }
756
+ export {
757
+ CATALOG_FORMAT_VERSION,
758
+ CATALOG_GENERATOR,
759
+ CATALOG_JSON_CONTENT_MODES,
760
+ CATALOG_LIMITS,
761
+ CATALOG_SORT_ORDERS,
762
+ DEFAULT_CATALOG_HTML_PATH,
763
+ DEFAULT_CATALOG_JSON_PATH,
764
+ DEFAULT_CATALOG_TITLE,
765
+ EMPTY_WORKSPACE_SETTINGS,
766
+ IDLE_WORKSPACE_OUTPUTS_STATUS,
767
+ LOADING_WORKSPACE_SETTINGS,
768
+ OUTSIDE_IN_FORMAT_IDS,
769
+ UNAVAILABLE_WORKSPACE_SETTINGS,
770
+ UnsupportedWorkspaceSettingsVersionError,
771
+ WORKSPACE_OUTPUTS_DEBOUNCE_MS,
772
+ WORKSPACE_OUTPUTS_MAX_WAIT_MS,
773
+ WORKSPACE_OUTPUT_LIMITS,
774
+ WORKSPACE_SETTINGS_DIRECTORY,
775
+ WORKSPACE_SETTINGS_JSON_SCHEMA,
776
+ WORKSPACE_SETTINGS_LIMITS,
777
+ WORKSPACE_SETTINGS_PATH,
778
+ WORKSPACE_SETTINGS_VERSION,
779
+ WORKSPACE_THEME_ID_PATTERN,
780
+ WorkspaceOutputsLimitError,
781
+ applyWorkspaceSettingsPatch,
782
+ buildCatalogMarkdownDocument,
783
+ catalogEntryForUnreadable,
784
+ catalogEntryFromDocument,
785
+ catalogEntryFromMarkdown,
786
+ checkCatalogOutputPath,
787
+ chooseOutsideInMarkdownPath,
788
+ collectWorkspaceCatalog,
789
+ createWorkspaceCatalog,
790
+ createWorkspaceCatalogCache,
791
+ createWorkspaceOutputsScheduler,
792
+ diffWorkspaceSettings,
793
+ isCatalogRelevantPath,
794
+ isDefaultWorkspaceSettings,
795
+ isEmptyWorkspaceSettingsPatch,
796
+ isGeneratedCatalogHtml,
797
+ isGeneratedCatalogJson,
798
+ isWorkspaceSettingsPath,
799
+ isWorkspaceSettingsWritable,
800
+ parseWorkspaceSettings,
801
+ parseWorkspaceSettingsPatch,
802
+ parseWorkspaceSettingsText,
803
+ readCatalogHtmlDigest,
804
+ readWorkspaceSettings,
805
+ refreshWorkspaceOutputs,
806
+ relativeCatalogLink,
807
+ relocateOutsideInLayout,
808
+ resolveCatalogOutputs,
809
+ resolveFallbackThemeId,
810
+ resolveOutsideInLayout,
811
+ resolveWorkspaceDefaultThemeId,
812
+ resolveWorkspaceVersioningEnabled,
813
+ saveWorkspaceSettingsPatch,
814
+ serializeCatalogJson,
815
+ serializeWorkspaceSettings,
816
+ sortCatalogEntries,
817
+ stampCatalogHtml,
818
+ tryApplyWorkspaceSettingsPatch,
819
+ validateCatalogOutputPath
820
+ };