@treeseed/sdk 0.10.24 → 0.10.25

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 (39) hide show
  1. package/dist/index.d.ts +12 -2
  2. package/dist/index.js +42 -1
  3. package/dist/market-client.d.ts +23 -0
  4. package/dist/market-client.js +30 -0
  5. package/dist/operations/providers/default.js +103 -10
  6. package/dist/operations/repository-operations.d.ts +6 -1
  7. package/dist/operations/repository-operations.js +44 -0
  8. package/dist/operations/services/config-runtime.d.ts +24 -9
  9. package/dist/operations/services/config-runtime.js +60 -12
  10. package/dist/operations/services/deploy.js +6 -1
  11. package/dist/operations/services/hub-launch.js +1 -0
  12. package/dist/operations/services/hub-provider-launch.d.ts +11 -1
  13. package/dist/operations/services/hub-provider-launch.js +81 -8
  14. package/dist/operations/services/project-host-operations.d.ts +153 -0
  15. package/dist/operations/services/project-host-operations.js +365 -0
  16. package/dist/operations/services/project-platform.d.ts +198 -193
  17. package/dist/operations/services/project-platform.js +29 -14
  18. package/dist/operations/services/railway-deploy.d.ts +3 -0
  19. package/dist/operations/services/railway-deploy.js +74 -35
  20. package/dist/operations/services/release-candidate.js +8 -2
  21. package/dist/operations/services/template-host-bindings.d.ts +68 -0
  22. package/dist/operations/services/template-host-bindings.js +400 -0
  23. package/dist/operations/services/template-registry.d.ts +22 -2
  24. package/dist/operations/services/template-registry.js +60 -3
  25. package/dist/operations/services/template-secret-sync.d.ts +97 -0
  26. package/dist/operations/services/template-secret-sync.js +292 -0
  27. package/dist/platform/environment.d.ts +3 -0
  28. package/dist/project-workflow.d.ts +7 -1
  29. package/dist/scripts/scaffold-site.js +3 -2
  30. package/dist/scripts/test-scaffold.js +2 -1
  31. package/dist/sdk-types.d.ts +87 -0
  32. package/dist/sdk-types.js +29 -0
  33. package/dist/template-catalog.js +3 -1
  34. package/dist/template-launch-requirements.d.ts +118 -0
  35. package/dist/template-launch-requirements.js +759 -0
  36. package/dist/template-launch-ui.d.ts +85 -0
  37. package/dist/template-launch-ui.js +189 -0
  38. package/dist/treeseed/template-catalog/catalog.fixture.json +330 -3
  39. package/package.json +13 -1
@@ -0,0 +1,365 @@
1
+ import {
2
+ executePlatformRepositoryOperation
3
+ } from "../repository-operations.js";
4
+ import {
5
+ ProjectLaunchSecretSyncError,
6
+ syncProjectLaunchHostBindingSecrets
7
+ } from "./template-secret-sync.js";
8
+ import {
9
+ resolveProjectLaunchHostBindings
10
+ } from "../../template-launch-requirements.js";
11
+ function requirementByKey(requirements) {
12
+ return new Map((requirements?.hosts ?? []).map((requirement) => [requirement.key, requirement]));
13
+ }
14
+ function bindingMode(binding) {
15
+ if (!binding) return null;
16
+ if (binding.managedHostKey || binding.host?.ownership === "treeseed_managed" || binding.provenance.selectedBy === "managed-default") return "treeseed_managed";
17
+ if (binding.hostId || binding.host?.id) return "team_owned";
18
+ return "none";
19
+ }
20
+ function inputFromResolved(binding) {
21
+ return {
22
+ requirementKey: binding.requirementKey,
23
+ requirementKind: binding.requirementKind,
24
+ type: binding.type,
25
+ provider: binding.provider,
26
+ hostId: binding.hostId ?? binding.host?.id ?? null,
27
+ managedHostKey: binding.managedHostKey ?? null,
28
+ mode: bindingMode(binding),
29
+ displayName: binding.displayName,
30
+ environmentScopes: binding.environmentScopes,
31
+ configValues: binding.configValues,
32
+ environmentValues: binding.environmentValues,
33
+ secretRefs: binding.secretRefs,
34
+ selectedBy: binding.provenance.selectedBy
35
+ };
36
+ }
37
+ function inventoryFromResolved(binding) {
38
+ const id = binding.host?.id ?? binding.hostId ?? binding.managedHostKey ?? null;
39
+ if (!id) return null;
40
+ return {
41
+ id,
42
+ type: binding.type,
43
+ provider: binding.provider,
44
+ ownership: binding.host?.ownership ?? (binding.managedHostKey ? "treeseed_managed" : null),
45
+ name: binding.host?.name ?? binding.displayName,
46
+ accountLabel: binding.host?.accountLabel ?? null,
47
+ organizationOrOwner: binding.host?.organizationOrOwner ?? null,
48
+ allowedEnvironments: binding.environmentScopes,
49
+ status: binding.host?.status ?? "active",
50
+ metadata: {
51
+ ...binding.host?.metadata ?? {},
52
+ hostType: binding.type
53
+ }
54
+ };
55
+ }
56
+ function mergeInventory(currentBindings, input, type) {
57
+ const records = [...input ?? []];
58
+ const seen = new Set(records.map((record) => record.id));
59
+ for (const binding of Object.values(currentBindings)) {
60
+ if (binding.type !== type) continue;
61
+ const record = inventoryFromResolved(binding);
62
+ if (record && !seen.has(record.id)) {
63
+ records.push(record);
64
+ seen.add(record.id);
65
+ }
66
+ }
67
+ return records;
68
+ }
69
+ function mergeTeamHostInventory(currentBindings, input) {
70
+ const records = [...input ?? []];
71
+ const seen = new Set(records.map((record) => record.id));
72
+ for (const binding of Object.values(currentBindings)) {
73
+ if (!["web", "email", "ai"].includes(binding.type)) continue;
74
+ const record = inventoryFromResolved(binding);
75
+ if (record && !seen.has(record.id)) {
76
+ records.push(record);
77
+ seen.add(record.id);
78
+ }
79
+ }
80
+ return records;
81
+ }
82
+ function hostRequirementInputSet(currentHostBindings, replacementHostBindings) {
83
+ const inputs = {};
84
+ for (const [key, binding] of Object.entries(currentHostBindings)) {
85
+ inputs[key] = inputFromResolved(binding);
86
+ }
87
+ for (const [key, binding] of Object.entries(replacementHostBindings ?? {})) {
88
+ inputs[key] = binding;
89
+ }
90
+ return inputs;
91
+ }
92
+ function bindingChanged(previous, next) {
93
+ return JSON.stringify({
94
+ provider: previous?.provider ?? null,
95
+ hostId: previous?.hostId ?? previous?.host?.id ?? null,
96
+ managedHostKey: previous?.managedHostKey ?? null,
97
+ mode: bindingMode(previous)
98
+ }) !== JSON.stringify({
99
+ provider: next?.provider ?? null,
100
+ hostId: next?.hostId ?? next?.host?.id ?? null,
101
+ managedHostKey: next?.managedHostKey ?? null,
102
+ mode: bindingMode(next)
103
+ });
104
+ }
105
+ function requirementDiagnostics(requirement, binding) {
106
+ const diagnostics = [];
107
+ const hostId = binding?.hostId ?? binding?.host?.id ?? binding?.managedHostKey ?? null;
108
+ if (!binding || bindingMode(binding) === "none") {
109
+ if (requirement.required) {
110
+ diagnostics.push({
111
+ code: "missing_required_host",
112
+ status: "blocked",
113
+ message: `${requirement.displayName} is required and has no selected host.`,
114
+ requirementKey: requirement.key
115
+ });
116
+ }
117
+ return diagnostics;
118
+ }
119
+ if (binding.type !== requirement.type) {
120
+ diagnostics.push({
121
+ code: "incompatible_host_type",
122
+ status: "blocked",
123
+ message: `${requirement.displayName} requires ${requirement.type} hosts, but ${binding.type} is selected.`,
124
+ requirementKey: requirement.key,
125
+ provider: binding.provider,
126
+ hostId
127
+ });
128
+ }
129
+ if (requirement.compatibleProviders?.length && !requirement.compatibleProviders.includes(binding.provider)) {
130
+ diagnostics.push({
131
+ code: "incompatible_provider",
132
+ status: "blocked",
133
+ message: `${requirement.displayName} requires ${requirement.compatibleProviders.join(", ")} provider support.`,
134
+ requirementKey: requirement.key,
135
+ provider: binding.provider,
136
+ hostId
137
+ });
138
+ }
139
+ const status = String(binding.host?.status ?? "").trim();
140
+ if (status && !["active", "ready"].includes(status)) {
141
+ diagnostics.push({
142
+ code: "host_not_ready",
143
+ status: requirement.required ? "blocked" : "warning",
144
+ message: `${requirement.displayName} host is ${status}.`,
145
+ requirementKey: requirement.key,
146
+ provider: binding.provider,
147
+ hostId
148
+ });
149
+ }
150
+ return diagnostics;
151
+ }
152
+ function worstStatus(diagnostics) {
153
+ if (diagnostics.some((diagnostic) => diagnostic.status === "blocked")) return "blocked";
154
+ if (diagnostics.some((diagnostic) => diagnostic.status === "warning")) return "warning";
155
+ return "ok";
156
+ }
157
+ function deriveProjectHostBindingsView(options) {
158
+ const bindings = options.hostBindings ?? {};
159
+ const configWrites = options.hostBindingPlans?.configWrites ?? [];
160
+ const secretItems = options.hostBindingPlans?.secretDeployment?.items ?? [];
161
+ const requirements = (options.launchRequirements?.hosts ?? []).map((requirement) => {
162
+ const binding = bindings[requirement.key];
163
+ const diagnostics2 = requirementDiagnostics(requirement, binding);
164
+ const marketHostId = binding?.hostId ?? binding?.host?.id ?? binding?.managedHostKey ?? null;
165
+ const scopedConfigWrites = configWrites.filter((write) => write.requirementKey === requirement.key);
166
+ const scopedSecretItems = secretItems.filter((item) => item.requirementKey === requirement.key);
167
+ return {
168
+ requirementKey: requirement.key,
169
+ displayName: requirement.displayName,
170
+ type: requirement.type,
171
+ required: requirement.required,
172
+ purpose: requirement.purpose,
173
+ compatibleProviders: requirement.compatibleProviders ?? [],
174
+ binding: binding ? {
175
+ provider: binding.provider,
176
+ hostId: binding.hostId ?? binding.host?.id ?? null,
177
+ managedHostKey: binding.managedHostKey ?? null,
178
+ mode: bindingMode(binding),
179
+ displayName: binding.displayName,
180
+ ownership: binding.host?.ownership ?? null,
181
+ status: binding.host?.status ?? null,
182
+ environmentScopes: binding.environmentScopes,
183
+ selectedBy: binding.provenance.selectedBy,
184
+ selectedAt: binding.provenance.selectedAt
185
+ } : null,
186
+ configWrites: scopedConfigWrites.map((write) => ({
187
+ target: write.target,
188
+ path: write.path,
189
+ valueFrom: write.valueFrom,
190
+ provider: write.provider ?? binding?.provider ?? null
191
+ })),
192
+ secretTargets: scopedSecretItems.map((item) => ({
193
+ env: item.env,
194
+ targets: item.targets,
195
+ scopes: item.scopes,
196
+ sensitivity: item.sensitivity,
197
+ provider: binding?.provider ?? null
198
+ })),
199
+ audit: {
200
+ status: worstStatus(diagnostics2),
201
+ diagnostics: diagnostics2,
202
+ marketHostId,
203
+ repositoryConfig: scopedConfigWrites.length > 0 ? "planned" : "not_declared"
204
+ }
205
+ };
206
+ });
207
+ const diagnostics = requirements.flatMap((requirement) => requirement.audit.diagnostics);
208
+ return {
209
+ requirements,
210
+ summary: {
211
+ status: worstStatus(diagnostics),
212
+ total: requirements.length,
213
+ blocked: diagnostics.filter((diagnostic) => diagnostic.status === "blocked").length,
214
+ warnings: diagnostics.filter((diagnostic) => diagnostic.status === "warning").length
215
+ },
216
+ diagnostics
217
+ };
218
+ }
219
+ function planProjectHostBindingOperation(options) {
220
+ const current = options.currentHostBindings ?? {};
221
+ const replacements = options.replacementHostBindings ?? {};
222
+ const requirementsByKey = requirementByKey(options.launchRequirements);
223
+ const requirementKey = options.requirementKey ?? Object.keys(replacements)[0] ?? null;
224
+ if (requirementKey && !requirementsByKey.has(requirementKey)) {
225
+ throw new Error(`Unknown launch host requirement "${requirementKey}".`);
226
+ }
227
+ const inputs = hostRequirementInputSet(current, replacements);
228
+ const resolved = resolveProjectLaunchHostBindings({
229
+ hostBindings: inputs,
230
+ launchRequirements: options.launchRequirements,
231
+ repositoryHosts: mergeInventory(current, options.repositoryHosts, "repository"),
232
+ teamHosts: mergeTeamHostInventory(current, options.teamHosts),
233
+ managedHosts: options.managedHosts,
234
+ defaultHosts: options.defaultHosts,
235
+ projectSlug: options.projectSlug,
236
+ projectName: options.projectName,
237
+ standardProjectLaunch: true,
238
+ selectedAt: options.selectedAt
239
+ });
240
+ const changedRequirementKeys = [.../* @__PURE__ */ new Set([
241
+ ...Object.keys(current),
242
+ ...Object.keys(resolved.hostBindings)
243
+ ])].filter((key) => bindingChanged(current[key], resolved.hostBindings[key]));
244
+ const hostBindingPlans = {
245
+ configWrites: resolved.configWritePlan,
246
+ secretDeployment: resolved.secretDeploymentPlan
247
+ };
248
+ const audit = deriveProjectHostBindingsView({
249
+ launchRequirements: options.launchRequirements,
250
+ hostBindings: resolved.hostBindings,
251
+ hostBindingPlans
252
+ });
253
+ const scopedKeys = requirementKey ? [requirementKey] : changedRequirementKeys;
254
+ return {
255
+ kind: options.kind,
256
+ requirementKey,
257
+ previousHostBindings: current,
258
+ nextHostBindings: resolved.hostBindings,
259
+ compatibility: resolved.compatibility,
260
+ hostBindingPlans,
261
+ audit,
262
+ operationSummary: {
263
+ requiresRepositoryConfigWrite: resolved.configWritePlan.some((write) => scopedKeys.length === 0 || scopedKeys.includes(write.requirementKey)),
264
+ requiresSecretSync: (resolved.secretDeploymentPlan.items ?? []).some((item) => scopedKeys.length === 0 || scopedKeys.includes(item.requirementKey)),
265
+ changedRequirementKeys
266
+ }
267
+ };
268
+ }
269
+ function scopedRequirementKeys(input) {
270
+ if (input.requirementKey) return [input.requirementKey];
271
+ if (input.kind === "replace") return input.operationSummary?.changedRequirementKeys ?? [];
272
+ return [];
273
+ }
274
+ function scopedPlans(input) {
275
+ const keys = scopedRequirementKeys(input);
276
+ if (keys.length === 0) return input.hostBindingPlans;
277
+ const keySet = new Set(keys);
278
+ return {
279
+ configWrites: input.hostBindingPlans.configWrites.filter((write) => keySet.has(write.requirementKey)),
280
+ secretDeployment: {
281
+ items: (input.hostBindingPlans.secretDeployment.items ?? []).filter((item) => keySet.has(item.requirementKey))
282
+ }
283
+ };
284
+ }
285
+ function repositorySlug(result) {
286
+ return result.repository.owner ? `${result.repository.owner}/${result.repository.name}` : result.repository.name;
287
+ }
288
+ async function executeProjectHostBindingOperation(input, context) {
289
+ const plans = scopedPlans(input);
290
+ const requiresRepositoryConfigWrite = input.kind === "replace" && plans.configWrites.length > 0;
291
+ const repositoryOperation = requiresRepositoryConfigWrite ? "apply_host_binding_config" : "audit_host_binding_config";
292
+ const repositoryResult = await executePlatformRepositoryOperation(repositoryOperation, {
293
+ projectId: input.projectId ?? void 0,
294
+ teamId: input.teamId ?? void 0,
295
+ repository: input.repository,
296
+ hostBindings: input.hostBindings,
297
+ hostBindingPlans: plans,
298
+ launchInput: {
299
+ projectSlug: input.projectSlug ?? null,
300
+ projectName: input.projectName ?? null,
301
+ repoName: input.repositoryName ?? input.projectSlug ?? null
302
+ },
303
+ derived: {
304
+ projectSlug: input.projectSlug ?? null,
305
+ projectName: input.projectName ?? null,
306
+ repositoryName: input.repositoryName ?? input.projectSlug ?? null
307
+ },
308
+ commitMessage: input.commitMessage ?? void 0,
309
+ approvalRequired: input.approvalRequired,
310
+ approvalId: input.approvalId ?? void 0
311
+ }, {
312
+ workspaceRoot: context.workspaceRoot,
313
+ environment: context.environment
314
+ });
315
+ let secretSync = null;
316
+ const requiresSecretSync = ["replace", "resync", "rotate"].includes(input.kind) && (plans.secretDeployment.items ?? []).length > 0;
317
+ if (requiresSecretSync) {
318
+ try {
319
+ secretSync = await syncProjectLaunchHostBindingSecrets({
320
+ projectRoot: repositoryResult.repositoryPath,
321
+ repository: repositorySlug(repositoryResult),
322
+ hostBindings: input.hostBindings,
323
+ secretDeploymentPlan: plans.secretDeployment,
324
+ valuesOverlay: context.valuesOverlay,
325
+ valuesByScope: context.valuesByScope,
326
+ processEnv: context.processEnv,
327
+ dryRun: input.dryRun,
328
+ onProgress: context.onProgress
329
+ });
330
+ } catch (error) {
331
+ if (error instanceof ProjectLaunchSecretSyncError) {
332
+ secretSync = error.result;
333
+ } else {
334
+ throw error;
335
+ }
336
+ }
337
+ }
338
+ return {
339
+ ok: secretSync ? secretSync.ok : true,
340
+ kind: input.kind,
341
+ requirementKey: input.requirementKey ?? null,
342
+ hostBindings: input.hostBindings,
343
+ previousHostBindings: input.previousHostBindings ?? {},
344
+ hostBindingPlans: plans,
345
+ repository: {
346
+ operation: repositoryOperation,
347
+ branch: repositoryResult.operationBranch ?? repositoryResult.branch,
348
+ commitSha: repositoryResult.commitSha,
349
+ changedPaths: repositoryResult.changedPaths,
350
+ audit: repositoryResult.output.hostBindingAudit ?? null,
351
+ config: repositoryResult.output.hostBindingConfig ?? null
352
+ },
353
+ secretSync,
354
+ summary: {
355
+ requiresRepositoryConfigWrite,
356
+ requiresSecretSync,
357
+ changedRequirementKeys: input.operationSummary?.changedRequirementKeys ?? []
358
+ }
359
+ };
360
+ }
361
+ export {
362
+ deriveProjectHostBindingsView,
363
+ executeProjectHostBindingOperation,
364
+ planProjectHostBindingOperation
365
+ };