@treeseed/sdk 0.12.45 → 0.12.46

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.
@@ -19,7 +19,7 @@ import {
19
19
  } from "./hosted-service-checks.js";
20
20
  const DEFAULT_RETRY_ATTEMPTS = 3;
21
21
  const DEFAULT_RETRY_INTERVAL_MS = 1500;
22
- const DEFAULT_RAILWAY_DEPLOYMENT_SETTLE_ATTEMPTS = 60;
22
+ const DEFAULT_RAILWAY_DEPLOYMENT_SETTLE_ATTEMPTS = 120;
23
23
  const DEFAULT_RAILWAY_DEPLOYMENT_SETTLE_INTERVAL_MS = 5e3;
24
24
  function sleep(ms) {
25
25
  return new Promise((resolve2) => setTimeout(resolve2, ms));
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
1
+ import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
2
  import { basename, resolve, relative } from "node:path";
3
3
  import { spawn, spawnSync } from "node:child_process";
4
4
  import { tmpdir } from "node:os";
@@ -731,18 +731,28 @@ function syncDirectGitDependencyLockfileEntries(node, options, references) {
731
731
  let changed = false;
732
732
  for (const reference of references) {
733
733
  const manifestSpec = reference.manifestSpec ?? reference.spec;
734
- for (const field of dependencyFields(rootPackage)) {
735
- const values = rootPackage[field];
736
- if (!values || typeof values !== "object" || Array.isArray(values)) continue;
737
- const dependencies = values;
738
- if (reference.packageName in dependencies && dependencies[reference.packageName] !== manifestSpec) {
739
- dependencies[reference.packageName] = manifestSpec;
740
- changed = true;
734
+ const visitDependencyMaps = (value) => {
735
+ if (!value || typeof value !== "object") return;
736
+ if (Array.isArray(value)) {
737
+ for (const item of value) visitDependencyMaps(item);
738
+ return;
741
739
  }
742
- }
743
- const entryKey = `node_modules/${reference.packageName}`;
744
- const entry = packageEntries[entryKey];
745
- if (entry) {
740
+ const record = value;
741
+ for (const field of ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"]) {
742
+ const dependencies = record[field];
743
+ if (!dependencies || typeof dependencies !== "object" || Array.isArray(dependencies)) continue;
744
+ const dependencyMap = dependencies;
745
+ const current = dependencyMap[reference.packageName];
746
+ if (typeof current === "string" && /(?:git|github:|#[0-9a-f]{7,40}$)/iu.test(current) && current !== manifestSpec) {
747
+ dependencyMap[reference.packageName] = manifestSpec;
748
+ changed = true;
749
+ }
750
+ }
751
+ for (const nested of Object.values(record)) visitDependencyMaps(nested);
752
+ };
753
+ visitDependencyMaps(lockfile);
754
+ for (const [entryKey, entry] of Object.entries(packageEntries)) {
755
+ if (entryKey !== `node_modules/${reference.packageName}` && !entryKey.endsWith(`/node_modules/${reference.packageName}`)) continue;
746
756
  const nextResolved = normalizeGitRemoteForDependency(reference.remoteUrl ?? "", "ssh");
747
757
  const resolved = nextResolved ? `${nextResolved}#${manifestSpec.slice(manifestSpec.lastIndexOf("#") + 1)}` : manifestSpec;
748
758
  if (entry.resolved !== resolved) {
@@ -764,6 +774,51 @@ function syncDirectGitDependencyLockfileEntries(node, options, references) {
764
774
  emitProgress(options, node, "lockfile", "Synchronized direct internal Git dependency lockfile entries without npm git preparation.");
765
775
  return true;
766
776
  }
777
+ function validateStandaloneGitDependencyLockfile(node, options) {
778
+ const lockfilePath = resolve(node.path, "package-lock.json");
779
+ const lockfileExists = existsSync(lockfilePath);
780
+ const validateArgs = [
781
+ "ci",
782
+ "--package-lock-only",
783
+ "--ignore-scripts",
784
+ "--workspaces=false",
785
+ "--no-audit",
786
+ "--no-fund"
787
+ ];
788
+ try {
789
+ if (!lockfileExists) throw new Error("standalone lockfile missing");
790
+ runCapturedCommand(node, options, "lockfile", "npm", validateArgs, { timeoutMs: 5 * 6e4 });
791
+ } catch (validationError) {
792
+ const previousLockfile = lockfileExists ? readFileSync(lockfilePath, "utf8") : null;
793
+ const isolatedRoot = mkdtempSync(resolve(tmpdir(), "treeseed-lockfile-"));
794
+ try {
795
+ copyFileSync(resolve(node.path, "package.json"), resolve(isolatedRoot, "package.json"));
796
+ runCapturedCommand(node, options, "lockfile", "npm", [
797
+ "install",
798
+ "--package-lock-only",
799
+ "--ignore-scripts",
800
+ "--workspaces=false",
801
+ "--no-audit",
802
+ "--no-fund"
803
+ ], {
804
+ cwd: isolatedRoot,
805
+ timeoutMs: 15 * 6e4
806
+ });
807
+ runCapturedCommand(node, options, "lockfile", "npm", validateArgs, {
808
+ cwd: isolatedRoot,
809
+ timeoutMs: 5 * 6e4
810
+ });
811
+ copyFileSync(resolve(isolatedRoot, "package-lock.json"), lockfilePath);
812
+ } catch (regenerationError) {
813
+ if (previousLockfile !== null) writeFileSync(lockfilePath, previousLockfile, "utf8");
814
+ throw regenerationError instanceof Error ? regenerationError : validationError;
815
+ } finally {
816
+ rmSync(isolatedRoot, { recursive: true, force: true });
817
+ }
818
+ }
819
+ emitProgress(options, node, "lockfile", "Validated the standalone lockfile for exact internal Git refs.");
820
+ return true;
821
+ }
767
822
  function planPackageVersion(node, options) {
768
823
  if (!node.packageJson || !node.packageJsonPath) return null;
769
824
  const current = String(node.packageJson.version ?? "0.0.0");
@@ -827,7 +882,7 @@ function syncRootWorkspaceLockfileMetadata(node, options) {
827
882
  const mergedDeps = { ...currentDeps };
828
883
  let fieldChanged = false;
829
884
  for (const [dependencyName, dependencySpec] of Object.entries(nextValue)) {
830
- if (mergedDeps[dependencyName] != null) continue;
885
+ if (mergedDeps[dependencyName] === dependencySpec) continue;
831
886
  mergedDeps[dependencyName] = dependencySpec;
832
887
  fieldChanged = true;
833
888
  }
@@ -1711,8 +1766,15 @@ async function saveOneRepository(node, options, state) {
1711
1766
  ensureWritableRemote(node, options);
1712
1767
  const dependencyUpdates = isRootWorkspaceRepository(node, options) ? [] : updateDependencyReferences(node, state.finalizedReferences);
1713
1768
  const dependencyChanged = dependencyUpdates.length > 0;
1714
- const gitDependencyRefreshReferences = dependencyUpdates.map((update) => state.finalizedReferences.get(update.packageName)).filter((reference) => Boolean(reference) && reference.mode === "dev-git-commit");
1769
+ const directDependencyNames = new Set(dependencyFields(node.packageJson ?? {}).flatMap((field) => {
1770
+ const value = node.packageJson?.[field];
1771
+ return value && typeof value === "object" && !Array.isArray(value) ? Object.keys(value) : [];
1772
+ }));
1773
+ const gitDependencyRefreshReferences = [...state.finalizedReferences.values()].filter((reference) => reference.mode === "dev-git-commit" && directDependencyNames.has(reference.packageName));
1715
1774
  const lockfileGitDependenciesSynced = syncDirectGitDependencyLockfileEntries(node, options, gitDependencyRefreshReferences);
1775
+ if (!isRootWorkspaceRepository(node, options) && (lockfileGitDependenciesSynced || gitDependencyRefreshReferences.length > 0 && !existsSync(resolve(node.path, "package-lock.json")))) {
1776
+ validateStandaloneGitDependencyLockfile(node, options);
1777
+ }
1716
1778
  const gitDependencyRefreshSpecs = lockfileGitDependenciesSynced ? [] : gitDependencyRefreshReferences.map((reference) => `${reference.packageName}@${reference.installSpec ?? reference.spec}`);
1717
1779
  const submodulePointers = collectSubmodulePointerChanges(node, state.finalizedCommits);
1718
1780
  const submodulesChanged = submodulePointers.length > 0;
@@ -274,6 +274,10 @@ function removeLinkCandidate(link, managedLinks) {
274
274
  unlinkSync(link.linkPath);
275
275
  return true;
276
276
  }
277
+ if (managed) {
278
+ rmSync(link.linkPath, { recursive: true, force: true });
279
+ return true;
280
+ }
277
281
  if (isInstalledTreeseedPackage(link.linkPath, link.packageName)) {
278
282
  rmSync(link.linkPath, { recursive: true, force: true });
279
283
  return true;
@@ -62,6 +62,7 @@ function environmentFromTarget(target) {
62
62
  return "staging";
63
63
  }
64
64
  function packageReleaseCapability(adapter) {
65
+ if (!adapter.capabilities.publish) return adapter.releaseChecks.length > 0 ? "deploy-only" : "none";
65
66
  if (adapter.artifacts.some((artifact) => artifact.provider === "docker")) return "image";
66
67
  if (adapter.artifacts.some((artifact) => artifact.provider === "npm")) return "npm";
67
68
  if (adapter.releaseChecks.length > 0) return "deploy-only";
@@ -11,6 +11,7 @@ export declare function runReleaseVerifyCommand(input: {
11
11
  status?: undefined;
12
12
  signal?: undefined;
13
13
  command?: undefined;
14
+ dependencies?: undefined;
14
15
  stdout?: undefined;
15
16
  stderr?: undefined;
16
17
  } | {
@@ -18,6 +19,15 @@ export declare function runReleaseVerifyCommand(input: {
18
19
  status: number | null;
19
20
  signal: NodeJS.Signals | null;
20
21
  command: import("../../index.js").TreeseedPackageCommand;
22
+ dependencies: {
23
+ status: "not-applicable";
24
+ } | {
25
+ status: "ready";
26
+ } | {
27
+ status: "restored";
28
+ } | {
29
+ status: "workspace-linked";
30
+ };
21
31
  stdout: string;
22
32
  stderr: string;
23
33
  skipped?: undefined;
@@ -1,9 +1,43 @@
1
1
  import { spawn, spawnSync } from "node:child_process";
2
- import { mkdirSync, writeFileSync } from "node:fs";
2
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
3
3
  import { dirname, resolve } from "node:path";
4
4
  import { findTreeseedPackageAdapter } from "../../operations/services/package-adapters.js";
5
5
  import { checkedOutTemplateRepositories } from "../../operations/services/managed-repositories.js";
6
6
  import { runTreeseedGitText } from "../../operations/services/git-runner.js";
7
+ import { ensureLocalWorkspaceLinks } from "../../operations/services/workspace-dependency-mode.js";
8
+ function ensureReleaseVerifyDependencies(input) {
9
+ if (!existsSync(resolve(input.packageDir, "package.json")) || !existsSync(resolve(input.packageDir, "package-lock.json"))) {
10
+ return { status: "not-applicable" };
11
+ }
12
+ const env = { ...process.env, ...input.env ?? {} };
13
+ const inspection = spawnSync("npm", ["ls", "--depth=0", "--workspaces=false"], {
14
+ cwd: input.packageDir,
15
+ env,
16
+ encoding: "utf8"
17
+ });
18
+ if (inspection.status === 0) return { status: "ready" };
19
+ input.onProgress?.("Package dependencies are incomplete; restoring the standalone lockfile installation.");
20
+ const install = spawnSync("npm", [
21
+ "ci",
22
+ "--ignore-scripts",
23
+ "--workspaces=false",
24
+ "--no-audit",
25
+ "--no-fund"
26
+ ], {
27
+ cwd: input.packageDir,
28
+ env,
29
+ encoding: "utf8"
30
+ });
31
+ if (install.status !== 0) {
32
+ throw new Error([
33
+ "Standalone package dependency hydration failed before release verification.",
34
+ install.stderr,
35
+ install.stdout
36
+ ].filter(Boolean).join("\n").trim());
37
+ }
38
+ ensureLocalWorkspaceLinks(input.tenantRoot, { env: input.env });
39
+ return { status: "restored" };
40
+ }
7
41
  async function runReleaseVerifyCommand(input) {
8
42
  const adapter = findTreeseedPackageAdapter(input.tenantRoot, input.packageId);
9
43
  if (!adapter) {
@@ -17,6 +51,15 @@ async function runReleaseVerifyCommand(input) {
17
51
  reason: `${input.packageId} has no release verify command.`
18
52
  };
19
53
  }
54
+ const dependencies = adapter.capabilities.localOnly ? (() => {
55
+ ensureLocalWorkspaceLinks(input.tenantRoot, { env: input.env });
56
+ return { status: "workspace-linked" };
57
+ })() : ensureReleaseVerifyDependencies({
58
+ tenantRoot: input.tenantRoot,
59
+ packageDir: adapter.dir,
60
+ env: input.env,
61
+ onProgress: input.onProgress
62
+ });
20
63
  const renderedCommand = [command.command, ...command.args].join(" ");
21
64
  input.onProgress?.(`Running ${input.packageId} release verification: ${renderedCommand}`);
22
65
  const started = Date.now();
@@ -53,6 +96,7 @@ async function runReleaseVerifyCommand(input) {
53
96
  status: result.status,
54
97
  signal: result.signal,
55
98
  command,
99
+ dependencies,
56
100
  stdout,
57
101
  stderr
58
102
  };
@@ -1222,6 +1222,14 @@ function remoteTagCommit(repoDir, tagName) {
1222
1222
  const direct = output.split("\n").find((line) => line.endsWith(`refs/tags/${tagName}`));
1223
1223
  return (peeled ?? direct)?.split(/\s+/u)[0] ?? null;
1224
1224
  }
1225
+ function releaseTagExists(repoDir, tagName) {
1226
+ if (gitObjectCommit(repoDir, tagName)) return true;
1227
+ try {
1228
+ return remoteTagCommit(repoDir, tagName) !== null;
1229
+ } catch {
1230
+ return false;
1231
+ }
1232
+ }
1225
1233
  function ensureReleaseTag(repoDir, tagName, commitSha, message) {
1226
1234
  const localCommit = gitObjectCommit(repoDir, tagName);
1227
1235
  if (localCommit && localCommit !== commitSha) {
@@ -2629,6 +2637,14 @@ function buildReleasePlanSnapshot(input) {
2629
2637
  versionPlan.versions.set(adapter.id, incrementVersion(adapter.version, input.level));
2630
2638
  }
2631
2639
  }
2640
+ for (const adapter of discoverTreeseedPackageAdapters(input.root)) {
2641
+ let version = versionPlan.versions.get(adapter.id);
2642
+ if (!version) continue;
2643
+ while (releaseTagExists(adapter.dir, version)) {
2644
+ version = incrementVersion(version, input.level);
2645
+ }
2646
+ versionPlan.versions.set(adapter.id, version);
2647
+ }
2632
2648
  const plannedSelected = orderReleasePackageNames([...versionPlan.selected].filter((name) => versionPlan.versions.has(name)));
2633
2649
  const plannedChanged = input.repairVersionLine === true ? plannedSelected : Array.from(new Set(publishablePackageSelection.changed.filter((name) => plannedSelected.includes(name))));
2634
2650
  const plannedDependents = plannedSelected.filter((name) => !plannedChanged.includes(name));
@@ -4633,7 +4649,7 @@ async function workflowSave(helpers, input) {
4633
4649
  verifyMode: normalizeSaveVerifyMode(effectiveInput.verify === false ? "skip" : effectiveInput.verifyMode),
4634
4650
  commitMessageMode: effectiveInput.commitMessageMode ?? "auto",
4635
4651
  workflowRunId: workflowRun.runId,
4636
- deferPushUntilVerified: true,
4652
+ deferPushUntilVerified: false,
4637
4653
  onProgress: (line, stream) => helpers.write(line, stream),
4638
4654
  onWaveSaved: branch === STAGING_BRANCH && shouldUseHostedSaveCi(effectiveInput, branch, saveLane) ? async ({ nodes, reports, rootRepo: waveRootRepo }) => {
4639
4655
  const nonRootReportsForWave = reports.filter((repo, index) => nodes[index]?.id !== ".");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treeseed/sdk",
3
- "version": "0.12.45",
3
+ "version": "0.12.46",
4
4
  "description": "Shared Treeseed SDK for content-backed and D1-backed object models.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {