@treeseed/sdk 0.4.6 → 0.4.8

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.
@@ -0,0 +1,449 @@
1
+ import { createHash } from "node:crypto";
2
+ import {
3
+ copyFileSync,
4
+ existsSync,
5
+ mkdirSync,
6
+ readFileSync,
7
+ readdirSync,
8
+ rmSync,
9
+ statSync,
10
+ writeFileSync
11
+ } from "node:fs";
12
+ import path, { dirname, extname, relative, resolve } from "node:path";
13
+ import { runDefaultAction, setLogLevel } from "repomix";
14
+ import { parse as parseYaml } from "yaml";
15
+ import { buildTenantBookRuntime } from "./books-data.js";
16
+ import { loadTreeseedManifest } from "./tenant-config.js";
17
+ const BOOK_EXPORT_PACKAGE_VERSION = 1;
18
+ function sha1(value) {
19
+ return createHash("sha1").update(value).digest("hex");
20
+ }
21
+ function sortPaths(paths) {
22
+ return [...paths].sort((left, right) => left.localeCompare(right, void 0, { numeric: true, sensitivity: "base" }));
23
+ }
24
+ function collectMarkdownFiles(rootPath) {
25
+ if (!existsSync(rootPath)) {
26
+ throw new Error(`Book export root not found: ${rootPath}`);
27
+ }
28
+ const stats = statSync(rootPath);
29
+ if (stats.isFile()) {
30
+ return [rootPath];
31
+ }
32
+ return sortPaths(
33
+ readdirSync(rootPath, { withFileTypes: true }).flatMap((entry) => {
34
+ const fullPath = path.join(rootPath, entry.name);
35
+ if (entry.isDirectory()) {
36
+ return collectMarkdownFiles(fullPath);
37
+ }
38
+ if (entry.isFile() && (entry.name.endsWith(".md") || entry.name.endsWith(".mdx"))) {
39
+ return [fullPath];
40
+ }
41
+ return [];
42
+ })
43
+ );
44
+ }
45
+ function frontmatter(filePath) {
46
+ const raw = readFileSync(filePath, "utf8");
47
+ const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
48
+ return match ? parseYaml(match[1]) : {};
49
+ }
50
+ function frontmatterOrder(filePath) {
51
+ const order = frontmatter(filePath).order;
52
+ return typeof order === "number" && Number.isFinite(order) ? order : null;
53
+ }
54
+ function contentTypeFor(filePath) {
55
+ const extension = extname(filePath).toLowerCase();
56
+ if (extension === ".md") return "md";
57
+ if (extension === ".mdx") return "mdx";
58
+ return "text";
59
+ }
60
+ function inferExportRoots(book, tenantConfig) {
61
+ const knowledgePrefix = "/knowledge/";
62
+ const normalizedBasePath = String(book.basePath || "").trim();
63
+ if (!normalizedBasePath.startsWith(knowledgePrefix)) {
64
+ throw new Error(`Book basePath must start with "${knowledgePrefix}" to infer exports: ${book.basePath}`);
65
+ }
66
+ const relativeKnowledgePath = normalizedBasePath.slice(knowledgePrefix.length).replace(/^\/+|\/+$/g, "");
67
+ if (!relativeKnowledgePath) {
68
+ throw new Error(`Book basePath must identify a knowledge directory: ${book.basePath}`);
69
+ }
70
+ return [path.join(tenantConfig.content.docs, relativeKnowledgePath)];
71
+ }
72
+ function resolveBookRoots(book, projectRoot, tenantConfig) {
73
+ const configuredRoots = Array.isArray(book.exportRoots) && book.exportRoots.length > 0 ? book.exportRoots.map((entry) => resolve(projectRoot, entry)) : inferExportRoots(book, tenantConfig).map((entry) => resolve(entry));
74
+ return configuredRoots;
75
+ }
76
+ function rootKeyFor(book, rootPath, projectRoot, index, total) {
77
+ if (total === 1) {
78
+ return "";
79
+ }
80
+ const rel = relative(projectRoot, rootPath).replaceAll(path.sep, "/");
81
+ const cleaned = rel.replaceAll(/[^a-zA-Z0-9/_-]+/g, "-").replaceAll(/-+/g, "-").replace(/^\/+|\/+$/g, "");
82
+ return cleaned || `root-${String(index + 1).padStart(2, "0")}`;
83
+ }
84
+ function orderedBookFiles(book, tenantConfig, projectRoot) {
85
+ const resolvedRoots = resolveBookRoots(book, projectRoot, tenantConfig);
86
+ const seen = /* @__PURE__ */ new Set();
87
+ const files = resolvedRoots.flatMap(
88
+ (rootPath, rootIndex) => collectMarkdownFiles(rootPath).sort((left, right) => {
89
+ const orderDelta = (frontmatterOrder(left) ?? Number.POSITIVE_INFINITY) - (frontmatterOrder(right) ?? Number.POSITIVE_INFINITY);
90
+ if (orderDelta !== 0) {
91
+ return orderDelta;
92
+ }
93
+ return left.localeCompare(right, void 0, { numeric: true, sensitivity: "base" });
94
+ }).map((absolutePath) => {
95
+ if (seen.has(absolutePath)) {
96
+ return null;
97
+ }
98
+ seen.add(absolutePath);
99
+ const rootRelativePath = relative(rootPath, absolutePath).replaceAll(path.sep, "/");
100
+ const rootKey = rootKeyFor(book, rootPath, projectRoot, rootIndex, resolvedRoots.length);
101
+ const bookRelativePath = rootKey ? `${rootKey}/${rootRelativePath}` : rootRelativePath;
102
+ return {
103
+ absolutePath,
104
+ rootPath,
105
+ rootRelativePath,
106
+ bookRelativePath
107
+ };
108
+ }).filter(Boolean)
109
+ );
110
+ return {
111
+ resolvedRoots,
112
+ files: files.map((file, index) => {
113
+ const projectRelativePath = relative(projectRoot, file.absolutePath).replaceAll(path.sep, "/");
114
+ const markerId = `marker:${book.id}:${String(index + 1).padStart(4, "0")}`;
115
+ return {
116
+ fileId: sha1(`${book.id}:${projectRelativePath}`),
117
+ bookId: book.id ?? book.slug,
118
+ memberBookId: book.id ?? book.slug,
119
+ absolutePath: file.absolutePath,
120
+ projectRelativePath,
121
+ bookRelativePath: file.bookRelativePath,
122
+ rootRelativePath: file.rootRelativePath,
123
+ rootPath: file.rootPath,
124
+ ordinal: index + 1,
125
+ frontmatterOrder: frontmatterOrder(file.absolutePath),
126
+ sourceType: contentTypeFor(file.absolutePath),
127
+ chunkId: sha1(`chunk:${book.id}:${projectRelativePath}:${index + 1}`),
128
+ markerId
129
+ };
130
+ })
131
+ };
132
+ }
133
+ function loadTenant(projectRoot = process.cwd()) {
134
+ const manifestPath = resolve(projectRoot, "src", "manifest.yaml");
135
+ const tenantConfig = loadTreeseedManifest(manifestPath);
136
+ const runtime = buildTenantBookRuntime(tenantConfig, { projectRoot });
137
+ return {
138
+ projectRoot,
139
+ tenantConfig,
140
+ runtime
141
+ };
142
+ }
143
+ function findBookOrThrow(bookId, books) {
144
+ const book = books.find((entry) => entry.id === bookId || entry.slug === bookId);
145
+ if (!book) {
146
+ throw new Error(`Unknown book export target: ${bookId}`);
147
+ }
148
+ return book;
149
+ }
150
+ function buildBookManifestFromBook(book, tenantConfig, projectRoot) {
151
+ const ordered = orderedBookFiles(book, tenantConfig, projectRoot);
152
+ return {
153
+ packageKind: "book",
154
+ packageVersion: BOOK_EXPORT_PACKAGE_VERSION,
155
+ packageId: `book:${book.id}`,
156
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
157
+ tenantRoot: projectRoot,
158
+ tenantId: tenantConfig.id,
159
+ book: {
160
+ id: book.id ?? book.slug,
161
+ slug: book.slug,
162
+ title: book.title,
163
+ order: book.order,
164
+ basePath: book.basePath,
165
+ downloadFileName: book.downloadFileName,
166
+ downloadHref: book.downloadHref,
167
+ downloadTitle: book.downloadTitle,
168
+ resolvedRoots: ordered.resolvedRoots.map((entry) => relative(projectRoot, entry).replaceAll(path.sep, "/"))
169
+ },
170
+ files: ordered.files
171
+ };
172
+ }
173
+ function buildBookExportManifest(bookId, options = {}) {
174
+ const { projectRoot, tenantConfig, runtime } = loadTenant(resolve(options.projectRoot ?? process.cwd()));
175
+ const book = findBookOrThrow(bookId, runtime.BOOKS);
176
+ return buildBookManifestFromBook(book, tenantConfig, projectRoot);
177
+ }
178
+ function stageManifest(manifest, stageRoot) {
179
+ rmSync(stageRoot, { recursive: true, force: true });
180
+ mkdirSync(resolve(stageRoot, "manifest"), { recursive: true });
181
+ mkdirSync(resolve(stageRoot, "content"), { recursive: true });
182
+ writeFileSync(resolve(stageRoot, "manifest", "book.json"), `${JSON.stringify({
183
+ packageKind: manifest.packageKind,
184
+ packageVersion: manifest.packageVersion,
185
+ packageId: manifest.packageId,
186
+ generatedAt: manifest.generatedAt,
187
+ book: manifest.book,
188
+ members: manifest.members ?? []
189
+ }, null, 2)}
190
+ `, "utf8");
191
+ writeFileSync(resolve(stageRoot, "manifest", "files.json"), `${JSON.stringify(manifest.files, null, 2)}
192
+ `, "utf8");
193
+ for (const file of manifest.files) {
194
+ const stagedPath = resolve(
195
+ stageRoot,
196
+ "content",
197
+ String(file.ordinal).padStart(4, "0"),
198
+ file.bookRelativePath
199
+ );
200
+ mkdirSync(dirname(stagedPath), { recursive: true });
201
+ copyFileSync(file.absolutePath, stagedPath);
202
+ }
203
+ }
204
+ async function withCleanNodeExecArgv(action) {
205
+ const previousExecArgv = [...process.execArgv];
206
+ process.execArgv = previousExecArgv.filter(
207
+ (arg) => !arg.startsWith("--test") && !arg.startsWith("--input-type") && !arg.startsWith("--experimental-test") && !arg.startsWith("--watch")
208
+ );
209
+ try {
210
+ return await action();
211
+ } finally {
212
+ process.execArgv = previousExecArgv;
213
+ }
214
+ }
215
+ async function renderRepomixMarkdown(stageRoot, outputPath) {
216
+ const options = {
217
+ output: outputPath,
218
+ style: "markdown",
219
+ ignore: "",
220
+ quiet: true,
221
+ skipLocalConfig: true,
222
+ copy: false,
223
+ stdout: false
224
+ };
225
+ setLogLevel(0);
226
+ const result = await withCleanNodeExecArgv(() => runDefaultAction(["."], stageRoot, options));
227
+ return {
228
+ markdown: readFileSync(outputPath, "utf8"),
229
+ packResult: result.packResult
230
+ };
231
+ }
232
+ function normalizePackResultOutputFiles(packResult, outputPath) {
233
+ return packResult.outputFiles && packResult.outputFiles.length > 0 ? packResult.outputFiles : [outputPath];
234
+ }
235
+ function jsonFence(value) {
236
+ return ["```json", JSON.stringify(value, null, 2), "```"].join("\n");
237
+ }
238
+ function renderBookPackageMarkdown(manifest, repomixMarkdown) {
239
+ const header = {
240
+ packageKind: manifest.packageKind,
241
+ packageVersion: manifest.packageVersion,
242
+ packageId: manifest.packageId,
243
+ book: manifest.book,
244
+ sourceFileCount: manifest.files.length
245
+ };
246
+ const fileIndex = manifest.files.map((file) => ({
247
+ fileId: file.fileId,
248
+ ordinal: file.ordinal,
249
+ projectRelativePath: file.projectRelativePath,
250
+ bookRelativePath: file.bookRelativePath,
251
+ sourceType: file.sourceType,
252
+ chunkId: file.chunkId,
253
+ markerId: file.markerId
254
+ }));
255
+ return [
256
+ `# ${manifest.book.downloadTitle}`,
257
+ "",
258
+ "> Auto-generated Treeseed AI package. Treeseed owns the manifest and file ordering; Repomix owns the content serialization payload.",
259
+ "",
260
+ "<!-- TRESEED_PACKAGE_HEADER_BEGIN -->",
261
+ jsonFence(header),
262
+ "<!-- TRESEED_PACKAGE_HEADER_END -->",
263
+ "",
264
+ "<!-- TRESEED_PACKAGE_MANIFEST_BEGIN -->",
265
+ jsonFence(manifest),
266
+ "<!-- TRESEED_PACKAGE_MANIFEST_END -->",
267
+ "",
268
+ "<!-- TRESEED_PACKAGE_FILE_INDEX_BEGIN -->",
269
+ jsonFence(fileIndex),
270
+ "<!-- TRESEED_PACKAGE_FILE_INDEX_END -->",
271
+ "",
272
+ "<!-- TRESEED_PACKAGE_CONTENT_BEGIN -->",
273
+ repomixMarkdown.trim(),
274
+ "<!-- TRESEED_PACKAGE_CONTENT_END -->",
275
+ ""
276
+ ].join("\n");
277
+ }
278
+ function buildBookIndexPayload(manifest, markdownPath) {
279
+ return {
280
+ packageKind: manifest.packageKind,
281
+ packageVersion: manifest.packageVersion,
282
+ packageId: manifest.packageId,
283
+ markdownPath,
284
+ book: manifest.book,
285
+ files: manifest.files.map((file) => ({
286
+ fileId: file.fileId,
287
+ memberBookId: file.memberBookId,
288
+ ordinal: file.ordinal,
289
+ projectRelativePath: file.projectRelativePath,
290
+ bookRelativePath: file.bookRelativePath,
291
+ rootRelativePath: file.rootRelativePath,
292
+ sourceType: file.sourceType,
293
+ chunkId: file.chunkId,
294
+ markerId: file.markerId,
295
+ relations: {
296
+ book: manifest.book.id,
297
+ nextFileId: manifest.files.find((candidate) => candidate.ordinal === file.ordinal + 1)?.fileId ?? null
298
+ }
299
+ }))
300
+ };
301
+ }
302
+ function booksOutputRoot(projectRoot) {
303
+ return resolve(projectRoot, "public", "books");
304
+ }
305
+ function sidecarIndexPath(markdownPath) {
306
+ return markdownPath.replace(/\.md$/u, ".json");
307
+ }
308
+ async function exportManifestPackage(manifest, markdownPath, indexPath) {
309
+ const tempRoot = resolve(manifest.tenantRoot, ".treeseed", "tmp", "book-exports", manifest.packageId.replaceAll(":", "-"));
310
+ const repomixOutputPath = resolve(tempRoot, "..", `${manifest.packageId.replaceAll(":", "-")}.repomix.md`);
311
+ stageManifest(manifest, tempRoot);
312
+ const { markdown: repomixMarkdown, packResult } = await renderRepomixMarkdown(tempRoot, repomixOutputPath);
313
+ const wrappedMarkdown = renderBookPackageMarkdown(manifest, repomixMarkdown);
314
+ mkdirSync(dirname(markdownPath), { recursive: true });
315
+ writeFileSync(markdownPath, wrappedMarkdown, "utf8");
316
+ writeFileSync(indexPath, `${JSON.stringify({
317
+ ...buildBookIndexPayload(manifest, markdownPath),
318
+ repomixSummary: {
319
+ totalFiles: packResult.totalFiles,
320
+ totalCharacters: packResult.totalCharacters,
321
+ totalTokens: packResult.totalTokens,
322
+ outputFiles: normalizePackResultOutputFiles(packResult, repomixOutputPath)
323
+ }
324
+ }, null, 2)}
325
+ `, "utf8");
326
+ rmSync(tempRoot, { recursive: true, force: true });
327
+ rmSync(repomixOutputPath, { force: true });
328
+ }
329
+ async function exportBookPackage(bookId, options = {}) {
330
+ const manifest = buildBookExportManifest(bookId, options);
331
+ const markdownPath = resolve(booksOutputRoot(manifest.tenantRoot), manifest.book.downloadFileName);
332
+ const indexPath = sidecarIndexPath(markdownPath);
333
+ await exportManifestPackage(manifest, markdownPath, indexPath);
334
+ return {
335
+ manifest,
336
+ markdownPath,
337
+ indexPath,
338
+ sourceFileCount: manifest.files.length,
339
+ includedRoots: manifest.book.resolvedRoots
340
+ };
341
+ }
342
+ function renderLibraryMarkdown(manifest, memberPackages) {
343
+ return [
344
+ `# ${manifest.book.downloadTitle}`,
345
+ "",
346
+ "> Auto-generated Treeseed AI library package. Each member section embeds a reconstructable per-book package.",
347
+ "",
348
+ "<!-- TRESEED_LIBRARY_HEADER_BEGIN -->",
349
+ jsonFence({
350
+ packageKind: manifest.packageKind,
351
+ packageVersion: manifest.packageVersion,
352
+ packageId: manifest.packageId,
353
+ book: manifest.book,
354
+ memberCount: memberPackages.length
355
+ }),
356
+ "<!-- TRESEED_LIBRARY_HEADER_END -->",
357
+ "",
358
+ "<!-- TRESEED_LIBRARY_MANIFEST_BEGIN -->",
359
+ jsonFence(manifest),
360
+ "<!-- TRESEED_LIBRARY_MANIFEST_END -->",
361
+ "",
362
+ ...memberPackages.flatMap((member) => [
363
+ `<!-- TRESEED_AGGREGATE_MEMBER_BEGIN ${member.manifest.book.id} -->`,
364
+ readFileSync(member.markdownPath, "utf8").trim(),
365
+ `<!-- TRESEED_AGGREGATE_MEMBER_END ${member.manifest.book.id} -->`,
366
+ ""
367
+ ])
368
+ ].join("\n");
369
+ }
370
+ async function exportBookLibrary(options = {}) {
371
+ const { projectRoot, tenantConfig, runtime } = loadTenant(resolve(options.projectRoot ?? process.cwd()));
372
+ const memberPackages = [];
373
+ for (const book of runtime.BOOKS) {
374
+ memberPackages.push(await exportBookPackage(book.id ?? book.slug, { projectRoot }));
375
+ }
376
+ const files = memberPackages.flatMap((entry) => entry.manifest.files);
377
+ const manifest = {
378
+ packageKind: "library",
379
+ packageVersion: BOOK_EXPORT_PACKAGE_VERSION,
380
+ packageId: "library:books",
381
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
382
+ tenantRoot: projectRoot,
383
+ tenantId: tenantConfig.id,
384
+ book: {
385
+ id: "library",
386
+ slug: "library",
387
+ title: runtime.TREESEED_LIBRARY_DOWNLOAD.downloadTitle,
388
+ order: 0,
389
+ basePath: "/books/",
390
+ downloadFileName: runtime.TREESEED_LIBRARY_DOWNLOAD.downloadFileName,
391
+ downloadHref: runtime.TREESEED_LIBRARY_DOWNLOAD.downloadHref,
392
+ downloadTitle: runtime.TREESEED_LIBRARY_DOWNLOAD.downloadTitle,
393
+ resolvedRoots: Array.from(new Set(memberPackages.flatMap((entry) => entry.includedRoots))).sort((left, right) => left.localeCompare(right))
394
+ },
395
+ files,
396
+ members: memberPackages.map((entry) => ({
397
+ bookId: entry.manifest.book.id,
398
+ slug: entry.manifest.book.slug,
399
+ title: entry.manifest.book.title,
400
+ order: entry.manifest.book.order,
401
+ downloadFileName: entry.manifest.book.downloadFileName,
402
+ downloadHref: entry.manifest.book.downloadHref,
403
+ sourceFileCount: entry.sourceFileCount,
404
+ markdownPath: entry.markdownPath,
405
+ indexPath: entry.indexPath
406
+ }))
407
+ };
408
+ const markdownPath = resolve(booksOutputRoot(projectRoot), runtime.TREESEED_LIBRARY_DOWNLOAD.downloadFileName);
409
+ const indexPath = sidecarIndexPath(markdownPath);
410
+ mkdirSync(dirname(markdownPath), { recursive: true });
411
+ writeFileSync(markdownPath, renderLibraryMarkdown(manifest, memberPackages), "utf8");
412
+ writeFileSync(indexPath, `${JSON.stringify({
413
+ packageKind: manifest.packageKind,
414
+ packageVersion: manifest.packageVersion,
415
+ packageId: manifest.packageId,
416
+ markdownPath,
417
+ book: manifest.book,
418
+ members: manifest.members,
419
+ files: buildBookIndexPayload(manifest, markdownPath).files
420
+ }, null, 2)}
421
+ `, "utf8");
422
+ return {
423
+ manifest,
424
+ markdownPath,
425
+ indexPath,
426
+ memberPackages,
427
+ sourceFileCount: files.length,
428
+ includedRoots: manifest.book.resolvedRoots
429
+ };
430
+ }
431
+ async function exportTenantBookPackages(options = {}) {
432
+ const { projectRoot } = loadTenant(resolve(options.projectRoot ?? process.cwd()));
433
+ const libraryPackage = await exportBookLibrary({ projectRoot });
434
+ const legacyOutputFile = resolve(projectRoot, "public", "book.md");
435
+ if (existsSync(legacyOutputFile)) {
436
+ rmSync(legacyOutputFile, { force: true });
437
+ }
438
+ return {
439
+ projectRoot,
440
+ bookPackages: libraryPackage.memberPackages,
441
+ libraryPackage
442
+ };
443
+ }
444
+ export {
445
+ buildBookExportManifest,
446
+ exportBookLibrary,
447
+ exportBookPackage,
448
+ exportTenantBookPackages
449
+ };
@@ -155,6 +155,10 @@ export interface TreeseedProviderSelections {
155
155
  };
156
156
  site?: string;
157
157
  }
158
+ export interface TreeseedExportConfig {
159
+ ignore?: string[];
160
+ bundledPaths?: string[];
161
+ }
158
162
  export interface TreeseedDeployConfig {
159
163
  name: string;
160
164
  slug: string;
@@ -174,6 +178,7 @@ export interface TreeseedDeployConfig {
174
178
  turnstile?: {
175
179
  enabled?: boolean;
176
180
  };
181
+ export?: TreeseedExportConfig;
177
182
  }
178
183
  export interface TreeseedTenantConfig {
179
184
  id: string;
@@ -1,4 +1,6 @@
1
1
  import type { TreeseedDeployConfig } from './contracts.ts';
2
2
  export declare function resolveTreeseedDeployConfigPath(configPath?: string): string;
3
+ export declare function resolveTreeseedDeployConfigPathFromRoot(tenantRoot: string, configPath?: string): string;
3
4
  export declare function deriveCloudflareWorkerName(config: TreeseedDeployConfig): string;
4
5
  export declare function loadTreeseedDeployConfig(configPath?: string): TreeseedDeployConfig;
6
+ export declare function loadTreeseedDeployConfigFromPath(resolvedConfigPath: string): TreeseedDeployConfig;
@@ -41,6 +41,15 @@ function optionalBoolean(value, label) {
41
41
  }
42
42
  return value;
43
43
  }
44
+ function optionalStringArray(value, label) {
45
+ if (value === void 0) {
46
+ return void 0;
47
+ }
48
+ if (!Array.isArray(value)) {
49
+ throw new Error(`Invalid deploy config: expected ${label} to be an array of strings when provided.`);
50
+ }
51
+ return value.map((entry, index) => expectString(entry, `${label}[${index}]`)).filter(Boolean);
52
+ }
44
53
  function optionalRecord(value, label) {
45
54
  if (value === void 0 || value === null) {
46
55
  return void 0;
@@ -185,6 +194,16 @@ function parsePlatformSurfacesConfig(value) {
185
194
  ])
186
195
  );
187
196
  }
197
+ function parseExportConfig(value) {
198
+ const record = optionalRecord(value, "export");
199
+ if (!record) {
200
+ return void 0;
201
+ }
202
+ return {
203
+ ignore: optionalStringArray(record.ignore, "export.ignore"),
204
+ bundledPaths: optionalStringArray(record.bundledPaths, "export.bundledPaths")
205
+ };
206
+ }
188
207
  function parseDeployConfig(raw) {
189
208
  const parsed = normalizeAliasedRecord(
190
209
  deployConfigFieldAliases,
@@ -215,11 +234,15 @@ function parseDeployConfig(raw) {
215
234
  },
216
235
  turnstile: {
217
236
  enabled: true
218
- }
237
+ },
238
+ export: parseExportConfig(parsed.export)
219
239
  };
220
240
  }
221
241
  function resolveTreeseedDeployConfigPath(configPath = "treeseed.site.yaml") {
222
242
  const tenantRoot = resolveTreeseedTenantRoot();
243
+ return resolveTreeseedDeployConfigPathFromRoot(tenantRoot, configPath);
244
+ }
245
+ function resolveTreeseedDeployConfigPathFromRoot(tenantRoot, configPath = "treeseed.site.yaml") {
223
246
  const candidate = resolve(tenantRoot, configPath);
224
247
  if (!existsSync(candidate)) {
225
248
  throw new Error(`Unable to resolve Treeseed deploy config at "${candidate}".`);
@@ -231,6 +254,9 @@ function deriveCloudflareWorkerName(config) {
231
254
  }
232
255
  function loadTreeseedDeployConfig(configPath = "treeseed.site.yaml") {
233
256
  const resolvedConfigPath = resolveTreeseedDeployConfigPath(configPath);
257
+ return loadTreeseedDeployConfigFromPath(resolvedConfigPath);
258
+ }
259
+ function loadTreeseedDeployConfigFromPath(resolvedConfigPath) {
234
260
  const tenantRoot = dirname(resolvedConfigPath);
235
261
  const parsed = parseDeployConfig(readFileSync(resolvedConfigPath, "utf8"));
236
262
  Object.defineProperty(parsed, "__tenantRoot", {
@@ -246,5 +272,7 @@ function loadTreeseedDeployConfig(configPath = "treeseed.site.yaml") {
246
272
  export {
247
273
  deriveCloudflareWorkerName,
248
274
  loadTreeseedDeployConfig,
249
- resolveTreeseedDeployConfigPath
275
+ loadTreeseedDeployConfigFromPath,
276
+ resolveTreeseedDeployConfigPath,
277
+ resolveTreeseedDeployConfigPathFromRoot
250
278
  };
@@ -12,6 +12,7 @@ entries:
12
12
  - local
13
13
  - staging
14
14
  - prod
15
+ storage: shared
15
16
  requirement: required
16
17
  purposes:
17
18
  - save
@@ -34,6 +35,7 @@ entries:
34
35
  - local
35
36
  - staging
36
37
  - prod
38
+ storage: shared
37
39
  requirement: required
38
40
  purposes:
39
41
  - save
@@ -58,6 +60,7 @@ entries:
58
60
  - local
59
61
  - staging
60
62
  - prod
63
+ storage: shared
61
64
  requirement: conditional
62
65
  purposes:
63
66
  - deploy
@@ -83,6 +86,7 @@ entries:
83
86
  - local
84
87
  - staging
85
88
  - prod
89
+ storage: shared
86
90
  requirement: optional
87
91
  purposes:
88
92
  - deploy
@@ -107,6 +111,7 @@ entries:
107
111
  scopes:
108
112
  - staging
109
113
  - prod
114
+ storage: shared
110
115
  requirement: required
111
116
  purposes:
112
117
  - save
@@ -5,18 +5,20 @@ export declare const TREESEED_ENVIRONMENT_REQUIREMENTS: readonly ["required", "c
5
5
  export declare const TREESEED_ENVIRONMENT_TARGETS: readonly ["local-file", "wrangler-dev-vars", "github-secret", "github-variable", "cloudflare-secret", "cloudflare-var", "railway-secret", "railway-var", "config-file"];
6
6
  export declare const TREESEED_ENVIRONMENT_PURPOSES: readonly ["dev", "save", "deploy", "destroy", "config"];
7
7
  export declare const TREESEED_ENVIRONMENT_SENSITIVITY: readonly ["secret", "plain", "derived"];
8
+ export declare const TREESEED_ENVIRONMENT_STORAGE: readonly ["scoped", "shared"];
8
9
  export type TreeseedEnvironmentScope = (typeof TREESEED_ENVIRONMENT_SCOPES)[number];
9
10
  export type TreeseedEnvironmentRequirement = (typeof TREESEED_ENVIRONMENT_REQUIREMENTS)[number];
10
11
  export type TreeseedEnvironmentTarget = (typeof TREESEED_ENVIRONMENT_TARGETS)[number];
11
12
  export type TreeseedEnvironmentPurpose = (typeof TREESEED_ENVIRONMENT_PURPOSES)[number];
12
13
  export type TreeseedEnvironmentSensitivity = (typeof TREESEED_ENVIRONMENT_SENSITIVITY)[number];
14
+ export type TreeseedEnvironmentStorage = (typeof TREESEED_ENVIRONMENT_STORAGE)[number];
13
15
  export type TreeseedEnvironmentValidation = {
14
16
  kind: 'string' | 'nonempty' | 'boolean' | 'number' | 'url' | 'email';
15
17
  } | {
16
18
  kind: 'enum';
17
19
  values: string[];
18
20
  };
19
- export type TreeseedEnvironmentValueResolver = string | ((context: TreeseedEnvironmentContext, scope: TreeseedEnvironmentScope) => string);
21
+ export type TreeseedEnvironmentValueResolver = string | ((context: TreeseedEnvironmentContext, scope: TreeseedEnvironmentScope, values?: Record<string, string | undefined>) => string | undefined);
20
22
  export type TreeseedMachineSecretPayload = {
21
23
  algorithm: 'aes-256-gcm';
22
24
  iv: string;
@@ -39,6 +41,10 @@ export type TreeseedMachineConfig = {
39
41
  cloudflare: boolean;
40
42
  };
41
43
  };
44
+ shared: {
45
+ values: Record<string, string>;
46
+ secrets: Record<string, TreeseedMachineSecretPayload>;
47
+ };
42
48
  environments: Record<TreeseedEnvironmentScope, {
43
49
  values: Record<string, string>;
44
50
  secrets: Record<string, TreeseedMachineSecretPayload>;
@@ -61,6 +67,7 @@ export type TreeseedEnvironmentEntry = {
61
67
  scopes: TreeseedEnvironmentScope[];
62
68
  requirement: TreeseedEnvironmentRequirement;
63
69
  purposes: TreeseedEnvironmentPurpose[];
70
+ storage?: TreeseedEnvironmentStorage;
64
71
  validation?: TreeseedEnvironmentValidation;
65
72
  sourcePriority?: string[];
66
73
  defaultValue?: TreeseedEnvironmentValueResolver;
@@ -119,7 +126,9 @@ export declare function getTreeseedEnvironmentSuggestedValues(options: {
119
126
  deployConfig?: TreeseedDeployConfig;
120
127
  tenantConfig?: TreeseedTenantConfig;
121
128
  plugins?: LoadedTreeseedPluginEntry[];
129
+ values?: Record<string, string | undefined>;
122
130
  }): Record<string, string>;
131
+ export declare function isTreeseedEnvironmentEntryRequired(entry: TreeseedEnvironmentEntry, context: TreeseedEnvironmentContext, scope: TreeseedEnvironmentScope, purpose?: TreeseedEnvironmentPurpose): boolean;
123
132
  export declare function validateTreeseedEnvironmentValues(options: {
124
133
  values: Record<string, string | undefined>;
125
134
  scope: TreeseedEnvironmentScope;