@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,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
- }