@git.zone/cli 6.9.0 → 6.11.0
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_ts/00_commitinfo_data.js +2 -2
- package/dist_ts/helpers.workflow.d.ts +23 -1
- package/dist_ts/helpers.workflow.js +93 -3
- package/dist_ts/mod_config/index.js +49 -7
- package/dist_ts/mod_release/classes.releasejournal.d.ts +20 -1
- package/dist_ts/mod_release/classes.releasejournal.js +191 -9
- package/dist_ts/mod_release/helpers.giteaassets.d.ts +36 -0
- package/dist_ts/mod_release/helpers.giteaassets.js +661 -0
- package/dist_ts/mod_release/helpers.npmartifact.d.ts +1 -3
- package/dist_ts/mod_release/helpers.npmartifact.js +88 -66
- package/dist_ts/mod_release/helpers.releasepublication.d.ts +2 -0
- package/dist_ts/mod_release/helpers.releasepublication.js +147 -6
- package/dist_ts/mod_release/index.js +86 -11
- package/dist_ts/mod_release/interfaces.giteaassets.d.ts +50 -0
- package/dist_ts/mod_release/interfaces.giteaassets.js +2 -0
- package/dist_ts/mod_tools/classes.packagemanager.d.ts +2 -0
- package/dist_ts/mod_tools/classes.packagemanager.js +3 -3
- package/package.json +3 -3
- package/readme.md +94 -5
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/helpers.workflow.ts +154 -3
- package/ts/mod_config/index.ts +61 -6
- package/ts/mod_release/classes.releasejournal.ts +298 -14
- package/ts/mod_release/helpers.giteaassets.ts +932 -0
- package/ts/mod_release/helpers.npmartifact.ts +156 -77
- package/ts/mod_release/helpers.releasepublication.ts +224 -4
- package/ts/mod_release/index.ts +111 -8
- package/ts/mod_release/interfaces.giteaassets.ts +67 -0
- package/ts/mod_tools/classes.packagemanager.ts +2 -2
|
@@ -4,16 +4,15 @@ import {
|
|
|
4
4
|
type IReleaseArtifact,
|
|
5
5
|
} from "./classes.releasejournal.js";
|
|
6
6
|
import { assertCredentialFreeGitPushDestination } from "./helpers.releasebranch.js";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"11.25.0": {
|
|
7
|
+
import {
|
|
8
|
+
getDefaultMinimumReleaseAgeMinutes,
|
|
9
|
+
packageSelectorMatches,
|
|
10
|
+
} from "../mod_tools/classes.packagemanager.js";
|
|
11
|
+
|
|
12
|
+
const stablePnpmVersion = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
|
|
13
|
+
// Qualify the command contract, independently of the executable's version.
|
|
14
|
+
const pnpmHelpRequirements = [
|
|
15
|
+
{
|
|
17
16
|
pack: ["--out <path>", "--json"],
|
|
18
17
|
publish: [
|
|
19
18
|
"publish [<tarball>|<dir>]",
|
|
@@ -24,33 +23,7 @@ const pnpmHelpRequirements = {
|
|
|
24
23
|
"--access <public|restricted>",
|
|
25
24
|
],
|
|
26
25
|
},
|
|
27
|
-
|
|
28
|
-
pack: ["Usage: pnpm pack [OPTIONS]", "--out <OUT>", "--json"],
|
|
29
|
-
publish: [
|
|
30
|
-
"Usage: pnpm publish [OPTIONS] [PACKAGE]",
|
|
31
|
-
"Tarball or directory to publish",
|
|
32
|
-
"--no-git-checks",
|
|
33
|
-
"--ignore-scripts",
|
|
34
|
-
"--json",
|
|
35
|
-
"--tag <TAG>",
|
|
36
|
-
"--access <ACCESS>",
|
|
37
|
-
"[possible values: public, restricted]",
|
|
38
|
-
],
|
|
39
|
-
},
|
|
40
|
-
"12.2.1": {
|
|
41
|
-
pack: ["Usage: pnpm pack [OPTIONS]", "--out <OUT>", "--json"],
|
|
42
|
-
publish: [
|
|
43
|
-
"Usage: pnpm publish [OPTIONS] [PACKAGE]",
|
|
44
|
-
"Tarball or directory to publish",
|
|
45
|
-
"--no-git-checks",
|
|
46
|
-
"--ignore-scripts",
|
|
47
|
-
"--json",
|
|
48
|
-
"--tag <TAG>",
|
|
49
|
-
"--access <ACCESS>",
|
|
50
|
-
"[possible values: public, restricted]",
|
|
51
|
-
],
|
|
52
|
-
},
|
|
53
|
-
"12.3.0": {
|
|
26
|
+
{
|
|
54
27
|
pack: ["Usage: pnpm pack [OPTIONS]", "--out <OUT>", "--json"],
|
|
55
28
|
publish: [
|
|
56
29
|
"Usage: pnpm publish [OPTIONS] [PACKAGE]",
|
|
@@ -63,20 +36,7 @@ const pnpmHelpRequirements = {
|
|
|
63
36
|
"[possible values: public, restricted]",
|
|
64
37
|
],
|
|
65
38
|
},
|
|
66
|
-
|
|
67
|
-
pack: ["Usage: pnpm pack [OPTIONS]", "--out <OUT>", "--json"],
|
|
68
|
-
publish: [
|
|
69
|
-
"Usage: pnpm publish [OPTIONS] [PACKAGE]",
|
|
70
|
-
"Tarball or directory to publish",
|
|
71
|
-
"--no-git-checks",
|
|
72
|
-
"--ignore-scripts",
|
|
73
|
-
"--json",
|
|
74
|
-
"--tag <TAG>",
|
|
75
|
-
"--access <ACCESS>",
|
|
76
|
-
"[possible values: public, restricted]",
|
|
77
|
-
],
|
|
78
|
-
},
|
|
79
|
-
} as const;
|
|
39
|
+
] as const;
|
|
80
40
|
const maximumMetadataResponseBytes = 8 * 1024 * 1024;
|
|
81
41
|
const capabilityCommandTimeoutMs = 30_000;
|
|
82
42
|
const packCommandTimeoutMs = 10 * 60_000;
|
|
@@ -91,17 +51,14 @@ export function assertNpmPackageName(value: unknown): asserts value is string {
|
|
|
91
51
|
}
|
|
92
52
|
|
|
93
53
|
export interface IPnpmReleaseCapability {
|
|
94
|
-
readonly version:
|
|
54
|
+
readonly version: string;
|
|
95
55
|
}
|
|
96
56
|
|
|
97
57
|
export const buildQualifiedPnpmArgs = (
|
|
98
58
|
capabilityArg: IPnpmReleaseCapability | undefined,
|
|
99
59
|
argsArg: readonly string[],
|
|
100
60
|
): string[] => {
|
|
101
|
-
if (
|
|
102
|
-
!capabilityArg ||
|
|
103
|
-
!supportedPnpmVersions.includes(capabilityArg.version)
|
|
104
|
-
) {
|
|
61
|
+
if (!capabilityArg || !stablePnpmVersion.test(capabilityArg.version)) {
|
|
105
62
|
throw new Error(
|
|
106
63
|
"npm release commands require a qualified pnpm invocation.",
|
|
107
64
|
);
|
|
@@ -165,15 +122,140 @@ const readPackageIdentity = async (
|
|
|
165
122
|
|
|
166
123
|
const requireHelpFragments = (
|
|
167
124
|
outputArg: string,
|
|
168
|
-
|
|
169
|
-
commandArg: string,
|
|
125
|
+
commandArg: "pack" | "publish",
|
|
170
126
|
): void => {
|
|
171
|
-
const missing =
|
|
172
|
-
|
|
127
|
+
const missing = pnpmHelpRequirements.map((requirements) =>
|
|
128
|
+
requirements[commandArg].filter(
|
|
129
|
+
(fragment) => !outputArg.includes(fragment),
|
|
130
|
+
),
|
|
131
|
+
);
|
|
132
|
+
if (missing.every((fragments) => fragments.length > 0)) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
`pnpm ${commandArg} is missing required exact-release capabilities: ${missing.map((fragments) => fragments.join(", ")).join("; or ")}.`,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const readPnpmJson = async (
|
|
140
|
+
shellArg: plugins.smartshell.Smartshell,
|
|
141
|
+
cwdArg: string,
|
|
142
|
+
capabilityArg: IPnpmReleaseCapability,
|
|
143
|
+
argsArg: string[],
|
|
144
|
+
labelArg: string,
|
|
145
|
+
): Promise<unknown> => {
|
|
146
|
+
const result = await shellArg.execSpawn(
|
|
147
|
+
"pnpm",
|
|
148
|
+
buildQualifiedPnpmArgs(capabilityArg, [...argsArg, "--json"]),
|
|
149
|
+
{
|
|
150
|
+
cwd: cwdArg,
|
|
151
|
+
silent: true,
|
|
152
|
+
timeout: capabilityCommandTimeoutMs,
|
|
153
|
+
timeoutKillGraceMs: 5_000,
|
|
154
|
+
maxBuffer: maximumMetadataResponseBytes,
|
|
155
|
+
},
|
|
156
|
+
);
|
|
157
|
+
if (result.exitCode !== 0 || result.timedOut) {
|
|
158
|
+
throw new Error(`Unable to inspect pnpm ${labelArg}.`);
|
|
159
|
+
}
|
|
160
|
+
if (result.stdout.trim() === "undefined") return undefined;
|
|
161
|
+
try {
|
|
162
|
+
return JSON.parse(result.stdout);
|
|
163
|
+
} catch {
|
|
164
|
+
throw new Error(`Invalid pnpm ${labelArg} response.`);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const assertPnpmReleaseAge = async (
|
|
169
|
+
shellArg: plugins.smartshell.Smartshell,
|
|
170
|
+
cwdArg: string,
|
|
171
|
+
capabilityArg: IPnpmReleaseCapability,
|
|
172
|
+
): Promise<void> => {
|
|
173
|
+
const configuredAge = await readPnpmJson(
|
|
174
|
+
shellArg,
|
|
175
|
+
cwdArg,
|
|
176
|
+
capabilityArg,
|
|
177
|
+
["config", "get", "minimum-release-age"],
|
|
178
|
+
"minimum release age",
|
|
179
|
+
);
|
|
180
|
+
const age =
|
|
181
|
+
configuredAge === undefined || configuredAge === null
|
|
182
|
+
? getDefaultMinimumReleaseAgeMinutes(capabilityArg.version)
|
|
183
|
+
: configuredAge;
|
|
184
|
+
if (
|
|
185
|
+
typeof age !== "number" ||
|
|
186
|
+
!Number.isSafeInteger(age) ||
|
|
187
|
+
age < 0 ||
|
|
188
|
+
age > Number.MAX_SAFE_INTEGER / 60_000
|
|
189
|
+
) {
|
|
190
|
+
throw new Error(
|
|
191
|
+
"pnpm minimum-release-age must be a nonnegative integer number of minutes.",
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
if (age === 0) return;
|
|
195
|
+
const configuredExclusions = await readPnpmJson(
|
|
196
|
+
shellArg,
|
|
197
|
+
cwdArg,
|
|
198
|
+
capabilityArg,
|
|
199
|
+
["config", "get", "minimum-release-age-exclude"],
|
|
200
|
+
"release-age exclusions",
|
|
173
201
|
);
|
|
174
|
-
|
|
202
|
+
const exclusions =
|
|
203
|
+
configuredExclusions === undefined || configuredExclusions === null
|
|
204
|
+
? []
|
|
205
|
+
: configuredExclusions;
|
|
206
|
+
if (
|
|
207
|
+
!Array.isArray(exclusions) ||
|
|
208
|
+
exclusions.some((value) => typeof value !== "string" || !value.trim())
|
|
209
|
+
) {
|
|
175
210
|
throw new Error(
|
|
176
|
-
|
|
211
|
+
"pnpm minimum-release-age-exclude must be an array of package selectors.",
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
if (
|
|
215
|
+
exclusions.some((selector) =>
|
|
216
|
+
packageSelectorMatches("pnpm", capabilityArg.version, selector),
|
|
217
|
+
)
|
|
218
|
+
)
|
|
219
|
+
return;
|
|
220
|
+
|
|
221
|
+
const times = await readPnpmJson(
|
|
222
|
+
shellArg,
|
|
223
|
+
cwdArg,
|
|
224
|
+
capabilityArg,
|
|
225
|
+
["view", `pnpm@${capabilityArg.version}`, "time"],
|
|
226
|
+
"publication times",
|
|
227
|
+
);
|
|
228
|
+
if (!times || typeof times !== "object" || Array.isArray(times)) {
|
|
229
|
+
throw new Error("pnpm publication times must be an object.");
|
|
230
|
+
}
|
|
231
|
+
const publishedAt = Object.hasOwn(times, capabilityArg.version)
|
|
232
|
+
? (times as Record<string, unknown>)[capabilityArg.version]
|
|
233
|
+
: undefined;
|
|
234
|
+
if (publishedAt === undefined) {
|
|
235
|
+
const ignoreMissingTime = await readPnpmJson(
|
|
236
|
+
shellArg,
|
|
237
|
+
cwdArg,
|
|
238
|
+
capabilityArg,
|
|
239
|
+
["config", "get", "minimum-release-age-ignore-missing-time"],
|
|
240
|
+
"missing publication time policy",
|
|
241
|
+
);
|
|
242
|
+
if (
|
|
243
|
+
ignoreMissingTime === true ||
|
|
244
|
+
ignoreMissingTime === undefined ||
|
|
245
|
+
ignoreMissingTime === null
|
|
246
|
+
)
|
|
247
|
+
return;
|
|
248
|
+
throw new Error(
|
|
249
|
+
`The registry has no publication time for pnpm ${capabilityArg.version}; its minimum release age cannot be verified.`,
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
const timestamp =
|
|
253
|
+
typeof publishedAt === "string" ? Date.parse(publishedAt) : NaN;
|
|
254
|
+
if (!Number.isFinite(timestamp))
|
|
255
|
+
throw new Error("Invalid pnpm publication timestamp.");
|
|
256
|
+
if (timestamp > Date.now() - age * 60_000) {
|
|
257
|
+
throw new Error(
|
|
258
|
+
`pnpm ${capabilityArg.version} does not yet satisfy minimum-release-age (${age} minutes).`,
|
|
177
259
|
);
|
|
178
260
|
}
|
|
179
261
|
};
|
|
@@ -189,16 +271,17 @@ export const assertPnpmReleaseCapability = async (
|
|
|
189
271
|
timeoutKillGraceMs: 5_000,
|
|
190
272
|
});
|
|
191
273
|
const version = versionResult.stdout.trim();
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
274
|
+
if (
|
|
275
|
+
versionResult.exitCode !== 0 ||
|
|
276
|
+
versionResult.timedOut ||
|
|
277
|
+
!stablePnpmVersion.test(version)
|
|
278
|
+
) {
|
|
196
279
|
throw new Error(
|
|
197
|
-
|
|
280
|
+
"Exact release packaging requires a canonical stable pnpm version.",
|
|
198
281
|
);
|
|
199
282
|
}
|
|
200
|
-
const
|
|
201
|
-
|
|
283
|
+
const capability = Object.freeze({ version });
|
|
284
|
+
await assertPnpmReleaseAge(smartshellArg, cwdArg, capability);
|
|
202
285
|
|
|
203
286
|
const packHelp = await smartshellArg.execSpawn(
|
|
204
287
|
"pnpm",
|
|
@@ -210,12 +293,12 @@ export const assertPnpmReleaseCapability = async (
|
|
|
210
293
|
timeoutKillGraceMs: 5_000,
|
|
211
294
|
},
|
|
212
295
|
);
|
|
213
|
-
if (packHelp.exitCode !== 0) {
|
|
296
|
+
if (packHelp.exitCode !== 0 || packHelp.timedOut) {
|
|
214
297
|
throw new Error(
|
|
215
298
|
"Unable to inspect the selected pnpm pack capabilities. Exact release commands require standalone pnpm with support; Corepack launchers are unsupported.",
|
|
216
299
|
);
|
|
217
300
|
}
|
|
218
|
-
requireHelpFragments(packHelp.combinedOutput,
|
|
301
|
+
requireHelpFragments(packHelp.combinedOutput, "pack");
|
|
219
302
|
|
|
220
303
|
const publishHelp = await smartshellArg.execSpawn(
|
|
221
304
|
"pnpm",
|
|
@@ -227,14 +310,10 @@ export const assertPnpmReleaseCapability = async (
|
|
|
227
310
|
timeoutKillGraceMs: 5_000,
|
|
228
311
|
},
|
|
229
312
|
);
|
|
230
|
-
if (publishHelp.exitCode !== 0) {
|
|
313
|
+
if (publishHelp.exitCode !== 0 || publishHelp.timedOut) {
|
|
231
314
|
throw new Error("Unable to inspect pnpm publish capabilities.");
|
|
232
315
|
}
|
|
233
|
-
requireHelpFragments(
|
|
234
|
-
publishHelp.combinedOutput,
|
|
235
|
-
requirements.publish,
|
|
236
|
-
"publish",
|
|
237
|
-
);
|
|
316
|
+
requireHelpFragments(publishHelp.combinedOutput, "publish");
|
|
238
317
|
return capability;
|
|
239
318
|
};
|
|
240
319
|
|
|
@@ -7,12 +7,20 @@ import {
|
|
|
7
7
|
getReleaseDockerPromotionContext,
|
|
8
8
|
getReleaseArtifacts,
|
|
9
9
|
getReleaseNpmArtifact,
|
|
10
|
+
hasReleasePackages,
|
|
10
11
|
type TReleaseJournal,
|
|
11
12
|
type IReleaseJournalV2,
|
|
13
|
+
type IReleaseJournalV4,
|
|
12
14
|
type IReleaseTargetStatus,
|
|
13
15
|
type TReleaseErrorCode,
|
|
14
16
|
type TReleaseTargetState,
|
|
15
17
|
} from "./classes.releasejournal.js";
|
|
18
|
+
import {
|
|
19
|
+
GiteaAssetsClient,
|
|
20
|
+
GiteaAssetConflictError,
|
|
21
|
+
validateGiteaAssets,
|
|
22
|
+
} from "./helpers.giteaassets.js";
|
|
23
|
+
import { assertGiteaAssetsGitDestination } from "../helpers.workflow.js";
|
|
16
24
|
import {
|
|
17
25
|
probeAnonymousNpmArtifact,
|
|
18
26
|
publishNpmArtifact,
|
|
@@ -75,6 +83,8 @@ export function buildReleaseGitPushArgs(
|
|
|
75
83
|
|
|
76
84
|
type TReleaseTargetSelector =
|
|
77
85
|
| { kind: "git" }
|
|
86
|
+
| { kind: "gitea-release" }
|
|
87
|
+
| { kind: "gitea-asset"; name: string }
|
|
78
88
|
| { kind: "npm"; registry: string; packageName?: string }
|
|
79
89
|
| { kind: "docker-qualification" }
|
|
80
90
|
| { kind: "docker-promotion"; promotionId: string }
|
|
@@ -96,6 +106,15 @@ export interface IReleasePublicationOptions {
|
|
|
96
106
|
recoveryAttemptId?: string;
|
|
97
107
|
npmProbeOptions?: INpmArtifactProbeOptions;
|
|
98
108
|
tsdocker?: ITsdockerReleaseClient;
|
|
109
|
+
giteaAssets?: Pick<
|
|
110
|
+
GiteaAssetsClient,
|
|
111
|
+
| "preflight"
|
|
112
|
+
| "inspectRelease"
|
|
113
|
+
| "createRelease"
|
|
114
|
+
| "inspectAssets"
|
|
115
|
+
| "uploadAsset"
|
|
116
|
+
| "verifyComplete"
|
|
117
|
+
>;
|
|
99
118
|
ensurePublicationState: (expectedRemoteMainOidArg?: string) => Promise<void>;
|
|
100
119
|
}
|
|
101
120
|
|
|
@@ -106,6 +125,20 @@ const getTargetStatus = (
|
|
|
106
125
|
if (selectorArg.kind === "git") {
|
|
107
126
|
return journalArg.git;
|
|
108
127
|
}
|
|
128
|
+
if (
|
|
129
|
+
selectorArg.kind === "gitea-release" ||
|
|
130
|
+
selectorArg.kind === "gitea-asset"
|
|
131
|
+
) {
|
|
132
|
+
if (journalArg.schemaVersion !== 4)
|
|
133
|
+
throw new Error("Release journal has no Gitea assets.");
|
|
134
|
+
if (selectorArg.kind === "gitea-release")
|
|
135
|
+
return journalArg.giteaAssets.release;
|
|
136
|
+
const entry = journalArg.giteaAssets.assets.find(
|
|
137
|
+
(entry) => entry.asset.name === selectorArg.name,
|
|
138
|
+
);
|
|
139
|
+
if (!entry) throw new Error("Release journal has no matching Gitea asset.");
|
|
140
|
+
return entry;
|
|
141
|
+
}
|
|
109
142
|
if (selectorArg.kind === "docker-qualification") {
|
|
110
143
|
if (journalArg.schemaVersion !== 2) {
|
|
111
144
|
throw new Error("Release journal does not contain Docker qualification.");
|
|
@@ -616,7 +649,7 @@ const executeNpmRegistry = async (
|
|
|
616
649
|
// the accepted artifact on resume. Older root/Docker journal behavior stays
|
|
617
650
|
// unchanged.
|
|
618
651
|
if (
|
|
619
|
-
journal
|
|
652
|
+
hasReleasePackages(journal) &&
|
|
620
653
|
status.state === "failed" &&
|
|
621
654
|
status.attempts > 0 &&
|
|
622
655
|
status.error === "verification-inconclusive" &&
|
|
@@ -692,7 +725,7 @@ const executeNpmRegistry = async (
|
|
|
692
725
|
}
|
|
693
726
|
if (probe.status !== "absent") {
|
|
694
727
|
if (
|
|
695
|
-
journal
|
|
728
|
+
hasReleasePackages(journal) &&
|
|
696
729
|
status.state === "failed" &&
|
|
697
730
|
probe.status === "inconclusive"
|
|
698
731
|
) {
|
|
@@ -1432,6 +1465,165 @@ const executeDockerPromotion = async (
|
|
|
1432
1465
|
);
|
|
1433
1466
|
};
|
|
1434
1467
|
|
|
1468
|
+
const executeGiteaTarget = async (
|
|
1469
|
+
options: IReleasePublicationOptions,
|
|
1470
|
+
journalArg: TReleaseJournal,
|
|
1471
|
+
selector: TReleaseTargetSelector,
|
|
1472
|
+
inspect: () => Promise<boolean>,
|
|
1473
|
+
publish: () => Promise<void>,
|
|
1474
|
+
onVerified?: (target: IReleaseTargetStatus) => void,
|
|
1475
|
+
): Promise<TReleaseJournal> => {
|
|
1476
|
+
let journal = journalArg;
|
|
1477
|
+
let status = getTargetStatus(journal, selector);
|
|
1478
|
+
if (status.state === "conflict")
|
|
1479
|
+
throw new Error("Gitea publication has a recorded destination conflict.");
|
|
1480
|
+
const mark = async (
|
|
1481
|
+
state: "verified" | "failed" | "conflict",
|
|
1482
|
+
error: TReleaseErrorCode | null,
|
|
1483
|
+
) => {
|
|
1484
|
+
status = getTargetStatus(journal, selector);
|
|
1485
|
+
journal = await updateTarget(
|
|
1486
|
+
options.store,
|
|
1487
|
+
journal,
|
|
1488
|
+
selector,
|
|
1489
|
+
status.state,
|
|
1490
|
+
status.attempt?.id || null,
|
|
1491
|
+
(target) => {
|
|
1492
|
+
target.state = state;
|
|
1493
|
+
target.error = error;
|
|
1494
|
+
target.attempt = null;
|
|
1495
|
+
if (state === "verified") onVerified?.(target);
|
|
1496
|
+
},
|
|
1497
|
+
);
|
|
1498
|
+
};
|
|
1499
|
+
let present: boolean;
|
|
1500
|
+
try {
|
|
1501
|
+
present = await inspect();
|
|
1502
|
+
} catch (error) {
|
|
1503
|
+
if (error instanceof GiteaAssetConflictError)
|
|
1504
|
+
await mark("conflict", "destination-conflict");
|
|
1505
|
+
throw error;
|
|
1506
|
+
}
|
|
1507
|
+
if (present) {
|
|
1508
|
+
if (status.state !== "verified") await mark("verified", null);
|
|
1509
|
+
return journal;
|
|
1510
|
+
}
|
|
1511
|
+
if (status.state === "verified") {
|
|
1512
|
+
await mark("conflict", "destination-conflict");
|
|
1513
|
+
throw new Error(
|
|
1514
|
+
"A previously verified Gitea release or attachment is missing.",
|
|
1515
|
+
);
|
|
1516
|
+
}
|
|
1517
|
+
if (status.state === "publishing") {
|
|
1518
|
+
journal = await recoverPublishingTarget(
|
|
1519
|
+
options.store,
|
|
1520
|
+
journal,
|
|
1521
|
+
selector,
|
|
1522
|
+
options.recoveryAttemptId,
|
|
1523
|
+
);
|
|
1524
|
+
}
|
|
1525
|
+
await options.ensurePublicationState(journal.release.mainOid);
|
|
1526
|
+
journal = await claimTarget(options.store, journal, selector);
|
|
1527
|
+
// One mutation per claimed attempt. Reconcile an accepted request even when
|
|
1528
|
+
// its response was lost; never overwrite an attachment or recreate a tag.
|
|
1529
|
+
let publicationError: unknown;
|
|
1530
|
+
try {
|
|
1531
|
+
await publish();
|
|
1532
|
+
} catch (error) {
|
|
1533
|
+
publicationError = error;
|
|
1534
|
+
}
|
|
1535
|
+
try {
|
|
1536
|
+
present = await inspect();
|
|
1537
|
+
} catch (error) {
|
|
1538
|
+
await mark(
|
|
1539
|
+
error instanceof GiteaAssetConflictError ? "conflict" : "failed",
|
|
1540
|
+
error instanceof GiteaAssetConflictError
|
|
1541
|
+
? "destination-conflict"
|
|
1542
|
+
: "verification-inconclusive",
|
|
1543
|
+
);
|
|
1544
|
+
throw error;
|
|
1545
|
+
}
|
|
1546
|
+
if (present) {
|
|
1547
|
+
await mark("verified", null);
|
|
1548
|
+
return journal;
|
|
1549
|
+
}
|
|
1550
|
+
await mark(
|
|
1551
|
+
publicationError instanceof GiteaAssetConflictError ? "conflict" : "failed",
|
|
1552
|
+
publicationError instanceof GiteaAssetConflictError
|
|
1553
|
+
? "destination-conflict"
|
|
1554
|
+
: publicationError
|
|
1555
|
+
? "command-failed"
|
|
1556
|
+
: "verification-inconclusive",
|
|
1557
|
+
);
|
|
1558
|
+
throw new Error(
|
|
1559
|
+
"Gitea publication could not be verified; npm publication remains blocked. Inspect the retained journal before resuming.",
|
|
1560
|
+
);
|
|
1561
|
+
};
|
|
1562
|
+
|
|
1563
|
+
const executeGiteaAssets = async (
|
|
1564
|
+
options: IReleasePublicationOptions,
|
|
1565
|
+
journalArg: IReleaseJournalV4,
|
|
1566
|
+
client: NonNullable<IReleasePublicationOptions["giteaAssets"]>,
|
|
1567
|
+
): Promise<IReleaseJournalV4> => {
|
|
1568
|
+
let releaseId = journalArg.giteaAssets.release.id;
|
|
1569
|
+
let journal = (await executeGiteaTarget(
|
|
1570
|
+
options,
|
|
1571
|
+
journalArg,
|
|
1572
|
+
{ kind: "gitea-release" },
|
|
1573
|
+
async () => {
|
|
1574
|
+
const release = await client.inspectRelease();
|
|
1575
|
+
if (!release) return false;
|
|
1576
|
+
if (releaseId !== null && release.id !== releaseId)
|
|
1577
|
+
throw new GiteaAssetConflictError("Gitea release identity changed.");
|
|
1578
|
+
releaseId = release.id;
|
|
1579
|
+
return true;
|
|
1580
|
+
},
|
|
1581
|
+
async () => {
|
|
1582
|
+
await client.createRelease();
|
|
1583
|
+
},
|
|
1584
|
+
(target) => {
|
|
1585
|
+
(target as IReleaseJournalV4["giteaAssets"]["release"]).id = releaseId;
|
|
1586
|
+
},
|
|
1587
|
+
)) as IReleaseJournalV4;
|
|
1588
|
+
if (releaseId === null)
|
|
1589
|
+
throw new Error("Gitea release has no verified identity.");
|
|
1590
|
+
const expected = journal.giteaAssets.assets.map((entry) => entry.asset);
|
|
1591
|
+
const directory = options.store.getReleaseDirectory(journal.release.version);
|
|
1592
|
+
for (const { asset } of journal.giteaAssets.assets) {
|
|
1593
|
+
journal = (await executeGiteaTarget(
|
|
1594
|
+
options,
|
|
1595
|
+
journal,
|
|
1596
|
+
{ kind: "gitea-asset", name: asset.name },
|
|
1597
|
+
async () => {
|
|
1598
|
+
const inspection = await client.inspectAssets(releaseId!, expected);
|
|
1599
|
+
return inspection.present.some((entry) => entry.name === asset.name);
|
|
1600
|
+
},
|
|
1601
|
+
async () => {
|
|
1602
|
+
await validateGiteaAssets(directory, expected);
|
|
1603
|
+
await client.uploadAsset(releaseId!, asset, directory);
|
|
1604
|
+
},
|
|
1605
|
+
)) as IReleaseJournalV4;
|
|
1606
|
+
}
|
|
1607
|
+
// One full anonymous verification immediately before entering the npm phase.
|
|
1608
|
+
// Resume always repeats it, including an otherwise completed journal.
|
|
1609
|
+
try {
|
|
1610
|
+
await client.verifyComplete(releaseId, expected);
|
|
1611
|
+
} catch (error) {
|
|
1612
|
+
if (error instanceof GiteaAssetConflictError) {
|
|
1613
|
+
await markUnclaimedTarget(
|
|
1614
|
+
options.store,
|
|
1615
|
+
journal,
|
|
1616
|
+
{ kind: "gitea-release" },
|
|
1617
|
+
["verified"],
|
|
1618
|
+
"conflict",
|
|
1619
|
+
"destination-conflict",
|
|
1620
|
+
);
|
|
1621
|
+
}
|
|
1622
|
+
throw error;
|
|
1623
|
+
}
|
|
1624
|
+
return journal;
|
|
1625
|
+
};
|
|
1626
|
+
|
|
1435
1627
|
export const executeReleasePublication = async (
|
|
1436
1628
|
optionsArg: IReleasePublicationOptions,
|
|
1437
1629
|
): Promise<TReleaseJournal> => {
|
|
@@ -1443,6 +1635,32 @@ export const executeReleasePublication = async (
|
|
|
1443
1635
|
artifact,
|
|
1444
1636
|
);
|
|
1445
1637
|
}
|
|
1638
|
+
let giteaClient = optionsArg.giteaAssets;
|
|
1639
|
+
if (journal.schemaVersion === 4) {
|
|
1640
|
+
const { destination, tokenEnv, manifestPath, assets } = journal.giteaAssets;
|
|
1641
|
+
assertGiteaAssetsGitDestination(
|
|
1642
|
+
{ ...destination, tokenEnv, manifestPath },
|
|
1643
|
+
optionsArg.pushUrl || "",
|
|
1644
|
+
);
|
|
1645
|
+
await validateGiteaAssets(
|
|
1646
|
+
optionsArg.store.getReleaseDirectory(journal.release.version),
|
|
1647
|
+
assets.map((entry) => entry.asset),
|
|
1648
|
+
);
|
|
1649
|
+
if (!giteaClient) {
|
|
1650
|
+
const token = process.env[tokenEnv];
|
|
1651
|
+
if (!token)
|
|
1652
|
+
throw new Error(
|
|
1653
|
+
`Gitea assets require the configured credential in ${tokenEnv}.`,
|
|
1654
|
+
);
|
|
1655
|
+
giteaClient = new GiteaAssetsClient({
|
|
1656
|
+
destination,
|
|
1657
|
+
token,
|
|
1658
|
+
tag: journal.release.tag,
|
|
1659
|
+
commit: journal.release.mainOid,
|
|
1660
|
+
});
|
|
1661
|
+
}
|
|
1662
|
+
await giteaClient.preflight();
|
|
1663
|
+
}
|
|
1446
1664
|
if (journal.schemaVersion === 2) {
|
|
1447
1665
|
journal = await executeDockerQualification(optionsArg, journal);
|
|
1448
1666
|
if (
|
|
@@ -1453,6 +1671,8 @@ export const executeReleasePublication = async (
|
|
|
1453
1671
|
}
|
|
1454
1672
|
}
|
|
1455
1673
|
journal = await executeGitTarget(optionsArg, journal);
|
|
1674
|
+
if (journal.schemaVersion === 4)
|
|
1675
|
+
journal = await executeGiteaAssets(optionsArg, journal, giteaClient!);
|
|
1456
1676
|
const expectedRemoteMainOid =
|
|
1457
1677
|
journal.git.state === "verified"
|
|
1458
1678
|
? journal.release.mainOid
|
|
@@ -1464,12 +1684,12 @@ export const executeReleasePublication = async (
|
|
|
1464
1684
|
journal,
|
|
1465
1685
|
registry.registry,
|
|
1466
1686
|
registry.packageName,
|
|
1467
|
-
journal
|
|
1687
|
+
hasReleasePackages(journal),
|
|
1468
1688
|
);
|
|
1469
1689
|
}
|
|
1470
1690
|
// Submit in dependency order before waiting for npm's publish-time scans.
|
|
1471
1691
|
// Success still requires every package/registry cell to verify exactly.
|
|
1472
|
-
if (journal
|
|
1692
|
+
if (hasReleasePackages(journal)) {
|
|
1473
1693
|
for (const registry of journal.npm.registries) {
|
|
1474
1694
|
if (
|
|
1475
1695
|
registry.state !== "failed" ||
|