@llm4ts/shell 2.27.0 → 2.28.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/kits/soap-ace/README.md +68 -0
- package/kits/soap-ace/fixtures/demo-bank-soap/RUNBOOK.md +15 -0
- package/kits/soap-ace/flows/lib/soap/Auth.js +133 -11
- package/kits/soap-ace/flows/lib/soap/Call.js +17 -5
- package/kits/soap-ace/flows/lib/soap/Discover.js +31 -3
- package/kits/soap-ace/flows/lib/soap/Envelope.js +29 -4
- package/kits/soap-ace/flows/lib/soap/Explore.js +1 -0
- package/kits/soap-ace/flows/lib/soap/Setup.js +202 -0
- package/kits/soap-ace/flows/lib/soap/Sts.js +216 -0
- package/kits/soap-ace/flows/lib/soap/Stub.js +8 -1
- package/kits/soap-ace/flows/lib/soap/Transport.js +245 -75
- package/kits/soap-ace/flows/lib/soap/Xml.js +4 -2
- package/kits/soap-ace/flows/soap-discover.js +1 -1
- package/kits/soap-ace/flows/soap-explore.js +20 -3
- package/kits/soap-ace/flows/soap-sample.js +143 -12
- package/package.json +4 -4
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import * as http from "node:http";
|
|
2
2
|
import * as https from "node:https";
|
|
3
|
+
import * as net from "node:net";
|
|
4
|
+
import * as tls from "node:tls";
|
|
3
5
|
import * as Context from "effect/Context";
|
|
4
6
|
import * as Duration from "effect/Duration";
|
|
5
7
|
import * as Effect from "effect/Effect";
|
|
6
8
|
import * as Redacted from "effect/Redacted";
|
|
7
9
|
import * as Ref from "effect/Ref";
|
|
8
10
|
import * as Schema from "effect/Schema";
|
|
11
|
+
import { bypassesProxy } from "./Auth.js";
|
|
9
12
|
import { DocumentLoadError, makeFileDocumentLoader } from "./Wsdl.js";
|
|
10
13
|
export class TransportError extends Schema.TaggedError()("TransportError", {
|
|
11
14
|
url: Schema.String,
|
|
12
|
-
reason: Schema.Literals(["scheme", "network", "tls", "timeout", "aborted"]),
|
|
15
|
+
reason: Schema.Literals(["scheme", "network", "tls", "timeout", "aborted", "proxy"]),
|
|
13
16
|
detail: Schema.String
|
|
14
17
|
}) {
|
|
15
18
|
get message() {
|
|
@@ -32,32 +35,166 @@ const errorCode = (cause) => typeof cause === "object" && cause !== null && "cod
|
|
|
32
35
|
? cause.code
|
|
33
36
|
: "request failed";
|
|
34
37
|
const tlsCodes = /CERT|SSL|TLS|SELF_SIGNED|UNABLE_TO|DEPTH_ZERO|ERR_OSSL/;
|
|
38
|
+
const unknownIssuer = /SELF_SIGNED|UNABLE_TO_VERIFY_LEAF_SIGNATURE|UNABLE_TO_GET_ISSUER|DEPTH_ZERO|CERT_UNTRUSTED|UNABLE_TO_GET_LOCAL_ISSUER/;
|
|
39
|
+
export const trustHint = 'the chain is not trusted; capture it with soap-sample "trust <side>"';
|
|
40
|
+
const describeTlsFailure = (code) => unknownIssuer.test(code) ? `${code} (${trustHint})` : code;
|
|
41
|
+
class ProxyRefused extends Error {
|
|
42
|
+
status;
|
|
43
|
+
constructor(status) {
|
|
44
|
+
super(`proxy refused the tunnel: HTTP ${status}`);
|
|
45
|
+
this.status = status;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
35
48
|
const flatHeaders = (headers) => Object.fromEntries(Object.entries(headers).flatMap(([name, value]) => value === undefined ? [] : [[name, Array.isArray(value) ? value.join(", ") : value]]));
|
|
36
49
|
const bufferOf = (secret) => secret === undefined ? undefined : Buffer.from(Redacted.value(secret));
|
|
50
|
+
/** The CA list a connection verifies against: system roots plus the profile's, or the profile's alone. */
|
|
51
|
+
export const caListFor = (resolved) => {
|
|
52
|
+
if (resolved?.ca === undefined) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
const own = new TextDecoder().decode(Redacted.value(resolved.ca));
|
|
56
|
+
return resolved.trust === "ca-only" ? [own] : [...tls.rootCertificates, own];
|
|
57
|
+
};
|
|
58
|
+
const tlsOptionsOf = (resolved, host) => {
|
|
59
|
+
const ca = caListFor(resolved);
|
|
60
|
+
return {
|
|
61
|
+
...(resolved === undefined
|
|
62
|
+
? {}
|
|
63
|
+
: {
|
|
64
|
+
cert: bufferOf(resolved.cert),
|
|
65
|
+
key: bufferOf(resolved.key),
|
|
66
|
+
pfx: bufferOf(resolved.pfx),
|
|
67
|
+
passphrase: resolved.passphrase === undefined ? undefined : Redacted.value(resolved.passphrase)
|
|
68
|
+
}),
|
|
69
|
+
// A pinned chain may hold only what the server sent (a leaf signed by a
|
|
70
|
+
// CA it never presents): trust it as captured, not only self-signed roots.
|
|
71
|
+
...(ca === undefined ? {} : { ca: [...ca], allowPartialTrustChain: true }),
|
|
72
|
+
servername: resolved?.servername ?? (net.isIP(host) === 0 ? host : undefined)
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
const portOf = (url) => url.port !== "" ? Number(url.port) : url.protocol === "https:" ? 443 : 80;
|
|
76
|
+
/** Whether this request goes through the proxy: configured and the host not excluded. */
|
|
77
|
+
export const proxyFor = (url, proxy) => proxy === undefined || bypassesProxy(url.hostname, proxy.noProxy) ? undefined : proxy;
|
|
78
|
+
/** An HTTP CONNECT tunnel to `target` through `proxy`; the raw socket, not yet TLS. */
|
|
79
|
+
const connectTunnel = (proxy, target, signal) => new Promise((resolve, reject) => {
|
|
80
|
+
const proxyUrl = new URL(proxy.url);
|
|
81
|
+
const headers = { host: `${target.hostname}:${portOf(target)}` };
|
|
82
|
+
if (proxy.authorization !== undefined) {
|
|
83
|
+
headers["proxy-authorization"] = Redacted.value(proxy.authorization);
|
|
84
|
+
}
|
|
85
|
+
const request = http.request({
|
|
86
|
+
host: proxyUrl.hostname,
|
|
87
|
+
port: portOf(proxyUrl),
|
|
88
|
+
method: "CONNECT",
|
|
89
|
+
path: `${target.hostname}:${portOf(target)}`,
|
|
90
|
+
headers,
|
|
91
|
+
signal
|
|
92
|
+
});
|
|
93
|
+
request.on("connect", (response, socket) => {
|
|
94
|
+
if (response.statusCode === 200) {
|
|
95
|
+
resolve(socket);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
socket.destroy();
|
|
99
|
+
reject(new ProxyRefused(response.statusCode ?? 0));
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
request.on("error", reject);
|
|
103
|
+
request.end();
|
|
104
|
+
});
|
|
105
|
+
/** The socket a request runs on: direct, or through the tunnel; TLS-wrapped for https. */
|
|
106
|
+
const openSocket = async (url, resolved, proxy, signal) => {
|
|
107
|
+
const via = proxyFor(url, proxy);
|
|
108
|
+
if (via === undefined) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
const tunnel = await connectTunnel(via, url, signal);
|
|
112
|
+
if (url.protocol !== "https:") {
|
|
113
|
+
return tunnel;
|
|
114
|
+
}
|
|
115
|
+
return new Promise((resolve, reject) => {
|
|
116
|
+
const secure = tls.connect({ ...tlsOptionsOf(resolved, url.hostname), socket: tunnel }, () => resolve(secure));
|
|
117
|
+
secure.on("error", reject);
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
const transportFailure = (shown, cause) => {
|
|
121
|
+
if (cause instanceof ProxyRefused) {
|
|
122
|
+
return new TransportError({ url: shown, reason: "proxy", detail: `HTTP ${cause.status}` });
|
|
123
|
+
}
|
|
124
|
+
const code = errorCode(cause);
|
|
125
|
+
return new TransportError({
|
|
126
|
+
url: shown,
|
|
127
|
+
reason: code === "ABORT_ERR" ? "aborted" : tlsCodes.test(code) ? "tls" : "network",
|
|
128
|
+
detail: tlsCodes.test(code) ? describeTlsFailure(code) : code
|
|
129
|
+
});
|
|
130
|
+
};
|
|
131
|
+
const withTimeout = (shown, timeout) => (effect) => effect.pipe(Effect.timeoutOrElse({
|
|
132
|
+
duration: timeout,
|
|
133
|
+
orElse: () => Effect.fail(new TransportError({
|
|
134
|
+
url: shown,
|
|
135
|
+
reason: "timeout",
|
|
136
|
+
detail: `no response within ${Duration.format(timeout)}`
|
|
137
|
+
}))
|
|
138
|
+
}));
|
|
139
|
+
const parseTarget = (raw, needsTls) => {
|
|
140
|
+
const shown = displayUrl(raw);
|
|
141
|
+
let url;
|
|
142
|
+
try {
|
|
143
|
+
url = new URL(raw);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return new TransportError({ url: shown, reason: "scheme", detail: "invalid URL" });
|
|
147
|
+
}
|
|
148
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
149
|
+
return new TransportError({
|
|
150
|
+
url: shown,
|
|
151
|
+
reason: "scheme",
|
|
152
|
+
detail: `${url.protocol} is not http(s)`
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
if (url.protocol === "http:" && needsTls) {
|
|
156
|
+
return new TransportError({
|
|
157
|
+
url: shown,
|
|
158
|
+
reason: "scheme",
|
|
159
|
+
detail: "client TLS needs an https URL"
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
return url;
|
|
163
|
+
};
|
|
164
|
+
const pemOf = (der) => `-----BEGIN CERTIFICATE-----\n${der
|
|
165
|
+
.toString("base64")
|
|
166
|
+
.replace(/(.{64})/g, "$1\n")
|
|
167
|
+
.trimEnd()}\n-----END CERTIFICATE-----\n`;
|
|
168
|
+
const nameOf = (subject) => Object.entries(subject ?? {})
|
|
169
|
+
.map(([key, value]) => `${key}=${Array.isArray(value) ? value.join("+") : String(value)}`)
|
|
170
|
+
.join(", ");
|
|
171
|
+
/** Walk `issuerCertificate` links, leaf first, stopping at the self-signed root. */
|
|
172
|
+
const chainOf = (leaf) => {
|
|
173
|
+
const out = [];
|
|
174
|
+
const seen = new Set();
|
|
175
|
+
let current = leaf;
|
|
176
|
+
while (current !== undefined && current.raw !== undefined && !seen.has(current.fingerprint256)) {
|
|
177
|
+
seen.add(current.fingerprint256);
|
|
178
|
+
out.push({
|
|
179
|
+
subject: nameOf(current.subject),
|
|
180
|
+
issuer: nameOf(current.issuer),
|
|
181
|
+
validFrom: current.valid_from,
|
|
182
|
+
validTo: current.valid_to,
|
|
183
|
+
fingerprint256: current.fingerprint256,
|
|
184
|
+
pem: pemOf(current.raw)
|
|
185
|
+
});
|
|
186
|
+
current = current.issuerCertificate === current ? undefined : current.issuerCertificate;
|
|
187
|
+
}
|
|
188
|
+
return out;
|
|
189
|
+
};
|
|
37
190
|
export const makeNodeSoapTransport = () => ({
|
|
38
191
|
send: (request) => {
|
|
39
192
|
const shown = displayUrl(request.url);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
return Effect.fail(new TransportError({ url: shown, reason: "scheme", detail: "invalid URL" }));
|
|
46
|
-
}
|
|
47
|
-
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
48
|
-
return Effect.fail(new TransportError({
|
|
49
|
-
url: shown,
|
|
50
|
-
reason: "scheme",
|
|
51
|
-
detail: `${url.protocol} is not http(s)`
|
|
52
|
-
}));
|
|
53
|
-
}
|
|
54
|
-
if (url.protocol === "http:" && request.tls !== undefined) {
|
|
55
|
-
return Effect.fail(new TransportError({
|
|
56
|
-
url: shown,
|
|
57
|
-
reason: "scheme",
|
|
58
|
-
detail: "client TLS needs an https URL"
|
|
59
|
-
}));
|
|
193
|
+
const target = parseTarget(request.url, request.tls !== undefined);
|
|
194
|
+
if (target instanceof TransportError) {
|
|
195
|
+
return Effect.fail(target);
|
|
60
196
|
}
|
|
197
|
+
const url = target;
|
|
61
198
|
const headers = { ...request.headers };
|
|
62
199
|
for (const [name, value] of request.secretHeaders)
|
|
63
200
|
headers[name] = Redacted.value(value);
|
|
@@ -65,67 +202,99 @@ export const makeNodeSoapTransport = () => ({
|
|
|
65
202
|
if (body !== undefined)
|
|
66
203
|
headers["content-length"] = String(body.length);
|
|
67
204
|
return Effect.tryPromise({
|
|
68
|
-
try: (signal) =>
|
|
205
|
+
try: (signal) => {
|
|
69
206
|
const started = performance.now();
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
? {}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
}).pipe(Effect.timeoutOrElse({
|
|
113
|
-
duration: request.timeout,
|
|
114
|
-
orElse: () => Effect.fail(new TransportError({
|
|
207
|
+
return openSocket(url, request.tls, request.proxy, signal).then((socket) => new Promise((resolve, reject) => {
|
|
208
|
+
const options = {
|
|
209
|
+
method: request.method,
|
|
210
|
+
headers,
|
|
211
|
+
signal,
|
|
212
|
+
...(url.protocol === "https:" ? tlsOptionsOf(request.tls, url.hostname) : {}),
|
|
213
|
+
...(socket === undefined ? {} : { createConnection: () => socket })
|
|
214
|
+
};
|
|
215
|
+
const handler = (response) => {
|
|
216
|
+
const chunks = [];
|
|
217
|
+
response.on("data", (chunk) => chunks.push(chunk));
|
|
218
|
+
response.on("error", reject);
|
|
219
|
+
response.on("end", () => resolve({
|
|
220
|
+
status: response.statusCode ?? 0,
|
|
221
|
+
headers: flatHeaders(response.headers),
|
|
222
|
+
body: new Uint8Array(Buffer.concat(chunks)),
|
|
223
|
+
elapsedMs: Math.round(performance.now() - started)
|
|
224
|
+
}));
|
|
225
|
+
};
|
|
226
|
+
const outgoing = url.protocol === "https:"
|
|
227
|
+
? https.request(url, options, handler)
|
|
228
|
+
: http.request(url, options, handler);
|
|
229
|
+
outgoing.on("error", reject);
|
|
230
|
+
if (body !== undefined)
|
|
231
|
+
outgoing.write(body);
|
|
232
|
+
outgoing.end();
|
|
233
|
+
}));
|
|
234
|
+
},
|
|
235
|
+
catch: (cause) => transportFailure(shown, cause)
|
|
236
|
+
}).pipe(withTimeout(shown, request.timeout));
|
|
237
|
+
},
|
|
238
|
+
peerChain: (raw, options) => {
|
|
239
|
+
const shown = displayUrl(raw);
|
|
240
|
+
const target = parseTarget(raw, false);
|
|
241
|
+
if (target instanceof TransportError) {
|
|
242
|
+
return Effect.fail(target);
|
|
243
|
+
}
|
|
244
|
+
const url = target;
|
|
245
|
+
if (url.protocol !== "https:") {
|
|
246
|
+
return Effect.fail(new TransportError({
|
|
115
247
|
url: shown,
|
|
116
|
-
reason: "
|
|
117
|
-
detail:
|
|
118
|
-
}))
|
|
119
|
-
}
|
|
248
|
+
reason: "scheme",
|
|
249
|
+
detail: "only an https endpoint has a chain"
|
|
250
|
+
}));
|
|
251
|
+
}
|
|
252
|
+
return Effect.tryPromise({
|
|
253
|
+
try: (signal) => {
|
|
254
|
+
const via = proxyFor(url, options.proxy);
|
|
255
|
+
const tunnelled = via === undefined ? Promise.resolve(undefined) : connectTunnel(via, url, signal);
|
|
256
|
+
return tunnelled.then((tunnel) => new Promise((resolve, reject) => {
|
|
257
|
+
const socket = tls.connect({
|
|
258
|
+
...(tunnel === undefined
|
|
259
|
+
? { host: url.hostname, port: portOf(url) }
|
|
260
|
+
: { socket: tunnel }),
|
|
261
|
+
servername: options.servername ?? (net.isIP(url.hostname) === 0 ? url.hostname : undefined),
|
|
262
|
+
rejectUnauthorized: false
|
|
263
|
+
}, () => {
|
|
264
|
+
const leaf = socket.getPeerCertificate(true);
|
|
265
|
+
const chain = {
|
|
266
|
+
host: url.hostname,
|
|
267
|
+
certificates: chainOf(leaf),
|
|
268
|
+
trustedBySystem: socket.authorized,
|
|
269
|
+
authorizationError: socket.authorizationError === undefined || socket.authorizationError === null
|
|
270
|
+
? undefined
|
|
271
|
+
: String(socket.authorizationError)
|
|
272
|
+
};
|
|
273
|
+
socket.end();
|
|
274
|
+
resolve(chain);
|
|
275
|
+
});
|
|
276
|
+
socket.on("error", reject);
|
|
277
|
+
signal.addEventListener("abort", () => socket.destroy());
|
|
278
|
+
}));
|
|
279
|
+
},
|
|
280
|
+
catch: (cause) => transportFailure(shown, cause)
|
|
281
|
+
}).pipe(withTimeout(shown, options.timeout));
|
|
120
282
|
}
|
|
121
283
|
});
|
|
122
284
|
/**
|
|
123
285
|
* A scripted transport for tests: each request goes to `respond`, and every
|
|
124
|
-
* request is recorded (secrets still redacted) for assertions.
|
|
286
|
+
* request is recorded (secrets still redacted) for assertions. `chain`
|
|
287
|
+
* answers `peerChain`; by default an endpoint presents nothing.
|
|
125
288
|
*/
|
|
126
|
-
export const makeFakeSoapTransport = (respond
|
|
289
|
+
export const makeFakeSoapTransport = (respond, chain = (url) => Effect.succeed({
|
|
290
|
+
host: new URL(url).hostname,
|
|
291
|
+
certificates: [],
|
|
292
|
+
trustedBySystem: true,
|
|
293
|
+
authorizationError: undefined
|
|
294
|
+
})) => Effect.map(Ref.make([]), (log) => ({
|
|
127
295
|
transport: {
|
|
128
|
-
send: (request) => Effect.andThen(Ref.update(log, (all) => [...all, request]), respond(request))
|
|
296
|
+
send: (request) => Effect.andThen(Ref.update(log, (all) => [...all, request]), respond(request)),
|
|
297
|
+
peerChain: (url) => chain(url)
|
|
129
298
|
},
|
|
130
299
|
requests: Ref.get(log)
|
|
131
300
|
}));
|
|
@@ -138,7 +307,7 @@ export const xmlResponse = (xml, status = 200) => ({
|
|
|
138
307
|
const isUrl = (location) => /^https?:\/\//i.test(location);
|
|
139
308
|
/**
|
|
140
309
|
* The document loader for discovery: local paths from disk, http(s) URLs
|
|
141
|
-
* through the transport with the profile's fetch-side auth and
|
|
310
|
+
* through the transport with the profile's fetch-side auth, TLS and proxy.
|
|
142
311
|
*/
|
|
143
312
|
export const makeTransportDocumentLoader = (transport, side, timeout = Duration.seconds(30)) => {
|
|
144
313
|
const files = makeFileDocumentLoader();
|
|
@@ -152,6 +321,7 @@ export const makeTransportDocumentLoader = (transport, side, timeout = Duration.
|
|
|
152
321
|
headers: { accept: "text/xml, application/xml, */*" },
|
|
153
322
|
secretHeaders: side.headers,
|
|
154
323
|
...(side.tls === undefined ? {} : { tls: side.tls }),
|
|
324
|
+
...(side.proxy === undefined ? {} : { proxy: side.proxy }),
|
|
155
325
|
timeout
|
|
156
326
|
})
|
|
157
327
|
.pipe(Effect.mapError((error) => new DocumentLoadError({
|
|
@@ -297,7 +297,8 @@ class Parser {
|
|
|
297
297
|
attributes,
|
|
298
298
|
children: [],
|
|
299
299
|
scope,
|
|
300
|
-
line
|
|
300
|
+
line,
|
|
301
|
+
span: { start, end: this.position }
|
|
301
302
|
};
|
|
302
303
|
}
|
|
303
304
|
if (this.text[this.position] !== ">")
|
|
@@ -340,7 +341,8 @@ class Parser {
|
|
|
340
341
|
attributes,
|
|
341
342
|
children,
|
|
342
343
|
scope,
|
|
343
|
-
line
|
|
344
|
+
line,
|
|
345
|
+
span: { start, end: this.position }
|
|
344
346
|
};
|
|
345
347
|
}
|
|
346
348
|
if (this.text.startsWith("<![CDATA[", this.position)) {
|
|
@@ -47,7 +47,7 @@ const program = Effect.gen(function* () {
|
|
|
47
47
|
const workspace = yield* makeNodeWorkspace(input.workDir);
|
|
48
48
|
const profile = service === undefined || service === ""
|
|
49
49
|
? undefined
|
|
50
|
-
: yield* loadAuthProfile(workspace, service);
|
|
50
|
+
: yield* loadAuthProfile(workspace, service, process.env.LLM4TS_SOAP_ENV?.trim() || undefined);
|
|
51
51
|
if (isUrl(location) && profile === undefined) {
|
|
52
52
|
yield* say("fetching without credentials (set LLM4TS_SOAP_SERVICE and write its auth.json for an authenticated WSDL)");
|
|
53
53
|
}
|
|
@@ -37,8 +37,9 @@ import { FlowAborted, Info, makeNodeWorkspace, mock, resolveFlowInput, runFlowMa
|
|
|
37
37
|
import { coderFor, judgmentConnectorFromEnvironment } from "@llm4ts/runner/Connectors";
|
|
38
38
|
import { nodeSecretSource, resolveSide } from "./lib/soap/Auth.js";
|
|
39
39
|
import { heuristicClass, judgeClass } from "./lib/soap/Classification.js";
|
|
40
|
-
import { discoverService, loadAuthProfile, loadCatalog, loadOperationsFile } from "./lib/soap/Discover.js";
|
|
40
|
+
import { discoverService, loadAuthProfile, profilePath, loadCatalog, loadOperationsFile } from "./lib/soap/Discover.js";
|
|
41
41
|
import { designStep, explorePaths, exploreService, renderExploreReport, writeEvidence } from "./lib/soap/Explore.js";
|
|
42
|
+
import { makeTokenSource } from "./lib/soap/Sts.js";
|
|
42
43
|
import { makeDirectoryStubTransport } from "./lib/soap/Stub.js";
|
|
43
44
|
import { makeNodeSoapTransport, makeTransportDocumentLoader } from "./lib/soap/Transport.js";
|
|
44
45
|
import { DocumentLoader } from "./lib/soap/Wsdl.js";
|
|
@@ -94,7 +95,9 @@ const program = Effect.gen(function* () {
|
|
|
94
95
|
let catalog;
|
|
95
96
|
let judged;
|
|
96
97
|
if (location !== undefined) {
|
|
97
|
-
const fetchProfile = requested === undefined
|
|
98
|
+
const fetchProfile = requested === undefined
|
|
99
|
+
? undefined
|
|
100
|
+
: yield* loadAuthProfile(workspace, requested, environment.LLM4TS_SOAP_ENV?.trim() || undefined);
|
|
98
101
|
const fetchSide = yield* resolveSide(nodeSecretSource(environment), fetchProfile?.fetch);
|
|
99
102
|
const discovered = yield* discoverService({
|
|
100
103
|
workspace,
|
|
@@ -124,7 +127,10 @@ const program = Effect.gen(function* () {
|
|
|
124
127
|
catalog = yield* loadCatalog(workspace, service);
|
|
125
128
|
yield* say(`continuing ${service}: ${catalog.operations.length} operations`);
|
|
126
129
|
}
|
|
127
|
-
const
|
|
130
|
+
const env = environment.LLM4TS_SOAP_ENV?.trim() || undefined;
|
|
131
|
+
const profile = yield* loadAuthProfile(workspace, service, env);
|
|
132
|
+
if (profile !== undefined)
|
|
133
|
+
yield* say(`profile: ${profilePath(service, env)}`);
|
|
128
134
|
const operations = yield* loadOperationsFile(workspace, service);
|
|
129
135
|
// Provisional probing asks the judgment only where the listed class
|
|
130
136
|
// and the heuristic already say read.
|
|
@@ -149,6 +155,17 @@ const program = Effect.gen(function* () {
|
|
|
149
155
|
judged,
|
|
150
156
|
secrets: nodeSecretSource(environment),
|
|
151
157
|
transport,
|
|
158
|
+
...(profile?.sts === undefined
|
|
159
|
+
? {}
|
|
160
|
+
: {
|
|
161
|
+
token: yield* makeTokenSource({
|
|
162
|
+
workspace,
|
|
163
|
+
service,
|
|
164
|
+
config: profile.sts,
|
|
165
|
+
secrets: nodeSecretSource(environment),
|
|
166
|
+
transport
|
|
167
|
+
})
|
|
168
|
+
}),
|
|
152
169
|
refresh: flags.refresh,
|
|
153
170
|
progress: (line) => say(` ${line}`)
|
|
154
171
|
});
|