@treeseed/sdk 0.4.8 → 0.4.10

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 (73) hide show
  1. package/README.md +1 -1
  2. package/dist/control-plane-client.d.ts +45 -0
  3. package/dist/control-plane-client.js +229 -0
  4. package/dist/control-plane.d.ts +94 -0
  5. package/dist/control-plane.js +125 -0
  6. package/dist/d1-store.d.ts +56 -1
  7. package/dist/d1-store.js +132 -0
  8. package/dist/dispatch.d.ts +4 -0
  9. package/dist/dispatch.js +180 -0
  10. package/dist/index.d.ts +14 -2
  11. package/dist/index.js +94 -4
  12. package/dist/operations/services/config-runtime.d.ts +10 -0
  13. package/dist/operations/services/config-runtime.js +62 -4
  14. package/dist/operations/services/deploy.d.ts +95 -3
  15. package/dist/operations/services/deploy.js +351 -10
  16. package/dist/operations/services/github-automation.d.ts +37 -1
  17. package/dist/operations/services/github-automation.js +71 -14
  18. package/dist/operations/services/project-platform.d.ts +835 -0
  19. package/dist/operations/services/project-platform.js +782 -0
  20. package/dist/operations/services/railway-deploy.d.ts +113 -18
  21. package/dist/operations/services/railway-deploy.js +357 -8
  22. package/dist/operations/services/runtime-tools.d.ts +25 -1
  23. package/dist/operations/services/runtime-tools.js +66 -5
  24. package/dist/operations/services/template-registry.d.ts +1 -1
  25. package/dist/operations/services/template-registry.js +17 -3
  26. package/dist/platform/books-data.d.ts +3 -4
  27. package/dist/platform/books-data.js +30 -4
  28. package/dist/platform/contracts.d.ts +56 -4
  29. package/dist/platform/deploy-config.js +109 -4
  30. package/dist/platform/deploy-runtime.d.ts +2 -0
  31. package/dist/platform/deploy-runtime.js +9 -1
  32. package/dist/platform/env.yaml +677 -0
  33. package/dist/platform/environment.js +57 -2
  34. package/dist/platform/plugin.d.ts +8 -0
  35. package/dist/platform/plugins/constants.d.ts +2 -0
  36. package/dist/platform/plugins/constants.js +2 -0
  37. package/dist/platform/plugins/runtime.d.ts +2 -0
  38. package/dist/platform/plugins/runtime.js +9 -1
  39. package/dist/platform/plugins.d.ts +1 -1
  40. package/dist/platform/plugins.js +4 -0
  41. package/dist/platform/published-content-pipeline.d.ts +84 -0
  42. package/dist/platform/published-content-pipeline.js +543 -0
  43. package/dist/platform/published-content.d.ts +223 -0
  44. package/dist/platform/published-content.js +588 -0
  45. package/dist/platform/tenant/runtime-config.d.ts +1 -1
  46. package/dist/platform/tenant/runtime-config.js +34 -1
  47. package/dist/platform/tenant-config.d.ts +2 -1
  48. package/dist/platform/tenant-config.js +17 -1
  49. package/dist/platform/utils/site-config-schema.js +104 -0
  50. package/dist/plugin-default.d.ts +2 -0
  51. package/dist/plugin-default.js +2 -0
  52. package/dist/remote.d.ts +65 -9
  53. package/dist/remote.js +104 -28
  54. package/dist/scripts/check-build-warnings.js +50 -0
  55. package/dist/scripts/config-treeseed.js +7 -0
  56. package/dist/scripts/tenant-workflow-action.js +71 -0
  57. package/dist/sdk-dispatch.d.ts +12 -0
  58. package/dist/sdk-dispatch.js +142 -0
  59. package/dist/sdk-types.d.ts +579 -7
  60. package/dist/sdk-types.js +53 -1
  61. package/dist/sdk.d.ts +17 -1
  62. package/dist/sdk.js +109 -0
  63. package/dist/stores/operational-store.d.ts +22 -2
  64. package/dist/stores/operational-store.js +235 -0
  65. package/dist/template-catalog.js +8 -1
  66. package/dist/treeseed/template-catalog/templates/starter-basic/template/treeseed.site.yaml +20 -0
  67. package/dist/types/cloudflare.d.ts +23 -0
  68. package/dist/workflow/operations.d.ts +12 -3
  69. package/dist/workflow/policy.d.ts +1 -1
  70. package/dist/workflow-state.js +2 -1
  71. package/package.json +7 -2
  72. package/templates/github/deploy.workflow.yml +442 -0
  73. package/templates/github/hosted-project.workflow.yml +77 -0
@@ -0,0 +1,543 @@
1
+ import { createHash } from "node:crypto";
2
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
3
+ import { basename, extname, join, relative, resolve } from "node:path";
4
+ import { unified } from "unified";
5
+ import remarkParse from "remark-parse";
6
+ import { toString } from "mdast-util-to-string";
7
+ import { parseFrontmatterDocument } from "../frontmatter.js";
8
+ import { buildTenantBookRuntime } from "./books-data.js";
9
+ import { exportBookLibrary, exportBookPackage } from "./book-export.js";
10
+ import {
11
+ PUBLISHED_CONTENT_MANIFEST_SCHEMA_VERSION,
12
+ resolvePublishedContentPreviewTtlHours,
13
+ resolveTeamScopedContentLocator
14
+ } from "./published-content.js";
15
+ function stableHash(value) {
16
+ return createHash("sha256").update(value).digest("hex");
17
+ }
18
+ function inferContentType(fileName) {
19
+ const extension = extname(fileName).toLowerCase();
20
+ if (extension === ".json") return "application/json";
21
+ if (extension === ".md") return "text/markdown; charset=utf-8";
22
+ if (extension === ".mdx") return "text/mdx; charset=utf-8";
23
+ return "application/octet-stream";
24
+ }
25
+ function objectPointerForBuffer(teamId, buffer, kind, extension = ".json") {
26
+ const sha256 = stableHash(buffer);
27
+ return {
28
+ objectKey: `teams/${teamId}/${kind}/${sha256}${extension}`,
29
+ sha256,
30
+ size: buffer.byteLength,
31
+ contentType: inferContentType(`entry${extension}`)
32
+ };
33
+ }
34
+ function objectInputForJson(teamId, kind, value) {
35
+ const buffer = Buffer.from(JSON.stringify(value, null, 2));
36
+ const pointer = objectPointerForBuffer(teamId, buffer, kind, ".json");
37
+ return {
38
+ pointer,
39
+ object: {
40
+ pointer: { ...pointer, contentType: "application/json" },
41
+ body: buffer,
42
+ httpMetadata: { contentType: "application/json" }
43
+ }
44
+ };
45
+ }
46
+ function objectInputForFile(teamId, filePath, kind) {
47
+ const buffer = readFileSync(filePath);
48
+ const extension = extname(filePath) || ".bin";
49
+ const pointer = objectPointerForBuffer(teamId, buffer, kind, extension);
50
+ return {
51
+ pointer: { ...pointer, contentType: inferContentType(filePath) },
52
+ object: {
53
+ pointer: { ...pointer, contentType: inferContentType(filePath) },
54
+ body: buffer,
55
+ httpMetadata: { contentType: inferContentType(filePath) }
56
+ }
57
+ };
58
+ }
59
+ function normalizeRelativeMarkdownPath(filePath, rootPath) {
60
+ return relative(rootPath, filePath).replaceAll("\\", "/");
61
+ }
62
+ function normalizeSlug(model, relativePath, frontmatter) {
63
+ const configured = typeof frontmatter.slug === "string" && frontmatter.slug.trim() ? frontmatter.slug.trim() : relativePath.replace(/\.(md|mdx)$/iu, "").replace(/\/index$/iu, "").replace(/^\/+|\/+$/gu, "");
64
+ if (model === "docs") {
65
+ if (configured === "knowledge" || configured.startsWith("knowledge/")) {
66
+ return configured;
67
+ }
68
+ return configured ? `knowledge/${configured}` : "knowledge";
69
+ }
70
+ return configured || basename(relativePath, extname(relativePath));
71
+ }
72
+ function inferTitle(relativePath, frontmatter) {
73
+ if (typeof frontmatter.title === "string" && frontmatter.title.trim()) {
74
+ return frontmatter.title.trim();
75
+ }
76
+ if (typeof frontmatter.name === "string" && frontmatter.name.trim()) {
77
+ return frontmatter.name.trim();
78
+ }
79
+ return basename(relativePath, extname(relativePath)).replace(/[-_]+/g, " ").replace(/\b\w/g, (character) => character.toUpperCase());
80
+ }
81
+ function inferSummary(frontmatter, searchText) {
82
+ for (const field of ["summary", "description", "excerpt"]) {
83
+ if (typeof frontmatter[field] === "string" && frontmatter[field].trim()) {
84
+ return frontmatter[field].trim();
85
+ }
86
+ }
87
+ return searchText.trim().slice(0, 240) || void 0;
88
+ }
89
+ function inferStatus(frontmatter) {
90
+ if (typeof frontmatter.status === "string" && frontmatter.status.trim()) {
91
+ return frontmatter.status.trim();
92
+ }
93
+ if (frontmatter.draft === true) {
94
+ return "draft";
95
+ }
96
+ return "live";
97
+ }
98
+ function inferVisibility(frontmatter) {
99
+ if (typeof frontmatter.visibility === "string" && frontmatter.visibility.trim()) {
100
+ return frontmatter.visibility.trim();
101
+ }
102
+ if (frontmatter.draft === true) {
103
+ return "team";
104
+ }
105
+ return "public";
106
+ }
107
+ function markdownText(body) {
108
+ try {
109
+ const tree = unified().use(remarkParse).parse(body);
110
+ return toString(tree).replace(/\s+/g, " ").trim();
111
+ } catch {
112
+ return body.replace(/`{1,3}[^`]*`{1,3}/g, " ").replace(/[#>*_~[\]()!-]+/g, " ").replace(/\s+/g, " ").trim();
113
+ }
114
+ }
115
+ function canonicalEntryPath(entry) {
116
+ return `${entry.model}/${entry.slug || entry.id}`.replace(/^\/+|\/+$/g, "");
117
+ }
118
+ function canonicalArtifactKey(artifact) {
119
+ return `${artifact.kind}:${artifact.itemId}:${artifact.version}`;
120
+ }
121
+ function entrySignature(entry) {
122
+ const { publishedAt, ...rest } = entry;
123
+ return stableHash(JSON.stringify(rest));
124
+ }
125
+ function artifactSignature(artifact) {
126
+ const { publishedAt, ...rest } = artifact;
127
+ return stableHash(JSON.stringify(rest));
128
+ }
129
+ function listMarkdownFiles(rootPath) {
130
+ if (!existsSync(rootPath)) {
131
+ return [];
132
+ }
133
+ const stats = statSync(rootPath);
134
+ if (stats.isFile()) {
135
+ return rootPath.endsWith(".md") || rootPath.endsWith(".mdx") ? [rootPath] : [];
136
+ }
137
+ return readdirSync(rootPath, { withFileTypes: true }).flatMap((entry) => {
138
+ const fullPath = join(rootPath, entry.name);
139
+ if (entry.isDirectory()) {
140
+ return listMarkdownFiles(fullPath);
141
+ }
142
+ if (entry.isFile() && (entry.name.endsWith(".md") || entry.name.endsWith(".mdx"))) {
143
+ return [fullPath];
144
+ }
145
+ return [];
146
+ }).sort((left, right) => left.localeCompare(right, void 0, { numeric: true, sensitivity: "base" }));
147
+ }
148
+ class FilesystemContentSource {
149
+ constructor(tenantConfig) {
150
+ this.tenantConfig = tenantConfig;
151
+ }
152
+ tenantConfig;
153
+ async listEntries() {
154
+ const entries = [];
155
+ for (const [model, rootPath] of Object.entries(this.tenantConfig.content ?? {})) {
156
+ if (!rootPath) continue;
157
+ for (const filePath of listMarkdownFiles(String(rootPath))) {
158
+ const relativePath = normalizeRelativeMarkdownPath(filePath, String(rootPath));
159
+ const raw = readFileSync(filePath, "utf8");
160
+ const parsed = parseFrontmatterDocument(raw);
161
+ const searchText = markdownText(parsed.body);
162
+ const slug = normalizeSlug(model, relativePath, parsed.frontmatter);
163
+ entries.push({
164
+ model,
165
+ id: basename(filePath, extname(filePath)),
166
+ slug,
167
+ title: inferTitle(relativePath, parsed.frontmatter),
168
+ summary: inferSummary(parsed.frontmatter, searchText),
169
+ status: inferStatus(parsed.frontmatter),
170
+ visibility: inferVisibility(parsed.frontmatter),
171
+ frontmatter: parsed.frontmatter,
172
+ body: parsed.body,
173
+ relativePath,
174
+ filePath,
175
+ updatedAt: statSync(filePath).mtime.toISOString()
176
+ });
177
+ }
178
+ }
179
+ return entries;
180
+ }
181
+ }
182
+ class DefaultEntryRenderer {
183
+ async render(entry, context) {
184
+ const contentPayload = {
185
+ model: entry.model,
186
+ id: entry.id,
187
+ slug: entry.slug,
188
+ title: entry.title,
189
+ summary: entry.summary,
190
+ status: entry.status,
191
+ visibility: entry.visibility,
192
+ frontmatter: entry.frontmatter,
193
+ body: entry.body,
194
+ relativePath: entry.relativePath,
195
+ filePath: entry.filePath,
196
+ updatedAt: entry.updatedAt
197
+ };
198
+ const renderedPayload = {
199
+ format: "markdown",
200
+ body: entry.body
201
+ };
202
+ const searchText = markdownText(entry.body);
203
+ const searchPayload = {
204
+ model: entry.model,
205
+ id: entry.id,
206
+ slug: entry.slug,
207
+ title: entry.title,
208
+ summary: entry.summary,
209
+ text: searchText,
210
+ updatedAt: entry.updatedAt
211
+ };
212
+ const content = objectInputForJson(context.teamId, "objects", contentPayload);
213
+ const rendered = objectInputForJson(context.teamId, "objects", renderedPayload);
214
+ const search = objectInputForJson(context.teamId, "objects", searchPayload);
215
+ return {
216
+ entry: {
217
+ id: entry.id,
218
+ model: entry.model,
219
+ slug: entry.slug,
220
+ title: entry.title,
221
+ summary: entry.summary,
222
+ status: entry.status,
223
+ visibility: entry.visibility,
224
+ teamId: context.teamId,
225
+ publishedAt: context.generatedAt,
226
+ updatedAt: entry.updatedAt,
227
+ content: content.pointer,
228
+ rendered: rendered.pointer,
229
+ search: search.pointer,
230
+ metadata: {
231
+ relativePath: entry.relativePath,
232
+ ...entry.frontmatter
233
+ }
234
+ },
235
+ objects: [content.object, rendered.object, search.object],
236
+ searchText
237
+ };
238
+ }
239
+ }
240
+ class DefaultCollectionIndexBuilder {
241
+ async build(model, entries, context) {
242
+ const index = {
243
+ model,
244
+ generatedAt: context.generatedAt,
245
+ count: entries.length,
246
+ entries
247
+ };
248
+ const object = objectInputForJson(context.teamId, "objects", index);
249
+ return { index, object: object.object };
250
+ }
251
+ }
252
+ function buildDocsTree(entries) {
253
+ return entries.filter((entry) => entry.model === "docs").map((entry) => ({
254
+ id: entry.id,
255
+ slug: entry.slug,
256
+ title: entry.title,
257
+ summary: entry.summary,
258
+ path: entry.slug.startsWith("knowledge/") ? `/${entry.slug}/` : `/knowledge/${entry.slug}/`
259
+ }));
260
+ }
261
+ class DefaultRuntimeBundleBuilder {
262
+ async build(context, entries) {
263
+ const objects = [];
264
+ const runtime = {};
265
+ if (context.tenantConfig.content.books && existsSync(String(context.tenantConfig.content.books))) {
266
+ const booksRuntime = buildTenantBookRuntime(context.tenantConfig, {
267
+ projectRoot: context.projectRoot
268
+ });
269
+ const pointer = objectInputForJson(context.teamId, "objects", booksRuntime);
270
+ objects.push(pointer.object);
271
+ runtime.booksRuntime = pointer.pointer;
272
+ runtime.docsHomePath = booksRuntime.TREESEED_LINKS.home;
273
+ }
274
+ const docsTree = buildDocsTree(entries);
275
+ if (docsTree.length > 0) {
276
+ const pointer = objectInputForJson(context.teamId, "objects", docsTree);
277
+ objects.push(pointer.object);
278
+ runtime.docsTree = pointer.pointer;
279
+ runtime.docsHomePath = runtime.docsHomePath ?? "/knowledge/";
280
+ }
281
+ const searchIndex = entries.map((entry) => ({
282
+ id: `${entry.model}:${entry.id}`,
283
+ model: entry.model,
284
+ slug: entry.slug,
285
+ title: entry.title,
286
+ summary: entry.summary,
287
+ search: entry.search?.objectKey ?? null,
288
+ updatedAt: entry.updatedAt
289
+ }));
290
+ if (searchIndex.length > 0) {
291
+ const pointer = objectInputForJson(context.teamId, "objects", searchIndex);
292
+ objects.push(pointer.object);
293
+ runtime.searchIndex = pointer.pointer;
294
+ }
295
+ return { runtime, objects };
296
+ }
297
+ }
298
+ function collectGeneratedArtifacts(projectRoot) {
299
+ const outputRoot = resolve(projectRoot, "public", "books");
300
+ if (!existsSync(outputRoot)) {
301
+ return [];
302
+ }
303
+ return readdirSync(outputRoot).map((entry) => resolve(outputRoot, entry)).filter((filePath) => statSync(filePath).isFile() && extname(filePath).toLowerCase() === ".md").sort((left, right) => left.localeCompare(right));
304
+ }
305
+ class DefaultArtifactBuilder {
306
+ async build(context, entries) {
307
+ const artifacts = [];
308
+ const objects = [];
309
+ const catalog = [];
310
+ if (context.tenantConfig.content.books && existsSync(String(context.tenantConfig.content.books))) {
311
+ const runtime = buildTenantBookRuntime(context.tenantConfig, { projectRoot: context.projectRoot });
312
+ for (const book of runtime.BOOKS) {
313
+ await exportBookPackage(book.slug, { projectRoot: context.projectRoot });
314
+ }
315
+ await exportBookLibrary({ projectRoot: context.projectRoot });
316
+ }
317
+ for (const filePath of collectGeneratedArtifacts(context.projectRoot)) {
318
+ const artifact = objectInputForFile(context.teamId, filePath, "artifacts");
319
+ objects.push(artifact.object);
320
+ const itemId = basename(filePath, extname(filePath));
321
+ artifacts.push({
322
+ id: `${itemId}-${String(context.sourceCommit ?? "current").slice(0, 12) || context.generatedAt}`,
323
+ itemId,
324
+ kind: itemId === "treeseed-knowledge" ? "knowledge_pack" : "book_export",
325
+ version: String(context.sourceCommit ?? context.generatedAt).slice(0, 12) || context.generatedAt,
326
+ label: itemId,
327
+ visibility: "public",
328
+ teamId: context.teamId,
329
+ publishedAt: context.generatedAt,
330
+ content: artifact.pointer,
331
+ metadata: {
332
+ fileName: basename(filePath),
333
+ source: "generated"
334
+ }
335
+ });
336
+ }
337
+ for (const entry of entries.filter((candidate) => candidate.model === "templates")) {
338
+ const source = entry.metadata?.fulfillment?.source;
339
+ if (!source || source.kind !== "r2" || typeof source.objectKey !== "string" || typeof source.version !== "string") {
340
+ continue;
341
+ }
342
+ artifacts.push({
343
+ id: `${entry.slug}-${String(source.version)}`,
344
+ itemId: entry.slug,
345
+ kind: "template_artifact",
346
+ version: String(source.version),
347
+ label: entry.title ?? entry.slug,
348
+ visibility: entry.visibility,
349
+ teamId: context.teamId,
350
+ publishedAt: context.generatedAt,
351
+ content: {
352
+ objectKey: source.objectKey,
353
+ sha256: typeof source.integrity === "string" ? source.integrity : stableHash(source.objectKey),
354
+ contentType: "application/octet-stream",
355
+ publicUrl: typeof source.publicUrl === "string" ? source.publicUrl : void 0
356
+ },
357
+ metadata: {
358
+ manifestKey: resolveTeamScopedContentLocator(context.siteConfig, context.teamId).manifestKey,
359
+ source
360
+ }
361
+ });
362
+ }
363
+ for (const entry of entries.filter((candidate) => candidate.model === "templates" || candidate.model === "knowledge_packs")) {
364
+ catalog.push({
365
+ id: `${entry.model}:${entry.id}`,
366
+ teamId: context.teamId,
367
+ kind: entry.model === "templates" ? "template" : "knowledge_pack",
368
+ slug: entry.slug,
369
+ title: entry.title ?? entry.slug,
370
+ summary: entry.summary,
371
+ visibility: entry.visibility,
372
+ listingEnabled: entry.metadata?.listingEnabled !== false,
373
+ offerMode: typeof entry.metadata?.offer?.priceModel === "string" ? entry.metadata.offer.priceModel : "free",
374
+ manifestKey: resolveTeamScopedContentLocator(context.siteConfig, context.teamId).manifestKey,
375
+ artifactKey: void 0,
376
+ updatedAt: context.generatedAt,
377
+ searchText: [entry.title, entry.summary].filter(Boolean).join(" ").trim(),
378
+ metadata: entry.metadata
379
+ });
380
+ }
381
+ return { artifacts, objects, catalog };
382
+ }
383
+ }
384
+ function dedupeObjects(objects) {
385
+ const deduped = /* @__PURE__ */ new Map();
386
+ for (const object of objects) {
387
+ deduped.set(object.pointer.objectKey, object);
388
+ }
389
+ return [...deduped.values()];
390
+ }
391
+ function buildTombstones(previousManifest, nextEntries, generatedAt) {
392
+ const previousEntries = previousManifest?.entries ?? [];
393
+ const nextPaths = new Set(nextEntries.map((entry) => canonicalEntryPath(entry)));
394
+ return previousEntries.filter((entry) => !nextPaths.has(canonicalEntryPath(entry))).map((entry) => ({
395
+ path: canonicalEntryPath(entry),
396
+ removedAt: generatedAt,
397
+ previousSha256: entry.content.sha256
398
+ }));
399
+ }
400
+ function filterChangedEntries(previousManifest, nextEntries) {
401
+ const previous = new Map((previousManifest?.entries ?? []).map((entry) => [canonicalEntryPath(entry), entrySignature(entry)]));
402
+ return nextEntries.filter((entry) => previous.get(canonicalEntryPath(entry)) !== entrySignature(entry));
403
+ }
404
+ function filterChangedArtifacts(previousManifest, nextArtifacts) {
405
+ const previous = new Map((previousManifest?.artifacts ?? []).map((artifact) => [canonicalArtifactKey(artifact), artifactSignature(artifact)]));
406
+ return nextArtifacts.filter((artifact) => previous.get(canonicalArtifactKey(artifact)) !== artifactSignature(artifact));
407
+ }
408
+ function collectReferencedObjectKeys(entries, artifacts, runtime) {
409
+ const keys = /* @__PURE__ */ new Set();
410
+ for (const entry of entries) {
411
+ keys.add(entry.content.objectKey);
412
+ if (entry.rendered?.objectKey) keys.add(entry.rendered.objectKey);
413
+ if (entry.search?.objectKey) keys.add(entry.search.objectKey);
414
+ }
415
+ for (const artifact of artifacts) {
416
+ keys.add(artifact.content.objectKey);
417
+ }
418
+ for (const pointer of [runtime?.booksRuntime, runtime?.docsTree, runtime?.searchIndex]) {
419
+ if (pointer?.objectKey) keys.add(pointer.objectKey);
420
+ }
421
+ return keys;
422
+ }
423
+ function changedRuntimePointers(previousManifest, nextRuntime) {
424
+ const changed = /* @__PURE__ */ new Set();
425
+ for (const key of ["booksRuntime", "docsTree", "searchIndex"]) {
426
+ const nextPointer = nextRuntime[key];
427
+ if (!nextPointer?.objectKey) {
428
+ continue;
429
+ }
430
+ const previousPointer = previousManifest?.runtime?.[key];
431
+ if (!previousPointer || previousPointer.sha256 !== nextPointer.sha256) {
432
+ changed.add(nextPointer.objectKey);
433
+ }
434
+ }
435
+ return changed;
436
+ }
437
+ function createFilesystemContentSource(tenantConfig) {
438
+ return new FilesystemContentSource(tenantConfig);
439
+ }
440
+ function createPublishedContentPipeline(context, options = {}) {
441
+ const contentSource = options.contentSource ?? createFilesystemContentSource(context.tenantConfig);
442
+ const entryRenderer = options.entryRenderer ?? new DefaultEntryRenderer();
443
+ const collectionIndexBuilder = options.collectionIndexBuilder ?? new DefaultCollectionIndexBuilder();
444
+ const runtimeBundleBuilder = options.runtimeBundleBuilder ?? new DefaultRuntimeBundleBuilder();
445
+ const artifactBuilder = options.artifactBuilder ?? new DefaultArtifactBuilder();
446
+ async function buildFullState() {
447
+ const sourceEntries = await contentSource.listEntries();
448
+ const renderedEntries = await Promise.all(sourceEntries.map((entry) => entryRenderer.render(entry, context)));
449
+ const entries = renderedEntries.map((entry) => entry.entry);
450
+ const objects = renderedEntries.flatMap((entry) => entry.objects);
451
+ const collections = /* @__PURE__ */ new Map();
452
+ for (const [model, modelEntries] of Object.entries(Object.groupBy(entries, (entry) => entry.model))) {
453
+ const actualEntries = (modelEntries ?? []).sort((left, right) => left.slug.localeCompare(right.slug));
454
+ const { object } = await collectionIndexBuilder.build(model, actualEntries, context);
455
+ collections.set(model, object.pointer);
456
+ objects.push(object);
457
+ }
458
+ const runtimeBundle = await runtimeBundleBuilder.build(context, entries);
459
+ objects.push(...runtimeBundle.objects);
460
+ const artifactBundle = await artifactBuilder.build(context, entries);
461
+ objects.push(...artifactBundle.objects);
462
+ return {
463
+ entries: entries.sort((left, right) => canonicalEntryPath(left).localeCompare(canonicalEntryPath(right))),
464
+ artifacts: artifactBundle.artifacts.sort((left, right) => canonicalArtifactKey(left).localeCompare(canonicalArtifactKey(right))),
465
+ runtime: runtimeBundle.runtime,
466
+ collections: Object.fromEntries(collections),
467
+ objects: dedupeObjects(objects),
468
+ catalog: artifactBundle.catalog ?? []
469
+ };
470
+ }
471
+ return {
472
+ async buildProductionRevision(options2 = {}) {
473
+ const state = await buildFullState();
474
+ const revisionSource = `${context.sourceRef ?? "content"}:${context.sourceCommit ?? context.generatedAt}`;
475
+ const revision = `${String(context.sourceRef ?? context.siteConfig.slug).replace(/[^a-zA-Z0-9._-]+/g, "-")}-${stableHash(revisionSource).slice(0, 12)}`;
476
+ return {
477
+ manifest: {
478
+ schemaVersion: PUBLISHED_CONTENT_MANIFEST_SCHEMA_VERSION,
479
+ siteSlug: context.siteConfig.slug,
480
+ teamId: context.teamId,
481
+ revision,
482
+ generatedAt: context.generatedAt,
483
+ mode: "production",
484
+ sourceCommit: context.sourceCommit ?? void 0,
485
+ appRevision: context.sourceCommit ?? void 0,
486
+ locator: resolveTeamScopedContentLocator(context.siteConfig, context.teamId),
487
+ collections: state.collections,
488
+ entries: state.entries,
489
+ artifacts: state.artifacts,
490
+ runtime: state.runtime,
491
+ tombstones: buildTombstones(options2.previousManifest, state.entries, context.generatedAt),
492
+ metadata: {
493
+ publishedFromBranch: context.sourceRef,
494
+ publishedScope: "prod"
495
+ }
496
+ },
497
+ objects: state.objects,
498
+ catalog: state.catalog
499
+ };
500
+ },
501
+ async buildEditorialOverlay(options2) {
502
+ const state = await buildFullState();
503
+ const previousManifest = options2.previousManifest ?? null;
504
+ const changedEntries = filterChangedEntries(previousManifest, state.entries);
505
+ const changedArtifacts = filterChangedArtifacts(previousManifest, state.artifacts);
506
+ const tombstones = buildTombstones(previousManifest, state.entries, context.generatedAt);
507
+ const referencedKeys = collectReferencedObjectKeys(changedEntries, changedArtifacts, void 0);
508
+ for (const objectKey of changedRuntimePointers(previousManifest, state.runtime)) {
509
+ referencedKeys.add(objectKey);
510
+ }
511
+ const changedObjects = state.objects.filter((object) => referencedKeys.has(object.pointer.objectKey));
512
+ return {
513
+ overlay: {
514
+ schemaVersion: PUBLISHED_CONTENT_MANIFEST_SCHEMA_VERSION,
515
+ siteSlug: context.siteConfig.slug,
516
+ teamId: context.teamId,
517
+ previewId: options2.previewId,
518
+ generatedAt: context.generatedAt,
519
+ mode: "editorial_overlay",
520
+ baseManifestKey: resolveTeamScopedContentLocator(context.siteConfig, context.teamId).manifestKey,
521
+ baseRevision: previousManifest?.revision,
522
+ sourceCommit: context.sourceCommit ?? void 0,
523
+ expiresAt: new Date(Date.now() + resolvePublishedContentPreviewTtlHours(context.siteConfig) * 60 * 60 * 1e3).toISOString(),
524
+ locator: resolveTeamScopedContentLocator(context.siteConfig, context.teamId, options2.previewId),
525
+ entries: changedEntries,
526
+ artifacts: changedArtifacts,
527
+ runtime: state.runtime,
528
+ tombstones,
529
+ metadata: {
530
+ publishedFromBranch: context.sourceRef,
531
+ publishedScope: "staging"
532
+ }
533
+ },
534
+ objects: dedupeObjects(changedObjects),
535
+ catalog: state.catalog
536
+ };
537
+ }
538
+ };
539
+ }
540
+ export {
541
+ createFilesystemContentSource,
542
+ createPublishedContentPipeline
543
+ };