trm-core 10.0.1 → 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 +10 -0
  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 +2 -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,6 +8,16 @@ Legend
8
8
  + : added
9
9
  - : removed
10
10
 
11
+ 2026-10-12 v10.1.0
12
+ -------------------
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
+
11
21
  2026-10-11 v10.0.1
12
22
  -------------------
13
23
  * rest client https agent import
@@ -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
  };
@@ -19,6 +19,7 @@ const trm_commons_2 = require("trm-commons");
19
19
  const systemConnector_1 = require("../../systemConnector");
20
20
  const commons_1 = require("../../commons");
21
21
  const utils_1 = require("../commons/utils");
22
+ const crypto_1 = require("crypto");
22
23
  /** Throws when any of the objects is locked in a SAP transport request. */
23
24
  function checkObjectsLocks(e071) {
24
25
  return __awaiter(this, void 0, void 0, function* () {
@@ -205,6 +206,11 @@ exports.checkTransports = {
205
206
  if (skippedCust.includes(transport.trkorr)) {
206
207
  readEntries = false;
207
208
  }
209
+ // the fixed TADIR transport of trm-server/trm-rest only comes with the artifact (below)
210
+ if ((context.runtime.isTrmServer || context.runtime.isTrmRest) && transport.type === transport_1.TrmTransportIdentifier.TADIR
211
+ && utils_1.TRM_FIXED_TADIR_TRANSPORTS[context.runtime.package.data.manifest.name] === transport.trkorr) {
212
+ readEntries = false;
213
+ }
208
214
  if (readEntries) {
209
215
  const entries = yield context.rawInput.packageData.registry.transportEntries(context.runtime.package.data.manifest.name, context.runtime.package.data.manifest.version, transport.trkorr);
210
216
  const binaries = {
@@ -235,6 +241,29 @@ exports.checkTransports = {
235
241
  }
236
242
  }
237
243
  }
244
+ // The registry metadata can omit a transport of the release, and the fixed TADIR transport of
245
+ // trm-server and trm-rest is never downloaded alone: read them from the artifact, once its integrity is verified.
246
+ if (!context.runtime.transports.devc || !context.runtime.transports.tadir) {
247
+ trm_commons_1.Logger.log(`No ${!context.runtime.transports.devc ? 'DEVC' : 'TADIR'} transport to download from the registry: reading the release artifact`, true);
248
+ trm_commons_1.Logger.loading(`Reading package transports from the release artifact...`);
249
+ const artifact = yield context.rawInput.packageData.registry.downloadArtifact(context.runtime.package.data.manifest.name, context.runtime.package.data.manifest.version);
250
+ const expectedChecksum = context.runtime.package.data.checksum;
251
+ const checksum = (0, crypto_1.createHash)('sha512').update(artifact.binary).digest('base64');
252
+ if (!expectedChecksum || checksum !== expectedChecksum) {
253
+ trm_commons_1.Logger.error(`SECURITY ISSUE! Release artifact integrity does NOT match the registry SHA ${expectedChecksum}: received ${checksum}`, { important: true });
254
+ throw new Error(`Cannot continue due to security issues.`);
255
+ }
256
+ for (const binary of yield artifact.getTransportBinaries()) {
257
+ if (binary.type === transport_1.TrmTransportIdentifier.DEVC && !context.runtime.transports.devc) {
258
+ context.runtime.transports.devc = { binaries: binary };
259
+ mergeEntries(binary.entries);
260
+ }
261
+ else if (binary.type === transport_1.TrmTransportIdentifier.TADIR && !context.runtime.transports.tadir) {
262
+ context.runtime.transports.tadir = { binaries: binary };
263
+ mergeEntries(binary.entries);
264
+ }
265
+ }
266
+ }
238
267
  }
239
268
  //4- check devc and tadir existance
240
269
  if (!context.runtime.transports.devc) {
@@ -272,6 +301,7 @@ exports.checkTransports = {
272
301
  }
273
302
  //8- check objects existance
274
303
  const checkedTadir = existenceCheckObjects(mergedTADIR);
304
+ trm_commons_1.Logger.loading(`Checking whether ${checkedTadir.length} package object${checkedTadir.length === 1 ? '' : 's'} already exist${checkedTadir.length === 1 ? 's' : ''}...`);
275
305
  const existingObjects = checkedTadir.length > 0 ? yield findExistingObjects(context, checkedTadir) : [];
276
306
  // lock-resources checks again, once locked, that no other object appeared
277
307
  context.runtime.existingObjects = existingObjects;
@@ -279,9 +309,18 @@ exports.checkTransports = {
279
309
  //if updating and existing object is part of the package (devclass in hierarchy) ok, else throw error
280
310
  if (existingObjects.length > 0) {
281
311
  // Name the installed TRM package that contains each existing object: it can't be taken over silently.
312
+ trm_commons_1.Logger.loading(`Checking ownership of ${existingObjects.length} existing object${existingObjects.length === 1 ? '' : 's'}...`);
282
313
  const owners = new Map();
314
+ // objects share few SAP packages: look each one up once
315
+ const packageOwners = new Map();
283
316
  for (const o of existingObjects) {
284
- const owner = o.devclass ? yield (0, utils_1.getOwningInstallation)(o.devclass, context.rawInput.contextData.systemPackages || [], context.runtime.update) : undefined;
317
+ if (!o.devclass) {
318
+ continue;
319
+ }
320
+ if (!packageOwners.has(o.devclass)) {
321
+ packageOwners.set(o.devclass, (0, utils_1.getOwningInstallation)(o.devclass, context.rawInput.contextData.systemPackages || [], context.runtime.update));
322
+ }
323
+ const owner = yield packageOwners.get(o.devclass);
285
324
  if (owner) {
286
325
  owners.set(o, owner.packageName);
287
326
  }
@@ -125,7 +125,10 @@ exports.generateLandscapeTransport = {
125
125
  //8- lock objects
126
126
  //what is lockable gets locked here
127
127
  //all workbench should be lockable here
128
- yield context.output.transport.lock();
128
+ //a legacy backend has no API for it
129
+ if (!context.legacyBackend) {
130
+ yield context.output.transport.lock();
131
+ }
129
132
  }),
130
133
  revert: (context) => __awaiter(void 0, void 0, void 0, function* () {
131
134
  if (context.output.transport && (yield context.output.transport.canBeDeleted())) {
@@ -18,3 +18,10 @@ export declare function keepsInstalledCustomizing(context: InstallWorkflowContex
18
18
  * release without an incoming one.
19
19
  */
20
20
  export declare const generateUpdateTransport: Step<InstallWorkflowContext>;
21
+ /**
22
+ * Workflow step that cleans up an upgrade of trm-server or trm-rest, last in the install (see
23
+ * {@link generateUpdateTransport}); every other package is cleaned up before its import. The installed
24
+ * release runs the install: deleting its objects first would remove the APIs the install still calls.
25
+ * Once the upgrade is complete, only the objects the new release doesn't ship are deleted, with its APIs.
26
+ */
27
+ export declare const generatePostImportUpdateTransport: Step<InstallWorkflowContext>;
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.generateUpdateTransport = exports.deleteTemporaryCleanupPackages = void 0;
12
+ exports.generatePostImportUpdateTransport = exports.generateUpdateTransport = exports.deleteTemporaryCleanupPackages = void 0;
13
13
  exports.keepsInstalledCustomizing = keepsInstalledCustomizing;
14
14
  const trm_commons_1 = require("trm-commons");
15
15
  const stopWarning_1 = require("../stopWarning");
@@ -60,6 +60,11 @@ function keepsInstalledCustomizing(context) {
60
60
  exports.generateUpdateTransport = {
61
61
  name: 'generate-update-transport',
62
62
  filter: (context) => __awaiter(void 0, void 0, void 0, function* () {
63
+ if (context.runtime.selfUpdate) {
64
+ // The installed release runs this install: its objects are cleaned up after the import
65
+ trm_commons_1.Logger.log(`Upgrade cleanup of ${context.rawInput.packageData.name} runs after the import`, true);
66
+ return false;
67
+ }
63
68
  //a local (.trm) upgrade generates its deletion transport through the registry the artifact was published to
64
69
  if (context.runtime.update) {
65
70
  return true;
@@ -70,19 +75,78 @@ exports.generateUpdateTransport = {
70
75
  }
71
76
  }),
72
77
  run: (context) => __awaiter(void 0, void 0, void 0, function* () {
73
- var _a, _b, _c, _d;
78
+ yield cleanupPreviousRelease(context, false);
79
+ }),
80
+ revert: revertUpdateCleanup
81
+ };
82
+ /**
83
+ * Workflow step that cleans up an upgrade of trm-server or trm-rest, last in the install (see
84
+ * {@link generateUpdateTransport}); every other package is cleaned up before its import. The installed
85
+ * release runs the install: deleting its objects first would remove the APIs the install still calls.
86
+ * Once the upgrade is complete, only the objects the new release doesn't ship are deleted, with its APIs.
87
+ */
88
+ exports.generatePostImportUpdateTransport = {
89
+ name: 'generate-post-import-update-transport',
90
+ filter: (context) => __awaiter(void 0, void 0, void 0, function* () { return !!context.runtime.selfUpdate; }),
91
+ run: (context) => __awaiter(void 0, void 0, void 0, function* () {
92
+ var _a, _b, _c;
93
+ // The new release is installed: a failed cleanup leaves objects of the previous one, it doesn't undo the install
94
+ try {
95
+ yield cleanupPreviousRelease(context, true);
96
+ }
97
+ catch (error) {
98
+ let revertError;
99
+ if (!context.revert.deleImportStarted) {
100
+ try {
101
+ yield revertUpdateCleanup(context);
102
+ }
103
+ catch (e) {
104
+ revertError = e;
105
+ }
106
+ }
107
+ const version = context.runtime.update.manifest.get().version;
108
+ trm_commons_1.Logger.warning(`Cleanup of ${context.rawInput.packageData.name} ${version} failed: ${error instanceof Error ? error.message : String(error)}`, { important: true });
109
+ if (revertError) {
110
+ trm_commons_1.Logger.warning(`Cleanup could not be undone either: ${revertError instanceof Error ? revertError.message : String(revertError)}`, { important: true });
111
+ }
112
+ trm_commons_1.Logger.warning(`${context.rawInput.packageData.name} ${context.runtime.package.data.manifest.version} is installed. Objects of ${version} it doesn't ship may be left on ${systemConnector_1.SystemConnector.getDest()}: delete them manually.`, { important: true });
113
+ return;
114
+ }
115
+ // The landscape transport is already released: the deletion follows it to the target
116
+ const targetSystem = (_b = (_a = context.rawInput.installData) === null || _a === void 0 ? void 0 : _a.landscapeTransport) === null || _b === void 0 ? void 0 : _b.targetSystem;
117
+ if (targetSystem && ((_c = context.output) === null || _c === void 0 ? void 0 : _c.transport) && (0, utils_1.isDeletionForwardable)(context)) {
118
+ try {
119
+ yield (0, utils_1.forwardDeletionTransport)(context, targetSystem);
120
+ }
121
+ catch (error) {
122
+ trm_commons_1.Logger.warning(`Deletion transport ${context.revert.dele.trkorr} could not be added to the ${targetSystem} import queue: ${error instanceof Error ? error.message : String(error)}. Add it in STMS after ${context.output.transport.trkorr}.`, { important: true });
123
+ }
124
+ }
125
+ }),
126
+ revert: (context) => __awaiter(void 0, void 0, void 0, function* () {
127
+ // The imported release stays (see import-batch): objects it doesn't ship stay deleted too.
128
+ // A cleanup that stopped before its deletion transport is undone.
129
+ if (context.revert.deleImportStarted) {
130
+ trm_commons_1.Logger.log(`Keeping the cleanup of ${context.rawInput.packageData.name}: its imported release is not rolled back`, true);
131
+ return;
132
+ }
133
+ yield revertUpdateCleanup(context);
134
+ })
135
+ };
136
+ function cleanupPreviousRelease(context, incomingImported) {
137
+ return __awaiter(this, void 0, void 0, function* () {
138
+ var _a, _b, _c;
74
139
  if (!context.runtime.stopWarningShown) {
75
140
  context.runtime.stopWarningShown = true;
76
141
  (0, stopWarning_1.stopWarning)('install');
77
142
  }
78
143
  // The cleanup deletes and re-imports the root package: read it now, so the import can put it back
79
144
  // under the superpackage it has on this system (prepare-devc reads it after the cleanup).
80
- if (((_b = (_a = context.rawInput.installData) === null || _a === void 0 ? void 0 : _a.installDevclass) === null || _b === void 0 ? void 0 : _b.keepOriginal) && !context.runtime.rootDevclassBeforeImport) {
145
+ if (!incomingImported && ((_b = (_a = context.rawInput.installData) === null || _a === void 0 ? void 0 : _a.installDevclass) === null || _b === void 0 ? void 0 : _b.keepOriginal) && !context.runtime.rootDevclassBeforeImport) {
81
146
  context.runtime.rootDevclassBeforeImport = yield systemConnector_1.SystemConnector.getDevclass(context.runtime.package.hierarchy.devclass);
82
147
  }
83
148
  // Read the target lazily: a failure reading the previous release must still be tracked first.
84
149
  const installDevclass = (_c = context.rawInput.installData) === null || _c === void 0 ? void 0 : _c.installDevclass;
85
- const importData = (_d = context.rawInput.installData) === null || _d === void 0 ? void 0 : _d.import;
86
150
  const keepCustomizing = keepsInstalledCustomizing(context);
87
151
  if (keepCustomizing) {
88
152
  const skipped = context.runtime.skippedCust || [];
@@ -97,6 +161,7 @@ exports.generateUpdateTransport = {
97
161
  ? flattenDevclasses(context.runtime.package.hierarchy)
98
162
  : installDevclass.replacements.map(replacement => replacement.installDevclass);
99
163
  },
164
+ incomingImported,
100
165
  prefix: `(${transport_1.Transport.getTransportIcon()} Upgrade cleanup) `,
101
166
  actionName: 'Update',
102
167
  requireDeletion: false,
@@ -107,8 +172,10 @@ exports.generateUpdateTransport = {
107
172
  return incomingCustomizingRows(context);
108
173
  }
109
174
  });
110
- }),
111
- revert: (context) => __awaiter(void 0, void 0, void 0, function* () {
175
+ });
176
+ }
177
+ function revertUpdateCleanup(context) {
178
+ return __awaiter(this, void 0, void 0, function* () {
112
179
  // Rollback of the imported objects ran first: when it failed, they are still on the system
113
180
  // and the previous release must not be restored over them.
114
181
  const restore = !(context.revert.cleanupImported && !context.revert.cleanupSucceeded);
@@ -118,5 +185,5 @@ exports.generateUpdateTransport = {
118
185
  if (restore) {
119
186
  yield (0, utils_1.deleteCleanupStagingPackages)(context);
120
187
  }
121
- })
122
- };
188
+ });
189
+ }
Binary file
@@ -222,6 +222,11 @@ type WorkflowRuntime = {
222
222
  lockedNamespaces?: string[];
223
223
  /** Required SAP entries missing on the system: the install is aborted by check-engines, with the unmet engines. */
224
224
  missingSapEntries?: number;
225
+ /**
226
+ * Upgrade of trm-server or trm-rest: the installed release runs the install itself. Its objects are
227
+ * only cleaned up after the incoming release is imported, and the import can't be rolled back.
228
+ */
229
+ selfUpdate?: boolean;
225
230
  };
226
231
  /** Dependency queued for install: missing from the system, or installed in an incompatible version. */
227
232
  export type InstallDependencyEntry = {
@@ -300,6 +305,11 @@ export type InstallActionOutput = {
300
305
  /** Internal state shared by package-install workflow steps and rollback handlers. */
301
306
  export interface InstallWorkflowContext extends IActionContext {
302
307
  lockScope?: ActionLockScope;
308
+ /**
309
+ * trm-server/trm-rest upgrade on a backend older than the APIs this trm-core needs (see isLegacyTrmBackend):
310
+ * only the APIs of the installed generation are used.
311
+ */
312
+ legacyBackend?: boolean;
303
313
  /** Custom namespaces already locked by the parent installs of a dependency install. */
304
314
  inheritedNamespaceLocks?: string[];
305
315
  /** Original action input; optional groups are normalized during initialization. */
@@ -87,15 +87,18 @@ const installWorkflow = [
87
87
  generateLandscapeTransport_1.generateLandscapeTransport,
88
88
  executePostActivities_1.executePostActivities,
89
89
  releaseLandscapeTransport_1.releaseLandscapeTransport,
90
- updatePackageData_1.updatePackageData
90
+ updatePackageData_1.updatePackageData,
91
+ generateUpdateTransport_1.generatePostImportUpdateTransport
91
92
  ];
92
93
  function runInstall(inputData_1, retainRollback_1) {
93
94
  return __awaiter(this, arguments, void 0, function* (inputData, retainRollback, inheritedNamespaceLocks = []) {
94
95
  var _a;
95
- const lockScope = new utils_2.ActionLockScope(WORKFLOW_NAME);
96
- const context = { rawInput: inputData, lockScope, inheritedNamespaceLocks };
97
96
  //a local artifact is locked under the package it installs, as a remote install of it would be
98
97
  const lockPackage = yield (0, utils_2.resolveInstallPackage)(inputData.packageData.registry, inputData.packageData.name);
98
+ // A legacy backend has no action locks: only trm-server and trm-rest can install there, to upgrade it.
99
+ const legacyBackend = (0, utils_2.isTrmBackendPackage)(lockPackage.registry, lockPackage.name) && (yield (0, utils_2.isLegacyTrmBackend)());
100
+ const lockScope = new utils_2.ActionLockScope(WORKFLOW_NAME, !legacyBackend);
101
+ const context = { rawInput: inputData, lockScope, inheritedNamespaceLocks, legacyBackend };
99
102
  yield lockScope.acquire([(0, utils_2.packageLockResource)(lockPackage.registry, lockPackage.name)]);
100
103
  const release = () => __awaiter(this, void 0, void 0, function* () {
101
104
  var _a;
@@ -225,10 +225,16 @@ exports.init = {
225
225
  //5- check if already installed
226
226
  context.runtime.update = context.rawInput.contextData.systemPackages.find(o => manifest_1.Manifest.compare(o.manifest, new manifest_1.Manifest(context.runtime.package.data.manifest), false));
227
227
  if (context.runtime.update) {
228
+ const installedVersion = context.runtime.update.manifest.get().version;
229
+ // trm-server and trm-rest run this install: see the selfUpdate steps
230
+ context.runtime.selfUpdate = context.runtime.isTrmServer || context.runtime.isTrmRest;
228
231
  context.runtime.previousInstallPackages = yield systemConnector_1.SystemConnector.getInstallPackages(context.rawInput.packageData.name, context.runtime.installRegistry);
229
- context.runtime.previousInstallTransports = yield systemConnector_1.SystemConnector.getInstallTransports(context.rawInput.packageData.name, context.runtime.installRegistry);
232
+ // A legacy backend has no install transports table: trm-server and trm-rest ship
233
+ // no customizing or translations, so there is nothing recorded to read.
234
+ if (!context.legacyBackend) {
235
+ context.runtime.previousInstallTransports = yield systemConnector_1.SystemConnector.getInstallTransports(context.rawInput.packageData.name, context.runtime.installRegistry);
236
+ }
230
237
  const installVersion = context.runtime.package.data.manifest.version;
231
- const installedVersion = context.runtime.update.manifest.get().version;
232
238
  //transports TRM generated for the package (e.g. of a rolled back release) aren't changes made on the system
233
239
  const dirtyEntries = yield (0, utils_1.withoutOwnTrmTransports)(context.runtime.update.getDirtyEntries(), context.runtime.update.packageName);
234
240
  if ((0, semver_1.eq)(installVersion, installedVersion)) {
@@ -266,6 +272,10 @@ exports.init = {
266
272
  trm_commons_1.Logger.warning(`${context.rawInput.packageData.name} has changes made on ${systemConnector_1.SystemConnector.getDest()} that will be overwritten!`, { important: true });
267
273
  (0, utils_1.logDirtyEntries)(dirtyEntries);
268
274
  }
275
+ if (context.runtime.isTrmServer && (0, semver_1.valid)(installedVersion) && (0, semver_1.lt)(installedVersion, utils_1.TRM_SERVER_API_VERSION) && !(0, semver_1.lt)(installVersion, utils_1.TRM_SERVER_API_VERSION)) {
276
+ // the install continues with the imported release: an unauthorized user can't complete it
277
+ trm_commons_1.Logger.warning(`trm-server ${utils_1.TRM_SERVER_API_VERSION} authorizes users with authorization object ZTRM_AUTH (ACTVT 16) instead of table /ATRM/USERS: ${systemConnector_1.SystemConnector.getLogonUser()} and the other TRM users need it.`, { important: true });
278
+ }
269
279
  }
270
280
  }
271
281
  else {
@@ -94,10 +94,15 @@ exports.prepareCust = {
94
94
  trTarget: systemConnector_1.SystemConnector.getDest()
95
95
  });
96
96
  //3- test import transport
97
- trm_commons_1.Logger.loading(`Testing import...`);
98
- const testRc = yield cust.instance.import(true);
99
- if (testRc < 0 || testRc > 8) {
100
- throw new Error(`Test import of customizing failed: check logs.`);
97
+ if (context.legacyBackend) {
98
+ trm_commons_1.Logger.log(`Skipping test import: legacy TRM backend`, true);
99
+ }
100
+ else {
101
+ trm_commons_1.Logger.loading(`Testing import...`);
102
+ const testRc = yield cust.instance.import(true);
103
+ if (testRc < 0 || testRc > 8) {
104
+ throw new Error(`Test import of customizing failed: check logs.`);
105
+ }
101
106
  }
102
107
  }));
103
108
  }
@@ -93,10 +93,15 @@ exports.prepareDevc = {
93
93
  trTarget: systemConnector_1.SystemConnector.getDest()
94
94
  });
95
95
  //4- test import transport
96
- trm_commons_1.Logger.loading(`Testing import...`);
97
- const testRc = yield context.runtime.transports.devc.instance.import(true);
98
- if (testRc < 0 || testRc > 8) {
99
- throw new Error(`Test import of SAP packages failed: check logs.`);
96
+ if (context.legacyBackend) {
97
+ trm_commons_1.Logger.log(`Skipping test import: legacy TRM backend`, true);
98
+ }
99
+ else {
100
+ trm_commons_1.Logger.loading(`Testing import...`);
101
+ const testRc = yield context.runtime.transports.devc.instance.import(true);
102
+ if (testRc < 0 || testRc > 8) {
103
+ throw new Error(`Test import of SAP packages failed: check logs.`);
104
+ }
100
105
  }
101
106
  }));
102
107
  }),
@@ -90,10 +90,15 @@ exports.prepareLang = {
90
90
  trTarget: systemConnector_1.SystemConnector.getDest()
91
91
  });
92
92
  //3- test import transport
93
- trm_commons_1.Logger.loading(`Testing import...`);
94
- const testRc = yield context.runtime.transports.lang.instance.import(true);
95
- if (testRc < 0 || testRc > 8) {
96
- throw new Error(`Test import of translations failed: check logs.`);
93
+ if (context.legacyBackend) {
94
+ trm_commons_1.Logger.log(`Skipping test import: legacy TRM backend`, true);
95
+ }
96
+ else {
97
+ trm_commons_1.Logger.loading(`Testing import...`);
98
+ const testRc = yield context.runtime.transports.lang.instance.import(true);
99
+ if (testRc < 0 || testRc > 8) {
100
+ throw new Error(`Test import of translations failed: check logs.`);
101
+ }
97
102
  }
98
103
  }));
99
104
  }),
@@ -75,10 +75,15 @@ exports.prepareTadir = {
75
75
  trTarget: systemConnector_1.SystemConnector.getDest()
76
76
  });
77
77
  //3- test import transport
78
- trm_commons_1.Logger.loading(`Testing import...`);
79
- const testRc = yield context.runtime.transports.tadir.instance.import(true);
80
- if (testRc < 0 || testRc > 8) {
81
- throw new Error(`Test import of workbench failed: check logs.`);
78
+ if (context.legacyBackend) {
79
+ trm_commons_1.Logger.log(`Skipping test import: legacy TRM backend`, true);
80
+ }
81
+ else {
82
+ trm_commons_1.Logger.loading(`Testing import...`);
83
+ const testRc = yield context.runtime.transports.tadir.instance.import(true);
84
+ if (testRc < 0 || testRc > 8) {
85
+ throw new Error(`Test import of workbench failed: check logs.`);
86
+ }
82
87
  }
83
88
  }));
84
89
  }),
@@ -58,7 +58,21 @@ exports.setInstallDevclass = {
58
58
  }
59
59
  }),
60
60
  run: (context) => __awaiter(void 0, void 0, void 0, function* () {
61
- var _a, _b, _c;
61
+ var _a, _b, _c, _d;
62
+ // trm-server and trm-rest are installed in `$` packages (`$TRM...`), unless they are already in their
63
+ // original `/ATRM/` packages: then they stay there. They are never renamed otherwise.
64
+ if (context.runtime.isTrmServer || context.runtime.isTrmRest) {
65
+ const original = (((_a = context.runtime.update) === null || _a === void 0 ? void 0 : _a.getDevclass()) || '').trim().toUpperCase().startsWith('/ATRM/');
66
+ context.rawInput.installData.installDevclass.replacements = (0, addNamespace_1.flattenDevclasses)(context.runtime.package.hierarchy).map(originalDevclass => ({
67
+ originalDevclass,
68
+ installDevclass: original ? originalDevclass : (0, commons_1.adjustTrmServerRestDevclass)(originalDevclass)
69
+ }));
70
+ context.rawInput.installData.installDevclass.keepOriginal = context.rawInput.installData.installDevclass.replacements
71
+ .every(o => o.installDevclass === o.originalDevclass);
72
+ (0, addNamespace_1.getInstallNamespace)(context);
73
+ trm_commons_1.Logger.log(`${context.rawInput.packageData.name} SAP packages: ${context.rawInput.installData.installDevclass.replacements.map(o => `${o.originalDevclass} -> ${o.installDevclass}`).join(', ')}`, true);
74
+ return;
75
+ }
62
76
  //1- find already defined replacements in system
63
77
  if (context.rawInput.installData.installDevclass.replacements.length <= 0) {
64
78
  //no input replacements = get from the trm table devclass replacements the corresponding name
@@ -78,7 +92,7 @@ exports.setInstallDevclass = {
78
92
  //the release root can have a new original name (e.g. moved to a namespace): a renamed installation keeps its root
79
93
  //package, matched by position, so the installation stays where the customer put it
80
94
  const releaseRoot = context.runtime.package.hierarchy.devclass;
81
- const installedRoot = (_a = context.runtime.update) === null || _a === void 0 ? void 0 : _a.getDevclass();
95
+ const installedRoot = (_b = context.runtime.update) === null || _b === void 0 ? void 0 : _b.getDevclass();
82
96
  if (installedRoot && !context.rawInput.installData.installDevclass.replacements.some(o => o.originalDevclass === releaseRoot)) {
83
97
  const storedRoot = context.rawInput.installData.installDevclass.replacements.find(o => o.installDevclass.trim().toUpperCase() === installedRoot.trim().toUpperCase());
84
98
  if (storedRoot && storedRoot.installDevclass !== storedRoot.originalDevclass) {
@@ -133,7 +147,7 @@ exports.setInstallDevclass = {
133
147
  if (context.runtime.update && hasCustomization) {
134
148
  //the installed root devclass can be unknown: fall back to the stored root replacement
135
149
  const installedRootDevclass = context.runtime.update.getDevclass()
136
- || ((_c = (_b = context.runtime.previousInstallPackages) === null || _b === void 0 ? void 0 : _b.find(o => o.originalDevclass === context.runtime.package.hierarchy.devclass)) === null || _c === void 0 ? void 0 : _c.installDevclass);
150
+ || ((_d = (_c = context.runtime.previousInstallPackages) === null || _c === void 0 ? void 0 : _c.find(o => o.originalDevclass === context.runtime.package.hierarchy.devclass)) === null || _d === void 0 ? void 0 : _d.installDevclass);
137
151
  if (installedRootDevclass) {
138
152
  updateNamespace = (0, commons_1.getPackageNamespace)(installedRootDevclass);
139
153
  }
@@ -1,5 +1,10 @@
1
1
  import { Step } from "@simonegaffurini/sammarksworkflow";
2
2
  import { InstallWorkflowContext } from ".";
3
+ /**
4
+ * Records the installed release: TRM packages table row, install packages and install transports.
5
+ * A trm-server/trm-rest upgrade also records it right after its import, which is never rolled back.
6
+ */
7
+ export declare function recordInstalledRelease(context: InstallWorkflowContext): Promise<void>;
3
8
  /**
4
9
  * Workflow step that records the installed release in the target system's TRM package table.
5
10
  *
@@ -10,6 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.updatePackageData = void 0;
13
+ exports.recordInstalledRelease = recordInstalledRelease;
13
14
  const trm_commons_1 = require("trm-commons");
14
15
  const systemConnector_1 = require("../../systemConnector");
15
16
  const utils_1 = require("../commons/utils");
@@ -33,20 +34,12 @@ function installDevcRows(packageName, packageRegistry, replacements) {
33
34
  }));
34
35
  }
35
36
  /**
36
- * Workflow step that records the installed release in the target system's TRM package table.
37
- *
38
- * Creates/update record in TRM packages table, with its install packages and the
39
- * customizing and translation transports imported on this system
40
- *
41
- * 1- commit new values
42
- *
43
- */
44
- exports.updatePackageData = {
45
- name: 'update-package-data',
46
- run: (context) => __awaiter(void 0, void 0, void 0, function* () {
37
+ * Records the installed release: TRM packages table row, install packages and install transports.
38
+ * A trm-server/trm-rest upgrade also records it right after its import, which is never rolled back.
39
+ */
40
+ function recordInstalledRelease(context) {
41
+ return __awaiter(this, void 0, void 0, function* () {
47
42
  var _a, _b;
48
- trm_commons_1.Logger.loading(`Finalizing install...`);
49
- //1- commit new values
50
43
  trm_commons_1.Logger.loading(`Updating TRM data...`);
51
44
  const originalTransport = context.runtime.transports.tadir.instance.trkorr;
52
45
  const installTransport = (_a = context.output.transport) === null || _a === void 0 ? void 0 : _a.trkorr;
@@ -79,7 +72,8 @@ exports.updatePackageData = {
79
72
  : [])
80
73
  ]);
81
74
  context.revert.metadataPackageRegistry = packageRegistry;
82
- if (context.runtime.update) {
75
+ // A trm-server/trm-rest upgrade is not rolled back after its import: nothing to restore
76
+ if (context.runtime.update && !context.runtime.selfUpdate) {
83
77
  // The installed packages may have been read without their rows: read the stored row
84
78
  // before writing, so the revert restores it exactly, or removes the row it wrote.
85
79
  // A failed read aborts before any write.
@@ -101,18 +95,43 @@ exports.updatePackageData = {
101
95
  // fail while returning the response.
102
96
  context.revert.metadataWriteStarted = true;
103
97
  yield systemConnector_1.SystemConnector.setInstallDevc(installDevc);
104
- if (staleInstallDevc.length > 0) {
105
- yield systemConnector_1.SystemConnector.deleteInstallDevc(staleInstallDevc);
98
+ // A legacy backend has neither API: trm-server and trm-rest have no stale mappings nor install transports
99
+ if (!context.legacyBackend) {
100
+ if (staleInstallDevc.length > 0) {
101
+ yield systemConnector_1.SystemConnector.deleteInstallDevc(staleInstallDevc);
102
+ }
103
+ context.revert.metadataTransportsWriteStarted = true;
104
+ yield systemConnector_1.SystemConnector.setInstallTransports(context.rawInput.packageData.name, packageRegistry, installTr);
106
105
  }
107
- context.revert.metadataTransportsWriteStarted = true;
108
- yield systemConnector_1.SystemConnector.setInstallTransports(context.rawInput.packageData.name, packageRegistry, installTr);
109
106
  yield systemConnector_1.SystemConnector.updateTrmPackageData(context.revert.metadataPackageRow);
107
+ });
108
+ }
109
+ /**
110
+ * Workflow step that records the installed release in the target system's TRM package table.
111
+ *
112
+ * Creates/update record in TRM packages table, with its install packages and the
113
+ * customizing and translation transports imported on this system
114
+ *
115
+ * 1- commit new values
116
+ *
117
+ */
118
+ exports.updatePackageData = {
119
+ name: 'update-package-data',
120
+ run: (context) => __awaiter(void 0, void 0, void 0, function* () {
121
+ trm_commons_1.Logger.loading(`Finalizing install...`);
122
+ //1- commit new values
123
+ yield recordInstalledRelease(context);
110
124
  }),
111
125
  revert: (context) => __awaiter(void 0, void 0, void 0, function* () {
112
126
  // First installs remove the newly written row through the same atomic SAP operation.
113
127
  if (!context.revert.metadataWriteStarted) {
114
128
  return;
115
129
  }
130
+ if (context.runtime.selfUpdate) {
131
+ // The imported release stays installed (see import-batch): its record is kept
132
+ trm_commons_1.Logger.log(`Keeping the TRM data of the imported ${context.rawInput.packageData.name}`, true);
133
+ return;
134
+ }
116
135
  if (!context.runtime.update) {
117
136
  if (context.revert.metadataPackageRow) {
118
137
  yield systemConnector_1.SystemConnector.restoreInstallMetadata({
@@ -510,11 +510,8 @@ class RESTClient {
510
510
  }
511
511
  importTransport(trkorr, system, test) {
512
512
  return __awaiter(this, void 0, void 0, function* () {
513
- const result = (yield this._axiosInstance.post('/import_tr', {
514
- system: system.trim().toUpperCase(),
515
- trkorr: trkorr.trim().toUpperCase(),
516
- test: test ? 'X' : ' '
517
- })).data;
513
+ // as RFC: TEST is only sent for a test import
514
+ const result = (yield this._axiosInstance.post('/import_tr', Object.assign({ system: system.trim().toUpperCase(), trkorr: trkorr.trim().toUpperCase() }, (test ? { test: 'X' } : {})))).data;
518
515
  if (result && result.testResult) {
519
516
  return result.testResult;
520
517
  }
@@ -631,11 +631,9 @@ class RFCClient {
631
631
  }
632
632
  importTransport(trkorr, system, test) {
633
633
  return __awaiter(this, void 0, void 0, function* () {
634
- const result = yield this._call("/ATRM/IMPORT_TR", {
635
- system: system.trim().toUpperCase(),
636
- trkorr: trkorr.trim().toUpperCase(),
637
- test: test ? 'X' : ' '
638
- });
634
+ // TEST is only sent for a test import: releases before trm-server 7 have no such parameter, and
635
+ // their RFC metadata stays cached in this process even after a newer release is imported
636
+ const result = yield this._call("/ATRM/IMPORT_TR", Object.assign({ system: system.trim().toUpperCase(), trkorr: trkorr.trim().toUpperCase() }, (test ? { test: 'X' } : {})));
639
637
  if (result && result.testResult) {
640
638
  return this.parseImportTransportResult(result['testResult'], '/ATRM/IMPORT_TR');
641
639
  }
@@ -430,14 +430,36 @@ class SystemConnectorBase {
430
430
  }
431
431
  getExistingObjects(objects) {
432
432
  return __awaiter(this, void 0, void 0, function* () {
433
- var ret = [];
433
+ // TADIR is read in batches of objects of the same type: one read per object is slow on large packages
434
+ const batchSize = 25;
435
+ const key = (pgmid, object, objName) => `${pgmid.trim().toUpperCase()} ${object.trim().toUpperCase()} ${objName.trim().toUpperCase()}`;
436
+ const found = new Map();
437
+ const singles = [];
438
+ const byType = new Map();
434
439
  for (const object of objects) {
440
+ // an option line holds 72 characters: "OR ( OBJ_NAME EQ '...' )"
441
+ if (object.objName.trim().replace(/'/g, "''").length > 50 || /\s(AND|OR)\s/i.test(object.objName)) {
442
+ singles.push(object);
443
+ continue;
444
+ }
445
+ const type = `${object.pgmid.trim().toUpperCase()} ${object.object.trim().toUpperCase()}`;
446
+ byType.set(type, [...(byType.get(type) || []), object]);
447
+ }
448
+ for (const [type, typeObjects] of byType) {
449
+ const [pgmid, object] = type.split(' ');
450
+ for (let i = 0; i < typeObjects.length; i += batchSize) {
451
+ const names = typeObjects.slice(i, i + batchSize).map(o => o.objName.trim().toUpperCase().replace(/'/g, "''"));
452
+ const tadir = yield this.readTable('TADIR', [{ fieldName: 'PGMID' }, { fieldName: 'OBJECT' }, { fieldName: 'OBJ_NAME' }, { fieldName: 'DEVCLASS' }, { fieldName: 'SRCSYSTEM' }, { fieldName: 'AUTHOR' }], `PGMID EQ '${pgmid}' AND OBJECT EQ '${object}' AND ( ${names.map(name => `OBJ_NAME EQ '${name}'`).join(' OR ')} )`);
453
+ tadir.forEach(o => found.set(key(o.pgmid, o.object, o.objName), o));
454
+ }
455
+ }
456
+ for (const object of singles) {
435
457
  const oTadir = yield this.getObject(object.pgmid, object.object, object.objName);
436
458
  if (oTadir) {
437
- ret.push(oTadir);
459
+ found.set(key(object.pgmid, object.object, object.objName), oTadir);
438
460
  }
439
461
  }
440
- return ret;
462
+ return objects.map(o => found.get(key(o.pgmid, o.object, o.objName))).filter(Boolean);
441
463
  });
442
464
  }
443
465
  getNamespace(namespace) {
@@ -623,6 +623,7 @@ class Transport {
623
623
  var lastCheck;
624
624
  if (this._trTarget) {
625
625
  var inQueueAttempts = 0;
626
+ var queueReadFailures = 0;
626
627
  trm_commons_1.Logger.loading(`Checking ${this.trkorr} status...`, skipLog);
627
628
  while (!inQueue) {
628
629
  inQueueAttempts++;
@@ -644,15 +645,28 @@ class Transport {
644
645
  }
645
646
  }
646
647
  catch (e) {
647
- trm_commons_1.Logger.loading(`Last check ${lastCheck.toISOString().split("T")[0]} ${lastCheck.toTimeString().split(" ")[0]}, last update /: Unable to read status`, skipLog);
648
- trm_commons_1.Logger.error(`Unable to read status!`, true);
648
+ trm_commons_1.Logger.loading(`Last check ${lastCheck.toISOString().split("T")[0]} ${lastCheck.toTimeString().split(" ")[0]}...`, skipLog);
649
+ trm_commons_1.Logger.warning(`Unable to read status: check continues, this error can be ignored`, true);
649
650
  trm_commons_1.Logger.error(e.toString(), true);
650
651
  }
651
652
  }
652
653
  //reading the queue is outdated now and getTransportImportStatus should be used
653
654
  //HOWEVER it doesn't work for VIRTUAL TARGETS!
654
655
  //for now, keep it like this
655
- var tmsQueue = yield systemConnector_1.SystemConnector.readTmsQueue(this._trTarget);
656
+ var tmsQueue;
657
+ try {
658
+ tmsQueue = yield systemConnector_1.SystemConnector.readTmsQueue(this._trTarget);
659
+ queueReadFailures = 0;
660
+ }
661
+ catch (e) {
662
+ // an import can replace the TRM backend itself (trm-server upgrade): its API can
663
+ // fail while it's being imported and activated
664
+ if (++queueReadFailures > 10) {
665
+ throw e;
666
+ }
667
+ trm_commons_1.Logger.log(`Unable to read ${this._trTarget} import queue (attempt ${queueReadFailures}), retrying: ${e}`, true);
668
+ continue;
669
+ }
656
670
  tmsQueue = tmsQueue.filter(o => o.trkorr === this.trkorr);
657
671
  tmsQueue = tmsQueue.sort((a, b) => parseInt(b.bufpos) - parseInt(a.bufpos));
658
672
  if (!checkImpSing) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trm-core",
3
- "version": "10.0.1",
3
+ "version": "10.1.0",
4
4
  "description": "TRM (Transport Request Manager) Core",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",