Skip to content

Commit ddb5177

Browse files
committed
Merge remote-tracking branch 'upstream/main' into issue-215-36d0d217ca4a
2 parents fa86ac1 + 15f7045 commit ddb5177

38 files changed

Lines changed: 598 additions & 338 deletions

.changeset/tidy-session-sync-tool.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
"@prover-coder-ai/docker-git": patch
44
---
55

6-
Extract AI agent session synchronization into a standalone docker-git-session-sync package.
6+
Publish docker-git-session-sync as a public npm CLI and install it for post-push session backup comments, with a local Docker build fallback before first publish.

packages/app/eslint.config.mts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,21 @@ export default defineConfig(
296296
'sonarjs/no-empty-test-file': 'off',
297297
},
298298
},
299+
{
300+
files: [
301+
"src/docker-git/menu-create-shared.ts",
302+
"src/web/app-ready-terminal-screen.tsx",
303+
"src/web/panel-content.tsx",
304+
"src/web/panel-create-select.tsx",
305+
"src/web/panel-project-details.tsx",
306+
"src/web/panel-terminal.tsx",
307+
"src/web/terminal-panel-runtime-core.ts",
308+
],
309+
rules: {
310+
"max-lines": "off",
311+
"max-lines-per-function": "off",
312+
},
313+
},
299314

300315
// 3) Для JS-файлов отключим типо-зависимые проверки
301316
{

packages/app/src/docker-git/menu-create-shared.ts

Lines changed: 111 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { Either, Match } from "effect"
2-
import { type CreateCommand, type ParseError, deriveRepoPathParts, resolveRepoInput } from "./frontend-lib/core/domain.js"
2+
import {
3+
type CreateCommand,
4+
deriveRepoPathParts,
5+
type ParseError,
6+
resolveRepoInput
7+
} from "./frontend-lib/core/domain.js"
38
import { defaultProjectsRoot } from "./frontend-lib/usecases/menu-helpers.js"
49

510
import { buildCreateCommand } from "./cli/parser-create.js"
@@ -25,6 +30,12 @@ type AdvanceCreateFlowResult =
2530
| { readonly _tag: "Error"; readonly error: ParseError }
2631
| { readonly _tag: "Complete"; readonly inputs: CreateInputs }
2732

33+
type AdvanceCreateFlowHandlers = {
34+
readonly onComplete: (inputs: CreateInputs) => void
35+
readonly onContinue: (view: CreateFlowView) => void
36+
readonly onError: (error: ParseError) => void
37+
}
38+
2839
type AdvanceCreateFlowOptions = {
2940
readonly quickCreate?: boolean
3041
}
@@ -134,59 +145,67 @@ const createParseError = (reason: string): ParseError => ({
134145
reason
135146
})
136147

148+
type CreateTokenizeState = {
149+
current: string
150+
escaping: boolean
151+
quote: "'" | "\"" | null
152+
readonly tokens: Array<string>
153+
}
154+
155+
const pushCreateToken = (state: CreateTokenizeState): void => {
156+
if (state.current.length > 0) {
157+
state.tokens.push(state.current)
158+
state.current = ""
159+
}
160+
}
161+
162+
const consumeCreateTokenChar = (state: CreateTokenizeState, char: string): void => {
163+
if (state.escaping) {
164+
state.current += char
165+
state.escaping = false
166+
return
167+
}
168+
if (char === "\\") {
169+
state.escaping = true
170+
return
171+
}
172+
if (state.quote !== null) {
173+
if (char === state.quote) {
174+
state.quote = null
175+
return
176+
}
177+
state.current += char
178+
return
179+
}
180+
if (char === "'" || char === "\"") {
181+
state.quote = char
182+
return
183+
}
184+
if (/\s/u.test(char)) {
185+
pushCreateToken(state)
186+
return
187+
}
188+
state.current += char
189+
}
190+
137191
const tokenizeCreateCommandLine = (
138192
input: string
139193
): Either.Either<ReadonlyArray<string>, ParseError> => {
140-
const tokens: Array<string> = []
141-
let current = ""
142-
let quote: "'" | "\"" | null = null
143-
let escaping = false
144-
145-
const pushCurrent = () => {
146-
if (current.length > 0) {
147-
tokens.push(current)
148-
current = ""
149-
}
150-
}
194+
const state: CreateTokenizeState = { current: "", escaping: false, quote: null, tokens: [] }
151195

152196
for (const char of input.trim()) {
153-
if (escaping) {
154-
current += char
155-
escaping = false
156-
continue
157-
}
158-
if (char === "\\") {
159-
escaping = true
160-
continue
161-
}
162-
if (quote !== null) {
163-
if (char === quote) {
164-
quote = null
165-
} else {
166-
current += char
167-
}
168-
continue
169-
}
170-
if (char === "'" || char === "\"") {
171-
quote = char
172-
continue
173-
}
174-
if (/\s/u.test(char)) {
175-
pushCurrent()
176-
continue
177-
}
178-
current += char
197+
consumeCreateTokenChar(state, char)
179198
}
180199

181-
if (escaping) {
200+
if (state.escaping) {
182201
return Either.left(createParseError("unterminated escape sequence"))
183202
}
184-
if (quote !== null) {
203+
if (state.quote !== null) {
185204
return Either.left(createParseError("unterminated quoted value"))
186205
}
187206

188-
pushCurrent()
189-
return Either.right(tokens)
207+
pushCreateToken(state)
208+
return Either.right(state.tokens)
190209
}
191210

192211
const unsupportedCreatePrefixes = new Set([
@@ -234,22 +253,40 @@ const normalizeCreateTokens = (
234253
return Either.right(withoutBinary)
235254
}
236255

256+
type RawCreateOptions = Parameters<typeof buildCreateCommand>[0]
257+
258+
const cpuLimitCreateInput = (raw: RawCreateOptions, command: CreateCommand): Partial<CreateInputs> =>
259+
raw.cpuLimit === undefined ? {} : { cpuLimit: command.config.cpuLimit ?? "" }
260+
261+
const ramLimitCreateInput = (raw: RawCreateOptions, command: CreateCommand): Partial<CreateInputs> =>
262+
raw.ramLimit === undefined ? {} : { ramLimit: command.config.ramLimit ?? "" }
263+
264+
const runUpCreateInput = (raw: RawCreateOptions, command: CreateCommand): Partial<CreateInputs> =>
265+
raw.up === undefined ? {} : { runUp: command.runUp }
266+
267+
const playwrightCreateInput = (raw: RawCreateOptions, command: CreateCommand): Partial<CreateInputs> =>
268+
raw.enableMcpPlaywright === undefined ? {} : { enableMcpPlaywright: command.config.enableMcpPlaywright }
269+
270+
const forceCreateInput = (raw: RawCreateOptions, command: CreateCommand): Partial<CreateInputs> =>
271+
raw.force === undefined ? {} : { force: command.force }
272+
273+
const forceEnvCreateInput = (raw: RawCreateOptions, command: CreateCommand): Partial<CreateInputs> =>
274+
raw.forceEnv === undefined ? {} : { forceEnv: command.forceEnv }
275+
237276
const createInputsFromCommand = (
238277
repoUrl: string,
239-
raw: Parameters<typeof buildCreateCommand>[0],
278+
raw: RawCreateOptions,
240279
command: CreateCommand
241280
): Partial<CreateInputs> => ({
242281
repoUrl,
243282
repoRef: command.config.repoRef,
244283
outDir: command.outDir,
245-
...(raw.cpuLimit !== undefined ? { cpuLimit: command.config.cpuLimit ?? "" } : {}),
246-
...(raw.ramLimit !== undefined ? { ramLimit: command.config.ramLimit ?? "" } : {}),
247-
...(raw.up !== undefined ? { runUp: command.runUp } : {}),
248-
...(raw.enableMcpPlaywright !== undefined
249-
? { enableMcpPlaywright: command.config.enableMcpPlaywright }
250-
: {}),
251-
...(raw.force !== undefined ? { force: command.force } : {}),
252-
...(raw.forceEnv !== undefined ? { forceEnv: command.forceEnv } : {})
284+
...cpuLimitCreateInput(raw, command),
285+
...ramLimitCreateInput(raw, command),
286+
...runUpCreateInput(raw, command),
287+
...playwrightCreateInput(raw, command),
288+
...forceCreateInput(raw, command),
289+
...forceEnvCreateInput(raw, command)
253290
})
254291

255292
const parseRepoStepInput = (
@@ -279,9 +316,12 @@ const parseRepoStepInput = (
279316
})
280317
}
281318

282-
const createStepApplied = (): Either.Either<true, ParseError> => Either.right(true as const)
319+
const createStepApplied = (): Either.Either<true, ParseError> => {
320+
const applied = true
321+
return Either.right(applied)
322+
}
283323

284-
const hasOwn = <K extends keyof CreateInputs>(values: Partial<CreateInputs>, key: K): boolean =>
324+
const hasOwn = (values: Partial<CreateInputs>, key: keyof CreateInputs): boolean =>
285325
Object.prototype.hasOwnProperty.call(values, key)
286326

287327
const isCreateStepSatisfied = (
@@ -432,6 +472,24 @@ export const advanceCreateFlow = (
432472
}
433473
}
434474

475+
export const handleAdvanceCreateFlowResult = (
476+
next: AdvanceCreateFlowResult | null,
477+
handlers: AdvanceCreateFlowHandlers
478+
): void => {
479+
if (next === null) {
480+
return
481+
}
482+
if (next._tag === "Error") {
483+
handlers.onError(next.error)
484+
return
485+
}
486+
if (next._tag === "Continue") {
487+
handlers.onContinue(next.view)
488+
return
489+
}
490+
handlers.onComplete(next.inputs)
491+
}
492+
435493
export const createProjectDraftFromInputs = (
436494
input: CreateInputs
437495
): {

packages/app/src/docker-git/menu-create.ts

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ import { formatParseError, usageText } from "./cli/usage.js"
77
import type { MenuError } from "./menu-errors.js"
88

99
import { nextBufferValue } from "./menu-buffer-input.js"
10-
import { advanceCreateFlow, createInitialFlowView, resolveCreateInputs } from "./menu-create-shared.js"
10+
import {
11+
advanceCreateFlow,
12+
createInitialFlowView,
13+
handleAdvanceCreateFlowResult,
14+
resolveCreateInputs
15+
} from "./menu-create-shared.js"
1116
import { resetToMenu } from "./menu-shared.js"
1217
import { type CreateInputs, type MenuEnv, type MenuState, type ViewState } from "./menu-types.js"
1318

@@ -138,25 +143,24 @@ const handleCreateReturn = (
138143
quickCreate = false
139144
) => {
140145
const next = advanceCreateFlow(context.state.cwd, context.view, { quickCreate })
141-
if (next === null) {
142-
return
143-
}
144-
if (next._tag === "Error") {
145-
context.setMessage(formatParseError(next.error))
146-
return
147-
}
148-
if (next._tag === "Continue") {
149-
context.setView({ _tag: "Create", ...next.view })
150-
context.setMessage(null)
151-
return
152-
}
153-
finalizeCreateFlow({
154-
state: context.state,
155-
nextValues: next.inputs,
156-
setView: context.setView,
157-
setMessage: context.setMessage,
158-
runner: context.runner,
159-
setActiveDir: context.setActiveDir
146+
handleAdvanceCreateFlowResult(next, {
147+
onComplete: (inputs) => {
148+
finalizeCreateFlow({
149+
state: context.state,
150+
nextValues: inputs,
151+
setView: context.setView,
152+
setMessage: context.setMessage,
153+
runner: context.runner,
154+
setActiveDir: context.setActiveDir
155+
})
156+
},
157+
onContinue: (view) => {
158+
context.setView({ _tag: "Create", ...view })
159+
context.setMessage(null)
160+
},
161+
onError: (error) => {
162+
context.setMessage(formatParseError(error))
163+
}
160164
})
161165
}
162166

packages/app/src/docker-git/menu-render.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ import {
1313
type SelectPurpose,
1414
selectTitle
1515
} from "./menu-render-select.js"
16-
import type { CreateInputs, SelectProjectRuntime } from "./menu-types.js"
17-
import { type CreateStep, menuItems } from "./menu-types.js"
16+
import { type CreateInputs, type CreateStep, menuItems, type SelectProjectRuntime } from "./menu-types.js"
1817
import type { ProjectItem } from "./project-item.js"
1918

2019
// CHANGE: render menu views with Ink without JSX
@@ -71,6 +70,15 @@ type MenuRenderInput = {
7170
readonly message: string | null
7271
}
7372

73+
type CreateRenderInput = {
74+
readonly buffer: string
75+
readonly defaults: CreateInputs
76+
readonly label: string
77+
readonly message: string | null
78+
readonly stepIndex: number
79+
readonly steps: ReadonlyArray<CreateStep>
80+
}
81+
7482
export const renderMenu = (input: MenuRenderInput): React.ReactElement => {
7583
const { activeDir, busy, cwd, message, runningDockerGitContainers, selected } = input
7684
const el = React.createElement
@@ -109,14 +117,8 @@ export const renderMenu = (input: MenuRenderInput): React.ReactElement => {
109117
)
110118
}
111119

112-
export const renderCreate = (
113-
label: string,
114-
buffer: string,
115-
message: string | null,
116-
stepIndex: number,
117-
defaults: CreateInputs,
118-
steps: ReadonlyArray<CreateStep>
119-
): React.ReactElement => {
120+
export const renderCreate = (input: CreateRenderInput): React.ReactElement => {
121+
const { buffer, defaults, label, message, stepIndex, steps } = input
120122
const el = React.createElement
121123
const hint = stepIndex === 0
122124
? "Enter = next, Shift+Enter = quick create, Esc = cancel."

packages/app/src/docker-git/menu.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,14 @@ const renderView = (context: RenderContext) => {
6262
const step = steps[context.view.step] ?? "repoUrl"
6363
const label = renderCreateStepLabel(step, currentDefaults)
6464

65-
return renderCreate(label, context.view.buffer, context.message, context.view.step, currentDefaults, steps)
65+
return renderCreate({
66+
buffer: context.view.buffer,
67+
defaults: currentDefaults,
68+
label,
69+
message: context.message,
70+
stepIndex: context.view.step,
71+
steps
72+
})
6673
}
6774

6875
if (context.view._tag === "AuthMenu") {

packages/app/src/lib/core/templates.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const renderGitignore = (): string =>
1414
`# docker-git project files
1515
# NOTE: bootstrap secrets stay local-only and should not be committed.
1616
17-
# docker-git scripts/tools (copied from workspace, rebuilt on each project update)
17+
# docker-git scripts/tools (scripts plus local session-sync fallback)
1818
scripts/
1919
.docker-git-tools/
2020

0 commit comments

Comments
 (0)