trm-core 10.0.0 → 10.1.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 (35) hide show
  1. package/changelog.txt +27 -3
  2. package/dist/actions/checkEngines/analyze.js +6 -0
  3. package/dist/actions/checkSapEntries/analyze.d.ts +2 -0
  4. package/dist/actions/checkSapEntries/analyze.js +100 -18
  5. package/dist/actions/commons/setSystemPackages.js +1 -1
  6. package/dist/actions/commons/utils/actionLocks.d.ts +2 -1
  7. package/dist/actions/commons/utils/actionLocks.js +8 -1
  8. package/dist/actions/commons/utils/index.d.ts +1 -0
  9. package/dist/actions/commons/utils/index.js +1 -0
  10. package/dist/actions/commons/utils/packageCleanup.d.ts +8 -0
  11. package/dist/actions/commons/utils/packageCleanup.js +59 -10
  12. package/dist/actions/commons/utils/releaseDeletionTransport.js +11 -4
  13. package/dist/actions/commons/utils/trmBackend.d.ts +20 -0
  14. package/dist/actions/commons/utils/trmBackend.js +64 -0
  15. package/dist/actions/install/checkSapEntries.js +1 -1
  16. package/dist/actions/install/checkTransports.js +40 -1
  17. package/dist/actions/install/generateLandscapeTransport.js +4 -1
  18. package/dist/actions/install/generateUpdateTransport.d.ts +7 -0
  19. package/dist/actions/install/generateUpdateTransport.js +75 -8
  20. package/dist/actions/install/importBatch.js +0 -0
  21. package/dist/actions/install/index.d.ts +10 -0
  22. package/dist/actions/install/index.js +6 -3
  23. package/dist/actions/install/init.js +12 -2
  24. package/dist/actions/install/prepareCust.js +9 -4
  25. package/dist/actions/install/prepareDevc.js +9 -4
  26. package/dist/actions/install/prepareLang.js +9 -4
  27. package/dist/actions/install/prepareTadir.js +9 -4
  28. package/dist/actions/install/setInstallDevclass.js +17 -3
  29. package/dist/actions/install/updatePackageData.d.ts +5 -0
  30. package/dist/actions/install/updatePackageData.js +37 -18
  31. package/dist/client/RESTClient.js +6 -5
  32. package/dist/client/RFCClient.js +3 -5
  33. package/dist/systemConnector/SystemConnectorBase.js +25 -3
  34. package/dist/transport/Transport.js +17 -3
  35. package/package.json +1 -1
package/changelog.txt CHANGED
@@ -8,10 +8,34 @@ Legend
8
8
  + : added
9
9
  - : removed
10
10
 
11
- 2026-08-05 v10.0.0
11
+ 2026-10-12 v10.1.0
12
12
  -------------------
13
- + usage of registry /transport route (when available)
14
- + important log messages (kept visible at the end of an action)
13
+ * trm-server/trm-rest upgrade from a backend before trm-server 7.0.0/trm-rest 6.0.0
14
+ * trm-server/trm-rest upgrade cleanup after the install, deleting only objects not in the new release
15
+ * trm-server/trm-rest kept in $ packages unless installed in /ATRM/ packages
16
+ * deletion transport only includes objects that exist on the system
17
+ * release transports missing from registry metadata read from the verified artifact
18
+ ! SAP entries check with percentage progress and batched reads
19
+ ! existing objects check batched
20
+
21
+ 2026-10-11 v10.0.1
22
+ -------------------
23
+ * rest client https agent import
24
+
25
+ 2026-10-11 v10.0.0
26
+ -------------------
27
+ + usage of registry /transport route
28
+ + important log messages
29
+ ! uninstall
30
+ * flows changes
31
+
32
+ 2026-09-23 v9.10.6
33
+ -------------------
34
+ ! trm-commons ^5.1.0
35
+
36
+ 2026-07-29 v9.10.5
37
+ -------------------
38
+ + allow self signed certificates in rest client
15
39
 
16
40
  2026-07-29 v9.10.4
17
41
  -------------------
@@ -208,26 +208,31 @@ function evaluate(context, engines, path, required) {
208
208
  switch (key) {
209
209
  case 'trm':
210
210
  for (const name of Object.keys(engines.trm)) {
211
+ trm_commons_1.Logger.loading(`Checking engine requirement ${prefix}trm.${name}...`);
211
212
  ok = push(`${prefix}trm.${name}`, yield checkTrm(context, name, engines.trm[name])) && ok;
212
213
  }
213
214
  break;
214
215
  case 'components':
215
216
  for (const name of Object.keys(engines.components)) {
217
+ trm_commons_1.Logger.loading(`Checking engine requirement ${prefix}components.${name}...`);
216
218
  ok = push(`${prefix}components.${name}`, yield checkComponent(context, name, engines.components[name])) && ok;
217
219
  }
218
220
  break;
219
221
  case 'products':
220
222
  for (const name of Object.keys(engines.products)) {
223
+ trm_commons_1.Logger.loading(`Checking engine requirement ${prefix}products.${name}...`);
221
224
  ok = push(`${prefix}products.${name}`, yield checkProduct(context, name, engines.products[name])) && ok;
222
225
  }
223
226
  break;
224
227
  case 'notes':
225
228
  for (const note of Object.keys(engines.notes)) {
229
+ trm_commons_1.Logger.loading(`Checking engine requirement ${prefix}notes.${note}...`);
226
230
  ok = push(`${prefix}notes.${note}`, yield checkNote(note, engines.notes[note])) && ok;
227
231
  }
228
232
  break;
229
233
  case 'tables':
230
234
  for (var i = 0; i < engines.tables.length; i++) {
235
+ trm_commons_1.Logger.loading(`Checking engine requirement ${prefix}tables[${i}]...`);
231
236
  ok = push(`${prefix}tables[${i}]`, yield checkTable(engines.tables[i])) && ok;
232
237
  }
233
238
  break;
@@ -235,6 +240,7 @@ function evaluate(context, engines, path, required) {
235
240
  var alternatives = [];
236
241
  var anyOk = false;
237
242
  for (var j = 0; j < engines.anyOf.length; j++) {
243
+ trm_commons_1.Logger.loading(`Checking engine alternative ${prefix}anyOf[${j}]...`);
238
244
  const alternative = yield evaluate(context, engines.anyOf[j], `${prefix}anyOf[${j}]`, false);
239
245
  alternatives = alternatives.concat(alternative.results);
240
246
  anyOk = anyOk || alternative.ok;
@@ -1,5 +1,7 @@
1
1
  import { Step } from "@simonegaffurini/sammarksworkflow";
2
2
  import { CheckSapEntriesWorkflowContext } from ".";
3
+ /** Entries read with one query. */
4
+ export declare const SAP_ENTRIES_BATCH_SIZE = 25;
3
5
  /**
4
6
  * Workflow step that queries required SAP tables and builds a per-entry status report.
5
7
  *
@@ -9,9 +9,93 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.analyze = void 0;
12
+ exports.analyze = exports.SAP_ENTRIES_BATCH_SIZE = void 0;
13
13
  const trm_commons_1 = require("trm-commons");
14
14
  const systemConnector_1 = require("../../systemConnector");
15
+ const manifest_1 = require("../../manifest");
16
+ const commons_1 = require("../../commons");
17
+ /** Entries read with one query. */
18
+ exports.SAP_ENTRIES_BATCH_SIZE = 25;
19
+ // RFC_READ_TABLE option lines hold 72 characters: a batched condition also carries "OR ( " and " )"
20
+ const BATCHED_CONDITION_MAX_LENGTH = 65;
21
+ function entryError(table, tableEntry, e) {
22
+ const reason = e instanceof Error ? e.message : String(e);
23
+ return new Error(`Unable to check required SAP entry ${JSON.stringify(tableEntry)} in table "${table}": ${reason}`);
24
+ }
25
+ function checkEntry(table, tableEntry) {
26
+ return __awaiter(this, void 0, void 0, function* () {
27
+ try {
28
+ return yield systemConnector_1.SystemConnector.checkSapEntryExists(table, tableEntry);
29
+ }
30
+ catch (e) {
31
+ throw entryError(table, tableEntry, e);
32
+ }
33
+ });
34
+ }
35
+ /**
36
+ * Whether each entry of a table exists. Entries with the same fields are read together, in batches of
37
+ * {@link SAP_ENTRIES_BATCH_SIZE}; an entry a batch doesn't return is checked on its own, as are the entries
38
+ * of a batch that can't be read (e.g. rows too wide).
39
+ */
40
+ function checkTableEntries(table, entries, onChecked) {
41
+ return __awaiter(this, void 0, void 0, function* () {
42
+ const status = new Array(entries.length);
43
+ const batches = new Map();
44
+ const singles = [];
45
+ entries.forEach((tableEntry, index) => {
46
+ let conditions;
47
+ try {
48
+ conditions = (0, manifest_1.getSapEntryConditions)(tableEntry);
49
+ }
50
+ catch (e) {
51
+ throw entryError(table, tableEntry, e);
52
+ }
53
+ if (conditions.some(condition => condition.length > BATCHED_CONDITION_MAX_LENGTH)) {
54
+ singles.push(index);
55
+ return;
56
+ }
57
+ const values = new Map(Object.keys(tableEntry).map(field => [field.trim().toUpperCase(), tableEntry[field].toString()]));
58
+ const fieldsKey = Array.from(values.keys()).sort().join(',');
59
+ if (!batches.has(fieldsKey)) {
60
+ batches.set(fieldsKey, []);
61
+ }
62
+ batches.get(fieldsKey).push({ index, values, conditions });
63
+ });
64
+ for (const [fieldsKey, group] of batches) {
65
+ const fields = fieldsKey.split(',');
66
+ // readTable returns camel-cased field names
67
+ const rowKeys = fields.map(field => Object.keys((0, commons_1.normalize)({ [field]: '' }))[0]);
68
+ for (let i = 0; i < group.length; i += exports.SAP_ENTRIES_BATCH_SIZE) {
69
+ const batch = group.slice(i, i + exports.SAP_ENTRIES_BATCH_SIZE);
70
+ let rows;
71
+ try {
72
+ rows = yield systemConnector_1.SystemConnector.readTable(table.trim().toUpperCase(), fields.map(fieldName => ({ fieldName })), batch.map(o => `( ${o.conditions.join(' AND ')} )`).join(' OR '));
73
+ }
74
+ catch (e) {
75
+ trm_commons_1.Logger.log(`Batch read of ${batch.length} entries in ${table} failed, checking them one by one: ${e}`, true);
76
+ singles.push(...batch.map(o => o.index));
77
+ continue;
78
+ }
79
+ for (const o of batch) {
80
+ const found = rows.some(row => fields.every((field, f) => { var _a; return `${(_a = row[rowKeys[f]]) !== null && _a !== void 0 ? _a : ''}`.trim() === o.values.get(field).trim(); }));
81
+ if (found) {
82
+ status[o.index] = true;
83
+ }
84
+ else {
85
+ // values the database matches but that read differently (e.g. numbers) are confirmed on their own
86
+ singles.push(o.index);
87
+ }
88
+ }
89
+ onChecked(batch.length - batch.filter(o => !status[o.index]).length);
90
+ }
91
+ }
92
+ for (const index of singles) {
93
+ status[index] = yield checkEntry(table, entries[index]);
94
+ onChecked(1);
95
+ }
96
+ return status;
97
+ });
98
+ }
15
99
  /**
16
100
  * Workflow step that queries required SAP tables and builds a per-entry status report.
17
101
  *
@@ -60,7 +144,11 @@ exports.analyze = {
60
144
  return;
61
145
  }
62
146
  //2- check entries
147
+ let checked = 0;
148
+ const progress = (table) => trm_commons_1.Logger.loading(`Checking ${entriesCount} SAP entries: ${Math.floor(checked * 100 / entriesCount)}% (${table.trim()})...`);
63
149
  for (const table of Object.keys(context.output.sapEntries)) {
150
+ const requiredEntries = context.output.sapEntries[table];
151
+ progress(table);
64
152
  var tableExists = false;
65
153
  try {
66
154
  for (const object of ['TABL', 'VIEW']) {
@@ -80,30 +168,26 @@ exports.analyze = {
80
168
  }
81
169
  if (!tableExists) {
82
170
  context.runtime.missingTables.push(table);
83
- context.output.sapEntries[table].forEach(tableEntry => {
171
+ requiredEntries.forEach(tableEntry => {
84
172
  context.runtime.entriesStatus.bad.push({
85
173
  table,
86
174
  tableEntry
87
175
  });
88
176
  });
89
- trm_commons_1.Logger.error(`Required ${context.output.sapEntries[table].length} entries in ${table}, but table or view was not found`, { debug: !context.rawInput.printOptions.information, important: true });
177
+ checked += requiredEntries.length;
178
+ trm_commons_1.Logger.error(`Required ${requiredEntries.length} entries in ${table}, but table or view was not found`, { debug: !context.rawInput.printOptions.information, important: true });
90
179
  }
91
180
  else {
92
181
  var printTableHead = ['Table name'];
93
182
  var printTableData = [];
94
- var tableData;
95
183
  printTableHead = printTableHead.concat(tableFields.find(o => o.tableName === table).fields);
96
184
  printTableHead.push('Status');
97
- for (const tableEntry of context.output.sapEntries[table]) {
98
- tableData = [table];
99
- var exists;
100
- try {
101
- exists = yield systemConnector_1.SystemConnector.checkSapEntryExists(table, tableEntry);
102
- }
103
- catch (e) {
104
- const reason = e instanceof Error ? e.message : String(e);
105
- throw new Error(`Unable to check required SAP entry ${JSON.stringify(tableEntry)} in table "${table}": ${reason}`);
106
- }
185
+ const statuses = yield checkTableEntries(table, requiredEntries, count => {
186
+ checked += count;
187
+ progress(table);
188
+ });
189
+ requiredEntries.forEach((tableEntry, index) => {
190
+ const exists = statuses[index];
107
191
  if (exists) {
108
192
  context.runtime.entriesStatus.good.push({
109
193
  table,
@@ -117,10 +201,8 @@ exports.analyze = {
117
201
  });
118
202
  }
119
203
  const entryStatus = exists ? `OK` : `NOT FOUND`;
120
- tableData = tableData.concat(printTableHead.slice(1, -1).map(field => { var _a; return (_a = tableEntry[field]) !== null && _a !== void 0 ? _a : ''; }));
121
- tableData.push(entryStatus);
122
- printTableData.push(tableData);
123
- }
204
+ printTableData.push([table, ...printTableHead.slice(1, -1).map(field => { var _a; return (_a = tableEntry[field]) !== null && _a !== void 0 ? _a : ''; }), entryStatus]);
205
+ });
124
206
  logTable.push({
125
207
  header: printTableHead,
126
208
  data: printTableData
@@ -27,7 +27,7 @@ exports.setSystemPackages = {
27
27
  context.rawInput.contextData = {};
28
28
  }
29
29
  if (context.rawInput.contextData.systemPackages === undefined) {
30
- trm_commons_1.Logger.loading(`Reading system data...`);
30
+ trm_commons_1.Logger.loading(`Reading system packages...`);
31
31
  context.rawInput.contextData.systemPackages = yield systemConnector_1.SystemConnector.getInstalledPackages(true, true);
32
32
  }
33
33
  })
@@ -15,9 +15,10 @@ export declare function objectLockResource(object: {
15
15
  /** One non-expiring SAP lock owner; close only after workflow rollback has finished. */
16
16
  export declare class ActionLockScope {
17
17
  private readonly actionName;
18
+ private readonly enabled;
18
19
  private readonly ownerToken;
19
20
  private readonly held;
20
- constructor(actionName: string);
21
+ constructor(actionName: string, enabled?: boolean);
21
22
  acquire(resources: LockResource[]): Promise<void>;
22
23
  release(): Promise<void>;
23
24
  }
@@ -42,13 +42,17 @@ function objectLockResource(object) {
42
42
  }
43
43
  /** One non-expiring SAP lock owner; close only after workflow rollback has finished. */
44
44
  class ActionLockScope {
45
- constructor(actionName) {
45
+ constructor(actionName, enabled = true) {
46
46
  this.actionName = actionName;
47
+ this.enabled = enabled;
47
48
  this.ownerToken = (0, crypto_1.randomUUID)().replace(/-/g, "").toUpperCase();
48
49
  this.held = new Map();
49
50
  }
50
51
  acquire(resources) {
51
52
  return __awaiter(this, void 0, void 0, function* () {
53
+ if (!this.enabled) {
54
+ return;
55
+ }
52
56
  const requested = resources.map(actionLockKey);
53
57
  const fresh = requested.filter(key => !this.held.has(`${key.resourceType}:${key.resourceHash}`));
54
58
  if (fresh.length === 0) {
@@ -82,6 +86,9 @@ class ActionLockScope {
82
86
  }
83
87
  release() {
84
88
  return __awaiter(this, void 0, void 0, function* () {
89
+ if (!this.enabled) {
90
+ return;
91
+ }
85
92
  if (this.held.size === 0) {
86
93
  return;
87
94
  }
@@ -8,3 +8,4 @@ export * from "./installedDependency";
8
8
  export * from "./installRegistry";
9
9
  export * from "./dirtyEntries";
10
10
  export * from "./owningInstallation";
11
+ export * from "./trmBackend";
@@ -24,3 +24,4 @@ __exportStar(require("./installedDependency"), exports);
24
24
  __exportStar(require("./installRegistry"), exports);
25
25
  __exportStar(require("./dirtyEntries"), exports);
26
26
  __exportStar(require("./owningInstallation"), exports);
27
+ __exportStar(require("./trmBackend"), exports);
@@ -41,6 +41,8 @@ export interface PackageCleanupRevert {
41
41
  /** Minimum workflow context needed to remove an installed release from the target system. */
42
42
  export interface PackageCleanupContext {
43
43
  lockScope?: ActionLockScope;
44
+ /** Only the APIs of a backend older than this trm-core can be used: no test imports. */
45
+ legacyBackend?: boolean;
44
46
  rawInput: {
45
47
  packageData: {
46
48
  name: string;
@@ -74,6 +76,12 @@ export type PackageCleanupTarget = {
74
76
  incomingObjects: TADIR[];
75
77
  /** SAP packages that hold the incoming release. Empty when uninstalling. */
76
78
  keptDevclasses: string[];
79
+ /**
80
+ * When `true`, the incoming release is already imported: only the installed objects it doesn't
81
+ * ship are deleted. Otherwise every installed object is deleted before the import, except the
82
+ * tables the incoming release ships again.
83
+ */
84
+ incomingImported?: boolean;
77
85
  /** Logger/Inquirer prefix shown while the cleanup runs. */
78
86
  prefix: string;
79
87
  /** Action name used in abort messages, e.g. `Update`. */
@@ -402,7 +402,8 @@ function getCustomizingSources(context, target) {
402
402
  if (sources.length === 0) {
403
403
  return [];
404
404
  }
405
- if (target.keepCustomizing) {
405
+ // After the import, deleting by key would also delete the rows the incoming release has just written.
406
+ if (target.keepCustomizing || target.incomingImported) {
406
407
  trm_commons_1.Logger.warning(`Keeping customizing of ${sources.length} transports (${sources.join(', ')}).`, { important: true });
407
408
  return [];
408
409
  }
@@ -513,7 +514,10 @@ function createStagingPackage(context, namespace) {
513
514
  ctext: 'TRM upgrade cleanup',
514
515
  as4user: systemConnector_1.SystemConnector.getLogonUser(),
515
516
  dlvunit: 'HOME',
516
- pdevclass: yield systemConnector_1.SystemConnector.getDefaultTransportLayer()
517
+ pdevclass: yield systemConnector_1.SystemConnector.getDefaultTransportLayer(),
518
+ // The package immediately receives transported objects. Some systems do not infer
519
+ // change recording from the transport layer when CREATE_PACKAGE is called directly.
520
+ korrflag: 'X'
517
521
  });
518
522
  yield systemConnector_1.SystemConnector.tadirInterface({
519
523
  pgmid: 'R3TR', object: 'DEVC', objName: stagingDevclass,
@@ -544,18 +548,46 @@ function cleanupInstalledPackage(context, target) {
544
548
  target: systemConnector_1.SystemConnector.getDest()
545
549
  });
546
550
  context.revert.updateCleanupTransport = dummy;
547
- const { previousNamespaces, previousTransportObjects } = yield readInstalledTransportObjects(installed);
551
+ const installedTransport = yield readInstalledTransportObjects(installed);
552
+ const previousNamespaces = installedTransport.previousNamespaces;
553
+ const previousTransportObjects = installedTransport.previousTransportObjects;
548
554
  const incomingObjects = target.incomingObjects;
555
+ const currentDevclasses = new Set(target.keptDevclasses.map(normalize));
556
+ const deletedKeys = new Set((context.runtime.deletedObjects || []).map(objectKey));
557
+ const { previousDevclasses, installedDevclasses, packageParents, otherInstallations, isOtherInstallation } = yield readInstallationTree(installed, context.runtime.previousInstallPackages, previousTransportObjects, context.runtime.deletedObjects || [], ((_a = context.rawInput.contextData) === null || _a === void 0 ? void 0 : _a.systemPackages) || []);
558
+ // After the import, the previous release is also what its SAP packages still contain beyond the
559
+ // incoming release: an installation without transport (e.g. trm-server/trm-rest installed with
560
+ // abapGit) or leftovers of an earlier upgrade.
561
+ if (target.incomingImported) {
562
+ const previousKeys = new Set(previousTransportObjects.map(objectKey));
563
+ let added = 0;
564
+ for (const devclass of previousDevclasses.values()) {
565
+ if (isOtherInstallation(devclass)) {
566
+ continue;
567
+ }
568
+ for (const object of yield systemConnector_1.SystemConnector.getDevclassObjects(devclass, false)) {
569
+ if (!isDevclass(object) && !isNamespace(object) && !deletedKeys.has(objectKey(object)) && !previousKeys.has(objectKey(object))) {
570
+ previousKeys.add(objectKey(object));
571
+ previousTransportObjects.push({ pgmid: object.pgmid, object: object.object, objName: object.objName });
572
+ added++;
573
+ }
574
+ }
575
+ }
576
+ trm_commons_1.Logger.log(`${added} objects of the previous release found in its SAP packages only`, true);
577
+ }
549
578
  // Tables shipped again by the new release are adjusted by its import instead of
550
579
  // being dropped and re-created, which would lose their data.
551
580
  const incomingKeys = new Set(incomingObjects.map(objectKey));
581
+ // Already imported, the objects of the incoming release are simply kept.
582
+ const keptIncoming = target.incomingImported ? previousTransportObjects.filter(object => incomingKeys.has(objectKey(object))) : [];
583
+ const keptIncomingKeys = new Set(keptIncoming.map(objectKey));
584
+ if (keptIncoming.length > 0) {
585
+ trm_commons_1.Logger.log(`Keeping ${keptIncoming.length} objects shipped again by the imported release`, true);
586
+ }
552
587
  const retainedTables = previousTransportObjects.filter(object => normalize(object.pgmid) === 'R3TR' && normalize(object.object) === 'TABL'
553
- && incomingKeys.has(objectKey(object)));
588
+ && incomingKeys.has(objectKey(object)) && !keptIncomingKeys.has(objectKey(object)));
554
589
  const retainedKeys = new Set(retainedTables.map(objectKey));
555
590
  retainedTables.forEach(object => trm_commons_1.Logger.log(`Keeping table ${object.objName} (present in new release)`, true));
556
- const currentDevclasses = new Set(target.keptDevclasses.map(normalize));
557
- const deletedKeys = new Set((context.runtime.deletedObjects || []).map(objectKey));
558
- const { previousDevclasses, installedDevclasses, packageParents, otherInstallations, isOtherInstallation } = yield readInstallationTree(installed, context.runtime.previousInstallPackages, previousTransportObjects, context.runtime.deletedObjects || [], ((_a = context.rawInput.contextData) === null || _a === void 0 ? void 0 : _a.systemPackages) || []);
559
591
  const generatedDevclasses = new Set(context.revert.sapPackages.map(normalize));
560
592
  otherInstallations.forEach(pkg => trm_commons_1.Logger.log(`Ignoring SAP package ${pkg.getDevclass()}: TRM package ${pkg.packageName} is installed there`, true));
561
593
  // Local additions remain cleanup candidates even when reused as incoming targets.
@@ -687,7 +719,7 @@ function cleanupInstalledPackage(context, target) {
687
719
  }
688
720
  // Installed objects moved to a package outside this installation (customer development)
689
721
  // are kept unless confirmed; those now in another TRM package belong to it and are kept.
690
- const relocatedObjects = yield readRelocatedObjects(previousTransportObjects.filter(object => !retainedKeys.has(objectKey(object))), { previousDevclasses, isOtherInstallation });
722
+ const relocatedObjects = yield readRelocatedObjects(previousTransportObjects.filter(object => !retainedKeys.has(objectKey(object)) && !keptIncomingKeys.has(objectKey(object))), { previousDevclasses, isOtherInstallation });
691
723
  const movedKeys = new Set();
692
724
  const movedObjects = [];
693
725
  for (const object of relocatedObjects) {
@@ -743,13 +775,30 @@ function cleanupInstalledPackage(context, target) {
743
775
  // SAP packages the incoming release keeps are never deleted: a renamed installation has already
744
776
  // generated them (generate-devclass), and the import of the others updates them in place.
745
777
  const deletionObjects = new Map([...previousTransportObjects, ...additionalObjects]
746
- .filter(object => !retainedKeys.has(objectKey(object)) && !movedKeys.has(objectKey(object)))
778
+ .filter(object => !retainedKeys.has(objectKey(object)) && !movedKeys.has(objectKey(object)) && !keptIncomingKeys.has(objectKey(object)))
747
779
  .filter(object => !(isDevclass(object) && currentDevclasses.has(normalize(object.objName))))
748
780
  .map(object => [objectKey(object), object]));
781
+ // Only objects that exist can be transported: the installed transport may list objects this system
782
+ // never had or already lost (e.g. a system copy).
783
+ const checkedObjects = Array.from(deletionObjects.values()).filter(object => normalize(object.pgmid) === 'R3TR' && !isNamespace(object));
784
+ if (checkedObjects.length > 0) {
785
+ const existing = new Set((yield systemConnector_1.SystemConnector.getExistingObjects(checkedObjects.map(object => ({
786
+ pgmid: object.pgmid, object: object.object, objName: object.objName, devclass: ''
787
+ })))).map(objectKey));
788
+ checkedObjects.filter(object => !existing.has(objectKey(object))).forEach(object => {
789
+ trm_commons_1.Logger.log(`Skipping ${object.pgmid} ${object.object} ${object.objName}: not on ${systemConnector_1.SystemConnector.getDest()}, nothing to delete`, true);
790
+ deletionObjects.delete(objectKey(object));
791
+ });
792
+ }
749
793
  // TRM comment rows of the installed transport are not objects to delete.
750
794
  const hasObjectsToDelete = Array.from(deletionObjects.values()).some(object => normalize(object.pgmid) !== '*');
751
795
  if (!hasObjectsToDelete && retainedTables.length === 0 && customizingSources.length === 0) {
752
- trm_commons_1.Logger.warning(`Nothing to delete for package ${context.rawInput.packageData.name}: deletion transport was not generated. Manual cleanup of previous release install might be necessary.`, { important: true });
796
+ if (target.incomingImported) {
797
+ trm_commons_1.Logger.log(`The imported release ships every object of the previous one: nothing to delete`, true);
798
+ }
799
+ else {
800
+ trm_commons_1.Logger.warning(`Nothing to delete for package ${context.rawInput.packageData.name}: deletion transport was not generated. Manual cleanup of previous release install might be necessary.`, { important: true });
801
+ }
753
802
  try {
754
803
  if (yield dummy.canBeDeleted()) {
755
804
  yield dummy.delete();
@@ -50,10 +50,17 @@ function releaseDeletionTransport(deletionTransport_1, registry_2, context_1) {
50
50
  trTarget: systemConnector_1.SystemConnector.getDest()
51
51
  });
52
52
  //3- import transport
53
- trm_commons_1.Logger.loading(`Testing import...`);
54
- const testRc = yield context.runtime.dele.import(true);
55
- if (testRc < 0 || testRc > 8) {
56
- throw new Error(`Test import of deletion transport failed: check logs.`);
53
+ // A legacy backend has no test import; in the same process the RFC metadata of its import
54
+ // function stays cached even after the upgrade is imported.
55
+ if (context.legacyBackend) {
56
+ trm_commons_1.Logger.log(`Skipping test import of ${deletionTransport.trkorr}: legacy TRM backend`, true);
57
+ }
58
+ else {
59
+ trm_commons_1.Logger.loading(`Testing import...`);
60
+ const testRc = yield context.runtime.dele.import(true);
61
+ if (testRc < 0 || testRc > 8) {
62
+ throw new Error(`Test import of deletion transport failed: check logs.`);
63
+ }
57
64
  }
58
65
  trm_commons_1.Logger.loading(`Importing ${deletionTransport.trkorr}`, true);
59
66
  // Mark before the mutating await: the import may change objects and still fail.
@@ -0,0 +1,20 @@
1
+ import { AbstractRegistry } from "../../../registry";
2
+ /**
3
+ * First trm-server and trm-rest releases with the backend APIs this trm-core relies on
4
+ * (action locks, batch import, install transports, test imports, transport object locks).
5
+ */
6
+ export declare const TRM_SERVER_API_VERSION = "7.0.0";
7
+ export declare const TRM_REST_API_VERSION = "6.0.0";
8
+ /**
9
+ * Permanent TADIR transport of the public trm-server and trm-rest: the same request in every release.
10
+ * The registry doesn't deliver it through its transport download: it's read from the release artifact.
11
+ */
12
+ export declare const TRM_FIXED_TADIR_TRANSPORTS: Readonly<Record<string, string>>;
13
+ /** Whether a package is the public trm-server or trm-rest: the TRM backend itself. */
14
+ export declare function isTrmBackendPackage(registry: AbstractRegistry, name: string): boolean;
15
+ /**
16
+ * Whether the connected TRM backend predates {@link TRM_SERVER_API_VERSION} / {@link TRM_REST_API_VERSION}.
17
+ * Such a backend can only upgrade itself, with the APIs of its own generation: the newer APIs exist
18
+ * only once the upgrade is imported.
19
+ */
20
+ export declare function isLegacyTrmBackend(): Promise<boolean>;
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.TRM_FIXED_TADIR_TRANSPORTS = exports.TRM_REST_API_VERSION = exports.TRM_SERVER_API_VERSION = void 0;
13
+ exports.isTrmBackendPackage = isTrmBackendPackage;
14
+ exports.isLegacyTrmBackend = isLegacyTrmBackend;
15
+ const semver_1 = require("semver");
16
+ const trm_commons_1 = require("trm-commons");
17
+ const registry_1 = require("../../../registry");
18
+ const systemConnector_1 = require("../../../systemConnector");
19
+ /**
20
+ * First trm-server and trm-rest releases with the backend APIs this trm-core relies on
21
+ * (action locks, batch import, install transports, test imports, transport object locks).
22
+ */
23
+ exports.TRM_SERVER_API_VERSION = '7.0.0';
24
+ exports.TRM_REST_API_VERSION = '6.0.0';
25
+ /**
26
+ * Permanent TADIR transport of the public trm-server and trm-rest: the same request in every release.
27
+ * The registry doesn't deliver it through its transport download: it's read from the release artifact.
28
+ */
29
+ exports.TRM_FIXED_TADIR_TRANSPORTS = {
30
+ 'trm-server': 'TRMK900000',
31
+ 'trm-rest': 'TRMK900001'
32
+ };
33
+ /** Whether a package is the public trm-server or trm-rest: the TRM backend itself. */
34
+ function isTrmBackendPackage(registry, name) {
35
+ return registry.getRegistryType() === registry_1.RegistryType.PUBLIC
36
+ && [systemConnector_1.TRM_SERVER_PACKAGE_NAME, systemConnector_1.TRM_REST_PACKAGE_NAME].includes(name.trim().toLowerCase());
37
+ }
38
+ function installedVersion(read) {
39
+ return __awaiter(this, void 0, void 0, function* () {
40
+ var _a;
41
+ try {
42
+ return (_a = (yield read()).manifest) === null || _a === void 0 ? void 0 : _a.get().version;
43
+ }
44
+ catch (_b) {
45
+ // not installed
46
+ return undefined;
47
+ }
48
+ });
49
+ }
50
+ /**
51
+ * Whether the connected TRM backend predates {@link TRM_SERVER_API_VERSION} / {@link TRM_REST_API_VERSION}.
52
+ * Such a backend can only upgrade itself, with the APIs of its own generation: the newer APIs exist
53
+ * only once the upgrade is imported.
54
+ */
55
+ function isLegacyTrmBackend() {
56
+ return __awaiter(this, void 0, void 0, function* () {
57
+ const isLegacy = (version, apiVersion) => !!version && (!(0, semver_1.valid)(version) || (0, semver_1.lt)(version, apiVersion));
58
+ const serverVersion = yield installedVersion(() => systemConnector_1.SystemConnector.getTrmServerPackage());
59
+ const restVersion = yield installedVersion(() => systemConnector_1.SystemConnector.getTrmRestPackage());
60
+ const legacy = isLegacy(serverVersion, exports.TRM_SERVER_API_VERSION) || isLegacy(restVersion, exports.TRM_REST_API_VERSION);
61
+ trm_commons_1.Logger.log(`TRM backend: trm-server ${serverVersion || '-'}, trm-rest ${restVersion || '-'}${legacy ? ' (legacy APIs)' : ''}`, true);
62
+ return legacy;
63
+ });
64
+ }
@@ -57,7 +57,7 @@ exports.checkSapEntries = {
57
57
  context.runtime.missingSapEntries = missingEntries.length;
58
58
  }
59
59
  else {
60
- trm_commons_1.Logger.success(`SAP entries checked.`);
60
+ trm_commons_1.Logger.success(`Requirements checked.`);
61
61
  }
62
62
  })
63
63
  };