@mcp-abap-adt/adt-backup 0.1.1 → 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.
- package/README.md +34 -0
- 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.d.ts.map +1 -1
- package/dist/lib/backup/backupObject.js +19 -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 -92
- 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 -87
- 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 -87
- package/dist/lib/constants/typeOrder.d.ts.map +1 -1
- package/dist/lib/constants/typeOrder.js +1 -0
- package/dist/lib/dependencies/collectTreeDependencies.d.ts.map +1 -1
- package/dist/lib/dependencies/collectTreeDependencies.js +1 -0
- 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/restoreObject.d.ts.map +1 -1
- package/dist/lib/restore/restoreObject.js +13 -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 +128 -36
- package/dist/lib/run.d.ts.map +1 -1
- package/dist/lib/run.js +393 -559
- package/dist/lib/tree/buildConfigForNode.d.ts.map +1 -1
- package/dist/lib/tree/buildConfigForNode.js +19 -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 +2 -0
- package/dist/lib/tree/mapAdtTypeToSupported.d.ts.map +1 -1
- package/dist/lib/tree/mapAdtTypeToSupported.js +6 -0
- package/dist/lib/tree/readPayloadForType.d.ts.map +1 -1
- package/dist/lib/tree/readPayloadForType.js +5 -3
- 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 +6 -0
- package/dist/lib/utils/normalizeType.d.ts.map +1 -1
- package/dist/lib/utils/normalizeType.js +2 -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/findOtherType.d.ts.map +1 -1
- package/dist/lib/verify/findOtherType.js +1 -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/dist/lib/xml/index.d.ts +1 -0
- package/dist/lib/xml/index.d.ts.map +1 -1
- package/dist/lib/xml/index.js +1 -0
- package/dist/lib/xml/parseServiceBindingConfig.d.ts +3 -0
- package/dist/lib/xml/parseServiceBindingConfig.d.ts.map +1 -0
- package/dist/lib/xml/parseServiceBindingConfig.js +70 -0
- package/package.json +12 -12
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,477 @@ 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
|
-
console.log(`Validated object ${(0, formatObjectSpec_1.formatObjectSpec)(spec)}`);
|
|
293
|
-
}
|
|
294
|
-
else {
|
|
295
|
-
console.log('Backup validated');
|
|
296
|
-
}
|
|
297
|
-
return;
|
|
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
|
+
client = new adt_clients_1.AdtClient(connection, adtLogger);
|
|
298
173
|
}
|
|
299
|
-
throw new Error('Invalid backup file format');
|
|
300
174
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
175
|
+
if (command === 'tree') {
|
|
176
|
+
if (!client)
|
|
177
|
+
throw new Error('Client required');
|
|
178
|
+
const packageName = typeof args.package === 'string' ? args.package : undefined;
|
|
179
|
+
if (!packageName)
|
|
180
|
+
throw new Error('Missing --package');
|
|
181
|
+
const output = typeof args.output === 'string' ? args.output : 'tree.yaml';
|
|
182
|
+
(0, logVerbose_1.logVerbose)(1, `Fetching package hierarchy for ${packageName}`);
|
|
183
|
+
const hierarchy = await client
|
|
184
|
+
.getUtils()
|
|
185
|
+
.getPackageHierarchy(packageName.toUpperCase());
|
|
186
|
+
const rootTree = { ...hierarchy, restoreStatus: 'ok' };
|
|
187
|
+
const enrichedRoot = await (0, enrichTreeNode_1.enrichTreeNode)(rootTree, client, false);
|
|
188
|
+
await (0, collectTreeDependencies_1.collectTreeDependencies)(client, enrichedRoot);
|
|
189
|
+
const payload = {
|
|
190
|
+
schemaVersion: 2,
|
|
191
|
+
generatedAt: new Date().toISOString(),
|
|
192
|
+
package: packageName.toUpperCase(),
|
|
193
|
+
root: enrichedRoot,
|
|
194
|
+
};
|
|
195
|
+
payload.checksum = (0, computeBackupChecksum_1.computeBackupChecksum)(payload);
|
|
196
|
+
fs.writeFileSync(output, yaml_1.default.stringify(payload, { lineWidth: 0 }), 'utf8');
|
|
197
|
+
console.log(`Tree written to ${output}`);
|
|
198
|
+
console.log(`Next step: adt-backup enrich --input ${output} --output backup.yaml --target ${destination || '<sys>'}`);
|
|
199
|
+
return;
|
|
310
200
|
}
|
|
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
201
|
if (command === 'backup') {
|
|
324
|
-
|
|
202
|
+
if (!client)
|
|
203
|
+
throw new Error('Client required');
|
|
325
204
|
const packageName = typeof args.package === 'string' ? args.package : undefined;
|
|
205
|
+
const rawObjects = args.objects;
|
|
206
|
+
const output = typeof args.output === 'string' ? args.output : 'backup.yaml';
|
|
326
207
|
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
208
|
const tree = await (0, buildPackageBackupTree_1.buildPackageBackupTree)(client, packageName);
|
|
330
209
|
(0, updateTreeChecksums_1.updateTreeChecksums)(tree.root);
|
|
331
210
|
tree.checksum = (0, computeBackupChecksum_1.computeBackupChecksum)(tree);
|
|
332
|
-
|
|
333
|
-
fs.writeFileSync(output, yamlText, 'utf8');
|
|
211
|
+
fs.writeFileSync(output, yaml_1.default.stringify(tree, { lineWidth: 0 }), 'utf8');
|
|
334
212
|
console.log(`Backup written to ${output}`);
|
|
213
|
+
console.log(`Next step: adt-backup plan --input ${output} --output plan.yaml`);
|
|
335
214
|
return;
|
|
336
215
|
}
|
|
337
|
-
if (typeof rawObjects !== 'string')
|
|
216
|
+
if (typeof rawObjects !== 'string')
|
|
338
217
|
throw new Error('Missing --objects or --package');
|
|
339
|
-
}
|
|
340
|
-
(0, logVerbose_1.logVerbose)(2, `Starting objects backup (${rawObjects})`);
|
|
341
218
|
const specs = rawObjects
|
|
342
219
|
.split(',')
|
|
343
|
-
.map((
|
|
220
|
+
.map((s) => s.trim())
|
|
344
221
|
.filter(Boolean)
|
|
345
222
|
.map(parseObjectSpec_1.parseObjectSpec);
|
|
346
223
|
const objects = [];
|
|
347
224
|
for (const spec of specs) {
|
|
348
|
-
(0,
|
|
349
|
-
const backup = await (0, backupObject_1.backupObject)(client, spec);
|
|
350
|
-
objects.push(backup);
|
|
225
|
+
objects.push(await (0, backupObject_1.backupObject)(client, spec));
|
|
351
226
|
}
|
|
352
|
-
const output = typeof args.output === 'string' ? args.output : 'backup.yaml';
|
|
353
227
|
const payload = {
|
|
354
228
|
schemaVersion: 1,
|
|
355
229
|
generatedAt: new Date().toISOString(),
|
|
356
230
|
objects,
|
|
357
231
|
};
|
|
358
232
|
payload.checksum = (0, computeBackupChecksum_1.computeBackupChecksum)(payload);
|
|
359
|
-
|
|
360
|
-
fs.writeFileSync(output, yamlText, 'utf8');
|
|
233
|
+
fs.writeFileSync(output, yaml_1.default.stringify(payload, { lineWidth: 0 }), 'utf8');
|
|
361
234
|
console.log(`Backup written to ${output}`);
|
|
362
235
|
return;
|
|
363
236
|
}
|
|
237
|
+
if (command === 'enrich') {
|
|
238
|
+
if (!client)
|
|
239
|
+
throw new Error('Client required');
|
|
240
|
+
const input = args.input;
|
|
241
|
+
if (typeof input !== 'string')
|
|
242
|
+
throw new Error('Missing --input');
|
|
243
|
+
const output = typeof args.output === 'string' ? args.output : input;
|
|
244
|
+
const backup = yaml_1.default.parse(fs.readFileSync(input, 'utf8'));
|
|
245
|
+
backup.root = await (0, enrichTreeNode_1.enrichTreeNode)(backup.root, client, true);
|
|
246
|
+
(0, updateTreeChecksums_1.updateTreeChecksums)(backup.root);
|
|
247
|
+
backup.checksum = (0, computeBackupChecksum_1.computeBackupChecksum)(backup);
|
|
248
|
+
fs.writeFileSync(output, yaml_1.default.stringify(backup, { lineWidth: 0 }), 'utf8');
|
|
249
|
+
console.log(`Enriched backup written to ${output}`);
|
|
250
|
+
console.log(`Next step: adt-backup plan --input ${output} --output plan.yaml`);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
if (command === 'plan') {
|
|
254
|
+
const input = args.input;
|
|
255
|
+
if (typeof input !== 'string')
|
|
256
|
+
throw new Error('Missing --input');
|
|
257
|
+
const output = typeof args.output === 'string' ? args.output : 'plan.yaml';
|
|
258
|
+
const mode = args.mode || 'create';
|
|
259
|
+
(0, logVerbose_1.logVerbose)(1, `Planning restoration from ${input} (offline)...`);
|
|
260
|
+
const backup = yaml_1.default.parse(fs.readFileSync(input, 'utf8'));
|
|
261
|
+
if (!backup || backup.schemaVersion !== 2)
|
|
262
|
+
throw new Error('SchemaVersion 2 required');
|
|
263
|
+
const allNodes = (0, flattenTree_1.flattenTree)(backup.root).filter((n) => n.type && n.restoreStatus === 'ok');
|
|
264
|
+
const packageNodes = allNodes.filter((n) => n.type === 'package');
|
|
265
|
+
const nonPackageNodes = allNodes.filter((n) => n.type !== 'package');
|
|
266
|
+
const groups = (0, analyzeDependencies_1.analyzeDependencies)(nonPackageNodes);
|
|
267
|
+
const plan = {
|
|
268
|
+
schemaVersion: 1,
|
|
269
|
+
generatedAt: new Date().toISOString(),
|
|
270
|
+
backupFile: path.resolve(input),
|
|
271
|
+
targetPackage: backup.package,
|
|
272
|
+
groups: [
|
|
273
|
+
{
|
|
274
|
+
id: 0,
|
|
275
|
+
isCircular: false,
|
|
276
|
+
actions: packageNodes.map((node) => ({
|
|
277
|
+
id: (0, getNodeObjectId_1.getNodeObjectId)(node),
|
|
278
|
+
type: node.type,
|
|
279
|
+
name: node.name,
|
|
280
|
+
action: mode,
|
|
281
|
+
adtType: node.adtType,
|
|
282
|
+
})),
|
|
283
|
+
},
|
|
284
|
+
...groups.map((group, idx) => ({
|
|
285
|
+
id: idx + 1,
|
|
286
|
+
isCircular: group.isCircular,
|
|
287
|
+
actions: group.nodes.map((node) => ({
|
|
288
|
+
id: (0, getNodeObjectId_1.getNodeObjectId)(node),
|
|
289
|
+
type: node.type,
|
|
290
|
+
name: node.name,
|
|
291
|
+
functionGroupName: node.functionGroupName,
|
|
292
|
+
action: mode,
|
|
293
|
+
adtType: node.adtType,
|
|
294
|
+
})),
|
|
295
|
+
})),
|
|
296
|
+
],
|
|
297
|
+
};
|
|
298
|
+
fs.writeFileSync(output, yaml_1.default.stringify(plan, { lineWidth: 0 }), 'utf8');
|
|
299
|
+
console.log(`Plan written to ${output}`);
|
|
300
|
+
console.log(`Next step: adt-backup verify --plan ${output} --target ${destination || '<sys>'}`);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (command === 'verify') {
|
|
304
|
+
if (!client)
|
|
305
|
+
throw new Error('Client required');
|
|
306
|
+
const planPath = args.plan;
|
|
307
|
+
if (!planPath || typeof planPath !== 'string')
|
|
308
|
+
throw new Error('Missing --plan');
|
|
309
|
+
const output = typeof args.output === 'string' ? args.output : planPath;
|
|
310
|
+
const plan = yaml_1.default.parse(fs.readFileSync(planPath, 'utf8'));
|
|
311
|
+
const backup = yaml_1.default.parse(fs.readFileSync(plan.backupFile, 'utf8'));
|
|
312
|
+
const verifyResult = await (0, verifyBackup_1.verifyBackup)(client, backup, {
|
|
313
|
+
mode: 'pre-restore',
|
|
314
|
+
});
|
|
315
|
+
const systemState = new Map();
|
|
316
|
+
for (const entry of verifyResult.entries) {
|
|
317
|
+
systemState.set(`${entry.type}:${entry.name}`, entry.status);
|
|
318
|
+
}
|
|
319
|
+
const skipExisting = Boolean(args['skip-existing']);
|
|
320
|
+
const skipUnchanged = Boolean(args['skip-unchanged']);
|
|
321
|
+
for (const group of plan.groups) {
|
|
322
|
+
for (const action of group.actions) {
|
|
323
|
+
const id = `${action.type}:${action.name}`;
|
|
324
|
+
const status = systemState.get(id);
|
|
325
|
+
if (status === 'ok') {
|
|
326
|
+
action.action = skipExisting || skipUnchanged ? 'skip' : 'update';
|
|
327
|
+
}
|
|
328
|
+
else if (status === 'package-mismatch' ||
|
|
329
|
+
status === 'source-mismatch') {
|
|
330
|
+
action.action = skipExisting ? 'skip' : 'update';
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
action.action = 'create';
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
fs.writeFileSync(output, yaml_1.default.stringify(plan, { lineWidth: 0 }), 'utf8');
|
|
338
|
+
const skipActions = plan.groups
|
|
339
|
+
.flatMap((g) => g.actions)
|
|
340
|
+
.filter((a) => a.action === 'skip');
|
|
341
|
+
const skipCount = skipActions.length;
|
|
342
|
+
const skipIdSet = new Set(skipActions.map((a) => `${a.type}:${a.name}`));
|
|
343
|
+
if (skipExisting && skipCount > 0) {
|
|
344
|
+
verifyResult.summary.skip = skipCount;
|
|
345
|
+
verifyResult.summary.update =
|
|
346
|
+
(verifyResult.summary.update ?? 0) - skipCount;
|
|
347
|
+
}
|
|
348
|
+
console.log((0, formatVerifyResultsText_1.formatVerifyResultsText)(verifyResult.entries, verifyResult.summary, 'pre-restore', verbosity_1.verbosityState.level, skipIdSet.size > 0 ? skipIdSet : undefined));
|
|
349
|
+
if (skipCount > 0) {
|
|
350
|
+
console.log(` (${skipCount} existing objects will be skipped)`);
|
|
351
|
+
}
|
|
352
|
+
console.log(`Plan updated and saved to ${output}`);
|
|
353
|
+
console.log(`Next step: adt-backup restore --plan ${output} --target ${destination || '<sys>'}`);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
if (command === 'restore') {
|
|
357
|
+
if (!client)
|
|
358
|
+
throw new Error('Client required');
|
|
359
|
+
const planPath = args.plan;
|
|
360
|
+
if (!planPath || typeof planPath !== 'string')
|
|
361
|
+
throw new Error('Missing --plan');
|
|
362
|
+
const plan = yaml_1.default.parse(fs.readFileSync(planPath, 'utf8'));
|
|
363
|
+
const backup = yaml_1.default.parse(fs.readFileSync(plan.backupFile, 'utf8'));
|
|
364
|
+
const allNodes = (0, flattenTree_1.flattenTree)(backup.root);
|
|
365
|
+
const _nodeMap = new Map(allNodes.map((n) => [(0, getNodeObjectId_1.getNodeObjectId)(n), n]));
|
|
366
|
+
const _backupPackageNames = new Set(allNodes.filter((n) => n.type === 'package').map((n) => n.name));
|
|
367
|
+
const noActivate = Boolean(args['no-activate']);
|
|
368
|
+
const activate = !noActivate && (Boolean(args.activate) || !args['no-activate-on-update']);
|
|
369
|
+
const activateOnCreate = !noActivate && !args['no-activate-on-create'];
|
|
370
|
+
const superPackage = typeof args['super-package'] === 'string'
|
|
371
|
+
? args['super-package']
|
|
372
|
+
: undefined;
|
|
373
|
+
const transportLayer = typeof args['transport-layer'] === 'string'
|
|
374
|
+
? args['transport-layer']
|
|
375
|
+
: undefined;
|
|
376
|
+
await (0, restoreTreeBackup_1.restoreTreeBackup)(client, backup.root, 'upsert', activate, typeof args.transport === 'string' ? args.transport : undefined, undefined, new Map(plan.groups
|
|
377
|
+
.flatMap((g) => g.actions)
|
|
378
|
+
.map((a) => [a.id, a.action])), activateOnCreate, typeof args['software-component'] === 'string'
|
|
379
|
+
? args['software-component']
|
|
380
|
+
: undefined, superPackage, transportLayer);
|
|
381
|
+
console.log('Restore completed. Running post-restore check on TARGET system...');
|
|
382
|
+
const postResult = await (0, verifyBackup_1.verifyBackup)(client, backup, {
|
|
383
|
+
mode: 'post-restore',
|
|
384
|
+
});
|
|
385
|
+
console.log((0, formatVerifyResultsText_1.formatVerifyResultsText)(postResult.entries, postResult.summary, 'post-restore', verbosity_1.verbosityState.level));
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
if (command === 'activate') {
|
|
389
|
+
if (!client)
|
|
390
|
+
throw new Error('Client required');
|
|
391
|
+
const planPath = args.plan;
|
|
392
|
+
if (!planPath || typeof planPath !== 'string')
|
|
393
|
+
throw new Error('Missing --plan');
|
|
394
|
+
const filter = typeof args.filter === 'string' ? args.filter : 'all';
|
|
395
|
+
if (!['skip', 'update', 'all'].includes(filter)) {
|
|
396
|
+
throw new Error(`Invalid --filter value: "${filter}". Must be skip, update, or all.`);
|
|
397
|
+
}
|
|
398
|
+
const plan = yaml_1.default.parse(fs.readFileSync(planPath, 'utf8'));
|
|
399
|
+
const allActions = plan.groups.flatMap((g) => g.actions);
|
|
400
|
+
const filtered = allActions.filter((a) => {
|
|
401
|
+
if (a.type === 'package')
|
|
402
|
+
return false;
|
|
403
|
+
if (!a.adtType)
|
|
404
|
+
return false;
|
|
405
|
+
if (filter === 'all')
|
|
406
|
+
return a.action !== 'create';
|
|
407
|
+
return a.action === filter;
|
|
408
|
+
});
|
|
409
|
+
if (filtered.length === 0) {
|
|
410
|
+
console.log('No objects to activate.');
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
console.log(`Activating ${filtered.length} object(s) (filter: ${filter})...`);
|
|
414
|
+
// Group by plan groups to respect dependency order
|
|
415
|
+
let activated = 0;
|
|
416
|
+
let skipped = 0;
|
|
417
|
+
let failed = 0;
|
|
418
|
+
for (const group of plan.groups) {
|
|
419
|
+
const groupRefs = [];
|
|
420
|
+
for (const action of group.actions) {
|
|
421
|
+
if (action.type === 'package' || !action.adtType)
|
|
422
|
+
continue;
|
|
423
|
+
if (filter !== 'all' && action.action !== filter)
|
|
424
|
+
continue;
|
|
425
|
+
if (filter === 'all' && action.action === 'create')
|
|
426
|
+
continue;
|
|
427
|
+
groupRefs.push({ name: action.name, type: action.adtType });
|
|
428
|
+
}
|
|
429
|
+
if (groupRefs.length === 0)
|
|
430
|
+
continue;
|
|
431
|
+
(0, logVerbose_1.logVerbose)(2, ` [GROUP ${group.id}] Activating ${groupRefs.length} object(s)...`);
|
|
432
|
+
try {
|
|
433
|
+
await client.getUtils().activateObjectsGroup(groupRefs, true);
|
|
434
|
+
activated += groupRefs.length;
|
|
435
|
+
for (const ref of groupRefs) {
|
|
436
|
+
(0, logVerbose_1.logVerbose)(2, ` OK ${ref.type} ${ref.name}`);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
catch (error) {
|
|
440
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
441
|
+
failed += groupRefs.length;
|
|
442
|
+
(0, logVerbose_1.logVerbose)(1, ` [!] Group ${group.id} activation failed: ${message}`);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
skipped = allActions.length - activated - failed;
|
|
446
|
+
console.log(`Activation complete: ${activated} activated, ${skipped} skipped, ${failed} failed`);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
364
449
|
if (command === 'diff') {
|
|
450
|
+
if (!client)
|
|
451
|
+
throw new Error('Client required');
|
|
365
452
|
const input = args.input;
|
|
366
453
|
const objectSpec = args.object;
|
|
367
454
|
const diffAll = Boolean(args.all);
|
|
368
|
-
const
|
|
455
|
+
const _showOk = Boolean(args['show-ok']);
|
|
369
456
|
const objectSpecValue = typeof objectSpec === 'string' ? objectSpec : '';
|
|
370
|
-
if (typeof input !== 'string')
|
|
457
|
+
if (typeof input !== 'string')
|
|
371
458
|
throw new Error('Missing --input');
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
throw new Error('Missing --object (or use --all)');
|
|
375
|
-
}
|
|
459
|
+
if (!diffAll && typeof objectSpec !== 'string')
|
|
460
|
+
throw new Error('Missing --object');
|
|
376
461
|
const raw = fs.readFileSync(input, 'utf8');
|
|
377
462
|
const parsed = yaml_1.default.parse(raw);
|
|
378
|
-
if (!parsed || typeof parsed !== 'object') {
|
|
379
|
-
throw new Error('Invalid backup file format');
|
|
380
|
-
}
|
|
381
463
|
(0, verifyBackupChecksum_1.verifyBackupChecksum)(parsed);
|
|
382
464
|
const diffMetadata = async (label, backupText, metadataXml, showNoDiff) => {
|
|
383
465
|
const beforeMeta = (0, extractMetadata_1.extractMetadata)(backupText);
|
|
384
466
|
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
|
-
}
|
|
467
|
+
if (beforeMeta.packageName === afterMeta.packageName) {
|
|
468
|
+
if (showNoDiff)
|
|
469
|
+
console.log(`=== ${label}\nNo differences`);
|
|
398
470
|
return false;
|
|
399
471
|
}
|
|
400
|
-
console.log(`=== ${label}`);
|
|
401
|
-
for (const change of changes) {
|
|
402
|
-
console.log(`changed ${change.key}: "${change.before ?? ''}" -> "${change.after ?? ''}"`);
|
|
403
|
-
}
|
|
472
|
+
console.log(`=== ${label}\nchanged packageName: "${beforeMeta.packageName ?? ''}" -> "${afterMeta.packageName ?? ''}"`);
|
|
404
473
|
return true;
|
|
405
474
|
};
|
|
406
475
|
const diffSource = async (label, backupText, actualSource, showNoDiff) => {
|
|
407
476
|
const unified = (0, diffUnified_1.diffUnified)(backupText, actualSource);
|
|
408
477
|
if (!unified.trim()) {
|
|
409
|
-
if (showNoDiff)
|
|
410
|
-
console.log(`=== ${label}`);
|
|
411
|
-
console.log('No differences');
|
|
412
|
-
}
|
|
478
|
+
if (showNoDiff)
|
|
479
|
+
console.log(`=== ${label}\nNo differences`);
|
|
413
480
|
return false;
|
|
414
481
|
}
|
|
415
|
-
console.log(`=== ${label}`);
|
|
416
|
-
console.log(unified);
|
|
482
|
+
console.log(`=== ${label}\n${unified}`);
|
|
417
483
|
return true;
|
|
418
484
|
};
|
|
419
|
-
let hasAnyDiff = false;
|
|
420
485
|
if (parsed.schemaVersion === 2) {
|
|
421
|
-
|
|
422
|
-
(0, verifyTreeChecksums_1.verifyTreeChecksums)(tree.root);
|
|
486
|
+
(0, verifyTreeChecksums_1.verifyTreeChecksums)(parsed.root);
|
|
423
487
|
if (!diffAll) {
|
|
424
488
|
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);
|
|
489
|
+
const node = (0, findNodeInTree_1.findNodeInTree)(parsed.root, spec);
|
|
490
|
+
if (!node || !node.codeBase64)
|
|
491
|
+
throw new Error('Object not found');
|
|
433
492
|
const backupText = (0, decodeBase64_1.decodeBase64)(node.codeBase64);
|
|
434
493
|
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');
|
|
494
|
+
const metadataXml = await (0, readMetadataXmlForType_1.readMetadataXmlForType)(client, node.type, node.name, node.functionGroupName);
|
|
495
|
+
if (metadataXml)
|
|
496
|
+
await diffMetadata((0, formatObjectSpec_1.formatObjectSpec)(spec), backupText, metadataXml, true);
|
|
463
497
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
498
|
+
else {
|
|
499
|
+
const actualSource = await (0, readSourceText_1.readSourceText)(client, spec);
|
|
500
|
+
await diffSource((0, formatObjectSpec_1.formatObjectSpec)(spec), backupText, actualSource ?? '', true);
|
|
467
501
|
}
|
|
468
502
|
return;
|
|
469
503
|
}
|
|
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
504
|
}
|
|
567
505
|
return;
|
|
568
506
|
}
|
|
569
|
-
if (command === '
|
|
507
|
+
if (command === 'validate') {
|
|
570
508
|
const input = args.input;
|
|
571
|
-
if (typeof input !== 'string')
|
|
509
|
+
if (typeof input !== 'string')
|
|
572
510
|
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
|
-
}
|
|
511
|
+
const parsed = yaml_1.default.parse(fs.readFileSync(input, 'utf8'));
|
|
581
512
|
(0, verifyBackupChecksum_1.verifyBackupChecksum)(parsed);
|
|
582
|
-
if (parsed.schemaVersion === 2)
|
|
513
|
+
if (parsed.schemaVersion === 2)
|
|
583
514
|
(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
|
-
}
|
|
515
|
+
console.log('Backup validated');
|
|
595
516
|
return;
|
|
596
517
|
}
|
|
597
|
-
if (command === '
|
|
518
|
+
if (command === 'extract') {
|
|
598
519
|
const input = args.input;
|
|
599
|
-
|
|
520
|
+
const objectSpec = args.object;
|
|
521
|
+
const output = args.out;
|
|
522
|
+
if (typeof input !== 'string' ||
|
|
523
|
+
typeof objectSpec !== 'string' ||
|
|
524
|
+
typeof output !== 'string')
|
|
525
|
+
throw new Error('Missing args');
|
|
526
|
+
const parsed = yaml_1.default.parse(fs.readFileSync(input, 'utf8'));
|
|
527
|
+
const spec = (0, parseObjectSpec_1.parseObjectSpec)(objectSpec);
|
|
528
|
+
const node = (0, findNodeInTree_1.findNodeInTree)(parsed.root, spec);
|
|
529
|
+
if (!node || !node.codeBase64)
|
|
530
|
+
throw new Error('Not found');
|
|
531
|
+
fs.writeFileSync(output, (0, decodeBase64_1.decodeBase64)(node.codeBase64), 'utf8');
|
|
532
|
+
console.log(`Extracted to ${output}`);
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
if (command === 'patch') {
|
|
536
|
+
const input = args.input;
|
|
537
|
+
const objectSpec = args.object;
|
|
538
|
+
const filePath = args.file;
|
|
539
|
+
if (typeof input !== 'string' ||
|
|
540
|
+
typeof objectSpec !== 'string' ||
|
|
541
|
+
typeof filePath !== 'string')
|
|
542
|
+
throw new Error('Missing args');
|
|
543
|
+
const output = typeof args.output === 'string' ? args.output : input;
|
|
544
|
+
const parsed = yaml_1.default.parse(fs.readFileSync(input, 'utf8'));
|
|
545
|
+
const node = (0, findNodeInTree_1.findNodeInTree)(parsed.root, (0, parseObjectSpec_1.parseObjectSpec)(objectSpec));
|
|
546
|
+
if (!node)
|
|
547
|
+
throw new Error('Not found');
|
|
548
|
+
node.codeBase64 = (0, encodeBase64_1.encodeBase64)(fs.readFileSync(filePath, 'utf8'));
|
|
549
|
+
node.codeChecksum = (0, computeCodeChecksum_1.computeCodeChecksum)(node.codeBase64);
|
|
550
|
+
(0, updateTreeChecksums_1.updateTreeChecksums)(parsed.root);
|
|
551
|
+
parsed.checksum = (0, computeBackupChecksum_1.computeBackupChecksum)(parsed);
|
|
552
|
+
fs.writeFileSync(output, yaml_1.default.stringify(parsed, { lineWidth: 0 }), 'utf8');
|
|
553
|
+
console.log(`Backup updated`);
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
if (command === 'check') {
|
|
557
|
+
if (!client)
|
|
558
|
+
throw new Error('Client required');
|
|
559
|
+
const input = args.input;
|
|
560
|
+
if (typeof input !== 'string')
|
|
600
561
|
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)`);
|
|
562
|
+
const backup = yaml_1.default.parse(fs.readFileSync(input, 'utf8'));
|
|
563
|
+
const result = await (0, verifyBackup_1.verifyBackup)(client, backup, {
|
|
564
|
+
strict: Boolean(args.strict),
|
|
565
|
+
});
|
|
566
|
+
console.log((0, formatVerifyResultsText_1.formatVerifyResultsText)(result.entries, result.summary, 'pre-restore', verbosity_1.verbosityState.level));
|
|
712
567
|
return;
|
|
713
568
|
}
|
|
714
569
|
throw new Error(`Unknown command: ${command}`);
|
|
715
570
|
}
|
|
716
571
|
function enableLogFile(logFile) {
|
|
717
572
|
const dir = path.dirname(logFile);
|
|
718
|
-
if (dir && dir !== '.')
|
|
573
|
+
if (dir && dir !== '.')
|
|
719
574
|
fs.mkdirSync(dir, { recursive: true });
|
|
720
|
-
}
|
|
721
575
|
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
576
|
const wrap = (fn) => (...args) => {
|
|
727
577
|
fn(...args);
|
|
728
|
-
|
|
578
|
+
stream.write(`${args.map((a) => (typeof a === 'string' ? a : JSON.stringify(a))).join(' ')}\n`);
|
|
729
579
|
};
|
|
730
580
|
console.log = wrap(console.log.bind(console));
|
|
731
|
-
console.info = wrap(console.info.bind(console));
|
|
732
|
-
console.warn = wrap(console.warn.bind(console));
|
|
733
581
|
console.error = wrap(console.error.bind(console));
|
|
734
582
|
}
|
|
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
|
-
}
|