{"version":3,"file":"docker.mjs","names":[],"sources":["../src/docker/create-docker-runtime.ts","../src/docker/plugin.ts","../src/default-plugin.ts"],"sourcesContent":["import { mkdir } from \"node:fs/promises\";\nimport type Docker from \"dockerode\";\n\nimport type { ProjectRuntime } from \"../runtime\";\n\nimport type { HostingOptions } from \"../plugin\";\nimport { formatDockerEnv, resolveRuntimeLaunch } from \"../runtime-env\";\nimport { waitForHealth } from \"../wait-for-health\";\n\nexport type WorkspacesMount = {\n  hostPath: string;\n  containerPath: string;\n};\n\nexport type DockerPluginOptions = HostingOptions & {\n  docker?: Docker;\n  /** Bind a shared host workspace dir into dev workers (VM sandbox proxy mount path). */\n  workspacesMount?: WorkspacesMount;\n  /** Additional Docker bind mounts (`host:container[:options]`). */\n  extraBinds?: string[];\n};\n\nconst MINIO_DOCKER_NETWORK = process.env.MINIO_DOCKER_NETWORK ?? \"keystroke\";\n\nasync function resolveDocker(options: DockerPluginOptions): Promise<Docker> {\n  if (options.docker) {\n    return options.docker;\n  }\n  const { default: DockerCtor } = await import(\"dockerode\");\n  return new DockerCtor();\n}\n\nexport function createDockerRuntime(options: DockerPluginOptions = {}): ProjectRuntime {\n  return {\n    async start(input) {\n      if (input.projects.length === 0) {\n        throw new Error(\"Docker org runtime requires at least one project deploy\");\n      }\n\n      const docker = await resolveDocker(options);\n\n      let launch = resolveRuntimeLaunch(\n        options,\n        input.projects.map((project) => ({\n          projectId: project.projectId,\n          artifactStorageKey: project.artifactStorageKey,\n          artifactId: project.artifactId,\n          artifactVersion: project.artifactVersion,\n        })),\n        input.database,\n        input.env,\n      );\n\n      if (options.workspacesMount) {\n        const { hostPath, containerPath } = options.workspacesMount;\n        await mkdir(hostPath, { recursive: true });\n        launch = {\n          ...launch,\n          env: {\n            ...launch.env,\n            KEYSTROKE_WORKSPACES_ROOT: containerPath,\n          },\n        };\n      }\n\n      await ensureImage(docker, launch.image);\n      await removeExistingContainer(docker, input.organizationId);\n\n      const binds = [\n        ...(options.workspacesMount\n          ? [`${options.workspacesMount.hostPath}:${options.workspacesMount.containerPath}`]\n          : []),\n        ...(options.extraBinds ?? []),\n      ];\n\n      const container = await docker.createContainer({\n        Image: launch.image,\n        name: containerName(input.organizationId),\n        Labels: {\n          \"keystroke.organization.id\": input.organizationId,\n          \"keystroke.project.ids\": input.projects.map((project) => project.projectId).join(\",\"),\n        },\n        ExposedPorts: {\n          [`${launch.port}/tcp`]: {},\n        },\n        HostConfig: {\n          ExtraHosts: [\"host.docker.internal:host-gateway\"],\n          PortBindings: {\n            [`${launch.port}/tcp`]: [{ HostPort: \"0\" }],\n          },\n          ...(binds.length > 0 ? { Binds: binds } : {}),\n        },\n        NetworkingConfig: {\n          EndpointsConfig: {\n            [MINIO_DOCKER_NETWORK]: {},\n          },\n        },\n        Env: formatDockerEnv(launch.env),\n      });\n\n      await container.start();\n\n      const inspect = await container.inspect();\n      const binding = inspect.NetworkSettings.Ports?.[`${launch.port}/tcp`]?.[0];\n      if (!binding?.HostPort) {\n        throw new Error(\"Failed to resolve org container host port\");\n      }\n\n      const baseUrl = `http://127.0.0.1:${binding.HostPort}`;\n      await waitForHealth(baseUrl, {\n        fetchImpl: options.fetchImpl,\n        timeoutMs: options.healthTimeoutMs,\n      });\n\n      return {\n        runtimeId: inspect.Id,\n        baseUrl,\n      };\n    },\n\n    async destroy(input) {\n      try {\n        const docker = await resolveDocker(options);\n        const container = docker.getContainer(input.runtimeId);\n        await container.remove({ force: true });\n      } catch {\n        // Container already removed or never existed.\n      }\n    },\n\n    async sleep(input) {\n      const docker = await resolveDocker(options);\n      const container = docker.getContainer(input.runtimeId);\n      const inspect = await container.inspect().catch(() => null);\n      if (!inspect) {\n        return;\n      }\n      if (inspect.State.Running) {\n        await container.stop({ t: 10 });\n      }\n    },\n\n    async wake(input) {\n      const docker = await resolveDocker(options);\n      const container = docker.getContainer(input.runtimeId);\n      const inspect = await container.inspect();\n      if (!inspect.State.Running) {\n        await container.start();\n      }\n\n      const refreshed = await container.inspect();\n      const ports = refreshed.NetworkSettings.Ports ?? {};\n      const binding = Object.values(ports)\n        .flat()\n        .find((entry) => entry?.HostPort)?.HostPort;\n      if (!binding) {\n        throw new Error(`Failed to resolve host port for container ${input.runtimeId}`);\n      }\n\n      const baseUrl = `http://127.0.0.1:${binding}`;\n      await waitForHealth(baseUrl, {\n        fetchImpl: options.fetchImpl,\n        timeoutMs: options.healthTimeoutMs,\n      });\n\n      return {\n        runtimeId: refreshed.Id,\n        baseUrl,\n      };\n    },\n  };\n}\n\nfunction containerName(organizationId: string): string {\n  return `keystroke-org-${organizationId}`;\n}\n\nasync function removeExistingContainer(docker: Docker, organizationId: string): Promise<void> {\n  try {\n    const existing = docker.getContainer(containerName(organizationId));\n    await existing.stop({ t: 5 }).catch(() => undefined);\n    await existing.remove({ force: true });\n  } catch {\n    // no existing container\n  }\n}\n\nasync function ensureImage(docker: Docker, image: string): Promise<void> {\n  try {\n    await docker.getImage(image).inspect();\n    return;\n  } catch {\n    // pull below\n  }\n\n  await new Promise<void>((resolvePromise, reject) => {\n    docker.pull(image, (error: Error | null, stream: NodeJS.ReadableStream | undefined) => {\n      if (error) {\n        reject(error);\n        return;\n      }\n\n      if (!stream) {\n        reject(new Error(`Failed to pull image ${image}`));\n        return;\n      }\n\n      docker.modem.followProgress(stream, (progressError: Error | null) => {\n        if (progressError) {\n          reject(progressError);\n          return;\n        }\n\n        resolvePromise();\n      });\n    });\n  });\n}\n","import type { HostingPlugin } from \"../plugin\";\nimport { resolveWorkerRuntimeConfig } from \"../worker-runtime-config\";\n\nimport { createDockerRuntime } from \"./create-docker-runtime\";\nimport type { DockerPluginOptions } from \"./create-docker-runtime\";\n\nexport type { DockerPluginOptions } from \"./create-docker-runtime\";\n\nexport function dockerPlugin(options: DockerPluginOptions = {}): HostingPlugin {\n  const env = options.env ?? process.env;\n\n  return {\n    name: \"docker\",\n    workerRuntime: resolveWorkerRuntimeConfig(env),\n    createRuntime: () => createDockerRuntime({ ...options, env }),\n  };\n}\n","import type { HostingPlugin } from \"./plugin\";\nimport { dockerPlugin, type DockerPluginOptions } from \"./docker/plugin\";\n\n/** Local Docker hosting — default for platform and dev. */\nexport function defaultHostingPlugin(options: DockerPluginOptions = {}): HostingPlugin {\n  return dockerPlugin(options);\n}\n"],"mappings":"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"}