windows-development-sandbox 1.10.0 → 1.11.4
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/README.md +64 -17
- package/config.example.json +5 -1
- package/dist/app/features/accounts/accounts.def.d.ts +122 -0
- package/dist/app/features/accounts/accounts.def.js +243 -0
- package/dist/app/features/accounts/accounts.run.d.ts +51 -0
- package/dist/app/features/accounts/accounts.run.js +125 -0
- package/dist/app/features/accounts/index.d.ts +2 -0
- package/dist/app/features/accounts/index.js +3 -0
- package/dist/app/features/agents/agent-entry.d.ts +17 -0
- package/dist/app/features/agents/agent-entry.js +50 -0
- package/dist/app/features/agents/agents.def.d.ts +65 -0
- package/dist/app/features/agents/agents.def.js +142 -0
- package/dist/app/features/agents/agents.run.d.ts +31 -0
- package/dist/app/features/agents/agents.run.js +189 -0
- package/dist/app/features/agents/index.d.ts +3 -0
- package/dist/app/features/agents/index.js +4 -0
- package/dist/app/features/apps/apps-rows.d.ts +31 -0
- package/dist/app/features/apps/apps-rows.js +73 -0
- package/dist/app/features/apps/apps.def.d.ts +181 -0
- package/dist/app/features/apps/apps.def.js +264 -0
- package/dist/app/features/apps/apps.run.d.ts +58 -0
- package/dist/app/features/apps/apps.run.js +76 -0
- package/dist/app/features/apps/index.d.ts +3 -0
- package/dist/app/features/apps/index.js +4 -0
- package/dist/app/features/data/data.def.d.ts +154 -0
- package/dist/app/features/data/data.def.js +194 -0
- package/dist/app/features/data/data.run.d.ts +46 -0
- package/dist/app/features/data/data.run.js +141 -0
- package/dist/app/features/data/index.d.ts +2 -0
- package/dist/app/features/data/index.js +3 -0
- package/dist/app/features/defs.d.ts +959 -4
- package/dist/app/features/defs.js +151 -10
- package/dist/app/features/engine/engine.def.d.ts +228 -0
- package/dist/app/features/engine/engine.def.js +194 -0
- package/dist/app/features/engine/engine.run.d.ts +72 -0
- package/dist/app/features/engine/engine.run.js +77 -0
- package/dist/app/features/engine/index.d.ts +2 -0
- package/dist/app/features/engine/index.js +4 -0
- package/dist/app/features/files/files.def.d.ts +51 -0
- package/dist/app/features/files/files.def.js +38 -0
- package/dist/app/features/files/files.run.d.ts +20 -0
- package/dist/app/features/files/files.run.js +59 -0
- package/dist/app/features/files/index.d.ts +2 -0
- package/dist/app/features/files/index.js +3 -0
- package/dist/app/features/github/github.def.d.ts +80 -0
- package/dist/app/features/github/github.def.js +142 -0
- package/dist/app/features/github/github.run.d.ts +36 -0
- package/dist/app/features/github/github.run.js +59 -0
- package/dist/app/features/github/index.d.ts +2 -0
- package/dist/app/features/github/index.js +3 -0
- package/dist/app/features/health/health.def.d.ts +275 -0
- package/dist/app/features/health/health.def.js +297 -0
- package/dist/app/features/health/health.run.d.ts +125 -0
- package/dist/app/features/health/health.run.js +204 -0
- package/dist/app/features/health/index.d.ts +2 -0
- package/dist/app/features/health/index.js +3 -0
- package/dist/app/features/logs/index.d.ts +2 -0
- package/dist/app/features/logs/index.js +3 -0
- package/dist/app/features/logs/logs.def.d.ts +78 -0
- package/dist/app/features/logs/logs.def.js +115 -0
- package/dist/app/features/logs/logs.run.d.ts +20 -0
- package/dist/app/features/logs/logs.run.js +42 -0
- package/dist/app/features/projects/index.d.ts +2 -0
- package/dist/app/features/projects/index.js +3 -0
- package/dist/app/features/projects/projects.def.d.ts +101 -0
- package/dist/app/features/projects/projects.def.js +208 -0
- package/dist/app/features/projects/projects.run.d.ts +56 -0
- package/dist/app/features/projects/projects.run.js +155 -0
- package/dist/app/features/sandbox/sandbox.def.js +11 -5
- package/dist/app/features/sessions/sessions.def.js +3 -0
- package/dist/app/features/settings/categories.def.js +3 -2
- package/dist/{cli/menu/settings/share.d.ts → app/features/settings/settings-share.d.ts} +1 -1
- package/dist/{cli/menu/settings/share.js → app/features/settings/settings-share.js} +3 -2
- package/dist/app/features/settings/settings.def.d.ts +22 -5
- package/dist/app/features/settings/settings.def.js +81 -6
- package/dist/app/features/settings/settings.run.d.ts +17 -0
- package/dist/app/features/settings/settings.run.js +89 -4
- package/dist/app/features/storage/index.d.ts +2 -0
- package/dist/app/features/storage/index.js +3 -0
- package/dist/app/features/storage/storage.def.d.ts +133 -0
- package/dist/app/features/storage/storage.def.js +220 -0
- package/dist/app/features/storage/storage.run.d.ts +62 -0
- package/dist/app/features/storage/storage.run.js +370 -0
- package/dist/app/features/updates/index.d.ts +3 -0
- package/dist/app/features/updates/index.js +4 -0
- package/dist/app/features/updates/update-lines.d.ts +13 -0
- package/dist/app/features/updates/update-lines.js +61 -0
- package/dist/app/features/updates/updates.def.d.ts +89 -0
- package/dist/app/features/updates/updates.def.js +224 -0
- package/dist/app/features/updates/updates.run.d.ts +48 -0
- package/dist/app/features/updates/updates.run.js +154 -0
- package/dist/app/features/vpn/index.d.ts +2 -0
- package/dist/app/features/vpn/index.js +3 -0
- package/dist/app/features/vpn/vpn.def.d.ts +112 -0
- package/dist/app/features/vpn/vpn.def.js +174 -0
- package/dist/app/features/vpn/vpn.run.d.ts +37 -0
- package/dist/app/features/vpn/vpn.run.js +54 -0
- package/dist/app/features/web/index.d.ts +2 -0
- package/dist/app/features/web/index.js +3 -0
- package/dist/app/features/web/web.def.d.ts +33 -0
- package/dist/app/features/web/web.def.js +90 -0
- package/dist/app/features/web/web.run.d.ts +24 -0
- package/dist/app/features/web/web.run.js +93 -0
- package/dist/app/kernel/define.d.ts +10 -1
- package/dist/app/kernel/io.d.ts +34 -0
- package/dist/app/kernel/io.js +9 -0
- package/dist/app/kernel/rpc.d.ts +17 -0
- package/dist/app/kernel/text.d.ts +2 -0
- package/dist/app/kernel/text.js +7 -0
- package/dist/app/kernel/view.d.ts +24 -1
- package/dist/app/kernel/view.js +1 -1
- package/dist/app/registry.js +74 -1
- package/dist/cli/args.js +1 -1
- package/dist/cli/commands/context.d.ts +7 -0
- package/dist/cli/commands/context.js +29 -2
- package/dist/cli/commands/engine.js +5 -58
- package/dist/cli/commands/github.d.ts +2 -0
- package/dist/cli/commands/github.js +11 -0
- package/dist/cli/commands/index.d.ts +3 -5
- package/dist/cli/commands/index.js +3 -5
- package/dist/cli/commands/launch/launch.js +21 -4
- package/dist/cli/commands/launch/running.js +4 -1
- package/dist/cli/commands/maintenance/config.js +1 -6
- package/dist/cli/commands/maintenance/index.d.ts +0 -1
- package/dist/cli/commands/maintenance/index.js +1 -2
- package/dist/cli/commands/maintenance/logs.js +27 -32
- package/dist/cli/commands/maintenance/migrate.d.ts +0 -8
- package/dist/cli/commands/maintenance/migrate.js +2 -44
- package/dist/cli/commands/maintenance/update.d.ts +0 -19
- package/dist/cli/commands/maintenance/update.js +1 -99
- package/dist/cli/commands/projects/index.d.ts +0 -1
- package/dist/cli/commands/projects/index.js +0 -1
- package/dist/cli/commands/registry.d.ts +1 -1
- package/dist/cli/commands/registry.js +29 -11
- package/dist/cli/commands/repair/checks/data.js +1 -1
- package/dist/cli/commands/repair/checks/hcs.js +28 -1
- package/dist/cli/commands/repair/checks/sandbox.js +1 -1
- package/dist/cli/commands/repair/health-run.d.ts +30 -0
- package/dist/cli/commands/repair/health-run.js +32 -0
- package/dist/cli/commands/repair/printer.d.ts +2 -2
- package/dist/cli/commands/repair/printer.js +2 -11
- package/dist/cli/commands/repair/run.d.ts +1 -21
- package/dist/cli/commands/repair/run.js +13 -70
- package/dist/cli/commands/storage/index.d.ts +0 -1
- package/dist/cli/commands/storage/index.js +0 -1
- package/dist/cli/commands/storage/offer.js +6 -2
- package/dist/cli/commands/vpn.d.ts +1 -3
- package/dist/cli/commands/vpn.js +8 -89
- package/dist/cli/commands/web.d.ts +2 -1
- package/dist/cli/commands/web.js +14 -78
- package/dist/cli/index.js +3 -2
- package/dist/cli/manifest.d.ts +16 -153
- package/dist/cli/manifest.js +20 -50
- package/dist/cli/menu/command-form.js +1 -2
- package/dist/cli/menu/io-menu.d.ts +2 -2
- package/dist/cli/menu/io-menu.js +6 -4
- package/dist/cli/menu/menu.js +1 -1
- package/dist/cli/menu/pages/accounts-page.d.ts +5 -17
- package/dist/cli/menu/pages/accounts-page.js +28 -170
- package/dist/cli/menu/pages/agents-page.d.ts +17 -18
- package/dist/cli/menu/pages/agents-page.js +67 -124
- package/dist/cli/menu/pages/apps-page.d.ts +15 -8
- package/dist/cli/menu/pages/apps-page.js +121 -29
- package/dist/cli/menu/pages/list-page.js +10 -1
- package/dist/cli/menu/pages/maintenance-page.d.ts +51 -52
- package/dist/cli/menu/pages/maintenance-page.js +246 -427
- package/dist/cli/menu/pages/sandbox-page.d.ts +3 -5
- package/dist/cli/menu/pages/sandbox-page.js +34 -112
- package/dist/cli/menu/pages/settings-page.d.ts +21 -3
- package/dist/cli/menu/pages/settings-page.js +89 -17
- package/dist/cli/menu/render-view.js +2 -1
- package/dist/cli/menu/settings/index.d.ts +0 -1
- package/dist/cli/menu/settings/index.js +3 -3
- package/dist/cli/menu/views.js +28 -0
- package/dist/cli/operations/handler.js +7 -3
- package/dist/cli/operations/input.d.ts +1 -1
- package/dist/cli/operations/input.js +29 -5
- package/dist/cli/operations/io-cli.js +9 -1
- package/dist/cli/operations/spec.js +8 -2
- package/dist/cli/output/render-view.js +2 -0
- package/dist/cli/{commands/data/collect.js → services/data-collect.js} +2 -2
- package/dist/cli/{commands/data/remove.js → services/data-remove.js} +2 -2
- package/dist/cli/services/data-service.d.ts +1 -1
- package/dist/cli/services/data-service.js +2 -2
- package/dist/cli/services/databases-service.d.ts +5 -0
- package/dist/cli/services/databases-service.js +15 -0
- package/dist/cli/services/health-service.d.ts +8 -4
- package/dist/cli/services/health-service.js +10 -8
- package/dist/cli/services/index.d.ts +4 -1
- package/dist/cli/services/index.js +4 -1
- package/dist/cli/services/logs-service.d.ts +7 -0
- package/dist/cli/services/logs-service.js +13 -0
- package/dist/cli/services/storage-service.d.ts +19 -0
- package/dist/cli/services/storage-service.js +71 -0
- package/dist/cli/services/vpn-service.d.ts +6 -4
- package/dist/cli/services/vpn-service.js +5 -11
- package/dist/cli/services/web-service.d.ts +4 -3
- package/dist/cli/services/web-service.js +12 -32
- package/dist/core/config/defaults.js +2 -1
- package/dist/core/schema/config.d.ts +5 -0
- package/dist/core/schema/config.js +2 -0
- package/dist/core/schema/github.d.ts +4 -0
- package/dist/core/schema/github.js +7 -0
- package/dist/core/schema/index.d.ts +1 -0
- package/dist/core/schema/index.js +1 -0
- package/dist/core/schema/profile.d.ts +1 -0
- package/dist/core/schema/profile.js +3 -0
- package/dist/core/schema/sandbox.js +3 -3
- package/dist/core/schema/toolchains.js +2 -2
- package/dist/core/types.d.ts +6 -3
- package/dist/guest/boot/boot.js +8 -0
- package/dist/guest/daemon/daemon.js +1 -1
- package/dist/guest/daemon/sessions.d.ts +3 -2
- package/dist/guest/daemon/sessions.js +38 -7
- package/dist/guest/governor/core.d.ts +44 -0
- package/dist/guest/governor/core.js +124 -0
- package/dist/guest/governor/decide.d.ts +46 -18
- package/dist/guest/governor/decide.js +136 -82
- package/dist/guest/governor/governor.d.ts +2 -38
- package/dist/guest/governor/governor.js +33 -113
- package/dist/guest/governor/index.d.ts +1 -0
- package/dist/guest/governor/index.js +1 -0
- package/dist/guest/network/firewall.d.ts +2 -0
- package/dist/guest/network/firewall.js +3 -3
- package/dist/guest/sessions/viewer.js +5 -1
- package/dist/guest/setup/github-token.d.ts +7 -0
- package/dist/guest/setup/github-token.js +65 -0
- package/dist/guest/toolchains/languages/node.js +11 -1
- package/dist/guest/toolchains/native/rust-extras.js +2 -2
- package/dist/guest/toolchains/tools/cli.d.ts +8 -0
- package/dist/guest/toolchains/tools/cli.js +35 -2
- package/dist/guest/vpn/openvpn.d.ts +5 -0
- package/dist/guest/vpn/openvpn.js +14 -1
- package/dist/guest/vpn/vpn.js +55 -16
- package/dist/guest/vpn/wireguard.js +2 -0
- package/dist/host/channel/attach.d.ts +1 -0
- package/dist/host/channel/attach.js +1 -0
- package/dist/host/channel/channel.d.ts +1 -1
- package/dist/host/channel/channel.js +2 -2
- package/dist/host/engines/hcs/check.d.ts +1 -0
- package/dist/host/engines/hcs/check.js +1 -1
- package/dist/host/engines/hcs/engine.d.ts +1 -1
- package/dist/host/engines/hcs/engine.js +6 -4
- package/dist/host/engines/hcs/helper.d.ts +11 -0
- package/dist/host/engines/hcs/helper.js +31 -2
- package/dist/host/engines/hcs/leftovers.d.ts +42 -0
- package/dist/host/engines/hcs/leftovers.js +101 -0
- package/dist/host/engines/index.d.ts +1 -0
- package/dist/host/engines/index.js +1 -0
- package/dist/host/github/deliver.d.ts +13 -0
- package/dist/host/github/deliver.js +51 -0
- package/dist/host/github/index.d.ts +2 -0
- package/dist/host/github/index.js +3 -0
- package/dist/host/github/token.d.ts +21 -0
- package/dist/host/github/token.js +74 -0
- package/dist/host/launch/generate.js +11 -5
- package/dist/host/launch/index.d.ts +1 -1
- package/dist/host/launch/index.js +1 -1
- package/dist/host/launch/session-file.js +1 -2
- package/dist/host/launch/wsb-file.d.ts +11 -7
- package/dist/host/launch/wsb-file.js +30 -12
- package/dist/host/life/host-memory.d.ts +18 -0
- package/dist/host/life/host-memory.js +66 -0
- package/dist/host/life/watchdog.d.ts +9 -0
- package/dist/host/life/watchdog.js +17 -1
- package/dist/host/sandbox/tasklist.d.ts +6 -0
- package/dist/host/sandbox/tasklist.js +37 -0
- package/dist/host/secrets/launch-secrets.d.ts +2 -1
- package/dist/host/secrets/launch-secrets.js +4 -2
- package/dist/host/vpn/status.js +13 -3
- package/dist/host/web/backend.d.ts +1 -3
- package/dist/host/web/index.d.ts +2 -1
- package/dist/host/web/index.js +2 -1
- package/dist/host/web/live.d.ts +23 -0
- package/dist/host/web/live.js +107 -0
- package/dist/host/web/rpc.d.ts +6 -1
- package/dist/host/web/rpc.js +23 -31
- package/dist/host/web/runner.d.ts +4 -0
- package/dist/host/web/runner.js +12 -1
- package/dist/host/web/server.d.ts +30 -2
- package/dist/host/web/server.js +87 -29
- package/dist/host/web/state.d.ts +1 -0
- package/dist/host/web/state.js +2 -0
- package/dist/runtime/agent.js +30 -6
- package/dist/runtime/daemon.js +337 -177
- package/dist/runtime/db.js +30 -6
- package/dist/runtime/main.js +389 -77
- package/dist/runtime/netproxy.js +30 -6
- package/dist/runtime/view.js +495 -146
- package/dist/selftest/app.test.js +632 -12
- package/dist/selftest/channel.test.js +18 -1
- package/dist/selftest/cli.test.js +1 -1
- package/dist/selftest/config.test.js +1 -1
- package/dist/selftest/contained.test.js +1 -1
- package/dist/selftest/data.test.js +1 -2
- package/dist/selftest/engine.test.js +3 -1
- package/dist/selftest/github.test.d.ts +2 -0
- package/dist/selftest/github.test.js +243 -0
- package/dist/selftest/hcs.test.js +105 -8
- package/dist/selftest/health.test.js +71 -0
- package/dist/selftest/heavy-load.test.js +132 -72
- package/dist/selftest/index.js +4 -0
- package/dist/selftest/layout.test.js +8 -0
- package/dist/selftest/live-terminal.test.js +74 -0
- package/dist/selftest/menu-screens.js +171 -56
- package/dist/selftest/menu.test.js +54 -37
- package/dist/selftest/migrate.test.js +14 -12
- package/dist/selftest/powershell-variables.test.d.ts +2 -0
- package/dist/selftest/powershell-variables.test.js +61 -0
- package/dist/selftest/support/engine-golden.js +1 -0
- package/dist/selftest/support/fixtures.d.ts +4 -0
- package/dist/selftest/support/web-preview.d.ts +1 -0
- package/dist/selftest/support/web-preview.js +856 -0
- package/dist/selftest/update-service.test.js +15 -8
- package/dist/selftest/vpn.test.js +86 -2
- package/dist/selftest/web.test.js +58 -11
- package/dist/shared/channel/run-dir.d.ts +2 -0
- package/dist/shared/channel/run-dir.js +3 -0
- package/dist/shared/channel/schema.d.ts +7 -0
- package/dist/shared/channel/schema.js +9 -1
- package/dist/shared/channel/secrets.d.ts +1 -0
- package/dist/shared/channel/secrets.js +3 -0
- package/dist/shared/github/index.d.ts +1 -0
- package/dist/shared/github/index.js +2 -0
- package/dist/shared/github/token.d.ts +29 -0
- package/dist/shared/github/token.js +89 -0
- package/dist/shared/memory/policy.d.ts +33 -12
- package/dist/shared/memory/policy.js +50 -19
- package/dist/shared/pty/ctrl-c.d.ts +18 -0
- package/dist/shared/pty/ctrl-c.js +36 -0
- package/dist/shared/pty/index.d.ts +1 -0
- package/dist/shared/pty/index.js +1 -0
- package/dist/shared/pty/terminal.d.ts +2 -0
- package/dist/shared/pty/terminal.js +21 -2
- package/dist/shared/vpn/kill-switch.d.ts +13 -2
- package/dist/shared/vpn/kill-switch.js +88 -8
- package/dist/shared/web/protocol.d.ts +0 -4
- package/dist/sync/watcher.d.ts +2 -0
- package/dist/sync/watcher.js +8 -3
- package/dist/web/index.html +2 -2
- package/dist/web/{main-T5DMCHYV.css → main-MU5XWZMS.css} +1 -1
- package/dist/web/main-SLQNJRXD.js +219 -0
- package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
- package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
- package/native/csharp/HostTools/MemoryStatus.cs +58 -2
- package/native/csharp/HostTools/Program.cs +7 -1
- package/package.json +10 -3
- package/sandbox/csharp/MemGov.cs +240 -38
- package/sandbox/csharp/PtyHost.cs +104 -15
- package/sandbox/csharp/SandboxTools/CpuCap.cs +50 -1
- package/sandbox/csharp/SandboxTools/Program.cs +1 -1
- package/sandbox/scripts/repair-wmi-services.ps1 +26 -0
- package/schemas/config.schema.json +19 -5
- package/schemas/sandbox-config.schema.json +7 -3
- package/dist/cli/commands/accounts.d.ts +0 -2
- package/dist/cli/commands/accounts.js +0 -96
- package/dist/cli/commands/data/clean.d.ts +0 -3
- package/dist/cli/commands/data/clean.js +0 -60
- package/dist/cli/commands/data/index.d.ts +0 -4
- package/dist/cli/commands/data/index.js +0 -62
- package/dist/cli/commands/maintenance/clean.d.ts +0 -2
- package/dist/cli/commands/maintenance/clean.js +0 -24
- package/dist/cli/commands/maintenance/config-share.d.ts +0 -12
- package/dist/cli/commands/maintenance/config-share.js +0 -80
- package/dist/cli/commands/projects/projects.d.ts +0 -2
- package/dist/cli/commands/projects/projects.js +0 -162
- package/dist/cli/commands/storage/contained.d.ts +0 -3
- package/dist/cli/commands/storage/contained.js +0 -179
- package/dist/cli/commands/storage/storage.d.ts +0 -8
- package/dist/cli/commands/storage/storage.js +0 -179
- package/dist/cli/menu/add-agent.d.ts +0 -47
- package/dist/cli/menu/add-agent.js +0 -168
- package/dist/cli/menu/apps.d.ts +0 -69
- package/dist/cli/menu/apps.js +0 -214
- package/dist/cli/menu/data-folder.d.ts +0 -18
- package/dist/cli/menu/data-folder.js +0 -99
- package/dist/cli/menu/share-settings.d.ts +0 -21
- package/dist/cli/menu/share-settings.js +0 -112
- package/dist/cli/menu/vpn-login.d.ts +0 -16
- package/dist/cli/menu/vpn-login.js +0 -74
- package/dist/web/main-NRRS5O6Z.js +0 -157
- /package/dist/cli/{commands/data/collect.d.ts → services/data-collect.d.ts} +0 -0
- /package/dist/cli/{commands/data/remove.d.ts → services/data-remove.d.ts} +0 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
// Storage: where the saved data (tools, logins, history, logs) lives, moving it to another folder or switching to one
|
|
2
|
+
// without moving, and contained storage (beta: a disk image). Pure: schemas, labels and the view; storage.run.ts does
|
|
3
|
+
// the work. Sizes come as bytes and as words (the run measures; bytesText formats).
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { action, bytesText, defineOperation, defineQuery, defineView, } from '../../kernel/index.js';
|
|
6
|
+
import { browseFolder } from '../files/files.def.js';
|
|
7
|
+
const MeasuredSchema = z.object({
|
|
8
|
+
bytes: z.number().describe('Bytes counted'),
|
|
9
|
+
files: z.number().describe('Files counted'),
|
|
10
|
+
partial: z.boolean().describe('The count stopped at its limit: there is at least this much'),
|
|
11
|
+
});
|
|
12
|
+
export const StorageShowSchema = z.object({
|
|
13
|
+
root: z.string().describe('The folder your saved data is in'),
|
|
14
|
+
exists: z.boolean().describe('It exists'),
|
|
15
|
+
holdsData: z.boolean().describe('Something is in it'),
|
|
16
|
+
measured: MeasuredSchema.nullable().describe('Its size; null when not measured'),
|
|
17
|
+
size: z.string().describe('Its size in words; "nothing in it yet" when empty; "not measured yet" when not counted'),
|
|
18
|
+
defaultRoot: z.string().describe('The default folder'),
|
|
19
|
+
defaultInUse: z.boolean().describe('The default folder is the one in use'),
|
|
20
|
+
otherFoldersWithData: z.array(z.string()).describe('Other folders that hold data'),
|
|
21
|
+
});
|
|
22
|
+
export const showStorage = defineQuery({
|
|
23
|
+
id: 'storage.show',
|
|
24
|
+
title: 'Data folder',
|
|
25
|
+
summary: 'where your saved data lives',
|
|
26
|
+
input: z.object({ measure: z.boolean().default(true).describe('add up its size (big folders take a moment)') }),
|
|
27
|
+
output: StorageShowSchema,
|
|
28
|
+
text: (o) => [
|
|
29
|
+
'Your saved data is in: ' + o.root,
|
|
30
|
+
' ' + o.size,
|
|
31
|
+
'The default folder is: ' + o.defaultRoot + (o.defaultInUse ? ' (in use)' : ''),
|
|
32
|
+
...o.otherFoldersWithData.map((f) => 'Another folder with data: ' + f),
|
|
33
|
+
'',
|
|
34
|
+
'Move it with: sandbox storage move <folder> (or switch without moving: sandbox storage use <folder>)',
|
|
35
|
+
'What takes the space: sandbox data',
|
|
36
|
+
].join('\n'),
|
|
37
|
+
cli: {
|
|
38
|
+
name: 'storage',
|
|
39
|
+
action: 'show',
|
|
40
|
+
json: true,
|
|
41
|
+
// The --json shape `sandbox storage` has always printed.
|
|
42
|
+
shapeJson: (o) => ({
|
|
43
|
+
root: o.root,
|
|
44
|
+
exists: o.exists,
|
|
45
|
+
...(o.measured ?? { bytes: 0, files: 0, partial: false }),
|
|
46
|
+
defaultRoot: o.defaultRoot,
|
|
47
|
+
otherFoldersWithData: o.otherFoldersWithData,
|
|
48
|
+
}),
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
const MoveOutputSchema = z.object({
|
|
52
|
+
from: z.string().describe('The old folder'),
|
|
53
|
+
to: z.string().describe('The new folder'),
|
|
54
|
+
copied: z.boolean().describe('It was copied (another drive)'),
|
|
55
|
+
files: z.number().describe('Files moved'),
|
|
56
|
+
bytes: z.number().describe('Bytes moved'),
|
|
57
|
+
pinned: z.string().describe('What storage.root now says'),
|
|
58
|
+
removedOld: z.boolean().describe('The old folder is gone'),
|
|
59
|
+
notes: z.array(z.string()).describe('Extra notes'),
|
|
60
|
+
message: z.string().describe('What happened'),
|
|
61
|
+
});
|
|
62
|
+
export const moveStorage = defineOperation({
|
|
63
|
+
id: 'storage.move',
|
|
64
|
+
title: 'Move the data…',
|
|
65
|
+
summary: 'move your saved data to another folder',
|
|
66
|
+
input: z.object({
|
|
67
|
+
folder: z.string().optional().describe('the folder to move to'),
|
|
68
|
+
keepOld: z.boolean().default(false).describe('copy instead of moving: the old folder stays (needs room for both)'),
|
|
69
|
+
}),
|
|
70
|
+
output: MoveOutputSchema,
|
|
71
|
+
text: (o) => o.message,
|
|
72
|
+
cli: {
|
|
73
|
+
name: 'storage',
|
|
74
|
+
action: 'move',
|
|
75
|
+
positional: ['folder'],
|
|
76
|
+
flags: { keepOld: { flag: '--keep-old' } },
|
|
77
|
+
json: true,
|
|
78
|
+
shapeJson: ({ message: _message, ...result }) => result,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
export const useStorage = defineOperation({
|
|
82
|
+
id: 'storage.use',
|
|
83
|
+
title: 'Use another folder…',
|
|
84
|
+
summary: 'use another folder for your saved data, without moving it',
|
|
85
|
+
input: z.object({ folder: z.string().optional().describe('the folder to use') }),
|
|
86
|
+
output: z.object({ message: z.string().describe('What happened') }),
|
|
87
|
+
text: (o) => o.message,
|
|
88
|
+
cli: { name: 'storage', action: 'use', positional: ['folder'] },
|
|
89
|
+
});
|
|
90
|
+
export const changeDataFolder = defineOperation({
|
|
91
|
+
id: 'storage.change',
|
|
92
|
+
title: 'Data folder',
|
|
93
|
+
summary: 'pick another folder for your saved data, then move it there or switch to it',
|
|
94
|
+
input: z.object({}),
|
|
95
|
+
output: z.object({ message: z.string().describe('What happened') }),
|
|
96
|
+
text: (o) => o.message,
|
|
97
|
+
surfaces: { except: { cli: 'the command line has storage move <folder> and storage use <folder>' } },
|
|
98
|
+
});
|
|
99
|
+
export const ContainedStatusSchema = z.object({
|
|
100
|
+
setting: z.enum(['off', 'vhdx']).describe('storage.contained'),
|
|
101
|
+
maxGb: z.number().describe('storage.containedMaxGb'),
|
|
102
|
+
available: z.boolean().describe('This PC can use it'),
|
|
103
|
+
reasons: z.array(z.string()).describe('Why not'),
|
|
104
|
+
fixes: z.array(z.string()).describe('What would fix it'),
|
|
105
|
+
image: z.string().describe('The disk image file'),
|
|
106
|
+
imageExists: z.boolean().describe('It exists'),
|
|
107
|
+
imageBytes: z.number().nullable().describe('Its size on disk'),
|
|
108
|
+
mountFolder: z.string().describe('Where it is mounted'),
|
|
109
|
+
recordedMounted: z.boolean().describe('The record says it is mounted'),
|
|
110
|
+
holders: z.number().describe('Sandboxes using it'),
|
|
111
|
+
mountedNow: z.boolean().nullable().describe('It is mounted now (null: not checked)'),
|
|
112
|
+
});
|
|
113
|
+
export const containedStatus = defineQuery({
|
|
114
|
+
id: 'storage.containedStatus',
|
|
115
|
+
title: 'Contained storage (beta)',
|
|
116
|
+
summary: '(beta) the disk image for tools, state and logs',
|
|
117
|
+
input: z.object({}),
|
|
118
|
+
output: ContainedStatusSchema,
|
|
119
|
+
text: (o) => [
|
|
120
|
+
'Contained storage (beta, untested on a real PC)',
|
|
121
|
+
' setting: storage.contained = ' + o.setting + ' (limit ' + o.maxGb + ' GB)',
|
|
122
|
+
' this PC: ' +
|
|
123
|
+
(o.available ? 'can use it' : 'cannot use it: ' + o.reasons.join(' ') + ' ' + o.fixes.join(' ')),
|
|
124
|
+
' image: ' +
|
|
125
|
+
o.image +
|
|
126
|
+
(o.imageExists ? ' (' + bytesText(o.imageBytes ?? 0) + ' on disk)' : ' (not made yet)'),
|
|
127
|
+
' mount folder: ' + o.mountFolder,
|
|
128
|
+
...(o.mountedNow === null
|
|
129
|
+
? []
|
|
130
|
+
: [
|
|
131
|
+
' mounted now: ' +
|
|
132
|
+
(o.mountedNow ? 'yes' : 'no') +
|
|
133
|
+
(o.recordedMounted !== o.mountedNow
|
|
134
|
+
? ' (the record says ' + (o.recordedMounted ? 'yes' : 'no') + '; sandbox doctor looks into it)'
|
|
135
|
+
: ''),
|
|
136
|
+
]),
|
|
137
|
+
'',
|
|
138
|
+
'Turn it on with: sandbox storage contained on Turn it off with: sandbox storage contained off',
|
|
139
|
+
].join('\n'),
|
|
140
|
+
cli: { name: 'storage', action: 'contained status', json: true, shapeJson: ({ fixes: _fixes, ...rest }) => rest },
|
|
141
|
+
});
|
|
142
|
+
export const containedOn = defineOperation({
|
|
143
|
+
id: 'storage.containedOn',
|
|
144
|
+
title: 'Turn contained storage on…',
|
|
145
|
+
summary: '(beta) keep tools, state and logs in a disk image',
|
|
146
|
+
input: z.object({}),
|
|
147
|
+
output: z.object({ message: z.string().describe('What happened') }),
|
|
148
|
+
text: (o) => o.message,
|
|
149
|
+
cli: { name: 'storage', action: 'contained on' },
|
|
150
|
+
});
|
|
151
|
+
export const containedOff = defineOperation({
|
|
152
|
+
id: 'storage.containedOff',
|
|
153
|
+
title: 'Turn contained storage off…',
|
|
154
|
+
summary: '(beta) move tools, state and logs back into plain folders',
|
|
155
|
+
danger: true,
|
|
156
|
+
input: z.object({}),
|
|
157
|
+
output: z.object({ message: z.string().describe('What happened') }),
|
|
158
|
+
text: (o) => o.message,
|
|
159
|
+
cli: { name: 'storage', action: 'contained off' },
|
|
160
|
+
});
|
|
161
|
+
/** The Storage page: the data folder (change, move, use another), then contained storage (beta). */
|
|
162
|
+
export function storagePage(o, c) {
|
|
163
|
+
// Move and use another folder are the two ways to change it (the same choice as the Data folder setting).
|
|
164
|
+
const data = {
|
|
165
|
+
id: 'folder',
|
|
166
|
+
title: 'Data folder',
|
|
167
|
+
description: 'Where your tools, logins, history and logs are kept between sandboxes.',
|
|
168
|
+
facts: [
|
|
169
|
+
{ id: 'folder', label: 'Folder', value: o.root },
|
|
170
|
+
{ id: 'size', label: 'Size', value: o.size },
|
|
171
|
+
{ id: 'default', label: 'Default folder', value: o.defaultRoot + (o.defaultInUse ? ' (in use)' : '') },
|
|
172
|
+
...o.otherFoldersWithData.map((f, i) => ({ id: 'other:' + i, label: 'Other folder with data', value: f })),
|
|
173
|
+
],
|
|
174
|
+
actions: [action(moveStorage, {}), action(useStorage, {})],
|
|
175
|
+
};
|
|
176
|
+
const contained = {
|
|
177
|
+
id: 'contained',
|
|
178
|
+
title: 'Contained storage (beta)',
|
|
179
|
+
description: 'Beta, and not yet tested on a real PC: keeps the sandbox tools, state and logs in one disk image file.',
|
|
180
|
+
facts: [
|
|
181
|
+
{
|
|
182
|
+
id: 'setting',
|
|
183
|
+
label: 'Turned on',
|
|
184
|
+
value: c.setting === 'vhdx' ? 'yes' : 'no',
|
|
185
|
+
tone: c.setting === 'vhdx' ? 'ok' : 'muted',
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
id: 'pc',
|
|
189
|
+
label: 'This PC',
|
|
190
|
+
value: c.available ? 'can use it' : 'cannot use it',
|
|
191
|
+
tone: c.available ? 'ok' : 'warn',
|
|
192
|
+
...(c.available ? {} : { description: [...c.reasons, ...c.fixes].join(' ') }),
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
id: 'image',
|
|
196
|
+
label: 'Image',
|
|
197
|
+
value: c.imageExists ? bytesText(c.imageBytes ?? 0) + ' on disk' : 'not made yet',
|
|
198
|
+
description: c.image,
|
|
199
|
+
},
|
|
200
|
+
{ id: 'mount', label: 'Mount folder', value: c.mountFolder },
|
|
201
|
+
{
|
|
202
|
+
id: 'mounted',
|
|
203
|
+
label: 'Mounted now',
|
|
204
|
+
value: c.mountedNow === null ? 'not checked' : c.mountedNow ? 'yes' : 'no',
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
actions: c.setting === 'off' ? [action(containedOn, {})] : [action(containedOff, {})],
|
|
208
|
+
};
|
|
209
|
+
return { title: 'Storage', sections: [data, contained] };
|
|
210
|
+
}
|
|
211
|
+
export const storageView = defineView({
|
|
212
|
+
id: 'storage',
|
|
213
|
+
title: 'Storage',
|
|
214
|
+
summary: 'Where your saved data lives: move it, switch folders, (beta) a disk image.',
|
|
215
|
+
queries: { storage: showStorage, contained: containedStatus },
|
|
216
|
+
operations: [changeDataFolder, moveStorage, useStorage, containedOn, containedOff],
|
|
217
|
+
pickers: [browseFolder],
|
|
218
|
+
inputs: () => ({ storage: { measure: true } }),
|
|
219
|
+
build: ({ storage, contained }) => storagePage(storage, contained),
|
|
220
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type Context } from '../../../cli/commands/context.js';
|
|
2
|
+
export declare const showStorageRun: import("../../kernel/define.js").Implementation<import("../../kernel/define.js").QueryDef<import("zod").ZodObject<{
|
|
3
|
+
measure: import("zod").ZodDefault<import("zod").ZodBoolean>;
|
|
4
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
5
|
+
root: import("zod").ZodString;
|
|
6
|
+
exists: import("zod").ZodBoolean;
|
|
7
|
+
holdsData: import("zod").ZodBoolean;
|
|
8
|
+
measured: import("zod").ZodNullable<import("zod").ZodObject<{
|
|
9
|
+
bytes: import("zod").ZodNumber;
|
|
10
|
+
files: import("zod").ZodNumber;
|
|
11
|
+
partial: import("zod").ZodBoolean;
|
|
12
|
+
}, import("zod/v4/core").$strip>>;
|
|
13
|
+
size: import("zod").ZodString;
|
|
14
|
+
defaultRoot: import("zod").ZodString;
|
|
15
|
+
defaultInUse: import("zod").ZodBoolean;
|
|
16
|
+
otherFoldersWithData: import("zod").ZodArray<import("zod").ZodString>;
|
|
17
|
+
}, import("zod/v4/core").$strip>, "storage.show">, Context>;
|
|
18
|
+
export declare const moveStorageRun: import("../../kernel/define.js").Implementation<import("../../kernel/define.js").OperationDef<import("zod").ZodObject<{
|
|
19
|
+
folder: import("zod").ZodOptional<import("zod").ZodString>;
|
|
20
|
+
keepOld: import("zod").ZodDefault<import("zod").ZodBoolean>;
|
|
21
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
22
|
+
from: import("zod").ZodString;
|
|
23
|
+
to: import("zod").ZodString;
|
|
24
|
+
copied: import("zod").ZodBoolean;
|
|
25
|
+
files: import("zod").ZodNumber;
|
|
26
|
+
bytes: import("zod").ZodNumber;
|
|
27
|
+
pinned: import("zod").ZodString;
|
|
28
|
+
removedOld: import("zod").ZodBoolean;
|
|
29
|
+
notes: import("zod").ZodArray<import("zod").ZodString>;
|
|
30
|
+
message: import("zod").ZodString;
|
|
31
|
+
}, import("zod/v4/core").$strip>, "storage.move">, Context>;
|
|
32
|
+
export declare const useStorageRun: import("../../kernel/define.js").Implementation<import("../../kernel/define.js").OperationDef<import("zod").ZodObject<{
|
|
33
|
+
folder: import("zod").ZodOptional<import("zod").ZodString>;
|
|
34
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
35
|
+
message: import("zod").ZodString;
|
|
36
|
+
}, import("zod/v4/core").$strip>, "storage.use">, Context>;
|
|
37
|
+
export declare const changeDataFolderRun: import("../../kernel/define.js").Implementation<import("../../kernel/define.js").OperationDef<import("zod").ZodObject<{}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
38
|
+
message: import("zod").ZodString;
|
|
39
|
+
}, import("zod/v4/core").$strip>, "storage.change">, Context>;
|
|
40
|
+
export declare const containedStatusRun: import("../../kernel/define.js").Implementation<import("../../kernel/define.js").QueryDef<import("zod").ZodObject<{}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
41
|
+
setting: import("zod").ZodEnum<{
|
|
42
|
+
off: "off";
|
|
43
|
+
vhdx: "vhdx";
|
|
44
|
+
}>;
|
|
45
|
+
maxGb: import("zod").ZodNumber;
|
|
46
|
+
available: import("zod").ZodBoolean;
|
|
47
|
+
reasons: import("zod").ZodArray<import("zod").ZodString>;
|
|
48
|
+
fixes: import("zod").ZodArray<import("zod").ZodString>;
|
|
49
|
+
image: import("zod").ZodString;
|
|
50
|
+
imageExists: import("zod").ZodBoolean;
|
|
51
|
+
imageBytes: import("zod").ZodNullable<import("zod").ZodNumber>;
|
|
52
|
+
mountFolder: import("zod").ZodString;
|
|
53
|
+
recordedMounted: import("zod").ZodBoolean;
|
|
54
|
+
holders: import("zod").ZodNumber;
|
|
55
|
+
mountedNow: import("zod").ZodNullable<import("zod").ZodBoolean>;
|
|
56
|
+
}, import("zod/v4/core").$strip>, "storage.containedStatus">, Context>;
|
|
57
|
+
export declare const containedOnRun: import("../../kernel/define.js").Implementation<import("../../kernel/define.js").OperationDef<import("zod").ZodObject<{}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
58
|
+
message: import("zod").ZodString;
|
|
59
|
+
}, import("zod/v4/core").$strip>, "storage.containedOn">, Context>;
|
|
60
|
+
export declare const containedOffRun: import("../../kernel/define.js").Implementation<import("../../kernel/define.js").OperationDef<import("zod").ZodObject<{}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
61
|
+
message: import("zod").ZodString;
|
|
62
|
+
}, import("zod/v4/core").$strip>, "storage.containedOff">, Context>;
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
// The storage area's work: where the saved data is, moving it (size, time and a warning first) or switching folders,
|
|
2
|
+
// and contained storage (beta: the disk image, which Windows asks permission to mount). Talks only through io.
|
|
3
|
+
// Every run is on this PC only (requireOnPc). The web's folder picker gives full paths. A relative path typed on the
|
|
4
|
+
// web is resolved against the web server's folder (as the command line does against the current folder); the move
|
|
5
|
+
// and switch questions then show the full path before anything changes, so the person still sees where it goes.
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { userConfigPath, userDir } from '../../../core/config/index.js';
|
|
9
|
+
import { humanBytes } from '../../../core/disk.js';
|
|
10
|
+
import { expandPath, fail } from '../../../core/util/index.js';
|
|
11
|
+
import { requireOnPc } from '../../../cli/commands/context.js';
|
|
12
|
+
import { StorageService } from '../../../cli/services/index.js';
|
|
13
|
+
import { isSandboxRunning, realContainedDeps } from '../../../host/sandbox/index.js';
|
|
14
|
+
import { assertTargetsFree, containedAvailability, ContainedStorage, describeDuration, describeSize, estimate, holdsData, measureFolder, moveProblem, moveWarning, otherDataFolders, planTransfer, runTransfer, setStateOf, sharedDefaultRoot, StateFile, StorageHistory, writeContainedSetting, } from '../../../host/storage/index.js';
|
|
15
|
+
import { implement } from '../../context.js';
|
|
16
|
+
import { cancelled } from '../../kernel/index.js';
|
|
17
|
+
import { changeDataFolder, containedOff, containedOn, containedStatus, moveStorage, showStorage, useStorage, } from './storage.def.js';
|
|
18
|
+
const BETA = 'Beta, and not yet tested on a real PC. ';
|
|
19
|
+
/** The menu's change of folder measures for at most this long (the size then reads "at least"). */
|
|
20
|
+
const MEASURE_MS = 8000;
|
|
21
|
+
const NO_TERMINAL = 'Cannot ask here (no terminal).';
|
|
22
|
+
function requireAvailable() {
|
|
23
|
+
const check = containedAvailability();
|
|
24
|
+
if (!check.available)
|
|
25
|
+
fail('Contained storage cannot be used: ' + check.reasons.join(' '), check.fixes.join(' '));
|
|
26
|
+
}
|
|
27
|
+
function requireNoSandbox() {
|
|
28
|
+
if (isSandboxRunning())
|
|
29
|
+
fail('A sandbox is running.', 'Close it (sandbox stop) first: the folders in the image are in use.');
|
|
30
|
+
}
|
|
31
|
+
function storageFor(ctx) {
|
|
32
|
+
const { config } = ctx.loaded;
|
|
33
|
+
return new ContainedStorage(realContainedDeps(ctx.storageRoot, config.storage.containedMaxGb), config.storage.dataName);
|
|
34
|
+
}
|
|
35
|
+
/** The folder a move or a switch goes to: picked from the known folders, or typed (expanded like the command line). */
|
|
36
|
+
async function chooseFolder(io, service, what, cancelMessage) {
|
|
37
|
+
const typed = '@type';
|
|
38
|
+
const fix = 'Use: sandbox storage ' + what + ' <folder>';
|
|
39
|
+
const picked = await io.choose({
|
|
40
|
+
title: 'Which folder?',
|
|
41
|
+
choices: [
|
|
42
|
+
...service.knownFolders().map((f) => ({ value: f, label: f })),
|
|
43
|
+
{ value: typed, label: 'Type a path...' },
|
|
44
|
+
],
|
|
45
|
+
noTerminal: { message: NO_TERMINAL, fix },
|
|
46
|
+
});
|
|
47
|
+
if (picked === undefined)
|
|
48
|
+
cancelled(cancelMessage);
|
|
49
|
+
if (picked !== typed)
|
|
50
|
+
return picked;
|
|
51
|
+
const answer = await io.path({
|
|
52
|
+
title: 'Folder:',
|
|
53
|
+
pathKind: 'dir',
|
|
54
|
+
validate: (v) => v.length > 0 || 'Type a folder path.',
|
|
55
|
+
noTerminal: { message: NO_TERMINAL, fix },
|
|
56
|
+
});
|
|
57
|
+
if (answer === undefined)
|
|
58
|
+
cancelled(cancelMessage);
|
|
59
|
+
return expandPath(answer, process.cwd());
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Moves the data to `to`: the plan is written out first (size, time, a warning, room on the drive). Asks first when
|
|
63
|
+
* `ask` (the command asks, the menu has asked already). Returns the move's result with its message.
|
|
64
|
+
*/
|
|
65
|
+
async function moveData(io, service, from, to, keepOld, ask) {
|
|
66
|
+
service.validate(to);
|
|
67
|
+
io.progress('Measuring your saved data');
|
|
68
|
+
const measured = measureFolder(from);
|
|
69
|
+
const est = estimate(from, to, measured, keepOld);
|
|
70
|
+
const time = est.sameVolume
|
|
71
|
+
? 'a few seconds (same drive, nothing is copied)'
|
|
72
|
+
: describeDuration(est.seconds) + ' (copied to another drive)';
|
|
73
|
+
io.log('Move from: ' + from);
|
|
74
|
+
io.log(' to: ' + to);
|
|
75
|
+
io.log(' size: ' + describeSize(measured));
|
|
76
|
+
io.log(' time: ' + time);
|
|
77
|
+
const warning = moveWarning(measured, est);
|
|
78
|
+
if (warning)
|
|
79
|
+
io.log('Warning: ' + warning, 'warn');
|
|
80
|
+
if (!est.fits)
|
|
81
|
+
fail('Not enough free space on the target drive.', 'Free some space or pick another drive.');
|
|
82
|
+
if (ask) {
|
|
83
|
+
const yes = await io.confirm({
|
|
84
|
+
title: 'Move your data to ' + to + '?',
|
|
85
|
+
yes: 'Move',
|
|
86
|
+
noTerminal: { message: NO_TERMINAL, fix: 'Add --yes to move without asking.' },
|
|
87
|
+
});
|
|
88
|
+
if (!yes)
|
|
89
|
+
cancelled('Nothing was moved.');
|
|
90
|
+
}
|
|
91
|
+
io.progress('Moving your data');
|
|
92
|
+
const result = await service.move(to, keepOld, (p) => io.progress('Copying: ' + p.files.toLocaleString('en-US') + ' files, ' + humanBytes(p.bytes)));
|
|
93
|
+
for (const note of result.notes)
|
|
94
|
+
io.log(note);
|
|
95
|
+
io.log('Your settings now point to ' + result.pinned + ', so older versions find the data too.');
|
|
96
|
+
const left = result.removedOld ? '' : '; the old folder is still at ' + result.from;
|
|
97
|
+
return {
|
|
98
|
+
...result,
|
|
99
|
+
notes: [...result.notes],
|
|
100
|
+
message: 'Moved ' + result.files.toLocaleString('en-US') + ' files. Your data is now in ' + result.to + left + '.',
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/** Looks for the data in `to` from now on, without moving it. Asks first when `ask`. Returns the message. */
|
|
104
|
+
async function useData(io, service, from, to, ask) {
|
|
105
|
+
// A relative folder would be pinned as it is (and resolved against another folder later): refuse it.
|
|
106
|
+
if (!path.isAbsolute(to))
|
|
107
|
+
fail(to + ' is not a full path.', 'Give a path like D:\\Sandbox.');
|
|
108
|
+
const busy = service.busy();
|
|
109
|
+
if (busy)
|
|
110
|
+
fail(busy, 'Wait a minute, then try again.');
|
|
111
|
+
const state = holdsData(to)
|
|
112
|
+
? to + ' is used as it is.'
|
|
113
|
+
: to + ' starts empty: tools, logins and history are set up again.';
|
|
114
|
+
io.log('Warning: your data stays in ' + from + '. ' + state, 'warn');
|
|
115
|
+
if (ask) {
|
|
116
|
+
const yes = await io.confirm({
|
|
117
|
+
title: 'Use ' + to + ' from now on, without moving your data?',
|
|
118
|
+
yes: 'Use',
|
|
119
|
+
noTerminal: { message: NO_TERMINAL, fix: 'Add --yes to switch without asking.' },
|
|
120
|
+
});
|
|
121
|
+
if (!yes)
|
|
122
|
+
cancelled('Nothing was changed.');
|
|
123
|
+
}
|
|
124
|
+
service.use(to);
|
|
125
|
+
return 'Your data is now looked for in ' + to + '. Move it later with: sandbox storage move <folder>';
|
|
126
|
+
}
|
|
127
|
+
export const showStorageRun = implement(showStorage, async (ctx, input, io) => {
|
|
128
|
+
requireOnPc('storage');
|
|
129
|
+
const root = ctx.storageRoot;
|
|
130
|
+
if (input.measure)
|
|
131
|
+
io.progress('Measuring your saved data (big folders take a moment)');
|
|
132
|
+
const measured = input.measure ? measureFolder(root) : null;
|
|
133
|
+
const has = holdsData(root);
|
|
134
|
+
const shared = sharedDefaultRoot();
|
|
135
|
+
const size = !has ? 'nothing in it yet' : measured ? describeSize(measured) : 'not measured yet';
|
|
136
|
+
const answer = {
|
|
137
|
+
root,
|
|
138
|
+
exists: fs.existsSync(root),
|
|
139
|
+
holdsData: has,
|
|
140
|
+
measured: measured ? { bytes: measured.bytes, files: measured.files, partial: measured.partial } : null,
|
|
141
|
+
size,
|
|
142
|
+
defaultRoot: shared,
|
|
143
|
+
defaultInUse: shared.toLowerCase() === root.toLowerCase(),
|
|
144
|
+
otherFoldersWithData: otherDataFolders(root, new StorageHistory(userDir())),
|
|
145
|
+
};
|
|
146
|
+
return answer;
|
|
147
|
+
});
|
|
148
|
+
export const moveStorageRun = implement(moveStorage, async (ctx, input, io) => {
|
|
149
|
+
requireOnPc('storage');
|
|
150
|
+
const service = new StorageService(ctx);
|
|
151
|
+
const from = ctx.storageRoot;
|
|
152
|
+
const to = input.folder
|
|
153
|
+
? expandPath(input.folder, process.cwd())
|
|
154
|
+
: await chooseFolder(io, service, 'move', 'Nothing was moved.');
|
|
155
|
+
if (!fs.existsSync(from))
|
|
156
|
+
fail('There is nothing to move: ' + from + ' does not exist.', 'Use sandbox storage use ' + to + ' to switch.');
|
|
157
|
+
return moveData(io, service, from, to, input.keepOld, true);
|
|
158
|
+
});
|
|
159
|
+
export const useStorageRun = implement(useStorage, async (ctx, input, io) => {
|
|
160
|
+
requireOnPc('storage');
|
|
161
|
+
const service = new StorageService(ctx);
|
|
162
|
+
const to = input.folder
|
|
163
|
+
? expandPath(input.folder, process.cwd())
|
|
164
|
+
: await chooseFolder(io, service, 'use', 'Nothing was changed.');
|
|
165
|
+
if (to.toLowerCase() === ctx.storageRoot.toLowerCase())
|
|
166
|
+
return { message: 'That folder is in use already.' };
|
|
167
|
+
return { message: await useData(io, service, ctx.storageRoot, to, true) };
|
|
168
|
+
});
|
|
169
|
+
export const changeDataFolderRun = implement(changeDataFolder, async (ctx, _input, io) => {
|
|
170
|
+
requireOnPc('storage');
|
|
171
|
+
const service = new StorageService(ctx);
|
|
172
|
+
const from = ctx.storageRoot;
|
|
173
|
+
const exists = fs.existsSync(from);
|
|
174
|
+
if (exists)
|
|
175
|
+
io.progress('Measuring your saved data');
|
|
176
|
+
const measured = exists ? measureFolder(from, MEASURE_MS) : { bytes: 0, files: 0, partial: false };
|
|
177
|
+
const size = exists && holdsData(from) ? describeSize(measured) : 'nothing in it yet';
|
|
178
|
+
const to = await io.path({
|
|
179
|
+
title: 'Choose the folder for your saved data',
|
|
180
|
+
lines: [
|
|
181
|
+
'Now: ' + from + ' (' + size + ').',
|
|
182
|
+
'Pick a new or empty folder to move your data there, or a folder that already holds data to use it.',
|
|
183
|
+
],
|
|
184
|
+
pathKind: 'dir',
|
|
185
|
+
initial: from.replace(/[\\/][^\\/]*$/, '\\'),
|
|
186
|
+
validate: (picked) => moveProblem(from, picked, true)?.message ?? true,
|
|
187
|
+
});
|
|
188
|
+
if (to === undefined || path.resolve(to).toLowerCase() === path.resolve(from).toLowerCase())
|
|
189
|
+
cancelled();
|
|
190
|
+
// The folder is stored as a full, normalised path (no trailing separator or `..` left in storage.root).
|
|
191
|
+
const dest = path.resolve(to);
|
|
192
|
+
const target = holdsData(dest);
|
|
193
|
+
const lines = [];
|
|
194
|
+
let canMove = exists && holdsData(from) && !target;
|
|
195
|
+
if (canMove) {
|
|
196
|
+
const est = estimate(from, dest, measured);
|
|
197
|
+
lines.push('From: ' + from, 'To: ' + dest, 'Size: ' + describeSize(measured));
|
|
198
|
+
lines.push(est.sameVolume
|
|
199
|
+
? 'Time: a few seconds (same drive, nothing is copied)'
|
|
200
|
+
: 'Time: ' + describeDuration(est.seconds) + ' (copied to another drive)');
|
|
201
|
+
if (est.freeBytes !== null)
|
|
202
|
+
lines.push('Free space on the target drive: ' + humanBytes(est.freeBytes));
|
|
203
|
+
const warning = moveWarning(measured, est);
|
|
204
|
+
if (warning)
|
|
205
|
+
lines.push('', warning);
|
|
206
|
+
if (!est.fits)
|
|
207
|
+
canMove = false;
|
|
208
|
+
}
|
|
209
|
+
else
|
|
210
|
+
lines.push(target ? dest + ' already holds data, so it is used as it is.' : 'There is nothing in ' + from + ' to move.', 'Your data stays where it is.');
|
|
211
|
+
lines.push('', 'Use without moving: the new folder is used from now on and your old data is left behind.');
|
|
212
|
+
const choice = await io.choose({
|
|
213
|
+
title: canMove ? 'Move your data to ' + dest + '?' : 'Use ' + dest + '?',
|
|
214
|
+
lines,
|
|
215
|
+
choices: [
|
|
216
|
+
...(canMove ? [{ value: 'move', label: 'Move data' }] : []),
|
|
217
|
+
{ value: 'use', label: 'Switch without moving' },
|
|
218
|
+
{ value: 'cancel', label: 'Cancel' },
|
|
219
|
+
],
|
|
220
|
+
});
|
|
221
|
+
if (choice === 'move')
|
|
222
|
+
return { message: (await moveData(io, service, from, dest, false, false)).message };
|
|
223
|
+
if (choice === 'use')
|
|
224
|
+
return { message: await useData(io, service, from, dest, false) };
|
|
225
|
+
return cancelled();
|
|
226
|
+
});
|
|
227
|
+
export const containedStatusRun = implement(containedStatus, async (ctx) => {
|
|
228
|
+
requireOnPc('storage');
|
|
229
|
+
const storage = storageFor(ctx);
|
|
230
|
+
const check = containedAvailability();
|
|
231
|
+
const record = setStateOf(new StateFile(storage.paths).read(), storage.set);
|
|
232
|
+
const reality = check.available && storage.imageExists() ? await storage.reality() : null;
|
|
233
|
+
return {
|
|
234
|
+
setting: ctx.loaded.config.storage.contained,
|
|
235
|
+
maxGb: ctx.loaded.config.storage.containedMaxGb,
|
|
236
|
+
available: check.available,
|
|
237
|
+
reasons: [...check.reasons],
|
|
238
|
+
fixes: [...check.fixes],
|
|
239
|
+
image: storage.paths.image,
|
|
240
|
+
imageExists: storage.imageExists(),
|
|
241
|
+
imageBytes: reality?.imageBytes ?? null,
|
|
242
|
+
mountFolder: storage.paths.mount,
|
|
243
|
+
recordedMounted: record.mounted,
|
|
244
|
+
holders: record.holders.length,
|
|
245
|
+
mountedNow: reality?.mountIsMountPoint ?? null,
|
|
246
|
+
};
|
|
247
|
+
});
|
|
248
|
+
/** Writes the plan's sizes and time as lines; a plan that does not fit stops here. */
|
|
249
|
+
function planLines(io, plan, to) {
|
|
250
|
+
for (const f of plan.folders)
|
|
251
|
+
io.log(' ' + f.name + ': ' + describeSize(f.measured));
|
|
252
|
+
io.log(' total: ' + describeSize(plan.total) + ' to ' + to);
|
|
253
|
+
io.log(' time: about ' + describeDuration(plan.estimate.seconds) + ' (copied, then the originals are removed)');
|
|
254
|
+
if (!plan.estimate.fits)
|
|
255
|
+
fail('Not enough free space for the copy.', 'Free some space on that drive and try again.');
|
|
256
|
+
}
|
|
257
|
+
export const containedOnRun = implement(containedOn, async (ctx, _input, io) => {
|
|
258
|
+
requireOnPc('storage');
|
|
259
|
+
requireAvailable();
|
|
260
|
+
requireNoSandbox();
|
|
261
|
+
const storage = storageFor(ctx);
|
|
262
|
+
const { config } = ctx.loaded;
|
|
263
|
+
const configFile = ctx.loaded.configPath ?? userConfigPath();
|
|
264
|
+
io.log(BETA + 'Contained storage keeps the sandbox tools, state and logs in one disk image file.', 'warn');
|
|
265
|
+
io.log(' image: ' + storage.paths.image + (storage.imageExists() ? ' (exists, it is used as it is)' : ''));
|
|
266
|
+
io.log(' limit: ' + config.storage.containedMaxGb + ' GB (storage.containedMaxGb; the file grows only as it fills)');
|
|
267
|
+
io.log(' Windows asks for permission to mount it each time a sandbox starts (and once when it closes).');
|
|
268
|
+
const turnOn = await io.confirm({
|
|
269
|
+
title: 'Turn contained storage on?',
|
|
270
|
+
yes: 'Turn on',
|
|
271
|
+
noTerminal: { message: NO_TERMINAL, fix: 'Add --yes to turn it on without asking.' },
|
|
272
|
+
});
|
|
273
|
+
if (!turnOn)
|
|
274
|
+
fail('Nothing was changed.', 'Run sandbox storage contained on when you want it.');
|
|
275
|
+
io.progress('Making and mounting the image (Windows asks for permission)');
|
|
276
|
+
const mount = await storage.acquire({ id: 'cli', pid: process.pid }, { create: true });
|
|
277
|
+
try {
|
|
278
|
+
const plan = planTransfer(ctx.dataPath, mount, ctx.storageRoot);
|
|
279
|
+
if (plan.folders.length > 0) {
|
|
280
|
+
io.log('These folders hold your tools, state and logs now:');
|
|
281
|
+
planLines(io, plan, 'the image');
|
|
282
|
+
assertTargetsFree(plan);
|
|
283
|
+
const move = await io.confirm({
|
|
284
|
+
title: 'Move them into the image?',
|
|
285
|
+
yes: 'Move',
|
|
286
|
+
noTerminal: { message: NO_TERMINAL, fix: 'Add --yes to move without asking.' },
|
|
287
|
+
});
|
|
288
|
+
if (move) {
|
|
289
|
+
io.progress('Copying');
|
|
290
|
+
const result = await runTransfer(plan, ctx.dataPath, (p) => io.progress('Copying: ' + p.files.toLocaleString('en-US') + ' files, ' + humanBytes(p.bytes)));
|
|
291
|
+
io.log('Moved ' + result.files.toLocaleString('en-US') + ' files into the image.');
|
|
292
|
+
for (const note of result.notes)
|
|
293
|
+
io.log(note, 'warn');
|
|
294
|
+
}
|
|
295
|
+
else
|
|
296
|
+
io.log('They stay where they are and are not used: tools are downloaded again into the image. Delete them later with sandbox data clean.');
|
|
297
|
+
}
|
|
298
|
+
writeContainedSetting(configFile, 'vhdx');
|
|
299
|
+
}
|
|
300
|
+
finally {
|
|
301
|
+
io.progress('Dismounting the image (Windows asks for permission)');
|
|
302
|
+
const done = await storage.release('cli');
|
|
303
|
+
if (!done.dismounted)
|
|
304
|
+
io.log('The image stays mounted (' + (done.note ?? '') + '). sandbox repair dismounts it.', 'warn');
|
|
305
|
+
}
|
|
306
|
+
return { message: 'Contained storage is on. sandbox launch mounts the image first.' };
|
|
307
|
+
});
|
|
308
|
+
export const containedOffRun = implement(containedOff, async (ctx, _input, io) => {
|
|
309
|
+
requireOnPc('storage');
|
|
310
|
+
requireAvailable();
|
|
311
|
+
requireNoSandbox();
|
|
312
|
+
const storage = storageFor(ctx);
|
|
313
|
+
const configFile = ctx.loaded.configPath ?? userConfigPath();
|
|
314
|
+
io.log(BETA + 'Turning contained storage off moves the files back into plain folders.', 'warn');
|
|
315
|
+
if (!storage.imageExists()) {
|
|
316
|
+
writeContainedSetting(configFile, 'off');
|
|
317
|
+
return { message: 'There is no image, so there was nothing to move. Contained storage is off.' };
|
|
318
|
+
}
|
|
319
|
+
io.progress('Mounting the image (Windows asks for permission)');
|
|
320
|
+
const mount = await storage.acquire({ id: 'cli', pid: process.pid });
|
|
321
|
+
let moved = false;
|
|
322
|
+
try {
|
|
323
|
+
const plan = planTransfer(mount, ctx.dataPath, ctx.dataPath);
|
|
324
|
+
if (plan.folders.length > 0) {
|
|
325
|
+
io.log('In the image:');
|
|
326
|
+
planLines(io, plan, ctx.dataPath);
|
|
327
|
+
assertTargetsFree(plan);
|
|
328
|
+
const back = await io.confirm({
|
|
329
|
+
title: 'Move them back out of the image?',
|
|
330
|
+
yes: 'Move',
|
|
331
|
+
noTerminal: { message: NO_TERMINAL, fix: 'Add --yes to move without asking.' },
|
|
332
|
+
});
|
|
333
|
+
if (!back)
|
|
334
|
+
fail('Nothing was moved.', 'Contained storage is still on.');
|
|
335
|
+
io.progress('Copying');
|
|
336
|
+
const result = await runTransfer(plan, mount, (p) => io.progress('Copying: ' + p.files.toLocaleString('en-US') + ' files, ' + humanBytes(p.bytes)));
|
|
337
|
+
io.log('Moved ' + result.files.toLocaleString('en-US') + ' files back.');
|
|
338
|
+
for (const note of result.notes)
|
|
339
|
+
io.log(note, 'warn');
|
|
340
|
+
}
|
|
341
|
+
writeContainedSetting(configFile, 'off');
|
|
342
|
+
moved = true;
|
|
343
|
+
}
|
|
344
|
+
finally {
|
|
345
|
+
io.progress('Dismounting the image (Windows asks for permission)');
|
|
346
|
+
const done = await storage.release('cli');
|
|
347
|
+
if (!done.dismounted)
|
|
348
|
+
io.log('The image stays mounted (' + (done.note ?? '') + '). sandbox repair dismounts it.', 'warn');
|
|
349
|
+
else if (!moved)
|
|
350
|
+
io.log('The image was dismounted; contained storage is still on.');
|
|
351
|
+
}
|
|
352
|
+
// The image is removed only when the person says so; a command line with --yes keeps it (skipWithYes).
|
|
353
|
+
const bytes = fs.statSync(storage.paths.image).size;
|
|
354
|
+
const remove = await io.confirm({
|
|
355
|
+
title: 'Delete the image file now (' + humanBytes(bytes) + ')? Everything in it was moved out.',
|
|
356
|
+
yes: 'Delete',
|
|
357
|
+
danger: true,
|
|
358
|
+
skipWithYes: true,
|
|
359
|
+
noTerminal: { message: NO_TERMINAL, fix: 'Answer no to keep it.' },
|
|
360
|
+
});
|
|
361
|
+
if (remove) {
|
|
362
|
+
await storage.deleteImage();
|
|
363
|
+
io.log('Deleted ' + storage.paths.image + '.');
|
|
364
|
+
}
|
|
365
|
+
else
|
|
366
|
+
io.log('The image file is kept: ' + storage.paths.image + ' (delete it yourself when you are sure).');
|
|
367
|
+
return {
|
|
368
|
+
message: 'Contained storage is off: tools, state and logs are plain folders in ' + ctx.dataPath + '.',
|
|
369
|
+
};
|
|
370
|
+
});
|