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.
- package/changelog.txt +27 -3
- package/dist/actions/checkEngines/analyze.js +6 -0
- package/dist/actions/checkSapEntries/analyze.d.ts +2 -0
- package/dist/actions/checkSapEntries/analyze.js +100 -18
- package/dist/actions/commons/setSystemPackages.js +1 -1
- package/dist/actions/commons/utils/actionLocks.d.ts +2 -1
- package/dist/actions/commons/utils/actionLocks.js +8 -1
- package/dist/actions/commons/utils/index.d.ts +1 -0
- package/dist/actions/commons/utils/index.js +1 -0
- package/dist/actions/commons/utils/packageCleanup.d.ts +8 -0
- package/dist/actions/commons/utils/packageCleanup.js +59 -10
- package/dist/actions/commons/utils/releaseDeletionTransport.js +11 -4
- package/dist/actions/commons/utils/trmBackend.d.ts +20 -0
- package/dist/actions/commons/utils/trmBackend.js +64 -0
- package/dist/actions/install/checkSapEntries.js +1 -1
- package/dist/actions/install/checkTransports.js +40 -1
- package/dist/actions/install/generateLandscapeTransport.js +4 -1
- package/dist/actions/install/generateUpdateTransport.d.ts +7 -0
- package/dist/actions/install/generateUpdateTransport.js +75 -8
- package/dist/actions/install/importBatch.js +0 -0
- package/dist/actions/install/index.d.ts +10 -0
- package/dist/actions/install/index.js +6 -3
- package/dist/actions/install/init.js +12 -2
- package/dist/actions/install/prepareCust.js +9 -4
- package/dist/actions/install/prepareDevc.js +9 -4
- package/dist/actions/install/prepareLang.js +9 -4
- package/dist/actions/install/prepareTadir.js +9 -4
- package/dist/actions/install/setInstallDevclass.js +17 -3
- package/dist/actions/install/updatePackageData.d.ts +5 -0
- package/dist/actions/install/updatePackageData.js +37 -18
- package/dist/client/RESTClient.js +6 -5
- package/dist/client/RFCClient.js +3 -5
- package/dist/systemConnector/SystemConnectorBase.js +25 -3
- package/dist/transport/Transport.js +17 -3
- package/package.json +1 -1
package/changelog.txt
CHANGED
|
@@ -8,10 +8,34 @@ Legend
|
|
|
8
8
|
+ : added
|
|
9
9
|
- : removed
|
|
10
10
|
|
|
11
|
-
2026-
|
|
11
|
+
2026-10-12 v10.1.0
|
|
12
12
|
-------------------
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
171
|
+
requiredEntries.forEach(tableEntry => {
|
|
84
172
|
context.runtime.entriesStatus.bad.push({
|
|
85
173
|
table,
|
|
86
174
|
tableEntry
|
|
87
175
|
});
|
|
88
176
|
});
|
|
89
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
121
|
-
|
|
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
|
|
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
|
}
|
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
if (
|
|
56
|
-
|
|
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
|
+
}
|