@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
package/dist/lib/run.js
CHANGED
|
@@ -51,7 +51,6 @@ const createLogger_1 = require("./cli/createLogger");
|
|
|
51
51
|
const getVerbosity_1 = require("./cli/getVerbosity");
|
|
52
52
|
const logVerbose_1 = require("./cli/logVerbose");
|
|
53
53
|
const parseArgs_1 = require("./cli/parseArgs");
|
|
54
|
-
const redact_1 = require("./cli/redact");
|
|
55
54
|
const shouldEnableAdtLogger_1 = require("./cli/shouldEnableAdtLogger");
|
|
56
55
|
const shouldEnableConnectionLogger_1 = require("./cli/shouldEnableConnectionLogger");
|
|
57
56
|
const usage_1 = require("./cli/usage");
|
|
@@ -62,31 +61,28 @@ const encodeBase64_1 = require("./crypto/encodeBase64");
|
|
|
62
61
|
const updateTreeChecksums_1 = require("./crypto/updateTreeChecksums");
|
|
63
62
|
const verifyBackupChecksum_1 = require("./crypto/verifyBackupChecksum");
|
|
64
63
|
const verifyTreeChecksums_1 = require("./crypto/verifyTreeChecksums");
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
const restoreObjects_1 = require("./restore/restoreObjects");
|
|
64
|
+
const collectTreeDependencies_1 = require("./dependencies/collectTreeDependencies");
|
|
65
|
+
const analyzeDependencies_1 = require("./restore/analyzeDependencies");
|
|
68
66
|
const restoreTreeBackup_1 = require("./restore/restoreTreeBackup");
|
|
69
67
|
const verbosity_1 = require("./state/verbosity");
|
|
70
68
|
const buildPackageBackupTree_1 = require("./tree/buildPackageBackupTree");
|
|
71
|
-
const
|
|
72
|
-
const collectTreeObjects_1 = require("./tree/collectTreeObjects");
|
|
69
|
+
const enrichTreeNode_1 = require("./tree/enrichTreeNode");
|
|
73
70
|
const findNodeInTree_1 = require("./tree/findNodeInTree");
|
|
74
|
-
const
|
|
75
|
-
const
|
|
71
|
+
const flattenTree_1 = require("./tree/flattenTree");
|
|
72
|
+
const getNodeObjectId_1 = require("./tree/getNodeObjectId");
|
|
76
73
|
const diffUnified_1 = require("./utils/diffUnified");
|
|
77
74
|
const formatObjectSpec_1 = require("./utils/formatObjectSpec");
|
|
78
75
|
const parseObjectSpec_1 = require("./utils/parseObjectSpec");
|
|
79
|
-
const collectBackupNodes_1 = require("./verify/collectBackupNodes");
|
|
80
|
-
const findOtherType_1 = require("./verify/findOtherType");
|
|
81
76
|
const formatVerifyResultsText_1 = require("./verify/formatVerifyResultsText");
|
|
82
77
|
const verifyBackup_1 = require("./verify/verifyBackup");
|
|
83
78
|
const extractMetadata_1 = require("./xml/extractMetadata");
|
|
84
79
|
async function run() {
|
|
85
80
|
const argv = process.argv.slice(2);
|
|
86
|
-
|
|
81
|
+
const args = (0, parseArgs_1.parseArgs)(argv.slice(1));
|
|
82
|
+
verbosity_1.verbosityState.level =
|
|
83
|
+
typeof args.verbosity === 'number' ? args.verbosity : (0, getVerbosity_1.getVerbosity)(argv);
|
|
87
84
|
const logger = (0, createLogger_1.createLogger)(verbosity_1.verbosityState.level);
|
|
88
85
|
const command = argv[0];
|
|
89
|
-
const args = (0, parseArgs_1.parseArgs)(argv.slice(1));
|
|
90
86
|
const logFile = typeof args['log-file'] === 'string' ? args['log-file'] : '';
|
|
91
87
|
if (logFile) {
|
|
92
88
|
enableLogFile(logFile);
|
|
@@ -110,639 +106,486 @@ async function run() {
|
|
|
110
106
|
console.log((0, usage_1.usage)(helpCommand));
|
|
111
107
|
process.exit(0);
|
|
112
108
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
throw new Error('Missing --object');
|
|
122
|
-
}
|
|
123
|
-
if (typeof output !== 'string') {
|
|
124
|
-
throw new Error('Missing --out');
|
|
125
|
-
}
|
|
126
|
-
(0, logVerbose_1.logVerbose)(2, `Extracting ${objectSpec} from ${input}`);
|
|
127
|
-
const raw = fs.readFileSync(input, 'utf8');
|
|
128
|
-
const parsed = yaml_1.default.parse(raw);
|
|
129
|
-
if (!parsed || parsed.schemaVersion !== 2) {
|
|
130
|
-
throw new Error('Extract supports only schemaVersion 2 backups');
|
|
131
|
-
}
|
|
132
|
-
(0, verifyBackupChecksum_1.verifyBackupChecksum)(parsed);
|
|
133
|
-
(0, verifyTreeChecksums_1.verifyTreeChecksums)(parsed.root);
|
|
134
|
-
const spec = (0, parseObjectSpec_1.parseObjectSpec)(objectSpec);
|
|
135
|
-
(0, logVerbose_1.logVerbose)(3, `Parsed object spec: ${spec.type}:${spec.name}`);
|
|
136
|
-
const node = (0, findNodeInTree_1.findNodeInTree)(parsed.root, spec);
|
|
137
|
-
if (!node || !node.codeBase64) {
|
|
138
|
-
throw new Error('Object not found or no codeBase64 in backup');
|
|
139
|
-
}
|
|
140
|
-
fs.writeFileSync(output, (0, decodeBase64_1.decodeBase64)(node.codeBase64), 'utf8');
|
|
141
|
-
console.log(`Extracted to ${output}`);
|
|
142
|
-
return;
|
|
109
|
+
const isMcp = Boolean(args.mcp);
|
|
110
|
+
const isEnv = Boolean(args.env);
|
|
111
|
+
const envPathArg = typeof args['env-path'] === 'string' ? args['env-path'] : undefined;
|
|
112
|
+
let destination;
|
|
113
|
+
let envPath;
|
|
114
|
+
if (envPathArg) {
|
|
115
|
+
envPath = envPathArg;
|
|
116
|
+
destination = 'env';
|
|
143
117
|
}
|
|
144
|
-
if (
|
|
145
|
-
|
|
146
|
-
if (typeof input !== 'string') {
|
|
147
|
-
throw new Error('Missing --input');
|
|
148
|
-
}
|
|
149
|
-
const format = typeof args.format === 'string' ? args.format : 'text';
|
|
150
|
-
const flat = Boolean(args.flat);
|
|
151
|
-
const showDeps = Boolean(args.deps);
|
|
152
|
-
const raw = fs.readFileSync(input, 'utf8');
|
|
153
|
-
const parsed = yaml_1.default.parse(raw);
|
|
154
|
-
if (!parsed || typeof parsed !== 'object') {
|
|
155
|
-
throw new Error('Invalid backup file format');
|
|
156
|
-
}
|
|
157
|
-
if (parsed.schemaVersion === 2) {
|
|
158
|
-
const tree = parsed;
|
|
159
|
-
if (flat) {
|
|
160
|
-
const objects = [];
|
|
161
|
-
(0, collectTreeObjects_1.collectTreeObjects)(tree.root, objects);
|
|
162
|
-
if (format === 'json') {
|
|
163
|
-
console.log(JSON.stringify(objects, null, 2));
|
|
164
|
-
}
|
|
165
|
-
else {
|
|
166
|
-
for (const spec of objects) {
|
|
167
|
-
console.log((0, formatObjectSpec_1.formatObjectSpec)(spec));
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
if (format === 'json') {
|
|
173
|
-
console.log(JSON.stringify((0, buildTreeList_1.buildTreeList)(tree.root, { showDeps }), null, 2));
|
|
174
|
-
}
|
|
175
|
-
else {
|
|
176
|
-
const lines = (0, formatTreeListText_1.formatTreeListText)(tree.root, 0, { showDeps });
|
|
177
|
-
console.log(lines.join('\n'));
|
|
178
|
-
}
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
if (parsed.schemaVersion === 1) {
|
|
182
|
-
const flat = parsed;
|
|
183
|
-
const objects = flat.objects.map((obj) => ({
|
|
184
|
-
type: obj.type,
|
|
185
|
-
name: obj.name,
|
|
186
|
-
functionGroupName: obj.functionGroupName,
|
|
187
|
-
}));
|
|
188
|
-
if (format === 'json') {
|
|
189
|
-
console.log(JSON.stringify(objects, null, 2));
|
|
190
|
-
}
|
|
191
|
-
else {
|
|
192
|
-
for (const spec of objects) {
|
|
193
|
-
console.log((0, formatObjectSpec_1.formatObjectSpec)(spec));
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
throw new Error('Invalid backup file format');
|
|
118
|
+
else if (isEnv) {
|
|
119
|
+
destination = 'env';
|
|
199
120
|
}
|
|
200
|
-
if (
|
|
201
|
-
|
|
202
|
-
const objectSpec = args.object;
|
|
203
|
-
const filePath = args.file;
|
|
204
|
-
if (typeof input !== 'string') {
|
|
205
|
-
throw new Error('Missing --input');
|
|
206
|
-
}
|
|
207
|
-
if (typeof objectSpec !== 'string') {
|
|
208
|
-
throw new Error('Missing --object');
|
|
209
|
-
}
|
|
210
|
-
if (typeof filePath !== 'string') {
|
|
211
|
-
throw new Error('Missing --file');
|
|
212
|
-
}
|
|
213
|
-
const output = typeof args.output === 'string' ? args.output : input;
|
|
214
|
-
(0, logVerbose_1.logVerbose)(2, `Patching ${objectSpec} in ${input}`);
|
|
215
|
-
const raw = fs.readFileSync(input, 'utf8');
|
|
216
|
-
const parsed = yaml_1.default.parse(raw);
|
|
217
|
-
if (!parsed || parsed.schemaVersion !== 2) {
|
|
218
|
-
throw new Error('Patch supports only schemaVersion 2 backups');
|
|
219
|
-
}
|
|
220
|
-
(0, verifyBackupChecksum_1.verifyBackupChecksum)(parsed);
|
|
221
|
-
(0, verifyTreeChecksums_1.verifyTreeChecksums)(parsed.root);
|
|
222
|
-
const spec = (0, parseObjectSpec_1.parseObjectSpec)(objectSpec);
|
|
223
|
-
(0, logVerbose_1.logVerbose)(3, `Parsed object spec: ${spec.type}:${spec.name}`);
|
|
224
|
-
const node = (0, findNodeInTree_1.findNodeInTree)(parsed.root, spec);
|
|
225
|
-
if (!node) {
|
|
226
|
-
throw new Error('Object not found in backup');
|
|
227
|
-
}
|
|
228
|
-
const fileContent = fs.readFileSync(filePath, 'utf8');
|
|
229
|
-
node.codeBase64 = (0, encodeBase64_1.encodeBase64)(fileContent);
|
|
230
|
-
node.codeChecksum = undefined;
|
|
231
|
-
node.restoreStatus = 'ok';
|
|
232
|
-
if (!node.codeFormat) {
|
|
233
|
-
node.codeFormat = 'source';
|
|
234
|
-
}
|
|
235
|
-
(0, updateTreeChecksums_1.updateTreeChecksums)(parsed.root);
|
|
236
|
-
parsed.checksum = (0, computeBackupChecksum_1.computeBackupChecksum)(parsed);
|
|
237
|
-
const yamlText = yaml_1.default.stringify(parsed, { lineWidth: 0 });
|
|
238
|
-
fs.writeFileSync(output, yamlText, 'utf8');
|
|
239
|
-
console.log(`Backup updated at ${output}`);
|
|
240
|
-
return;
|
|
121
|
+
else if (isMcp) {
|
|
122
|
+
destination = 'SAP';
|
|
241
123
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
124
|
+
else {
|
|
125
|
+
destination =
|
|
126
|
+
typeof args.destination === 'string'
|
|
127
|
+
? args.destination
|
|
128
|
+
: typeof args.target === 'string'
|
|
129
|
+
? args.target
|
|
130
|
+
: undefined;
|
|
131
|
+
}
|
|
132
|
+
// Authentication logic
|
|
133
|
+
const needsAuth = [
|
|
134
|
+
'tree',
|
|
135
|
+
'enrich',
|
|
136
|
+
'backup',
|
|
137
|
+
'diff',
|
|
138
|
+
'check',
|
|
139
|
+
'verify',
|
|
140
|
+
'restore',
|
|
141
|
+
'activate',
|
|
142
|
+
].includes(command);
|
|
143
|
+
const canUseAuth = command === 'plan';
|
|
144
|
+
let client;
|
|
145
|
+
if (needsAuth || (canUseAuth && (envPath || destination))) {
|
|
146
|
+
if (needsAuth && !envPath && !destination) {
|
|
147
|
+
throw new Error(`Command "${command}" requires a TARGET system. Provide --target, --env or --env-path.`);
|
|
148
|
+
}
|
|
149
|
+
if (envPath || destination) {
|
|
150
|
+
(0, logVerbose_1.logVerbose)(1, `Connecting to system: ${destination || envPath}...`);
|
|
151
|
+
const sapAuth = await (0, getSapConfigFromBroker_1.getSapConfigFromBroker)({
|
|
152
|
+
destination,
|
|
153
|
+
envPath,
|
|
154
|
+
authRoot: typeof args['auth-root'] === 'string' ? args['auth-root'] : undefined,
|
|
155
|
+
browserAuthPort: typeof args['browser-auth-port'] === 'string'
|
|
156
|
+
? Number.parseInt(args['browser-auth-port'], 10)
|
|
157
|
+
: 10001,
|
|
158
|
+
logger,
|
|
159
|
+
});
|
|
160
|
+
const allowAdtLogs = verbosity_1.verbosityState.level >= 3 || Boolean(args['debug-adt']);
|
|
161
|
+
const allowConnectionLogs = verbosity_1.verbosityState.level >= 3 || Boolean(args['debug-adt']);
|
|
162
|
+
const connectionLogger = allowConnectionLogs && (0, shouldEnableConnectionLogger_1.shouldEnableConnectionLogger)()
|
|
163
|
+
? logger
|
|
164
|
+
: undefined;
|
|
165
|
+
const adtLogger = command !== 'verify' &&
|
|
166
|
+
command !== 'restore' &&
|
|
167
|
+
allowAdtLogs &&
|
|
168
|
+
(0, shouldEnableAdtLogger_1.shouldEnableAdtLogger)()
|
|
169
|
+
? logger
|
|
170
|
+
: undefined;
|
|
171
|
+
const connection = (0, connection_1.createAbapConnection)(sapAuth.config, connectionLogger, undefined, sapAuth.tokenRefresher);
|
|
172
|
+
// Resolve masterSystem/responsible for AdtClient:
|
|
173
|
+
// Cloud (BTP): both from getSystemInformation endpoint
|
|
174
|
+
// On-premise: responsible from connection username, no masterSystem
|
|
175
|
+
const systemInfo = await (0, adt_clients_1.getSystemInformation)(connection);
|
|
176
|
+
const masterSystem = systemInfo?.systemID;
|
|
177
|
+
const responsible = systemInfo?.userName || sapAuth.config.username;
|
|
178
|
+
client = new adt_clients_1.AdtClient(connection, adtLogger, {
|
|
179
|
+
masterSystem,
|
|
180
|
+
responsible,
|
|
181
|
+
});
|
|
298
182
|
}
|
|
299
|
-
throw new Error('Invalid backup file format');
|
|
300
183
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
184
|
+
if (command === 'tree') {
|
|
185
|
+
if (!client)
|
|
186
|
+
throw new Error('Client required');
|
|
187
|
+
const packageName = typeof args.package === 'string' ? args.package : undefined;
|
|
188
|
+
if (!packageName)
|
|
189
|
+
throw new Error('Missing --package');
|
|
190
|
+
const output = typeof args.output === 'string' ? args.output : 'tree.yaml';
|
|
191
|
+
(0, logVerbose_1.logVerbose)(1, `Fetching package hierarchy for ${packageName}`);
|
|
192
|
+
const hierarchy = await client
|
|
193
|
+
.getUtils()
|
|
194
|
+
.getPackageHierarchy(packageName.toUpperCase());
|
|
195
|
+
const rootTree = { ...hierarchy, restoreStatus: 'ok' };
|
|
196
|
+
const enrichedRoot = await (0, enrichTreeNode_1.enrichTreeNode)(rootTree, client, false);
|
|
197
|
+
await (0, collectTreeDependencies_1.collectTreeDependencies)(client, enrichedRoot);
|
|
198
|
+
const payload = {
|
|
199
|
+
schemaVersion: 2,
|
|
200
|
+
generatedAt: new Date().toISOString(),
|
|
201
|
+
package: packageName.toUpperCase(),
|
|
202
|
+
root: enrichedRoot,
|
|
203
|
+
};
|
|
204
|
+
payload.checksum = (0, computeBackupChecksum_1.computeBackupChecksum)(payload);
|
|
205
|
+
fs.writeFileSync(output, yaml_1.default.stringify(payload, { lineWidth: 0 }), 'utf8');
|
|
206
|
+
console.log(`Tree written to ${output}`);
|
|
207
|
+
console.log(`Next step: adt-backup enrich --input ${output} --output backup.yaml --target ${destination || '<sys>'}`);
|
|
208
|
+
return;
|
|
310
209
|
}
|
|
311
|
-
const { config, tokenRefresher } = await (0, getSapConfigFromBroker_1.getSapConfigFromBroker)({
|
|
312
|
-
destination,
|
|
313
|
-
envPath,
|
|
314
|
-
authRoot,
|
|
315
|
-
logger,
|
|
316
|
-
});
|
|
317
|
-
const allowAdtLogs = verbosity_1.verbosityState.level >= 2 || debugAdt;
|
|
318
|
-
const allowConnectionLogs = verbosity_1.verbosityState.level >= 3 || debugAdt;
|
|
319
|
-
const connectionLogger = allowConnectionLogs && (0, shouldEnableConnectionLogger_1.shouldEnableConnectionLogger)() ? logger : undefined;
|
|
320
|
-
const adtLogger = allowAdtLogs && (0, shouldEnableAdtLogger_1.shouldEnableAdtLogger)() ? logger : undefined;
|
|
321
|
-
const connection = (0, connection_1.createAbapConnection)(config, connectionLogger, undefined, tokenRefresher);
|
|
322
|
-
const client = new adt_clients_1.AdtClient(connection, adtLogger);
|
|
323
210
|
if (command === 'backup') {
|
|
324
|
-
|
|
211
|
+
if (!client)
|
|
212
|
+
throw new Error('Client required');
|
|
325
213
|
const packageName = typeof args.package === 'string' ? args.package : undefined;
|
|
214
|
+
const rawObjects = args.objects;
|
|
215
|
+
const output = typeof args.output === 'string' ? args.output : 'backup.yaml';
|
|
326
216
|
if (packageName) {
|
|
327
|
-
(0, logVerbose_1.logVerbose)(2, `Starting package backup for ${packageName}`);
|
|
328
|
-
const output = typeof args.output === 'string' ? args.output : 'backup.yaml';
|
|
329
217
|
const tree = await (0, buildPackageBackupTree_1.buildPackageBackupTree)(client, packageName);
|
|
330
218
|
(0, updateTreeChecksums_1.updateTreeChecksums)(tree.root);
|
|
331
219
|
tree.checksum = (0, computeBackupChecksum_1.computeBackupChecksum)(tree);
|
|
332
|
-
|
|
333
|
-
fs.writeFileSync(output, yamlText, 'utf8');
|
|
220
|
+
fs.writeFileSync(output, yaml_1.default.stringify(tree, { lineWidth: 0 }), 'utf8');
|
|
334
221
|
console.log(`Backup written to ${output}`);
|
|
222
|
+
console.log(`Next step: adt-backup plan --input ${output} --output plan.yaml`);
|
|
335
223
|
return;
|
|
336
224
|
}
|
|
337
|
-
if (typeof rawObjects !== 'string')
|
|
225
|
+
if (typeof rawObjects !== 'string')
|
|
338
226
|
throw new Error('Missing --objects or --package');
|
|
339
|
-
}
|
|
340
|
-
(0, logVerbose_1.logVerbose)(2, `Starting objects backup (${rawObjects})`);
|
|
341
227
|
const specs = rawObjects
|
|
342
228
|
.split(',')
|
|
343
|
-
.map((
|
|
229
|
+
.map((s) => s.trim())
|
|
344
230
|
.filter(Boolean)
|
|
345
231
|
.map(parseObjectSpec_1.parseObjectSpec);
|
|
346
232
|
const objects = [];
|
|
347
233
|
for (const spec of specs) {
|
|
348
|
-
(0,
|
|
349
|
-
const backup = await (0, backupObject_1.backupObject)(client, spec);
|
|
350
|
-
objects.push(backup);
|
|
234
|
+
objects.push(await (0, backupObject_1.backupObject)(client, spec));
|
|
351
235
|
}
|
|
352
|
-
const output = typeof args.output === 'string' ? args.output : 'backup.yaml';
|
|
353
236
|
const payload = {
|
|
354
237
|
schemaVersion: 1,
|
|
355
238
|
generatedAt: new Date().toISOString(),
|
|
356
239
|
objects,
|
|
357
240
|
};
|
|
358
241
|
payload.checksum = (0, computeBackupChecksum_1.computeBackupChecksum)(payload);
|
|
359
|
-
|
|
360
|
-
fs.writeFileSync(output, yamlText, 'utf8');
|
|
242
|
+
fs.writeFileSync(output, yaml_1.default.stringify(payload, { lineWidth: 0 }), 'utf8');
|
|
361
243
|
console.log(`Backup written to ${output}`);
|
|
362
244
|
return;
|
|
363
245
|
}
|
|
246
|
+
if (command === 'enrich') {
|
|
247
|
+
if (!client)
|
|
248
|
+
throw new Error('Client required');
|
|
249
|
+
const input = args.input;
|
|
250
|
+
if (typeof input !== 'string')
|
|
251
|
+
throw new Error('Missing --input');
|
|
252
|
+
const output = typeof args.output === 'string' ? args.output : input;
|
|
253
|
+
const backup = yaml_1.default.parse(fs.readFileSync(input, 'utf8'));
|
|
254
|
+
backup.root = await (0, enrichTreeNode_1.enrichTreeNode)(backup.root, client, true);
|
|
255
|
+
(0, updateTreeChecksums_1.updateTreeChecksums)(backup.root);
|
|
256
|
+
backup.checksum = (0, computeBackupChecksum_1.computeBackupChecksum)(backup);
|
|
257
|
+
fs.writeFileSync(output, yaml_1.default.stringify(backup, { lineWidth: 0 }), 'utf8');
|
|
258
|
+
console.log(`Enriched backup written to ${output}`);
|
|
259
|
+
console.log(`Next step: adt-backup plan --input ${output} --output plan.yaml`);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (command === 'plan') {
|
|
263
|
+
const input = args.input;
|
|
264
|
+
if (typeof input !== 'string')
|
|
265
|
+
throw new Error('Missing --input');
|
|
266
|
+
const output = typeof args.output === 'string' ? args.output : 'plan.yaml';
|
|
267
|
+
const mode = args.mode || 'create';
|
|
268
|
+
(0, logVerbose_1.logVerbose)(1, `Planning restoration from ${input} (offline)...`);
|
|
269
|
+
const backup = yaml_1.default.parse(fs.readFileSync(input, 'utf8'));
|
|
270
|
+
if (!backup || backup.schemaVersion !== 2)
|
|
271
|
+
throw new Error('SchemaVersion 2 required');
|
|
272
|
+
const allNodes = (0, flattenTree_1.flattenTree)(backup.root).filter((n) => n.type && n.restoreStatus === 'ok');
|
|
273
|
+
const packageNodes = allNodes.filter((n) => n.type === 'package');
|
|
274
|
+
const nonPackageNodes = allNodes.filter((n) => n.type !== 'package');
|
|
275
|
+
const groups = (0, analyzeDependencies_1.analyzeDependencies)(nonPackageNodes);
|
|
276
|
+
const plan = {
|
|
277
|
+
schemaVersion: 1,
|
|
278
|
+
generatedAt: new Date().toISOString(),
|
|
279
|
+
backupFile: path.resolve(input),
|
|
280
|
+
targetPackage: backup.package,
|
|
281
|
+
groups: [
|
|
282
|
+
{
|
|
283
|
+
id: 0,
|
|
284
|
+
isCircular: false,
|
|
285
|
+
actions: packageNodes.map((node) => ({
|
|
286
|
+
id: (0, getNodeObjectId_1.getNodeObjectId)(node),
|
|
287
|
+
type: node.type,
|
|
288
|
+
name: node.name,
|
|
289
|
+
action: mode,
|
|
290
|
+
adtType: node.adtType,
|
|
291
|
+
})),
|
|
292
|
+
},
|
|
293
|
+
...groups.map((group, idx) => ({
|
|
294
|
+
id: idx + 1,
|
|
295
|
+
isCircular: group.isCircular,
|
|
296
|
+
actions: group.nodes.map((node) => ({
|
|
297
|
+
id: (0, getNodeObjectId_1.getNodeObjectId)(node),
|
|
298
|
+
type: node.type,
|
|
299
|
+
name: node.name,
|
|
300
|
+
functionGroupName: node.functionGroupName,
|
|
301
|
+
action: mode,
|
|
302
|
+
adtType: node.adtType,
|
|
303
|
+
})),
|
|
304
|
+
})),
|
|
305
|
+
],
|
|
306
|
+
};
|
|
307
|
+
fs.writeFileSync(output, yaml_1.default.stringify(plan, { lineWidth: 0 }), 'utf8');
|
|
308
|
+
console.log(`Plan written to ${output}`);
|
|
309
|
+
console.log(`Next step: adt-backup verify --plan ${output} --target ${destination || '<sys>'}`);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
if (command === 'verify') {
|
|
313
|
+
if (!client)
|
|
314
|
+
throw new Error('Client required');
|
|
315
|
+
const planPath = args.plan;
|
|
316
|
+
if (!planPath || typeof planPath !== 'string')
|
|
317
|
+
throw new Error('Missing --plan');
|
|
318
|
+
const output = typeof args.output === 'string' ? args.output : planPath;
|
|
319
|
+
const plan = yaml_1.default.parse(fs.readFileSync(planPath, 'utf8'));
|
|
320
|
+
const backup = yaml_1.default.parse(fs.readFileSync(plan.backupFile, 'utf8'));
|
|
321
|
+
const verifyResult = await (0, verifyBackup_1.verifyBackup)(client, backup, {
|
|
322
|
+
mode: 'pre-restore',
|
|
323
|
+
});
|
|
324
|
+
const systemState = new Map();
|
|
325
|
+
for (const entry of verifyResult.entries) {
|
|
326
|
+
systemState.set(`${entry.type}:${entry.name}`, entry.status);
|
|
327
|
+
}
|
|
328
|
+
const skipExisting = Boolean(args['skip-existing']);
|
|
329
|
+
const skipUnchanged = Boolean(args['skip-unchanged']);
|
|
330
|
+
for (const group of plan.groups) {
|
|
331
|
+
for (const action of group.actions) {
|
|
332
|
+
const id = `${action.type}:${action.name}`;
|
|
333
|
+
const status = systemState.get(id);
|
|
334
|
+
if (status === 'ok') {
|
|
335
|
+
action.action = skipExisting || skipUnchanged ? 'skip' : 'update';
|
|
336
|
+
}
|
|
337
|
+
else if (status === 'package-mismatch' ||
|
|
338
|
+
status === 'source-mismatch') {
|
|
339
|
+
action.action = skipExisting ? 'skip' : 'update';
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
action.action = 'create';
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
fs.writeFileSync(output, yaml_1.default.stringify(plan, { lineWidth: 0 }), 'utf8');
|
|
347
|
+
const skipActions = plan.groups
|
|
348
|
+
.flatMap((g) => g.actions)
|
|
349
|
+
.filter((a) => a.action === 'skip');
|
|
350
|
+
const skipCount = skipActions.length;
|
|
351
|
+
const skipIdSet = new Set(skipActions.map((a) => `${a.type}:${a.name}`));
|
|
352
|
+
if (skipExisting && skipCount > 0) {
|
|
353
|
+
verifyResult.summary.skip = skipCount;
|
|
354
|
+
verifyResult.summary.update =
|
|
355
|
+
(verifyResult.summary.update ?? 0) - skipCount;
|
|
356
|
+
}
|
|
357
|
+
console.log((0, formatVerifyResultsText_1.formatVerifyResultsText)(verifyResult.entries, verifyResult.summary, 'pre-restore', verbosity_1.verbosityState.level, skipIdSet.size > 0 ? skipIdSet : undefined));
|
|
358
|
+
if (skipCount > 0) {
|
|
359
|
+
console.log(` (${skipCount} existing objects will be skipped)`);
|
|
360
|
+
}
|
|
361
|
+
console.log(`Plan updated and saved to ${output}`);
|
|
362
|
+
console.log(`Next step: adt-backup restore --plan ${output} --target ${destination || '<sys>'}`);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
if (command === 'restore') {
|
|
366
|
+
if (!client)
|
|
367
|
+
throw new Error('Client required');
|
|
368
|
+
const planPath = args.plan;
|
|
369
|
+
if (!planPath || typeof planPath !== 'string')
|
|
370
|
+
throw new Error('Missing --plan');
|
|
371
|
+
const plan = yaml_1.default.parse(fs.readFileSync(planPath, 'utf8'));
|
|
372
|
+
const backup = yaml_1.default.parse(fs.readFileSync(plan.backupFile, 'utf8'));
|
|
373
|
+
const allNodes = (0, flattenTree_1.flattenTree)(backup.root);
|
|
374
|
+
const _nodeMap = new Map(allNodes.map((n) => [(0, getNodeObjectId_1.getNodeObjectId)(n), n]));
|
|
375
|
+
const _backupPackageNames = new Set(allNodes.filter((n) => n.type === 'package').map((n) => n.name));
|
|
376
|
+
const noActivate = Boolean(args['no-activate']);
|
|
377
|
+
const activate = !noActivate && (Boolean(args.activate) || !args['no-activate-on-update']);
|
|
378
|
+
const activateOnCreate = !noActivate && !args['no-activate-on-create'];
|
|
379
|
+
const superPackage = typeof args['super-package'] === 'string'
|
|
380
|
+
? args['super-package']
|
|
381
|
+
: undefined;
|
|
382
|
+
const transportLayer = typeof args['transport-layer'] === 'string'
|
|
383
|
+
? args['transport-layer']
|
|
384
|
+
: undefined;
|
|
385
|
+
await (0, restoreTreeBackup_1.restoreTreeBackup)(client, backup.root, 'upsert', activate, typeof args.transport === 'string' ? args.transport : undefined, undefined, new Map(plan.groups
|
|
386
|
+
.flatMap((g) => g.actions)
|
|
387
|
+
.map((a) => [a.id, a.action])), activateOnCreate, typeof args['software-component'] === 'string'
|
|
388
|
+
? args['software-component']
|
|
389
|
+
: undefined, superPackage, transportLayer);
|
|
390
|
+
console.log('Restore completed. Running post-restore check on TARGET system...');
|
|
391
|
+
const postResult = await (0, verifyBackup_1.verifyBackup)(client, backup, {
|
|
392
|
+
mode: 'post-restore',
|
|
393
|
+
});
|
|
394
|
+
console.log((0, formatVerifyResultsText_1.formatVerifyResultsText)(postResult.entries, postResult.summary, 'post-restore', verbosity_1.verbosityState.level));
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
if (command === 'activate') {
|
|
398
|
+
if (!client)
|
|
399
|
+
throw new Error('Client required');
|
|
400
|
+
const planPath = args.plan;
|
|
401
|
+
if (!planPath || typeof planPath !== 'string')
|
|
402
|
+
throw new Error('Missing --plan');
|
|
403
|
+
const filter = typeof args.filter === 'string' ? args.filter : 'all';
|
|
404
|
+
if (!['skip', 'update', 'all'].includes(filter)) {
|
|
405
|
+
throw new Error(`Invalid --filter value: "${filter}". Must be skip, update, or all.`);
|
|
406
|
+
}
|
|
407
|
+
const plan = yaml_1.default.parse(fs.readFileSync(planPath, 'utf8'));
|
|
408
|
+
const allActions = plan.groups.flatMap((g) => g.actions);
|
|
409
|
+
const filtered = allActions.filter((a) => {
|
|
410
|
+
if (a.type === 'package')
|
|
411
|
+
return false;
|
|
412
|
+
if (!a.adtType)
|
|
413
|
+
return false;
|
|
414
|
+
if (filter === 'all')
|
|
415
|
+
return a.action !== 'create';
|
|
416
|
+
return a.action === filter;
|
|
417
|
+
});
|
|
418
|
+
if (filtered.length === 0) {
|
|
419
|
+
console.log('No objects to activate.');
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
console.log(`Activating ${filtered.length} object(s) (filter: ${filter})...`);
|
|
423
|
+
// Group by plan groups to respect dependency order
|
|
424
|
+
let activated = 0;
|
|
425
|
+
let skipped = 0;
|
|
426
|
+
let failed = 0;
|
|
427
|
+
for (const group of plan.groups) {
|
|
428
|
+
const groupRefs = [];
|
|
429
|
+
for (const action of group.actions) {
|
|
430
|
+
if (action.type === 'package' || !action.adtType)
|
|
431
|
+
continue;
|
|
432
|
+
if (filter !== 'all' && action.action !== filter)
|
|
433
|
+
continue;
|
|
434
|
+
if (filter === 'all' && action.action === 'create')
|
|
435
|
+
continue;
|
|
436
|
+
groupRefs.push({ name: action.name, type: action.adtType });
|
|
437
|
+
}
|
|
438
|
+
if (groupRefs.length === 0)
|
|
439
|
+
continue;
|
|
440
|
+
(0, logVerbose_1.logVerbose)(2, ` [GROUP ${group.id}] Activating ${groupRefs.length} object(s)...`);
|
|
441
|
+
try {
|
|
442
|
+
await client.getUtils().activateObjectsGroup(groupRefs, true);
|
|
443
|
+
activated += groupRefs.length;
|
|
444
|
+
for (const ref of groupRefs) {
|
|
445
|
+
(0, logVerbose_1.logVerbose)(2, ` OK ${ref.type} ${ref.name}`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
catch (error) {
|
|
449
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
450
|
+
failed += groupRefs.length;
|
|
451
|
+
(0, logVerbose_1.logVerbose)(1, ` [!] Group ${group.id} activation failed: ${message}`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
skipped = allActions.length - activated - failed;
|
|
455
|
+
console.log(`Activation complete: ${activated} activated, ${skipped} skipped, ${failed} failed`);
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
364
458
|
if (command === 'diff') {
|
|
459
|
+
if (!client)
|
|
460
|
+
throw new Error('Client required');
|
|
365
461
|
const input = args.input;
|
|
366
462
|
const objectSpec = args.object;
|
|
367
463
|
const diffAll = Boolean(args.all);
|
|
368
|
-
const
|
|
464
|
+
const _showOk = Boolean(args['show-ok']);
|
|
369
465
|
const objectSpecValue = typeof objectSpec === 'string' ? objectSpec : '';
|
|
370
|
-
if (typeof input !== 'string')
|
|
466
|
+
if (typeof input !== 'string')
|
|
371
467
|
throw new Error('Missing --input');
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
throw new Error('Missing --object (or use --all)');
|
|
375
|
-
}
|
|
468
|
+
if (!diffAll && typeof objectSpec !== 'string')
|
|
469
|
+
throw new Error('Missing --object');
|
|
376
470
|
const raw = fs.readFileSync(input, 'utf8');
|
|
377
471
|
const parsed = yaml_1.default.parse(raw);
|
|
378
|
-
if (!parsed || typeof parsed !== 'object') {
|
|
379
|
-
throw new Error('Invalid backup file format');
|
|
380
|
-
}
|
|
381
472
|
(0, verifyBackupChecksum_1.verifyBackupChecksum)(parsed);
|
|
382
473
|
const diffMetadata = async (label, backupText, metadataXml, showNoDiff) => {
|
|
383
474
|
const beforeMeta = (0, extractMetadata_1.extractMetadata)(backupText);
|
|
384
475
|
const afterMeta = (0, extractMetadata_1.extractMetadata)(metadataXml);
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
key: 'packageName',
|
|
389
|
-
before: beforeMeta.packageName,
|
|
390
|
-
after: afterMeta.packageName,
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
|
-
if (changes.length === 0) {
|
|
394
|
-
if (showNoDiff) {
|
|
395
|
-
console.log(`=== ${label}`);
|
|
396
|
-
console.log('No differences');
|
|
397
|
-
}
|
|
476
|
+
if (beforeMeta.packageName === afterMeta.packageName) {
|
|
477
|
+
if (showNoDiff)
|
|
478
|
+
console.log(`=== ${label}\nNo differences`);
|
|
398
479
|
return false;
|
|
399
480
|
}
|
|
400
|
-
console.log(`=== ${label}`);
|
|
401
|
-
for (const change of changes) {
|
|
402
|
-
console.log(`changed ${change.key}: "${change.before ?? ''}" -> "${change.after ?? ''}"`);
|
|
403
|
-
}
|
|
481
|
+
console.log(`=== ${label}\nchanged packageName: "${beforeMeta.packageName ?? ''}" -> "${afterMeta.packageName ?? ''}"`);
|
|
404
482
|
return true;
|
|
405
483
|
};
|
|
406
484
|
const diffSource = async (label, backupText, actualSource, showNoDiff) => {
|
|
407
485
|
const unified = (0, diffUnified_1.diffUnified)(backupText, actualSource);
|
|
408
486
|
if (!unified.trim()) {
|
|
409
|
-
if (showNoDiff)
|
|
410
|
-
console.log(`=== ${label}`);
|
|
411
|
-
console.log('No differences');
|
|
412
|
-
}
|
|
487
|
+
if (showNoDiff)
|
|
488
|
+
console.log(`=== ${label}\nNo differences`);
|
|
413
489
|
return false;
|
|
414
490
|
}
|
|
415
|
-
console.log(`=== ${label}`);
|
|
416
|
-
console.log(unified);
|
|
491
|
+
console.log(`=== ${label}\n${unified}`);
|
|
417
492
|
return true;
|
|
418
493
|
};
|
|
419
|
-
let hasAnyDiff = false;
|
|
420
494
|
if (parsed.schemaVersion === 2) {
|
|
421
|
-
|
|
422
|
-
(0, verifyTreeChecksums_1.verifyTreeChecksums)(tree.root);
|
|
495
|
+
(0, verifyTreeChecksums_1.verifyTreeChecksums)(parsed.root);
|
|
423
496
|
if (!diffAll) {
|
|
424
497
|
const spec = (0, parseObjectSpec_1.parseObjectSpec)(objectSpecValue);
|
|
425
|
-
const node = (0, findNodeInTree_1.findNodeInTree)(
|
|
426
|
-
if (!node || !node.
|
|
427
|
-
throw new Error(
|
|
428
|
-
}
|
|
429
|
-
if (!node.codeBase64) {
|
|
430
|
-
throw new Error('Object has no payload to diff');
|
|
431
|
-
}
|
|
432
|
-
const label = (0, formatObjectSpec_1.formatObjectSpec)(spec);
|
|
498
|
+
const node = (0, findNodeInTree_1.findNodeInTree)(parsed.root, spec);
|
|
499
|
+
if (!node || !node.codeBase64)
|
|
500
|
+
throw new Error('Object not found');
|
|
433
501
|
const backupText = (0, decodeBase64_1.decodeBase64)(node.codeBase64);
|
|
434
502
|
if (node.codeFormat === 'xml') {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
throw new Error('Metadata not available for diff');
|
|
439
|
-
}
|
|
440
|
-
const hasDiff = await diffMetadata(label, backupText, metadataXml, true);
|
|
441
|
-
if (!hasDiff) {
|
|
442
|
-
console.log('No metadata differences detected');
|
|
443
|
-
}
|
|
444
|
-
return;
|
|
445
|
-
}
|
|
446
|
-
catch (_error) {
|
|
447
|
-
const otherType = await (0, findOtherType_1.findOtherType)(client, node.type, node.name);
|
|
448
|
-
if (otherType && otherType !== node.type) {
|
|
449
|
-
console.log(`=== ${label}`);
|
|
450
|
-
console.log(`changed type: "${node.type}" -> "${otherType}"`);
|
|
451
|
-
return;
|
|
452
|
-
}
|
|
453
|
-
throw _error;
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
const actualSource = await (0, readSourceText_1.readSourceText)(client, {
|
|
457
|
-
type: node.type,
|
|
458
|
-
name: node.name,
|
|
459
|
-
functionGroupName: node.functionGroupName,
|
|
460
|
-
});
|
|
461
|
-
if (actualSource === undefined) {
|
|
462
|
-
throw new Error('Source not available for diff');
|
|
503
|
+
const metadataXml = await (0, readMetadataXmlForType_1.readMetadataXmlForType)(client, node.type, node.name, node.functionGroupName);
|
|
504
|
+
if (metadataXml)
|
|
505
|
+
await diffMetadata((0, formatObjectSpec_1.formatObjectSpec)(spec), backupText, metadataXml, true);
|
|
463
506
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
507
|
+
else {
|
|
508
|
+
const actualSource = await (0, readSourceText_1.readSourceText)(client, spec);
|
|
509
|
+
await diffSource((0, formatObjectSpec_1.formatObjectSpec)(spec), backupText, actualSource ?? '', true);
|
|
467
510
|
}
|
|
468
511
|
return;
|
|
469
512
|
}
|
|
470
|
-
const nodes = [];
|
|
471
|
-
(0, collectBackupNodes_1.collectBackupNodes)(tree.root, nodes);
|
|
472
|
-
for (const node of nodes) {
|
|
473
|
-
if (!node.type || !node.codeBase64) {
|
|
474
|
-
continue;
|
|
475
|
-
}
|
|
476
|
-
const spec = {
|
|
477
|
-
type: node.type,
|
|
478
|
-
name: node.name,
|
|
479
|
-
functionGroupName: node.functionGroupName,
|
|
480
|
-
};
|
|
481
|
-
const label = (0, formatObjectSpec_1.formatObjectSpec)(spec);
|
|
482
|
-
const backupText = (0, decodeBase64_1.decodeBase64)(node.codeBase64);
|
|
483
|
-
if (node.codeFormat === 'xml') {
|
|
484
|
-
try {
|
|
485
|
-
const metadataXml = await (0, readMetadataXmlForType_1.readMetadataXmlForType)(client, node.type, node.name, node.functionGroupName);
|
|
486
|
-
if (!metadataXml) {
|
|
487
|
-
continue;
|
|
488
|
-
}
|
|
489
|
-
const hasDiff = await diffMetadata(label, backupText, metadataXml, showOk);
|
|
490
|
-
if (hasDiff) {
|
|
491
|
-
hasAnyDiff = true;
|
|
492
|
-
}
|
|
493
|
-
continue;
|
|
494
|
-
}
|
|
495
|
-
catch (_error) {
|
|
496
|
-
const otherType = await (0, findOtherType_1.findOtherType)(client, node.type, node.name);
|
|
497
|
-
if (otherType && otherType !== node.type) {
|
|
498
|
-
console.log(`=== ${label}`);
|
|
499
|
-
console.log(`changed type: "${node.type}" -> "${otherType}"`);
|
|
500
|
-
hasAnyDiff = true;
|
|
501
|
-
}
|
|
502
|
-
continue;
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
const actualSource = await (0, readSourceText_1.readSourceText)(client, spec);
|
|
506
|
-
if (actualSource === undefined) {
|
|
507
|
-
continue;
|
|
508
|
-
}
|
|
509
|
-
const hasDiff = await diffSource(label, backupText, actualSource, showOk);
|
|
510
|
-
if (hasDiff) {
|
|
511
|
-
hasAnyDiff = true;
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
if (!hasAnyDiff) {
|
|
515
|
-
console.log('No differences detected');
|
|
516
|
-
}
|
|
517
|
-
return;
|
|
518
|
-
}
|
|
519
|
-
const flat = parsed;
|
|
520
|
-
if (!Array.isArray(flat.objects)) {
|
|
521
|
-
throw new Error('Invalid backup file format');
|
|
522
|
-
}
|
|
523
|
-
if (!diffAll) {
|
|
524
|
-
const spec = (0, parseObjectSpec_1.parseObjectSpec)(objectSpecValue);
|
|
525
|
-
const obj = flat.objects.find((item) => item.type === spec.type &&
|
|
526
|
-
item.name === spec.name &&
|
|
527
|
-
(spec.functionGroupName
|
|
528
|
-
? item.functionGroupName === spec.functionGroupName
|
|
529
|
-
: true));
|
|
530
|
-
if (!obj) {
|
|
531
|
-
throw new Error(`Object not found: ${(0, formatObjectSpec_1.formatObjectSpec)(spec)}`);
|
|
532
|
-
}
|
|
533
|
-
if (!obj.source) {
|
|
534
|
-
throw new Error('Object has no payload to diff');
|
|
535
|
-
}
|
|
536
|
-
const actualSource = await (0, readSourceText_1.readSourceText)(client, spec);
|
|
537
|
-
if (actualSource === undefined) {
|
|
538
|
-
throw new Error('Source not available for diff');
|
|
539
|
-
}
|
|
540
|
-
const hasDiff = await diffSource((0, formatObjectSpec_1.formatObjectSpec)(spec), obj.source, actualSource, true);
|
|
541
|
-
if (!hasDiff) {
|
|
542
|
-
console.log('No source differences detected');
|
|
543
|
-
}
|
|
544
|
-
return;
|
|
545
|
-
}
|
|
546
|
-
for (const obj of flat.objects) {
|
|
547
|
-
if (!obj.source) {
|
|
548
|
-
continue;
|
|
549
|
-
}
|
|
550
|
-
const spec = {
|
|
551
|
-
type: obj.type,
|
|
552
|
-
name: obj.name,
|
|
553
|
-
functionGroupName: obj.functionGroupName,
|
|
554
|
-
};
|
|
555
|
-
const actualSource = await (0, readSourceText_1.readSourceText)(client, spec);
|
|
556
|
-
if (actualSource === undefined) {
|
|
557
|
-
continue;
|
|
558
|
-
}
|
|
559
|
-
const hasDiff = await diffSource((0, formatObjectSpec_1.formatObjectSpec)(spec), obj.source, actualSource, showOk);
|
|
560
|
-
if (hasDiff) {
|
|
561
|
-
hasAnyDiff = true;
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
if (!hasAnyDiff) {
|
|
565
|
-
console.log('No differences detected');
|
|
566
513
|
}
|
|
567
514
|
return;
|
|
568
515
|
}
|
|
569
|
-
if (command === '
|
|
516
|
+
if (command === 'validate') {
|
|
570
517
|
const input = args.input;
|
|
571
|
-
if (typeof input !== 'string')
|
|
518
|
+
if (typeof input !== 'string')
|
|
572
519
|
throw new Error('Missing --input');
|
|
573
|
-
|
|
574
|
-
const format = typeof args.format === 'string' ? args.format : 'text';
|
|
575
|
-
const strict = Boolean(args.strict);
|
|
576
|
-
const raw = fs.readFileSync(input, 'utf8');
|
|
577
|
-
const parsed = yaml_1.default.parse(raw);
|
|
578
|
-
if (!parsed || typeof parsed !== 'object') {
|
|
579
|
-
throw new Error('Invalid backup file format');
|
|
580
|
-
}
|
|
520
|
+
const parsed = yaml_1.default.parse(fs.readFileSync(input, 'utf8'));
|
|
581
521
|
(0, verifyBackupChecksum_1.verifyBackupChecksum)(parsed);
|
|
582
|
-
if (parsed.schemaVersion === 2)
|
|
522
|
+
if (parsed.schemaVersion === 2)
|
|
583
523
|
(0, verifyTreeChecksums_1.verifyTreeChecksums)(parsed.root);
|
|
584
|
-
|
|
585
|
-
const result = await (0, verifyBackup_1.verifyBackup)(client, parsed, { strict });
|
|
586
|
-
if (format === 'json') {
|
|
587
|
-
console.log(JSON.stringify(result, null, 2));
|
|
588
|
-
}
|
|
589
|
-
else {
|
|
590
|
-
console.log((0, formatVerifyResultsText_1.formatVerifyResultsText)(result.entries, result.summary));
|
|
591
|
-
}
|
|
592
|
-
if (result.summary.conflicts > 0) {
|
|
593
|
-
throw new Error(`Conflicts found: ${result.summary.conflicts}`);
|
|
594
|
-
}
|
|
524
|
+
console.log('Backup validated');
|
|
595
525
|
return;
|
|
596
526
|
}
|
|
597
|
-
if (command === '
|
|
527
|
+
if (command === 'extract') {
|
|
598
528
|
const input = args.input;
|
|
599
|
-
|
|
529
|
+
const objectSpec = args.object;
|
|
530
|
+
const output = args.out;
|
|
531
|
+
if (typeof input !== 'string' ||
|
|
532
|
+
typeof objectSpec !== 'string' ||
|
|
533
|
+
typeof output !== 'string')
|
|
534
|
+
throw new Error('Missing args');
|
|
535
|
+
const parsed = yaml_1.default.parse(fs.readFileSync(input, 'utf8'));
|
|
536
|
+
const spec = (0, parseObjectSpec_1.parseObjectSpec)(objectSpec);
|
|
537
|
+
const node = (0, findNodeInTree_1.findNodeInTree)(parsed.root, spec);
|
|
538
|
+
if (!node || !node.codeBase64)
|
|
539
|
+
throw new Error('Not found');
|
|
540
|
+
fs.writeFileSync(output, (0, decodeBase64_1.decodeBase64)(node.codeBase64), 'utf8');
|
|
541
|
+
console.log(`Extracted to ${output}`);
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
if (command === 'patch') {
|
|
545
|
+
const input = args.input;
|
|
546
|
+
const objectSpec = args.object;
|
|
547
|
+
const filePath = args.file;
|
|
548
|
+
if (typeof input !== 'string' ||
|
|
549
|
+
typeof objectSpec !== 'string' ||
|
|
550
|
+
typeof filePath !== 'string')
|
|
551
|
+
throw new Error('Missing args');
|
|
552
|
+
const output = typeof args.output === 'string' ? args.output : input;
|
|
553
|
+
const parsed = yaml_1.default.parse(fs.readFileSync(input, 'utf8'));
|
|
554
|
+
const node = (0, findNodeInTree_1.findNodeInTree)(parsed.root, (0, parseObjectSpec_1.parseObjectSpec)(objectSpec));
|
|
555
|
+
if (!node)
|
|
556
|
+
throw new Error('Not found');
|
|
557
|
+
node.codeBase64 = (0, encodeBase64_1.encodeBase64)(fs.readFileSync(filePath, 'utf8'));
|
|
558
|
+
node.codeChecksum = (0, computeCodeChecksum_1.computeCodeChecksum)(node.codeBase64);
|
|
559
|
+
(0, updateTreeChecksums_1.updateTreeChecksums)(parsed.root);
|
|
560
|
+
parsed.checksum = (0, computeBackupChecksum_1.computeBackupChecksum)(parsed);
|
|
561
|
+
fs.writeFileSync(output, yaml_1.default.stringify(parsed, { lineWidth: 0 }), 'utf8');
|
|
562
|
+
console.log(`Backup updated`);
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
if (command === 'check') {
|
|
566
|
+
if (!client)
|
|
567
|
+
throw new Error('Client required');
|
|
568
|
+
const input = args.input;
|
|
569
|
+
if (typeof input !== 'string')
|
|
600
570
|
throw new Error('Missing --input');
|
|
601
|
-
|
|
602
|
-
(0,
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
const parsed = yaml_1.default.parse(raw);
|
|
607
|
-
if (!parsed || typeof parsed !== 'object') {
|
|
608
|
-
throw new Error('Invalid backup file format');
|
|
609
|
-
}
|
|
610
|
-
const force = Boolean(args.force);
|
|
611
|
-
const strict = Boolean(args.strict);
|
|
612
|
-
const dangerous = Boolean(args.dangerous);
|
|
613
|
-
const activateOnCreate = !args['no-activate-on-create'];
|
|
614
|
-
const transportRequest = typeof args.transport === 'string' ? args.transport : undefined;
|
|
615
|
-
const softwareComponent = typeof args['software-component'] === 'string'
|
|
616
|
-
? args['software-component']
|
|
617
|
-
: undefined;
|
|
618
|
-
if (transportRequest) {
|
|
619
|
-
(0, logVerbose_1.logVerbose)(1, `Using transport request for restore: ${transportRequest}`);
|
|
620
|
-
}
|
|
621
|
-
if (softwareComponent) {
|
|
622
|
-
(0, logVerbose_1.logVerbose)(1, `Using software component override for packages: ${softwareComponent}`);
|
|
623
|
-
}
|
|
624
|
-
if (parsed.schemaVersion === 2) {
|
|
625
|
-
const tree = parsed;
|
|
626
|
-
(0, verifyBackupChecksum_1.verifyBackupChecksum)(tree);
|
|
627
|
-
(0, verifyTreeChecksums_1.verifyTreeChecksums)(tree.root);
|
|
628
|
-
if (dangerous) {
|
|
629
|
-
(0, logVerbose_1.logVerbose)(1, `Dangerous mode: deleting ${tree.package} objects from backup before restore`);
|
|
630
|
-
await (0, deleteBackupObjects_1.deleteBackupObjects)(client, tree, transportRequest);
|
|
631
|
-
}
|
|
632
|
-
if (!force) {
|
|
633
|
-
const result = await (0, verifyBackup_1.verifyBackup)(client, tree, { strict });
|
|
634
|
-
if (result.summary.conflicts > 0) {
|
|
635
|
-
throw new Error(`Conflicts found: ${result.summary.conflicts}. Use --force to restore anyway.`);
|
|
636
|
-
}
|
|
637
|
-
const plan = (0, buildRestorePlan_1.buildRestorePlan)(tree.root, result.entries);
|
|
638
|
-
const summary = plan.reduce((acc, item) => {
|
|
639
|
-
acc[item.action] = (acc[item.action] || 0) + 1;
|
|
640
|
-
return acc;
|
|
641
|
-
}, {});
|
|
642
|
-
(0, logVerbose_1.logVerbose)(1, `Restore plan: create ${summary.create || 0}, update ${summary.update || 0}, skip ${summary.skip || 0}, error ${summary.error || 0}`);
|
|
643
|
-
for (const item of plan) {
|
|
644
|
-
if (item.action === 'skip') {
|
|
645
|
-
continue;
|
|
646
|
-
}
|
|
647
|
-
const spec = (0, getNodeObjectSpec_1.getNodeObjectSpec)(item.node);
|
|
648
|
-
const label = spec ? (0, formatObjectSpec_1.formatObjectSpec)(spec) : item.node.name;
|
|
649
|
-
(0, logVerbose_1.logVerbose)(2, `Plan ${item.action}: ${label} (${item.status})`);
|
|
650
|
-
}
|
|
651
|
-
const restoreIds = new Set(plan
|
|
652
|
-
.filter((item) => item.action === 'create' || item.action === 'update')
|
|
653
|
-
.map((item) => item.id));
|
|
654
|
-
const restoreActions = new Map();
|
|
655
|
-
for (const item of plan) {
|
|
656
|
-
if (item.action === 'create') {
|
|
657
|
-
restoreActions.set(item.id, 'create');
|
|
658
|
-
}
|
|
659
|
-
else if (item.action === 'update') {
|
|
660
|
-
restoreActions.set(item.id, 'update');
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
if (restoreIds.size === 0) {
|
|
664
|
-
console.log('Restore skipped: no changes detected');
|
|
665
|
-
return;
|
|
666
|
-
}
|
|
667
|
-
(0, logVerbose_1.logVerbose)(2, `Restoring tree backup for package ${tree.package}`);
|
|
668
|
-
await (0, restoreTreeBackup_1.restoreTreeBackup)(client, tree.root, mode, activateOnUpdate, transportRequest, restoreIds, restoreActions, activateOnCreate, softwareComponent);
|
|
669
|
-
console.log('Restore completed');
|
|
670
|
-
return;
|
|
671
|
-
}
|
|
672
|
-
(0, logVerbose_1.logVerbose)(2, `Restoring tree backup for package ${tree.package}`);
|
|
673
|
-
await (0, restoreTreeBackup_1.restoreTreeBackup)(client, tree.root, mode, activateOnUpdate, transportRequest, undefined, undefined, activateOnCreate, softwareComponent);
|
|
674
|
-
console.log('Restore completed');
|
|
675
|
-
return;
|
|
676
|
-
}
|
|
677
|
-
if (!Array.isArray(parsed.objects)) {
|
|
678
|
-
throw new Error('Invalid backup file format');
|
|
679
|
-
}
|
|
680
|
-
const flat = parsed;
|
|
681
|
-
(0, verifyBackupChecksum_1.verifyBackupChecksum)(flat);
|
|
682
|
-
if (dangerous) {
|
|
683
|
-
throw new Error('Dangerous mode is supported only for package backups');
|
|
684
|
-
}
|
|
685
|
-
if (!force) {
|
|
686
|
-
const result = await (0, verifyBackup_1.verifyBackup)(client, flat, { strict });
|
|
687
|
-
if (result.summary.conflicts > 0) {
|
|
688
|
-
throw new Error(`Conflicts found: ${result.summary.conflicts}. Use --force to restore anyway.`);
|
|
689
|
-
}
|
|
690
|
-
const restoreActions = new Map();
|
|
691
|
-
for (const entry of result.entries) {
|
|
692
|
-
if (entry.status === 'missing') {
|
|
693
|
-
restoreActions.set(`${entry.type}:${entry.name}`, 'create');
|
|
694
|
-
}
|
|
695
|
-
else if (entry.status === 'package-mismatch' ||
|
|
696
|
-
entry.status === 'source-mismatch') {
|
|
697
|
-
restoreActions.set(`${entry.type}:${entry.name}`, 'update');
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
if (restoreActions.size === 0) {
|
|
701
|
-
console.log('Restore skipped: no changes detected');
|
|
702
|
-
return;
|
|
703
|
-
}
|
|
704
|
-
(0, logVerbose_1.logVerbose)(2, `Restoring flat backup (${flat.objects.length} objects)`);
|
|
705
|
-
await (0, restoreObjects_1.restoreObjects)(client, flat.objects, mode, activateOnUpdate, transportRequest, restoreActions, activateOnCreate);
|
|
706
|
-
console.log(`Restore completed for ${flat.objects.length} object(s)`);
|
|
707
|
-
return;
|
|
708
|
-
}
|
|
709
|
-
(0, logVerbose_1.logVerbose)(2, `Restoring flat backup (${flat.objects.length} objects)`);
|
|
710
|
-
await (0, restoreObjects_1.restoreObjects)(client, flat.objects, mode, activateOnUpdate, transportRequest, undefined, activateOnCreate);
|
|
711
|
-
console.log(`Restore completed for ${flat.objects.length} object(s)`);
|
|
571
|
+
const backup = yaml_1.default.parse(fs.readFileSync(input, 'utf8'));
|
|
572
|
+
const result = await (0, verifyBackup_1.verifyBackup)(client, backup, {
|
|
573
|
+
strict: Boolean(args.strict),
|
|
574
|
+
});
|
|
575
|
+
console.log((0, formatVerifyResultsText_1.formatVerifyResultsText)(result.entries, result.summary, 'pre-restore', verbosity_1.verbosityState.level));
|
|
712
576
|
return;
|
|
713
577
|
}
|
|
714
578
|
throw new Error(`Unknown command: ${command}`);
|
|
715
579
|
}
|
|
716
580
|
function enableLogFile(logFile) {
|
|
717
581
|
const dir = path.dirname(logFile);
|
|
718
|
-
if (dir && dir !== '.')
|
|
582
|
+
if (dir && dir !== '.')
|
|
719
583
|
fs.mkdirSync(dir, { recursive: true });
|
|
720
|
-
}
|
|
721
584
|
const stream = fs.createWriteStream(logFile, { flags: 'a' });
|
|
722
|
-
const writeLine = (args) => {
|
|
723
|
-
const line = args.map(formatLogArg).join(' ');
|
|
724
|
-
stream.write(`${line}\n`);
|
|
725
|
-
};
|
|
726
585
|
const wrap = (fn) => (...args) => {
|
|
727
586
|
fn(...args);
|
|
728
|
-
|
|
587
|
+
stream.write(`${args.map((a) => (typeof a === 'string' ? a : JSON.stringify(a))).join(' ')}\n`);
|
|
729
588
|
};
|
|
730
589
|
console.log = wrap(console.log.bind(console));
|
|
731
|
-
console.info = wrap(console.info.bind(console));
|
|
732
|
-
console.warn = wrap(console.warn.bind(console));
|
|
733
590
|
console.error = wrap(console.error.bind(console));
|
|
734
591
|
}
|
|
735
|
-
function formatLogArg(arg) {
|
|
736
|
-
if (arg instanceof Error) {
|
|
737
|
-
return (0, redact_1.redactText)(arg.stack ?? arg.message);
|
|
738
|
-
}
|
|
739
|
-
if (typeof arg === 'string') {
|
|
740
|
-
return (0, redact_1.redactText)(arg);
|
|
741
|
-
}
|
|
742
|
-
try {
|
|
743
|
-
return (0, redact_1.safeStringify)(arg);
|
|
744
|
-
}
|
|
745
|
-
catch {
|
|
746
|
-
return String(arg);
|
|
747
|
-
}
|
|
748
|
-
}
|