Skip to content

Commit 875fbd5

Browse files
committed
fix(app): keep controller compose lintable
1 parent 025b925 commit 875fbd5

4 files changed

Lines changed: 334 additions & 282 deletions

File tree

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import type { PlatformError } from "@effect/platform/Error"
2+
import * as FileSystem from "@effect/platform/FileSystem"
3+
import * as Path from "@effect/platform/Path"
4+
import { Effect } from "effect"
5+
6+
import { type ControllerBootstrapError, controllerBootstrapError } from "./host-errors.js"
7+
8+
export const controllerGpuModeEnvKey = "DOCKER_GIT_CONTROLLER_GPU"
9+
export const controllerComposeExtraFileEnvKey = "DOCKER_GIT_CONTROLLER_COMPOSE_EXTRA_FILE"
10+
11+
export type ControllerGpuMode = "none" | "all"
12+
13+
export type ControllerComposeFiles = {
14+
readonly composePath: string
15+
readonly extraOverlayPath: string | null
16+
readonly gpuOverlayPath: string | null
17+
readonly runtimeOverlayPath: string | null
18+
}
19+
20+
const mapComposePathError = (error: PlatformError): ControllerBootstrapError =>
21+
controllerBootstrapError(`Failed to resolve docker-compose.yml path.\nDetails: ${String(error)}`)
22+
23+
// CHANGE: add a verified controller compose overlay boundary for E2E/runtime callers
24+
// WHY: temporary compose overrides must be part of the explicit docker compose argument vector
25+
// QUOTE(ТЗ): n/a
26+
// REF: issue-440-review-compose-overlay
27+
// SOURCE: n/a
28+
// FORMAT THEOREM: forall p: env(extra)=p and exists(resolve(p)) -> resolve(extra)=Some(resolve(p))
29+
// PURITY: SHELL
30+
// EFFECT: Effect<string | null, ControllerBootstrapError, FileSystem | Path>
31+
// INVARIANT: non-empty extra compose env values either resolve to an existing file or fail before docker compose
32+
// COMPLEXITY: O(1)
33+
export const loadControllerComposeExtraPath = (): Effect.Effect<
34+
string | null,
35+
ControllerBootstrapError,
36+
FileSystem.FileSystem | Path.Path
37+
> =>
38+
Effect.gen(function*(_) {
39+
const raw = process.env[controllerComposeExtraFileEnvKey]?.trim() ?? ""
40+
if (raw.length === 0) {
41+
return null
42+
}
43+
44+
const fs = yield* _(FileSystem.FileSystem)
45+
const path = yield* _(Path.Path)
46+
const extraOverlayPath = path.resolve(raw)
47+
const isExists = yield* _(fs.exists(extraOverlayPath).pipe(Effect.mapError(mapComposePathError)))
48+
return isExists
49+
? extraOverlayPath
50+
: yield* _(
51+
Effect.fail(
52+
controllerBootstrapError(
53+
`${controllerComposeExtraFileEnvKey} points to ${extraOverlayPath}, but it was not found.`
54+
)
55+
)
56+
)
57+
})
58+
59+
export const composeFilesForMode = (
60+
composePath: string,
61+
gpuOverlayPath: string | null,
62+
runtimeOverlayPath: string | null = null,
63+
extraOverlayPath: string | null = null
64+
): ReadonlyArray<string> => [
65+
"-f",
66+
composePath,
67+
...(runtimeOverlayPath === null ? [] : ["-f", runtimeOverlayPath]),
68+
...(gpuOverlayPath === null ? [] : ["-f", gpuOverlayPath]),
69+
...(extraOverlayPath === null ? [] : ["-f", extraOverlayPath])
70+
]
71+
72+
export const composeFilesToArgs = (composeFiles: ControllerComposeFiles): ReadonlyArray<string> =>
73+
composeFilesForMode(
74+
composeFiles.composePath,
75+
composeFiles.gpuOverlayPath,
76+
composeFiles.runtimeOverlayPath,
77+
composeFiles.extraOverlayPath
78+
)
79+
80+
const requireGpuOverlayPath = (
81+
composePath: string
82+
): Effect.Effect<string, ControllerBootstrapError, FileSystem.FileSystem | Path.Path> =>
83+
Effect.gen(function*(_) {
84+
const fs = yield* _(FileSystem.FileSystem)
85+
const path = yield* _(Path.Path)
86+
const gpuOverlayPath = path.join(path.dirname(composePath), "docker-compose.gpu.yml")
87+
const isExists = yield* _(fs.exists(gpuOverlayPath).pipe(Effect.mapError(mapComposePathError)))
88+
return isExists
89+
? gpuOverlayPath
90+
: yield* _(
91+
Effect.fail(
92+
controllerBootstrapError(`${controllerGpuModeEnvKey}=all requires ${gpuOverlayPath}, but it was not found.`)
93+
)
94+
)
95+
})
96+
97+
export const composeFilesForGpuMode = (
98+
composePath: string,
99+
gpuMode: ControllerGpuMode
100+
): Effect.Effect<ControllerComposeFiles, ControllerBootstrapError, FileSystem.FileSystem | Path.Path> =>
101+
gpuMode === "none"
102+
? Effect.succeed({ composePath, extraOverlayPath: null, gpuOverlayPath: null, runtimeOverlayPath: null })
103+
: requireGpuOverlayPath(composePath).pipe(
104+
Effect.map((gpuOverlayPath) => ({ composePath, extraOverlayPath: null, gpuOverlayPath, runtimeOverlayPath: null }))
105+
)

packages/app/src/docker-git/controller-compose.ts

Lines changed: 15 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,31 @@ import * as FileSystem from "@effect/platform/FileSystem"
44
import * as Path from "@effect/platform/Path"
55
import { Duration, Effect } from "effect"
66

7+
import {
8+
type ControllerComposeFiles,
9+
type ControllerGpuMode,
10+
composeFilesForGpuMode,
11+
controllerGpuModeEnvKey,
12+
loadControllerComposeExtraPath
13+
} from "./controller-compose-files.js"
714
import { loadControllerDockerRuntime, resolveControllerRuntimeOverlayPath } from "./controller-compose-runtime.js"
815
import { computeLocalControllerRevision, controllerRevisionEnvKey } from "./controller-revision.js"
916
import type { ControllerDockerRuntime } from "./controller-runtime.js"
1017
import { runCommandWithCapturedOutput } from "./frontend-lib/shell/command-runner.js"
1118
import { findExistingUpwards } from "./frontend-lib/usecases/path-helpers.js"
12-
import type { ControllerBootstrapError } from "./host-errors.js"
19+
import { type ControllerBootstrapError, controllerBootstrapError } from "./host-errors.js"
1320

14-
export const controllerGpuModeEnvKey = "DOCKER_GIT_CONTROLLER_GPU"
1521
export const controllerBuildSkillerEnvKey = "DOCKER_GIT_CONTROLLER_BUILD_SKILLER"
16-
export const controllerComposeExtraFileEnvKey = "DOCKER_GIT_CONTROLLER_COMPOSE_EXTRA_FILE"
1722

18-
export type ControllerGpuMode = "none" | "all"
1923
export type ControllerBuildSkillerMode = "0" | "1"
2024

21-
export type ControllerComposeFiles = {
22-
readonly composePath: string
23-
readonly extraOverlayPath: string | null
24-
readonly gpuOverlayPath: string | null
25-
readonly runtimeOverlayPath: string | null
26-
}
25+
export {
26+
composeFilesForMode,
27+
composeFilesToArgs,
28+
controllerComposeExtraFileEnvKey,
29+
controllerGpuModeEnvKey
30+
} from "./controller-compose-files.js"
31+
export type { ControllerComposeFiles, ControllerGpuMode } from "./controller-compose-files.js"
2732

2833
export const controllerComposeProjectName = "docker-git"
2934

@@ -45,11 +50,6 @@ export const controllerComposeProjectArgs: ReadonlyArray<string> = [
4550
const skillerSubmodulePath = "third_party/skiller-desktop-skills-manager"
4651
const skillerPackagePath = `${skillerSubmodulePath}/package.json`
4752

48-
const controllerBootstrapError = (message: string): ControllerBootstrapError => ({
49-
_tag: "ControllerBootstrapError",
50-
message
51-
})
52-
5353
export const parseControllerGpuMode = (raw?: string): ControllerGpuMode | null => {
5454
const trimmed = raw?.trim() ?? ""
5555
if (trimmed.length === 0 || trimmed === "none") {
@@ -116,42 +116,6 @@ const mapSkillerPathError = (error: PlatformError): ControllerBootstrapError =>
116116
const mapControllerRevisionError = (error: PlatformError): ControllerBootstrapError =>
117117
controllerBootstrapError(`Failed to compute docker-git controller revision.\nDetails: ${String(error)}`)
118118

119-
// CHANGE: add a verified controller compose overlay boundary for E2E/runtime callers
120-
// WHY: temporary compose overrides must be part of the explicit docker compose argument vector
121-
// QUOTE(ТЗ): n/a
122-
// REF: issue-440-review-compose-overlay
123-
// SOURCE: n/a
124-
// FORMAT THEOREM: forall p: env(extra)=p and exists(resolve(p)) -> resolve(extra)=Some(resolve(p))
125-
// PURITY: SHELL
126-
// EFFECT: Effect<string | null, ControllerBootstrapError, FileSystem | Path>
127-
// INVARIANT: non-empty extra compose env values either resolve to an existing file or fail before docker compose
128-
// COMPLEXITY: O(1)
129-
const loadControllerComposeExtraPath = (): Effect.Effect<
130-
string | null,
131-
ControllerBootstrapError,
132-
FileSystem.FileSystem | Path.Path
133-
> =>
134-
Effect.gen(function*(_) {
135-
const raw = process.env[controllerComposeExtraFileEnvKey]?.trim() ?? ""
136-
if (raw.length === 0) {
137-
return null
138-
}
139-
140-
const fs = yield* _(FileSystem.FileSystem)
141-
const path = yield* _(Path.Path)
142-
const extraOverlayPath = path.resolve(raw)
143-
const isExists = yield* _(fs.exists(extraOverlayPath).pipe(Effect.mapError(mapComposePathError)))
144-
return isExists
145-
? extraOverlayPath
146-
: yield* _(
147-
Effect.fail(
148-
controllerBootstrapError(
149-
`${controllerComposeExtraFileEnvKey} points to ${extraOverlayPath}, but it was not found.`
150-
)
151-
)
152-
)
153-
})
154-
155119
const skillerSubmoduleCommand = [
156120
"submodule",
157121
"update",
@@ -244,54 +208,6 @@ export const ensureSkillerSubmoduleInitialized = (
244208
)
245209
})
246210

247-
export const composeFilesForMode = (
248-
composePath: string,
249-
gpuOverlayPath: string | null,
250-
runtimeOverlayPath: string | null = null,
251-
extraOverlayPath: string | null = null
252-
): ReadonlyArray<string> => [
253-
"-f",
254-
composePath,
255-
...(runtimeOverlayPath === null ? [] : ["-f", runtimeOverlayPath]),
256-
...(gpuOverlayPath === null ? [] : ["-f", gpuOverlayPath]),
257-
...(extraOverlayPath === null ? [] : ["-f", extraOverlayPath])
258-
]
259-
260-
export const composeFilesToArgs = (composeFiles: ControllerComposeFiles): ReadonlyArray<string> =>
261-
composeFilesForMode(
262-
composeFiles.composePath,
263-
composeFiles.gpuOverlayPath,
264-
composeFiles.runtimeOverlayPath,
265-
composeFiles.extraOverlayPath
266-
)
267-
268-
const requireGpuOverlayPath = (
269-
composePath: string
270-
): Effect.Effect<string, ControllerBootstrapError, FileSystem.FileSystem | Path.Path> =>
271-
Effect.gen(function*(_) {
272-
const fs = yield* _(FileSystem.FileSystem)
273-
const path = yield* _(Path.Path)
274-
const gpuOverlayPath = path.join(path.dirname(composePath), "docker-compose.gpu.yml")
275-
const isExists = yield* _(fs.exists(gpuOverlayPath).pipe(Effect.mapError(mapComposePathError)))
276-
return isExists
277-
? gpuOverlayPath
278-
: yield* _(
279-
Effect.fail(
280-
controllerBootstrapError(`${controllerGpuModeEnvKey}=all requires ${gpuOverlayPath}, but it was not found.`)
281-
)
282-
)
283-
})
284-
285-
const composeFilesForGpuMode = (
286-
composePath: string,
287-
gpuMode: ControllerGpuMode
288-
): Effect.Effect<ControllerComposeFiles, ControllerBootstrapError, FileSystem.FileSystem | Path.Path> =>
289-
gpuMode === "none"
290-
? Effect.succeed({ composePath, extraOverlayPath: null, gpuOverlayPath: null, runtimeOverlayPath: null })
291-
: requireGpuOverlayPath(composePath).pipe(
292-
Effect.map((gpuOverlayPath) => ({ composePath, extraOverlayPath: null, gpuOverlayPath, runtimeOverlayPath: null }))
293-
)
294-
295211
type ComposePathAndGpuMode = {
296212
readonly composePath: string
297213
readonly dockerRuntime: ControllerDockerRuntime

0 commit comments

Comments
 (0)