agl 22.0.1 → 22.0.2
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/changelog.md +10 -0
- package/dist_serve/bundle.js +1 -1
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.cli.d.ts +4 -2
- package/dist_ts/classes.cli.js +161 -68
- package/dist_ts/classes.controller.js +23 -10
- package/dist_ts/classes.upgradecoordinator.d.ts +39 -1
- package/dist_ts/classes.upgradecoordinator.js +740 -74
- package/dist_ts/classes.upgradetransaction.d.ts +68 -1
- package/dist_ts/classes.upgradetransaction.js +383 -223
- package/package.json +1 -1
- package/readme.md +255 -378
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.cli.ts +184 -83
- package/ts/classes.controller.ts +25 -7
- package/ts/classes.upgradecoordinator.ts +876 -74
- package/ts/classes.upgradetransaction.ts +574 -250
- package/ts_web/00_commitinfo_data.ts +1 -1
|
@@ -17,36 +17,43 @@ import {
|
|
|
17
17
|
import {
|
|
18
18
|
inspectControllerProcess,
|
|
19
19
|
listControllerDataWriterProcessesForCli,
|
|
20
|
+
listControllerDataWriterProcessesForCliPaths,
|
|
20
21
|
listControllerProcessesForCli,
|
|
21
22
|
processIdentityHasCliCommand,
|
|
22
23
|
readControllerProcessIdentity,
|
|
23
24
|
readProcessGroupMemberPids,
|
|
25
|
+
type IControllerProcessIdentity,
|
|
24
26
|
} from './classes.processinspection.js';
|
|
25
27
|
import {
|
|
26
28
|
createUpgradeToken,
|
|
29
|
+
compareUpgradeSemver,
|
|
27
30
|
type TUpgradeTransaction,
|
|
31
|
+
type IUpgradeTransactionInventoryEntry,
|
|
28
32
|
type IUpgradeTransactionV3,
|
|
29
33
|
type IUpgradeExpectedController,
|
|
30
34
|
type IUpgradeWorkerPayload,
|
|
35
|
+
normalizeUpgradeRegistryUrl,
|
|
31
36
|
UpgradeCoordinator,
|
|
37
|
+
type UpgradeInstallationLock,
|
|
32
38
|
upgradeCoordinationVersion,
|
|
33
39
|
upgradePackageTransitionTransactionVersion,
|
|
34
40
|
upgradePackageTransitionSource,
|
|
35
41
|
upgradePackageTransitionTarget,
|
|
42
|
+
upgradeTransactionIsStalled,
|
|
43
|
+
upgradeTransactionTargetVersion,
|
|
36
44
|
serializeUpgradeWorkerPayload,
|
|
45
|
+
terminateVerifiedUpgradeWorkerProcessGroup,
|
|
37
46
|
} from './classes.upgradecoordinator.js';
|
|
38
47
|
import {
|
|
39
48
|
upgradeCoordinationRootEnvironmentVariable,
|
|
40
49
|
upgradeTokenEnvironmentVariable,
|
|
41
50
|
} from './constants.upgradeenvironment.js';
|
|
42
51
|
|
|
43
|
-
const npmRegistryUrl = 'https://registry.npmjs.org/';
|
|
44
52
|
const commandOutputByteLimit = 1024 * 1024;
|
|
45
53
|
const loggedCommandOutputByteLimit = 16 * 1024 * 1024;
|
|
46
54
|
const packageManagerTimeoutMs = 10 * 60 * 1000;
|
|
47
55
|
const controllerRestartTimeoutMs = 90_000;
|
|
48
56
|
const commandGroupDrainTimeoutMs = 5_000;
|
|
49
|
-
const candidateCleanupRetryIntervalMs = 250;
|
|
50
57
|
const retainedUpgradeLogCount = 10;
|
|
51
58
|
const minimumPrunableUpgradeLogAgeMs = 30 * 60 * 1000;
|
|
52
59
|
const upgradePreparationPollMs = 250;
|
|
@@ -59,7 +66,6 @@ const scrubUpgradeCoordinationEnvironment = (): void => {
|
|
|
59
66
|
delete process.env[upgradeCoordinationRootEnvironmentVariable];
|
|
60
67
|
};
|
|
61
68
|
const upgradeLogPattern = /^upgrade-[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{6}\.[0-9]{3}Z-[A-Za-z0-9_-]{10}\.log$/;
|
|
62
|
-
const semverPattern = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
63
69
|
let upgradeWorkerCliPath: string | undefined;
|
|
64
70
|
const currentPackageIdentity = String(controllerPackageName)
|
|
65
71
|
=== upgradePackageTransitionSource.packageName
|
|
@@ -67,6 +73,10 @@ const currentPackageIdentity = String(controllerPackageName)
|
|
|
67
73
|
: upgradePackageTransitionTarget;
|
|
68
74
|
const currentCliName = currentPackageIdentity.cliName;
|
|
69
75
|
|
|
76
|
+
const registryArguments = (registryUrlArg?: string): string[] => registryUrlArg
|
|
77
|
+
? [`--registry=${registryUrlArg}`]
|
|
78
|
+
: [];
|
|
79
|
+
|
|
70
80
|
const packageManagerEnvironmentAllowlist = new Set([
|
|
71
81
|
'CI',
|
|
72
82
|
'COREPACK_HOME',
|
|
@@ -81,6 +91,7 @@ const packageManagerEnvironmentAllowlist = new Set([
|
|
|
81
91
|
'NPM_CONFIG_REGISTRY',
|
|
82
92
|
'NPM_CONFIG_USERCONFIG',
|
|
83
93
|
'PATH',
|
|
94
|
+
'PNPM_CONFIG_REGISTRY',
|
|
84
95
|
'PNPM_HOME',
|
|
85
96
|
'SHELL',
|
|
86
97
|
'SSL_CERT_DIR',
|
|
@@ -98,13 +109,6 @@ const packageManagerEnvironmentAllowlist = new Set([
|
|
|
98
109
|
'XDG_STATE_HOME',
|
|
99
110
|
]);
|
|
100
111
|
|
|
101
|
-
interface IParsedSemver {
|
|
102
|
-
major: string;
|
|
103
|
-
minor: string;
|
|
104
|
-
patch: string;
|
|
105
|
-
prerelease: string[];
|
|
106
|
-
}
|
|
107
|
-
|
|
108
112
|
class UpgradeCommandCleanupError extends Error {}
|
|
109
113
|
|
|
110
114
|
interface IGlobalListDependency {
|
|
@@ -134,6 +138,74 @@ export interface IDetachedUpgradeWorker {
|
|
|
134
138
|
logFilePath: string;
|
|
135
139
|
}
|
|
136
140
|
|
|
141
|
+
export interface IUpgradeWorkerLaunchCandidate {
|
|
142
|
+
pid: number;
|
|
143
|
+
cliPath: string;
|
|
144
|
+
logFilePath: string;
|
|
145
|
+
processGroupId?: number;
|
|
146
|
+
fingerprint?: string;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export class UpgradeWorkerHandoffError extends Error {
|
|
150
|
+
constructor(
|
|
151
|
+
messageArg: string,
|
|
152
|
+
causeArg: unknown,
|
|
153
|
+
public readonly recoveryLock: UpgradeInstallationLock,
|
|
154
|
+
) {
|
|
155
|
+
super(messageArg, { cause: causeArg });
|
|
156
|
+
this.name = 'UpgradeWorkerHandoffError';
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export class UpgradeWorkerCandidateDrainageError extends Error {
|
|
161
|
+
constructor(messageArg: string, causeArg: unknown) {
|
|
162
|
+
super(messageArg, { cause: causeArg });
|
|
163
|
+
this.name = 'UpgradeWorkerCandidateDrainageError';
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export interface ITerminalizeUpgradeWorkerHandoffFailureOptions {
|
|
168
|
+
coordinator: UpgradeCoordinator;
|
|
169
|
+
token: string;
|
|
170
|
+
error: UpgradeWorkerHandoffError;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export const terminalizeUpgradeWorkerHandoffFailure = async (
|
|
174
|
+
optionsArg: ITerminalizeUpgradeWorkerHandoffFailureOptions,
|
|
175
|
+
): Promise<never> => {
|
|
176
|
+
const handoffFailure = optionsArg.error.cause ?? optionsArg.error;
|
|
177
|
+
let terminalizationError: unknown;
|
|
178
|
+
let terminalized = false;
|
|
179
|
+
try {
|
|
180
|
+
await optionsArg.error.recoveryLock.assertOwned();
|
|
181
|
+
await optionsArg.coordinator.finishTransaction(
|
|
182
|
+
optionsArg.token,
|
|
183
|
+
false,
|
|
184
|
+
'The upgrade worker handoff failed before package work began.',
|
|
185
|
+
handoffFailure instanceof Error ? handoffFailure.message : String(handoffFailure),
|
|
186
|
+
);
|
|
187
|
+
terminalized = true;
|
|
188
|
+
} catch (errorArg) {
|
|
189
|
+
terminalizationError = errorArg;
|
|
190
|
+
}
|
|
191
|
+
let releaseError: unknown;
|
|
192
|
+
if (terminalized) {
|
|
193
|
+
try {
|
|
194
|
+
await optionsArg.error.recoveryLock.release();
|
|
195
|
+
} catch (errorArg) {
|
|
196
|
+
releaseError = errorArg;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const failures = [optionsArg.error, terminalizationError, releaseError]
|
|
200
|
+
.filter((failureArg) => failureArg !== undefined);
|
|
201
|
+
throw failures.length > 1
|
|
202
|
+
? new AggregateError(
|
|
203
|
+
failures,
|
|
204
|
+
'The upgrade worker handoff failed and its transaction cleanup was incomplete.',
|
|
205
|
+
)
|
|
206
|
+
: optionsArg.error;
|
|
207
|
+
};
|
|
208
|
+
|
|
137
209
|
const proxyEnvironmentValueIsSafe = (valueArg: string): boolean => {
|
|
138
210
|
try {
|
|
139
211
|
const parsed = new URL(valueArg);
|
|
@@ -158,6 +230,13 @@ export const createPackageManagerEnvironment = (
|
|
|
158
230
|
(normalizedName === 'HTTP_PROXY' || normalizedName === 'HTTPS_PROXY')
|
|
159
231
|
&& !proxyEnvironmentValueIsSafe(value)
|
|
160
232
|
) continue;
|
|
233
|
+
if (normalizedName === 'NPM_CONFIG_REGISTRY' || normalizedName === 'PNPM_CONFIG_REGISTRY') {
|
|
234
|
+
try {
|
|
235
|
+
normalizeUpgradeRegistryUrl(value);
|
|
236
|
+
} catch {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
161
240
|
environment[name] = value;
|
|
162
241
|
}
|
|
163
242
|
return environment;
|
|
@@ -787,7 +866,12 @@ const resolveAuthoritativeRecoveryInstallation = async (
|
|
|
787
866
|
: upgradePackageTransitionSource;
|
|
788
867
|
return {
|
|
789
868
|
packageName: identity.packageName,
|
|
790
|
-
versions:
|
|
869
|
+
versions: transactionArg.targetPackageCommitStarted === true
|
|
870
|
+
? [...new Set([
|
|
871
|
+
identity.version,
|
|
872
|
+
transactionArg.recoveryTargetVersion ?? capturedInstallationArg.packageVersion,
|
|
873
|
+
])]
|
|
874
|
+
: [identity.version],
|
|
791
875
|
managementVersion: identity.managementVersion,
|
|
792
876
|
cliName: identity.cliName,
|
|
793
877
|
cliRelativePath: identity.cliRelativePath,
|
|
@@ -865,6 +949,7 @@ const resolveAuthoritativeRecoveryInstallation = async (
|
|
|
865
949
|
const verifyPackageTransitionTopology = async (
|
|
866
950
|
contextArg: IPackageTransitionContext,
|
|
867
951
|
expectedArg: TPackageTransitionTopology,
|
|
952
|
+
targetVersionArg: string = upgradePackageTransitionTarget.version,
|
|
868
953
|
): Promise<IPackageTransitionTopology> => {
|
|
869
954
|
const output = await runCapturedCommand(
|
|
870
955
|
contextArg.packageManagerPath,
|
|
@@ -894,7 +979,10 @@ const verifyPackageTransitionTopology = async (
|
|
|
894
979
|
? readExactGlobalPackage(contextArg, sourceDependency, upgradePackageTransitionSource)
|
|
895
980
|
: undefined,
|
|
896
981
|
targetDependency
|
|
897
|
-
? readExactGlobalPackage(contextArg, targetDependency,
|
|
982
|
+
? readExactGlobalPackage(contextArg, targetDependency, {
|
|
983
|
+
...upgradePackageTransitionTarget,
|
|
984
|
+
version: targetVersionArg,
|
|
985
|
+
})
|
|
898
986
|
: undefined,
|
|
899
987
|
]);
|
|
900
988
|
if (expectedArg === 'grouped' && source?.groupDirectory !== target?.groupDirectory) {
|
|
@@ -944,6 +1032,7 @@ export const resolveInheritedUpgradeTargetInstallation = async (
|
|
|
944
1032
|
const topology = await verifyPackageTransitionTopology(
|
|
945
1033
|
await resolvePackageManagerContext(),
|
|
946
1034
|
'target-only',
|
|
1035
|
+
upgradeTransactionTargetVersion(transactionArg)!,
|
|
947
1036
|
);
|
|
948
1037
|
if (!topology.target) throw new Error('The committed target package is unavailable.');
|
|
949
1038
|
return topology.target;
|
|
@@ -983,66 +1072,143 @@ export const tryResolveCurrentPnpmGlobalInstallation = async (
|
|
|
983
1072
|
return await resolveCurrentPnpmGlobalInstallation();
|
|
984
1073
|
};
|
|
985
1074
|
|
|
986
|
-
const
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1075
|
+
export const compareSemver = compareUpgradeSemver;
|
|
1076
|
+
|
|
1077
|
+
export interface IInspectOrphanedUpgradeForAdoptionOptions {
|
|
1078
|
+
coordinator: UpgradeCoordinator;
|
|
1079
|
+
installation: IPnpmGlobalInstallation;
|
|
1080
|
+
port: number;
|
|
1081
|
+
registryUrl?: string;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
export const inspectOrphanedUpgradeForAdoption = async (
|
|
1085
|
+
optionsArg: IInspectOrphanedUpgradeForAdoptionOptions,
|
|
1086
|
+
): Promise<IUpgradeTransactionInventoryEntry | undefined> => {
|
|
1087
|
+
const inventory = await optionsArg.coordinator.inspectCanonicalTransactionInventory();
|
|
1088
|
+
const nonterminal = inventory.transactions.filter((entryArg) => !entryArg.transaction.terminal);
|
|
1089
|
+
if (nonterminal.length === 0) return undefined;
|
|
1090
|
+
if (nonterminal.length !== 1) {
|
|
1091
|
+
throw new Error('Multiple nonterminal upgrade transactions block a fresh upgrade.');
|
|
1092
|
+
}
|
|
1093
|
+
const entry = nonterminal[0];
|
|
1094
|
+
const transaction = entry.transaction;
|
|
1095
|
+
const eligibleV2 = transaction.version === upgradeCoordinationVersion
|
|
1096
|
+
&& transaction.controllerWasRunning === true
|
|
1097
|
+
&& transaction.targetStartupInvoked === true
|
|
1098
|
+
&& transaction.targetVersion !== undefined
|
|
1099
|
+
&& transaction.preparationCompletedAt !== undefined
|
|
1100
|
+
&& (transaction.phase === 'restarting' || transaction.phase === 'continuing')
|
|
1101
|
+
&& compareSemver(transaction.sourceVersion, upgradePackageTransitionTarget.version) >= 0
|
|
1102
|
+
&& compareSemver(transaction.sourceVersion, transaction.targetVersion) < 0
|
|
1103
|
+
&& compareSemver(transaction.targetVersion, optionsArg.installation.packageVersion) <= 0;
|
|
1104
|
+
const eligibleV3 = transaction.version === upgradePackageTransitionTransactionVersion
|
|
1105
|
+
&& currentPackageIdentity.packageName === upgradePackageTransitionTarget.packageName
|
|
1106
|
+
&& transaction.targetPackageCommitStarted === true
|
|
1107
|
+
&& (!transaction.controllerWasRunning || transaction.preparationCompletedAt !== undefined)
|
|
1108
|
+
&& (
|
|
1109
|
+
transaction.phase === 'installing'
|
|
1110
|
+
|| transaction.phase === 'restarting'
|
|
1111
|
+
|| transaction.phase === 'continuing'
|
|
1112
|
+
)
|
|
1113
|
+
&& compareSemver(
|
|
1114
|
+
upgradeTransactionTargetVersion(transaction)!,
|
|
1115
|
+
optionsArg.installation.packageVersion,
|
|
1116
|
+
) <= 0;
|
|
1117
|
+
if (
|
|
1118
|
+
transaction.port !== optionsArg.port
|
|
1119
|
+
|| (!eligibleV2 && !eligibleV3)
|
|
1120
|
+
|| !upgradeTransactionIsStalled(transaction)
|
|
1121
|
+
) throw new Error('A nonterminal upgrade transaction blocks a fresh upgrade.');
|
|
1122
|
+
if (transaction.version === upgradePackageTransitionTransactionVersion && optionsArg.registryUrl) {
|
|
1123
|
+
throw new Error(
|
|
1124
|
+
'Legacy package-transition recovery uses pnpm registry configuration and cannot retain --registry.',
|
|
1125
|
+
);
|
|
994
1126
|
}
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1127
|
+
if (
|
|
1128
|
+
transaction.version === upgradeCoordinationVersion
|
|
1129
|
+
&& transaction.registryUrl !== undefined
|
|
1130
|
+
&& optionsArg.registryUrl !== undefined
|
|
1131
|
+
&& transaction.registryUrl !== optionsArg.registryUrl
|
|
1132
|
+
) throw new Error('The orphaned upgrade registry conflicts with the requested registry.');
|
|
1133
|
+
if (await optionsArg.coordinator.transactionWorkerIsLive(transaction)) {
|
|
1134
|
+
throw new Error('A nonterminal upgrade worker is still running.');
|
|
1135
|
+
}
|
|
1136
|
+
if (
|
|
1137
|
+
transaction.controller
|
|
1138
|
+
&& await optionsArg.coordinator.transactionControllerIsLive(transaction)
|
|
1139
|
+
) throw new Error('A nonterminal upgrade controller is still running.');
|
|
1140
|
+
const adoptionMatch = /^transaction-([a-f0-9]{20})\.json\.adopting-([a-f0-9]{20})$/
|
|
1141
|
+
.exec(entry.fileName);
|
|
1142
|
+
const prefixes = new Set([
|
|
1143
|
+
transaction.tokenHash.slice(0, 20),
|
|
1144
|
+
...(adoptionMatch ? [adoptionMatch[1], adoptionMatch[2]] : []),
|
|
1145
|
+
]);
|
|
1146
|
+
if (inventory.tokenMetadataFileNames.some((fileNameArg) => (
|
|
1147
|
+
[...prefixes].some((prefixArg) => fileNameArg.includes(`-${prefixArg}.json`))
|
|
1148
|
+
))) throw new Error('The orphaned upgrade retains token-bound coordination metadata.');
|
|
1149
|
+
return entry;
|
|
1001
1150
|
};
|
|
1002
1151
|
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1152
|
+
export interface IAdoptOrphanedUpgradeUnderLockOptions
|
|
1153
|
+
extends IInspectOrphanedUpgradeForAdoptionOptions {
|
|
1154
|
+
lock: UpgradeInstallationLock;
|
|
1155
|
+
token: string;
|
|
1156
|
+
expected: IUpgradeTransactionInventoryEntry;
|
|
1157
|
+
}
|
|
1007
1158
|
|
|
1008
|
-
export const
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
const
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1159
|
+
export const adoptOrphanedUpgradeUnderLock = async (
|
|
1160
|
+
optionsArg: IAdoptOrphanedUpgradeUnderLockOptions,
|
|
1161
|
+
): Promise<TUpgradeTransaction> => {
|
|
1162
|
+
await optionsArg.lock.assertOwned();
|
|
1163
|
+
const candidate = await inspectOrphanedUpgradeForAdoption(optionsArg);
|
|
1164
|
+
if (
|
|
1165
|
+
!candidate
|
|
1166
|
+
|| candidate.transaction.tokenHash !== optionsArg.expected.transaction.tokenHash
|
|
1167
|
+
|| candidate.transaction.revision !== optionsArg.expected.transaction.revision
|
|
1168
|
+
) throw new Error('The orphaned upgrade transaction changed before adoption.');
|
|
1169
|
+
const cliPaths = [
|
|
1170
|
+
optionsArg.installation.cliPath,
|
|
1171
|
+
...(candidate.transaction.worker ? [candidate.transaction.worker.cliPath] : []),
|
|
1172
|
+
...(candidate.transaction.controller ? [candidate.transaction.controller.cliPath] : []),
|
|
1173
|
+
];
|
|
1174
|
+
const assertNoRuntimeWriters = async (): Promise<void> => {
|
|
1175
|
+
if (await isLoopbackPortListening(optionsArg.port)) {
|
|
1176
|
+
throw new Error('The orphaned upgrade port became occupied before recovery.');
|
|
1177
|
+
}
|
|
1178
|
+
const writers = await listControllerDataWriterProcessesForCliPaths(cliPaths, {
|
|
1179
|
+
includeInstalledPackagePaths: true,
|
|
1180
|
+
});
|
|
1181
|
+
if (writers.length > 0) {
|
|
1182
|
+
throw new Error(`A ${writers[0].kind} data writer prevents orphaned upgrade recovery.`);
|
|
1183
|
+
}
|
|
1184
|
+
};
|
|
1185
|
+
await assertNoRuntimeWriters();
|
|
1186
|
+
await optionsArg.lock.assertOwned();
|
|
1187
|
+
const adopted = await optionsArg.coordinator.adoptOrphanedTransaction({
|
|
1188
|
+
lock: optionsArg.lock,
|
|
1189
|
+
token: optionsArg.token,
|
|
1190
|
+
expectedTokenHash: candidate.transaction.tokenHash,
|
|
1191
|
+
expectedRevision: candidate.transaction.revision,
|
|
1192
|
+
port: optionsArg.port,
|
|
1193
|
+
installedVersion: optionsArg.installation.packageVersion,
|
|
1194
|
+
...(optionsArg.registryUrl === undefined ? {} : { registryUrl: optionsArg.registryUrl }),
|
|
1195
|
+
});
|
|
1196
|
+
await assertNoRuntimeWriters();
|
|
1197
|
+
await optionsArg.lock.assertOwned();
|
|
1198
|
+
return adopted;
|
|
1036
1199
|
};
|
|
1037
1200
|
|
|
1038
|
-
const readLatestVersion = async (
|
|
1201
|
+
const readLatestVersion = async (
|
|
1202
|
+
installationArg: IPnpmGlobalInstallation,
|
|
1203
|
+
registryUrlArg?: string,
|
|
1204
|
+
): Promise<string> => {
|
|
1039
1205
|
const output = await runCapturedCommand(
|
|
1040
1206
|
installationArg.packageManagerPath,
|
|
1041
1207
|
[
|
|
1042
1208
|
'view',
|
|
1043
1209
|
controllerPackageName,
|
|
1044
1210
|
'dist-tags.latest',
|
|
1045
|
-
|
|
1211
|
+
...registryArguments(registryUrlArg),
|
|
1046
1212
|
'--json',
|
|
1047
1213
|
],
|
|
1048
1214
|
createPackageManagerEnvironment(),
|
|
@@ -1051,40 +1217,25 @@ const readLatestVersion = async (installationArg: IPnpmGlobalInstallation): Prom
|
|
|
1051
1217
|
try {
|
|
1052
1218
|
version = JSON.parse(output) as unknown;
|
|
1053
1219
|
} catch {
|
|
1054
|
-
throw new Error('pnpm view returned malformed JSON for the
|
|
1220
|
+
throw new Error('pnpm view returned malformed JSON for the registry latest tag.');
|
|
1055
1221
|
}
|
|
1056
1222
|
if (typeof version !== 'string') {
|
|
1057
|
-
throw new Error('The
|
|
1223
|
+
throw new Error('The registry latest tag did not resolve to one package version.');
|
|
1058
1224
|
}
|
|
1059
|
-
|
|
1225
|
+
compareUpgradeSemver(version, version);
|
|
1060
1226
|
return version;
|
|
1061
1227
|
};
|
|
1062
1228
|
|
|
1063
|
-
const assertInternalRegistryUrl = (valueArg: string): string => {
|
|
1064
|
-
let parsed: URL;
|
|
1065
|
-
try {
|
|
1066
|
-
parsed = new URL(valueArg);
|
|
1067
|
-
} catch (errorArg) {
|
|
1068
|
-
throw new Error('The internal upgrade registry URL is invalid.', { cause: errorArg });
|
|
1069
|
-
}
|
|
1070
|
-
if (
|
|
1071
|
-
(parsed.protocol !== 'https:' && parsed.protocol !== 'http:')
|
|
1072
|
-
|| parsed.username.length > 0
|
|
1073
|
-
|| parsed.password.length > 0
|
|
1074
|
-
) throw new Error('The internal upgrade registry URL is unsafe.');
|
|
1075
|
-
return parsed.toString();
|
|
1076
|
-
};
|
|
1077
|
-
|
|
1078
1229
|
const resolveExactPackageTransitionTarget = async (
|
|
1079
1230
|
contextArg: IPackageTransitionContext,
|
|
1080
|
-
registryUrlArg
|
|
1231
|
+
registryUrlArg?: string,
|
|
1081
1232
|
): Promise<void> => {
|
|
1082
1233
|
const output = await runCapturedCommand(
|
|
1083
1234
|
contextArg.packageManagerPath,
|
|
1084
1235
|
[
|
|
1085
1236
|
'view',
|
|
1086
1237
|
`${upgradePackageTransitionTarget.packageName}@${upgradePackageTransitionTarget.version}`,
|
|
1087
|
-
|
|
1238
|
+
...registryArguments(registryUrlArg),
|
|
1088
1239
|
'--json',
|
|
1089
1240
|
],
|
|
1090
1241
|
createPackageManagerEnvironment(),
|
|
@@ -1226,63 +1377,42 @@ const parseUpgradeRecoveryWorkerContext = (
|
|
|
1226
1377
|
return context as unknown as IUpgradeRecoveryWorkerContext;
|
|
1227
1378
|
};
|
|
1228
1379
|
|
|
1229
|
-
const
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
): Promise<
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
return (await readProcessGroupMemberPids(processGroupIdArg)).length === 0;
|
|
1239
|
-
};
|
|
1240
|
-
|
|
1241
|
-
const terminateSpawnedDetachedWorker = async (processIdArg: number): Promise<void> => {
|
|
1242
|
-
const signal = (signalArg: NodeJS.Signals): void => {
|
|
1243
|
-
try {
|
|
1244
|
-
process.kill(-processIdArg, signalArg);
|
|
1245
|
-
} catch (errorArg) {
|
|
1246
|
-
if ((errorArg as NodeJS.ErrnoException).code !== 'ESRCH') throw errorArg;
|
|
1247
|
-
}
|
|
1248
|
-
};
|
|
1249
|
-
signal('SIGTERM');
|
|
1250
|
-
if (await waitForProcessGroupDrain(processIdArg, commandGroupDrainTimeoutMs)) return;
|
|
1251
|
-
signal('SIGKILL');
|
|
1252
|
-
if (!await waitForProcessGroupDrain(processIdArg, commandGroupDrainTimeoutMs)) {
|
|
1253
|
-
throw new Error('The detached upgrade worker candidate group did not terminate.');
|
|
1254
|
-
}
|
|
1255
|
-
};
|
|
1380
|
+
const terminateSpawnedDetachedWorker = async (
|
|
1381
|
+
identityArg: IControllerProcessIdentity,
|
|
1382
|
+
cliPathArg: string,
|
|
1383
|
+
): Promise<void> => await terminateVerifiedUpgradeWorkerProcessGroup({
|
|
1384
|
+
pid: identityArg.pid,
|
|
1385
|
+
processGroupId: identityArg.processGroupId,
|
|
1386
|
+
fingerprint: identityArg.fingerprint,
|
|
1387
|
+
cliPath: cliPathArg,
|
|
1388
|
+
});
|
|
1256
1389
|
|
|
1257
|
-
interface
|
|
1390
|
+
export interface ICandidateCleanupDependencies {
|
|
1258
1391
|
beforeAttempt?: (processIdArg?: number) => Promise<void>;
|
|
1259
|
-
delay?: (millisecondsArg: number) => Promise<void>;
|
|
1260
1392
|
}
|
|
1261
1393
|
|
|
1262
|
-
const
|
|
1394
|
+
const cleanupCandidateUntilDrained = async (
|
|
1263
1395
|
cleanupArg: () => Promise<void>,
|
|
1264
|
-
dependenciesArg:
|
|
1396
|
+
dependenciesArg: ICandidateCleanupDependencies = {},
|
|
1265
1397
|
processIdArg?: number,
|
|
1266
1398
|
): Promise<void> => {
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
setTimeout(resolve, millisecondsArg)
|
|
1270
|
-
)));
|
|
1271
|
-
while (true) {
|
|
1272
|
-
try {
|
|
1273
|
-
await dependenciesArg.beforeAttempt?.(processIdArg);
|
|
1274
|
-
await cleanupArg();
|
|
1275
|
-
return;
|
|
1276
|
-
} catch {
|
|
1277
|
-
await delay(candidateCleanupRetryIntervalMs);
|
|
1278
|
-
}
|
|
1279
|
-
}
|
|
1399
|
+
await dependenciesArg.beforeAttempt?.(processIdArg);
|
|
1400
|
+
await cleanupArg();
|
|
1280
1401
|
};
|
|
1281
1402
|
|
|
1403
|
+
export const terminateUpgradeWorkerCandidateUntilDrained = async (
|
|
1404
|
+
coordinatorArg: UpgradeCoordinator,
|
|
1405
|
+
candidateArg: IDetachedUpgradeWorker,
|
|
1406
|
+
): Promise<void> => await cleanupCandidateUntilDrained(
|
|
1407
|
+
async () => await coordinatorArg.terminateUpgradeWorkerCandidate(candidateArg),
|
|
1408
|
+
{},
|
|
1409
|
+
candidateArg.pid,
|
|
1410
|
+
);
|
|
1411
|
+
|
|
1282
1412
|
interface IDetachedUpgradeWorkerProcessDependencies {
|
|
1283
1413
|
readProcessIdentity?: typeof readControllerProcessIdentity;
|
|
1284
1414
|
processHasCliCommand?: typeof processIdentityHasCliCommand;
|
|
1285
|
-
candidateCleanup?:
|
|
1415
|
+
candidateCleanup?: ICandidateCleanupDependencies;
|
|
1286
1416
|
}
|
|
1287
1417
|
|
|
1288
1418
|
const launchDetachedUpgradeWorkerProcess = async (optionsArg: {
|
|
@@ -1296,6 +1426,7 @@ const launchDetachedUpgradeWorkerProcess = async (optionsArg: {
|
|
|
1296
1426
|
}
|
|
1297
1427
|
const log = await openUpgradeLog(optionsArg.payload.token);
|
|
1298
1428
|
let child: plugins.childProcess.ChildProcess | undefined;
|
|
1429
|
+
let spawnedIdentity: IControllerProcessIdentity | undefined;
|
|
1299
1430
|
let logClosed = false;
|
|
1300
1431
|
try {
|
|
1301
1432
|
const workerEnvironment = { ...(optionsArg.environment ?? process.env) };
|
|
@@ -1333,13 +1464,15 @@ const launchDetachedUpgradeWorkerProcess = async (optionsArg: {
|
|
|
1333
1464
|
launchedChild.once('spawn', resolve);
|
|
1334
1465
|
launchedChild.once('error', reject);
|
|
1335
1466
|
});
|
|
1336
|
-
await log.handle.close();
|
|
1337
|
-
logClosed = true;
|
|
1338
1467
|
if (!launchedChild.pid) {
|
|
1339
1468
|
throw new Error(`The detached ${currentCliName} upgrade worker has no process ID.`);
|
|
1340
1469
|
}
|
|
1470
|
+
spawnedIdentity = await readControllerProcessIdentity(launchedChild.pid) ?? undefined;
|
|
1471
|
+
await log.handle.close();
|
|
1472
|
+
logClosed = true;
|
|
1341
1473
|
const identity = await (dependenciesArg.readProcessIdentity
|
|
1342
|
-
|
|
1474
|
+
? dependenciesArg.readProcessIdentity(launchedChild.pid)
|
|
1475
|
+
: spawnedIdentity);
|
|
1343
1476
|
if (
|
|
1344
1477
|
!identity
|
|
1345
1478
|
|| !identity.processGroupLeader
|
|
@@ -1360,6 +1493,7 @@ const launchDetachedUpgradeWorkerProcess = async (optionsArg: {
|
|
|
1360
1493
|
};
|
|
1361
1494
|
} catch (errorArg) {
|
|
1362
1495
|
const errors: unknown[] = [errorArg];
|
|
1496
|
+
let candidateCleanupError: unknown;
|
|
1363
1497
|
if (!logClosed) {
|
|
1364
1498
|
try {
|
|
1365
1499
|
await log.handle.close();
|
|
@@ -1369,16 +1503,33 @@ const launchDetachedUpgradeWorkerProcess = async (optionsArg: {
|
|
|
1369
1503
|
}
|
|
1370
1504
|
}
|
|
1371
1505
|
if (child?.pid) {
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1506
|
+
try {
|
|
1507
|
+
if (!spawnedIdentity) {
|
|
1508
|
+
throw new Error('The detached upgrade worker cleanup identity was not established.');
|
|
1509
|
+
}
|
|
1510
|
+
await cleanupCandidateUntilDrained(
|
|
1511
|
+
async () => terminateSpawnedDetachedWorker(
|
|
1512
|
+
spawnedIdentity!,
|
|
1513
|
+
optionsArg.installation.cliPath,
|
|
1514
|
+
),
|
|
1515
|
+
dependenciesArg.candidateCleanup,
|
|
1516
|
+
child.pid,
|
|
1517
|
+
);
|
|
1518
|
+
} catch (cleanupErrorArg) {
|
|
1519
|
+
candidateCleanupError = cleanupErrorArg;
|
|
1520
|
+
errors.push(cleanupErrorArg);
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
if (candidateCleanupError !== undefined) {
|
|
1524
|
+
throw new UpgradeWorkerCandidateDrainageError(
|
|
1525
|
+
'The detached upgrade worker launch failed and candidate drainage is unproven.',
|
|
1526
|
+
new AggregateError(errors, 'Detached upgrade worker launch cleanup was incomplete.'),
|
|
1376
1527
|
);
|
|
1377
1528
|
}
|
|
1378
1529
|
if (errors.length > 1) {
|
|
1379
1530
|
throw new AggregateError(
|
|
1380
1531
|
errors,
|
|
1381
|
-
'The detached upgrade worker launch and
|
|
1532
|
+
'The detached upgrade worker launch failed and cleanup was incomplete.',
|
|
1382
1533
|
);
|
|
1383
1534
|
}
|
|
1384
1535
|
throw errorArg;
|
|
@@ -1433,6 +1584,7 @@ const runLoggedCommand = async (optionsArg: {
|
|
|
1433
1584
|
const installVersion = async (
|
|
1434
1585
|
installationArg: IPnpmGlobalInstallation,
|
|
1435
1586
|
versionArg: string,
|
|
1587
|
+
registryUrlArg?: string,
|
|
1436
1588
|
): Promise<void> => {
|
|
1437
1589
|
await runLoggedCommand({
|
|
1438
1590
|
executable: installationArg.packageManagerPath,
|
|
@@ -1444,7 +1596,7 @@ const installVersion = async (
|
|
|
1444
1596
|
'--reporter=append-only',
|
|
1445
1597
|
'--allow-build=node-pty',
|
|
1446
1598
|
'--allow-build=fs-ext-extra-prebuilt',
|
|
1447
|
-
|
|
1599
|
+
...registryArguments(registryUrlArg),
|
|
1448
1600
|
`${controllerPackageName}@${versionArg}`,
|
|
1449
1601
|
],
|
|
1450
1602
|
environment: createPackageManagerEnvironment(),
|
|
@@ -1452,7 +1604,7 @@ const installVersion = async (
|
|
|
1452
1604
|
});
|
|
1453
1605
|
};
|
|
1454
1606
|
|
|
1455
|
-
const packageAddArguments = (packageSpecArg: string, registryUrlArg
|
|
1607
|
+
const packageAddArguments = (packageSpecArg: string, registryUrlArg?: string): string[] => [
|
|
1456
1608
|
'add',
|
|
1457
1609
|
'--global',
|
|
1458
1610
|
'--save-exact',
|
|
@@ -1460,14 +1612,14 @@ const packageAddArguments = (packageSpecArg: string, registryUrlArg: string): st
|
|
|
1460
1612
|
'--reporter=append-only',
|
|
1461
1613
|
'--allow-build=node-pty',
|
|
1462
1614
|
'--allow-build=fs-ext-extra-prebuilt',
|
|
1463
|
-
|
|
1615
|
+
...registryArguments(registryUrlArg),
|
|
1464
1616
|
packageSpecArg,
|
|
1465
1617
|
];
|
|
1466
1618
|
|
|
1467
1619
|
const preparePackageAdd = async (
|
|
1468
1620
|
contextArg: IPackageTransitionContext,
|
|
1469
1621
|
packageSpecArg: string,
|
|
1470
|
-
registryUrlArg
|
|
1622
|
+
registryUrlArg?: string,
|
|
1471
1623
|
): Promise<IPreparedManagedCommand> => await prepareManagedCommand({
|
|
1472
1624
|
executable: contextArg.packageManagerPath,
|
|
1473
1625
|
arguments: packageAddArguments(packageSpecArg, registryUrlArg),
|
|
@@ -1480,7 +1632,7 @@ const preparePackageAdd = async (
|
|
|
1480
1632
|
const addExactPackage = async (
|
|
1481
1633
|
contextArg: IPackageTransitionContext,
|
|
1482
1634
|
packageSpecArg: string,
|
|
1483
|
-
registryUrlArg
|
|
1635
|
+
registryUrlArg?: string,
|
|
1484
1636
|
): Promise<void> => {
|
|
1485
1637
|
await runPreparedManagedCommand(
|
|
1486
1638
|
await preparePackageAdd(contextArg, packageSpecArg, registryUrlArg),
|
|
@@ -1505,12 +1657,14 @@ const prepareControllerStart = async (
|
|
|
1505
1657
|
installationArg: IPnpmGlobalInstallation,
|
|
1506
1658
|
portArg: number,
|
|
1507
1659
|
tokenArg: string,
|
|
1660
|
+
coordinatorArg: UpgradeCoordinator,
|
|
1508
1661
|
): Promise<IPreparedManagedCommand> => await prepareManagedCommand({
|
|
1509
1662
|
executable: process.execPath,
|
|
1510
1663
|
arguments: [installationArg.cliPath, 'start', '--port', String(portArg)],
|
|
1511
1664
|
environment: {
|
|
1512
1665
|
...process.env,
|
|
1513
1666
|
[upgradeTokenEnvironmentVariable]: tokenArg,
|
|
1667
|
+
[upgradeCoordinationRootEnvironmentVariable]: coordinatorArg.baseDirectory,
|
|
1514
1668
|
},
|
|
1515
1669
|
timeoutMs: controllerRestartTimeoutMs,
|
|
1516
1670
|
captureOutput: false,
|
|
@@ -1521,8 +1675,9 @@ const startController = async (
|
|
|
1521
1675
|
installationArg: IPnpmGlobalInstallation,
|
|
1522
1676
|
portArg: number,
|
|
1523
1677
|
tokenArg: string,
|
|
1678
|
+
coordinatorArg: UpgradeCoordinator,
|
|
1524
1679
|
): Promise<void> => await runPreparedManagedCommand(
|
|
1525
|
-
await prepareControllerStart(installationArg, portArg, tokenArg),
|
|
1680
|
+
await prepareControllerStart(installationArg, portArg, tokenArg, coordinatorArg),
|
|
1526
1681
|
).then(() => undefined);
|
|
1527
1682
|
|
|
1528
1683
|
const retainedTargetTransactionMatches = (
|
|
@@ -1539,7 +1694,7 @@ const retainedTargetTransactionMatches = (
|
|
|
1539
1694
|
&& (transactionArg.version === upgradeCoordinationVersion
|
|
1540
1695
|
|| transactionArg.targetPackageCommitted === true)
|
|
1541
1696
|
&& (transactionArg.phase === 'restarting' || transactionArg.phase === 'continuing')
|
|
1542
|
-
&& transactionArg
|
|
1697
|
+
&& upgradeTransactionTargetVersion(transactionArg) === installationArg.packageVersion
|
|
1543
1698
|
&& transactionArg.controller?.command === '__serve'
|
|
1544
1699
|
&& transactionArg.controller.cliPath === installationArg.cliPath
|
|
1545
1700
|
&& transactionArg.controller.pid === transactionArg.controller.processGroupId
|
|
@@ -1816,6 +1971,7 @@ export const prepareAndStopRunningController = async (
|
|
|
1816
1971
|
|
|
1817
1972
|
const restorePreviousInstallation = async (
|
|
1818
1973
|
installationArg: IPnpmGlobalInstallation,
|
|
1974
|
+
registryUrlArg?: string,
|
|
1819
1975
|
): Promise<IPnpmGlobalInstallation> => {
|
|
1820
1976
|
try {
|
|
1821
1977
|
const current = await resolvePnpmGlobalInstallation();
|
|
@@ -1824,7 +1980,7 @@ const restorePreviousInstallation = async (
|
|
|
1824
1980
|
if (errorContainsCommandCleanupFailure(errorArg)) throw errorArg;
|
|
1825
1981
|
// Reinstalling the exact prior version is the authoritative rollback.
|
|
1826
1982
|
}
|
|
1827
|
-
await installVersion(installationArg, installationArg.packageVersion);
|
|
1983
|
+
await installVersion(installationArg, installationArg.packageVersion, registryUrlArg);
|
|
1828
1984
|
const restored = await resolvePnpmGlobalInstallation();
|
|
1829
1985
|
if (restored.packageVersion !== installationArg.packageVersion) {
|
|
1830
1986
|
throw new Error('The previous pnpm-global package version was not restored.');
|
|
@@ -1882,7 +2038,7 @@ const targetControllerIdentity = (transactionArg: TUpgradeTransaction): {
|
|
|
1882
2038
|
}
|
|
1883
2039
|
: {
|
|
1884
2040
|
packageName: transactionArg.targetPackageName,
|
|
1885
|
-
packageVersion: transactionArg
|
|
2041
|
+
packageVersion: upgradeTransactionTargetVersion(transactionArg)!,
|
|
1886
2042
|
managementVersion: transactionArg.targetManagementVersion,
|
|
1887
2043
|
};
|
|
1888
2044
|
|
|
@@ -1899,7 +2055,7 @@ export const assertInheritedUpgradeTransaction = (
|
|
|
1899
2055
|
if (
|
|
1900
2056
|
!transactionArg.controllerWasRunning
|
|
1901
2057
|
|| transactionArg.targetStartupInvoked !== true
|
|
1902
|
-
|| transactionArg
|
|
2058
|
+
|| upgradeTransactionTargetVersion(transactionArg) !== currentIdentityArg.packageVersion
|
|
1903
2059
|
) throw new Error('The inherited upgrade transaction did not authorize target startup.');
|
|
1904
2060
|
if (transactionArg.version === upgradeCoordinationVersion) {
|
|
1905
2061
|
if (
|
|
@@ -1922,7 +2078,7 @@ const runPackageTransitionUpgradeWorker = async (optionsArg: {
|
|
|
1922
2078
|
payload: IUpgradeWorkerPayload;
|
|
1923
2079
|
transaction: IUpgradeTransactionV3;
|
|
1924
2080
|
currentInstallation: IPnpmGlobalInstallation;
|
|
1925
|
-
registryUrl
|
|
2081
|
+
registryUrl?: string;
|
|
1926
2082
|
}): Promise<void> => {
|
|
1927
2083
|
const {
|
|
1928
2084
|
coordinator,
|
|
@@ -2109,7 +2265,7 @@ const runPackageTransitionUpgradeWorker = async (optionsArg: {
|
|
|
2109
2265
|
const sourceTopology = await verifyPackageTransitionTopology(context, 'source-only');
|
|
2110
2266
|
if (!sourceTopology.source) throw new Error('The exact bridge package was not restored.');
|
|
2111
2267
|
if (runningStatus) {
|
|
2112
|
-
await startController(sourceTopology.source, payload.port, payload.token);
|
|
2268
|
+
await startController(sourceTopology.source, payload.port, payload.token, coordinator);
|
|
2113
2269
|
const restoredStatus = await queryUpgradeManagedController(
|
|
2114
2270
|
payload.port,
|
|
2115
2271
|
sourceControllerIdentity(initialTransaction),
|
|
@@ -2147,6 +2303,7 @@ const runPackageTransitionUpgradeWorker = async (optionsArg: {
|
|
|
2147
2303
|
targetInstallation,
|
|
2148
2304
|
payload.port,
|
|
2149
2305
|
payload.token,
|
|
2306
|
+
coordinator,
|
|
2150
2307
|
);
|
|
2151
2308
|
await coordinator.mutateTransaction(payload.token, (current) => {
|
|
2152
2309
|
if (current.version !== upgradePackageTransitionTransactionVersion) {
|
|
@@ -2318,11 +2475,13 @@ export async function runUpgradeWorker(
|
|
|
2318
2475
|
payload: payloadArg,
|
|
2319
2476
|
transaction,
|
|
2320
2477
|
currentInstallation,
|
|
2321
|
-
registryUrl:
|
|
2478
|
+
registryUrl: internalOptionsArg.registryUrl === undefined
|
|
2479
|
+
? undefined
|
|
2480
|
+
: normalizeUpgradeRegistryUrl(internalOptionsArg.registryUrl),
|
|
2322
2481
|
});
|
|
2323
2482
|
return;
|
|
2324
2483
|
}
|
|
2325
|
-
const latestVersion = await readLatestVersion(currentInstallation);
|
|
2484
|
+
const latestVersion = await readLatestVersion(currentInstallation, transaction.registryUrl);
|
|
2326
2485
|
await coordinator.mutateTransaction(payloadArg.token, (current) => {
|
|
2327
2486
|
if (current.version !== upgradeCoordinationVersion) {
|
|
2328
2487
|
throw new Error('The same-package upgrade transaction changed format.');
|
|
@@ -2331,25 +2490,25 @@ export async function runUpgradeWorker(
|
|
|
2331
2490
|
...current,
|
|
2332
2491
|
targetVersion: latestVersion,
|
|
2333
2492
|
phase: 'checking',
|
|
2334
|
-
message: `Installed ${currentInstallation.packageVersion};
|
|
2493
|
+
message: `Installed ${currentInstallation.packageVersion}; registry latest is ${latestVersion}.`,
|
|
2335
2494
|
};
|
|
2336
2495
|
});
|
|
2337
2496
|
const versionComparison = compareSemver(latestVersion, currentInstallation.packageVersion);
|
|
2338
2497
|
process.stdout.write(
|
|
2339
|
-
`${currentCliName} upgrade: installed ${currentInstallation.packageVersion},
|
|
2498
|
+
`${currentCliName} upgrade: installed ${currentInstallation.packageVersion}, registry latest ${latestVersion}.\n`,
|
|
2340
2499
|
);
|
|
2341
2500
|
if (versionComparison <= 0) {
|
|
2342
2501
|
process.stdout.write(
|
|
2343
2502
|
versionComparison === 0
|
|
2344
2503
|
? `${currentCliName} upgrade: already up to date.\n`
|
|
2345
|
-
: `${currentCliName} upgrade: installed version is newer than
|
|
2504
|
+
: `${currentCliName} upgrade: installed version is newer than registry latest; refusing to downgrade.\n`,
|
|
2346
2505
|
);
|
|
2347
2506
|
await coordinator.finishTransaction(
|
|
2348
2507
|
payloadArg.token,
|
|
2349
2508
|
true,
|
|
2350
2509
|
versionComparison === 0
|
|
2351
2510
|
? 'Already up to date.'
|
|
2352
|
-
: 'Installed version is newer than
|
|
2511
|
+
: 'Installed version is newer than registry latest; no downgrade was performed.',
|
|
2353
2512
|
);
|
|
2354
2513
|
return;
|
|
2355
2514
|
}
|
|
@@ -2412,18 +2571,21 @@ export async function runUpgradeWorker(
|
|
|
2412
2571
|
`Package replacement is blocked by ${writer.kind} source writer PID ${writer.identity.pid}.`,
|
|
2413
2572
|
);
|
|
2414
2573
|
}
|
|
2415
|
-
await installVersion(currentInstallation, latestVersion);
|
|
2574
|
+
await installVersion(currentInstallation, latestVersion, transaction.registryUrl);
|
|
2416
2575
|
upgradedInstallation = await resolvePnpmGlobalInstallation();
|
|
2417
2576
|
if (upgradedInstallation.packageVersion !== latestVersion) {
|
|
2418
|
-
throw new Error('pnpm completed without installing the requested
|
|
2577
|
+
throw new Error('pnpm completed without installing the requested registry version.');
|
|
2419
2578
|
}
|
|
2420
2579
|
} catch (upgradeErrorArg) {
|
|
2421
2580
|
if (errorContainsCommandCleanupFailure(upgradeErrorArg)) throw upgradeErrorArg;
|
|
2422
2581
|
const recoveryErrors: unknown[] = [upgradeErrorArg];
|
|
2423
2582
|
try {
|
|
2424
|
-
const restored = await restorePreviousInstallation(
|
|
2583
|
+
const restored = await restorePreviousInstallation(
|
|
2584
|
+
currentInstallation,
|
|
2585
|
+
transaction.registryUrl,
|
|
2586
|
+
);
|
|
2425
2587
|
if (runningStatus) {
|
|
2426
|
-
await startController(restored, payloadArg.port, payloadArg.token);
|
|
2588
|
+
await startController(restored, payloadArg.port, payloadArg.token, coordinator);
|
|
2427
2589
|
const restoredStatus = await queryUpgradeManagedController(
|
|
2428
2590
|
payloadArg.port,
|
|
2429
2591
|
{
|
|
@@ -2465,7 +2627,7 @@ export async function runUpgradeWorker(
|
|
|
2465
2627
|
targetStartupInvoked: true,
|
|
2466
2628
|
message: `Invoking ${controllerPackageName} ${latestVersion}.`,
|
|
2467
2629
|
}));
|
|
2468
|
-
await startController(upgradedInstallation, payloadArg.port, payloadArg.token);
|
|
2630
|
+
await startController(upgradedInstallation, payloadArg.port, payloadArg.token, coordinator);
|
|
2469
2631
|
const upgradedStatus = await queryUpgradeManagedController(payloadArg.port, {
|
|
2470
2632
|
packageName: controllerPackageName,
|
|
2471
2633
|
packageVersion: latestVersion,
|
|
@@ -2532,18 +2694,27 @@ const recoverPackageTransition = async (optionsArg: {
|
|
|
2532
2694
|
globalRoot: optionsArg.installation.globalRoot,
|
|
2533
2695
|
globalBinDirectory: optionsArg.installation.globalBinDirectory,
|
|
2534
2696
|
};
|
|
2535
|
-
const registryUrl =
|
|
2697
|
+
const registryUrl = undefined;
|
|
2536
2698
|
const transaction = optionsArg.transaction;
|
|
2537
2699
|
if (transaction.targetPackageCommitStarted === true) {
|
|
2538
|
-
const
|
|
2700
|
+
const targetVersion = upgradeTransactionTargetVersion(transaction)!;
|
|
2701
|
+
const targetSpec = `${transaction.targetPackageName}@${targetVersion}`;
|
|
2539
2702
|
let targetTopology: IPackageTransitionTopology;
|
|
2540
2703
|
if (transaction.targetPackageCommitted === true) {
|
|
2541
2704
|
try {
|
|
2542
|
-
targetTopology = await verifyPackageTransitionTopology(
|
|
2705
|
+
targetTopology = await verifyPackageTransitionTopology(
|
|
2706
|
+
context,
|
|
2707
|
+
'target-only',
|
|
2708
|
+
targetVersion,
|
|
2709
|
+
);
|
|
2543
2710
|
} catch (verificationErrorArg) {
|
|
2544
2711
|
try {
|
|
2545
2712
|
await addExactPackage(context, targetSpec, registryUrl);
|
|
2546
|
-
targetTopology = await verifyPackageTransitionTopology(
|
|
2713
|
+
targetTopology = await verifyPackageTransitionTopology(
|
|
2714
|
+
context,
|
|
2715
|
+
'target-only',
|
|
2716
|
+
targetVersion,
|
|
2717
|
+
);
|
|
2547
2718
|
} catch (normalizationErrorArg) {
|
|
2548
2719
|
throw new AggregateError(
|
|
2549
2720
|
[verificationErrorArg, normalizationErrorArg],
|
|
@@ -2553,7 +2724,11 @@ const recoverPackageTransition = async (optionsArg: {
|
|
|
2553
2724
|
}
|
|
2554
2725
|
} else {
|
|
2555
2726
|
await addExactPackage(context, targetSpec, registryUrl);
|
|
2556
|
-
targetTopology = await verifyPackageTransitionTopology(
|
|
2727
|
+
targetTopology = await verifyPackageTransitionTopology(
|
|
2728
|
+
context,
|
|
2729
|
+
'target-only',
|
|
2730
|
+
targetVersion,
|
|
2731
|
+
);
|
|
2557
2732
|
await optionsArg.coordinator.mutateTransaction(optionsArg.token, (current) => {
|
|
2558
2733
|
if (current.version !== upgradePackageTransitionTransactionVersion) {
|
|
2559
2734
|
throw new Error('The package-transition transaction changed format.');
|
|
@@ -2583,6 +2758,7 @@ const recoverPackageTransition = async (optionsArg: {
|
|
|
2583
2758
|
targetInstallation,
|
|
2584
2759
|
current.port,
|
|
2585
2760
|
optionsArg.token,
|
|
2761
|
+
optionsArg.coordinator,
|
|
2586
2762
|
);
|
|
2587
2763
|
current = requirePackageTransitionTransaction(await optionsArg.coordinator.mutateTransaction(optionsArg.token, (candidate) => {
|
|
2588
2764
|
if (candidate.version !== upgradePackageTransitionTransactionVersion) {
|
|
@@ -2599,7 +2775,7 @@ const recoverPackageTransition = async (optionsArg: {
|
|
|
2599
2775
|
}
|
|
2600
2776
|
let status = await queryControllerStatus(current.port, {
|
|
2601
2777
|
packageName: current.targetPackageName,
|
|
2602
|
-
packageVersion: current
|
|
2778
|
+
packageVersion: upgradeTransactionTargetVersion(current)!,
|
|
2603
2779
|
upgradeManagementVersion: current.targetManagementVersion,
|
|
2604
2780
|
}).catch(() => undefined);
|
|
2605
2781
|
if (!status) {
|
|
@@ -2614,7 +2790,12 @@ const recoverPackageTransition = async (optionsArg: {
|
|
|
2614
2790
|
'A target controller is already starting or listening but cannot be adopted safely; refusing to launch a replacement.',
|
|
2615
2791
|
);
|
|
2616
2792
|
}
|
|
2617
|
-
await startController(
|
|
2793
|
+
await startController(
|
|
2794
|
+
targetInstallation,
|
|
2795
|
+
current.port,
|
|
2796
|
+
optionsArg.token,
|
|
2797
|
+
optionsArg.coordinator,
|
|
2798
|
+
);
|
|
2618
2799
|
status = await queryUpgradeManagedController(
|
|
2619
2800
|
current.port,
|
|
2620
2801
|
targetControllerIdentity(current),
|
|
@@ -2630,7 +2811,7 @@ const recoverPackageTransition = async (optionsArg: {
|
|
|
2630
2811
|
await optionsArg.coordinator.finishTransaction(
|
|
2631
2812
|
optionsArg.token,
|
|
2632
2813
|
true,
|
|
2633
|
-
`Upgrade to ${current.targetPackageName}@${current
|
|
2814
|
+
`Upgrade to ${current.targetPackageName}@${upgradeTransactionTargetVersion(current)} completed during bounded recovery.`,
|
|
2634
2815
|
);
|
|
2635
2816
|
return true;
|
|
2636
2817
|
}
|
|
@@ -2652,7 +2833,12 @@ const recoverPackageTransition = async (optionsArg: {
|
|
|
2652
2833
|
upgradeManagementVersion: transaction.sourceManagementVersion,
|
|
2653
2834
|
}).catch(() => undefined);
|
|
2654
2835
|
if (!status) {
|
|
2655
|
-
await startController(
|
|
2836
|
+
await startController(
|
|
2837
|
+
sourceInstallation,
|
|
2838
|
+
transaction.port,
|
|
2839
|
+
optionsArg.token,
|
|
2840
|
+
optionsArg.coordinator,
|
|
2841
|
+
);
|
|
2656
2842
|
status = await queryUpgradeManagedController(
|
|
2657
2843
|
transaction.port,
|
|
2658
2844
|
sourceControllerIdentity(transaction),
|
|
@@ -2725,7 +2911,12 @@ const recoverStalledUpgradeUnderLock = async (optionsArg: {
|
|
|
2725
2911
|
'A target controller is already starting or listening but cannot be adopted safely; refusing to launch a replacement.',
|
|
2726
2912
|
);
|
|
2727
2913
|
}
|
|
2728
|
-
await startController(
|
|
2914
|
+
await startController(
|
|
2915
|
+
targetInstallation,
|
|
2916
|
+
transaction.port,
|
|
2917
|
+
optionsArg.token,
|
|
2918
|
+
optionsArg.coordinator,
|
|
2919
|
+
);
|
|
2729
2920
|
status = await queryUpgradeManagedController(
|
|
2730
2921
|
transaction.port,
|
|
2731
2922
|
{
|
|
@@ -2753,7 +2944,7 @@ const recoverStalledUpgradeUnderLock = async (optionsArg: {
|
|
|
2753
2944
|
const sourceInstallation = await restorePreviousInstallation({
|
|
2754
2945
|
...optionsArg.installation,
|
|
2755
2946
|
packageVersion: transaction.sourceVersion,
|
|
2756
|
-
});
|
|
2947
|
+
}, transaction.version === upgradeCoordinationVersion ? transaction.registryUrl : undefined);
|
|
2757
2948
|
if (transaction.controllerWasRunning) {
|
|
2758
2949
|
let status = await queryControllerStatus(transaction.port, {
|
|
2759
2950
|
packageName: controllerPackageName,
|
|
@@ -2761,7 +2952,12 @@ const recoverStalledUpgradeUnderLock = async (optionsArg: {
|
|
|
2761
2952
|
upgradeManagementVersion: controllerUpgradeManagementVersion,
|
|
2762
2953
|
}).catch(() => undefined);
|
|
2763
2954
|
if (!status) {
|
|
2764
|
-
await startController(
|
|
2955
|
+
await startController(
|
|
2956
|
+
sourceInstallation,
|
|
2957
|
+
transaction.port,
|
|
2958
|
+
optionsArg.token,
|
|
2959
|
+
optionsArg.coordinator,
|
|
2960
|
+
);
|
|
2765
2961
|
status = await queryUpgradeManagedController(
|
|
2766
2962
|
transaction.port,
|
|
2767
2963
|
{
|
|
@@ -2795,45 +2991,188 @@ interface IRecoverStalledUpgradeOptions {
|
|
|
2795
2991
|
command: '__serve' | 'foreground' | 'upgrade';
|
|
2796
2992
|
}
|
|
2797
2993
|
|
|
2798
|
-
interface
|
|
2799
|
-
launchRecoveryWorker?: typeof launchDetachedRecoveryWorker;
|
|
2994
|
+
export interface IUpgradeWorkerHandoffDependencies {
|
|
2800
2995
|
terminateWorkerCandidate?: (
|
|
2801
2996
|
candidateArg: IDetachedUpgradeWorker,
|
|
2802
2997
|
) => Promise<void>;
|
|
2803
|
-
candidateCleanup?:
|
|
2998
|
+
candidateCleanup?: ICandidateCleanupDependencies;
|
|
2804
2999
|
}
|
|
2805
3000
|
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
3001
|
+
interface IRecoverStalledUpgradeDependencies extends IUpgradeWorkerHandoffDependencies {
|
|
3002
|
+
launchRecoveryWorker?: typeof launchDetachedRecoveryWorker;
|
|
3003
|
+
}
|
|
3004
|
+
|
|
3005
|
+
export interface IHandoffLaunchedUpgradeWorkerUnderLockOptions {
|
|
3006
|
+
coordinator: UpgradeCoordinator;
|
|
3007
|
+
token: string;
|
|
3008
|
+
installation: IPnpmGlobalInstallation;
|
|
3009
|
+
command: '__serve' | 'foreground' | 'upgrade';
|
|
3010
|
+
lock: UpgradeInstallationLock;
|
|
3011
|
+
candidate: IUpgradeWorkerLaunchCandidate;
|
|
3012
|
+
retainLockOnFailure?: boolean;
|
|
3013
|
+
}
|
|
3014
|
+
|
|
3015
|
+
export const handoffLaunchedUpgradeWorkerUnderLock = async (
|
|
3016
|
+
optionsArg: IHandoffLaunchedUpgradeWorkerUnderLockOptions,
|
|
3017
|
+
dependenciesArg: IUpgradeWorkerHandoffDependencies = {},
|
|
3018
|
+
): Promise<IDetachedUpgradeWorker> => {
|
|
3019
|
+
let worker: IDetachedUpgradeWorker | undefined;
|
|
2815
3020
|
try {
|
|
2816
|
-
|
|
3021
|
+
await optionsArg.coordinator.removeWorkerAcknowledgement(optionsArg.token);
|
|
3022
|
+
const launchedTransaction = await optionsArg.coordinator.recordWorkerLaunch(
|
|
3023
|
+
optionsArg.token,
|
|
3024
|
+
optionsArg.candidate.pid,
|
|
3025
|
+
optionsArg.candidate.cliPath,
|
|
3026
|
+
optionsArg.candidate.logFilePath,
|
|
3027
|
+
);
|
|
3028
|
+
const retainedWorker = launchedTransaction.worker;
|
|
3029
|
+
if (
|
|
3030
|
+
!retainedWorker
|
|
3031
|
+
|| retainedWorker.pid !== optionsArg.candidate.pid
|
|
3032
|
+
|| retainedWorker.cliPath !== optionsArg.candidate.cliPath
|
|
3033
|
+
|| retainedWorker.logFilePath !== optionsArg.candidate.logFilePath
|
|
3034
|
+
|| (optionsArg.candidate.processGroupId !== undefined
|
|
3035
|
+
&& retainedWorker.processGroupId !== optionsArg.candidate.processGroupId)
|
|
3036
|
+
|| (optionsArg.candidate.fingerprint !== undefined
|
|
3037
|
+
&& retainedWorker.fingerprint !== optionsArg.candidate.fingerprint)
|
|
3038
|
+
) throw new Error('The upgrade worker launch identity was not retained exactly.');
|
|
3039
|
+
worker = {
|
|
3040
|
+
pid: retainedWorker.pid,
|
|
3041
|
+
processGroupId: retainedWorker.processGroupId,
|
|
3042
|
+
fingerprint: retainedWorker.fingerprint,
|
|
3043
|
+
cliPath: retainedWorker.cliPath,
|
|
3044
|
+
logFilePath: optionsArg.candidate.logFilePath,
|
|
3045
|
+
};
|
|
3046
|
+
await optionsArg.coordinator.transferUpgradeLockToWorker({
|
|
3047
|
+
lock: optionsArg.lock,
|
|
2817
3048
|
token: optionsArg.token,
|
|
2818
|
-
|
|
2819
|
-
command: optionsArg.command,
|
|
3049
|
+
worker,
|
|
2820
3050
|
});
|
|
3051
|
+
await optionsArg.coordinator.acknowledgeWorker(optionsArg.token, worker);
|
|
3052
|
+
return {
|
|
3053
|
+
pid: worker.pid,
|
|
3054
|
+
processGroupId: worker.processGroupId,
|
|
3055
|
+
fingerprint: worker.fingerprint,
|
|
3056
|
+
cliPath: worker.cliPath,
|
|
3057
|
+
logFilePath: worker.logFilePath!,
|
|
3058
|
+
};
|
|
2821
3059
|
} catch (errorArg) {
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
3060
|
+
const errors: unknown[] = [errorArg];
|
|
3061
|
+
if (!worker) {
|
|
3062
|
+
const current = await optionsArg.coordinator.readTransaction(optionsArg.token).catch(() => undefined);
|
|
3063
|
+
if (
|
|
3064
|
+
current?.worker
|
|
3065
|
+
&& current.worker.pid === optionsArg.candidate.pid
|
|
3066
|
+
&& current.worker.cliPath === optionsArg.candidate.cliPath
|
|
3067
|
+
&& current.worker.logFilePath === optionsArg.candidate.logFilePath
|
|
3068
|
+
) worker = {
|
|
3069
|
+
pid: current.worker.pid,
|
|
3070
|
+
processGroupId: current.worker.processGroupId,
|
|
3071
|
+
fingerprint: current.worker.fingerprint,
|
|
3072
|
+
cliPath: current.worker.cliPath,
|
|
3073
|
+
logFilePath: optionsArg.candidate.logFilePath,
|
|
3074
|
+
};
|
|
3075
|
+
}
|
|
3076
|
+
if (!worker && (
|
|
3077
|
+
optionsArg.candidate.processGroupId !== undefined
|
|
3078
|
+
&& optionsArg.candidate.fingerprint !== undefined
|
|
3079
|
+
)) {
|
|
3080
|
+
worker = {
|
|
3081
|
+
pid: optionsArg.candidate.pid,
|
|
3082
|
+
processGroupId: optionsArg.candidate.processGroupId,
|
|
3083
|
+
fingerprint: optionsArg.candidate.fingerprint,
|
|
3084
|
+
cliPath: optionsArg.candidate.cliPath,
|
|
3085
|
+
logFilePath: optionsArg.candidate.logFilePath,
|
|
3086
|
+
};
|
|
3087
|
+
}
|
|
3088
|
+
if (!worker) {
|
|
3089
|
+
throw new UpgradeWorkerCandidateDrainageError(
|
|
3090
|
+
'The upgrade worker handoff failed before its cleanup identity could be retained.',
|
|
3091
|
+
errorArg,
|
|
3092
|
+
);
|
|
3093
|
+
}
|
|
3094
|
+
const cleanupWorker = worker;
|
|
3095
|
+
try {
|
|
3096
|
+
await cleanupCandidateUntilDrained(
|
|
3097
|
+
async () => await (dependenciesArg.terminateWorkerCandidate
|
|
3098
|
+
?? ((candidateArg) => optionsArg.coordinator.terminateUpgradeWorkerCandidate(candidateArg)))(
|
|
3099
|
+
cleanupWorker,
|
|
3100
|
+
),
|
|
3101
|
+
dependenciesArg.candidateCleanup,
|
|
3102
|
+
cleanupWorker.pid,
|
|
3103
|
+
);
|
|
3104
|
+
} catch (cleanupErrorArg) {
|
|
3105
|
+
errors.push(cleanupErrorArg);
|
|
3106
|
+
}
|
|
3107
|
+
if (errors.length > 1) {
|
|
3108
|
+
throw new AggregateError(
|
|
3109
|
+
errors,
|
|
3110
|
+
'Upgrade worker handoff failed and candidate drainage is unproven; ownership was retained.',
|
|
3111
|
+
);
|
|
3112
|
+
}
|
|
3113
|
+
const acquireCallerLock = async (): Promise<UpgradeInstallationLock> => {
|
|
3114
|
+
try {
|
|
3115
|
+
await optionsArg.lock.assertOwned();
|
|
3116
|
+
return optionsArg.lock;
|
|
3117
|
+
} catch {
|
|
3118
|
+
return await optionsArg.coordinator.acquireUpgradeLock({
|
|
3119
|
+
token: optionsArg.token,
|
|
3120
|
+
cliPath: optionsArg.installation.cliPath,
|
|
3121
|
+
command: optionsArg.command,
|
|
3122
|
+
});
|
|
3123
|
+
}
|
|
3124
|
+
};
|
|
3125
|
+
if (optionsArg.retainLockOnFailure) {
|
|
3126
|
+
try {
|
|
3127
|
+
const recoveryLock = await acquireCallerLock();
|
|
3128
|
+
await recoveryLock.assertOwned();
|
|
3129
|
+
throw new UpgradeWorkerHandoffError(
|
|
3130
|
+
'The upgrade worker handoff failed; exclusive ownership was retained for recovery.',
|
|
3131
|
+
errors.length === 1
|
|
3132
|
+
? errorArg
|
|
3133
|
+
: new AggregateError(errors, 'Upgrade worker handoff and candidate cleanup failed.'),
|
|
3134
|
+
recoveryLock,
|
|
3135
|
+
);
|
|
3136
|
+
} catch (lockRetentionErrorArg) {
|
|
3137
|
+
if (lockRetentionErrorArg instanceof UpgradeWorkerHandoffError) {
|
|
3138
|
+
throw lockRetentionErrorArg;
|
|
3139
|
+
}
|
|
3140
|
+
errors.push(lockRetentionErrorArg);
|
|
3141
|
+
}
|
|
3142
|
+
}
|
|
3143
|
+
try {
|
|
3144
|
+
await (await acquireCallerLock()).release();
|
|
3145
|
+
} catch (lockCleanupErrorArg) {
|
|
3146
|
+
errors.push(lockCleanupErrorArg);
|
|
3147
|
+
}
|
|
3148
|
+
if (errors.length > 1) {
|
|
3149
|
+
throw new AggregateError(
|
|
3150
|
+
errors,
|
|
3151
|
+
'Upgrade worker handoff failed and cleanup was incomplete.',
|
|
3152
|
+
);
|
|
3153
|
+
}
|
|
2827
3154
|
throw errorArg;
|
|
2828
3155
|
}
|
|
3156
|
+
};
|
|
3157
|
+
|
|
3158
|
+
export interface IHandoffStalledUpgradeRecoveryUnderLockOptions {
|
|
3159
|
+
coordinator: UpgradeCoordinator;
|
|
3160
|
+
token: string;
|
|
3161
|
+
installation: IPnpmGlobalInstallation;
|
|
3162
|
+
command: '__serve' | 'foreground' | 'upgrade';
|
|
3163
|
+
lock: UpgradeInstallationLock;
|
|
3164
|
+
}
|
|
2829
3165
|
|
|
2830
|
-
|
|
2831
|
-
|
|
3166
|
+
export const handoffStalledUpgradeRecoveryUnderLock = async (
|
|
3167
|
+
optionsArg: IHandoffStalledUpgradeRecoveryUnderLockOptions,
|
|
3168
|
+
dependenciesArg: IRecoverStalledUpgradeDependencies = {},
|
|
3169
|
+
): Promise<IDetachedUpgradeWorker | undefined> => {
|
|
3170
|
+
let candidate: IDetachedUpgradeWorker;
|
|
2832
3171
|
try {
|
|
2833
3172
|
const transaction = await optionsArg.coordinator.readTransaction(optionsArg.token);
|
|
2834
3173
|
if (transaction.terminal) {
|
|
2835
|
-
await lock.release();
|
|
2836
|
-
return
|
|
3174
|
+
await optionsArg.lock.release();
|
|
3175
|
+
return undefined;
|
|
2837
3176
|
}
|
|
2838
3177
|
const recoveryInstallation = await resolveAuthoritativeRecoveryInstallation(
|
|
2839
3178
|
transaction,
|
|
@@ -2846,76 +3185,61 @@ export async function recoverStalledUpgrade(
|
|
|
2846
3185
|
gracePeriodMs: transaction.gracePeriodMs,
|
|
2847
3186
|
continueSessions: transaction.continueSessions,
|
|
2848
3187
|
};
|
|
3188
|
+
await optionsArg.coordinator.removeWorkerAcknowledgement(optionsArg.token);
|
|
2849
3189
|
candidate = await (dependenciesArg.launchRecoveryWorker ?? launchDetachedRecoveryWorker)({
|
|
2850
3190
|
installation: recoveryInstallation,
|
|
2851
3191
|
payload,
|
|
2852
3192
|
});
|
|
2853
|
-
const launchedTransaction = await optionsArg.coordinator.recordWorkerLaunch(
|
|
2854
|
-
optionsArg.token,
|
|
2855
|
-
candidate.pid,
|
|
2856
|
-
candidate.cliPath,
|
|
2857
|
-
candidate.logFilePath,
|
|
2858
|
-
);
|
|
2859
|
-
const worker = launchedTransaction.worker;
|
|
2860
|
-
if (
|
|
2861
|
-
!worker
|
|
2862
|
-
|| worker.pid !== candidate.pid
|
|
2863
|
-
|| worker.processGroupId !== candidate.processGroupId
|
|
2864
|
-
|| worker.fingerprint !== candidate.fingerprint
|
|
2865
|
-
|| worker.cliPath !== candidate.cliPath
|
|
2866
|
-
|| worker.logFilePath !== candidate.logFilePath
|
|
2867
|
-
) throw new Error('The recovery worker launch identity was not retained exactly.');
|
|
2868
|
-
await optionsArg.coordinator.transferUpgradeLockToWorker({
|
|
2869
|
-
lock,
|
|
2870
|
-
token: optionsArg.token,
|
|
2871
|
-
worker,
|
|
2872
|
-
});
|
|
2873
|
-
lockTransferred = true;
|
|
2874
|
-
await optionsArg.coordinator.acknowledgeWorker(optionsArg.token);
|
|
2875
|
-
return true;
|
|
2876
3193
|
} catch (errorArg) {
|
|
2877
|
-
|
|
2878
|
-
if (candidate) {
|
|
2879
|
-
await retryCandidateCleanupUntilDrained(
|
|
2880
|
-
async () => await (dependenciesArg.terminateWorkerCandidate
|
|
2881
|
-
?? ((candidateArg) => optionsArg.coordinator.terminateUpgradeWorkerCandidate(candidateArg)))(
|
|
2882
|
-
candidate!,
|
|
2883
|
-
),
|
|
2884
|
-
dependenciesArg.candidateCleanup,
|
|
2885
|
-
candidate.pid,
|
|
2886
|
-
);
|
|
2887
|
-
}
|
|
3194
|
+
if (errorArg instanceof UpgradeWorkerCandidateDrainageError) throw errorArg;
|
|
2888
3195
|
try {
|
|
2889
|
-
|
|
2890
|
-
try {
|
|
2891
|
-
await lock.release();
|
|
2892
|
-
} catch {
|
|
2893
|
-
const replacementLock = await optionsArg.coordinator.acquireUpgradeLock({
|
|
2894
|
-
token: optionsArg.token,
|
|
2895
|
-
cliPath: optionsArg.installation.cliPath,
|
|
2896
|
-
command: optionsArg.command,
|
|
2897
|
-
});
|
|
2898
|
-
await replacementLock.release();
|
|
2899
|
-
}
|
|
2900
|
-
} else {
|
|
2901
|
-
const replacementLock = await optionsArg.coordinator.acquireUpgradeLock({
|
|
2902
|
-
token: optionsArg.token,
|
|
2903
|
-
cliPath: optionsArg.installation.cliPath,
|
|
2904
|
-
command: optionsArg.command,
|
|
2905
|
-
});
|
|
2906
|
-
await replacementLock.release();
|
|
2907
|
-
}
|
|
3196
|
+
await optionsArg.lock.release();
|
|
2908
3197
|
} catch (lockCleanupErrorArg) {
|
|
2909
|
-
errors.push(lockCleanupErrorArg);
|
|
2910
|
-
}
|
|
2911
|
-
if (errors.length > 1) {
|
|
2912
3198
|
throw new AggregateError(
|
|
2913
|
-
|
|
2914
|
-
'Stalled upgrade recovery
|
|
3199
|
+
[errorArg, lockCleanupErrorArg],
|
|
3200
|
+
'Stalled upgrade recovery launch failed and its lock could not be released.',
|
|
2915
3201
|
);
|
|
2916
3202
|
}
|
|
2917
3203
|
throw errorArg;
|
|
2918
3204
|
}
|
|
3205
|
+
return await handoffLaunchedUpgradeWorkerUnderLock({
|
|
3206
|
+
...optionsArg,
|
|
3207
|
+
candidate,
|
|
3208
|
+
}, dependenciesArg);
|
|
3209
|
+
};
|
|
3210
|
+
|
|
3211
|
+
export function recoverStalledUpgrade(
|
|
3212
|
+
optionsArg: IRecoverStalledUpgradeOptions,
|
|
3213
|
+
): Promise<boolean>;
|
|
3214
|
+
export async function recoverStalledUpgrade(
|
|
3215
|
+
optionsArg: IRecoverStalledUpgradeOptions,
|
|
3216
|
+
dependenciesArg: IRecoverStalledUpgradeDependencies = {},
|
|
3217
|
+
): Promise<boolean> {
|
|
3218
|
+
await optionsArg.coordinator.terminateTransactionWorker(optionsArg.transaction);
|
|
3219
|
+
let lock: Awaited<ReturnType<UpgradeCoordinator['acquireUpgradeLock']>>;
|
|
3220
|
+
try {
|
|
3221
|
+
lock = await optionsArg.coordinator.acquireUpgradeLock({
|
|
3222
|
+
token: optionsArg.token,
|
|
3223
|
+
cliPath: optionsArg.installation.cliPath,
|
|
3224
|
+
command: optionsArg.command,
|
|
3225
|
+
});
|
|
3226
|
+
} catch (errorArg) {
|
|
3227
|
+
if (
|
|
3228
|
+
errorArg instanceof Error
|
|
3229
|
+
&& (errorArg.message.includes('already running')
|
|
3230
|
+
|| errorArg.message.includes('initializing'))
|
|
3231
|
+
) return false;
|
|
3232
|
+
throw errorArg;
|
|
3233
|
+
}
|
|
3234
|
+
|
|
3235
|
+
await handoffStalledUpgradeRecoveryUnderLock({
|
|
3236
|
+
coordinator: optionsArg.coordinator,
|
|
3237
|
+
token: optionsArg.token,
|
|
3238
|
+
installation: optionsArg.installation,
|
|
3239
|
+
command: optionsArg.command,
|
|
3240
|
+
lock,
|
|
3241
|
+
}, dependenciesArg);
|
|
3242
|
+
return true;
|
|
2919
3243
|
}
|
|
2920
3244
|
|
|
2921
3245
|
export const createUpgradeWorkerPayload = (optionsArg: {
|