@prover-coder-ai/docker-git 1.0.23 → 1.0.24

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 (74) hide show
  1. package/package.json +4 -1
  2. package/.jscpd.json +0 -16
  3. package/.package.json.release.bak +0 -111
  4. package/CHANGELOG.md +0 -139
  5. package/biome.json +0 -34
  6. package/eslint.config.mts +0 -305
  7. package/eslint.effect-ts-check.config.mjs +0 -220
  8. package/linter.config.json +0 -33
  9. package/src/app/main.ts +0 -18
  10. package/src/app/program.ts +0 -78
  11. package/src/docker-git/cli/input.ts +0 -29
  12. package/src/docker-git/cli/parser-apply.ts +0 -28
  13. package/src/docker-git/cli/parser-attach.ts +0 -22
  14. package/src/docker-git/cli/parser-auth.ts +0 -154
  15. package/src/docker-git/cli/parser-clone.ts +0 -50
  16. package/src/docker-git/cli/parser-create.ts +0 -3
  17. package/src/docker-git/cli/parser-mcp-playwright.ts +0 -24
  18. package/src/docker-git/cli/parser-options.ts +0 -211
  19. package/src/docker-git/cli/parser-panes.ts +0 -22
  20. package/src/docker-git/cli/parser-scrap.ts +0 -106
  21. package/src/docker-git/cli/parser-sessions.ts +0 -101
  22. package/src/docker-git/cli/parser-shared.ts +0 -51
  23. package/src/docker-git/cli/parser-state.ts +0 -86
  24. package/src/docker-git/cli/parser.ts +0 -83
  25. package/src/docker-git/cli/read-command.ts +0 -26
  26. package/src/docker-git/cli/usage.ts +0 -131
  27. package/src/docker-git/main.ts +0 -18
  28. package/src/docker-git/menu-actions.ts +0 -273
  29. package/src/docker-git/menu-auth-data.ts +0 -184
  30. package/src/docker-git/menu-auth-helpers.ts +0 -30
  31. package/src/docker-git/menu-auth.ts +0 -311
  32. package/src/docker-git/menu-buffer-input.ts +0 -18
  33. package/src/docker-git/menu-create.ts +0 -310
  34. package/src/docker-git/menu-input-handler.ts +0 -183
  35. package/src/docker-git/menu-input-utils.ts +0 -85
  36. package/src/docker-git/menu-input.ts +0 -2
  37. package/src/docker-git/menu-labeled-env.ts +0 -37
  38. package/src/docker-git/menu-menu.ts +0 -58
  39. package/src/docker-git/menu-project-auth-claude.ts +0 -70
  40. package/src/docker-git/menu-project-auth-data.ts +0 -292
  41. package/src/docker-git/menu-project-auth.ts +0 -271
  42. package/src/docker-git/menu-render-auth.ts +0 -65
  43. package/src/docker-git/menu-render-common.ts +0 -67
  44. package/src/docker-git/menu-render-layout.ts +0 -30
  45. package/src/docker-git/menu-render-project-auth.ts +0 -70
  46. package/src/docker-git/menu-render-select.ts +0 -250
  47. package/src/docker-git/menu-render.ts +0 -292
  48. package/src/docker-git/menu-select-actions.ts +0 -150
  49. package/src/docker-git/menu-select-connect.ts +0 -27
  50. package/src/docker-git/menu-select-load.ts +0 -33
  51. package/src/docker-git/menu-select-order.ts +0 -37
  52. package/src/docker-git/menu-select-runtime.ts +0 -143
  53. package/src/docker-git/menu-select-view.ts +0 -25
  54. package/src/docker-git/menu-select.ts +0 -145
  55. package/src/docker-git/menu-shared.ts +0 -256
  56. package/src/docker-git/menu-startup.ts +0 -83
  57. package/src/docker-git/menu-types.ts +0 -170
  58. package/src/docker-git/menu.ts +0 -303
  59. package/src/docker-git/program.ts +0 -154
  60. package/src/docker-git/tmux.ts +0 -292
  61. package/tests/app/main.test.ts +0 -65
  62. package/tests/docker-git/entrypoint-auth.test.ts +0 -40
  63. package/tests/docker-git/fixtures/project-item.ts +0 -24
  64. package/tests/docker-git/menu-select-connect.test.ts +0 -55
  65. package/tests/docker-git/menu-select-order.test.ts +0 -84
  66. package/tests/docker-git/menu-startup.test.ts +0 -51
  67. package/tests/docker-git/parser-helpers.ts +0 -76
  68. package/tests/docker-git/parser-network-options.test.ts +0 -47
  69. package/tests/docker-git/parser.test.ts +0 -284
  70. package/tsconfig.build.json +0 -8
  71. package/tsconfig.json +0 -20
  72. package/vite.config.ts +0 -32
  73. package/vite.docker-git.config.ts +0 -34
  74. package/vitest.config.ts +0 -85
@@ -1,211 +0,0 @@
1
- import { Either } from "effect"
2
-
3
- import type { RawOptions } from "@effect-template/lib/core/command-options"
4
- import type { ParseError } from "@effect-template/lib/core/domain"
5
-
6
- interface ValueOptionSpec {
7
- readonly flag: string
8
- readonly key:
9
- | "repoUrl"
10
- | "repoRef"
11
- | "targetDir"
12
- | "sshPort"
13
- | "sshUser"
14
- | "containerName"
15
- | "serviceName"
16
- | "volumeName"
17
- | "secretsRoot"
18
- | "authorizedKeysPath"
19
- | "envGlobalPath"
20
- | "envProjectPath"
21
- | "codexAuthPath"
22
- | "codexHome"
23
- | "dockerNetworkMode"
24
- | "dockerSharedNetworkName"
25
- | "archivePath"
26
- | "scrapMode"
27
- | "label"
28
- | "gitTokenLabel"
29
- | "codexTokenLabel"
30
- | "claudeTokenLabel"
31
- | "token"
32
- | "scopes"
33
- | "message"
34
- | "outDir"
35
- | "projectDir"
36
- | "lines"
37
- }
38
-
39
- const valueOptionSpecs: ReadonlyArray<ValueOptionSpec> = [
40
- { flag: "--repo-url", key: "repoUrl" },
41
- { flag: "--repo-ref", key: "repoRef" },
42
- { flag: "--branch", key: "repoRef" },
43
- { flag: "-b", key: "repoRef" },
44
- { flag: "--target-dir", key: "targetDir" },
45
- { flag: "--ssh-port", key: "sshPort" },
46
- { flag: "--ssh-user", key: "sshUser" },
47
- { flag: "--container-name", key: "containerName" },
48
- { flag: "--service-name", key: "serviceName" },
49
- { flag: "--volume-name", key: "volumeName" },
50
- { flag: "--secrets-root", key: "secretsRoot" },
51
- { flag: "--authorized-keys", key: "authorizedKeysPath" },
52
- { flag: "--env-global", key: "envGlobalPath" },
53
- { flag: "--env-project", key: "envProjectPath" },
54
- { flag: "--codex-auth", key: "codexAuthPath" },
55
- { flag: "--codex-home", key: "codexHome" },
56
- { flag: "--network-mode", key: "dockerNetworkMode" },
57
- { flag: "--shared-network", key: "dockerSharedNetworkName" },
58
- { flag: "--archive", key: "archivePath" },
59
- { flag: "--mode", key: "scrapMode" },
60
- { flag: "--label", key: "label" },
61
- { flag: "--git-token", key: "gitTokenLabel" },
62
- { flag: "--codex-token", key: "codexTokenLabel" },
63
- { flag: "--claude-token", key: "claudeTokenLabel" },
64
- { flag: "--token", key: "token" },
65
- { flag: "--scopes", key: "scopes" },
66
- { flag: "--message", key: "message" },
67
- { flag: "-m", key: "message" },
68
- { flag: "--out-dir", key: "outDir" },
69
- { flag: "--project-dir", key: "projectDir" },
70
- { flag: "--lines", key: "lines" }
71
- ]
72
-
73
- const valueOptionSpecByFlag: ReadonlyMap<string, ValueOptionSpec> = new Map(
74
- valueOptionSpecs.map((spec) => [spec.flag, spec])
75
- )
76
-
77
- type ValueKey = ValueOptionSpec["key"]
78
-
79
- const booleanFlagUpdaters: Readonly<Record<string, (raw: RawOptions) => RawOptions>> = {
80
- "--up": (raw) => ({ ...raw, up: true }),
81
- "--no-up": (raw) => ({ ...raw, up: false }),
82
- "--ssh": (raw) => ({ ...raw, openSsh: true }),
83
- "--no-ssh": (raw) => ({ ...raw, openSsh: false }),
84
- "--force": (raw) => ({ ...raw, force: true }),
85
- "--force-env": (raw) => ({ ...raw, forceEnv: true }),
86
- "--mcp-playwright": (raw) => ({ ...raw, enableMcpPlaywright: true }),
87
- "--no-mcp-playwright": (raw) => ({ ...raw, enableMcpPlaywright: false }),
88
- "--wipe": (raw) => ({ ...raw, wipe: true }),
89
- "--no-wipe": (raw) => ({ ...raw, wipe: false }),
90
- "--web": (raw) => ({ ...raw, authWeb: true }),
91
- "--include-default": (raw) => ({ ...raw, includeDefault: true })
92
- }
93
-
94
- const valueFlagUpdaters: { readonly [K in ValueKey]: (raw: RawOptions, value: string) => RawOptions } = {
95
- repoUrl: (raw, value) => ({ ...raw, repoUrl: value }),
96
- repoRef: (raw, value) => ({ ...raw, repoRef: value }),
97
- targetDir: (raw, value) => ({ ...raw, targetDir: value }),
98
- sshPort: (raw, value) => ({ ...raw, sshPort: value }),
99
- sshUser: (raw, value) => ({ ...raw, sshUser: value }),
100
- containerName: (raw, value) => ({ ...raw, containerName: value }),
101
- serviceName: (raw, value) => ({ ...raw, serviceName: value }),
102
- volumeName: (raw, value) => ({ ...raw, volumeName: value }),
103
- secretsRoot: (raw, value) => ({ ...raw, secretsRoot: value }),
104
- authorizedKeysPath: (raw, value) => ({ ...raw, authorizedKeysPath: value }),
105
- envGlobalPath: (raw, value) => ({ ...raw, envGlobalPath: value }),
106
- envProjectPath: (raw, value) => ({ ...raw, envProjectPath: value }),
107
- codexAuthPath: (raw, value) => ({ ...raw, codexAuthPath: value }),
108
- codexHome: (raw, value) => ({ ...raw, codexHome: value }),
109
- dockerNetworkMode: (raw, value) => ({ ...raw, dockerNetworkMode: value }),
110
- dockerSharedNetworkName: (raw, value) => ({ ...raw, dockerSharedNetworkName: value }),
111
- archivePath: (raw, value) => ({ ...raw, archivePath: value }),
112
- scrapMode: (raw, value) => ({ ...raw, scrapMode: value }),
113
- label: (raw, value) => ({ ...raw, label: value }),
114
- gitTokenLabel: (raw, value) => ({ ...raw, gitTokenLabel: value }),
115
- codexTokenLabel: (raw, value) => ({ ...raw, codexTokenLabel: value }),
116
- claudeTokenLabel: (raw, value) => ({ ...raw, claudeTokenLabel: value }),
117
- token: (raw, value) => ({ ...raw, token: value }),
118
- scopes: (raw, value) => ({ ...raw, scopes: value }),
119
- message: (raw, value) => ({ ...raw, message: value }),
120
- outDir: (raw, value) => ({ ...raw, outDir: value }),
121
- projectDir: (raw, value) => ({ ...raw, projectDir: value }),
122
- lines: (raw, value) => ({ ...raw, lines: value })
123
- }
124
-
125
- export const applyCommandBooleanFlag = (raw: RawOptions, token: string): RawOptions | null => {
126
- const updater = booleanFlagUpdaters[token]
127
- return updater ? updater(raw) : null
128
- }
129
-
130
- export const applyCommandValueFlag = (
131
- raw: RawOptions,
132
- token: string,
133
- value: string
134
- ): Either.Either<RawOptions, ParseError> => {
135
- const valueSpec = valueOptionSpecByFlag.get(token)
136
- if (valueSpec === undefined) {
137
- return Either.left({ _tag: "UnknownOption", option: token })
138
- }
139
-
140
- const update = valueFlagUpdaters[valueSpec.key]
141
- return Either.right(update(raw, value))
142
- }
143
-
144
- type ParseRawOptionsStep =
145
- | { readonly _tag: "ok"; readonly raw: RawOptions; readonly nextIndex: number }
146
- | { readonly _tag: "error"; readonly error: ParseError }
147
-
148
- const parseInlineValueToken = (
149
- raw: RawOptions,
150
- token: string
151
- ): Either.Either<RawOptions, ParseError> | null => {
152
- const equalIndex = token.indexOf("=")
153
- if (equalIndex <= 0 || !token.startsWith("-")) {
154
- return null
155
- }
156
-
157
- const flag = token.slice(0, equalIndex)
158
- const inlineValue = token.slice(equalIndex + 1)
159
- return applyCommandValueFlag(raw, flag, inlineValue)
160
- }
161
-
162
- const parseRawOptionsStep = (
163
- args: ReadonlyArray<string>,
164
- index: number,
165
- raw: RawOptions
166
- ): ParseRawOptionsStep => {
167
- const token = args[index] ?? ""
168
- const inlineApplied = parseInlineValueToken(raw, token)
169
- if (inlineApplied !== null) {
170
- return Either.isLeft(inlineApplied)
171
- ? { _tag: "error", error: inlineApplied.left }
172
- : { _tag: "ok", raw: inlineApplied.right, nextIndex: index + 1 }
173
- }
174
-
175
- const booleanApplied = applyCommandBooleanFlag(raw, token)
176
- if (booleanApplied !== null) {
177
- return { _tag: "ok", raw: booleanApplied, nextIndex: index + 1 }
178
- }
179
-
180
- if (!token.startsWith("-")) {
181
- return { _tag: "error", error: { _tag: "UnexpectedArgument", value: token } }
182
- }
183
-
184
- const value = args[index + 1]
185
- if (value === undefined) {
186
- return { _tag: "error", error: { _tag: "MissingOptionValue", option: token } }
187
- }
188
-
189
- const nextRaw = applyCommandValueFlag(raw, token, value)
190
- return Either.isLeft(nextRaw)
191
- ? { _tag: "error", error: nextRaw.left }
192
- : { _tag: "ok", raw: nextRaw.right, nextIndex: index + 2 }
193
- }
194
-
195
- export const parseRawOptions = (args: ReadonlyArray<string>): Either.Either<RawOptions, ParseError> => {
196
- let index = 0
197
- let raw: RawOptions = {}
198
-
199
- while (index < args.length) {
200
- const step = parseRawOptionsStep(args, index, raw)
201
- if (step._tag === "error") {
202
- return Either.left(step.error)
203
- }
204
- raw = step.raw
205
- index = step.nextIndex
206
- }
207
-
208
- return Either.right(raw)
209
- }
210
-
211
- export { type RawOptions } from "@effect-template/lib/core/command-options"
@@ -1,22 +0,0 @@
1
- import { Either } from "effect"
2
-
3
- import { type PanesCommand, type ParseError } from "@effect-template/lib/core/domain"
4
-
5
- import { parseProjectDirArgs } from "./parser-shared.js"
6
-
7
- // CHANGE: parse panes command into a project selection
8
- // WHY: allow listing tmux panes without attaching
9
- // QUOTE(ТЗ): "покажи команду ... отобразит терминалы"
10
- // REF: user-request-2026-02-02-panes
11
- // SOURCE: n/a
12
- // FORMAT THEOREM: forall argv: parsePanes(argv) = cmd -> deterministic(cmd)
13
- // PURITY: CORE
14
- // EFFECT: Effect<PanesCommand, ParseError, never>
15
- // INVARIANT: projectDir is never empty
16
- // COMPLEXITY: O(n) where n = |argv|
17
- export const parsePanes = (args: ReadonlyArray<string>): Either.Either<PanesCommand, ParseError> => {
18
- return Either.map(parseProjectDirArgs(args), ({ projectDir }) => ({
19
- _tag: "Panes",
20
- projectDir
21
- }))
22
- }
@@ -1,106 +0,0 @@
1
- import { Either, Match } from "effect"
2
-
3
- import type { Command, ParseError } from "@effect-template/lib/core/domain"
4
-
5
- import { parseProjectDirWithOptions } from "./parser-shared.js"
6
-
7
- const missingRequired = (option: string): ParseError => ({
8
- _tag: "MissingRequiredOption",
9
- option
10
- })
11
-
12
- const invalidScrapAction = (value: string): ParseError => ({
13
- _tag: "InvalidOption",
14
- option: "scrap",
15
- reason: `unknown action: ${value}`
16
- })
17
-
18
- const defaultSessionArchiveDir = ".orch/scrap/session"
19
-
20
- const invalidScrapMode = (value: string): ParseError => ({
21
- _tag: "InvalidOption",
22
- option: "--mode",
23
- reason: `unknown value: ${value} (expected session)`
24
- })
25
-
26
- const parseScrapMode = (raw: string | undefined): Either.Either<"session", ParseError> => {
27
- const value = raw?.trim()
28
- if (!value || value.length === 0) {
29
- return Either.right("session")
30
- }
31
- if (value === "session") {
32
- return Either.right("session")
33
- }
34
- if (value === "recipe") {
35
- // Backwards/semantic alias: "recipe" behaves like "session" (git state + rebuildable deps).
36
- return Either.right("session")
37
- }
38
- return Either.left(invalidScrapMode(value))
39
- }
40
-
41
- const makeScrapExportCommand = (projectDir: string, archivePath: string, mode: "session"): Command => ({
42
- _tag: "ScrapExport",
43
- projectDir,
44
- archivePath,
45
- mode
46
- })
47
-
48
- const makeScrapImportCommand = (
49
- projectDir: string,
50
- archivePath: string,
51
- wipe: boolean,
52
- mode: "session"
53
- ): Command => ({
54
- _tag: "ScrapImport",
55
- projectDir,
56
- archivePath,
57
- wipe,
58
- mode
59
- })
60
-
61
- // CHANGE: parse scrap session export/import commands
62
- // WHY: store a small reproducible snapshot (git state + secrets) instead of large caches like node_modules
63
- // QUOTE(ТЗ): "не должно быть старого режима где он качает весь шлак типо node_modules"
64
- // REF: user-request-2026-02-15
65
- // SOURCE: n/a
66
- // FORMAT THEOREM: forall argv: parseScrap(argv) = cmd -> deterministic(cmd)
67
- // PURITY: CORE
68
- // EFFECT: Effect<Command, ParseError, never>
69
- // INVARIANT: export/import always resolves a projectDir
70
- // COMPLEXITY: O(n) where n = |argv|
71
- export const parseScrap = (args: ReadonlyArray<string>): Either.Either<Command, ParseError> => {
72
- const action = args[0]?.trim()
73
- if (!action || action.length === 0) {
74
- return Either.left(missingRequired("scrap <action>"))
75
- }
76
-
77
- const rest = args.slice(1)
78
-
79
- return Match.value(action).pipe(
80
- Match.when(
81
- "export",
82
- () =>
83
- Either.flatMap(
84
- parseProjectDirWithOptions(rest),
85
- ({ projectDir, raw }) =>
86
- Either.map(parseScrapMode(raw.scrapMode), (mode) => {
87
- const archivePathRaw = raw.archivePath?.trim()
88
- if (archivePathRaw && archivePathRaw.length > 0) {
89
- return makeScrapExportCommand(projectDir, archivePathRaw, mode)
90
- }
91
- return makeScrapExportCommand(projectDir, defaultSessionArchiveDir, mode)
92
- })
93
- )
94
- ),
95
- Match.when("import", () =>
96
- Either.flatMap(parseProjectDirWithOptions(rest), ({ projectDir, raw }) => {
97
- const archivePath = raw.archivePath?.trim()
98
- if (!archivePath || archivePath.length === 0) {
99
- return Either.left(missingRequired("--archive"))
100
- }
101
- return Either.map(parseScrapMode(raw.scrapMode), (mode) =>
102
- makeScrapImportCommand(projectDir, archivePath, raw.wipe ?? true, mode))
103
- })),
104
- Match.orElse(() => Either.left(invalidScrapAction(action)))
105
- )
106
- }
@@ -1,101 +0,0 @@
1
- import { Either, Match } from "effect"
2
-
3
- import { type ParseError, type SessionsCommand } from "@effect-template/lib/core/domain"
4
-
5
- import { parseProjectDirWithOptions } from "./parser-shared.js"
6
-
7
- const defaultLines = 200
8
-
9
- const parsePositiveInt = (
10
- option: string,
11
- raw: string
12
- ): Either.Either<number, ParseError> => {
13
- const value = Number.parseInt(raw, 10)
14
- if (!Number.isFinite(value) || value <= 0) {
15
- const error: ParseError = {
16
- _tag: "InvalidOption",
17
- option,
18
- reason: "expected positive integer"
19
- }
20
- return Either.left(error)
21
- }
22
- return Either.right(value)
23
- }
24
-
25
- const parseList = (args: ReadonlyArray<string>): Either.Either<SessionsCommand, ParseError> =>
26
- Either.map(parseProjectDirWithOptions(args), ({ projectDir, raw }) => ({
27
- _tag: "SessionsList",
28
- projectDir,
29
- includeDefault: raw.includeDefault === true
30
- }))
31
-
32
- const parsePidContext = (
33
- args: ReadonlyArray<string>
34
- ): Either.Either<
35
- { readonly pid: number; readonly projectDir: string; readonly raw: { readonly lines?: string } },
36
- ParseError
37
- > =>
38
- Either.gen(function*(_) {
39
- const pidRaw = args[0]
40
- if (!pidRaw) {
41
- const error: ParseError = { _tag: "MissingRequiredOption", option: "pid" }
42
- return yield* _(Either.left(error))
43
- }
44
- const pid = yield* _(parsePositiveInt("pid", pidRaw))
45
- const { projectDir, raw } = yield* _(parseProjectDirWithOptions(args.slice(1)))
46
- return { pid, projectDir, raw }
47
- })
48
-
49
- const parseKill = (args: ReadonlyArray<string>): Either.Either<SessionsCommand, ParseError> =>
50
- Either.map(parsePidContext(args), ({ pid, projectDir }) => ({
51
- _tag: "SessionsKill",
52
- projectDir,
53
- pid
54
- }))
55
-
56
- const parseLogs = (args: ReadonlyArray<string>): Either.Either<SessionsCommand, ParseError> =>
57
- Either.gen(function*(_) {
58
- const { pid, projectDir, raw } = yield* _(parsePidContext(args))
59
- const lines = raw.lines ? yield* _(parsePositiveInt("--lines", raw.lines)) : defaultLines
60
- return { _tag: "SessionsLogs", projectDir, pid, lines }
61
- })
62
-
63
- // CHANGE: parse sessions command into list/kill/logs actions
64
- // WHY: surface container terminal sessions and background processes from CLI
65
- // QUOTE(ТЗ): "CLI команду которая из докера вернёт запущенные терминал сессии"
66
- // REF: user-request-2026-02-04-terminal-sessions
67
- // SOURCE: n/a
68
- // FORMAT THEOREM: forall argv: parseSessions(argv) = cmd -> deterministic(cmd)
69
- // PURITY: CORE
70
- // EFFECT: Effect<SessionsCommand, ParseError, never>
71
- // INVARIANT: pid/lines must be positive integers
72
- // COMPLEXITY: O(n) where n = |argv|
73
- export const parseSessions = (
74
- args: ReadonlyArray<string>
75
- ): Either.Either<SessionsCommand, ParseError> => {
76
- if (args.length === 0) {
77
- return parseList(args)
78
- }
79
-
80
- const first = args[0] ?? ""
81
- if (first.startsWith("-")) {
82
- return parseList(args)
83
- }
84
-
85
- const rest = args.slice(1)
86
- return Match.value(first).pipe(
87
- Match.when("list", () => parseList(rest)),
88
- Match.when("kill", () => parseKill(rest)),
89
- Match.when("stop", () => parseKill(rest)),
90
- Match.when("logs", () => parseLogs(rest)),
91
- Match.when("log", () => parseLogs(rest)),
92
- Match.orElse(() => {
93
- const error: ParseError = {
94
- _tag: "InvalidOption",
95
- option: "sessions",
96
- reason: `unknown action ${first}`
97
- }
98
- return Either.left(error)
99
- })
100
- )
101
- }
@@ -1,51 +0,0 @@
1
- import { Either } from "effect"
2
-
3
- import { deriveRepoPathParts, type ParseError, resolveRepoInput } from "@effect-template/lib/core/domain"
4
-
5
- import { parseRawOptions, type RawOptions } from "./parser-options.js"
6
-
7
- type PositionalRepo = {
8
- readonly positionalRepoUrl: string | undefined
9
- readonly restArgs: ReadonlyArray<string>
10
- }
11
-
12
- export const resolveWorkspaceRepoPath = (
13
- resolvedRepo: ReturnType<typeof resolveRepoInput>
14
- ): string => {
15
- const baseParts = deriveRepoPathParts(resolvedRepo.repoUrl).pathParts
16
- const projectParts = resolvedRepo.workspaceSuffix ? [...baseParts, resolvedRepo.workspaceSuffix] : baseParts
17
- return projectParts.join("/")
18
- }
19
-
20
- export const splitPositionalRepo = (args: ReadonlyArray<string>): PositionalRepo => {
21
- const first = args[0]
22
- const positionalRepoUrl = first !== undefined && !first.startsWith("-") ? first : undefined
23
- const restArgs = positionalRepoUrl ? args.slice(1) : args
24
- return { positionalRepoUrl, restArgs }
25
- }
26
-
27
- export const parseProjectDirWithOptions = (
28
- args: ReadonlyArray<string>,
29
- defaultProjectDir: string = "."
30
- ): Either.Either<{ readonly projectDir: string; readonly raw: RawOptions }, ParseError> =>
31
- Either.gen(function*(_) {
32
- const { positionalRepoUrl, restArgs } = splitPositionalRepo(args)
33
- const raw = yield* _(parseRawOptions(restArgs))
34
- const rawRepoUrl = raw.repoUrl ?? positionalRepoUrl
35
- const repoPath = rawRepoUrl ? resolveWorkspaceRepoPath(resolveRepoInput(rawRepoUrl)) : null
36
- const projectDir = raw.projectDir ??
37
- (repoPath
38
- ? `.docker-git/${repoPath}`
39
- : defaultProjectDir)
40
-
41
- return { projectDir, raw }
42
- })
43
-
44
- export const parseProjectDirArgs = (
45
- args: ReadonlyArray<string>,
46
- defaultProjectDir: string = "."
47
- ): Either.Either<{ readonly projectDir: string }, ParseError> =>
48
- Either.map(
49
- parseProjectDirWithOptions(args, defaultProjectDir),
50
- ({ projectDir }) => ({ projectDir })
51
- )
@@ -1,86 +0,0 @@
1
- import { Either, Match } from "effect"
2
-
3
- import type { Command, ParseError } from "@effect-template/lib/core/domain"
4
-
5
- import { parseRawOptions } from "./parser-options.js"
6
-
7
- const invalidStateAction = (value: string): ParseError => ({
8
- _tag: "InvalidOption",
9
- option: "state",
10
- reason: `unknown action: ${value}`
11
- })
12
-
13
- const unexpectedArgs = (value: string): Either.Either<Command, ParseError> =>
14
- Either.left({ _tag: "UnexpectedArgument", value })
15
-
16
- const parseStateInit = (args: ReadonlyArray<string>): Either.Either<Command, ParseError> =>
17
- Either.flatMap(parseRawOptions(args), (raw) => {
18
- const repoUrl = raw.repoUrl?.trim()
19
- if (!repoUrl || repoUrl.length === 0) {
20
- return Either.left({ _tag: "MissingRequiredOption", option: "--repo-url" })
21
- }
22
- return Either.right({
23
- _tag: "StateInit",
24
- repoUrl,
25
- repoRef: raw.repoRef?.trim() && raw.repoRef.trim().length > 0 ? raw.repoRef.trim() : "main"
26
- })
27
- })
28
-
29
- const parseStateCommit = (args: ReadonlyArray<string>): Either.Either<Command, ParseError> =>
30
- Either.flatMap(parseRawOptions(args), (raw) => {
31
- const message = raw.message?.trim()
32
- if (!message || message.length === 0) {
33
- return Either.left({ _tag: "MissingRequiredOption", option: "--message" })
34
- }
35
- return Either.right({ _tag: "StateCommit", message })
36
- })
37
-
38
- const parseStateSync = (args: ReadonlyArray<string>): Either.Either<Command, ParseError> =>
39
- Either.map(parseRawOptions(args), (raw) => {
40
- const message = raw.message?.trim()
41
- return { _tag: "StateSync", message: message && message.length > 0 ? message : null }
42
- })
43
-
44
- export const parseState = (args: ReadonlyArray<string>): Either.Either<Command, ParseError> => {
45
- const action = args[0]?.trim()
46
- if (!action || action.length === 0) {
47
- return Either.left({ _tag: "MissingRequiredOption", option: "state <action>" })
48
- }
49
-
50
- const rest = args.slice(1)
51
-
52
- return Match.value(action).pipe(
53
- Match.when("path", () => {
54
- if (rest.length > 0) {
55
- return unexpectedArgs(rest[0] ?? "")
56
- }
57
- const command: Command = { _tag: "StatePath" }
58
- return Either.right(command)
59
- }),
60
- Match.when("init", () => parseStateInit(rest)),
61
- Match.when("pull", () => {
62
- if (rest.length > 0) {
63
- return unexpectedArgs(rest[0] ?? "")
64
- }
65
- const command: Command = { _tag: "StatePull" }
66
- return Either.right(command)
67
- }),
68
- Match.when("push", () => {
69
- if (rest.length > 0) {
70
- return unexpectedArgs(rest[0] ?? "")
71
- }
72
- const command: Command = { _tag: "StatePush" }
73
- return Either.right(command)
74
- }),
75
- Match.when("status", () => {
76
- if (rest.length > 0) {
77
- return unexpectedArgs(rest[0] ?? "")
78
- }
79
- const command: Command = { _tag: "StateStatus" }
80
- return Either.right(command)
81
- }),
82
- Match.when("commit", () => parseStateCommit(rest)),
83
- Match.when("sync", () => parseStateSync(rest)),
84
- Match.orElse(() => Either.left(invalidStateAction(action)))
85
- )
86
- }
@@ -1,83 +0,0 @@
1
- import { Either, Match } from "effect"
2
-
3
- import { type Command, type ParseError } from "@effect-template/lib/core/domain"
4
-
5
- import { parseApply } from "./parser-apply.js"
6
- import { parseAttach } from "./parser-attach.js"
7
- import { parseAuth } from "./parser-auth.js"
8
- import { parseClone } from "./parser-clone.js"
9
- import { buildCreateCommand } from "./parser-create.js"
10
- import { parseMcpPlaywright } from "./parser-mcp-playwright.js"
11
- import { parseRawOptions } from "./parser-options.js"
12
- import { parsePanes } from "./parser-panes.js"
13
- import { parseScrap } from "./parser-scrap.js"
14
- import { parseSessions } from "./parser-sessions.js"
15
- import { parseState } from "./parser-state.js"
16
- import { usageText } from "./usage.js"
17
-
18
- const isHelpFlag = (token: string): boolean => token === "--help" || token === "-h"
19
-
20
- const helpCommand: Command = { _tag: "Help", message: usageText }
21
- const menuCommand: Command = { _tag: "Menu" }
22
- const statusCommand: Command = { _tag: "Status" }
23
- const downAllCommand: Command = { _tag: "DownAll" }
24
-
25
- const parseCreate = (args: ReadonlyArray<string>): Either.Either<Command, ParseError> =>
26
- Either.flatMap(parseRawOptions(args), (raw) => buildCreateCommand(raw))
27
-
28
- // CHANGE: parse CLI arguments into a typed command
29
- // WHY: enforce deterministic, pure parsing before any effects run
30
- // QUOTE(ТЗ): "Надо написать CLI команду с помощью которой мы будем создавать докер образы"
31
- // REF: user-request-2026-01-07
32
- // SOURCE: n/a
33
- // FORMAT THEOREM: forall argv: parse(argv) = cmd -> deterministic(cmd)
34
- // PURITY: CORE
35
- // EFFECT: Effect<Command, ParseError, never>
36
- // INVARIANT: parse does not perform IO and returns the same result for same argv
37
- // COMPLEXITY: O(n) where n = |argv|
38
- export const parseArgs = (args: ReadonlyArray<string>): Either.Either<Command, ParseError> => {
39
- if (args.length === 0) {
40
- return Either.right(menuCommand)
41
- }
42
-
43
- if (args.some((arg) => isHelpFlag(arg))) {
44
- return Either.right(helpCommand)
45
- }
46
-
47
- const command = args[0]
48
- const rest = args.slice(1)
49
- const unknownCommandError: ParseError = {
50
- _tag: "UnknownCommand",
51
- command: command ?? ""
52
- }
53
-
54
- return Match.value(command)
55
- .pipe(
56
- Match.when("create", () => parseCreate(rest)),
57
- Match.when("init", () => parseCreate(rest)),
58
- Match.when("clone", () => parseClone(rest)),
59
- Match.when("attach", () => parseAttach(rest)),
60
- Match.when("tmux", () => parseAttach(rest)),
61
- Match.when("panes", () => parsePanes(rest)),
62
- Match.when("terms", () => parsePanes(rest)),
63
- Match.when("terminals", () => parsePanes(rest)),
64
- Match.when("sessions", () => parseSessions(rest)),
65
- Match.when("scrap", () => parseScrap(rest)),
66
- Match.when("mcp-playwright", () => parseMcpPlaywright(rest)),
67
- Match.when("help", () => Either.right(helpCommand)),
68
- Match.when("ps", () => Either.right(statusCommand)),
69
- Match.when("status", () => Either.right(statusCommand)),
70
- Match.when("down-all", () => Either.right(downAllCommand)),
71
- Match.when("stop-all", () => Either.right(downAllCommand)),
72
- Match.when("kill-all", () => Either.right(downAllCommand)),
73
- Match.when("menu", () => Either.right(menuCommand)),
74
- Match.when("ui", () => Either.right(menuCommand)),
75
- Match.when("auth", () => parseAuth(rest))
76
- )
77
- .pipe(
78
- Match.when("open", () => parseAttach(rest)),
79
- Match.when("apply", () => parseApply(rest)),
80
- Match.when("state", () => parseState(rest)),
81
- Match.orElse(() => Either.left(unknownCommandError))
82
- )
83
- }