@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
@@ -46,11 +46,13 @@ const TREESEED_DEFAULT_PROVIDER_SELECTIONS = {
46
46
  },
47
47
  deploy: "cloudflare",
48
48
  content: {
49
+ runtime: "team_scoped_r2_overlay",
50
+ publish: "team_scoped_r2_overlay",
49
51
  docs: "default"
50
52
  },
51
53
  site: "default"
52
54
  };
53
- const TRESEED_MANAGED_SERVICE_KEYS = ["api", "agents", "gateway", "manager", "worker", "workdayStart", "workdayReport"];
55
+ const TRESEED_MANAGED_SERVICE_KEYS = ["api", "agents", "manager", "worker", "runner", "workdayStart", "workdayReport"];
54
56
  const TRESEED_WORKSPACE_PACKAGE_DIRS = ["sdk", "core", "cli"];
55
57
  const CLOUDFLARE_ACCOUNT_ID_PLACEHOLDER = "replace-with-cloudflare-account-id";
56
58
  function parseServiceEnvironmentConfig(value) {
@@ -84,7 +86,8 @@ function parseManagedServiceConfig(value, label) {
84
86
  serviceName: optionalString(railway.serviceName),
85
87
  rootDir: optionalString(railway.rootDir),
86
88
  buildCommand: optionalString(railway.buildCommand),
87
- startCommand: optionalString(railway.startCommand)
89
+ startCommand: optionalString(railway.startCommand),
90
+ schedule: Array.isArray(railway.schedule) ? railway.schedule.map((entry) => optionalString(entry)).filter(Boolean) : optionalString(railway.schedule)
88
91
  },
89
92
  environments: {
90
93
  local: parseServiceEnvironmentConfig(environments.local),
@@ -260,6 +263,25 @@ function optionalBoolean(value, label) {
260
263
  }
261
264
  return value;
262
265
  }
266
+ function optionalPositiveNumber(value, label) {
267
+ if (value === void 0) {
268
+ return void 0;
269
+ }
270
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
271
+ throw new Error(`Invalid deploy config: expected ${label} to be a positive number when provided.`);
272
+ }
273
+ return value;
274
+ }
275
+ function optionalEnum(value, label, allowed) {
276
+ const normalized = optionalString(value);
277
+ if (!normalized) {
278
+ return void 0;
279
+ }
280
+ if (!allowed.includes(normalized)) {
281
+ throw new Error(`Invalid deploy config: expected ${label} to be one of ${allowed.join(", ")}.`);
282
+ }
283
+ return normalized;
284
+ }
263
285
  function optionalRecord(value, label) {
264
286
  if (value === void 0 || value === null) {
265
287
  return void 0;
@@ -289,6 +311,9 @@ function parseFallbackDeployConfig(configPath) {
289
311
  const parsed = parseYaml(readFileSync(configPath, "utf8")) ?? {};
290
312
  const record = optionalRecord(parsed, "root") ?? {};
291
313
  const cloudflare = optionalRecord(record.cloudflare, "cloudflare") ?? {};
314
+ const cloudflarePages = optionalRecord(cloudflare.pages, "cloudflare.pages") ?? {};
315
+ const cloudflareR2 = optionalRecord(cloudflare.r2, "cloudflare.r2") ?? {};
316
+ const hosting = optionalRecord(record.hosting, "hosting") ?? {};
292
317
  const smtp = optionalRecord(record.smtp, "smtp") ?? {};
293
318
  const turnstile = optionalRecord(record.turnstile, "turnstile") ?? {};
294
319
  const agentProviders = optionalRecord(optionalRecord(record.providers, "providers")?.agents, "providers.agents") ?? {};
@@ -298,14 +323,39 @@ function parseFallbackDeployConfig(configPath) {
298
323
  slug: expectString(record.slug, "slug"),
299
324
  siteUrl: expectString(record.siteUrl, "siteUrl"),
300
325
  contactEmail: expectString(record.contactEmail, "contactEmail"),
326
+ hosting: Object.keys(hosting).length === 0 ? void 0 : {
327
+ kind: optionalEnum(hosting.kind, "hosting.kind", [
328
+ "market_control_plane",
329
+ "hosted_project",
330
+ "self_hosted_project"
331
+ ]) ?? "self_hosted_project",
332
+ registration: optionalEnum(hosting.registration, "hosting.registration", ["optional", "none"]) ?? "none",
333
+ marketBaseUrl: optionalString(hosting.marketBaseUrl),
334
+ teamId: optionalString(hosting.teamId),
335
+ projectId: optionalString(hosting.projectId)
336
+ },
301
337
  cloudflare: {
302
338
  accountId: optionalCloudflareAccountId(cloudflare.accountId) ?? optionalCloudflareAccountId(process.env.CLOUDFLARE_ACCOUNT_ID) ?? CLOUDFLARE_ACCOUNT_ID_PLACEHOLDER,
303
339
  workerName: optionalString(cloudflare.workerName),
304
- gatewayWorkerName: optionalString(cloudflare.gatewayWorkerName),
305
340
  queueName: optionalString(cloudflare.queueName),
306
341
  dlqName: optionalString(cloudflare.dlqName),
307
342
  d1Binding: optionalString(cloudflare.d1Binding),
308
- queueBinding: optionalString(cloudflare.queueBinding)
343
+ queueBinding: optionalString(cloudflare.queueBinding),
344
+ pages: cloudflare.pages === void 0 ? void 0 : {
345
+ projectName: optionalString(cloudflarePages.projectName) ?? optionalString(process.env.TREESEED_CLOUDFLARE_PAGES_PROJECT_NAME),
346
+ previewProjectName: optionalString(cloudflarePages.previewProjectName) ?? optionalString(process.env.TREESEED_CLOUDFLARE_PAGES_PREVIEW_PROJECT_NAME),
347
+ productionBranch: optionalString(cloudflarePages.productionBranch) ?? "main",
348
+ stagingBranch: optionalString(cloudflarePages.stagingBranch) ?? "staging",
349
+ buildOutputDir: optionalString(cloudflarePages.buildOutputDir)
350
+ },
351
+ r2: cloudflare.r2 === void 0 ? void 0 : {
352
+ binding: optionalString(cloudflareR2.binding) ?? optionalString(process.env.TREESEED_CONTENT_BUCKET_BINDING),
353
+ bucketName: optionalString(cloudflareR2.bucketName) ?? optionalString(process.env.TREESEED_CONTENT_BUCKET_NAME),
354
+ publicBaseUrl: optionalString(cloudflareR2.publicBaseUrl) ?? optionalString(process.env.TREESEED_CONTENT_PUBLIC_BASE_URL),
355
+ manifestKeyTemplate: optionalString(cloudflareR2.manifestKeyTemplate ?? cloudflareR2.manifestKey) ?? "teams/{teamId}/published/common.json",
356
+ previewRootTemplate: optionalString(cloudflareR2.previewRootTemplate ?? cloudflareR2.previewRoot) ?? "teams/{teamId}/previews",
357
+ previewTtlHours: optionalPositiveNumber(cloudflareR2.previewTtlHours, "cloudflare.r2.previewTtlHours") ?? 168
358
+ }
309
359
  },
310
360
  plugins: parsePluginReferences(record.plugins),
311
361
  providers: {
@@ -320,7 +370,18 @@ function parseFallbackDeployConfig(configPath) {
320
370
  },
321
371
  deploy: expectString(record.providers?.deploy ?? TREESEED_DEFAULT_PROVIDER_SELECTIONS.deploy, "providers.deploy"),
322
372
  content: {
323
- docs: expectString(contentProviders.docs ?? TREESEED_DEFAULT_PROVIDER_SELECTIONS.content.docs, "providers.content.docs")
373
+ runtime: expectString(
374
+ contentProviders.runtime ?? TREESEED_DEFAULT_PROVIDER_SELECTIONS.content.runtime,
375
+ "providers.content.runtime"
376
+ ),
377
+ publish: expectString(
378
+ contentProviders.publish ?? contentProviders.runtime ?? TREESEED_DEFAULT_PROVIDER_SELECTIONS.content.publish,
379
+ "providers.content.publish"
380
+ ),
381
+ docs: expectString(
382
+ contentProviders.docs ?? contentProviders.runtime ?? TREESEED_DEFAULT_PROVIDER_SELECTIONS.content.docs ?? TREESEED_DEFAULT_PROVIDER_SELECTIONS.content.runtime,
383
+ "providers.content.docs"
384
+ )
324
385
  },
325
386
  site: expectString(record.providers?.site ?? TREESEED_DEFAULT_PROVIDER_SELECTIONS.site, "providers.site")
326
387
  },
@@ -83,7 +83,7 @@ export declare function serializeTemplateRegistryEntry(product: Pick<TemplatePro
83
83
  templateApiVersion: number;
84
84
  minCliVersion: string;
85
85
  minCoreVersion: string | undefined;
86
- fulfillmentMode: "git" | "packaged";
86
+ fulfillmentMode: "r2" | "git" | "packaged";
87
87
  source: import("../../sdk-types.ts").SdkTemplateCatalogSource;
88
88
  };
89
89
  export declare function exportTemplateCatalogYaml(options?: TemplateCatalogOptions): Promise<string>;
@@ -97,9 +97,10 @@ function validateTemplatePlaceholders(definition) {
97
97
  }
98
98
  function normalizeTemplateProduct(remoteProduct) {
99
99
  const artifactRoot = resolve(localTemplateArtifactsRoot, remoteProduct.id);
100
+ const source = remoteProduct.fulfillment.source;
100
101
  return {
101
102
  ...remoteProduct,
102
- contentPath: `${remoteProduct.fulfillment.source.repoUrl}#${remoteProduct.id}`,
103
+ contentPath: source.kind === "git" ? `${source.repoUrl}#${remoteProduct.id}` : `r2://${source.bucket ?? "bucket"}/${source.objectKey}#${remoteProduct.id}`,
103
104
  artifactRoot,
104
105
  artifactManifestPath: resolve(artifactRoot, "template.config.json"),
105
106
  templateRoot: resolve(artifactRoot, "template"),
@@ -111,7 +112,8 @@ function sanitizeCacheSegment(value) {
111
112
  }
112
113
  function resolveTemplateSourceCacheRoot(product, options) {
113
114
  const cachePath = resolveTreeseedTemplateCatalogCachePath(options.cwd ?? process.cwd());
114
- return resolve(dirname(cachePath), "templates", sanitizeCacheSegment(product.id), sanitizeCacheSegment(product.fulfillment.source.ref));
115
+ const sourceVersion = product.fulfillment.source.kind === "git" ? product.fulfillment.source.ref : product.fulfillment.source.version;
116
+ return resolve(dirname(cachePath), "templates", sanitizeCacheSegment(product.id), sanitizeCacheSegment(sourceVersion));
115
117
  }
116
118
  function runGit(commandArgs, cwd) {
117
119
  const result = spawnSync("git", commandArgs, {
@@ -141,6 +143,18 @@ function materializeGitTemplateSource(product, options) {
141
143
  templateRoot: resolve(artifactRoot, "template")
142
144
  };
143
145
  }
146
+ function materializeR2TemplateSource(product) {
147
+ if (existsSync(product.artifactManifestPath) && existsSync(product.templateRoot)) {
148
+ return {
149
+ artifactRoot: product.artifactRoot,
150
+ manifestPath: product.artifactManifestPath,
151
+ templateRoot: product.templateRoot
152
+ };
153
+ }
154
+ throw new Error(
155
+ `Template ${product.id} uses an R2 fulfillment source (${product.fulfillment.source.objectKey}) but no packaged artifact is present in the local cache yet.`
156
+ );
157
+ }
144
158
  function resolveTemplateDefinitionPaths(product, options) {
145
159
  if (existsSync(product.artifactManifestPath) && existsSync(product.templateRoot)) {
146
160
  return {
@@ -149,7 +163,7 @@ function resolveTemplateDefinitionPaths(product, options) {
149
163
  templateRoot: product.templateRoot
150
164
  };
151
165
  }
152
- return materializeGitTemplateSource(product, options);
166
+ return product.fulfillment.source.kind === "git" ? materializeGitTemplateSource(product, options) : materializeR2TemplateSource(product);
153
167
  }
154
168
  function readTemplateCatalogCache(cachePath) {
155
169
  if (!existsSync(cachePath)) {
@@ -1,10 +1,10 @@
1
1
  import type { TreeseedBookDefinition, TreeseedTenantConfig } from './contracts.ts';
2
- interface DocsLibraryDownload {
2
+ export interface DocsLibraryDownload {
3
3
  downloadFileName: string;
4
4
  downloadHref: string;
5
5
  downloadTitle: string;
6
6
  }
7
- interface TenantBookRuntime {
7
+ export interface TreeseedBookRuntime {
8
8
  BOOKS: TreeseedBookDefinition[];
9
9
  BOOKS_LINK: {
10
10
  label: string;
@@ -19,11 +19,10 @@ export declare function buildTenantBookRuntime(tenantConfig: Pick<TreeseedTenant
19
19
  projectRoot?: string;
20
20
  docsHomePath?: string;
21
21
  docsLibraryDownload?: DocsLibraryDownload;
22
- }): TenantBookRuntime;
22
+ }): TreeseedBookRuntime;
23
23
  export declare const BOOKS: TreeseedBookDefinition[], BOOKS_LINK: {
24
24
  label: string;
25
25
  link: string;
26
26
  }, TREESEED_LINKS: {
27
27
  home: string;
28
28
  }, TREESEED_LIBRARY_DOWNLOAD: DocsLibraryDownload;
29
- export {};
@@ -3,6 +3,25 @@ import path from "node:path";
3
3
  import { parse as parseYaml } from "yaml";
4
4
  import { getTenantContentRoot } from "./tenant-config.js";
5
5
  import { RUNTIME_PROJECT_ROOT, RUNTIME_TENANT } from "./tenant/runtime-config.js";
6
+ function fallbackTenantBookRuntime(options = {}) {
7
+ const docsHomePath = options.docsHomePath ?? "/knowledge/";
8
+ const docsLibraryDownload = options.docsLibraryDownload ?? {
9
+ downloadFileName: "treeseed-knowledge.md",
10
+ downloadHref: "/books/treeseed-knowledge.md",
11
+ downloadTitle: "TreeSeed Knowledge Library"
12
+ };
13
+ return {
14
+ BOOKS: [],
15
+ BOOKS_LINK: {
16
+ label: "Books",
17
+ link: docsHomePath
18
+ },
19
+ TREESEED_LINKS: {
20
+ home: docsHomePath
21
+ },
22
+ TREESEED_LIBRARY_DOWNLOAD: docsLibraryDownload
23
+ };
24
+ }
6
25
  function sortPaths(paths) {
7
26
  return [...paths].sort((left, right) => left.localeCompare(right, void 0, { numeric: true, sensitivity: "base" }));
8
27
  }
@@ -64,14 +83,21 @@ function buildTenantBookRuntime(tenantConfig, options = {}) {
64
83
  TREESEED_LIBRARY_DOWNLOAD: docsLibraryDownload
65
84
  };
66
85
  }
67
- const runtime = buildTenantBookRuntime(RUNTIME_TENANT, {
68
- projectRoot: RUNTIME_PROJECT_ROOT,
69
- docsLibraryDownload: {
86
+ const runtime = (() => {
87
+ const docsLibraryDownload = {
70
88
  downloadFileName: "treeseed-knowledge.md",
71
89
  downloadHref: "/books/treeseed-knowledge.md",
72
90
  downloadTitle: "TreeSeed Knowledge Library"
91
+ };
92
+ try {
93
+ return buildTenantBookRuntime(RUNTIME_TENANT, {
94
+ projectRoot: RUNTIME_PROJECT_ROOT,
95
+ docsLibraryDownload
96
+ });
97
+ } catch {
98
+ return fallbackTenantBookRuntime({ docsLibraryDownload });
73
99
  }
74
- });
100
+ })();
75
101
  const { BOOKS, BOOKS_LINK, TREESEED_LINKS, TREESEED_LIBRARY_DOWNLOAD } = runtime;
76
102
  export {
77
103
  BOOKS,
@@ -1,5 +1,5 @@
1
1
  export type TreeseedFeatureName = 'docs' | 'books' | 'notes' | 'questions' | 'objectives' | 'agents' | 'forms';
2
- export type TreeseedContentCollection = 'pages' | 'notes' | 'questions' | 'objectives' | 'people' | 'agents' | 'books' | 'docs';
2
+ export type TreeseedContentCollection = 'pages' | 'notes' | 'questions' | 'objectives' | 'people' | 'agents' | 'books' | 'docs' | 'templates' | 'knowledge_packs' | 'workdays';
3
3
  export interface TreeseedFeatureModules {
4
4
  docs?: boolean;
5
5
  books?: boolean;
@@ -19,6 +19,20 @@ export interface TreeseedContentMap {
19
19
  agents: string;
20
20
  books: string;
21
21
  docs: string;
22
+ templates?: string;
23
+ knowledge_packs?: string;
24
+ workdays?: string;
25
+ [key: string]: string | undefined;
26
+ }
27
+ export interface TreeseedTenantSiteModelConfig {
28
+ /**
29
+ * Controls whether this content model should be rendered by the site runtime.
30
+ * Content remains managed in Git and available through SDK/content pipelines.
31
+ */
32
+ rendered?: boolean;
33
+ }
34
+ export interface TreeseedTenantSiteConfig {
35
+ models?: Partial<Record<TreeseedContentCollection, TreeseedTenantSiteModelConfig>>;
22
36
  }
23
37
  export interface TreeseedBookDefinition {
24
38
  order: number;
@@ -83,7 +97,7 @@ export interface TreeseedPluginReference {
83
97
  enabled?: boolean;
84
98
  config?: Record<string, unknown>;
85
99
  }
86
- export type TreeseedPlatformSurfaceName = 'web' | 'api' | 'gateway' | (string & {});
100
+ export type TreeseedPlatformSurfaceName = 'web' | 'api' | (string & {});
87
101
  export type TreeseedPlatformResourceKind = 'pages' | 'styles' | 'components' | 'routes' | 'middleware' | 'handlers' | 'config';
88
102
  export interface TreeseedPlatformLayerDefinition {
89
103
  root: string;
@@ -105,11 +119,38 @@ export interface TreeseedPlatformSurfaceConfig {
105
119
  publicBaseUrl?: string;
106
120
  localBaseUrl?: string;
107
121
  }
122
+ export interface TreeseedCloudflareR2Config {
123
+ binding?: string;
124
+ bucketName?: string;
125
+ publicBaseUrl?: string;
126
+ manifestKeyTemplate?: string;
127
+ previewRootTemplate?: string;
128
+ previewTtlHours?: number;
129
+ }
130
+ export interface TreeseedCloudflarePagesConfig {
131
+ projectName?: string;
132
+ previewProjectName?: string;
133
+ productionBranch?: string;
134
+ stagingBranch?: string;
135
+ buildOutputDir?: string;
136
+ }
137
+ export type TreeseedHostingKind = 'market_control_plane' | 'hosted_project' | 'self_hosted_project';
138
+ export type TreeseedHostingRegistration = 'optional' | 'none';
139
+ export interface TreeseedHostingConfig {
140
+ kind: TreeseedHostingKind;
141
+ registration?: TreeseedHostingRegistration;
142
+ marketBaseUrl?: string;
143
+ teamId?: string;
144
+ projectId?: string;
145
+ }
108
146
  export interface TreeseedManagedServiceEnvironmentConfig {
109
147
  baseUrl?: string;
110
148
  domain?: string;
111
149
  railwayEnvironment?: string;
112
150
  }
151
+ export interface TreeseedManagedServiceCloudflareConfig {
152
+ workerName?: string;
153
+ }
113
154
  export interface TreeseedManagedServiceRailwayConfig {
114
155
  projectId?: string;
115
156
  projectName?: string;
@@ -118,12 +159,14 @@ export interface TreeseedManagedServiceRailwayConfig {
118
159
  rootDir?: string;
119
160
  buildCommand?: string;
120
161
  startCommand?: string;
162
+ schedule?: string | string[];
121
163
  }
122
164
  export interface TreeseedManagedServiceConfig {
123
165
  enabled?: boolean;
124
166
  provider?: string;
125
167
  rootDir?: string;
126
168
  publicBaseUrl?: string;
169
+ cloudflare?: TreeseedManagedServiceCloudflareConfig;
127
170
  railway?: TreeseedManagedServiceRailwayConfig;
128
171
  environments?: Partial<Record<'local' | 'staging' | 'prod', TreeseedManagedServiceEnvironmentConfig>>;
129
172
  }
@@ -135,7 +178,6 @@ export interface TreeseedManagedServicesConfig {
135
178
  export interface TreeseedPlatformSurfacesConfig {
136
179
  web?: TreeseedPlatformSurfaceConfig;
137
180
  api?: TreeseedPlatformSurfaceConfig;
138
- gateway?: TreeseedPlatformSurfaceConfig;
139
181
  [key: string]: TreeseedPlatformSurfaceConfig | undefined;
140
182
  }
141
183
  export interface TreeseedProviderSelections {
@@ -151,7 +193,9 @@ export interface TreeseedProviderSelections {
151
193
  };
152
194
  deploy: string;
153
195
  content?: {
154
- docs: string;
196
+ runtime: string;
197
+ publish: string;
198
+ docs?: string;
155
199
  };
156
200
  site?: string;
157
201
  }
@@ -164,9 +208,16 @@ export interface TreeseedDeployConfig {
164
208
  slug: string;
165
209
  siteUrl: string;
166
210
  contactEmail: string;
211
+ hosting?: TreeseedHostingConfig;
167
212
  cloudflare: {
168
213
  accountId: string;
169
214
  workerName?: string;
215
+ queueName?: string;
216
+ dlqName?: string;
217
+ d1Binding?: string;
218
+ queueBinding?: string;
219
+ pages?: TreeseedCloudflarePagesConfig;
220
+ r2?: TreeseedCloudflareR2Config;
170
221
  };
171
222
  plugins: TreeseedPluginReference[];
172
223
  providers: TreeseedProviderSelections;
@@ -185,5 +236,6 @@ export interface TreeseedTenantConfig {
185
236
  siteConfigPath: string;
186
237
  content: TreeseedContentMap;
187
238
  features: TreeseedFeatureModules;
239
+ site?: TreeseedTenantSiteConfig;
188
240
  overrides?: TreeseedTenantOverrides;
189
241
  }
@@ -11,9 +11,35 @@ const deployConfigFieldAliases = {
11
11
  siteUrl: { key: "siteUrl", aliases: ["site_url"] },
12
12
  contactEmail: { key: "contactEmail", aliases: ["contact_email"] }
13
13
  };
14
+ const hostingFieldAliases = {
15
+ kind: { key: "kind", aliases: ["kind"] },
16
+ registration: { key: "registration", aliases: ["registration"] },
17
+ marketBaseUrl: { key: "marketBaseUrl", aliases: ["market_base_url"] },
18
+ teamId: { key: "teamId", aliases: ["team_id"] },
19
+ projectId: { key: "projectId", aliases: ["project_id"] }
20
+ };
14
21
  const cloudflareFieldAliases = {
15
22
  accountId: { key: "accountId", aliases: ["account_id"] },
16
- workerName: { key: "workerName", aliases: ["worker_name"] }
23
+ workerName: { key: "workerName", aliases: ["worker_name"] },
24
+ queueName: { key: "queueName", aliases: ["queue_name"] },
25
+ dlqName: { key: "dlqName", aliases: ["dlq_name"] },
26
+ d1Binding: { key: "d1Binding", aliases: ["d1_binding"] },
27
+ queueBinding: { key: "queueBinding", aliases: ["queue_binding"] }
28
+ };
29
+ const cloudflarePagesFieldAliases = {
30
+ projectName: { key: "projectName", aliases: ["project_name"] },
31
+ previewProjectName: { key: "previewProjectName", aliases: ["preview_project_name"] },
32
+ productionBranch: { key: "productionBranch", aliases: ["production_branch"] },
33
+ stagingBranch: { key: "stagingBranch", aliases: ["staging_branch"] },
34
+ buildOutputDir: { key: "buildOutputDir", aliases: ["build_output_dir"] }
35
+ };
36
+ const cloudflareR2FieldAliases = {
37
+ binding: { key: "binding", aliases: ["binding"] },
38
+ bucketName: { key: "bucketName", aliases: ["bucket_name"] },
39
+ publicBaseUrl: { key: "publicBaseUrl", aliases: ["public_base_url"] },
40
+ manifestKeyTemplate: { key: "manifestKeyTemplate", aliases: ["manifest_key_template", "manifest_key"] },
41
+ previewRootTemplate: { key: "previewRootTemplate", aliases: ["preview_root_template", "preview_root"] },
42
+ previewTtlHours: { key: "previewTtlHours", aliases: ["preview_ttl_hours"] }
17
43
  };
18
44
  const CLOUDFLARE_ACCOUNT_ID_PLACEHOLDER = "replace-with-cloudflare-account-id";
19
45
  function expectString(value, label) {
@@ -32,6 +58,28 @@ function optionalCloudflareAccountId(value) {
32
58
  const accountId = optionalString(value);
33
59
  return accountId === CLOUDFLARE_ACCOUNT_ID_PLACEHOLDER ? void 0 : accountId;
34
60
  }
61
+ function optionalPositiveNumber(value, label) {
62
+ if (value === void 0) {
63
+ return void 0;
64
+ }
65
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
66
+ throw new Error(`Invalid deploy config: expected ${label} to be a positive number when provided.`);
67
+ }
68
+ return value;
69
+ }
70
+ function optionalEnum(value, label, allowed) {
71
+ if (value === void 0) {
72
+ return void 0;
73
+ }
74
+ const normalized = optionalString(value);
75
+ if (!normalized) {
76
+ return void 0;
77
+ }
78
+ if (!allowed.includes(normalized)) {
79
+ throw new Error(`Invalid deploy config: expected ${label} to be one of ${allowed.join(", ")}.`);
80
+ }
81
+ return normalized;
82
+ }
35
83
  function optionalBoolean(value, label) {
36
84
  if (value === void 0) {
37
85
  return void 0;
@@ -75,6 +123,26 @@ function parsePluginReferences(value) {
75
123
  };
76
124
  });
77
125
  }
126
+ function parseHostingConfig(value) {
127
+ const record = normalizeAliasedRecord(
128
+ hostingFieldAliases,
129
+ optionalRecord(value, "hosting") ?? {}
130
+ );
131
+ if (!value || Object.keys(record).length === 0) {
132
+ return void 0;
133
+ }
134
+ return {
135
+ kind: optionalEnum(record.kind, "hosting.kind", [
136
+ "market_control_plane",
137
+ "hosted_project",
138
+ "self_hosted_project"
139
+ ]) ?? "self_hosted_project",
140
+ registration: optionalEnum(record.registration, "hosting.registration", ["optional", "none"]) ?? "none",
141
+ marketBaseUrl: optionalString(record.marketBaseUrl),
142
+ teamId: optionalString(record.teamId),
143
+ projectId: optionalString(record.projectId)
144
+ };
145
+ }
78
146
  function parseProviderSelections(value) {
79
147
  const record = optionalRecord(value, "providers");
80
148
  if (!record) {
@@ -113,8 +181,16 @@ function parseProviderSelections(value) {
113
181
  },
114
182
  deploy: expectString(record.deploy ?? TREESEED_DEFAULT_PROVIDER_SELECTIONS.deploy, "providers.deploy"),
115
183
  content: {
184
+ runtime: expectString(
185
+ contentProviders.runtime ?? TREESEED_DEFAULT_PROVIDER_SELECTIONS.content.runtime,
186
+ "providers.content.runtime"
187
+ ),
188
+ publish: expectString(
189
+ contentProviders.publish ?? contentProviders.runtime ?? TREESEED_DEFAULT_PROVIDER_SELECTIONS.content.publish,
190
+ "providers.content.publish"
191
+ ),
116
192
  docs: expectString(
117
- contentProviders.docs ?? TREESEED_DEFAULT_PROVIDER_SELECTIONS.content.docs,
193
+ contentProviders.docs ?? contentProviders.runtime ?? TREESEED_DEFAULT_PROVIDER_SELECTIONS.content.docs ?? TREESEED_DEFAULT_PROVIDER_SELECTIONS.content.runtime,
118
194
  "providers.content.docs"
119
195
  )
120
196
  },
@@ -148,7 +224,8 @@ function parseManagedServiceConfig(value, label) {
148
224
  serviceName: optionalString(railway.serviceName),
149
225
  rootDir: optionalString(railway.rootDir),
150
226
  buildCommand: optionalString(railway.buildCommand),
151
- startCommand: optionalString(railway.startCommand)
227
+ startCommand: optionalString(railway.startCommand),
228
+ schedule: Array.isArray(railway.schedule) ? railway.schedule.map((entry) => optionalString(entry)).filter(Boolean) : optionalString(railway.schedule)
152
229
  },
153
230
  environments: {
154
231
  local: parseServiceEnvironmentConfig(environments.local, `${label}.environments.local`),
@@ -213,6 +290,14 @@ function parseDeployConfig(raw) {
213
290
  cloudflareFieldAliases,
214
291
  optionalRecord(parsed.cloudflare, "cloudflare") ?? {}
215
292
  );
293
+ const cloudflarePages = normalizeAliasedRecord(
294
+ cloudflarePagesFieldAliases,
295
+ optionalRecord(cloudflare.pages, "cloudflare.pages") ?? {}
296
+ );
297
+ const cloudflareR2 = normalizeAliasedRecord(
298
+ cloudflareR2FieldAliases,
299
+ optionalRecord(cloudflare.r2, "cloudflare.r2") ?? {}
300
+ );
216
301
  const smtp = optionalRecord(parsed.smtp, "smtp") ?? {};
217
302
  const turnstile = optionalRecord(parsed.turnstile, "turnstile") ?? {};
218
303
  optionalBoolean(turnstile.enabled, "turnstile.enabled");
@@ -221,9 +306,29 @@ function parseDeployConfig(raw) {
221
306
  slug: expectString(parsed.slug, "slug"),
222
307
  siteUrl: expectString(parsed.siteUrl, "siteUrl"),
223
308
  contactEmail: expectString(parsed.contactEmail, "contactEmail"),
309
+ hosting: parseHostingConfig(parsed.hosting),
224
310
  cloudflare: {
225
311
  accountId: optionalCloudflareAccountId(cloudflare.accountId) ?? optionalCloudflareAccountId(process.env.CLOUDFLARE_ACCOUNT_ID) ?? CLOUDFLARE_ACCOUNT_ID_PLACEHOLDER,
226
- workerName: optionalString(cloudflare.workerName)
312
+ workerName: optionalString(cloudflare.workerName),
313
+ queueName: optionalString(cloudflare.queueName),
314
+ dlqName: optionalString(cloudflare.dlqName),
315
+ d1Binding: optionalString(cloudflare.d1Binding),
316
+ queueBinding: optionalString(cloudflare.queueBinding),
317
+ pages: cloudflare.pages === void 0 ? void 0 : {
318
+ projectName: optionalString(cloudflarePages.projectName) ?? optionalString(process.env.TREESEED_CLOUDFLARE_PAGES_PROJECT_NAME),
319
+ previewProjectName: optionalString(cloudflarePages.previewProjectName) ?? optionalString(process.env.TREESEED_CLOUDFLARE_PAGES_PREVIEW_PROJECT_NAME),
320
+ productionBranch: optionalString(cloudflarePages.productionBranch) ?? "main",
321
+ stagingBranch: optionalString(cloudflarePages.stagingBranch) ?? "staging",
322
+ buildOutputDir: optionalString(cloudflarePages.buildOutputDir)
323
+ },
324
+ r2: cloudflare.r2 === void 0 ? void 0 : {
325
+ binding: optionalString(cloudflareR2.binding) ?? optionalString(process.env.TREESEED_CONTENT_BUCKET_BINDING),
326
+ bucketName: optionalString(cloudflareR2.bucketName) ?? optionalString(process.env.TREESEED_CONTENT_BUCKET_NAME),
327
+ publicBaseUrl: optionalString(cloudflareR2.publicBaseUrl) ?? optionalString(process.env.TREESEED_CONTENT_PUBLIC_BASE_URL),
328
+ manifestKeyTemplate: optionalString(cloudflareR2.manifestKeyTemplate) ?? "teams/{teamId}/published/common.json",
329
+ previewRootTemplate: optionalString(cloudflareR2.previewRootTemplate) ?? "teams/{teamId}/previews",
330
+ previewTtlHours: optionalPositiveNumber(cloudflareR2.previewTtlHours, "cloudflare.r2.previewTtlHours") ?? 168
331
+ }
227
332
  },
228
333
  plugins: parsePluginReferences(parsed.plugins),
229
334
  providers: parseProviderSelections(parsed.providers),
@@ -12,6 +12,8 @@ export declare function getTreeseedAgentProviderSelections(): {
12
12
  research: string;
13
13
  };
14
14
  export declare function getTreeseedDeployProvider(): string;
15
+ export declare function getTreeseedContentRuntimeProvider(): string;
16
+ export declare function getTreeseedContentPublishProvider(): string;
15
17
  export declare function getTreeseedDocsProvider(): string;
16
18
  export declare function getTreeseedSiteProvider(): string;
17
19
  export declare function isTreeseedSmtpEnabled(): boolean;
@@ -52,8 +52,14 @@ function getTreeseedAgentProviderSelections() {
52
52
  function getTreeseedDeployProvider() {
53
53
  return getTreeseedDeployConfig().providers?.deploy ?? TREESEED_DEFAULT_PROVIDER_SELECTIONS.deploy;
54
54
  }
55
+ function getTreeseedContentRuntimeProvider() {
56
+ return getTreeseedDeployConfig().providers?.content?.runtime ?? TREESEED_DEFAULT_PROVIDER_SELECTIONS.content.runtime;
57
+ }
58
+ function getTreeseedContentPublishProvider() {
59
+ return getTreeseedDeployConfig().providers?.content?.publish ?? TREESEED_DEFAULT_PROVIDER_SELECTIONS.content.publish;
60
+ }
55
61
  function getTreeseedDocsProvider() {
56
- return getTreeseedDeployConfig().providers?.content?.docs ?? TREESEED_DEFAULT_PROVIDER_SELECTIONS.content.docs;
62
+ return getTreeseedDeployConfig().providers?.content?.docs ?? getTreeseedDeployConfig().providers?.content?.runtime ?? TREESEED_DEFAULT_PROVIDER_SELECTIONS.content.docs ?? TREESEED_DEFAULT_PROVIDER_SELECTIONS.content.runtime;
57
63
  }
58
64
  function getTreeseedSiteProvider() {
59
65
  return getTreeseedDeployConfig().providers?.site ?? TREESEED_DEFAULT_PROVIDER_SELECTIONS.site;
@@ -66,6 +72,8 @@ function isTreeseedTurnstileEnabled() {
66
72
  }
67
73
  export {
68
74
  getTreeseedAgentProviderSelections,
75
+ getTreeseedContentPublishProvider,
76
+ getTreeseedContentRuntimeProvider,
69
77
  getTreeseedDeployConfig,
70
78
  getTreeseedDeployProvider,
71
79
  getTreeseedDocsProvider,