@tiphareth/dsh-hardssh 0.1.2 → 0.2.2
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 +30 -0
- package/README.md +49 -25
- package/SKILLS.md +140 -0
- package/lib/base/index.js +95 -0
- package/lib/client.js +1171 -1321
- package/lib/fs.js +307 -416
- package/lib/index.js +5428 -2991
- package/lib/ledger-B-LXlftp.js +43 -0
- package/lib/ledger-D2ezq1iW.js +376 -0
- package/lib/model-Cp7f70Mb.js +5 -0
- package/lib/registry-CViuzKYI.js +421 -0
- package/lib/subprocess.js +92 -549
- package/lib/types/backend.d.ts +73 -78
- package/lib/types/base/capability.d.ts +4 -0
- package/lib/types/base/ledger-router.d.ts +42 -26
- package/lib/types/base/ledger.d.ts +30 -19
- package/lib/types/base/model.d.ts +17 -8
- package/lib/types/base/plugin.d.ts +11 -0
- package/lib/types/base/registry.d.ts +28 -31
- package/lib/types/base/router.d.ts +0 -7
- package/lib/types/client/api.d.ts +8 -8
- package/lib/types/client/connect-host.d.ts +13 -0
- package/lib/types/client/index.d.ts +6 -4
- package/lib/types/client/locales.d.ts +15 -1
- package/lib/types/client/session-connect-gate.d.ts +71 -0
- package/lib/types/client/ssh/api.d.ts +11 -9
- package/lib/types/client/ssh/apply.d.ts +14 -8
- package/lib/types/client/ssh/locales.d.ts +8 -1
- package/lib/types/client/ssh/ops-tab.d.ts +34 -0
- package/lib/types/client/ssh/panel/ClusterTab.d.ts +3 -1
- package/lib/types/client/ssh/panel/ConnectionErrorDialog.d.ts +8 -0
- package/lib/types/client/ssh/panel/SessionSecretDialog.d.ts +4 -1
- package/lib/types/client/ssh/panel/SshPanel.d.ts +8 -8
- package/lib/types/client/ssh/panel/TerminalTab.d.ts +3 -5
- package/lib/types/client/ssh/panel/TransferTab.d.ts +5 -1
- package/lib/types/client/ssh/panel/TunnelsTab.d.ts +3 -1
- package/lib/types/client/ssh/session-target.d.ts +17 -0
- package/lib/types/client/state.d.ts +4 -1
- package/lib/types/client/workspace-panel-entry.d.ts +30 -0
- package/lib/types/client/workspace-panel.d.ts +24 -0
- package/lib/types/client-http.d.ts +77 -6
- package/lib/types/core.d.ts +5 -16
- package/lib/types/fs.d.ts +18 -25
- package/lib/types/index.d.ts +138 -9
- package/lib/types/ledger.d.ts +16 -111
- package/lib/types/protocol.d.ts +0 -5
- package/lib/types/providers/index.d.ts +6 -3
- package/lib/types/providers/local/provider.d.ts +83 -23
- package/lib/types/providers/ssh/provider.d.ts +24 -57
- package/lib/types/remote/environment.d.ts +2 -0
- package/lib/types/remote/remote-fs.d.ts +12 -1
- package/lib/types/remote/remote-process.d.ts +22 -1
- package/lib/types/remote/remote-subprocess.d.ts +13 -1
- package/lib/types/remote/remote-terminal.d.ts +4 -0
- package/lib/types/remote-search.d.ts +3 -3
- package/lib/types/routes.d.ts +36 -8
- package/lib/types/runtime/dsh-capabilities.d.ts +18 -0
- package/lib/types/runtime/workspace-core.d.ts +73 -38
- package/lib/types/runtime/workspace-migration.d.ts +113 -0
- package/lib/types/ssh/connection/lease.d.ts +23 -0
- package/lib/types/ssh/connection/manager.d.ts +359 -0
- package/lib/types/ssh/connection/pool.d.ts +8 -1
- package/lib/types/ssh/engine.d.ts +97 -267
- package/lib/types/ssh/known-hosts.d.ts +22 -5
- package/lib/types/ssh/local-transfer-policy.d.ts +20 -0
- package/lib/types/ssh/plugin.d.ts +10 -15
- package/lib/types/ssh/protocol.d.ts +18 -0
- package/lib/types/ssh/routes.d.ts +15 -7
- package/lib/types/ssh/sftp/service.d.ts +216 -0
- package/lib/types/ssh/store.d.ts +20 -8
- package/lib/types/ssh/terminal/service.d.ts +49 -0
- package/lib/types/ssh/tunnel/service.d.ts +26 -0
- package/lib/types/ssh/vault.d.ts +51 -4
- package/lib/types/subprocess.d.ts +13 -21
- package/lib/types/switch/switch-fs.d.ts +52 -2
- package/lib/types/switch/switch-subprocess.d.ts +32 -3
- package/lib/types/tools.d.ts +7 -6
- package/lib/types/workspace-tool-ops.d.ts +33 -0
- package/lib/types/workspace.d.ts +18 -0
- package/lib/vault-3gpWct2Q.js +559 -0
- package/lib/workspace.js +2 -0
- package/package.json +58 -25
- package/scripts/export-legacy-workspaces.mjs +136 -0
- package/src/backend.ts +155 -483
- package/src/base/capability.ts +4 -0
- package/src/base/ledger-router.ts +214 -65
- package/src/base/ledger.ts +175 -48
- package/src/base/model.ts +18 -8
- package/src/base/plugin.ts +15 -4
- package/src/base/registry.ts +49 -41
- package/src/base/router.ts +0 -8
- package/src/client/api.ts +8 -36
- package/src/client/connect-host.ts +199 -0
- package/src/client/directory-flow.tsx +91 -36
- package/src/client/index.ts +56 -53
- package/src/client/locales.ts +14 -0
- package/src/client/session-connect-gate.ts +163 -0
- package/src/client/ssh/api.ts +245 -108
- package/src/client/ssh/apply.ts +23 -24
- package/src/client/ssh/locales.ts +16 -2
- package/src/client/ssh/ops-tab.tsx +79 -0
- package/src/client/ssh/panel/ClusterTab.tsx +9 -20
- package/src/client/ssh/panel/ConnectionErrorDialog.tsx +33 -0
- package/src/client/ssh/panel/SessionSecretDialog.tsx +5 -1
- package/src/client/ssh/panel/SshPanel.tsx +81 -77
- package/src/client/ssh/panel/TerminalTab.tsx +18 -37
- package/src/client/ssh/panel/TransferTab.tsx +43 -33
- package/src/client/ssh/panel/TunnelsTab.tsx +9 -26
- package/src/client/ssh/panel/panel.module.css +90 -127
- package/src/client/ssh/session-target.ts +69 -0
- package/src/client/state.ts +9 -1
- package/src/client/workspace-badges.ts +99 -96
- package/src/client/workspace-panel-entry.tsx +91 -0
- package/src/client/workspace-panel.tsx +227 -0
- package/src/client/workspace.module.css +76 -2
- package/src/client-http.ts +118 -18
- package/src/core.ts +37 -47
- package/src/fs.ts +137 -85
- package/src/index.ts +535 -244
- package/src/ledger.ts +62 -416
- package/src/protocol.ts +0 -6
- package/src/providers/index.ts +8 -4
- package/src/providers/local/provider.ts +259 -87
- package/src/providers/ssh/provider.ts +158 -155
- package/src/remote/environment.ts +22 -1
- package/src/remote/remote-fs.ts +123 -34
- package/src/remote/remote-process.ts +265 -203
- package/src/remote/remote-subprocess.ts +61 -18
- package/src/remote/remote-terminal.ts +106 -21
- package/src/remote-search.ts +122 -26
- package/src/routes.ts +416 -395
- package/src/runtime/dsh-capabilities.ts +19 -0
- package/src/runtime/workspace-core.ts +171 -88
- package/src/runtime/workspace-migration.ts +472 -0
- package/src/ssh/connection/lease.ts +35 -0
- package/src/ssh/connection/manager.ts +1083 -0
- package/src/ssh/connection/pool.ts +341 -275
- package/src/ssh/engine.ts +269 -1477
- package/src/ssh/known-hosts.ts +42 -18
- package/src/ssh/local-transfer-policy.ts +83 -0
- package/src/ssh/plugin.ts +20 -19
- package/src/ssh/protocol.ts +22 -1
- package/src/ssh/routes.ts +419 -187
- package/src/ssh/sftp/service.ts +967 -0
- package/src/ssh/store.ts +169 -72
- package/src/ssh/terminal/service.ts +177 -0
- package/src/ssh/tools.ts +42 -20
- package/src/ssh/tunnel/service.ts +217 -0
- package/src/ssh/vault.ts +245 -91
- package/src/subprocess.ts +88 -71
- package/src/switch/switch-fs.ts +141 -10
- package/src/switch/switch-subprocess.ts +80 -6
- package/src/tools.ts +217 -221
- package/src/workspace-tool-ops.ts +101 -0
- package/src/workspace.ts +52 -0
- package/lib/environment-BL1jddfB.js +0 -449
- package/lib/switch-fs-CAJpFY9C.js +0 -193
- package/lib/switch-fs-RrZtG2gv.js +0 -210
- package/lib/types/client/manager-button.d.ts +0 -32
- package/lib/types/client/ssh/mount.d.ts +0 -13
- package/lib/types/client/ssh/panel/HostsTab.d.ts +0 -10
- package/lib/types/client/ssh/panel/controller.d.ts +0 -23
- package/lib/types/client/ssh/sidebar-entry.d.ts +0 -25
- package/lib/types/client/workspace-gate.d.ts +0 -15
- package/lib/types/remote-runner.d.ts +0 -83
- package/lib/types/seam-state.d.ts +0 -69
- package/src/client/manager-button.tsx +0 -269
- package/src/client/ssh/mount.tsx +0 -83
- package/src/client/ssh/panel/HostsTab.tsx +0 -236
- package/src/client/ssh/panel/controller.ts +0 -48
- package/src/client/ssh/sidebar-entry.ts +0 -123
- package/src/client/workspace-gate.ts +0 -225
- package/src/remote-runner.ts +0 -201
- package/src/seam-state.ts +0 -185
package/package.json
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiphareth/dsh-hardssh",
|
|
3
|
-
"description": "Generic workspace base for the dsh harness (local + remote workspace providers with per-workspace routing), with SSH as one concrete provider:
|
|
4
|
-
"version": "0.
|
|
3
|
+
"description": "Generic workspace base for the dsh harness (local + remote workspace providers with per-workspace routing), with SSH as one concrete provider: bind a server directory as an SSH workspace so the session's file I/O and commands run on that host, manage hosts and workspaces in the GUI, and operate remote servers through the ssh_* / remote_* agent tools",
|
|
4
|
+
"version": "0.2.2",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/tiphareth0/dsh-hardssh.git",
|
|
9
|
+
"directory": "packages/dsh-hardssh"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/tiphareth0/dsh-hardssh",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/tiphareth0/dsh-hardssh/issues"
|
|
14
|
+
},
|
|
6
15
|
"keywords": [
|
|
7
16
|
"dsh",
|
|
8
17
|
"deepseek-harness",
|
|
@@ -17,6 +26,9 @@
|
|
|
17
26
|
},
|
|
18
27
|
"main": "lib/index.js",
|
|
19
28
|
"types": "lib/types/index.d.ts",
|
|
29
|
+
"bin": {
|
|
30
|
+
"dsh-hardssh-export-legacy": "./scripts/export-legacy-workspaces.mjs"
|
|
31
|
+
},
|
|
20
32
|
"exports": {
|
|
21
33
|
".": {
|
|
22
34
|
"types": "./lib/types/index.d.ts",
|
|
@@ -30,6 +42,14 @@
|
|
|
30
42
|
"types": "./lib/types/subprocess.d.ts",
|
|
31
43
|
"default": "./lib/subprocess.js"
|
|
32
44
|
},
|
|
45
|
+
"./workspace": {
|
|
46
|
+
"types": "./lib/types/workspace.d.ts",
|
|
47
|
+
"default": "./lib/workspace.js"
|
|
48
|
+
},
|
|
49
|
+
"./base": {
|
|
50
|
+
"types": "./lib/types/base/index.d.ts",
|
|
51
|
+
"default": "./lib/base/index.js"
|
|
52
|
+
},
|
|
33
53
|
"./client": {
|
|
34
54
|
"types": "./lib/types/client/index.d.ts",
|
|
35
55
|
"default": "./lib/client.js"
|
|
@@ -45,17 +65,22 @@
|
|
|
45
65
|
"inject": [
|
|
46
66
|
"@deepseek-ai/dsh-client-runtime",
|
|
47
67
|
"@deepseek-ai/dsh-client-connection",
|
|
68
|
+
"@deepseek-ai/dsh-client-ui-sidebar-right",
|
|
48
69
|
"@deepseek-ai/dsh-client-ui-settings"
|
|
49
70
|
],
|
|
50
71
|
"platform": "web"
|
|
51
72
|
}
|
|
52
73
|
},
|
|
53
74
|
"scripts": {
|
|
54
|
-
"
|
|
75
|
+
"clean": "node ./scripts/clean-build.mjs",
|
|
76
|
+
"build": "pnpm run clean && tsc -b && tsdown",
|
|
55
77
|
"bundle": "tsdown",
|
|
56
78
|
"watch": "tsdown --watch",
|
|
57
79
|
"typecheck": "tsc -b --pretty false",
|
|
58
|
-
"test": "vitest run",
|
|
80
|
+
"test": "vitest run --exclude tests/ssh/vault.test.ts",
|
|
81
|
+
"test:all": "vitest run",
|
|
82
|
+
"test:vault": "vitest run tests/ssh/vault.test.ts",
|
|
83
|
+
"workspace:export-legacy": "node ./scripts/export-legacy-workspaces.mjs",
|
|
59
84
|
"prepare": "tsdown"
|
|
60
85
|
},
|
|
61
86
|
"dependencies": {
|
|
@@ -66,22 +91,25 @@
|
|
|
66
91
|
},
|
|
67
92
|
"peerDependencies": {
|
|
68
93
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
69
|
-
"@deepseek-ai/dsh-client-connection": "
|
|
70
|
-
"@deepseek-ai/dsh-client-locale": "
|
|
71
|
-
"@deepseek-ai/dsh-client-runtime": "
|
|
72
|
-
"@deepseek-ai/dsh-client-ui-settings": "
|
|
73
|
-
"@deepseek-ai/dsh-client-ui-sidebar": "
|
|
74
|
-
"@deepseek-ai/dsh-client-ui-
|
|
75
|
-
"@deepseek-ai/dsh-
|
|
76
|
-
"@deepseek-ai/dsh-
|
|
77
|
-
"@deepseek-ai/dsh-
|
|
94
|
+
"@deepseek-ai/dsh-client-connection": "*",
|
|
95
|
+
"@deepseek-ai/dsh-client-locale": "*",
|
|
96
|
+
"@deepseek-ai/dsh-client-runtime": "*",
|
|
97
|
+
"@deepseek-ai/dsh-client-ui-settings": "*",
|
|
98
|
+
"@deepseek-ai/dsh-client-ui-sidebar": "*",
|
|
99
|
+
"@deepseek-ai/dsh-client-ui-sidebar-right": "*",
|
|
100
|
+
"@deepseek-ai/dsh-client-ui-slots": "*",
|
|
101
|
+
"@deepseek-ai/dsh-client-ui-layout": "*",
|
|
102
|
+
"@deepseek-ai/dsh-fs": "*",
|
|
103
|
+
"@deepseek-ai/dsh-fs-local": "*",
|
|
104
|
+
"@deepseek-ai/dsh-fs-sandbox": "*",
|
|
105
|
+
"@deepseek-ai/dsh-host-webserver": "*",
|
|
78
106
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
79
|
-
"@deepseek-ai/dsh-settings": "
|
|
80
|
-
"@deepseek-ai/dsh-subprocess": "
|
|
81
|
-
"@deepseek-ai/dsh-subprocess-local": "
|
|
82
|
-
"@deepseek-ai/dsh-system-prompt": "
|
|
83
|
-
"@deepseek-ai/dsh-timeout": "
|
|
84
|
-
"@deepseek-ai/dsh-tools": "
|
|
107
|
+
"@deepseek-ai/dsh-settings": "*",
|
|
108
|
+
"@deepseek-ai/dsh-subprocess": "*",
|
|
109
|
+
"@deepseek-ai/dsh-subprocess-local": "*",
|
|
110
|
+
"@deepseek-ai/dsh-system-prompt": "*",
|
|
111
|
+
"@deepseek-ai/dsh-timeout": "*",
|
|
112
|
+
"@deepseek-ai/dsh-tools": "*",
|
|
85
113
|
"react": "^18.2.0",
|
|
86
114
|
"react-dom": "^18.2.0"
|
|
87
115
|
},
|
|
@@ -90,11 +118,13 @@
|
|
|
90
118
|
"@deepseek-ai/dsh-client-connection": "0.1.2-alpha.3",
|
|
91
119
|
"@deepseek-ai/dsh-client-locale": "0.1.2-alpha.3",
|
|
92
120
|
"@deepseek-ai/dsh-client-runtime": "0.1.1-rc.2",
|
|
93
|
-
"@deepseek-ai/dsh-client-ui-conversation": "0.1.
|
|
94
|
-
"@deepseek-ai/dsh-client-ui-
|
|
95
|
-
"@deepseek-ai/dsh-client-ui-
|
|
96
|
-
"@deepseek-ai/dsh-client-ui-
|
|
97
|
-
"@deepseek-ai/dsh-client-ui-
|
|
121
|
+
"@deepseek-ai/dsh-client-ui-conversation": "0.1.5-rc.1",
|
|
122
|
+
"@deepseek-ai/dsh-client-ui-layout": "0.1.5-rc.1",
|
|
123
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.1.5-rc.1",
|
|
124
|
+
"@deepseek-ai/dsh-client-ui-sidebar": "0.1.5-rc.1",
|
|
125
|
+
"@deepseek-ai/dsh-client-ui-sidebar-right": "0.1.5-rc.1",
|
|
126
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.1.5-rc.1",
|
|
127
|
+
"@deepseek-ai/dsh-client-ui-workspace": "0.1.5-rc.1",
|
|
98
128
|
"@deepseek-ai/dsh-fs": "0.1.2-alpha.3",
|
|
99
129
|
"@deepseek-ai/dsh-fs-local": "0.1.2-alpha.3",
|
|
100
130
|
"@deepseek-ai/dsh-fs-sandbox": "0.1.2-alpha.3",
|
|
@@ -125,10 +155,13 @@
|
|
|
125
155
|
},
|
|
126
156
|
"files": [
|
|
127
157
|
"lib/*.js",
|
|
158
|
+
"lib/base/*.js",
|
|
128
159
|
"lib/types/**/*.d.ts",
|
|
129
160
|
"src",
|
|
161
|
+
"scripts/export-legacy-workspaces.mjs",
|
|
130
162
|
"cordis.patch.yml",
|
|
131
|
-
"README.md"
|
|
163
|
+
"README.md",
|
|
164
|
+
"SKILLS.md"
|
|
132
165
|
],
|
|
133
166
|
"license": "BSD-3-Clause"
|
|
134
167
|
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Offline emergency export: convert the authoritative generic workspace ledger
|
|
5
|
+
* back to the frozen legacy SSH ledger shape. This command never changes
|
|
6
|
+
* anchors and defaults to preview-only; pass --apply to write the legacy file.
|
|
7
|
+
*/
|
|
8
|
+
import { constants } from 'node:fs'
|
|
9
|
+
import { access, mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises'
|
|
10
|
+
import { homedir } from 'node:os'
|
|
11
|
+
import { dirname, join } from 'node:path'
|
|
12
|
+
import { pathToFileURL } from 'node:url'
|
|
13
|
+
import { randomUUID } from 'node:crypto'
|
|
14
|
+
|
|
15
|
+
function parseArgs(argv) {
|
|
16
|
+
const result = {
|
|
17
|
+
genericPath: join(homedir(), '.dsh', 'workspaces', 'index.v1.json'),
|
|
18
|
+
legacyPath: join(homedir(), '.dsh', 'dsh-hardssh-workspaces.json'),
|
|
19
|
+
apply: false,
|
|
20
|
+
}
|
|
21
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
22
|
+
const arg = argv[index]
|
|
23
|
+
if (arg === '--apply') {
|
|
24
|
+
result.apply = true
|
|
25
|
+
} else if (arg === '--generic' || arg === '--legacy') {
|
|
26
|
+
const value = argv[index + 1]
|
|
27
|
+
if (value === undefined || value === '') throw new Error(`${arg} requires a path`)
|
|
28
|
+
if (arg === '--generic') result.genericPath = value
|
|
29
|
+
else result.legacyPath = value
|
|
30
|
+
index += 1
|
|
31
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
32
|
+
process.stdout.write('Usage: export-legacy-workspaces [--generic PATH] [--legacy PATH] [--apply]\n')
|
|
33
|
+
process.exit(0)
|
|
34
|
+
} else {
|
|
35
|
+
throw new Error(`unknown argument '${arg}'`)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return result
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function requiredString(value, label) {
|
|
42
|
+
if (typeof value !== 'string' || value === '') throw new Error(`invalid generic SSH workspace: ${label} is required`)
|
|
43
|
+
return value
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function toLegacyRecord(value) {
|
|
47
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
48
|
+
throw new Error('generic workspace entries must be objects')
|
|
49
|
+
}
|
|
50
|
+
const provider = value.provider
|
|
51
|
+
const location = value.location
|
|
52
|
+
const anchor = value.anchor
|
|
53
|
+
if (typeof provider !== 'object' || provider === null || Array.isArray(provider)) throw new Error('generic workspace provider is invalid')
|
|
54
|
+
if (provider.id !== 'ssh') return undefined
|
|
55
|
+
if (typeof provider.connectionRef !== 'object' || provider.connectionRef === null || Array.isArray(provider.connectionRef)) {
|
|
56
|
+
throw new Error(`generic SSH workspace '${String(value.id)}' has no connectionRef`)
|
|
57
|
+
}
|
|
58
|
+
if (typeof location !== 'object' || location === null || Array.isArray(location)) throw new Error(`generic SSH workspace '${String(value.id)}' has no location`)
|
|
59
|
+
if (typeof anchor !== 'object' || anchor === null || Array.isArray(anchor)) throw new Error(`generic SSH workspace '${String(value.id)}' has no anchor`)
|
|
60
|
+
return {
|
|
61
|
+
id: requiredString(value.id, 'id'),
|
|
62
|
+
title: requiredString(value.title, 'title'),
|
|
63
|
+
alias: requiredString(provider.connectionRef.id, 'provider.connectionRef.id'),
|
|
64
|
+
remoteRoot: requiredString(location.root, 'location.root'),
|
|
65
|
+
anchorPath: requiredString(anchor.path, 'anchor.path'),
|
|
66
|
+
createdAt: requiredString(value.createdAt, 'createdAt'),
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function exists(path) {
|
|
71
|
+
try {
|
|
72
|
+
await access(path, constants.F_OK)
|
|
73
|
+
return true
|
|
74
|
+
} catch {
|
|
75
|
+
return false
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function atomicExport(path, bytes) {
|
|
80
|
+
await mkdir(dirname(path), { recursive: true })
|
|
81
|
+
const tempPath = `${path}.tmp-${process.pid}-${randomUUID()}`
|
|
82
|
+
const backupPath = await exists(path)
|
|
83
|
+
? `${path}.backup-${new Date().toISOString().replace(/[:.]/g, '-')}-${randomUUID()}`
|
|
84
|
+
: undefined
|
|
85
|
+
await writeFile(tempPath, bytes, { encoding: 'utf8', mode: 0o600 })
|
|
86
|
+
try {
|
|
87
|
+
if (backupPath !== undefined) await rename(path, backupPath)
|
|
88
|
+
try {
|
|
89
|
+
await rename(tempPath, path)
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (backupPath !== undefined) await rename(backupPath, path).catch(() => undefined)
|
|
92
|
+
throw error
|
|
93
|
+
}
|
|
94
|
+
} finally {
|
|
95
|
+
await rm(tempPath, { force: true }).catch(() => undefined)
|
|
96
|
+
}
|
|
97
|
+
return backupPath
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function exportLegacyWorkspaces(options) {
|
|
101
|
+
const raw = JSON.parse(await readFile(options.genericPath, 'utf8'))
|
|
102
|
+
if (!Array.isArray(raw)) throw new Error('generic workspace ledger must be a JSON array')
|
|
103
|
+
const records = []
|
|
104
|
+
const ids = new Set()
|
|
105
|
+
const anchors = new Set()
|
|
106
|
+
for (const value of raw) {
|
|
107
|
+
const mapped = toLegacyRecord(value)
|
|
108
|
+
if (mapped === undefined) continue
|
|
109
|
+
if (ids.has(mapped.id)) throw new Error(`duplicate SSH workspace id '${mapped.id}'`)
|
|
110
|
+
if (anchors.has(mapped.anchorPath)) throw new Error(`duplicate SSH workspace anchor '${mapped.anchorPath}'`)
|
|
111
|
+
ids.add(mapped.id)
|
|
112
|
+
anchors.add(mapped.anchorPath)
|
|
113
|
+
records.push(mapped)
|
|
114
|
+
}
|
|
115
|
+
const bytes = `${JSON.stringify(records, null, 2)}\n`
|
|
116
|
+
const backupPath = options.apply ? await atomicExport(options.legacyPath, bytes) : undefined
|
|
117
|
+
return {
|
|
118
|
+
mode: options.apply ? 'applied' : 'preview',
|
|
119
|
+
genericPath: options.genericPath,
|
|
120
|
+
legacyPath: options.legacyPath,
|
|
121
|
+
recordCount: records.length,
|
|
122
|
+
ids: records.map(record => record.id),
|
|
123
|
+
...(backupPath === undefined ? {} : { backupPath }),
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const isMain = process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href
|
|
128
|
+
if (isMain) {
|
|
129
|
+
exportLegacyWorkspaces(parseArgs(process.argv.slice(2))).then(
|
|
130
|
+
result => { process.stdout.write(`${JSON.stringify(result)}\n`) },
|
|
131
|
+
error => {
|
|
132
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`)
|
|
133
|
+
process.exitCode = 1
|
|
134
|
+
},
|
|
135
|
+
)
|
|
136
|
+
}
|