@treeseed/sdk 0.10.5 → 0.10.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +58 -0
- package/dist/operations/repository-operations.d.ts +129 -0
- package/dist/operations/repository-operations.js +634 -0
- package/dist/operations/services/config-runtime.d.ts +7 -6
- package/dist/operations/services/config-runtime.js +45 -25
- package/dist/operations/services/deploy.d.ts +42 -0
- package/dist/operations/services/deploy.js +1 -1
- package/dist/operations/services/project-platform.d.ts +41 -1
- package/dist/operations/services/project-platform.js +13 -0
- package/dist/operations/services/railway-api.d.ts +35 -1
- package/dist/operations/services/railway-api.js +240 -35
- package/dist/operations/services/railway-deploy.d.ts +16 -234
- package/dist/operations/services/railway-deploy.js +177 -62
- package/dist/operations/services/release-candidate.js +1 -2
- package/dist/operations/services/runtime-tools.d.ts +14 -0
- package/dist/operations/services/runtime-tools.js +15 -1
- package/dist/operations/services/workspace-save.d.ts +24 -0
- package/dist/operations/services/workspace-save.js +143 -3
- package/dist/operations/services/workspace-tools.js +1 -1
- package/dist/platform/env.yaml +163 -2
- package/dist/platform/environment.d.ts +1 -0
- package/dist/platform/environment.js +9 -0
- package/dist/platform-operation-store.d.ts +90 -0
- package/dist/platform-operation-store.js +505 -0
- package/dist/platform-operations.d.ts +265 -0
- package/dist/platform-operations.js +421 -0
- package/dist/reconcile/bootstrap-systems.js +3 -3
- package/dist/reconcile/builtin-adapters.js +225 -29
- package/dist/reconcile/contracts.d.ts +1 -1
- package/dist/reconcile/desired-state.d.ts +14 -0
- package/dist/reconcile/desired-state.js +4 -0
- package/dist/reconcile/engine.d.ts +28 -0
- package/dist/reconcile/state.js +3 -0
- package/dist/reconcile/units.js +2 -0
- package/dist/workflow/operations.d.ts +13 -5
- package/dist/workflow/operations.js +69 -12
- package/dist/workflow-state.d.ts +2 -0
- package/dist/workflow-state.js +7 -2
- package/dist/workflow.d.ts +2 -0
- package/package.json +15 -2
|
@@ -175,9 +175,23 @@ export declare function loadCliDeployConfig(tenantRoot: any): {
|
|
|
175
175
|
projectName: string | undefined;
|
|
176
176
|
serviceId: string | undefined;
|
|
177
177
|
serviceName: string | undefined;
|
|
178
|
+
resourceType: string | undefined;
|
|
179
|
+
environmentVariable: string | undefined;
|
|
180
|
+
serviceTargets: any;
|
|
178
181
|
rootDir: string | undefined;
|
|
179
182
|
buildCommand: string | undefined;
|
|
180
183
|
startCommand: string | undefined;
|
|
184
|
+
healthcheckPath: string | undefined;
|
|
185
|
+
healthcheckTimeoutSeconds: number | undefined;
|
|
186
|
+
healthcheckIntervalSeconds: number | undefined;
|
|
187
|
+
restartPolicy: string | undefined;
|
|
188
|
+
runtimeMode: string | undefined;
|
|
189
|
+
volumeMountPath: string | undefined;
|
|
190
|
+
runnerPool: {
|
|
191
|
+
bootstrapCount: number | undefined;
|
|
192
|
+
maxRunners: number | undefined;
|
|
193
|
+
volumeMountPath: string | undefined;
|
|
194
|
+
} | undefined;
|
|
181
195
|
schedule: any;
|
|
182
196
|
};
|
|
183
197
|
environments: {
|
|
@@ -53,7 +53,7 @@ const TREESEED_DEFAULT_PROVIDER_SELECTIONS = {
|
|
|
53
53
|
},
|
|
54
54
|
site: "default"
|
|
55
55
|
};
|
|
56
|
-
const TRESEED_MANAGED_SERVICE_KEYS = ["
|
|
56
|
+
const TRESEED_MANAGED_SERVICE_KEYS = ["marketDatabase", "api", "marketOperationsRunner"];
|
|
57
57
|
const TRESEED_WORKSPACE_PACKAGE_DIRS = ["sdk", "core", "cli"];
|
|
58
58
|
const CLOUDFLARE_ACCOUNT_ID_PLACEHOLDER = "replace-with-cloudflare-account-id";
|
|
59
59
|
function normalizePlanesFromLegacyHosting(hosting) {
|
|
@@ -143,9 +143,23 @@ function parseManagedServiceConfig(value, label) {
|
|
|
143
143
|
projectName: optionalString(railway.projectName),
|
|
144
144
|
serviceId: optionalString(railway.serviceId),
|
|
145
145
|
serviceName: optionalString(railway.serviceName),
|
|
146
|
+
resourceType: optionalString(railway.resourceType),
|
|
147
|
+
environmentVariable: optionalString(railway.environmentVariable),
|
|
148
|
+
serviceTargets: Array.isArray(railway.serviceTargets) ? railway.serviceTargets.map((entry) => optionalString(entry)).filter(Boolean) : void 0,
|
|
146
149
|
rootDir: optionalString(railway.rootDir),
|
|
147
150
|
buildCommand: optionalString(railway.buildCommand),
|
|
148
151
|
startCommand: optionalString(railway.startCommand),
|
|
152
|
+
healthcheckPath: optionalString(railway.healthcheckPath),
|
|
153
|
+
healthcheckTimeoutSeconds: optionalNonNegativeNumber(railway.healthcheckTimeoutSeconds, `${label}.railway.healthcheckTimeoutSeconds`),
|
|
154
|
+
healthcheckIntervalSeconds: optionalNonNegativeNumber(railway.healthcheckIntervalSeconds, `${label}.railway.healthcheckIntervalSeconds`),
|
|
155
|
+
restartPolicy: optionalString(railway.restartPolicy),
|
|
156
|
+
runtimeMode: optionalString(railway.runtimeMode),
|
|
157
|
+
volumeMountPath: optionalString(railway.volumeMountPath),
|
|
158
|
+
runnerPool: optionalRecord(railway.runnerPool, `${label}.railway.runnerPool`) ? {
|
|
159
|
+
bootstrapCount: optionalNonNegativeNumber(railway.runnerPool.bootstrapCount, `${label}.railway.runnerPool.bootstrapCount`),
|
|
160
|
+
maxRunners: optionalNonNegativeNumber(railway.runnerPool.maxRunners, `${label}.railway.runnerPool.maxRunners`),
|
|
161
|
+
volumeMountPath: optionalString(railway.runnerPool.volumeMountPath)
|
|
162
|
+
} : void 0,
|
|
149
163
|
schedule: Array.isArray(railway.schedule) ? railway.schedule.map((entry) => optionalString(entry)).filter(Boolean) : optionalString(railway.schedule)
|
|
150
164
|
},
|
|
151
165
|
environments: {
|
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
export declare const MERGE_CONFLICT_EXIT_CODE = 12;
|
|
2
|
+
export declare const TREESEED_PUBLIC_RELEASE_PACKAGE_NAMES: string[];
|
|
3
|
+
export declare function highestStableGitTagOnLine(repoDir: any, lineLabel: any): string | null;
|
|
2
4
|
export declare function incrementPatchVersion(version: any): string;
|
|
3
5
|
export declare function incrementVersion(version: any, level?: string): string;
|
|
6
|
+
export declare function collectPublicPackageReleaseLineState(root?: any): {
|
|
7
|
+
group: string[];
|
|
8
|
+
packages: {
|
|
9
|
+
name: any;
|
|
10
|
+
path: any;
|
|
11
|
+
version: string;
|
|
12
|
+
line: string;
|
|
13
|
+
major: number;
|
|
14
|
+
minor: number;
|
|
15
|
+
}[];
|
|
16
|
+
lines: string[];
|
|
17
|
+
highestLine: string | null;
|
|
18
|
+
aligned: boolean;
|
|
19
|
+
drifted: boolean;
|
|
20
|
+
};
|
|
4
21
|
export declare function planWorkspaceVersionChanges(root?: any): {
|
|
5
22
|
packages: any[];
|
|
6
23
|
publishable: Set<any>;
|
|
@@ -14,6 +31,13 @@ export declare function planWorkspaceReleaseBump(level?: string, root?: any, opt
|
|
|
14
31
|
versions: Map<any, any>;
|
|
15
32
|
level: string;
|
|
16
33
|
selected: Set<any>;
|
|
34
|
+
releaseLine: {
|
|
35
|
+
group: string[];
|
|
36
|
+
repair: boolean;
|
|
37
|
+
targetLine: string;
|
|
38
|
+
highestCurrentLine: string;
|
|
39
|
+
alignedBefore: boolean;
|
|
40
|
+
};
|
|
17
41
|
};
|
|
18
42
|
export declare function collectWorkspaceVersionConsistencyIssues(root?: any): {
|
|
19
43
|
packageName: any;
|
|
@@ -2,6 +2,7 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { changedWorkspacePackages, publishableWorkspacePackages, run, sortWorkspacePackages, workspacePackages, workspaceRoot } from "./workspace-tools.js";
|
|
4
4
|
const MERGE_CONFLICT_EXIT_CODE = 12;
|
|
5
|
+
const TREESEED_PUBLIC_RELEASE_PACKAGE_NAMES = ["@treeseed/sdk", "@treeseed/core", "@treeseed/cli", "@treeseed/agent"];
|
|
5
6
|
function parseSemver(version) {
|
|
6
7
|
const match = String(version).trim().match(/^(\d+)\.(\d+)\.(\d+)(?:-[0-9A-Za-z.-]+)?$/);
|
|
7
8
|
if (!match) {
|
|
@@ -14,6 +15,82 @@ function parseSemver(version) {
|
|
|
14
15
|
prerelease: String(version).includes("-")
|
|
15
16
|
};
|
|
16
17
|
}
|
|
18
|
+
function versionLine(version) {
|
|
19
|
+
const parsed = parseSemver(version);
|
|
20
|
+
return {
|
|
21
|
+
major: parsed.major,
|
|
22
|
+
minor: parsed.minor,
|
|
23
|
+
label: `${parsed.major}.${parsed.minor}`
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function compareVersionLines(left, right) {
|
|
27
|
+
if (left.major !== right.major) return left.major - right.major;
|
|
28
|
+
return left.minor - right.minor;
|
|
29
|
+
}
|
|
30
|
+
function parseVersionLine(input) {
|
|
31
|
+
const match = String(input ?? "").trim().match(/^(\d+)\.(\d+)$/);
|
|
32
|
+
if (!match) {
|
|
33
|
+
throw new Error(`Unsupported release version line "${input}". Expected major.minor, for example 0.10.`);
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
major: Number(match[1]),
|
|
37
|
+
minor: Number(match[2]),
|
|
38
|
+
label: `${Number(match[1])}.${Number(match[2])}`
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function nextLineFor(level, highestLine) {
|
|
42
|
+
if (level === "major") {
|
|
43
|
+
return {
|
|
44
|
+
major: highestLine.major + 1,
|
|
45
|
+
minor: 0,
|
|
46
|
+
label: `${highestLine.major + 1}.0`
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (level === "minor") {
|
|
50
|
+
return {
|
|
51
|
+
major: highestLine.major,
|
|
52
|
+
minor: highestLine.minor + 1,
|
|
53
|
+
label: `${highestLine.major}.${highestLine.minor + 1}`
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return highestLine;
|
|
57
|
+
}
|
|
58
|
+
function versionForLine(line, patch = 0) {
|
|
59
|
+
return `${line.major}.${line.minor}.${patch}`;
|
|
60
|
+
}
|
|
61
|
+
function localGitTagExists(repoDir, tagName) {
|
|
62
|
+
try {
|
|
63
|
+
return run("git", ["tag", "--list", tagName], { cwd: repoDir, capture: true }).trim() === tagName;
|
|
64
|
+
} catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function highestStableGitTagOnLine(repoDir, lineLabel) {
|
|
69
|
+
const line = parseVersionLine(lineLabel);
|
|
70
|
+
try {
|
|
71
|
+
const tags = run("git", ["tag", "--list", `${line.label}.*`], { cwd: repoDir, capture: true }).split(/\r?\n/u).map((tag) => tag.trim()).filter(Boolean).map((tag) => {
|
|
72
|
+
try {
|
|
73
|
+
const parsed = parseSemver(tag);
|
|
74
|
+
if (parsed.prerelease || parsed.major !== line.major || parsed.minor !== line.minor) return null;
|
|
75
|
+
return { tag, patch: parsed.patch };
|
|
76
|
+
} catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}).filter((entry) => entry != null).sort((left, right) => right.patch - left.patch);
|
|
80
|
+
return tags[0]?.tag ?? null;
|
|
81
|
+
} catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function firstAvailablePatchVersionOnLine(pkg, line) {
|
|
86
|
+
for (let patch = 0; patch < 1e3; patch += 1) {
|
|
87
|
+
const candidate = versionForLine(line, patch);
|
|
88
|
+
if (!localGitTagExists(pkg.dir, candidate)) {
|
|
89
|
+
return candidate;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
throw new Error(`Unable to find an available ${line.label}.x version for ${pkg.name}.`);
|
|
93
|
+
}
|
|
17
94
|
function incrementPatchVersion(version) {
|
|
18
95
|
const parsed = parseSemver(version);
|
|
19
96
|
return `${parsed.major}.${parsed.minor}.${parsed.patch + 1}`;
|
|
@@ -34,6 +111,32 @@ function incrementVersion(version, level = "patch") {
|
|
|
34
111
|
}
|
|
35
112
|
throw new Error(`Unsupported release bump "${level}". Expected major, minor, or patch.`);
|
|
36
113
|
}
|
|
114
|
+
function collectPublicPackageReleaseLineState(root = workspaceRoot()) {
|
|
115
|
+
const publishable = new Set(publishableWorkspacePackages(root).map((pkg) => pkg.name));
|
|
116
|
+
const packages = sortWorkspacePackages(workspacePackages(root)).filter((pkg) => TREESEED_PUBLIC_RELEASE_PACKAGE_NAMES.includes(pkg.name)).filter((pkg) => publishable.has(pkg.name)).map((pkg) => {
|
|
117
|
+
const packageJson = readPackageJson(resolve(pkg.dir, "package.json"));
|
|
118
|
+
const line = versionLine(packageJson.version);
|
|
119
|
+
return {
|
|
120
|
+
name: pkg.name,
|
|
121
|
+
path: pkg.relativeDir,
|
|
122
|
+
version: String(packageJson.version ?? ""),
|
|
123
|
+
line: line.label,
|
|
124
|
+
major: line.major,
|
|
125
|
+
minor: line.minor
|
|
126
|
+
};
|
|
127
|
+
});
|
|
128
|
+
const lineMap = new Map(packages.map((pkg) => [pkg.line, { major: pkg.major, minor: pkg.minor, label: pkg.line }]));
|
|
129
|
+
const lines = [...lineMap.values()].sort(compareVersionLines);
|
|
130
|
+
const highestLine = lines.at(-1) ?? null;
|
|
131
|
+
return {
|
|
132
|
+
group: TREESEED_PUBLIC_RELEASE_PACKAGE_NAMES.filter((name) => packages.some((pkg) => pkg.name === name)),
|
|
133
|
+
packages,
|
|
134
|
+
lines: lines.map((line) => line.label),
|
|
135
|
+
highestLine: highestLine?.label ?? null,
|
|
136
|
+
aligned: lines.length <= 1,
|
|
137
|
+
drifted: lines.length > 1
|
|
138
|
+
};
|
|
139
|
+
}
|
|
37
140
|
function readPackageJson(filePath) {
|
|
38
141
|
return JSON.parse(readFileSync(filePath, "utf8"));
|
|
39
142
|
}
|
|
@@ -124,16 +227,43 @@ function planWorkspaceReleaseBump(level = "patch", root = workspaceRoot(), optio
|
|
|
124
227
|
packageJson: readPackageJson(resolve(pkg.dir, "package.json"))
|
|
125
228
|
}));
|
|
126
229
|
const publishable = new Set(publishableWorkspacePackages(root).map((pkg) => pkg.name));
|
|
127
|
-
const
|
|
230
|
+
const baseSelected = options.selectedPackageNames ? new Set(
|
|
128
231
|
[...options.selectedPackageNames].map((name) => String(name)).filter((name) => publishable.has(name))
|
|
129
232
|
) : new Set(publishable);
|
|
233
|
+
const publicPackages = sortWorkspacePackages(packages).filter((pkg) => TREESEED_PUBLIC_RELEASE_PACKAGE_NAMES.includes(pkg.name)).filter((pkg) => publishable.has(pkg.name));
|
|
234
|
+
const publicLines = publicPackages.map((pkg) => versionLine(pkg.packageJson.version));
|
|
235
|
+
const highestPublicLine = publicLines.sort(compareVersionLines).at(-1) ?? { major: 0, minor: 0, label: "0.0" };
|
|
236
|
+
const repairVersionLine = options.repairVersionLine === true;
|
|
237
|
+
const explicitTargetLine = options.targetVersionLine ? parseVersionLine(options.targetVersionLine) : null;
|
|
238
|
+
let targetLine = explicitTargetLine ?? highestPublicLine;
|
|
239
|
+
if (repairVersionLine) {
|
|
240
|
+
if (explicitTargetLine && compareVersionLines(explicitTargetLine, highestPublicLine) !== 0) {
|
|
241
|
+
throw new Error(`Release line repair target must match the highest current public package line (${highestPublicLine.label}). Received ${explicitTargetLine.label}.`);
|
|
242
|
+
}
|
|
243
|
+
} else if (level === "major" || level === "minor") {
|
|
244
|
+
targetLine = nextLineFor(level, highestPublicLine);
|
|
245
|
+
}
|
|
246
|
+
const selected = new Set(baseSelected);
|
|
247
|
+
if (repairVersionLine) {
|
|
248
|
+
selected.clear();
|
|
249
|
+
for (const pkg of publicPackages) {
|
|
250
|
+
if (versionLine(pkg.packageJson.version).label !== targetLine.label) {
|
|
251
|
+
selected.add(pkg.name);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
} else if (level === "major" || level === "minor") {
|
|
255
|
+
for (const pkg of publicPackages) {
|
|
256
|
+
selected.add(pkg.name);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
130
259
|
const touched = /* @__PURE__ */ new Set();
|
|
131
260
|
const versions = /* @__PURE__ */ new Map();
|
|
132
261
|
for (const pkg of packages) {
|
|
133
262
|
if (!publishable.has(pkg.name) || !selected.has(pkg.name)) {
|
|
134
263
|
continue;
|
|
135
264
|
}
|
|
136
|
-
const
|
|
265
|
+
const isPublicReleasePackage = TREESEED_PUBLIC_RELEASE_PACKAGE_NAMES.includes(pkg.name);
|
|
266
|
+
const nextVersion = repairVersionLine && isPublicReleasePackage ? firstAvailablePatchVersionOnLine(pkg, targetLine) : isPublicReleasePackage && (level === "major" || level === "minor") ? versionForLine(targetLine, 0) : incrementVersion(pkg.packageJson.version, level);
|
|
137
267
|
pkg.packageJson.version = nextVersion;
|
|
138
268
|
versions.set(pkg.name, nextVersion);
|
|
139
269
|
touched.add(pkg.name);
|
|
@@ -157,7 +287,14 @@ function planWorkspaceReleaseBump(level = "patch", root = workspaceRoot(), optio
|
|
|
157
287
|
touched,
|
|
158
288
|
versions,
|
|
159
289
|
level,
|
|
160
|
-
selected
|
|
290
|
+
selected,
|
|
291
|
+
releaseLine: {
|
|
292
|
+
group: TREESEED_PUBLIC_RELEASE_PACKAGE_NAMES.filter((name) => publishable.has(name)),
|
|
293
|
+
repair: repairVersionLine,
|
|
294
|
+
targetLine: targetLine.label,
|
|
295
|
+
highestCurrentLine: highestPublicLine.label,
|
|
296
|
+
alignedBefore: new Set(publicLines.map((line) => line.label)).size <= 1
|
|
297
|
+
}
|
|
161
298
|
};
|
|
162
299
|
}
|
|
163
300
|
function collectWorkspaceVersionConsistencyIssues(root = workspaceRoot()) {
|
|
@@ -272,15 +409,18 @@ function formatMergeConflictReport(report, repoDir, targetBranch = "main") {
|
|
|
272
409
|
}
|
|
273
410
|
export {
|
|
274
411
|
MERGE_CONFLICT_EXIT_CODE,
|
|
412
|
+
TREESEED_PUBLIC_RELEASE_PACKAGE_NAMES,
|
|
275
413
|
applyWorkspaceVersionChanges,
|
|
276
414
|
assertWorkspaceVersionConsistency,
|
|
277
415
|
collectMergeConflictReport,
|
|
416
|
+
collectPublicPackageReleaseLineState,
|
|
278
417
|
collectWorkspaceVersionConsistencyIssues,
|
|
279
418
|
countConflictMarkers,
|
|
280
419
|
currentBranch,
|
|
281
420
|
formatMergeConflictReport,
|
|
282
421
|
gitStatusPorcelain,
|
|
283
422
|
hasMeaningfulChanges,
|
|
423
|
+
highestStableGitTagOnLine,
|
|
284
424
|
incrementPatchVersion,
|
|
285
425
|
incrementVersion,
|
|
286
426
|
originRemoteUrl,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync } from "node:fs";
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
3
|
import { join, relative, resolve } from "node:path";
|
|
4
|
-
const TREESEED_WORKSPACE_PACKAGE_DIRS = ["sdk", "core", "cli"];
|
|
4
|
+
const TREESEED_WORKSPACE_PACKAGE_DIRS = ["sdk", "core", "cli", "agent"];
|
|
5
5
|
function packageSortWeight(pkg) {
|
|
6
6
|
const relativeDir = String(pkg.relativeDir ?? "");
|
|
7
7
|
const dirName = relativeDir.split("/").pop() ?? "";
|
package/dist/platform/env.yaml
CHANGED
|
@@ -23,6 +23,32 @@ entries:
|
|
|
23
23
|
sourcePriority:
|
|
24
24
|
- machine-config
|
|
25
25
|
- process-env
|
|
26
|
+
RAILWAY_API_TOKEN:
|
|
27
|
+
label: Railway API token
|
|
28
|
+
group: auth
|
|
29
|
+
description: Account-level Railway API token used by Treeseed to provision, reconcile, deploy, and inspect Market Railway services.
|
|
30
|
+
howToGet: Create a Railway account API token with access to the Treeseed Market project/workspace, then store it through Treeseed config as RAILWAY_API_TOKEN.
|
|
31
|
+
sensitivity: secret
|
|
32
|
+
targets:
|
|
33
|
+
- local-runtime
|
|
34
|
+
- github-secret
|
|
35
|
+
scopes:
|
|
36
|
+
- local
|
|
37
|
+
- staging
|
|
38
|
+
- prod
|
|
39
|
+
storage: shared
|
|
40
|
+
requirement: required
|
|
41
|
+
purposes:
|
|
42
|
+
- deploy
|
|
43
|
+
- config
|
|
44
|
+
validation:
|
|
45
|
+
kind: nonempty
|
|
46
|
+
minLength: 8
|
|
47
|
+
sourcePriority:
|
|
48
|
+
- machine-config
|
|
49
|
+
- process-env
|
|
50
|
+
relevanceRef: railwayManagedEnabled
|
|
51
|
+
requiredWhenRef: railwayManagedEnabled
|
|
26
52
|
TREESEED_GITHUB_OWNER:
|
|
27
53
|
label: GitHub repository owner
|
|
28
54
|
group: github
|
|
@@ -351,7 +377,7 @@ entries:
|
|
|
351
377
|
- staging
|
|
352
378
|
- prod
|
|
353
379
|
storage: shared
|
|
354
|
-
requirement:
|
|
380
|
+
requirement: optional
|
|
355
381
|
purposes:
|
|
356
382
|
- deploy
|
|
357
383
|
- config
|
|
@@ -364,6 +390,142 @@ entries:
|
|
|
364
390
|
relevanceRef: projectRegistrationEnabled
|
|
365
391
|
requiredWhenRef: projectRegistrationEnabled
|
|
366
392
|
defaultValueRef: marketBaseUrlDefault
|
|
393
|
+
TREESEED_MARKET_DATABASE_URL:
|
|
394
|
+
label: Market PostgreSQL database URL
|
|
395
|
+
group: hosting
|
|
396
|
+
cluster: hosting:market-control-plane
|
|
397
|
+
visibility: system
|
|
398
|
+
description: Railway PostgreSQL connection URL for Market control-plane tables. Browser/UI code must never receive this value.
|
|
399
|
+
howToGet: Provision the managed Market PostgreSQL resource through Treeseed/Railway reconciliation and store the generated connection URL as a service secret.
|
|
400
|
+
sensitivity: secret
|
|
401
|
+
targets:
|
|
402
|
+
- railway-secret
|
|
403
|
+
serviceTargets:
|
|
404
|
+
- api
|
|
405
|
+
- marketOperationsRunner
|
|
406
|
+
scopes:
|
|
407
|
+
- staging
|
|
408
|
+
- prod
|
|
409
|
+
storage: scoped
|
|
410
|
+
requirement: optional
|
|
411
|
+
purposes:
|
|
412
|
+
- deploy
|
|
413
|
+
- config
|
|
414
|
+
validation:
|
|
415
|
+
kind: url
|
|
416
|
+
sourcePriority:
|
|
417
|
+
- machine-config
|
|
418
|
+
- process-env
|
|
419
|
+
- treeseed.site.yaml
|
|
420
|
+
TREESEED_PLATFORM_RUNNER_SECRET:
|
|
421
|
+
label: Market operations runner secret
|
|
422
|
+
group: hosting
|
|
423
|
+
cluster: hosting:market-operations-runner
|
|
424
|
+
visibility: system
|
|
425
|
+
description: Bearer credential used only by the TreeSeed-owned Market operations runner to claim and update platform operation jobs.
|
|
426
|
+
howToGet: Generate a strong secret with `openssl rand -base64 48` and store it through `treeseed config` for staging and production.
|
|
427
|
+
sensitivity: secret
|
|
428
|
+
targets:
|
|
429
|
+
- railway-secret
|
|
430
|
+
serviceTargets:
|
|
431
|
+
- api
|
|
432
|
+
- marketOperationsRunner
|
|
433
|
+
scopes:
|
|
434
|
+
- staging
|
|
435
|
+
- prod
|
|
436
|
+
storage: scoped
|
|
437
|
+
requirement: conditional
|
|
438
|
+
purposes:
|
|
439
|
+
- deploy
|
|
440
|
+
- config
|
|
441
|
+
validation:
|
|
442
|
+
kind: nonempty
|
|
443
|
+
minLength: 24
|
|
444
|
+
sourcePriority:
|
|
445
|
+
- machine-config
|
|
446
|
+
- process-env
|
|
447
|
+
defaultValueRef: generatedSecret
|
|
448
|
+
TREESEED_PLATFORM_RUNNER_ID:
|
|
449
|
+
label: Market operations runner ID
|
|
450
|
+
group: hosting
|
|
451
|
+
cluster: hosting:market-operations-runner
|
|
452
|
+
visibility: system
|
|
453
|
+
description: Stable identity for the TreeSeed-owned Market operations runner service in each environment.
|
|
454
|
+
howToGet: Treeseed derives this from the environment. Override it only when replacing a runner identity deliberately.
|
|
455
|
+
sensitivity: plain
|
|
456
|
+
targets:
|
|
457
|
+
- railway-var
|
|
458
|
+
serviceTargets:
|
|
459
|
+
- marketOperationsRunner
|
|
460
|
+
scopes:
|
|
461
|
+
- staging
|
|
462
|
+
- prod
|
|
463
|
+
storage: scoped
|
|
464
|
+
requirement: conditional
|
|
465
|
+
purposes:
|
|
466
|
+
- deploy
|
|
467
|
+
- config
|
|
468
|
+
validation:
|
|
469
|
+
kind: nonempty
|
|
470
|
+
sourcePriority:
|
|
471
|
+
- machine-config
|
|
472
|
+
- process-env
|
|
473
|
+
- treeseed.site.yaml
|
|
474
|
+
defaultValueRef: platformRunnerIdDefault
|
|
475
|
+
TREESEED_PLATFORM_RUNNER_DATA_DIR:
|
|
476
|
+
label: Market operations runner data directory
|
|
477
|
+
group: hosting
|
|
478
|
+
cluster: hosting:market-operations-runner
|
|
479
|
+
visibility: system
|
|
480
|
+
description: Writable data directory mounted into the Market operations runner for repository workspaces and operation state.
|
|
481
|
+
howToGet: Use `/data` for Railway runner services with an attached volume.
|
|
482
|
+
sensitivity: plain
|
|
483
|
+
targets:
|
|
484
|
+
- railway-var
|
|
485
|
+
serviceTargets:
|
|
486
|
+
- marketOperationsRunner
|
|
487
|
+
scopes:
|
|
488
|
+
- staging
|
|
489
|
+
- prod
|
|
490
|
+
storage: scoped
|
|
491
|
+
requirement: conditional
|
|
492
|
+
purposes:
|
|
493
|
+
- deploy
|
|
494
|
+
- config
|
|
495
|
+
validation:
|
|
496
|
+
kind: nonempty
|
|
497
|
+
sourcePriority:
|
|
498
|
+
- machine-config
|
|
499
|
+
- process-env
|
|
500
|
+
- treeseed.site.yaml
|
|
501
|
+
defaultValueRef: platformRunnerDataDirDefault
|
|
502
|
+
TREESEED_PLATFORM_RUNNER_ENVIRONMENT:
|
|
503
|
+
label: Market operations runner environment
|
|
504
|
+
group: hosting
|
|
505
|
+
cluster: hosting:market-operations-runner
|
|
506
|
+
visibility: system
|
|
507
|
+
description: Runtime environment label reported by the Market operations runner in registration and heartbeat records.
|
|
508
|
+
howToGet: Treeseed derives this from the selected environment.
|
|
509
|
+
sensitivity: plain
|
|
510
|
+
targets:
|
|
511
|
+
- railway-var
|
|
512
|
+
serviceTargets:
|
|
513
|
+
- marketOperationsRunner
|
|
514
|
+
scopes:
|
|
515
|
+
- staging
|
|
516
|
+
- prod
|
|
517
|
+
storage: scoped
|
|
518
|
+
requirement: conditional
|
|
519
|
+
purposes:
|
|
520
|
+
- deploy
|
|
521
|
+
- config
|
|
522
|
+
validation:
|
|
523
|
+
kind: nonempty
|
|
524
|
+
sourcePriority:
|
|
525
|
+
- machine-config
|
|
526
|
+
- process-env
|
|
527
|
+
- treeseed.site.yaml
|
|
528
|
+
defaultValueRef: platformRunnerEnvironmentDefault
|
|
367
529
|
TREESEED_CATALOG_MARKET_API_BASE_URLS:
|
|
368
530
|
label: Treeseed catalog market API URLs
|
|
369
531
|
group: hosting
|
|
@@ -439,4 +601,3 @@ entries:
|
|
|
439
601
|
relevanceRef: projectRegistrationEnabled
|
|
440
602
|
requiredWhenRef: projectRegistrationEnabled
|
|
441
603
|
defaultValueRef: hostingProjectIdDefault
|
|
442
|
-
|
|
@@ -75,6 +75,7 @@ export type TreeseedEnvironmentEntry = {
|
|
|
75
75
|
howToGet: string;
|
|
76
76
|
sensitivity: TreeseedEnvironmentSensitivity;
|
|
77
77
|
targets: TreeseedEnvironmentTarget[];
|
|
78
|
+
serviceTargets?: string[];
|
|
78
79
|
scopes: TreeseedEnvironmentScope[];
|
|
79
80
|
requirement: TreeseedEnvironmentRequirement;
|
|
80
81
|
purposes: TreeseedEnvironmentPurpose[];
|
|
@@ -258,6 +258,12 @@ function resolveHostedProjectId(context) {
|
|
|
258
258
|
function resolveRailwayWorkspaceDefault() {
|
|
259
259
|
return "knowledge-coop";
|
|
260
260
|
}
|
|
261
|
+
function resolvePlatformRunnerIdDefault(_context, scope) {
|
|
262
|
+
return scope === "prod" ? "market-ops-prod-1" : scope === "staging" ? "market-ops-staging-1" : "market-ops-local-1";
|
|
263
|
+
}
|
|
264
|
+
function resolvePlatformRunnerEnvironmentDefault(_context, scope) {
|
|
265
|
+
return scope === "prod" ? "production" : scope;
|
|
266
|
+
}
|
|
261
267
|
function parseGitHubRepositorySlugFromRemote(remoteUrl) {
|
|
262
268
|
const normalized = String(remoteUrl ?? "").trim();
|
|
263
269
|
const sshMatch = normalized.match(/^git@github\.com:([^/]+)\/(.+?)(?:\.git)?$/u);
|
|
@@ -312,6 +318,9 @@ const VALUE_RESOLVERS = {
|
|
|
312
318
|
hostingTeamIdDefault: (context) => resolveHostedTeamId(context),
|
|
313
319
|
hostingProjectIdDefault: (context) => resolveHostedProjectId(context),
|
|
314
320
|
railwayWorkspaceDefault: () => resolveRailwayWorkspaceDefault(),
|
|
321
|
+
platformRunnerIdDefault: (context, scope) => resolvePlatformRunnerIdDefault(context, scope),
|
|
322
|
+
platformRunnerEnvironmentDefault: (context, scope) => resolvePlatformRunnerEnvironmentDefault(context, scope),
|
|
323
|
+
platformRunnerDataDirDefault: () => "/data",
|
|
315
324
|
githubOwnerDefault: (context) => resolveGitHubOwnerDefault(context),
|
|
316
325
|
githubRepositoryNameDefault: (context) => resolveGitHubRepositoryNameDefault(context),
|
|
317
326
|
githubRepositoryVisibilityDefault: () => "private",
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { type PlatformOperation, type PlatformOperationEvent, type PlatformRunnerClaimRequest, type PlatformRunnerHeartbeatRequest, type PlatformRunnerJobUpdateRequest, type PlatformRunnerRegistrationRequest } from './platform-operations.ts';
|
|
2
|
+
export type DatabaseProvider = 'd1' | 'sqlite' | 'postgres';
|
|
3
|
+
export interface RelationalDatabaseAdapter {
|
|
4
|
+
readonly provider: DatabaseProvider;
|
|
5
|
+
run(query: string, params?: unknown[]): Promise<void>;
|
|
6
|
+
first<T = Record<string, unknown>>(query: string, params?: unknown[]): Promise<T | null>;
|
|
7
|
+
all<T = Record<string, unknown>>(query: string, params?: unknown[]): Promise<T[]>;
|
|
8
|
+
exec?(query: string): Promise<void>;
|
|
9
|
+
transaction?<T>(callback: () => Promise<T>): Promise<T>;
|
|
10
|
+
close?(): Promise<void> | void;
|
|
11
|
+
}
|
|
12
|
+
export interface PlatformOperationStoreOptions {
|
|
13
|
+
database: RelationalDatabaseAdapter;
|
|
14
|
+
initializeSchema?: boolean;
|
|
15
|
+
now?: () => Date;
|
|
16
|
+
}
|
|
17
|
+
export interface CreatePlatformOperationStoreFromEnvOptions {
|
|
18
|
+
databaseUrl?: string | null;
|
|
19
|
+
initializeSchema?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export declare const PLATFORM_OPERATION_SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS platform_operations (\n\tid TEXT PRIMARY KEY,\n\tnamespace TEXT NOT NULL,\n\toperation TEXT NOT NULL,\n\tstatus TEXT NOT NULL,\n\ttarget TEXT NOT NULL,\n\tidempotency_key TEXT,\n\tinput_json TEXT NOT NULL DEFAULT '{}',\n\toutput_json TEXT,\n\terror_json TEXT,\n\trequested_by_type TEXT NOT NULL,\n\trequested_by_id TEXT,\n\tassigned_runner_id TEXT,\n\tlease_expires_at TEXT,\n\tcreated_at TEXT NOT NULL,\n\tupdated_at TEXT NOT NULL,\n\tstarted_at TEXT,\n\tfinished_at TEXT,\n\tcancelled_at TEXT\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS idx_platform_operations_idempotency\n\tON platform_operations(namespace, operation, idempotency_key)\n\tWHERE idempotency_key IS NOT NULL;\n\nCREATE INDEX IF NOT EXISTS idx_platform_operations_runnable\n\tON platform_operations(status, created_at ASC);\n\nCREATE TABLE IF NOT EXISTS platform_operation_events (\n\tid TEXT PRIMARY KEY,\n\toperation_id TEXT NOT NULL,\n\tseq INTEGER NOT NULL,\n\tkind TEXT NOT NULL,\n\tdata_json TEXT NOT NULL DEFAULT '{}',\n\tcreated_at TEXT NOT NULL,\n\tFOREIGN KEY (operation_id) REFERENCES platform_operations(id) ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS idx_platform_operation_events_seq\n\tON platform_operation_events(operation_id, seq);\n\nCREATE TABLE IF NOT EXISTS market_operation_runners (\n\tid TEXT PRIMARY KEY,\n\trunner_key TEXT NOT NULL UNIQUE,\n\tname TEXT NOT NULL,\n\tenvironment TEXT NOT NULL,\n\tstatus TEXT NOT NULL DEFAULT 'online',\n\tversion TEXT,\n\tcapabilities_json TEXT NOT NULL DEFAULT '[]',\n\tactive_job_count INTEGER NOT NULL DEFAULT 0,\n\tmax_concurrent_jobs INTEGER NOT NULL DEFAULT 1,\n\theartbeat_at TEXT,\n\tmetadata_json TEXT NOT NULL DEFAULT '{}',\n\tcreated_at TEXT NOT NULL,\n\tupdated_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS platform_repository_claims (\n\tid TEXT PRIMARY KEY,\n\trepository_key TEXT NOT NULL,\n\trunner_id TEXT NOT NULL,\n\tworkspace_path TEXT NOT NULL,\n\tbranch TEXT,\n\tcommit_sha TEXT,\n\tclaim_state TEXT NOT NULL DEFAULT 'active',\n\tlease_expires_at TEXT,\n\tmetadata_json TEXT NOT NULL DEFAULT '{}',\n\tcreated_at TEXT NOT NULL,\n\tupdated_at TEXT NOT NULL\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS idx_platform_repository_claims_active\n\tON platform_repository_claims(repository_key, runner_id)\n\tWHERE claim_state = 'active';\n\nCREATE INDEX IF NOT EXISTS idx_platform_repository_claims_runner\n\tON platform_repository_claims(runner_id, claim_state);\n";
|
|
22
|
+
export declare function createD1RelationalAdapter(db: {
|
|
23
|
+
prepare(query: string): {
|
|
24
|
+
bind(...values: unknown[]): {
|
|
25
|
+
run(): Promise<unknown>;
|
|
26
|
+
first<T = Record<string, unknown>>(): Promise<T | null>;
|
|
27
|
+
all<T = Record<string, unknown>>(): Promise<{
|
|
28
|
+
results?: T[];
|
|
29
|
+
} | T[]>;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
exec?(query: string): Promise<unknown>;
|
|
33
|
+
}): RelationalDatabaseAdapter;
|
|
34
|
+
export declare function createSqliteRelationalAdapter(path?: string | null): RelationalDatabaseAdapter;
|
|
35
|
+
export declare function createPostgresRelationalAdapter(databaseUrl: string): Promise<RelationalDatabaseAdapter>;
|
|
36
|
+
export declare function createRelationalAdapterFromUrl(databaseUrl: string): Promise<RelationalDatabaseAdapter>;
|
|
37
|
+
export declare function createPlatformOperationStoreFromEnv(options?: CreatePlatformOperationStoreFromEnvOptions): Promise<PlatformOperationStore>;
|
|
38
|
+
export declare class PlatformOperationStore {
|
|
39
|
+
private initialized;
|
|
40
|
+
private readonly database;
|
|
41
|
+
private readonly now;
|
|
42
|
+
private readonly initializeSchema;
|
|
43
|
+
constructor(options: PlatformOperationStoreOptions);
|
|
44
|
+
close(): Promise<void>;
|
|
45
|
+
ensureInitialized(): Promise<void>;
|
|
46
|
+
private appendPlatformOperationEvent;
|
|
47
|
+
register(request: PlatformRunnerRegistrationRequest): Promise<{
|
|
48
|
+
ok: true;
|
|
49
|
+
runner: Record<string, unknown> | null;
|
|
50
|
+
}>;
|
|
51
|
+
heartbeat(request: PlatformRunnerHeartbeatRequest): Promise<{
|
|
52
|
+
ok: true;
|
|
53
|
+
runner: Record<string, unknown> | null;
|
|
54
|
+
}>;
|
|
55
|
+
private upsertRunner;
|
|
56
|
+
getOperation(operationId: string): Promise<{
|
|
57
|
+
ok: true;
|
|
58
|
+
operation: PlatformOperation;
|
|
59
|
+
}>;
|
|
60
|
+
claimJob(input: PlatformRunnerClaimRequest): Promise<{
|
|
61
|
+
ok: true;
|
|
62
|
+
operation: PlatformOperation | null;
|
|
63
|
+
}>;
|
|
64
|
+
private assertRunnerUpdate;
|
|
65
|
+
appendEvent(operationId: string, request: PlatformRunnerJobUpdateRequest): Promise<{
|
|
66
|
+
ok: true;
|
|
67
|
+
event: PlatformOperationEvent;
|
|
68
|
+
}>;
|
|
69
|
+
renewLease(operationId: string, request: PlatformRunnerJobUpdateRequest & {
|
|
70
|
+
leaseSeconds?: number;
|
|
71
|
+
}): Promise<{
|
|
72
|
+
ok: true;
|
|
73
|
+
operation: PlatformOperation;
|
|
74
|
+
}>;
|
|
75
|
+
checkpoint(operationId: string, request: PlatformRunnerJobUpdateRequest): Promise<{
|
|
76
|
+
ok: true;
|
|
77
|
+
operation: PlatformOperation;
|
|
78
|
+
}>;
|
|
79
|
+
complete(operationId: string, request: PlatformRunnerJobUpdateRequest): Promise<{
|
|
80
|
+
ok: true;
|
|
81
|
+
operation: PlatformOperation;
|
|
82
|
+
}>;
|
|
83
|
+
fail(operationId: string, request: PlatformRunnerJobUpdateRequest): Promise<{
|
|
84
|
+
ok: true;
|
|
85
|
+
operation: PlatformOperation;
|
|
86
|
+
}>;
|
|
87
|
+
private upsertRepositoryClaim;
|
|
88
|
+
private renewRepositoryClaimsForRunner;
|
|
89
|
+
private releaseRepositoryClaimsForRunner;
|
|
90
|
+
}
|