@treeseed/sdk 0.6.7 → 0.6.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/copilot.d.ts +15 -0
  2. package/dist/copilot.js +75 -0
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.js +18 -0
  5. package/dist/managed-dependencies.d.ts +56 -0
  6. package/dist/managed-dependencies.js +668 -0
  7. package/dist/operations/providers/default.js +30 -1
  8. package/dist/operations/services/commit-message-provider.d.ts +33 -0
  9. package/dist/operations/services/commit-message-provider.js +319 -0
  10. package/dist/operations/services/config-runtime.js +50 -23
  11. package/dist/operations/services/git-remote-policy.d.ts +9 -0
  12. package/dist/operations/services/git-remote-policy.js +55 -0
  13. package/dist/operations/services/git-workflow.js +22 -3
  14. package/dist/operations/services/github-api.js +9 -4
  15. package/dist/operations/services/knowledge-coop-launch.js +4 -0
  16. package/dist/operations/services/local-dev.js +7 -2
  17. package/dist/operations/services/package-reference-policy.d.ts +70 -0
  18. package/dist/operations/services/package-reference-policy.js +330 -0
  19. package/dist/operations/services/project-platform.d.ts +4 -0
  20. package/dist/operations/services/project-platform.js +28 -4
  21. package/dist/operations/services/railway-deploy.d.ts +4 -1
  22. package/dist/operations/services/railway-deploy.js +76 -38
  23. package/dist/operations/services/repository-save-orchestrator.d.ts +172 -0
  24. package/dist/operations/services/repository-save-orchestrator.js +1462 -0
  25. package/dist/operations/services/workspace-dependency-mode.d.ts +70 -0
  26. package/dist/operations/services/workspace-dependency-mode.js +404 -0
  27. package/dist/operations/services/workspace-preflight.js +5 -0
  28. package/dist/operations/services/workspace-save.js +10 -6
  29. package/dist/operations-registry.js +5 -0
  30. package/dist/operations-types.d.ts +1 -0
  31. package/dist/platform/books-data.js +4 -1
  32. package/dist/platform/env.yaml +6 -3
  33. package/dist/reconcile/builtin-adapters.js +37 -7
  34. package/dist/scripts/cleanup-markdown.js +4 -0
  35. package/dist/scripts/prepare.js +14 -0
  36. package/dist/scripts/publish-package.js +5 -0
  37. package/dist/scripts/tenant-workflow-action.js +11 -2
  38. package/dist/verification.js +46 -13
  39. package/dist/workflow/operations.d.ts +381 -55
  40. package/dist/workflow/operations.js +725 -261
  41. package/dist/workflow-state.d.ts +40 -1
  42. package/dist/workflow-state.js +220 -17
  43. package/dist/workflow-support.d.ts +3 -0
  44. package/dist/workflow-support.js +34 -0
  45. package/dist/workflow.d.ts +19 -3
  46. package/dist/workflow.js +3 -3
  47. package/dist/wrangler-d1.js +6 -1
  48. package/package.json +17 -1
  49. package/templates/github/deploy.workflow.yml +59 -14
@@ -1,11 +1,20 @@
1
1
  import { run, workspaceRoot } from "./workspace-tools.js";
2
2
  import { currentBranch, gitStatusPorcelain, repoRoot } from "./workspace-save.js";
3
+ import { ensureSshPushUrlForOrigin } from "./git-remote-policy.js";
4
+ import { createTreeseedManagedToolEnv, resolveTreeseedToolBinary } from "../../managed-dependencies.js";
3
5
  const STAGING_BRANCH = "staging";
4
6
  const PRODUCTION_BRANCH = "main";
5
7
  const RESERVED_BRANCHES = /* @__PURE__ */ new Set([STAGING_BRANCH, PRODUCTION_BRANCH]);
6
8
  function runGit(args, { cwd, capture = false } = {}) {
7
9
  return run("git", args, { cwd, capture });
8
10
  }
11
+ function ensureWritableOrigin(repoDir) {
12
+ try {
13
+ const remoteUrl = runGit(["remote", "get-url", "origin"], { cwd: repoDir, capture: true }).trim();
14
+ ensureSshPushUrlForOrigin(repoDir, remoteUrl);
15
+ } catch {
16
+ }
17
+ }
9
18
  function repoHasStagedChanges(repoDir) {
10
19
  try {
11
20
  runGit(["diff", "--cached", "--quiet"], { cwd: repoDir });
@@ -130,7 +139,7 @@ function syncBranchWithOrigin(repoDir, branchName) {
130
139
  checkoutBranch(repoDir, branchName);
131
140
  }
132
141
  if (remoteBranchExists(repoDir, branchName)) {
133
- runGit(["pull", "--rebase", "origin", branchName], { cwd: repoDir });
142
+ runGit(["merge", "--ff-only", `origin/${branchName}`], { cwd: repoDir });
134
143
  }
135
144
  }
136
145
  function createFeatureBranchFromStaging(cwd, branchName) {
@@ -144,6 +153,7 @@ function createFeatureBranchFromStaging(cwd, branchName) {
144
153
  return result;
145
154
  }
146
155
  function pushBranch(repoDir, branchName, { setUpstream = false } = {}) {
156
+ ensureWritableOrigin(repoDir);
147
157
  const args = setUpstream ? ["push", "-u", "origin", branchName] : ["push", "origin", branchName];
148
158
  runGit(args, { cwd: repoDir });
149
159
  }
@@ -188,6 +198,7 @@ function deleteRemoteBranch(repoDir, branchName) {
188
198
  if (!remoteBranchExists(repoDir, branchName)) {
189
199
  return false;
190
200
  }
201
+ ensureWritableOrigin(repoDir);
191
202
  runGit(["push", "origin", "--delete", branchName], { cwd: repoDir });
192
203
  return true;
193
204
  }
@@ -268,6 +279,7 @@ function createDeprecatedTaskTag(repoDir, branchName, message) {
268
279
  const shortSha = head.slice(0, 12);
269
280
  const tagName = `deprecated/${taskTagSlug(branchName)}/${shortSha}`;
270
281
  runGit(["tag", "-a", tagName, head, "-m", message], { cwd: repoDir });
282
+ ensureWritableOrigin(repoDir);
271
283
  runGit(["push", "origin", tagName], { cwd: repoDir, capture: true });
272
284
  return { tagName, head };
273
285
  }
@@ -276,7 +288,14 @@ function waitForStagingAutomation(repoDir) {
276
288
  return { status: "skipped", reason: "stubbed" };
277
289
  }
278
290
  try {
279
- run("gh", ["run", "watch", "--branch", STAGING_BRANCH, "--exit-status"], { cwd: repoDir });
291
+ const gh = resolveTreeseedToolBinary("gh");
292
+ if (!gh) {
293
+ throw new Error("GitHub CLI `gh` is unavailable.");
294
+ }
295
+ run(gh, ["run", "watch", "--branch", STAGING_BRANCH, "--exit-status"], {
296
+ cwd: repoDir,
297
+ env: createTreeseedManagedToolEnv(process.env)
298
+ });
280
299
  return { status: "completed", branch: STAGING_BRANCH };
281
300
  } catch (error) {
282
301
  throw new Error([
@@ -308,7 +327,7 @@ function mergeBranchIntoTarget(cwd = workspaceRoot(), { sourceBranch, targetBran
308
327
  const repoDir = prepareReleaseBranches(cwd);
309
328
  checkoutBranch(repoDir, targetBranch);
310
329
  if (remoteBranchExists(repoDir, targetBranch)) {
311
- runGit(["pull", "--rebase", "origin", targetBranch], { cwd: repoDir });
330
+ runGit(["merge", "--ff-only", `origin/${targetBranch}`], { cwd: repoDir });
312
331
  }
313
332
  runGit(["merge", "--no-ff", sourceBranch, "-m", message], { cwd: repoDir });
314
333
  pushBranch(repoDir, STAGING_BRANCH);
@@ -2,6 +2,7 @@ import { Buffer } from "node:buffer";
2
2
  import { spawnSync } from "node:child_process";
3
3
  import { createRequire } from "node:module";
4
4
  import { Octokit } from "octokit";
5
+ import { createTreeseedManagedToolEnv, resolveTreeseedToolBinary } from "../../managed-dependencies.js";
5
6
  const require2 = createRequire(import.meta.url);
6
7
  const sodium = require2("libsodium-wrappers");
7
8
  const DEFAULT_GITHUB_API_TIMEOUT_MS = 6e4;
@@ -465,8 +466,12 @@ function upsertGitHubRepositoryVariableWithGhCli(repository, name, value, {
465
466
  GH_TOKEN: token,
466
467
  GITHUB_TOKEN: token
467
468
  };
469
+ const gh = resolveTreeseedToolBinary("gh", { env: ghEnv });
470
+ if (!gh) {
471
+ throw new Error("GitHub CLI `gh` is unavailable.");
472
+ }
468
473
  const create = spawnSync(
469
- "gh",
474
+ gh,
470
475
  [
471
476
  "api",
472
477
  `repos/${owner}/${repo}/actions/variables`,
@@ -477,7 +482,7 @@ function upsertGitHubRepositoryVariableWithGhCli(repository, name, value, {
477
482
  "-f",
478
483
  `value=${value}`
479
484
  ],
480
- { encoding: "utf8", env: ghEnv }
485
+ { encoding: "utf8", env: createTreeseedManagedToolEnv(ghEnv) }
481
486
  );
482
487
  if (create.status === 0) {
483
488
  return;
@@ -488,7 +493,7 @@ ${create.stderr ?? ""}`.trim();
488
493
  throw new Error(combinedCreateOutput || `gh api exited with status ${create.status ?? 1}`);
489
494
  }
490
495
  const update = spawnSync(
491
- "gh",
496
+ gh,
492
497
  [
493
498
  "api",
494
499
  `repos/${owner}/${repo}/actions/variables/${name}`,
@@ -499,7 +504,7 @@ ${create.stderr ?? ""}`.trim();
499
504
  "-f",
500
505
  `value=${value}`
501
506
  ],
502
- { encoding: "utf8", env: ghEnv }
507
+ { encoding: "utf8", env: createTreeseedManagedToolEnv(ghEnv) }
503
508
  );
504
509
  if (update.status === 0) {
505
510
  return;
@@ -17,6 +17,7 @@ import { loadCliDeployConfig } from "./runtime-tools.js";
17
17
  import { templateCatalogRoot } from "./runtime-paths.js";
18
18
  import { scaffoldTemplateProject } from "./template-registry.js";
19
19
  import { buildKnowledgeCoopKnowledgePackPackage, buildKnowledgeCoopTemplatePackage, importKnowledgeCoopKnowledgePack } from "./knowledge-coop-packaging.js";
20
+ import { resolveTreeseedToolBinary } from "../../managed-dependencies.js";
20
21
  class KnowledgeCoopLaunchError extends Error {
21
22
  phase;
22
23
  phases;
@@ -531,6 +532,9 @@ function loadProjectMetadata(projectId, input, seed, workstream, siteUrl, projec
531
532
  };
532
533
  }
533
534
  function commandAvailable(command) {
535
+ if (command === "gh" || command === "wrangler" || command === "railway") {
536
+ return Boolean(resolveTreeseedToolBinary(command));
537
+ }
534
538
  return spawnSync("bash", ["-lc", `command -v ${command}`], { stdio: "ignore" }).status === 0;
535
539
  }
536
540
  function appendPhase(phases, phase, status, detail) {
@@ -7,6 +7,7 @@ import {
7
7
  fixtureWranglerConfig,
8
8
  corePackageRoot
9
9
  } from "./runtime-paths.js";
10
+ import { resolveTreeseedToolCommand } from "../../managed-dependencies.js";
10
11
  function mergeEnv(extraEnv = {}) {
11
12
  return { ...process.env, ...extraEnv };
12
13
  }
@@ -74,9 +75,13 @@ function prepareCloudflareLocalRuntime({ envOverrides = {}, persistTo, outDir }
74
75
  });
75
76
  }
76
77
  function startWranglerDev(args = [], options = {}) {
78
+ const wrangler = resolveTreeseedToolCommand("wrangler");
79
+ if (!wrangler) {
80
+ throw new Error("Wrangler CLI is unavailable.");
81
+ }
77
82
  return spawnProcess(
78
- "wrangler",
79
- ["dev", "--local", "--config", fixtureWranglerConfig, ...args],
83
+ wrangler.command,
84
+ [...wrangler.argsPrefix, "dev", "--local", "--config", fixtureWranglerConfig, ...args],
80
85
  {
81
86
  ...options,
82
87
  cwd: options.cwd ?? fixtureRoot
@@ -0,0 +1,70 @@
1
+ export type DevDependencyReferenceMode = 'git-tag' | 'registry-prerelease';
2
+ export type DevTagCleanupMode = 'safe-after-release' | 'off';
3
+ export type GitDependencyProtocol = 'preserve-origin' | 'https' | 'ssh';
4
+ export type PackageDependencyReference = {
5
+ packageName: string;
6
+ version: string;
7
+ spec: string;
8
+ manifestSpec: string;
9
+ installSpec: string;
10
+ tagName: string | null;
11
+ remoteUrl: string | null;
12
+ mode: 'stable-semver' | 'dev-git-tag' | 'dev-registry-prerelease';
13
+ };
14
+ export type RewrittenDevReference = {
15
+ packageName: string;
16
+ field: string;
17
+ from: string;
18
+ to: string;
19
+ tagName: string | null;
20
+ };
21
+ export declare function internalDependencyFields(packageJson: Record<string, unknown> | null): string[];
22
+ export declare function isPrereleaseVersion(version: string): boolean;
23
+ export declare function isStableVersion(version: string): boolean;
24
+ export declare function isGitDependencySpec(spec: string): boolean;
25
+ export declare function devTagFromDependencySpec(spec: string): string | null;
26
+ export declare function normalizeGitRemoteForDependency(remoteUrl: string, protocol?: GitDependencyProtocol): string | null;
27
+ export declare function normalizeGitRemoteForManifest(remoteUrl: string, protocol?: GitDependencyProtocol): string | null;
28
+ export declare function createPackageDependencyReference(input: {
29
+ packageName: string;
30
+ version: string;
31
+ branchMode: 'package-release-main' | 'package-dev-save';
32
+ remoteUrl?: string | null;
33
+ devDependencyReferenceMode?: DevDependencyReferenceMode;
34
+ gitDependencyProtocol?: GitDependencyProtocol;
35
+ }): PackageDependencyReference;
36
+ export declare function updateInternalDependencySpecs(packageJson: Record<string, unknown> | null, references: Map<string, PackageDependencyReference>): RewrittenDevReference[];
37
+ export declare function rewriteInternalDependenciesToStableVersions(root: any, versions: Map<string, string>): (RewrittenDevReference & {
38
+ repoName: string;
39
+ packageJsonPath: string;
40
+ })[];
41
+ export declare function rewriteProjectInternalDependenciesToStableVersions(root: any, versions: Map<string, string>): (RewrittenDevReference & {
42
+ repoName: string;
43
+ packageJsonPath: string;
44
+ })[];
45
+ export declare function collectInternalDevReferenceIssues(root?: any, packageNames?: Set<any>): {
46
+ repoName: string;
47
+ filePath: string;
48
+ field?: string;
49
+ dependencyName?: string;
50
+ spec: string;
51
+ reason: string;
52
+ }[];
53
+ export declare function assertNoInternalDevReferences(root?: any, packageNames?: Set<string>): void;
54
+ export declare function createDevTagMessage(input: {
55
+ packageName: string;
56
+ version: string;
57
+ branch: string;
58
+ commitSha: string;
59
+ workflowRunId?: string | null;
60
+ createdAt?: string;
61
+ }): string;
62
+ export declare function gitTagMessage(repoDir: string, tagName: string): string;
63
+ export declare function tagHasTreeseedDevMetadata(repoDir: string, tagName: string): boolean;
64
+ export declare function cleanupDevTags(repoDir: string, tagNames: string[], activeReferences?: string[]): {
65
+ cleaned: string[];
66
+ skipped: {
67
+ tagName: string;
68
+ reason: string;
69
+ }[];
70
+ };
@@ -0,0 +1,330 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import { run, workspacePackages, workspaceRoot } from "./workspace-tools.js";
5
+ const INTERNAL_DEPENDENCY_FIELDS = ["dependencies", "optionalDependencies", "peerDependencies", "devDependencies"];
6
+ function readJson(filePath) {
7
+ return JSON.parse(readFileSync(filePath, "utf8"));
8
+ }
9
+ function writeJson(filePath, value) {
10
+ writeFileSync(filePath, `${JSON.stringify(value, null, 2)}
11
+ `, "utf8");
12
+ }
13
+ function internalDependencyFields(packageJson) {
14
+ if (!packageJson) return [];
15
+ return INTERNAL_DEPENDENCY_FIELDS.filter((field) => packageJson[field] && typeof packageJson[field] === "object" && !Array.isArray(packageJson[field]));
16
+ }
17
+ function isPrereleaseVersion(version) {
18
+ return /^\d+\.\d+\.\d+-[0-9A-Za-z.-]+$/u.test(String(version).trim());
19
+ }
20
+ function isStableVersion(version) {
21
+ return /^\d+\.\d+\.\d+$/u.test(String(version).trim());
22
+ }
23
+ function isGitDependencySpec(spec) {
24
+ return /^(?:git\+|github:|gitlab:|bitbucket:|ssh:\/\/|https:\/\/|file:)/u.test(String(spec).trim()) && String(spec).includes("#");
25
+ }
26
+ function devTagFromDependencySpec(spec) {
27
+ const value = String(spec).trim();
28
+ const hashIndex = value.lastIndexOf("#");
29
+ if (hashIndex === -1) return null;
30
+ const ref = decodeURIComponent(value.slice(hashIndex + 1));
31
+ return ref.includes("-dev.") ? ref : null;
32
+ }
33
+ function normalizeGitRemoteForDependency(remoteUrl, protocol = "preserve-origin") {
34
+ const remote = String(remoteUrl).trim();
35
+ if (!remote) return null;
36
+ if (/^file:\/\//u.test(remote)) return remote;
37
+ if (remote.startsWith("/") || remote.startsWith("./") || remote.startsWith("../")) {
38
+ return `git+${pathToFileURL(remote).href}`;
39
+ }
40
+ if (remote.endsWith(".git") && existsSync(remote)) {
41
+ return `git+${pathToFileURL(remote).href}`;
42
+ }
43
+ const sshMatch = remote.match(/^git@([^:]+):(.+?)(?:\.git)?$/u);
44
+ if (sshMatch) {
45
+ if (protocol === "https") {
46
+ return `git+https://${sshMatch[1]}/${sshMatch[2]}.git`;
47
+ }
48
+ return `git+ssh://git@${sshMatch[1]}/${sshMatch[2]}.git`;
49
+ }
50
+ const httpsMatch = remote.match(/^https:\/\/([^/]+)\/(.+?)(?:\.git)?$/u);
51
+ if (httpsMatch) {
52
+ if (protocol === "ssh") {
53
+ return `git+ssh://git@${httpsMatch[1]}/${httpsMatch[2]}.git`;
54
+ }
55
+ return `git+https://${httpsMatch[1]}/${httpsMatch[2]}.git`;
56
+ }
57
+ if (/^ssh:\/\//u.test(remote)) return `git+${remote}`;
58
+ if (/^git\+/u.test(remote)) return remote;
59
+ return remote;
60
+ }
61
+ function normalizeGitRemoteForManifest(remoteUrl, protocol = "preserve-origin") {
62
+ const dependencyRemote = normalizeGitRemoteForDependency(remoteUrl, protocol);
63
+ if (!dependencyRemote) return null;
64
+ const githubSshMatch = dependencyRemote.match(/^git\+ssh:\/\/git@github\.com\/(.+?)(?:\.git)?$/u);
65
+ if (githubSshMatch) {
66
+ return `github:${githubSshMatch[1]}`;
67
+ }
68
+ const githubHttpsMatch = dependencyRemote.match(/^git\+https:\/\/github\.com\/(.+?)(?:\.git)?$/u);
69
+ if (githubHttpsMatch) {
70
+ return `github:${githubHttpsMatch[1]}`;
71
+ }
72
+ return dependencyRemote;
73
+ }
74
+ function createPackageDependencyReference(input) {
75
+ if (input.branchMode === "package-release-main") {
76
+ return {
77
+ packageName: input.packageName,
78
+ version: input.version,
79
+ spec: input.version,
80
+ manifestSpec: input.version,
81
+ installSpec: input.version,
82
+ tagName: input.version,
83
+ remoteUrl: input.remoteUrl ?? null,
84
+ mode: "stable-semver"
85
+ };
86
+ }
87
+ if ((input.devDependencyReferenceMode ?? "git-tag") === "registry-prerelease") {
88
+ return {
89
+ packageName: input.packageName,
90
+ version: input.version,
91
+ spec: input.version,
92
+ manifestSpec: input.version,
93
+ installSpec: input.version,
94
+ tagName: input.version,
95
+ remoteUrl: input.remoteUrl ?? null,
96
+ mode: "dev-registry-prerelease"
97
+ };
98
+ }
99
+ const installRemote = normalizeGitRemoteForDependency(input.remoteUrl ?? "", input.gitDependencyProtocol ?? "preserve-origin");
100
+ const manifestRemote = normalizeGitRemoteForManifest(input.remoteUrl ?? "", input.gitDependencyProtocol ?? "preserve-origin");
101
+ if (!installRemote || !manifestRemote) {
102
+ throw new Error(`Unable to create Git-tag dependency for ${input.packageName}; origin remote is missing.`);
103
+ }
104
+ const manifestSpec = `${manifestRemote}#${input.version}`;
105
+ return {
106
+ packageName: input.packageName,
107
+ version: input.version,
108
+ spec: manifestSpec,
109
+ manifestSpec,
110
+ installSpec: manifestSpec,
111
+ tagName: input.version,
112
+ remoteUrl: input.remoteUrl ?? null,
113
+ mode: "dev-git-tag"
114
+ };
115
+ }
116
+ function updateInternalDependencySpecs(packageJson, references) {
117
+ if (!packageJson) return [];
118
+ const changed = [];
119
+ for (const field of internalDependencyFields(packageJson)) {
120
+ const values = packageJson[field];
121
+ for (const [depName, reference] of references.entries()) {
122
+ if (!(depName in values)) continue;
123
+ const current = String(values[depName]);
124
+ const nextSpec = reference.manifestSpec ?? reference.spec;
125
+ if (current === nextSpec) continue;
126
+ values[depName] = nextSpec;
127
+ changed.push({
128
+ packageName: depName,
129
+ field,
130
+ from: current,
131
+ to: nextSpec,
132
+ tagName: devTagFromDependencySpec(current) ?? (isPrereleaseVersion(current) ? current : null)
133
+ });
134
+ }
135
+ }
136
+ return changed;
137
+ }
138
+ function rewriteInternalDependenciesToStableVersions(root = workspaceRoot(), versions) {
139
+ const rewrites = [];
140
+ for (const pkg of workspacePackages(root)) {
141
+ const packageJsonPath = resolve(pkg.dir, "package.json");
142
+ const packageJson = readJson(packageJsonPath);
143
+ const changed = updateInternalDependencySpecs(
144
+ packageJson,
145
+ new Map([...versions.entries()].map(([packageName, version]) => [packageName, {
146
+ packageName,
147
+ version,
148
+ spec: version,
149
+ manifestSpec: version,
150
+ installSpec: version,
151
+ tagName: version,
152
+ remoteUrl: null,
153
+ mode: "stable-semver"
154
+ }]))
155
+ );
156
+ if (changed.length === 0) continue;
157
+ writeJson(packageJsonPath, packageJson);
158
+ rewrites.push(...changed.map((entry) => ({
159
+ ...entry,
160
+ repoName: pkg.name,
161
+ packageJsonPath
162
+ })));
163
+ }
164
+ return rewrites;
165
+ }
166
+ function rewriteProjectInternalDependenciesToStableVersions(root = workspaceRoot(), versions) {
167
+ const rewrites = [];
168
+ const targets = [
169
+ { name: "@treeseed/market", dir: root },
170
+ ...workspacePackages(root).map((pkg) => ({ name: pkg.name, dir: pkg.dir }))
171
+ ];
172
+ for (const target of targets) {
173
+ const packageJsonPath = resolve(target.dir, "package.json");
174
+ if (!existsSync(packageJsonPath)) continue;
175
+ const packageJson = readJson(packageJsonPath);
176
+ const changed = updateInternalDependencySpecs(
177
+ packageJson,
178
+ new Map([...versions.entries()].map(([packageName, version]) => [packageName, {
179
+ packageName,
180
+ version,
181
+ spec: version,
182
+ manifestSpec: version,
183
+ installSpec: version,
184
+ tagName: version,
185
+ remoteUrl: null,
186
+ mode: "stable-semver"
187
+ }]))
188
+ );
189
+ if (changed.length === 0) continue;
190
+ writeJson(packageJsonPath, packageJson);
191
+ rewrites.push(...changed.map((entry) => ({
192
+ ...entry,
193
+ repoName: target.name,
194
+ packageJsonPath
195
+ })));
196
+ }
197
+ return rewrites;
198
+ }
199
+ function collectInternalDevReferenceIssues(root = workspaceRoot(), packageNames = new Set(workspacePackages(root).map((pkg) => pkg.name))) {
200
+ const issues = [];
201
+ const manifestRoots = [
202
+ { name: "@treeseed/market", dir: root },
203
+ ...workspacePackages(root).map((pkg) => ({ name: pkg.name, dir: pkg.dir }))
204
+ ];
205
+ for (const pkg of manifestRoots) {
206
+ const packageJsonPath = resolve(pkg.dir, "package.json");
207
+ if (!existsSync(packageJsonPath)) continue;
208
+ const packageJson = readJson(packageJsonPath);
209
+ for (const field of internalDependencyFields(packageJson)) {
210
+ const values = packageJson[field];
211
+ for (const [depName, specValue] of Object.entries(values)) {
212
+ if (!packageNames.has(depName)) continue;
213
+ const spec = String(specValue);
214
+ if (isGitDependencySpec(spec) || devTagFromDependencySpec(spec)) {
215
+ issues.push({ repoName: pkg.name, filePath: packageJsonPath, field, dependencyName: depName, spec, reason: "git-dev-ref" });
216
+ } else if (isPrereleaseVersion(spec)) {
217
+ issues.push({ repoName: pkg.name, filePath: packageJsonPath, field, dependencyName: depName, spec, reason: "prerelease-dev-ref" });
218
+ }
219
+ }
220
+ }
221
+ }
222
+ const lockRoots = [{ name: "@treeseed/market", dir: root }, ...workspacePackages(root).map((pkg) => ({ name: pkg.name, dir: pkg.dir }))];
223
+ for (const lockRoot of lockRoots) {
224
+ for (const lockName of ["package-lock.json", "npm-shrinkwrap.json"]) {
225
+ const lockPath = resolve(lockRoot.dir, lockName);
226
+ if (!existsSync(lockPath)) continue;
227
+ const lockfile = readJson(lockPath);
228
+ const packageEntries = lockfile.packages && typeof lockfile.packages === "object" ? Object.values(lockfile.packages) : [];
229
+ for (const packageName of packageNames) {
230
+ const entries = [
231
+ lockfile.dependencies?.[packageName],
232
+ ...packageEntries.map((entry) => entry && typeof entry === "object" ? entry.dependencies?.[packageName] : null)
233
+ ];
234
+ for (const entry of entries) {
235
+ if (!entry || typeof entry !== "object") continue;
236
+ const record = entry;
237
+ const spec = [
238
+ record.version,
239
+ record.resolved,
240
+ record.from
241
+ ].map((value) => typeof value === "string" ? value : "").find(
242
+ (value) => isGitDependencySpec(value) || devTagFromDependencySpec(value) || isPrereleaseVersion(value)
243
+ );
244
+ if (spec) {
245
+ issues.push({ repoName: lockRoot.name, filePath: lockPath, spec, reason: "lockfile-dev-ref", dependencyName: packageName });
246
+ }
247
+ }
248
+ }
249
+ }
250
+ }
251
+ return issues;
252
+ }
253
+ function assertNoInternalDevReferences(root = workspaceRoot(), packageNames) {
254
+ const issues = collectInternalDevReferenceIssues(root, packageNames);
255
+ if (issues.length === 0) return;
256
+ const rendered = issues.map((issue) => `${issue.filePath}${issue.field ? ` ${issue.field}.${issue.dependencyName}` : ""}: ${issue.reason} ${issue.spec}`).join("\n");
257
+ throw new Error(`Stable release still contains internal Git/dev dependency references.
258
+ ${rendered}`);
259
+ }
260
+ function createDevTagMessage(input) {
261
+ const branchSlug = input.branch.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "dev";
262
+ return [
263
+ `save: ${input.packageName} ${input.version}`,
264
+ "",
265
+ "treeseed-dev-tag: true",
266
+ `package: ${input.packageName}`,
267
+ `version: ${input.version}`,
268
+ `branch: ${input.branch}`,
269
+ `branchSlug: ${branchSlug}`,
270
+ `createdAt: ${input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()}`,
271
+ `workflowRunId: ${input.workflowRunId ?? ""}`,
272
+ `commitSha: ${input.commitSha}`
273
+ ].join("\n");
274
+ }
275
+ function gitTagMessage(repoDir, tagName) {
276
+ try {
277
+ return run("git", ["tag", "-l", tagName, "--format=%(contents)"], { cwd: repoDir, capture: true });
278
+ } catch {
279
+ return "";
280
+ }
281
+ }
282
+ function tagHasTreeseedDevMetadata(repoDir, tagName) {
283
+ return gitTagMessage(repoDir, tagName).includes("treeseed-dev-tag: true");
284
+ }
285
+ function cleanupDevTags(repoDir, tagNames, activeReferences = []) {
286
+ const active = new Set(activeReferences.filter(Boolean));
287
+ const cleaned = [];
288
+ const skipped = [];
289
+ for (const tagName of [...new Set(tagNames.filter(Boolean))].sort()) {
290
+ if (!tagName.includes("-dev.")) {
291
+ skipped.push({ tagName, reason: "not-dev-tag" });
292
+ continue;
293
+ }
294
+ if (active.has(tagName)) {
295
+ skipped.push({ tagName, reason: "still-referenced" });
296
+ continue;
297
+ }
298
+ if (!tagHasTreeseedDevMetadata(repoDir, tagName)) {
299
+ skipped.push({ tagName, reason: "missing-treeseed-metadata" });
300
+ continue;
301
+ }
302
+ try {
303
+ run("git", ["tag", "-d", tagName], { cwd: repoDir });
304
+ run("git", ["push", "origin", `:refs/tags/${tagName}`], { cwd: repoDir });
305
+ cleaned.push(tagName);
306
+ } catch (error) {
307
+ skipped.push({ tagName, reason: error instanceof Error ? error.message : String(error) });
308
+ }
309
+ }
310
+ return { cleaned, skipped };
311
+ }
312
+ export {
313
+ assertNoInternalDevReferences,
314
+ cleanupDevTags,
315
+ collectInternalDevReferenceIssues,
316
+ createDevTagMessage,
317
+ createPackageDependencyReference,
318
+ devTagFromDependencySpec,
319
+ gitTagMessage,
320
+ internalDependencyFields,
321
+ isGitDependencySpec,
322
+ isPrereleaseVersion,
323
+ isStableVersion,
324
+ normalizeGitRemoteForDependency,
325
+ normalizeGitRemoteForManifest,
326
+ rewriteInternalDependenciesToStableVersions,
327
+ rewriteProjectInternalDependenciesToStableVersions,
328
+ tagHasTreeseedDevMetadata,
329
+ updateInternalDependencySpecs
330
+ };
@@ -247,6 +247,10 @@ export declare function deployProjectPlatform(options: ProjectPlatformActionOpti
247
247
  } | null;
248
248
  } | undefined)[];
249
249
  }>;
250
+ export declare function resolveRailwayServiceDeployDependencies({ includeDataDependency, previousRailwayDeployNodeId, }: {
251
+ includeDataDependency: boolean;
252
+ previousRailwayDeployNodeId?: string | null;
253
+ }): string[];
250
254
  export declare function publishProjectContent(options: ProjectPlatformActionOptions): Promise<{
251
255
  ok: boolean;
252
256
  scope: ProjectPlatformScope;
@@ -37,7 +37,8 @@ import {
37
37
  validateRailwayServiceConfiguration,
38
38
  verifyRailwayScheduledJobs
39
39
  } from "./railway-deploy.js";
40
- import { loadCliDeployConfig, packageScriptPath, resolveWranglerBin } from "./runtime-tools.js";
40
+ import { loadCliDeployConfig, packageScriptPath } from "./runtime-tools.js";
41
+ import { resolveTreeseedToolCommand } from "../../managed-dependencies.js";
41
42
  import { CloudflareQueuePullClient, CloudflareQueuePushClient } from "../../remote.js";
42
43
  import { runPrefixedCommand, runTreeseedBootstrapDag, sleep, writeTreeseedBootstrapLine } from "./bootstrap-runner.js";
43
44
  import { runTenantDeployPreflight } from "./save-deploy-preflight.js";
@@ -130,7 +131,11 @@ function runTenantPublishContentPreflight(options) {
130
131
  return target;
131
132
  }
132
133
  function runWrangler(tenantRoot, args, extraEnv = {}, options = {}) {
133
- const result = spawnSync(process.execPath, [resolveWranglerBin(), ...args], {
134
+ const wrangler = resolveTreeseedToolCommand("wrangler");
135
+ if (!wrangler) {
136
+ throw new Error("Wrangler CLI is unavailable.");
137
+ }
138
+ const result = spawnSync(wrangler.command, [...wrangler.argsPrefix, ...args], {
134
139
  cwd: tenantRoot,
135
140
  stdio: options.capture ? "pipe" : "inherit",
136
141
  encoding: options.capture ? "utf8" : void 0,
@@ -151,7 +156,11 @@ async function runPrefixedWranglerWithRetry(tenantRoot, args, {
151
156
  }) {
152
157
  let lastOutput = "";
153
158
  for (let attempt = 1; attempt <= 3; attempt += 1) {
154
- const result = await runPrefixedCommand(process.execPath, [resolveWranglerBin(), ...args], {
159
+ const wrangler = resolveTreeseedToolCommand("wrangler");
160
+ if (!wrangler) {
161
+ throw new Error("Wrangler CLI is unavailable.");
162
+ }
163
+ const result = await runPrefixedCommand(wrangler.command, [...wrangler.argsPrefix, ...args], {
155
164
  cwd: tenantRoot,
156
165
  env,
157
166
  write,
@@ -1080,13 +1089,17 @@ async function deployProjectPlatform(options) {
1080
1089
  const selectedServices = validation.services.filter(
1081
1090
  (service) => service.key === "api" ? selectedSystems.has("api") : selectedSystems.has("agents")
1082
1091
  );
1092
+ let previousRailwayDeployNodeId = null;
1083
1093
  for (const service of selectedServices) {
1084
1094
  const system = service.key === "api" ? "api" : "agents";
1085
1095
  const nodeId = `${system}:${service.key}-railway-deploy`;
1086
1096
  selectedRailwayServiceKeys.push(service.key);
1087
1097
  nodes.push({
1088
1098
  id: nodeId,
1089
- dependencies: selectedSystems.has("data") ? ["data:d1-migrate"] : [],
1099
+ dependencies: resolveRailwayServiceDeployDependencies({
1100
+ includeDataDependency: selectedSystems.has("data"),
1101
+ previousRailwayDeployNodeId
1102
+ }),
1090
1103
  run: async () => {
1091
1104
  const result = await deployRailwayService(options.tenantRoot, service, {
1092
1105
  dryRun: options.dryRun,
@@ -1103,6 +1116,7 @@ async function deployProjectPlatform(options) {
1103
1116
  return result;
1104
1117
  }
1105
1118
  });
1119
+ previousRailwayDeployNodeId = nodeId;
1106
1120
  }
1107
1121
  }
1108
1122
  let railwaySchedules = [];
@@ -1178,6 +1192,15 @@ async function deployProjectPlatform(options) {
1178
1192
  serviceResults
1179
1193
  };
1180
1194
  }
1195
+ function resolveRailwayServiceDeployDependencies({
1196
+ includeDataDependency,
1197
+ previousRailwayDeployNodeId
1198
+ }) {
1199
+ return [
1200
+ ...includeDataDependency ? ["data:d1-migrate"] : [],
1201
+ ...previousRailwayDeployNodeId ? [previousRailwayDeployNodeId] : []
1202
+ ];
1203
+ }
1181
1204
  async function publishProjectContent(options) {
1182
1205
  const reporter = resolveReporter(options.tenantRoot, options.reporter);
1183
1206
  return publishContent(options, reporter);
@@ -1303,6 +1326,7 @@ export {
1303
1326
  prepareTenantCloudflareDeploy,
1304
1327
  provisionProjectPlatform,
1305
1328
  publishProjectContent,
1329
+ resolveRailwayServiceDeployDependencies,
1306
1330
  resolveScope,
1307
1331
  runProjectPlatformAction,
1308
1332
  runTenantDataMigration,