@treeseed/sdk 0.10.23 → 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 (49) 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/bootstrap-runner.d.ts +5 -1
  9. package/dist/operations/services/bootstrap-runner.js +34 -5
  10. package/dist/operations/services/config-runtime.d.ts +25 -9
  11. package/dist/operations/services/config-runtime.js +60 -12
  12. package/dist/operations/services/deploy.js +6 -1
  13. package/dist/operations/services/hub-launch.js +1 -0
  14. package/dist/operations/services/hub-provider-launch.d.ts +11 -1
  15. package/dist/operations/services/hub-provider-launch.js +81 -8
  16. package/dist/operations/services/project-host-operations.d.ts +153 -0
  17. package/dist/operations/services/project-host-operations.js +365 -0
  18. package/dist/operations/services/project-platform.d.ts +207 -177
  19. package/dist/operations/services/project-platform.js +96 -29
  20. package/dist/operations/services/railway-deploy.d.ts +33 -1
  21. package/dist/operations/services/railway-deploy.js +153 -44
  22. package/dist/operations/services/release-candidate.js +8 -2
  23. package/dist/operations/services/template-host-bindings.d.ts +68 -0
  24. package/dist/operations/services/template-host-bindings.js +400 -0
  25. package/dist/operations/services/template-registry.d.ts +22 -2
  26. package/dist/operations/services/template-registry.js +93 -6
  27. package/dist/operations/services/template-secret-sync.d.ts +97 -0
  28. package/dist/operations/services/template-secret-sync.js +292 -0
  29. package/dist/platform/contracts.d.ts +1 -0
  30. package/dist/platform/deploy-config.js +8 -1
  31. package/dist/platform/deploy-runtime.js +1 -0
  32. package/dist/platform/environment.d.ts +3 -0
  33. package/dist/project-workflow.d.ts +7 -1
  34. package/dist/reconcile/engine.d.ts +2 -0
  35. package/dist/reconcile/engine.js +58 -3
  36. package/dist/scripts/scaffold-site.js +3 -2
  37. package/dist/scripts/test-scaffold.js +2 -1
  38. package/dist/sdk-types.d.ts +87 -0
  39. package/dist/sdk-types.js +29 -0
  40. package/dist/template-catalog.js +3 -1
  41. package/dist/template-launch-requirements.d.ts +118 -0
  42. package/dist/template-launch-requirements.js +759 -0
  43. package/dist/template-launch-ui.d.ts +85 -0
  44. package/dist/template-launch-ui.js +189 -0
  45. package/dist/timing.d.ts +20 -0
  46. package/dist/timing.js +73 -0
  47. package/dist/treeseed/template-catalog/catalog.fixture.json +477 -0
  48. package/package.json +13 -1
  49. package/templates/github/deploy-web.workflow.yml +4 -0
@@ -0,0 +1,85 @@
1
+ import type { ProjectEnvironmentName, TemplateLaunchRequirements, TemplateSecretTarget } from './sdk-types.ts';
2
+ import type { ProjectLaunchHostInventoryRecord } from './template-launch-requirements.ts';
3
+ export interface ProjectLaunchRequirementHostChoice {
4
+ value: string;
5
+ label: string;
6
+ mode: 'none' | 'team_owned' | 'treeseed_managed' | 'new';
7
+ hostId?: string | null;
8
+ managedHostKey?: string | null;
9
+ provider: string;
10
+ type: string;
11
+ selected: boolean;
12
+ readiness: 'ready' | 'configuration_required' | 'inactive' | 'new' | 'none';
13
+ rootDomain?: string | null;
14
+ }
15
+ export interface ProjectLaunchHostRequirementViewModel {
16
+ kind: 'host';
17
+ key: string;
18
+ type: string;
19
+ displayName: string;
20
+ purpose: string;
21
+ required: boolean;
22
+ compatibleProviders: string[];
23
+ defaultSelection?: 'team-default' | 'managed' | 'none';
24
+ environmentScopes: ProjectEnvironmentName[];
25
+ configWritePreviews: Array<{
26
+ target: string;
27
+ path: string;
28
+ valueFrom: string;
29
+ writeWhen?: string;
30
+ }>;
31
+ environmentWritePreviews: Array<{
32
+ env: string;
33
+ targets: string[];
34
+ scopes: ProjectEnvironmentName[];
35
+ sensitivity: string;
36
+ }>;
37
+ choices: ProjectLaunchRequirementHostChoice[];
38
+ }
39
+ export interface ProjectLaunchResourceRequirementViewModel {
40
+ kind: 'resource';
41
+ key: string;
42
+ type: string;
43
+ displayName: string;
44
+ purpose: string;
45
+ required: boolean;
46
+ compatibleProviders: string[];
47
+ configWritePreviews: Array<{
48
+ target: string;
49
+ path: string;
50
+ valueFrom: string;
51
+ writeWhen?: string;
52
+ }>;
53
+ environmentWritePreviews: Array<{
54
+ env: string;
55
+ targets: string[];
56
+ scopes: ProjectEnvironmentName[];
57
+ sensitivity: string;
58
+ }>;
59
+ status: 'unsupported_in_standard_launch';
60
+ }
61
+ export interface ProjectLaunchSecretRequirementViewModel {
62
+ kind: 'secret';
63
+ key: string;
64
+ env: string;
65
+ required: boolean;
66
+ sensitivity: string;
67
+ source: string;
68
+ targets: TemplateSecretTarget[];
69
+ status: 'planned';
70
+ }
71
+ export interface ProjectLaunchRequirementsViewModel {
72
+ version?: number;
73
+ hosts: ProjectLaunchHostRequirementViewModel[];
74
+ resources: ProjectLaunchResourceRequirementViewModel[];
75
+ secrets: ProjectLaunchSecretRequirementViewModel[];
76
+ }
77
+ export interface DeriveProjectLaunchRequirementsViewModelOptions {
78
+ launchRequirements?: TemplateLaunchRequirements | null;
79
+ repositoryHosts?: ProjectLaunchHostInventoryRecord[];
80
+ teamHosts?: ProjectLaunchHostInventoryRecord[];
81
+ managedHosts?: ProjectLaunchHostInventoryRecord[];
82
+ defaultHosts?: Record<string, unknown> | null;
83
+ environmentScopes?: ProjectEnvironmentName[];
84
+ }
85
+ export declare function deriveProjectLaunchRequirementsViewModel(options: DeriveProjectLaunchRequirementsViewModelOptions): ProjectLaunchRequirementsViewModel;
@@ -0,0 +1,189 @@
1
+ function hostType(host) {
2
+ const metadataType = typeof host.metadata?.hostType === "string" ? host.metadata.hostType : host.type;
3
+ if (metadataType === "repository_host" || metadataType === "repository") return "repository";
4
+ if (metadataType === "web_host" || metadataType === "cloudflare" || metadataType === "web") return "web";
5
+ if (metadataType === "email_host" || metadataType === "smtp" || metadataType === "email") return "email";
6
+ if (metadataType === "ai_host" || metadataType === "ai") return "ai";
7
+ if (host.provider === "github") return "repository";
8
+ if (host.provider === "cloudflare") return "web";
9
+ if (host.provider === "smtp") return "email";
10
+ return metadataType ?? "";
11
+ }
12
+ function rootDomain(host) {
13
+ const metadata = host.metadata ?? {};
14
+ const dns = metadata.dns && typeof metadata.dns === "object" ? metadata.dns : {};
15
+ const value = dns.zoneName ?? dns.rootDomain ?? metadata.rootDomain ?? metadata.webRootDomain ?? metadata.cloudflareZoneName;
16
+ return typeof value === "string" && value.trim() ? value.trim() : null;
17
+ }
18
+ function hostLabel(host, fallback) {
19
+ const ownership = host.ownership === "treeseed_managed" ? "managed" : host.accountLabel ?? host.organizationOrOwner ?? host.provider ?? "team-owned";
20
+ return `${host.name || fallback} (${ownership})`;
21
+ }
22
+ function compatibleHosts(requirement, options) {
23
+ const source = requirement.type === "repository" ? options.repositoryHosts ?? [] : [...options.teamHosts ?? [], ...options.managedHosts ?? []];
24
+ return source.filter((host) => {
25
+ if (hostType(host) !== requirement.type) return false;
26
+ return !requirement.compatibleProviders?.length || requirement.compatibleProviders.includes(host.provider);
27
+ });
28
+ }
29
+ function defaultHostId(requirement, defaultHosts) {
30
+ for (const key of [requirement.key, requirement.type, requirement.key === "sourceRepository" ? "repository" : null, requirement.key === "publicWeb" ? "web" : null, requirement.key === "transactionalEmail" ? "email" : null]) {
31
+ if (!key) continue;
32
+ const value = defaultHosts?.[key];
33
+ if (typeof value === "string" && value.trim()) return value.trim();
34
+ }
35
+ return null;
36
+ }
37
+ function selectedChoiceIndex(choices, requirement, defaultHosts) {
38
+ const configuredDefault = defaultHostId(requirement, defaultHosts);
39
+ if (configuredDefault) {
40
+ const index = choices.findIndex((choice) => choice.hostId === configuredDefault || choice.managedHostKey === configuredDefault);
41
+ if (index >= 0) return index;
42
+ }
43
+ if (requirement.defaultSelection === "team-default") {
44
+ const teamIndex = choices.findIndex((choice) => choice.mode === "team_owned");
45
+ if (teamIndex >= 0) return teamIndex;
46
+ }
47
+ if (requirement.defaultSelection === "managed" || requirement.defaultSelection === "team-default") {
48
+ const managedIndex = choices.findIndex((choice) => choice.mode === "treeseed_managed");
49
+ if (managedIndex >= 0) return managedIndex;
50
+ }
51
+ if (!requirement.required) {
52
+ const noneIndex = choices.findIndex((choice) => choice.mode === "none");
53
+ if (noneIndex >= 0) return noneIndex;
54
+ }
55
+ return choices.length > 0 ? 0 : -1;
56
+ }
57
+ function deriveHostRequirement(requirement, options) {
58
+ const scopes = options.environmentScopes ?? ["staging", "prod"];
59
+ const choices = compatibleHosts(requirement, options).map((host) => {
60
+ const managed = host.ownership === "treeseed_managed";
61
+ const status = typeof host.status === "string" && host.status ? host.status : "active";
62
+ return {
63
+ value: `${managed ? "treeseed_managed" : "team_owned"}:${host.id}`,
64
+ label: hostLabel(host, requirement.displayName),
65
+ mode: managed ? "treeseed_managed" : "team_owned",
66
+ hostId: managed && requirement.type !== "repository" ? null : host.id,
67
+ managedHostKey: managed ? host.id : null,
68
+ provider: host.provider,
69
+ type: requirement.type,
70
+ selected: false,
71
+ readiness: status === "active" || status === "ready" ? "ready" : status === "configuration_required" ? "configuration_required" : "inactive",
72
+ rootDomain: rootDomain(host)
73
+ };
74
+ });
75
+ if (requirement.type === "repository" && !choices.some((choice) => choice.value === "treeseed_managed:platform:github:hosted-hubs" || choice.value === "team_owned:platform:github:hosted-hubs")) {
76
+ choices.unshift({
77
+ value: "treeseed_managed:platform:github:hosted-hubs",
78
+ label: "TreeSeed hosted repositories (managed)",
79
+ mode: "treeseed_managed",
80
+ hostId: "platform:github:hosted-hubs",
81
+ managedHostKey: "platform:github:hosted-hubs",
82
+ provider: "github",
83
+ type: "repository",
84
+ selected: false,
85
+ readiness: "ready",
86
+ rootDomain: null
87
+ });
88
+ }
89
+ if (!requirement.required) {
90
+ choices.push({
91
+ value: `none:${requirement.key}`,
92
+ label: `No ${requirement.displayName.toLowerCase()}`,
93
+ mode: "none",
94
+ hostId: null,
95
+ managedHostKey: null,
96
+ provider: requirement.compatibleProviders?.[0] ?? "",
97
+ type: requirement.type,
98
+ selected: false,
99
+ readiness: "none",
100
+ rootDomain: null
101
+ });
102
+ }
103
+ choices.push({
104
+ value: `new:${requirement.type}`,
105
+ label: `Create new ${requirement.type} host...`,
106
+ mode: "new",
107
+ hostId: null,
108
+ managedHostKey: null,
109
+ provider: requirement.compatibleProviders?.[0] ?? "",
110
+ type: requirement.type,
111
+ selected: false,
112
+ readiness: "new",
113
+ rootDomain: null
114
+ });
115
+ const selectedIndex = selectedChoiceIndex(choices, requirement, options.defaultHosts);
116
+ if (selectedIndex >= 0) choices[selectedIndex] = { ...choices[selectedIndex], selected: true };
117
+ return {
118
+ kind: "host",
119
+ key: requirement.key,
120
+ type: requirement.type,
121
+ displayName: requirement.displayName,
122
+ purpose: requirement.purpose,
123
+ required: requirement.required,
124
+ compatibleProviders: requirement.compatibleProviders ?? [],
125
+ defaultSelection: requirement.defaultSelection,
126
+ environmentScopes: scopes,
127
+ configWritePreviews: (requirement.configWrites ?? []).map((write) => ({
128
+ target: write.target,
129
+ path: write.path,
130
+ valueFrom: write.valueFrom,
131
+ writeWhen: write.writeWhen
132
+ })),
133
+ environmentWritePreviews: (requirement.environmentWrites ?? []).map((write) => ({
134
+ env: write.env,
135
+ targets: write.targets ?? [],
136
+ scopes: write.scopes ?? scopes,
137
+ sensitivity: write.sensitivity ?? "plain"
138
+ })),
139
+ choices
140
+ };
141
+ }
142
+ function deriveResourceRequirement(requirement) {
143
+ return {
144
+ kind: "resource",
145
+ key: requirement.key,
146
+ type: requirement.type,
147
+ displayName: requirement.displayName,
148
+ purpose: requirement.purpose,
149
+ required: requirement.required,
150
+ compatibleProviders: requirement.compatibleProviders ?? [],
151
+ configWritePreviews: (requirement.configWrites ?? []).map((write) => ({
152
+ target: write.target,
153
+ path: write.path,
154
+ valueFrom: write.valueFrom,
155
+ writeWhen: write.writeWhen
156
+ })),
157
+ environmentWritePreviews: (requirement.environmentWrites ?? []).map((write) => ({
158
+ env: write.env,
159
+ targets: write.targets ?? [],
160
+ scopes: write.scopes ?? ["staging", "prod"],
161
+ sensitivity: write.sensitivity ?? "plain"
162
+ })),
163
+ status: "unsupported_in_standard_launch"
164
+ };
165
+ }
166
+ function deriveSecretRequirement(requirement) {
167
+ return {
168
+ kind: "secret",
169
+ key: requirement.key,
170
+ env: requirement.env,
171
+ required: requirement.required,
172
+ sensitivity: requirement.sensitivity,
173
+ source: requirement.source,
174
+ targets: requirement.targets,
175
+ status: "planned"
176
+ };
177
+ }
178
+ function deriveProjectLaunchRequirementsViewModel(options) {
179
+ const launchRequirements = options.launchRequirements ?? {};
180
+ return {
181
+ version: launchRequirements.version,
182
+ hosts: (launchRequirements.hosts ?? []).map((requirement) => deriveHostRequirement(requirement, options)),
183
+ resources: (launchRequirements.resources ?? []).map(deriveResourceRequirement),
184
+ secrets: (launchRequirements.secrets ?? []).map(deriveSecretRequirement)
185
+ };
186
+ }
187
+ export {
188
+ deriveProjectLaunchRequirementsViewModel
189
+ };
@@ -0,0 +1,20 @@
1
+ export type TreeseedTimingEntry = {
2
+ name: string;
3
+ durationMs: number;
4
+ status?: string;
5
+ metadata?: Record<string, unknown>;
6
+ children?: TreeseedTimingEntry[];
7
+ };
8
+ export declare function nowMs(): number;
9
+ export declare function elapsedMs(startMs: number): number;
10
+ export declare function formatDurationMs(durationMs: number): string;
11
+ export declare function summarizeSlowestTimings(entries: TreeseedTimingEntry[], limit?: number): TreeseedTimingEntry[];
12
+ export declare function flattenTimings(entries: TreeseedTimingEntry[]): TreeseedTimingEntry[];
13
+ export declare function formatTimingSummary(entries: TreeseedTimingEntry[], { title, limit }?: {
14
+ title?: string | undefined;
15
+ limit?: number | undefined;
16
+ }): string;
17
+ export declare function formatTimingMarkdown(entries: TreeseedTimingEntry[], { title, limit }?: {
18
+ title?: string | undefined;
19
+ limit?: number | undefined;
20
+ }): string;
package/dist/timing.js ADDED
@@ -0,0 +1,73 @@
1
+ function nowMs() {
2
+ return performance.now();
3
+ }
4
+ function elapsedMs(startMs) {
5
+ return Math.max(0, performance.now() - startMs);
6
+ }
7
+ function formatDurationMs(durationMs) {
8
+ const value = Math.max(0, Number(durationMs) || 0);
9
+ if (value < 1e3) {
10
+ return `${Math.round(value)}ms`;
11
+ }
12
+ if (value < 6e4) {
13
+ return `${(value / 1e3).toFixed(value < 1e4 ? 1 : 0)}s`;
14
+ }
15
+ const minutes = Math.floor(value / 6e4);
16
+ const seconds = Math.round(value % 6e4 / 1e3);
17
+ return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
18
+ }
19
+ function summarizeSlowestTimings(entries, limit = 8) {
20
+ return flattenTimings(entries).sort((left, right) => right.durationMs - left.durationMs).slice(0, Math.max(0, limit));
21
+ }
22
+ function flattenTimings(entries) {
23
+ const flattened = [];
24
+ const visit = (entry) => {
25
+ flattened.push(entry);
26
+ for (const child of entry.children ?? []) {
27
+ visit(child);
28
+ }
29
+ };
30
+ for (const entry of entries) {
31
+ visit(entry);
32
+ }
33
+ return flattened;
34
+ }
35
+ function formatTimingSummary(entries, { title = "Provider deploy timing summary", limit = 12 } = {}) {
36
+ const slowest = summarizeSlowestTimings(entries, limit);
37
+ const lines = [`${title}:`];
38
+ if (slowest.length === 0) {
39
+ lines.push("- no timed steps recorded");
40
+ return lines.join("\n");
41
+ }
42
+ for (const entry of slowest) {
43
+ const status = entry.status ? ` [${entry.status}]` : "";
44
+ lines.push(`- ${entry.name}: ${formatDurationMs(entry.durationMs)}${status}`);
45
+ }
46
+ return lines.join("\n");
47
+ }
48
+ function formatTimingMarkdown(entries, { title = "Provider deploy timing summary", limit = 20 } = {}) {
49
+ const slowest = summarizeSlowestTimings(entries, limit);
50
+ const lines = [`### ${title}`, "", "| Step | Duration | Status |", "| --- | ---: | --- |"];
51
+ if (slowest.length === 0) {
52
+ lines.push("| No timed steps recorded | 0ms | skipped |");
53
+ return `${lines.join("\n")}
54
+ `;
55
+ }
56
+ for (const entry of slowest) {
57
+ lines.push(`| ${escapeMarkdownCell(entry.name)} | ${formatDurationMs(entry.durationMs)} | ${escapeMarkdownCell(entry.status ?? "")} |`);
58
+ }
59
+ return `${lines.join("\n")}
60
+ `;
61
+ }
62
+ function escapeMarkdownCell(value) {
63
+ return value.replace(/\\/gu, "\\\\").replace(/\|/gu, "\\|").replace(/\n/gu, " ");
64
+ }
65
+ export {
66
+ elapsedMs,
67
+ flattenTimings,
68
+ formatDurationMs,
69
+ formatTimingMarkdown,
70
+ formatTimingSummary,
71
+ nowMs,
72
+ summarizeSlowestTimings
73
+ };