@treeseed/sdk 0.10.24 → 0.10.26

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,292 @@
1
+ import {
2
+ syncTreeseedCloudflareEnvironment,
3
+ syncTreeseedGitHubEnvironment,
4
+ syncTreeseedRailwayEnvironment
5
+ } from "./config-runtime.js";
6
+ const PROVIDER_TARGETS = {
7
+ github: ["github-secret", "github-variable"],
8
+ cloudflare: ["cloudflare-secret", "cloudflare-var"],
9
+ railway: ["railway-secret", "railway-var"]
10
+ };
11
+ const DEFAULT_SCOPES = ["staging", "prod"];
12
+ const NON_PROVIDER_TARGETS = /* @__PURE__ */ new Set(["local-runtime", "local-cloudflare", "config-file"]);
13
+ const SECRET_TOKEN_PATTERN = /(?:gh[pousr]_[A-Za-z0-9_]{20,}|sk-[A-Za-z0-9_-]{16,}|[A-Za-z0-9+/=]{32,})/gu;
14
+ class ProjectLaunchSecretSyncError extends Error {
15
+ result;
16
+ constructor(message, result) {
17
+ super(message);
18
+ this.name = "ProjectLaunchSecretSyncError";
19
+ this.result = result;
20
+ }
21
+ }
22
+ function stringValue(value) {
23
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : "";
24
+ }
25
+ function normalizeSecretTargets(targets) {
26
+ const allowed = new Set(Object.values(PROVIDER_TARGETS).flat());
27
+ return targets.map((target) => String(target ?? "").trim()).filter((target) => allowed.has(target));
28
+ }
29
+ function normalizeScopes(items, requested) {
30
+ const scopes = new Set(requested?.length ? requested : DEFAULT_SCOPES);
31
+ for (const item of items) {
32
+ for (const scope of item.scopes ?? []) {
33
+ if (scope === "staging" || scope === "prod") {
34
+ scopes.add(scope);
35
+ }
36
+ }
37
+ }
38
+ return [...scopes];
39
+ }
40
+ function valueCandidatesForSource(source) {
41
+ const value = String(source ?? "").trim();
42
+ if (!value) return [];
43
+ const [, suffix = ""] = /^(?:selectedHost|host)\.(?:secret|config|environment|env):(.+)$/u.exec(value) ?? [];
44
+ if (suffix) {
45
+ return [suffix, suffix.toUpperCase(), `TREESEED_${suffix.toUpperCase()}`];
46
+ }
47
+ const [, dotted = ""] = /^(?:selectedHost|host)\.([A-Za-z0-9_]+)$/u.exec(value) ?? [];
48
+ if (dotted) {
49
+ return [dotted, dotted.toUpperCase(), `TREESEED_${dotted.toUpperCase()}`];
50
+ }
51
+ return [];
52
+ }
53
+ function scopedOverlay(options, scope) {
54
+ return {
55
+ ...options.processEnv ?? process.env,
56
+ ...options.valuesOverlay ?? {},
57
+ ...options.valuesByScope?.[scope] ?? {}
58
+ };
59
+ }
60
+ function bindingValue(binding, key) {
61
+ if (!binding || !key) return "";
62
+ const record = binding;
63
+ for (const containerKey of ["environmentValues", "configValues", "secrets", "secretValues", "metadata"]) {
64
+ const container = record[containerKey];
65
+ if (container && typeof container === "object") {
66
+ const value = stringValue(container[key]) || stringValue(container[key.toUpperCase()]);
67
+ if (value) return value;
68
+ }
69
+ }
70
+ return stringValue(record[key]) || stringValue(record[key.toUpperCase()]);
71
+ }
72
+ function resolvePlanItemValue(item, scope, options) {
73
+ const values = scopedOverlay(options, scope);
74
+ const direct = stringValue(values[item.env]);
75
+ if (direct) return direct;
76
+ const binding = item.sourceHostId ? options.hostBindings?.[item.requirementKey] : void 0;
77
+ for (const candidate of valueCandidatesForSource(item.source)) {
78
+ const overlayValue = stringValue(values[candidate]);
79
+ if (overlayValue) return overlayValue;
80
+ const hostValue = bindingValue(binding, candidate);
81
+ if (hostValue) return hostValue;
82
+ }
83
+ return "";
84
+ }
85
+ function providerForTarget(target) {
86
+ if (target.startsWith("github-")) return "github";
87
+ if (target.startsWith("cloudflare-")) return "cloudflare";
88
+ return "railway";
89
+ }
90
+ function redactSecretSyncMessage(message) {
91
+ return String(message instanceof Error ? message.message : message ?? "Secret sync failed.").replace(SECRET_TOKEN_PATTERN, "[redacted]").replace(/(token|password|secret|key)=([^,\s]+)/giu, "$1=[redacted]");
92
+ }
93
+ function resolveProjectLaunchSecretValueOverlay(options) {
94
+ const planItems = options.secretDeploymentPlan?.items?.filter(Boolean) ?? [];
95
+ const scopes = normalizeScopes(planItems, options.scopes);
96
+ const valuesOverlay = {};
97
+ const resolvedItems = [];
98
+ const diagnostics = [];
99
+ for (const item of planItems) {
100
+ const targets = normalizeSecretTargets(item.targets ?? []);
101
+ const itemScopes = (item.scopes ?? []).filter((scope) => scope === "local" || scope === "staging" || scope === "prod");
102
+ if (targets.length === 0) {
103
+ if ((item.targets ?? []).every((target) => NON_PROVIDER_TARGETS.has(String(target)))) {
104
+ continue;
105
+ }
106
+ diagnostics.push({
107
+ code: "unsupported_target",
108
+ requirementKey: item.requirementKey,
109
+ requirementKind: item.requirementKind,
110
+ env: item.env,
111
+ source: item.source,
112
+ targets: item.targets ?? [],
113
+ scopes: itemScopes,
114
+ message: `Secret deployment target is not supported for ${item.env}.`
115
+ });
116
+ continue;
117
+ }
118
+ const activeScopes = itemScopes.filter((scope) => scopes.includes(scope));
119
+ let resolved = false;
120
+ for (const scope of activeScopes) {
121
+ const value = resolvePlanItemValue(item, scope, options);
122
+ if (value) {
123
+ valuesOverlay[item.env] = value;
124
+ resolved = true;
125
+ break;
126
+ }
127
+ }
128
+ if (!resolved) {
129
+ diagnostics.push({
130
+ code: "missing_value",
131
+ requirementKey: item.requirementKey,
132
+ requirementKind: item.requirementKind,
133
+ env: item.env,
134
+ source: item.source,
135
+ targets,
136
+ scopes: activeScopes,
137
+ message: `No launch secret value was available for ${item.env}.`
138
+ });
139
+ }
140
+ resolvedItems.push({
141
+ requirementKey: item.requirementKey,
142
+ requirementKind: item.requirementKind,
143
+ env: item.env,
144
+ source: item.source,
145
+ targets,
146
+ scopes: activeScopes,
147
+ sensitivity: item.sensitivity,
148
+ sourceHostId: item.sourceHostId ?? null,
149
+ resolved
150
+ });
151
+ }
152
+ return { valuesOverlay, items: resolvedItems, diagnostics };
153
+ }
154
+ function itemsForProviderScope(items, provider, scope) {
155
+ const targets = new Set(PROVIDER_TARGETS[provider]);
156
+ return items.filter((item) => item.resolved && item.scopes.includes(scope) && item.targets.some((target) => targets.has(target)));
157
+ }
158
+ function summaryItemsFor(items, provider, scope, status, error) {
159
+ const targets = new Set(PROVIDER_TARGETS[provider]);
160
+ return items.flatMap((item) => item.targets.filter((target) => targets.has(target)).map((target) => ({
161
+ provider,
162
+ scope,
163
+ target,
164
+ env: item.env,
165
+ requirementKey: item.requirementKey,
166
+ requirementKind: item.requirementKind,
167
+ sensitivity: item.sensitivity,
168
+ status,
169
+ ...error ? { error: { message: redactSecretSyncMessage(error) } } : {}
170
+ })));
171
+ }
172
+ async function syncProjectLaunchHostBindingSecrets(options) {
173
+ const planItems = options.secretDeploymentPlan?.items?.filter(Boolean) ?? [];
174
+ const scopes = normalizeScopes(planItems, options.scopes);
175
+ const providers = options.providers?.length ? options.providers : Object.keys(PROVIDER_TARGETS);
176
+ const overlay = resolveProjectLaunchSecretValueOverlay({ ...options, scopes });
177
+ const relevantDiagnostics = overlay.diagnostics.filter((diagnostic) => diagnostic.scopes.some((scope) => scopes.includes(scope)) && diagnostic.targets.some((target) => providers.includes(providerForTarget(target))));
178
+ const result = {
179
+ ok: relevantDiagnostics.length === 0,
180
+ items: [],
181
+ providers: [],
182
+ diagnostics: overlay.diagnostics
183
+ };
184
+ if (relevantDiagnostics.length > 0) {
185
+ for (const diagnostic of relevantDiagnostics) {
186
+ for (const target of normalizeSecretTargets(diagnostic.targets)) {
187
+ const provider = providerForTarget(target);
188
+ for (const scope of diagnostic.scopes.filter((entry) => scopes.includes(entry))) {
189
+ result.items.push({
190
+ provider,
191
+ scope,
192
+ target,
193
+ env: diagnostic.env,
194
+ requirementKey: diagnostic.requirementKey,
195
+ requirementKind: diagnostic.requirementKind,
196
+ sensitivity: "secret",
197
+ status: "failed",
198
+ error: { message: diagnostic.message }
199
+ });
200
+ }
201
+ }
202
+ }
203
+ throw new ProjectLaunchSecretSyncError("Host-bound secret sync could not resolve every required value.", result);
204
+ }
205
+ const adapters = {
206
+ github: options.adapters?.github ?? syncTreeseedGitHubEnvironment,
207
+ cloudflare: options.adapters?.cloudflare ?? syncTreeseedCloudflareEnvironment,
208
+ railway: options.adapters?.railway ?? syncTreeseedRailwayEnvironment
209
+ };
210
+ for (const provider of providers) {
211
+ for (const scope of scopes) {
212
+ if (scope === "local") continue;
213
+ const scopedItems = itemsForProviderScope(overlay.items, provider, scope);
214
+ if (scopedItems.length === 0) continue;
215
+ const entryIds = [...new Set(scopedItems.map((item) => item.env))];
216
+ await options.onProgress?.({
217
+ provider,
218
+ scope,
219
+ entryIds,
220
+ status: "running",
221
+ message: `Syncing ${entryIds.length} host-bound ${provider} entr${entryIds.length === 1 ? "y" : "ies"} for ${scope}.`
222
+ });
223
+ try {
224
+ const valuesOverlay = {
225
+ ...overlay.valuesOverlay,
226
+ ...options.valuesByScope?.[scope] ?? {}
227
+ };
228
+ if (provider === "github") {
229
+ await adapters.github({
230
+ tenantRoot: options.projectRoot,
231
+ scope,
232
+ dryRun: options.dryRun,
233
+ repository: options.repository ?? null,
234
+ valuesOverlay,
235
+ entryIds,
236
+ execution: "sequential"
237
+ });
238
+ } else if (provider === "cloudflare") {
239
+ adapters.cloudflare({
240
+ tenantRoot: options.projectRoot,
241
+ scope,
242
+ dryRun: options.dryRun,
243
+ valuesOverlay,
244
+ entryIds
245
+ });
246
+ } else {
247
+ adapters.railway({
248
+ tenantRoot: options.projectRoot,
249
+ scope,
250
+ dryRun: options.dryRun,
251
+ valuesOverlay,
252
+ entryIds
253
+ });
254
+ }
255
+ result.items.push(...summaryItemsFor(scopedItems, provider, scope, "synced"));
256
+ result.providers.push({ provider, scope, entryIds, status: "completed" });
257
+ await options.onProgress?.({
258
+ provider,
259
+ scope,
260
+ entryIds,
261
+ status: "completed",
262
+ message: `Synced host-bound ${provider} entries for ${scope}.`
263
+ });
264
+ } catch (error) {
265
+ result.ok = false;
266
+ result.items.push(...summaryItemsFor(scopedItems, provider, scope, "failed", error));
267
+ result.providers.push({
268
+ provider,
269
+ scope,
270
+ entryIds,
271
+ status: "failed",
272
+ error: { message: redactSecretSyncMessage(error) }
273
+ });
274
+ await options.onProgress?.({
275
+ provider,
276
+ scope,
277
+ entryIds,
278
+ status: "failed",
279
+ message: redactSecretSyncMessage(error)
280
+ });
281
+ throw new ProjectLaunchSecretSyncError(redactSecretSyncMessage(error), result);
282
+ }
283
+ }
284
+ }
285
+ result.ok = result.providers.every((provider) => provider.status === "completed") && result.diagnostics.length === 0;
286
+ return result;
287
+ }
288
+ export {
289
+ ProjectLaunchSecretSyncError,
290
+ resolveProjectLaunchSecretValueOverlay,
291
+ syncProjectLaunchHostBindingSecrets
292
+ };
@@ -86,6 +86,9 @@ export type TreeseedEnvironmentEntry = {
86
86
  localDefaultValue?: TreeseedEnvironmentValueResolver;
87
87
  isRelevant?: (context: TreeseedEnvironmentContext, scope: TreeseedEnvironmentScope, purpose?: TreeseedEnvironmentPurpose) => boolean;
88
88
  requiredWhen?: (context: TreeseedEnvironmentContext, scope: TreeseedEnvironmentScope, purpose?: TreeseedEnvironmentPurpose) => boolean;
89
+ sourceRequirement?: string;
90
+ sourceHostType?: string | null;
91
+ sourceProvider?: string | null;
89
92
  };
90
93
  export type TreeseedEnvironmentEntryYaml = Omit<TreeseedEnvironmentEntry, 'id' | 'defaultValue' | 'localDefaultValue' | 'isRelevant' | 'requiredWhen'> & {
91
94
  cluster?: string;
@@ -1,4 +1,4 @@
1
- import type { ProjectConnection, RemoteJobStatus } from './sdk-types.ts';
1
+ import type { ProjectConnection, ProjectLaunchHostBindingInput, RemoteJobStatus } from './sdk-types.ts';
2
2
  export declare const PROJECT_TEAM_CAPABILITIES: readonly ["launch_projects", "edit_direct", "manage_workstreams", "stage_releases", "publish_releases", "publish_market_listings", "manage_products", "manage_billing", "approve_remote_execution"];
3
3
  export declare const PROJECT_JOB_STATUSES: readonly ["queued", "running", "waiting_for_approval", "failed", "completed", "rolled_back", "cancelled"];
4
4
  export declare const WORKSTREAM_STATES: readonly ["drafting", "active_local", "verifying", "saved_remote", "in_staging", "archived"];
@@ -201,9 +201,15 @@ export interface LaunchProjectRequest {
201
201
  sourceKind: 'blank' | 'template' | 'knowledge_pack';
202
202
  sourceRef?: string | null;
203
203
  hostingMode: 'managed' | 'hybrid' | 'self_hosted';
204
+ repositoryHostId?: string | null;
205
+ repositoryHostConfig?: Record<string, unknown> | null;
204
206
  cloudflareHostId?: string | null;
205
207
  cloudflareHostMode?: 'team_owned' | 'treeseed_managed' | null;
206
208
  cloudflareHostConfig?: Record<string, unknown> | null;
209
+ emailHostId?: string | null;
210
+ emailHostMode?: 'team_owned' | 'treeseed_managed' | null;
211
+ emailHostConfig?: Record<string, unknown> | null;
212
+ hostBindings?: Record<string, ProjectLaunchHostBindingInput>;
207
213
  targetEnvironments?: Array<'local' | 'staging' | 'prod'>;
208
214
  publicSite?: boolean;
209
215
  repoProvider?: 'github';
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { resolve } from 'node:path';
3
3
  import { scaffoldTemplateProject } from '../operations/services/template-registry.js';
4
+ import { TREESEED_DEFAULT_STARTER_TEMPLATE_ID } from '../sdk-types.js';
4
5
  function parseArgs(argv) {
5
6
  const args = {
6
7
  target: null,
7
- template: 'starter-basic',
8
+ template: TREESEED_DEFAULT_STARTER_TEMPLATE_ID,
8
9
  name: null,
9
10
  slug: null,
10
11
  siteUrl: null,
@@ -59,7 +60,7 @@ console.log(`Created Treeseed tenant from ${definition.id} at ${targetRoot}`);
59
60
  console.log('Next steps:');
60
61
  console.log(` cd ${options.target}`);
61
62
  console.log(' npm install');
62
- console.log(' treeseed template show starter-basic');
63
+ console.log(` treeseed template show ${options.template}`);
63
64
  console.log(' treeseed sync --check');
64
65
  console.log(' treeseed config --environment local');
65
66
  console.log(' treeseed dev');
@@ -5,6 +5,7 @@ import { dirname, join, resolve } from 'node:path';
5
5
  import { spawnSync } from 'node:child_process';
6
6
  import { corePackageRoot, packageRoot, packageScriptPath, sdkPackageRoot } from '../operations/services/runtime-tools.js';
7
7
  import { listTemplateProducts, validateTemplateProduct } from '../operations/services/template-registry.js';
8
+ import { TREESEED_DEFAULT_STARTER_TEMPLATE_ID } from '../sdk-types.js';
8
9
  const npmCacheDir = process.env.TREESEED_SCAFFOLD_NPM_CACHE_DIR
9
10
  ? resolve(process.env.TREESEED_SCAFFOLD_NPM_CACHE_DIR)
10
11
  : resolve(tmpdir(), 'treeseed-npm-cache');
@@ -204,7 +205,7 @@ async function scaffoldSite(siteRoot) {
204
205
  for (const definition of await listTemplateProducts({ writeWarning: (message) => console.warn(message) })) {
205
206
  await validateTemplateProduct(definition, { writeWarning: (message) => console.warn(message) });
206
207
  }
207
- runStep(process.execPath, [packageScriptPath('scaffold-site'), siteRoot, '--template', 'starter-basic', '--name', 'Smoke Site', '--site-url', 'https://smoke.example.com', '--contact-email', 'hello@example.com']);
208
+ runStep(process.execPath, [packageScriptPath('scaffold-site'), siteRoot, '--template', TREESEED_DEFAULT_STARTER_TEMPLATE_ID, '--name', 'Smoke Site', '--site-url', 'https://smoke.example.com', '--contact-email', 'hello@example.com']);
208
209
  }
209
210
  function installScaffold(siteRoot, { coreTarballPath, sdkTarballPath, cliTarballPath }) {
210
211
  if (coreTarballPath && sdkTarballPath && cliTarballPath) {
@@ -21,6 +21,16 @@ export declare const PROJECT_DEPLOYMENT_STATUSES: readonly ["pending", "queued",
21
21
  export declare const PROJECT_INFRA_RESOURCE_PROVIDERS: readonly ["cloudflare", "railway", "github", "market"];
22
22
  export declare const PROJECT_INFRA_RESOURCE_KINDS: readonly ["pages", "worker", "kv", "turnstile-widget", "r2", "d1", "queue", "dlq", "railway_project", "railway_service", "railway_schedule"];
23
23
  export declare const AGENT_POOL_STATUSES: readonly ["pending", "active", "degraded", "offline"];
24
+ export declare const TREESEED_DEFAULT_STARTER_TEMPLATE_ID: "starter-research";
25
+ export declare const TEMPLATE_HOST_REQUIREMENT_TYPES: readonly ["repository", "web", "email", "ai"];
26
+ export declare const TEMPLATE_RESOURCE_REQUIREMENT_TYPES: readonly ["service", "database", "object-storage", "queue", "dns-zone"];
27
+ export declare const TEMPLATE_SECRET_SENSITIVITIES: readonly ["secret", "plain", "derived"];
28
+ export declare const TEMPLATE_SECRET_TARGETS: readonly ["github-secret", "github-variable", "cloudflare-secret", "cloudflare-var", "railway-secret", "railway-var", "config-file", "local-runtime"];
29
+ export declare const TEMPLATE_SECRET_SOURCES: readonly ["generated", "selected-host", "user-input", "derived"];
30
+ export declare const TEMPLATE_CONFIG_WRITE_TARGETS: readonly ["treeseed.site.yaml", "src/env.yaml", "src/manifest.yaml", "package.json"];
31
+ export declare const TEMPLATE_CONFIG_WRITE_WHEN: readonly ["always", "host-selected", "feature-enabled"];
32
+ export declare const TEMPLATE_CONFIG_MERGE_STRATEGIES: readonly ["replace", "deep-merge", "append-unique"];
33
+ export declare const PROJECT_LAUNCH_REQUIREMENT_KINDS: readonly ["host", "resource", "secret"];
24
34
  export type SdkBuiltinModelName = (typeof SDK_MODEL_NAMES)[number];
25
35
  export type SdkModelName = SdkBuiltinModelName | (string & {});
26
36
  export type SdkOperation = (typeof SDK_OPERATIONS)[number];
@@ -49,6 +59,82 @@ export type ProjectInfrastructureResourceProvider = (typeof PROJECT_INFRA_RESOUR
49
59
  export type ProjectInfrastructureResourceKind = (typeof PROJECT_INFRA_RESOURCE_KINDS)[number];
50
60
  export type AgentPoolStatus = (typeof AGENT_POOL_STATUSES)[number];
51
61
  export type RemoteJobRequestedByType = 'user' | 'team_api_key' | 'service' | 'runner' | 'system';
62
+ export type TemplateHostRequirementType = (typeof TEMPLATE_HOST_REQUIREMENT_TYPES)[number];
63
+ export type TemplateResourceRequirementType = (typeof TEMPLATE_RESOURCE_REQUIREMENT_TYPES)[number];
64
+ export type TemplateSecretSensitivity = (typeof TEMPLATE_SECRET_SENSITIVITIES)[number];
65
+ export type TemplateSecretTarget = (typeof TEMPLATE_SECRET_TARGETS)[number];
66
+ export type TemplateSecretSource = (typeof TEMPLATE_SECRET_SOURCES)[number];
67
+ export type TemplateConfigWriteTarget = (typeof TEMPLATE_CONFIG_WRITE_TARGETS)[number];
68
+ export type TemplateConfigWriteWhen = (typeof TEMPLATE_CONFIG_WRITE_WHEN)[number];
69
+ export type TemplateConfigMergeStrategy = (typeof TEMPLATE_CONFIG_MERGE_STRATEGIES)[number];
70
+ export type ProjectLaunchRequirementKind = (typeof PROJECT_LAUNCH_REQUIREMENT_KINDS)[number];
71
+ export interface TemplateConfigWrite {
72
+ target: TemplateConfigWriteTarget;
73
+ path: string;
74
+ valueFrom: string;
75
+ writeWhen?: TemplateConfigWriteWhen;
76
+ mergeStrategy?: TemplateConfigMergeStrategy;
77
+ }
78
+ export interface TemplateEnvironmentWrite {
79
+ env: string;
80
+ valueFrom: string;
81
+ targets?: TemplateSecretTarget[];
82
+ scopes?: ProjectEnvironmentName[];
83
+ sensitivity?: TemplateSecretSensitivity;
84
+ }
85
+ export interface TemplateHostRequirement {
86
+ kind: 'host';
87
+ key: string;
88
+ type: TemplateHostRequirementType;
89
+ required: boolean;
90
+ compatibleProviders?: string[];
91
+ displayName: string;
92
+ purpose: string;
93
+ defaultSelection?: 'team-default' | 'managed' | 'none';
94
+ configWrites: TemplateConfigWrite[];
95
+ environmentWrites?: TemplateEnvironmentWrite[];
96
+ }
97
+ export interface TemplateResourceRequirement {
98
+ kind: 'resource';
99
+ key: string;
100
+ type: TemplateResourceRequirementType;
101
+ required: boolean;
102
+ compatibleProviders?: string[];
103
+ displayName: string;
104
+ purpose: string;
105
+ configWrites: TemplateConfigWrite[];
106
+ environmentWrites?: TemplateEnvironmentWrite[];
107
+ }
108
+ export interface TemplateSecretRequirement {
109
+ kind: 'secret';
110
+ key: string;
111
+ env: string;
112
+ required: boolean;
113
+ sensitivity: TemplateSecretSensitivity;
114
+ targets: TemplateSecretTarget[];
115
+ source: TemplateSecretSource;
116
+ }
117
+ export interface TemplateLaunchRequirements {
118
+ version?: number;
119
+ hosts?: TemplateHostRequirement[];
120
+ resources?: TemplateResourceRequirement[];
121
+ secrets?: TemplateSecretRequirement[];
122
+ }
123
+ export interface ProjectLaunchHostBindingInput {
124
+ requirementKey: string;
125
+ requirementKind: ProjectLaunchRequirementKind;
126
+ type: string;
127
+ provider: string;
128
+ hostId?: string | null;
129
+ managedHostKey?: string | null;
130
+ mode?: string | null;
131
+ displayName?: string;
132
+ environmentScopes?: ProjectEnvironmentName[];
133
+ configValues?: Record<string, unknown>;
134
+ environmentValues?: Record<string, string>;
135
+ secretRefs?: Record<string, string>;
136
+ selectedBy?: 'user' | 'team-default' | 'managed-default' | 'template-default';
137
+ }
52
138
  export declare function projectConnectionModeFromHosting(kind: TreeseedHostingKind, registration?: TreeseedHostingRegistration): ProjectConnectionMode;
53
139
  export interface SdkDispatchCapability {
54
140
  namespace: SdkDispatchNamespace;
@@ -2536,6 +2622,7 @@ export interface SdkTemplateCatalogEntry {
2536
2622
  relatedBooks?: string[];
2537
2623
  relatedKnowledge?: string[];
2538
2624
  relatedObjectives?: string[];
2625
+ launchRequirements?: TemplateLaunchRequirements;
2539
2626
  }
2540
2627
  export interface SdkTemplateCatalogResponse {
2541
2628
  items: SdkTemplateCatalogEntry[];
package/dist/sdk-types.js CHANGED
@@ -59,6 +59,25 @@ const PROJECT_INFRA_RESOURCE_KINDS = [
59
59
  "railway_schedule"
60
60
  ];
61
61
  const AGENT_POOL_STATUSES = ["pending", "active", "degraded", "offline"];
62
+ const TREESEED_DEFAULT_STARTER_TEMPLATE_ID = "starter-research";
63
+ const TEMPLATE_HOST_REQUIREMENT_TYPES = ["repository", "web", "email", "ai"];
64
+ const TEMPLATE_RESOURCE_REQUIREMENT_TYPES = ["service", "database", "object-storage", "queue", "dns-zone"];
65
+ const TEMPLATE_SECRET_SENSITIVITIES = ["secret", "plain", "derived"];
66
+ const TEMPLATE_SECRET_TARGETS = [
67
+ "github-secret",
68
+ "github-variable",
69
+ "cloudflare-secret",
70
+ "cloudflare-var",
71
+ "railway-secret",
72
+ "railway-var",
73
+ "config-file",
74
+ "local-runtime"
75
+ ];
76
+ const TEMPLATE_SECRET_SOURCES = ["generated", "selected-host", "user-input", "derived"];
77
+ const TEMPLATE_CONFIG_WRITE_TARGETS = ["treeseed.site.yaml", "src/env.yaml", "src/manifest.yaml", "package.json"];
78
+ const TEMPLATE_CONFIG_WRITE_WHEN = ["always", "host-selected", "feature-enabled"];
79
+ const TEMPLATE_CONFIG_MERGE_STRATEGIES = ["replace", "deep-merge", "append-unique"];
80
+ const PROJECT_LAUNCH_REQUIREMENT_KINDS = ["host", "resource", "secret"];
62
81
  function projectConnectionModeFromHosting(kind, registration = "none") {
63
82
  if (kind === "hosted_project") {
64
83
  return "hosted";
@@ -78,6 +97,7 @@ export {
78
97
  PROJECT_EXECUTION_OWNERS,
79
98
  PROJECT_INFRA_RESOURCE_KINDS,
80
99
  PROJECT_INFRA_RESOURCE_PROVIDERS,
100
+ PROJECT_LAUNCH_REQUIREMENT_KINDS,
81
101
  PROJECT_RUNNER_REGISTRATION_STATES,
82
102
  PROJECT_WEB_DEPLOYMENT_ACTIONS,
83
103
  REMOTE_JOB_STATUSES,
@@ -89,6 +109,15 @@ export {
89
109
  SDK_OPERATIONS,
90
110
  SDK_PICK_STRATEGIES,
91
111
  SDK_STORAGE_BACKENDS,
112
+ TEMPLATE_CONFIG_MERGE_STRATEGIES,
113
+ TEMPLATE_CONFIG_WRITE_TARGETS,
114
+ TEMPLATE_CONFIG_WRITE_WHEN,
115
+ TEMPLATE_HOST_REQUIREMENT_TYPES,
116
+ TEMPLATE_RESOURCE_REQUIREMENT_TYPES,
117
+ TEMPLATE_SECRET_SENSITIVITIES,
118
+ TEMPLATE_SECRET_SOURCES,
119
+ TEMPLATE_SECRET_TARGETS,
120
+ TREESEED_DEFAULT_STARTER_TEMPLATE_ID,
92
121
  TREESEED_HOSTING_KINDS,
93
122
  TREESEED_HOSTING_REGISTRATIONS,
94
123
  projectConnectionModeFromHosting
@@ -1,5 +1,6 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
+ import { normalizeTemplateLaunchRequirements } from "./template-launch-requirements.js";
3
4
  function expectRecord(value, label) {
4
5
  if (!value || typeof value !== "object" || Array.isArray(value)) {
5
6
  throw new Error(`Invalid template catalog response: expected ${label} to be an object.`);
@@ -91,7 +92,8 @@ function normalizeTemplateCatalogEntry(value) {
91
92
  } : void 0,
92
93
  relatedBooks: optionalStringArray(record.relatedBooks, "relatedBooks") ?? [],
93
94
  relatedKnowledge: optionalStringArray(record.relatedKnowledge, "relatedKnowledge") ?? [],
94
- relatedObjectives: optionalStringArray(record.relatedObjectives, "relatedObjectives") ?? []
95
+ relatedObjectives: optionalStringArray(record.relatedObjectives, "relatedObjectives") ?? [],
96
+ launchRequirements: normalizeTemplateLaunchRequirements(record.launchRequirements, "launchRequirements")
95
97
  };
96
98
  }
97
99
  function parseTemplateCatalogResponse(payload) {
@@ -0,0 +1,118 @@
1
+ import { type ProjectEnvironmentName, type ProjectLaunchHostBindingInput, type ProjectLaunchRequirementKind, type TemplateConfigWrite, type TemplateLaunchRequirements } from './sdk-types.ts';
2
+ type Mutable<T> = {
3
+ -readonly [P in keyof T]: T[P];
4
+ };
5
+ export type { TemplateLaunchRequirements } from './sdk-types.ts';
6
+ export interface ProjectLaunchHostInventoryRecord {
7
+ id: string;
8
+ type?: string | null;
9
+ provider: string;
10
+ ownership?: string | null;
11
+ name?: string | null;
12
+ accountLabel?: string | null;
13
+ organizationOrOwner?: string | null;
14
+ allowedEnvironments?: ProjectEnvironmentName[];
15
+ status?: string | null;
16
+ metadata?: Record<string, unknown>;
17
+ }
18
+ export interface ProjectLaunchResolvedHostBinding {
19
+ requirementKey: string;
20
+ requirementKind: ProjectLaunchRequirementKind;
21
+ type: string;
22
+ provider: string;
23
+ hostId?: string | null;
24
+ managedHostKey?: string | null;
25
+ displayName: string;
26
+ environmentScopes: ProjectEnvironmentName[];
27
+ configValues: Record<string, unknown>;
28
+ environmentValues: Record<string, string>;
29
+ secretRefs: Record<string, string>;
30
+ provenance: {
31
+ selectedBy: NonNullable<ProjectLaunchHostBindingInput['selectedBy']>;
32
+ selectedAt: string;
33
+ };
34
+ host: {
35
+ id: string;
36
+ name: string | null;
37
+ ownership: string | null;
38
+ status: string | null;
39
+ accountLabel?: string | null;
40
+ organizationOrOwner?: string | null;
41
+ metadata?: Record<string, unknown>;
42
+ } | null;
43
+ }
44
+ export interface ProjectLaunchConfigWritePlanItem extends TemplateConfigWrite {
45
+ requirementKey: string;
46
+ requirementKind: ProjectLaunchRequirementKind;
47
+ requirementType: string;
48
+ provider: string;
49
+ }
50
+ export interface ProjectLaunchSecretDeploymentPlanItem {
51
+ requirementKey: string;
52
+ requirementKind: ProjectLaunchRequirementKind;
53
+ env: string;
54
+ sensitivity: string;
55
+ source: string;
56
+ targets: string[];
57
+ scopes: ProjectEnvironmentName[];
58
+ sourceHostId?: string | null;
59
+ }
60
+ export interface ResolveProjectLaunchHostBindingsOptions {
61
+ hostBindings?: Record<string, ProjectLaunchHostBindingInput>;
62
+ launchRequirements?: TemplateLaunchRequirements | null;
63
+ repositoryHosts?: ProjectLaunchHostInventoryRecord[];
64
+ teamHosts?: ProjectLaunchHostInventoryRecord[];
65
+ managedHosts?: ProjectLaunchHostInventoryRecord[];
66
+ defaultHosts?: Record<string, unknown> | null;
67
+ domains?: Record<string, unknown> | null;
68
+ projectSlug?: string | null;
69
+ projectName?: string | null;
70
+ standardProjectLaunch?: boolean;
71
+ selectedAt?: string;
72
+ }
73
+ export interface ResolveProjectLaunchHostBindingsResult {
74
+ hostBindings: Record<string, ProjectLaunchResolvedHostBinding>;
75
+ compatibility: {
76
+ repositoryHostId?: string | null;
77
+ cloudflareHostMode?: 'team_owned' | 'treeseed_managed' | null;
78
+ cloudflareHostId?: string | null;
79
+ emailHostMode?: 'team_owned' | 'treeseed_managed' | null;
80
+ emailHostId?: string | null;
81
+ };
82
+ configWritePlan: ProjectLaunchConfigWritePlanItem[];
83
+ secretDeploymentPlan: {
84
+ items: ProjectLaunchSecretDeploymentPlanItem[];
85
+ };
86
+ diagnostics: Array<{
87
+ code: string;
88
+ message: string;
89
+ requirementKey?: string;
90
+ }>;
91
+ }
92
+ export interface ProjectLaunchLocalHostBindingSummary {
93
+ requirementKey: string;
94
+ requirementKind: ProjectLaunchRequirementKind;
95
+ type: string;
96
+ provider: string | null;
97
+ alias: string | null;
98
+ mode: 'team_owned' | 'treeseed_managed' | 'none';
99
+ displayName: string;
100
+ }
101
+ export interface ParseProjectLaunchHostBindingSpecsOptions {
102
+ specs?: string | string[] | null;
103
+ launchRequirements?: TemplateLaunchRequirements | null;
104
+ selectedAt?: string;
105
+ }
106
+ export interface ParseProjectLaunchHostBindingSpecsResult {
107
+ hostBindings: Record<string, ProjectLaunchHostBindingInput>;
108
+ repositoryHosts: ProjectLaunchHostInventoryRecord[];
109
+ teamHosts: ProjectLaunchHostInventoryRecord[];
110
+ managedHosts: ProjectLaunchHostInventoryRecord[];
111
+ summaries: ProjectLaunchLocalHostBindingSummary[];
112
+ omitted: ProjectLaunchLocalHostBindingSummary[];
113
+ }
114
+ export declare function normalizeTemplateLaunchRequirements(value: unknown, label?: string): TemplateLaunchRequirements | undefined;
115
+ export declare function validateTemplateLaunchRequirements(value: unknown, label?: string): void;
116
+ export declare function normalizeProjectLaunchHostBindings(input: unknown): Mutable<Record<string, ProjectLaunchHostBindingInput>>;
117
+ export declare function parseProjectLaunchHostBindingSpecs(options: ParseProjectLaunchHostBindingSpecsOptions): ParseProjectLaunchHostBindingSpecsResult;
118
+ export declare function resolveProjectLaunchHostBindings(options: ResolveProjectLaunchHostBindingsOptions): ResolveProjectLaunchHostBindingsResult;