mirror of
https://github.com/penpot/penpot.git
synced 2026-05-10 18:48:23 +00:00
Merge remote-tracking branch 'origin/staging' into develop
This commit is contained in:
commit
49759021bf
120
.opencode/skills/nrepl-eval/SKILL.md
Normal file
120
.opencode/skills/nrepl-eval/SKILL.md
Normal file
@ -0,0 +1,120 @@
|
||||
---
|
||||
name: nrepl-eval
|
||||
description: Evaluate Clojure code via nREPL using the standalone tools/nrepl-eval.mjs CLI tool.
|
||||
---
|
||||
|
||||
# nREPL Eval
|
||||
|
||||
Evaluate Clojure (or ClojureScript) code via a running nREPL server using
|
||||
`tools/nrepl-eval.mjs` — a standalone CLI application.
|
||||
|
||||
Session state (defs, in-ns, etc.) persists across invocations via a stored
|
||||
session ID, so you can build up state incrementally.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
node tools/nrepl-eval.mjs [options] [<code>]
|
||||
```
|
||||
|
||||
The tool is also executable directly:
|
||||
```bash
|
||||
./tools/nrepl-eval.mjs [options] [<code>]
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
| Flag | Description | Default |
|
||||
|------|-------------|---------|
|
||||
| `-p, --port PORT` | nREPL server port | `6064` |
|
||||
| `-H, --host HOST` | nREPL server host | `127.0.0.1` |
|
||||
| `-t, --timeout MS` | Timeout in milliseconds | `120000` |
|
||||
| `--reset-session` | Discard stored session and start fresh | — |
|
||||
| `-e, --last-error` | Evaluate `*e` to retrieve the last exception | — |
|
||||
| `-h, --help` | Show help message | — |
|
||||
|
||||
## When to Use
|
||||
|
||||
Use this tool when you need to:
|
||||
|
||||
1. **Evaluate Clojure code** during development — test functions, inspect
|
||||
state, or run experiments against a running Clojure process.
|
||||
2. **Verify that edited files compile** — require namespaces with `:reload`
|
||||
to pick up changes.
|
||||
3. **Inspect the last exception** after a failed evaluation — use `-e` to
|
||||
print the error stored in `*e`.
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Session management
|
||||
|
||||
Sessions are persisted to `/tmp/penpot-nrepl-session-<host>-<port>`. State
|
||||
carries across calls automatically:
|
||||
|
||||
```bash
|
||||
./tools/nrepl-eval.mjs '(def x 42)'
|
||||
./tools/nrepl-eval.mjs 'x'
|
||||
# => 42
|
||||
```
|
||||
|
||||
Reset the session to start fresh:
|
||||
|
||||
```bash
|
||||
./tools/nrepl-eval.mjs --reset-session '(def x 0)'
|
||||
```
|
||||
|
||||
### 2. Evaluate code
|
||||
|
||||
**Single expression (inline) — uses default port 6064:**
|
||||
```bash
|
||||
./tools/nrepl-eval.mjs '(+ 1 2 3)'
|
||||
```
|
||||
|
||||
**Multiple expressions via heredoc (recommended — avoids escaping issues):**
|
||||
```bash
|
||||
./tools/nrepl-eval.mjs <<'EOF'
|
||||
(def x 10)
|
||||
(+ x 20)
|
||||
EOF
|
||||
```
|
||||
|
||||
**Override with a different port:**
|
||||
```bash
|
||||
./tools/nrepl-eval.mjs -p 7888 '(+ 1 2 3)'
|
||||
```
|
||||
|
||||
### 3. Inspect last exception
|
||||
|
||||
After code throws an error, retrieve the full exception details:
|
||||
|
||||
```bash
|
||||
./tools/nrepl-eval.mjs -e
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
**Require a namespace with reload:**
|
||||
```bash
|
||||
./tools/nrepl-eval.mjs "(require '[my.namespace :as ns] :reload)"
|
||||
```
|
||||
|
||||
**Test a function:**
|
||||
```bash
|
||||
./tools/nrepl-eval.mjs "(ns/my-function arg1 arg2)"
|
||||
```
|
||||
|
||||
**Long-running operation with custom timeout:**
|
||||
```bash
|
||||
./tools/nrepl-eval.mjs -t 300000 "(long-running-fn)"
|
||||
```
|
||||
|
||||
## Key Principles
|
||||
|
||||
- **Default port is 6064** — just pass code directly, no `-p` needed when
|
||||
your nREPL server is on 6064. Use `-p <PORT>` for a different port.
|
||||
- **Always use `:reload`** when requiring namespaces to pick up file changes.
|
||||
- **Session is reused** across invocations — defs, in-ns, and var bindings
|
||||
persist. Use `--reset-session` to clear.
|
||||
- **Do not start any server** — the tool connects to an existing nREPL
|
||||
server, it is not the agent's responsibility to start the nREPL server
|
||||
(assume the server is already running on the specified port).
|
||||
@ -659,9 +659,8 @@
|
||||
[& _args]
|
||||
(try
|
||||
(let [p (promise)]
|
||||
(when (contains? cf/flags :nrepl-server)
|
||||
(l/inf :hint "start nrepl server" :port 6064)
|
||||
(nrepl/start-server :bind "0.0.0.0" :port 6064))
|
||||
(l/inf :hint "start nrepl server" :port 6064)
|
||||
(nrepl/start-server :bind "0.0.0.0" :port 6064)
|
||||
|
||||
(start)
|
||||
(deref p))
|
||||
|
||||
@ -16,9 +16,10 @@
|
||||
"fmt": "./scripts/fmt"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@github/copilot": "^1.0.43",
|
||||
"@types/node": "^25.6.0",
|
||||
"@github/copilot": "^1.0.44",
|
||||
"@types/node": "^25.6.2",
|
||||
"esbuild": "^0.28.0",
|
||||
"opencode-ai": "^1.14.40"
|
||||
"nrepl-client": "^0.3.0",
|
||||
"opencode-ai": "^1.14.46"
|
||||
}
|
||||
}
|
||||
|
||||
196
pnpm-lock.yaml
generated
196
pnpm-lock.yaml
generated
@ -9,17 +9,20 @@ importers:
|
||||
.:
|
||||
devDependencies:
|
||||
'@github/copilot':
|
||||
specifier: ^1.0.43
|
||||
version: 1.0.43
|
||||
specifier: ^1.0.44
|
||||
version: 1.0.44
|
||||
'@types/node':
|
||||
specifier: ^25.6.0
|
||||
version: 25.6.0
|
||||
specifier: ^25.6.2
|
||||
version: 25.6.2
|
||||
esbuild:
|
||||
specifier: ^0.28.0
|
||||
version: 0.28.0
|
||||
nrepl-client:
|
||||
specifier: ^0.3.0
|
||||
version: 0.3.0
|
||||
opencode-ai:
|
||||
specifier: ^1.14.40
|
||||
version: 1.14.40
|
||||
specifier: ^1.14.46
|
||||
version: 1.14.46
|
||||
|
||||
packages:
|
||||
|
||||
@ -179,118 +182,128 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@github/copilot-darwin-arm64@1.0.43':
|
||||
resolution: {integrity: sha512-VMaWfoUwIt19TGzmvTv/In5ITgFWfu91ZILt4Lb77gSmVbwbs+DahP2lbvM1s/GtGwOknwhOJLp4q2WMgK3CoQ==}
|
||||
'@github/copilot-darwin-arm64@1.0.44':
|
||||
resolution: {integrity: sha512-9NqA5sT2spmNsehxhs51GhXRZIZga5nq+WcMl4LG2QrUPJRDwvHf1bDKqETJUBbYvBY8jONGuTKMRofkMI68YQ==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
hasBin: true
|
||||
|
||||
'@github/copilot-darwin-x64@1.0.43':
|
||||
resolution: {integrity: sha512-lCxg75zbgtWggb1p+IHhlhmulQj7BKIc+9pUsTwJ3Mjt51kiUYmD6LF2BD1/Ed4M3GMumSu4UTSIv9pL96n0Wg==}
|
||||
'@github/copilot-darwin-x64@1.0.44':
|
||||
resolution: {integrity: sha512-QPD8KtXx07SIKILGBl4JDhPyL2Qo0FMmaTYVxR6nkyHkHnFPsUZD6VWGR+T/KMLkcUXFM85Xc1ba9Y27s4nRrQ==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
hasBin: true
|
||||
|
||||
'@github/copilot-linux-arm64@1.0.43':
|
||||
resolution: {integrity: sha512-Jr1rvt/Syz5oVSiU53keRKEV8f5xTLkmiB2qasAV6Emk7K82/BZW5HfW8cDadfHnlAS1+UVPpoO+8ykgx4dikw==}
|
||||
'@github/copilot-linux-arm64@1.0.44':
|
||||
resolution: {integrity: sha512-Z8ScIUP433xS18f68NP9jM9zW320Xzpi2wf7Nig/VyfrwupBy25UTezydQMT0KQHLWTEleHOPcYnASY3HgJXnQ==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
hasBin: true
|
||||
|
||||
'@github/copilot-linux-x64@1.0.43':
|
||||
resolution: {integrity: sha512-uyWyPpcwMC1tIHJTA1OPzIm1i/Eeku0NjFzPYnFvpfGug9pkL4xcUr8A2UNl8cVvxq/VQMwtYckV3G12ySJTFw==}
|
||||
'@github/copilot-linux-x64@1.0.44':
|
||||
resolution: {integrity: sha512-KUl6lvJt0HNKaXSx0T0bIWJ3rvrGwgZYMlkDfqMbuMnZatEQJbjPwxmL/IDfp/c0DyKd7K+ajl17wHYcN/hJIQ==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
hasBin: true
|
||||
|
||||
'@github/copilot-win32-arm64@1.0.43':
|
||||
resolution: {integrity: sha512-vY3rwkW1h7ixSqYd9XT/xGjxkepvQVJK2q1sYhSPBN4Wvuk37fRjN7ox3QU1lhpxlYQMc/g+ZR58u5cx31n1lw==}
|
||||
'@github/copilot-win32-arm64@1.0.44':
|
||||
resolution: {integrity: sha512-JVJxZJwAc95ZfapgOXjNFwSqrWlvC3heo128L+CDkdZ6lwpD1dTGMHT/6rMMEeo3xjZmMm8tiynfwsHLDgTtvQ==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
hasBin: true
|
||||
|
||||
'@github/copilot-win32-x64@1.0.43':
|
||||
resolution: {integrity: sha512-AjGsebDbJmBxe9FFf2McSN5r2Joi8cFY879lxaQaXxfGjzOHblzvbhflbsX9x2GnvCfDdDiow413WvOGqKDH8g==}
|
||||
'@github/copilot-win32-x64@1.0.44':
|
||||
resolution: {integrity: sha512-Yj3KQ/DqwS50PwRtyQITX2mWIVZeJeX+y0faVSMwUUzG1qxmMcme7wimhKOyc4LSV11DpgVB9MSiBw2xys7iww==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
hasBin: true
|
||||
|
||||
'@github/copilot@1.0.43':
|
||||
resolution: {integrity: sha512-2FO825Aq4bwmHcXVyplW+CpZaJFUYjqtqjBGnueM31gu4ufn6ReurzB2swBQ6bn4Pquyy2KeodMRPpT6JaLMhw==}
|
||||
'@github/copilot@1.0.44':
|
||||
resolution: {integrity: sha512-wr/GmNOUaJK/giJK5abyB1oTpEowgFKLi+NJnlyAymKiK/GKCaRlJqiX23H2RetM8vD2hDYUFUFm9lTCooGy0g==}
|
||||
hasBin: true
|
||||
|
||||
'@types/node@25.6.0':
|
||||
resolution: {integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==}
|
||||
'@types/node@25.6.2':
|
||||
resolution: {integrity: sha512-sokuT28dxf9JT5Kady1fsXOvI4HVpjZa95NKT5y9PNTIrs2AsobR4GFAA90ZG8M+nxVRLysCXsVj6eGC7Vbrlw==}
|
||||
|
||||
bencode@2.0.3:
|
||||
resolution: {integrity: sha512-D/vrAD4dLVX23NalHwb8dSvsUsxeRPO8Y7ToKA015JQYq69MLDOMkC0uGZYA/MPpltLO8rt8eqFC2j8DxjTZ/w==}
|
||||
|
||||
esbuild@0.28.0:
|
||||
resolution: {integrity: sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
opencode-ai@1.14.40:
|
||||
resolution: {integrity: sha512-2Iwqe3wLdAGWxWGnTAPeOv7QNcm4stuWL2VQ7FM6OxKRE0h9zGoKtJ/bnX193hR6/QH81goOrocKfamRk4pM/A==}
|
||||
nrepl-client@0.3.0:
|
||||
resolution: {integrity: sha512-EcROXUrzlGHKOdu/E/5WB0OESCI0iGHhdXeYk9cULYtd72eFJrM/Q1umvjTBfKWlT62y76cnyLG/3CmSCqT12w==}
|
||||
|
||||
opencode-ai@1.14.46:
|
||||
resolution: {integrity: sha512-BX3xG3B/t85LpaVEZilbszOfP5aXc4C9e8oRBLX3rPsnnPXweKQZYfz9pn8/kfOz+8rOYuXGrBpJ86UxH/Le+Q==}
|
||||
hasBin: true
|
||||
|
||||
opencode-darwin-arm64@1.14.40:
|
||||
resolution: {integrity: sha512-dVItdZeaJw9xRtpVKQ0K9sigcaf8p/3nr3hW/NxJY3I/L+op29Wly5jnH0sdxq05gV/cGmJ0BTJzoYizXDDoKg==}
|
||||
opencode-darwin-arm64@1.14.46:
|
||||
resolution: {integrity: sha512-KFUZxvEjYHz4cecbbHKJ3utc61iq4GYPjGDEM/GZ9x885FV0xQwhjQXVSQgd1gy/bj/3tFWzVLho3GC6Ogy03A==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
opencode-darwin-x64-baseline@1.14.40:
|
||||
resolution: {integrity: sha512-ab2vqJmTPG48U2/6xZvPcZ+HdQBHFt6rpS0w2gURJobXHWgbW+uytfX4MCBprunS3K1DL7qoVWIgJRRu3YEMzQ==}
|
||||
opencode-darwin-x64-baseline@1.14.46:
|
||||
resolution: {integrity: sha512-3OZgCnPZZ19fI1Ju0EHTM7kHPIwadD58OBFSLhCAdJLH00OGRpbpoqxy24gbCm9LnompFjp2DnEQ+KA6+RFIeQ==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
opencode-darwin-x64@1.14.40:
|
||||
resolution: {integrity: sha512-Mcfh2LP/QA3LJ03m6OF1FNCMO/O0PsT94Mw7NXbcmi6g+nQ6I2vHensIjKtTJM3LSmosOlTduxdv/b82AVFt+w==}
|
||||
opencode-darwin-x64@1.14.46:
|
||||
resolution: {integrity: sha512-+cC9EoVeoU2yLWIq7835QQginkTE8S7rrjMRJk543ZZ+S0yHsrnhEQVkgqhX+Yzncb8Subt2wISF7yROAzq7zA==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
opencode-linux-arm64-musl@1.14.40:
|
||||
resolution: {integrity: sha512-5COW6bDCCbM/BZcrkiVR91VqrP9vD8J4uS7cHLPbdQHaDYETxOhHWgsYnp9JXWEbcDi6lBaxapj11RZPyUJziw==}
|
||||
opencode-linux-arm64-musl@1.14.46:
|
||||
resolution: {integrity: sha512-2DmmX4WuqrKdEo/rxK/ARA39fCecco53F0/v8t7gk+njbe5fWrKYrfbtL3OaGr8LUsQolny5O05I0tjBsGcTUQ==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
opencode-linux-arm64@1.14.40:
|
||||
resolution: {integrity: sha512-nqsnGxV237XWCbaMtFXOiN1ndukNAhAmbe5dnDThSFOKcgwMs6VKuhHa+iOpQL1r6FZ5eE3SU6IKgscVM/eAAQ==}
|
||||
opencode-linux-arm64@1.14.46:
|
||||
resolution: {integrity: sha512-gahGbcQNJ6CAXWeG06wW/U2S+GB2ccmhvp4bAXDZeRm6Liw7M4NzOZX9H7sboTGtcYTnVouBPvoKPaX59qv9rA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
opencode-linux-x64-baseline-musl@1.14.40:
|
||||
resolution: {integrity: sha512-l0xfAHVy2klD181UbKUjeMVltqEn+9DTP/bZ9ZyDuiFbVNeKfwiU7HMkPw5GFnSnlQTUrDHniXMwgBcI5yrRcA==}
|
||||
opencode-linux-x64-baseline-musl@1.14.46:
|
||||
resolution: {integrity: sha512-lMJiPsb8b+aGjqXdAmfu9f5HyTAS6Cfk8O1GieZFu06pi8kO9oiJ6wPyQwwL8IM6J2ssNF4PxloHgqeNQI7kEg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
opencode-linux-x64-baseline@1.14.40:
|
||||
resolution: {integrity: sha512-XmXJHicRNylnGz/nzJCmsXy6mozjmrrXHntrrS3DWgq8UiSdg9wB2Hsq+1EDbPjzF2NzQaVi6/9/AyERuxW2iA==}
|
||||
opencode-linux-x64-baseline@1.14.46:
|
||||
resolution: {integrity: sha512-oq6Px+0epCwk2nZn54EUDtxccGb9JlSnF1stAm4oKUeKg/G76hGEbCgg4x3TNiIrpA8QnS9grRlh1oFYIl/efg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
opencode-linux-x64-musl@1.14.40:
|
||||
resolution: {integrity: sha512-AU8SAE4aOaGxzTDcv95GU8vrWeiYOzPVEt5LfcqhmOmhRiuN0jnDBO/gl62SITkGyu/sSes554L4UqkqkglC7g==}
|
||||
opencode-linux-x64-musl@1.14.46:
|
||||
resolution: {integrity: sha512-rUmQHsrlIWcLBmefiom71DWhRzQ0oSqjgvF2Xvg5ySs0GKklRzMB/HcS2ccIydqv02ImL94FbqVVXSZ2lnw4IQ==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
opencode-linux-x64@1.14.40:
|
||||
resolution: {integrity: sha512-Cb+keGDsjo1wyOJ2Kf+KOZWlJUs1fD/VmjYepl9Fv3KGqWQNhSOH/4kiwj3KHWwEMWroCtw5KnAFykiOgbsz8A==}
|
||||
opencode-linux-x64@1.14.46:
|
||||
resolution: {integrity: sha512-qxFHxbyP3dCFX6vibX71JfVntEbzE7rSMmot6EwbdDB0vq+tcahFEZ+/KVC7qV3zL3ZbDAMsx8OiZknI7Cscmw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
opencode-windows-arm64@1.14.40:
|
||||
resolution: {integrity: sha512-049+Z8G4o10OO7s5dGIVIZ31DMR+0JlAWCM/XR+VmBlbPeZYRKL/9Q2dM0Ljw11ekZ1qbZfGlK7xUEHkwvy8fQ==}
|
||||
opencode-windows-arm64@1.14.46:
|
||||
resolution: {integrity: sha512-SmeKzxFNiiF9s9l0lj1CxIZesE+m0VvBX5GuW5CHAlVqNLjmQeW3X8qhHmXelvm9ujGxAudjmXBUlZTkBkH9FQ==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
opencode-windows-x64-baseline@1.14.40:
|
||||
resolution: {integrity: sha512-6JKSyc8oyCw4ZLMZ38l9/M7iQYfMF7qnc3d+P9dpfx80FiqHwWBxgYaGEaNHkQmoiTPVc39EsjaOiiBOLGZOQA==}
|
||||
opencode-windows-x64-baseline@1.14.46:
|
||||
resolution: {integrity: sha512-6rehvrK+KCqUUKBKFnjpk6YchpiH3TNJdOYTmg+QcRirynyhNjs83F7h29vr+WbSnImMCPYFeRfBAf51yLiAmQ==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
opencode-windows-x64@1.14.40:
|
||||
resolution: {integrity: sha512-0+La4i8fj+E+kj6hu8sQKfjbHm6VgbbSiZ6vo3fYqU4+Ex2UIyNa31A3rWOQ9EFbmKQ8jb2PmfBDvDE0ihJvRw==}
|
||||
opencode-windows-x64@1.14.46:
|
||||
resolution: {integrity: sha512-m9LPvvNV9UvgYc5AbzR+hBBD2wPx+bitjbBTXNx/7s+WE/5DhlaHsxVQPePrA9bZ8XAO0EndRnDjvCrLLrkxWA==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
tree-kill@1.2.2:
|
||||
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
|
||||
hasBin: true
|
||||
|
||||
undici-types@7.19.2:
|
||||
resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==}
|
||||
|
||||
@ -374,37 +387,39 @@ snapshots:
|
||||
'@esbuild/win32-x64@0.28.0':
|
||||
optional: true
|
||||
|
||||
'@github/copilot-darwin-arm64@1.0.43':
|
||||
'@github/copilot-darwin-arm64@1.0.44':
|
||||
optional: true
|
||||
|
||||
'@github/copilot-darwin-x64@1.0.43':
|
||||
'@github/copilot-darwin-x64@1.0.44':
|
||||
optional: true
|
||||
|
||||
'@github/copilot-linux-arm64@1.0.43':
|
||||
'@github/copilot-linux-arm64@1.0.44':
|
||||
optional: true
|
||||
|
||||
'@github/copilot-linux-x64@1.0.43':
|
||||
'@github/copilot-linux-x64@1.0.44':
|
||||
optional: true
|
||||
|
||||
'@github/copilot-win32-arm64@1.0.43':
|
||||
'@github/copilot-win32-arm64@1.0.44':
|
||||
optional: true
|
||||
|
||||
'@github/copilot-win32-x64@1.0.43':
|
||||
'@github/copilot-win32-x64@1.0.44':
|
||||
optional: true
|
||||
|
||||
'@github/copilot@1.0.43':
|
||||
'@github/copilot@1.0.44':
|
||||
optionalDependencies:
|
||||
'@github/copilot-darwin-arm64': 1.0.43
|
||||
'@github/copilot-darwin-x64': 1.0.43
|
||||
'@github/copilot-linux-arm64': 1.0.43
|
||||
'@github/copilot-linux-x64': 1.0.43
|
||||
'@github/copilot-win32-arm64': 1.0.43
|
||||
'@github/copilot-win32-x64': 1.0.43
|
||||
'@github/copilot-darwin-arm64': 1.0.44
|
||||
'@github/copilot-darwin-x64': 1.0.44
|
||||
'@github/copilot-linux-arm64': 1.0.44
|
||||
'@github/copilot-linux-x64': 1.0.44
|
||||
'@github/copilot-win32-arm64': 1.0.44
|
||||
'@github/copilot-win32-x64': 1.0.44
|
||||
|
||||
'@types/node@25.6.0':
|
||||
'@types/node@25.6.2':
|
||||
dependencies:
|
||||
undici-types: 7.19.2
|
||||
|
||||
bencode@2.0.3: {}
|
||||
|
||||
esbuild@0.28.0:
|
||||
optionalDependencies:
|
||||
'@esbuild/aix-ppc64': 0.28.0
|
||||
@ -434,55 +449,62 @@ snapshots:
|
||||
'@esbuild/win32-ia32': 0.28.0
|
||||
'@esbuild/win32-x64': 0.28.0
|
||||
|
||||
opencode-ai@1.14.40:
|
||||
nrepl-client@0.3.0:
|
||||
dependencies:
|
||||
bencode: 2.0.3
|
||||
tree-kill: 1.2.2
|
||||
|
||||
opencode-ai@1.14.46:
|
||||
optionalDependencies:
|
||||
opencode-darwin-arm64: 1.14.40
|
||||
opencode-darwin-x64: 1.14.40
|
||||
opencode-darwin-x64-baseline: 1.14.40
|
||||
opencode-linux-arm64: 1.14.40
|
||||
opencode-linux-arm64-musl: 1.14.40
|
||||
opencode-linux-x64: 1.14.40
|
||||
opencode-linux-x64-baseline: 1.14.40
|
||||
opencode-linux-x64-baseline-musl: 1.14.40
|
||||
opencode-linux-x64-musl: 1.14.40
|
||||
opencode-windows-arm64: 1.14.40
|
||||
opencode-windows-x64: 1.14.40
|
||||
opencode-windows-x64-baseline: 1.14.40
|
||||
opencode-darwin-arm64: 1.14.46
|
||||
opencode-darwin-x64: 1.14.46
|
||||
opencode-darwin-x64-baseline: 1.14.46
|
||||
opencode-linux-arm64: 1.14.46
|
||||
opencode-linux-arm64-musl: 1.14.46
|
||||
opencode-linux-x64: 1.14.46
|
||||
opencode-linux-x64-baseline: 1.14.46
|
||||
opencode-linux-x64-baseline-musl: 1.14.46
|
||||
opencode-linux-x64-musl: 1.14.46
|
||||
opencode-windows-arm64: 1.14.46
|
||||
opencode-windows-x64: 1.14.46
|
||||
opencode-windows-x64-baseline: 1.14.46
|
||||
|
||||
opencode-darwin-arm64@1.14.40:
|
||||
opencode-darwin-arm64@1.14.46:
|
||||
optional: true
|
||||
|
||||
opencode-darwin-x64-baseline@1.14.40:
|
||||
opencode-darwin-x64-baseline@1.14.46:
|
||||
optional: true
|
||||
|
||||
opencode-darwin-x64@1.14.40:
|
||||
opencode-darwin-x64@1.14.46:
|
||||
optional: true
|
||||
|
||||
opencode-linux-arm64-musl@1.14.40:
|
||||
opencode-linux-arm64-musl@1.14.46:
|
||||
optional: true
|
||||
|
||||
opencode-linux-arm64@1.14.40:
|
||||
opencode-linux-arm64@1.14.46:
|
||||
optional: true
|
||||
|
||||
opencode-linux-x64-baseline-musl@1.14.40:
|
||||
opencode-linux-x64-baseline-musl@1.14.46:
|
||||
optional: true
|
||||
|
||||
opencode-linux-x64-baseline@1.14.40:
|
||||
opencode-linux-x64-baseline@1.14.46:
|
||||
optional: true
|
||||
|
||||
opencode-linux-x64-musl@1.14.40:
|
||||
opencode-linux-x64-musl@1.14.46:
|
||||
optional: true
|
||||
|
||||
opencode-linux-x64@1.14.40:
|
||||
opencode-linux-x64@1.14.46:
|
||||
optional: true
|
||||
|
||||
opencode-windows-arm64@1.14.40:
|
||||
opencode-windows-arm64@1.14.46:
|
||||
optional: true
|
||||
|
||||
opencode-windows-x64-baseline@1.14.40:
|
||||
opencode-windows-x64-baseline@1.14.46:
|
||||
optional: true
|
||||
|
||||
opencode-windows-x64@1.14.40:
|
||||
opencode-windows-x64@1.14.46:
|
||||
optional: true
|
||||
|
||||
tree-kill@1.2.2: {}
|
||||
|
||||
undici-types@7.19.2: {}
|
||||
|
||||
259
tools/nrepl-eval.mjs
Executable file
259
tools/nrepl-eval.mjs
Executable file
@ -0,0 +1,259 @@
|
||||
#!/usr/bin/env node
|
||||
import nreplClient from "nrepl-client";
|
||||
import { readFileSync, writeFileSync, existsSync, unlinkSync } from "fs";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
|
||||
const DEFAULT_TIMEOUT = 120000;
|
||||
|
||||
// ============================================================================
|
||||
// Session persistence
|
||||
// ============================================================================
|
||||
|
||||
function sessionFilePath(host, port) {
|
||||
return path.join(os.tmpdir(), `penpot-nrepl-session-${host}-${port}`);
|
||||
}
|
||||
|
||||
function readSession(host, port) {
|
||||
const fp = sessionFilePath(host, port);
|
||||
try {
|
||||
return readFileSync(fp, "utf8").trim() || null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function writeSession(host, port, id) {
|
||||
writeFileSync(sessionFilePath(host, port), id, "utf8");
|
||||
}
|
||||
|
||||
function deleteSession(host, port) {
|
||||
const fp = sessionFilePath(host, port);
|
||||
if (existsSync(fp)) unlinkSync(fp);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// nREPL helpers (promisified)
|
||||
// ============================================================================
|
||||
|
||||
function nreplSend(con, msg) {
|
||||
return new Promise((resolve, reject) => {
|
||||
con.send(msg, (err, messages) => {
|
||||
if (err) {
|
||||
const text = Array.isArray(err)
|
||||
? err.map((e) => (e && e.message) || String(e)).join("; ")
|
||||
: String(err);
|
||||
reject(new Error(text));
|
||||
} else {
|
||||
resolve(messages || []);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function nreplEval({ host, port, code, sessionId, timeout = DEFAULT_TIMEOUT }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const con = nreplClient.connect({ host, port });
|
||||
let finished = false;
|
||||
|
||||
const finish = (err, result) => {
|
||||
if (finished) return;
|
||||
finished = true;
|
||||
clearTimeout(timer);
|
||||
try { con.end(); } catch (_) {}
|
||||
if (err) reject(err);
|
||||
else resolve(result);
|
||||
};
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
finish(new Error(`nREPL eval timed out after ${timeout}ms`));
|
||||
}, timeout);
|
||||
|
||||
con.on("error", (err) => {
|
||||
finish(err);
|
||||
});
|
||||
|
||||
con.once("connect", async () => {
|
||||
try {
|
||||
let sid = sessionId;
|
||||
|
||||
if (!sid) {
|
||||
const msgs = await nreplSend(con, { op: "clone" });
|
||||
const m = msgs.find((m) => m["new-session"]);
|
||||
if (!m) throw new Error("Clone response missing new-session");
|
||||
sid = m["new-session"];
|
||||
}
|
||||
|
||||
const messages = await nreplSend(con, { op: "eval", code, session: sid });
|
||||
finish(null, { messages, sessionId: sid });
|
||||
} catch (err) {
|
||||
finish(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Output formatting
|
||||
// ============================================================================
|
||||
|
||||
function formatEvalMessages(messages) {
|
||||
const lines = [];
|
||||
let hasContent = false;
|
||||
for (const msg of messages) {
|
||||
if (msg.out) {
|
||||
lines.push(msg.out);
|
||||
hasContent = true;
|
||||
}
|
||||
if (msg.err) {
|
||||
lines.push(`[ERROR] ${msg.err}`);
|
||||
hasContent = true;
|
||||
}
|
||||
if (msg.value) {
|
||||
const ns = msg.ns ? ` (ns: ${msg.ns})` : "";
|
||||
lines.push(`=> ${msg.value}${ns}`);
|
||||
hasContent = true;
|
||||
}
|
||||
}
|
||||
if (!hasContent) {
|
||||
const statuses = messages.map((m) => m.status).filter(Boolean).flat();
|
||||
return `Evaluation completed. Status: ${statuses.join(", ") || "done"}`;
|
||||
}
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CLI argument parsing
|
||||
// ============================================================================
|
||||
|
||||
function parseArgs(argv) {
|
||||
const args = {
|
||||
port: 6064,
|
||||
host: "127.0.0.1",
|
||||
timeout: DEFAULT_TIMEOUT,
|
||||
help: false,
|
||||
resetSession: false,
|
||||
lastError: false,
|
||||
code: null,
|
||||
};
|
||||
|
||||
for (let i = 0; i < argv.length; i++) {
|
||||
const a = argv[i];
|
||||
if (a === "-p" || a === "--port") {
|
||||
const val = argv[++i];
|
||||
if (val === undefined) { console.error("Error: --port requires a value."); process.exit(1); }
|
||||
args.port = parseInt(val, 10);
|
||||
} else if (a === "-H" || a === "--host") {
|
||||
const val = argv[++i];
|
||||
if (val === undefined) { console.error("Error: --host requires a value."); process.exit(1); }
|
||||
args.host = val;
|
||||
} else if (a === "-t" || a === "--timeout") {
|
||||
const val = argv[++i];
|
||||
if (val === undefined) { console.error("Error: --timeout requires a value."); process.exit(1); }
|
||||
args.timeout = parseInt(val, 10);
|
||||
} else if (a === "--reset-session") {
|
||||
args.resetSession = true;
|
||||
} else if (a === "-e" || a === "--last-error") {
|
||||
args.lastError = true;
|
||||
} else if (a === "-h" || a === "--help") {
|
||||
args.help = true;
|
||||
} else {
|
||||
if (args.code === null) {
|
||||
args.code = a;
|
||||
} else {
|
||||
args.code += " " + a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
function printHelp() {
|
||||
const bin = path.basename(process.argv[1]);
|
||||
console.log(`Usage: ${bin} [options] [<code>]
|
||||
|
||||
Evaluate Clojure code via a running nREPL server. Session state (defs, in-ns)
|
||||
persists across invocations via a stored session ID.
|
||||
|
||||
Options:
|
||||
-p, --port PORT nREPL port (default: 6064)
|
||||
-H, --host HOST nREPL host (default: 127.0.0.1)
|
||||
-t, --timeout MILLISECONDS Timeout in milliseconds (default: 120000)
|
||||
--reset-session Discard stored session and start fresh
|
||||
-e, --last-error Evaluate *e to retrieve the last exception
|
||||
-h, --help Show this help message
|
||||
|
||||
Examples:
|
||||
${bin} '(def x 42)'
|
||||
${bin} 'x'
|
||||
${bin} --reset-session '(def x 0)'
|
||||
${bin} --last-error
|
||||
${bin} <<'EOF'
|
||||
(def x 10)
|
||||
(+ x 20)
|
||||
EOF`);
|
||||
}
|
||||
|
||||
function readStdin() {
|
||||
return new Promise((resolve) => {
|
||||
if (process.stdin.isTTY) {
|
||||
resolve("");
|
||||
return;
|
||||
}
|
||||
let data = "";
|
||||
process.stdin.setEncoding("utf-8");
|
||||
process.stdin.on("data", (chunk) => { data += chunk; });
|
||||
process.stdin.on("end", () => { resolve(data.trim()); });
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Main
|
||||
// ============================================================================
|
||||
|
||||
async function main() {
|
||||
const args = parseArgs(process.argv.slice(2));
|
||||
|
||||
if (args.help) {
|
||||
printHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNaN(args.port) || args.port < 1 || args.port > 65535) {
|
||||
console.error("Error: invalid port number.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (args.resetSession) {
|
||||
deleteSession(args.host, args.port);
|
||||
}
|
||||
|
||||
let code = args.lastError ? "*e" : args.code;
|
||||
if (!code) {
|
||||
code = await readStdin();
|
||||
}
|
||||
|
||||
if (!code) {
|
||||
console.error("Error: No code provided. Pass code as an argument or pipe it via stdin.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const storedSession = readSession(args.host, args.port);
|
||||
|
||||
const { messages, sessionId } = await nreplEval({
|
||||
host: args.host,
|
||||
port: args.port,
|
||||
code,
|
||||
sessionId: storedSession,
|
||||
timeout: args.timeout,
|
||||
});
|
||||
|
||||
writeSession(args.host, args.port, sessionId);
|
||||
console.log(formatEvalMessages(messages));
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(`Error: ${err.message || err}`);
|
||||
process.exit(1);
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user