@treeseed/core 0.4.9 → 0.4.11

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 (82) hide show
  1. package/README.md +1 -2
  2. package/dist/agent.d.ts +0 -1
  3. package/dist/agent.js +0 -2
  4. package/dist/agents/spec-types.d.ts +10 -10
  5. package/dist/api/agent-routes.d.ts +2 -2
  6. package/dist/api/agent-routes.js +51 -125
  7. package/dist/api/app.js +56 -4
  8. package/dist/api/auth/d1-store.d.ts +1 -0
  9. package/dist/api/auth/d1-store.js +21 -1
  10. package/dist/api/auth/rbac.d.ts +2 -2
  11. package/dist/api/auth/rbac.js +2 -1
  12. package/dist/api/config.js +4 -0
  13. package/dist/api/http.d.ts +4 -0
  14. package/dist/api/http.js +7 -0
  15. package/dist/api/index.d.ts +1 -1
  16. package/dist/api/index.js +2 -2
  17. package/dist/api/operations-routes.d.ts +1 -0
  18. package/dist/api/operations-routes.js +6 -1
  19. package/dist/api/railway.d.ts +4 -0
  20. package/dist/api/sdk-dispatch.d.ts +2 -11
  21. package/dist/api/sdk-dispatch.js +1 -133
  22. package/dist/api/sdk-routes.d.ts +1 -0
  23. package/dist/api/sdk-routes.js +5 -1
  24. package/dist/api/types.d.ts +32 -16
  25. package/dist/components/site/RouteNotFound.astro +25 -0
  26. package/dist/content-config.d.ts +1 -0
  27. package/dist/content.d.ts +1 -0
  28. package/dist/content.js +177 -1
  29. package/dist/dev.d.ts +7 -2
  30. package/dist/dev.js +83 -2
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.js +9 -3
  33. package/dist/middleware/editorial-preview.d.ts +26 -0
  34. package/dist/middleware/editorial-preview.js +37 -0
  35. package/dist/middleware/starlightRouteData.js +15 -4
  36. package/dist/pages/[slug].astro +12 -10
  37. package/dist/pages/agents/[slug].astro +28 -21
  38. package/dist/pages/books/[slug].astro +19 -12
  39. package/dist/pages/feed.xml.js +6 -4
  40. package/dist/pages/index.astro +43 -14
  41. package/dist/pages/notes/[slug].astro +19 -12
  42. package/dist/pages/objectives/[slug].astro +30 -23
  43. package/dist/pages/people/[slug].astro +28 -21
  44. package/dist/pages/questions/[slug].astro +30 -23
  45. package/dist/scripts/build-dist.js +6 -1
  46. package/dist/scripts/dev-platform.js +9 -1
  47. package/dist/scripts/test-smoke.js +0 -1
  48. package/dist/services/agents.d.ts +22 -0
  49. package/dist/services/agents.js +29 -0
  50. package/dist/services/common.d.ts +37 -4
  51. package/dist/services/common.js +135 -17
  52. package/dist/services/index.d.ts +4 -1
  53. package/dist/services/index.js +14 -2
  54. package/dist/services/manager.d.ts +246 -3
  55. package/dist/services/manager.js +1101 -171
  56. package/dist/services/remote-runner.d.ts +30 -0
  57. package/dist/services/remote-runner.js +111 -0
  58. package/dist/services/workday-content.d.ts +53 -0
  59. package/dist/services/workday-content.js +190 -0
  60. package/dist/services/workday-report.d.ts +160 -2
  61. package/dist/services/workday-report.js +4 -31
  62. package/dist/services/workday-start.d.ts +174 -1
  63. package/dist/services/workday-start.js +3 -13
  64. package/dist/services/worker-pool-scaler.d.ts +27 -0
  65. package/dist/services/worker-pool-scaler.js +109 -0
  66. package/dist/services/worker.d.ts +7 -0
  67. package/dist/services/worker.js +41 -57
  68. package/dist/site.js +43 -27
  69. package/dist/templates.d.ts +98 -0
  70. package/dist/templates.js +170 -0
  71. package/dist/tenant/runtime-config.d.ts +4 -0
  72. package/dist/tenant/runtime-config.js +34 -1
  73. package/dist/utils/hub-content.js +35 -0
  74. package/dist/utils/published-content.js +60 -0
  75. package/dist/utils/site-models.d.ts +6 -0
  76. package/dist/utils/site-models.js +16 -0
  77. package/dist/utils/starlight-nav.js +50 -0
  78. package/package.json +23 -9
  79. package/templates/github/deploy.workflow.yml +404 -9
  80. package/templates/github/hosted-project.workflow.yml +77 -0
  81. package/dist/api/gateway.d.ts +0 -5
  82. package/dist/api/gateway.js +0 -35
@@ -1,2 +1,175 @@
1
1
  #!/usr/bin/env node
2
- export declare function runWorkdayStart(): Promise<any>;
2
+ export declare function runWorkdayStart(): Promise<{
3
+ ok: boolean;
4
+ mode: "reconcile";
5
+ managerId: string;
6
+ projectId: string;
7
+ environment: "local" | "prod" | "staging";
8
+ insideWorkWindow: boolean;
9
+ workPolicy: import("@treeseed/sdk").WorkdayPolicy;
10
+ workDay: Record<string, unknown>;
11
+ prioritySnapshot: import("@treeseed/sdk").PrioritySnapshot;
12
+ seededTasks: {
13
+ [x: string]: unknown;
14
+ }[];
15
+ queuedCount: number;
16
+ activeLeases: number;
17
+ desiredWorkers: number;
18
+ scaleResult: import("@treeseed/sdk").WorkerPoolScaleResult;
19
+ workdaySummary: Record<string, unknown>;
20
+ } | {
21
+ ok: boolean;
22
+ created: boolean;
23
+ workDay: Record<string, unknown>;
24
+ skipped?: undefined;
25
+ reason?: undefined;
26
+ prioritySnapshot?: undefined;
27
+ } | {
28
+ ok: boolean;
29
+ created: boolean;
30
+ skipped: boolean;
31
+ reason: string;
32
+ workDay?: undefined;
33
+ prioritySnapshot?: undefined;
34
+ } | {
35
+ ok: boolean;
36
+ created: boolean;
37
+ workDay: import("@treeseed/sdk").SdkWorkDayEntity;
38
+ prioritySnapshot: import("@treeseed/sdk").PrioritySnapshot;
39
+ skipped?: undefined;
40
+ reason?: undefined;
41
+ } | {
42
+ ok: boolean;
43
+ skipped: boolean;
44
+ reason: string;
45
+ workDay?: undefined;
46
+ summary?: undefined;
47
+ scale?: undefined;
48
+ } | {
49
+ ok: boolean;
50
+ workDay: import("@treeseed/sdk").SdkWorkDayEntity;
51
+ summary: {
52
+ contentSnapshot: {
53
+ relativePath: string;
54
+ slug: string;
55
+ reportVersion: string;
56
+ title: string;
57
+ };
58
+ projectId: string;
59
+ environment: "local" | "prod" | "staging";
60
+ workDayId: string;
61
+ state: string;
62
+ totalTasks: number;
63
+ completedTasks: number;
64
+ failedTasks: number;
65
+ queuedTasks: number;
66
+ activeTasks: number;
67
+ dailyTaskCreditBudget: number;
68
+ usedTaskCredits: number;
69
+ remainingTaskCredits: number;
70
+ creditLedgerEntries: number;
71
+ prioritySnapshotId: string;
72
+ priorityItemCount: number;
73
+ priorityItems: import("@treeseed/sdk").PrioritySnapshotItem[];
74
+ taskItems: {
75
+ id: string;
76
+ agentId: string;
77
+ type: string;
78
+ state: string;
79
+ priority: number;
80
+ idempotencyKey: string;
81
+ createdAt: string;
82
+ startedAt: string;
83
+ completedAt: string;
84
+ lastErrorCode: string;
85
+ lastErrorMessage: string;
86
+ lastEventKind: string;
87
+ outputCount: number;
88
+ changedFiles: string[];
89
+ }[];
90
+ changedFiles: string[];
91
+ releases: {
92
+ id: string;
93
+ deploymentKind: string;
94
+ status: string;
95
+ releaseTag: string;
96
+ commitSha: string;
97
+ sourceRef: string;
98
+ startedAt: string;
99
+ finishedAt: string;
100
+ createdAt: string;
101
+ }[];
102
+ scaleDecision: import("@treeseed/sdk").ScaleDecision;
103
+ scaleResult: import("@treeseed/sdk").WorkerPoolScaleResult;
104
+ generatedAt: string;
105
+ };
106
+ scale: import("@treeseed/sdk").WorkerPoolScaleResult;
107
+ skipped?: undefined;
108
+ reason?: undefined;
109
+ } | {
110
+ ok: boolean;
111
+ skipped: boolean;
112
+ reason: string;
113
+ workDayId?: undefined;
114
+ summary?: undefined;
115
+ } | {
116
+ ok: boolean;
117
+ workDayId: unknown;
118
+ summary: {
119
+ contentSnapshot: {
120
+ relativePath: string;
121
+ slug: string;
122
+ reportVersion: string;
123
+ title: string;
124
+ };
125
+ projectId: string;
126
+ environment: "local" | "prod" | "staging";
127
+ workDayId: string;
128
+ state: string;
129
+ totalTasks: number;
130
+ completedTasks: number;
131
+ failedTasks: number;
132
+ queuedTasks: number;
133
+ activeTasks: number;
134
+ dailyTaskCreditBudget: number;
135
+ usedTaskCredits: number;
136
+ remainingTaskCredits: number;
137
+ creditLedgerEntries: number;
138
+ prioritySnapshotId: string;
139
+ priorityItemCount: number;
140
+ priorityItems: import("@treeseed/sdk").PrioritySnapshotItem[];
141
+ taskItems: {
142
+ id: string;
143
+ agentId: string;
144
+ type: string;
145
+ state: string;
146
+ priority: number;
147
+ idempotencyKey: string;
148
+ createdAt: string;
149
+ startedAt: string;
150
+ completedAt: string;
151
+ lastErrorCode: string;
152
+ lastErrorMessage: string;
153
+ lastEventKind: string;
154
+ outputCount: number;
155
+ changedFiles: string[];
156
+ }[];
157
+ changedFiles: string[];
158
+ releases: {
159
+ id: string;
160
+ deploymentKind: string;
161
+ status: string;
162
+ releaseTag: string;
163
+ commitSha: string;
164
+ sourceRef: string;
165
+ startedAt: string;
166
+ finishedAt: string;
167
+ createdAt: string;
168
+ }[];
169
+ scaleDecision: import("@treeseed/sdk").ScaleDecision;
170
+ scaleResult: import("@treeseed/sdk").WorkerPoolScaleResult;
171
+ generatedAt: string;
172
+ };
173
+ skipped?: undefined;
174
+ reason?: undefined;
175
+ }>;
@@ -1,20 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { fileURLToPath } from "node:url";
3
- import { resolveWorkerConfig } from "./common.js";
3
+ import { runManagerAction } from "./manager.js";
4
4
  async function runWorkdayStart() {
5
- const managerBaseUrl = resolveWorkerConfig().managerBaseUrl;
6
- const response = await fetch(`${managerBaseUrl}/internal/workdays/start`, {
7
- method: "POST",
8
- headers: {
9
- accept: "application/json",
10
- "content-type": "application/json"
11
- },
12
- body: JSON.stringify({})
5
+ return runManagerAction({
6
+ mode: "open-workday"
13
7
  });
14
- if (!response.ok) {
15
- throw new Error(`Workday start failed with ${response.status}.`);
16
- }
17
- return response.json();
18
8
  }
19
9
  const currentFile = fileURLToPath(import.meta.url);
20
10
  const entryFile = process.argv[1] ?? "";
@@ -0,0 +1,27 @@
1
+ import type { ScaleDecision, WorkerPoolScaleResult, WorkerPoolScaler } from '@treeseed/sdk';
2
+ export type WorkerPoolScalerKind = 'noop' | 'railway';
3
+ export interface RailwayWorkerPoolScalerOptions {
4
+ apiToken?: string | null;
5
+ apiUrl?: string | null;
6
+ serviceId?: string | null;
7
+ environmentId?: string | null;
8
+ projectId?: string | null;
9
+ fetchImpl?: typeof fetch;
10
+ mutation?: string | null;
11
+ }
12
+ export declare class NoopWorkerPoolScaler implements WorkerPoolScaler {
13
+ scale(decision: ScaleDecision): Promise<WorkerPoolScaleResult>;
14
+ }
15
+ export declare class RailwayWorkerPoolScaler implements WorkerPoolScaler {
16
+ private readonly apiToken;
17
+ private readonly apiUrl;
18
+ private readonly serviceId;
19
+ private readonly environmentId;
20
+ private readonly projectId;
21
+ private readonly fetchImpl;
22
+ private readonly mutation;
23
+ constructor(options?: RailwayWorkerPoolScalerOptions);
24
+ private configured;
25
+ scale(decision: ScaleDecision): Promise<WorkerPoolScaleResult>;
26
+ }
27
+ export declare function createWorkerPoolScaler(kind?: WorkerPoolScalerKind | null, options?: RailwayWorkerPoolScalerOptions): WorkerPoolScaler;
@@ -0,0 +1,109 @@
1
+ function envValue(name) {
2
+ const value = process.env[name]?.trim();
3
+ return value ? value : "";
4
+ }
5
+ const DEFAULT_RAILWAY_API_URL = "https://backboard.railway.com/graphql/v2";
6
+ const DEFAULT_SCALE_MUTATION = `
7
+ mutation TreeseedScaleService($serviceId: String!, $environmentId: String!, $replicas: Int!) {
8
+ serviceInstanceUpdate(
9
+ serviceId: $serviceId
10
+ environmentId: $environmentId
11
+ input: { numReplicas: $replicas }
12
+ ) {
13
+ id
14
+ }
15
+ }
16
+ `.trim();
17
+ class NoopWorkerPoolScaler {
18
+ async scale(decision) {
19
+ return {
20
+ applied: false,
21
+ provider: "noop",
22
+ desiredWorkers: decision.desiredWorkers,
23
+ metadata: {
24
+ reason: "scaler_unconfigured"
25
+ }
26
+ };
27
+ }
28
+ }
29
+ class RailwayWorkerPoolScaler {
30
+ apiToken;
31
+ apiUrl;
32
+ serviceId;
33
+ environmentId;
34
+ projectId;
35
+ fetchImpl;
36
+ mutation;
37
+ constructor(options = {}) {
38
+ this.apiToken = options.apiToken?.trim() || envValue("RAILWAY_API_TOKEN") || envValue("RAILWAY_TOKEN") || null;
39
+ this.apiUrl = options.apiUrl?.trim() || envValue("TREESEED_RAILWAY_API_URL") || DEFAULT_RAILWAY_API_URL;
40
+ this.serviceId = options.serviceId?.trim() || envValue("TREESEED_RAILWAY_WORKER_SERVICE_ID") || envValue("TREESEED_WORKER_SERVICE_ID") || null;
41
+ this.environmentId = options.environmentId?.trim() || envValue("TREESEED_RAILWAY_ENVIRONMENT_ID") || null;
42
+ this.projectId = options.projectId?.trim() || envValue("TREESEED_RAILWAY_PROJECT_ID") || null;
43
+ this.fetchImpl = options.fetchImpl ?? fetch;
44
+ this.mutation = options.mutation?.trim() || envValue("TREESEED_RAILWAY_SCALE_MUTATION") || DEFAULT_SCALE_MUTATION;
45
+ }
46
+ configured() {
47
+ return Boolean(this.apiToken && this.serviceId && this.environmentId);
48
+ }
49
+ async scale(decision) {
50
+ if (!this.configured() || !this.apiToken || !this.serviceId || !this.environmentId) {
51
+ return {
52
+ applied: false,
53
+ provider: "railway",
54
+ desiredWorkers: decision.desiredWorkers,
55
+ metadata: {
56
+ reason: "railway_scaler_unconfigured",
57
+ projectId: this.projectId,
58
+ serviceId: this.serviceId,
59
+ environmentId: this.environmentId
60
+ }
61
+ };
62
+ }
63
+ const response = await this.fetchImpl(this.apiUrl, {
64
+ method: "POST",
65
+ headers: {
66
+ authorization: `Bearer ${this.apiToken}`,
67
+ "content-type": "application/json"
68
+ },
69
+ body: JSON.stringify({
70
+ query: this.mutation,
71
+ variables: {
72
+ serviceId: this.serviceId,
73
+ environmentId: this.environmentId,
74
+ replicas: decision.desiredWorkers
75
+ }
76
+ })
77
+ });
78
+ const payload = await response.json().catch(() => ({}));
79
+ if (!response.ok || Array.isArray(payload.errors) && payload.errors.length > 0) {
80
+ throw new Error(
81
+ payload.errors?.[0]?.message ?? `Railway worker scale request failed with ${response.status}.`
82
+ );
83
+ }
84
+ return {
85
+ applied: true,
86
+ provider: "railway",
87
+ desiredWorkers: decision.desiredWorkers,
88
+ metadata: {
89
+ projectId: this.projectId,
90
+ serviceId: this.serviceId,
91
+ environmentId: this.environmentId
92
+ }
93
+ };
94
+ }
95
+ }
96
+ function createWorkerPoolScaler(kind, options = {}) {
97
+ const configuredKind = envValue("TREESEED_WORKER_POOL_SCALER") || null;
98
+ const inferredKind = envValue("RAILWAY_API_TOKEN") && (envValue("TREESEED_RAILWAY_WORKER_SERVICE_ID") || envValue("TREESEED_WORKER_SERVICE_ID")) ? "railway" : "noop";
99
+ const resolvedKind = kind ?? configuredKind ?? inferredKind;
100
+ if (resolvedKind === "railway") {
101
+ return new RailwayWorkerPoolScaler(options);
102
+ }
103
+ return new NoopWorkerPoolScaler();
104
+ }
105
+ export {
106
+ NoopWorkerPoolScaler,
107
+ RailwayWorkerPoolScaler,
108
+ createWorkerPoolScaler
109
+ };
@@ -2,5 +2,12 @@
2
2
  export declare function runWorkerCycle(): Promise<{
3
3
  ok: boolean;
4
4
  processed: number;
5
+ idle: boolean;
6
+ reason: string;
7
+ } | {
8
+ ok: boolean;
9
+ processed: number;
10
+ idle?: undefined;
11
+ reason?: undefined;
5
12
  }>;
6
13
  export declare function startWorkerLoop(): Promise<void>;
@@ -1,27 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import { fileURLToPath } from "node:url";
3
- import { createGatewayClient, createQueueClient, resolveWorkerConfig } from "./common.js";
4
- async function managerRequest(baseUrl, path, body) {
5
- const response = await fetch(`${baseUrl}${path}`, {
6
- method: "POST",
7
- headers: {
8
- accept: "application/json",
9
- "content-type": "application/json"
10
- },
11
- body: JSON.stringify(body)
12
- });
13
- const payload = await response.json().catch(() => ({}));
14
- if (!response.ok) {
15
- throw new Error(typeof payload.error === "string" ? payload.error : `Manager request failed with ${response.status}.`);
16
- }
17
- return payload;
18
- }
3
+ import { buildTaskContext, createQueueClient, createServiceSdk, resolveWorkerConfig } from "./common.js";
19
4
  async function runWorkerCycle() {
20
- const gateway = createGatewayClient();
5
+ const sdk = createServiceSdk();
21
6
  const queue = createQueueClient();
22
7
  const config = resolveWorkerConfig();
23
- if (!gateway || !queue) {
24
- throw new Error("Worker requires TREESEED_GATEWAY_BASE_URL, TREESEED_GATEWAY_BEARER_TOKEN, CLOUDFLARE_ACCOUNT_ID, TREESEED_QUEUE_ID, and TREESEED_QUEUE_PULL_TOKEN.");
8
+ if (!queue) {
9
+ if (process.env.TREESEED_LOCAL_DEV_MODE?.trim()) {
10
+ return { ok: true, processed: 0, idle: true, reason: "queue_unconfigured" };
11
+ }
12
+ throw new Error("Worker requires CLOUDFLARE_ACCOUNT_ID, TREESEED_QUEUE_ID, and TREESEED_QUEUE_PULL_TOKEN.");
25
13
  }
26
14
  const pulled = await queue.pull({
27
15
  batchSize: config.batchSize,
@@ -33,52 +21,48 @@ async function runWorkerCycle() {
33
21
  let processed = 0;
34
22
  for (const message of pulled.messages) {
35
23
  try {
36
- await gateway.requestJson(`/tasks/${encodeURIComponent(message.body.taskId)}/claim`, {
37
- body: {
38
- workerId: config.workerId,
39
- leaseSeconds: config.leaseSeconds
40
- }
24
+ await sdk.claimTask({
25
+ id: message.body.taskId,
26
+ workerId: config.workerId,
27
+ leaseSeconds: config.leaseSeconds,
28
+ actor: "worker"
41
29
  });
42
- const context = await managerRequest(
43
- config.managerBaseUrl,
44
- "/internal/context/resolve-task",
45
- { taskId: message.body.taskId }
46
- );
47
- const task = context.payload.task;
30
+ const context = await buildTaskContext(sdk, message.body.taskId);
31
+ const task = context.task;
48
32
  const payload = task && typeof task.payloadJson === "string" ? JSON.parse(task.payloadJson) : {};
49
- await gateway.requestJson(`/tasks/${encodeURIComponent(message.body.taskId)}/progress`, {
50
- body: {
51
- workerId: config.workerId,
52
- state: "running",
53
- appendEvent: {
54
- kind: "worker_started",
55
- data: { workerId: config.workerId, queueAttempt: message.attempts }
56
- }
57
- }
33
+ await sdk.recordTaskProgress({
34
+ id: message.body.taskId,
35
+ workerId: config.workerId,
36
+ state: "running",
37
+ appendEvent: {
38
+ kind: "worker_started",
39
+ data: { workerId: config.workerId, queueAttempt: message.attempts }
40
+ },
41
+ actor: "worker"
58
42
  });
59
- await gateway.requestJson(`/tasks/${encodeURIComponent(message.body.taskId)}/complete`, {
60
- body: {
61
- output: {
62
- workerId: config.workerId,
63
- queueAttempt: message.attempts,
64
- payload
65
- },
66
- summary: {
67
- status: "completed",
68
- workerId: config.workerId
69
- }
70
- }
43
+ await sdk.completeTask({
44
+ id: message.body.taskId,
45
+ output: {
46
+ workerId: config.workerId,
47
+ queueAttempt: message.attempts,
48
+ payload
49
+ },
50
+ summary: {
51
+ status: "completed",
52
+ workerId: config.workerId
53
+ },
54
+ actor: "worker"
71
55
  });
72
56
  await queue.ack([message.leaseId]);
73
57
  processed += 1;
74
58
  } catch (error) {
75
59
  const retryDelaySeconds = Math.min(300, Math.max(15, message.attempts * 30));
76
- await gateway.requestJson(`/tasks/${encodeURIComponent(message.body.taskId)}/fail`, {
77
- body: {
78
- errorMessage: error instanceof Error ? error.message : String(error),
79
- retryable: true,
80
- nextVisibleAt: new Date(Date.now() + retryDelaySeconds * 1e3).toISOString()
81
- }
60
+ await sdk.failTask({
61
+ id: message.body.taskId,
62
+ errorMessage: error instanceof Error ? error.message : String(error),
63
+ retryable: true,
64
+ nextVisibleAt: new Date(Date.now() + retryDelaySeconds * 1e3).toISOString(),
65
+ actor: "worker"
82
66
  }).catch(() => null);
83
67
  await queue.retry([{ leaseId: message.leaseId, delaySeconds: retryDelaySeconds }]);
84
68
  }
package/dist/site.js CHANGED
@@ -18,6 +18,7 @@ import {
18
18
  resolveTreeseedSiteResource,
19
19
  resolveTreeseedStyleEntrypoint
20
20
  } from "./site-resources.js";
21
+ import { isSiteRenderedModel } from "./utils/site-models.js";
21
22
  const TENANT_THEME_VIRTUAL_ID = "virtual:treeseed/tenant-theme.css";
22
23
  const RESOLVED_TENANT_THEME_VIRTUAL_ID = "\0treeseed:tenant-theme.css";
23
24
  function packageFile(relativePath) {
@@ -37,20 +38,20 @@ const PACKAGE_ROUTE_ENTRIES = [
37
38
  { pattern: "/", resourcePath: "pages/index.astro" },
38
39
  { pattern: "/404", resourcePath: "pages/404.astro" },
39
40
  { pattern: "/contact", resourcePath: "pages/contact.astro" },
40
- { pattern: "/feed.xml", resourcePath: "pages/feed.xml" },
41
- { pattern: "/[slug]", resourcePath: "pages/[slug].astro" },
42
- { pattern: "/agents", resourcePath: "pages/agents/index.astro" },
43
- { pattern: "/agents/[slug]", resourcePath: "pages/agents/[slug].astro" },
44
- { pattern: "/books", resourcePath: "pages/books/index.astro" },
45
- { pattern: "/books/[slug]", resourcePath: "pages/books/[slug].astro" },
46
- { pattern: "/notes", resourcePath: "pages/notes/index.astro" },
47
- { pattern: "/notes/[slug]", resourcePath: "pages/notes/[slug].astro" },
48
- { pattern: "/objectives", resourcePath: "pages/objectives/index.astro" },
49
- { pattern: "/objectives/[slug]", resourcePath: "pages/objectives/[slug].astro" },
50
- { pattern: "/people", resourcePath: "pages/people/index.astro" },
51
- { pattern: "/people/[slug]", resourcePath: "pages/people/[slug].astro" },
52
- { pattern: "/questions", resourcePath: "pages/questions/index.astro" },
53
- { pattern: "/questions/[slug]", resourcePath: "pages/questions/[slug].astro" }
41
+ { pattern: "/feed.xml", resourcePath: "pages/feed.xml", model: "notes" },
42
+ { pattern: "/[slug]", resourcePath: "pages/[slug].astro", model: "pages" },
43
+ { pattern: "/agents", resourcePath: "pages/agents/index.astro", model: "agents" },
44
+ { pattern: "/agents/[slug]", resourcePath: "pages/agents/[slug].astro", model: "agents" },
45
+ { pattern: "/books", resourcePath: "pages/books/index.astro", model: "books" },
46
+ { pattern: "/books/[slug]", resourcePath: "pages/books/[slug].astro", model: "books" },
47
+ { pattern: "/notes", resourcePath: "pages/notes/index.astro", model: "notes" },
48
+ { pattern: "/notes/[slug]", resourcePath: "pages/notes/[slug].astro", model: "notes" },
49
+ { pattern: "/objectives", resourcePath: "pages/objectives/index.astro", model: "objectives" },
50
+ { pattern: "/objectives/[slug]", resourcePath: "pages/objectives/[slug].astro", model: "objectives" },
51
+ { pattern: "/people", resourcePath: "pages/people/index.astro", model: "people" },
52
+ { pattern: "/people/[slug]", resourcePath: "pages/people/[slug].astro", model: "people" },
53
+ { pattern: "/questions", resourcePath: "pages/questions/index.astro", model: "questions" },
54
+ { pattern: "/questions/[slug]", resourcePath: "pages/questions/[slug].astro", model: "questions" }
54
55
  ];
55
56
  function createTreeseedRoutesIntegration(tenantConfig, routes = []) {
56
57
  return {
@@ -58,11 +59,7 @@ function createTreeseedRoutesIntegration(tenantConfig, routes = []) {
58
59
  hooks: {
59
60
  "astro:config:setup"({ injectRoute }) {
60
61
  for (const route of routes) {
61
- if (route.pattern.startsWith("/agents") && tenantConfig.features?.agents === false) continue;
62
- if (route.pattern.startsWith("/books") && tenantConfig.features?.books === false) continue;
63
- if (route.pattern.startsWith("/notes") && tenantConfig.features?.notes === false) continue;
64
- if (route.pattern.startsWith("/objectives") && tenantConfig.features?.objectives === false) continue;
65
- if (route.pattern.startsWith("/questions") && tenantConfig.features?.questions === false) continue;
62
+ if (route.model && !isSiteRenderedModel(tenantConfig, route.model)) continue;
66
63
  injectRoute(route);
67
64
  }
68
65
  }
@@ -80,14 +77,16 @@ function resolveRouteEntry(route, siteLayers) {
80
77
  return {
81
78
  pattern: route.pattern,
82
79
  entrypoint: route.entrypoint,
83
- resourcePath: route.resourcePath
80
+ resourcePath: route.resourcePath,
81
+ model: route.model
84
82
  };
85
83
  }
86
84
  if (route.resourcePath) {
87
85
  return {
88
86
  pattern: route.pattern,
89
87
  entrypoint: resolveTreeseedPageEntrypoint(siteLayers, route.resourcePath),
90
- resourcePath: route.resourcePath
88
+ resourcePath: route.resourcePath,
89
+ model: route.model
91
90
  };
92
91
  }
93
92
  throw new Error(`Treeseed route "${route.pattern}" must define either entrypoint or resourcePath.`);
@@ -208,6 +207,8 @@ function createTreeseedSite(tenantConfig, { starlight }) {
208
207
  const deployConfig = loadTreeseedDeployConfig();
209
208
  const pluginRuntime = loadTreeseedPluginRuntime(deployConfig);
210
209
  const bookRuntime = buildTenantBookRuntime(tenantConfig, { projectRoot });
210
+ const docsRendered = isSiteRenderedModel(tenantConfig, "docs");
211
+ const booksRendered = isSiteRenderedModel(tenantConfig, "books");
211
212
  const tenantThemeCss = buildTenantThemeCss(siteConfig.site.theme);
212
213
  const siteLayers = buildTreeseedSiteLayers(pluginRuntime, {
213
214
  coreRoot: fileURLToPath(new URL(".", import.meta.url)),
@@ -230,10 +231,16 @@ function createTreeseedSite(tenantConfig, { starlight }) {
230
231
  const injectedSiteConfig = JSON.stringify(siteConfig);
231
232
  const injectedDeployConfig = JSON.stringify(deployConfig);
232
233
  const resolvedGlobalCss = resolveTreeseedStyleEntrypoint(siteLayers, "styles/global.css");
234
+ const serverRendered = deployConfig.surfaces?.web?.provider === "cloudflare" || deployConfig.providers.deploy === "cloudflare";
233
235
  return defineConfig({
234
- adapter: deployConfig.surfaces?.web?.provider === "cloudflare" || deployConfig.providers.deploy === "cloudflare" ? cloudflare() : void 0,
235
- output: deployConfig.surfaces?.web?.provider === "cloudflare" || deployConfig.providers.deploy === "cloudflare" ? "server" : "static",
236
+ adapter: serverRendered ? cloudflare({ imageService: "compile" }) : void 0,
237
+ output: serverRendered ? "server" : "static",
236
238
  site: siteConfig.site.siteUrl,
239
+ image: {
240
+ service: {
241
+ entrypoint: "astro/assets/services/sharp"
242
+ }
243
+ },
237
244
  vite: {
238
245
  define: {
239
246
  __TREESEED_TENANT_CONFIG__: injectedTenantConfig,
@@ -247,7 +254,7 @@ function createTreeseedSite(tenantConfig, { starlight }) {
247
254
  ...siteExtensions.vitePlugins
248
255
  ],
249
256
  ssr: {
250
- external: ["node:fs", "node:path", "node:url"]
257
+ external: ["node:async_hooks", "node:crypto", "node:fs", "node:module", "node:path", "node:url"]
251
258
  }
252
259
  },
253
260
  markdown: {
@@ -268,6 +275,13 @@ function createTreeseedSite(tenantConfig, { starlight }) {
268
275
  TREESEED_SMTP_FROM: envField.string({ context: "server", access: "secret", optional: true }),
269
276
  TREESEED_SMTP_REPLY_TO: envField.string({ context: "server", access: "secret", optional: true }),
270
277
  TREESEED_FORM_TOKEN_SECRET: envField.string({ context: "server", access: "secret", optional: true }),
278
+ TREESEED_EDITORIAL_PREVIEW_SECRET: envField.string({ context: "server", access: "secret", optional: true }),
279
+ TREESEED_EDITORIAL_PREVIEW_ROOT: envField.string({ context: "server", access: "secret", optional: true }),
280
+ TREESEED_EDITORIAL_PREVIEW_TTL_HOURS: envField.number({ context: "server", access: "secret", optional: true }),
281
+ TREESEED_CONTENT_BUCKET_NAME: envField.string({ context: "server", access: "secret", optional: true }),
282
+ TREESEED_CONTENT_DEFAULT_TEAM_ID: envField.string({ context: "server", access: "secret", optional: true }),
283
+ TREESEED_CONTENT_MANIFEST_KEY_TEMPLATE: envField.string({ context: "server", access: "secret", optional: true }),
284
+ TREESEED_CONTENT_PREVIEW_ROOT_TEMPLATE: envField.string({ context: "server", access: "secret", optional: true }),
271
285
  TREESEED_LOCAL_DEV_MODE: envField.enum({ values: ["cloudflare"], context: "server", access: "secret", optional: true }),
272
286
  TREESEED_FORMS_LOCAL_BYPASS_TURNSTILE: envField.boolean({ context: "server", access: "secret", optional: true }),
273
287
  TREESEED_FORMS_LOCAL_BYPASS_CLOUDFLARE_GUARDS: envField.boolean({ context: "server", access: "secret", optional: true }),
@@ -279,7 +293,9 @@ function createTreeseedSite(tenantConfig, { starlight }) {
279
293
  },
280
294
  integrations: [
281
295
  createTreeseedRoutesIntegration(tenantConfig, resolvedRoutes),
282
- starlight({
296
+ ...docsRendered ? [starlight({
297
+ prerender: !serverRendered,
298
+ pagefind: !serverRendered,
283
299
  disable404Route: true,
284
300
  expressiveCode: false,
285
301
  customCss: [resolvedGlobalCss, TENANT_THEME_VIRTUAL_ID, ...siteExtensions.customCss],
@@ -303,9 +319,9 @@ function createTreeseedSite(tenantConfig, { starlight }) {
303
319
  ThemeSelect: resolveCoreComponentEntrypoint(siteLayers, "components/docs/ThemeSelect.astro", "./components/docs/ThemeSelect.astro"),
304
320
  ...siteExtensions.starlightComponents
305
321
  },
306
- sidebar: getStarlightSidebarConfigFromRuntime(bookRuntime),
322
+ sidebar: booksRendered ? getStarlightSidebarConfigFromRuntime(bookRuntime) : [],
307
323
  routeMiddleware: [packageModuleFile("./middleware/starlightRouteData"), ...siteExtensions.routeMiddleware]
308
- }),
324
+ })] : [],
309
325
  ...siteExtensions.integrations
310
326
  ]
311
327
  });