Skip to content
Open
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
26 changes: 13 additions & 13 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions packages/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@ glance and the CLI now distinguishes them in its error output:
(`docker` group / rootless Docker / socket ownership). This is a host
configuration problem, not a `docker-git` outage.
- **Controller container not running / unreachable** – the API at
`DOCKER_GIT_API_URL` (default `http://127.0.0.1:3334`) does not answer.
Bring the controller up with `docker compose up -d --build` or point the
CLI at an existing controller via `DOCKER_GIT_API_URL`.
a custom `DOCKER_GIT_API_URL` does not answer. Bring the controller up
with `docker compose up -d --build` or point the CLI at an existing
controller via `DOCKER_GIT_API_URL`. The default local value
(`http://127.0.0.1:3334`, `http://localhost:3334`, or `http://[::1]:3334`)
does not block local Docker bootstrap.

Diagnostic classification + remediation messages live in
`packages/app/src/docker-git/controller-docker-diagnostics.ts` and are
Expand Down
9 changes: 9 additions & 0 deletions packages/app/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# @prover-coder-ai/docker-git

## 1.1.37

### Patch Changes

- chore: automated version bump

- Updated dependencies []:
- @prover-coder-ai/docker-git-session-sync@1.0.40

## 1.1.36

### Patch Changes
Expand Down
6 changes: 3 additions & 3 deletions packages/app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@prover-coder-ai/docker-git",
"version": "1.1.36",
"version": "1.1.37",
"description": "docker-git Bun and Gridland CLI plus browser frontend",
"main": "dist/src/docker-git/main.js",
"bin": {
Expand Down Expand Up @@ -87,7 +87,7 @@
"xterm-addon-fit": "^0.8.0"
},
"devDependencies": {
"@biomejs/biome": "^2.4.15",
"@biomejs/biome": "^2.4.16",
"@effect-template/lib": "workspace:*",
"@effect/eslint-plugin": "^0.3.2",
"@effect/language-service": "latest",
Expand All @@ -107,7 +107,7 @@
"@vitejs/plugin-react": "^6.0.2",
"@vitest/coverage-v8": "^4.1.7",
"@vitest/eslint-plugin": "^1.6.18",
"biome": "npm:@biomejs/biome@^2.4.15",
"biome": "npm:@biomejs/biome@^2.4.16",
"eslint": "^10.4.0",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-codegen": "0.34.1",
Expand Down
23 changes: 16 additions & 7 deletions packages/app/src/docker-git/browser-frontend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ import {
shouldReuseBrowserFrontend
} from "./browser-frontend-state.js"
import { findReachableApiBaseUrl } from "./controller-health.js"
import { resolveConfiguredApiBaseUrl, resolveExplicitApiBaseUrl } from "./controller-reachability.js"
import {
resolveConfiguredApiBaseUrl,
resolveDefaultLocalApiBaseUrl,
resolveExplicitApiBaseUrl,
uniqueStrings
} from "./controller-reachability.js"
import { type ControllerRuntime, ensureControllerReady, resolveApiBaseUrl } from "./controller.js"
import {
runCommandCapture,
Expand Down Expand Up @@ -153,19 +158,19 @@ const readBrowserFrontendRuntimeState = (
// QUOTE(ТЗ): "комментарии ребита надо было тоже поддержать"
// REF: PR #344 E2E (Browser command) regression.
// SOURCE: n/a
// FORMAT THEOREM: explicit_api -> explicit_api; reachable(configured_api) -> configured_api; otherwise -> selected_api
// FORMAT THEOREM: strict_explicit_api -> strict_explicit_api; reachable(local_api) -> local_api; otherwise -> selected_api
// PURITY: SHELL
// EFFECT: Effect<string, never, ControllerRuntime>
// INVARIANT: explicit DOCKER_GIT_API_URL is never overridden by auto-discovery.
// INVARIANT: strict explicit DOCKER_GIT_API_URL is never overridden by auto-discovery.
// COMPLEXITY: O(1) probes/O(1) space.
/**
* Resolves the API URL used by the browser frontend proxy.
*
* @returns Effect with the explicit API URL, the reachable configured host URL, or the selected controller URL.
* @returns Effect with the strict explicit API URL, a reachable local host URL, or the selected controller URL.
*
* @pure false
* @effect FetchHttpClient through controller health probing.
* @invariant Explicit `DOCKER_GIT_API_URL` has precedence over all inferred endpoints.
* @invariant Strict explicit `DOCKER_GIT_API_URL` has precedence over all inferred endpoints.
* @precondition `ensureControllerReady` has already completed for inferred endpoints.
* @postcondition A configured host URL is used only after a successful health probe.
* @complexity O(1) time and O(1) space for the bounded candidate set.
Expand All @@ -179,11 +184,15 @@ const resolveBrowserFrontendApiBaseUrl = (): Effect.Effect<string, never, Contro
}

const configuredApiBaseUrl = resolveConfiguredApiBaseUrl()
if (configuredApiBaseUrl === selectedApiBaseUrl) {
const candidateApiBaseUrls = uniqueStrings([
resolveDefaultLocalApiBaseUrl() ?? "",
configuredApiBaseUrl
].filter((value) => value.length > 0))
if (candidateApiBaseUrls.includes(selectedApiBaseUrl)) {
return Effect.succeed(selectedApiBaseUrl)
}

return findReachableApiBaseUrl([configuredApiBaseUrl]).pipe(
return findReachableApiBaseUrl(candidateApiBaseUrls).pipe(
Effect.match({
onFailure: () => selectedApiBaseUrl,
onSuccess: (apiBaseUrl) => apiBaseUrl
Expand Down
2 changes: 2 additions & 0 deletions packages/app/src/docker-git/controller-health.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,13 @@ export const findReachableApiBaseUrl = (

export const findReachableDirectHealthProbe = (options: {
readonly explicitApiBaseUrl: string | undefined
readonly defaultLocalApiBaseUrl: string | undefined
readonly cachedApiBaseUrl: string | undefined
}): Effect.Effect<HealthProbeResult | null> =>
findReachableHealthProbeOrNull(
buildApiBaseUrlCandidates({
explicitApiBaseUrl: options.explicitApiBaseUrl,
defaultLocalApiBaseUrl: options.defaultLocalApiBaseUrl,
cachedApiBaseUrl: options.cachedApiBaseUrl,
defaultApiBaseUrl: resolveConfiguredApiBaseUrl(),
currentContainerNetworks: {},
Expand Down
65 changes: 63 additions & 2 deletions packages/app/src/docker-git/controller-reachability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type DockerNetworkIps = Readonly<Record<string, string>>

export type ApiBaseUrlCandidatesInput = {
readonly explicitApiBaseUrl?: string | undefined
readonly defaultLocalApiBaseUrl?: string | undefined
readonly cachedApiBaseUrl?: string | undefined
readonly defaultApiBaseUrl: string
readonly currentContainerNetworks: DockerNetworkIps
Expand All @@ -28,11 +29,69 @@ const normalizePort = (value: string | undefined): string => {
return trimmed.length > 0 ? trimmed : defaultApiPort
}

const normalizeApiBaseUrl = (value: string | undefined): string | undefined => {
const trimmed = value?.trim()
return trimmed !== undefined && trimmed.length > 0 ? trimTrailingSlashes(trimmed) : undefined
}

export const resolveApiPort = (): string => normalizePort(process.env["DOCKER_GIT_API_PORT"])

const defaultLocalHostnames = new Set(["127.0.0.1", "localhost", "[::1]"])

const isRootApiUrlPath = (url: URL): boolean => url.pathname === "/" && url.search.length === 0 && url.hash.length === 0

const isDefaultLocalApiUrlObject = (url: URL, port: string): boolean =>
url.protocol === "http:" &&
defaultLocalHostnames.has(url.hostname) &&
url.port === port &&
isRootApiUrlPath(url)

// CHANGE: classify default localhost API URLs as non-strict bootstrap hints.
// WHY: Windows shells can persist DOCKER_GIT_API_URL=http://127.0.0.1:3334, which should not block local controller startup.
// QUOTE(ТЗ): "сделать из коробки что бы всё само работало"
// REF: user-request-2026-05-29-default-local-api-url-bootstrap
// SOURCE: n/a
// FORMAT THEOREM: local_http(url, port) and empty(path, query, hash) -> default_local(url)
// PURITY: CORE
// EFFECT: n/a
// INVARIANT: only localhost loopback HTTP URLs on the configured API port are default-local.
// COMPLEXITY: O(n) where n = |value|.
export const isDefaultLocalApiBaseUrl = (value: string, port = resolveApiPort()): boolean => {
const normalized = normalizeApiBaseUrl(value)
if (normalized === undefined || !URL.canParse(normalized)) {
return false
}
return isDefaultLocalApiUrlObject(new URL(normalized), port)
}

// CHANGE: preserve default-local DOCKER_GIT_API_URL as an endpoint candidate instead of a strict override.
// WHY: a stale default localhost env var should still allow compose bootstrap when nothing is listening yet.
// QUOTE(ТЗ): "fallback только для дефолтного localhost URL"
// REF: user-request-2026-05-29-default-local-api-url-bootstrap
// SOURCE: n/a
// FORMAT THEOREM: default_local(env) -> env; otherwise -> undefined
// PURITY: SHELL
// EFFECT: reads process.env
// INVARIANT: custom DOCKER_GIT_API_URL values are never returned here.
// COMPLEXITY: O(n) where n = |DOCKER_GIT_API_URL|.
export const resolveDefaultLocalApiBaseUrl = (): string | undefined => {
const explicit = normalizeApiBaseUrl(process.env["DOCKER_GIT_API_URL"])
return explicit !== undefined && isDefaultLocalApiBaseUrl(explicit) ? explicit : undefined
}

// CHANGE: treat only custom DOCKER_GIT_API_URL values as strict explicit controller endpoints.
// WHY: custom remote backends should fail loudly when unreachable, while default localhost should bootstrap locally.
// QUOTE(ТЗ): "кастомные URL остаются строгими"
// REF: user-request-2026-05-29-default-local-api-url-bootstrap
// SOURCE: n/a
// FORMAT THEOREM: nonempty(env) and not default_local(env) -> env; otherwise -> undefined
// PURITY: SHELL
// EFFECT: reads process.env
// INVARIANT: default-local URLs do not block local bootstrap.
// COMPLEXITY: O(n) where n = |DOCKER_GIT_API_URL|.
export const resolveExplicitApiBaseUrl = (): string | undefined => {
const explicit = process.env["DOCKER_GIT_API_URL"]?.trim()
return explicit !== undefined && explicit.length > 0 ? trimTrailingSlashes(explicit) : undefined
const explicit = normalizeApiBaseUrl(process.env["DOCKER_GIT_API_URL"])
return explicit !== undefined && !isDefaultLocalApiBaseUrl(explicit) ? explicit : undefined
}

export const resolveConfiguredApiBaseUrl = (): string => {
Expand Down Expand Up @@ -126,6 +185,7 @@ export const buildApiBaseUrlCandidates = ({
controllerNetworks,
currentContainerNetworks,
defaultApiBaseUrl,
defaultLocalApiBaseUrl,
explicitApiBaseUrl,
port
}: ApiBaseUrlCandidatesInput): ReadonlyArray<string> => {
Expand All @@ -150,6 +210,7 @@ export const buildApiBaseUrlCandidates = ({

return uniqueStrings(
[
defaultLocalApiBaseUrl ?? "",
cachedApiBaseUrl ?? "",
defaultApiBaseUrl,
resolveControllerDnsApiBaseUrl(),
Expand Down
Loading