@mcp-abap-adt/adt-backup 0.1.2 → 1.0.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 (66) hide show
  1. package/README.md +4 -2
  2. package/dist/bin/adt-backup.js +0 -0
  3. package/dist/lib/auth/createTokenProvider.d.ts +1 -1
  4. package/dist/lib/auth/createTokenProvider.d.ts.map +1 -1
  5. package/dist/lib/auth/createTokenProvider.js +2 -1
  6. package/dist/lib/auth/getSapConfigFromBroker.d.ts +1 -0
  7. package/dist/lib/auth/getSapConfigFromBroker.d.ts.map +1 -1
  8. package/dist/lib/auth/getSapConfigFromBroker.js +94 -95
  9. package/dist/lib/backup/backupObject.js +5 -5
  10. package/dist/lib/backup/readMetadataXmlForType.d.ts +1 -1
  11. package/dist/lib/backup/readMetadataXmlForType.d.ts.map +1 -1
  12. package/dist/lib/backup/readMetadataXmlForType.js +113 -98
  13. package/dist/lib/backup/readSourceText.d.ts +1 -1
  14. package/dist/lib/backup/readSourceText.d.ts.map +1 -1
  15. package/dist/lib/backup/readSourceText.js +96 -93
  16. package/dist/lib/cli/createLogger.d.ts.map +1 -1
  17. package/dist/lib/cli/createLogger.js +18 -0
  18. package/dist/lib/cli/parseArgs.d.ts +1 -1
  19. package/dist/lib/cli/parseArgs.d.ts.map +1 -1
  20. package/dist/lib/cli/parseArgs.js +25 -10
  21. package/dist/lib/cli/usage.d.ts.map +1 -1
  22. package/dist/lib/cli/usage.js +92 -88
  23. package/dist/lib/restore/analyzeDependencies.d.ts +13 -0
  24. package/dist/lib/restore/analyzeDependencies.d.ts.map +1 -0
  25. package/dist/lib/restore/analyzeDependencies.js +187 -0
  26. package/dist/lib/restore/restoreObjects.d.ts.map +1 -1
  27. package/dist/lib/restore/restoreObjects.js +49 -10
  28. package/dist/lib/restore/restoreTreeBackup.d.ts +1 -1
  29. package/dist/lib/restore/restoreTreeBackup.d.ts.map +1 -1
  30. package/dist/lib/restore/restoreTreeBackup.js +192 -42
  31. package/dist/lib/restore/restoreTreeNode.d.ts +1 -1
  32. package/dist/lib/restore/restoreTreeNode.d.ts.map +1 -1
  33. package/dist/lib/restore/restoreTreeNode.js +116 -37
  34. package/dist/lib/run.d.ts.map +1 -1
  35. package/dist/lib/run.js +393 -559
  36. package/dist/lib/tree/buildConfigForNode.d.ts.map +1 -1
  37. package/dist/lib/tree/buildConfigForNode.js +11 -0
  38. package/dist/lib/tree/buildPackageBackupTree.d.ts.map +1 -1
  39. package/dist/lib/tree/buildPackageBackupTree.js +9 -3
  40. package/dist/lib/tree/enrichTreeNode.d.ts.map +1 -1
  41. package/dist/lib/tree/enrichTreeNode.js +17 -1
  42. package/dist/lib/tree/isRestoreImplemented.d.ts.map +1 -1
  43. package/dist/lib/tree/isRestoreImplemented.js +1 -0
  44. package/dist/lib/tree/mapAdtTypeToSupported.d.ts.map +1 -1
  45. package/dist/lib/tree/mapAdtTypeToSupported.js +3 -0
  46. package/dist/lib/tree/readPayloadForType.d.ts.map +1 -1
  47. package/dist/lib/tree/readPayloadForType.js +3 -2
  48. package/dist/lib/types.d.ts +22 -2
  49. package/dist/lib/types.d.ts.map +1 -1
  50. package/dist/lib/utils/applyConfigName.d.ts.map +1 -1
  51. package/dist/lib/utils/applyConfigName.js +3 -0
  52. package/dist/lib/utils/parseBdefSource.d.ts +9 -0
  53. package/dist/lib/utils/parseBdefSource.d.ts.map +1 -0
  54. package/dist/lib/utils/parseBdefSource.js +18 -0
  55. package/dist/lib/verify/formatVerifyResultsText.d.ts +1 -1
  56. package/dist/lib/verify/formatVerifyResultsText.d.ts.map +1 -1
  57. package/dist/lib/verify/formatVerifyResultsText.js +76 -14
  58. package/dist/lib/verify/types.d.ts +3 -0
  59. package/dist/lib/verify/types.d.ts.map +1 -1
  60. package/dist/lib/verify/verifyBackup.d.ts +4 -2
  61. package/dist/lib/verify/verifyBackup.d.ts.map +1 -1
  62. package/dist/lib/verify/verifyBackup.js +67 -32
  63. package/dist/lib/verify/verifyObjectInSystem.d.ts +1 -1
  64. package/dist/lib/verify/verifyObjectInSystem.d.ts.map +1 -1
  65. package/dist/lib/verify/verifyObjectInSystem.js +39 -105
  66. 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,EAAE,MAAM,2BAA2B,CAAC;AAE3D,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAI1D,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,CA0Cf"}
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
- const nodeMode = mode === 'upsert' && restoreActions?.has(obj.id)
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
- try {
18
- await (0, restoreObject_1.restoreObject)(client, obj, 'create', effectiveActivate, transportRequest);
19
- }
20
- catch (_error) {
21
- await (0, restoreObject_1.restoreObject)(client, obj, 'update', effectiveActivate, transportRequest);
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
- else {
25
- await (0, restoreObject_1.restoreObject)(client, obj, nodeMode || mode, effectiveActivate, transportRequest);
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;AAK5E,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAI5D,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,GACzB,OAAO,CAAC,IAAI,CAAC,CAsGf"}
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 sortTreeNodesByDependencies_1 = require("./sortTreeNodesByDependencies");
10
- async function restoreTreeBackup(client, root, mode, activate, transportRequest, restoreIds, restoreActions, activateOnCreate = true, softwareComponent) {
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
- (0, logVerbose_1.logVerbose)(2, `Restoring ${orderedNodes.length} node(s) from tree (mode=${mode}, activate=${activate})`);
37
- (0, logVerbose_1.logVerbose)(2, ` Packages: ${packageNodes.length}, Other objects: ${sortedNonPackages.length}`);
38
- const activationList = [];
39
- for (const node of orderedNodes) {
40
- (0, logVerbose_1.logVerbose)(3, `Restore ${node.type}:${node.name}`);
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
- const nodeMode = mode === 'upsert' && nodeId && restoreActions?.has(nodeId)
43
- ? restoreActions.get(nodeId)
44
- : mode;
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
- const isPackage = node.type === 'package';
47
- // Defer activation for non-package objects if activation is requested
48
- const effectiveActivate = isPackage ? shouldActivate : false;
49
- if (nodeMode === 'upsert') {
50
- try {
51
- await (0, restoreTreeNode_1.restoreTreeNode)(client, node, 'create', effectiveActivate, transportRequest, softwareComponent, backupPackageNames);
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
- catch (_error) {
54
- await (0, restoreTreeNode_1.restoreTreeNode)(client, node, 'update', effectiveActivate, transportRequest, softwareComponent, backupPackageNames);
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
- else {
58
- await (0, restoreTreeNode_1.restoreTreeNode)(client, node, nodeMode || mode, effectiveActivate, transportRequest, softwareComponent, backupPackageNames);
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 (!isPackage && shouldActivate && node.adtType) {
61
- activationList.push({ name: node.name, type: node.adtType });
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
- if (activationList.length > 0) {
65
- (0, logVerbose_1.logVerbose)(2, `Activating ${activationList.length} object(s)...`);
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>): Promise<void>;
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,EAmBV,MAAM,2BAA2B,CAAC;AAGnC,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAK5D,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,GAC/B,OAAO,CAAC,IAAI,CAAC,CAkVf"}
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"}