@prover-coder-ai/docker-git 1.0.21 → 1.0.23
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 +5 -3
- package/CHANGELOG.md +12 -0
- package/README.md +31 -1
- package/dist/src/docker-git/main.js +10259 -12
- package/dist/src/docker-git/main.js.map +1 -0
- package/package.json +4 -4
- package/src/app/program.ts +16 -13
- package/src/docker-git/cli/parser-options.ts +6 -0
- package/src/docker-git/cli/parser.ts +1 -0
- package/src/docker-git/cli/usage.ts +9 -4
- package/src/docker-git/menu-actions.ts +5 -2
- package/src/docker-git/menu-create.ts +9 -13
- package/src/docker-git/menu-render.ts +1 -1
- package/tests/docker-git/parser-helpers.ts +76 -0
- package/tests/docker-git/parser-network-options.test.ts +47 -0
- package/tests/docker-git/parser.test.ts +30 -71
- package/vite.docker-git.config.ts +34 -0
- package/dist/main.js +0 -930
- package/dist/main.js.map +0 -1
- package/dist/src/app/main.js +0 -15
- package/dist/src/app/program.js +0 -61
- package/dist/src/docker-git/cli/input.js +0 -21
- package/dist/src/docker-git/cli/parser-apply.js +0 -22
- package/dist/src/docker-git/cli/parser-attach.js +0 -19
- package/dist/src/docker-git/cli/parser-auth.js +0 -90
- package/dist/src/docker-git/cli/parser-clone.js +0 -40
- package/dist/src/docker-git/cli/parser-create.js +0 -1
- package/dist/src/docker-git/cli/parser-mcp-playwright.js +0 -18
- package/dist/src/docker-git/cli/parser-options.js +0 -134
- package/dist/src/docker-git/cli/parser-panes.js +0 -19
- package/dist/src/docker-git/cli/parser-scrap.js +0 -74
- package/dist/src/docker-git/cli/parser-sessions.js +0 -69
- package/dist/src/docker-git/cli/parser-shared.js +0 -26
- package/dist/src/docker-git/cli/parser-state.js +0 -62
- package/dist/src/docker-git/cli/parser.js +0 -47
- package/dist/src/docker-git/cli/read-command.js +0 -17
- package/dist/src/docker-git/cli/usage.js +0 -113
- package/dist/src/docker-git/menu-actions.js +0 -135
- package/dist/src/docker-git/menu-auth-data.js +0 -90
- package/dist/src/docker-git/menu-auth-helpers.js +0 -20
- package/dist/src/docker-git/menu-auth.js +0 -159
- package/dist/src/docker-git/menu-buffer-input.js +0 -9
- package/dist/src/docker-git/menu-create.js +0 -199
- package/dist/src/docker-git/menu-input-handler.js +0 -109
- package/dist/src/docker-git/menu-input-utils.js +0 -47
- package/dist/src/docker-git/menu-input.js +0 -2
- package/dist/src/docker-git/menu-labeled-env.js +0 -33
- package/dist/src/docker-git/menu-menu.js +0 -46
- package/dist/src/docker-git/menu-project-auth-claude.js +0 -43
- package/dist/src/docker-git/menu-project-auth-data.js +0 -165
- package/dist/src/docker-git/menu-project-auth.js +0 -124
- package/dist/src/docker-git/menu-render-auth.js +0 -45
- package/dist/src/docker-git/menu-render-common.js +0 -26
- package/dist/src/docker-git/menu-render-layout.js +0 -14
- package/dist/src/docker-git/menu-render-project-auth.js +0 -37
- package/dist/src/docker-git/menu-render-select.js +0 -129
- package/dist/src/docker-git/menu-render.js +0 -137
- package/dist/src/docker-git/menu-select-actions.js +0 -66
- package/dist/src/docker-git/menu-select-connect.js +0 -6
- package/dist/src/docker-git/menu-select-load.js +0 -12
- package/dist/src/docker-git/menu-select-order.js +0 -21
- package/dist/src/docker-git/menu-select-runtime.js +0 -82
- package/dist/src/docker-git/menu-select-view.js +0 -15
- package/dist/src/docker-git/menu-select.js +0 -98
- package/dist/src/docker-git/menu-shared.js +0 -180
- package/dist/src/docker-git/menu-startup.js +0 -57
- package/dist/src/docker-git/menu-types.js +0 -21
- package/dist/src/docker-git/menu.js +0 -226
- package/dist/src/docker-git/program.js +0 -43
- package/dist/src/docker-git/tmux.js +0 -176
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prover-coder-ai/docker-git",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.23",
|
|
4
4
|
"description": "Minimal Vite-powered TypeScript console starter using Effect",
|
|
5
5
|
"main": "dist/src/docker-git/main.js",
|
|
6
6
|
"bin": {
|
|
@@ -35,8 +35,7 @@
|
|
|
35
35
|
"@effect/schema": "^0.75.5",
|
|
36
36
|
"effect": "^3.19.14",
|
|
37
37
|
"ink": "^5.0.1",
|
|
38
|
-
"react": "^18.3.1"
|
|
39
|
-
"@effect-template/lib": "1.0.0"
|
|
38
|
+
"react": "^18.3.1"
|
|
40
39
|
},
|
|
41
40
|
"scripts": {
|
|
42
41
|
"prebuild": "pnpm -C ../lib build",
|
|
@@ -48,9 +47,10 @@
|
|
|
48
47
|
"lint:tests": "PATH=../../scripts:$PATH vibecode-linter tests/",
|
|
49
48
|
"lint:effect": "PATH=../../scripts:$PATH eslint --config eslint.effect-ts-check.config.mjs .",
|
|
50
49
|
"prebuild:docker-git": "pnpm -C ../lib build",
|
|
51
|
-
"build:docker-git": "
|
|
50
|
+
"build:docker-git": "vite build --config vite.docker-git.config.ts",
|
|
52
51
|
"check": "pnpm run typecheck",
|
|
53
52
|
"clone": "pnpm -C ../.. run clone",
|
|
53
|
+
"open": "pnpm -C ../.. run open",
|
|
54
54
|
"docker-git": "node dist/src/docker-git/main.js",
|
|
55
55
|
"list": "pnpm -C ../.. run list",
|
|
56
56
|
"prestart": "pnpm run build",
|
package/src/app/program.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { listProjects, readCloneRequest, runDockerGitClone } from "@effect-template/lib"
|
|
1
|
+
import { listProjects, readCloneRequest, runDockerGitClone, runDockerGitOpen } from "@effect-template/lib"
|
|
2
2
|
import { Console, Effect, Match, pipe } from "effect"
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Compose the CLI program as a single effect.
|
|
6
6
|
*
|
|
7
|
-
* @returns Effect that either runs docker-git clone or prints usage.
|
|
7
|
+
* @returns Effect that either runs docker-git clone/open or prints usage.
|
|
8
8
|
*
|
|
9
|
-
* @pure false - uses Console output and spawns commands when
|
|
9
|
+
* @pure false - uses Console output and spawns commands when running shortcuts
|
|
10
10
|
* @effect Console, CommandExecutor, Path
|
|
11
|
-
* @invariant forall args in Argv:
|
|
11
|
+
* @invariant forall args in Argv: shortcut(args) -> docker_git_invoked(args)
|
|
12
12
|
* @precondition true
|
|
13
|
-
* @postcondition
|
|
14
|
-
* @complexity O(build +
|
|
13
|
+
* @postcondition shortcut(args) -> docker_git_invoked(args); otherwise usage printed
|
|
14
|
+
* @complexity O(build + shortcut)
|
|
15
15
|
* @throws Never - all errors are typed in the Effect error channel
|
|
16
16
|
*/
|
|
17
17
|
// CHANGE: replace greeting demo with deterministic usage text
|
|
@@ -28,32 +28,35 @@ const usageText = [
|
|
|
28
28
|
"Usage:",
|
|
29
29
|
" pnpm docker-git",
|
|
30
30
|
" pnpm clone <repo-url> [ref]",
|
|
31
|
+
" pnpm open <repo-url>",
|
|
31
32
|
" pnpm list",
|
|
32
33
|
"",
|
|
33
34
|
"Notes:",
|
|
34
35
|
" - docker-git is the interactive TUI.",
|
|
35
|
-
" - clone builds + runs docker-git clone for you."
|
|
36
|
+
" - clone builds + runs docker-git clone for you.",
|
|
37
|
+
" - open builds + runs docker-git open for existing projects."
|
|
36
38
|
].join("\n")
|
|
37
39
|
|
|
38
40
|
// PURITY: SHELL
|
|
39
41
|
// EFFECT: Effect<void, never, Console>
|
|
40
42
|
const runHelp = Console.log(usageText)
|
|
41
43
|
|
|
42
|
-
// CHANGE: route between
|
|
43
|
-
// WHY: allow pnpm run clone <url> while keeping a single entrypoint
|
|
44
|
-
// QUOTE(ТЗ): "
|
|
44
|
+
// CHANGE: route between shortcut runners and help based on CLI context
|
|
45
|
+
// WHY: allow pnpm run clone/open <url> while keeping a single entrypoint
|
|
46
|
+
// QUOTE(ТЗ): "Добавить команду open."
|
|
45
47
|
// REF: user-request-2026-01-27
|
|
46
48
|
// SOURCE: n/a
|
|
47
|
-
// FORMAT THEOREM: forall argv:
|
|
49
|
+
// FORMAT THEOREM: forall argv: shortcut(argv) -> docker_git_invoked(argv)
|
|
48
50
|
// PURITY: SHELL
|
|
49
51
|
// EFFECT: Effect<void, Error, Console | CommandExecutor | Path>
|
|
50
|
-
// INVARIANT: help is printed when
|
|
51
|
-
// COMPLEXITY: O(build +
|
|
52
|
+
// INVARIANT: help is printed when shortcut is not requested
|
|
53
|
+
// COMPLEXITY: O(build + shortcut)
|
|
52
54
|
const runDockerGit = pipe(
|
|
53
55
|
readCloneRequest,
|
|
54
56
|
Effect.flatMap((request) =>
|
|
55
57
|
Match.value(request).pipe(
|
|
56
58
|
Match.when({ _tag: "Clone" }, ({ args }) => runDockerGitClone(args)),
|
|
59
|
+
Match.when({ _tag: "Open" }, ({ args }) => runDockerGitOpen(args)),
|
|
57
60
|
Match.when({ _tag: "None" }, () => runHelp),
|
|
58
61
|
Match.exhaustive
|
|
59
62
|
)
|
|
@@ -20,6 +20,8 @@ interface ValueOptionSpec {
|
|
|
20
20
|
| "envProjectPath"
|
|
21
21
|
| "codexAuthPath"
|
|
22
22
|
| "codexHome"
|
|
23
|
+
| "dockerNetworkMode"
|
|
24
|
+
| "dockerSharedNetworkName"
|
|
23
25
|
| "archivePath"
|
|
24
26
|
| "scrapMode"
|
|
25
27
|
| "label"
|
|
@@ -51,6 +53,8 @@ const valueOptionSpecs: ReadonlyArray<ValueOptionSpec> = [
|
|
|
51
53
|
{ flag: "--env-project", key: "envProjectPath" },
|
|
52
54
|
{ flag: "--codex-auth", key: "codexAuthPath" },
|
|
53
55
|
{ flag: "--codex-home", key: "codexHome" },
|
|
56
|
+
{ flag: "--network-mode", key: "dockerNetworkMode" },
|
|
57
|
+
{ flag: "--shared-network", key: "dockerSharedNetworkName" },
|
|
54
58
|
{ flag: "--archive", key: "archivePath" },
|
|
55
59
|
{ flag: "--mode", key: "scrapMode" },
|
|
56
60
|
{ flag: "--label", key: "label" },
|
|
@@ -102,6 +106,8 @@ const valueFlagUpdaters: { readonly [K in ValueKey]: (raw: RawOptions, value: st
|
|
|
102
106
|
envProjectPath: (raw, value) => ({ ...raw, envProjectPath: value }),
|
|
103
107
|
codexAuthPath: (raw, value) => ({ ...raw, codexAuthPath: value }),
|
|
104
108
|
codexHome: (raw, value) => ({ ...raw, codexHome: value }),
|
|
109
|
+
dockerNetworkMode: (raw, value) => ({ ...raw, dockerNetworkMode: value }),
|
|
110
|
+
dockerSharedNetworkName: (raw, value) => ({ ...raw, dockerSharedNetworkName: value }),
|
|
105
111
|
archivePath: (raw, value) => ({ ...raw, archivePath: value }),
|
|
106
112
|
scrapMode: (raw, value) => ({ ...raw, scrapMode: value }),
|
|
107
113
|
label: (raw, value) => ({ ...raw, label: value }),
|
|
@@ -75,6 +75,7 @@ export const parseArgs = (args: ReadonlyArray<string>): Either.Either<Command, P
|
|
|
75
75
|
Match.when("auth", () => parseAuth(rest))
|
|
76
76
|
)
|
|
77
77
|
.pipe(
|
|
78
|
+
Match.when("open", () => parseAttach(rest)),
|
|
78
79
|
Match.when("apply", () => parseApply(rest)),
|
|
79
80
|
Match.when("state", () => parseState(rest)),
|
|
80
81
|
Match.orElse(() => Either.left(unknownCommandError))
|
|
@@ -3,8 +3,9 @@ import { Match } from "effect"
|
|
|
3
3
|
import type { ParseError } from "@effect-template/lib/core/domain"
|
|
4
4
|
|
|
5
5
|
export const usageText = `docker-git menu
|
|
6
|
-
docker-git create --repo-url <url> [options]
|
|
6
|
+
docker-git create [--repo-url <url>] [options]
|
|
7
7
|
docker-git clone <url> [options]
|
|
8
|
+
docker-git open [<url>] [options]
|
|
8
9
|
docker-git apply [<url>] [options]
|
|
9
10
|
docker-git mcp-playwright [<url>] [options]
|
|
10
11
|
docker-git attach [<url>] [options]
|
|
@@ -20,11 +21,12 @@ docker-git state <action> [options]
|
|
|
20
21
|
|
|
21
22
|
Commands:
|
|
22
23
|
menu Interactive menu (default when no args)
|
|
23
|
-
create, init Generate docker development environment
|
|
24
|
+
create, init Generate docker development environment (repo URL optional)
|
|
24
25
|
clone Create + run container and clone repo
|
|
26
|
+
open Open existing docker-git project workspace
|
|
25
27
|
apply Apply docker-git config to an existing project/container (current dir by default)
|
|
26
28
|
mcp-playwright Enable Playwright MCP + Chromium sidecar for an existing project dir
|
|
27
|
-
attach, tmux
|
|
29
|
+
attach, tmux Alias for open
|
|
28
30
|
panes, terms List tmux panes for a docker-git project
|
|
29
31
|
scrap Export/import project scrap (session snapshot + rebuildable deps)
|
|
30
32
|
sessions List/kill/log container terminal processes
|
|
@@ -34,6 +36,7 @@ Commands:
|
|
|
34
36
|
state Manage docker-git state directory via git (sync across machines)
|
|
35
37
|
|
|
36
38
|
Options:
|
|
39
|
+
--repo-url <url> Repository URL (create: optional; clone: required via positional arg or flag)
|
|
37
40
|
--repo-ref <ref> Git ref/branch (default: main)
|
|
38
41
|
--branch, -b <ref> Alias for --repo-ref
|
|
39
42
|
--target-dir <path> Target dir inside container (create default: /home/dev/app, clone default: ~/workspaces/<org>/<repo>[/issue-<id>|/pr-<id>])
|
|
@@ -47,8 +50,10 @@ Options:
|
|
|
47
50
|
--env-project <path> Host path to project env file (default: ./.orch/env/project.env)
|
|
48
51
|
--codex-auth <path> Host path for Codex auth cache (default: <projectsRoot>/.orch/auth/codex)
|
|
49
52
|
--codex-home <path> Container path for Codex auth (default: /home/dev/.codex)
|
|
53
|
+
--network-mode <mode> Compose network mode: shared|project (default: shared)
|
|
54
|
+
--shared-network <name> Shared Docker network name when network-mode=shared (default: docker-git-shared)
|
|
50
55
|
--out-dir <path> Output directory (default: <projectsRoot>/<org>/<repo>[/issue-<id>|/pr-<id>])
|
|
51
|
-
--project-dir <path> Project directory for attach (default: .)
|
|
56
|
+
--project-dir <path> Project directory for open/attach (default: .)
|
|
52
57
|
--archive <path> Scrap snapshot directory (default: .orch/scrap/session)
|
|
53
58
|
--mode <session> Scrap mode (default: session)
|
|
54
59
|
--git-token <label> Token label for clone/create (maps to GITHUB_TOKEN__<LABEL>, example: agiens)
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
listProjectStatus,
|
|
10
10
|
listRunningProjectItems
|
|
11
11
|
} from "@effect-template/lib/usecases/projects"
|
|
12
|
+
import { gcProjectNetworkByTemplate } from "@effect-template/lib/usecases/docker-network-gc"
|
|
12
13
|
import { runDockerComposeUpWithPortCheck } from "@effect-template/lib/usecases/projects-up"
|
|
13
14
|
import { Effect, Match, pipe } from "effect"
|
|
14
15
|
|
|
@@ -149,8 +150,10 @@ const handleMenuAction = (
|
|
|
149
150
|
withProjectConfig(state, setMessage, () =>
|
|
150
151
|
runDockerComposeLogs(state.activeDir ?? state.cwd))),
|
|
151
152
|
Match.when({ _tag: "Down" }, () =>
|
|
152
|
-
withProjectConfig(state, setMessage, () =>
|
|
153
|
-
runDockerComposeDown(state.activeDir ?? state.cwd)
|
|
153
|
+
withProjectConfig(state, setMessage, (config) =>
|
|
154
|
+
runDockerComposeDown(state.activeDir ?? state.cwd).pipe(
|
|
155
|
+
Effect.zipRight(gcProjectNetworkByTemplate(state.activeDir ?? state.cwd, config.template))
|
|
156
|
+
))),
|
|
154
157
|
Match.when({ _tag: "DownAll" }, () =>
|
|
155
158
|
pipe(
|
|
156
159
|
downAllDockerGitProjects,
|
|
@@ -46,11 +46,16 @@ type CreateReturnContext = CreateContext & {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
export const buildCreateArgs = (input: CreateInputs): ReadonlyArray<string> => {
|
|
49
|
-
const args: Array<string> = ["create"
|
|
49
|
+
const args: Array<string> = ["create"]
|
|
50
|
+
if (input.repoUrl.length > 0) {
|
|
51
|
+
args.push("--repo-url", input.repoUrl)
|
|
52
|
+
}
|
|
50
53
|
if (input.repoRef.length > 0) {
|
|
51
54
|
args.push("--repo-ref", input.repoRef)
|
|
52
55
|
}
|
|
53
|
-
|
|
56
|
+
if (input.outDir.length > 0) {
|
|
57
|
+
args.push("--out-dir", input.outDir)
|
|
58
|
+
}
|
|
54
59
|
if (!input.runUp) {
|
|
55
60
|
args.push("--no-up")
|
|
56
61
|
}
|
|
@@ -106,8 +111,8 @@ export const resolveCreateInputs = (
|
|
|
106
111
|
values: Partial<CreateInputs>
|
|
107
112
|
): CreateInputs => {
|
|
108
113
|
const repoUrl = values.repoUrl ?? ""
|
|
109
|
-
const resolvedRepoRef =
|
|
110
|
-
const outDir = values.outDir ??
|
|
114
|
+
const resolvedRepoRef = resolveRepoInput(repoUrl).repoRef
|
|
115
|
+
const outDir = values.outDir ?? resolveDefaultOutDir(cwd, repoUrl)
|
|
111
116
|
|
|
112
117
|
return {
|
|
113
118
|
repoUrl,
|
|
@@ -179,10 +184,6 @@ const applyCreateStep = (input: {
|
|
|
179
184
|
}): boolean =>
|
|
180
185
|
Match.value(input.step).pipe(
|
|
181
186
|
Match.when("repoUrl", () => {
|
|
182
|
-
if (input.buffer.length === 0) {
|
|
183
|
-
input.setMessage("Repo URL is required.")
|
|
184
|
-
return false
|
|
185
|
-
}
|
|
186
187
|
input.nextValues.repoUrl = input.buffer
|
|
187
188
|
input.nextValues.outDir = resolveDefaultOutDir(input.cwd, input.buffer)
|
|
188
189
|
return true
|
|
@@ -222,11 +223,6 @@ const finalizeCreateFlow = (input: {
|
|
|
222
223
|
readonly setActiveDir: (dir: string | null) => void
|
|
223
224
|
}) => {
|
|
224
225
|
const inputs = resolveCreateInputs(input.state.cwd, input.nextValues)
|
|
225
|
-
if (inputs.repoUrl.length === 0) {
|
|
226
|
-
input.setMessage("Repo URL is required.")
|
|
227
|
-
return
|
|
228
|
-
}
|
|
229
|
-
|
|
230
226
|
const parsed = parseArgs(buildCreateArgs(inputs))
|
|
231
227
|
if (Either.isLeft(parsed)) {
|
|
232
228
|
input.setMessage(formatParseError(parsed.left))
|
|
@@ -28,7 +28,7 @@ import { createSteps, menuItems } from "./menu-types.js"
|
|
|
28
28
|
|
|
29
29
|
export const renderStepLabel = (step: CreateStep, defaults: CreateInputs): string =>
|
|
30
30
|
Match.value(step).pipe(
|
|
31
|
-
Match.when("repoUrl", () => "Repo URL"),
|
|
31
|
+
Match.when("repoUrl", () => "Repo URL (optional for empty workspace)"),
|
|
32
32
|
Match.when("repoRef", () => `Repo ref [${defaults.repoRef}]`),
|
|
33
33
|
Match.when("outDir", () => `Output dir [${defaults.outDir}]`),
|
|
34
34
|
Match.when("runUp", () => `Run docker compose up now? [${defaults.runUp ? "Y" : "n"}]`),
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { expect } from "@effect/vitest"
|
|
2
|
+
import { Effect, Either } from "effect"
|
|
3
|
+
|
|
4
|
+
import type { Command } from "@effect-template/lib/core/domain"
|
|
5
|
+
import { parseArgs } from "../../src/docker-git/cli/parser.js"
|
|
6
|
+
|
|
7
|
+
export type CreateCommand = Extract<Command, { _tag: "Create" }>
|
|
8
|
+
type ProjectDirRunUpCommand = Extract<Command, { readonly projectDir: string; readonly runUp: boolean }>
|
|
9
|
+
|
|
10
|
+
export const expectParseErrorTag = (
|
|
11
|
+
args: ReadonlyArray<string>,
|
|
12
|
+
expectedTag: string
|
|
13
|
+
) =>
|
|
14
|
+
Effect.sync(() => {
|
|
15
|
+
const parsed = parseArgs(args)
|
|
16
|
+
Either.match(parsed, {
|
|
17
|
+
onLeft: (error) => {
|
|
18
|
+
expect(error._tag).toBe(expectedTag)
|
|
19
|
+
},
|
|
20
|
+
onRight: () => {
|
|
21
|
+
throw new Error("expected parse error")
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
export const parseOrThrow = (args: ReadonlyArray<string>): Command => {
|
|
27
|
+
const parsed = parseArgs(args)
|
|
28
|
+
return Either.match(parsed, {
|
|
29
|
+
onLeft: (error) => {
|
|
30
|
+
throw new Error(`unexpected error ${error._tag}`)
|
|
31
|
+
},
|
|
32
|
+
onRight: (command) => command
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const expectProjectDirRunUpCommand = (
|
|
37
|
+
args: ReadonlyArray<string>,
|
|
38
|
+
expectedTag: ProjectDirRunUpCommand["_tag"],
|
|
39
|
+
expectedProjectDir: string,
|
|
40
|
+
expectedRunUp: boolean
|
|
41
|
+
) =>
|
|
42
|
+
Effect.sync(() => {
|
|
43
|
+
const command = parseOrThrow(args)
|
|
44
|
+
if (command._tag !== expectedTag) {
|
|
45
|
+
throw new Error(`expected ${expectedTag} command`)
|
|
46
|
+
}
|
|
47
|
+
if (!("projectDir" in command) || !("runUp" in command)) {
|
|
48
|
+
throw new Error("expected command with projectDir and runUp")
|
|
49
|
+
}
|
|
50
|
+
expect(command.projectDir).toBe(expectedProjectDir)
|
|
51
|
+
expect(command.runUp).toBe(expectedRunUp)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
export const expectAttachProjectDirCommand = (
|
|
55
|
+
args: ReadonlyArray<string>,
|
|
56
|
+
expectedProjectDir: string
|
|
57
|
+
) =>
|
|
58
|
+
Effect.sync(() => {
|
|
59
|
+
const command = parseOrThrow(args)
|
|
60
|
+
if (command._tag !== "Attach") {
|
|
61
|
+
throw new Error("expected Attach command")
|
|
62
|
+
}
|
|
63
|
+
expect(command.projectDir).toBe(expectedProjectDir)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
export const expectCreateCommand = (
|
|
67
|
+
args: ReadonlyArray<string>,
|
|
68
|
+
onRight: (command: CreateCommand) => void
|
|
69
|
+
) =>
|
|
70
|
+
Effect.sync(() => {
|
|
71
|
+
const command = parseOrThrow(args)
|
|
72
|
+
if (command._tag !== "Create") {
|
|
73
|
+
throw new Error("expected Create command")
|
|
74
|
+
}
|
|
75
|
+
onRight(command)
|
|
76
|
+
})
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { describe, expect, it } from "@effect/vitest"
|
|
2
|
+
import { Effect, Either } from "effect"
|
|
3
|
+
|
|
4
|
+
import { parseArgs } from "../../src/docker-git/cli/parser.js"
|
|
5
|
+
|
|
6
|
+
describe("parseArgs network options", () => {
|
|
7
|
+
it.effect("parses create network mode options", () =>
|
|
8
|
+
Effect.sync(() => {
|
|
9
|
+
const parsed = parseArgs([
|
|
10
|
+
"create",
|
|
11
|
+
"--repo-url",
|
|
12
|
+
"https://github.com/org/repo.git",
|
|
13
|
+
"--network-mode",
|
|
14
|
+
"project",
|
|
15
|
+
"--shared-network",
|
|
16
|
+
"ignored-shared-network"
|
|
17
|
+
])
|
|
18
|
+
if (Either.isLeft(parsed)) {
|
|
19
|
+
throw new Error(`unexpected parse error: ${parsed.left._tag}`)
|
|
20
|
+
}
|
|
21
|
+
const command = parsed.right
|
|
22
|
+
if (command._tag !== "Create") {
|
|
23
|
+
throw new Error("expected Create command")
|
|
24
|
+
}
|
|
25
|
+
expect(command.config.dockerNetworkMode).toBe("project")
|
|
26
|
+
expect(command.config.dockerSharedNetworkName).toBe("ignored-shared-network")
|
|
27
|
+
}))
|
|
28
|
+
|
|
29
|
+
it.effect("fails on invalid network mode", () =>
|
|
30
|
+
Effect.sync(() => {
|
|
31
|
+
const command = parseArgs([
|
|
32
|
+
"create",
|
|
33
|
+
"--repo-url",
|
|
34
|
+
"https://github.com/org/repo.git",
|
|
35
|
+
"--network-mode",
|
|
36
|
+
"invalid"
|
|
37
|
+
])
|
|
38
|
+
Either.match(command, {
|
|
39
|
+
onLeft: (error) => {
|
|
40
|
+
expect(error._tag).toBe("InvalidOption")
|
|
41
|
+
},
|
|
42
|
+
onRight: () => {
|
|
43
|
+
throw new Error("expected parse error")
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
}))
|
|
47
|
+
})
|
|
@@ -1,69 +1,16 @@
|
|
|
1
1
|
import { describe, expect, it } from "@effect/vitest"
|
|
2
|
-
import { Effect
|
|
2
|
+
import { Effect } from "effect"
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { defaultTemplateConfig } from "@effect-template/lib/core/domain"
|
|
5
5
|
import { expandContainerHome } from "@effect-template/lib/usecases/scrap-path"
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
Effect.sync(() => {
|
|
15
|
-
const parsed = parseArgs(args)
|
|
16
|
-
Either.match(parsed, {
|
|
17
|
-
onLeft: (error) => {
|
|
18
|
-
expect(error._tag).toBe(expectedTag)
|
|
19
|
-
},
|
|
20
|
-
onRight: () => {
|
|
21
|
-
throw new Error("expected parse error")
|
|
22
|
-
}
|
|
23
|
-
})
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
const parseOrThrow = (args: ReadonlyArray<string>): Command => {
|
|
27
|
-
const parsed = parseArgs(args)
|
|
28
|
-
return Either.match(parsed, {
|
|
29
|
-
onLeft: (error) => {
|
|
30
|
-
throw new Error(`unexpected error ${error._tag}`)
|
|
31
|
-
},
|
|
32
|
-
onRight: (command) => command
|
|
33
|
-
})
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
type ProjectDirRunUpCommand = Extract<Command, { readonly projectDir: string; readonly runUp: boolean }>
|
|
37
|
-
|
|
38
|
-
const expectProjectDirRunUpCommand = (
|
|
39
|
-
args: ReadonlyArray<string>,
|
|
40
|
-
expectedTag: ProjectDirRunUpCommand["_tag"],
|
|
41
|
-
expectedProjectDir: string,
|
|
42
|
-
expectedRunUp: boolean
|
|
43
|
-
) =>
|
|
44
|
-
Effect.sync(() => {
|
|
45
|
-
const command = parseOrThrow(args)
|
|
46
|
-
if (command._tag !== expectedTag) {
|
|
47
|
-
throw new Error(`expected ${expectedTag} command`)
|
|
48
|
-
}
|
|
49
|
-
if (!("projectDir" in command) || !("runUp" in command)) {
|
|
50
|
-
throw new Error("expected command with projectDir and runUp")
|
|
51
|
-
}
|
|
52
|
-
expect(command.projectDir).toBe(expectedProjectDir)
|
|
53
|
-
expect(command.runUp).toBe(expectedRunUp)
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
const expectCreateCommand = (
|
|
57
|
-
args: ReadonlyArray<string>,
|
|
58
|
-
onRight: (command: CreateCommand) => void
|
|
59
|
-
) =>
|
|
60
|
-
Effect.sync(() => {
|
|
61
|
-
const command = parseOrThrow(args)
|
|
62
|
-
if (command._tag !== "Create") {
|
|
63
|
-
throw new Error("expected Create command")
|
|
64
|
-
}
|
|
65
|
-
onRight(command)
|
|
66
|
-
})
|
|
6
|
+
import {
|
|
7
|
+
type CreateCommand,
|
|
8
|
+
expectAttachProjectDirCommand,
|
|
9
|
+
expectCreateCommand,
|
|
10
|
+
expectParseErrorTag,
|
|
11
|
+
expectProjectDirRunUpCommand,
|
|
12
|
+
parseOrThrow
|
|
13
|
+
} from "./parser-helpers.js"
|
|
67
14
|
|
|
68
15
|
const expectCreateDefaults = (command: CreateCommand) => {
|
|
69
16
|
expect(command.config.repoUrl).toBe("https://github.com/org/repo.git")
|
|
@@ -71,6 +18,8 @@ const expectCreateDefaults = (command: CreateCommand) => {
|
|
|
71
18
|
expect(command.outDir).toBe(".docker-git/org/repo")
|
|
72
19
|
expect(command.runUp).toBe(true)
|
|
73
20
|
expect(command.forceEnv).toBe(false)
|
|
21
|
+
expect(command.config.dockerNetworkMode).toBe("shared")
|
|
22
|
+
expect(command.config.dockerSharedNetworkName).toBe("docker-git-shared")
|
|
74
23
|
}
|
|
75
24
|
|
|
76
25
|
const expandDefaultTargetDir = (path: string): string => expandContainerHome(defaultTemplateConfig.sshUser, path)
|
|
@@ -99,7 +48,20 @@ describe("parseArgs", () => {
|
|
|
99
48
|
expect(command.config.volumeName).toBe("dg-repo-issue-9-home")
|
|
100
49
|
}))
|
|
101
50
|
|
|
102
|
-
it.effect("
|
|
51
|
+
it.effect("parses create command without repo url into empty workspace defaults", () =>
|
|
52
|
+
expectCreateCommand(["create"], (command) => {
|
|
53
|
+
expect(command.config.repoUrl).toBe("")
|
|
54
|
+
expect(command.config.repoRef).toBe(defaultTemplateConfig.repoRef)
|
|
55
|
+
expect(command.outDir).toBe(".docker-git/app")
|
|
56
|
+
expect(command.openSsh).toBe(false)
|
|
57
|
+
expect(command.waitForClone).toBe(false)
|
|
58
|
+
expect(command.config.containerName).toBe("dg-app")
|
|
59
|
+
expect(command.config.serviceName).toBe("dg-app")
|
|
60
|
+
expect(command.config.volumeName).toBe("dg-app-home")
|
|
61
|
+
expect(command.config.targetDir).toBe(expandDefaultTargetDir(defaultTemplateConfig.targetDir))
|
|
62
|
+
}))
|
|
63
|
+
|
|
64
|
+
it.effect("fails clone when repo url is missing", () => expectParseErrorTag(["clone"], "MissingRequiredOption"))
|
|
103
65
|
|
|
104
66
|
it.effect("parses clone command with positional repo url", () =>
|
|
105
67
|
expectCreateCommand(["clone", "https://github.com/org/repo.git"], (command) => {
|
|
@@ -194,13 +156,10 @@ describe("parseArgs", () => {
|
|
|
194
156
|
}))
|
|
195
157
|
|
|
196
158
|
it.effect("parses attach with GitHub issue url into issue workspace", () =>
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
expect(command.projectDir).toBe(".docker-git/org/repo/issue-7")
|
|
203
|
-
}))
|
|
159
|
+
expectAttachProjectDirCommand(["attach", "https://github.com/org/repo/issues/7"], ".docker-git/org/repo/issue-7"))
|
|
160
|
+
|
|
161
|
+
it.effect("parses open with GitHub issue url into issue workspace", () =>
|
|
162
|
+
expectAttachProjectDirCommand(["open", "https://github.com/org/repo/issues/7"], ".docker-git/org/repo/issue-7"))
|
|
204
163
|
|
|
205
164
|
it.effect("parses mcp-playwright command in current directory", () =>
|
|
206
165
|
expectProjectDirRunUpCommand(["mcp-playwright"], "McpPlaywrightUp", ".", true))
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import path from "node:path"
|
|
2
|
+
import { fileURLToPath } from "node:url"
|
|
3
|
+
import { defineConfig } from "vite"
|
|
4
|
+
import tsconfigPaths from "vite-tsconfig-paths"
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
7
|
+
const __dirname = path.dirname(__filename)
|
|
8
|
+
|
|
9
|
+
export default defineConfig({
|
|
10
|
+
plugins: [tsconfigPaths()],
|
|
11
|
+
publicDir: false,
|
|
12
|
+
resolve: {
|
|
13
|
+
alias: {
|
|
14
|
+
"@": path.resolve(__dirname, "src"),
|
|
15
|
+
"@effect-template/lib": path.resolve(__dirname, "../lib/src")
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
build: {
|
|
19
|
+
target: "node20",
|
|
20
|
+
outDir: "dist",
|
|
21
|
+
sourcemap: true,
|
|
22
|
+
ssr: "src/docker-git/main.ts",
|
|
23
|
+
rollupOptions: {
|
|
24
|
+
output: {
|
|
25
|
+
format: "es",
|
|
26
|
+
entryFileNames: "src/docker-git/main.js",
|
|
27
|
+
inlineDynamicImports: true
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
ssr: {
|
|
32
|
+
target: "node"
|
|
33
|
+
}
|
|
34
|
+
})
|