@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.
Files changed (29) hide show
  1. package/dist_ts/00_commitinfo_data.js +2 -2
  2. package/dist_ts/helpers.workflow.d.ts +23 -1
  3. package/dist_ts/helpers.workflow.js +93 -3
  4. package/dist_ts/mod_config/index.js +49 -7
  5. package/dist_ts/mod_release/classes.releasejournal.d.ts +20 -1
  6. package/dist_ts/mod_release/classes.releasejournal.js +191 -9
  7. package/dist_ts/mod_release/helpers.giteaassets.d.ts +36 -0
  8. package/dist_ts/mod_release/helpers.giteaassets.js +661 -0
  9. package/dist_ts/mod_release/helpers.npmartifact.d.ts +1 -3
  10. package/dist_ts/mod_release/helpers.npmartifact.js +88 -66
  11. package/dist_ts/mod_release/helpers.releasepublication.d.ts +2 -0
  12. package/dist_ts/mod_release/helpers.releasepublication.js +147 -6
  13. package/dist_ts/mod_release/index.js +86 -11
  14. package/dist_ts/mod_release/interfaces.giteaassets.d.ts +50 -0
  15. package/dist_ts/mod_release/interfaces.giteaassets.js +2 -0
  16. package/dist_ts/mod_tools/classes.packagemanager.d.ts +2 -0
  17. package/dist_ts/mod_tools/classes.packagemanager.js +3 -3
  18. package/package.json +3 -3
  19. package/readme.md +94 -5
  20. package/ts/00_commitinfo_data.ts +1 -1
  21. package/ts/helpers.workflow.ts +154 -3
  22. package/ts/mod_config/index.ts +61 -6
  23. package/ts/mod_release/classes.releasejournal.ts +298 -14
  24. package/ts/mod_release/helpers.giteaassets.ts +932 -0
  25. package/ts/mod_release/helpers.npmartifact.ts +156 -77
  26. package/ts/mod_release/helpers.releasepublication.ts +224 -4
  27. package/ts/mod_release/index.ts +111 -8
  28. package/ts/mod_release/interfaces.giteaassets.ts +67 -0
  29. package/ts/mod_tools/classes.packagemanager.ts +2 -2
@@ -19,7 +19,10 @@ import {
19
19
  CURRENT_GITZONE_CLI_SCHEMA_VERSION,
20
20
  migrateSmartconfigData,
21
21
  } from "../helpers.smartconfigmigrations.js";
22
- import { getDeclaredTsdockerRange } from "../helpers.workflow.js";
22
+ import {
23
+ getDeclaredTsdockerRange,
24
+ resolveGiteaAssetsConfiguration,
25
+ } from "../helpers.workflow.js";
23
26
  import { hasInstalledProjectTsdockerBinary } from "../helpers.tsdocker.js";
24
27
  import { TsdockerReleaseClient } from "../mod_release/helpers.tsdockerprotocol.js";
25
28
  import { getSealedReleaseConfigError } from "../helpers.sealedrelease.js";
@@ -304,6 +307,10 @@ async function handleShow(mode: ICliMode): Promise<void> {
304
307
 
305
308
  printConfigSection("Release Targets", [
306
309
  ["git", formatTarget(targets.git?.enabled, targets.git)],
310
+ [
311
+ "giteaAssets",
312
+ formatTarget(targets.giteaAssets?.enabled, targets.giteaAssets),
313
+ ],
307
314
  ["npm", formatTarget(targets.npm?.enabled, targets.npm)],
308
315
  ["docker", formatTarget(targets.docker?.enabled, targets.docker)],
309
316
  ]);
@@ -1800,6 +1807,37 @@ async function validateReleaseConfig(
1800
1807
 
1801
1808
  const targets = releaseConfig.targets || {};
1802
1809
  await validateGitTarget(targets.git || {}, findings);
1810
+ if (
1811
+ targets.giteaAssets?.enabled !== undefined &&
1812
+ typeof targets.giteaAssets.enabled !== "boolean"
1813
+ ) {
1814
+ findings.push({
1815
+ level: "error",
1816
+ message: "Gitea assets enabled must be true or false",
1817
+ });
1818
+ }
1819
+ if (targets.giteaAssets?.enabled === true) {
1820
+ try {
1821
+ resolveGiteaAssetsConfiguration(targets.giteaAssets);
1822
+ if (targets.git?.enabled === false || targets.docker?.enabled === true)
1823
+ throw new Error(
1824
+ "Gitea assets require Git publication and cannot combine Docker.",
1825
+ );
1826
+ findings.push({
1827
+ level: "ok",
1828
+ message:
1829
+ "Gitea assets configured; release verifies Git destination and publisher capability before publication",
1830
+ });
1831
+ } catch (error) {
1832
+ findings.push({
1833
+ level: "error",
1834
+ message:
1835
+ error instanceof Error
1836
+ ? error.message
1837
+ : "Invalid Gitea asset configuration",
1838
+ });
1839
+ }
1840
+ }
1803
1841
  await validateNpmTarget(targets.npm || {}, findings);
1804
1842
  await validateDockerTarget(targets.docker || {}, smartconfigData, findings);
1805
1843
  }
@@ -1830,9 +1868,19 @@ async function validateAssetsConfig(
1830
1868
  }
1831
1869
  if (rawAssetsConfig.kind === "tspackRelease") {
1832
1870
  const error = getSealedReleaseConfigError(rawAssetsConfig.sealedRelease);
1833
- findings.push(error ? {
1834
- level: "error", message: error, fix: "Set @git.zone/cli.assets.sealedRelease using the documented TsPack release configuration.",
1835
- } : { level: "ok", message: "Managed tspackRelease assets have valid component commands and retention output" });
1871
+ findings.push(
1872
+ error
1873
+ ? {
1874
+ level: "error",
1875
+ message: error,
1876
+ fix: "Set @git.zone/cli.assets.sealedRelease using the documented TsPack release configuration.",
1877
+ }
1878
+ : {
1879
+ level: "ok",
1880
+ message:
1881
+ "Managed tspackRelease assets have valid component commands and retention output",
1882
+ },
1883
+ );
1836
1884
  return;
1837
1885
  }
1838
1886
  if (rawAssetsConfig.kind !== "denoBinaryCli") {
@@ -2001,8 +2049,15 @@ async function validateNpmTarget(
2001
2049
  fix: "Use success or error.",
2002
2050
  });
2003
2051
  }
2004
- if (npmTarget.packageSource !== undefined && !["root", "tspublish"].includes(npmTarget.packageSource)) {
2005
- findings.push({ level: "error", message: `Invalid npm package source: ${npmTarget.packageSource}`, fix: "Use root or tspublish." });
2052
+ if (
2053
+ npmTarget.packageSource !== undefined &&
2054
+ !["root", "tspublish"].includes(npmTarget.packageSource)
2055
+ ) {
2056
+ findings.push({
2057
+ level: "error",
2058
+ message: `Invalid npm package source: ${npmTarget.packageSource}`,
2059
+ fix: "Use root or tspublish.",
2060
+ });
2006
2061
  }
2007
2062
 
2008
2063
  const smartNetwork = new plugins.smartnetwork.SmartNetwork();
@@ -1,5 +1,12 @@
1
1
  import * as plugins from "./mod.plugins.js";
2
2
  import { InterProcessLock } from "../mod_services/classes.interprocesslock.js";
3
+ import { resolveGiteaAssetsConfiguration } from "../helpers.workflow.js";
4
+ import {
5
+ assertGiteaFrozenAssets,
6
+ validateGiteaAssets,
7
+ type IGiteaAssetDestination,
8
+ type IGiteaFrozenAsset,
9
+ } from "./helpers.giteaassets.js";
3
10
  import {
4
11
  assertReleaseCleanupResult,
5
12
  assertReleasePromotionEvidence,
@@ -151,8 +158,35 @@ export interface IReleaseJournalV3 extends Omit<
151
158
  packages: IReleasePackage[];
152
159
  }
153
160
 
161
+ export interface IReleaseGiteaAssetJournal extends IReleaseTargetStatus {
162
+ asset: IGiteaFrozenAsset;
163
+ }
164
+
165
+ export interface IReleaseGiteaAssetsJournal {
166
+ destination: IGiteaAssetDestination;
167
+ tokenEnv: string;
168
+ manifestPath: string;
169
+ release: IReleaseTargetStatus & { id: number | null };
170
+ assets: IReleaseGiteaAssetJournal[];
171
+ }
172
+
173
+ export interface IReleaseJournalV4 extends Omit<
174
+ IReleaseJournalV1,
175
+ "schemaVersion"
176
+ > {
177
+ schemaVersion: 4;
178
+ packages: IReleasePackage[];
179
+ giteaAssets: IReleaseGiteaAssetsJournal;
180
+ }
181
+
154
182
  export type TReleaseJournal =
155
- IReleaseJournalV1 | IReleaseJournalV2 | IReleaseJournalV3;
183
+ IReleaseJournalV1 | IReleaseJournalV2 | IReleaseJournalV3 | IReleaseJournalV4;
184
+
185
+ export const hasReleasePackages = (
186
+ journalArg: TReleaseJournal,
187
+ ): journalArg is IReleaseJournalV3 | IReleaseJournalV4 =>
188
+ journalArg.schemaVersion === 3 ||
189
+ (journalArg.schemaVersion === 4 && journalArg.packages.length > 0);
156
190
 
157
191
  export const releasePackageArtifactFile = (packageNameArg: string): string =>
158
192
  `package.${plugins.crypto.createHash("sha256").update(packageNameArg).digest("hex")}.tgz`;
@@ -160,7 +194,7 @@ export const releasePackageArtifactFile = (packageNameArg: string): string =>
160
194
  export const getReleaseArtifacts = (
161
195
  journalArg: TReleaseJournal,
162
196
  ): IReleaseArtifact[] =>
163
- journalArg.schemaVersion === 3
197
+ hasReleasePackages(journalArg)
164
198
  ? journalArg.packages.map((entryArg) => entryArg.artifact)
165
199
  : journalArg.artifact
166
200
  ? [journalArg.artifact]
@@ -170,12 +204,11 @@ export const getReleaseNpmArtifact = (
170
204
  journalArg: TReleaseJournal,
171
205
  packageNameArg?: string,
172
206
  ): IReleaseArtifact => {
173
- const artifact =
174
- journalArg.schemaVersion === 3
175
- ? journalArg.packages.find(
176
- (entryArg) => entryArg.artifact.packageName === packageNameArg,
177
- )?.artifact
178
- : journalArg.artifact;
207
+ const artifact = hasReleasePackages(journalArg)
208
+ ? journalArg.packages.find(
209
+ (entryArg) => entryArg.artifact.packageName === packageNameArg,
210
+ )?.artifact
211
+ : journalArg.artifact;
179
212
  if (!artifact) throw new Error("npm release target has no exact artifact.");
180
213
  return artifact;
181
214
  };
@@ -396,6 +429,12 @@ const isComplete = (journalArg: TReleaseJournal): boolean => {
396
429
  const commonComplete = [
397
430
  journalArg.git.state,
398
431
  ...journalArg.npm.registries.map((registryArg) => registryArg.state),
432
+ ...(journalArg.schemaVersion === 4
433
+ ? [
434
+ journalArg.giteaAssets.release.state,
435
+ ...journalArg.giteaAssets.assets.map((entry) => entry.state),
436
+ ]
437
+ : []),
399
438
  ].every((stateArg) => stateArg === "verified" || stateArg === "skipped");
400
439
  if (
401
440
  !commonComplete ||
@@ -418,7 +457,7 @@ const immutableJournalIdentity = (journalArg: TReleaseJournal): string =>
418
457
  schemaVersion: journalArg.schemaVersion,
419
458
  release: journalArg.release,
420
459
  artifact: journalArg.artifact,
421
- ...(journalArg.schemaVersion === 3
460
+ ...(hasReleasePackages(journalArg)
422
461
  ? { packages: journalArg.packages }
423
462
  : {}),
424
463
  git: {
@@ -431,11 +470,20 @@ const immutableJournalIdentity = (journalArg: TReleaseJournal): string =>
431
470
  tag: journalArg.npm.tag,
432
471
  alreadyPublished: journalArg.npm.alreadyPublished,
433
472
  registries: journalArg.npm.registries.map((registryArg) =>
434
- journalArg.schemaVersion === 3
473
+ hasReleasePackages(journalArg)
435
474
  ? [registryArg.packageName, registryArg.registry]
436
475
  : registryArg.registry,
437
476
  ),
438
477
  },
478
+ giteaAssets:
479
+ journalArg.schemaVersion === 4
480
+ ? {
481
+ destination: journalArg.giteaAssets.destination,
482
+ tokenEnv: journalArg.giteaAssets.tokenEnv,
483
+ manifestPath: journalArg.giteaAssets.manifestPath,
484
+ assets: journalArg.giteaAssets.assets.map((entry) => entry.asset),
485
+ }
486
+ : undefined,
439
487
  docker:
440
488
  journalArg.schemaVersion === releaseJournalSchemaVersion
441
489
  ? {
@@ -1174,6 +1222,160 @@ const assertReleaseJournalV3 = (
1174
1222
  return journal;
1175
1223
  };
1176
1224
 
1225
+ const assertReleaseJournalV4 = (
1226
+ valueArg: Record<string, unknown>,
1227
+ ): IReleaseJournalV4 => {
1228
+ assertExactKeys(
1229
+ valueArg,
1230
+ [
1231
+ "kind",
1232
+ "schemaVersion",
1233
+ "revision",
1234
+ "release",
1235
+ "artifact",
1236
+ "packages",
1237
+ "git",
1238
+ "npm",
1239
+ "giteaAssets",
1240
+ "createdAt",
1241
+ "updatedAt",
1242
+ "completedAt",
1243
+ ],
1244
+ "Release journal",
1245
+ );
1246
+ if (
1247
+ !Array.isArray(valueArg.packages) ||
1248
+ !isPlainObject(valueArg.giteaAssets) ||
1249
+ !isPlainObject(valueArg.git) ||
1250
+ !isPlainObject(valueArg.npm) ||
1251
+ !Array.isArray(valueArg.npm.registries)
1252
+ ) {
1253
+ throw new Error("Schema 4 requires an exact Gitea asset publication set.");
1254
+ }
1255
+ const assets = valueArg.giteaAssets;
1256
+ assertExactKeys(
1257
+ assets,
1258
+ ["destination", "tokenEnv", "manifestPath", "release", "assets"],
1259
+ "Gitea assets",
1260
+ );
1261
+ if (
1262
+ !isPlainObject(assets.destination) ||
1263
+ !isPlainObject(assets.release) ||
1264
+ !Array.isArray(assets.assets)
1265
+ )
1266
+ throw new Error("Invalid Gitea asset publication intent.");
1267
+ assertExactKeys(
1268
+ assets.destination,
1269
+ ["apiOrigin", "owner", "repository"],
1270
+ "Gitea destination",
1271
+ );
1272
+ resolveGiteaAssetsConfiguration({
1273
+ ...assets.destination,
1274
+ tokenEnv: assets.tokenEnv,
1275
+ manifestPath: assets.manifestPath,
1276
+ } as Parameters<typeof resolveGiteaAssetsConfiguration>[0]);
1277
+ assertExactKeys(
1278
+ assets.release,
1279
+ ["state", "attempts", "attempt", "error", "id"],
1280
+ "Gitea release target",
1281
+ );
1282
+ assertTargetStatus(assets.release, "Gitea release target");
1283
+ if (
1284
+ assets.release.state === "skipped" ||
1285
+ (assets.release.id !== null &&
1286
+ (!Number.isSafeInteger(assets.release.id) ||
1287
+ (assets.release.id as number) <= 0)) ||
1288
+ (assets.release.state === "verified" && assets.release.id === null)
1289
+ ) {
1290
+ throw new Error("Invalid Gitea release identity or status.");
1291
+ }
1292
+ const frozen: unknown[] = [];
1293
+ for (const entry of assets.assets) {
1294
+ if (!isPlainObject(entry)) throw new Error("Invalid Gitea asset target.");
1295
+ assertExactKeys(
1296
+ entry,
1297
+ ["state", "attempts", "attempt", "error", "asset"],
1298
+ "Gitea asset target",
1299
+ );
1300
+ assertTargetStatus(entry, "Gitea asset target");
1301
+ if (entry.state === "skipped")
1302
+ throw new Error("Gitea asset targets cannot be skipped.");
1303
+ frozen.push(entry.asset);
1304
+ }
1305
+ assertGiteaFrozenAssets(
1306
+ frozen,
1307
+ assets.destination as unknown as IGiteaAssetDestination,
1308
+ (valueArg.release as IReleaseJournalV1["release"])?.tag,
1309
+ );
1310
+ const commonComplete = [valueArg.git, ...valueArg.npm.registries].every(
1311
+ (entry) => entry?.state === "verified" || entry?.state === "skipped",
1312
+ );
1313
+ const common = {
1314
+ kind: valueArg.kind,
1315
+ schemaVersion: valueArg.packages.length ? 3 : 1,
1316
+ revision: valueArg.revision,
1317
+ release: valueArg.release,
1318
+ artifact: valueArg.artifact,
1319
+ ...(valueArg.packages.length ? { packages: valueArg.packages } : {}),
1320
+ git: valueArg.git,
1321
+ npm: valueArg.npm,
1322
+ createdAt: valueArg.createdAt,
1323
+ updatedAt: valueArg.updatedAt,
1324
+ completedAt: commonComplete ? valueArg.updatedAt : null,
1325
+ };
1326
+ if (valueArg.packages.length) assertReleaseJournalV3(common);
1327
+ else assertReleaseJournalV1(common);
1328
+ const journal = valueArg as unknown as IReleaseJournalV4;
1329
+ const statuses = [
1330
+ journal.git,
1331
+ journal.giteaAssets.release,
1332
+ ...journal.giteaAssets.assets,
1333
+ ...journal.npm.registries,
1334
+ ];
1335
+ if (
1336
+ journal.git.state === "skipped" ||
1337
+ statuses.filter((entry) => entry.state === "publishing").length > 1
1338
+ ) {
1339
+ throw new Error(
1340
+ "Gitea assets require an active Git target and one publication owner.",
1341
+ );
1342
+ }
1343
+ const assetStates = journal.giteaAssets.assets;
1344
+ const npmStarted = journal.npm.registries.some(
1345
+ (entry) => entry.state !== "pending",
1346
+ );
1347
+ if (
1348
+ (journal.git.state !== "verified" &&
1349
+ journal.git.state !== "conflict" &&
1350
+ statuses.slice(1).some((entry) => entry.state !== "pending")) ||
1351
+ (journal.giteaAssets.release.state !== "verified" &&
1352
+ journal.giteaAssets.release.state !== "conflict" &&
1353
+ assetStates.some((entry) => entry.state !== "pending")) ||
1354
+ (npmStarted &&
1355
+ (journal.giteaAssets.release.id === null ||
1356
+ assetStates.some(
1357
+ (entry) => !["verified", "conflict"].includes(entry.state),
1358
+ )))
1359
+ )
1360
+ throw new Error(
1361
+ "Release targets violate Git, Gitea assets, npm publication order.",
1362
+ );
1363
+ if (journal.completedAt !== null) {
1364
+ const completed = assertIsoTimestamp(
1365
+ journal.completedAt,
1366
+ "Release journal completedAt",
1367
+ );
1368
+ if (
1369
+ !isComplete(journal) ||
1370
+ completed < journal.createdAt ||
1371
+ completed > journal.updatedAt
1372
+ )
1373
+ throw new Error("Release journal completion state is inconsistent.");
1374
+ } else if (isComplete(journal))
1375
+ throw new Error("Complete release journal must record completedAt.");
1376
+ return journal;
1377
+ };
1378
+
1177
1379
  export const assertReleaseJournal = (valueArg: unknown): TReleaseJournal => {
1178
1380
  if (!isPlainObject(valueArg)) {
1179
1381
  throw new Error("Release journal root must be a JSON object.");
@@ -1185,6 +1387,7 @@ export const assertReleaseJournal = (valueArg: unknown): TReleaseJournal => {
1185
1387
  return assertReleaseJournalV2(valueArg);
1186
1388
  }
1187
1389
  if (valueArg.schemaVersion === 3) return assertReleaseJournalV3(valueArg);
1390
+ if (valueArg.schemaVersion === 4) return assertReleaseJournalV4(valueArg);
1188
1391
  throw new Error("Release journal header is invalid or unsupported.");
1189
1392
  };
1190
1393
 
@@ -1409,6 +1612,68 @@ const assertSchema2AppendOnlyTransition = (
1409
1612
  }
1410
1613
  };
1411
1614
 
1615
+ const assertSchema4AppendOnlyTransition = (
1616
+ current: IReleaseJournalV4,
1617
+ next: IReleaseJournalV4,
1618
+ ): void => {
1619
+ if (
1620
+ current.giteaAssets.release.id !== null &&
1621
+ current.giteaAssets.release.id !== next.giteaAssets.release.id
1622
+ ) {
1623
+ throw new Error("The recorded Gitea release identity cannot be replaced.");
1624
+ }
1625
+ if (
1626
+ current.giteaAssets.release.id === null &&
1627
+ next.giteaAssets.release.id !== null &&
1628
+ next.giteaAssets.release.state !== "verified"
1629
+ ) {
1630
+ throw new Error(
1631
+ "A Gitea release identity must be verified before recording it.",
1632
+ );
1633
+ }
1634
+ const statuses = (journal: IReleaseJournalV4) => [
1635
+ journal.git,
1636
+ journal.giteaAssets.release,
1637
+ ...journal.giteaAssets.assets,
1638
+ ...journal.npm.registries,
1639
+ ];
1640
+ const nextStatuses = statuses(next);
1641
+ for (const [index, before] of statuses(current).entries()) {
1642
+ const after = nextStatuses[index];
1643
+ if (before.state === "publishing" && after.state === "publishing") {
1644
+ if (
1645
+ JSON.stringify(before.attempt) !== JSON.stringify(after.attempt) ||
1646
+ before.attempts !== after.attempts
1647
+ )
1648
+ throw new Error("Publication attempt ownership cannot change.");
1649
+ continue;
1650
+ }
1651
+ if (
1652
+ (before.state === "pending" || before.state === "failed") &&
1653
+ after.state === "publishing"
1654
+ ) {
1655
+ if (after.attempts !== before.attempts + 1)
1656
+ throw new Error(
1657
+ "Publication claims must increment the attempt counter once.",
1658
+ );
1659
+ continue;
1660
+ }
1661
+ if (
1662
+ after.attempts !== before.attempts ||
1663
+ (after.state === "pending" && before.state !== "pending") ||
1664
+ (before.state === "conflict" && after.state !== "conflict") ||
1665
+ (before.state === "verified" &&
1666
+ !["verified", "conflict"].includes(after.state)) ||
1667
+ (before.state === "publishing" &&
1668
+ !["verified", "failed", "conflict"].includes(after.state))
1669
+ ) {
1670
+ throw new Error(
1671
+ "Release publication status cannot regress or rewrite attempt history.",
1672
+ );
1673
+ }
1674
+ }
1675
+ };
1676
+
1412
1677
  export const createReleaseAttempt = (): IReleaseAttempt => ({
1413
1678
  id: plugins.crypto.randomBytes(16).toString("hex"),
1414
1679
  pid: process.pid,
@@ -1479,7 +1744,8 @@ export class ReleaseJournalStore {
1479
1744
  ): string {
1480
1745
  if (
1481
1746
  fileArg !== releaseArtifactFileName &&
1482
- !/^package\.[0-9a-f]{64}\.tgz$/.test(fileArg)
1747
+ !/^package\.[0-9a-f]{64}\.tgz$/.test(fileArg) &&
1748
+ !/^asset\.[0-9a-f]{64}\.bin$/.test(fileArg)
1483
1749
  ) {
1484
1750
  throw new Error("Invalid release artifact filename.");
1485
1751
  }
@@ -1567,9 +1833,15 @@ export class ReleaseJournalStore {
1567
1833
  throw new Error("New release journals must start at revision 1.");
1568
1834
  }
1569
1835
  if (
1570
- journal.schemaVersion === 3 &&
1836
+ (journal.schemaVersion === 3 || journal.schemaVersion === 4) &&
1571
1837
  (journal.completedAt !== null ||
1572
- [journal.git, ...journal.npm.registries].some(
1838
+ [
1839
+ journal.git,
1840
+ ...journal.npm.registries,
1841
+ ...(journal.schemaVersion === 4
1842
+ ? [journal.giteaAssets.release, ...journal.giteaAssets.assets]
1843
+ : []),
1844
+ ].some(
1573
1845
  (target) =>
1574
1846
  !["pending", "skipped"].includes(target.state) ||
1575
1847
  target.attempts !== 0 ||
@@ -1578,7 +1850,7 @@ export class ReleaseJournalStore {
1578
1850
  ))
1579
1851
  ) {
1580
1852
  throw new Error(
1581
- "New schema 3 journals require unclaimed publication targets.",
1853
+ "New package and asset journals require unclaimed publication targets.",
1582
1854
  );
1583
1855
  }
1584
1856
  if (journal.schemaVersion === releaseJournalSchemaVersion) {
@@ -1646,6 +1918,16 @@ export class ReleaseJournalStore {
1646
1918
  );
1647
1919
  }
1648
1920
  }
1921
+ if (journal.schemaVersion === 4) {
1922
+ if (journal.giteaAssets.release.id !== null)
1923
+ throw new Error(
1924
+ "New Gitea asset journals must not preclaim a release identity.",
1925
+ );
1926
+ await validateGiteaAssets(
1927
+ temporaryDirectory,
1928
+ journal.giteaAssets.assets.map((entry) => entry.asset),
1929
+ );
1930
+ }
1649
1931
 
1650
1932
  const releaseDirectory = this.getReleaseDirectory(journal.release.version);
1651
1933
  const lock = new InterProcessLock({
@@ -1721,6 +2003,8 @@ export class ReleaseJournalStore {
1721
2003
  ) {
1722
2004
  assertSchema2AppendOnlyTransition(current, next);
1723
2005
  }
2006
+ if (current.schemaVersion === 4 && next.schemaVersion === 4)
2007
+ assertSchema4AppendOnlyTransition(current, next);
1724
2008
  await this.writeAtomic(version, next);
1725
2009
  return next;
1726
2010
  });