@keystrokehq/hosting 0.1.15 → 0.1.17

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,154 @@
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
+ async sleep(input) {
72
+ const container = (await resolveDocker(options)).getContainer(input.runtimeId);
73
+ const inspect = await container.inspect().catch(() => null);
74
+ if (!inspect) return;
75
+ if (inspect.State.Running) await container.stop({ t: 10 });
76
+ },
77
+ async wake(input) {
78
+ const container = (await resolveDocker(options)).getContainer(input.runtimeId);
79
+ if (!(await container.inspect()).State.Running) await container.start();
80
+ const refreshed = await container.inspect();
81
+ const ports = refreshed.NetworkSettings.Ports ?? {};
82
+ const binding = Object.values(ports).flat().find((entry) => entry?.HostPort)?.HostPort;
83
+ if (!binding) throw new Error(`Failed to resolve host port for container ${input.runtimeId}`);
84
+ const baseUrl = `http://127.0.0.1:${binding}`;
85
+ await require_wait_for_health.waitForHealth(baseUrl, {
86
+ fetchImpl: options.fetchImpl,
87
+ timeoutMs: options.healthTimeoutMs
88
+ });
89
+ return {
90
+ runtimeId: refreshed.Id,
91
+ baseUrl
92
+ };
93
+ }
94
+ };
95
+ }
96
+ function containerName(organizationId) {
97
+ return `keystroke-org-${organizationId}`;
98
+ }
99
+ async function removeExistingContainer(docker, organizationId) {
100
+ try {
101
+ const existing = docker.getContainer(containerName(organizationId));
102
+ await existing.stop({ t: 5 }).catch(() => void 0);
103
+ await existing.remove({ force: true });
104
+ } catch {}
105
+ }
106
+ async function ensureImage(docker, image) {
107
+ try {
108
+ await docker.getImage(image).inspect();
109
+ return;
110
+ } catch {}
111
+ await new Promise((resolvePromise, reject) => {
112
+ docker.pull(image, (error, stream) => {
113
+ if (error) {
114
+ reject(error);
115
+ return;
116
+ }
117
+ if (!stream) {
118
+ reject(/* @__PURE__ */ new Error(`Failed to pull image ${image}`));
119
+ return;
120
+ }
121
+ docker.modem.followProgress(stream, (progressError) => {
122
+ if (progressError) {
123
+ reject(progressError);
124
+ return;
125
+ }
126
+ resolvePromise();
127
+ });
128
+ });
129
+ });
130
+ }
131
+ //#endregion
132
+ //#region src/docker/plugin.ts
133
+ function dockerPlugin(options = {}) {
134
+ const env = options.env ?? process.env;
135
+ return {
136
+ name: "docker",
137
+ workerRuntime: require_wait_for_health.resolveWorkerRuntimeConfig(env),
138
+ createRuntime: () => createDockerRuntime({
139
+ ...options,
140
+ env
141
+ })
142
+ };
143
+ }
144
+ //#endregion
145
+ //#region src/default-plugin.ts
146
+ /** Local Docker hosting — default for platform and dev. */
147
+ function defaultHostingPlugin(options = {}) {
148
+ return dockerPlugin(options);
149
+ }
150
+ //#endregion
151
+ exports.defaultHostingPlugin = defaultHostingPlugin;
152
+ exports.dockerPlugin = dockerPlugin;
153
+
154
+ //# 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 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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"}
@@ -0,0 +1,23 @@
1
+ import { n as HostingPlugin, t as HostingOptions } from "./plugin-4cuXkf42.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-eiAXqE7P.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,152 @@
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
+ async sleep(input) {
71
+ const container = (await resolveDocker(options)).getContainer(input.runtimeId);
72
+ const inspect = await container.inspect().catch(() => null);
73
+ if (!inspect) return;
74
+ if (inspect.State.Running) await container.stop({ t: 10 });
75
+ },
76
+ async wake(input) {
77
+ const container = (await resolveDocker(options)).getContainer(input.runtimeId);
78
+ if (!(await container.inspect()).State.Running) await container.start();
79
+ const refreshed = await container.inspect();
80
+ const ports = refreshed.NetworkSettings.Ports ?? {};
81
+ const binding = Object.values(ports).flat().find((entry) => entry?.HostPort)?.HostPort;
82
+ if (!binding) throw new Error(`Failed to resolve host port for container ${input.runtimeId}`);
83
+ const baseUrl = `http://127.0.0.1:${binding}`;
84
+ await waitForHealth(baseUrl, {
85
+ fetchImpl: options.fetchImpl,
86
+ timeoutMs: options.healthTimeoutMs
87
+ });
88
+ return {
89
+ runtimeId: refreshed.Id,
90
+ baseUrl
91
+ };
92
+ }
93
+ };
94
+ }
95
+ function containerName(organizationId) {
96
+ return `keystroke-org-${organizationId}`;
97
+ }
98
+ async function removeExistingContainer(docker, organizationId) {
99
+ try {
100
+ const existing = docker.getContainer(containerName(organizationId));
101
+ await existing.stop({ t: 5 }).catch(() => void 0);
102
+ await existing.remove({ force: true });
103
+ } catch {}
104
+ }
105
+ async function ensureImage(docker, image) {
106
+ try {
107
+ await docker.getImage(image).inspect();
108
+ return;
109
+ } catch {}
110
+ await new Promise((resolvePromise, reject) => {
111
+ docker.pull(image, (error, stream) => {
112
+ if (error) {
113
+ reject(error);
114
+ return;
115
+ }
116
+ if (!stream) {
117
+ reject(/* @__PURE__ */ new Error(`Failed to pull image ${image}`));
118
+ return;
119
+ }
120
+ docker.modem.followProgress(stream, (progressError) => {
121
+ if (progressError) {
122
+ reject(progressError);
123
+ return;
124
+ }
125
+ resolvePromise();
126
+ });
127
+ });
128
+ });
129
+ }
130
+ //#endregion
131
+ //#region src/docker/plugin.ts
132
+ function dockerPlugin(options = {}) {
133
+ const env = options.env ?? process.env;
134
+ return {
135
+ name: "docker",
136
+ workerRuntime: resolveWorkerRuntimeConfig(env),
137
+ createRuntime: () => createDockerRuntime({
138
+ ...options,
139
+ env
140
+ })
141
+ };
142
+ }
143
+ //#endregion
144
+ //#region src/default-plugin.ts
145
+ /** Local Docker hosting — default for platform and dev. */
146
+ function defaultHostingPlugin(options = {}) {
147
+ return dockerPlugin(options);
148
+ }
149
+ //#endregion
150
+ export { defaultHostingPlugin, dockerPlugin };
151
+
152
+ //# 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 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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"}