arc-1-lsp 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/LICENSE +30 -0
  2. package/README.md +295 -0
  3. package/dist/adt-ls/cert.js +121 -0
  4. package/dist/adt-ls/cert.js.map +1 -0
  5. package/dist/adt-ls/destinations.js +116 -0
  6. package/dist/adt-ls/destinations.js.map +1 -0
  7. package/dist/adt-ls/discovery.js +59 -0
  8. package/dist/adt-ls/discovery.js.map +1 -0
  9. package/dist/adt-ls/driver.js +150 -0
  10. package/dist/adt-ls/driver.js.map +1 -0
  11. package/dist/adt-ls/lifecycle.js +96 -0
  12. package/dist/adt-ls/lifecycle.js.map +1 -0
  13. package/dist/adt-ls/mcp-federation.js +67 -0
  14. package/dist/adt-ls/mcp-federation.js.map +1 -0
  15. package/dist/adt-ls/mcp-lifecycle.js +10 -0
  16. package/dist/adt-ls/mcp-lifecycle.js.map +1 -0
  17. package/dist/adt-ls/repository.js +59 -0
  18. package/dist/adt-ls/repository.js.map +1 -0
  19. package/dist/adt-ls/session-retry.js +79 -0
  20. package/dist/adt-ls/session-retry.js.map +1 -0
  21. package/dist/adt-ls/tls-reverse-proxy.js +80 -0
  22. package/dist/adt-ls/tls-reverse-proxy.js.map +1 -0
  23. package/dist/btp/bridge.js +75 -0
  24. package/dist/btp/bridge.js.map +1 -0
  25. package/dist/btp/connectivity.js +27 -0
  26. package/dist/btp/connectivity.js.map +1 -0
  27. package/dist/btp/destination.js +22 -0
  28. package/dist/btp/destination.js.map +1 -0
  29. package/dist/btp/token.js +23 -0
  30. package/dist/btp/token.js.map +1 -0
  31. package/dist/btp/types.js +7 -0
  32. package/dist/btp/types.js.map +1 -0
  33. package/dist/btp/vcap.js +58 -0
  34. package/dist/btp/vcap.js.map +1 -0
  35. package/dist/index.js +35 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/server/auth.js +26 -0
  38. package/dist/server/auth.js.map +1 -0
  39. package/dist/server/config.js +53 -0
  40. package/dist/server/config.js.map +1 -0
  41. package/dist/server/engine.js +239 -0
  42. package/dist/server/engine.js.map +1 -0
  43. package/dist/server/http.js +52 -0
  44. package/dist/server/http.js.map +1 -0
  45. package/dist/server/logger.js +14 -0
  46. package/dist/server/logger.js.map +1 -0
  47. package/dist/server/safety.js +25 -0
  48. package/dist/server/safety.js.map +1 -0
  49. package/dist/server/server.js +186 -0
  50. package/dist/server/server.js.map +1 -0
  51. package/package.json +66 -0
package/LICENSE ADDED
@@ -0,0 +1,30 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Marian Zeis and contributors
4
+
5
+ Portions of this software are derived from ARC-1 (https://github.com/marianfoo/arc-1),
6
+ MIT License, Copyright (c) 2025-2026 Alice Vinogradova and contributors — specifically
7
+ the MCP server shell, configuration, authorization/scope model, audit, logging, and
8
+ Zod schema patterns that arc-1-lsp reuses.
9
+
10
+ This project does NOT include or redistribute SAP's `adt-ls` language server or any
11
+ other SAP binaries; those remain under SAP's own license and must be brought by the
12
+ user (see docs/adr/0002-byo-adt-ls-no-redistribution.md).
13
+
14
+ Permission is hereby granted, free of charge, to any person obtaining a copy
15
+ of this software and associated documentation files (the "Software"), to deal
16
+ in the Software without restriction, including without limitation the rights
17
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
+ copies of the Software, and to permit persons to whom the Software is
19
+ furnished to do so, subject to the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be included in all
22
+ copies or substantial portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,295 @@
1
+ # arc-1-lsp
2
+
3
+ An edition of **ARC-1** — a Model Context Protocol (MCP) server for SAP ABAP
4
+ development — that delegates **all** ABAP/ADT interaction to SAP's own embedded
5
+ **`adt-ls`** language server instead of a hand-rolled ADT HTTP client. arc-1-lsp
6
+ owns the MCP front-end, auth/scopes, write-safety, and orchestration; `adt-ls`
7
+ owns CSRF, locking, XML, activation, transport — everything system-specific.
8
+
9
+ > **Status:** working — connects headless to a SAP system, exposes 16 MCP tools
10
+ > (reads + a full create→edit→activate→test→delete authoring loop), runs locally
11
+ > over stdio or as a Docker app on SAP BTP Cloud Foundry. Single-tenant / one
12
+ > technical user today; per-user principal propagation is on the roadmap.
13
+
14
+ ## Where `adt-ls` comes from
15
+
16
+ `adt-ls` is **SAP's** language server: the headless core of the Eclipse-based
17
+ **ABAP Development Tools (ADT)**, shipped inside the official
18
+ [**ABAP Development Tools for VS Code**](https://marketplace.visualstudio.com/items?itemName=SAPSE.adt-vscode)
19
+ extension (`sapse.adt-vscode`). It speaks a private LSP namespace (`adtLs/*` —
20
+ destinations, logon, filesystem, activation, transport, unit tests) and embeds an
21
+ **experimental MCP server** (object creation, activation, generators, …).
22
+
23
+ arc-1-lsp does not reimplement any of that — it **discovers, spawns, and drives**
24
+ the developer-provided `adt-ls` headless (no Eclipse, no VS Code, no browser). The
25
+ `adt-ls` binary is under SAP's Developer License and is **never bundled or
26
+ redistributed** — you bring your own (see [Prerequisites](#prerequisites) and
27
+ [ADR-0002](docs/adr/0002-byo-adt-ls-no-redistribution.md)).
28
+
29
+ ## arc-1-lsp vs. main ARC-1 — which should I use?
30
+
31
+ Both are MCP servers for SAP ABAP and share the same tool shape. They differ in
32
+ *how* they talk to SAP, and therefore in what they can do.
33
+
34
+ | | **[ARC-1](https://github.com/marianfoo/arc-1)** (main) | **arc-1-lsp** (this repo) |
35
+ |---|---|---|
36
+ | ADT protocol | Hand-rolled (CSRF, locking, XML, version quirks) | Delegated to SAP's `adt-ls` |
37
+ | System-specific code to maintain | ~29 ADT modules | ~zero (it's SAP's job) |
38
+ | Object-type coverage | **All** — classic *and* modern (programs, tables, function groups, domains, CDS, classes, RAP, …) | **Modern ABAP-Cloud types only** (class, interface, CDS, behavior def, service def/binding, …) |
39
+ | Free SQL / data preview | ✅ | ❌ (absent in adt-ls) |
40
+ | Navigation / where-used, ATC/lint | ✅ | ❌ (not reachable headless) |
41
+ | Git (gCTS / abapGit) | ✅ | ❌ (absent in adt-ls) |
42
+ | Maturity | Production, multi-user, write-capable | Working; reads + authoring loop; single technical user |
43
+
44
+ **Use main ARC-1** for the broadest coverage (classic objects, SQL, ATC, git,
45
+ where-used) and production multi-user deployments. **Use arc-1-lsp** when you want
46
+ SAP itself to own the ADT protocol — less code to maintain, and behavior that
47
+ tracks ADT exactly — and your work is on modern ABAP-Cloud objects.
48
+
49
+ The honest, line-by-line map of what is and isn't wired (and *why*) lives in
50
+ [`docs/arc-1-feature-parity.md`](docs/arc-1-feature-parity.md); the live-verified
51
+ capability boundary of `adt-ls` itself is in
52
+ [`docs/adt-ls-reference.md`](docs/adt-ls-reference.md).
53
+
54
+ ## What works today
55
+
56
+ **16 MCP tools.** Reads work read-only; the authoring loop is gated behind
57
+ `ARC1_ALLOW_WRITES` + a package allowlist.
58
+
59
+ - **Reads (11):** `health`, `list_destinations`, `list_creatable_objects`,
60
+ `search_objects`, `list_inactive_objects`, `list_users`, `list_generators`,
61
+ `get_generator_schema`, `get_object_type_details`, `get_service_binding`,
62
+ `read_source`.
63
+ - **Authoring loop (5, write-gated):** `create_object`, `update_source`,
64
+ `activate_object`, `run_unit_tests`, `delete_object` — a full
65
+ create → edit → activate → test → delete cycle, by object name, for modern
66
+ ABAP-Cloud types. `activate_object` returns ranged syntax diagnostics so an
67
+ agent can self-correct.
68
+
69
+ **Out of scope here (use main ARC-1):** classic object types (program/table/
70
+ function group/domain/…), free SQL, navigation/where-used, ATC/lint, transport
71
+ *writes*, and git. These are honest limits of `adt-ls`'s headless surface, not
72
+ missing features — details in [`docs/arc-1-feature-parity.md`](docs/arc-1-feature-parity.md).
73
+
74
+ The SAP session behind `adt-ls` self-heals: if it expires (idle timeout →
75
+ "logged off"), arc-1-lsp transparently re-logs on and retries the call once.
76
+
77
+ ## Architecture
78
+
79
+ ```
80
+ agent (Claude / Copilot / Cursor / …)
81
+ │ MCP (stdio | http-streamable)
82
+
83
+ arc-1-lsp (Node/TS — discovers, spawns & supervises adt-ls; auth + scopes; owns SAP logon)
84
+ ├─ LSP over pipe ───────────▶ adt-ls (headless, BYO) ← bootstrap, destinations, logon, filesystem, activation
85
+ └─ HTTP localhost ─────────▶ adt-ls's own /mcp ← federated tools
86
+ │ HTTPS
87
+
88
+ TLS reverse proxy (CN=localhost, in arc-1-lsp)
89
+ │ DIRECT ───────────────▶ SAP ABAP (internet-reachable)
90
+ └ CC ─▶ connectivity bridge ─▶ BTP Connectivity ─▶ Cloud Connector ─▶ SAP ABAP
91
+ ```
92
+
93
+ `adt-ls` requires an **HTTPS** backend and validates its hostname; SAP's default
94
+ self-signed cert (`CN=*.dummy.nodomain`) fails that. So arc-1-lsp runs a local
95
+ **TLS-terminating reverse proxy** (cert `CN=localhost`, trusted via a truststore
96
+ built from `adt-ls`'s own JRE) and re-originates to the real backend — directly,
97
+ or through the connectivity bridge on BTP. Logon is **headless reentrance-ticket**
98
+ emulation (no browser). Full recipe + decisions:
99
+ [`docs/adt-ls-headless-notes.md`](docs/adt-ls-headless-notes.md) +
100
+ [`docs/adr/`](docs/adr/README.md).
101
+
102
+ ## Prerequisites
103
+
104
+ 1. **Node.js 22+**.
105
+ 2. **A developer-provided `adt-ls`** (BYO — never redistributed). Install the
106
+ official **ABAP Development Tools for VS Code** extension (`sapse.adt-vscode`,
107
+ which accepts SAP's Developer License) and arc-1-lsp finds its `adt-ls`
108
+ automatically. Discovery order:
109
+ 1. `ARC1_ADT_LS_PATH` (explicit path)
110
+ 2. `vendor/adt-ls/<platform>/…` (build-time injection, for containers)
111
+ 3. the newest installed `sapse.adt-vscode-*` VS Code extension
112
+ 3. **A reachable SAP ABAP system** to connect to (optional — the server also
113
+ starts disconnected and still serves `health`/`tools`). Runtime cert/proxy
114
+ deps: `openssl` + `keytool` (the latter ships inside `adt-ls`'s JRE). See
115
+ [`docs/native-deps.md`](docs/native-deps.md).
116
+
117
+ ## Install & run
118
+
119
+ ### From source (stdio)
120
+
121
+ ```bash
122
+ npm install
123
+ npm run build
124
+ node dist/index.js # or: npm run dev (tsx, no build)
125
+ ```
126
+
127
+ Point an MCP client at the process over **stdio**. With no SAP vars set it starts
128
+ disconnected (handy for inspecting the tool list); set `ARC1_SAP_*` to auto-connect
129
+ (see [Connect a SAP system](#connect-a-sap-system)).
130
+
131
+ ### As a CLI (npm)
132
+
133
+ ```bash
134
+ npm install -g arc1-lsp
135
+ arc1-lsp # stdio MCP server (honors the same env/flags)
136
+ ```
137
+
138
+ The npm package ships the Node wrapper only — it still discovers your BYO `adt-ls`
139
+ (it does **not** contain any SAP binary).
140
+
141
+ ### Docker / http-streamable
142
+
143
+ The container bundles a **build-time-injected** linux `adt-ls` and serves MCP over
144
+ http-streamable behind an API key — this is the artifact deployed to BTP CF.
145
+
146
+ ```bash
147
+ # stage the linux adt-ls (admin provides the licensed VSIX → vendor/)
148
+ node scripts/extract-adt-ls.mjs
149
+ # build the linux/amd64 image (host-builds dist; only prod deps + adt-ls are amd64)
150
+ IMAGE=arc-1-lsp:dev bash scripts/docker-build.sh
151
+ # run
152
+ docker run -e ARC1_API_KEYS=devkey -p 8080:8080 arc-1-lsp:dev
153
+ ```
154
+
155
+ `GET /healthz` (no auth) for health checks; `POST /mcp` with
156
+ `Authorization: Bearer <key>` for MCP.
157
+
158
+ ## Connect a SAP system
159
+
160
+ Set the `ARC1_SAP_*` vars (or `--sap-*` flags) and arc-1-lsp logs on at startup.
161
+
162
+ ```bash
163
+ ARC1_SAP_HOST=a4h.example.com ARC1_SAP_PORT=50001 \
164
+ ARC1_SAP_USER=DEVELOPER ARC1_SAP_PASSWORD=… ARC1_SAP_DESTINATION=A4H \
165
+ node dist/index.js
166
+ ```
167
+
168
+ `health` then reports `connectedDestination`, and `list_creatable_objects` returns
169
+ the system's object catalog. Two connection modes:
170
+
171
+ - **DIRECT** (default) — the reverse proxy connects straight to an
172
+ internet-reachable backend. All four of `HOST`/`PORT`/`USER`/`PASSWORD` must be set.
173
+ - **CC** (on-prem via Cloud Connector, on BTP) — bind the `connectivity` +
174
+ `destination` services and set only `ARC1_SAP_DESTINATION <btp-destination-name>`;
175
+ the engine resolves it and routes through the connectivity bridge automatically.
176
+
177
+ ### Connect an MCP client
178
+
179
+ ```jsonc
180
+ // Claude Code (HTTP):
181
+ // claude mcp add --transport http arc1lsp https://<host>/mcp \
182
+ // --header "Authorization: Bearer <api-key>"
183
+ //
184
+ // Cursor / Claude Desktop / VS Code — mcp.json:
185
+ {
186
+ "mcpServers": {
187
+ "arc1lsp": {
188
+ "url": "https://<host>/mcp",
189
+ "headers": { "Authorization": "Bearer <api-key>" }
190
+ }
191
+ }
192
+ }
193
+ ```
194
+
195
+ For local stdio, point the client at the `node dist/index.js` (or `arc1-lsp`)
196
+ process instead of a URL. GUI inspector: `npx @modelcontextprotocol/inspector`.
197
+
198
+ ## Configuration (precedence: CLI flag > env var > default)
199
+
200
+ | Env / flag | Default | Meaning |
201
+ |------------|---------|---------|
202
+ | `ARC1_ADT_LS_PATH` / `--adt-ls-path` | (discovered) | explicit `adt-ls` binary |
203
+ | `ARC1_ADT_LS_MCP_PORT` / `--adt-ls-mcp-port` | `2240` | port for `adt-ls`'s own MCP server |
204
+ | `ARC1_ADT_LS_MCP_TOKEN` / `--adt-ls-mcp-token` | (generated) | bearer for `adt-ls`'s MCP server |
205
+ | `ARC1_TRANSPORT` / `--transport` | `stdio` | `stdio` \| `http-streamable` |
206
+ | `ARC1_PORT` / `--port` | `8080` | HTTP port (http-streamable; CF `$PORT` honored) |
207
+ | `ARC1_API_KEYS` / `--api-keys` | (none) | edge auth: `key[:label][,key2…]`; empty disables auth (local only) |
208
+ | `ARC1_ALLOW_WRITES` / `--allow-writes` | `false` | enable mutating tools (create/update/activate/delete) |
209
+ | `ARC1_ALLOWED_PACKAGES` / `--allowed-packages` | `$TMP` | packages writes may target — exact / `PREFIX*` / `*` |
210
+ | `ARC1_LOG_LEVEL` | `info` | `debug`\|`info`\|`warn`\|`error` (stderr only) |
211
+ | **SAP connection — DIRECT mode** (internet-reachable backend) | | |
212
+ | `ARC1_SAP_HOST` / `--sap-host` | — | backend host |
213
+ | `ARC1_SAP_PORT` / `--sap-port` | — | backend **HTTPS** port |
214
+ | `ARC1_SAP_USER` / `--sap-user` | — | SAP user (the reentrance ticket is fetched with these creds) |
215
+ | `ARC1_SAP_PASSWORD` / `--sap-password` | — | SAP password (set via env / `cf set-env`, never committed) |
216
+ | `ARC1_SAP_DESTINATION` / `--sap-destination` | `SAP` | `adt-ls` destination id (DIRECT) **or** BTP destination name (CC) |
217
+ | `ARC1_SAP_CLIENT` / `--sap-client` | `001` | SAP client |
218
+ | `ARC1_SAP_LANGUAGE` / `--sap-language` | `EN` | SAP logon language |
219
+ | `ARC1_SAP_INSECURE` / `--sap-insecure` | `true` | accept the backend's self-signed cert (the proxy's own TLS is trusted separately) |
220
+ | **SAP connection — CC mode** (on-prem via Cloud Connector) | | |
221
+ | `ARC1_SAP_DESTINATION` | — | BTP Destination Service name; resolved when `connectivity` is bound |
222
+
223
+ > Config is read from CLI flags and the process environment only (no `.env`
224
+ > auto-loading) — export the vars in your shell or set them via `cf set-env`.
225
+
226
+ ## Deploy to BTP Cloud Foundry
227
+
228
+ The image deploys to CF as a docker app (see `manifest.yml`). Secrets stay out of
229
+ git — the API key, SAP creds, and the registry pull token are passed at deploy time:
230
+
231
+ ```bash
232
+ docker push ghcr.io/<owner>/arc-1-lsp:0.0.1
233
+ # secrets via cf set-env (never committed):
234
+ cf set-env arc-1-lsp ARC1_API_KEYS "$(openssl rand -hex 16)"
235
+ cf set-env arc-1-lsp ARC1_SAP_HOST <host>
236
+ cf set-env arc-1-lsp ARC1_SAP_PORT 50001
237
+ cf set-env arc-1-lsp ARC1_SAP_USER <user>
238
+ cf set-env arc-1-lsp ARC1_SAP_PASSWORD <secret>
239
+ cf set-env arc-1-lsp ARC1_SAP_DESTINATION <id>
240
+ cf set-env arc-1-lsp ARC1_ALLOW_WRITES true # optional, to enable the authoring loop
241
+ cf set-env arc-1-lsp ARC1_ALLOWED_PACKAGES '$TMP' # scope writes
242
+ # re-push (stop first if the org memory quota is tight — avoids a transient 2×2G):
243
+ cf stop arc-1-lsp
244
+ CF_DOCKER_PASSWORD=$(gh auth token) cf push arc-1-lsp -f manifest.yml
245
+ ```
246
+
247
+ `cf logs` shows `engine: connected destination …`; the MCP `health` tool reports
248
+ `connectedDestination`. `cf push` preserves `cf set-env` vars not listed in the
249
+ manifest. If the ghcr image is private, CF pulls it with `CF_DOCKER_PASSWORD`;
250
+ make the package public to drop that.
251
+
252
+ ## Test a running instance (local or CF)
253
+
254
+ The `/mcp` endpoint is **stateless** StreamableHTTP — a bare `tools/call` works, no
255
+ session handshake.
256
+
257
+ ```bash
258
+ ARC1_URL=https://<host>/mcp ARC1_KEY=<api-key> bash scripts/smoke-remote.sh
259
+ # → /healthz ok · health {connectedDestination} · tools/list · list_creatable_objects
260
+ ```
261
+
262
+ ## Documentation
263
+
264
+ | Doc | What it covers |
265
+ |-----|----------------|
266
+ | [`docs/adt-ls-reference.md`](docs/adt-ls-reference.md) | **The authoritative, live-verified `adt-ls` capability map** — URI model, the `getLsUri` name→URI resolver, the method/tool matrix, the object-type boundary, the proven lifecycle, session self-heal, gotchas |
267
+ | [`docs/arc-1-feature-parity.md`](docs/arc-1-feature-parity.md) | arc-1 vs arc-1-lsp coverage, per-capability "implemented? why / why not" |
268
+ | [`docs/adt-ls-headless-notes.md`](docs/adt-ls-headless-notes.md) | The reverse-engineered headless connection recipe (initialize, reentrance-ticket logon, TLS/truststore) |
269
+ | [`docs/adr/`](docs/adr/README.md) | Architecture Decision Records — each decision, its context, and **when to revisit** it |
270
+ | [`docs/assumptions-and-future-changes.md`](docs/assumptions-and-future-changes.md) | The watch-list: what to re-verify against new `adt-ls` releases, and what would let us delete complexity |
271
+ | [`docs/native-deps.md`](docs/native-deps.md) | System libraries `adt-ls` needs in a slim container |
272
+ | [`docs/journey.md`](docs/journey.md) | The chronological story, including dead-ends (so they aren't re-walked) |
273
+
274
+ Working on the code with Claude Code? [`CLAUDE.md`](CLAUDE.md) is the contributor
275
+ guide (design principles, codebase map, conventions).
276
+
277
+ ## Status & roadmap
278
+
279
+ ✅ foundation → ✅ containerize → ✅ deploy to BTP CF → ✅ headless connect
280
+ (DIRECT) → ✅ read + authoring-loop tools (16) → ✅ session self-heal.
281
+
282
+ **Next:** CC-mode deploy (code ready; needs a running Cloud Connector + bound
283
+ `connectivity`/`destination`), then **per-user principal propagation** (one
284
+ `adt-ls` session per user via the BTP Destination Service). Roadmap detail in
285
+ [`docs/plans/`](docs/plans/) and [`docs/assumptions-and-future-changes.md`](docs/assumptions-and-future-changes.md).
286
+
287
+ ## License & credits
288
+
289
+ [MIT](LICENSE) © 2026 Marian Zeis and contributors.
290
+
291
+ Built on the shell of **[ARC-1](https://github.com/marianfoo/arc-1)** (MIT,
292
+ © Alice Vinogradova and contributors) — arc-1-lsp reuses its MCP server,
293
+ configuration, authorization model, audit, and logging patterns. SAP's `adt-ls`
294
+ is **not** included or redistributed (SAP Developer License) — bring your own; see
295
+ [ADR-0002](docs/adr/0002-byo-adt-ls-no-redistribution.md).
@@ -0,0 +1,121 @@
1
+ /**
2
+ * TLS material for the headless adt-ls connection (ADR-0006).
3
+ *
4
+ * Two artifacts, both ephemeral (built at startup into a temp dir, never
5
+ * persisted):
6
+ * 1. a `CN=localhost` keypair+cert for the TLS reverse proxy
7
+ * (`tls-reverse-proxy.ts`), and
8
+ * 2. a JVM truststore = a copy of adt-ls's own JRE `cacerts` (so all public CAs
9
+ * still validate — needed for BTP later) + that localhost cert, so the JVM
10
+ * trusts the proxy. Injected into adt-ls via `JAVA_TOOL_OPTIONS`
11
+ * (launcher-agnostic; no `-vmargs` parsing risk).
12
+ *
13
+ * `keytool` is required and always ships with adt-ls's JRE; `openssl` is required
14
+ * for cert generation (present on macOS/Linux + the container image).
15
+ */
16
+ import { execFile } from 'node:child_process';
17
+ import { promises as fsp } from 'node:fs';
18
+ import fs from 'node:fs';
19
+ import path from 'node:path';
20
+ import { promisify } from 'node:util';
21
+ const execFileP = promisify(execFile);
22
+ export const TRUSTSTORE_PASSWORD = 'changeit'; // JRE cacerts default; truststore holds only public certs
23
+ export const PROXY_CERT_ALIAS = 'arc1-proxy-localhost';
24
+ /**
25
+ * Locate the bundled SAP Machine JRE's `keytool` + `cacerts` relative to the
26
+ * adt-ls binary. Layouts differ by platform:
27
+ * - linux/win: binDir/plugins/com.sap.adt.jvm.<ver>/jre/...
28
+ * - macOS: binDir/../Eclipse/plugins/com.sap.adt.jvm.<ver>/jre/...
29
+ */
30
+ export function resolveJreTools(adtLsBin) {
31
+ const binDir = path.dirname(adtLsBin);
32
+ const pluginRoots = [
33
+ path.join(binDir, 'plugins'),
34
+ path.join(binDir, '..', 'Eclipse', 'plugins'),
35
+ path.join(binDir, '..', '..', 'Eclipse', 'plugins'),
36
+ ];
37
+ const keytoolName = process.platform === 'win32' ? 'keytool.exe' : 'keytool';
38
+ for (const plugins of pluginRoots) {
39
+ if (!fs.existsSync(plugins))
40
+ continue;
41
+ const jvm = fs.readdirSync(plugins).find((d) => d.startsWith('com.sap.adt.jvm.'));
42
+ if (!jvm)
43
+ continue;
44
+ const jreLib = path.join(plugins, jvm, 'jre');
45
+ const keytool = path.join(jreLib, 'bin', keytoolName);
46
+ const cacerts = path.join(jreLib, 'lib', 'security', 'cacerts');
47
+ if (fs.existsSync(keytool))
48
+ return { keytool, cacerts };
49
+ }
50
+ throw new Error(`Could not locate the adt-ls JRE keytool relative to ${adtLsBin}. Looked under: ${pluginRoots.join(', ')}`);
51
+ }
52
+ /** Generate an ephemeral `CN=localhost` self-signed keypair+cert via openssl. */
53
+ export async function generateLocalhostCert(dir) {
54
+ await fsp.mkdir(dir, { recursive: true });
55
+ const keyPath = path.join(dir, 'proxy-key.pem');
56
+ const certPath = path.join(dir, 'proxy-cert.pem');
57
+ // CN=localhost (no SAN): adt-ls's verifier falls back to CN when no SAN is
58
+ // present, and the proxy is only ever reached as `localhost`. Avoiding -addext
59
+ // keeps this portable across OpenSSL and macOS LibreSSL.
60
+ await execFileP('openssl', [
61
+ 'req',
62
+ '-x509',
63
+ '-newkey',
64
+ 'rsa:2048',
65
+ '-nodes',
66
+ '-keyout',
67
+ keyPath,
68
+ '-out',
69
+ certPath,
70
+ '-days',
71
+ '825',
72
+ '-subj',
73
+ '/CN=localhost',
74
+ ]);
75
+ const [keyPem, certPem] = await Promise.all([fsp.readFile(keyPath, 'utf8'), fsp.readFile(certPath, 'utf8')]);
76
+ return { keyPath, certPath, keyPem, certPem };
77
+ }
78
+ /**
79
+ * Build a JVM truststore: copy the JRE cacerts (preserving public CAs) and import
80
+ * the proxy cert under PROXY_CERT_ALIAS. Returns the truststore path.
81
+ */
82
+ export async function buildTruststore(opts) {
83
+ const password = opts.password ?? TRUSTSTORE_PASSWORD;
84
+ await fsp.mkdir(path.dirname(opts.outPath), { recursive: true });
85
+ await fsp.copyFile(opts.cacerts, opts.outPath);
86
+ await execFileP(opts.keytool, [
87
+ '-importcert',
88
+ '-noprompt',
89
+ '-keystore',
90
+ opts.outPath,
91
+ '-storepass',
92
+ password,
93
+ '-alias',
94
+ PROXY_CERT_ALIAS,
95
+ '-file',
96
+ opts.certPath,
97
+ ]);
98
+ return opts.outPath;
99
+ }
100
+ /**
101
+ * One-shot: generate the localhost proxy cert + build the truststore from adt-ls's
102
+ * own JRE, into `workDir`. Returns everything the engine needs to start the proxy
103
+ * and spawn adt-ls trusting it.
104
+ */
105
+ export async function prepareAdtLsTls(opts) {
106
+ const { keytool, cacerts } = resolveJreTools(opts.adtLsBin);
107
+ const cert = await generateLocalhostCert(opts.workDir);
108
+ const trustStorePath = await buildTruststore({
109
+ keytool,
110
+ cacerts,
111
+ certPath: cert.certPath,
112
+ outPath: path.join(opts.workDir, 'truststore.p12'),
113
+ });
114
+ const javaToolOptions = [
115
+ `-Djavax.net.ssl.trustStore=${trustStorePath}`,
116
+ `-Djavax.net.ssl.trustStorePassword=${TRUSTSTORE_PASSWORD}`,
117
+ '-Djavax.net.ssl.trustStoreType=PKCS12',
118
+ ].join(' ');
119
+ return { proxyKeyPem: cert.keyPem, proxyCertPem: cert.certPem, trustStorePath, javaToolOptions };
120
+ }
121
+ //# sourceMappingURL=cert.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cert.js","sourceRoot":"","sources":["../../src/adt-ls/cert.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,116 @@
1
+ /**
2
+ * adt-ls destination + headless reentrance-ticket logon (ADR-0006).
3
+ *
4
+ * Proven end-to-end against a4h → `logonState:"connected"`. The recipe (see
5
+ * `docs/adt-ls-headless-notes.md`):
6
+ * 1. initializeService with an ISOLATED store path (never the global
7
+ * ~/.adtls/destinations.json, which is shared with the user's IDEs).
8
+ * 2. create with protocol:"http" (URL scheme lives in systemUrl) and
9
+ * authenticationKind:"reentranceTicket" (NOT basicAuth — that fails session
10
+ * dispatch: "password must not be null"). systemUrl points at the local TLS
11
+ * reverse proxy (https://localhost:<port>).
12
+ * 3. ensureLoggedOn triggers the server→client request
13
+ * `adtLs/destinations/requestBrowserBasedLogon`; our handler emulates the
14
+ * browser: GET logonUrl with real creds → 307 + reentrance-ticket → deliver
15
+ * to adt-ls's 127.0.0.1 listener → return TRUE IMMEDIATELY (fire-and-forget;
16
+ * awaiting the delivery deadlocks).
17
+ */
18
+ import http from 'node:http';
19
+ import https from 'node:https';
20
+ import { logger } from '../server/logger.js';
21
+ export const REQUEST_BROWSER_LOGON = 'adtLs/destinations/requestBrowserBasedLogon';
22
+ /** Must run before any destination op. Empty path = global store — DO NOT use. */
23
+ export function initializeDestinationsService(driver, storePath) {
24
+ return driver.sendRequest('adtLs/destinations/initializeService', {
25
+ destinationsStorePath: storePath,
26
+ workspaceFolderUris: [],
27
+ fileUris: [],
28
+ });
29
+ }
30
+ /** Create a reentrance-ticket destination. Returns the destination id on success. */
31
+ export function createDestination(driver, cfg) {
32
+ return driver.sendRequest('adtLs/destinations/create', {
33
+ id: cfg.id,
34
+ protocol: 'http', // ADT-over-HTTP (vs RFC); the URL scheme lives in systemUrl
35
+ properties: {
36
+ systemUrl: cfg.systemUrl,
37
+ authenticationKind: 'reentranceTicket',
38
+ ...(cfg.user ? { user: cfg.user } : {}),
39
+ client: cfg.client ?? '001',
40
+ language: cfg.language ?? 'EN',
41
+ },
42
+ });
43
+ }
44
+ export function ensureLoggedOn(driver, destinationId) {
45
+ return driver.sendRequest('adtLs/destinations/ensureLoggedOn', destinationId);
46
+ }
47
+ export function getLogonInfo(driver, destinationId) {
48
+ return driver.sendRequest('adtLs/destinations/getLogonInfo', destinationId);
49
+ }
50
+ /** Pull the reentrance `logonUrl` out of a requestBrowserBasedLogon params payload. */
51
+ export function extractLogonUrl(params) {
52
+ const p = params;
53
+ for (const item of p?.params ?? []) {
54
+ if (item?.field?.key === 'logonUrl' && typeof item.field.value === 'string')
55
+ return item.field.value;
56
+ }
57
+ // Fallback: find any reentranceticket URL in the payload.
58
+ const m = JSON.stringify(params ?? null).match(/https?:[^"\\]*reentranceticket[^"\\]*/);
59
+ return m ? m[0] : undefined;
60
+ }
61
+ function authHeader(creds) {
62
+ if (creds.kind === 'bearer')
63
+ return { Authorization: `Bearer ${creds.token}` };
64
+ return { Authorization: `Basic ${Buffer.from(`${creds.user}:${creds.password}`).toString('base64')}` };
65
+ }
66
+ /** GET without following redirects; resolves with status + Location. */
67
+ function httpGet(urlStr, opts = {}) {
68
+ return new Promise((resolve, reject) => {
69
+ const u = new URL(urlStr);
70
+ const lib = u.protocol === 'https:' ? https : http;
71
+ const req = lib.request({
72
+ hostname: u.hostname,
73
+ port: u.port,
74
+ path: `${u.pathname}${u.search}`,
75
+ method: 'GET',
76
+ headers: opts.headers,
77
+ ...(u.protocol === 'https:' ? { rejectUnauthorized: !opts.insecure } : {}),
78
+ }, (res) => {
79
+ res.resume(); // drain so the socket frees
80
+ resolve({ status: res.statusCode ?? 0, location: res.headers.location });
81
+ });
82
+ req.on('error', reject);
83
+ req.end();
84
+ });
85
+ }
86
+ /**
87
+ * Emulate the browser reentrance flow: GET logonUrl with real creds → 307 +
88
+ * reentrance-ticket in Location → deliver it to adt-ls's local 127.0.0.1 listener.
89
+ * `insecure` skips TLS verification when WE call the proxy/backend (self-signed).
90
+ */
91
+ export async function performReentranceLogon(logonUrl, creds, opts = {}) {
92
+ const r1 = await httpGet(logonUrl, { headers: authHeader(creds), insecure: opts.insecure });
93
+ if (!r1.location) {
94
+ throw new Error(`reentrance: no redirect Location (status ${r1.status}) from ${logonUrl}`);
95
+ }
96
+ // adt-ls's listener is bound on 127.0.0.1; the redirect uses `localhost`.
97
+ const deliver = r1.location.replace('localhost', '127.0.0.1');
98
+ await httpGet(deliver, { insecure: opts.insecure });
99
+ }
100
+ /**
101
+ * Build the `requestBrowserBasedLogon` handler. Fires the reentrance delivery
102
+ * fire-and-forget and returns `true` immediately — adt-ls's listener won't respond
103
+ * until this resolves (browser-flow semantics), so awaiting it deadlocks.
104
+ */
105
+ export function makeReentranceLogonHandler(creds, opts = {}) {
106
+ return (params) => {
107
+ const logonUrl = extractLogonUrl(params);
108
+ if (!logonUrl) {
109
+ logger.warn('requestBrowserBasedLogon: could not find logonUrl in params');
110
+ return false;
111
+ }
112
+ performReentranceLogon(logonUrl, creds, opts).catch((e) => logger.warn(`reentrance logon failed: ${e instanceof Error ? e.message : String(e)}`));
113
+ return true;
114
+ };
115
+ }
116
+ //# sourceMappingURL=destinations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"destinations.js","sourceRoot":"","sources":["../../src/adt-ls/destinations.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Locate a developer-provided `adt-ls` binary. arc-1-lsp never ships or
3
+ * redistributes adt-ls (SAP Developer License) — it discovers one the developer
4
+ * already installed.
5
+ *
6
+ * Resolution order:
7
+ * 1. explicit path (opts.explicitPath / ARC1_ADT_LS_PATH)
8
+ * 2. vendor/adt-ls/ in the repo (build-time injection for containers)
9
+ * 3. newest installed `sapse.adt-vscode-*` VS Code extension
10
+ */
11
+ import fs from 'node:fs';
12
+ import os from 'node:os';
13
+ import path from 'node:path';
14
+ /** Platform/arch-specific sub-path under an `adt-ls/` root. */
15
+ export function platformSubPath(platform = process.platform, arch = process.arch) {
16
+ const a = arch === 'arm64' ? 'aarch64' : arch === 'x64' ? 'x86_64' : arch;
17
+ switch (platform) {
18
+ case 'darwin':
19
+ return ['macosx', 'cocoa', a, 'Adt-ls.app', 'Contents', 'MacOS', 'adt-ls'];
20
+ case 'linux':
21
+ return ['linux', 'gtk', a, 'adt-ls'];
22
+ case 'win32':
23
+ return ['win32', 'win32', a, 'adt-lsc.exe'];
24
+ default:
25
+ throw new Error(`Unsupported platform: ${platform}`);
26
+ }
27
+ }
28
+ export function resolveAdtLsPath(opts = {}) {
29
+ const tried = [];
30
+ const sub = platformSubPath(opts.platform, opts.arch);
31
+ const explicit = opts.explicitPath ?? process.env.ARC1_ADT_LS_PATH;
32
+ if (explicit) {
33
+ tried.push(explicit);
34
+ if (fs.existsSync(explicit))
35
+ return explicit;
36
+ }
37
+ const repoRoot = opts.repoRoot ?? process.cwd();
38
+ const vendor = path.join(repoRoot, 'vendor', 'adt-ls', ...sub);
39
+ tried.push(vendor);
40
+ if (fs.existsSync(vendor))
41
+ return vendor;
42
+ const extDir = opts.extensionsDir ?? path.join(os.homedir(), '.vscode', 'extensions');
43
+ if (fs.existsSync(extDir)) {
44
+ const candidates = fs
45
+ .readdirSync(extDir)
46
+ .filter((d) => d.startsWith('sapse.adt-vscode-'))
47
+ .sort()
48
+ .reverse();
49
+ for (const c of candidates) {
50
+ const p = path.join(extDir, c, 'adt-ls', ...sub);
51
+ tried.push(p);
52
+ if (fs.existsSync(p))
53
+ return p;
54
+ }
55
+ }
56
+ const triedList = tried.map((t) => ` - ${t}`).join('\n');
57
+ throw new Error(`adt-ls binary not found. Set ARC1_ADT_LS_PATH, drop it in vendor/adt-ls/, or install the 'sapse.adt-vscode' extension. Tried:\n${triedList}`);
58
+ }
59
+ //# sourceMappingURL=discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../src/adt-ls/discovery.ts"],"names":[],"mappings":"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"}