infra-kit 0.1.113 → 0.1.115
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/.eslintcache +1 -1
- package/.omc/state/agent-replay-0d1515ce-4ebe-4ce8-a865-2897f68de168.jsonl +1 -0
- package/.omc/state/agent-replay-1fcfe0ec-7567-41c3-9bfd-501e99e86819.jsonl +19 -0
- package/.omc/state/agent-replay-38382db5-3088-4b5a-aff7-2561a60827c7.jsonl +17 -0
- package/.omc/state/agent-replay-50c36be9-a3a2-42b9-9c85-5a06d3e22f6a.jsonl +5 -0
- package/.omc/state/agent-replay-8198b3e5-fad6-4ee0-9c59-ef773ad89af4.jsonl +1 -0
- package/.omc/state/agent-replay-8cd7652a-b8b5-49cf-9b1f-bee8e5972458.jsonl +3 -0
- package/.omc/state/agent-replay-9bea518f-a9ee-4d14-bb53-a936ebaf647c.jsonl +7 -0
- package/.omc/state/agent-replay-b52d89fb-0e24-4901-bc66-27e3c1697126.jsonl +2 -0
- package/.omc/state/agent-replay-f581a1ae-0ee4-48a3-96f3-ce31a0633bb0.jsonl +1 -0
- package/.omc/state/agent-replay-f99d8dad-81d1-4987-8183-62edf0843d0c.jsonl +2 -0
- package/.omc/state/agent-replay-fe1057b4-9a25-4e8e-8c3f-3e11652ee697.jsonl +5 -0
- package/.omc/state/idle-notif-cooldown.json +1 -1
- package/.omc/state/sessions/0d1515ce-4ebe-4ce8-a865-2897f68de168/pre-tool-advisory-throttle.json +10 -0
- package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/last-tool-error-state.json +7 -0
- package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/mission-state.json +109 -0
- package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/pre-tool-advisory-throttle.json +42 -0
- package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/ralph-state.json +13 -0
- package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/skill-active-state.json +15 -0
- package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/subagent-tracking-state.json +62 -0
- package/.omc/state/sessions/1fcfe0ec-7567-41c3-9bfd-501e99e86819/ultrawork-state.json +11 -0
- package/.omc/state/sessions/23d0ff82-17e5-428b-8743-38f7979bb6f3/last-tool-error-state.json +7 -0
- package/.omc/state/sessions/23d0ff82-17e5-428b-8743-38f7979bb6f3/pre-tool-advisory-throttle.json +18 -0
- package/.omc/state/sessions/38382db5-3088-4b5a-aff7-2561a60827c7/last-tool-error-state.json +7 -0
- package/.omc/state/sessions/38382db5-3088-4b5a-aff7-2561a60827c7/mission-state.json +145 -0
- package/.omc/state/sessions/38382db5-3088-4b5a-aff7-2561a60827c7/pre-tool-advisory-throttle.json +38 -0
- package/.omc/state/sessions/38382db5-3088-4b5a-aff7-2561a60827c7/ralplan-state.json +11 -0
- package/.omc/state/sessions/38382db5-3088-4b5a-aff7-2561a60827c7/subagent-tracking-state.json +98 -0
- package/.omc/state/sessions/41336850-028c-476b-be1a-2164e1e46cac/last-tool-error-state.json +7 -0
- package/.omc/state/sessions/41336850-028c-476b-be1a-2164e1e46cac/pre-tool-advisory-throttle.json +18 -0
- package/.omc/state/sessions/50c36be9-a3a2-42b9-9c85-5a06d3e22f6a/mission-state.json +61 -0
- package/.omc/state/sessions/50c36be9-a3a2-42b9-9c85-5a06d3e22f6a/pre-tool-advisory-throttle.json +22 -0
- package/.omc/state/sessions/50c36be9-a3a2-42b9-9c85-5a06d3e22f6a/skill-active-state.json +15 -0
- package/.omc/state/sessions/50c36be9-a3a2-42b9-9c85-5a06d3e22f6a/subagent-tracking-state.json +26 -0
- package/.omc/state/sessions/560ea519-574a-4713-b134-5bf6cfaf3c33/pre-tool-advisory-throttle.json +18 -0
- package/.omc/state/sessions/8198b3e5-fad6-4ee0-9c59-ef773ad89af4/pre-tool-advisory-throttle.json +14 -0
- package/.omc/state/sessions/8198b3e5-fad6-4ee0-9c59-ef773ad89af4/subagent-tracking-state.json +17 -0
- package/.omc/state/sessions/8cd7652a-b8b5-49cf-9b1f-bee8e5972458/pre-tool-advisory-throttle.json +18 -0
- package/.omc/state/sessions/8cd7652a-b8b5-49cf-9b1f-bee8e5972458/ralplan-state.json +11 -0
- package/.omc/state/sessions/8cd7652a-b8b5-49cf-9b1f-bee8e5972458/subagent-tracking-state.json +35 -0
- package/.omc/state/sessions/9bea518f-a9ee-4d14-bb53-a936ebaf647c/last-tool-error-state.json +7 -0
- package/.omc/state/sessions/9bea518f-a9ee-4d14-bb53-a936ebaf647c/mission-state.json +79 -0
- package/.omc/state/sessions/9bea518f-a9ee-4d14-bb53-a936ebaf647c/pre-tool-advisory-throttle.json +22 -0
- package/.omc/state/sessions/9bea518f-a9ee-4d14-bb53-a936ebaf647c/progress.txt +7 -0
- package/.omc/state/sessions/9bea518f-a9ee-4d14-bb53-a936ebaf647c/skill-active-state.json +15 -0
- package/.omc/state/sessions/9bea518f-a9ee-4d14-bb53-a936ebaf647c/subagent-tracking-state.json +26 -0
- package/.omc/state/sessions/b52d89fb-0e24-4901-bc66-27e3c1697126/pre-tool-advisory-throttle.json +14 -0
- package/.omc/state/sessions/b52d89fb-0e24-4901-bc66-27e3c1697126/subagent-tracking-state.json +17 -0
- package/.omc/state/sessions/c513ef38-cc3e-4a29-9c4c-1e0137245dd5/pre-tool-advisory-throttle.json +18 -0
- package/.omc/state/sessions/ed3e2cfb-a666-4bb1-bcc1-569d5d0b837a/pre-tool-advisory-throttle.json +18 -0
- package/.omc/state/sessions/f581a1ae-0ee4-48a3-96f3-ce31a0633bb0/pre-tool-advisory-throttle.json +18 -0
- package/.omc/state/sessions/f581a1ae-0ee4-48a3-96f3-ce31a0633bb0/subagent-tracking-state.json +17 -0
- package/.omc/state/sessions/f99d8dad-81d1-4987-8183-62edf0843d0c/pre-tool-advisory-throttle.json +14 -0
- package/.omc/state/sessions/f99d8dad-81d1-4987-8183-62edf0843d0c/ralph-state.json +13 -0
- package/.omc/state/sessions/f99d8dad-81d1-4987-8183-62edf0843d0c/ultrawork-state.json +11 -0
- package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/last-tool-error-state.json +7 -0
- package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/mission-state.json +61 -0
- package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/prd.json +22 -0
- package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/pre-tool-advisory-throttle.json +22 -0
- package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/progress.txt +22 -0
- package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/skill-active-state.json +15 -0
- package/.omc/state/sessions/fe1057b4-9a25-4e8e-8c3f-3e11652ee697/subagent-tracking-state.json +26 -0
- package/.turbo/turbo-build.log +3 -3
- package/.turbo/turbo-test.log +92 -44
- package/dist/boot-K4QUAFSP.js +2 -0
- package/dist/boot-K4QUAFSP.js.map +7 -0
- package/dist/chunk-RTSP2FCA.js +2 -0
- package/dist/chunk-RTSP2FCA.js.map +7 -0
- package/dist/chunk-VPBZR2PY.js +166 -0
- package/dist/chunk-VPBZR2PY.js.map +7 -0
- package/dist/cli.js +11 -107
- package/dist/cli.js.map +4 -4
- package/dist/entry/index.d.ts +2 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +4 -4
- package/dist/lib/vendor/config-schema.d.ts +58 -0
- package/dist/mcp.js +1 -54
- package/dist/mcp.js.map +4 -4
- package/eslint.config.js +33 -1
- package/package.json +5 -1
- package/scripts/build.js +11 -1
- package/src/.omc/state/agent-replay-c513ef38-cc3e-4a29-9c4c-1e0137245dd5.jsonl +4 -0
- package/src/.omc/state/agent-replay-ed3e2cfb-a666-4bb1-bcc1-569d5d0b837a.jsonl +2 -0
- package/src/.omc/state/idle-notif-cooldown.json +1 -1
- package/src/.omc/state/sessions/c513ef38-cc3e-4a29-9c4c-1e0137245dd5/mission-state.json +79 -0
- package/src/.omc/state/sessions/c513ef38-cc3e-4a29-9c4c-1e0137245dd5/pre-tool-advisory-throttle.json +22 -0
- package/src/.omc/state/sessions/c513ef38-cc3e-4a29-9c4c-1e0137245dd5/subagent-tracking-state.json +26 -0
- package/src/.omc/state/sessions/ed3e2cfb-a666-4bb1-bcc1-569d5d0b837a/pre-tool-advisory-throttle.json +18 -0
- package/src/.omc/state/sessions/ed3e2cfb-a666-4bb1-bcc1-569d5d0b837a/ralph-state.json +13 -0
- package/src/.omc/state/sessions/ed3e2cfb-a666-4bb1-bcc1-569d5d0b837a/ultrawork-state.json +11 -0
- package/src/.omc/state/sessions/f99d8dad-81d1-4987-8183-62edf0843d0c/pre-tool-advisory-throttle.json +14 -0
- package/src/commands/config/.omc/state/sessions/41336850-028c-476b-be1a-2164e1e46cac/last-tool-error-state.json +7 -0
- package/src/commands/config/.omc/state/sessions/41336850-028c-476b-be1a-2164e1e46cac/pre-tool-advisory-throttle.json +18 -0
- package/src/commands/config/config.ts +7 -37
- package/src/commands/doctor/__tests__/agent-files.test.ts +8 -30
- package/src/commands/doctor/__tests__/check-legacy-user-global-config.test.ts +110 -0
- package/src/commands/doctor/doctor.ts +110 -44
- package/src/commands/env-autoload/env-autoload.ts +12 -0
- package/src/commands/env-autoload/index.ts +1 -0
- package/src/commands/env-clear/__tests__/env-clear.test.ts +27 -0
- package/src/commands/env-clear/env-clear.ts +22 -8
- package/src/commands/env-load/__tests__/env-load.test.ts +36 -1
- package/src/commands/env-load/env-load.ts +152 -35
- package/src/commands/env-load/index.ts +2 -1
- package/src/commands/env-status/__tests__/env-status.test.ts +78 -0
- package/src/commands/env-status/env-status.ts +19 -7
- package/src/commands/gh-release-deploy-all/gh-release-deploy-all.ts +45 -1
- package/src/commands/gh-release-deploy-selected/gh-release-deploy-selected.ts +46 -1
- package/src/commands/init/__tests__/agent-files.test.ts +145 -27
- package/src/commands/init/__tests__/migrate-config.test.ts +136 -5
- package/src/commands/init/__tests__/migrate-factory-config.test.ts +93 -0
- package/src/commands/init/__tests__/seed-user-global-config.test.ts +64 -0
- package/src/commands/init/__tests__/shell-body.test.ts +30 -0
- package/src/commands/init/agent-files.ts +65 -45
- package/src/commands/init/init.ts +140 -31
- package/src/commands/init/migrate-config.ts +160 -32
- package/src/commands/vendor-check/__tests__/runtime-isolation.test.ts +71 -0
- package/src/commands/vendor-check/__tests__/vendor-check.test.ts +87 -0
- package/src/commands/vendor-check/index.ts +1 -0
- package/src/commands/vendor-check/vendor-check.ts +127 -0
- package/src/commands/vendor-config/__tests__/vendor-config.test.ts +103 -0
- package/src/commands/vendor-config/index.ts +1 -0
- package/src/commands/vendor-config/vendor-config.ts +162 -0
- package/src/commands/vendor-diff/__tests__/vendor-diff.test.ts +74 -0
- package/src/commands/vendor-diff/index.ts +1 -0
- package/src/commands/vendor-diff/vendor-diff.ts +77 -0
- package/src/commands/vendor-manifest/index.ts +1 -0
- package/src/commands/vendor-manifest/vendor-manifest.ts +60 -0
- package/src/commands/vendor-sync/__tests__/vendor-write.test.ts +86 -0
- package/src/commands/vendor-sync/index.ts +1 -0
- package/src/commands/vendor-sync/vendor-sync.ts +90 -0
- package/src/commands/worktrees-add/worktrees-add.ts +1 -1
- package/src/commands/worktrees-reload/worktrees-reload.ts +6 -5
- package/src/commands/worktrees-remove/worktrees-remove.ts +10 -1
- package/src/commands/worktrees-sync/worktrees-sync.ts +9 -1
- package/src/entry/cli.ts +417 -210
- package/src/entry/index.ts +7 -1
- package/src/integrations/cmux/__tests__/open-workspace-with-layout.test.ts +127 -0
- package/src/integrations/cmux/close-workspace-by-title.ts +2 -2
- package/src/integrations/cmux/list-workspace-titles.ts +1 -1
- package/src/integrations/cmux/open-workspace-with-layout.ts +15 -4
- package/src/integrations/cursor/.omc/state/agent-replay-f99d8dad-81d1-4987-8183-62edf0843d0c.jsonl +2 -0
- package/src/integrations/cursor/.omc/state/idle-notif-cooldown.json +3 -0
- package/src/integrations/cursor/.omc/state/sessions/f99d8dad-81d1-4987-8183-62edf0843d0c/pre-tool-advisory-throttle.json +18 -0
- package/src/integrations/cursor/.omc/state/sessions/f99d8dad-81d1-4987-8183-62edf0843d0c/subagent-tracking-state.json +17 -0
- package/src/integrations/cursor/index.ts +1 -0
- package/src/integrations/cursor/launch-cursor.ts +12 -0
- package/src/integrations/cursor/open-cursor-workspace.ts +8 -14
- package/src/integrations/doppler/__tests__/doppler-errors.test.ts +76 -0
- package/src/integrations/doppler/doppler-cli-auth.ts +12 -4
- package/src/integrations/doppler/doppler-errors.ts +88 -0
- package/src/integrations/doppler/doppler-project.ts +80 -0
- package/src/integrations/doppler/index.ts +3 -1
- package/src/integrations/gh/gh-cli-auth/gh-cli-auth.ts +9 -5
- package/src/integrations/gh/gh-release-prs/gh-release-prs.ts +16 -8
- package/src/integrations/ide/__tests__/ide-facade.test.ts +57 -10
- package/src/integrations/ide/add-ide-worktree-folders.ts +2 -4
- package/src/integrations/ide/open-ide-workspace.ts +6 -6
- package/src/integrations/ide/remove-ide-worktree-folders.ts +95 -30
- package/src/integrations/ide/types.ts +10 -1
- package/src/integrations/zed/__tests__/open-zed-workspace.test.ts +1 -17
- package/src/integrations/zed/__tests__/reuse-zed-workspace.test.ts +86 -0
- package/src/integrations/zed/index.ts +1 -0
- package/src/integrations/zed/open-zed-workspace.ts +4 -10
- package/src/integrations/zed/reuse-zed-workspace.ts +57 -0
- package/src/lib/command-catalog/__tests__/__snapshots__/command-catalog.test.ts.snap +212 -0
- package/src/lib/command-catalog/__tests__/command-catalog.test.ts +193 -0
- package/src/lib/command-catalog/command-catalog.ts +141 -0
- package/src/lib/command-catalog/index.ts +2 -0
- package/src/lib/command-echo/__tests__/command-echo.test.ts +127 -0
- package/src/lib/constants/constants.ts +13 -0
- package/src/lib/constants/index.ts +2 -0
- package/src/lib/env-autoload/__tests__/env-autoload.test.ts +288 -0
- package/src/lib/env-autoload/env-autoload.ts +294 -0
- package/src/lib/env-autoload/index.ts +9 -0
- package/src/lib/errors/operation-error.ts +1 -1
- package/src/lib/infra-kit-config/__tests__/env-autoload-schema.test.ts +71 -0
- package/src/lib/infra-kit-config/__tests__/infra-kit-config.test.ts +77 -6
- package/src/lib/infra-kit-config/index.ts +9 -1
- package/src/lib/infra-kit-config/infra-kit-config.ts +71 -11
- package/src/lib/json-output/__tests__/json-output.test.ts +139 -0
- package/src/lib/json-output/index.ts +1 -0
- package/src/lib/json-output/json-output.ts +47 -0
- package/src/lib/managed-block/managed-block.ts +4 -4
- package/src/lib/path-display/index.ts +1 -0
- package/src/lib/path-display/path-display.ts +42 -0
- package/src/lib/render/__tests__/render.test.ts +48 -0
- package/src/lib/render/index.ts +1 -0
- package/src/lib/render/render.ts +24 -0
- package/src/lib/vendor/__tests__/config-schema.test.ts +31 -0
- package/src/lib/vendor/__tests__/config.test.ts +63 -0
- package/src/lib/vendor/__tests__/factory-config.test.ts +92 -0
- package/src/lib/vendor/__tests__/vendor-lib.test.ts +158 -0
- package/src/lib/vendor/__tests__/zx-mock.ts +32 -0
- package/src/lib/vendor/config-schema.ts +78 -0
- package/src/lib/vendor/config.ts +58 -0
- package/src/lib/vendor/factory-config-schema.ts +35 -0
- package/src/lib/vendor/factory-config.ts +91 -0
- package/src/lib/vendor/hash.ts +16 -0
- package/src/lib/vendor/index.ts +23 -0
- package/src/lib/vendor/manifest.ts +151 -0
- package/src/lib/vendor/skip-sets.ts +48 -0
- package/src/lib/vendor/sync-ops.ts +290 -0
- package/src/lib/vendor/walk.ts +56 -0
- package/src/lib/worktrees/remove-worktrees.ts +15 -0
- package/src/mcp/tools/index.ts +5 -42
- package/src/sanity.test.tsx +1 -0
- package/src/tui/__tests__/no-react-boundary.test.ts +51 -0
- package/src/tui/boot.tsx +45 -0
- package/src/tui/screens/__tests__/command-palette.test.tsx +136 -0
- package/src/tui/screens/command-palette.tsx +159 -0
- package/src/tui/types.ts +8 -0
- package/tsconfig.json +1 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.config.ts +8 -0
- package/src/commands/worktrees-open/__tests__/open-cmux.test.ts +0 -96
- package/src/commands/worktrees-open/index.ts +0 -1
- package/src/commands/worktrees-open/worktrees-open.ts +0 -195
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import process from 'node:process'
|
|
2
1
|
import { $ } from 'zx'
|
|
3
2
|
|
|
3
|
+
import { OperationError } from 'src/lib/errors/operation-error'
|
|
4
4
|
import { logger } from 'src/lib/logger'
|
|
5
5
|
import { compareReleaseIds, formatBranchName, formatPrTitle, parseBranchName } from 'src/lib/release-id'
|
|
6
6
|
import type { ReleaseId } from 'src/lib/release-id'
|
|
@@ -84,18 +84,21 @@ export const getReleasePRs = async (): Promise<string[]> => {
|
|
|
84
84
|
const prs = await fetchAllReleasePRs()
|
|
85
85
|
|
|
86
86
|
if (prs.length === 0) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
throw new OperationError(undefined, {
|
|
88
|
+
operation: 'find open release PRs',
|
|
89
|
+
remediation: 'open a release PR first, or check you are in the right repo',
|
|
90
|
+
})
|
|
90
91
|
}
|
|
91
92
|
|
|
92
93
|
return sortReleasePRs(prs).map((pr) => {
|
|
93
94
|
return pr.headRefName
|
|
94
95
|
})
|
|
95
96
|
} catch (error) {
|
|
97
|
+
if (error instanceof OperationError) throw error
|
|
98
|
+
|
|
96
99
|
logger.error({ error }, '❌ Error fetching release PRs')
|
|
97
100
|
|
|
98
|
-
|
|
101
|
+
throw new OperationError(error, { operation: 'fetch release PRs' })
|
|
99
102
|
}
|
|
100
103
|
}
|
|
101
104
|
|
|
@@ -110,8 +113,10 @@ export const getReleasePRsWithInfo = async (): Promise<ReleasePRInfo[]> => {
|
|
|
110
113
|
const prs = await fetchAllReleasePRs()
|
|
111
114
|
|
|
112
115
|
if (prs.length === 0) {
|
|
113
|
-
|
|
114
|
-
|
|
116
|
+
throw new OperationError(undefined, {
|
|
117
|
+
operation: 'find open release PRs',
|
|
118
|
+
remediation: 'open a release PR first, or check you are in the right repo',
|
|
119
|
+
})
|
|
115
120
|
}
|
|
116
121
|
|
|
117
122
|
return sortReleasePRs(prs).map((pr) => {
|
|
@@ -122,8 +127,11 @@ export const getReleasePRsWithInfo = async (): Promise<ReleasePRInfo[]> => {
|
|
|
122
127
|
}
|
|
123
128
|
})
|
|
124
129
|
} catch (error) {
|
|
130
|
+
if (error instanceof OperationError) throw error
|
|
131
|
+
|
|
125
132
|
logger.error({ error }, '❌ Error fetching release PRs')
|
|
126
|
-
|
|
133
|
+
|
|
134
|
+
throw new OperationError(error, { operation: 'fetch release PRs' })
|
|
127
135
|
}
|
|
128
136
|
}
|
|
129
137
|
|
|
@@ -33,6 +33,7 @@ const cursor = vi.hoisted(() => {
|
|
|
33
33
|
openCursorWorkspace: vi.fn(),
|
|
34
34
|
addFoldersToCursorWorkspace: vi.fn(),
|
|
35
35
|
removeFoldersFromCursorWorkspace: vi.fn(),
|
|
36
|
+
launchCursor: vi.fn(),
|
|
36
37
|
resolveCursorWorkspacePath: vi.fn((value: string) => {
|
|
37
38
|
return `/abs/${value}`
|
|
38
39
|
}),
|
|
@@ -43,10 +44,18 @@ vi.mock('src/integrations/cursor', () => {
|
|
|
43
44
|
return cursor
|
|
44
45
|
})
|
|
45
46
|
|
|
47
|
+
// Defense-in-depth: never let a test shell out to a real editor. Even if an
|
|
48
|
+
// editor spawn leaks back into a layer this suite exercises directly, `$` is a
|
|
49
|
+
// no-op here so `cursor <path>` can never open a real window during the run.
|
|
50
|
+
vi.mock('zx', () => {
|
|
51
|
+
return { $: vi.fn() }
|
|
52
|
+
})
|
|
53
|
+
|
|
46
54
|
const zed = vi.hoisted(() => {
|
|
47
55
|
return {
|
|
48
56
|
openZedWorkspace: vi.fn(),
|
|
49
57
|
addFoldersToZedWorkspace: vi.fn(),
|
|
58
|
+
reuseZedWorkspace: vi.fn(),
|
|
50
59
|
}
|
|
51
60
|
})
|
|
52
61
|
|
|
@@ -65,7 +74,6 @@ const baseOpenArgs = {
|
|
|
65
74
|
projectRoot: '/repo',
|
|
66
75
|
worktreeDir: '/repo.worktrees',
|
|
67
76
|
currentBranches: ['release/v1.0.0'],
|
|
68
|
-
skipRelaunchWhenEmpty: false,
|
|
69
77
|
}
|
|
70
78
|
|
|
71
79
|
beforeEach(() => {
|
|
@@ -183,7 +191,14 @@ describe('addIdeWorktreeFolders', () => {
|
|
|
183
191
|
})
|
|
184
192
|
|
|
185
193
|
describe('removeIdeWorktreeFolders', () => {
|
|
186
|
-
|
|
194
|
+
// currentWorktrees \ removedWorktrees => remaining = ['release/v1.1.0']
|
|
195
|
+
const args = {
|
|
196
|
+
projectRoot: '/repo',
|
|
197
|
+
worktreeDir: '/repo.worktrees',
|
|
198
|
+
currentWorktrees: ['release/v1.0.0', 'release/v1.1.0'],
|
|
199
|
+
removedWorktrees: ['release/v1.0.0'],
|
|
200
|
+
allowEditorRelaunch: false,
|
|
201
|
+
}
|
|
187
202
|
|
|
188
203
|
it('removes from the Cursor workspace', async () => {
|
|
189
204
|
config.value = cursorConfig
|
|
@@ -204,31 +219,63 @@ describe('removeIdeWorktreeFolders', () => {
|
|
|
204
219
|
expect(outcomes).toEqual([{ provider: 'cursor', supported: true, removed: [] }])
|
|
205
220
|
})
|
|
206
221
|
|
|
207
|
-
it('
|
|
222
|
+
it('does NOT relaunch Zed on the non-interactive path (allowEditorRelaunch: false)', async () => {
|
|
208
223
|
config.value = zedConfig
|
|
209
224
|
|
|
210
|
-
const outcomes = await removeIdeWorktreeFolders(args)
|
|
225
|
+
const outcomes = await removeIdeWorktreeFolders({ ...args, allowEditorRelaunch: false })
|
|
211
226
|
|
|
212
|
-
expect(
|
|
227
|
+
expect(zed.reuseZedWorkspace).not.toHaveBeenCalled()
|
|
228
|
+
expect(outcomes).toEqual([{ provider: 'zed', supported: true, removed: [] }])
|
|
213
229
|
})
|
|
214
230
|
|
|
215
|
-
it('
|
|
231
|
+
it('relaunches Zed with the remaining set on the interactive path (allowEditorRelaunch: true)', async () => {
|
|
232
|
+
config.value = zedConfig
|
|
233
|
+
|
|
234
|
+
const outcomes = await removeIdeWorktreeFolders({ ...args, allowEditorRelaunch: true })
|
|
235
|
+
|
|
236
|
+
expect(zed.reuseZedWorkspace).toHaveBeenCalledWith({
|
|
237
|
+
projectRoot: '/repo',
|
|
238
|
+
worktreeDir: '/repo.worktrees',
|
|
239
|
+
remainingBranches: ['release/v1.1.0'],
|
|
240
|
+
})
|
|
241
|
+
expect(outcomes).toEqual([{ provider: 'zed', supported: true, removed: [] }])
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
it('reports Zed removed:[] when the relaunch swallowed a failure (ran:false), never throwing', async () => {
|
|
245
|
+
config.value = zedConfig
|
|
246
|
+
// reuseZedWorkspace is best-effort: it swallows launch failures internally and resolves
|
|
247
|
+
// { ran: false } rather than rejecting, so the facade never sees a throw.
|
|
248
|
+
zed.reuseZedWorkspace.mockResolvedValue({ ran: false })
|
|
249
|
+
|
|
250
|
+
const outcomes = await removeIdeWorktreeFolders({ ...args, allowEditorRelaunch: true })
|
|
251
|
+
|
|
252
|
+
expect(outcomes).toEqual([{ provider: 'zed', supported: true, removed: [] }])
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
it('removes from Cursor surgically AND relaunches Zed when both are configured (interactive)', async () => {
|
|
216
256
|
config.value = bothConfig
|
|
217
257
|
cursor.removeFoldersFromCursorWorkspace.mockResolvedValue({ removed: ['x'], notFound: [] })
|
|
218
258
|
|
|
219
|
-
const outcomes = await removeIdeWorktreeFolders(args)
|
|
259
|
+
const outcomes = await removeIdeWorktreeFolders({ ...args, allowEditorRelaunch: true })
|
|
220
260
|
|
|
261
|
+
expect(cursor.removeFoldersFromCursorWorkspace).toHaveBeenCalled()
|
|
262
|
+
expect(zed.reuseZedWorkspace).toHaveBeenCalledWith({
|
|
263
|
+
projectRoot: '/repo',
|
|
264
|
+
worktreeDir: '/repo.worktrees',
|
|
265
|
+
remainingBranches: ['release/v1.1.0'],
|
|
266
|
+
})
|
|
221
267
|
expect(outcomes).toEqual([
|
|
222
268
|
{ provider: 'cursor', supported: true, removed: ['x'] },
|
|
223
|
-
{ provider: 'zed', supported:
|
|
269
|
+
{ provider: 'zed', supported: true, removed: [] },
|
|
224
270
|
])
|
|
225
271
|
})
|
|
226
272
|
|
|
227
|
-
it('short-circuits to an empty array when
|
|
273
|
+
it('short-circuits to an empty array when nothing was removed (no config read needed)', async () => {
|
|
228
274
|
config.value = zedConfig
|
|
229
275
|
|
|
230
|
-
const outcomes = await removeIdeWorktreeFolders({ ...args,
|
|
276
|
+
const outcomes = await removeIdeWorktreeFolders({ ...args, removedWorktrees: [] })
|
|
231
277
|
|
|
278
|
+
expect(zed.reuseZedWorkspace).not.toHaveBeenCalled()
|
|
232
279
|
expect(outcomes).toEqual([])
|
|
233
280
|
})
|
|
234
281
|
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import { addFoldersToCursorWorkspace, resolveCursorWorkspacePath } from 'src/integrations/cursor'
|
|
1
|
+
import { addFoldersToCursorWorkspace, launchCursor, resolveCursorWorkspacePath } from 'src/integrations/cursor'
|
|
4
2
|
import { addFoldersToZedWorkspace } from 'src/integrations/zed'
|
|
5
3
|
import { assertNever } from 'src/lib/assert-never'
|
|
6
4
|
import { getInfraKitConfig, resolveConfiguredIdes } from 'src/lib/infra-kit-config'
|
|
@@ -56,7 +54,7 @@ export const addIdeWorktreeFolders = async (
|
|
|
56
54
|
|
|
57
55
|
logger.info(`✅ Added ${added.length} folder(s) to ${workspacePath}${skippedSuffix}`)
|
|
58
56
|
|
|
59
|
-
await
|
|
57
|
+
await launchCursor(workspacePath)
|
|
60
58
|
|
|
61
59
|
outcomes.push({ ran: true, provider: 'cursor', added: added.length, skipped: skipped.length })
|
|
62
60
|
break
|
|
@@ -9,15 +9,15 @@ interface OpenIdeWorkspaceArgs {
|
|
|
9
9
|
projectRoot: string
|
|
10
10
|
worktreeDir: string
|
|
11
11
|
currentBranches: string[]
|
|
12
|
-
skipRelaunchWhenEmpty: boolean
|
|
13
12
|
}
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
|
-
* Provider-agnostic entry point for the
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
15
|
+
* Provider-agnostic entry point for the reload open: for every configured
|
|
16
|
+
* editor, reconciles (or, for Zed, simply assembles) the workspace against the
|
|
17
|
+
* release worktrees on disk and launches it — skipping the launch when there are
|
|
18
|
+
* no worktrees, so `worktrees-reload` never pops a bare editor window. Returns
|
|
19
|
+
* one outcome per configured provider (empty array when no IDE is configured).
|
|
20
|
+
* Iterates sequentially — `worktrees-reload` already wraps this call in an outer
|
|
21
21
|
* `Promise.all` with cmux, so a `Promise.all` here would compound editor-spawn
|
|
22
22
|
* concurrency. Best-effort — every provider swallows failures into a warning.
|
|
23
23
|
*/
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { removeFoldersFromCursorWorkspace, resolveCursorWorkspacePath } from 'src/integrations/cursor'
|
|
2
|
+
import { reuseZedWorkspace } from 'src/integrations/zed'
|
|
2
3
|
import { assertNever } from 'src/lib/assert-never'
|
|
3
4
|
import { getInfraKitConfig, resolveConfiguredIdes } from 'src/lib/infra-kit-config'
|
|
4
5
|
import { logger } from 'src/lib/logger'
|
|
@@ -8,7 +9,17 @@ import type { RemoveIdeWorktreeFoldersOutcome } from './types'
|
|
|
8
9
|
interface RemoveIdeWorktreeFoldersArgs {
|
|
9
10
|
projectRoot: string
|
|
10
11
|
worktreeDir: string
|
|
11
|
-
|
|
12
|
+
/** All release worktrees BEFORE removal (release-only — see Zed note below). */
|
|
13
|
+
currentWorktrees: string[]
|
|
14
|
+
/** The subset of `currentWorktrees` that was just removed. */
|
|
15
|
+
removedWorktrees: string[]
|
|
16
|
+
/**
|
|
17
|
+
* Whether the caller is on an interactive path where firing Zed's destructive
|
|
18
|
+
* `zed --reuse` is acceptable (a human is present and confirming). Interactive
|
|
19
|
+
* `worktrees-remove` passes `!confirmedCommand`; `worktrees-sync` and every
|
|
20
|
+
* non-interactive/MCP run pass `false`.
|
|
21
|
+
*/
|
|
22
|
+
allowEditorRelaunch: boolean
|
|
12
23
|
}
|
|
13
24
|
|
|
14
25
|
/**
|
|
@@ -17,57 +28,51 @@ interface RemoveIdeWorktreeFoldersArgs {
|
|
|
17
28
|
* provider (empty array when no IDE is configured or no worktrees were removed),
|
|
18
29
|
* iterating sequentially.
|
|
19
30
|
*
|
|
20
|
-
* Cursor edits the `.code-workspace` `folders` array
|
|
21
|
-
*
|
|
22
|
-
*
|
|
31
|
+
* Cursor surgically edits the `.code-workspace` `folders` array — it removes
|
|
32
|
+
* only the named paths and leaves everything else intact.
|
|
33
|
+
*
|
|
34
|
+
* Zed has no surgical remove. The only mutation mechanism, `zed --reuse`,
|
|
35
|
+
* REPLACES the focused window's entire folder set, so it can only re-state the
|
|
36
|
+
* release worktrees we know about and silently drops any other open folder
|
|
37
|
+
* (`remaining` is built from release worktrees only). Because that is
|
|
38
|
+
* destructive and unrecoverable, it fires ONLY when `allowEditorRelaunch` is
|
|
39
|
+
* true (interactive `worktrees-remove`). On the non-interactive/MCP path it is a
|
|
40
|
+
* deliberate no-op (`supported: true`, `removed: []`) with an info message — the
|
|
41
|
+
* capability exists; skipping is a policy choice, not a missing capability.
|
|
42
|
+
*
|
|
43
|
+
* Zed's `removed` is ALWAYS `[]`: `--reuse` performs no diff, so it confirms no
|
|
44
|
+
* specific removal and we never report intended-but-unverified paths.
|
|
23
45
|
*/
|
|
24
46
|
export const removeIdeWorktreeFolders = async (
|
|
25
47
|
args: RemoveIdeWorktreeFoldersArgs,
|
|
26
48
|
): Promise<RemoveIdeWorktreeFoldersOutcome[]> => {
|
|
27
|
-
const { projectRoot, worktreeDir,
|
|
49
|
+
const { projectRoot, worktreeDir, currentWorktrees, removedWorktrees, allowEditorRelaunch } = args
|
|
28
50
|
|
|
29
|
-
if (
|
|
51
|
+
if (removedWorktrees.length === 0) {
|
|
30
52
|
return []
|
|
31
53
|
}
|
|
32
54
|
|
|
33
55
|
const config = await getInfraKitConfig()
|
|
34
56
|
const ides = resolveConfiguredIdes(config)
|
|
35
57
|
|
|
36
|
-
const folderPaths =
|
|
58
|
+
const folderPaths = removedWorktrees.map((branch) => {
|
|
37
59
|
return `${worktreeDir}/${branch}`
|
|
38
60
|
})
|
|
39
61
|
|
|
62
|
+
const remainingBranches = currentWorktrees.filter((branch) => {
|
|
63
|
+
return !removedWorktrees.includes(branch)
|
|
64
|
+
})
|
|
65
|
+
|
|
40
66
|
const outcomes: RemoveIdeWorktreeFoldersOutcome[] = []
|
|
41
67
|
|
|
42
68
|
for (const ide of ides) {
|
|
43
69
|
switch (ide.provider) {
|
|
44
70
|
case 'cursor': {
|
|
45
|
-
|
|
46
|
-
outcomes.push({ provider: 'cursor', supported: true, removed: [] })
|
|
47
|
-
break
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const workspacePath = resolveCursorWorkspacePath(ide.config.workspaceConfigPath, projectRoot)
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
const { removed } = await removeFoldersFromCursorWorkspace({ workspacePath, folderPaths })
|
|
54
|
-
|
|
55
|
-
if (removed.length > 0) {
|
|
56
|
-
logger.info(`✅ Removed ${removed.length} folder(s) from ${workspacePath}`)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
outcomes.push({ provider: 'cursor', supported: true, removed })
|
|
60
|
-
} catch (error) {
|
|
61
|
-
logger.warn({ error }, `⚠️ Failed to update Cursor workspace at ${workspacePath}`)
|
|
62
|
-
|
|
63
|
-
outcomes.push({ provider: 'cursor', supported: true, removed: [] })
|
|
64
|
-
}
|
|
71
|
+
outcomes.push(await removeFromCursor({ ide, projectRoot, folderPaths }))
|
|
65
72
|
break
|
|
66
73
|
}
|
|
67
74
|
case 'zed': {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
outcomes.push({ provider: 'zed', supported: false, removed: [] })
|
|
75
|
+
outcomes.push(await removeFromZed({ projectRoot, worktreeDir, remainingBranches, allowEditorRelaunch }))
|
|
71
76
|
break
|
|
72
77
|
}
|
|
73
78
|
default: {
|
|
@@ -78,3 +83,63 @@ export const removeIdeWorktreeFolders = async (
|
|
|
78
83
|
|
|
79
84
|
return outcomes
|
|
80
85
|
}
|
|
86
|
+
|
|
87
|
+
interface RemoveFromCursorArgs {
|
|
88
|
+
ide: Extract<ReturnType<typeof resolveConfiguredIdes>[number], { provider: 'cursor' }>
|
|
89
|
+
projectRoot: string
|
|
90
|
+
folderPaths: string[]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Surgically strip the removed folder paths from the Cursor `.code-workspace`. */
|
|
94
|
+
const removeFromCursor = async (args: RemoveFromCursorArgs): Promise<RemoveIdeWorktreeFoldersOutcome> => {
|
|
95
|
+
const { ide, projectRoot, folderPaths } = args
|
|
96
|
+
|
|
97
|
+
if (!ide.config.workspaceConfigPath) {
|
|
98
|
+
return { provider: 'cursor', supported: true, removed: [] }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const workspacePath = resolveCursorWorkspacePath(ide.config.workspaceConfigPath, projectRoot)
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const { removed } = await removeFoldersFromCursorWorkspace({ workspacePath, folderPaths })
|
|
105
|
+
|
|
106
|
+
if (removed.length > 0) {
|
|
107
|
+
logger.info(`✅ Removed ${removed.length} folder(s) from ${workspacePath}`)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return { provider: 'cursor', supported: true, removed }
|
|
111
|
+
} catch (error) {
|
|
112
|
+
logger.warn({ error }, `⚠️ Failed to update Cursor workspace at ${workspacePath}`)
|
|
113
|
+
|
|
114
|
+
return { provider: 'cursor', supported: true, removed: [] }
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
interface RemoveFromZedArgs {
|
|
119
|
+
projectRoot: string
|
|
120
|
+
worktreeDir: string
|
|
121
|
+
remainingBranches: string[]
|
|
122
|
+
allowEditorRelaunch: boolean
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Reflect the removal in Zed by relaunching the focused window onto the
|
|
127
|
+
* remaining set — but ONLY on an interactive path (see the module doc for why
|
|
128
|
+
* `zed --reuse` is destructive). Otherwise a deliberate no-op. `removed` is
|
|
129
|
+
* always `[]` because `--reuse` performs no diff.
|
|
130
|
+
*/
|
|
131
|
+
const removeFromZed = async (args: RemoveFromZedArgs): Promise<RemoveIdeWorktreeFoldersOutcome> => {
|
|
132
|
+
const { projectRoot, worktreeDir, remainingBranches, allowEditorRelaunch } = args
|
|
133
|
+
|
|
134
|
+
if (!allowEditorRelaunch) {
|
|
135
|
+
logger.info(
|
|
136
|
+
'ℹ️ Zed folder removal skipped (no interactive session); close removed worktree folders in Zed manually if needed.',
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
return { provider: 'zed', supported: true, removed: [] }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
await reuseZedWorkspace({ projectRoot, worktreeDir, remainingBranches })
|
|
143
|
+
|
|
144
|
+
return { provider: 'zed', supported: true, removed: [] }
|
|
145
|
+
}
|
|
@@ -26,7 +26,16 @@ export interface AddIdeWorktreeFoldersOutcome {
|
|
|
26
26
|
|
|
27
27
|
export interface RemoveIdeWorktreeFoldersOutcome {
|
|
28
28
|
provider: IdeProvider
|
|
29
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* True when the provider can reflect a removal. Both providers now report
|
|
31
|
+
* `true`; `false` is reserved for a future provider with no remove capability.
|
|
32
|
+
*/
|
|
30
33
|
supported: boolean
|
|
34
|
+
/**
|
|
35
|
+
* Folders confirmed removed. For Cursor this is the real diff of the
|
|
36
|
+
* `.code-workspace` `folders` array. For Zed it is ALWAYS `[]`: the only
|
|
37
|
+
* mechanism (`zed --reuse`) replaces the whole window without producing a
|
|
38
|
+
* diff, so it confirms no specific removal even when it refreshed the window.
|
|
39
|
+
*/
|
|
31
40
|
removed: string[]
|
|
32
41
|
}
|
|
@@ -37,7 +37,6 @@ describe('openZedWorkspace', () => {
|
|
|
37
37
|
projectRoot: '/repo',
|
|
38
38
|
worktreeDir: '/repo.worktrees',
|
|
39
39
|
currentBranches: ['release/v1.0.0', 'release/v1.1.0'],
|
|
40
|
-
skipRelaunchWhenEmpty: false,
|
|
41
40
|
})
|
|
42
41
|
|
|
43
42
|
expect(zx.calls).toHaveLength(1)
|
|
@@ -45,25 +44,11 @@ describe('openZedWorkspace', () => {
|
|
|
45
44
|
expect(outcome).toEqual({ ran: true, added: 2, removed: 0 })
|
|
46
45
|
})
|
|
47
46
|
|
|
48
|
-
it('
|
|
47
|
+
it('skips launching when there are no worktrees', async () => {
|
|
49
48
|
const outcome = await openZedWorkspace({
|
|
50
49
|
projectRoot: '/repo',
|
|
51
50
|
worktreeDir: '/repo.worktrees',
|
|
52
51
|
currentBranches: [],
|
|
53
|
-
skipRelaunchWhenEmpty: false,
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
expect(zx.calls).toHaveLength(1)
|
|
57
|
-
expect(zx.calls[0]?.[0]).toEqual(['/repo'])
|
|
58
|
-
expect(outcome).toEqual({ ran: true, added: 0, removed: 0 })
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
it('skips launching when skipRelaunchWhenEmpty is set and there are no worktrees', async () => {
|
|
62
|
-
const outcome = await openZedWorkspace({
|
|
63
|
-
projectRoot: '/repo',
|
|
64
|
-
worktreeDir: '/repo.worktrees',
|
|
65
|
-
currentBranches: [],
|
|
66
|
-
skipRelaunchWhenEmpty: true,
|
|
67
52
|
})
|
|
68
53
|
|
|
69
54
|
expect(zx.calls).toHaveLength(0)
|
|
@@ -77,7 +62,6 @@ describe('openZedWorkspace', () => {
|
|
|
77
62
|
projectRoot: '/repo',
|
|
78
63
|
worktreeDir: '/repo.worktrees',
|
|
79
64
|
currentBranches: ['release/v1.0.0'],
|
|
80
|
-
skipRelaunchWhenEmpty: false,
|
|
81
65
|
})
|
|
82
66
|
|
|
83
67
|
expect(outcome).toEqual({ ran: false, added: 0, removed: 0 })
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { reuseZedWorkspace } from '../reuse-zed-workspace'
|
|
4
|
+
|
|
5
|
+
const zx = vi.hoisted(() => {
|
|
6
|
+
return { calls: [] as unknown[][], shouldThrow: false }
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
vi.mock('zx', () => {
|
|
10
|
+
return {
|
|
11
|
+
$: vi.fn((_strings: TemplateStringsArray, ...values: unknown[]) => {
|
|
12
|
+
zx.calls.push(values)
|
|
13
|
+
|
|
14
|
+
if (zx.shouldThrow) {
|
|
15
|
+
return Promise.reject(new Error('zed failed to launch'))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return Promise.resolve({ stdout: '' })
|
|
19
|
+
}),
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const log = vi.hoisted(() => {
|
|
24
|
+
return { info: vi.fn(), warn: vi.fn(), error: vi.fn() }
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
vi.mock('src/lib/logger', () => {
|
|
28
|
+
return { logger: log }
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
describe('reuseZedWorkspace', () => {
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
zx.calls = []
|
|
34
|
+
zx.shouldThrow = false
|
|
35
|
+
vi.clearAllMocks()
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('runs `zed --reuse` with the project root plus the remaining worktree paths', async () => {
|
|
39
|
+
const outcome = await reuseZedWorkspace({
|
|
40
|
+
projectRoot: '/repo',
|
|
41
|
+
worktreeDir: '/repo.worktrees',
|
|
42
|
+
remainingBranches: ['release/v1.0.0', 'release/v1.1.0'],
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
expect(zx.calls).toHaveLength(1)
|
|
46
|
+
expect(zx.calls[0]?.[0]).toEqual(['/repo', '/repo.worktrees/release/v1.0.0', '/repo.worktrees/release/v1.1.0'])
|
|
47
|
+
expect(outcome).toEqual({ ran: true })
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('collapses to root-only when no worktrees remain', async () => {
|
|
51
|
+
const outcome = await reuseZedWorkspace({
|
|
52
|
+
projectRoot: '/repo',
|
|
53
|
+
worktreeDir: '/repo.worktrees',
|
|
54
|
+
remainingBranches: [],
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
expect(zx.calls).toHaveLength(1)
|
|
58
|
+
expect(zx.calls[0]?.[0]).toEqual(['/repo'])
|
|
59
|
+
expect(outcome).toEqual({ ran: true })
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('emits a disclosure log line warning that other open folders are not preserved', async () => {
|
|
63
|
+
await reuseZedWorkspace({
|
|
64
|
+
projectRoot: '/repo',
|
|
65
|
+
worktreeDir: '/repo.worktrees',
|
|
66
|
+
remainingBranches: ['release/v1.0.0'],
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
expect(log.info).toHaveBeenCalledWith(
|
|
70
|
+
expect.stringContaining('other folders in the focused window are not preserved'),
|
|
71
|
+
)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('swallows a launch failure into a best-effort warning', async () => {
|
|
75
|
+
zx.shouldThrow = true
|
|
76
|
+
|
|
77
|
+
const outcome = await reuseZedWorkspace({
|
|
78
|
+
projectRoot: '/repo',
|
|
79
|
+
worktreeDir: '/repo.worktrees',
|
|
80
|
+
remainingBranches: ['release/v1.0.0'],
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
expect(outcome).toEqual({ ran: false })
|
|
84
|
+
expect(log.warn).toHaveBeenCalled()
|
|
85
|
+
})
|
|
86
|
+
})
|
|
@@ -6,12 +6,6 @@ interface OpenZedWorkspaceArgs {
|
|
|
6
6
|
projectRoot: string
|
|
7
7
|
worktreeDir: string
|
|
8
8
|
currentBranches: string[]
|
|
9
|
-
/**
|
|
10
|
-
* When true, skip launching Zed if there are no release worktrees. Lets
|
|
11
|
-
* `worktrees-reload` avoid popping a bare Zed window while keeping the
|
|
12
|
-
* cold-start `worktrees-open` behaviour (launch unconditionally) intact.
|
|
13
|
-
*/
|
|
14
|
-
skipRelaunchWhenEmpty: boolean
|
|
15
9
|
}
|
|
16
10
|
|
|
17
11
|
interface OpenZedWorkspaceOutcome {
|
|
@@ -23,8 +17,8 @@ interface OpenZedWorkspaceOutcome {
|
|
|
23
17
|
/**
|
|
24
18
|
* Opens a single Zed workspace containing the project root plus every release
|
|
25
19
|
* worktree, via one `zed <root> <wt...>` invocation (Zed realizes a multi-folder
|
|
26
|
-
* workspace from multiple path arguments).
|
|
27
|
-
*
|
|
20
|
+
* workspace from multiple path arguments). Used by `worktrees-reload`; skips the
|
|
21
|
+
* launch when there are no worktrees so it never pops a bare Zed window.
|
|
28
22
|
*
|
|
29
23
|
* Zed has no portable workspace file and no folder-remove CLI, so there is
|
|
30
24
|
* nothing to reconcile: `added` is the number of worktrees opened and `removed`
|
|
@@ -32,9 +26,9 @@ interface OpenZedWorkspaceOutcome {
|
|
|
32
26
|
* best-effort, mirroring the Cursor integration.
|
|
33
27
|
*/
|
|
34
28
|
export const openZedWorkspace = async (args: OpenZedWorkspaceArgs): Promise<OpenZedWorkspaceOutcome> => {
|
|
35
|
-
const { projectRoot, worktreeDir, currentBranches
|
|
29
|
+
const { projectRoot, worktreeDir, currentBranches } = args
|
|
36
30
|
|
|
37
|
-
if (
|
|
31
|
+
if (currentBranches.length === 0) {
|
|
38
32
|
return { ran: false, added: 0, removed: 0 }
|
|
39
33
|
}
|
|
40
34
|
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { $ } from 'zx'
|
|
2
|
+
|
|
3
|
+
import { logger } from 'src/lib/logger'
|
|
4
|
+
|
|
5
|
+
interface ReuseZedWorkspaceArgs {
|
|
6
|
+
projectRoot: string
|
|
7
|
+
worktreeDir: string
|
|
8
|
+
remainingBranches: string[]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface ReuseZedWorkspaceOutcome {
|
|
12
|
+
ran: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Reflects a worktree removal in the open Zed window by running
|
|
17
|
+
* `zed --reuse <root> <remaining release worktrees...>`.
|
|
18
|
+
*
|
|
19
|
+
* IMPORTANT — this is destructive: `--reuse` REPLACES the focused window's
|
|
20
|
+
* ENTIRE folder set with exactly these paths. The remaining set is built from
|
|
21
|
+
* release worktrees only (`getCurrentWorktrees('release')`), so any other folder
|
|
22
|
+
* open in that window — feature worktrees, scratch dirs, user-added dirs — is
|
|
23
|
+
* DROPPED and is NOT recoverable by infra-kit (Zed offers no undo and we keep no
|
|
24
|
+
* record of the prior set). The caller MUST therefore only invoke this on an
|
|
25
|
+
* interactive path where a human is present and can re-add folders via
|
|
26
|
+
* `zed --add`. We emit a disclosure log line saying as much.
|
|
27
|
+
*
|
|
28
|
+
* Distinct from `openZedWorkspace`, which runs `zed <paths>` WITHOUT `--reuse`
|
|
29
|
+
* to realize a multi-folder workspace; that does not replace a focused window.
|
|
30
|
+
*
|
|
31
|
+
* Best-effort: any launch failure is swallowed into a warning, mirroring
|
|
32
|
+
* `openZedWorkspace`. Root is always included so the path list is never empty.
|
|
33
|
+
*/
|
|
34
|
+
export const reuseZedWorkspace = async (args: ReuseZedWorkspaceArgs): Promise<ReuseZedWorkspaceOutcome> => {
|
|
35
|
+
const { projectRoot, worktreeDir, remainingBranches } = args
|
|
36
|
+
|
|
37
|
+
const paths = [
|
|
38
|
+
projectRoot,
|
|
39
|
+
...remainingBranches.map((branch) => {
|
|
40
|
+
return `${worktreeDir}/${branch}`
|
|
41
|
+
}),
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
await $`zed --reuse ${paths}`
|
|
46
|
+
|
|
47
|
+
logger.info(
|
|
48
|
+
`↻ Refreshed Zed workspace to root + ${remainingBranches.length} release worktree(s) (other folders in the focused window are not preserved)`,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
return { ran: true }
|
|
52
|
+
} catch (error) {
|
|
53
|
+
logger.warn({ error }, '⚠️ Failed to refresh Zed workspace')
|
|
54
|
+
|
|
55
|
+
return { ran: false }
|
|
56
|
+
}
|
|
57
|
+
}
|