@treeseed/sdk 0.5.3 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.js +46 -0
  3. package/dist/operations/providers/default.js +1 -1
  4. package/dist/operations/services/config-runtime.d.ts +49 -42
  5. package/dist/operations/services/config-runtime.js +449 -136
  6. package/dist/operations/services/deploy.d.ts +298 -0
  7. package/dist/operations/services/deploy.js +381 -137
  8. package/dist/operations/services/git-workflow.d.ts +9 -0
  9. package/dist/operations/services/git-workflow.js +32 -0
  10. package/dist/operations/services/github-api.d.ts +115 -0
  11. package/dist/operations/services/github-api.js +455 -0
  12. package/dist/operations/services/github-automation.d.ts +19 -33
  13. package/dist/operations/services/github-automation.js +44 -131
  14. package/dist/operations/services/key-agent.d.ts +20 -1
  15. package/dist/operations/services/key-agent.js +267 -102
  16. package/dist/operations/services/knowledge-coop-launch.d.ts +2 -3
  17. package/dist/operations/services/knowledge-coop-launch.js +26 -12
  18. package/dist/operations/services/project-platform.d.ts +157 -150
  19. package/dist/operations/services/project-platform.js +129 -26
  20. package/dist/operations/services/railway-api.d.ts +244 -0
  21. package/dist/operations/services/railway-api.js +882 -0
  22. package/dist/operations/services/railway-deploy.d.ts +171 -27
  23. package/dist/operations/services/railway-deploy.js +672 -172
  24. package/dist/operations/services/runtime-tools.d.ts +18 -0
  25. package/dist/operations/services/runtime-tools.js +19 -6
  26. package/dist/operations/services/workspace-preflight.js +2 -2
  27. package/dist/platform/contracts.d.ts +7 -0
  28. package/dist/platform/deploy-config.js +23 -0
  29. package/dist/platform/deploy-runtime.d.ts +1 -0
  30. package/dist/platform/deploy-runtime.js +7 -9
  31. package/dist/platform/env.yaml +10 -9
  32. package/dist/platform/environment.js +4 -0
  33. package/dist/platform/plugin.d.ts +6 -0
  34. package/dist/platform/plugins/constants.d.ts +1 -0
  35. package/dist/platform/plugins/constants.js +1 -0
  36. package/dist/platform/plugins/runtime.d.ts +4 -0
  37. package/dist/platform/plugins/runtime.js +8 -1
  38. package/dist/platform/published-content.js +27 -4
  39. package/dist/platform/tenant/runtime-config.js +33 -24
  40. package/dist/plugin-default.d.ts +1 -0
  41. package/dist/plugin-default.js +1 -0
  42. package/dist/reconcile/builtin-adapters.d.ts +3 -0
  43. package/dist/reconcile/builtin-adapters.js +2093 -0
  44. package/dist/reconcile/contracts.d.ts +155 -0
  45. package/dist/reconcile/contracts.js +0 -0
  46. package/dist/reconcile/desired-state.d.ts +179 -0
  47. package/dist/reconcile/desired-state.js +319 -0
  48. package/dist/reconcile/engine.d.ts +405 -0
  49. package/dist/reconcile/engine.js +356 -0
  50. package/dist/reconcile/errors.d.ts +5 -0
  51. package/dist/reconcile/errors.js +13 -0
  52. package/dist/reconcile/index.d.ts +7 -0
  53. package/dist/reconcile/index.js +7 -0
  54. package/dist/reconcile/registry.d.ts +7 -0
  55. package/dist/reconcile/registry.js +64 -0
  56. package/dist/reconcile/state.d.ts +7 -0
  57. package/dist/reconcile/state.js +303 -0
  58. package/dist/reconcile/units.d.ts +6 -0
  59. package/dist/reconcile/units.js +68 -0
  60. package/dist/scripts/config-treeseed.js +27 -19
  61. package/dist/scripts/tenant-deploy.js +35 -14
  62. package/dist/workflow/operations.js +127 -22
  63. package/dist/workflow-support.d.ts +3 -1
  64. package/dist/workflow-support.js +50 -0
  65. package/dist/workflow.d.ts +2 -0
  66. package/package.json +7 -1
@@ -0,0 +1,155 @@
1
+ import type { TreeseedDeployConfig } from '../platform/contracts.ts';
2
+ export type TreeseedReconcileProviderId = string;
3
+ export type TreeseedReconcileActionKind = 'noop' | 'create' | 'update' | 'reuse' | 'drift_correct' | 'destroy';
4
+ export type TreeseedReconcileStatusKind = 'pending' | 'ready' | 'drifted' | 'error';
5
+ export type TreeseedReconcileVerificationSource = 'cli' | 'api' | 'sdk' | 'derived';
6
+ export type TreeseedReconcileUnitType = 'web-ui' | 'api-runtime' | 'manager-runtime' | 'worker-runtime' | 'workday-start-runtime' | 'workday-report-runtime' | 'edge-worker' | 'content-store' | 'queue' | 'database' | 'kv-form-guard' | 'kv-session' | 'pages-project' | 'custom-domain:web' | 'custom-domain:api' | 'dns-record' | 'railway-service:api' | 'railway-service:manager' | 'railway-service:worker' | 'railway-service:workday-start' | 'railway-service:workday-report';
7
+ export type TreeseedReconcileTarget = {
8
+ kind: 'persistent';
9
+ scope: 'local' | 'staging' | 'prod';
10
+ } | {
11
+ kind: 'branch';
12
+ branchName: string;
13
+ };
14
+ export type TreeseedReconcileUnitId = string;
15
+ export interface TreeseedReconcileIdentity {
16
+ teamId: string;
17
+ projectId: string;
18
+ slug: string;
19
+ environment: string;
20
+ deploymentKey: string;
21
+ environmentKey: string;
22
+ }
23
+ export interface TreeseedDesiredUnit {
24
+ unitId: TreeseedReconcileUnitId;
25
+ unitType: TreeseedReconcileUnitType;
26
+ provider: TreeseedReconcileProviderId;
27
+ identity: TreeseedReconcileIdentity;
28
+ target: TreeseedReconcileTarget;
29
+ logicalName: string;
30
+ dependencies: TreeseedReconcileUnitId[];
31
+ spec: Record<string, unknown>;
32
+ secrets: Record<string, string | null | undefined>;
33
+ metadata: Record<string, unknown>;
34
+ }
35
+ export interface TreeseedObservedUnitState {
36
+ exists: boolean;
37
+ status: TreeseedReconcileStatusKind;
38
+ live: Record<string, unknown>;
39
+ locators: Record<string, string | null>;
40
+ warnings: string[];
41
+ }
42
+ export interface TreeseedUnitDiff {
43
+ action: TreeseedReconcileActionKind;
44
+ reasons: string[];
45
+ before: Record<string, unknown>;
46
+ after: Record<string, unknown>;
47
+ }
48
+ export interface TreeseedUnitPersistedState {
49
+ unitId: TreeseedReconcileUnitId;
50
+ unitType: TreeseedReconcileUnitType;
51
+ provider: TreeseedReconcileProviderId;
52
+ identity: TreeseedReconcileIdentity;
53
+ target: TreeseedReconcileTarget;
54
+ logicalName: string;
55
+ desiredSpecHash: string;
56
+ lastObservedAt: string | null;
57
+ lastReconciledAt: string | null;
58
+ lastVerifiedAt: string | null;
59
+ lastStatus: TreeseedReconcileStatusKind;
60
+ lastObservedState: Record<string, unknown>;
61
+ lastReconciledState: Record<string, unknown>;
62
+ lastDiff: TreeseedUnitDiff | null;
63
+ lastVerification: TreeseedUnitVerificationResult | null;
64
+ lastAction: TreeseedReconcileActionKind | null;
65
+ resourceLocators: Record<string, string | null>;
66
+ warnings: string[];
67
+ error: string | null;
68
+ }
69
+ export interface TreeseedUnitPostcondition {
70
+ key: string;
71
+ description: string;
72
+ }
73
+ export interface TreeseedUnitVerificationCheck {
74
+ key: string;
75
+ description: string;
76
+ source: TreeseedReconcileVerificationSource;
77
+ exists: boolean;
78
+ configured: boolean;
79
+ ready: boolean;
80
+ verified: boolean;
81
+ expected?: unknown;
82
+ observed?: unknown;
83
+ issues: string[];
84
+ }
85
+ export interface TreeseedUnitVerificationResult {
86
+ unitId: TreeseedReconcileUnitId;
87
+ supported: boolean;
88
+ exists: boolean;
89
+ configured: boolean;
90
+ ready: boolean;
91
+ verified: boolean;
92
+ checks: TreeseedUnitVerificationCheck[];
93
+ missing: string[];
94
+ drifted: string[];
95
+ warnings: string[];
96
+ }
97
+ export interface TreeseedReconcilePlan {
98
+ unit: TreeseedDesiredUnit;
99
+ observed: TreeseedObservedUnitState;
100
+ diff: TreeseedUnitDiff;
101
+ persisted: TreeseedUnitPersistedState | null;
102
+ }
103
+ export interface TreeseedReconcileResult {
104
+ unit: TreeseedDesiredUnit;
105
+ observed: TreeseedObservedUnitState;
106
+ diff: TreeseedUnitDiff;
107
+ action: TreeseedReconcileActionKind;
108
+ warnings: string[];
109
+ resourceLocators: Record<string, string | null>;
110
+ state: Record<string, unknown>;
111
+ verification: TreeseedUnitVerificationResult | null;
112
+ }
113
+ export interface TreeseedReconcileRunContext {
114
+ tenantRoot: string;
115
+ target: TreeseedReconcileTarget;
116
+ deployConfig: TreeseedDeployConfig;
117
+ launchEnv: NodeJS.ProcessEnv;
118
+ write?: (line: string) => void;
119
+ session: Map<string, unknown>;
120
+ }
121
+ export interface TreeseedReconcileAdapterInput {
122
+ context: TreeseedReconcileRunContext;
123
+ unit: TreeseedDesiredUnit;
124
+ persistedState: TreeseedUnitPersistedState | null;
125
+ }
126
+ export interface TreeseedReconcileAdapter {
127
+ providerId: TreeseedReconcileProviderId;
128
+ unitTypes: TreeseedReconcileUnitType[];
129
+ supports(unitType: TreeseedReconcileUnitType, providerId: TreeseedReconcileProviderId): boolean;
130
+ validate?(input: TreeseedReconcileAdapterInput): Promise<void> | void;
131
+ requiredPostconditions?(input: TreeseedReconcileAdapterInput): Promise<TreeseedUnitPostcondition[]> | TreeseedUnitPostcondition[];
132
+ observe(input: TreeseedReconcileAdapterInput): Promise<TreeseedObservedUnitState> | TreeseedObservedUnitState;
133
+ plan(input: TreeseedReconcileAdapterInput & {
134
+ observed: TreeseedObservedUnitState;
135
+ }): Promise<TreeseedUnitDiff> | TreeseedUnitDiff;
136
+ reconcile(input: TreeseedReconcileAdapterInput & {
137
+ observed: TreeseedObservedUnitState;
138
+ diff: TreeseedUnitDiff;
139
+ }): Promise<TreeseedReconcileResult> | TreeseedReconcileResult;
140
+ verify(input: TreeseedReconcileAdapterInput & {
141
+ observed: TreeseedObservedUnitState;
142
+ diff: TreeseedUnitDiff;
143
+ result: TreeseedReconcileResult | null;
144
+ postconditions: TreeseedUnitPostcondition[];
145
+ }): Promise<TreeseedUnitVerificationResult> | TreeseedUnitVerificationResult;
146
+ destroy?(input: TreeseedReconcileAdapterInput & {
147
+ observed: TreeseedObservedUnitState;
148
+ }): Promise<TreeseedReconcileResult> | TreeseedReconcileResult;
149
+ }
150
+ export interface TreeseedReconcileStateRecord {
151
+ version: 1;
152
+ target: TreeseedReconcileTarget;
153
+ dependencyGraphVersion: number;
154
+ units: Record<TreeseedReconcileUnitId, TreeseedUnitPersistedState>;
155
+ }
File without changes
@@ -0,0 +1,179 @@
1
+ import type { TreeseedDesiredUnit, TreeseedReconcileTarget } from './contracts.ts';
2
+ export declare function deriveTreeseedDesiredUnits({ tenantRoot, target, }: {
3
+ tenantRoot: string;
4
+ target: TreeseedReconcileTarget;
5
+ }): {
6
+ deployConfig: {
7
+ name: string;
8
+ slug: string;
9
+ siteUrl: string;
10
+ contactEmail: string;
11
+ hosting: {
12
+ kind: string;
13
+ registration: any;
14
+ marketBaseUrl: any;
15
+ teamId: any;
16
+ projectId: any;
17
+ };
18
+ hub: {
19
+ mode: string;
20
+ };
21
+ runtime: {
22
+ mode: string;
23
+ registration: any;
24
+ marketBaseUrl: any;
25
+ teamId: any;
26
+ projectId: any;
27
+ };
28
+ cloudflare: {
29
+ accountId: string;
30
+ zoneId: string | undefined;
31
+ workerName: string | undefined;
32
+ queueName: string | undefined;
33
+ dlqName: string | undefined;
34
+ d1Binding: string | undefined;
35
+ queueBinding: string | undefined;
36
+ pages: {
37
+ projectName: string | undefined;
38
+ previewProjectName: string | undefined;
39
+ productionBranch: string;
40
+ stagingBranch: string;
41
+ buildOutputDir: string | undefined;
42
+ } | undefined;
43
+ r2: {
44
+ binding: string | undefined;
45
+ bucketName: string | undefined;
46
+ publicBaseUrl: string | undefined;
47
+ manifestKeyTemplate: string;
48
+ previewRootTemplate: string;
49
+ previewTtlHours: number;
50
+ } | undefined;
51
+ };
52
+ plugins: {
53
+ package: string;
54
+ enabled: boolean;
55
+ }[] | {
56
+ package: string;
57
+ enabled: boolean | undefined;
58
+ config: any;
59
+ }[];
60
+ providers: {
61
+ forms: string;
62
+ agents: {
63
+ execution: string;
64
+ mutation: string;
65
+ repository: string;
66
+ verification: string;
67
+ notification: string;
68
+ research: string;
69
+ };
70
+ deploy: string;
71
+ dns: string;
72
+ content: {
73
+ runtime: string;
74
+ publish: string;
75
+ docs: string;
76
+ serving: any;
77
+ };
78
+ site: string;
79
+ };
80
+ surfaces: {
81
+ [k: string]: {
82
+ enabled: boolean | undefined;
83
+ provider: string | undefined;
84
+ rootDir: string | undefined;
85
+ publicBaseUrl: string | undefined;
86
+ localBaseUrl: string | undefined;
87
+ environments: {
88
+ local: {
89
+ baseUrl: string | undefined;
90
+ domain: string | undefined;
91
+ railwayEnvironment: string | undefined;
92
+ };
93
+ staging: {
94
+ baseUrl: string | undefined;
95
+ domain: string | undefined;
96
+ railwayEnvironment: string | undefined;
97
+ };
98
+ prod: {
99
+ baseUrl: string | undefined;
100
+ domain: string | undefined;
101
+ railwayEnvironment: string | undefined;
102
+ };
103
+ } | undefined;
104
+ cache: {
105
+ sourcePages: {
106
+ browserTtlSeconds: number | undefined;
107
+ edgeTtlSeconds: number | undefined;
108
+ staleWhileRevalidateSeconds: number | undefined;
109
+ staleIfErrorSeconds: number | undefined;
110
+ } | {
111
+ paths: any[];
112
+ } | undefined;
113
+ contentPages: {
114
+ browserTtlSeconds: number | undefined;
115
+ edgeTtlSeconds: number | undefined;
116
+ staleWhileRevalidateSeconds: number | undefined;
117
+ staleIfErrorSeconds: number | undefined;
118
+ } | {
119
+ paths: any[];
120
+ } | undefined;
121
+ r2PublishedObjects: {
122
+ browserTtlSeconds: number | undefined;
123
+ edgeTtlSeconds: number | undefined;
124
+ staleWhileRevalidateSeconds: number | undefined;
125
+ staleIfErrorSeconds: number | undefined;
126
+ } | {
127
+ paths: any[];
128
+ } | undefined;
129
+ } | undefined;
130
+ } | undefined;
131
+ } | undefined;
132
+ services: {
133
+ [k: string]: {
134
+ enabled: boolean | undefined;
135
+ provider: string | undefined;
136
+ rootDir: string | undefined;
137
+ publicBaseUrl: string | undefined;
138
+ cloudflare: {
139
+ workerName: string | undefined;
140
+ };
141
+ railway: {
142
+ projectId: string | undefined;
143
+ projectName: string | undefined;
144
+ serviceId: string | undefined;
145
+ serviceName: string | undefined;
146
+ rootDir: string | undefined;
147
+ buildCommand: string | undefined;
148
+ startCommand: string | undefined;
149
+ schedule: any;
150
+ };
151
+ environments: {
152
+ local: {
153
+ baseUrl: string | undefined;
154
+ domain: string | undefined;
155
+ railwayEnvironment: string | undefined;
156
+ };
157
+ staging: {
158
+ baseUrl: string | undefined;
159
+ domain: string | undefined;
160
+ railwayEnvironment: string | undefined;
161
+ };
162
+ prod: {
163
+ baseUrl: string | undefined;
164
+ domain: string | undefined;
165
+ railwayEnvironment: string | undefined;
166
+ };
167
+ };
168
+ } | undefined;
169
+ } | undefined;
170
+ smtp: {
171
+ enabled: boolean | undefined;
172
+ };
173
+ turnstile: {
174
+ enabled: boolean;
175
+ };
176
+ };
177
+ legacyState: any;
178
+ units: TreeseedDesiredUnit[];
179
+ };
@@ -0,0 +1,319 @@
1
+ import { loadCliDeployConfig } from "../operations/services/runtime-tools.js";
2
+ import {
3
+ loadDeployState,
4
+ resolveConfiguredSurfaceDomain,
5
+ resolveTreeseedResourceIdentity
6
+ } from "../operations/services/deploy.js";
7
+ import { configuredRailwayServices } from "../operations/services/railway-deploy.js";
8
+ import { normalizeRailwayEnvironmentName } from "../operations/services/railway-api.js";
9
+ import { createTreeseedReconcileUnitId } from "./units.js";
10
+ function railwayConcreteUnitTypeForServiceKey(serviceKey) {
11
+ switch (serviceKey) {
12
+ case "api":
13
+ return "railway-service:api";
14
+ case "manager":
15
+ return "railway-service:manager";
16
+ case "worker":
17
+ return "railway-service:worker";
18
+ case "workdayStart":
19
+ return "railway-service:workday-start";
20
+ case "workdayReport":
21
+ return "railway-service:workday-report";
22
+ default:
23
+ return "railway-service:api";
24
+ }
25
+ }
26
+ function deriveTreeseedDesiredUnits({
27
+ tenantRoot,
28
+ target
29
+ }) {
30
+ const deployConfig = loadCliDeployConfig(tenantRoot);
31
+ const legacyState = loadDeployState(tenantRoot, deployConfig, { target });
32
+ const identity = legacyState.identity ?? resolveTreeseedResourceIdentity(deployConfig, target);
33
+ const units = [];
34
+ const add = (unit) => {
35
+ units.push(unit);
36
+ return unit.unitId;
37
+ };
38
+ const queueId = add({
39
+ unitId: createTreeseedReconcileUnitId("queue", legacyState.queues.agentWork.name),
40
+ unitType: "queue",
41
+ provider: "cloudflare",
42
+ identity,
43
+ target,
44
+ logicalName: legacyState.queues.agentWork.name,
45
+ dependencies: [],
46
+ spec: {
47
+ name: legacyState.queues.agentWork.name,
48
+ dlqName: legacyState.queues.agentWork.dlqName,
49
+ binding: legacyState.queues.agentWork.binding
50
+ },
51
+ secrets: {},
52
+ metadata: {}
53
+ });
54
+ const databaseId = add({
55
+ unitId: createTreeseedReconcileUnitId("database", legacyState.d1Databases.SITE_DATA_DB.databaseName),
56
+ unitType: "database",
57
+ provider: "cloudflare",
58
+ identity,
59
+ target,
60
+ logicalName: legacyState.d1Databases.SITE_DATA_DB.databaseName,
61
+ dependencies: [],
62
+ spec: {
63
+ databaseName: legacyState.d1Databases.SITE_DATA_DB.databaseName,
64
+ binding: "SITE_DATA_DB"
65
+ },
66
+ secrets: {},
67
+ metadata: {}
68
+ });
69
+ const formGuardKvId = add({
70
+ unitId: createTreeseedReconcileUnitId("kv-form-guard", legacyState.kvNamespaces.FORM_GUARD_KV.name),
71
+ unitType: "kv-form-guard",
72
+ provider: "cloudflare",
73
+ identity,
74
+ target,
75
+ logicalName: legacyState.kvNamespaces.FORM_GUARD_KV.name,
76
+ dependencies: [],
77
+ spec: { binding: "FORM_GUARD_KV", name: legacyState.kvNamespaces.FORM_GUARD_KV.name },
78
+ secrets: {},
79
+ metadata: {}
80
+ });
81
+ const sessionKvId = add({
82
+ unitId: createTreeseedReconcileUnitId("kv-session", legacyState.kvNamespaces.SESSION.name),
83
+ unitType: "kv-session",
84
+ provider: "cloudflare",
85
+ identity,
86
+ target,
87
+ logicalName: legacyState.kvNamespaces.SESSION.name,
88
+ dependencies: [],
89
+ spec: { binding: "SESSION", name: legacyState.kvNamespaces.SESSION.name },
90
+ secrets: {},
91
+ metadata: {}
92
+ });
93
+ const contentStoreId = add({
94
+ unitId: createTreeseedReconcileUnitId("content-store", legacyState.content.bucketName ?? deployConfig.slug),
95
+ unitType: "content-store",
96
+ provider: "cloudflare",
97
+ identity,
98
+ target,
99
+ logicalName: legacyState.content.bucketName ?? deployConfig.slug,
100
+ dependencies: [],
101
+ spec: {
102
+ bucketName: legacyState.content.bucketName,
103
+ binding: legacyState.content.r2Binding,
104
+ publicBaseUrl: legacyState.content.publicBaseUrl,
105
+ manifestKeyTemplate: legacyState.content.manifestKeyTemplate,
106
+ previewRootTemplate: legacyState.content.previewRootTemplate
107
+ },
108
+ secrets: {},
109
+ metadata: { shared: true }
110
+ });
111
+ const pagesProjectId = add({
112
+ unitId: createTreeseedReconcileUnitId("pages-project", legacyState.pages.projectName),
113
+ unitType: "pages-project",
114
+ provider: "cloudflare",
115
+ identity,
116
+ target,
117
+ logicalName: legacyState.pages.projectName,
118
+ dependencies: [],
119
+ spec: {
120
+ projectName: legacyState.pages.projectName,
121
+ productionBranch: legacyState.pages.productionBranch,
122
+ stagingBranch: legacyState.pages.stagingBranch,
123
+ buildOutputDir: legacyState.pages.buildOutputDir
124
+ },
125
+ secrets: {},
126
+ metadata: {}
127
+ });
128
+ const edgeWorkerId = add({
129
+ unitId: createTreeseedReconcileUnitId("edge-worker", legacyState.workerName),
130
+ unitType: "edge-worker",
131
+ provider: "cloudflare",
132
+ identity,
133
+ target,
134
+ logicalName: legacyState.workerName,
135
+ dependencies: [queueId, databaseId, formGuardKvId, sessionKvId, contentStoreId, pagesProjectId],
136
+ spec: {
137
+ workerName: legacyState.workerName
138
+ },
139
+ secrets: {},
140
+ metadata: {}
141
+ });
142
+ if (deployConfig.surfaces?.web?.enabled !== false) {
143
+ const scope2 = target.kind === "persistent" ? target.scope : "staging";
144
+ const webDomain = target.kind === "persistent" ? resolveConfiguredSurfaceDomain(deployConfig, target, "web") : null;
145
+ const webDomainUnitId = webDomain ? add({
146
+ unitId: createTreeseedReconcileUnitId("custom-domain:web", webDomain),
147
+ unitType: "custom-domain:web",
148
+ provider: "cloudflare",
149
+ identity,
150
+ target,
151
+ logicalName: webDomain,
152
+ dependencies: [pagesProjectId],
153
+ spec: {
154
+ domain: webDomain,
155
+ projectName: legacyState.pages.projectName
156
+ },
157
+ secrets: {},
158
+ metadata: { surface: "web" }
159
+ }) : null;
160
+ const webDnsUnitId = webDomain ? add({
161
+ unitId: createTreeseedReconcileUnitId("dns-record", `web:${webDomain}`),
162
+ unitType: "dns-record",
163
+ provider: deployConfig.providers?.dns ?? "cloudflare-dns",
164
+ identity,
165
+ target,
166
+ logicalName: `web:${webDomain}`,
167
+ dependencies: webDomainUnitId ? [webDomainUnitId] : [],
168
+ spec: {
169
+ domain: webDomain,
170
+ zoneHost: webDomain,
171
+ recordName: webDomain,
172
+ recordType: "CNAME",
173
+ recordContent: scope2 === "prod" ? `${legacyState.pages.projectName}.pages.dev` : `${legacyState.pages.stagingBranch ?? "staging"}.${legacyState.pages.projectName}.pages.dev`,
174
+ proxied: true,
175
+ targetKind: "pages-project"
176
+ },
177
+ secrets: {},
178
+ metadata: { surface: "web" }
179
+ }) : null;
180
+ add({
181
+ unitId: createTreeseedReconcileUnitId("web-ui", "web-ui"),
182
+ unitType: "web-ui",
183
+ provider: "treeseed",
184
+ identity,
185
+ target,
186
+ logicalName: "web-ui",
187
+ dependencies: [
188
+ edgeWorkerId,
189
+ pagesProjectId,
190
+ contentStoreId,
191
+ ...webDomainUnitId ? [webDomainUnitId] : [],
192
+ ...webDnsUnitId ? [webDnsUnitId] : []
193
+ ],
194
+ spec: {
195
+ publicBaseUrl: deployConfig.surfaces?.web?.publicBaseUrl ?? deployConfig.siteUrl,
196
+ localBaseUrl: deployConfig.surfaces?.web?.localBaseUrl ?? null
197
+ },
198
+ secrets: {},
199
+ metadata: {}
200
+ });
201
+ }
202
+ const scope = target.kind === "persistent" ? target.scope : "staging";
203
+ for (const configuredService of configuredRailwayServices(tenantRoot, scope)) {
204
+ const serviceKey = configuredService.key;
205
+ const service = deployConfig.services?.[serviceKey];
206
+ const serviceState = legacyState.services?.[serviceKey];
207
+ if (!service || service.enabled === false || service.provider !== "railway") {
208
+ continue;
209
+ }
210
+ const concreteType = railwayConcreteUnitTypeForServiceKey(serviceKey);
211
+ const concreteId = add({
212
+ unitId: createTreeseedReconcileUnitId(concreteType, serviceState?.serviceName ?? configuredService.serviceName ?? serviceKey),
213
+ unitType: concreteType,
214
+ provider: "railway",
215
+ identity,
216
+ target,
217
+ logicalName: serviceState?.serviceName ?? configuredService.serviceName ?? serviceKey,
218
+ dependencies: [],
219
+ spec: {
220
+ projectId: serviceState?.projectId ?? configuredService.projectId,
221
+ projectName: serviceState?.projectName ?? configuredService.projectName,
222
+ serviceId: serviceState?.serviceId ?? configuredService.serviceId,
223
+ serviceName: serviceState?.serviceName ?? configuredService.serviceName,
224
+ rootDir: serviceState?.rootDir ?? configuredService.rootDir,
225
+ environment: normalizeRailwayEnvironmentName(serviceState?.environment ?? configuredService.railwayEnvironment),
226
+ buildCommand: configuredService.buildCommand,
227
+ startCommand: configuredService.startCommand,
228
+ healthcheckPath: configuredService.healthcheckPath,
229
+ healthcheckTimeoutSeconds: configuredService.healthcheckTimeoutSeconds,
230
+ healthcheckIntervalSeconds: configuredService.healthcheckIntervalSeconds,
231
+ restartPolicy: configuredService.restartPolicy,
232
+ runtimeMode: configuredService.runtimeMode,
233
+ schedule: serviceState?.schedule ?? configuredService.schedule,
234
+ publicBaseUrl: serviceState?.publicBaseUrl ?? configuredService.publicBaseUrl
235
+ },
236
+ secrets: {},
237
+ metadata: {
238
+ serviceKey,
239
+ scheduleManaged: Array.isArray(configuredService.schedule) && configuredService.schedule.length > 0,
240
+ scheduleBootstrap: false,
241
+ scheduleDeployScopes: ["prod"]
242
+ }
243
+ });
244
+ const apiDomain = serviceKey === "api" && target.kind === "persistent" ? resolveConfiguredSurfaceDomain(deployConfig, target, "api") : null;
245
+ const apiCustomDomainId = apiDomain ? add({
246
+ unitId: createTreeseedReconcileUnitId("custom-domain:api", apiDomain),
247
+ unitType: "custom-domain:api",
248
+ provider: "railway",
249
+ identity,
250
+ target,
251
+ logicalName: apiDomain,
252
+ dependencies: [concreteId],
253
+ spec: {
254
+ domain: apiDomain,
255
+ serviceName: serviceState?.serviceName ?? configuredService.serviceName,
256
+ projectName: serviceState?.projectName ?? configuredService.projectName,
257
+ environment: normalizeRailwayEnvironmentName(serviceState?.environment ?? configuredService.railwayEnvironment)
258
+ },
259
+ secrets: {},
260
+ metadata: { surface: "api", serviceKey }
261
+ }) : null;
262
+ const apiDnsUnitId = apiDomain ? add({
263
+ unitId: createTreeseedReconcileUnitId("dns-record", `api:${apiDomain}`),
264
+ unitType: "dns-record",
265
+ provider: deployConfig.providers?.dns ?? "cloudflare-dns",
266
+ identity,
267
+ target,
268
+ logicalName: `api:${apiDomain}`,
269
+ dependencies: apiCustomDomainId ? [apiCustomDomainId] : [concreteId],
270
+ spec: {
271
+ domain: apiDomain,
272
+ zoneHost: apiDomain,
273
+ targetKind: "railway-service",
274
+ serviceKey
275
+ },
276
+ secrets: {},
277
+ metadata: { surface: "api", serviceKey }
278
+ }) : null;
279
+ const runtimeUnitType = (() => {
280
+ switch (serviceKey) {
281
+ case "api":
282
+ return "api-runtime";
283
+ case "manager":
284
+ return "manager-runtime";
285
+ case "worker":
286
+ return "worker-runtime";
287
+ case "workdayStart":
288
+ return "workday-start-runtime";
289
+ case "workdayReport":
290
+ return "workday-report-runtime";
291
+ default:
292
+ return "api-runtime";
293
+ }
294
+ })();
295
+ add({
296
+ unitId: createTreeseedReconcileUnitId(runtimeUnitType, serviceKey),
297
+ unitType: runtimeUnitType,
298
+ provider: "treeseed",
299
+ identity,
300
+ target,
301
+ logicalName: serviceKey,
302
+ dependencies: [
303
+ concreteId,
304
+ ...apiCustomDomainId ? [apiCustomDomainId] : [],
305
+ ...apiDnsUnitId ? [apiDnsUnitId] : []
306
+ ],
307
+ spec: {
308
+ serviceKey,
309
+ publicBaseUrl: serviceState?.publicBaseUrl ?? null
310
+ },
311
+ secrets: {},
312
+ metadata: {}
313
+ });
314
+ }
315
+ return { deployConfig, legacyState, units };
316
+ }
317
+ export {
318
+ deriveTreeseedDesiredUnits
319
+ };