@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.
Files changed (70) hide show
  1. package/.package.json.release.bak +5 -3
  2. package/CHANGELOG.md +12 -0
  3. package/README.md +31 -1
  4. package/dist/src/docker-git/main.js +10259 -12
  5. package/dist/src/docker-git/main.js.map +1 -0
  6. package/package.json +4 -4
  7. package/src/app/program.ts +16 -13
  8. package/src/docker-git/cli/parser-options.ts +6 -0
  9. package/src/docker-git/cli/parser.ts +1 -0
  10. package/src/docker-git/cli/usage.ts +9 -4
  11. package/src/docker-git/menu-actions.ts +5 -2
  12. package/src/docker-git/menu-create.ts +9 -13
  13. package/src/docker-git/menu-render.ts +1 -1
  14. package/tests/docker-git/parser-helpers.ts +76 -0
  15. package/tests/docker-git/parser-network-options.test.ts +47 -0
  16. package/tests/docker-git/parser.test.ts +30 -71
  17. package/vite.docker-git.config.ts +34 -0
  18. package/dist/main.js +0 -930
  19. package/dist/main.js.map +0 -1
  20. package/dist/src/app/main.js +0 -15
  21. package/dist/src/app/program.js +0 -61
  22. package/dist/src/docker-git/cli/input.js +0 -21
  23. package/dist/src/docker-git/cli/parser-apply.js +0 -22
  24. package/dist/src/docker-git/cli/parser-attach.js +0 -19
  25. package/dist/src/docker-git/cli/parser-auth.js +0 -90
  26. package/dist/src/docker-git/cli/parser-clone.js +0 -40
  27. package/dist/src/docker-git/cli/parser-create.js +0 -1
  28. package/dist/src/docker-git/cli/parser-mcp-playwright.js +0 -18
  29. package/dist/src/docker-git/cli/parser-options.js +0 -134
  30. package/dist/src/docker-git/cli/parser-panes.js +0 -19
  31. package/dist/src/docker-git/cli/parser-scrap.js +0 -74
  32. package/dist/src/docker-git/cli/parser-sessions.js +0 -69
  33. package/dist/src/docker-git/cli/parser-shared.js +0 -26
  34. package/dist/src/docker-git/cli/parser-state.js +0 -62
  35. package/dist/src/docker-git/cli/parser.js +0 -47
  36. package/dist/src/docker-git/cli/read-command.js +0 -17
  37. package/dist/src/docker-git/cli/usage.js +0 -113
  38. package/dist/src/docker-git/menu-actions.js +0 -135
  39. package/dist/src/docker-git/menu-auth-data.js +0 -90
  40. package/dist/src/docker-git/menu-auth-helpers.js +0 -20
  41. package/dist/src/docker-git/menu-auth.js +0 -159
  42. package/dist/src/docker-git/menu-buffer-input.js +0 -9
  43. package/dist/src/docker-git/menu-create.js +0 -199
  44. package/dist/src/docker-git/menu-input-handler.js +0 -109
  45. package/dist/src/docker-git/menu-input-utils.js +0 -47
  46. package/dist/src/docker-git/menu-input.js +0 -2
  47. package/dist/src/docker-git/menu-labeled-env.js +0 -33
  48. package/dist/src/docker-git/menu-menu.js +0 -46
  49. package/dist/src/docker-git/menu-project-auth-claude.js +0 -43
  50. package/dist/src/docker-git/menu-project-auth-data.js +0 -165
  51. package/dist/src/docker-git/menu-project-auth.js +0 -124
  52. package/dist/src/docker-git/menu-render-auth.js +0 -45
  53. package/dist/src/docker-git/menu-render-common.js +0 -26
  54. package/dist/src/docker-git/menu-render-layout.js +0 -14
  55. package/dist/src/docker-git/menu-render-project-auth.js +0 -37
  56. package/dist/src/docker-git/menu-render-select.js +0 -129
  57. package/dist/src/docker-git/menu-render.js +0 -137
  58. package/dist/src/docker-git/menu-select-actions.js +0 -66
  59. package/dist/src/docker-git/menu-select-connect.js +0 -6
  60. package/dist/src/docker-git/menu-select-load.js +0 -12
  61. package/dist/src/docker-git/menu-select-order.js +0 -21
  62. package/dist/src/docker-git/menu-select-runtime.js +0 -82
  63. package/dist/src/docker-git/menu-select-view.js +0 -15
  64. package/dist/src/docker-git/menu-select.js +0 -98
  65. package/dist/src/docker-git/menu-shared.js +0 -180
  66. package/dist/src/docker-git/menu-startup.js +0 -57
  67. package/dist/src/docker-git/menu-types.js +0 -21
  68. package/dist/src/docker-git/menu.js +0 -226
  69. package/dist/src/docker-git/program.js +0 -43
  70. 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.21",
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": "tsc -p tsconfig.build.json",
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",
@@ -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 cloning
9
+ * @pure false - uses Console output and spawns commands when running shortcuts
10
10
  * @effect Console, CommandExecutor, Path
11
- * @invariant forall args in Argv: clone(args) -> docker_git_invoked(args)
11
+ * @invariant forall args in Argv: shortcut(args) -> docker_git_invoked(args)
12
12
  * @precondition true
13
- * @postcondition clone(args) -> docker_git_invoked(args); otherwise usage printed
14
- * @complexity O(build + clone)
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 clone runner and help based on CLI context
43
- // WHY: allow pnpm run clone <url> while keeping a single entrypoint
44
- // QUOTE(ТЗ): "pnpm run clone <url>"
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: clone(argv) -> docker_git_invoked(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 clone is not requested
51
- // COMPLEXITY: O(build + clone)
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 Open tmux workspace for a docker-git project
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", "--repo-url", input.repoUrl]
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
- args.push("--out-dir", input.outDir)
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 = repoUrl.length > 0 ? resolveRepoInput(repoUrl).repoRef : undefined
110
- const outDir = values.outDir ?? (repoUrl.length > 0 ? resolveDefaultOutDir(cwd, repoUrl) : "")
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, Either } from "effect"
2
+ import { Effect } from "effect"
3
3
 
4
- import { type Command, defaultTemplateConfig } from "@effect-template/lib/core/domain"
4
+ import { defaultTemplateConfig } from "@effect-template/lib/core/domain"
5
5
  import { expandContainerHome } from "@effect-template/lib/usecases/scrap-path"
6
- import { parseArgs } from "../../src/docker-git/cli/parser.js"
7
-
8
- type CreateCommand = Extract<Command, { _tag: "Create" }>
9
-
10
- 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
- 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("fails on missing repo url", () => expectParseErrorTag(["create"], "MissingRequiredOption"))
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
- Effect.sync(() => {
198
- const command = parseOrThrow(["attach", "https://github.com/org/repo/issues/7"])
199
- if (command._tag !== "Attach") {
200
- throw new Error("expected Attach command")
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
+ })