@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.
- package/dist/operations/services/live-hosted-service-checks.js +1 -1
- package/dist/operations/services/repository-save-orchestrator.js +76 -14
- package/dist/operations/services/workspace-dependency-mode.js +4 -0
- package/dist/platform/desired-state.js +1 -0
- package/dist/reconcile/providers/release-private.d.ts +10 -0
- package/dist/reconcile/providers/release-private.js +45 -1
- package/dist/workflow/operations.js +17 -1
- package/package.json +1 -1
|
@@ -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 =
|
|
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
|
-
|
|
735
|
-
|
|
736
|
-
if (
|
|
737
|
-
|
|
738
|
-
|
|
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
|
-
|
|
744
|
-
|
|
745
|
-
|
|
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]
|
|
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
|
|
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:
|
|
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 !== ".");
|