dsh-over-ssh 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +119 -2
- package/agents/agent-manifest.json +20 -0
- package/agents/linux-amd64/dsh-over-ssh-agent +0 -0
- package/agents/linux-arm64/dsh-over-ssh-agent +0 -0
- package/cordis.patch.yml +84 -0
- package/lib/client.js +6611 -0
- package/lib/file-references.js +366 -0
- package/lib/fs.js +1368 -0
- package/lib/index.js +19034 -0
- package/lib/local-bridge-refs.js +16 -0
- package/lib/local-bridge.js +16 -0
- package/lib/terminal-wrapper.js +294 -0
- package/lib/types/src/client/controls.d.ts +131 -0
- package/lib/types/src/client/core/card-focus-trap.d.ts +45 -0
- package/lib/types/src/client/core/controls.d.ts +164 -0
- package/lib/types/src/client/core/latest.d.ts +48 -0
- package/lib/types/src/client/core/locale.d.ts +38 -0
- package/lib/types/src/client/core/message.d.ts +37 -0
- package/lib/types/src/client/core/panel-model.d.ts +134 -0
- package/lib/types/src/client/core/portal.d.ts +266 -0
- package/lib/types/src/client/core/remote.d.ts +84 -0
- package/lib/types/src/client/core/styles.d.ts +458 -0
- package/lib/types/src/client/file-tree-io.d.ts +19 -0
- package/lib/types/src/client/file-tree.d.ts +271 -0
- package/lib/types/src/client/flows/directory-browser.d.ts +587 -0
- package/lib/types/src/client/flows/session-directory.d.ts +415 -0
- package/lib/types/src/client/flows/workspace-directory-flow.d.ts +480 -0
- package/lib/types/src/client/index.d.ts +49 -0
- package/lib/types/src/client/locale.d.ts +31 -0
- package/lib/types/src/client/panel-model.d.ts +37 -0
- package/lib/types/src/client/panel.d.ts +77 -0
- package/lib/types/src/client/roots/connection-test.d.ts +69 -0
- package/lib/types/src/client/roots/roots-panel-state.d.ts +307 -0
- package/lib/types/src/client/roots/roots-panel-view.d.ts +280 -0
- package/lib/types/src/client/roots/roots-panel.d.ts +364 -0
- package/lib/types/src/client/roots/transfer-panel-view.d.ts +35 -0
- package/lib/types/src/client/roots/transfer-panel.d.ts +241 -0
- package/lib/types/src/client/roots/transfer-progress.d.ts +67 -0
- package/lib/types/src/client/roots-panel-view.d.ts +236 -0
- package/lib/types/src/client/roots-panel.d.ts +347 -0
- package/lib/types/src/client/session/session-panel-view.d.ts +336 -0
- package/lib/types/src/client/session/session-panel.d.ts +322 -0
- package/lib/types/src/client/session/session-remote-mark.d.ts +110 -0
- package/lib/types/src/client/session/session-row-actions.d.ts +398 -0
- package/lib/types/src/client/session-panel.d.ts +152 -0
- package/lib/types/src/client/styles.d.ts +252 -0
- package/lib/types/src/client/workspace-directory-flow.d.ts +342 -0
- package/lib/types/src/config.d.ts +312 -0
- package/lib/types/src/file-references.d.ts +25 -0
- package/lib/types/src/fs.d.ts +46 -0
- package/lib/types/src/host/app/backend.d.ts +170 -0
- package/lib/types/src/host/app/recycle-bin/recycle-bin-contract.d.ts +247 -0
- package/lib/types/src/host/app/recycle-bin/recycle-bin-move.d.ts +18 -0
- package/lib/types/src/host/app/recycle-bin/recycle-bin-service.d.ts +45 -0
- package/lib/types/src/host/app/recycle-bin/recycle-bin-world.d.ts +38 -0
- package/lib/types/src/host/app/recycle-bin-contract.d.ts +215 -0
- package/lib/types/src/host/app/recycle-bin-move.d.ts +18 -0
- package/lib/types/src/host/app/recycle-bin-service.d.ts +45 -0
- package/lib/types/src/host/app/recycle-bin-world.d.ts +35 -0
- package/lib/types/src/host/app/roots/roots-api-connections.d.ts +49 -0
- package/lib/types/src/host/app/roots/roots-api-contract.d.ts +369 -0
- package/lib/types/src/host/app/roots/roots-api-directories.d.ts +13 -0
- package/lib/types/src/host/app/roots/roots-api-http.d.ts +77 -0
- package/lib/types/src/host/app/roots/roots-api-long-poll.d.ts +130 -0
- package/lib/types/src/host/app/roots/roots-api-projections.d.ts +102 -0
- package/lib/types/src/host/app/roots/roots-api-roots.d.ts +13 -0
- package/lib/types/src/host/app/roots/roots-api-state.d.ts +22 -0
- package/lib/types/src/host/app/roots/roots-api-transfers.d.ts +26 -0
- package/lib/types/src/host/app/roots/roots-api.d.ts +55 -0
- package/lib/types/src/host/app/roots/user-roots.d.ts +552 -0
- package/lib/types/src/host/app/roots-api-connections.d.ts +49 -0
- package/lib/types/src/host/app/roots-api-contract.d.ts +339 -0
- package/lib/types/src/host/app/roots-api-directories.d.ts +13 -0
- package/lib/types/src/host/app/roots-api-http.d.ts +77 -0
- package/lib/types/src/host/app/roots-api-projections.d.ts +102 -0
- package/lib/types/src/host/app/roots-api-roots.d.ts +13 -0
- package/lib/types/src/host/app/roots-api-state.d.ts +22 -0
- package/lib/types/src/host/app/roots-api-support.d.ts +182 -0
- package/lib/types/src/host/app/roots-api-transfers.d.ts +13 -0
- package/lib/types/src/host/app/roots-api.d.ts +55 -0
- package/lib/types/src/host/app/routed-fs.d.ts +239 -0
- package/lib/types/src/host/app/serialize.d.ts +37 -0
- package/lib/types/src/host/app/session-admin/session-admin-context.d.ts +36 -0
- package/lib/types/src/host/app/session-admin/session-admin-contract.d.ts +221 -0
- package/lib/types/src/host/app/session-admin/session-admin-delete.d.ts +34 -0
- package/lib/types/src/host/app/session-admin/session-admin-service.d.ts +40 -0
- package/lib/types/src/host/app/session-admin-context.d.ts +36 -0
- package/lib/types/src/host/app/session-admin-contract.d.ts +214 -0
- package/lib/types/src/host/app/session-admin-delete.d.ts +34 -0
- package/lib/types/src/host/app/session-admin-service.d.ts +38 -0
- package/lib/types/src/host/app/takeover/takeover-plan.d.ts +227 -0
- package/lib/types/src/host/app/takeover/takeover-rows.d.ts +55 -0
- package/lib/types/src/host/app/transport/transport-selector.d.ts +98 -0
- package/lib/types/src/host/app/transport/transport-service.d.ts +82 -0
- package/lib/types/src/host/app/transport-selector.d.ts +98 -0
- package/lib/types/src/host/app/transport-service.d.ts +75 -0
- package/lib/types/src/host/app/user-roots.d.ts +461 -0
- package/lib/types/src/host/domain/bin-location.d.ts +79 -0
- package/lib/types/src/host/domain/capability.d.ts +78 -0
- package/lib/types/src/host/domain/connection.d.ts +95 -0
- package/lib/types/src/host/domain/errors.d.ts +55 -0
- package/lib/types/src/host/domain/glob-pattern.d.ts +62 -0
- package/lib/types/src/host/domain/literal-text.d.ts +63 -0
- package/lib/types/src/host/domain/path-grammar.d.ts +84 -0
- package/lib/types/src/host/domain/recycle-bin.d.ts +337 -0
- package/lib/types/src/host/domain/root.d.ts +74 -0
- package/lib/types/src/host/domain/roots/bin-location.d.ts +91 -0
- package/lib/types/src/host/domain/roots/connection.d.ts +106 -0
- package/lib/types/src/host/domain/roots/root.d.ts +108 -0
- package/lib/types/src/host/domain/roots/route-table.d.ts +102 -0
- package/lib/types/src/host/domain/roots/session-roots.d.ts +151 -0
- package/lib/types/src/host/domain/roots/ssh-config.d.ts +73 -0
- package/lib/types/src/host/domain/route-table.d.ts +80 -0
- package/lib/types/src/host/domain/session/session-admin.d.ts +345 -0
- package/lib/types/src/host/domain/session/terminal-target.d.ts +136 -0
- package/lib/types/src/host/domain/session-admin.d.ts +330 -0
- package/lib/types/src/host/domain/shell.d.ts +47 -0
- package/lib/types/src/host/domain/ssh-config.d.ts +73 -0
- package/lib/types/src/host/domain/terminal-target.d.ts +132 -0
- package/lib/types/src/host/domain/tool-requests.d.ts +369 -0
- package/lib/types/src/host/domain/transfer/recycle-bin.d.ts +337 -0
- package/lib/types/src/host/domain/transfer/transfer.d.ts +260 -0
- package/lib/types/src/host/domain/transfer.d.ts +260 -0
- package/lib/types/src/host/infra/backend/local-backend.d.ts +84 -0
- package/lib/types/src/host/infra/backend/local-version.d.ts +43 -0
- package/lib/types/src/host/infra/backend/platform-local-backend.d.ts +89 -0
- package/lib/types/src/host/infra/backend/remote-backend.d.ts +239 -0
- package/lib/types/src/host/infra/cache/lru.d.ts +52 -0
- package/lib/types/src/host/infra/cache/versioned.d.ts +77 -0
- package/lib/types/src/host/infra/temp-name.d.ts +19 -0
- package/lib/types/src/host/infra/transport/agent/agent-installer.d.ts +121 -0
- package/lib/types/src/host/infra/transport/agent/agent-protocol-client.d.ts +132 -0
- package/lib/types/src/host/infra/transport/agent/agent-protocol-codec.d.ts +140 -0
- package/lib/types/src/host/infra/transport/agent/agent-protocol-errors.d.ts +41 -0
- package/lib/types/src/host/infra/transport/agent/agent-protocol-frames.d.ts +197 -0
- package/lib/types/src/host/infra/transport/agent/agent-protocol-types.d.ts +564 -0
- package/lib/types/src/host/infra/transport/agent/agent-transport.d.ts +418 -0
- package/lib/types/src/host/infra/transport/agent-installer.d.ts +121 -0
- package/lib/types/src/host/infra/transport/agent-protocol-client.d.ts +123 -0
- package/lib/types/src/host/infra/transport/agent-protocol-codec.d.ts +131 -0
- package/lib/types/src/host/infra/transport/agent-protocol-errors.d.ts +41 -0
- package/lib/types/src/host/infra/transport/agent-protocol-frames.d.ts +197 -0
- package/lib/types/src/host/infra/transport/agent-protocol-types.d.ts +509 -0
- package/lib/types/src/host/infra/transport/agent-transport.d.ts +353 -0
- package/lib/types/src/host/infra/transport/alias-transport.d.ts +206 -0
- package/lib/types/src/host/infra/transport/askpass-fs.d.ts +20 -0
- package/lib/types/src/host/infra/transport/askpass.d.ts +133 -0
- package/lib/types/src/host/infra/transport/command-pool.d.ts +61 -0
- package/lib/types/src/host/infra/transport/fs-commands.d.ts +140 -0
- package/lib/types/src/host/infra/transport/git-status-route.d.ts +63 -0
- package/lib/types/src/host/infra/transport/git-status.d.ts +101 -0
- package/lib/types/src/host/infra/transport/node-spawn.d.ts +26 -0
- package/lib/types/src/host/infra/transport/session-window.d.ts +51 -0
- package/lib/types/src/host/infra/transport/shell-quote.d.ts +59 -0
- package/lib/types/src/host/infra/transport/ssh/alias-transport.d.ts +206 -0
- package/lib/types/src/host/infra/transport/ssh/askpass-fs.d.ts +20 -0
- package/lib/types/src/host/infra/transport/ssh/askpass.d.ts +133 -0
- package/lib/types/src/host/infra/transport/ssh/shell-quote.d.ts +62 -0
- package/lib/types/src/host/infra/transport/ssh/ssh-argv.d.ts +43 -0
- package/lib/types/src/host/infra/transport/ssh/ssh2-transport.d.ts +328 -0
- package/lib/types/src/host/infra/transport/ssh-argv.d.ts +43 -0
- package/lib/types/src/host/infra/transport/ssh2-transport.d.ts +302 -0
- package/lib/types/src/host/infra/transport/transport-base.d.ts +34 -0
- package/lib/types/src/host/infra/transport/transport.d.ts +527 -0
- package/lib/types/src/host/integration/agent/agent-install-io.d.ts +33 -0
- package/lib/types/src/host/integration/agent/agent-preflight.d.ts +56 -0
- package/lib/types/src/host/integration/agent/askpass-registry.d.ts +66 -0
- package/lib/types/src/host/integration/agent-install-io.d.ts +33 -0
- package/lib/types/src/host/integration/agent-preflight.d.ts +56 -0
- package/lib/types/src/host/integration/anchor-directory.d.ts +56 -0
- package/lib/types/src/host/integration/askpass-registry.d.ts +59 -0
- package/lib/types/src/host/integration/bin-root-resolver.d.ts +81 -0
- package/lib/types/src/host/integration/connection-test.d.ts +73 -0
- package/lib/types/src/host/integration/file-reference-provider.d.ts +115 -0
- package/lib/types/src/host/integration/fs/file-reference-provider.d.ts +142 -0
- package/lib/types/src/host/integration/fs/fs-provider.d.ts +368 -0
- package/lib/types/src/host/integration/fs/git-status-wiring.d.ts +21 -0
- package/lib/types/src/host/integration/fs/platform-errors.d.ts +94 -0
- package/lib/types/src/host/integration/fs/remote-file-tools.d.ts +369 -0
- package/lib/types/src/host/integration/fs/remote-index.d.ts +177 -0
- package/lib/types/src/host/integration/fs-provider.d.ts +226 -0
- package/lib/types/src/host/integration/lsp/lsp-framing.d.ts +38 -0
- package/lib/types/src/host/integration/lsp/lsp-registry.d.ts +67 -0
- package/lib/types/src/host/integration/lsp/lsp-session.d.ts +130 -0
- package/lib/types/src/host/integration/management-tools.d.ts +64 -0
- package/lib/types/src/host/integration/recycle-bin-service.d.ts +247 -0
- package/lib/types/src/host/integration/remote/connection-test.d.ts +73 -0
- package/lib/types/src/host/integration/remote/remote-face.d.ts +127 -0
- package/lib/types/src/host/integration/remote/remote-tool-backend.d.ts +108 -0
- package/lib/types/src/host/integration/remote-face.d.ts +127 -0
- package/lib/types/src/host/integration/remote-file-tools.d.ts +241 -0
- package/lib/types/src/host/integration/remote-home.d.ts +69 -0
- package/lib/types/src/host/integration/remote-index.d.ts +165 -0
- package/lib/types/src/host/integration/remote-job-tools.d.ts +295 -0
- package/lib/types/src/host/integration/remote-tool-backend.d.ts +97 -0
- package/lib/types/src/host/integration/remote-tools.d.ts +206 -0
- package/lib/types/src/host/integration/root-registry.d.ts +115 -0
- package/lib/types/src/host/integration/roots/anchor-directory.d.ts +56 -0
- package/lib/types/src/host/integration/roots/bin-root-resolver.d.ts +81 -0
- package/lib/types/src/host/integration/roots/remote-home.d.ts +69 -0
- package/lib/types/src/host/integration/roots/root-registry.d.ts +144 -0
- package/lib/types/src/host/integration/roots/roots-wiring.d.ts +219 -0
- package/lib/types/src/host/integration/roots/session-root-index.d.ts +52 -0
- package/lib/types/src/host/integration/roots-api.d.ts +407 -0
- package/lib/types/src/host/integration/roots-wiring.d.ts +211 -0
- package/lib/types/src/host/integration/session/session-api.d.ts +88 -0
- package/lib/types/src/host/integration/session/session-bin-lock.d.ts +80 -0
- package/lib/types/src/host/integration/session/session-header.d.ts +151 -0
- package/lib/types/src/host/integration/session/session-labels.d.ts +56 -0
- package/lib/types/src/host/integration/session/session-route-wiring.d.ts +50 -0
- package/lib/types/src/host/integration/session-admin-service.d.ts +236 -0
- package/lib/types/src/host/integration/session-api.d.ts +81 -0
- package/lib/types/src/host/integration/session-bin-lock.d.ts +80 -0
- package/lib/types/src/host/integration/session-header.d.ts +118 -0
- package/lib/types/src/host/integration/session-labels.d.ts +56 -0
- package/lib/types/src/host/integration/session-route-wiring.d.ts +50 -0
- package/lib/types/src/host/integration/session-scope.d.ts +113 -0
- package/lib/types/src/host/integration/session-tools.d.ts +44 -0
- package/lib/types/src/host/integration/strict-arguments.d.ts +79 -0
- package/lib/types/src/host/integration/takeover/takeover-api.d.ts +62 -0
- package/lib/types/src/host/integration/takeover/takeover-route-wiring.d.ts +60 -0
- package/lib/types/src/host/integration/takeover/takeover-service.d.ts +167 -0
- package/lib/types/src/host/integration/terminal-manifest.d.ts +94 -0
- package/lib/types/src/host/integration/tools/management-tools.d.ts +245 -0
- package/lib/types/src/host/integration/tools/remote-job-tools.d.ts +316 -0
- package/lib/types/src/host/integration/tools/remote-lsp-tools.d.ts +43 -0
- package/lib/types/src/host/integration/tools/remote-tools.d.ts +236 -0
- package/lib/types/src/host/integration/tools/session-target.d.ts +52 -0
- package/lib/types/src/host/integration/tools/session-tools.d.ts +46 -0
- package/lib/types/src/host/integration/tools/strict-arguments.d.ts +79 -0
- package/lib/types/src/host/integration/tools/terminal-manifest.d.ts +94 -0
- package/lib/types/src/host/integration/transfer/remote-copy.d.ts +66 -0
- package/lib/types/src/host/integration/transfer/transfer-adapters.d.ts +48 -0
- package/lib/types/src/host/integration/transfer/transfer-service.d.ts +212 -0
- package/lib/types/src/host/integration/transfer/transfer-tracker.d.ts +106 -0
- package/lib/types/src/host/integration/transfer-adapters.d.ts +44 -0
- package/lib/types/src/host/integration/transfer-service.d.ts +164 -0
- package/lib/types/src/host/integration/transfer-tracker.d.ts +106 -0
- package/lib/types/src/host/integration/user-roots.d.ts +461 -0
- package/lib/types/src/index.d.ts +66 -0
- package/lib/types/src/local-bridge-refs.d.ts +26 -0
- package/lib/types/src/local-bridge.d.ts +36 -0
- package/lib/types/src/shared/format.d.ts +13 -0
- package/lib/types/src/wiring/connections.d.ts +189 -0
- package/lib/types/src/wiring/fs.d.ts +13 -0
- package/lib/types/src/wiring/helpers.d.ts +144 -0
- package/lib/types/src/wiring/install-memo.d.ts +32 -0
- package/lib/types/src/wiring/legacy-dirs.d.ts +62 -0
- package/lib/types/src/wiring/package-paths.d.ts +35 -0
- package/lib/types/src/wiring/roots.d.ts +21 -0
- package/lib/types/src/wiring/state-dir.d.ts +29 -0
- package/lib/types/src/wiring/tools.d.ts +35 -0
- package/package.json +120 -4
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recycle bin: entry model, on-disk layout, retention policy and the undo stack — pure logic only.
|
|
3
|
+
*
|
|
4
|
+
* Why the domain layer (AGENTS.md 红线 2): entry ids, the bin path layout, the retention decision and the
|
|
5
|
+
* undo state machine are all decisions that can be verified offline in milliseconds. Every real filesystem
|
|
6
|
+
* or SSH interaction stays behind the injected seam of
|
|
7
|
+
* [`integration/recycle-bin-service.ts`](../integration/recycle-bin-service.ts).
|
|
8
|
+
*
|
|
9
|
+
* Contract notes:
|
|
10
|
+
*
|
|
11
|
+
* - **Deterministic ids.** The same inputs (root key, original path, deletion instant, delete ordinal for
|
|
12
|
+
* that path) always yield the same id. Two deletes of the same path at the same millisecond are told
|
|
13
|
+
* apart by `sequence`, and {@link allocateRecycleBinId} breaks genuine hash collisions deterministically.
|
|
14
|
+
* - **Layout.** `<binRoot>/<id>/<original basename>`. The id is unique, so same-named files never collide;
|
|
15
|
+
* keeping the original basename in the bin means a restore does not rename anything. Everything else a
|
|
16
|
+
* restore needs (root key, original absolute path) lives on the entry, which the service persists in an
|
|
17
|
+
* index file next to the bins.
|
|
18
|
+
* - **Restore conflicts.** When the original path is occupied, {@link planRestore} reports `blocked` plus an
|
|
19
|
+
* informational suffix candidate; the caller must **refuse**, never silently write to a different name.
|
|
20
|
+
* - **Retention.** `maxEntries` / `maxBytes` / `maxAgeMs` are applied per dimension: a **non-positive value
|
|
21
|
+
* means "no limit"** for that dimension (0 and negatives never wipe anything — deliberate, because a
|
|
22
|
+
* misconfigured 0 must not empty the bin). Oversized bins shed **oldest first**, with the id as a stable
|
|
23
|
+
* tie-break, so the same input always produces the same decision.
|
|
24
|
+
* - **Undo.** A stack. The inverse of `discard` is `restore` (and vice versa, which is what makes the undo
|
|
25
|
+
* redoable); `purge` is a physical delete and is therefore **explicitly irreversible**.
|
|
26
|
+
*
|
|
27
|
+
* @module dsh-remote-workspace/domain/recycle-bin
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* Opaque identity of one recycle-bin entry.
|
|
31
|
+
*
|
|
32
|
+
* Branded on purpose: an id is the directory name inside the bin and a handle the UI holds across calls, so
|
|
33
|
+
* it must not be confused with a path or a session id.
|
|
34
|
+
*/
|
|
35
|
+
export type RecycleBinEntryId = string & {
|
|
36
|
+
readonly __recycleBinEntryId: 'RecycleBinEntryId';
|
|
37
|
+
};
|
|
38
|
+
/** Which world a target lives in; same vocabulary as `RootRef.location`. */
|
|
39
|
+
export type RecycleBinLocation = 'local' | 'remote';
|
|
40
|
+
/** Name of the index file inside a bin root. */
|
|
41
|
+
export declare const RECYCLE_BIN_INDEX_FILE = "index.json";
|
|
42
|
+
/** Version stamped into the index file; a different version is a hard parse failure. */
|
|
43
|
+
export declare const RECYCLE_BIN_INDEX_VERSION = 1;
|
|
44
|
+
/** Inputs that determine an entry id. */
|
|
45
|
+
export interface RecycleBinIdInput {
|
|
46
|
+
/** Stable root key (`rootKey()`); scopes ids to one root. */
|
|
47
|
+
readonly rootKey: string;
|
|
48
|
+
/** Absolute path of the deleted target, in this repository's normalized spelling. */
|
|
49
|
+
readonly originalPath: string;
|
|
50
|
+
/** Deletion instant in milliseconds since the epoch. */
|
|
51
|
+
readonly deletedAtMs: number;
|
|
52
|
+
/** Which delete of that same path this is, 1-based. */
|
|
53
|
+
readonly sequence: number;
|
|
54
|
+
}
|
|
55
|
+
/** One recycle-bin entry: everything a restore needs, plus the facts the bin view shows. */
|
|
56
|
+
export interface RecycleBinEntry {
|
|
57
|
+
readonly id: RecycleBinEntryId;
|
|
58
|
+
/** Root key (`rootKey()`) the target was deleted from. */
|
|
59
|
+
readonly rootKey: string;
|
|
60
|
+
readonly location: RecycleBinLocation;
|
|
61
|
+
/** Original absolute path, normalized (local: `/` separators; remote: POSIX). */
|
|
62
|
+
readonly originalPath: string;
|
|
63
|
+
/** Final segment of {@link originalPath}; the bin keeps this name. */
|
|
64
|
+
readonly originalName: string;
|
|
65
|
+
/** Bin root directory (`binRootFor(root)`). */
|
|
66
|
+
readonly binRoot: string;
|
|
67
|
+
/** Actual location inside the bin: `<binRoot>/<id>/<originalName>`. */
|
|
68
|
+
readonly binPath: string;
|
|
69
|
+
readonly deletedAtMs: number;
|
|
70
|
+
/** Bytes reported by the pre-delete `stat`; for directories this is the directory entry, not a sum. */
|
|
71
|
+
readonly sizeBytes: number;
|
|
72
|
+
readonly isDirectory: boolean;
|
|
73
|
+
/** 1-based ordinal of this delete among deletes of the same path. */
|
|
74
|
+
readonly sequence: number;
|
|
75
|
+
}
|
|
76
|
+
/** Input for {@link makeRecycleBinEntry}; {@link RecycleBinIdInput} plus the id and the observed facts. */
|
|
77
|
+
export interface MakeRecycleBinEntryInput extends RecycleBinIdInput {
|
|
78
|
+
readonly id: RecycleBinEntryId;
|
|
79
|
+
readonly location: RecycleBinLocation;
|
|
80
|
+
readonly binRoot: string;
|
|
81
|
+
readonly sizeBytes: number;
|
|
82
|
+
readonly isDirectory: boolean;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Normalize a deletion target into the spelling the rest of the repository uses.
|
|
86
|
+
*
|
|
87
|
+
* Local targets go through `normalizeLocalPath` (separators to `/`, case preserved), remote targets through
|
|
88
|
+
* `normalizeRemotePath` (POSIX, absolute). A filesystem root is rejected because it can never be a target.
|
|
89
|
+
*
|
|
90
|
+
* @param path - target path in either separator spelling.
|
|
91
|
+
* @param location - which world the path belongs to.
|
|
92
|
+
* @returns the normalized absolute path.
|
|
93
|
+
* @throws SshDevError `ROOT_INVALID` when the path is empty, relative, or a filesystem root.
|
|
94
|
+
*/
|
|
95
|
+
export declare function normalizeRecycleTarget(path: string, location: RecycleBinLocation): string;
|
|
96
|
+
/**
|
|
97
|
+
* The deterministic id for one {@link RecycleBinIdInput}.
|
|
98
|
+
*
|
|
99
|
+
* Same input → same id (a test can therefore pin ids). Different inputs collide only if FNV-1a collides,
|
|
100
|
+
* which {@link allocateRecycleBinId} handles.
|
|
101
|
+
*
|
|
102
|
+
* @param input - root key, original path, deletion instant and delete ordinal.
|
|
103
|
+
* @returns a filesystem-safe id of the form `rb-<8 hex>`.
|
|
104
|
+
*/
|
|
105
|
+
export declare function makeRecycleBinEntryId(input: RecycleBinIdInput): RecycleBinEntryId;
|
|
106
|
+
/**
|
|
107
|
+
* Allocate an id that is not already taken by `existing`.
|
|
108
|
+
*
|
|
109
|
+
* The first candidate is {@link makeRecycleBinEntryId}; on collision the suffix `-2`, `-3`, … is appended.
|
|
110
|
+
* The loop is bounded by the number of live entries, because every candidate it rejects is one of them.
|
|
111
|
+
*
|
|
112
|
+
* @param existing - ids already present in the bin index.
|
|
113
|
+
* @param input - id material.
|
|
114
|
+
* @returns an id not present in `existing`.
|
|
115
|
+
*/
|
|
116
|
+
export declare function allocateRecycleBinId(existing: Iterable<string>, input: RecycleBinIdInput): RecycleBinEntryId;
|
|
117
|
+
/**
|
|
118
|
+
* Build one entry, validating its inputs and computing the bin layout.
|
|
119
|
+
*
|
|
120
|
+
* @param input - id, root, worlds, observed `stat` facts.
|
|
121
|
+
* @returns the entry, with `binPath` = `<binRoot>/<id>/<originalName>`.
|
|
122
|
+
* @throws SshDevError `ROOT_INVALID` when a path, the bin root, or a numeric field is unusable.
|
|
123
|
+
*/
|
|
124
|
+
export declare function makeRecycleBinEntry(input: MakeRecycleBinEntryInput): RecycleBinEntry;
|
|
125
|
+
/**
|
|
126
|
+
* Join segments under an absolute base with `/`.
|
|
127
|
+
*
|
|
128
|
+
* Both worlds use this spelling (`local` roots are stored normalized by `normalizeLocalPath`, remote ones are
|
|
129
|
+
* POSIX), so the bin layout is spelled the same way everywhere. A drive-rooted base (`D:`) keeps its slash.
|
|
130
|
+
*
|
|
131
|
+
* @param base - absolute base directory.
|
|
132
|
+
* @param segments - segments to append.
|
|
133
|
+
* @returns the joined path.
|
|
134
|
+
*/
|
|
135
|
+
export declare function joinUnderPath(base: string, ...segments: readonly string[]): string;
|
|
136
|
+
/** A restore decision: either the move is ready, or the original path is occupied and must not be reused. */
|
|
137
|
+
export type RestorePlan = {
|
|
138
|
+
readonly kind: 'ready';
|
|
139
|
+
readonly from: string;
|
|
140
|
+
readonly to: string;
|
|
141
|
+
} | {
|
|
142
|
+
readonly kind: 'blocked';
|
|
143
|
+
readonly to: string;
|
|
144
|
+
readonly reason: 'occupied';
|
|
145
|
+
readonly suggestedPath: string;
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* Decide how to restore one entry.
|
|
149
|
+
*
|
|
150
|
+
* Policy: **an occupied original path is refused, never renamed silently.** `suggestedPath` is returned so a
|
|
151
|
+
* UI can *offer* an alternative, but no code path in this module moves a target to it.
|
|
152
|
+
*
|
|
153
|
+
* @param entry - entry to restore.
|
|
154
|
+
* @param options - `occupied` is the caller's `stat` of `entry.originalPath`.
|
|
155
|
+
* @returns the plan.
|
|
156
|
+
*/
|
|
157
|
+
export declare function planRestore(entry: RecycleBinEntry, options: {
|
|
158
|
+
readonly occupied: boolean;
|
|
159
|
+
}): RestorePlan;
|
|
160
|
+
/** Retention policy. A non-positive value means "no limit" for that dimension. */
|
|
161
|
+
export interface RecycleBinPolicy {
|
|
162
|
+
/** Keep at most this many entries; non-positive = unlimited. */
|
|
163
|
+
readonly maxEntries: number;
|
|
164
|
+
/** Keep at most this many bytes (sum of entry sizes); non-positive = unlimited. */
|
|
165
|
+
readonly maxBytes: number;
|
|
166
|
+
/** Keep entries at most this old, in milliseconds; non-positive = unlimited. */
|
|
167
|
+
readonly maxAgeMs: number;
|
|
168
|
+
}
|
|
169
|
+
/** Neutral element: no dimension is limited, therefore nothing is ever swept with it. */
|
|
170
|
+
export declare const NO_RECYCLE_BIN_LIMITS: RecycleBinPolicy;
|
|
171
|
+
/** Which dimension caused one entry to be swept. */
|
|
172
|
+
export type SweepDimension = 'age' | 'count' | 'bytes';
|
|
173
|
+
/** Why one entry is scheduled for physical deletion. */
|
|
174
|
+
export interface SweepReason {
|
|
175
|
+
readonly id: RecycleBinEntryId;
|
|
176
|
+
readonly dimension: SweepDimension;
|
|
177
|
+
readonly detail: string;
|
|
178
|
+
}
|
|
179
|
+
/** The pure retention decision for one bin. */
|
|
180
|
+
export interface SweepDecision {
|
|
181
|
+
/** Ids to delete physically, oldest first. */
|
|
182
|
+
readonly purge: readonly RecycleBinEntryId[];
|
|
183
|
+
/** Ids to keep, oldest first. */
|
|
184
|
+
readonly keep: readonly RecycleBinEntryId[];
|
|
185
|
+
/** One reason per purged id, in the same order. */
|
|
186
|
+
readonly reasons: readonly SweepReason[];
|
|
187
|
+
/** Entries and bytes left after applying the decision. */
|
|
188
|
+
readonly remainingEntries: number;
|
|
189
|
+
readonly remainingBytes: number;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Apply a retention policy to one bin.
|
|
193
|
+
*
|
|
194
|
+
* Rules, in this order (all of them deterministic: entries are ordered oldest first with the id as tie-break):
|
|
195
|
+
*
|
|
196
|
+
* 1. `maxAgeMs > 0`: purge everything older than the limit. An entry exactly **at** the limit is kept, and a
|
|
197
|
+
* negative age (clock skew, future timestamp) is never purged.
|
|
198
|
+
* 2. `maxBytes > 0`: walk the survivors oldest first and purge until the remaining sum is within the limit.
|
|
199
|
+
* A single entry larger than the whole budget is purged too — otherwise the budget would be meaningless.
|
|
200
|
+
* 3. `maxEntries > 0`: purge the oldest survivors until the count is within the limit.
|
|
201
|
+
*
|
|
202
|
+
* @param entries - entries of one bin.
|
|
203
|
+
* @param policy - limits; non-positive values are ignored.
|
|
204
|
+
* @param nowMs - current instant in milliseconds since the epoch.
|
|
205
|
+
* @returns the decision.
|
|
206
|
+
*/
|
|
207
|
+
export declare function decideSweep(entries: readonly RecycleBinEntry[], policy: RecycleBinPolicy, nowMs: number): SweepDecision;
|
|
208
|
+
/** What one committed operation did; the unit of the undo stack. */
|
|
209
|
+
export type OperationKind = 'discard' | 'restore' | 'purge';
|
|
210
|
+
/** One committed operation. */
|
|
211
|
+
export interface RecycleBinOperation {
|
|
212
|
+
readonly kind: OperationKind;
|
|
213
|
+
/** Entry the operation acted on; `undefined` for a permanent delete, which has no bin entry. */
|
|
214
|
+
readonly entry: RecycleBinEntry | undefined;
|
|
215
|
+
/** Entry id, or a synthesized id for a permanent delete. */
|
|
216
|
+
readonly id: RecycleBinEntryId;
|
|
217
|
+
readonly atMs: number;
|
|
218
|
+
}
|
|
219
|
+
/** Stack-shaped undo state: `undo` is the history, `redo` what was undone and can be re-applied. */
|
|
220
|
+
export interface UndoStack {
|
|
221
|
+
readonly undo: readonly RecycleBinOperation[];
|
|
222
|
+
readonly redo: readonly RecycleBinOperation[];
|
|
223
|
+
/** Operations dropped for exceeding the depth limit; explains "too old to undo". */
|
|
224
|
+
readonly dropped: number;
|
|
225
|
+
}
|
|
226
|
+
/** Fresh state with no history. */
|
|
227
|
+
export declare const EMPTY_UNDO_STACK: UndoStack;
|
|
228
|
+
/** Why an undo/redo step was refused. */
|
|
229
|
+
export type UndoRejectionReason = 'nothing-to-undo' | 'nothing-to-redo' | 'entry-missing' | 'irreversible';
|
|
230
|
+
/** The world state an undo decision is validated against. */
|
|
231
|
+
export interface UndoContext {
|
|
232
|
+
/**
|
|
233
|
+
* Ids **currently present in the recycle bin**.
|
|
234
|
+
*
|
|
235
|
+
* One rule covers both directions: a `restore` action needs the bin copy (the id must be listed), and a
|
|
236
|
+
* `discard` action needs the file to be back at its original path (the id must not be listed).
|
|
237
|
+
*/
|
|
238
|
+
readonly liveIds: readonly RecycleBinEntryId[];
|
|
239
|
+
}
|
|
240
|
+
/** A step to execute: move the entry out of the bin, or back into it. */
|
|
241
|
+
export interface UndoAction {
|
|
242
|
+
readonly kind: 'restore' | 'discard';
|
|
243
|
+
readonly operation: RecycleBinOperation;
|
|
244
|
+
readonly entry: RecycleBinEntry;
|
|
245
|
+
}
|
|
246
|
+
/** Why a step is not executable. */
|
|
247
|
+
export interface UndoRejection {
|
|
248
|
+
readonly reason: UndoRejectionReason;
|
|
249
|
+
readonly message: string;
|
|
250
|
+
readonly operation: RecycleBinOperation | undefined;
|
|
251
|
+
}
|
|
252
|
+
/** Outcome of one undo/redo step: either an action to run, or a rejection. */
|
|
253
|
+
export interface UndoOutcome {
|
|
254
|
+
/** State after the step; a rejected record is dropped from the stack (it can never succeed). */
|
|
255
|
+
readonly stack: UndoStack;
|
|
256
|
+
readonly action: UndoAction | undefined;
|
|
257
|
+
readonly rejection: UndoRejection | undefined;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Record one committed operation.
|
|
261
|
+
*
|
|
262
|
+
* The redo half is cleared (a new operation starts a new branch of history), the record goes on top of the
|
|
263
|
+
* undo half, and anything beyond `undoDepth` is dropped. A non-positive `undoDepth` keeps no history at all.
|
|
264
|
+
*
|
|
265
|
+
* @param stack - current state.
|
|
266
|
+
* @param operation - the operation that just succeeded.
|
|
267
|
+
* @param options - `undoDepth` from configuration.
|
|
268
|
+
* @returns the new state.
|
|
269
|
+
*/
|
|
270
|
+
export declare function recordOperation(stack: UndoStack, operation: RecycleBinOperation, options: {
|
|
271
|
+
readonly undoDepth: number;
|
|
272
|
+
}): UndoStack;
|
|
273
|
+
/** The operation {@link takeUndo} would consume, or `undefined` for an empty history. */
|
|
274
|
+
export declare function nextUndo(stack: UndoStack): RecycleBinOperation | undefined;
|
|
275
|
+
/** The operation that can be re-applied, or `undefined` when nothing was undone. */
|
|
276
|
+
export declare function nextRedo(stack: UndoStack): RecycleBinOperation | undefined;
|
|
277
|
+
/**
|
|
278
|
+
* Consume the newest operation as an **inverse** step.
|
|
279
|
+
*
|
|
280
|
+
* `discard` reverses to `restore`; `restore` reverses to `discard`; `purge` is refused as `irreversible`
|
|
281
|
+
* (a physical delete cannot be undone, and the record is dropped instead of being offered again).
|
|
282
|
+
*
|
|
283
|
+
* @param stack - current state.
|
|
284
|
+
* @param context - which ids are actually in the bin right now.
|
|
285
|
+
* @returns the action to run plus the new state, or a rejection plus the state with the dead record dropped.
|
|
286
|
+
*/
|
|
287
|
+
export declare function takeUndo(stack: UndoStack, context: UndoContext): UndoOutcome;
|
|
288
|
+
/**
|
|
289
|
+
* Re-apply the newest undone operation.
|
|
290
|
+
*
|
|
291
|
+
* This is the exact inverse of {@link takeUndo}: it consumes the redo half and pushes the same operation back
|
|
292
|
+
* onto the undo half.
|
|
293
|
+
*
|
|
294
|
+
* @param stack - current state.
|
|
295
|
+
* @param context - which ids are actually in the bin right now.
|
|
296
|
+
* @returns the action to run plus the new state, or a rejection.
|
|
297
|
+
*/
|
|
298
|
+
export declare function takeRedo(stack: UndoStack, context: UndoContext): UndoOutcome;
|
|
299
|
+
/** The persisted index document. */
|
|
300
|
+
export interface RecycleBinIndex {
|
|
301
|
+
readonly version: number;
|
|
302
|
+
readonly entries: readonly RecycleBinEntry[];
|
|
303
|
+
}
|
|
304
|
+
/** One record the parser refused, with the reason (a corrupt index must never silently lose data). */
|
|
305
|
+
export interface SkippedIndexRecord {
|
|
306
|
+
readonly index: number;
|
|
307
|
+
readonly reason: string;
|
|
308
|
+
}
|
|
309
|
+
/** Result of {@link parseRecycleBinIndex}. */
|
|
310
|
+
export interface ParsedRecycleBinIndex {
|
|
311
|
+
readonly entries: readonly RecycleBinEntry[];
|
|
312
|
+
readonly skipped: readonly SkippedIndexRecord[];
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Serialize the index.
|
|
316
|
+
*
|
|
317
|
+
* Entries are sorted by id so the same set always produces byte-identical text (easy to diff, easy to test).
|
|
318
|
+
* The result is a plain JavaScript string: written as UTF-8 it carries **no BOM**, and {@link parseRecycleBinIndex}
|
|
319
|
+
* tolerates one anyway because remote tooling and editors add them.
|
|
320
|
+
*
|
|
321
|
+
* @param entries - entries to persist.
|
|
322
|
+
* @returns JSON text.
|
|
323
|
+
*/
|
|
324
|
+
export declare function serializeRecycleBinIndex(entries: readonly RecycleBinEntry[]): string;
|
|
325
|
+
/**
|
|
326
|
+
* Parse the index.
|
|
327
|
+
*
|
|
328
|
+
* A malformed document or an unknown version is a hard failure (the bin contents cannot be trusted); a single
|
|
329
|
+
* unusable record is skipped and reported. Stored `binPath` values are taken as authoritative and not recomputed,
|
|
330
|
+
* so changing the `binRootFor` configuration later does not invalidate existing entries.
|
|
331
|
+
*
|
|
332
|
+
* @param text - index file content.
|
|
333
|
+
* @returns the usable entries plus the skipped records.
|
|
334
|
+
* @throws SshDevError `TRASH_NOT_FOUND` when the document cannot be read as an index.
|
|
335
|
+
*/
|
|
336
|
+
export declare function parseRecycleBinIndex(text: string): ParsedRecycleBinIndex;
|
|
337
|
+
//# sourceMappingURL=recycle-bin.d.ts.map
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Root references: one project root is either a local directory or a remote directory on
|
|
3
|
+
* one connection. Remote roots additionally carry a local *anchor* directory, because DSH
|
|
4
|
+
* requires a workspace path that exists on the host filesystem.
|
|
5
|
+
*
|
|
6
|
+
* @module dsh-remote-workspace/domain/root
|
|
7
|
+
*/
|
|
8
|
+
/** Where a root lives. */
|
|
9
|
+
export type RootLocation = 'local' | 'remote';
|
|
10
|
+
/** One project root. */
|
|
11
|
+
export interface RootRef {
|
|
12
|
+
/** Local or remote. */
|
|
13
|
+
readonly location: RootLocation;
|
|
14
|
+
/** Connection id; present exactly for remote roots. */
|
|
15
|
+
readonly connectionId?: string;
|
|
16
|
+
/** Absolute local path, or absolute POSIX path for a remote root. */
|
|
17
|
+
readonly path: string;
|
|
18
|
+
/** Whether the sandbox fence admits writes under this root. */
|
|
19
|
+
readonly writable: boolean;
|
|
20
|
+
/** Local anchor directory for a remote root (empty directory, never mirrored content). */
|
|
21
|
+
readonly anchorPath?: string;
|
|
22
|
+
/** Display label; defaults to the final path segment. */
|
|
23
|
+
readonly label?: string;
|
|
24
|
+
}
|
|
25
|
+
/** Options shared by both root kinds. */
|
|
26
|
+
export interface RootOptions {
|
|
27
|
+
/** Whether writes under this root are allowed; defaults to true. */
|
|
28
|
+
readonly writable?: boolean;
|
|
29
|
+
/** Display label override. */
|
|
30
|
+
readonly label?: string;
|
|
31
|
+
}
|
|
32
|
+
/** Create a remote root, validating the connection id and remote path. */
|
|
33
|
+
export declare function makeRemoteRoot(connectionId: string, remotePath: string, options?: RootOptions & {
|
|
34
|
+
readonly anchorPath?: string;
|
|
35
|
+
}): RootRef;
|
|
36
|
+
/**
|
|
37
|
+
* Create a local root from an absolute host path.
|
|
38
|
+
*
|
|
39
|
+
* The stored path **preserves the caller's case** (only separators, `.`/`..` and the drive letter
|
|
40
|
+
* are canonicalised). Case folding belongs to comparison and keying — `rootKey` folds, and
|
|
41
|
+
* `localPathContains` folds on request — because the root's path is also what the UI shows and what
|
|
42
|
+
* a child process receives via `processPath`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function makeLocalRoot(path: string, options?: RootOptions): RootRef;
|
|
45
|
+
/** Whether a host path is absolute in either the Windows or the POSIX spelling. */
|
|
46
|
+
export declare function isAbsoluteLocalPath(path: string): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Normalize a host path for comparison: separators to `/`, `.`/`..` resolved, trailing
|
|
49
|
+
* separators dropped, and (optionally) the drive letter and case folded.
|
|
50
|
+
*
|
|
51
|
+
* @param path - host path in either separator spelling.
|
|
52
|
+
* @param options - `caseInsensitive` folds case for comparison keys.
|
|
53
|
+
* @returns the normalized path.
|
|
54
|
+
*/
|
|
55
|
+
export declare function normalizeLocalPath(path: string, options?: {
|
|
56
|
+
readonly caseInsensitive?: boolean;
|
|
57
|
+
}): string;
|
|
58
|
+
/** Whether a host path is `root` itself or a descendant of it. */
|
|
59
|
+
export declare function localPathContains(root: string, candidate: string, options?: {
|
|
60
|
+
readonly caseInsensitive?: boolean;
|
|
61
|
+
}): boolean;
|
|
62
|
+
/** Stable identity of a root, used as a cache/route key. */
|
|
63
|
+
export declare function rootKey(root: RootRef): string;
|
|
64
|
+
/** The model-facing and UI-facing spelling of a root's directory. */
|
|
65
|
+
export declare function rootDisplay(root: RootRef): string;
|
|
66
|
+
/** The display label of a root: explicit label, else the final path segment. */
|
|
67
|
+
export declare function rootLabel(root: RootRef): string;
|
|
68
|
+
/** Whether two roots of the same location overlap (one contains the other). */
|
|
69
|
+
export declare function rootsOverlap(left: RootRef, right: RootRef): boolean;
|
|
70
|
+
/** Validate a root reference produced outside this module. */
|
|
71
|
+
export declare function validateRoot(root: RootRef): void;
|
|
72
|
+
/** FNV-1a (32-bit) over UTF-16 code units, rendered as 8 lowercase hex characters. */
|
|
73
|
+
export declare function fnv1aHex(input: string): string;
|
|
74
|
+
//# sourceMappingURL=root.d.ts.map
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a root's **file-level recycle bin** lives, and how to tell whether that place is usable.
|
|
3
|
+
*
|
|
4
|
+
* The bin used to sit at `<root>/.dsh-remote-workspace/trash` — inside the caller's own repository, so every
|
|
5
|
+
* `git status` and `git add .` saw the bytes someone had just "safely deleted". Moving it out is not a matter
|
|
6
|
+
* of changing a default (audit 0064 §3): the router refuses any path that is not inside a registered root, so
|
|
7
|
+
* the new location has to be *registered* as a root of its own — one that is deliberately **not** listed
|
|
8
|
+
* anywhere the user looks. The placement rules live here, as pure functions, because they are the part that
|
|
9
|
+
* must be identical for every root and every world.
|
|
10
|
+
*
|
|
11
|
+
* Two constraints decide the location, and both are hard:
|
|
12
|
+
*
|
|
13
|
+
* 1. **Same device as the root.** The bin's whole guarantee is "move first, then confirm", and a cross-device
|
|
14
|
+
* `rename` is refused outright rather than degraded to a copy (`fs-commands.ts`), because a copy can fail
|
|
15
|
+
* half way and leave the bytes neither here nor there. The file-level bin has no copy fallback. So the bin
|
|
16
|
+
* must be on the root's own filesystem.
|
|
17
|
+
* 2. **Outside the repository, but not somewhere unguessable.** `$HOME` looks tidy and is the wrong answer:
|
|
18
|
+
* on the site this plugin was built against the login home is `/home1/<user>` while projects live under
|
|
19
|
+
* `/srv` or `/mnt`, which are usually separate mounts (NFS/automount) — so a home-based bin would make
|
|
20
|
+
* **every** delete fail with `cross-device`. The root's own **parent** is the closest directory that is
|
|
21
|
+
* almost always on the same filesystem.
|
|
22
|
+
*
|
|
23
|
+
* @module dsh-over-ssh/domain/bin-location
|
|
24
|
+
*/
|
|
25
|
+
import { type RootRef } from './root.ts';
|
|
26
|
+
/** The bin directory name used when the caller configures none (`binDir`). */
|
|
27
|
+
export declare const DEFAULT_BIN_DIR = ".dsh-over-ssh/trash";
|
|
28
|
+
/**
|
|
29
|
+
* Bin directory names this plugin used before it was renamed to `dsh-over-ssh`.
|
|
30
|
+
*
|
|
31
|
+
* Kept as **read sources**, never as write targets: the bin index is the only record of what is recoverable
|
|
32
|
+
* and each entry names its bytes by absolute path, so a rename without a migration would leave every already
|
|
33
|
+
* deleted file on disk but unreachable. The resolver moves an old bin into {@link DEFAULT_BIN_DIR} the first
|
|
34
|
+
* time it sees one (see `bin-root-resolver.ts`).
|
|
35
|
+
*
|
|
36
|
+
* Both spellings are listed because the bin lived in two places over its lifetime: inside the root, and
|
|
37
|
+
* beside it under the root's parent.
|
|
38
|
+
*/
|
|
39
|
+
export declare const LEGACY_BIN_DIRS: readonly [".dsh-remote-workspace/trash"];
|
|
40
|
+
/**
|
|
41
|
+
* The root's own directory, in the spelling that world uses.
|
|
42
|
+
*
|
|
43
|
+
* Remote paths are always POSIX and are normalised as such; a local path is normalised by the host's rules
|
|
44
|
+
* (which is what makes `D:\work\a` and `D:/work/a` the same directory on Windows).
|
|
45
|
+
*
|
|
46
|
+
* @param root - the root whose directory is wanted.
|
|
47
|
+
* @returns a `/`-separated path.
|
|
48
|
+
*/
|
|
49
|
+
export declare function rootDirectory(root: RootRef): string;
|
|
50
|
+
/**
|
|
51
|
+
* The bin directory **inside** the root — the historical location, kept as the documented fallback.
|
|
52
|
+
*
|
|
53
|
+
* @param root - the root being served.
|
|
54
|
+
* @param binDir - configured directory name, relative to the root.
|
|
55
|
+
* @returns the absolute bin directory.
|
|
56
|
+
*/
|
|
57
|
+
export declare function binRootInsideRoot(root: RootRef, binDir?: string): string;
|
|
58
|
+
/**
|
|
59
|
+
* A stable, filesystem-safe directory name for one root's bin.
|
|
60
|
+
*
|
|
61
|
+
* Two roots can share a parent (and even a basename, across connections), so the name carries a hash of the
|
|
62
|
+
* root's identity rather than only its last segment. Stable across runs: the bin directory must be findable
|
|
63
|
+
* again after a restart, and an unstable name would silently orphan every deleted byte.
|
|
64
|
+
*
|
|
65
|
+
* @param root - the root being served.
|
|
66
|
+
* @returns a name such as `work-1f3a9c02`.
|
|
67
|
+
*/
|
|
68
|
+
export declare function binRootSlug(root: RootRef): string;
|
|
69
|
+
/**
|
|
70
|
+
* The bin directory **beside** the root, under its parent.
|
|
71
|
+
*
|
|
72
|
+
* The parent is used rather than `$HOME` because it is almost always the root's own filesystem, which is the
|
|
73
|
+
* one property the bin cannot do without. `binDir` keeps its configured name so the plugin's footprint reads
|
|
74
|
+
* the same as before; the per-root slug keeps two roots in one parent apart.
|
|
75
|
+
*
|
|
76
|
+
* @param root - the root being served.
|
|
77
|
+
* @param binDir - configured directory name, relative to the parent.
|
|
78
|
+
* @returns the absolute bin directory, or `undefined` when the root has no usable parent (it *is* `/`).
|
|
79
|
+
*/
|
|
80
|
+
export declare function binRootBesideRoot(root: RootRef, binDir?: string): string | undefined;
|
|
81
|
+
/**
|
|
82
|
+
* The directory a same-device check must compare the bin directory against.
|
|
83
|
+
*
|
|
84
|
+
* The bin is created by the plugin, so on the first delete it may not exist yet; the parent is what has to be
|
|
85
|
+
* on the same filesystem, and it always exists (it contains the root).
|
|
86
|
+
*
|
|
87
|
+
* @param root - the root being served.
|
|
88
|
+
* @returns the absolute parent directory, or `undefined` for a root with no parent.
|
|
89
|
+
*/
|
|
90
|
+
export declare function binParentDirectory(root: RootRef): string | undefined;
|
|
91
|
+
//# sourceMappingURL=bin-location.d.ts.map
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connection profiles and their validation.
|
|
3
|
+
*
|
|
4
|
+
* A profile never carries secret material: a password is referenced by credential key and
|
|
5
|
+
* resolved from `ctx.credentials` per operation.
|
|
6
|
+
*
|
|
7
|
+
* @module dsh-over-ssh/domain/connection
|
|
8
|
+
*/
|
|
9
|
+
import type { SshConfigImportDraft } from './ssh-config.ts';
|
|
10
|
+
/** How the transport reaches the host. `agent` is the opt-in Tier 2 proxy (launched over the system ssh). */
|
|
11
|
+
export type TransportKind = 'alias' | 'ssh2' | 'agent';
|
|
12
|
+
/** Host key verification policy. */
|
|
13
|
+
export type HostKeyPolicy = 'strict' | 'accept-new';
|
|
14
|
+
/** Authentication method. */
|
|
15
|
+
export type AuthSpec = {
|
|
16
|
+
readonly kind: 'agent';
|
|
17
|
+
} | {
|
|
18
|
+
readonly kind: 'key';
|
|
19
|
+
readonly keyPath: string;
|
|
20
|
+
readonly hasPassphrase: boolean;
|
|
21
|
+
} | {
|
|
22
|
+
readonly kind: 'password';
|
|
23
|
+
readonly credentialKey: string;
|
|
24
|
+
};
|
|
25
|
+
/** One saved connection. */
|
|
26
|
+
export interface ConnectionProfile {
|
|
27
|
+
/** Stable slug used inside `ssh://` paths and as the pool key. */
|
|
28
|
+
readonly id: string;
|
|
29
|
+
/** Human-facing name. */
|
|
30
|
+
readonly name: string;
|
|
31
|
+
/** Transport implementation. */
|
|
32
|
+
readonly transport: TransportKind;
|
|
33
|
+
/** Host name used by the ssh2 transport, and by alias transport only for diagnostics. */
|
|
34
|
+
readonly host: string;
|
|
35
|
+
readonly port: number;
|
|
36
|
+
readonly user: string;
|
|
37
|
+
/** OpenSSH alias; required by the alias transport. */
|
|
38
|
+
readonly alias?: string;
|
|
39
|
+
readonly auth: AuthSpec;
|
|
40
|
+
/** ProxyJump chain, applied through `-J` (ssh2) or the alias (config). */
|
|
41
|
+
readonly proxyJump: readonly string[];
|
|
42
|
+
readonly hostKeyPolicy: HostKeyPolicy;
|
|
43
|
+
/** Remote directory used as the default root when creating a remote workspace. */
|
|
44
|
+
readonly remoteDefaultRoot?: string;
|
|
45
|
+
readonly connectTimeoutMs: number;
|
|
46
|
+
readonly commandTimeoutMs: number;
|
|
47
|
+
readonly keepAliveSeconds: number;
|
|
48
|
+
}
|
|
49
|
+
/** Defaults for a new connection. */
|
|
50
|
+
export declare const CONNECTION_DEFAULTS: {
|
|
51
|
+
readonly port: 22;
|
|
52
|
+
readonly connectTimeoutMs: 15000;
|
|
53
|
+
readonly commandTimeoutMs: 30000;
|
|
54
|
+
readonly keepAliveSeconds: 60;
|
|
55
|
+
};
|
|
56
|
+
/** Whether a string can name a value in the credential store. */
|
|
57
|
+
export declare function isValidCredentialRef(value: string): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* 一个凭据引用名**可能落在哪几个名字上**,按优先顺序。
|
|
60
|
+
*
|
|
61
|
+
* `credentialKey` 会落盘进 `user-roots.json`,所以老连接的引用名仍是 `DSH_SSH_…`,直接用就行。回退是
|
|
62
|
+
* 为了那些**没落盘**的早期记录:它们的引用名是运行时按 id 现推的,改名后会推出新前缀,不回退就取不到
|
|
63
|
+
* 已存的密码。反过来,新名字在前,所以迁移过的连接不会去读旧位置。
|
|
64
|
+
*
|
|
65
|
+
* @param key - 当前的引用名。
|
|
66
|
+
* @returns 一个或两个候选名字;不是本插件派生的名字时只有一个(原样)。
|
|
67
|
+
*/
|
|
68
|
+
export declare function credentialRefCandidates(key: string): readonly string[];
|
|
69
|
+
/**
|
|
70
|
+
* Derive the credential-reference name that holds one connection's password.
|
|
71
|
+
*
|
|
72
|
+
* The platform names stored values with a POSIX identifier, so a user-chosen name — `腾讯云` included —
|
|
73
|
+
* cannot be used verbatim. The derivation keeps whatever ASCII is readable and then appends a 64-bit digest
|
|
74
|
+
* of the **exact** id: readable enough to find in the credentials file, and collision-free between two names
|
|
75
|
+
* that sanitise to the same slug (`a b` and `a_b`). It is a pure function of the id, so re-adding the same
|
|
76
|
+
* name reuses one stored secret instead of accumulating orphans.
|
|
77
|
+
*
|
|
78
|
+
* @param connectionId - the user's connection name.
|
|
79
|
+
* @returns a name satisfying {@link isValidCredentialRef}.
|
|
80
|
+
*/
|
|
81
|
+
export declare function credentialRefFor(connectionId: string): string;
|
|
82
|
+
/**
|
|
83
|
+
* Validate untrusted input into a connection profile.
|
|
84
|
+
*
|
|
85
|
+
* Misconfiguration fails loudly at this boundary, so the rest of the plugin can treat a
|
|
86
|
+
* `ConnectionProfile` as trustworthy.
|
|
87
|
+
*
|
|
88
|
+
* @param input - candidate value (settings JSON, import draft or test fixture).
|
|
89
|
+
* @returns the validated profile.
|
|
90
|
+
* @throws SshDevError with code `CONNECTION_INVALID`.
|
|
91
|
+
*/
|
|
92
|
+
export declare function validateConnectionProfile(input: unknown): ConnectionProfile;
|
|
93
|
+
/**
|
|
94
|
+
* Turn one importable `~/.ssh/config` alias into a profile.
|
|
95
|
+
*
|
|
96
|
+
* The alias transport inherits user/port/identity/proxy settings from the alias itself, so
|
|
97
|
+
* the draft's explicit values are kept only for display and for the ssh2 fallback.
|
|
98
|
+
*
|
|
99
|
+
* @param id - connection id assigned by the caller (keeps this function deterministic).
|
|
100
|
+
* @param draft - one draft from {@link importableEntries}.
|
|
101
|
+
* @returns the validated profile.
|
|
102
|
+
*/
|
|
103
|
+
export declare function profileFromImportDraft(id: string, draft: SshConfigImportDraft): ConnectionProfile;
|
|
104
|
+
/** One-line summary used by diagnostics and the settings list. */
|
|
105
|
+
export declare function connectionSummary(profile: ConnectionProfile): string;
|
|
106
|
+
//# sourceMappingURL=connection.d.ts.map
|