@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.
- package/dist/index.d.ts +12 -2
- package/dist/index.js +42 -1
- package/dist/market-client.d.ts +23 -0
- package/dist/market-client.js +30 -0
- package/dist/operations/providers/default.js +103 -10
- package/dist/operations/repository-operations.d.ts +6 -1
- package/dist/operations/repository-operations.js +44 -0
- package/dist/operations/services/config-runtime.d.ts +24 -9
- package/dist/operations/services/config-runtime.js +60 -12
- package/dist/operations/services/deploy.js +6 -1
- package/dist/operations/services/hub-launch.js +1 -0
- package/dist/operations/services/hub-provider-launch.d.ts +11 -1
- package/dist/operations/services/hub-provider-launch.js +81 -8
- package/dist/operations/services/project-host-operations.d.ts +153 -0
- package/dist/operations/services/project-host-operations.js +365 -0
- package/dist/operations/services/project-platform.d.ts +198 -193
- package/dist/operations/services/project-platform.js +29 -14
- package/dist/operations/services/railway-deploy.d.ts +3 -0
- package/dist/operations/services/railway-deploy.js +74 -35
- package/dist/operations/services/release-candidate.js +8 -2
- package/dist/operations/services/template-host-bindings.d.ts +68 -0
- package/dist/operations/services/template-host-bindings.js +400 -0
- package/dist/operations/services/template-registry.d.ts +22 -2
- package/dist/operations/services/template-registry.js +60 -3
- package/dist/operations/services/template-secret-sync.d.ts +97 -0
- package/dist/operations/services/template-secret-sync.js +292 -0
- package/dist/platform/environment.d.ts +3 -0
- package/dist/project-workflow.d.ts +7 -1
- package/dist/scripts/scaffold-site.js +3 -2
- package/dist/scripts/test-scaffold.js +2 -1
- package/dist/sdk-types.d.ts +87 -0
- package/dist/sdk-types.js +29 -0
- package/dist/template-catalog.js +3 -1
- package/dist/template-launch-requirements.d.ts +118 -0
- package/dist/template-launch-requirements.js +759 -0
- package/dist/template-launch-ui.d.ts +85 -0
- package/dist/template-launch-ui.js +189 -0
- package/dist/treeseed/template-catalog/catalog.fixture.json +330 -3
- 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:
|
|
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(
|
|
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',
|
|
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) {
|
package/dist/sdk-types.d.ts
CHANGED
|
@@ -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
|
package/dist/template-catalog.js
CHANGED
|
@@ -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;
|