dsh-git-worktree 0.9.2 → 0.9.3
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/CHANGELOG.md +405 -386
- package/LICENSE +21 -21
- package/NOTICE +16 -14
- package/README.en.md +169 -169
- package/README.md +169 -169
- package/cordis.patch.yml +11 -11
- package/docs/i18n.md +63 -63
- package/lib/adapters/initial-commit.js +3 -3
- package/lib/client/actions.d.ts +21 -3
- package/lib/client/actions.js +23 -3
- package/lib/client/actions.js.map +1 -1
- package/lib/client/console-remote/index.js +36 -8
- package/lib/client/console-remote/index.js.map +1 -1
- package/lib/client/index.js +1 -0
- package/lib/client/index.js.map +1 -1
- package/lib/client/pre-session/controller.d.ts +1 -1
- package/lib/client/pre-session/controller.js +33 -18
- package/lib/client/pre-session/controller.js.map +1 -1
- package/lib/client/review-console/recovery-continuation.d.ts +1 -1
- package/lib/client/review-console/recovery-continuation.js +9 -4
- package/lib/client/review-console/recovery-continuation.js.map +1 -1
- package/lib/client/review-console/review-console.styles.js +190 -190
- package/lib/client/styles.js +146 -140
- package/lib/client/styles.js.map +1 -1
- package/lib/client/target-console/target-console.styles.js +155 -155
- package/lib/client/workspace-sidebar/index.d.ts +11 -2
- package/lib/client/workspace-sidebar/index.js +50 -8
- package/lib/client/workspace-sidebar/index.js.map +1 -1
- package/lib/client.js +7624 -161
- package/lib/console-contract.d.ts +2 -0
- package/lib/console-contract.js.map +1 -1
- package/lib/console-host/service.js +4 -2
- package/lib/console-host/service.js.map +1 -1
- package/lib/console-remote/descriptors.js +8 -3
- package/lib/console-remote/descriptors.js.map +1 -1
- package/lib/console-remote/schemas.js +1 -0
- package/lib/console-remote/schemas.js.map +1 -1
- package/lib/host-compat.d.ts +4 -0
- package/lib/host-compat.js +23 -0
- package/lib/host-compat.js.map +1 -0
- package/lib/i18n/client-messages.d.ts +4 -0
- package/lib/i18n/client-messages.js +4 -0
- package/lib/i18n/client-messages.js.map +1 -1
- package/lib/workspace-provider-lifecycle.d.ts +2 -1
- package/lib/workspace-provider-lifecycle.js +6 -1
- package/lib/workspace-provider-lifecycle.js.map +1 -1
- package/package.json +151 -149
- package/scripts/check-publish.mjs +238 -238
- package/scripts/check-workspace-sidebar-version.mjs +18 -14
- package/scripts/clean-lib.mjs +7 -7
- package/scripts/dev-dsh-lib.mjs +547 -509
- package/scripts/dev-dsh.mjs +98 -95
- package/scripts/dsh-source-runner.mjs +20 -14
- package/scripts/workspace-sidebar-upstream-alpha.mjs +102 -0
- package/scripts/workspace-sidebar-upstream-next.mjs +134 -0
- package/scripts/workspace-sidebar-upstream.d.mts +8 -8
- package/scripts/workspace-sidebar-upstream.mjs +162 -162
package/scripts/dev-dsh-lib.mjs
CHANGED
|
@@ -1,509 +1,547 @@
|
|
|
1
|
-
import { execFileSync, spawnSync } from 'node:child_process'
|
|
2
|
-
import {
|
|
3
|
-
copyFileSync,
|
|
4
|
-
existsSync,
|
|
5
|
-
lstatSync,
|
|
6
|
-
mkdirSync,
|
|
7
|
-
readFileSync,
|
|
8
|
-
readdirSync,
|
|
9
|
-
realpathSync,
|
|
10
|
-
rmSync,
|
|
11
|
-
statSync,
|
|
12
|
-
writeFileSync,
|
|
13
|
-
} from 'node:fs'
|
|
14
|
-
import { homedir } from 'node:os'
|
|
15
|
-
import { dirname, join, resolve } from 'node:path'
|
|
16
|
-
|
|
17
|
-
const FIXTURE_MARKER = '.dsh-git-worktree-fixture.json'
|
|
18
|
-
const FIXTURE_IDENTITY = Object.freeze({
|
|
19
|
-
owner: 'dsh-git-worktree',
|
|
20
|
-
schemaVersion: 1,
|
|
21
|
-
})
|
|
22
|
-
const MODES = new Set(['run', 'install', 'remove', 'smoke'])
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
const
|
|
340
|
-
if (
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
function
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
if (
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
}
|
|
1
|
+
import { execFileSync, spawnSync } from 'node:child_process'
|
|
2
|
+
import {
|
|
3
|
+
copyFileSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
lstatSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
readdirSync,
|
|
9
|
+
realpathSync,
|
|
10
|
+
rmSync,
|
|
11
|
+
statSync,
|
|
12
|
+
writeFileSync,
|
|
13
|
+
} from 'node:fs'
|
|
14
|
+
import { homedir, tmpdir } from 'node:os'
|
|
15
|
+
import { dirname, join, resolve } from 'node:path'
|
|
16
|
+
|
|
17
|
+
const FIXTURE_MARKER = '.dsh-git-worktree-fixture.json'
|
|
18
|
+
const FIXTURE_IDENTITY = Object.freeze({
|
|
19
|
+
owner: 'dsh-git-worktree',
|
|
20
|
+
schemaVersion: 1,
|
|
21
|
+
})
|
|
22
|
+
const MODES = new Set(['run', 'install', 'remove', 'smoke'])
|
|
23
|
+
|
|
24
|
+
/** Isolated Profile runs must not prune archives belonging to other DSH homes. */
|
|
25
|
+
export function devCacheRoot(environment = process.env) {
|
|
26
|
+
return environment.DSH_HOME
|
|
27
|
+
? join(resolve(environment.DSH_HOME), 'dev-dsh-cache')
|
|
28
|
+
: join(tmpdir(), 'dsh-git-worktree-dev')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function createDefaultOptions(projectRoot, cacheRoot, isolated = false) {
|
|
32
|
+
return {
|
|
33
|
+
projectRoot: resolve(projectRoot),
|
|
34
|
+
cacheRoot: resolve(cacheRoot),
|
|
35
|
+
profile: 'web',
|
|
36
|
+
port: 3081,
|
|
37
|
+
// Match the Host's own first-use Workspace path when DSH_HOME is isolated.
|
|
38
|
+
repo: isolated
|
|
39
|
+
? resolve(cacheRoot, 'Documents', 'deepseek-harness', 'default-workspace')
|
|
40
|
+
: resolve(cacheRoot, 'fixture'),
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function parseDevDshArgs(argv, defaults) {
|
|
45
|
+
const tokens = [...argv]
|
|
46
|
+
const candidateMode = tokens[0] && !tokens[0].startsWith('-') ? tokens.shift() : 'run'
|
|
47
|
+
if (!MODES.has(candidateMode)) {
|
|
48
|
+
throw new Error(`Unknown mode ${candidateMode}. Expected run, install, remove, or smoke.`)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let profile = defaults.profile
|
|
52
|
+
let port = defaults.port
|
|
53
|
+
let repo = defaults.repo
|
|
54
|
+
let repoExplicit = false
|
|
55
|
+
let harnessRoot = defaults.harnessRoot
|
|
56
|
+
|
|
57
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
58
|
+
const token = tokens[index]
|
|
59
|
+
if (token === '--') continue
|
|
60
|
+
if (token === '--profile' || token === '--repo' || token === '--port' || token === '--harness') {
|
|
61
|
+
const value = tokens[index + 1]
|
|
62
|
+
if (!value || value.startsWith('--')) throw new Error(`${token} requires a value.`)
|
|
63
|
+
index += 1
|
|
64
|
+
if (token === '--profile') profile = value
|
|
65
|
+
if (token === '--repo') {
|
|
66
|
+
repo = resolve(defaults.projectRoot, value)
|
|
67
|
+
repoExplicit = true
|
|
68
|
+
}
|
|
69
|
+
if (token === '--port') port = parsePort(value)
|
|
70
|
+
if (token === '--harness') harnessRoot = resolve(defaults.projectRoot, value)
|
|
71
|
+
continue
|
|
72
|
+
}
|
|
73
|
+
if (token?.startsWith('--profile=')) {
|
|
74
|
+
profile = token.slice('--profile='.length)
|
|
75
|
+
continue
|
|
76
|
+
}
|
|
77
|
+
if (token?.startsWith('--repo=')) {
|
|
78
|
+
repo = resolve(defaults.projectRoot, token.slice('--repo='.length))
|
|
79
|
+
repoExplicit = true
|
|
80
|
+
continue
|
|
81
|
+
}
|
|
82
|
+
if (token?.startsWith('--port=')) {
|
|
83
|
+
port = parsePort(token.slice('--port='.length))
|
|
84
|
+
continue
|
|
85
|
+
}
|
|
86
|
+
if (token?.startsWith('--harness=')) {
|
|
87
|
+
harnessRoot = resolve(defaults.projectRoot, token.slice('--harness='.length))
|
|
88
|
+
continue
|
|
89
|
+
}
|
|
90
|
+
throw new Error(`Unknown option ${token}.`)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(profile)) {
|
|
94
|
+
throw new Error('profile must contain only letters, numbers, dot, underscore, or dash.')
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
mode: candidateMode,
|
|
98
|
+
profile,
|
|
99
|
+
port,
|
|
100
|
+
repo,
|
|
101
|
+
repoExplicit,
|
|
102
|
+
...(harnessRoot ? { harnessRoot } : {}),
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function parsePort(value) {
|
|
107
|
+
const parsed = Number(value)
|
|
108
|
+
if (!Number.isSafeInteger(parsed) || parsed < 1 || parsed > 65_535) {
|
|
109
|
+
throw new Error('port must be an integer between 1 and 65535.')
|
|
110
|
+
}
|
|
111
|
+
return parsed
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function normalizedRealPath(path) {
|
|
115
|
+
const value = realpathSync(path).replaceAll('\\', '/')
|
|
116
|
+
return process.platform === 'win32' ? value.toLowerCase() : value
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function git(cwd, args) {
|
|
120
|
+
return execFileSync('git', args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function validateGitRoot(repoPath) {
|
|
124
|
+
let root
|
|
125
|
+
try {
|
|
126
|
+
root = git(repoPath, ['rev-parse', '--show-toplevel'])
|
|
127
|
+
} catch {
|
|
128
|
+
throw new Error(`Repository ${repoPath} is not a usable Git worktree.`)
|
|
129
|
+
}
|
|
130
|
+
if (normalizedRealPath(root) !== normalizedRealPath(repoPath)) {
|
|
131
|
+
throw new Error(`Repository ${repoPath} is nested inside another Git worktree; pass its actual root instead.`)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Create or validate the disposable repository used by the one-command workflow.
|
|
137
|
+
* Existing explicit repositories are read-only here. The default path requires our marker.
|
|
138
|
+
*/
|
|
139
|
+
export function ensureDevFixture(repoPath, options) {
|
|
140
|
+
const absolute = resolve(repoPath)
|
|
141
|
+
if (existsSync(absolute) && lstatSync(absolute).isSymbolicLink()) {
|
|
142
|
+
throw new Error(`Fixture path ${absolute} is a symbolic link; refusing to manage it.`)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (options.explicit) {
|
|
146
|
+
if (!existsSync(absolute) || !statSync(absolute).isDirectory()) {
|
|
147
|
+
throw new Error(`Explicit repository ${absolute} must already exist.`)
|
|
148
|
+
}
|
|
149
|
+
validateGitRoot(absolute)
|
|
150
|
+
return { path: absolute, created: false, managed: false }
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
let initialize = false
|
|
154
|
+
if (!existsSync(absolute)) {
|
|
155
|
+
mkdirSync(absolute, { recursive: true })
|
|
156
|
+
initialize = true
|
|
157
|
+
} else if (!statSync(absolute).isDirectory()) {
|
|
158
|
+
throw new Error(`Fixture path ${absolute} is not a directory.`)
|
|
159
|
+
} else {
|
|
160
|
+
const entries = readdirSync(absolute)
|
|
161
|
+
if (entries.length === 0) {
|
|
162
|
+
initialize = true
|
|
163
|
+
} else {
|
|
164
|
+
const markerPath = join(absolute, FIXTURE_MARKER)
|
|
165
|
+
if (!existsSync(markerPath)) {
|
|
166
|
+
throw new Error(`Fixture path ${absolute} is not managed by dsh-git-worktree; existing bytes were left untouched.`)
|
|
167
|
+
}
|
|
168
|
+
let marker
|
|
169
|
+
try {
|
|
170
|
+
marker = JSON.parse(readFileSync(markerPath, 'utf8'))
|
|
171
|
+
} catch {
|
|
172
|
+
throw new Error(`Fixture marker at ${markerPath} is invalid; refusing to modify the directory.`)
|
|
173
|
+
}
|
|
174
|
+
if (marker.owner !== FIXTURE_IDENTITY.owner || marker.schemaVersion !== FIXTURE_IDENTITY.schemaVersion) {
|
|
175
|
+
throw new Error(`Fixture marker at ${markerPath} has an unexpected identity.`)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (initialize) {
|
|
181
|
+
writeFileSync(join(absolute, FIXTURE_MARKER), `${JSON.stringify(FIXTURE_IDENTITY, null, 2)}\n`)
|
|
182
|
+
writeFileSync(join(absolute, 'tracked.txt'), 'base\n')
|
|
183
|
+
git(absolute, ['init'])
|
|
184
|
+
git(absolute, ['add', FIXTURE_MARKER, 'tracked.txt'])
|
|
185
|
+
git(absolute, [
|
|
186
|
+
'-c', 'user.name=DSH Local Test',
|
|
187
|
+
'-c', 'user.email=dsh-test@example.local',
|
|
188
|
+
'commit', '-m', 'test: initialize disposable DSH worktree fixture',
|
|
189
|
+
])
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
validateGitRoot(absolute)
|
|
193
|
+
const status = git(absolute, ['status', '--porcelain=v1', '--untracked-files=all'])
|
|
194
|
+
if (status !== '') {
|
|
195
|
+
throw new Error(`Managed fixture ${absolute} is not clean. Review it manually; the workflow will never reset or delete it.`)
|
|
196
|
+
}
|
|
197
|
+
const worktreeCount = git(absolute, ['worktree', 'list', '--porcelain'])
|
|
198
|
+
.split(/\r?\n/)
|
|
199
|
+
.filter((line) => line.startsWith('worktree '))
|
|
200
|
+
.length
|
|
201
|
+
if (worktreeCount !== 1) {
|
|
202
|
+
throw new Error(`Managed fixture ${absolute} still has ${worktreeCount - 1} linked Worktree(s). Finalize or remove them before reusing it.`)
|
|
203
|
+
}
|
|
204
|
+
return { path: absolute, created: initialize, managed: true }
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function isHarnessRoot(path) {
|
|
208
|
+
return existsSync(join(path, 'package.json'))
|
|
209
|
+
&& existsSync(join(path, 'apps', 'cli', 'src', 'bin.ts'))
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function isHarnessSourceReady(path) {
|
|
213
|
+
return isHarnessRoot(path)
|
|
214
|
+
&& existsSync(join(path, 'node_modules', 'tsx', 'package.json'))
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function discoverHarnessRoot(projectRoot, environment = process.env) {
|
|
218
|
+
if (environment.DSH_HARNESS_ROOT) {
|
|
219
|
+
const configured = resolve(environment.DSH_HARNESS_ROOT)
|
|
220
|
+
if (!isHarnessRoot(configured)) {
|
|
221
|
+
throw new Error(`DSH_HARNESS_ROOT does not point to a DeepSeek Harness checkout: ${configured}`)
|
|
222
|
+
}
|
|
223
|
+
return configured
|
|
224
|
+
}
|
|
225
|
+
try {
|
|
226
|
+
const commonDir = git(projectRoot, ['rev-parse', '--path-format=absolute', '--git-common-dir'])
|
|
227
|
+
const localProjectRoot = dirname(resolve(projectRoot, commonDir))
|
|
228
|
+
const parent = dirname(localProjectRoot)
|
|
229
|
+
const candidates = [
|
|
230
|
+
join(parent, 'deepseek-harness'),
|
|
231
|
+
join(dirname(parent), 'deepseek-harness'),
|
|
232
|
+
join(parent, 'DeepSeek', 'deepseek-harness'),
|
|
233
|
+
]
|
|
234
|
+
const validCandidates = candidates.filter(isHarnessRoot)
|
|
235
|
+
return validCandidates.find(isHarnessSourceReady) ?? validCandidates[0]
|
|
236
|
+
} catch {
|
|
237
|
+
return undefined
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function executable(name, platform = process.platform) {
|
|
242
|
+
return platform === 'win32' ? `${name}.cmd` : name
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function createDshInvocation(options) {
|
|
246
|
+
const cwd = options.cwd ?? options.projectRoot
|
|
247
|
+
const args = options.args ?? []
|
|
248
|
+
if (!options.harnessRoot) {
|
|
249
|
+
return {
|
|
250
|
+
command: executable('dsh', options.platform),
|
|
251
|
+
args,
|
|
252
|
+
cwd,
|
|
253
|
+
source: false,
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
const harnessRoot = resolve(options.harnessRoot)
|
|
257
|
+
if (!isHarnessRoot(harnessRoot)) {
|
|
258
|
+
throw new Error(`Harness source checkout is invalid: ${harnessRoot}`)
|
|
259
|
+
}
|
|
260
|
+
if (!isHarnessSourceReady(harnessRoot)) {
|
|
261
|
+
throw new Error(
|
|
262
|
+
`Harness source checkout is not prepared: ${harnessRoot}. Run pnpm --dir "${harnessRoot}" install before dev preview.`,
|
|
263
|
+
)
|
|
264
|
+
}
|
|
265
|
+
const pinnedPnpm = join(harnessRoot, 'node_modules', '.bin', executable('pnpm', options.platform))
|
|
266
|
+
if (!existsSync(pinnedPnpm)) {
|
|
267
|
+
throw new Error(`Harness source checkout is missing its pinned pnpm: ${pinnedPnpm}. Install the checkout's development dependencies before dev preview.`)
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
command: pinnedPnpm,
|
|
271
|
+
args: [
|
|
272
|
+
'--dir', harnessRoot,
|
|
273
|
+
'exec', 'node', '--import', 'tsx/esm',
|
|
274
|
+
join(options.projectRoot, 'scripts', 'dsh-source-runner.mjs'),
|
|
275
|
+
cwd,
|
|
276
|
+
join(harnessRoot, 'apps', 'cli', 'src', 'bin.ts'),
|
|
277
|
+
...args,
|
|
278
|
+
],
|
|
279
|
+
cwd: options.projectRoot,
|
|
280
|
+
source: true,
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function createDshLaunch(options) {
|
|
285
|
+
return createDshInvocation({
|
|
286
|
+
projectRoot: options.projectRoot,
|
|
287
|
+
harnessRoot: options.harnessRoot,
|
|
288
|
+
platform: options.platform,
|
|
289
|
+
cwd: options.workspaceRoot,
|
|
290
|
+
args: [
|
|
291
|
+
'--profile', options.profile,
|
|
292
|
+
...(options.isolationPatchPath ? ['--patch', options.isolationPatchPath] : []),
|
|
293
|
+
'--port', String(options.port),
|
|
294
|
+
...(options.isolationPatchPath ? ['--no-open'] : []),
|
|
295
|
+
],
|
|
296
|
+
})
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** Confine Host first-use Documents workspaces as well as its Profile to DSH_HOME. */
|
|
300
|
+
export function prepareIsolatedWorkspacePatch(cacheRoot) {
|
|
301
|
+
const documents = join(resolve(cacheRoot), 'Documents')
|
|
302
|
+
mkdirSync(documents, { recursive: true })
|
|
303
|
+
const patchPath = join(resolve(cacheRoot), 'workspace-isolation.patch.yml')
|
|
304
|
+
writeFileSync(patchPath, `- id: workspace-controller\n config:\n documentsDirectory: ${JSON.stringify(documents)}\n`)
|
|
305
|
+
return patchPath
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function assertWindowsCommandSafe(command, args) {
|
|
309
|
+
for (const value of [command, ...args]) {
|
|
310
|
+
if (/[\0\r\n"&|<>^%!]/.test(value)) {
|
|
311
|
+
throw new Error(`Windows command argument contains unsupported shell metacharacters: ${value}`)
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export function runProcess(command, args, options = {}) {
|
|
317
|
+
const capture = options.capture === true
|
|
318
|
+
const windows = process.platform === 'win32'
|
|
319
|
+
if (windows) assertWindowsCommandSafe(command, args)
|
|
320
|
+
const actualCommand = windows ? process.env.ComSpec || 'cmd.exe' : command
|
|
321
|
+
const actualArgs = windows
|
|
322
|
+
? ['/d', '/v:off', '/c', command, ...args]
|
|
323
|
+
: args
|
|
324
|
+
const result = spawnSync(actualCommand, actualArgs, {
|
|
325
|
+
cwd: options.cwd,
|
|
326
|
+
env: options.env,
|
|
327
|
+
encoding: 'utf8',
|
|
328
|
+
stdio: capture ? ['ignore', 'pipe', 'pipe'] : 'inherit',
|
|
329
|
+
})
|
|
330
|
+
if (result.error) throw result.error
|
|
331
|
+
if (result.status !== 0) {
|
|
332
|
+
const detail = capture ? `\n${result.stderr || result.stdout || ''}` : ''
|
|
333
|
+
throw new Error(`${command} ${args.join(' ')} failed with exit code ${result.status}.${detail}`)
|
|
334
|
+
}
|
|
335
|
+
return { stdout: capture ? result.stdout ?? '' : '' }
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function copyPackageEntry(source, destination) {
|
|
339
|
+
const info = lstatSync(source)
|
|
340
|
+
if (info.isSymbolicLink()) throw new Error(`Refusing to package symbolic link ${source}.`)
|
|
341
|
+
if (info.isDirectory()) {
|
|
342
|
+
mkdirSync(destination, { recursive: true })
|
|
343
|
+
for (const entry of readdirSync(source)) {
|
|
344
|
+
copyPackageEntry(join(source, entry), join(destination, entry))
|
|
345
|
+
}
|
|
346
|
+
return
|
|
347
|
+
}
|
|
348
|
+
if (!info.isFile()) throw new Error(`Unsupported package entry ${source}.`)
|
|
349
|
+
mkdirSync(resolve(destination, '..'), { recursive: true })
|
|
350
|
+
copyFileSync(source, destination)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function dshHomeOf(options) {
|
|
354
|
+
return options.environment?.DSH_HOME ?? process.env.DSH_HOME ?? join(homedir(), '.dsh')
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function profileManifestOf(options) {
|
|
358
|
+
return options.profileManifestPath ?? join(dshHomeOf(options), 'profiles', options.profile, 'package.json')
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function archiveReferenceFromManifest(manifestPath, required = false) {
|
|
362
|
+
if (!existsSync(manifestPath)) {
|
|
363
|
+
if (required) throw new Error(`DSH profile manifest is missing: ${manifestPath}`)
|
|
364
|
+
return undefined
|
|
365
|
+
}
|
|
366
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'))
|
|
367
|
+
const specifier = manifest.dependencies?.['dsh-git-worktree']
|
|
368
|
+
if (typeof specifier !== 'string' || !specifier.startsWith('file:')) {
|
|
369
|
+
if (required) throw new Error('DSH profile did not record dsh-git-worktree as a local file dependency.')
|
|
370
|
+
return undefined
|
|
371
|
+
}
|
|
372
|
+
let filePath
|
|
373
|
+
try {
|
|
374
|
+
filePath = decodeURIComponent(specifier.slice('file:'.length))
|
|
375
|
+
} catch {
|
|
376
|
+
if (required) throw new Error(`DSH profile recorded an invalid local file dependency: ${specifier}`)
|
|
377
|
+
return undefined
|
|
378
|
+
}
|
|
379
|
+
return resolve(dirname(manifestPath), filePath)
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function profileArchiveReference(options) {
|
|
383
|
+
return archiveReferenceFromManifest(profileManifestOf(options), true)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function comparablePath(path) {
|
|
387
|
+
const normalized = resolve(path).replaceAll('\\', '/')
|
|
388
|
+
return process.platform === 'win32' ? normalized.toLowerCase() : normalized
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function referencedProfileArchives(options) {
|
|
392
|
+
const references = new Set()
|
|
393
|
+
const addManifest = (manifestPath) => {
|
|
394
|
+
try {
|
|
395
|
+
const reference = archiveReferenceFromManifest(manifestPath)
|
|
396
|
+
if (reference !== undefined) references.add(comparablePath(reference))
|
|
397
|
+
} catch {
|
|
398
|
+
// A malformed unrelated profile must not make local snapshot cleanup delete more files.
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
addManifest(profileManifestOf(options))
|
|
402
|
+
const profilesRoot = join(dshHomeOf(options), 'profiles')
|
|
403
|
+
if (existsSync(profilesRoot)) {
|
|
404
|
+
for (const entry of readdirSync(profilesRoot)) addManifest(join(profilesRoot, entry, 'package.json'))
|
|
405
|
+
}
|
|
406
|
+
return references
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function cleanSupersededArchives(cacheRoot, keepPath, options) {
|
|
410
|
+
const preserved = referencedProfileArchives(options)
|
|
411
|
+
preserved.add(comparablePath(keepPath))
|
|
412
|
+
for (const entry of readdirSync(cacheRoot)) {
|
|
413
|
+
if (!/^dsh-git-worktree-\d+\.tgz$/.test(entry)) continue
|
|
414
|
+
const candidate = join(cacheRoot, entry)
|
|
415
|
+
if (!preserved.has(comparablePath(candidate))) rmSync(candidate, { force: true })
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function repairMissingCurrentProfileArchive(options, archivePath) {
|
|
420
|
+
const previous = archiveReferenceFromManifest(profileManifestOf(options))
|
|
421
|
+
if (previous === undefined || existsSync(previous)) return
|
|
422
|
+
const managedCache = comparablePath(dirname(previous)) === comparablePath(options.cacheRoot)
|
|
423
|
+
&& /^dsh-git-worktree-\d+\.tgz$/.test(previous.split(/[\\/]/u).at(-1) ?? '')
|
|
424
|
+
if (!managedCache) {
|
|
425
|
+
throw new Error(`DSH profile references missing local archive outside the managed cache: ${previous}`)
|
|
426
|
+
}
|
|
427
|
+
copyFileSync(archivePath, previous)
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function assertDshRuntimeAvailable(options) {
|
|
431
|
+
if (options.harnessRoot) {
|
|
432
|
+
createDshInvocation({
|
|
433
|
+
projectRoot: options.projectRoot,
|
|
434
|
+
harnessRoot: options.harnessRoot,
|
|
435
|
+
platform: options.platform,
|
|
436
|
+
args: [],
|
|
437
|
+
})
|
|
438
|
+
return
|
|
439
|
+
}
|
|
440
|
+
if (options.run) return
|
|
441
|
+
try {
|
|
442
|
+
runProcess(executable('dsh', options.platform), ['--version'], { capture: true })
|
|
443
|
+
} catch (cause) {
|
|
444
|
+
throw new Error(
|
|
445
|
+
'No Harness source checkout was selected and the installed dsh CLI is unavailable. Pass --harness <path>, set DSH_HARNESS_ROOT, or install dsh on PATH.',
|
|
446
|
+
{ cause },
|
|
447
|
+
)
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function runDsh(options, args, runOptions = {}) {
|
|
452
|
+
const invocation = createDshInvocation({
|
|
453
|
+
projectRoot: options.projectRoot,
|
|
454
|
+
harnessRoot: options.harnessRoot,
|
|
455
|
+
platform: options.platform,
|
|
456
|
+
cwd: runOptions.cwd ?? options.projectRoot,
|
|
457
|
+
args,
|
|
458
|
+
})
|
|
459
|
+
return (options.run ?? runProcess)(invocation.command, invocation.args, {
|
|
460
|
+
cwd: invocation.cwd,
|
|
461
|
+
...(runOptions.capture === true ? { capture: true } : {}),
|
|
462
|
+
})
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/** Build, pack and install only the current local snapshot. No registry publish occurs. */
|
|
466
|
+
export function installLocalSnapshot(options) {
|
|
467
|
+
const run = options.run ?? runProcess
|
|
468
|
+
const pnpm = executable('pnpm', options.platform)
|
|
469
|
+
assertDshRuntimeAvailable(options)
|
|
470
|
+
mkdirSync(options.cacheRoot, { recursive: true })
|
|
471
|
+
|
|
472
|
+
if (!existsSync(join(options.projectRoot, 'node_modules'))) {
|
|
473
|
+
run(pnpm, ['install', '--frozen-lockfile'], { cwd: options.projectRoot })
|
|
474
|
+
}
|
|
475
|
+
run(pnpm, ['run', 'typecheck'], { cwd: options.projectRoot })
|
|
476
|
+
run(pnpm, ['run', 'build'], { cwd: options.projectRoot })
|
|
477
|
+
|
|
478
|
+
const snapshotId = options.now?.() ?? Date.now()
|
|
479
|
+
const archivePath = join(options.cacheRoot, `dsh-git-worktree-${snapshotId}.tgz`)
|
|
480
|
+
const stagingRoot = join(options.cacheRoot, `.staging-${snapshotId}`)
|
|
481
|
+
for (const entry of readdirSync(options.cacheRoot)) {
|
|
482
|
+
if (/^\.staging-\d+$/.test(entry)) rmSync(join(options.cacheRoot, entry), { recursive: true, force: true })
|
|
483
|
+
}
|
|
484
|
+
mkdirSync(stagingRoot, { recursive: true })
|
|
485
|
+
try {
|
|
486
|
+
const manifest = JSON.parse(readFileSync(join(options.projectRoot, 'package.json'), 'utf8'))
|
|
487
|
+
const stagedManifest = {
|
|
488
|
+
...manifest,
|
|
489
|
+
version: `${manifest.version}-dev.${snapshotId}`,
|
|
490
|
+
scripts: {},
|
|
491
|
+
}
|
|
492
|
+
writeFileSync(join(stagingRoot, 'package.json'), `${JSON.stringify(stagedManifest, null, 2)}\n`)
|
|
493
|
+
for (const entry of ['lib', 'cordis.patch.yml', 'README.md', 'README.en.md', 'docs/i18n.md', 'LICENSE', 'NOTICE']) {
|
|
494
|
+
const source = join(options.projectRoot, entry)
|
|
495
|
+
if (existsSync(source)) copyPackageEntry(source, join(stagingRoot, entry))
|
|
496
|
+
}
|
|
497
|
+
run(pnpm, ['pack', '--out', archivePath, '--json'], { cwd: stagingRoot })
|
|
498
|
+
} finally {
|
|
499
|
+
rmSync(stagingRoot, { recursive: true, force: true })
|
|
500
|
+
}
|
|
501
|
+
if (!existsSync(archivePath)) throw new Error(`pnpm pack did not create ${archivePath}.`)
|
|
502
|
+
|
|
503
|
+
// The official config-dump entry initializes a missing named Profile using
|
|
504
|
+
// DSH's template. Plugin add alone may leave a fresh profile without its
|
|
505
|
+
// manifest; never create or overwrite that Host-owned file ourselves.
|
|
506
|
+
if (!existsSync(profileManifestOf(options))) {
|
|
507
|
+
runDsh(options, ['--profile', options.profile, '--dump-config'], { capture: true })
|
|
508
|
+
if (!existsSync(profileManifestOf(options))) {
|
|
509
|
+
throw new Error(`DSH did not initialize profile ${options.profile} before plugin install.`)
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// pnpm resolves the profile's existing direct file dependency before it can
|
|
514
|
+
// replace it. Repair only our own missing cache path with the new snapshot;
|
|
515
|
+
// after the add succeeds the obsolete recovery copy becomes collectible.
|
|
516
|
+
repairMissingCurrentProfileArchive(options, archivePath)
|
|
517
|
+
runDsh(options, ['plugin', '--profile', options.profile, 'add', archivePath])
|
|
518
|
+
const referencedArchive = profileArchiveReference(options)
|
|
519
|
+
if (comparablePath(referencedArchive) !== comparablePath(archivePath)) {
|
|
520
|
+
throw new Error(`DSH profile ${options.profile} still references ${referencedArchive}; preserving old and new archives for recovery.`)
|
|
521
|
+
}
|
|
522
|
+
const dump = runDsh(options, ['--profile', options.profile, '--dump-config'], { capture: true }).stdout
|
|
523
|
+
if (!dump.includes('dsh-git-worktree')) {
|
|
524
|
+
throw new Error(`Profile ${options.profile} installed the tarball but did not compose dsh-git-worktree.`)
|
|
525
|
+
}
|
|
526
|
+
cleanSupersededArchives(options.cacheRoot, archivePath, options)
|
|
527
|
+
return { archivePath, dumpConfig: dump }
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
export function removeLocalSnapshot(options) {
|
|
531
|
+
assertDshRuntimeAvailable(options)
|
|
532
|
+
runDsh(options, ['plugin', '--profile', options.profile, 'remove', 'dsh-git-worktree'])
|
|
533
|
+
if (existsSync(options.cacheRoot)) {
|
|
534
|
+
for (const entry of readdirSync(options.cacheRoot)) {
|
|
535
|
+
if (/^dsh-git-worktree-\d+\.tgz$/.test(entry)) rmSync(join(options.cacheRoot, entry), { force: true })
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
export function smokeLocalSnapshot(options) {
|
|
541
|
+
assertDshRuntimeAvailable(options)
|
|
542
|
+
const dump = runDsh(options, ['--profile', options.profile, '--dump-config'], { capture: true }).stdout
|
|
543
|
+
if (!dump.includes('dsh-git-worktree')) {
|
|
544
|
+
throw new Error(`Profile ${options.profile} does not compose dsh-git-worktree.`)
|
|
545
|
+
}
|
|
546
|
+
return dump
|
|
547
|
+
}
|