Skip to content

Commit 9541d64

Browse files
committed
fix(lib): pass effect lint
1 parent 35a7db7 commit 9541d64

15 files changed

Lines changed: 241 additions & 206 deletions

packages/lib/src/core/templates-entrypoint.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -101,19 +101,19 @@ fi`
101101
// INVARIANT: config.toml is only appended once per container (idempotent)
102102
// COMPLEXITY: O(1)
103103
const renderEntrypointMcpPlaywright = (config: TemplateConfig): string =>
104-
`# Optional: configure Playwright MCP for Codex (browser automation)
104+
String.raw`# Optional: configure Playwright MCP for Codex (browser automation)
105105
CODEX_CONFIG_FILE="${config.codexHome}/config.toml"
106106
107107
# Keep config.toml consistent with the container build.
108108
# If Playwright MCP is disabled for this container, remove the block so Codex
109109
# doesn't try (and fail) to spawn docker-git-playwright-mcp.
110110
if [[ "$MCP_PLAYWRIGHT_ENABLE" != "1" ]]; then
111-
if [[ -f "$CODEX_CONFIG_FILE" ]] && grep -q "^\\[mcp_servers\\.playwright" "$CODEX_CONFIG_FILE" 2>/dev/null; then
111+
if [[ -f "$CODEX_CONFIG_FILE" ]] && grep -q "^\[mcp_servers\.playwright" "$CODEX_CONFIG_FILE" 2>/dev/null; then
112112
awk '
113113
BEGIN { skip=0 }
114114
/^# docker-git: Playwright MCP/ { next }
115-
/^\\[mcp_servers[.]playwright([.]|\\])/ { skip=1; next }
116-
skip==1 && /^\\[/ { skip=0 }
115+
/^\[mcp_servers[.]playwright([.]|\])/ { skip=1; next }
116+
skip==1 && /^\[/ { skip=0 }
117117
skip==0 { print }
118118
' "$CODEX_CONFIG_FILE" > "$CODEX_CONFIG_FILE.tmp"
119119
mv "$CODEX_CONFIG_FILE.tmp" "$CODEX_CONFIG_FILE"
@@ -146,12 +146,12 @@ EOF
146146
fi
147147
148148
# Replace the docker-git Playwright block to allow upgrades via --force without manual edits.
149-
if grep -q "^\\[mcp_servers\\.playwright" "$CODEX_CONFIG_FILE" 2>/dev/null; then
149+
if grep -q "^\[mcp_servers\.playwright" "$CODEX_CONFIG_FILE" 2>/dev/null; then
150150
awk '
151151
BEGIN { skip=0 }
152152
/^# docker-git: Playwright MCP/ { next }
153-
/^\\[mcp_servers[.]playwright([.]|\\])/ { skip=1; next }
154-
skip==1 && /^\\[/ { skip=0 }
153+
/^\[mcp_servers[.]playwright([.]|\])/ { skip=1; next }
154+
skip==1 && /^\[/ { skip=0 }
155155
skip==0 { print }
156156
' "$CODEX_CONFIG_FILE" > "$CODEX_CONFIG_FILE.tmp"
157157
mv "$CODEX_CONFIG_FILE.tmp" "$CODEX_CONFIG_FILE"
@@ -573,11 +573,11 @@ fi`
573573
// INVARIANT: edits /etc/pam.d/sshd idempotently (comments only)
574574
// COMPLEXITY: O(n) where n = number of pam lines
575575
const renderEntrypointDisableMotd = (): string =>
576-
`# 4.75) Disable Ubuntu MOTD noise for SSH sessions
576+
String.raw`# 4.75) Disable Ubuntu MOTD noise for SSH sessions
577577
PAM_SSHD="/etc/pam.d/sshd"
578578
if [[ -f "$PAM_SSHD" ]]; then
579-
sed -i 's/^[[:space:]]*session[[:space:]]\\+optional[[:space:]]\\+pam_motd\\.so/#&/' "$PAM_SSHD" || true
580-
sed -i 's/^[[:space:]]*session[[:space:]]\\+optional[[:space:]]\\+pam_lastlog\\.so/#&/' "$PAM_SSHD" || true
579+
sed -i 's/^[[:space:]]*session[[:space:]]\+optional[[:space:]]\+pam_motd\.so/#&/' "$PAM_SSHD" || true
580+
sed -i 's/^[[:space:]]*session[[:space:]]\+optional[[:space:]]\+pam_lastlog\.so/#&/' "$PAM_SSHD" || true
581581
fi
582582
583583
# Also disable sshd's own banners (e.g. "Last login")

packages/lib/src/core/templates.ts

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,9 @@ RUN curl -fsSL https://bun.sh/install | bash
4545
RUN ln -sf /usr/local/bun/bin/bun /usr/local/bin/bun
4646
RUN script -q -e -c "bun add -g @openai/codex@latest" /dev/null
4747
RUN ln -sf /usr/local/bun/bin/codex /usr/local/bin/codex
48-
${config.enableMcpPlaywright
49-
? `RUN npm install -g @playwright/mcp@latest
48+
${
49+
config.enableMcpPlaywright
50+
? `RUN npm install -g @playwright/mcp@latest
5051
5152
# docker-git: wrapper that converts a CDP HTTP endpoint into a usable WS endpoint
5253
# Some Chromium images return webSocketDebuggerUrl pointing at 127.0.0.1 (container-local).
@@ -70,15 +71,15 @@ fi
7071
7172
# kechangdev/browser-vnc binds Chromium CDP on 127.0.0.1:9222; it also host-checks HTTP requests.
7273
JSON="$(curl -sSf --connect-timeout 3 --max-time 10 -H 'Host: 127.0.0.1:9222' "\${CDP_ENDPOINT%/}/json/version")"
73-
WS_URL="$(printf "%s" "$JSON" | node -e 'const fs=require(\"fs\"); const j=JSON.parse(fs.readFileSync(0,\"utf8\")); process.stdout.write(j.webSocketDebuggerUrl || \"\")')"
74+
WS_URL="$(printf "%s" "$JSON" | node -e 'const fs=require("fs"); const j=JSON.parse(fs.readFileSync(0,"utf8")); process.stdout.write(j.webSocketDebuggerUrl || "")')"
7475
if [[ -z "$WS_URL" ]]; then
7576
echo "docker-git-playwright-mcp: webSocketDebuggerUrl missing" >&2
7677
exit 1
7778
fi
7879
7980
# Rewrite ws origin to match the CDP endpoint origin (docker DNS).
80-
BASE_WS="$(CDP_ENDPOINT="$CDP_ENDPOINT" node -e 'const { URL } = require(\"url\"); const u=new URL(process.env.CDP_ENDPOINT); const proto=u.protocol===\"https:\"?\"wss:\":\"ws:\"; process.stdout.write(proto + \"//\" + u.host)')"
81-
WS_REWRITTEN="$(BASE_WS="$BASE_WS" WS_URL="$WS_URL" node -e 'const { URL } = require(\"url\"); const base=new URL(process.env.BASE_WS); const ws=new URL(process.env.WS_URL); ws.protocol=base.protocol; ws.host=base.host; process.stdout.write(ws.toString())')"
81+
BASE_WS="$(CDP_ENDPOINT="$CDP_ENDPOINT" node -e 'const { URL } = require("url"); const u=new URL(process.env.CDP_ENDPOINT); const proto=u.protocol==="https:"?"wss:":"ws:"; process.stdout.write(proto + "//" + u.host)')"
82+
WS_REWRITTEN="$(BASE_WS="$BASE_WS" WS_URL="$WS_URL" node -e 'const { URL } = require("url"); const base=new URL(process.env.BASE_WS); const ws=new URL(process.env.WS_URL); ws.protocol=base.protocol; ws.host=base.host; process.stdout.write(ws.toString())')"
8283
8384
EXTRA_ARGS=()
8485
if [[ "\${MCP_PLAYWRIGHT_ISOLATED:-1}" == "1" ]]; then
@@ -88,7 +89,8 @@ fi
8889
exec playwright-mcp --cdp-endpoint "$WS_REWRITTEN" "\${EXTRA_ARGS[@]}" "$@"
8990
EOF
9091
RUN chmod +x /usr/local/bin/docker-git-playwright-mcp`
91-
: ""}
92+
: ""
93+
}
9294
RUN printf "export BUN_INSTALL=/usr/local/bun\\nexport PATH=/usr/local/bun/bin:$PATH\\n" \
9395
> /etc/profile.d/bun.sh && chmod 0644 /etc/profile.d/bun.sh`
9496

@@ -303,14 +305,14 @@ const renderConfigJson = (config: TemplateConfig): string =>
303305
export const planFiles = (config: TemplateConfig): ReadonlyArray<FileSpec> => {
304306
const maybePlaywrightFiles = config.enableMcpPlaywright
305307
? ([
306-
{ _tag: "File", relativePath: "Dockerfile.browser", contents: renderPlaywrightBrowserDockerfile() },
307-
{
308-
_tag: "File",
309-
relativePath: "mcp-playwright-start-extra.sh",
310-
contents: renderPlaywrightStartExtra(),
311-
mode: 0o755
312-
}
313-
] satisfies ReadonlyArray<FileSpec>)
308+
{ _tag: "File", relativePath: "Dockerfile.browser", contents: renderPlaywrightBrowserDockerfile() },
309+
{
310+
_tag: "File",
311+
relativePath: "mcp-playwright-start-extra.sh",
312+
contents: renderPlaywrightStartExtra(),
313+
mode: 0o755
314+
}
315+
] satisfies ReadonlyArray<FileSpec>)
314316
: ([] satisfies ReadonlyArray<FileSpec>)
315317

316318
return [

packages/lib/src/shell/docker.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,20 @@ const composeSpec = (cwd: string, args: ReadonlyArray<string>) => ({
1313
args: ["compose", "--ansi", "never", "--progress", "plain", ...args]
1414
})
1515

16+
const parseInspectNetworkEntry = (line: string): ReadonlyArray<readonly [string, string]> => {
17+
const idx = line.indexOf("=")
18+
if (idx <= 0) {
19+
return []
20+
}
21+
const network = line.slice(0, idx).trim()
22+
const ip = line.slice(idx + 1).trim()
23+
if (network.length === 0 || ip.length === 0) {
24+
return []
25+
}
26+
const entry: readonly [string, string] = [network, ip]
27+
return [entry]
28+
}
29+
1630
const runCompose = (
1731
cwd: string,
1832
args: ReadonlyArray<string>,
@@ -215,7 +229,7 @@ export const runDockerInspectContainerIp = (
215229
// Example output:
216230
// bridge=172.17.0.4
217231
// <project>_dg-<repo>-net=192.168.64.3
218-
'{{range $k,$v := .NetworkSettings.Networks}}{{printf "%s=%s\\n" $k $v.IPAddress}}{{end}}',
232+
String.raw`{{range $k,$v := .NetworkSettings.Networks}}{{printf "%s=%s\n" $k $v.IPAddress}}{{end}}`,
219233
containerName
220234
]
221235
},
@@ -229,15 +243,7 @@ export const runDockerInspectContainerIp = (
229243
.map((line) => line.trim())
230244
.filter((line) => line.length > 0)
231245

232-
const entries = lines.flatMap((line) => {
233-
const idx = line.indexOf("=")
234-
if (idx <= 0) {
235-
return []
236-
}
237-
const network = line.slice(0, idx).trim()
238-
const ip = line.slice(idx + 1).trim()
239-
return network.length > 0 && ip.length > 0 ? ([[network, ip]] as const) : []
240-
})
246+
const entries = lines.flatMap((line) => parseInspectNetworkEntry(line))
241247

242248
if (entries.length === 0) {
243249
return ""
@@ -270,7 +276,7 @@ export const runDockerInspectContainerBridgeIp = (
270276
args: [
271277
"inspect",
272278
"-f",
273-
'{{with (index .NetworkSettings.Networks "bridge")}}{{.IPAddress}}{{end}}',
279+
"{{with (index .NetworkSettings.Networks \"bridge\")}}{{.IPAddress}}{{end}}",
274280
containerName
275281
]
276282
},

packages/lib/src/usecases/actions.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,7 @@ const buildProjectConfigs = (
168168
): ProjectConfigs => {
169169
// docker-compose resolves relative host paths from the project directory (where docker-compose.yml lives).
170170
// To keep generated projects portable across host OSes, we avoid embedding absolute host paths in templates.
171-
const relativeFromOutDir = (absolutePath: string): string =>
172-
toPosixPath(path.relative(resolvedOutDir, absolutePath))
171+
const relativeFromOutDir = (absolutePath: string): string => toPosixPath(path.relative(resolvedOutDir, absolutePath))
173172

174173
const globalConfig = {
175174
...resolvedConfig,
@@ -277,11 +276,15 @@ const runDockerUpIfNeeded = (
277276
? Effect.void
278277
: runDockerNetworkConnectBridge(resolvedOutDir, containerName)
279278
),
280-
Effect.catchAll((error) =>
281-
Effect.logWarning(
282-
`Failed to connect ${containerName} to bridge network: ${error instanceof Error ? error.message : String(error)}`
283-
).pipe(Effect.asVoid)
284-
)
279+
Effect.matchEffect({
280+
onFailure: (error) =>
281+
Effect.logWarning(
282+
`Failed to connect ${containerName} to bridge network: ${
283+
error instanceof Error ? error.message : String(error)
284+
}`
285+
),
286+
onSuccess: () => Effect.void
287+
})
285288
)
286289

287290
// Make container ports reachable from other (non-compose) containers by IP.

packages/lib/src/usecases/menu-helpers.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,17 @@ const expandHome = (value: string): string => {
1616
return value
1717
}
1818

19-
const trimTrailingSlash = (value: string): string => value.replace(/[\\/]+$/, "")
19+
const trimTrailingSlash = (value: string): string => {
20+
let end = value.length
21+
while (end > 0) {
22+
const char = value[end - 1]
23+
if (char !== "/" && char !== "\\") {
24+
break
25+
}
26+
end -= 1
27+
}
28+
return value.slice(0, end)
29+
}
2030

2131
export const defaultProjectsRoot = (cwd: string): string => {
2232
const explicit = process.env["DOCKER_GIT_PROJECTS_ROOT"]?.trim()

packages/lib/src/usecases/path-helpers.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,17 @@ export const resolvePathFromCwd = (
3434
return value
3535
}
3636

37-
const trimTrailingSlash = (value: string): string => value.replace(/[\\/]+$/, "")
37+
const trimTrailingSlash = (value: string): string => {
38+
let end = value.length
39+
while (end > 0) {
40+
const char = value[end - 1]
41+
if (char !== "/" && char !== "\\") {
42+
break
43+
}
44+
end -= 1
45+
}
46+
return value.slice(0, end)
47+
}
3848

3949
const defaultProjectsRoot = (): string => {
4050
const explicit = process.env["DOCKER_GIT_PROJECTS_ROOT"]?.trim()

packages/lib/src/usecases/projects-core.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ import { defaultProjectsRoot, formatConnectionInfo } from "./menu-helpers.js"
1313
import { findSshPrivateKey, resolveAuthorizedKeysPath, resolvePathFromCwd } from "./path-helpers.js"
1414
import { withFsPathContext } from "./runtime.js"
1515

16-
const sshOptions =
17-
"-tt -Y -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
16+
const sshOptions = "-tt -Y -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
1817

1918
export type ProjectLoadError = PlatformError | ConfigNotFoundError | ConfigDecodeError
2019

packages/lib/src/usecases/projects-delete.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,13 @@ import type { PlatformError } from "@effect/platform/Error"
33
import * as FileSystem from "@effect/platform/FileSystem"
44
import * as Path from "@effect/platform/Path"
55
import { Effect } from "effect"
6-
6+
import { deriveRepoPathParts } from "../core/domain.js"
77
import { runDockerComposeDown } from "../shell/docker.js"
88
import type { DockerCommandError } from "../shell/errors.js"
9-
import { deriveRepoPathParts } from "../core/domain.js"
10-
import { autoSyncState } from "./state-repo.js"
9+
import { renderError } from "./errors.js"
1110
import { defaultProjectsRoot } from "./menu-helpers.js"
1211
import type { ProjectItem } from "./projects-core.js"
13-
import { renderError } from "./errors.js"
12+
import { autoSyncState } from "./state-repo.js"
1413

1514
const isWithinProjectsRoot = (path: Path.Path, root: string, target: string): boolean => {
1615
const relative = path.relative(root, target)
@@ -60,8 +59,10 @@ export const deleteDockerGitProject = (
6059
// Best-effort: stop the container if possible before removing the compose dir.
6160
yield* _(
6261
runDockerComposeDown(targetDir).pipe(
63-
Effect.catchTag("DockerCommandError", (error: DockerCommandError) =>
64-
Effect.logWarning(`docker compose down failed before delete: ${renderError(error)}`)
62+
Effect.catchTag(
63+
"DockerCommandError",
64+
(error: DockerCommandError) =>
65+
Effect.logWarning(`docker compose down failed before delete: ${renderError(error)}`)
6566
)
6667
)
6768
)
@@ -72,4 +73,3 @@ export const deleteDockerGitProject = (
7273
const label = repoParts.length > 0 ? repoParts.join("/") : item.repoUrl
7374
yield* _(autoSyncState(`chore(state): delete ${label}`))
7475
}).pipe(Effect.asVoid)
75-

packages/lib/src/usecases/projects-down.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,11 @@ export const downAllDockerGitProjects: Effect.Effect<
5454
Effect.catchTag("DockerCommandError", (error: DockerCommandError) =>
5555
Effect.logWarning(
5656
`docker compose down failed for ${status.projectDir}: ${renderError(error)}`
57-
)
58-
)
57+
))
5958
)
6059
)
6160
}
6261
})
6362
),
6463
Effect.asVoid
6564
)
66-

packages/lib/src/usecases/projects-list.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,5 @@ export const listRunningProjectItems: Effect.Effect<
149149
FileSystem.FileSystem | Path.Path | CommandExecutor.CommandExecutor
150150
> = pipe(
151151
Effect.all([listProjectItems, runDockerPsNames(process.cwd())]),
152-
Effect.map(([items, runningNames]) =>
153-
items.filter((item) => runningNames.includes(item.containerName))
154-
)
152+
Effect.map(([items, runningNames]) => items.filter((item) => runningNames.includes(item.containerName)))
155153
)

0 commit comments

Comments
 (0)