@mcp-abap-adt/adt-backup 0.1.2 → 1.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/README.md +4 -2
- package/dist/bin/adt-backup.js +0 -0
- package/dist/lib/auth/createTokenProvider.d.ts +1 -1
- package/dist/lib/auth/createTokenProvider.d.ts.map +1 -1
- package/dist/lib/auth/createTokenProvider.js +2 -1
- package/dist/lib/auth/getSapConfigFromBroker.d.ts +1 -0
- package/dist/lib/auth/getSapConfigFromBroker.d.ts.map +1 -1
- package/dist/lib/auth/getSapConfigFromBroker.js +94 -95
- package/dist/lib/backup/backupObject.js +5 -5
- package/dist/lib/backup/readMetadataXmlForType.d.ts +1 -1
- package/dist/lib/backup/readMetadataXmlForType.d.ts.map +1 -1
- package/dist/lib/backup/readMetadataXmlForType.js +113 -98
- package/dist/lib/backup/readSourceText.d.ts +1 -1
- package/dist/lib/backup/readSourceText.d.ts.map +1 -1
- package/dist/lib/backup/readSourceText.js +96 -93
- package/dist/lib/cli/createLogger.d.ts.map +1 -1
- package/dist/lib/cli/createLogger.js +18 -0
- package/dist/lib/cli/parseArgs.d.ts +1 -1
- package/dist/lib/cli/parseArgs.d.ts.map +1 -1
- package/dist/lib/cli/parseArgs.js +25 -10
- package/dist/lib/cli/usage.d.ts.map +1 -1
- package/dist/lib/cli/usage.js +92 -88
- package/dist/lib/restore/analyzeDependencies.d.ts +13 -0
- package/dist/lib/restore/analyzeDependencies.d.ts.map +1 -0
- package/dist/lib/restore/analyzeDependencies.js +187 -0
- package/dist/lib/restore/restoreObjects.d.ts.map +1 -1
- package/dist/lib/restore/restoreObjects.js +49 -10
- package/dist/lib/restore/restoreTreeBackup.d.ts +1 -1
- package/dist/lib/restore/restoreTreeBackup.d.ts.map +1 -1
- package/dist/lib/restore/restoreTreeBackup.js +192 -42
- package/dist/lib/restore/restoreTreeNode.d.ts +1 -1
- package/dist/lib/restore/restoreTreeNode.d.ts.map +1 -1
- package/dist/lib/restore/restoreTreeNode.js +116 -37
- package/dist/lib/run.d.ts.map +1 -1
- package/dist/lib/run.js +402 -559
- package/dist/lib/tree/buildConfigForNode.d.ts.map +1 -1
- package/dist/lib/tree/buildConfigForNode.js +11 -0
- package/dist/lib/tree/buildPackageBackupTree.d.ts.map +1 -1
- package/dist/lib/tree/buildPackageBackupTree.js +9 -3
- package/dist/lib/tree/enrichTreeNode.d.ts.map +1 -1
- package/dist/lib/tree/enrichTreeNode.js +17 -1
- package/dist/lib/tree/isRestoreImplemented.d.ts.map +1 -1
- package/dist/lib/tree/isRestoreImplemented.js +1 -0
- package/dist/lib/tree/mapAdtTypeToSupported.d.ts.map +1 -1
- package/dist/lib/tree/mapAdtTypeToSupported.js +3 -0
- package/dist/lib/tree/readPayloadForType.d.ts.map +1 -1
- package/dist/lib/tree/readPayloadForType.js +3 -2
- package/dist/lib/types.d.ts +22 -2
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/utils/applyConfigName.d.ts.map +1 -1
- package/dist/lib/utils/applyConfigName.js +3 -0
- package/dist/lib/utils/parseBdefSource.d.ts +9 -0
- package/dist/lib/utils/parseBdefSource.d.ts.map +1 -0
- package/dist/lib/utils/parseBdefSource.js +18 -0
- package/dist/lib/verify/formatVerifyResultsText.d.ts +1 -1
- package/dist/lib/verify/formatVerifyResultsText.d.ts.map +1 -1
- package/dist/lib/verify/formatVerifyResultsText.js +76 -14
- package/dist/lib/verify/types.d.ts +3 -0
- package/dist/lib/verify/types.d.ts.map +1 -1
- package/dist/lib/verify/verifyBackup.d.ts +4 -2
- package/dist/lib/verify/verifyBackup.d.ts.map +1 -1
- package/dist/lib/verify/verifyBackup.js +67 -32
- package/dist/lib/verify/verifyObjectInSystem.d.ts +1 -1
- package/dist/lib/verify/verifyObjectInSystem.d.ts.map +1 -1
- package/dist/lib/verify/verifyObjectInSystem.js +39 -105
- package/package.json +6 -6
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.analyzeDependencies = analyzeDependencies;
|
|
4
|
+
const decodeBase64_1 = require("../crypto/decodeBase64");
|
|
5
|
+
function nodeKey(node) {
|
|
6
|
+
return `${node.type}:${node.name}`.toUpperCase();
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Robustly analyzes dependencies between objects by scanning both
|
|
10
|
+
* source code and XML metadata.
|
|
11
|
+
* Uses composite type:name keys to handle objects that share the same name
|
|
12
|
+
* (e.g. view and behaviorDefinition for the same CDS entity).
|
|
13
|
+
*/
|
|
14
|
+
function analyzeDependencies(nodes) {
|
|
15
|
+
const idToNode = new Map();
|
|
16
|
+
const nameToIds = new Map();
|
|
17
|
+
const allNames = new Set();
|
|
18
|
+
for (const node of nodes) {
|
|
19
|
+
const id = nodeKey(node);
|
|
20
|
+
const upperName = node.name.toUpperCase();
|
|
21
|
+
idToNode.set(id, node);
|
|
22
|
+
allNames.add(upperName);
|
|
23
|
+
const ids = nameToIds.get(upperName) || [];
|
|
24
|
+
ids.push(id);
|
|
25
|
+
nameToIds.set(upperName, ids);
|
|
26
|
+
}
|
|
27
|
+
const allIds = new Set(idToNode.keys());
|
|
28
|
+
const adj = new Map();
|
|
29
|
+
for (const node of nodes) {
|
|
30
|
+
const deps = new Set();
|
|
31
|
+
const id = nodeKey(node);
|
|
32
|
+
const nodeNameUpper = node.name.toUpperCase();
|
|
33
|
+
if (node.codeBase64) {
|
|
34
|
+
const decoded = (0, decodeBase64_1.decodeBase64)(node.codeBase64);
|
|
35
|
+
const contentUpper = decoded.toUpperCase();
|
|
36
|
+
// 1. Scan for all backup object names in the content
|
|
37
|
+
for (const targetName of allNames) {
|
|
38
|
+
if (targetName === nodeNameUpper)
|
|
39
|
+
continue;
|
|
40
|
+
// Escape name for regex, specifically handling namespaces with /
|
|
41
|
+
const escapedTarget = targetName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
42
|
+
// Match only if surrounded by boundaries that are NOT part of an ABAP name
|
|
43
|
+
// (i.e. not letters, digits, _, or /)
|
|
44
|
+
const regex = new RegExp(`(?<=[^A-Z0-9_/]|^)${escapedTarget}(?=[^A-Z0-9_/]|$)`, 'g');
|
|
45
|
+
if (regex.test(contentUpper)) {
|
|
46
|
+
// Resolve name to all node IDs with that name
|
|
47
|
+
const targetIds = nameToIds.get(targetName) || [];
|
|
48
|
+
for (const tid of targetIds) {
|
|
49
|
+
if (tid !== id)
|
|
50
|
+
deps.add(tid);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// 2. Explicit structural dependencies (from config or XML properties)
|
|
55
|
+
// Behavior Definition <-> Implementation Class (bidirectional = same SCC group)
|
|
56
|
+
// From class source: "FOR BEHAVIOR OF <bdef_name>"
|
|
57
|
+
if (node.type === 'class' || node.type === 'behaviorImplementation') {
|
|
58
|
+
const bdefMatch = contentUpper.match(/FOR\s+BEHAVIOR\s+OF\s+([A-Z0-9_/]+)/);
|
|
59
|
+
if (bdefMatch) {
|
|
60
|
+
const bdefId = `BEHAVIORDEFINITION:${bdefMatch[1]}`;
|
|
61
|
+
if (allIds.has(bdefId) && bdefId !== id)
|
|
62
|
+
deps.add(bdefId);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// From BDEF source: "IMPLEMENTATION IN CLASS <class_name>"
|
|
66
|
+
if (node.type === 'behaviorDefinition') {
|
|
67
|
+
for (const m of contentUpper.matchAll(/IMPLEMENTATION\s+IN\s+CLASS\s+([A-Z0-9_/]+)/g)) {
|
|
68
|
+
const className = m[1];
|
|
69
|
+
const classId = `CLASS:${className}`;
|
|
70
|
+
if (allIds.has(classId))
|
|
71
|
+
deps.add(classId);
|
|
72
|
+
const bimpId = `BEHAVIORIMPLEMENTATION:${className}`;
|
|
73
|
+
if (allIds.has(bimpId))
|
|
74
|
+
deps.add(bimpId);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Behavior Implementation -> Behavior Definition (from config)
|
|
78
|
+
if (node.type === 'behaviorImplementation' &&
|
|
79
|
+
node.config?.behaviorDefinition) {
|
|
80
|
+
const bdef = String(node.config.behaviorDefinition).toUpperCase();
|
|
81
|
+
const bdefId = `BEHAVIORDEFINITION:${bdef}`;
|
|
82
|
+
if (allIds.has(bdefId))
|
|
83
|
+
deps.add(bdefId);
|
|
84
|
+
}
|
|
85
|
+
// Service Binding -> Service Definition
|
|
86
|
+
if (node.type === 'serviceBinding' &&
|
|
87
|
+
node.config?.serviceDefinitionName) {
|
|
88
|
+
const srvd = String(node.config.serviceDefinitionName).toUpperCase();
|
|
89
|
+
const srvdId = `SERVICEDEFINITION:${srvd}`;
|
|
90
|
+
if (allIds.has(srvdId))
|
|
91
|
+
deps.add(srvdId);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
adj.set(id, deps);
|
|
95
|
+
}
|
|
96
|
+
// Tarjan's algorithm for SCCs
|
|
97
|
+
let index = 0;
|
|
98
|
+
const stack = [];
|
|
99
|
+
const onStack = new Set();
|
|
100
|
+
const indices = new Map();
|
|
101
|
+
const lowlink = new Map();
|
|
102
|
+
const sccs = [];
|
|
103
|
+
function strongConnect(v) {
|
|
104
|
+
indices.set(v, index);
|
|
105
|
+
lowlink.set(v, index);
|
|
106
|
+
index++;
|
|
107
|
+
stack.push(v);
|
|
108
|
+
onStack.add(v);
|
|
109
|
+
const deps = adj.get(v) || new Set();
|
|
110
|
+
for (const w of deps) {
|
|
111
|
+
if (!indices.has(w)) {
|
|
112
|
+
strongConnect(w);
|
|
113
|
+
const lowV = lowlink.get(v);
|
|
114
|
+
const lowW = lowlink.get(w);
|
|
115
|
+
if (lowV !== undefined && lowW !== undefined) {
|
|
116
|
+
lowlink.set(v, Math.min(lowV, lowW));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else if (onStack.has(w)) {
|
|
120
|
+
const lowV = lowlink.get(v);
|
|
121
|
+
const idxW = indices.get(w);
|
|
122
|
+
if (lowV !== undefined && idxW !== undefined) {
|
|
123
|
+
lowlink.set(v, Math.min(lowV, idxW));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (lowlink.get(v) === indices.get(v)) {
|
|
128
|
+
const scc = [];
|
|
129
|
+
let w;
|
|
130
|
+
do {
|
|
131
|
+
w = stack.pop() || '';
|
|
132
|
+
onStack.delete(w);
|
|
133
|
+
scc.push(w);
|
|
134
|
+
} while (w !== v);
|
|
135
|
+
sccs.push(scc);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
for (const id of allIds) {
|
|
139
|
+
if (!indices.has(id)) {
|
|
140
|
+
strongConnect(id);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// Dependency graph of SCCs
|
|
144
|
+
const sccAdj = new Map();
|
|
145
|
+
const nodeToSccIndex = new Map();
|
|
146
|
+
sccs.forEach((scc, i) => {
|
|
147
|
+
for (const id of scc) {
|
|
148
|
+
nodeToSccIndex.set(id, i);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
sccs.forEach((scc, i) => {
|
|
152
|
+
const deps = new Set();
|
|
153
|
+
for (const id of scc) {
|
|
154
|
+
const nodeDeps = adj.get(id) || new Set();
|
|
155
|
+
for (const depId of nodeDeps) {
|
|
156
|
+
const depSccIndex = nodeToSccIndex.get(depId);
|
|
157
|
+
if (depSccIndex !== undefined && depSccIndex !== i) {
|
|
158
|
+
deps.add(depSccIndex);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
sccAdj.set(i, deps);
|
|
163
|
+
});
|
|
164
|
+
// Topological sort
|
|
165
|
+
const visited = new Set();
|
|
166
|
+
const order = [];
|
|
167
|
+
function visit(i) {
|
|
168
|
+
if (visited.has(i))
|
|
169
|
+
return;
|
|
170
|
+
visited.add(i);
|
|
171
|
+
const deps = sccAdj.get(i) || new Set();
|
|
172
|
+
for (const d of deps) {
|
|
173
|
+
visit(d);
|
|
174
|
+
}
|
|
175
|
+
order.push(i);
|
|
176
|
+
}
|
|
177
|
+
for (let i = 0; i < sccs.length; i++) {
|
|
178
|
+
visit(i);
|
|
179
|
+
}
|
|
180
|
+
return order.map((i) => {
|
|
181
|
+
const ids = sccs[i];
|
|
182
|
+
return {
|
|
183
|
+
nodes: ids.map((id) => idToNode.get(id)),
|
|
184
|
+
isCircular: ids.length > 1,
|
|
185
|
+
};
|
|
186
|
+
});
|
|
187
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"restoreObjects.d.ts","sourceRoot":"","sources":["../../../src/lib/restore/restoreObjects.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,
|
|
1
|
+
{"version":3,"file":"restoreObjects.d.ts","sourceRoot":"","sources":["../../../src/lib/restore/restoreObjects.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAmB,MAAM,2BAA2B,CAAC;AAE5E,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AA2B1D,wBAAsB,cAAc,CAClC,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,YAAY,EAAE,EACvB,IAAI,EAAE,WAAW,EACjB,gBAAgB,EAAE,OAAO,EACzB,gBAAgB,CAAC,EAAE,MAAM,EACzB,cAAc,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,EACzC,gBAAgB,UAAO,GACtB,OAAO,CAAC,IAAI,CAAC,CA+Df"}
|
|
@@ -2,27 +2,66 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.restoreObjects = restoreObjects;
|
|
4
4
|
const logVerbose_1 = require("../cli/logVerbose");
|
|
5
|
+
const verifyObjectInSystem_1 = require("../verify/verifyObjectInSystem");
|
|
5
6
|
const restoreObject_1 = require("./restoreObject");
|
|
6
7
|
const sortByDependencies_1 = require("./sortByDependencies");
|
|
8
|
+
const ADT_TYPE_MAP = {
|
|
9
|
+
package: 'DEVC/K',
|
|
10
|
+
domain: 'DOMA/DT',
|
|
11
|
+
dataElement: 'DTEL/DE',
|
|
12
|
+
structure: 'TABL/DT',
|
|
13
|
+
table: 'TABL/DT',
|
|
14
|
+
tableType: 'TTYP/DT',
|
|
15
|
+
view: 'VIEW/DV',
|
|
16
|
+
class: 'CLAS/OC',
|
|
17
|
+
interface: 'INTF/OI',
|
|
18
|
+
program: 'PROG/P',
|
|
19
|
+
functionGroup: 'FUGR/FF',
|
|
20
|
+
functionModule: 'FUGR/I',
|
|
21
|
+
serviceDefinition: 'SRVD/SRV',
|
|
22
|
+
serviceBinding: 'SRVB/SRV',
|
|
23
|
+
metadataExtension: 'DDLX/PX',
|
|
24
|
+
behaviorDefinition: 'BDEF/B',
|
|
25
|
+
behaviorImplementation: 'CLAS/OC',
|
|
26
|
+
enhancement: 'ENHO/EI',
|
|
27
|
+
accessControl: 'DCLS/DL',
|
|
28
|
+
};
|
|
7
29
|
async function restoreObjects(client, objects, mode, activateOnUpdate, transportRequest, restoreActions, activateOnCreate = true) {
|
|
8
30
|
const ordered = (0, sortByDependencies_1.sortByDependencies)(objects);
|
|
9
31
|
(0, logVerbose_1.logVerbose)(2, `Restoring ${ordered.length} object(s) in flat mode (mode=${mode}, activate=${activateOnUpdate})`);
|
|
32
|
+
const activationList = [];
|
|
10
33
|
for (const obj of ordered) {
|
|
11
34
|
(0, logVerbose_1.logVerbose)(3, `Restore ${obj.type}:${obj.name}`);
|
|
12
|
-
|
|
35
|
+
let nodeMode = mode === 'upsert' && restoreActions?.has(obj.id)
|
|
13
36
|
? restoreActions.get(obj.id)
|
|
14
37
|
: mode;
|
|
15
|
-
const effectiveActivate = nodeMode === 'create' ? activateOnCreate : activateOnUpdate;
|
|
16
38
|
if (nodeMode === 'upsert') {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
39
|
+
const verifyResult = await (0, verifyObjectInSystem_1.verifyObjectInSystem)(client, {
|
|
40
|
+
type: obj.type,
|
|
41
|
+
name: obj.name,
|
|
42
|
+
functionGroupName: obj.functionGroupName,
|
|
43
|
+
});
|
|
44
|
+
nodeMode = verifyResult.status === 'missing' ? 'create' : 'update';
|
|
23
45
|
}
|
|
24
|
-
|
|
25
|
-
|
|
46
|
+
const isPackage = obj.type === 'package';
|
|
47
|
+
const shouldActivate = nodeMode === 'create' ? activateOnCreate : activateOnUpdate;
|
|
48
|
+
// Defer activation for non-package objects
|
|
49
|
+
const effectiveActivate = isPackage ? shouldActivate : false;
|
|
50
|
+
await (0, restoreObject_1.restoreObject)(client, obj, nodeMode || mode, effectiveActivate, transportRequest);
|
|
51
|
+
if (!isPackage && shouldActivate && ADT_TYPE_MAP[obj.type]) {
|
|
52
|
+
activationList.push({ name: obj.name, type: ADT_TYPE_MAP[obj.type] });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (activationList.length > 0) {
|
|
56
|
+
(0, logVerbose_1.logVerbose)(2, `Activating ${activationList.length} object(s) in group...`);
|
|
57
|
+
try {
|
|
58
|
+
await client.getUtils().activateObjectsGroup(activationList, true);
|
|
59
|
+
(0, logVerbose_1.logVerbose)(2, 'Group activation completed successfully.');
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
(0, logVerbose_1.logVerbose)(1, 'Group activation failed (this is common due to complex dependencies).');
|
|
63
|
+
(0, logVerbose_1.logVerbose)(1, `Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
64
|
+
(0, logVerbose_1.logVerbose)(1, 'Objects are restored but might remain inactive. Please check and activate them manually in ADT.');
|
|
26
65
|
}
|
|
27
66
|
}
|
|
28
67
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { AdtClient } from '@mcp-abap-adt/adt-clients';
|
|
2
2
|
import type { BackupTreeNode, RestoreMode } from '../types';
|
|
3
|
-
export declare function restoreTreeBackup(client: AdtClient, root: BackupTreeNode, mode: RestoreMode, activate: boolean, transportRequest?: string, restoreIds?: Set<string>, restoreActions?: Map<string, RestoreMode>, activateOnCreate?: boolean, softwareComponent?: string): Promise<void>;
|
|
3
|
+
export declare function restoreTreeBackup(client: AdtClient, root: BackupTreeNode, mode: RestoreMode, activate: boolean, transportRequest?: string, restoreIds?: Set<string>, restoreActions?: Map<string, RestoreMode>, activateOnCreate?: boolean, softwareComponent?: string, superPackageOverride?: string, transportLayer?: string): Promise<void>;
|
|
4
4
|
//# sourceMappingURL=restoreTreeBackup.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"restoreTreeBackup.d.ts","sourceRoot":"","sources":["../../../src/lib/restore/restoreTreeBackup.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAmB,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"restoreTreeBackup.d.ts","sourceRoot":"","sources":["../../../src/lib/restore/restoreTreeBackup.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAmB,MAAM,2BAA2B,CAAC;AAI5E,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAiB,MAAM,UAAU,CAAC;AAwD3E,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,cAAc,EACpB,IAAI,EAAE,WAAW,EACjB,QAAQ,EAAE,OAAO,EACjB,gBAAgB,CAAC,EAAE,MAAM,EACzB,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EACxB,cAAc,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,EACzC,gBAAgB,UAAO,EACvB,iBAAiB,CAAC,EAAE,MAAM,EAC1B,oBAAoB,CAAC,EAAE,MAAM,EAC7B,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,IAAI,CAAC,CA2Pf"}
|
|
@@ -2,12 +2,50 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.restoreTreeBackup = restoreTreeBackup;
|
|
4
4
|
const logVerbose_1 = require("../cli/logVerbose");
|
|
5
|
-
const typeOrder_1 = require("../constants/typeOrder");
|
|
6
5
|
const flattenTree_1 = require("../tree/flattenTree");
|
|
7
6
|
const getNodeObjectId_1 = require("../tree/getNodeObjectId");
|
|
7
|
+
const analyzeDependencies_1 = require("./analyzeDependencies");
|
|
8
8
|
const restoreTreeNode_1 = require("./restoreTreeNode");
|
|
9
|
-
const
|
|
10
|
-
|
|
9
|
+
const RESTORE_PHASES = [
|
|
10
|
+
{ name: 'Domains', types: ['domain'], activation: 'individual' },
|
|
11
|
+
{ name: 'Data Elements', types: ['dataElement'], activation: 'individual' },
|
|
12
|
+
{ name: 'Structures', types: ['structure'], activation: 'individual' },
|
|
13
|
+
{ name: 'Tables', types: ['table'], activation: 'individual' },
|
|
14
|
+
{ name: 'Table Types', types: ['tableType'], activation: 'individual' },
|
|
15
|
+
{ name: 'CDS Views', types: ['view'], activation: 'cluster' },
|
|
16
|
+
{
|
|
17
|
+
name: 'Behavior',
|
|
18
|
+
types: ['behaviorDefinition', 'behaviorImplementation'],
|
|
19
|
+
activation: 'bulk',
|
|
20
|
+
},
|
|
21
|
+
{ name: 'Classes', types: ['class'], activation: 'individual' },
|
|
22
|
+
{ name: 'Interfaces', types: ['interface'], activation: 'individual' },
|
|
23
|
+
{ name: 'Programs', types: ['program'], activation: 'individual' },
|
|
24
|
+
{
|
|
25
|
+
name: 'Function Groups',
|
|
26
|
+
types: ['functionGroup'],
|
|
27
|
+
activation: 'individual',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'Function Modules',
|
|
31
|
+
types: ['functionModule'],
|
|
32
|
+
activation: 'individual',
|
|
33
|
+
},
|
|
34
|
+
{ name: 'Access Control', types: ['accessControl'], activation: 'bulk' },
|
|
35
|
+
{
|
|
36
|
+
name: 'Metadata Extensions',
|
|
37
|
+
types: ['metadataExtension'],
|
|
38
|
+
activation: 'bulk',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'Service Definitions',
|
|
42
|
+
types: ['serviceDefinition'],
|
|
43
|
+
activation: 'bulk',
|
|
44
|
+
},
|
|
45
|
+
{ name: 'Service Bindings', types: ['serviceBinding'], activation: 'bulk' },
|
|
46
|
+
{ name: 'Enhancements', types: ['enhancement'], activation: 'individual' },
|
|
47
|
+
];
|
|
48
|
+
async function restoreTreeBackup(client, root, mode, activate, transportRequest, restoreIds, restoreActions, activateOnCreate = true, softwareComponent, superPackageOverride, transportLayer) {
|
|
11
49
|
const allNodes = (0, flattenTree_1.flattenTree)(root).filter((node) => node.type && node.restoreStatus === 'ok');
|
|
12
50
|
const nodes = restoreIds
|
|
13
51
|
? allNodes.filter((node) => {
|
|
@@ -15,55 +53,167 @@ async function restoreTreeBackup(client, root, mode, activate, transportRequest,
|
|
|
15
53
|
return id ? restoreIds.has(id) : false;
|
|
16
54
|
})
|
|
17
55
|
: allNodes;
|
|
18
|
-
// Separate packages from non-packages
|
|
19
|
-
// Packages keep their tree order (parent before children)
|
|
20
56
|
const packageNodes = nodes.filter((node) => node.type === 'package');
|
|
21
57
|
const nonPackageNodes = nodes.filter((node) => node.type !== 'package');
|
|
22
|
-
// Sort non-packages by dependencies or typeOrder
|
|
23
|
-
const priority = new Map(typeOrder_1.typeOrder.map((type, index) => [type, index]));
|
|
24
|
-
const hasDependencies = nonPackageNodes.some((node) => Array.isArray(node.usedBy) && node.usedBy.length > 0);
|
|
25
|
-
const sortedNonPackages = hasDependencies
|
|
26
|
-
? (0, sortTreeNodesByDependencies_1.sortTreeNodesByDependencies)(nonPackageNodes)
|
|
27
|
-
: nonPackageNodes.sort((a, b) => {
|
|
28
|
-
const aOrder = a.type ? (priority.get(a.type) ?? 999) : 999;
|
|
29
|
-
const bOrder = b.type ? (priority.get(b.type) ?? 999) : 999;
|
|
30
|
-
return aOrder - bOrder || a.name.localeCompare(b.name);
|
|
31
|
-
});
|
|
32
|
-
// Packages first (in tree order), then sorted non-packages
|
|
33
|
-
const orderedNodes = [...packageNodes, ...sortedNonPackages];
|
|
34
|
-
// Collect package names from backup tree for inheritance logic
|
|
35
58
|
const backupPackageNames = new Set(packageNodes.map((node) => node.name));
|
|
36
|
-
|
|
37
|
-
(0, logVerbose_1.logVerbose)(
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
59
|
+
const rootPackageName = root.name;
|
|
60
|
+
(0, logVerbose_1.logVerbose)(1, `\n>>> STARTING TYPE-PHASE RESTORE: ${nodes.length} objects`);
|
|
61
|
+
const failures = [];
|
|
62
|
+
// Phase 1: Packages (recursive hierarchy)
|
|
63
|
+
if (packageNodes.length > 0) {
|
|
64
|
+
(0, logVerbose_1.logVerbose)(1, '[PHASE 1] Restoring package hierarchy...');
|
|
65
|
+
const restorePackageRecursive = async (node, parentName) => {
|
|
66
|
+
const nodeId = (0, getNodeObjectId_1.getNodeObjectId)(node);
|
|
67
|
+
if (node.type === 'package' &&
|
|
68
|
+
nodeId &&
|
|
69
|
+
(!restoreIds || restoreIds.has(nodeId))) {
|
|
70
|
+
const isRootNode = node.name === rootPackageName;
|
|
71
|
+
const nodeMode = (restoreActions?.get(nodeId) || mode);
|
|
72
|
+
const effectiveMode = isRootNode ? 'update' : nodeMode;
|
|
73
|
+
if (effectiveMode === 'skip') {
|
|
74
|
+
(0, logVerbose_1.logVerbose)(2, ` [SKIP] package:${node.name}`);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
(0, logVerbose_1.logVerbose)(2, ` [PACKAGE] ${node.name}`);
|
|
78
|
+
try {
|
|
79
|
+
await (0, restoreTreeNode_1.restoreTreeNode)(client, node, effectiveMode, false, transportRequest, softwareComponent, backupPackageNames, parentName || superPackageOverride, transportLayer);
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
if (isRootNode) {
|
|
83
|
+
(0, logVerbose_1.logVerbose)(1, ` ! Warning: Root package ${node.name} already exists or update skipped.`);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
throw e;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (node.children) {
|
|
92
|
+
for (const child of node.children) {
|
|
93
|
+
await restorePackageRecursive(child, node.type === 'package' ? node.name : parentName);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
await restorePackageRecursive(root, undefined);
|
|
98
|
+
}
|
|
99
|
+
// Phase 2: Analyze dependencies for processing order
|
|
100
|
+
(0, logVerbose_1.logVerbose)(1, `[PHASE 2] Analyzing dependencies for ${nonPackageNodes.length} objects...`);
|
|
101
|
+
const restoreGroups = (0, analyzeDependencies_1.analyzeDependencies)(nonPackageNodes);
|
|
102
|
+
const orderedNodes = restoreGroups.flatMap((g) => g.nodes);
|
|
103
|
+
(0, logVerbose_1.logVerbose)(1, `Dependency analysis complete: ${restoreGroups.length} groups → ${orderedNodes.length} ordered nodes.`);
|
|
104
|
+
// Helper to bulk activate a list of refs
|
|
105
|
+
const bulkActivate = async (phaseName, refs) => {
|
|
106
|
+
if (refs.length === 0)
|
|
107
|
+
return;
|
|
108
|
+
(0, logVerbose_1.logVerbose)(2, ` [*] Bulk activating ${phaseName} (${refs.length} objects)...`);
|
|
109
|
+
try {
|
|
110
|
+
await client.getUtils().activateObjectsGroup(refs, true);
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
114
|
+
(0, logVerbose_1.logVerbose)(1, ` [!] WARNING: ${phaseName} activation failed: ${message}`);
|
|
115
|
+
(0, logVerbose_1.logVerbose)(1, ' Objects are created but might be inactive. Continuing...');
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
// Phase 3: Process by granular type phases with per-type activation strategy
|
|
119
|
+
const knownTypes = new Set(RESTORE_PHASES.flatMap((p) => p.types));
|
|
120
|
+
const uncategorizedNodes = orderedNodes.filter((n) => n.type && !knownTypes.has(n.type) && n.type !== 'package');
|
|
121
|
+
// Helper: process a single node (create/update without activation)
|
|
122
|
+
const processNode = async (node, activateFlag) => {
|
|
41
123
|
const nodeId = (0, getNodeObjectId_1.getNodeObjectId)(node);
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
124
|
+
if (!nodeId)
|
|
125
|
+
return null;
|
|
126
|
+
const nodeMode = (restoreActions?.get(nodeId) || mode);
|
|
127
|
+
if (nodeMode === 'skip') {
|
|
128
|
+
(0, logVerbose_1.logVerbose)(2, ` [SKIP] ${node.type}:${node.name}`);
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
45
131
|
const shouldActivate = nodeMode === 'create' ? activateOnCreate : activate;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
132
|
+
(0, logVerbose_1.logVerbose)(2, ` -> Process [${node.type?.toUpperCase()}] ${node.name} (${nodeMode})`);
|
|
133
|
+
try {
|
|
134
|
+
await (0, restoreTreeNode_1.restoreTreeNode)(client, node, nodeMode, activateFlag, transportRequest, softwareComponent, backupPackageNames, undefined, transportLayer);
|
|
135
|
+
if (shouldActivate && node.adtType) {
|
|
136
|
+
return { name: node.name, type: node.adtType };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
141
|
+
if (message.includes('status code 403')) {
|
|
142
|
+
(0, logVerbose_1.logVerbose)(1, ` [SKIP] ${node.type}:${node.name} — no authorization`);
|
|
52
143
|
}
|
|
53
|
-
|
|
54
|
-
|
|
144
|
+
else {
|
|
145
|
+
(0, logVerbose_1.logVerbose)(1, ` [FAIL] ${node.type}:${node.name} — ${message}`);
|
|
146
|
+
failures.push({ node, error: message });
|
|
55
147
|
}
|
|
56
148
|
}
|
|
57
|
-
|
|
58
|
-
|
|
149
|
+
return null;
|
|
150
|
+
};
|
|
151
|
+
const allProcessedRefs = [];
|
|
152
|
+
for (const phase of RESTORE_PHASES) {
|
|
153
|
+
const phaseTypeSet = new Set(phase.types);
|
|
154
|
+
const phaseNodes = orderedNodes.filter((n) => n.type && phaseTypeSet.has(n.type));
|
|
155
|
+
if (phaseNodes.length === 0)
|
|
156
|
+
continue;
|
|
157
|
+
(0, logVerbose_1.logVerbose)(1, `[${phase.name.toUpperCase()}] Processing ${phaseNodes.length} object(s) (${phase.activation})...`);
|
|
158
|
+
if (phase.activation === 'individual') {
|
|
159
|
+
// Activate each object at creation/update time
|
|
160
|
+
for (const node of phaseNodes) {
|
|
161
|
+
const ref = await processNode(node, true);
|
|
162
|
+
if (ref)
|
|
163
|
+
allProcessedRefs.push(ref);
|
|
164
|
+
}
|
|
59
165
|
}
|
|
60
|
-
if (
|
|
61
|
-
|
|
166
|
+
else if (phase.activation === 'bulk') {
|
|
167
|
+
// Create/update all without activation, then bulk activate together
|
|
168
|
+
const refs = [];
|
|
169
|
+
for (const node of phaseNodes) {
|
|
170
|
+
const ref = await processNode(node, false);
|
|
171
|
+
if (ref)
|
|
172
|
+
refs.push(ref);
|
|
173
|
+
}
|
|
174
|
+
await bulkActivate(phase.name, refs);
|
|
175
|
+
allProcessedRefs.push(...refs);
|
|
176
|
+
}
|
|
177
|
+
else if (phase.activation === 'cluster') {
|
|
178
|
+
// Cluster by dependencies (SCC groups), bulk activate per cluster
|
|
179
|
+
const groups = (0, analyzeDependencies_1.analyzeDependencies)(phaseNodes);
|
|
180
|
+
(0, logVerbose_1.logVerbose)(2, ` Dependency clustering: ${groups.length} cluster(s)`);
|
|
181
|
+
for (let gi = 0; gi < groups.length; gi++) {
|
|
182
|
+
const group = groups[gi];
|
|
183
|
+
const clusterRefs = [];
|
|
184
|
+
for (const node of group.nodes) {
|
|
185
|
+
const ref = await processNode(node, false);
|
|
186
|
+
if (ref)
|
|
187
|
+
clusterRefs.push(ref);
|
|
188
|
+
}
|
|
189
|
+
if (clusterRefs.length > 0) {
|
|
190
|
+
await bulkActivate(`${phase.name} cluster ${gi + 1}/${groups.length}${group.isCircular ? ' (circular)' : ''}`, clusterRefs);
|
|
191
|
+
}
|
|
192
|
+
allProcessedRefs.push(...clusterRefs);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// Uncategorized types (future types not in RESTORE_PHASES) — individual activation
|
|
197
|
+
if (uncategorizedNodes.length > 0) {
|
|
198
|
+
(0, logVerbose_1.logVerbose)(1, `[OTHER] Processing ${uncategorizedNodes.length} uncategorized object(s) (individual)...`);
|
|
199
|
+
for (const node of uncategorizedNodes) {
|
|
200
|
+
const ref = await processNode(node, true);
|
|
201
|
+
if (ref)
|
|
202
|
+
allProcessedRefs.push(ref);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// Final activation sweep — safety net for objects left inactive
|
|
206
|
+
if (allProcessedRefs.length > 0) {
|
|
207
|
+
(0, logVerbose_1.logVerbose)(1, `[FINAL] Activation sweep (${allProcessedRefs.length} objects)...`);
|
|
208
|
+
await bulkActivate('Final sweep', allProcessedRefs);
|
|
209
|
+
}
|
|
210
|
+
if (failures.length > 0) {
|
|
211
|
+
(0, logVerbose_1.logVerbose)(1, `\n>>> RESTORE COMPLETED WITH ${failures.length} FAILURE(S):`);
|
|
212
|
+
for (const f of failures) {
|
|
213
|
+
(0, logVerbose_1.logVerbose)(1, ` - ${f.node.type}:${f.node.name}: ${f.error}`);
|
|
62
214
|
}
|
|
63
215
|
}
|
|
64
|
-
|
|
65
|
-
(0, logVerbose_1.logVerbose)(
|
|
66
|
-
// Pass true for preauditRequested to check before activating
|
|
67
|
-
await client.getUtils().activateObjectsGroup(activationList, true);
|
|
216
|
+
else {
|
|
217
|
+
(0, logVerbose_1.logVerbose)(1, '\n>>> RESTORE COMPLETED SUCCESSFULLY.');
|
|
68
218
|
}
|
|
69
219
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { AdtClient } from '@mcp-abap-adt/adt-clients';
|
|
2
2
|
import type { BackupTreeNode, RestoreMode } from '../types';
|
|
3
|
-
export declare function restoreTreeNode(client: AdtClient, node: BackupTreeNode, mode: RestoreMode, activate: boolean, transportRequest?: string, softwareComponentOverride?: string, backupPackageNames?: Set<string
|
|
3
|
+
export declare function restoreTreeNode(client: AdtClient, node: BackupTreeNode, mode: RestoreMode, activate: boolean, transportRequest?: string, softwareComponentOverride?: string, backupPackageNames?: Set<string>, superPackageOverride?: string, transportLayerOverride?: string): Promise<void>;
|
|
4
4
|
//# sourceMappingURL=restoreTreeNode.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"restoreTreeNode.d.ts","sourceRoot":"","sources":["../../../src/lib/restore/restoreTreeNode.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,
|
|
1
|
+
{"version":3,"file":"restoreTreeNode.d.ts","sourceRoot":"","sources":["../../../src/lib/restore/restoreTreeNode.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EAoBV,MAAM,2BAA2B,CAAC;AAGnC,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAO5D,wBAAsB,eAAe,CACnC,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,cAAc,EACpB,IAAI,EAAE,WAAW,EACjB,QAAQ,EAAE,OAAO,EACjB,gBAAgB,CAAC,EAAE,MAAM,EACzB,yBAAyB,CAAC,EAAE,MAAM,EAClC,kBAAkB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EAChC,oBAAoB,CAAC,EAAE,MAAM,EAC7B,sBAAsB,CAAC,EAAE,MAAM,GAC9B,OAAO,CAAC,IAAI,CAAC,CA8Zf"}
|