@keystrokehq/hosting 0.1.14 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,131 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_wait_for_health = require("./wait-for-health-DM55_NQh.cjs");
3
+ let node_fs_promises = require("node:fs/promises");
4
+ //#region src/docker/create-docker-runtime.ts
5
+ const MINIO_DOCKER_NETWORK = process.env.MINIO_DOCKER_NETWORK ?? "keystroke";
6
+ async function resolveDocker(options) {
7
+ if (options.docker) return options.docker;
8
+ const { default: DockerCtor } = await import("dockerode");
9
+ return new DockerCtor();
10
+ }
11
+ function createDockerRuntime(options = {}) {
12
+ return {
13
+ async start(input) {
14
+ if (input.projects.length === 0) throw new Error("Docker org runtime requires at least one project deploy");
15
+ const docker = await resolveDocker(options);
16
+ let launch = require_wait_for_health.resolveRuntimeLaunch(options, input.projects.map((project) => ({
17
+ projectId: project.projectId,
18
+ artifactStorageKey: project.artifactStorageKey,
19
+ artifactId: project.artifactId,
20
+ artifactVersion: project.artifactVersion
21
+ })), input.database, input.env);
22
+ if (options.workspacesMount) {
23
+ const { hostPath, containerPath } = options.workspacesMount;
24
+ await (0, node_fs_promises.mkdir)(hostPath, { recursive: true });
25
+ launch = {
26
+ ...launch,
27
+ env: {
28
+ ...launch.env,
29
+ KEYSTROKE_WORKSPACES_ROOT: containerPath
30
+ }
31
+ };
32
+ }
33
+ await ensureImage(docker, launch.image);
34
+ await removeExistingContainer(docker, input.organizationId);
35
+ const binds = [...options.workspacesMount ? [`${options.workspacesMount.hostPath}:${options.workspacesMount.containerPath}`] : [], ...options.extraBinds ?? []];
36
+ const container = await docker.createContainer({
37
+ Image: launch.image,
38
+ name: containerName(input.organizationId),
39
+ Labels: {
40
+ "keystroke.organization.id": input.organizationId,
41
+ "keystroke.project.ids": input.projects.map((project) => project.projectId).join(",")
42
+ },
43
+ ExposedPorts: { [`${launch.port}/tcp`]: {} },
44
+ HostConfig: {
45
+ ExtraHosts: ["host.docker.internal:host-gateway"],
46
+ PortBindings: { [`${launch.port}/tcp`]: [{ HostPort: "0" }] },
47
+ ...binds.length > 0 ? { Binds: binds } : {}
48
+ },
49
+ NetworkingConfig: { EndpointsConfig: { [MINIO_DOCKER_NETWORK]: {} } },
50
+ Env: require_wait_for_health.formatDockerEnv(launch.env)
51
+ });
52
+ await container.start();
53
+ const inspect = await container.inspect();
54
+ const binding = inspect.NetworkSettings.Ports?.[`${launch.port}/tcp`]?.[0];
55
+ if (!binding?.HostPort) throw new Error("Failed to resolve org container host port");
56
+ const baseUrl = `http://127.0.0.1:${binding.HostPort}`;
57
+ await require_wait_for_health.waitForHealth(baseUrl, {
58
+ fetchImpl: options.fetchImpl,
59
+ timeoutMs: options.healthTimeoutMs
60
+ });
61
+ return {
62
+ runtimeId: inspect.Id,
63
+ baseUrl
64
+ };
65
+ },
66
+ async destroy(input) {
67
+ try {
68
+ await (await resolveDocker(options)).getContainer(input.runtimeId).remove({ force: true });
69
+ } catch {}
70
+ }
71
+ };
72
+ }
73
+ function containerName(organizationId) {
74
+ return `keystroke-org-${organizationId}`;
75
+ }
76
+ async function removeExistingContainer(docker, organizationId) {
77
+ try {
78
+ const existing = docker.getContainer(containerName(organizationId));
79
+ await existing.stop({ t: 5 }).catch(() => void 0);
80
+ await existing.remove({ force: true });
81
+ } catch {}
82
+ }
83
+ async function ensureImage(docker, image) {
84
+ try {
85
+ await docker.getImage(image).inspect();
86
+ return;
87
+ } catch {}
88
+ await new Promise((resolvePromise, reject) => {
89
+ docker.pull(image, (error, stream) => {
90
+ if (error) {
91
+ reject(error);
92
+ return;
93
+ }
94
+ if (!stream) {
95
+ reject(/* @__PURE__ */ new Error(`Failed to pull image ${image}`));
96
+ return;
97
+ }
98
+ docker.modem.followProgress(stream, (progressError) => {
99
+ if (progressError) {
100
+ reject(progressError);
101
+ return;
102
+ }
103
+ resolvePromise();
104
+ });
105
+ });
106
+ });
107
+ }
108
+ //#endregion
109
+ //#region src/docker/plugin.ts
110
+ function dockerPlugin(options = {}) {
111
+ const env = options.env ?? process.env;
112
+ return {
113
+ name: "docker",
114
+ workerRuntime: require_wait_for_health.resolveWorkerRuntimeConfig(env),
115
+ createRuntime: () => createDockerRuntime({
116
+ ...options,
117
+ env
118
+ })
119
+ };
120
+ }
121
+ //#endregion
122
+ //#region src/default-plugin.ts
123
+ /** Local Docker hosting — default for platform and dev. */
124
+ function defaultHostingPlugin(options = {}) {
125
+ return dockerPlugin(options);
126
+ }
127
+ //#endregion
128
+ exports.defaultHostingPlugin = defaultHostingPlugin;
129
+ exports.dockerPlugin = dockerPlugin;
130
+
131
+ //# sourceMappingURL=docker.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docker.cjs","names":["resolveRuntimeLaunch","formatDockerEnv","waitForHealth","resolveWorkerRuntimeConfig"],"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}\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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"}
@@ -0,0 +1,23 @@
1
+ import { n as HostingPlugin, t as HostingOptions } from "./plugin-is5oPt6x.cjs";
2
+ import Docker from "dockerode";
3
+
4
+ //#region src/docker/create-docker-runtime.d.ts
5
+ type WorkspacesMount = {
6
+ hostPath: string;
7
+ containerPath: string;
8
+ };
9
+ type DockerPluginOptions = HostingOptions & {
10
+ docker?: Docker; /** Bind a shared host workspace dir into dev workers (VM sandbox proxy mount path). */
11
+ workspacesMount?: WorkspacesMount; /** Additional Docker bind mounts (`host:container[:options]`). */
12
+ extraBinds?: string[];
13
+ };
14
+ //#endregion
15
+ //#region src/docker/plugin.d.ts
16
+ declare function dockerPlugin(options?: DockerPluginOptions): HostingPlugin;
17
+ //#endregion
18
+ //#region src/default-plugin.d.ts
19
+ /** Local Docker hosting — default for platform and dev. */
20
+ declare function defaultHostingPlugin(options?: DockerPluginOptions): HostingPlugin;
21
+ //#endregion
22
+ export { type DockerPluginOptions, defaultHostingPlugin, dockerPlugin };
23
+ //# sourceMappingURL=docker.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docker.d.cts","names":[],"sources":["../src/docker/create-docker-runtime.ts","../src/docker/plugin.ts","../src/default-plugin.ts"],"mappings":";;;;KASY,eAAA;EACV,QAAA;EACA,aAAa;AAAA;AAAA,KAGH,mBAAA,GAAsB,cAAA;EAChC,MAAA,GAAS,MAAA,EADC;EAGV,eAAA,GAAkB,eAAA;EAElB,UAAA;AAAA;;;iBCXc,YAAA,CAAa,OAAA,GAAS,mBAAA,GAA2B,aAAa;;;;iBCJ9D,oBAAA,CAAqB,OAAA,GAAS,mBAAA,GAA2B,aAAa"}
@@ -0,0 +1,23 @@
1
+ import { n as HostingPlugin, t as HostingOptions } from "./plugin-qEl5Sx4m.mjs";
2
+ import Docker from "dockerode";
3
+
4
+ //#region src/docker/create-docker-runtime.d.ts
5
+ type WorkspacesMount = {
6
+ hostPath: string;
7
+ containerPath: string;
8
+ };
9
+ type DockerPluginOptions = HostingOptions & {
10
+ docker?: Docker; /** Bind a shared host workspace dir into dev workers (VM sandbox proxy mount path). */
11
+ workspacesMount?: WorkspacesMount; /** Additional Docker bind mounts (`host:container[:options]`). */
12
+ extraBinds?: string[];
13
+ };
14
+ //#endregion
15
+ //#region src/docker/plugin.d.ts
16
+ declare function dockerPlugin(options?: DockerPluginOptions): HostingPlugin;
17
+ //#endregion
18
+ //#region src/default-plugin.d.ts
19
+ /** Local Docker hosting — default for platform and dev. */
20
+ declare function defaultHostingPlugin(options?: DockerPluginOptions): HostingPlugin;
21
+ //#endregion
22
+ export { type DockerPluginOptions, defaultHostingPlugin, dockerPlugin };
23
+ //# sourceMappingURL=docker.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docker.d.mts","names":[],"sources":["../src/docker/create-docker-runtime.ts","../src/docker/plugin.ts","../src/default-plugin.ts"],"mappings":";;;;KASY,eAAA;EACV,QAAA;EACA,aAAa;AAAA;AAAA,KAGH,mBAAA,GAAsB,cAAA;EAChC,MAAA,GAAS,MAAA,EADC;EAGV,eAAA,GAAkB,eAAA;EAElB,UAAA;AAAA;;;iBCXc,YAAA,CAAa,OAAA,GAAS,mBAAA,GAA2B,aAAa;;;;iBCJ9D,oBAAA,CAAqB,OAAA,GAAS,mBAAA,GAA2B,aAAa"}
@@ -0,0 +1,129 @@
1
+ import { a as formatDockerEnv, c as resolveRuntimeLaunch, h as resolveWorkerRuntimeConfig, t as waitForHealth } from "./wait-for-health-XEzPPQIO.mjs";
2
+ import { mkdir } from "node:fs/promises";
3
+ //#region src/docker/create-docker-runtime.ts
4
+ const MINIO_DOCKER_NETWORK = process.env.MINIO_DOCKER_NETWORK ?? "keystroke";
5
+ async function resolveDocker(options) {
6
+ if (options.docker) return options.docker;
7
+ const { default: DockerCtor } = await import("dockerode");
8
+ return new DockerCtor();
9
+ }
10
+ function createDockerRuntime(options = {}) {
11
+ return {
12
+ async start(input) {
13
+ if (input.projects.length === 0) throw new Error("Docker org runtime requires at least one project deploy");
14
+ const docker = await resolveDocker(options);
15
+ let launch = resolveRuntimeLaunch(options, input.projects.map((project) => ({
16
+ projectId: project.projectId,
17
+ artifactStorageKey: project.artifactStorageKey,
18
+ artifactId: project.artifactId,
19
+ artifactVersion: project.artifactVersion
20
+ })), input.database, input.env);
21
+ if (options.workspacesMount) {
22
+ const { hostPath, containerPath } = options.workspacesMount;
23
+ await mkdir(hostPath, { recursive: true });
24
+ launch = {
25
+ ...launch,
26
+ env: {
27
+ ...launch.env,
28
+ KEYSTROKE_WORKSPACES_ROOT: containerPath
29
+ }
30
+ };
31
+ }
32
+ await ensureImage(docker, launch.image);
33
+ await removeExistingContainer(docker, input.organizationId);
34
+ const binds = [...options.workspacesMount ? [`${options.workspacesMount.hostPath}:${options.workspacesMount.containerPath}`] : [], ...options.extraBinds ?? []];
35
+ const container = await docker.createContainer({
36
+ Image: launch.image,
37
+ name: containerName(input.organizationId),
38
+ Labels: {
39
+ "keystroke.organization.id": input.organizationId,
40
+ "keystroke.project.ids": input.projects.map((project) => project.projectId).join(",")
41
+ },
42
+ ExposedPorts: { [`${launch.port}/tcp`]: {} },
43
+ HostConfig: {
44
+ ExtraHosts: ["host.docker.internal:host-gateway"],
45
+ PortBindings: { [`${launch.port}/tcp`]: [{ HostPort: "0" }] },
46
+ ...binds.length > 0 ? { Binds: binds } : {}
47
+ },
48
+ NetworkingConfig: { EndpointsConfig: { [MINIO_DOCKER_NETWORK]: {} } },
49
+ Env: formatDockerEnv(launch.env)
50
+ });
51
+ await container.start();
52
+ const inspect = await container.inspect();
53
+ const binding = inspect.NetworkSettings.Ports?.[`${launch.port}/tcp`]?.[0];
54
+ if (!binding?.HostPort) throw new Error("Failed to resolve org container host port");
55
+ const baseUrl = `http://127.0.0.1:${binding.HostPort}`;
56
+ await waitForHealth(baseUrl, {
57
+ fetchImpl: options.fetchImpl,
58
+ timeoutMs: options.healthTimeoutMs
59
+ });
60
+ return {
61
+ runtimeId: inspect.Id,
62
+ baseUrl
63
+ };
64
+ },
65
+ async destroy(input) {
66
+ try {
67
+ await (await resolveDocker(options)).getContainer(input.runtimeId).remove({ force: true });
68
+ } catch {}
69
+ }
70
+ };
71
+ }
72
+ function containerName(organizationId) {
73
+ return `keystroke-org-${organizationId}`;
74
+ }
75
+ async function removeExistingContainer(docker, organizationId) {
76
+ try {
77
+ const existing = docker.getContainer(containerName(organizationId));
78
+ await existing.stop({ t: 5 }).catch(() => void 0);
79
+ await existing.remove({ force: true });
80
+ } catch {}
81
+ }
82
+ async function ensureImage(docker, image) {
83
+ try {
84
+ await docker.getImage(image).inspect();
85
+ return;
86
+ } catch {}
87
+ await new Promise((resolvePromise, reject) => {
88
+ docker.pull(image, (error, stream) => {
89
+ if (error) {
90
+ reject(error);
91
+ return;
92
+ }
93
+ if (!stream) {
94
+ reject(/* @__PURE__ */ new Error(`Failed to pull image ${image}`));
95
+ return;
96
+ }
97
+ docker.modem.followProgress(stream, (progressError) => {
98
+ if (progressError) {
99
+ reject(progressError);
100
+ return;
101
+ }
102
+ resolvePromise();
103
+ });
104
+ });
105
+ });
106
+ }
107
+ //#endregion
108
+ //#region src/docker/plugin.ts
109
+ function dockerPlugin(options = {}) {
110
+ const env = options.env ?? process.env;
111
+ return {
112
+ name: "docker",
113
+ workerRuntime: resolveWorkerRuntimeConfig(env),
114
+ createRuntime: () => createDockerRuntime({
115
+ ...options,
116
+ env
117
+ })
118
+ };
119
+ }
120
+ //#endregion
121
+ //#region src/default-plugin.ts
122
+ /** Local Docker hosting — default for platform and dev. */
123
+ function defaultHostingPlugin(options = {}) {
124
+ return dockerPlugin(options);
125
+ }
126
+ //#endregion
127
+ export { defaultHostingPlugin, dockerPlugin };
128
+
129
+ //# sourceMappingURL=docker.mjs.map
@@ -0,0 +1 @@
1
+ {"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}\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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"}