@treeseed/sdk 0.12.18 → 0.12.19

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.
@@ -3308,12 +3308,20 @@ async function resolveRailwayTopologyForScope(input, scope, {
3308
3308
  includeInstances = ensure,
3309
3309
  includeVariables = false
3310
3310
  } = {}) {
3311
+ const imageRefValues = resolveReconcileEnvironmentValues(input, scope);
3312
+ const imageRefFingerprint = [
3313
+ "TREESEED_API_IMAGE_REF",
3314
+ "TREESEED_OPERATIONS_RUNNER_IMAGE_REF",
3315
+ "TREESEED_AGENT_MANAGER_IMAGE_REF",
3316
+ "TREESEED_AGENT_RUNNER_IMAGE_REF",
3317
+ "TREESEED_PUBLIC_TREEDX_IMAGE_REF"
3318
+ ].map((key) => `${key}=${imageRefValues[key] ?? ""}`).join("|");
3311
3319
  const normalizedServiceKeys = Array.isArray(serviceKeys) && serviceKeys.length > 0 ? [...new Set(serviceKeys.map((value) => String(value).trim()).filter(Boolean))].sort() : ["__all__"];
3312
- const cacheKey = `railway:topology:${scope}:${ensure ? "ensure" : "observe"}:${includeInstances ? "instances" : "no-instances"}:${includeVariables ? "variables" : "no-variables"}:${normalizedServiceKeys.join(",")}`;
3320
+ const cacheKey = `railway:topology:${scope}:${ensure ? "ensure" : "observe"}:${includeInstances ? "instances" : "no-instances"}:${includeVariables ? "variables" : "no-variables"}:${normalizedServiceKeys.join(",")}:${imageRefFingerprint}`;
3313
3321
  return await providerCache(input, cacheKey, async () => {
3314
3322
  const env = buildRailwayEnv(input, scope);
3315
3323
  const deployState = loadDeployState(input.context.tenantRoot, input.context.deployConfig, { target: toDeployTarget(input.context.target) });
3316
- const services = configuredRailwayServices(input.context.tenantRoot, scope).filter((service) => normalizedServiceKeys.includes("__all__") || normalizedServiceKeys.includes(service.key));
3324
+ const services = configuredRailwayServicesForInput(input, scope).filter((service) => normalizedServiceKeys.includes("__all__") || normalizedServiceKeys.includes(service.key));
3317
3325
  traceRailwayReconcile(env, "topology:start", `scope=${scope} ensure=${ensure ? "yes" : "no"} services=${services.map((service) => service.key).join(",")}`);
3318
3326
  let workspace = null;
3319
3327
  const knownProjects = [];
@@ -190,7 +190,7 @@ function deriveTreeseedDesiredUnits({
190
190
  });
191
191
  }
192
192
  const scope = target.kind === "persistent" ? target.scope : "staging";
193
- for (const configuredService of configuredRailwayServices(tenantRoot, scope)) {
193
+ for (const configuredService of configuredRailwayServices(tenantRoot, scope, env)) {
194
194
  const serviceKey = configuredService.key;
195
195
  const service = configuredService.serviceConfig ?? deployConfig.services?.[serviceKey];
196
196
  const serviceState = legacyState.services?.[serviceKey];
@@ -217,6 +217,13 @@ function deriveTreeseedDesiredUnits({
217
217
  serviceId: persistedServiceMatchesDesired ? serviceState?.serviceId ?? configuredService.serviceId : configuredService.serviceId,
218
218
  serviceName: desiredServiceName,
219
219
  rootDir: configuredService.rootDir ?? serviceState?.rootDir,
220
+ imageRef: configuredService.imageRef,
221
+ imageRefEnv: configuredService.serviceConfig?.railway?.imageRefEnv,
222
+ sourceMode: configuredService.sourceMode,
223
+ sourceRepo: configuredService.sourceRepo,
224
+ sourceBranch: configuredService.sourceBranch,
225
+ sourceCommit: configuredService.sourceCommit,
226
+ sourceRootDirectory: configuredService.sourceRootDirectory,
220
227
  environment: normalizeRailwayEnvironmentName(configuredService.railwayEnvironment ?? serviceState?.environment),
221
228
  buildCommand: configuredService.buildCommand,
222
229
  startCommand: configuredService.startCommand,
@@ -91,7 +91,7 @@ export declare function workflowReleaseCandidate(helpers: WorkflowOperationHelpe
91
91
  }>>;
92
92
  type PublishedArtifactCheck = {
93
93
  id: string;
94
- kind: 'npm' | 'docker' | 'pypi' | 'crates' | 'hex';
94
+ kind: 'npm' | 'docker' | 'pypi' | 'crates' | 'hex' | 'github-tag';
95
95
  name: string;
96
96
  version: string;
97
97
  url: string;
@@ -2576,6 +2576,46 @@ async function verifyNpmArtifact(packageName, version) {
2576
2576
  };
2577
2577
  }
2578
2578
  }
2579
+ async function fetchDockerRegistryManifestStatus(image, version) {
2580
+ const [namespace, repository] = image.split("/");
2581
+ if (!namespace || !repository) {
2582
+ return { ok: false, status: null, message: `Invalid Docker image name ${image}.` };
2583
+ }
2584
+ const tokenUrl = `https://auth.docker.io/token?service=registry.docker.io&scope=repository:${namespace}/${repository}:pull`;
2585
+ try {
2586
+ const tokenResponse = await fetchJsonForArtifact(tokenUrl);
2587
+ const token = typeof stringRecord(tokenResponse.json)?.token === "string" ? String(stringRecord(tokenResponse.json)?.token) : "";
2588
+ if (!tokenResponse.ok || !token) {
2589
+ return { ok: false, status: tokenResponse.status, message: `Docker registry token request failed for ${image}.` };
2590
+ }
2591
+ const controller = new AbortController();
2592
+ const timeout = setTimeout(() => controller.abort(), 2e4);
2593
+ try {
2594
+ const manifestResponse = await fetch(`https://registry-1.docker.io/v2/${namespace}/${repository}/manifests/${version}`, {
2595
+ method: "HEAD",
2596
+ headers: {
2597
+ accept: [
2598
+ "application/vnd.docker.distribution.manifest.list.v2+json",
2599
+ "application/vnd.oci.image.index.v1+json",
2600
+ "application/vnd.docker.distribution.manifest.v2+json"
2601
+ ].join(", "),
2602
+ authorization: `Bearer ${token}`,
2603
+ "user-agent": "treeseed-release-verifier/1.0 (https://treeseed.dev)"
2604
+ },
2605
+ signal: controller.signal
2606
+ });
2607
+ return {
2608
+ ok: manifestResponse.ok,
2609
+ status: manifestResponse.status,
2610
+ ...manifestResponse.ok ? {} : { message: `Docker registry manifest for ${image}:${version} is not pullable yet.` }
2611
+ };
2612
+ } finally {
2613
+ clearTimeout(timeout);
2614
+ }
2615
+ } catch (error) {
2616
+ return { ok: false, status: null, message: error instanceof Error ? error.message : String(error) };
2617
+ }
2618
+ }
2579
2619
  async function verifyDockerHubArtifact(image, version) {
2580
2620
  const [namespace, repository] = image.split("/");
2581
2621
  const url = `https://hub.docker.com/v2/repositories/${namespace}/${repository}/tags/${version}`;
@@ -2583,7 +2623,8 @@ async function verifyDockerHubArtifact(image, version) {
2583
2623
  const response = await fetchJsonForArtifact(url);
2584
2624
  const images = Array.isArray(stringRecord(response.json)?.images) ? stringRecord(response.json)?.images : [];
2585
2625
  const architectures = new Set(images.map((entry) => stringRecord(entry)).map((entry) => typeof entry?.architecture === "string" ? entry.architecture : null).filter((entry) => Boolean(entry)));
2586
- const ok = response.ok && architectures.has("amd64") && architectures.has("arm64");
2626
+ const registry = await fetchDockerRegistryManifestStatus(image, version);
2627
+ const ok = response.ok && architectures.has("amd64") && architectures.has("arm64") && registry.ok;
2587
2628
  return {
2588
2629
  id: `docker:${image}:${version}`,
2589
2630
  kind: "docker",
@@ -2591,8 +2632,8 @@ async function verifyDockerHubArtifact(image, version) {
2591
2632
  version,
2592
2633
  url,
2593
2634
  ok,
2594
- status: response.status,
2595
- ...ok ? {} : { message: `${image}:${version} was not found on Docker Hub with amd64 and arm64 images.` }
2635
+ status: registry.status ?? response.status,
2636
+ ...ok ? {} : { message: registry.message ?? `${image}:${version} was not found on Docker Hub with amd64 and arm64 images.` }
2596
2637
  };
2597
2638
  } catch (error) {
2598
2639
  return {
@@ -2607,6 +2648,33 @@ async function verifyDockerHubArtifact(image, version) {
2607
2648
  };
2608
2649
  }
2609
2650
  }
2651
+ async function verifyGitHubTagArtifact(repository, version) {
2652
+ const url = `https://api.github.com/repos/${repository}/git/ref/tags/${encodeURIComponent(version)}`;
2653
+ try {
2654
+ const response = await fetchJsonForArtifact(url);
2655
+ return {
2656
+ id: `github-tag:${repository}:${version}`,
2657
+ kind: "github-tag",
2658
+ name: repository,
2659
+ version,
2660
+ url,
2661
+ ok: response.ok,
2662
+ status: response.status,
2663
+ ...response.ok ? {} : { message: `GitHub tag ${repository}@${version} was not found.` }
2664
+ };
2665
+ } catch (error) {
2666
+ return {
2667
+ id: `github-tag:${repository}:${version}`,
2668
+ kind: "github-tag",
2669
+ name: repository,
2670
+ version,
2671
+ url,
2672
+ ok: false,
2673
+ status: null,
2674
+ message: error instanceof Error ? error.message : String(error)
2675
+ };
2676
+ }
2677
+ }
2610
2678
  async function verifySimpleRegistryArtifact(input) {
2611
2679
  try {
2612
2680
  const response = await fetchJsonForArtifact(input.url);
@@ -2635,6 +2703,21 @@ async function verifySimpleRegistryArtifact(input) {
2635
2703
  }
2636
2704
  async function collectPublishedReleaseArtifactChecks(selectedVersions) {
2637
2705
  const checks = [];
2706
+ const githubRepositories = {
2707
+ "@treeseed/sdk": "treeseed-ai/sdk",
2708
+ "@treeseed/ui": "treeseed-ai/ui",
2709
+ "@treeseed/core": "treeseed-ai/core",
2710
+ "@treeseed/admin": "treeseed-ai/admin",
2711
+ "@treeseed/cli": "treeseed-ai/cli",
2712
+ "@treeseed/agent": "treeseed-ai/agent",
2713
+ "@treeseed/api": "treeseed-ai/api",
2714
+ treedx: "treeseed-ai/treedx",
2715
+ "@treeseed/treedx": "treeseed-ai/treedx"
2716
+ };
2717
+ for (const [packageName, repository] of Object.entries(githubRepositories)) {
2718
+ const version = selectedVersions.get(packageName);
2719
+ if (version) checks.push(await verifyGitHubTagArtifact(repository, version));
2720
+ }
2638
2721
  const npmPackages = ["@treeseed/sdk", "@treeseed/ui", "@treeseed/core", "@treeseed/admin", "@treeseed/cli", "@treeseed/agent"];
2639
2722
  for (const packageName of npmPackages) {
2640
2723
  const version = selectedVersions.get(packageName);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treeseed/sdk",
3
- "version": "0.12.18",
3
+ "version": "0.12.19",
4
4
  "description": "Shared Treeseed SDK for content-backed and D1-backed object models.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {