@prover-coder-ai/docker-git 1.0.11 → 1.0.13
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/.package.json.release.bak +1 -1
- package/CHANGELOG.md +12 -0
- package/README.md +5 -1
- package/dist/main.js.map +1 -1
- package/dist/src/docker-git/cli/parser-mcp-playwright.js +18 -0
- package/dist/src/docker-git/cli/parser.js +3 -2
- package/dist/src/docker-git/cli/usage.js +2 -0
- package/dist/src/docker-git/menu-render-select.js +93 -0
- package/dist/src/docker-git/menu-render.js +24 -60
- package/dist/src/docker-git/menu-select-connect.js +6 -0
- package/dist/src/docker-git/menu-select-runtime.js +50 -0
- package/dist/src/docker-git/menu-select.js +56 -18
- package/dist/src/docker-git/menu.js +9 -1
- package/dist/src/docker-git/program.js +2 -1
- package/package.json +1 -1
- package/src/docker-git/cli/parser-mcp-playwright.ts +25 -0
- package/src/docker-git/cli/parser.ts +7 -3
- package/src/docker-git/cli/usage.ts +2 -0
- package/src/docker-git/menu-render-select.ts +187 -0
- package/src/docker-git/menu-render.ts +53 -102
- package/src/docker-git/menu-select-connect.ts +27 -0
- package/src/docker-git/menu-select-runtime.ts +94 -0
- package/src/docker-git/menu-select.ts +107 -39
- package/src/docker-git/menu-types.ts +7 -0
- package/src/docker-git/menu.ts +9 -7
- package/src/docker-git/program.ts +5 -1
- package/tests/docker-git/menu-select-connect.test.ts +64 -0
- package/tests/docker-git/parser.test.ts +28 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { runDockerComposeDown } from "@effect-template/lib/shell/docker"
|
|
2
2
|
import type { AppError } from "@effect-template/lib/usecases/errors"
|
|
3
|
+
import { mcpPlaywrightUp } from "@effect-template/lib/usecases/mcp-playwright"
|
|
3
4
|
import {
|
|
4
5
|
connectProjectSshWithUp,
|
|
5
6
|
deleteDockerGitProject,
|
|
@@ -9,19 +10,17 @@ import {
|
|
|
9
10
|
|
|
10
11
|
import { Effect, Match, pipe } from "effect"
|
|
11
12
|
|
|
13
|
+
import { buildConnectEffect, isConnectMcpToggleInput } from "./menu-select-connect.js"
|
|
14
|
+
import { loadRuntimeByProject, runtimeForSelection } from "./menu-select-runtime.js"
|
|
12
15
|
import { resetToMenu, resumeTui, suspendTui } from "./menu-shared.js"
|
|
13
|
-
import type {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// PURITY: SHELL
|
|
22
|
-
// EFFECT: Effect<void, never, never>
|
|
23
|
-
// INVARIANT: selected index always within items length
|
|
24
|
-
// COMPLEXITY: O(1) per keypress
|
|
16
|
+
import type {
|
|
17
|
+
MenuEnv,
|
|
18
|
+
MenuKeyInput,
|
|
19
|
+
MenuRunner,
|
|
20
|
+
MenuViewContext,
|
|
21
|
+
SelectProjectRuntime,
|
|
22
|
+
ViewState
|
|
23
|
+
} from "./menu-types.js"
|
|
25
24
|
|
|
26
25
|
type SelectContext = MenuViewContext & {
|
|
27
26
|
readonly activeDir: string | null
|
|
@@ -30,13 +29,24 @@ type SelectContext = MenuViewContext & {
|
|
|
30
29
|
readonly setSkipInputs: (update: (value: number) => number) => void
|
|
31
30
|
}
|
|
32
31
|
|
|
32
|
+
const emptyRuntimeByProject = (): Readonly<Record<string, SelectProjectRuntime>> => ({})
|
|
33
|
+
|
|
33
34
|
export const startSelectView = (
|
|
34
35
|
items: ReadonlyArray<ProjectItem>,
|
|
35
36
|
purpose: "Connect" | "Down" | "Info" | "Delete",
|
|
36
|
-
context: Pick<SelectContext, "setView" | "setMessage"
|
|
37
|
+
context: Pick<SelectContext, "setView" | "setMessage">,
|
|
38
|
+
runtimeByProject: Readonly<Record<string, SelectProjectRuntime>> = emptyRuntimeByProject()
|
|
37
39
|
) => {
|
|
38
40
|
context.setMessage(null)
|
|
39
|
-
context.setView({
|
|
41
|
+
context.setView({
|
|
42
|
+
_tag: "SelectProject",
|
|
43
|
+
purpose,
|
|
44
|
+
items,
|
|
45
|
+
runtimeByProject,
|
|
46
|
+
selected: 0,
|
|
47
|
+
confirmDelete: false,
|
|
48
|
+
connectEnableMcpPlaywright: false
|
|
49
|
+
})
|
|
40
50
|
}
|
|
41
51
|
|
|
42
52
|
const clampIndex = (value: number, size: number): number => {
|
|
@@ -62,6 +72,9 @@ export const handleSelectInput = (
|
|
|
62
72
|
resetToMenu(context)
|
|
63
73
|
return
|
|
64
74
|
}
|
|
75
|
+
if (handleConnectOptionToggle(input, view, context)) {
|
|
76
|
+
return
|
|
77
|
+
}
|
|
65
78
|
if (handleSelectNavigation(key, view, context)) {
|
|
66
79
|
return
|
|
67
80
|
}
|
|
@@ -69,7 +82,27 @@ export const handleSelectInput = (
|
|
|
69
82
|
handleSelectReturn(view, context)
|
|
70
83
|
return
|
|
71
84
|
}
|
|
72
|
-
|
|
85
|
+
if (input.trim().length > 0) {
|
|
86
|
+
context.setMessage("Use arrows + Enter to select a project, Esc to cancel.")
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const handleConnectOptionToggle = (
|
|
91
|
+
input: string,
|
|
92
|
+
view: Extract<ViewState, { readonly _tag: "SelectProject" }>,
|
|
93
|
+
context: Pick<SelectContext, "setView" | "setMessage">
|
|
94
|
+
): boolean => {
|
|
95
|
+
if (view.purpose !== "Connect" || !isConnectMcpToggleInput(input)) {
|
|
96
|
+
return false
|
|
97
|
+
}
|
|
98
|
+
const nextValue = !view.connectEnableMcpPlaywright
|
|
99
|
+
context.setView({ ...view, connectEnableMcpPlaywright: nextValue, confirmDelete: false })
|
|
100
|
+
context.setMessage(
|
|
101
|
+
nextValue
|
|
102
|
+
? "Playwright MCP will be enabled before SSH (press Enter to connect)."
|
|
103
|
+
: "Playwright MCP toggle is OFF (press Enter to connect without changes)."
|
|
104
|
+
)
|
|
105
|
+
return true
|
|
73
106
|
}
|
|
74
107
|
|
|
75
108
|
const handleSelectNavigation = (
|
|
@@ -116,12 +149,30 @@ const runWithSuspendedTui = (
|
|
|
116
149
|
)
|
|
117
150
|
}
|
|
118
151
|
|
|
119
|
-
const runConnectSelection = (
|
|
120
|
-
|
|
152
|
+
const runConnectSelection = (
|
|
153
|
+
selected: ProjectItem,
|
|
154
|
+
context: SelectContext,
|
|
155
|
+
enableMcpPlaywright: boolean
|
|
156
|
+
) => {
|
|
157
|
+
context.setMessage(
|
|
158
|
+
enableMcpPlaywright
|
|
159
|
+
? `Enabling Playwright MCP for ${selected.displayName}, then connecting...`
|
|
160
|
+
: `Connecting to ${selected.displayName}...`
|
|
161
|
+
)
|
|
121
162
|
context.setSshActive(true)
|
|
122
163
|
runWithSuspendedTui(
|
|
123
164
|
context,
|
|
124
|
-
|
|
165
|
+
buildConnectEffect(selected, enableMcpPlaywright, {
|
|
166
|
+
connectWithUp: (item) =>
|
|
167
|
+
connectProjectSshWithUp(item).pipe(
|
|
168
|
+
Effect.mapError((error): AppError => error)
|
|
169
|
+
),
|
|
170
|
+
enableMcpPlaywright: (projectDir) =>
|
|
171
|
+
mcpPlaywrightUp({ _tag: "McpPlaywrightUp", projectDir, runUp: false }).pipe(
|
|
172
|
+
Effect.asVoid,
|
|
173
|
+
Effect.mapError((error): AppError => error)
|
|
174
|
+
)
|
|
175
|
+
}),
|
|
125
176
|
() => {
|
|
126
177
|
context.setSshActive(false)
|
|
127
178
|
},
|
|
@@ -136,14 +187,20 @@ const runDownSelection = (selected: ProjectItem, context: SelectContext) => {
|
|
|
136
187
|
Effect.sync(suspendTui),
|
|
137
188
|
Effect.zipRight(runDockerComposeDown(selected.projectDir)),
|
|
138
189
|
Effect.zipRight(listRunningProjectItems),
|
|
139
|
-
Effect.
|
|
190
|
+
Effect.flatMap((items) =>
|
|
191
|
+
pipe(
|
|
192
|
+
loadRuntimeByProject(items),
|
|
193
|
+
Effect.map((runtimeByProject) => ({ items, runtimeByProject }))
|
|
194
|
+
)
|
|
195
|
+
),
|
|
196
|
+
Effect.tap(({ items, runtimeByProject }) =>
|
|
140
197
|
Effect.sync(() => {
|
|
141
198
|
if (items.length === 0) {
|
|
142
199
|
resetToMenu(context)
|
|
143
200
|
context.setMessage("No running docker-git containers.")
|
|
144
201
|
return
|
|
145
202
|
}
|
|
146
|
-
startSelectView(items, "Down", context)
|
|
203
|
+
startSelectView(items, "Down", context, runtimeByProject)
|
|
147
204
|
context.setMessage("Container stopped. Select another to stop, or Esc to return.")
|
|
148
205
|
})
|
|
149
206
|
),
|
|
@@ -193,13 +250,24 @@ const handleSelectReturn = (
|
|
|
193
250
|
resetToMenu(context)
|
|
194
251
|
return
|
|
195
252
|
}
|
|
253
|
+
const selectedRuntime = runtimeForSelection(view, selected)
|
|
254
|
+
const sshSessionsLabel = selectedRuntime.sshSessions === 1
|
|
255
|
+
? "1 active SSH session"
|
|
256
|
+
: `${selectedRuntime.sshSessions} active SSH sessions`
|
|
196
257
|
|
|
197
258
|
Match.value(view.purpose).pipe(
|
|
198
259
|
Match.when("Connect", () => {
|
|
199
260
|
context.setActiveDir(selected.projectDir)
|
|
200
|
-
runConnectSelection(selected, context)
|
|
261
|
+
runConnectSelection(selected, context, view.connectEnableMcpPlaywright)
|
|
201
262
|
}),
|
|
202
263
|
Match.when("Down", () => {
|
|
264
|
+
if (selectedRuntime.sshSessions > 0 && !view.confirmDelete) {
|
|
265
|
+
context.setMessage(
|
|
266
|
+
`${selected.containerName} has ${sshSessionsLabel}. Press Enter again to stop, Esc to cancel.`
|
|
267
|
+
)
|
|
268
|
+
context.setView({ ...view, confirmDelete: true })
|
|
269
|
+
return
|
|
270
|
+
}
|
|
203
271
|
context.setActiveDir(selected.projectDir)
|
|
204
272
|
runDownSelection(selected, context)
|
|
205
273
|
}),
|
|
@@ -209,8 +277,9 @@ const handleSelectReturn = (
|
|
|
209
277
|
}),
|
|
210
278
|
Match.when("Delete", () => {
|
|
211
279
|
if (!view.confirmDelete) {
|
|
280
|
+
const activeSshWarning = selectedRuntime.sshSessions > 0 ? ` ${sshSessionsLabel}.` : ""
|
|
212
281
|
context.setMessage(
|
|
213
|
-
`Really delete ${selected.displayName}
|
|
282
|
+
`Really delete ${selected.displayName}?${activeSshWarning} Press Enter again to confirm, Esc to cancel.`
|
|
214
283
|
)
|
|
215
284
|
context.setView({ ...view, confirmDelete: true })
|
|
216
285
|
return
|
|
@@ -221,12 +290,6 @@ const handleSelectReturn = (
|
|
|
221
290
|
)
|
|
222
291
|
}
|
|
223
292
|
|
|
224
|
-
const handleSelectHint = (input: string, context: SelectContext) => {
|
|
225
|
-
if (input.trim().length > 0) {
|
|
226
|
-
context.setMessage("Use arrows + Enter to select a project, Esc to cancel.")
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
293
|
export const loadSelectView = <E>(
|
|
231
294
|
effect: Effect.Effect<ReadonlyArray<ProjectItem>, E, MenuEnv>,
|
|
232
295
|
purpose: "Connect" | "Down" | "Info" | "Delete",
|
|
@@ -235,16 +298,21 @@ export const loadSelectView = <E>(
|
|
|
235
298
|
pipe(
|
|
236
299
|
effect,
|
|
237
300
|
Effect.flatMap((items) =>
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
301
|
+
pipe(
|
|
302
|
+
loadRuntimeByProject(items),
|
|
303
|
+
Effect.flatMap((runtimeByProject) =>
|
|
304
|
+
Effect.sync(() => {
|
|
305
|
+
if (items.length === 0) {
|
|
306
|
+
context.setMessage(
|
|
307
|
+
purpose === "Down"
|
|
308
|
+
? "No running docker-git containers."
|
|
309
|
+
: "No docker-git projects found."
|
|
310
|
+
)
|
|
311
|
+
return
|
|
312
|
+
}
|
|
313
|
+
startSelectView(items, purpose, context, runtimeByProject)
|
|
314
|
+
})
|
|
315
|
+
)
|
|
316
|
+
)
|
|
249
317
|
)
|
|
250
318
|
)
|
|
@@ -77,10 +77,17 @@ export type ViewState =
|
|
|
77
77
|
readonly _tag: "SelectProject"
|
|
78
78
|
readonly purpose: "Connect" | "Down" | "Info" | "Delete"
|
|
79
79
|
readonly items: ReadonlyArray<ProjectItem>
|
|
80
|
+
readonly runtimeByProject: Readonly<Record<string, SelectProjectRuntime>>
|
|
80
81
|
readonly selected: number
|
|
81
82
|
readonly confirmDelete: boolean
|
|
83
|
+
readonly connectEnableMcpPlaywright: boolean
|
|
82
84
|
}
|
|
83
85
|
|
|
86
|
+
export type SelectProjectRuntime = {
|
|
87
|
+
readonly running: boolean
|
|
88
|
+
readonly sshSessions: number
|
|
89
|
+
}
|
|
90
|
+
|
|
84
91
|
export const menuItems: ReadonlyArray<{ readonly id: MenuAction; readonly label: string }> = [
|
|
85
92
|
{ id: { _tag: "Create" }, label: "Create project" },
|
|
86
93
|
{ id: { _tag: "Select" }, label: "Select project" },
|
package/src/docker-git/menu.ts
CHANGED
|
@@ -185,13 +185,15 @@ const renderView = (context: RenderContext) => {
|
|
|
185
185
|
return renderCreate(label, context.view.buffer, context.message, context.view.step, currentDefaults)
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
-
return renderSelect(
|
|
189
|
-
context.view.purpose,
|
|
190
|
-
context.view.items,
|
|
191
|
-
context.view.selected,
|
|
192
|
-
context.view.
|
|
193
|
-
context.
|
|
194
|
-
|
|
188
|
+
return renderSelect({
|
|
189
|
+
purpose: context.view.purpose,
|
|
190
|
+
items: context.view.items,
|
|
191
|
+
selected: context.view.selected,
|
|
192
|
+
runtimeByProject: context.view.runtimeByProject,
|
|
193
|
+
confirmDelete: context.view.confirmDelete,
|
|
194
|
+
connectEnableMcpPlaywright: context.view.connectEnableMcpPlaywright,
|
|
195
|
+
message: context.message
|
|
196
|
+
})
|
|
195
197
|
}
|
|
196
198
|
|
|
197
199
|
const useMenuState = () => {
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from "@effect-template/lib/usecases/auth"
|
|
11
11
|
import type { AppError } from "@effect-template/lib/usecases/errors"
|
|
12
12
|
import { renderError } from "@effect-template/lib/usecases/errors"
|
|
13
|
+
import { mcpPlaywrightUp } from "@effect-template/lib/usecases/mcp-playwright"
|
|
13
14
|
import { downAllDockerGitProjects, listProjectStatus } from "@effect-template/lib/usecases/projects"
|
|
14
15
|
import { exportScrap, importScrap } from "@effect-template/lib/usecases/scrap"
|
|
15
16
|
import {
|
|
@@ -92,7 +93,10 @@ const handleNonBaseCommand = (command: NonBaseCommand) =>
|
|
|
92
93
|
Match.when({ _tag: "ScrapExport" }, (cmd) => exportScrap(cmd)),
|
|
93
94
|
Match.when({ _tag: "ScrapImport" }, (cmd) => importScrap(cmd))
|
|
94
95
|
)
|
|
95
|
-
.pipe(
|
|
96
|
+
.pipe(
|
|
97
|
+
Match.when({ _tag: "McpPlaywrightUp" }, (cmd) => mcpPlaywrightUp(cmd)),
|
|
98
|
+
Match.exhaustive
|
|
99
|
+
)
|
|
96
100
|
|
|
97
101
|
// CHANGE: compose CLI program with typed errors and shell effects
|
|
98
102
|
// WHY: keep a thin entry layer over pure parsing and template generation
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Effect } from "effect"
|
|
2
|
+
import { describe, expect, it } from "vitest"
|
|
3
|
+
|
|
4
|
+
import type { ProjectItem } from "@effect-template/lib/usecases/projects"
|
|
5
|
+
import { selectHint } from "../../src/docker-git/menu-render-select.js"
|
|
6
|
+
import { buildConnectEffect, isConnectMcpToggleInput } from "../../src/docker-git/menu-select-connect.js"
|
|
7
|
+
|
|
8
|
+
const makeProjectItem = (): ProjectItem => ({
|
|
9
|
+
projectDir: "/home/dev/provercoderai/docker-git/workspaces/org/repo",
|
|
10
|
+
displayName: "org/repo",
|
|
11
|
+
repoUrl: "https://github.com/org/repo.git",
|
|
12
|
+
repoRef: "main",
|
|
13
|
+
containerName: "dg-repo",
|
|
14
|
+
serviceName: "dg-repo",
|
|
15
|
+
sshUser: "dev",
|
|
16
|
+
sshPort: 2222,
|
|
17
|
+
targetDir: "/home/dev/org/repo",
|
|
18
|
+
sshCommand: "ssh -p 2222 dev@localhost",
|
|
19
|
+
sshKeyPath: null,
|
|
20
|
+
authorizedKeysPath: "/home/dev/provercoderai/docker-git/workspaces/org/repo/.docker-git/authorized_keys",
|
|
21
|
+
authorizedKeysExists: true,
|
|
22
|
+
envGlobalPath: "/home/dev/provercoderai/docker-git/.orch/env/global.env",
|
|
23
|
+
envProjectPath: "/home/dev/provercoderai/docker-git/workspaces/org/repo/.orch/env/project.env",
|
|
24
|
+
codexAuthPath: "/home/dev/provercoderai/docker-git/.orch/auth/codex",
|
|
25
|
+
codexHome: "/home/dev/.codex"
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const record = (events: Array<string>, entry: string): Effect.Effect<void> =>
|
|
29
|
+
Effect.sync(() => {
|
|
30
|
+
events.push(entry)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const makeConnectDeps = (events: Array<string>) => ({
|
|
34
|
+
connectWithUp: (selected: ProjectItem) => record(events, `connect:${selected.projectDir}`),
|
|
35
|
+
enableMcpPlaywright: (projectDir: string) => record(events, `enable:${projectDir}`)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
describe("menu-select-connect", () => {
|
|
39
|
+
it("runs Playwright enable before SSH when toggle is ON", () => {
|
|
40
|
+
const item = makeProjectItem()
|
|
41
|
+
const events: Array<string> = []
|
|
42
|
+
Effect.runSync(buildConnectEffect(item, true, makeConnectDeps(events)))
|
|
43
|
+
expect(events).toEqual([`enable:${item.projectDir}`, `connect:${item.projectDir}`])
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it("skips Playwright enable when toggle is OFF", () => {
|
|
47
|
+
const item = makeProjectItem()
|
|
48
|
+
const events: Array<string> = []
|
|
49
|
+
Effect.runSync(buildConnectEffect(item, false, makeConnectDeps(events)))
|
|
50
|
+
expect(events).toEqual([`connect:${item.projectDir}`])
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it("parses connect toggle key from user input", () => {
|
|
54
|
+
expect(isConnectMcpToggleInput("p")).toBe(true)
|
|
55
|
+
expect(isConnectMcpToggleInput(" P ")).toBe(true)
|
|
56
|
+
expect(isConnectMcpToggleInput("x")).toBe(false)
|
|
57
|
+
expect(isConnectMcpToggleInput("")).toBe(false)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it("renders connect hint with current Playwright toggle state", () => {
|
|
61
|
+
expect(selectHint("Connect", true)).toContain("toggle Playwright MCP (on)")
|
|
62
|
+
expect(selectHint("Connect", false)).toContain("toggle Playwright MCP (off)")
|
|
63
|
+
})
|
|
64
|
+
})
|
|
@@ -152,6 +152,34 @@ describe("parseArgs", () => {
|
|
|
152
152
|
expect(command.projectDir).toBe(".docker-git/org/repo/issue-7")
|
|
153
153
|
}))
|
|
154
154
|
|
|
155
|
+
it.effect("parses mcp-playwright command in current directory", () =>
|
|
156
|
+
Effect.sync(() => {
|
|
157
|
+
const command = parseOrThrow(["mcp-playwright"])
|
|
158
|
+
if (command._tag !== "McpPlaywrightUp") {
|
|
159
|
+
throw new Error("expected McpPlaywrightUp command")
|
|
160
|
+
}
|
|
161
|
+
expect(command.projectDir).toBe(".")
|
|
162
|
+
expect(command.runUp).toBe(true)
|
|
163
|
+
}))
|
|
164
|
+
|
|
165
|
+
it.effect("parses mcp-playwright command with --no-up", () =>
|
|
166
|
+
Effect.sync(() => {
|
|
167
|
+
const command = parseOrThrow(["mcp-playwright", "--no-up"])
|
|
168
|
+
if (command._tag !== "McpPlaywrightUp") {
|
|
169
|
+
throw new Error("expected McpPlaywrightUp command")
|
|
170
|
+
}
|
|
171
|
+
expect(command.runUp).toBe(false)
|
|
172
|
+
}))
|
|
173
|
+
|
|
174
|
+
it.effect("parses mcp-playwright with positional repo url into project dir", () =>
|
|
175
|
+
Effect.sync(() => {
|
|
176
|
+
const command = parseOrThrow(["mcp-playwright", "https://github.com/org/repo.git"])
|
|
177
|
+
if (command._tag !== "McpPlaywrightUp") {
|
|
178
|
+
throw new Error("expected McpPlaywrightUp command")
|
|
179
|
+
}
|
|
180
|
+
expect(command.projectDir).toBe(".docker-git/org/repo")
|
|
181
|
+
}))
|
|
182
|
+
|
|
155
183
|
it.effect("parses down-all command", () =>
|
|
156
184
|
Effect.sync(() => {
|
|
157
185
|
const command = parseOrThrow(["down-all"])
|