@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,30 @@
|
|
|
1
|
+
import { type TreeseedHostingEnvironment } from '../../hosting/index.ts';
|
|
2
|
+
export type TreeseedDeploymentReadinessStatus = 'passed' | 'failed' | 'warning' | 'skipped';
|
|
3
|
+
export interface TreeseedDeploymentReadinessCheck {
|
|
4
|
+
id: string;
|
|
5
|
+
status: TreeseedDeploymentReadinessStatus;
|
|
6
|
+
expected?: Record<string, unknown>;
|
|
7
|
+
observed?: Record<string, unknown>;
|
|
8
|
+
message: string;
|
|
9
|
+
remediation?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface TreeseedDeploymentReadinessReport {
|
|
12
|
+
environment: TreeseedHostingEnvironment;
|
|
13
|
+
ok: boolean;
|
|
14
|
+
generatedAt: string;
|
|
15
|
+
checks: TreeseedDeploymentReadinessCheck[];
|
|
16
|
+
summary: {
|
|
17
|
+
passed: number;
|
|
18
|
+
failed: number;
|
|
19
|
+
warning: number;
|
|
20
|
+
skipped: number;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export interface TreeseedDeploymentReadinessOptions {
|
|
24
|
+
tenantRoot: string;
|
|
25
|
+
environment: TreeseedHostingEnvironment;
|
|
26
|
+
appId?: string;
|
|
27
|
+
now?: Date;
|
|
28
|
+
}
|
|
29
|
+
export declare function collectTreeseedDeploymentReadiness(options: TreeseedDeploymentReadinessOptions): TreeseedDeploymentReadinessReport;
|
|
30
|
+
export declare function formatTreeseedReadinessReport(report: TreeseedDeploymentReadinessReport): string;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { relative, resolve } from "node:path";
|
|
2
|
+
import { compileTreeseedHostingGraph, serializeHostingUnit } from "../../hosting/index.js";
|
|
3
|
+
import { configuredRailwayServices } from "./railway-deploy.js";
|
|
4
|
+
function relRoot(tenantRoot, path) {
|
|
5
|
+
const value = relative(tenantRoot, path).split("\\").join("/");
|
|
6
|
+
return value || ".";
|
|
7
|
+
}
|
|
8
|
+
function summary(checks) {
|
|
9
|
+
return {
|
|
10
|
+
passed: checks.filter((check2) => check2.status === "passed").length,
|
|
11
|
+
failed: checks.filter((check2) => check2.status === "failed").length,
|
|
12
|
+
warning: checks.filter((check2) => check2.status === "warning").length,
|
|
13
|
+
skipped: checks.filter((check2) => check2.status === "skipped").length
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function check(id, actual, expected, message, remediation, key = id.split(":").at(-1) ?? "value") {
|
|
17
|
+
const passed = actual === expected;
|
|
18
|
+
return {
|
|
19
|
+
id,
|
|
20
|
+
status: passed ? "passed" : "failed",
|
|
21
|
+
expected: { [key]: expected },
|
|
22
|
+
observed: { [key]: actual ?? null },
|
|
23
|
+
message: passed ? message : `${message} Expected ${String(expected)}, observed ${String(actual ?? "(unset)")}.`,
|
|
24
|
+
remediation: passed ? void 0 : remediation
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function checkIncludes(id, values, required, message, remediation) {
|
|
28
|
+
const observed = Array.isArray(values) ? values.map(String) : [];
|
|
29
|
+
const missing = required.filter((value) => !observed.includes(value));
|
|
30
|
+
return {
|
|
31
|
+
id,
|
|
32
|
+
status: missing.length === 0 ? "passed" : "failed",
|
|
33
|
+
expected: { includes: required },
|
|
34
|
+
observed: { values: observed },
|
|
35
|
+
message: missing.length === 0 ? message : `${message} Missing: ${missing.join(", ")}.`,
|
|
36
|
+
remediation: missing.length === 0 ? void 0 : remediation
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function unitById(units, id) {
|
|
40
|
+
return units.find((unit) => unit.id === id) ?? null;
|
|
41
|
+
}
|
|
42
|
+
function railwayServiceByKey(services, key) {
|
|
43
|
+
return services.find((service) => service.key === key) ?? null;
|
|
44
|
+
}
|
|
45
|
+
function collectTreeseedDeploymentReadiness(options) {
|
|
46
|
+
const tenantRoot = resolve(options.tenantRoot);
|
|
47
|
+
const environment = options.environment;
|
|
48
|
+
const graph = compileTreeseedHostingGraph({ tenantRoot, environment, appId: options.appId });
|
|
49
|
+
const units = graph.units.map((unit) => serializeHostingUnit(unit));
|
|
50
|
+
const railwayServices = configuredRailwayServices(tenantRoot, environment).filter((service) => !options.appId || service.application?.id === options.appId);
|
|
51
|
+
const checks = [];
|
|
52
|
+
const web = unitById(units, "web");
|
|
53
|
+
const api = unitById(units, "api");
|
|
54
|
+
const runner = unitById(units, "operationsRunner");
|
|
55
|
+
const database = unitById(units, "treeseedDatabase");
|
|
56
|
+
const hasApiPlane = Boolean(api || runner || database);
|
|
57
|
+
const hasWorkspaceMarketControlPlane = graph.applications?.some((app) => app.config.hosting?.kind === "treeseed_control_plane") === true || graph.deployConfig.hosting?.kind === "treeseed_control_plane";
|
|
58
|
+
const railwayApi = railwayServiceByKey(railwayServices, "api");
|
|
59
|
+
const railwayRunner = railwayServiceByKey(railwayServices, "operationsRunner");
|
|
60
|
+
if (!web) {
|
|
61
|
+
if (!hasApiPlane) {
|
|
62
|
+
checks.push({
|
|
63
|
+
id: "hosting:web:present",
|
|
64
|
+
status: "failed",
|
|
65
|
+
message: "Web hosting unit is missing from the effective hosting graph.",
|
|
66
|
+
remediation: "Restore surfaces.web in treeseed.site.yaml."
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
checks.push(check("hosting:web:host", web.hostId, environment === "local" ? "local-process" : "cloudflare", "Web host matches the expected environment host.", "Set surfaces.web.provider to cloudflare for hosted environments.", "hostId"));
|
|
71
|
+
checks.push(check("hosting:web:rootDir", web.config?.rootDir, ".", "Web rootDir is the top-level UI application.", 'Set surfaces.web.rootDir to ".".', "rootDir"));
|
|
72
|
+
const apiConnection = graph.deployConfig.connections?.api;
|
|
73
|
+
const configuredBaseUrl = environment === "local" ? apiConnection?.localBaseUrl : apiConnection?.environments?.[environment]?.baseUrl ?? apiConnection?.environments?.[environment]?.domain;
|
|
74
|
+
checks.push({
|
|
75
|
+
id: "connection:api",
|
|
76
|
+
status: hasApiPlane || configuredBaseUrl ? "passed" : "failed",
|
|
77
|
+
expected: { apiConnection: true },
|
|
78
|
+
observed: {
|
|
79
|
+
localApiAppPresent: hasApiPlane,
|
|
80
|
+
baseUrl: configuredBaseUrl ?? null,
|
|
81
|
+
proxyPrefix: apiConnection?.proxyPrefix ?? null
|
|
82
|
+
},
|
|
83
|
+
message: hasApiPlane || configuredBaseUrl ? "Web app has an API app or configured API connection." : "Web app requires an API connection when no local API app manifest is present.",
|
|
84
|
+
remediation: hasApiPlane || configuredBaseUrl ? void 0 : "Add connections.api to treeseed.site.yaml or include packages/api/treeseed.site.yaml in the workspace."
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
if (!hasApiPlane && !hasWorkspaceMarketControlPlane) {
|
|
88
|
+
const counts2 = summary(checks);
|
|
89
|
+
return {
|
|
90
|
+
environment,
|
|
91
|
+
ok: counts2.failed === 0,
|
|
92
|
+
generatedAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
93
|
+
checks,
|
|
94
|
+
summary: counts2
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const apiIsNestedApp = Boolean(api?.application?.relativeRoot && api.application.relativeRoot !== ".");
|
|
98
|
+
const runnerIsNestedApp = Boolean(runner?.application?.relativeRoot && runner.application.relativeRoot !== ".");
|
|
99
|
+
const expectedApiUnitRoot = apiIsNestedApp ? "." : "packages/api";
|
|
100
|
+
const expectedRunnerUnitRoot = runnerIsNestedApp ? "." : "packages/api";
|
|
101
|
+
const expectedApiRailwayRoot = api?.application?.relativeRoot && api.application.relativeRoot !== "." ? api.application.relativeRoot : expectedApiUnitRoot;
|
|
102
|
+
const expectedRunnerRailwayRoot = runner?.application?.relativeRoot && runner.application.relativeRoot !== "." ? runner.application.relativeRoot : expectedRunnerUnitRoot;
|
|
103
|
+
if (!api) {
|
|
104
|
+
checks.push({
|
|
105
|
+
id: "hosting:api:present",
|
|
106
|
+
status: "failed",
|
|
107
|
+
message: "API hosting unit is missing from the effective hosting graph.",
|
|
108
|
+
remediation: "Restore services.api in treeseed.site.yaml."
|
|
109
|
+
});
|
|
110
|
+
} else {
|
|
111
|
+
checks.push(check("hosting:api:host", api.hostId, environment === "local" ? "local-process" : "railway", "API host matches the expected environment host.", "Set services.api.provider to railway for hosted environments.", "hostId"));
|
|
112
|
+
checks.push(check("hosting:api:projectGroup", api.projectGroupId, "treeseed-control-plane", "API project group targets the Treeseed control plane.", "Bind services.api to the treeseed-control-plane project group.", "projectGroupId"));
|
|
113
|
+
checks.push(check("hosting:api:rootDir", api.config?.rootDir, expectedApiUnitRoot, "API effective rootDir points at the API package.", "Set services.api.rootDir and services.api.railway.rootDir relative to the owning API manifest.", "rootDir"));
|
|
114
|
+
checks.push(check("hosting:api:buildCommand", api.config?.buildCommand, "npm run build", "API build command is package-local.", 'Set services.api.railway.buildCommand to "npm run build".', "buildCommand"));
|
|
115
|
+
checks.push(check("hosting:api:startCommand", api.config?.startCommand, "npm run start:api", "API start command is package-local.", 'Set services.api.railway.startCommand to "npm run start:api".', "startCommand"));
|
|
116
|
+
checks.push(check("hosting:api:healthcheckPath", api.config?.healthcheckPath, "/healthz", "API healthcheck path is /healthz.", "Set services.api.railway.healthcheckPath to /healthz.", "healthcheckPath"));
|
|
117
|
+
}
|
|
118
|
+
if (railwayApi) {
|
|
119
|
+
checks.push(check("railway-config:api:serviceName", railwayApi.serviceName, "treeseed-api", "Railway API service uses the canonical name.", "Set services.api.railway.serviceName to treeseed-api.", "serviceName"));
|
|
120
|
+
checks.push(check("railway-config:api:rootDirectory", relRoot(tenantRoot, railwayApi.rootDir), expectedApiRailwayRoot, "Railway API effective rootDirectory points at the API app.", "Set services.api.railway.rootDir relative to the owning API manifest.", "rootDirectory"));
|
|
121
|
+
}
|
|
122
|
+
if (!runner) {
|
|
123
|
+
checks.push({
|
|
124
|
+
id: "hosting:operationsRunner:present",
|
|
125
|
+
status: "failed",
|
|
126
|
+
message: "Treeseed operations runner hosting unit is missing from the effective hosting graph.",
|
|
127
|
+
remediation: "Restore services.operationsRunner in treeseed.site.yaml."
|
|
128
|
+
});
|
|
129
|
+
} else {
|
|
130
|
+
checks.push(check("hosting:operationsRunner:host", runner.hostId, environment === "local" ? "local-docker" : "railway", "Runner host matches the expected environment host.", "Set services.operationsRunner.provider to railway for hosted environments.", "hostId"));
|
|
131
|
+
checks.push(check("hosting:operationsRunner:projectGroup", runner.projectGroupId, "treeseed-control-plane", "Runner project group targets the Treeseed control plane.", "Bind services.operationsRunner to the treeseed-control-plane project group.", "projectGroupId"));
|
|
132
|
+
checks.push(check("hosting:operationsRunner:rootDir", runner.config?.rootDir, expectedRunnerUnitRoot, "Runner effective rootDir points at the API package.", "Set services.operationsRunner.rootDir and services.operationsRunner.railway.rootDir relative to the owning API manifest.", "rootDir"));
|
|
133
|
+
checks.push(check("hosting:operationsRunner:buildCommand", runner.config?.buildCommand, "npm run build", "Runner build command is package-local.", 'Set services.operationsRunner.railway.buildCommand to "npm run build".', "buildCommand"));
|
|
134
|
+
checks.push(check("hosting:operationsRunner:startCommand", runner.config?.startCommand, "npm run start:runner", "Runner start command is package-local.", 'Set services.operationsRunner.railway.startCommand to "npm run start:runner".', "startCommand"));
|
|
135
|
+
checks.push(check("hosting:operationsRunner:healthcheckPath", runner.config?.healthcheckPath, "/healthz", "Runner healthcheck path is /healthz.", "Set services.operationsRunner.railway.healthcheckPath to /healthz.", "healthcheckPath"));
|
|
136
|
+
checks.push(check("hosting:operationsRunner:runtimeMode", runner.config?.runtimeMode, "service", "Runner runtime mode is a long-running service.", "Set services.operationsRunner.railway.runtimeMode to service.", "runtimeMode"));
|
|
137
|
+
checks.push(check("hosting:operationsRunner:volumeMountPath", runner.config?.volumeMountPath, "/data", "Runner volume mount path is stable.", "Set services.operationsRunner.railway.volumeMountPath to /data.", "volumeMountPath"));
|
|
138
|
+
}
|
|
139
|
+
if (railwayRunner) {
|
|
140
|
+
checks.push(check("railway-config:operationsRunner:serviceName", railwayRunner.serviceName, "treeseed-api-operations-runner-01", "Railway runner service instance uses the canonical name.", "Set the runner service instance name to treeseed-api-operations-runner-01.", "serviceName"));
|
|
141
|
+
checks.push(check("railway-config:operationsRunner:rootDirectory", relRoot(tenantRoot, railwayRunner.rootDir), expectedRunnerRailwayRoot, "Railway runner effective rootDirectory points at the API app.", "Set services.operationsRunner.railway.rootDir relative to the owning API manifest.", "rootDirectory"));
|
|
142
|
+
}
|
|
143
|
+
if (!database) {
|
|
144
|
+
checks.push({
|
|
145
|
+
id: "hosting:treeseedDatabase:present",
|
|
146
|
+
status: "failed",
|
|
147
|
+
message: "Treeseed database hosting unit is missing from the effective hosting graph.",
|
|
148
|
+
remediation: "Restore services.treeseedDatabase in treeseed.site.yaml."
|
|
149
|
+
});
|
|
150
|
+
} else {
|
|
151
|
+
checks.push(checkIncludes("hosting:treeseedDatabase:serviceTargets", database.config?.serviceTargets, ["api", "operationsRunner"], "Treeseed database targets API and runner services.", "Set services.treeseedDatabase.railway.serviceTargets to include api and operationsRunner."));
|
|
152
|
+
}
|
|
153
|
+
const counts = summary(checks);
|
|
154
|
+
return {
|
|
155
|
+
environment,
|
|
156
|
+
ok: counts.failed === 0,
|
|
157
|
+
generatedAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
158
|
+
checks,
|
|
159
|
+
summary: counts
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function formatTreeseedReadinessReport(report) {
|
|
163
|
+
const lines = [
|
|
164
|
+
`Deployment readiness for ${report.environment}: ${report.ok ? "passed" : "failed"}`,
|
|
165
|
+
`passed=${report.summary.passed} failed=${report.summary.failed} warning=${report.summary.warning} skipped=${report.summary.skipped}`
|
|
166
|
+
];
|
|
167
|
+
for (const check2 of report.checks.filter((entry) => entry.status !== "passed")) {
|
|
168
|
+
lines.push(`${check2.status.toUpperCase()} ${check2.id}: ${check2.message}${check2.remediation ? ` Remediation: ${check2.remediation}` : ""}`);
|
|
169
|
+
}
|
|
170
|
+
return lines.join("\n");
|
|
171
|
+
}
|
|
172
|
+
export {
|
|
173
|
+
collectTreeseedDeploymentReadiness,
|
|
174
|
+
formatTreeseedReadinessReport
|
|
175
|
+
};
|
|
@@ -179,9 +179,10 @@ export declare function mergeStagingIntoMain(cwd?: any): {
|
|
|
179
179
|
commitSha: string;
|
|
180
180
|
pushed: boolean;
|
|
181
181
|
};
|
|
182
|
-
export declare function mergeBranchIntoTarget(cwd?: any, { sourceBranch, targetBranch, message, pushTarget, quietMerge }?: {
|
|
182
|
+
export declare function mergeBranchIntoTarget(cwd?: any, { sourceBranch, targetBranch, message, pushTarget, quietMerge, allowUnrelatedHistories }?: {
|
|
183
183
|
pushTarget?: boolean | undefined;
|
|
184
184
|
quietMerge?: boolean | undefined;
|
|
185
|
+
allowUnrelatedHistories?: boolean | undefined;
|
|
185
186
|
}): {
|
|
186
187
|
repoDir: string;
|
|
187
188
|
targetBranch: any;
|
|
@@ -456,16 +456,22 @@ function mergeStagingIntoMain(cwd = workspaceRoot()) {
|
|
|
456
456
|
sourceBranch: STAGING_BRANCH,
|
|
457
457
|
targetBranch: PRODUCTION_BRANCH,
|
|
458
458
|
message: `release: ${STAGING_BRANCH} -> ${PRODUCTION_BRANCH}`,
|
|
459
|
-
pushTarget: true
|
|
459
|
+
pushTarget: true,
|
|
460
|
+
allowUnrelatedHistories: false
|
|
460
461
|
});
|
|
461
462
|
}
|
|
462
|
-
function mergeBranchIntoTarget(cwd = workspaceRoot(), { sourceBranch, targetBranch, message, pushTarget = true, quietMerge = false } = {}) {
|
|
463
|
+
function mergeBranchIntoTarget(cwd = workspaceRoot(), { sourceBranch, targetBranch, message, pushTarget = true, quietMerge = false, allowUnrelatedHistories = false } = {}) {
|
|
463
464
|
const repoDir = prepareReleaseBranches(cwd);
|
|
464
465
|
checkoutBranch(repoDir, targetBranch);
|
|
465
466
|
if (remoteBranchExists(repoDir, targetBranch)) {
|
|
466
467
|
runGit(["merge", "--ff-only", `origin/${targetBranch}`], { cwd: repoDir });
|
|
467
468
|
}
|
|
468
|
-
|
|
469
|
+
const mergeArgs = ["merge", "--no-ff"];
|
|
470
|
+
if (allowUnrelatedHistories) {
|
|
471
|
+
mergeArgs.push("--allow-unrelated-histories");
|
|
472
|
+
}
|
|
473
|
+
mergeArgs.push(sourceBranch, "-m", message);
|
|
474
|
+
runGit(mergeArgs, { cwd: repoDir, capture: quietMerge });
|
|
469
475
|
pushBranch(repoDir, STAGING_BRANCH);
|
|
470
476
|
if (pushTarget) {
|
|
471
477
|
pushBranch(repoDir, targetBranch);
|
|
@@ -135,6 +135,7 @@ export declare function waitForGitHubActionsGate(gate: GitHubActionsWorkflowGate
|
|
|
135
135
|
timeoutSeconds?: number;
|
|
136
136
|
pollSeconds?: number;
|
|
137
137
|
operation?: string;
|
|
138
|
+
env?: NodeJS.ProcessEnv | Record<string, string | undefined>;
|
|
138
139
|
onProgress?: (message: string, stream?: 'stdout' | 'stderr') => void;
|
|
139
140
|
}): Promise<Record<string, unknown>>;
|
|
140
141
|
export {};
|
|
@@ -544,6 +544,7 @@ async function waitForGitHubActionsGate(gate, options = {}) {
|
|
|
544
544
|
dispatchIfMissing: gate.dispatchIfMissing ?? gate.workflow === "verify.yml",
|
|
545
545
|
dispatchAfterSeconds: gate.dispatchAfterSeconds ?? 75,
|
|
546
546
|
dispatchInputs: gate.dispatchInputs,
|
|
547
|
+
env: options.env,
|
|
547
548
|
onProgress: reportProgress
|
|
548
549
|
});
|
|
549
550
|
}
|
|
@@ -794,7 +794,7 @@ async function waitForGitHubWorkflowRunCompletion(repository, {
|
|
|
794
794
|
continue;
|
|
795
795
|
}
|
|
796
796
|
for (; ; ) {
|
|
797
|
-
if (Date.now() - startedAt >= timeoutSeconds * 1e3) {
|
|
797
|
+
if (Date.now() - startedAt >= timeoutSeconds * 1e3 && lastProgress?.runId === match.id) {
|
|
798
798
|
break;
|
|
799
799
|
}
|
|
800
800
|
const current = await client.rest.actions.getWorkflowRun({
|
|
@@ -209,7 +209,7 @@ export declare function ensureGitHubDeployAutomation(tenantRoot: any, { dryRun,
|
|
|
209
209
|
skipped?: undefined;
|
|
210
210
|
};
|
|
211
211
|
}>;
|
|
212
|
-
export declare function waitForGitHubWorkflowCompletion(tenantRoot: any, { repository, workflow, headSha, branch, timeoutSeconds, pollSeconds, dispatchIfMissing, dispatchAfterSeconds, dispatchInputs, onProgress, }?: {
|
|
212
|
+
export declare function waitForGitHubWorkflowCompletion(tenantRoot: any, { repository, workflow, headSha, branch, timeoutSeconds, pollSeconds, dispatchIfMissing, dispatchAfterSeconds, dispatchInputs, onProgress, env, }?: {
|
|
213
213
|
workflow?: string | undefined;
|
|
214
214
|
timeoutSeconds?: number | undefined;
|
|
215
215
|
pollSeconds?: number | undefined;
|
|
@@ -358,7 +358,7 @@ function ensureStandardizedGitHubWorkflows(tenantRoot) {
|
|
|
358
358
|
const deployConfig = loadCliDeployConfig(tenantRoot);
|
|
359
359
|
const deploy = ensureDeployWorkflow(tenantRoot);
|
|
360
360
|
const workflows = [deploy, ...deploy.additionalWorkflows ?? []];
|
|
361
|
-
if ((deployConfig.hosting?.kind ?? "self_hosted_project") === "
|
|
361
|
+
if ((deployConfig.hosting?.kind ?? "self_hosted_project") === "treeseed_control_plane") {
|
|
362
362
|
workflows.push(ensureHostedProjectWorkflow(tenantRoot));
|
|
363
363
|
}
|
|
364
364
|
return workflows;
|
|
@@ -477,11 +477,12 @@ async function waitForGitHubWorkflowCompletion(tenantRoot, {
|
|
|
477
477
|
dispatchIfMissing = false,
|
|
478
478
|
dispatchAfterSeconds,
|
|
479
479
|
dispatchInputs,
|
|
480
|
-
onProgress
|
|
480
|
+
onProgress,
|
|
481
|
+
env
|
|
481
482
|
} = {}) {
|
|
482
483
|
const repo = repository ?? resolveGitHubRepositorySlug(tenantRoot);
|
|
483
484
|
return await waitForGitHubWorkflowRunCompletion(repo, {
|
|
484
|
-
client: createGitHubApiClient(),
|
|
485
|
+
client: createGitHubApiClient({ env }),
|
|
485
486
|
workflow,
|
|
486
487
|
headSha,
|
|
487
488
|
branch,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type TreeseedGitHubCredentialResolution = {
|
|
2
|
+
repository: string;
|
|
3
|
+
envName: string;
|
|
4
|
+
configured: boolean;
|
|
5
|
+
fallbackUsed: boolean;
|
|
6
|
+
source: 'repository' | 'fallback' | 'missing';
|
|
7
|
+
token: string | null;
|
|
8
|
+
};
|
|
9
|
+
export declare function githubRepositoryCredentialEnvName(repository: string): string;
|
|
10
|
+
export declare function resolveGitHubCredentialForRepository(repository: string, { values, env, }?: {
|
|
11
|
+
values?: Record<string, string | undefined>;
|
|
12
|
+
env?: NodeJS.ProcessEnv | Record<string, string | undefined>;
|
|
13
|
+
}): TreeseedGitHubCredentialResolution;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
function cleanRepositorySlug(repository) {
|
|
2
|
+
const raw = String(repository ?? "").trim().replace(/^https?:\/\/github\.com\//iu, "").replace(/^git@github\.com:/iu, "").replace(/^ssh:\/\/git@github\.com\//iu, "").replace(/\.git$/iu, "").replace(/^\/+|\/+$/gu, "");
|
|
3
|
+
const [owner, repo, ...extra] = raw.split("/").filter(Boolean);
|
|
4
|
+
if (!owner || !repo || extra.length > 0) {
|
|
5
|
+
throw new Error(`Invalid GitHub repository "${repository}". Expected owner/name.`);
|
|
6
|
+
}
|
|
7
|
+
return `${owner}/${repo}`;
|
|
8
|
+
}
|
|
9
|
+
function githubRepositoryCredentialEnvName(repository) {
|
|
10
|
+
const slug = cleanRepositorySlug(repository);
|
|
11
|
+
const suffix = slug.toUpperCase().replace(/[^A-Z0-9]+/gu, "_").replace(/^_+|_+$/gu, "").replace(/_+/gu, "_");
|
|
12
|
+
return `TREESEED_GITHUB_TOKEN_${suffix}`;
|
|
13
|
+
}
|
|
14
|
+
function configuredValue(values, key) {
|
|
15
|
+
const value = values?.[key];
|
|
16
|
+
return typeof value === "string" && value.trim() ? value.trim() : "";
|
|
17
|
+
}
|
|
18
|
+
function resolveGitHubCredentialForRepository(repository, {
|
|
19
|
+
values,
|
|
20
|
+
env = process.env
|
|
21
|
+
} = {}) {
|
|
22
|
+
const normalizedRepository = cleanRepositorySlug(repository);
|
|
23
|
+
const envName = githubRepositoryCredentialEnvName(normalizedRepository);
|
|
24
|
+
const repositoryToken = configuredValue(values, envName) || configuredValue(env, envName);
|
|
25
|
+
if (repositoryToken) {
|
|
26
|
+
return {
|
|
27
|
+
repository: normalizedRepository,
|
|
28
|
+
envName,
|
|
29
|
+
configured: true,
|
|
30
|
+
fallbackUsed: false,
|
|
31
|
+
source: "repository",
|
|
32
|
+
token: repositoryToken
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const fallbackToken = configuredValue(values, "GH_TOKEN") || configuredValue(values, "GITHUB_TOKEN") || configuredValue(env, "GH_TOKEN") || configuredValue(env, "GITHUB_TOKEN");
|
|
36
|
+
if (fallbackToken) {
|
|
37
|
+
return {
|
|
38
|
+
repository: normalizedRepository,
|
|
39
|
+
envName,
|
|
40
|
+
configured: true,
|
|
41
|
+
fallbackUsed: true,
|
|
42
|
+
source: "fallback",
|
|
43
|
+
token: fallbackToken
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
repository: normalizedRepository,
|
|
48
|
+
envName,
|
|
49
|
+
configured: false,
|
|
50
|
+
fallbackUsed: false,
|
|
51
|
+
source: "missing",
|
|
52
|
+
token: null
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export {
|
|
56
|
+
githubRepositoryCredentialEnvName,
|
|
57
|
+
resolveGitHubCredentialForRepository
|
|
58
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export type TreeseedHostedServiceCheckStatus = 'passed' | 'failed' | 'skipped' | 'warning';
|
|
2
|
+
export type TreeseedHostedServiceTarget = 'local' | 'staging' | 'prod';
|
|
3
|
+
export type TreeseedHostedServiceProvider = 'cloudflare' | 'railway' | 'http' | 'dns' | 'github';
|
|
4
|
+
export type TreeseedHostedServiceType = 'web' | 'api' | 'operationsRunner' | 'treeseedDatabase' | 'customDomain' | 'dnsRecord' | 'githubWorkflow' | 'unknown';
|
|
5
|
+
export interface TreeseedHostedServiceCheck {
|
|
6
|
+
id: string;
|
|
7
|
+
provider: TreeseedHostedServiceProvider;
|
|
8
|
+
serviceKey?: string;
|
|
9
|
+
serviceType: TreeseedHostedServiceType;
|
|
10
|
+
target: TreeseedHostedServiceTarget;
|
|
11
|
+
description: string;
|
|
12
|
+
expected?: Record<string, unknown>;
|
|
13
|
+
observed?: Record<string, unknown>;
|
|
14
|
+
status: TreeseedHostedServiceCheckStatus;
|
|
15
|
+
issues: string[];
|
|
16
|
+
remediation?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface TreeseedHostedServiceCheckReport {
|
|
19
|
+
target: TreeseedHostedServiceTarget;
|
|
20
|
+
tenantRoot: string;
|
|
21
|
+
generatedAt: string;
|
|
22
|
+
summary: {
|
|
23
|
+
passed: number;
|
|
24
|
+
failed: number;
|
|
25
|
+
skipped: number;
|
|
26
|
+
warning: number;
|
|
27
|
+
};
|
|
28
|
+
checks: TreeseedHostedServiceCheck[];
|
|
29
|
+
}
|
|
30
|
+
export interface TreeseedObservedRailwayServiceState {
|
|
31
|
+
projectName?: string | null;
|
|
32
|
+
environmentName?: string | null;
|
|
33
|
+
serviceName?: string | null;
|
|
34
|
+
rootDirectory?: string | null;
|
|
35
|
+
buildCommand?: string | null;
|
|
36
|
+
startCommand?: string | null;
|
|
37
|
+
healthcheckPath?: string | null;
|
|
38
|
+
healthcheckTimeoutSeconds?: number | null;
|
|
39
|
+
runtimeMode?: string | null;
|
|
40
|
+
volumeMountPath?: string | null;
|
|
41
|
+
variables?: Record<string, unknown> | string[];
|
|
42
|
+
secrets?: Record<string, unknown> | string[];
|
|
43
|
+
health?: 'ready' | 'failed' | 'unknown' | string | null;
|
|
44
|
+
}
|
|
45
|
+
export interface TreeseedHostedServiceCheckOptions {
|
|
46
|
+
tenantRoot: string;
|
|
47
|
+
target?: TreeseedHostedServiceTarget;
|
|
48
|
+
appId?: string;
|
|
49
|
+
now?: Date;
|
|
50
|
+
valuesOverlay?: Record<string, string | undefined>;
|
|
51
|
+
observedRailwayServices?: Record<string, TreeseedObservedRailwayServiceState | undefined>;
|
|
52
|
+
httpChecks?: Record<string, {
|
|
53
|
+
status?: number;
|
|
54
|
+
ok?: boolean;
|
|
55
|
+
skipped?: boolean;
|
|
56
|
+
error?: string;
|
|
57
|
+
fallbackUrl?: string;
|
|
58
|
+
fallbackStatus?: number;
|
|
59
|
+
fallbackOk?: boolean;
|
|
60
|
+
fallbackError?: string;
|
|
61
|
+
} | undefined>;
|
|
62
|
+
}
|
|
63
|
+
export declare function collectTreeseedHostedServiceChecks(options: TreeseedHostedServiceCheckOptions): TreeseedHostedServiceCheckReport;
|