Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/app/src/docker-git/cli/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const helpCommand: Command = { _tag: "Help", message: usageText }
const menuCommand: Command = { _tag: "Menu" }
const statusCommand: Command = { _tag: "Status" }
const downAllCommand: Command = { _tag: "DownAll" }
const applyAllCommand: Command = { _tag: "ApplyAll" }

const parseCreate = (args: ReadonlyArray<string>): Either.Either<Command, ParseError> =>
Either.flatMap(parseRawOptions(args), (raw) => buildCreateCommand(raw))
Expand Down Expand Up @@ -75,6 +76,8 @@ export const parseArgs = (args: ReadonlyArray<string>): Either.Either<Command, P
Match.when("ui", () => Either.right(menuCommand))
)
.pipe(
Match.when("apply-all", () => Either.right(applyAllCommand)),
Match.when("update-all", () => Either.right(applyAllCommand)),
Match.when("auth", () => parseAuth(rest)),
Match.when("open", () => parseAttach(rest)),
Match.when("apply", () => parseApply(rest)),
Expand Down
2 changes: 2 additions & 0 deletions packages/app/src/docker-git/cli/usage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ docker-git session-gists backup [<url>] [options]
docker-git session-gists view <snapshot-ref>
docker-git session-gists download <snapshot-ref> [options]
docker-git ps
docker-git apply-all
docker-git down-all
docker-git auth <provider> <action> [options]
docker-git state <action> [options]
Expand All @@ -36,6 +37,7 @@ Commands:
sessions List/kill/log container terminal processes
session-gists Manage AI session backups via a private session repository (backup/list/view/download)
ps, status Show docker compose status for all docker-git projects
apply-all Apply docker-git config and refresh all containers (docker compose up)
down-all Stop all docker-git containers (docker compose down)
auth Manage GitHub/Codex/Claude Code auth for docker-git
state Manage docker-git state directory via git (sync across machines)
Expand Down
4 changes: 3 additions & 1 deletion packages/app/src/docker-git/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
import type { AppError } from "@effect-template/lib/usecases/errors"
import { renderError } from "@effect-template/lib/usecases/errors"
import { mcpPlaywrightUp } from "@effect-template/lib/usecases/mcp-playwright"
import { downAllDockerGitProjects, listProjectStatus } from "@effect-template/lib/usecases/projects"
import { applyAllDockerGitProjects, downAllDockerGitProjects, listProjectStatus } from "@effect-template/lib/usecases/projects"
import { exportScrap, importScrap } from "@effect-template/lib/usecases/scrap"
import {
sessionGistBackup,
Expand Down Expand Up @@ -80,6 +80,7 @@ type NonBaseCommand = Exclude<
| { readonly _tag: "Create" }
| { readonly _tag: "Status" }
| { readonly _tag: "DownAll" }
| { readonly _tag: "ApplyAll" }
| { readonly _tag: "Menu" }
>

Expand Down Expand Up @@ -141,6 +142,7 @@ export const program = pipe(
Match.when({ _tag: "Create" }, (create) => createProject(create)),
Match.when({ _tag: "Status" }, () => listProjectStatus),
Match.when({ _tag: "DownAll" }, () => downAllDockerGitProjects),
Match.when({ _tag: "ApplyAll" }, () => applyAllDockerGitProjects),
Match.when({ _tag: "Menu" }, () => runMenu),
Match.orElse((cmd) => handleNonBaseCommand(cmd))
)
Expand Down
6 changes: 6 additions & 0 deletions packages/app/tests/docker-git/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,12 @@ describe("parseArgs", () => {
expect(command.enableMcpPlaywright).toBe(true)
}))

it.effect("parses apply-all and update-all commands", () =>
Effect.sync(() => {
expect(parseOrThrow(["apply-all"])._tag).toBe("ApplyAll")
expect(parseOrThrow(["update-all"])._tag).toBe("ApplyAll")
}))

it.effect("parses down-all command", () =>
Effect.sync(() => {
const command = parseOrThrow(["down-all"])
Expand Down
13 changes: 13 additions & 0 deletions packages/lib/src/core/domain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,18 @@ export interface ApplyCommand {
readonly enableMcpPlaywright?: boolean | undefined
}

// CHANGE: add apply-all command to apply docker-git config to every known project
// WHY: allow bulk-updating all containers in one command instead of running apply for each project manually
// QUOTE(ТЗ): "Сделать команду которая сама на все контейнеры применит новые настройки"
// REF: issue-164
// PURITY: CORE
// EFFECT: n/a
// INVARIANT: applies to all discovered projects; individual failures do not abort the batch
// COMPLEXITY: O(1)
export interface ApplyAllCommand {
readonly _tag: "ApplyAll"
}

export interface HelpCommand {
readonly _tag: "Help"
readonly message: string
Expand Down Expand Up @@ -322,6 +334,7 @@ export type Command =
| ScrapCommand
| McpPlaywrightUpCommand
| ApplyCommand
| ApplyAllCommand
| HelpCommand
| StatusCommand
| DownAllCommand
Expand Down
64 changes: 64 additions & 0 deletions packages/lib/src/usecases/projects-apply-all.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import type { CommandExecutor } from "@effect/platform/CommandExecutor"
import type { PlatformError } from "@effect/platform/Error"
import type { FileSystem } from "@effect/platform/FileSystem"
import type { Path } from "@effect/platform/Path"
import { Effect, pipe } from "effect"

import { ensureDockerDaemonAccess } from "../shell/docker.js"
import type { DockerAccessError, DockerCommandError } from "../shell/errors.js"
import { renderError } from "./errors.js"
import { forEachProjectStatus, loadProjectIndex, renderProjectStatusHeader } from "./projects-core.js"
import { runDockerComposeUpWithPortCheck } from "./projects-up.js"

// CHANGE: provide an "apply all" helper for docker-git managed projects
// WHY: allow applying updated docker-git config to every known project in one command
// QUOTE(ТЗ): "Сделать команду которая сама на все контейнеры применит новые настройки"
// REF: issue-164
// SOURCE: n/a
// FORMAT THEOREM: ∀p ∈ Projects: applyAll(p) → updated(p) ∨ warned(p)
// PURITY: SHELL
// EFFECT: Effect<void, PlatformError | DockerAccessError, FileSystem | Path | CommandExecutor>
// INVARIANT: continues applying to other projects when one docker compose up fails with DockerCommandError
// COMPLEXITY: O(n) where n = |projects|
export const applyAllDockerGitProjects: Effect.Effect<
void,
PlatformError | DockerAccessError,
FileSystem | Path | CommandExecutor
> = pipe(
ensureDockerDaemonAccess(process.cwd()),
Effect.zipRight(loadProjectIndex()),
Effect.flatMap((index) =>
index === null
? Effect.void
: forEachProjectStatus(index.configPaths, (status) =>
pipe(
Effect.log(renderProjectStatusHeader(status)),
Effect.zipRight(
runDockerComposeUpWithPortCheck(status.projectDir).pipe(
Effect.catchTag("DockerCommandError", (error: DockerCommandError) =>
Effect.logWarning(
`apply failed for ${status.projectDir}: ${renderError(error)}. Check the project docker-compose config (e.g. env files for merge conflicts, port conflicts in docker-compose.yml config) and retry.`
)),
Effect.catchTag("ConfigNotFoundError", (error) =>
Effect.logWarning(
`Skipping ${status.projectDir}: ${renderError(error)}`
)),
Effect.catchTag("ConfigDecodeError", (error) =>
Effect.logWarning(
`Skipping ${status.projectDir}: ${renderError(error)}`
)),
Effect.catchTag("PortProbeError", (error) =>
Effect.logWarning(
`Skipping ${status.projectDir}: ${renderError(error)}`
)),
Effect.catchTag("FileExistsError", (error) =>
Effect.logWarning(
`Skipping ${status.projectDir}: ${renderError(error)}`
)),
Effect.asVoid
)
)
))
),
Effect.asVoid
)
1 change: 1 addition & 0 deletions packages/lib/src/usecases/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export {
type ProjectLoadError,
type ProjectStatus
} from "./projects-core.js"
export { applyAllDockerGitProjects } from "./projects-apply-all.js"
export { deleteDockerGitProject } from "./projects-delete.js"
export { downAllDockerGitProjects } from "./projects-down.js"
export { listProjectItems, listProjects, listProjectSummaries, listRunningProjectItems } from "./projects-list.js"
Expand Down