@st-gr/abap-adt-skill 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/dist/client.js +79 -0
- package/dist/commands/activate.js +58 -0
- package/dist/commands/atc.js +39 -0
- package/dist/commands/classinfo.js +33 -0
- package/dist/commands/completion.js +57 -0
- package/dist/commands/create.js +69 -0
- package/dist/commands/debug.js +217 -0
- package/dist/commands/definition.js +66 -0
- package/dist/commands/delete.js +36 -0
- package/dist/commands/exec.js +112 -0
- package/dist/commands/package.js +24 -0
- package/dist/commands/pretty-print.js +57 -0
- package/dist/commands/quickfix.js +78 -0
- package/dist/commands/read.js +31 -0
- package/dist/commands/rename.js +40 -0
- package/dist/commands/run.js +16 -0
- package/dist/commands/search.js +25 -0
- package/dist/commands/structure.js +39 -0
- package/dist/commands/syntax.js +75 -0
- package/dist/commands/systems.js +514 -0
- package/dist/commands/table.js +65 -0
- package/dist/commands/test.js +100 -0
- package/dist/commands/transport.js +110 -0
- package/dist/commands/users.js +19 -0
- package/dist/commands/whereused.js +30 -0
- package/dist/commands/write.js +131 -0
- package/dist/diag-debug.js +47 -0
- package/dist/diag-raw.js +144 -0
- package/dist/diag-write.js +42 -0
- package/dist/index.js +336 -0
- package/dist/test-write.js +69 -0
- package/dist/util/credentials.js +42 -0
- package/dist/util/error-handler.js +39 -0
- package/dist/util/formatter.js +27 -0
- package/dist/util/landscape.js +121 -0
- package/dist/util/network.js +112 -0
- package/package.json +24 -0
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.connectionKey = connectionKey;
|
|
37
|
+
exports.saveConnection = saveConnection;
|
|
38
|
+
exports.loadConnection = loadConnection;
|
|
39
|
+
exports.loadConnectionByKey = loadConnectionByKey;
|
|
40
|
+
exports.setActiveConnection = setActiveConnection;
|
|
41
|
+
exports.listConnections = listConnections;
|
|
42
|
+
exports.systemsListCommand = systemsListCommand;
|
|
43
|
+
exports.systemsSearchCommand = systemsSearchCommand;
|
|
44
|
+
exports.systemsStatusCommand = systemsStatusCommand;
|
|
45
|
+
exports.systemsSwitchCommand = systemsSwitchCommand;
|
|
46
|
+
exports.systemsRemoveCommand = systemsRemoveCommand;
|
|
47
|
+
exports.systemsConnectCommand = systemsConnectCommand;
|
|
48
|
+
const fs = __importStar(require("fs"));
|
|
49
|
+
const path = __importStar(require("path"));
|
|
50
|
+
const readline = __importStar(require("readline"));
|
|
51
|
+
const abap_adt_api_patched_1 = require("@st-gr/abap-adt-api-patched");
|
|
52
|
+
const landscape_1 = require("../util/landscape");
|
|
53
|
+
const network_1 = require("../util/network");
|
|
54
|
+
const credentials_1 = require("../util/credentials");
|
|
55
|
+
function getConnectionFilePath() {
|
|
56
|
+
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
57
|
+
return path.join(home, ".sap", "adt-connection.json");
|
|
58
|
+
}
|
|
59
|
+
/** Decrypt a connection value — handles both encrypted (string) and plain (object) formats */
|
|
60
|
+
function decryptConnection(value) {
|
|
61
|
+
if (typeof value === "object")
|
|
62
|
+
return value; // legacy unencrypted
|
|
63
|
+
if (!(0, credentials_1.isWindows)())
|
|
64
|
+
return null; // can't decrypt on non-Windows
|
|
65
|
+
try {
|
|
66
|
+
return JSON.parse((0, credentials_1.decryptDPAPI)(value));
|
|
67
|
+
}
|
|
68
|
+
catch (_a) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/** Encrypt a connection for storage (returns DPAPI blob on Windows, plain object otherwise) */
|
|
73
|
+
function encryptConnection(conn) {
|
|
74
|
+
if (!(0, credentials_1.isWindows)())
|
|
75
|
+
return conn; // no DPAPI — store plaintext
|
|
76
|
+
try {
|
|
77
|
+
return (0, credentials_1.encryptDPAPI)(JSON.stringify(conn));
|
|
78
|
+
}
|
|
79
|
+
catch (_a) {
|
|
80
|
+
return conn; // fallback to plaintext
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function loadStore() {
|
|
84
|
+
try {
|
|
85
|
+
const raw = JSON.parse(fs.readFileSync(getConnectionFilePath(), "utf-8"));
|
|
86
|
+
// Migrate old single-connection format (pre-multi-connection)
|
|
87
|
+
if (raw.url && !raw.connections) {
|
|
88
|
+
const conn = raw;
|
|
89
|
+
const key = guessKeyFromUrl(conn);
|
|
90
|
+
const store = { active: key, connections: { [key]: encryptConnection(conn) } };
|
|
91
|
+
saveStore(store); // persist migration
|
|
92
|
+
return store;
|
|
93
|
+
}
|
|
94
|
+
// Migrate any plain-object entries to encrypted
|
|
95
|
+
const store = raw;
|
|
96
|
+
let migrated = false;
|
|
97
|
+
for (const [key, value] of Object.entries(store.connections)) {
|
|
98
|
+
if (typeof value === "object") {
|
|
99
|
+
store.connections[key] = encryptConnection(value);
|
|
100
|
+
migrated = true;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (migrated)
|
|
104
|
+
saveStore(store);
|
|
105
|
+
return store;
|
|
106
|
+
}
|
|
107
|
+
catch (_a) {
|
|
108
|
+
return { active: "", connections: {} };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function saveStore(store) {
|
|
112
|
+
const filePath = getConnectionFilePath();
|
|
113
|
+
const dir = path.dirname(filePath);
|
|
114
|
+
if (!fs.existsSync(dir))
|
|
115
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
116
|
+
fs.writeFileSync(filePath, JSON.stringify(store, null, 2));
|
|
117
|
+
}
|
|
118
|
+
function guessKeyFromUrl(conn) {
|
|
119
|
+
const host = new URL(conn.url).hostname.split(".")[0].toUpperCase();
|
|
120
|
+
return conn.client ? `${host}/${conn.client}` : host;
|
|
121
|
+
}
|
|
122
|
+
/** Make a connection key from SID + client */
|
|
123
|
+
function connectionKey(sid, client) {
|
|
124
|
+
return client ? `${sid.toUpperCase()}/${client}` : sid.toUpperCase();
|
|
125
|
+
}
|
|
126
|
+
/** Save a connection under a key and set it as active */
|
|
127
|
+
function saveConnection(key, conn) {
|
|
128
|
+
const store = loadStore();
|
|
129
|
+
store.connections[key] = encryptConnection(conn);
|
|
130
|
+
store.active = key;
|
|
131
|
+
saveStore(store);
|
|
132
|
+
}
|
|
133
|
+
/** Load the active connection */
|
|
134
|
+
function loadConnection() {
|
|
135
|
+
const store = loadStore();
|
|
136
|
+
if (!store.active)
|
|
137
|
+
return null;
|
|
138
|
+
const value = store.connections[store.active];
|
|
139
|
+
if (!value)
|
|
140
|
+
return null;
|
|
141
|
+
return decryptConnection(value);
|
|
142
|
+
}
|
|
143
|
+
/** Load a specific connection by key */
|
|
144
|
+
function loadConnectionByKey(key) {
|
|
145
|
+
const store = loadStore();
|
|
146
|
+
const value = store.connections[key];
|
|
147
|
+
if (!value)
|
|
148
|
+
return null;
|
|
149
|
+
return decryptConnection(value);
|
|
150
|
+
}
|
|
151
|
+
/** Set a different connection as active */
|
|
152
|
+
function setActiveConnection(key) {
|
|
153
|
+
const store = loadStore();
|
|
154
|
+
if (!store.connections[key])
|
|
155
|
+
return false;
|
|
156
|
+
store.active = key;
|
|
157
|
+
saveStore(store);
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
/** List all stored connections (decrypts each entry) */
|
|
161
|
+
function listConnections() {
|
|
162
|
+
const store = loadStore();
|
|
163
|
+
return Object.entries(store.connections).map(([key, value]) => ({
|
|
164
|
+
key,
|
|
165
|
+
conn: decryptConnection(value),
|
|
166
|
+
active: key === store.active,
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
169
|
+
function detectShell() {
|
|
170
|
+
const shell = (process.env.SHELL || process.env.ComSpec || "").toLowerCase();
|
|
171
|
+
if (shell.includes("bash") || shell.includes("zsh") || process.env.MSYSTEM)
|
|
172
|
+
return "bash";
|
|
173
|
+
if (shell.includes("powershell") || shell.includes("pwsh") || process.env.PSModulePath)
|
|
174
|
+
return "powershell";
|
|
175
|
+
return "cmd";
|
|
176
|
+
}
|
|
177
|
+
function envSetSyntax(shell, name, value) {
|
|
178
|
+
switch (shell) {
|
|
179
|
+
case "bash": return `export ${name}="${value}"`;
|
|
180
|
+
case "powershell": return `$env:${name} = "${value}"`;
|
|
181
|
+
case "cmd": return `set ${name}=${value}`;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function padRight(s, len) {
|
|
185
|
+
return s.length >= len ? s.slice(0, len) : s + " ".repeat(len - s.length);
|
|
186
|
+
}
|
|
187
|
+
function printTable(systems, showUrls) {
|
|
188
|
+
if (systems.length === 0) {
|
|
189
|
+
console.log("No systems found.");
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (showUrls) {
|
|
193
|
+
const hSid = "SID", hServer = "Server", hHttp = "HTTP URL", hName = "Name";
|
|
194
|
+
const wSid = Math.max(hSid.length, ...systems.map(s => s.sid.length));
|
|
195
|
+
const wServer = Math.max(hServer.length, ...systems.map(s => s.server.length));
|
|
196
|
+
const wHttp = Math.max(hHttp.length, ...systems.map(s => s.httpUrl.length));
|
|
197
|
+
console.log(`${padRight(hSid, wSid)} | ${padRight(hServer, wServer)} | ${padRight(hHttp, wHttp)} | ${hName}`);
|
|
198
|
+
console.log(`${"-".repeat(wSid)}-+-${"-".repeat(wServer)}-+-${"-".repeat(wHttp)}-+-${"-".repeat(40)}`);
|
|
199
|
+
for (const s of systems) {
|
|
200
|
+
console.log(`${padRight(s.sid, wSid)} | ${padRight(s.server, wServer)} | ${padRight(s.httpUrl, wHttp)} | ${s.name}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
const hSid = "SID", hServer = "Server", hName = "Name";
|
|
205
|
+
const wSid = Math.max(hSid.length, ...systems.map(s => s.sid.length));
|
|
206
|
+
const wServer = Math.max(hServer.length, ...systems.map(s => s.server.length));
|
|
207
|
+
console.log(`${padRight(hSid, wSid)} | ${padRight(hServer, wServer)} | ${hName}`);
|
|
208
|
+
console.log(`${"-".repeat(wSid)}-+-${"-".repeat(wServer)}-+-${"-".repeat(40)}`);
|
|
209
|
+
for (const s of systems) {
|
|
210
|
+
console.log(`${padRight(s.sid, wSid)} | ${padRight(s.server, wServer)} | ${s.name}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
async function systemsListCommand(options) {
|
|
215
|
+
const file = (0, landscape_1.findLandscapeFile)(options.landscape);
|
|
216
|
+
const systems = (0, landscape_1.parseLandscape)(file);
|
|
217
|
+
console.log(`Landscape: ${file} (${systems.length} systems)\n`);
|
|
218
|
+
printTable(systems, !!options.urls);
|
|
219
|
+
}
|
|
220
|
+
async function systemsSearchCommand(query, options) {
|
|
221
|
+
const file = (0, landscape_1.findLandscapeFile)(options.landscape);
|
|
222
|
+
const systems = (0, landscape_1.parseLandscape)(file);
|
|
223
|
+
const q = query.toUpperCase();
|
|
224
|
+
const filtered = systems.filter(s => s.sid.toUpperCase().includes(q) || s.name.toUpperCase().includes(q));
|
|
225
|
+
printTable(filtered, true);
|
|
226
|
+
}
|
|
227
|
+
function systemsStatusCommand() {
|
|
228
|
+
var _a;
|
|
229
|
+
const all = listConnections();
|
|
230
|
+
if (all.length === 0) {
|
|
231
|
+
console.log("No stored connections.");
|
|
232
|
+
console.log("Run 'abap-adt systems connect <SID> [client]' to connect.");
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
// Show all connections summary
|
|
236
|
+
console.log("Stored connections:\n");
|
|
237
|
+
for (const { key, conn, active } of all) {
|
|
238
|
+
const marker = active ? " *" : " ";
|
|
239
|
+
if (!conn) {
|
|
240
|
+
console.log(`${marker} ${padRight(key, 12)} (decrypt failed)`);
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
const proto = conn.url.startsWith("https://") ? "HTTPS" : "HTTP";
|
|
244
|
+
const cred = conn.encryptedPassword ? "DPAPI" : "no pwd";
|
|
245
|
+
console.log(`${marker} ${padRight(key, 12)} ${padRight(proto, 5)} ${padRight(conn.user || "-", 12)} ${padRight(cred, 7)} ${conn.url}`);
|
|
246
|
+
}
|
|
247
|
+
// Show active connection details
|
|
248
|
+
const conn = loadConnection();
|
|
249
|
+
if (conn) {
|
|
250
|
+
const isHttps = conn.url.startsWith("https://");
|
|
251
|
+
console.log(`\nActive: ${(_a = all.find(c => c.active)) === null || _a === void 0 ? void 0 : _a.key}`);
|
|
252
|
+
console.log(`URL: ${conn.url}`);
|
|
253
|
+
console.log(`Client: ${conn.client || "(default)"}`);
|
|
254
|
+
console.log(`Language: ${conn.language}`);
|
|
255
|
+
console.log(`HTTPS: ${isHttps ? "yes" : "no"}`);
|
|
256
|
+
console.log(`Self-signed: ${conn.allowSelfSigned ? "yes" : "no"}`);
|
|
257
|
+
console.log(`User: ${conn.user || "(not stored — defaults to OS username)"}`);
|
|
258
|
+
console.log(`Password: ${conn.encryptedPassword ? "stored (DPAPI-encrypted)" : "not stored (set ADT_PASS)"}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
function systemsSwitchCommand(key) {
|
|
262
|
+
// Try exact match first
|
|
263
|
+
if (setActiveConnection(key)) {
|
|
264
|
+
console.log(`Switched to ${key}`);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
// Try case-insensitive / partial match (e.g. "se1" matches "SE1/600")
|
|
268
|
+
const all = listConnections();
|
|
269
|
+
const upper = key.toUpperCase();
|
|
270
|
+
const matches = all.filter(c => c.key.toUpperCase().startsWith(upper));
|
|
271
|
+
if (matches.length === 1) {
|
|
272
|
+
setActiveConnection(matches[0].key);
|
|
273
|
+
console.log(`Switched to ${matches[0].key}`);
|
|
274
|
+
}
|
|
275
|
+
else if (matches.length > 1) {
|
|
276
|
+
console.error(`Ambiguous key '${key}'. Matches:`);
|
|
277
|
+
for (const m of matches) {
|
|
278
|
+
console.error(` ${m.key}`);
|
|
279
|
+
}
|
|
280
|
+
process.exit(1);
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
console.error(`No connection found for '${key}'.`);
|
|
284
|
+
console.error(`Stored: ${all.map(c => c.key).join(", ") || "(none)"}`);
|
|
285
|
+
process.exit(1);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
function systemsRemoveCommand(key) {
|
|
289
|
+
const store = loadStore();
|
|
290
|
+
// Try exact match
|
|
291
|
+
let matchedKey = Object.keys(store.connections).find(k => k === key);
|
|
292
|
+
// Try case-insensitive
|
|
293
|
+
if (!matchedKey) {
|
|
294
|
+
const upper = key.toUpperCase();
|
|
295
|
+
matchedKey = Object.keys(store.connections).find(k => k.toUpperCase() === upper);
|
|
296
|
+
}
|
|
297
|
+
if (!matchedKey) {
|
|
298
|
+
console.error(`No connection found for '${key}'.`);
|
|
299
|
+
process.exit(1);
|
|
300
|
+
}
|
|
301
|
+
delete store.connections[matchedKey];
|
|
302
|
+
if (store.active === matchedKey) {
|
|
303
|
+
const remaining = Object.keys(store.connections);
|
|
304
|
+
store.active = remaining.length > 0 ? remaining[0] : "";
|
|
305
|
+
}
|
|
306
|
+
saveStore(store);
|
|
307
|
+
console.log(`Removed ${matchedKey}`);
|
|
308
|
+
}
|
|
309
|
+
async function systemsConnectCommand(sid, clientArg, options) {
|
|
310
|
+
const file = (0, landscape_1.findLandscapeFile)(options.landscape);
|
|
311
|
+
const systems = (0, landscape_1.parseLandscape)(file);
|
|
312
|
+
const matches = systems.filter(s => s.sid.toUpperCase() === sid.toUpperCase());
|
|
313
|
+
if (matches.length === 0) {
|
|
314
|
+
console.error(`No system found with SID: ${sid}`);
|
|
315
|
+
console.error(`Use 'systems list' to see available systems.`);
|
|
316
|
+
process.exit(1);
|
|
317
|
+
}
|
|
318
|
+
let selected;
|
|
319
|
+
if (matches.length === 1) {
|
|
320
|
+
selected = matches[0];
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
// Multiple entries — pick first by default, show options to stderr
|
|
324
|
+
console.error(`Multiple entries for ${sid.toUpperCase()}:`);
|
|
325
|
+
for (let i = 0; i < matches.length; i++) {
|
|
326
|
+
console.error(` ${i + 1}. ${matches[i].name} — ${matches[i].server}`);
|
|
327
|
+
}
|
|
328
|
+
if (process.stdin.isTTY) {
|
|
329
|
+
const choice = await prompt(`Select [1]: `);
|
|
330
|
+
const idx = parseInt(choice || "1", 10) - 1;
|
|
331
|
+
if (idx < 0 || idx >= matches.length) {
|
|
332
|
+
console.error("Invalid selection.");
|
|
333
|
+
process.exit(1);
|
|
334
|
+
}
|
|
335
|
+
selected = matches[idx];
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
console.error(`Using first entry (non-interactive mode).`);
|
|
339
|
+
selected = matches[0];
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
// Resolve short hostname to FQDN via DNS
|
|
343
|
+
const fqdn = await (0, landscape_1.resolveHostnameFQDN)(selected.hostname);
|
|
344
|
+
// VPN safety: verify hostname resolves to a private IP
|
|
345
|
+
try {
|
|
346
|
+
await (0, network_1.assertPrivateHost)(fqdn);
|
|
347
|
+
}
|
|
348
|
+
catch (err) {
|
|
349
|
+
console.error(`Error: ${err.message}`);
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
352
|
+
// Gather credentials first (needed for HTTPURLLOC lookup)
|
|
353
|
+
const client = clientArg || "";
|
|
354
|
+
const defaultUser = (process.env.USERNAME || process.env.USER || "").toUpperCase();
|
|
355
|
+
let user = options.user || "";
|
|
356
|
+
let password = options.password || process.env.ADT_PASS || "";
|
|
357
|
+
const shell = detectShell();
|
|
358
|
+
if (process.stdin.isTTY && !user) {
|
|
359
|
+
user = await prompt(`Username [${defaultUser}]: `) || defaultUser;
|
|
360
|
+
}
|
|
361
|
+
else if (!user) {
|
|
362
|
+
user = defaultUser;
|
|
363
|
+
}
|
|
364
|
+
if (process.stdin.isTTY && !password) {
|
|
365
|
+
password = await promptHidden("Password: ");
|
|
366
|
+
}
|
|
367
|
+
if (!password) {
|
|
368
|
+
console.error("Error: No password. Set ADT_PASS in your shell before launching Claude:");
|
|
369
|
+
console.error(` ${envSetSyntax(shell, "ADT_PASS", "<your_password>")}`);
|
|
370
|
+
process.exit(1);
|
|
371
|
+
}
|
|
372
|
+
// Connect via HTTP and discover HTTPS from HTTPURLLOC
|
|
373
|
+
const httpPort = `80${selected.instanceNr}`;
|
|
374
|
+
const httpUrl = `http://${fqdn}:${httpPort}`;
|
|
375
|
+
let url = httpUrl;
|
|
376
|
+
let useHttps = false;
|
|
377
|
+
if (!options.noHttps) {
|
|
378
|
+
console.error(`Connecting via HTTP to discover HTTPS configuration...`);
|
|
379
|
+
try {
|
|
380
|
+
const adtClient = new abap_adt_api_patched_1.ADTClient(httpUrl, user, password, client, "EN", (0, abap_adt_api_patched_1.createSSLConfig)(true));
|
|
381
|
+
await adtClient.login();
|
|
382
|
+
// Query HTTPURLLOC for official HTTPS endpoint
|
|
383
|
+
const result = await adtClient.tableContents("HTTPURLLOC", 10);
|
|
384
|
+
const httpsRow = result.values.find((row) => String(row.PROTOCOL || "").trim().toUpperCase() === "HTTPS");
|
|
385
|
+
if (httpsRow) {
|
|
386
|
+
const httpsHost = String(httpsRow.HOST || "").trim().toLowerCase();
|
|
387
|
+
const httpsPort = parseInt(String(httpsRow.PORT || "0").trim(), 10);
|
|
388
|
+
if (httpsHost && httpsPort > 0) {
|
|
389
|
+
console.error(`HTTPURLLOC: HTTPS at ${httpsHost}:${httpsPort} — probing...`);
|
|
390
|
+
if (await (0, network_1.probePort)(httpsHost, httpsPort)) {
|
|
391
|
+
url = httpsPort === 443 ? `https://${httpsHost}` : `https://${httpsHost}:${httpsPort}`;
|
|
392
|
+
useHttps = true;
|
|
393
|
+
console.error(`HTTPS verified — using secure connection`);
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
console.error(`HTTPS port ${httpsPort} not reachable — using HTTP`);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
console.error(`No HTTPS configured in HTTPURLLOC — using HTTP`);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
catch (err) {
|
|
405
|
+
const is401 = String(err.message).includes("401");
|
|
406
|
+
if (is401) {
|
|
407
|
+
console.error(`Authentication failed (401) — check password for ${sid.toUpperCase()}`);
|
|
408
|
+
console.error(`HTTPS discovery skipped (cannot query HTTPURLLOC without valid credentials)`);
|
|
409
|
+
console.error(`Using HTTP — re-run 'systems connect' with correct password to discover HTTPS`);
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
console.error(`HTTPURLLOC lookup failed: ${err.message}`);
|
|
413
|
+
// TCP probe fallback — only for non-auth errors (timeout, connection refused)
|
|
414
|
+
// Auth failures mean we can't verify which system owns the HTTPS port
|
|
415
|
+
console.error(`Probing HTTPS ports on ${fqdn}...`);
|
|
416
|
+
for (const port of [443, 8443]) {
|
|
417
|
+
if (await (0, network_1.probePort)(fqdn, port)) {
|
|
418
|
+
url = port === 443 ? `https://${fqdn}` : `https://${fqdn}:${port}`;
|
|
419
|
+
useHttps = true;
|
|
420
|
+
console.error(`HTTPS available on port ${port}`);
|
|
421
|
+
break;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
if (!useHttps) {
|
|
425
|
+
console.error(`No HTTPS found — using HTTP`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
console.error(`Using HTTP (--no-https specified)`);
|
|
432
|
+
}
|
|
433
|
+
// Save connection to ~/.sap/adt-connection.json
|
|
434
|
+
const conn = {
|
|
435
|
+
url,
|
|
436
|
+
client,
|
|
437
|
+
language: "EN",
|
|
438
|
+
allowSelfSigned: useHttps,
|
|
439
|
+
user,
|
|
440
|
+
};
|
|
441
|
+
// Encrypt password with DPAPI on Windows
|
|
442
|
+
if ((0, credentials_1.isWindows)()) {
|
|
443
|
+
try {
|
|
444
|
+
conn.encryptedPassword = (0, credentials_1.encryptDPAPI)(password);
|
|
445
|
+
console.error(`Password encrypted with Windows DPAPI`);
|
|
446
|
+
}
|
|
447
|
+
catch (err) {
|
|
448
|
+
console.error(`Warning: DPAPI encryption failed: ${err.message}`);
|
|
449
|
+
console.error(`Password not stored — set ADT_PASS manually for subsequent commands`);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
else {
|
|
453
|
+
console.error(`Warning: DPAPI not available (non-Windows). Password not stored.`);
|
|
454
|
+
}
|
|
455
|
+
const key = connectionKey(selected.sid, client);
|
|
456
|
+
saveConnection(key, conn);
|
|
457
|
+
console.log(`Connected: ${selected.sid} client ${client || "(default)"} @ ${url}`);
|
|
458
|
+
console.log(`Connection saved to ~/.sap/adt-connection.json`);
|
|
459
|
+
if (conn.encryptedPassword) {
|
|
460
|
+
console.log(`Credentials: stored (DPAPI-encrypted, user: ${user})`);
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
console.log(`\nCredentials (set in your shell before using other commands):`);
|
|
464
|
+
if (user !== defaultUser) {
|
|
465
|
+
console.log(` ${envSetSyntax(shell, "ADT_USER", user)}`);
|
|
466
|
+
}
|
|
467
|
+
console.log(` ${envSetSyntax(shell, "ADT_PASS", "<your_password>")}`);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
function prompt(question) {
|
|
471
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
472
|
+
return new Promise(resolve => {
|
|
473
|
+
rl.question(question, answer => {
|
|
474
|
+
rl.close();
|
|
475
|
+
resolve(answer.trim());
|
|
476
|
+
});
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
function promptHidden(question) {
|
|
480
|
+
return new Promise(resolve => {
|
|
481
|
+
process.stderr.write(question);
|
|
482
|
+
const rl = readline.createInterface({ input: process.stdin, terminal: false });
|
|
483
|
+
// Attempt to hide input on TTY
|
|
484
|
+
if (process.stdin.isTTY) {
|
|
485
|
+
process.stdin.setRawMode(true);
|
|
486
|
+
}
|
|
487
|
+
let input = "";
|
|
488
|
+
const onData = (ch) => {
|
|
489
|
+
const c = ch.toString();
|
|
490
|
+
if (c === "\n" || c === "\r" || c === "\u0004") {
|
|
491
|
+
if (process.stdin.isTTY) {
|
|
492
|
+
process.stdin.setRawMode(false);
|
|
493
|
+
}
|
|
494
|
+
process.stdin.removeListener("data", onData);
|
|
495
|
+
process.stderr.write("\n");
|
|
496
|
+
rl.close();
|
|
497
|
+
resolve(input);
|
|
498
|
+
}
|
|
499
|
+
else if (c === "\u0003") {
|
|
500
|
+
// Ctrl+C
|
|
501
|
+
process.exit(1);
|
|
502
|
+
}
|
|
503
|
+
else if (c === "\u007f" || c === "\b") {
|
|
504
|
+
// Backspace
|
|
505
|
+
input = input.slice(0, -1);
|
|
506
|
+
}
|
|
507
|
+
else {
|
|
508
|
+
input += c;
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
process.stdin.on("data", onData);
|
|
512
|
+
process.stdin.resume();
|
|
513
|
+
});
|
|
514
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.tableCommand = tableCommand;
|
|
4
|
+
exports.sqlCommand = sqlCommand;
|
|
5
|
+
const client_1 = require("../client");
|
|
6
|
+
const formatter_1 = require("../util/formatter");
|
|
7
|
+
async function tableCommand(tableName, options) {
|
|
8
|
+
const client = await (0, client_1.getStatelessClient)();
|
|
9
|
+
const maxRows = options.rows ? parseInt(options.rows, 10) : 100;
|
|
10
|
+
const result = await client.tableContents(tableName.toUpperCase(), maxRows);
|
|
11
|
+
if (options.json) {
|
|
12
|
+
(0, formatter_1.output)(result, true);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if (!result.columns || result.columns.length === 0) {
|
|
16
|
+
console.log("No data returned.");
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
console.log(`Table: ${tableName.toUpperCase()}`);
|
|
20
|
+
console.log(`Rows returned: ${result.values.length}`);
|
|
21
|
+
console.log();
|
|
22
|
+
const cols = result.columns.map(c => c.name);
|
|
23
|
+
const rows = result.values.map((row) => {
|
|
24
|
+
var _a;
|
|
25
|
+
const r = {};
|
|
26
|
+
for (const col of cols) {
|
|
27
|
+
r[col] = String((_a = row[col]) !== null && _a !== void 0 ? _a : "");
|
|
28
|
+
}
|
|
29
|
+
return r;
|
|
30
|
+
});
|
|
31
|
+
if (rows.length > 0) {
|
|
32
|
+
console.log((0, formatter_1.formatTable)(rows, cols));
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
console.log("(empty)");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function sqlCommand(query, options) {
|
|
39
|
+
const client = await (0, client_1.getStatelessClient)();
|
|
40
|
+
const maxRows = options.rows ? parseInt(options.rows, 10) : 100;
|
|
41
|
+
const result = await client.runQuery(query, maxRows);
|
|
42
|
+
if (options.json) {
|
|
43
|
+
(0, formatter_1.output)(result, true);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (!result.columns || result.columns.length === 0) {
|
|
47
|
+
console.log("No data returned.");
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const cols = result.columns.map(c => c.name);
|
|
51
|
+
const rows = result.values.map((row) => {
|
|
52
|
+
var _a;
|
|
53
|
+
const r = {};
|
|
54
|
+
for (const col of cols) {
|
|
55
|
+
r[col] = String((_a = row[col]) !== null && _a !== void 0 ? _a : "");
|
|
56
|
+
}
|
|
57
|
+
return r;
|
|
58
|
+
});
|
|
59
|
+
if (rows.length > 0) {
|
|
60
|
+
console.log((0, formatter_1.formatTable)(rows, cols));
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
console.log("(empty)");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.testCommand = testCommand;
|
|
4
|
+
const client_1 = require("../client");
|
|
5
|
+
const formatter_1 = require("../util/formatter");
|
|
6
|
+
async function testCommand(objectUrl, options) {
|
|
7
|
+
const client = await (0, client_1.getClient)();
|
|
8
|
+
const flags = {
|
|
9
|
+
harmless: true,
|
|
10
|
+
dangerous: false,
|
|
11
|
+
critical: false,
|
|
12
|
+
short: true,
|
|
13
|
+
medium: false,
|
|
14
|
+
long: false
|
|
15
|
+
};
|
|
16
|
+
// Parse risk level
|
|
17
|
+
if (options.risk) {
|
|
18
|
+
const risks = options.risk.split(",");
|
|
19
|
+
flags.harmless = risks.includes("harmless");
|
|
20
|
+
flags.dangerous = risks.includes("dangerous");
|
|
21
|
+
flags.critical = risks.includes("critical");
|
|
22
|
+
// If user specifies a level, include all levels up to it
|
|
23
|
+
if (risks.includes("critical")) {
|
|
24
|
+
flags.harmless = true;
|
|
25
|
+
flags.dangerous = true;
|
|
26
|
+
flags.critical = true;
|
|
27
|
+
}
|
|
28
|
+
else if (risks.includes("dangerous")) {
|
|
29
|
+
flags.harmless = true;
|
|
30
|
+
flags.dangerous = true;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Parse duration
|
|
34
|
+
if (options.duration) {
|
|
35
|
+
const durations = options.duration.split(",");
|
|
36
|
+
flags.short = durations.includes("short");
|
|
37
|
+
flags.medium = durations.includes("medium");
|
|
38
|
+
flags.long = durations.includes("long");
|
|
39
|
+
if (durations.includes("long")) {
|
|
40
|
+
flags.short = true;
|
|
41
|
+
flags.medium = true;
|
|
42
|
+
flags.long = true;
|
|
43
|
+
}
|
|
44
|
+
else if (durations.includes("medium")) {
|
|
45
|
+
flags.short = true;
|
|
46
|
+
flags.medium = true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const classes = await client.unitTestRun(objectUrl, flags);
|
|
50
|
+
if (options.json) {
|
|
51
|
+
(0, formatter_1.output)(classes, true);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (classes.length === 0) {
|
|
55
|
+
console.log("No test classes found.");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
let totalMethods = 0;
|
|
59
|
+
let passedMethods = 0;
|
|
60
|
+
let failedMethods = 0;
|
|
61
|
+
for (const cls of classes) {
|
|
62
|
+
console.log(`\nTest Class: ${cls["adtcore:name"]}`);
|
|
63
|
+
console.log(` Risk: ${cls.riskLevel}, Duration: ${cls.durationCategory}`);
|
|
64
|
+
if (cls.alerts && cls.alerts.length > 0) {
|
|
65
|
+
for (const alert of cls.alerts) {
|
|
66
|
+
console.log(` [${alert.severity}] ${alert.kind}: ${alert.title}`);
|
|
67
|
+
for (const detail of alert.details) {
|
|
68
|
+
console.log(` ${detail}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
for (const method of cls.testmethods) {
|
|
73
|
+
totalMethods++;
|
|
74
|
+
const hasAlerts = method.alerts && method.alerts.length > 0;
|
|
75
|
+
const status = hasAlerts ? "FAILED" : "PASSED";
|
|
76
|
+
if (hasAlerts)
|
|
77
|
+
failedMethods++;
|
|
78
|
+
else
|
|
79
|
+
passedMethods++;
|
|
80
|
+
console.log(` ${status} ${method["adtcore:name"]} (${method.executionTime}ms)`);
|
|
81
|
+
if (hasAlerts) {
|
|
82
|
+
for (const alert of method.alerts) {
|
|
83
|
+
console.log(` [${alert.severity}] ${alert.kind}: ${alert.title}`);
|
|
84
|
+
for (const detail of alert.details) {
|
|
85
|
+
console.log(` ${detail}`);
|
|
86
|
+
}
|
|
87
|
+
if (alert.stack && alert.stack.length > 0) {
|
|
88
|
+
console.log(` Stack:`);
|
|
89
|
+
for (const entry of alert.stack) {
|
|
90
|
+
console.log(` ${entry["adtcore:name"]} ${entry["adtcore:description"]}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
console.log(`\n--- Summary: ${totalMethods} tests, ${passedMethods} passed, ${failedMethods} failed ---`);
|
|
98
|
+
if (failedMethods > 0)
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|