@prover-coder-ai/docker-git 1.0.22 → 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 (76) hide show
  1. package/README.md +3 -0
  2. package/dist/src/docker-git/main.js +5 -2
  3. package/dist/src/docker-git/main.js.map +1 -1
  4. package/package.json +5 -1
  5. package/.jscpd.json +0 -16
  6. package/.package.json.release.bak +0 -110
  7. package/CHANGELOG.md +0 -133
  8. package/biome.json +0 -34
  9. package/eslint.config.mts +0 -305
  10. package/eslint.effect-ts-check.config.mjs +0 -220
  11. package/linter.config.json +0 -33
  12. package/src/app/main.ts +0 -18
  13. package/src/app/program.ts +0 -75
  14. package/src/docker-git/cli/input.ts +0 -29
  15. package/src/docker-git/cli/parser-apply.ts +0 -28
  16. package/src/docker-git/cli/parser-attach.ts +0 -22
  17. package/src/docker-git/cli/parser-auth.ts +0 -154
  18. package/src/docker-git/cli/parser-clone.ts +0 -50
  19. package/src/docker-git/cli/parser-create.ts +0 -3
  20. package/src/docker-git/cli/parser-mcp-playwright.ts +0 -24
  21. package/src/docker-git/cli/parser-options.ts +0 -211
  22. package/src/docker-git/cli/parser-panes.ts +0 -22
  23. package/src/docker-git/cli/parser-scrap.ts +0 -106
  24. package/src/docker-git/cli/parser-sessions.ts +0 -101
  25. package/src/docker-git/cli/parser-shared.ts +0 -51
  26. package/src/docker-git/cli/parser-state.ts +0 -86
  27. package/src/docker-git/cli/parser.ts +0 -82
  28. package/src/docker-git/cli/read-command.ts +0 -26
  29. package/src/docker-git/cli/usage.ts +0 -129
  30. package/src/docker-git/main.ts +0 -18
  31. package/src/docker-git/menu-actions.ts +0 -273
  32. package/src/docker-git/menu-auth-data.ts +0 -184
  33. package/src/docker-git/menu-auth-helpers.ts +0 -30
  34. package/src/docker-git/menu-auth.ts +0 -311
  35. package/src/docker-git/menu-buffer-input.ts +0 -18
  36. package/src/docker-git/menu-create.ts +0 -310
  37. package/src/docker-git/menu-input-handler.ts +0 -183
  38. package/src/docker-git/menu-input-utils.ts +0 -85
  39. package/src/docker-git/menu-input.ts +0 -2
  40. package/src/docker-git/menu-labeled-env.ts +0 -37
  41. package/src/docker-git/menu-menu.ts +0 -58
  42. package/src/docker-git/menu-project-auth-claude.ts +0 -70
  43. package/src/docker-git/menu-project-auth-data.ts +0 -292
  44. package/src/docker-git/menu-project-auth.ts +0 -271
  45. package/src/docker-git/menu-render-auth.ts +0 -65
  46. package/src/docker-git/menu-render-common.ts +0 -67
  47. package/src/docker-git/menu-render-layout.ts +0 -30
  48. package/src/docker-git/menu-render-project-auth.ts +0 -70
  49. package/src/docker-git/menu-render-select.ts +0 -250
  50. package/src/docker-git/menu-render.ts +0 -292
  51. package/src/docker-git/menu-select-actions.ts +0 -150
  52. package/src/docker-git/menu-select-connect.ts +0 -27
  53. package/src/docker-git/menu-select-load.ts +0 -33
  54. package/src/docker-git/menu-select-order.ts +0 -37
  55. package/src/docker-git/menu-select-runtime.ts +0 -143
  56. package/src/docker-git/menu-select-view.ts +0 -25
  57. package/src/docker-git/menu-select.ts +0 -145
  58. package/src/docker-git/menu-shared.ts +0 -256
  59. package/src/docker-git/menu-startup.ts +0 -83
  60. package/src/docker-git/menu-types.ts +0 -170
  61. package/src/docker-git/menu.ts +0 -303
  62. package/src/docker-git/program.ts +0 -154
  63. package/src/docker-git/tmux.ts +0 -292
  64. package/tests/app/main.test.ts +0 -65
  65. package/tests/docker-git/entrypoint-auth.test.ts +0 -40
  66. package/tests/docker-git/fixtures/project-item.ts +0 -24
  67. package/tests/docker-git/menu-select-connect.test.ts +0 -55
  68. package/tests/docker-git/menu-select-order.test.ts +0 -84
  69. package/tests/docker-git/menu-startup.test.ts +0 -51
  70. package/tests/docker-git/parser-network-options.test.ts +0 -47
  71. package/tests/docker-git/parser.test.ts +0 -340
  72. package/tsconfig.build.json +0 -8
  73. package/tsconfig.json +0 -20
  74. package/vite.config.ts +0 -32
  75. package/vite.docker-git.config.ts +0 -34
  76. package/vitest.config.ts +0 -85
@@ -1,311 +0,0 @@
1
- import { Effect, Match, pipe } from "effect"
2
-
3
- import { authClaudeLogin, authClaudeLogout, authGithubLogin, claudeAuthRoot } from "@effect-template/lib/usecases/auth"
4
- import type { AppError } from "@effect-template/lib/usecases/errors"
5
- import { renderError } from "@effect-template/lib/usecases/errors"
6
-
7
- import {
8
- type AuthMenuAction,
9
- authMenuActionByIndex,
10
- authMenuSize,
11
- authViewSteps,
12
- readAuthSnapshot,
13
- successMessage,
14
- writeAuthFlow
15
- } from "./menu-auth-data.js"
16
- import { nextBufferValue } from "./menu-buffer-input.js"
17
- import { handleMenuNumberInput, submitPromptStep } from "./menu-input-utils.js"
18
- import { pauseOnError, resetToMenu, resumeSshWithSkipInputs, withSuspendedTui } from "./menu-shared.js"
19
- import type {
20
- AuthFlow,
21
- AuthSnapshot,
22
- MenuEnv,
23
- MenuKeyInput,
24
- MenuRunner,
25
- MenuState,
26
- MenuViewContext,
27
- ViewState
28
- } from "./menu-types.js"
29
-
30
- type AuthContext = MenuViewContext & {
31
- readonly state: MenuState
32
- readonly runner: MenuRunner
33
- }
34
-
35
- type AuthInputContext = AuthContext & {
36
- readonly setSshActive: (active: boolean) => void
37
- readonly setSkipInputs: (update: (value: number) => number) => void
38
- }
39
-
40
- type AuthPromptView = Extract<ViewState, { readonly _tag: "AuthPrompt" }>
41
-
42
- const defaultLabel = (value: string): string => {
43
- const trimmed = value.trim()
44
- return trimmed.length > 0 ? trimmed : "default"
45
- }
46
-
47
- const startAuthMenuWithSnapshot = (
48
- snapshot: AuthSnapshot,
49
- context: Pick<MenuViewContext, "setView" | "setMessage">
50
- ) => {
51
- context.setView({ _tag: "AuthMenu", selected: 0, snapshot })
52
- context.setMessage(null)
53
- }
54
-
55
- const startAuthPrompt = (
56
- snapshot: AuthSnapshot,
57
- flow: AuthFlow,
58
- context: Pick<MenuViewContext, "setView" | "setMessage">
59
- ) => {
60
- context.setView({
61
- _tag: "AuthPrompt",
62
- flow,
63
- step: 0,
64
- buffer: "",
65
- values: {},
66
- snapshot
67
- })
68
- context.setMessage(null)
69
- }
70
-
71
- const resolveLabelOption = (values: Readonly<Record<string, string>>): string | null => {
72
- const labelValue = (values["label"] ?? "").trim()
73
- return labelValue.length > 0 ? labelValue : null
74
- }
75
-
76
- const resolveAuthPromptEffect = (
77
- view: AuthPromptView,
78
- cwd: string,
79
- values: Readonly<Record<string, string>>
80
- ): Effect.Effect<void, AppError, MenuEnv> => {
81
- const labelOption = resolveLabelOption(values)
82
- return Match.value(view.flow).pipe(
83
- Match.when("GithubOauth", () =>
84
- authGithubLogin({
85
- _tag: "AuthGithubLogin",
86
- label: labelOption,
87
- token: null,
88
- scopes: null,
89
- envGlobalPath: view.snapshot.globalEnvPath
90
- })),
91
- Match.when("ClaudeOauth", () =>
92
- authClaudeLogin({
93
- _tag: "AuthClaudeLogin",
94
- label: labelOption,
95
- claudeAuthPath: claudeAuthRoot
96
- })),
97
- Match.when("ClaudeLogout", () =>
98
- authClaudeLogout({
99
- _tag: "AuthClaudeLogout",
100
- label: labelOption,
101
- claudeAuthPath: claudeAuthRoot
102
- })),
103
- Match.when("GithubRemove", (flow) => writeAuthFlow(cwd, flow, values)),
104
- Match.when("GitSet", (flow) => writeAuthFlow(cwd, flow, values)),
105
- Match.when("GitRemove", (flow) => writeAuthFlow(cwd, flow, values)),
106
- Match.exhaustive
107
- )
108
- }
109
-
110
- const runAuthPromptEffect = (
111
- effect: Effect.Effect<void, AppError, MenuEnv>,
112
- view: AuthPromptView,
113
- label: string,
114
- context: AuthInputContext,
115
- options: { readonly suspendTui: boolean }
116
- ) => {
117
- const withOptionalSuspension = options.suspendTui
118
- ? withSuspendedTui(effect, {
119
- onError: pauseOnError(renderError),
120
- onResume: resumeSshWithSkipInputs(context)
121
- })
122
- : effect
123
-
124
- context.setSshActive(options.suspendTui)
125
- context.runner.runEffect(
126
- pipe(
127
- withOptionalSuspension,
128
- Effect.zipRight(readAuthSnapshot(context.state.cwd)),
129
- Effect.tap((snapshot) =>
130
- Effect.sync(() => {
131
- startAuthMenuWithSnapshot(snapshot, context)
132
- context.setMessage(successMessage(view.flow, label))
133
- })
134
- ),
135
- Effect.asVoid
136
- )
137
- )
138
- }
139
-
140
- const loadAuthMenuView = (
141
- cwd: string,
142
- context: Pick<MenuViewContext, "setView" | "setMessage">
143
- ): Effect.Effect<void, AppError, MenuEnv> =>
144
- pipe(
145
- readAuthSnapshot(cwd),
146
- Effect.tap((snapshot) =>
147
- Effect.sync(() => {
148
- startAuthMenuWithSnapshot(snapshot, context)
149
- })
150
- ),
151
- Effect.asVoid
152
- )
153
-
154
- const runAuthAction = (
155
- action: AuthMenuAction,
156
- view: Extract<ViewState, { readonly _tag: "AuthMenu" }>,
157
- context: AuthContext
158
- ) => {
159
- if (action === "Back") {
160
- resetToMenu(context)
161
- return
162
- }
163
- if (action === "Refresh") {
164
- context.runner.runEffect(loadAuthMenuView(context.state.cwd, context))
165
- return
166
- }
167
- startAuthPrompt(view.snapshot, action, context)
168
- }
169
-
170
- const submitAuthPrompt = (
171
- view: AuthPromptView,
172
- context: AuthInputContext
173
- ) => {
174
- const steps = authViewSteps(view.flow)
175
- submitPromptStep(
176
- view,
177
- steps,
178
- context,
179
- () => {
180
- startAuthMenuWithSnapshot(view.snapshot, context)
181
- },
182
- (nextValues) => {
183
- const label = defaultLabel(nextValues["label"] ?? "")
184
- const effect = resolveAuthPromptEffect(view, context.state.cwd, nextValues)
185
- runAuthPromptEffect(effect, view, label, context, {
186
- suspendTui: view.flow === "GithubOauth" || view.flow === "ClaudeOauth" || view.flow === "ClaudeLogout"
187
- })
188
- }
189
- )
190
- }
191
-
192
- const setAuthMenuSelection = (
193
- view: Extract<ViewState, { readonly _tag: "AuthMenu" }>,
194
- selected: number,
195
- context: AuthContext
196
- ) => {
197
- context.setView({
198
- ...view,
199
- selected
200
- })
201
- }
202
-
203
- const shiftAuthMenuSelection = (
204
- view: Extract<ViewState, { readonly _tag: "AuthMenu" }>,
205
- delta: number,
206
- context: AuthContext
207
- ) => {
208
- const menuSize = authMenuSize()
209
- const selected = (view.selected + delta + menuSize) % menuSize
210
- setAuthMenuSelection(view, selected, context)
211
- }
212
-
213
- const runAuthMenuSelection = (
214
- selected: number,
215
- view: Extract<ViewState, { readonly _tag: "AuthMenu" }>,
216
- context: AuthContext
217
- ) => {
218
- const action = authMenuActionByIndex(selected)
219
- if (action === null) {
220
- return
221
- }
222
- runAuthAction(action, view, context)
223
- }
224
-
225
- const handleAuthMenuNumberInput = (
226
- input: string,
227
- view: Extract<ViewState, { readonly _tag: "AuthMenu" }>,
228
- context: AuthContext
229
- ) => {
230
- handleMenuNumberInput(input, context, authMenuActionByIndex, (action) => {
231
- runAuthAction(action, view, context)
232
- })
233
- }
234
-
235
- const handleAuthMenuInput = (
236
- input: string,
237
- key: MenuKeyInput,
238
- view: Extract<ViewState, { readonly _tag: "AuthMenu" }>,
239
- context: AuthContext
240
- ) => {
241
- if (key.escape) {
242
- resetToMenu(context)
243
- return
244
- }
245
- if (key.upArrow) {
246
- shiftAuthMenuSelection(view, -1, context)
247
- return
248
- }
249
- if (key.downArrow) {
250
- shiftAuthMenuSelection(view, 1, context)
251
- return
252
- }
253
- if (key.return) {
254
- runAuthMenuSelection(view.selected, view, context)
255
- return
256
- }
257
- handleAuthMenuNumberInput(input, view, context)
258
- }
259
-
260
- const handleAuthPromptInput = (
261
- input: string,
262
- key: MenuKeyInput,
263
- view: Extract<ViewState, { readonly _tag: "AuthPrompt" }>,
264
- context: AuthInputContext
265
- ) => {
266
- if (key.escape) {
267
- startAuthMenuWithSnapshot(view.snapshot, context)
268
- return
269
- }
270
- if (key.return) {
271
- submitAuthPrompt(view, context)
272
- return
273
- }
274
- setAuthPromptBuffer({ input, key, view, context })
275
- }
276
-
277
- type SetAuthPromptBufferArgs = {
278
- readonly input: string
279
- readonly key: MenuKeyInput
280
- readonly view: Extract<ViewState, { readonly _tag: "AuthPrompt" }>
281
- readonly context: Pick<MenuViewContext, "setView">
282
- }
283
-
284
- const setAuthPromptBuffer = (
285
- args: SetAuthPromptBufferArgs
286
- ) => {
287
- const { context, input, key, view } = args
288
- const nextBuffer = nextBufferValue(input, key, view.buffer)
289
- if (nextBuffer === null) {
290
- return
291
- }
292
- context.setView({ ...view, buffer: nextBuffer })
293
- }
294
-
295
- export const openAuthMenu = (context: AuthContext): void => {
296
- context.setMessage("Loading auth profiles...")
297
- context.runner.runEffect(loadAuthMenuView(context.state.cwd, context))
298
- }
299
-
300
- export const handleAuthInput = (
301
- input: string,
302
- key: MenuKeyInput,
303
- view: Extract<ViewState, { readonly _tag: "AuthMenu" | "AuthPrompt" }>,
304
- context: AuthInputContext
305
- ) => {
306
- if (view._tag === "AuthMenu") {
307
- handleAuthMenuInput(input, key, view, context)
308
- return
309
- }
310
- handleAuthPromptInput(input, key, view, context)
311
- }
@@ -1,18 +0,0 @@
1
- export type BufferInputKey = {
2
- readonly backspace?: boolean
3
- readonly delete?: boolean
4
- }
5
-
6
- export const nextBufferValue = (
7
- input: string,
8
- key: BufferInputKey,
9
- buffer: string
10
- ): string | null => {
11
- if (key.backspace || key.delete) {
12
- return buffer.slice(0, -1)
13
- }
14
- if (input.length > 0) {
15
- return buffer + input
16
- }
17
- return null
18
- }
@@ -1,310 +0,0 @@
1
- import { type CreateCommand, deriveRepoPathParts, resolveRepoInput } from "@effect-template/lib/core/domain"
2
- import { createProject } from "@effect-template/lib/usecases/actions"
3
- import type { AppError } from "@effect-template/lib/usecases/errors"
4
- import { defaultProjectsRoot } from "@effect-template/lib/usecases/menu-helpers"
5
- import * as Path from "@effect/platform/Path"
6
- import { Effect, Either, Match, pipe } from "effect"
7
- import { parseArgs } from "./cli/parser.js"
8
- import { formatParseError, usageText } from "./cli/usage.js"
9
-
10
- import { nextBufferValue } from "./menu-buffer-input.js"
11
- import { resetToMenu } from "./menu-shared.js"
12
- import {
13
- type CreateInputs,
14
- type CreateStep,
15
- createSteps,
16
- type MenuEnv,
17
- type MenuState,
18
- type ViewState
19
- } from "./menu-types.js"
20
-
21
- // CHANGE: move create-flow handling into a dedicated module
22
- // WHY: keep TUI entry slim and satisfy lint constraints
23
- // QUOTE(ТЗ): "TUI? Красивый, удобный"
24
- // REF: user-request-2026-02-01-tui
25
- // SOURCE: n/a
26
- // FORMAT THEOREM: forall s: step(s) -> step'(s)
27
- // PURITY: SHELL
28
- // EFFECT: Effect<void, AppError, FileSystem | Path | CommandExecutor>
29
- // INVARIANT: outDir resolves to a stable repo path
30
- // COMPLEXITY: O(1) per keypress
31
-
32
- type Mutable<T> = { -readonly [K in keyof T]: T[K] }
33
-
34
- type CreateRunner = { readonly runEffect: (effect: Effect.Effect<void, AppError, MenuEnv>) => void }
35
-
36
- type CreateContext = {
37
- readonly state: MenuState
38
- readonly setView: (view: ViewState) => void
39
- readonly setMessage: (message: string | null) => void
40
- readonly runner: CreateRunner
41
- readonly setActiveDir: (dir: string | null) => void
42
- }
43
-
44
- type CreateReturnContext = CreateContext & {
45
- readonly view: Extract<ViewState, { readonly _tag: "Create" }>
46
- }
47
-
48
- export const buildCreateArgs = (input: CreateInputs): ReadonlyArray<string> => {
49
- const args: Array<string> = ["create"]
50
- if (input.repoUrl.length > 0) {
51
- args.push("--repo-url", input.repoUrl)
52
- }
53
- if (input.repoRef.length > 0) {
54
- args.push("--repo-ref", input.repoRef)
55
- }
56
- if (input.outDir.length > 0) {
57
- args.push("--out-dir", input.outDir)
58
- }
59
- if (!input.runUp) {
60
- args.push("--no-up")
61
- }
62
- if (input.enableMcpPlaywright) {
63
- args.push("--mcp-playwright")
64
- }
65
- if (input.force) {
66
- args.push("--force")
67
- }
68
- if (input.forceEnv) {
69
- args.push("--force-env")
70
- }
71
- return args
72
- }
73
-
74
- const trimLeftSlash = (value: string): string => {
75
- let start = 0
76
- while (start < value.length && value[start] === "/") {
77
- start += 1
78
- }
79
- return value.slice(start)
80
- }
81
-
82
- const trimRightSlash = (value: string): string => {
83
- let end = value.length
84
- while (end > 0 && value[end - 1] === "/") {
85
- end -= 1
86
- }
87
- return value.slice(0, end)
88
- }
89
-
90
- const joinPath = (...parts: ReadonlyArray<string>): string => {
91
- const cleaned = parts
92
- .filter((part) => part.length > 0)
93
- .map((part, index) => {
94
- if (index === 0) {
95
- return trimRightSlash(part)
96
- }
97
- return trimRightSlash(trimLeftSlash(part))
98
- })
99
- return cleaned.join("/")
100
- }
101
-
102
- const resolveDefaultOutDir = (cwd: string, repoUrl: string): string => {
103
- const resolvedRepo = resolveRepoInput(repoUrl)
104
- const baseParts = deriveRepoPathParts(resolvedRepo.repoUrl).pathParts
105
- const projectParts = resolvedRepo.workspaceSuffix ? [...baseParts, resolvedRepo.workspaceSuffix] : baseParts
106
- return joinPath(defaultProjectsRoot(cwd), ...projectParts)
107
- }
108
-
109
- export const resolveCreateInputs = (
110
- cwd: string,
111
- values: Partial<CreateInputs>
112
- ): CreateInputs => {
113
- const repoUrl = values.repoUrl ?? ""
114
- const resolvedRepoRef = resolveRepoInput(repoUrl).repoRef
115
- const outDir = values.outDir ?? resolveDefaultOutDir(cwd, repoUrl)
116
-
117
- return {
118
- repoUrl,
119
- repoRef: values.repoRef ?? resolvedRepoRef ?? "main",
120
- outDir,
121
- runUp: values.runUp !== false,
122
- enableMcpPlaywright: values.enableMcpPlaywright === true,
123
- force: values.force === true,
124
- forceEnv: values.forceEnv === true
125
- }
126
- }
127
-
128
- const parseYesDefault = (input: string, fallback: boolean): boolean => {
129
- const normalized = input.trim().toLowerCase()
130
- if (normalized === "y" || normalized === "yes") {
131
- return true
132
- }
133
- if (normalized === "n" || normalized === "no") {
134
- return false
135
- }
136
- return fallback
137
- }
138
-
139
- const applyCreateCommand = (
140
- state: MenuState,
141
- create: CreateCommand
142
- ): Effect.Effect<{ readonly _tag: "Continue"; readonly state: MenuState }, AppError, MenuEnv> =>
143
- Effect.gen(function*(_) {
144
- const path = yield* _(Path.Path)
145
- const resolvedOutDir = path.resolve(create.outDir)
146
- yield* _(createProject(create))
147
- return { _tag: "Continue", state: { ...state, activeDir: resolvedOutDir } }
148
- })
149
-
150
- const isCreateCommand = (command: { readonly _tag: string }): command is CreateCommand => command._tag === "Create"
151
-
152
- const buildCreateEffect = (
153
- command: { readonly _tag: string },
154
- state: MenuState,
155
- setActiveDir: (dir: string | null) => void,
156
- setMessage: (message: string | null) => void
157
- ): Effect.Effect<void, AppError, MenuEnv> => {
158
- if (isCreateCommand(command)) {
159
- return pipe(
160
- applyCreateCommand(state, command),
161
- Effect.tap((outcome) =>
162
- Effect.sync(() => {
163
- setActiveDir(outcome.state.activeDir)
164
- })
165
- ),
166
- Effect.asVoid
167
- )
168
- }
169
- if (command._tag === "Help") {
170
- return Effect.sync(() => {
171
- setMessage(usageText)
172
- })
173
- }
174
- return Effect.void
175
- }
176
-
177
- const applyCreateStep = (input: {
178
- readonly step: CreateStep
179
- readonly buffer: string
180
- readonly currentDefaults: CreateInputs
181
- readonly nextValues: Partial<Mutable<CreateInputs>>
182
- readonly cwd: string
183
- readonly setMessage: (message: string | null) => void
184
- }): boolean =>
185
- Match.value(input.step).pipe(
186
- Match.when("repoUrl", () => {
187
- input.nextValues.repoUrl = input.buffer
188
- input.nextValues.outDir = resolveDefaultOutDir(input.cwd, input.buffer)
189
- return true
190
- }),
191
- Match.when("repoRef", () => {
192
- input.nextValues.repoRef = input.buffer.length > 0 ? input.buffer : input.currentDefaults.repoRef
193
- return true
194
- }),
195
- Match.when("outDir", () => {
196
- input.nextValues.outDir = input.buffer.length > 0 ? input.buffer : input.currentDefaults.outDir
197
- return true
198
- }),
199
- Match.when("runUp", () => {
200
- input.nextValues.runUp = parseYesDefault(input.buffer, input.currentDefaults.runUp)
201
- return true
202
- }),
203
- Match.when("mcpPlaywright", () => {
204
- input.nextValues.enableMcpPlaywright = parseYesDefault(
205
- input.buffer,
206
- input.currentDefaults.enableMcpPlaywright
207
- )
208
- return true
209
- }),
210
- Match.when("force", () => {
211
- input.nextValues.force = parseYesDefault(input.buffer, input.currentDefaults.force)
212
- return true
213
- }),
214
- Match.exhaustive
215
- )
216
-
217
- const finalizeCreateFlow = (input: {
218
- readonly state: MenuState
219
- readonly nextValues: Partial<CreateInputs>
220
- readonly setView: (view: ViewState) => void
221
- readonly setMessage: (message: string | null) => void
222
- readonly runner: CreateRunner
223
- readonly setActiveDir: (dir: string | null) => void
224
- }) => {
225
- const inputs = resolveCreateInputs(input.state.cwd, input.nextValues)
226
- const parsed = parseArgs(buildCreateArgs(inputs))
227
- if (Either.isLeft(parsed)) {
228
- input.setMessage(formatParseError(parsed.left))
229
- input.setView({ _tag: "Menu" })
230
- return
231
- }
232
-
233
- const effect = buildCreateEffect(parsed.right, input.state, input.setActiveDir, input.setMessage)
234
- input.runner.runEffect(effect)
235
- input.setView({ _tag: "Menu" })
236
- input.setMessage(null)
237
- }
238
-
239
- const handleCreateReturn = (context: CreateReturnContext) => {
240
- const step = createSteps[context.view.step]
241
- if (!step) {
242
- context.setView({ _tag: "Menu" })
243
- return
244
- }
245
-
246
- const buffer = context.view.buffer.trim()
247
- const currentDefaults = resolveCreateInputs(context.state.cwd, context.view.values)
248
- const nextValues: Partial<Mutable<CreateInputs>> = { ...context.view.values }
249
- const updated = applyCreateStep({
250
- step,
251
- buffer,
252
- currentDefaults,
253
- nextValues,
254
- cwd: context.state.cwd,
255
- setMessage: context.setMessage
256
- })
257
- if (!updated) {
258
- return
259
- }
260
-
261
- const nextStep = context.view.step + 1
262
- if (nextStep < createSteps.length) {
263
- context.setView({ _tag: "Create", step: nextStep, buffer: "", values: nextValues })
264
- context.setMessage(null)
265
- return
266
- }
267
-
268
- finalizeCreateFlow({
269
- state: context.state,
270
- nextValues,
271
- setView: context.setView,
272
- setMessage: context.setMessage,
273
- runner: context.runner,
274
- setActiveDir: context.setActiveDir
275
- })
276
- }
277
-
278
- export const startCreateView = (
279
- setView: (view: ViewState) => void,
280
- setMessage: (message: string | null) => void,
281
- buffer = ""
282
- ) => {
283
- setView({ _tag: "Create", step: 0, buffer, values: {} })
284
- setMessage(null)
285
- }
286
-
287
- export const handleCreateInput = (
288
- input: string,
289
- key: {
290
- readonly escape?: boolean
291
- readonly return?: boolean
292
- readonly backspace?: boolean
293
- readonly delete?: boolean
294
- },
295
- view: Extract<ViewState, { readonly _tag: "Create" }>,
296
- context: CreateContext
297
- ) => {
298
- if (key.escape) {
299
- resetToMenu(context)
300
- return
301
- }
302
- if (key.return) {
303
- handleCreateReturn({ ...context, view })
304
- return
305
- }
306
- const nextBuffer = nextBufferValue(input, key, view.buffer)
307
- if (nextBuffer !== null) {
308
- context.setView({ ...view, buffer: nextBuffer })
309
- }
310
- }