@agentlayer.tech/wallet 0.1.21 → 0.1.23
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/.openclaw/extensions/agent-wallet/README.md +3 -3
- package/.openclaw/extensions/agent-wallet/package.json +1 -1
- package/CHANGELOG.md +14 -0
- package/README.md +54 -21
- package/agent-wallet/README.md +4 -4
- package/agent-wallet/pyproject.toml +1 -1
- package/agent-wallet/scripts/install_agent_wallet.py +81 -1
- package/bin/openclaw-agent-wallet.mjs +63 -2
- package/package.json +1 -1
|
@@ -39,10 +39,10 @@ Recommended config:
|
|
|
39
39
|
"config": {
|
|
40
40
|
"userId": "openclaw-local-user",
|
|
41
41
|
"backend": "solana_local",
|
|
42
|
-
"network": "
|
|
42
|
+
"network": "mainnet",
|
|
43
43
|
"rpcUrls": [
|
|
44
44
|
"https://your-primary-rpc.example",
|
|
45
|
-
"https://api.
|
|
45
|
+
"https://api.mainnet-beta.solana.com"
|
|
46
46
|
],
|
|
47
47
|
"signOnly": false,
|
|
48
48
|
"encryptUserWallets": true,
|
|
@@ -77,7 +77,7 @@ The ClawHub plugin package only installs the native OpenClaw plugin. It expects
|
|
|
77
77
|
|
|
78
78
|
If that runtime is not present, set `plugins.entries.agent-wallet.config.packageRoot` explicitly.
|
|
79
79
|
|
|
80
|
-
That installs the Python backend, Node dependencies for the local BTC/EVM runtimes,
|
|
80
|
+
That installs the Python backend, Node dependencies for the local BTC/EVM runtimes, patches the OpenClaw plugin config, and provisions the first encrypted per-user Solana mainnet wallet when no explicit signer is already configured. EVM readiness can still be auto-healed during normal wallet switching when the runtime has sealed local vault credentials.
|
|
81
81
|
|
|
82
82
|
For self-hosted installs, prefer `SOLANA_RPC_URL` / `SOLANA_RPC_URLS` in local env and treat the plugin `rpcUrl` / `rpcUrls` fields as fallback only. If the local runtime exposes `ALCHEMY_API_KEY` or `HELIUS_API_KEY`, the wallet can derive the Solana RPC URL automatically for `mainnet` or `devnet`. Local env always takes precedence over `openclaw.json`.
|
|
83
83
|
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## v0.1.23 - 2026-05-23
|
|
6
|
+
|
|
7
|
+
- Made the default Solana install flow mainnet-first for fresh local
|
|
8
|
+
onboarding.
|
|
9
|
+
- Added host-side Solana wallet provisioning to the installer so
|
|
10
|
+
`wallet install --yes` creates an encrypted per-user mainnet wallet when no
|
|
11
|
+
explicit signer is already configured.
|
|
12
|
+
- Kept wallet secrets local by running runtime onboarding without
|
|
13
|
+
provisioning-only secret environment variables and returning only public
|
|
14
|
+
wallet metadata in installer output.
|
|
15
|
+
- Updated installer smoke coverage to verify encrypted wallet creation,
|
|
16
|
+
mainnet config, address pinning, and absence of boot/master/approval secrets
|
|
17
|
+
in stdout.
|
|
18
|
+
|
|
5
19
|
## v0.1.18 - 2026-05-19
|
|
6
20
|
|
|
7
21
|
- Started the native x402 buyer integration inside `agent-wallet` instead of
|
package/README.md
CHANGED
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
```bash
|
|
10
10
|
npx @agentlayer.tech/wallet install --yes
|
|
11
11
|
```
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
For Hermes:
|
|
14
|
+
|
|
13
15
|
```bash
|
|
14
16
|
npx @agentlayer.tech/wallet install --yes && npx @agentlayer.tech/wallet hermes install --yes
|
|
15
17
|
```
|
|
@@ -46,35 +48,35 @@ System prerequisites:
|
|
|
46
48
|
- `node`
|
|
47
49
|
- `npm`
|
|
48
50
|
|
|
49
|
-
Install
|
|
51
|
+
Install the local runtime:
|
|
50
52
|
|
|
51
53
|
```bash
|
|
52
54
|
npx @agentlayer.tech/wallet install --yes
|
|
53
55
|
```
|
|
54
56
|
|
|
55
|
-
Install the native OpenClaw
|
|
57
|
+
Install the native OpenClaw plugin from ClawHub:
|
|
56
58
|
|
|
57
59
|
```bash
|
|
58
60
|
openclaw plugins install clawhub:@agentlayertech/agent-wallet-plugin
|
|
59
61
|
```
|
|
60
62
|
|
|
61
|
-
|
|
63
|
+
The ClawHub package does not replace the npm installer. `npx @agentlayer.tech/wallet install --yes` installs the local runtime, Python backend, and helper services. ClawHub only installs the OpenClaw plugin surface that points at that runtime.
|
|
62
64
|
|
|
63
|
-
Or install the CLI globally
|
|
65
|
+
Or install the CLI globally:
|
|
64
66
|
|
|
65
67
|
```bash
|
|
66
68
|
npm install -g @agentlayer.tech/wallet
|
|
67
69
|
wallet install --yes
|
|
68
70
|
```
|
|
69
71
|
|
|
70
|
-
The
|
|
72
|
+
The CLI uses a versioned runtime layout:
|
|
71
73
|
|
|
72
74
|
```bash
|
|
73
75
|
~/.openclaw/agent-wallet-runtime/releases/<version>
|
|
74
76
|
~/.openclaw/agent-wallet-runtime/current
|
|
75
77
|
```
|
|
76
78
|
|
|
77
|
-
`--yes` generates local runtime secrets
|
|
79
|
+
On first install, `--yes` generates local runtime secrets. The installer stores `master_key` and `approval_secret` in `~/.openclaw/sealed_keys.json`; only the boot key needed to unlock that sealed bundle is written to the runtime `.env`.
|
|
78
80
|
|
|
79
81
|
Useful npm CLI commands:
|
|
80
82
|
|
|
@@ -87,12 +89,11 @@ wallet update --yes --dry-run
|
|
|
87
89
|
wallet rollback
|
|
88
90
|
```
|
|
89
91
|
|
|
90
|
-
`wallet update --yes`
|
|
91
|
-
Use `wallet update --yes --dry-run` to inspect the target runtime version and whether Python/Node dependency snapshots will be reused or rebuilt before switching `current`.
|
|
92
|
+
`wallet update --yes` delegates to the latest published npm package and reuses shared Python and Node dependency snapshots when possible. Use `wallet update --yes --dry-run` to inspect the target version and dependency plan before switching `current`.
|
|
92
93
|
|
|
93
94
|
## Native OpenClaw plugin installs
|
|
94
95
|
|
|
95
|
-
Use ClawHub when you want the plugin
|
|
96
|
+
Use ClawHub when you want the plugin installed through OpenClaw:
|
|
96
97
|
|
|
97
98
|
```bash
|
|
98
99
|
openclaw plugins install clawhub:@agentlayertech/agent-wallet-plugin
|
|
@@ -104,7 +105,7 @@ Recommended order:
|
|
|
104
105
|
2. Install the plugin package from ClawHub with `openclaw plugins install clawhub:...`.
|
|
105
106
|
3. Restart the OpenClaw gateway and enable/configure the plugin entry in `openclaw.json`.
|
|
106
107
|
|
|
107
|
-
The `agent-wallet` ClawHub plugin
|
|
108
|
+
The `agent-wallet` ClawHub plugin checks the standard runtime path at:
|
|
108
109
|
|
|
109
110
|
```bash
|
|
110
111
|
~/.openclaw/agent-wallet-runtime/current/agent-wallet
|
|
@@ -118,7 +119,30 @@ Install from a local clone:
|
|
|
118
119
|
sh ./setup.sh
|
|
119
120
|
```
|
|
120
121
|
|
|
121
|
-
|
|
122
|
+
## Updating
|
|
123
|
+
|
|
124
|
+
If your installed CLI is `0.1.22` or newer, use:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
wallet update --yes --dry-run
|
|
128
|
+
wallet update --yes
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
If your installed CLI is older than `0.1.22`, or `wallet` is missing, use:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
npx --yes @agentlayer.tech/wallet@latest update --yes --dry-run
|
|
135
|
+
npx --yes @agentlayer.tech/wallet@latest update --yes
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
After updating, verify the active runtime:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
npx --yes @agentlayer.tech/wallet@latest status --verbose
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
This flow keeps wallet files and `sealed_keys.json` in place, upgrades the runtime under `~/.openclaw/agent-wallet-runtime/releases/<version>`, and reuses shared Python and Node dependency snapshots when possible.
|
|
145
|
+
|
|
122
146
|
|
|
123
147
|
## Wallet capabilities through external services
|
|
124
148
|
|
|
@@ -225,6 +249,8 @@ Lido:
|
|
|
225
249
|
|
|
226
250
|
Across these service-backed flows, read operations remain directly callable, while write operations stay behind preview, explicit intent, and host-issued approval tokens before execution.
|
|
227
251
|
|
|
252
|
+
If you want the installer to finish the OpenClaw plugin wiring in the same pass, provide the runtime secrets first:
|
|
253
|
+
|
|
228
254
|
Solana:
|
|
229
255
|
|
|
230
256
|
```bash
|
|
@@ -258,7 +284,7 @@ Run it three times and assign the outputs to:
|
|
|
258
284
|
- `AGENT_WALLET_MASTER_KEY`
|
|
259
285
|
- `AGENT_WALLET_APPROVAL_SECRET`
|
|
260
286
|
|
|
261
|
-
Without those secrets, the installer still lays down the runtime and installs dependencies, but
|
|
287
|
+
Without those secrets, the installer still lays down the runtime and installs dependencies, but stops before the final hardened OpenClaw config step and prints the exact `next_configure_command` to run later.
|
|
262
288
|
|
|
263
289
|
## Connect the MCP server
|
|
264
290
|
|
|
@@ -280,7 +306,7 @@ OpenClaw remains the primary local environment, but the repo also ships an optio
|
|
|
280
306
|
hermes/plugins/agent_wallet
|
|
281
307
|
```
|
|
282
308
|
|
|
283
|
-
It exposes a thin bridge, not a
|
|
309
|
+
It exposes a thin bridge, not a separate wallet implementation:
|
|
284
310
|
|
|
285
311
|
- `agent_wallet_tools`
|
|
286
312
|
- `agent_wallet_invoke`
|
|
@@ -294,7 +320,7 @@ Install it by symlinking the plugin directory into Hermes:
|
|
|
294
320
|
npx @agentlayer.tech/wallet hermes install --yes
|
|
295
321
|
```
|
|
296
322
|
|
|
297
|
-
That command installs the Hermes plugin, runs `hermes plugins enable agent-wallet`, writes non-secret runtime paths into `~/.hermes/.env`, and points Hermes at a local boot-key file. Secrets stay in the
|
|
323
|
+
That command installs the Hermes plugin, runs `hermes plugins enable agent-wallet`, writes non-secret runtime paths into `~/.hermes/.env`, and points Hermes at a local boot-key file. Secrets stay in the protected OpenClaw runtime paths, especially `~/.openclaw/sealed_keys.json`; do not put wallet secrets into Hermes tool config.
|
|
298
324
|
|
|
299
325
|
## What you get after install
|
|
300
326
|
|
|
@@ -304,11 +330,11 @@ If you install through npm, the runtime is extracted under:
|
|
|
304
330
|
~/.openclaw/agent-wallet-runtime/current
|
|
305
331
|
```
|
|
306
332
|
|
|
307
|
-
The installer then
|
|
333
|
+
The installer then:
|
|
308
334
|
|
|
309
335
|
- creates `agent-wallet/.env` from `agent-wallet/.env.example` if it does not exist
|
|
310
|
-
- creates `agent-wallet/.venv` and installs the Python backend
|
|
311
|
-
- installs Node dependencies for `wdk-btc-wallet
|
|
336
|
+
- creates `agent-wallet/.runtime-venv` and installs the Python backend
|
|
337
|
+
- installs Node dependencies for `wdk-btc-wallet`, `wdk-evm-wallet`, and `flash-sdk-bridge`
|
|
312
338
|
- creates a minimal `~/.openclaw/openclaw.json` if one does not exist
|
|
313
339
|
- if the required secrets are already present, writes or updates `~/.openclaw/sealed_keys.json`
|
|
314
340
|
- if the required secrets are already present, patches `~/.openclaw/openclaw.json` to load the `agent-wallet` extension and point it at the installed runtime
|
|
@@ -317,7 +343,12 @@ The installer then does the following:
|
|
|
317
343
|
When the installer reaches the final config step, the default plugin config is:
|
|
318
344
|
|
|
319
345
|
- `backend=solana_local`
|
|
320
|
-
- `network=
|
|
346
|
+
- `network=mainnet`
|
|
347
|
+
|
|
348
|
+
For a fresh Solana install, `wallet install --yes` also provisions the first local mainnet
|
|
349
|
+
wallet for the configured local `userId`. The wallet secret stays encrypted under
|
|
350
|
+
`~/.openclaw/users/.../wallets/`; the agent-facing surface only receives the public
|
|
351
|
+
address and guarded wallet tools.
|
|
321
352
|
|
|
322
353
|
## What is not done automatically
|
|
323
354
|
|
|
@@ -332,10 +363,11 @@ The installer does not:
|
|
|
332
363
|
- expose seed phrases to the agent
|
|
333
364
|
- install `python3`, `node`, or `npm` for you
|
|
334
365
|
|
|
335
|
-
For Solana
|
|
366
|
+
For existing Solana installs, the runtime keeps the current identity precedence:
|
|
336
367
|
|
|
337
368
|
- read-only mode: `SOLANA_AGENT_PUBLIC_KEY`
|
|
338
369
|
- signing mode: a sealed `private_key` or `SOLANA_AGENT_KEYPAIR_PATH`
|
|
370
|
+
- otherwise: the encrypted per-user local wallet created by onboarding
|
|
339
371
|
|
|
340
372
|
Optional private Solana payout routing is also available through Houdini. To enable it, add the Houdini partner credentials to the wallet runtime:
|
|
341
373
|
|
|
@@ -445,7 +477,8 @@ You only need to bring your own RPC if you want to override the default route. S
|
|
|
445
477
|
- `ALCHEMY_API_KEY`
|
|
446
478
|
- `HELIUS_API_KEY`
|
|
447
479
|
|
|
448
|
-
|
|
480
|
+
The legacy global keypair auto-create flag still exists for compatibility, but normal
|
|
481
|
+
OpenClaw onboarding should use the encrypted per-user wallet path created by the installer:
|
|
449
482
|
|
|
450
483
|
```bash
|
|
451
484
|
SOLANA_AUTO_CREATE_WALLET=false
|
package/agent-wallet/README.md
CHANGED
|
@@ -284,12 +284,12 @@ and still append the official Solana endpoint as fallback.
|
|
|
284
284
|
|
|
285
285
|
For OpenClaw self-hosting, this means users can keep their own Alchemy/Helius key locally in `.env` or shell env. Nothing needs to be proxied or hosted by us.
|
|
286
286
|
|
|
287
|
-
For OpenClaw install/runtime, the
|
|
287
|
+
For OpenClaw install/runtime, the default creation flow is:
|
|
288
288
|
|
|
289
289
|
1. OpenClaw plugin config sets `backend=solana_local`
|
|
290
|
-
2.
|
|
291
|
-
3.
|
|
292
|
-
4.
|
|
290
|
+
2. The installer configures `network=mainnet` unless you pass `--network`
|
|
291
|
+
3. The installer calls host-side onboarding once the sealed runtime secrets exist
|
|
292
|
+
4. If no explicit sealed signer or `SOLANA_AGENT_KEYPAIR_PATH` exists, onboarding creates an encrypted per-user Solana wallet under `~/.openclaw/users/.../wallets/`
|
|
293
293
|
|
|
294
294
|
For multi-user OpenClaw integration, use `agent_wallet.user_wallets.create_wallet_backend_for_user(user_id)`.
|
|
295
295
|
That provisions a wallet per user under:
|
|
@@ -186,7 +186,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
186
186
|
parser.add_argument("--plugin-id", default="agent-wallet")
|
|
187
187
|
parser.add_argument("--user-id", default=_default_user_id())
|
|
188
188
|
parser.add_argument("--backend", default="solana_local")
|
|
189
|
-
parser.add_argument("--network", default="
|
|
189
|
+
parser.add_argument("--network", default="mainnet")
|
|
190
190
|
parser.add_argument("--rpc-url", default="")
|
|
191
191
|
parser.add_argument("--rpc-urls", default="")
|
|
192
192
|
parser.add_argument("--sign-only", action=argparse.BooleanOptionalAction, default=False)
|
|
@@ -667,6 +667,79 @@ def _build_next_steps(
|
|
|
667
667
|
return command
|
|
668
668
|
|
|
669
669
|
|
|
670
|
+
def _is_solana_backend(backend: str) -> bool:
|
|
671
|
+
return backend.strip().lower() in {"solana", "solana_local", "solana-local"}
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
def _build_solana_onboard_config(args: argparse.Namespace) -> dict[str, object]:
|
|
675
|
+
config: dict[str, object] = {
|
|
676
|
+
"backend": args.backend,
|
|
677
|
+
"network": args.network,
|
|
678
|
+
"signOnly": bool(args.sign_only),
|
|
679
|
+
"encryptUserWallets": True,
|
|
680
|
+
"migratePlaintextUserWallets": True,
|
|
681
|
+
"refuseMainnetWalletRecreation": True,
|
|
682
|
+
}
|
|
683
|
+
if args.rpc_url.strip():
|
|
684
|
+
config["rpcUrl"] = args.rpc_url.strip()
|
|
685
|
+
if args.rpc_urls.strip():
|
|
686
|
+
config["rpcUrls"] = [item.strip() for item in args.rpc_urls.split(",") if item.strip()]
|
|
687
|
+
return config
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
def _runtime_env_for_onboard(package_root: Path) -> dict[str, str]:
|
|
691
|
+
env = dict(os.environ)
|
|
692
|
+
python_path = env.get("PYTHONPATH", "").strip()
|
|
693
|
+
env["PYTHONPATH"] = (
|
|
694
|
+
f"{package_root}{os.pathsep}{python_path}" if python_path else str(package_root)
|
|
695
|
+
)
|
|
696
|
+
for var_name in (
|
|
697
|
+
"AGENT_WALLET_MASTER_KEY",
|
|
698
|
+
"AGENT_WALLET_APPROVAL_SECRET",
|
|
699
|
+
"SOLANA_AGENT_PRIVATE_KEY",
|
|
700
|
+
):
|
|
701
|
+
env.pop(var_name, None)
|
|
702
|
+
return env
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
def _bootstrap_solana_wallet(
|
|
706
|
+
python_bin: Path,
|
|
707
|
+
package_root: Path,
|
|
708
|
+
args: argparse.Namespace,
|
|
709
|
+
) -> dict[str, object] | None:
|
|
710
|
+
if not _is_solana_backend(args.backend):
|
|
711
|
+
return None
|
|
712
|
+
result = subprocess.run(
|
|
713
|
+
[
|
|
714
|
+
str(python_bin),
|
|
715
|
+
"-m",
|
|
716
|
+
"agent_wallet.openclaw_cli",
|
|
717
|
+
"onboard",
|
|
718
|
+
"--user-id",
|
|
719
|
+
args.user_id,
|
|
720
|
+
"--config-json",
|
|
721
|
+
json.dumps(_build_solana_onboard_config(args)),
|
|
722
|
+
],
|
|
723
|
+
cwd=package_root,
|
|
724
|
+
capture_output=True,
|
|
725
|
+
text=True,
|
|
726
|
+
check=True,
|
|
727
|
+
env=_runtime_env_for_onboard(package_root),
|
|
728
|
+
)
|
|
729
|
+
payload = json.loads(result.stdout)
|
|
730
|
+
session = dict(payload.get("session") or {})
|
|
731
|
+
return {
|
|
732
|
+
"ok": True,
|
|
733
|
+
"user_id": session.get("user_id") or args.user_id,
|
|
734
|
+
"address": session.get("address"),
|
|
735
|
+
"network": session.get("network") or args.network,
|
|
736
|
+
"wallet_path": session.get("wallet_path"),
|
|
737
|
+
"storage_format": session.get("storage_format"),
|
|
738
|
+
"created_now": bool(session.get("created_now")),
|
|
739
|
+
"backend": session.get("backend"),
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
|
|
670
743
|
def main() -> None:
|
|
671
744
|
args = build_parser().parse_args()
|
|
672
745
|
source_package_root = Path(args.package_root).expanduser().resolve()
|
|
@@ -798,6 +871,7 @@ def main() -> None:
|
|
|
798
871
|
pending_env = _pending_env_names() if backend_enabled else []
|
|
799
872
|
configured = False
|
|
800
873
|
configure_stdout = ""
|
|
874
|
+
solana_onboard_result: dict[str, object] | None = None
|
|
801
875
|
if backend_enabled and not pending_env and not args.dry_run:
|
|
802
876
|
result = subprocess.run(
|
|
803
877
|
_build_next_steps(
|
|
@@ -813,6 +887,11 @@ def main() -> None:
|
|
|
813
887
|
)
|
|
814
888
|
configured = True
|
|
815
889
|
configure_stdout = result.stdout
|
|
890
|
+
solana_onboard_result = _bootstrap_solana_wallet(
|
|
891
|
+
python_bin,
|
|
892
|
+
package_root,
|
|
893
|
+
args,
|
|
894
|
+
)
|
|
816
895
|
|
|
817
896
|
print(
|
|
818
897
|
json.dumps(
|
|
@@ -837,6 +916,7 @@ def main() -> None:
|
|
|
837
916
|
"runtime_sync": runtime_sync,
|
|
838
917
|
"configured": configured,
|
|
839
918
|
"pending_env": pending_env,
|
|
919
|
+
"solana_wallet": solana_onboard_result,
|
|
840
920
|
"next_configure_command": _build_next_steps(
|
|
841
921
|
python_bin,
|
|
842
922
|
install_config_script,
|
|
@@ -226,6 +226,62 @@ function activeVersion(env = process.env) {
|
|
|
226
226
|
return path.basename(path.resolve(path.dirname(current), link));
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
+
function detectRuntimeVersion(runtimeRoot) {
|
|
230
|
+
try {
|
|
231
|
+
const packageJsonText = fs.readFileSync(path.join(runtimeRoot, "package.json"), "utf8");
|
|
232
|
+
const pkg = JSON.parse(packageJsonText);
|
|
233
|
+
return String(pkg.version || "").trim() || null;
|
|
234
|
+
} catch {
|
|
235
|
+
// ignored
|
|
236
|
+
}
|
|
237
|
+
try {
|
|
238
|
+
const pyprojectText = fs.readFileSync(path.join(runtimeRoot, "agent-wallet", "pyproject.toml"), "utf8");
|
|
239
|
+
const match = pyprojectText.match(/^version = "([^"]+)"/m);
|
|
240
|
+
return match?.[1] || null;
|
|
241
|
+
} catch {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function uniquePathWithSuffix(targetPath) {
|
|
247
|
+
if (!fs.existsSync(targetPath)) return targetPath;
|
|
248
|
+
let counter = 2;
|
|
249
|
+
while (true) {
|
|
250
|
+
const candidate = `${targetPath}-${counter}`;
|
|
251
|
+
if (!fs.existsSync(candidate)) return candidate;
|
|
252
|
+
counter += 1;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function migrateDirectoryRuntimePointer(linkPath) {
|
|
257
|
+
const stat = fs.lstatSync(linkPath);
|
|
258
|
+
if (!stat.isDirectory()) return null;
|
|
259
|
+
const runtimeBase = path.dirname(linkPath);
|
|
260
|
+
const releasesDir = path.join(runtimeBase, "releases");
|
|
261
|
+
fs.mkdirSync(releasesDir, { recursive: true });
|
|
262
|
+
const detectedVersion = detectRuntimeVersion(linkPath);
|
|
263
|
+
const baseName = detectedVersion ? `${detectedVersion}-migrated` : `legacy-current-${Date.now()}`;
|
|
264
|
+
const destination = uniquePathWithSuffix(path.join(releasesDir, baseName));
|
|
265
|
+
fs.renameSync(linkPath, destination);
|
|
266
|
+
return destination;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function existingRuntimePointerTarget(linkPath) {
|
|
270
|
+
const link = readLinkOrNull(linkPath);
|
|
271
|
+
if (link) {
|
|
272
|
+
return path.resolve(path.dirname(linkPath), link);
|
|
273
|
+
}
|
|
274
|
+
try {
|
|
275
|
+
const stat = fs.lstatSync(linkPath);
|
|
276
|
+
if (stat.isDirectory()) {
|
|
277
|
+
return migrateDirectoryRuntimePointer(linkPath);
|
|
278
|
+
}
|
|
279
|
+
} catch (error) {
|
|
280
|
+
if (error?.code !== "ENOENT") throw error;
|
|
281
|
+
}
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
|
|
229
285
|
function listDirectories(rootPath) {
|
|
230
286
|
try {
|
|
231
287
|
return fs
|
|
@@ -665,6 +721,7 @@ function runInstall(args, { commandName = "install" } = {}) {
|
|
|
665
721
|
const currentPath = currentRuntimePath();
|
|
666
722
|
const previousPath = previousRuntimePath();
|
|
667
723
|
const installerArgs = withoutCliOnlyArgs(args);
|
|
724
|
+
const dryRun = hasFlag(args, "--dry-run");
|
|
668
725
|
|
|
669
726
|
if (!hasFlag(installerArgs, "--runtime-root")) {
|
|
670
727
|
installerArgs.push("--runtime-root", releaseRoot);
|
|
@@ -695,9 +752,13 @@ function runInstall(args, { commandName = "install" } = {}) {
|
|
|
695
752
|
return result.status ?? 1;
|
|
696
753
|
}
|
|
697
754
|
|
|
698
|
-
|
|
755
|
+
if (dryRun) {
|
|
756
|
+
return 0;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
const currentTarget = existingRuntimePointerTarget(currentPath);
|
|
699
760
|
if (currentTarget) {
|
|
700
|
-
switchSymlink(previousPath,
|
|
761
|
+
switchSymlink(previousPath, currentTarget);
|
|
701
762
|
}
|
|
702
763
|
switchSymlink(currentPath, releaseRoot);
|
|
703
764
|
|