@treeseed/sdk 0.10.27 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +207 -6
- package/dist/capacity-provider.d.ts +3 -1
- package/dist/capacity-provider.js +25 -5
- package/dist/control-plane.d.ts +1 -0
- package/dist/control-plane.js +38 -13
- package/dist/db/market-schema.d.ts +8860 -6172
- package/dist/db/market-schema.js +108 -0
- package/dist/db/node-sqlite.js +7 -2
- package/dist/hosting/apps.d.ts +12 -0
- package/dist/hosting/apps.js +107 -0
- package/dist/hosting/builtins.d.ts +25 -0
- package/dist/hosting/builtins.js +791 -0
- package/dist/hosting/contracts.d.ts +207 -0
- package/dist/hosting/contracts.js +0 -0
- package/dist/hosting/graph.d.ts +192 -0
- package/dist/hosting/graph.js +1106 -0
- package/dist/hosting/index.d.ts +4 -0
- package/dist/hosting/index.js +4 -0
- package/dist/index.d.ts +11 -4
- package/dist/index.js +71 -7
- package/dist/managed-dependencies.js +1 -2
- package/dist/market-client.d.ts +63 -3
- package/dist/market-client.js +83 -11
- package/dist/operations/services/bootstrap-runner.d.ts +3 -1
- package/dist/operations/services/bootstrap-runner.js +22 -2
- package/dist/operations/services/config-runtime.d.ts +10 -5
- package/dist/operations/services/config-runtime.js +209 -66
- package/dist/operations/services/deploy.d.ts +70 -7
- package/dist/operations/services/deploy.js +579 -64
- package/dist/operations/services/deployment-readiness.d.ts +30 -0
- package/dist/operations/services/deployment-readiness.js +175 -0
- package/dist/operations/services/git-workflow.d.ts +2 -1
- package/dist/operations/services/git-workflow.js +9 -3
- package/dist/operations/services/github-actions-verification.d.ts +1 -0
- package/dist/operations/services/github-actions-verification.js +1 -0
- package/dist/operations/services/github-api.js +1 -1
- package/dist/operations/services/github-automation.d.ts +1 -1
- package/dist/operations/services/github-automation.js +4 -3
- package/dist/operations/services/github-credentials.d.ts +13 -0
- package/dist/operations/services/github-credentials.js +58 -0
- package/dist/operations/services/hosted-service-checks.d.ts +63 -0
- package/dist/operations/services/hosted-service-checks.js +327 -0
- package/dist/operations/services/hub-provider-launch.js +3 -3
- package/dist/operations/services/live-hosted-service-checks.d.ts +25 -0
- package/dist/operations/services/live-hosted-service-checks.js +350 -0
- package/dist/operations/services/managed-host-security.js +1 -1
- package/dist/operations/services/operations-runner-smoke.d.ts +30 -0
- package/dist/operations/services/operations-runner-smoke.js +180 -0
- package/dist/operations/services/package-adapters.d.ts +95 -0
- package/dist/operations/services/package-adapters.js +288 -0
- package/dist/operations/services/package-reference-policy.d.ts +1 -0
- package/dist/operations/services/package-reference-policy.js +15 -2
- package/dist/operations/services/project-platform.d.ts +80 -22
- package/dist/operations/services/project-platform.js +49 -8
- package/dist/operations/services/project-web-monitor.js +26 -4
- package/dist/operations/services/railway-api.d.ts +88 -5
- package/dist/operations/services/railway-api.js +626 -35
- package/dist/operations/services/railway-deploy.d.ts +46 -40
- package/dist/operations/services/railway-deploy.js +261 -293
- package/dist/operations/services/release-candidate.d.ts +19 -0
- package/dist/operations/services/release-candidate.js +375 -38
- package/dist/operations/services/repository-save-orchestrator.d.ts +3 -1
- package/dist/operations/services/repository-save-orchestrator.js +279 -66
- package/dist/operations/services/runtime-tools.d.ts +1 -0
- package/dist/operations/services/runtime-tools.js +10 -9
- package/dist/operations/services/template-registry.js +14 -7
- package/dist/operations/services/verification-cache.d.ts +25 -0
- package/dist/operations/services/verification-cache.js +71 -0
- package/dist/operations/services/workspace-dependency-mode.js +9 -1
- package/dist/operations/services/workspace-save.js +1 -1
- package/dist/operations/services/workspace-tools.js +2 -1
- package/dist/platform/contracts.d.ts +32 -1
- package/dist/platform/deploy-config.js +73 -8
- package/dist/platform/env.yaml +163 -35
- package/dist/platform/environment.d.ts +1 -0
- package/dist/platform/environment.js +74 -5
- package/dist/platform/plugin.d.ts +9 -0
- package/dist/platform-operation-store.js +2 -2
- package/dist/platform-operations.js +1 -1
- package/dist/reconcile/bootstrap-systems.js +2 -2
- package/dist/reconcile/builtin-adapters.js +372 -189
- package/dist/reconcile/contracts.d.ts +9 -5
- package/dist/reconcile/desired-state.d.ts +1 -0
- package/dist/reconcile/desired-state.js +5 -5
- package/dist/reconcile/engine.d.ts +5 -2
- package/dist/reconcile/engine.js +53 -32
- package/dist/reconcile/index.d.ts +2 -0
- package/dist/reconcile/index.js +2 -0
- package/dist/reconcile/live-acceptance.d.ts +79 -0
- package/dist/reconcile/live-acceptance.js +1615 -0
- package/dist/reconcile/platform.d.ts +104 -0
- package/dist/reconcile/platform.js +100 -0
- package/dist/reconcile/state.js +4 -4
- package/dist/reconcile/units.js +2 -2
- package/dist/scripts/deployment-readiness.js +20 -0
- package/dist/scripts/generate-treedx-openapi-types.js +186 -0
- package/dist/scripts/operations-runner-smoke.js +16 -0
- package/dist/scripts/release-verify.js +4 -1
- package/dist/scripts/template-catalog.test.js +7 -7
- package/dist/scripts/tenant-workflow-action.js +10 -1
- package/dist/sdk-types.d.ts +172 -5
- package/dist/sdk-types.js +28 -3
- package/dist/sdk.d.ts +35 -24
- package/dist/sdk.js +186 -17
- package/dist/template-launch-requirements.js +9 -0
- package/dist/treedx/adapters.d.ts +6 -0
- package/dist/treedx/adapters.js +36 -0
- package/dist/treedx/client.d.ts +222 -0
- package/dist/treedx/client.js +871 -0
- package/dist/treedx/errors.d.ts +13 -0
- package/dist/treedx/errors.js +17 -0
- package/dist/treedx/federated-client.d.ts +27 -0
- package/dist/treedx/federated-client.js +158 -0
- package/dist/treedx/generated/openapi-types.d.ts +3558 -0
- package/dist/treedx/generated/openapi-types.js +0 -0
- package/dist/treedx/graph-adapter.d.ts +33 -0
- package/dist/treedx/graph-adapter.js +156 -0
- package/dist/treedx/index.d.ts +14 -0
- package/dist/treedx/index.js +48 -0
- package/dist/treedx/market-integration.d.ts +27 -0
- package/dist/treedx/market-integration.js +131 -0
- package/dist/treedx/ports.d.ts +166 -0
- package/dist/treedx/ports.js +231 -0
- package/dist/treedx/query-adapter.d.ts +19 -0
- package/dist/treedx/query-adapter.js +62 -0
- package/dist/treedx/registry-client.d.ts +11 -0
- package/dist/treedx/registry-client.js +19 -0
- package/dist/treedx/repository-adapter.d.ts +45 -0
- package/dist/treedx/repository-adapter.js +308 -0
- package/dist/treedx/sdk-integration.d.ts +27 -0
- package/dist/treedx/sdk-integration.js +63 -0
- package/dist/treedx/types.d.ts +1084 -0
- package/dist/treedx/types.js +8 -0
- package/dist/treedx/workspace-adapter.d.ts +27 -0
- package/dist/treedx/workspace-adapter.js +65 -0
- package/dist/treedx-backends.d.ts +218 -0
- package/dist/treedx-backends.js +632 -0
- package/dist/treedx-client.d.ts +86 -0
- package/dist/treedx-client.js +175 -0
- package/dist/treeseed/template-catalog/catalog.fixture.json +497 -138
- package/dist/workflow/operations.d.ts +119 -13
- package/dist/workflow/operations.js +309 -53
- package/dist/workflow-state.d.ts +13 -0
- package/dist/workflow-state.js +43 -26
- package/dist/workflow-support.d.ts +11 -3
- package/dist/workflow-support.js +67 -3
- package/dist/workflow.d.ts +5 -0
- package/drizzle/market/0004_treedx_market_integration.sql +99 -0
- package/package.json +34 -3
- package/templates/github/deploy-web.workflow.yml +39 -6
- package/dist/treeseed/template-catalog/templates/starter-basic/template/astro.config.d.ts +0 -3
- package/dist/treeseed/template-catalog/templates/starter-basic/template/astro.config.ts +0 -6
- package/dist/treeseed/template-catalog/templates/starter-basic/template/package.json +0 -35
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/api/server.js +0 -4
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/config.yaml +0 -65
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/decisions/adopt-initial-proposal-loop.mdx +0 -22
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/empty/.gitkeep +0 -1
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/knowledge/handbook/index.mdx +0 -11
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/pages/welcome.mdx +0 -11
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/people/starter-steward.mdx +0 -11
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/proposals/establish-initial-proposal-loop.mdx +0 -17
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content.config.d.ts +0 -1
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content.config.ts +0 -3
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/env.yaml +0 -1
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/manifest.yaml +0 -26
- package/dist/treeseed/template-catalog/templates/starter-basic/template/treeseed.site.yaml +0 -74
- package/dist/treeseed/template-catalog/templates/starter-basic/template/tsconfig.json +0 -9
- package/dist/treeseed/template-catalog/templates/starter-basic/template.config.json +0 -103
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { loadCliDeployConfig } from "./runtime-tools.js";
|
|
2
|
+
import {
|
|
3
|
+
getRailwayServiceInstance,
|
|
4
|
+
inspectRailwayServiceDeploymentHealth,
|
|
5
|
+
listRailwayEnvironments,
|
|
6
|
+
listRailwayProjects,
|
|
7
|
+
listRailwayServices,
|
|
8
|
+
listRailwayVariables,
|
|
9
|
+
listRailwayVolumes,
|
|
10
|
+
normalizeRailwayEnvironmentName,
|
|
11
|
+
resolveRailwayWorkspaceContext
|
|
12
|
+
} from "./railway-api.js";
|
|
13
|
+
import { configuredRailwayServices, findStaleTreeseedOperationsRunnerResources } from "./railway-deploy.js";
|
|
14
|
+
import { discoverTreeseedApplications } from "../../hosting/apps.js";
|
|
15
|
+
import {
|
|
16
|
+
collectTreeseedHostedServiceChecks
|
|
17
|
+
} from "./hosted-service-checks.js";
|
|
18
|
+
function sleep(ms) {
|
|
19
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
20
|
+
}
|
|
21
|
+
function normalizeBaseUrl(value) {
|
|
22
|
+
return typeof value === "string" && value.trim() ? value.trim().replace(/\/+$/u, "") : null;
|
|
23
|
+
}
|
|
24
|
+
function urlForDomain(value) {
|
|
25
|
+
const normalized = normalizeBaseUrl(value);
|
|
26
|
+
if (!normalized) return null;
|
|
27
|
+
return /^https?:\/\//iu.test(normalized) ? normalized : `https://${normalized}`;
|
|
28
|
+
}
|
|
29
|
+
function selectedWebConfig(deployConfig, selectedApplication) {
|
|
30
|
+
return selectedApplication?.roles.includes("web") ? selectedApplication.config : deployConfig;
|
|
31
|
+
}
|
|
32
|
+
function pagesBranchName(config, target) {
|
|
33
|
+
const pages = config.cloudflare?.pages && typeof config.cloudflare.pages === "object" ? config.cloudflare.pages : {};
|
|
34
|
+
const key = target === "prod" ? "productionBranch" : "stagingBranch";
|
|
35
|
+
const fallback = target === "prod" ? "main" : "staging";
|
|
36
|
+
return typeof pages[key] === "string" && pages[key].trim() ? pages[key].trim() : fallback;
|
|
37
|
+
}
|
|
38
|
+
async function observeHttp(url, options) {
|
|
39
|
+
const attempts = Math.max(1, Math.floor(options.retry?.attempts ?? 3));
|
|
40
|
+
const intervalMs = Math.max(0, Math.floor(options.retry?.intervalMs ?? 1500));
|
|
41
|
+
const timeoutMs = Math.max(1e3, Math.floor(options.timeoutMs ?? 1e4));
|
|
42
|
+
let lastError = "";
|
|
43
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
44
|
+
const controller = new AbortController();
|
|
45
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
46
|
+
try {
|
|
47
|
+
const response = await (options.fetchImpl ?? fetch)(url, {
|
|
48
|
+
method: "GET",
|
|
49
|
+
headers: { accept: "application/json, text/plain, */*" },
|
|
50
|
+
signal: controller.signal
|
|
51
|
+
});
|
|
52
|
+
clearTimeout(timeout);
|
|
53
|
+
return { status: response.status, ok: response.ok };
|
|
54
|
+
} catch (error) {
|
|
55
|
+
clearTimeout(timeout);
|
|
56
|
+
lastError = error instanceof Error ? error.message : String(error);
|
|
57
|
+
if (attempt + 1 < attempts) await sleep(intervalMs);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return { ok: false, error: lastError || "HTTP request failed." };
|
|
61
|
+
}
|
|
62
|
+
async function inspectRailwayServiceDeploymentHealthWithRetry(input) {
|
|
63
|
+
const attempts = Math.max(1, Math.floor(input.options.retry?.attempts ?? 3));
|
|
64
|
+
const intervalMs = Math.max(0, Math.floor(input.options.retry?.intervalMs ?? 1500));
|
|
65
|
+
let lastDeployment = null;
|
|
66
|
+
let lastError = null;
|
|
67
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
68
|
+
try {
|
|
69
|
+
const deployment = await inspectRailwayServiceDeploymentHealth({
|
|
70
|
+
serviceId: input.serviceId,
|
|
71
|
+
environmentId: input.environmentId,
|
|
72
|
+
env: input.options.env,
|
|
73
|
+
fetchImpl: input.options.fetchImpl
|
|
74
|
+
});
|
|
75
|
+
lastDeployment = deployment;
|
|
76
|
+
if (deployment.ok) return deployment;
|
|
77
|
+
} catch (error) {
|
|
78
|
+
lastError = error;
|
|
79
|
+
}
|
|
80
|
+
if (attempt + 1 < attempts) await sleep(intervalMs);
|
|
81
|
+
}
|
|
82
|
+
return lastDeployment ?? {
|
|
83
|
+
ok: false,
|
|
84
|
+
status: null,
|
|
85
|
+
message: lastError instanceof Error ? lastError.message : String(lastError ?? "Unable to inspect deployment health.")
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function findByName(items, nameOrId) {
|
|
89
|
+
if (!nameOrId) return null;
|
|
90
|
+
return items.find((item) => item.name === nameOrId || item.id === nameOrId) ?? null;
|
|
91
|
+
}
|
|
92
|
+
function indexedName(baseName, index) {
|
|
93
|
+
return `${baseName.replace(/-\d+$/u, "")}-${String(Math.max(1, index)).padStart(2, "0")}`;
|
|
94
|
+
}
|
|
95
|
+
function activeRailwayVolumeInstances(volume) {
|
|
96
|
+
const instances = Array.isArray(volume.instances) ? volume.instances : [];
|
|
97
|
+
return instances.filter((instance) => {
|
|
98
|
+
const state = String(instance.state ?? "READY").toUpperCase();
|
|
99
|
+
return state !== "DELETING" && state !== "DELETED";
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function railwayVolumeInstanceStates(volume) {
|
|
103
|
+
const states = (Array.isArray(volume.instances) ? volume.instances : []).map((instance) => String(instance.state ?? "READY").trim().toUpperCase()).filter(Boolean);
|
|
104
|
+
return states.length > 0 ? [...new Set(states)].join(",") : "none";
|
|
105
|
+
}
|
|
106
|
+
async function collectRailwayObservations(options) {
|
|
107
|
+
const observed = {};
|
|
108
|
+
const issues = [];
|
|
109
|
+
const inspectedVolumeScopes = /* @__PURE__ */ new Set();
|
|
110
|
+
const inspectedRunnerScopes = /* @__PURE__ */ new Set();
|
|
111
|
+
try {
|
|
112
|
+
const workspace = await resolveRailwayWorkspaceContext({ env: options.env, fetchImpl: options.fetchImpl });
|
|
113
|
+
const projects = await listRailwayProjects({ workspaceId: workspace.id, env: options.env, fetchImpl: options.fetchImpl });
|
|
114
|
+
const configuredServices = configuredRailwayServices(options.tenantRoot, options.target).filter((entry) => !options.appId || entry.application?.id === options.appId);
|
|
115
|
+
for (const service of configuredServices) {
|
|
116
|
+
const project = service.projectId ? findByName(projects, service.projectId) : findByName(projects, service.projectName);
|
|
117
|
+
if (!project?.id) {
|
|
118
|
+
issues.push(`${service.serviceName}: Railway project ${service.projectName} was not found.`);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const environments = await listRailwayEnvironments({ projectId: project.id, env: options.env, fetchImpl: options.fetchImpl });
|
|
122
|
+
const environment = findByName(environments, service.railwayEnvironment);
|
|
123
|
+
if (!environment?.id) {
|
|
124
|
+
issues.push(`${service.serviceName}: Railway environment ${service.railwayEnvironment} was not found.`);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const volumeScope = `${project.id}:${environment.id}`;
|
|
128
|
+
if (!inspectedVolumeScopes.has(volumeScope)) {
|
|
129
|
+
inspectedVolumeScopes.add(volumeScope);
|
|
130
|
+
const volumes2 = await listRailwayVolumes({ projectId: project.id, env: options.env, fetchImpl: options.fetchImpl }).catch(() => []);
|
|
131
|
+
for (const volume2 of volumes2) {
|
|
132
|
+
const detachedPostgresInstances = activeRailwayVolumeInstances(volume2).filter(
|
|
133
|
+
(instance2) => instance2.environmentId === environment.id && instance2.mountPath === "/var/lib/postgresql/data" && !instance2.serviceId
|
|
134
|
+
);
|
|
135
|
+
if (detachedPostgresInstances.length > 0) {
|
|
136
|
+
issues.push(`${volume2.name ?? volume2.id}: detached PostgreSQL volume remains in Railway project ${project.name} (states=${railwayVolumeInstanceStates(volume2)}).`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const services = await listRailwayServices({ projectId: project.id, env: options.env, fetchImpl: options.fetchImpl });
|
|
141
|
+
const runnerScope = `${project.id}:${environment.id}`;
|
|
142
|
+
if (service.key === "operationsRunner" && !inspectedRunnerScopes.has(runnerScope)) {
|
|
143
|
+
inspectedRunnerScopes.add(runnerScope);
|
|
144
|
+
const desiredRunnerNames = new Set(configuredServices.filter((entry) => entry.key === "operationsRunner").filter((entry) => entry.projectId ? entry.projectId === project.id : entry.projectName === project.name).filter((entry) => normalizeRailwayEnvironmentName(entry.railwayEnvironment) === normalizeRailwayEnvironmentName(environment.name)).map((entry) => entry.serviceName).filter(Boolean));
|
|
145
|
+
const desiredRunnerServiceIds = new Set(services.filter((entry) => desiredRunnerNames.has(entry.name)).map((entry) => entry.id));
|
|
146
|
+
for (const staleService of findStaleTreeseedOperationsRunnerResources(services, desiredRunnerNames)) {
|
|
147
|
+
issues.push(`${staleService.name}: stale operations runner Railway service remains in project ${project.name}.`);
|
|
148
|
+
}
|
|
149
|
+
const volumes2 = await listRailwayVolumes({ projectId: project.id, env: options.env, fetchImpl: options.fetchImpl }).catch(() => []);
|
|
150
|
+
const desiredRunnerVolumeNames = new Set([...desiredRunnerNames].map((name) => `${name}-volume`));
|
|
151
|
+
for (const staleVolume of findStaleTreeseedOperationsRunnerResources(volumes2, desiredRunnerVolumeNames)) {
|
|
152
|
+
const activeInstances = activeRailwayVolumeInstances(staleVolume);
|
|
153
|
+
const relevant = staleVolume.instances.length === 0 || activeInstances.length > 0;
|
|
154
|
+
const attachedToDesiredRunner = activeInstances.some((instance2) => desiredRunnerServiceIds.has(instance2.serviceId ?? ""));
|
|
155
|
+
if (relevant && !attachedToDesiredRunner) {
|
|
156
|
+
issues.push(`${staleVolume.name ?? staleVolume.id}: stale operations runner Railway volume remains in project ${project.name} (states=${railwayVolumeInstanceStates(staleVolume)}).`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const railwayService = service.serviceId ? findByName(services, service.serviceId) : findByName(services, service.serviceName);
|
|
161
|
+
if (!railwayService?.id) {
|
|
162
|
+
issues.push(`${service.serviceName}: Railway service was not found.`);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
const [instance, variables, volumes] = await Promise.all([
|
|
166
|
+
getRailwayServiceInstance({ serviceId: railwayService.id, environmentId: environment.id, env: options.env, fetchImpl: options.fetchImpl }),
|
|
167
|
+
listRailwayVariables({ projectId: project.id, environmentId: environment.id, serviceId: railwayService.id, env: options.env, fetchImpl: options.fetchImpl }).catch(() => ({})),
|
|
168
|
+
listRailwayVolumes({ projectId: project.id, env: options.env, fetchImpl: options.fetchImpl }).catch(() => [])
|
|
169
|
+
]);
|
|
170
|
+
const volume = Array.isArray(volumes) ? volumes.flatMap((entry) => Array.isArray(entry.instances) ? entry.instances : Array.isArray(entry.volumeInstances) ? entry.volumeInstances : []).find((entry) => entry?.serviceId === railwayService.id && entry?.environmentId === environment.id) : null;
|
|
171
|
+
const variableKeys = Object.keys(variables ?? {});
|
|
172
|
+
observed[service.serviceName] = {
|
|
173
|
+
projectName: project.name,
|
|
174
|
+
environmentName: environment.name,
|
|
175
|
+
serviceName: railwayService.name,
|
|
176
|
+
rootDirectory: instance.rootDirectory,
|
|
177
|
+
buildCommand: instance.buildCommand,
|
|
178
|
+
startCommand: instance.startCommand,
|
|
179
|
+
healthcheckPath: instance.healthcheckPath,
|
|
180
|
+
healthcheckTimeoutSeconds: instance.healthcheckTimeoutSeconds,
|
|
181
|
+
runtimeMode: service.runtimeMode ?? instance.runtimeMode,
|
|
182
|
+
volumeMountPath: volume?.mountPath ?? null,
|
|
183
|
+
variables: variableKeys,
|
|
184
|
+
secrets: variableKeys,
|
|
185
|
+
health: "unknown"
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
const deployConfig = loadCliDeployConfig(options.tenantRoot);
|
|
189
|
+
const appConfigs = [
|
|
190
|
+
...!options.appId || options.appId === "web" ? [deployConfig] : [],
|
|
191
|
+
...discoverTreeseedApplications(options.tenantRoot).filter((application) => application.id === "api" && (!options.appId || options.appId === "api")).map((application) => application.config)
|
|
192
|
+
];
|
|
193
|
+
for (const config of appConfigs) {
|
|
194
|
+
const nodePool = config.publicTreeDxFederation?.railway?.nodePool;
|
|
195
|
+
if (!nodePool && config.hosting?.kind !== "treeseed_control_plane") continue;
|
|
196
|
+
const bootstrapCount = Math.max(1, Number.parseInt(String(nodePool?.bootstrapCount ?? 1), 10) || 1);
|
|
197
|
+
const projectName = config.slug ?? "treeseed-api";
|
|
198
|
+
const environmentName = normalizeRailwayEnvironmentName(options.target) || options.target;
|
|
199
|
+
const project = findByName(projects, projectName);
|
|
200
|
+
if (!project?.id) {
|
|
201
|
+
issues.push(`public-treedx: Railway project ${projectName} was not found.`);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
const environments = await listRailwayEnvironments({ projectId: project.id, env: options.env, fetchImpl: options.fetchImpl });
|
|
205
|
+
const environment = findByName(environments, environmentName);
|
|
206
|
+
if (!environment?.id) {
|
|
207
|
+
issues.push(`public-treedx: Railway environment ${environmentName} was not found.`);
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
const [services, volumes] = await Promise.all([
|
|
211
|
+
listRailwayServices({ projectId: project.id, env: options.env, fetchImpl: options.fetchImpl }),
|
|
212
|
+
listRailwayVolumes({ projectId: project.id, env: options.env, fetchImpl: options.fetchImpl }).catch(() => [])
|
|
213
|
+
]);
|
|
214
|
+
for (let index = 1; index <= bootstrapCount; index += 1) {
|
|
215
|
+
const serviceName = indexedName("public-treedx-node", index);
|
|
216
|
+
const volumeName = `${serviceName}-volume`;
|
|
217
|
+
const configuredNode = Array.isArray(config.services) ? config.services.find((service2) => service2?.id === serviceName || service2?.name === serviceName) : null;
|
|
218
|
+
const volumeMountPath = typeof configuredNode?.volumeMountPath === "string" && configuredNode.volumeMountPath.trim() ? configuredNode.volumeMountPath.trim() : "/data";
|
|
219
|
+
const service = findByName(services, serviceName);
|
|
220
|
+
if (!service?.id) {
|
|
221
|
+
issues.push(`${serviceName}: public TreeDX Railway service was not found.`);
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
const deployment = await inspectRailwayServiceDeploymentHealthWithRetry({
|
|
225
|
+
serviceId: service.id,
|
|
226
|
+
environmentId: environment.id,
|
|
227
|
+
options
|
|
228
|
+
}).catch((error) => ({
|
|
229
|
+
ok: false,
|
|
230
|
+
status: null,
|
|
231
|
+
message: error instanceof Error ? error.message : String(error ?? "Unable to inspect TreeDX deployment health.")
|
|
232
|
+
}));
|
|
233
|
+
if (!deployment.ok) {
|
|
234
|
+
issues.push(`${serviceName}: public TreeDX latest deployment is not healthy. ${deployment.message}`);
|
|
235
|
+
}
|
|
236
|
+
const variables = await listRailwayVariables({ projectId: project.id, environmentId: environment.id, serviceId: service.id, env: options.env, fetchImpl: options.fetchImpl }).catch(() => ({}));
|
|
237
|
+
if (variables.TREEDX_FEDERATION_MODE !== "connected_library") {
|
|
238
|
+
issues.push(`${serviceName}: TREEDX_FEDERATION_MODE is not connected_library.`);
|
|
239
|
+
}
|
|
240
|
+
const mountedVolume = volumes.find((volume) => volume.name === volumeName && volume.instances.some(
|
|
241
|
+
(instance) => instance.serviceId === service.id && instance.environmentId === environment.id && instance.mountPath === volumeMountPath
|
|
242
|
+
));
|
|
243
|
+
if (!mountedVolume) {
|
|
244
|
+
issues.push(`${serviceName}: public TreeDX volume ${volumeName} is not mounted at ${volumeMountPath}.`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return { observed, status: "observed", issues };
|
|
249
|
+
} catch (error) {
|
|
250
|
+
return {
|
|
251
|
+
observed,
|
|
252
|
+
status: "failed",
|
|
253
|
+
issues: [...issues, error instanceof Error ? error.message : String(error)]
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
async function collectHttpObservations(options) {
|
|
258
|
+
const deployConfig = loadCliDeployConfig(options.tenantRoot);
|
|
259
|
+
const urls = /* @__PURE__ */ new Set();
|
|
260
|
+
const fallbacks = /* @__PURE__ */ new Map();
|
|
261
|
+
const selectedApplication = options.appId ? discoverTreeseedApplications(options.tenantRoot).find((application) => application.id === options.appId || application.relativeRoot === options.appId) : null;
|
|
262
|
+
if (!options.appId || options.appId === "web" || selectedApplication?.roles.includes("web")) {
|
|
263
|
+
const webConfig = selectedWebConfig(deployConfig, selectedApplication);
|
|
264
|
+
const webDomain = webConfig.surfaces?.web?.environments?.[options.target]?.domain ?? webConfig.surfaces?.web?.publicBaseUrl ?? webConfig.siteUrl;
|
|
265
|
+
const webUrl = urlForDomain(webDomain);
|
|
266
|
+
if (webUrl) {
|
|
267
|
+
urls.add(webUrl);
|
|
268
|
+
if (!options.appId || options.appId === "web" || selectedApplication?.roles.includes("api")) {
|
|
269
|
+
urls.add(`${webUrl}/v1/healthz`);
|
|
270
|
+
}
|
|
271
|
+
const pagesProjectName = webConfig.cloudflare?.pages?.projectName;
|
|
272
|
+
if (pagesProjectName) {
|
|
273
|
+
const branchName = pagesBranchName(webConfig, options.target);
|
|
274
|
+
const pagesUrl = options.target === "prod" ? `https://${pagesProjectName}.pages.dev` : `https://${branchName}.${pagesProjectName}.pages.dev`;
|
|
275
|
+
fallbacks.set(webUrl, pagesUrl);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
for (const service of configuredRailwayServices(options.tenantRoot, options.target).filter((entry) => !options.appId || entry.application?.id === options.appId)) {
|
|
280
|
+
const serviceConfig = deployConfig.services?.[service.key];
|
|
281
|
+
const domain = service.publicBaseUrl ?? serviceConfig?.environments?.[options.target]?.baseUrl ?? serviceConfig?.environments?.[options.target]?.domain ?? (service.key === "api" ? deployConfig.surfaces?.api?.environments?.[options.target]?.domain : null);
|
|
282
|
+
const baseUrl = urlForDomain(domain);
|
|
283
|
+
if (!baseUrl) continue;
|
|
284
|
+
urls.add(`${baseUrl}${service.healthcheckPath ?? "/healthz"}`);
|
|
285
|
+
if (service.key === "api") urls.add(`${baseUrl}/healthz/deep`);
|
|
286
|
+
}
|
|
287
|
+
const entries = await Promise.all([...urls].map(async (url) => {
|
|
288
|
+
const observed = await observeHttp(url, options);
|
|
289
|
+
const fallbackUrl = fallbacks.get(url);
|
|
290
|
+
if (observed.ok !== true && !observed.status && fallbackUrl) {
|
|
291
|
+
const fallback = await observeHttp(fallbackUrl, options);
|
|
292
|
+
return [url, {
|
|
293
|
+
...observed,
|
|
294
|
+
fallbackUrl,
|
|
295
|
+
fallbackStatus: fallback.status,
|
|
296
|
+
fallbackOk: fallback.ok,
|
|
297
|
+
fallbackError: fallback.error
|
|
298
|
+
}];
|
|
299
|
+
}
|
|
300
|
+
return [url, observed];
|
|
301
|
+
}));
|
|
302
|
+
return Object.fromEntries(entries);
|
|
303
|
+
}
|
|
304
|
+
function strictenReport(report, options) {
|
|
305
|
+
if (!options.strict) return report;
|
|
306
|
+
const checks = report.checks.map((check) => {
|
|
307
|
+
if (check.status !== "skipped") return check;
|
|
308
|
+
const providerRequired = options.requireLiveRailway && check.provider === "railway" || options.requireLiveHttp && check.provider === "http";
|
|
309
|
+
if (!providerRequired) return check;
|
|
310
|
+
return {
|
|
311
|
+
...check,
|
|
312
|
+
status: "failed",
|
|
313
|
+
issues: check.issues.length > 0 ? check.issues : ["Required live observation was not available."],
|
|
314
|
+
remediation: check.remediation ?? "Run provider configuration bootstrap or verify provider credentials, then rerun with --live."
|
|
315
|
+
};
|
|
316
|
+
});
|
|
317
|
+
const summary = {
|
|
318
|
+
passed: checks.filter((entry) => entry.status === "passed").length,
|
|
319
|
+
failed: checks.filter((entry) => entry.status === "failed").length,
|
|
320
|
+
skipped: checks.filter((entry) => entry.status === "skipped").length,
|
|
321
|
+
warning: checks.filter((entry) => entry.status === "warning").length
|
|
322
|
+
};
|
|
323
|
+
return { ...report, checks, summary };
|
|
324
|
+
}
|
|
325
|
+
async function collectTreeseedLiveHostedServiceChecks(options) {
|
|
326
|
+
const requireLiveRailway = options.requireLiveRailway ?? options.strict === true;
|
|
327
|
+
const requireLiveHttp = options.requireLiveHttp ?? options.strict === true;
|
|
328
|
+
const railway = requireLiveRailway ? await collectRailwayObservations(options) : { observed: {}, status: "skipped", issues: [] };
|
|
329
|
+
const httpChecks = requireLiveHttp ? await collectHttpObservations(options) : {};
|
|
330
|
+
const report = collectTreeseedHostedServiceChecks({
|
|
331
|
+
tenantRoot: options.tenantRoot,
|
|
332
|
+
target: options.target,
|
|
333
|
+
appId: options.appId,
|
|
334
|
+
observedRailwayServices: railway.observed,
|
|
335
|
+
httpChecks
|
|
336
|
+
});
|
|
337
|
+
const httpStatus = requireLiveHttp ? Object.values(httpChecks).some((entry) => entry.ok === true || entry.status) ? "observed" : "failed" : "skipped";
|
|
338
|
+
return strictenReport({
|
|
339
|
+
...report,
|
|
340
|
+
live: true,
|
|
341
|
+
liveObservation: {
|
|
342
|
+
railway: railway.status,
|
|
343
|
+
http: httpStatus,
|
|
344
|
+
issues: railway.issues
|
|
345
|
+
}
|
|
346
|
+
}, options);
|
|
347
|
+
}
|
|
348
|
+
export {
|
|
349
|
+
collectTreeseedLiveHostedServiceChecks
|
|
350
|
+
};
|
|
@@ -5,7 +5,7 @@ const SAFE_MANAGED_HOST_CI_VARIABLES = /* @__PURE__ */ new Set([
|
|
|
5
5
|
"TREESEED_HOSTING_KIND",
|
|
6
6
|
"TREESEED_HOSTING_REGISTRATION",
|
|
7
7
|
"TREESEED_HOSTING_TEAM_ID",
|
|
8
|
-
"
|
|
8
|
+
"TREESEED_API_BASE_URL",
|
|
9
9
|
"TREESEED_PROJECT_ID"
|
|
10
10
|
]);
|
|
11
11
|
const SAFE_MANAGED_HOST_CI_SECRETS = /* @__PURE__ */ new Set([
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface TreeseedOperationsRunnerSmokeReport {
|
|
2
|
+
environment: 'staging' | 'prod';
|
|
3
|
+
ok: boolean;
|
|
4
|
+
baseUrl: string;
|
|
5
|
+
operationId?: string;
|
|
6
|
+
finalStatus?: string;
|
|
7
|
+
runnerId?: string | null;
|
|
8
|
+
timings: Array<{
|
|
9
|
+
phase: string;
|
|
10
|
+
durationMs: number;
|
|
11
|
+
}>;
|
|
12
|
+
events: Array<{
|
|
13
|
+
kind: string;
|
|
14
|
+
createdAt: string;
|
|
15
|
+
}>;
|
|
16
|
+
issues: string[];
|
|
17
|
+
remediation?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface TreeseedOperationsRunnerSmokeOptions {
|
|
20
|
+
tenantRoot: string;
|
|
21
|
+
environment: 'staging' | 'prod';
|
|
22
|
+
baseUrl?: string | null;
|
|
23
|
+
serviceId?: string | null;
|
|
24
|
+
serviceSecret?: string | null;
|
|
25
|
+
timeoutMs?: number;
|
|
26
|
+
pollMs?: number;
|
|
27
|
+
fetchImpl?: typeof fetch;
|
|
28
|
+
env?: NodeJS.ProcessEnv | Record<string, string | undefined>;
|
|
29
|
+
}
|
|
30
|
+
export declare function runTreeseedOperationsRunnerSmoke(options: TreeseedOperationsRunnerSmokeOptions): Promise<TreeseedOperationsRunnerSmokeReport>;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { loadTreeseedDeployConfigFromPath } from "../../platform/deploy-config.js";
|
|
4
|
+
import { resolveTreeseedMachineEnvironmentValues } from "./config-runtime.js";
|
|
5
|
+
function sleep(ms) {
|
|
6
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
7
|
+
}
|
|
8
|
+
function normalizedBaseUrl(value2) {
|
|
9
|
+
return value2.trim().replace(/\/+$/u, "");
|
|
10
|
+
}
|
|
11
|
+
function value(name, values, env) {
|
|
12
|
+
const candidate = env[name] ?? values[name];
|
|
13
|
+
return typeof candidate === "string" && candidate.trim() ? candidate.trim() : null;
|
|
14
|
+
}
|
|
15
|
+
function manifestApiBaseUrl(tenantRoot, environment) {
|
|
16
|
+
const candidates = [
|
|
17
|
+
resolve(tenantRoot, "treeseed.site.yaml"),
|
|
18
|
+
resolve(tenantRoot, "..", "..", "treeseed.site.yaml")
|
|
19
|
+
];
|
|
20
|
+
for (const candidate of candidates) {
|
|
21
|
+
if (!existsSync(candidate)) continue;
|
|
22
|
+
try {
|
|
23
|
+
const config = loadTreeseedDeployConfigFromPath(candidate);
|
|
24
|
+
const connectionUrl = config.connections?.api?.environments?.[environment]?.baseUrl;
|
|
25
|
+
const serviceUrl = config.services?.api?.environments?.[environment]?.baseUrl;
|
|
26
|
+
const baseUrl = connectionUrl ?? serviceUrl;
|
|
27
|
+
if (typeof baseUrl === "string" && baseUrl.trim()) {
|
|
28
|
+
return baseUrl.trim();
|
|
29
|
+
}
|
|
30
|
+
} catch {
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
async function timed(timings, phase, action) {
|
|
36
|
+
const started = Date.now();
|
|
37
|
+
try {
|
|
38
|
+
return await action();
|
|
39
|
+
} finally {
|
|
40
|
+
timings.push({ phase, durationMs: Date.now() - started });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async function requestJson(fetchImpl, url, options = {}) {
|
|
44
|
+
const response = await fetchImpl(url, {
|
|
45
|
+
...options,
|
|
46
|
+
headers: {
|
|
47
|
+
accept: "application/json",
|
|
48
|
+
...options.body ? { "content-type": "application/json" } : {},
|
|
49
|
+
...options.headers ?? {}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
const text = await response.text();
|
|
53
|
+
const payload = text ? JSON.parse(text) : {};
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
const message = typeof payload?.error === "string" ? payload.error : typeof payload?.error?.message === "string" ? payload.error.message : typeof payload?.message === "string" ? payload.message : `HTTP ${response.status}`;
|
|
56
|
+
throw new Error(`${options.method ?? "GET"} ${url} failed: ${message}`);
|
|
57
|
+
}
|
|
58
|
+
return payload;
|
|
59
|
+
}
|
|
60
|
+
function operationFrom(payload) {
|
|
61
|
+
return payload?.operation ?? payload?.payload?.operation ?? null;
|
|
62
|
+
}
|
|
63
|
+
function eventsFrom(payload) {
|
|
64
|
+
const events = Array.isArray(payload?.events) ? payload.events : Array.isArray(payload?.payload?.events) ? payload.payload.events : [];
|
|
65
|
+
return events.map((event) => ({
|
|
66
|
+
kind: String(event?.kind ?? "unknown"),
|
|
67
|
+
createdAt: String(event?.createdAt ?? event?.created_at ?? "")
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
function isSuccessfulOperationStatus(status) {
|
|
71
|
+
return ["completed", "succeeded"].includes(String(status ?? "").toLowerCase());
|
|
72
|
+
}
|
|
73
|
+
function failure(baseUrl, environment, issues, timings, extra = {}) {
|
|
74
|
+
return {
|
|
75
|
+
environment,
|
|
76
|
+
ok: false,
|
|
77
|
+
baseUrl,
|
|
78
|
+
timings,
|
|
79
|
+
events: [],
|
|
80
|
+
issues,
|
|
81
|
+
remediation: "Verify the API service, Treeseed database, and operationsRunner Railway service, then run `npx trsd hosting verify --service operationsRunner --live --json`.",
|
|
82
|
+
...extra
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
async function runTreeseedOperationsRunnerSmoke(options) {
|
|
86
|
+
const env = options.env ?? process.env;
|
|
87
|
+
let values = {};
|
|
88
|
+
try {
|
|
89
|
+
values = resolveTreeseedMachineEnvironmentValues(options.tenantRoot, options.environment);
|
|
90
|
+
} catch {
|
|
91
|
+
values = {};
|
|
92
|
+
}
|
|
93
|
+
const baseUrl = normalizedBaseUrl(
|
|
94
|
+
options.baseUrl ?? manifestApiBaseUrl(options.tenantRoot, options.environment) ?? value("TREESEED_API_BASE_URL", values, env) ?? value("TREESEED_CENTRAL_MARKET_API_BASE_URL", values, env) ?? (options.environment === "prod" ? "https://api.treeseed.ai" : "https://api-treeseed-staging.treeseed.ai")
|
|
95
|
+
);
|
|
96
|
+
const serviceId = options.serviceId ?? value("TREESEED_ACCEPTANCE_SERVICE_ID", values, env) ?? value("TREESEED_API_WEB_SERVICE_ID", values, env) ?? value("TREESEED_WEB_SERVICE_ID", values, env) ?? "web";
|
|
97
|
+
const serviceSecret = options.serviceSecret ?? value("TREESEED_ACCEPTANCE_SERVICE_SECRET", values, env) ?? value("TREESEED_API_WEB_SERVICE_SECRET", values, env) ?? value("TREESEED_WEB_SERVICE_SECRET", values, env);
|
|
98
|
+
const timings = [];
|
|
99
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
100
|
+
const timeoutMs = Math.max(1e3, Math.floor(options.timeoutMs ?? (options.environment === "prod" ? 12e4 : 9e4)));
|
|
101
|
+
const pollMs = Math.max(1e3, Math.floor(options.pollMs ?? 3e3));
|
|
102
|
+
if (!serviceSecret) {
|
|
103
|
+
return failure(baseUrl, options.environment, ["Missing API service credential for runner smoke."], timings);
|
|
104
|
+
}
|
|
105
|
+
const headers = {
|
|
106
|
+
"x-treeseed-service-id": serviceId,
|
|
107
|
+
"x-treeseed-service-secret": serviceSecret
|
|
108
|
+
};
|
|
109
|
+
try {
|
|
110
|
+
await timed(timings, "healthz", () => requestJson(fetchImpl, `${baseUrl}/healthz`));
|
|
111
|
+
await timed(timings, "healthz-deep", () => requestJson(fetchImpl, `${baseUrl}/healthz/deep`));
|
|
112
|
+
const idempotencyKey = `smoke:${options.environment}:${Date.now()}`;
|
|
113
|
+
const created = await timed(timings, "operation-create", () => requestJson(fetchImpl, `${baseUrl}/v1/platform/operations`, {
|
|
114
|
+
method: "POST",
|
|
115
|
+
headers,
|
|
116
|
+
body: JSON.stringify({
|
|
117
|
+
namespace: "market",
|
|
118
|
+
operation: "diagnostic",
|
|
119
|
+
target: "market_operations_runner",
|
|
120
|
+
idempotencyKey,
|
|
121
|
+
input: {
|
|
122
|
+
source: "trsd.operations.smoke",
|
|
123
|
+
environment: options.environment
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
}));
|
|
127
|
+
const operation = operationFrom(created);
|
|
128
|
+
const operationId = typeof operation?.id === "string" ? operation.id : null;
|
|
129
|
+
if (!operationId) {
|
|
130
|
+
return failure(baseUrl, options.environment, ["API did not return a diagnostic operation id."], timings);
|
|
131
|
+
}
|
|
132
|
+
const deadline = Date.now() + timeoutMs;
|
|
133
|
+
let current = operation;
|
|
134
|
+
while (Date.now() < deadline) {
|
|
135
|
+
current = operationFrom(await timed(timings, "operation-poll", () => requestJson(fetchImpl, `${baseUrl}/v1/platform/operations/${encodeURIComponent(operationId)}`, { headers }))) ?? current;
|
|
136
|
+
if (isSuccessfulOperationStatus(current?.status)) {
|
|
137
|
+
const eventPayload = await timed(timings, "operation-events", () => requestJson(fetchImpl, `${baseUrl}/v1/platform/operations/${encodeURIComponent(operationId)}/events`, { headers }));
|
|
138
|
+
const events = eventsFrom(eventPayload);
|
|
139
|
+
const sawRunnerEvent = events.some((event) => /checkpoint|complete|runner|market\.noop/iu.test(event.kind));
|
|
140
|
+
if (!sawRunnerEvent) {
|
|
141
|
+
return failure(baseUrl, options.environment, ["Diagnostic operation completed but no runner checkpoint/completion event was recorded."], timings, {
|
|
142
|
+
operationId,
|
|
143
|
+
finalStatus: String(current?.status ?? "completed"),
|
|
144
|
+
runnerId: current?.assignedRunnerId ?? null,
|
|
145
|
+
events
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
environment: options.environment,
|
|
150
|
+
ok: true,
|
|
151
|
+
baseUrl,
|
|
152
|
+
operationId,
|
|
153
|
+
finalStatus: String(current?.status ?? "completed"),
|
|
154
|
+
runnerId: current?.assignedRunnerId ?? null,
|
|
155
|
+
timings,
|
|
156
|
+
events,
|
|
157
|
+
issues: []
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
if (["failed", "cancelled", "timed_out"].includes(String(current?.status ?? ""))) {
|
|
161
|
+
return failure(baseUrl, options.environment, [`Diagnostic operation ended with status ${current.status}.`], timings, {
|
|
162
|
+
operationId,
|
|
163
|
+
finalStatus: current.status,
|
|
164
|
+
runnerId: current?.assignedRunnerId ?? null
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
await sleep(pollMs);
|
|
168
|
+
}
|
|
169
|
+
return failure(baseUrl, options.environment, [`Diagnostic operation was not completed within ${timeoutMs}ms.`], timings, {
|
|
170
|
+
operationId,
|
|
171
|
+
finalStatus: current?.status ?? "unknown",
|
|
172
|
+
runnerId: current?.assignedRunnerId ?? null
|
|
173
|
+
});
|
|
174
|
+
} catch (error) {
|
|
175
|
+
return failure(baseUrl, options.environment, [error instanceof Error ? error.message : String(error)], timings);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
export {
|
|
179
|
+
runTreeseedOperationsRunnerSmoke
|
|
180
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
export type TreeseedPackageKind = 'node-typescript' | 'beam-elixir-rust';
|
|
2
|
+
export type TreeseedPackageCommand = {
|
|
3
|
+
label: string;
|
|
4
|
+
command: string;
|
|
5
|
+
args: string[];
|
|
6
|
+
cwd: string;
|
|
7
|
+
};
|
|
8
|
+
export type TreeseedPackageAdapter = {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
kind: TreeseedPackageKind;
|
|
12
|
+
dir: string;
|
|
13
|
+
relativeDir: string;
|
|
14
|
+
version: string | null;
|
|
15
|
+
publishTarget: string | null;
|
|
16
|
+
manifestPath: string | null;
|
|
17
|
+
versionSource: string | null;
|
|
18
|
+
verifyCommands: {
|
|
19
|
+
fast: TreeseedPackageCommand | null;
|
|
20
|
+
local: TreeseedPackageCommand | null;
|
|
21
|
+
release: TreeseedPackageCommand | null;
|
|
22
|
+
};
|
|
23
|
+
artifacts: Array<{
|
|
24
|
+
provider: 'npm' | 'docker';
|
|
25
|
+
name: string;
|
|
26
|
+
tags?: string[];
|
|
27
|
+
}>;
|
|
28
|
+
releaseChecks: Array<{
|
|
29
|
+
kind: 'npm-pack-dry-run' | 'github-workflow' | 'docker-manifest';
|
|
30
|
+
name: string;
|
|
31
|
+
detail: string;
|
|
32
|
+
}>;
|
|
33
|
+
metadata: Record<string, unknown>;
|
|
34
|
+
};
|
|
35
|
+
export type TreeseedPackageDevelopmentImagePlan = {
|
|
36
|
+
package: {
|
|
37
|
+
id: string;
|
|
38
|
+
name: string;
|
|
39
|
+
path: string;
|
|
40
|
+
kind: TreeseedPackageKind;
|
|
41
|
+
version: string | null;
|
|
42
|
+
publishTarget: string | null;
|
|
43
|
+
metadata: Record<string, unknown>;
|
|
44
|
+
};
|
|
45
|
+
repository: string;
|
|
46
|
+
workflow: string;
|
|
47
|
+
branch: string;
|
|
48
|
+
refs: {
|
|
49
|
+
imageName: string;
|
|
50
|
+
branch: string;
|
|
51
|
+
branchSlug: string;
|
|
52
|
+
sha: string;
|
|
53
|
+
shortSha: string;
|
|
54
|
+
immutableTag: string;
|
|
55
|
+
movingTag: string | null;
|
|
56
|
+
imageRef: string;
|
|
57
|
+
movingImageRef: string | null;
|
|
58
|
+
archImageRefs: string[];
|
|
59
|
+
};
|
|
60
|
+
hosting: {
|
|
61
|
+
app: string;
|
|
62
|
+
environment: string;
|
|
63
|
+
overrideEnvVar: string;
|
|
64
|
+
override: Record<string, string>;
|
|
65
|
+
command: string;
|
|
66
|
+
} | null;
|
|
67
|
+
};
|
|
68
|
+
export declare function readMixProjectVersion(filePath: string): string | null;
|
|
69
|
+
export declare function planTreeseedPackageDevelopmentImage(root: any, idOrName: string, { branch }?: {
|
|
70
|
+
branch?: string | null;
|
|
71
|
+
}): TreeseedPackageDevelopmentImagePlan;
|
|
72
|
+
export declare function discoverTreeseedPackageAdapters(root?: any): TreeseedPackageAdapter[];
|
|
73
|
+
export declare function findTreeseedPackageAdapter(root: string, idOrName: string): TreeseedPackageAdapter | null;
|
|
74
|
+
export declare function packageAdapterPlanSummary(root?: any): {
|
|
75
|
+
id: string;
|
|
76
|
+
name: string;
|
|
77
|
+
kind: TreeseedPackageKind;
|
|
78
|
+
path: string;
|
|
79
|
+
version: string | null;
|
|
80
|
+
publishTarget: string | null;
|
|
81
|
+
verify: {
|
|
82
|
+
[k: string]: string | null;
|
|
83
|
+
};
|
|
84
|
+
artifacts: {
|
|
85
|
+
provider: "npm" | "docker";
|
|
86
|
+
name: string;
|
|
87
|
+
tags?: string[];
|
|
88
|
+
}[];
|
|
89
|
+
releaseChecks: {
|
|
90
|
+
kind: "npm-pack-dry-run" | "github-workflow" | "docker-manifest";
|
|
91
|
+
name: string;
|
|
92
|
+
detail: string;
|
|
93
|
+
}[];
|
|
94
|
+
metadata: Record<string, unknown>;
|
|
95
|
+
}[];
|