@keystrokehq/hosting 0.1.16 → 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.
package/dist/docker.cjs CHANGED
@@ -67,6 +67,29 @@ function createDockerRuntime(options = {}) {
67
67
  try {
68
68
  await (await resolveDocker(options)).getContainer(input.runtimeId).remove({ force: true });
69
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
+ };
70
93
  }
71
94
  };
72
95
  }
@@ -1 +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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"}
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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"}
package/dist/docker.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { n as HostingPlugin, t as HostingOptions } from "./plugin-is5oPt6x.cjs";
1
+ import { n as HostingPlugin, t as HostingOptions } from "./plugin-4cuXkf42.cjs";
2
2
  import Docker from "dockerode";
3
3
 
4
4
  //#region src/docker/create-docker-runtime.d.ts
package/dist/docker.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { n as HostingPlugin, t as HostingOptions } from "./plugin-qEl5Sx4m.mjs";
1
+ import { n as HostingPlugin, t as HostingOptions } from "./plugin-eiAXqE7P.mjs";
2
2
  import Docker from "dockerode";
3
3
 
4
4
  //#region src/docker/create-docker-runtime.d.ts
package/dist/docker.mjs CHANGED
@@ -66,6 +66,29 @@ function createDockerRuntime(options = {}) {
66
66
  try {
67
67
  await (await resolveDocker(options)).getContainer(input.runtimeId).remove({ force: true });
68
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
+ };
69
92
  }
70
93
  };
71
94
  }
@@ -1 +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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"}
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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"}
package/dist/index.cjs CHANGED
@@ -5,6 +5,46 @@ const require_runtime = require("./runtime-CV7pSfMl.cjs");
5
5
  /** Timeout for a single project runtime `/health` probe. */
6
6
  const RUNTIME_PING_TIMEOUT_MS = 15e3;
7
7
  //#endregion
8
+ //#region src/runtime-control.ts
9
+ const PROJECT_SERVER_CONTROL = {
10
+ load: "/control/projects/load",
11
+ promote: "/control/projects/promote",
12
+ unload: "/control/projects/unload",
13
+ port: require_wait_for_health.PROJECT_SERVER_PORT
14
+ };
15
+ async function postControl(baseUrl, path, body, options = {}) {
16
+ const timeoutMs = options.timeoutMs ?? 15e3;
17
+ const headers = { "content-type": "application/json" };
18
+ if (options.workerToken) headers.authorization = `Bearer ${options.workerToken}`;
19
+ const response = await (options.fetchImpl ?? fetch)(new URL(path, baseUrl), {
20
+ method: "POST",
21
+ headers,
22
+ body: JSON.stringify(body),
23
+ signal: AbortSignal.timeout(timeoutMs)
24
+ });
25
+ const data = await response.json().catch(() => ({}));
26
+ if (!response.ok || data.ok === false) throw new Error(typeof data.error === "string" ? data.error : `Runtime control ${path} failed (${response.status})`);
27
+ return data;
28
+ }
29
+ async function loadProjectOnRuntime(baseUrl, input, options = {}) {
30
+ await postControl(baseUrl, PROJECT_SERVER_CONTROL.load, input, options);
31
+ }
32
+ async function promoteProjectOnRuntime(baseUrl, input, options = {}) {
33
+ await postControl(baseUrl, PROJECT_SERVER_CONTROL.promote, input, options);
34
+ }
35
+ async function unloadProjectOnRuntime(baseUrl, input, options = {}) {
36
+ await postControl(baseUrl, PROJECT_SERVER_CONTROL.unload, input, options);
37
+ }
38
+ /** True when the org runtime answers /health. */
39
+ async function pingRuntimeHealth(baseUrl, options = {}) {
40
+ const timeoutMs = options.timeoutMs ?? 15e3;
41
+ try {
42
+ return (await (options.fetchImpl ?? fetch)(new URL("/health", baseUrl), { signal: AbortSignal.timeout(timeoutMs) })).ok;
43
+ } catch {
44
+ return false;
45
+ }
46
+ }
47
+ //#endregion
8
48
  //#region src/ping-project.ts
9
49
  async function pingProject(baseUrl, options = {}) {
10
50
  const timeoutMs = options.timeoutMs ?? 15e3;
@@ -33,10 +73,14 @@ async function pingProjectTarget(target, options = {}) {
33
73
  }
34
74
  //#endregion
35
75
  //#region src/deploy-status.ts
76
+ const DEFAULT_WAIT_TIMEOUT_MS = 12e4;
77
+ const DEFAULT_WAIT_INTERVAL_MS = 500;
78
+ const REQUEST_TIMEOUT_MS = 2e3;
36
79
  /**
37
80
  * Ask a running org machine which projects bootstrapped and which failed.
38
- * Best-effort: returns undefined on any error so callers can fall back to
39
- * treating every project as healthy (preserving pre-existing behavior).
81
+ * Returns undefined when the endpoint is unreachable, non-OK, times out, or
82
+ * returns an unusable payload callers must treat that as non-affirmative
83
+ * health for every pending project (never promote on silence).
40
84
  */
41
85
  async function fetchDeployStatus(baseUrl, options = {}) {
42
86
  const timeoutMs = options.timeoutMs ?? 15e3;
@@ -51,9 +95,73 @@ async function fetchDeployStatus(baseUrl, options = {}) {
51
95
  return;
52
96
  }
53
97
  }
98
+ /**
99
+ * Poll `/deploy-status` until the real worker returns a usable payload or the
100
+ * deadline expires. Retries only transient unavailability (network errors,
101
+ * non-2xx including early-health 404, timeouts, malformed bodies). Any valid
102
+ * `{ projects: [...] }` response — including empty or explicit failures — is
103
+ * terminal so genuine bootstrap errors surface immediately.
104
+ */
105
+ async function waitForDeployStatus(baseUrl, options = {}) {
106
+ const timeoutMs = options.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS;
107
+ const intervalMs = options.intervalMs ?? DEFAULT_WAIT_INTERVAL_MS;
108
+ const deadline = Date.now() + timeoutMs;
109
+ while (Date.now() < deadline) {
110
+ const remainingMs = deadline - Date.now();
111
+ const requestTimeoutMs = Math.min(REQUEST_TIMEOUT_MS, Math.max(1, remainingMs));
112
+ const status = await fetchDeployStatus(baseUrl, {
113
+ fetchImpl: options.fetchImpl,
114
+ timeoutMs: requestTimeoutMs
115
+ });
116
+ if (status) return status;
117
+ if (Date.now() + intervalMs >= deadline) break;
118
+ await sleep(intervalMs);
119
+ }
120
+ }
121
+ function sleep(ms) {
122
+ return new Promise((resolve) => {
123
+ setTimeout(resolve, ms);
124
+ });
125
+ }
126
+ /**
127
+ * Require an explicit `{ projectId, ok: true }` entry for each candidate.
128
+ * Missing entries, `ok: false`, empty `projects`, or an unavailable status
129
+ * response all fail closed for that project.
130
+ */
131
+ function classifyDeployHealth(input) {
132
+ const verdicts = /* @__PURE__ */ new Map();
133
+ if (!input.deployStatus) {
134
+ for (const projectId of input.projectIds) verdicts.set(projectId, {
135
+ ok: false,
136
+ error: "Deploy health status unavailable"
137
+ });
138
+ return verdicts;
139
+ }
140
+ const byId = new Map(input.deployStatus.projects.filter((entry) => typeof entry?.projectId === "string" && entry.projectId.length > 0).map((entry) => [entry.projectId, entry]));
141
+ for (const projectId of input.projectIds) {
142
+ const entry = byId.get(projectId);
143
+ if (!entry) {
144
+ verdicts.set(projectId, {
145
+ ok: false,
146
+ error: "Deploy health status omitted project"
147
+ });
148
+ continue;
149
+ }
150
+ if (entry.ok !== true) {
151
+ verdicts.set(projectId, {
152
+ ok: false,
153
+ error: entry.error?.trim() || "Project failed to start"
154
+ });
155
+ continue;
156
+ }
157
+ verdicts.set(projectId, { ok: true });
158
+ }
159
+ return verdicts;
160
+ }
54
161
  //#endregion
55
162
  exports.DEV_PLATFORM_WORKER_TOKEN = require_wait_for_health.DEV_PLATFORM_WORKER_TOKEN;
56
163
  exports.FORBIDDEN_WORKER_ENV_KEYS = require_wait_for_health.FORBIDDEN_WORKER_ENV_KEYS;
164
+ exports.PROJECT_SERVER_CONTROL = PROJECT_SERVER_CONTROL;
57
165
  exports.PROJECT_SERVER_DEPLOY_STATUS = require_wait_for_health.PROJECT_SERVER_DEPLOY_STATUS;
58
166
  exports.PROJECT_SERVER_FRAMEWORK_NODE_MODULES = require_wait_for_health.PROJECT_SERVER_FRAMEWORK_NODE_MODULES;
59
167
  exports.PROJECT_SERVER_FRAMEWORK_ROOT = require_wait_for_health.PROJECT_SERVER_FRAMEWORK_ROOT;
@@ -65,13 +173,17 @@ exports.RUNTIME_PING_TIMEOUT_MS = RUNTIME_PING_TIMEOUT_MS;
65
173
  exports.WorkerRuntimeConfigError = require_wait_for_health.WorkerRuntimeConfigError;
66
174
  exports.buildRuntimeEnv = require_wait_for_health.buildRuntimeEnv;
67
175
  exports.canPingProjectTarget = canPingProjectTarget;
176
+ exports.classifyDeployHealth = classifyDeployHealth;
68
177
  exports.encodeOrgArtifacts = require_runtime.encodeOrgArtifacts;
69
178
  exports.fetchDeployStatus = fetchDeployStatus;
70
179
  exports.formatDockerEnv = require_wait_for_health.formatDockerEnv;
180
+ exports.loadProjectOnRuntime = loadProjectOnRuntime;
71
181
  exports.mintOrgWorkerToken = require_wait_for_health.mintOrgWorkerToken;
72
182
  exports.parseOrgArtifactsFromEnv = require_runtime.parseOrgArtifactsFromEnv;
73
183
  exports.pingProject = pingProject;
74
184
  exports.pingProjectTarget = pingProjectTarget;
185
+ exports.pingRuntimeHealth = pingRuntimeHealth;
186
+ exports.promoteProjectOnRuntime = promoteProjectOnRuntime;
75
187
  exports.resolvePlatformWorkerToken = require_wait_for_health.resolvePlatformWorkerToken;
76
188
  exports.resolveProjectServerImage = require_wait_for_health.resolveProjectServerImage;
77
189
  exports.resolveRuntimeLaunch = require_wait_for_health.resolveRuntimeLaunch;
@@ -79,7 +191,9 @@ exports.resolveWorkerPlatformUrl = require_wait_for_health.resolveWorkerPlatform
79
191
  exports.resolveWorkerRuntimeConfig = require_wait_for_health.resolveWorkerRuntimeConfig;
80
192
  exports.rewriteLoopbackHost = require_wait_for_health.rewriteLoopbackHost;
81
193
  exports.rewriteLoopbackUrl = require_wait_for_health.rewriteLoopbackUrl;
194
+ exports.unloadProjectOnRuntime = unloadProjectOnRuntime;
82
195
  exports.verifyOrgWorkerToken = require_wait_for_health.verifyOrgWorkerToken;
196
+ exports.waitForDeployStatus = waitForDeployStatus;
83
197
  exports.waitForHealth = require_wait_for_health.waitForHealth;
84
198
 
85
199
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["PROJECT_SERVER_HEALTH","PROJECT_SERVER_DEPLOY_STATUS"],"sources":["../src/runtime-constants.ts","../src/ping-project.ts","../src/ping-project-target.ts","../src/deploy-status.ts"],"sourcesContent":["/** Timeout for a single project runtime `/health` probe. */\nexport const RUNTIME_PING_TIMEOUT_MS = 15_000;\n","import { PROJECT_SERVER_HEALTH } from \"./constants\";\nimport type { HostingPlugin } from \"./plugin\";\nimport { RUNTIME_PING_TIMEOUT_MS } from \"./runtime-constants\";\n\nexport type PingProjectOptions = {\n runtimeId?: string | null;\n fetchImpl?: typeof fetch;\n timeoutMs?: number;\n plugin?: Pick<HostingPlugin, \"pingRequestHeaders\">;\n};\n\nexport async function pingProject(\n baseUrl: string,\n options: PingProjectOptions = {},\n): Promise<boolean> {\n const timeoutMs = options.timeoutMs ?? RUNTIME_PING_TIMEOUT_MS;\n const headers = options.plugin?.pingRequestHeaders?.(options.runtimeId ?? null) ?? {};\n\n try {\n const response = await (options.fetchImpl ?? fetch)(\n new URL(PROJECT_SERVER_HEALTH.path, baseUrl),\n {\n signal: AbortSignal.timeout(timeoutMs),\n headers,\n },\n );\n\n return response.ok;\n } catch {\n return false;\n }\n}\n","import type { HostingPlugin } from \"./plugin\";\nimport type { ProjectPingTarget } from \"./runtime\";\nimport { pingProject, type PingProjectOptions } from \"./ping-project\";\n\nexport function canPingProjectTarget(\n target: ProjectPingTarget,\n plugin?: Pick<HostingPlugin, \"canPingTarget\">,\n): target is { baseUrl: string; runtimeId: string | null } {\n if (plugin?.canPingTarget) {\n return plugin.canPingTarget(target);\n }\n\n return !!target.baseUrl;\n}\n\nexport async function pingProjectTarget(\n target: ProjectPingTarget,\n options: PingProjectOptions & {\n plugin?: Pick<HostingPlugin, \"canPingTarget\" | \"pingRequestHeaders\">;\n } = {},\n): Promise<boolean> {\n if (!canPingProjectTarget(target, options.plugin)) {\n return false;\n }\n\n return pingProject(target.baseUrl, {\n ...options,\n runtimeId: target.runtimeId,\n });\n}\n","import { PROJECT_SERVER_DEPLOY_STATUS } from \"./constants\";\nimport { RUNTIME_PING_TIMEOUT_MS } from \"./runtime-constants\";\n\n/** Per-project bootstrap outcome reported by an org runtime machine. */\nexport type ProjectDeployStatus = {\n projectId: string;\n ok: boolean;\n error?: string;\n};\n\nexport type DeployStatusResponse = {\n projects: ProjectDeployStatus[];\n};\n\nexport type FetchDeployStatusOptions = {\n fetchImpl?: typeof fetch;\n timeoutMs?: number;\n};\n\n/**\n * Ask a running org machine which projects bootstrapped and which failed.\n * Best-effort: returns undefined on any error so callers can fall back to\n * treating every project as healthy (preserving pre-existing behavior).\n */\nexport async function fetchDeployStatus(\n baseUrl: string,\n options: FetchDeployStatusOptions = {},\n): Promise<DeployStatusResponse | undefined> {\n const timeoutMs = options.timeoutMs ?? RUNTIME_PING_TIMEOUT_MS;\n\n try {\n const response = await (options.fetchImpl ?? fetch)(\n new URL(PROJECT_SERVER_DEPLOY_STATUS.path, baseUrl),\n { signal: AbortSignal.timeout(timeoutMs) },\n );\n\n if (!response.ok) {\n return undefined;\n }\n\n const data = (await response.json()) as DeployStatusResponse;\n if (!data || !Array.isArray(data.projects)) {\n return undefined;\n }\n\n return data;\n } catch (error) {\n // Best-effort: fall back to promote-all, but log so a consistently\n // unreachable /deploy-status (port/6PN/version skew) is diagnosable rather\n // than silently re-creating the pre-fix behavior.\n console.warn(`[deploy-status] failed to fetch from ${baseUrl}:`, error);\n return undefined;\n }\n}\n"],"mappings":";;;;;AACA,MAAa,0BAA0B;;;ACUvC,eAAsB,YACpB,SACA,UAA8B,CAAC,GACb;CAClB,MAAM,YAAY,QAAQ,aAAA;CAC1B,MAAM,UAAU,QAAQ,QAAQ,qBAAqB,QAAQ,aAAa,IAAI,KAAK,CAAC;CAEpF,IAAI;EASF,QAAO,OARiB,QAAQ,aAAa,OAC3C,IAAI,IAAIA,wBAAAA,sBAAsB,MAAM,OAAO,GAC3C;GACE,QAAQ,YAAY,QAAQ,SAAS;GACrC;EACF,CACF,GAEgB;CAClB,QAAQ;EACN,OAAO;CACT;AACF;;;AC3BA,SAAgB,qBACd,QACA,QACyD;CACzD,IAAI,QAAQ,eACV,OAAO,OAAO,cAAc,MAAM;CAGpC,OAAO,CAAC,CAAC,OAAO;AAClB;AAEA,eAAsB,kBACpB,QACA,UAEI,CAAC,GACa;CAClB,IAAI,CAAC,qBAAqB,QAAQ,QAAQ,MAAM,GAC9C,OAAO;CAGT,OAAO,YAAY,OAAO,SAAS;EACjC,GAAG;EACH,WAAW,OAAO;CACpB,CAAC;AACH;;;;;;;;ACLA,eAAsB,kBACpB,SACA,UAAoC,CAAC,GACM;CAC3C,MAAM,YAAY,QAAQ,aAAA;CAE1B,IAAI;EACF,MAAM,WAAW,OAAO,QAAQ,aAAa,OAC3C,IAAI,IAAIC,wBAAAA,6BAA6B,MAAM,OAAO,GAClD,EAAE,QAAQ,YAAY,QAAQ,SAAS,EAAE,CAC3C;EAEA,IAAI,CAAC,SAAS,IACZ;EAGF,MAAM,OAAQ,MAAM,SAAS,KAAK;EAClC,IAAI,CAAC,QAAQ,CAAC,MAAM,QAAQ,KAAK,QAAQ,GACvC;EAGF,OAAO;CACT,SAAS,OAAO;EAId,QAAQ,KAAK,wCAAwC,QAAQ,IAAI,KAAK;EACtE;CACF;AACF"}
1
+ {"version":3,"file":"index.cjs","names":["PROJECT_SERVER_PORT","PROJECT_SERVER_HEALTH","PROJECT_SERVER_DEPLOY_STATUS"],"sources":["../src/runtime-constants.ts","../src/runtime-control.ts","../src/ping-project.ts","../src/ping-project-target.ts","../src/deploy-status.ts"],"sourcesContent":["/** Timeout for a single project runtime `/health` probe. */\nexport const RUNTIME_PING_TIMEOUT_MS = 15_000;\n","import { PROJECT_SERVER_PORT } from \"./constants\";\nimport { RUNTIME_PING_TIMEOUT_MS } from \"./runtime-constants\";\n\nexport const PROJECT_SERVER_CONTROL = {\n load: \"/control/projects/load\",\n promote: \"/control/projects/promote\",\n unload: \"/control/projects/unload\",\n port: PROJECT_SERVER_PORT,\n} as const;\n\nexport type LoadProjectOnRuntimeInput = {\n projectId: string;\n artifactId: string;\n artifactVersion: number;\n storageKey: string;\n activate?: boolean;\n};\n\nexport type RuntimeControlOptions = {\n fetchImpl?: typeof fetch;\n timeoutMs?: number;\n /** Bearer token — typically the org's WORKER_INTERNAL_TOKEN. */\n workerToken?: string;\n};\n\nasync function postControl<T>(\n baseUrl: string,\n path: string,\n body: unknown,\n options: RuntimeControlOptions = {},\n): Promise<T> {\n const timeoutMs = options.timeoutMs ?? RUNTIME_PING_TIMEOUT_MS;\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n };\n if (options.workerToken) {\n headers.authorization = `Bearer ${options.workerToken}`;\n }\n\n const response = await (options.fetchImpl ?? fetch)(new URL(path, baseUrl), {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(timeoutMs),\n });\n\n const data = (await response.json().catch(() => ({}))) as T & { ok?: boolean; error?: string };\n if (!response.ok || data.ok === false) {\n throw new Error(\n typeof data.error === \"string\"\n ? data.error\n : `Runtime control ${path} failed (${response.status})`,\n );\n }\n\n return data;\n}\n\nexport async function loadProjectOnRuntime(\n baseUrl: string,\n input: LoadProjectOnRuntimeInput,\n options: RuntimeControlOptions = {},\n): Promise<void> {\n await postControl(baseUrl, PROJECT_SERVER_CONTROL.load, input, options);\n}\n\nexport async function promoteProjectOnRuntime(\n baseUrl: string,\n input: { projectId: string; artifactId: string },\n options: RuntimeControlOptions = {},\n): Promise<void> {\n await postControl(baseUrl, PROJECT_SERVER_CONTROL.promote, input, options);\n}\n\nexport async function unloadProjectOnRuntime(\n baseUrl: string,\n input: { projectId: string; artifactId: string; force?: boolean },\n options: RuntimeControlOptions = {},\n): Promise<void> {\n await postControl(baseUrl, PROJECT_SERVER_CONTROL.unload, input, options);\n}\n\n/** True when the org runtime answers /health. */\nexport async function pingRuntimeHealth(\n baseUrl: string,\n options: RuntimeControlOptions = {},\n): Promise<boolean> {\n const timeoutMs = options.timeoutMs ?? RUNTIME_PING_TIMEOUT_MS;\n try {\n const response = await (options.fetchImpl ?? fetch)(new URL(\"/health\", baseUrl), {\n signal: AbortSignal.timeout(timeoutMs),\n });\n return response.ok;\n } catch {\n return false;\n }\n}\n","import { PROJECT_SERVER_HEALTH } from \"./constants\";\nimport type { HostingPlugin } from \"./plugin\";\nimport { RUNTIME_PING_TIMEOUT_MS } from \"./runtime-constants\";\n\nexport type PingProjectOptions = {\n runtimeId?: string | null;\n fetchImpl?: typeof fetch;\n timeoutMs?: number;\n plugin?: Pick<HostingPlugin, \"pingRequestHeaders\">;\n};\n\nexport async function pingProject(\n baseUrl: string,\n options: PingProjectOptions = {},\n): Promise<boolean> {\n const timeoutMs = options.timeoutMs ?? RUNTIME_PING_TIMEOUT_MS;\n const headers = options.plugin?.pingRequestHeaders?.(options.runtimeId ?? null) ?? {};\n\n try {\n const response = await (options.fetchImpl ?? fetch)(\n new URL(PROJECT_SERVER_HEALTH.path, baseUrl),\n {\n signal: AbortSignal.timeout(timeoutMs),\n headers,\n },\n );\n\n return response.ok;\n } catch {\n return false;\n }\n}\n","import type { HostingPlugin } from \"./plugin\";\nimport type { ProjectPingTarget } from \"./runtime\";\nimport { pingProject, type PingProjectOptions } from \"./ping-project\";\n\nexport function canPingProjectTarget(\n target: ProjectPingTarget,\n plugin?: Pick<HostingPlugin, \"canPingTarget\">,\n): target is { baseUrl: string; runtimeId: string | null } {\n if (plugin?.canPingTarget) {\n return plugin.canPingTarget(target);\n }\n\n return !!target.baseUrl;\n}\n\nexport async function pingProjectTarget(\n target: ProjectPingTarget,\n options: PingProjectOptions & {\n plugin?: Pick<HostingPlugin, \"canPingTarget\" | \"pingRequestHeaders\">;\n } = {},\n): Promise<boolean> {\n if (!canPingProjectTarget(target, options.plugin)) {\n return false;\n }\n\n return pingProject(target.baseUrl, {\n ...options,\n runtimeId: target.runtimeId,\n });\n}\n","import { PROJECT_SERVER_DEPLOY_STATUS } from \"./constants\";\nimport { RUNTIME_PING_TIMEOUT_MS } from \"./runtime-constants\";\n\nconst DEFAULT_WAIT_TIMEOUT_MS = 120_000;\nconst DEFAULT_WAIT_INTERVAL_MS = 500;\nconst REQUEST_TIMEOUT_MS = 2_000;\n\n/** Per-project bootstrap outcome reported by an org runtime machine. */\nexport type ProjectDeployStatus = {\n projectId: string;\n ok: boolean;\n error?: string;\n};\n\nexport type VersionDeployStatus = {\n projectId: string;\n artifactId: string;\n state: \"active\" | \"resident\" | \"indexed\";\n ok: boolean;\n activeJobCount?: number;\n error?: string;\n};\n\nexport type DeployStatusResponse = {\n projects: ProjectDeployStatus[];\n versions?: VersionDeployStatus[];\n};\n\nexport type FetchDeployStatusOptions = {\n fetchImpl?: typeof fetch;\n timeoutMs?: number;\n};\n\nexport type WaitForDeployStatusOptions = {\n fetchImpl?: typeof fetch;\n timeoutMs?: number;\n intervalMs?: number;\n};\n\n/**\n * Ask a running org machine which projects bootstrapped and which failed.\n * Returns undefined when the endpoint is unreachable, non-OK, times out, or\n * returns an unusable payload — callers must treat that as non-affirmative\n * health for every pending project (never promote on silence).\n */\nexport async function fetchDeployStatus(\n baseUrl: string,\n options: FetchDeployStatusOptions = {},\n): Promise<DeployStatusResponse | undefined> {\n const timeoutMs = options.timeoutMs ?? RUNTIME_PING_TIMEOUT_MS;\n\n try {\n const response = await (options.fetchImpl ?? fetch)(\n new URL(PROJECT_SERVER_DEPLOY_STATUS.path, baseUrl),\n { signal: AbortSignal.timeout(timeoutMs) },\n );\n\n if (!response.ok) {\n return undefined;\n }\n\n const data = (await response.json()) as DeployStatusResponse;\n if (!data || !Array.isArray(data.projects)) {\n return undefined;\n }\n\n return data;\n } catch (error) {\n console.warn(`[deploy-status] failed to fetch from ${baseUrl}:`, error);\n return undefined;\n }\n}\n\n/**\n * Poll `/deploy-status` until the real worker returns a usable payload or the\n * deadline expires. Retries only transient unavailability (network errors,\n * non-2xx including early-health 404, timeouts, malformed bodies). Any valid\n * `{ projects: [...] }` response — including empty or explicit failures — is\n * terminal so genuine bootstrap errors surface immediately.\n */\nexport async function waitForDeployStatus(\n baseUrl: string,\n options: WaitForDeployStatusOptions = {},\n): Promise<DeployStatusResponse | undefined> {\n const timeoutMs = options.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS;\n const intervalMs = options.intervalMs ?? DEFAULT_WAIT_INTERVAL_MS;\n const deadline = Date.now() + timeoutMs;\n\n while (Date.now() < deadline) {\n const remainingMs = deadline - Date.now();\n const requestTimeoutMs = Math.min(REQUEST_TIMEOUT_MS, Math.max(1, remainingMs));\n const status = await fetchDeployStatus(baseUrl, {\n fetchImpl: options.fetchImpl,\n timeoutMs: requestTimeoutMs,\n });\n if (status) {\n return status;\n }\n\n if (Date.now() + intervalMs >= deadline) {\n break;\n }\n await sleep(intervalMs);\n }\n\n return undefined;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n\nexport type DeployHealthVerdict = { ok: true } | { ok: false; error: string };\n\n/**\n * Require an explicit `{ projectId, ok: true }` entry for each candidate.\n * Missing entries, `ok: false`, empty `projects`, or an unavailable status\n * response all fail closed for that project.\n */\nexport function classifyDeployHealth(input: {\n projectIds: string[];\n deployStatus: DeployStatusResponse | undefined;\n}): Map<string, DeployHealthVerdict> {\n const verdicts = new Map<string, DeployHealthVerdict>();\n\n if (!input.deployStatus) {\n for (const projectId of input.projectIds) {\n verdicts.set(projectId, {\n ok: false,\n error: \"Deploy health status unavailable\",\n });\n }\n return verdicts;\n }\n\n const byId = new Map(\n input.deployStatus.projects\n .filter((entry) => typeof entry?.projectId === \"string\" && entry.projectId.length > 0)\n .map((entry) => [entry.projectId, entry]),\n );\n\n for (const projectId of input.projectIds) {\n const entry = byId.get(projectId);\n if (!entry) {\n verdicts.set(projectId, {\n ok: false,\n error: \"Deploy health status omitted project\",\n });\n continue;\n }\n if (entry.ok !== true) {\n verdicts.set(projectId, {\n ok: false,\n error: entry.error?.trim() || \"Project failed to start\",\n });\n continue;\n }\n verdicts.set(projectId, { ok: true });\n }\n\n return verdicts;\n}\n"],"mappings":"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"}
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { a as ProjectRuntimeDatabase, c as ProjectRuntimeInput, d as encodeOrgArtifacts, f as parseOrgArtifactsFromEnv, i as ProjectRuntime, l as ProjectRuntimeReconcileInput, n as OrgProjectDeploy, o as ProjectRuntimeDestroyInput, r as ProjectPingTarget, s as ProjectRuntimeHosting, t as OrgArtifactSpec, u as ProjectRuntimeResult } from "./runtime-B0jF0oMv.cjs";
2
- import { a as WorkerRuntimeConfig, i as OrganizationHostingResult, n as HostingPlugin, o as WorkerRuntimeConfigError, r as OrganizationHostingInput, s as resolveWorkerRuntimeConfig, t as HostingOptions } from "./plugin-is5oPt6x.cjs";
1
+ import { a as ProjectRuntimeDatabase, c as ProjectRuntimeInput, d as ProjectRuntimeSleepInput, f as ProjectRuntimeWakeInput, i as ProjectRuntime, l as ProjectRuntimeReconcileInput, m as parseOrgArtifactsFromEnv, n as OrgProjectDeploy, o as ProjectRuntimeDestroyInput, p as encodeOrgArtifacts, r as ProjectPingTarget, s as ProjectRuntimeHosting, t as OrgArtifactSpec, u as ProjectRuntimeResult } from "./runtime-Cp906goK.cjs";
2
+ import { a as WorkerRuntimeConfig, i as OrganizationHostingResult, n as HostingPlugin, o as WorkerRuntimeConfigError, r as OrganizationHostingInput, s as resolveWorkerRuntimeConfig, t as HostingOptions } from "./plugin-4cuXkf42.cjs";
3
3
 
4
4
  //#region src/constants.d.ts
5
5
  declare const PROJECT_SERVER_IMAGE = "keystroke/project-server:local";
@@ -16,6 +16,38 @@ declare const PROJECT_SERVER_DEPLOY_STATUS: {
16
16
  readonly port: 3000;
17
17
  };
18
18
  //#endregion
19
+ //#region src/runtime-control.d.ts
20
+ declare const PROJECT_SERVER_CONTROL: {
21
+ readonly load: "/control/projects/load";
22
+ readonly promote: "/control/projects/promote";
23
+ readonly unload: "/control/projects/unload";
24
+ readonly port: 3000;
25
+ };
26
+ type LoadProjectOnRuntimeInput = {
27
+ projectId: string;
28
+ artifactId: string;
29
+ artifactVersion: number;
30
+ storageKey: string;
31
+ activate?: boolean;
32
+ };
33
+ type RuntimeControlOptions = {
34
+ fetchImpl?: typeof fetch;
35
+ timeoutMs?: number; /** Bearer token — typically the org's WORKER_INTERNAL_TOKEN. */
36
+ workerToken?: string;
37
+ };
38
+ declare function loadProjectOnRuntime(baseUrl: string, input: LoadProjectOnRuntimeInput, options?: RuntimeControlOptions): Promise<void>;
39
+ declare function promoteProjectOnRuntime(baseUrl: string, input: {
40
+ projectId: string;
41
+ artifactId: string;
42
+ }, options?: RuntimeControlOptions): Promise<void>;
43
+ declare function unloadProjectOnRuntime(baseUrl: string, input: {
44
+ projectId: string;
45
+ artifactId: string;
46
+ force?: boolean;
47
+ }, options?: RuntimeControlOptions): Promise<void>;
48
+ /** True when the org runtime answers /health. */
49
+ declare function pingRuntimeHealth(baseUrl: string, options?: RuntimeControlOptions): Promise<boolean>;
50
+ //#endregion
19
51
  //#region src/runtime-env.d.ts
20
52
  /** Env keys that must never be forwarded into untrusted org workers. */
21
53
  declare const FORBIDDEN_WORKER_ENV_KEYS: readonly ["BETTER_AUTH_SECRET", "DATABASE_URL", "POSTHOG_API_KEY", "POSTGRES_PASSWORD", "PLATFORM_WORKER_TOKEN", "STRIPE_SECRET_KEY", "CREDENTIAL_ENCRYPTION_KEY"];
@@ -93,19 +125,57 @@ type ProjectDeployStatus = {
93
125
  ok: boolean;
94
126
  error?: string;
95
127
  };
128
+ type VersionDeployStatus = {
129
+ projectId: string;
130
+ artifactId: string;
131
+ state: "active" | "resident" | "indexed";
132
+ ok: boolean;
133
+ activeJobCount?: number;
134
+ error?: string;
135
+ };
96
136
  type DeployStatusResponse = {
97
137
  projects: ProjectDeployStatus[];
138
+ versions?: VersionDeployStatus[];
98
139
  };
99
140
  type FetchDeployStatusOptions = {
100
141
  fetchImpl?: typeof fetch;
101
142
  timeoutMs?: number;
102
143
  };
144
+ type WaitForDeployStatusOptions = {
145
+ fetchImpl?: typeof fetch;
146
+ timeoutMs?: number;
147
+ intervalMs?: number;
148
+ };
103
149
  /**
104
150
  * Ask a running org machine which projects bootstrapped and which failed.
105
- * Best-effort: returns undefined on any error so callers can fall back to
106
- * treating every project as healthy (preserving pre-existing behavior).
151
+ * Returns undefined when the endpoint is unreachable, non-OK, times out, or
152
+ * returns an unusable payload callers must treat that as non-affirmative
153
+ * health for every pending project (never promote on silence).
107
154
  */
108
155
  declare function fetchDeployStatus(baseUrl: string, options?: FetchDeployStatusOptions): Promise<DeployStatusResponse | undefined>;
156
+ /**
157
+ * Poll `/deploy-status` until the real worker returns a usable payload or the
158
+ * deadline expires. Retries only transient unavailability (network errors,
159
+ * non-2xx including early-health 404, timeouts, malformed bodies). Any valid
160
+ * `{ projects: [...] }` response — including empty or explicit failures — is
161
+ * terminal so genuine bootstrap errors surface immediately.
162
+ */
163
+ declare function waitForDeployStatus(baseUrl: string, options?: WaitForDeployStatusOptions): Promise<DeployStatusResponse | undefined>;
164
+ type DeployHealthVerdict = {
165
+ ok: true;
166
+ } | {
167
+ ok: false;
168
+ error: string;
169
+ };
170
+ /**
171
+ * Require an explicit `{ projectId, ok: true }` entry for each candidate.
172
+ * Missing entries, `ok: false`, empty `projects`, or an unavailable status
173
+ * response all fail closed for that project.
174
+ */
175
+ declare function classifyDeployHealth(input: {
176
+ projectIds: string[];
177
+ deployStatus: DeployStatusResponse | undefined;
178
+ }): Map<string, DeployHealthVerdict>;
109
179
  //#endregion
110
- export { DEV_PLATFORM_WORKER_TOKEN, type DeployStatusResponse, FORBIDDEN_WORKER_ENV_KEYS, type FetchDeployStatusOptions, type HostingOptions, type HostingPlugin, type OrgArtifactSpec, type OrgProjectDeploy, type OrganizationHostingInput, type OrganizationHostingResult, PROJECT_SERVER_DEPLOY_STATUS, PROJECT_SERVER_FRAMEWORK_NODE_MODULES, PROJECT_SERVER_FRAMEWORK_ROOT, PROJECT_SERVER_HEALTH, PROJECT_SERVER_IMAGE, PROJECT_SERVER_PORT, PROJECT_SERVER_ROOT, type PingProjectOptions, type ProjectDeployStatus, type ProjectPingTarget, type ProjectRuntime, type ProjectRuntimeDatabase, type ProjectRuntimeDestroyInput, type ProjectRuntimeHosting, type ProjectRuntimeInput, type ProjectRuntimeReconcileInput, type ProjectRuntimeResult, RUNTIME_PING_TIMEOUT_MS, type RuntimeLaunchSpec, type VerifiedOrgWorkerToken, type WaitForHealthOptions, type WorkerRuntimeConfig, WorkerRuntimeConfigError, buildRuntimeEnv, canPingProjectTarget, encodeOrgArtifacts, fetchDeployStatus, formatDockerEnv, mintOrgWorkerToken, parseOrgArtifactsFromEnv, pingProject, pingProjectTarget, resolvePlatformWorkerToken, resolveProjectServerImage, resolveRuntimeLaunch, resolveWorkerPlatformUrl, resolveWorkerRuntimeConfig, rewriteLoopbackHost, rewriteLoopbackUrl, verifyOrgWorkerToken, waitForHealth };
180
+ export { DEV_PLATFORM_WORKER_TOKEN, type DeployHealthVerdict, type DeployStatusResponse, FORBIDDEN_WORKER_ENV_KEYS, type FetchDeployStatusOptions, type HostingOptions, type HostingPlugin, type LoadProjectOnRuntimeInput, type OrgArtifactSpec, type OrgProjectDeploy, type OrganizationHostingInput, type OrganizationHostingResult, PROJECT_SERVER_CONTROL, PROJECT_SERVER_DEPLOY_STATUS, PROJECT_SERVER_FRAMEWORK_NODE_MODULES, PROJECT_SERVER_FRAMEWORK_ROOT, PROJECT_SERVER_HEALTH, PROJECT_SERVER_IMAGE, PROJECT_SERVER_PORT, PROJECT_SERVER_ROOT, type PingProjectOptions, type ProjectDeployStatus, type ProjectPingTarget, type ProjectRuntime, type ProjectRuntimeDatabase, type ProjectRuntimeDestroyInput, type ProjectRuntimeHosting, type ProjectRuntimeInput, type ProjectRuntimeReconcileInput, type ProjectRuntimeResult, type ProjectRuntimeSleepInput, type ProjectRuntimeWakeInput, RUNTIME_PING_TIMEOUT_MS, type RuntimeControlOptions, type RuntimeLaunchSpec, type VerifiedOrgWorkerToken, type VersionDeployStatus, type WaitForDeployStatusOptions, type WaitForHealthOptions, type WorkerRuntimeConfig, WorkerRuntimeConfigError, buildRuntimeEnv, canPingProjectTarget, classifyDeployHealth, encodeOrgArtifacts, fetchDeployStatus, formatDockerEnv, loadProjectOnRuntime, mintOrgWorkerToken, parseOrgArtifactsFromEnv, pingProject, pingProjectTarget, pingRuntimeHealth, promoteProjectOnRuntime, resolvePlatformWorkerToken, resolveProjectServerImage, resolveRuntimeLaunch, resolveWorkerPlatformUrl, resolveWorkerRuntimeConfig, rewriteLoopbackHost, rewriteLoopbackUrl, unloadProjectOnRuntime, verifyOrgWorkerToken, waitForDeployStatus, waitForHealth };
111
181
  //# sourceMappingURL=index.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../src/constants.ts","../src/runtime-env.ts","../src/org-worker-token.ts","../src/wait-for-health.ts","../src/runtime-constants.ts","../src/ping-project.ts","../src/ping-project-target.ts","../src/deploy-status.ts"],"mappings":";;;;cAAa,oBAAA;AAAA,cAEA,mBAAA;AAAA,cAEA,mBAAA;AAAA,cAEA,6BAAA;AAAA,cAEA,qCAAA;AAAA,cAEA,qBAAA;EAAA,SAGH,IAAA;EAAA,SAAA,IAAA;AAAA;AAAA,cAEG,4BAAA;EAAA,SAGH,IAAA;EAAA,SAAA,IAAA;AAAA;;;AAlBV;AAAA,cCyCa,yBAAA;;cAmBA,yBAAA;AAAA,KAED,iBAAA;EACV,KAAA;EACA,GAAA,EAAK,MAAA;EACL,IAAA;EACA,MAAA,SAAe,qBAAqB;AAAA;AD9DtC;;;;AAAA,iBCqEgB,oBAAA,CACd,OAAA,EAAS,cAAA,EACT,SAAA,EAAW,eAAA,IACX,QAAA,EAAU,sBAAA,EACV,QAAA,GAAW,MAAA,mBACV,iBAAA;;iBAsCa,eAAA,CACd,MAAA,EAAQ,MAAA,CAAO,UAAA,EACf,SAAA,EAAW,eAAA,IACX,QAAA,EAAU,sBAAA,EACV,QAAA,GAAW,MAAA,mBACV,MAAA;AAAA,iBA2Da,0BAAA,CACd,MAAA,GAAQ,MAAA,CAAO,UAAwB;;iBAezB,wBAAA,CACd,MAAA,GAAQ,MAAA,CAAO,UAAwB;;iBAczB,mBAAA,CAAoB,IAAY;AAAA,iBAQhC,kBAAA,CAAmB,GAAW;;iBAW9B,eAAA,CAAgB,GAA2B,EAAtB,MAAM;AAAA,iBAI3B,yBAAA,CACd,GAAA,GAAK,MAAA,CAAO,UAAwB,EACpC,QAAA;;;;;;;AD5OF;iBEWgB,kBAAA,CAAmB,MAAA,UAAgB,cAAsB;AAAA,KAI7D,sBAAA;EACV,cAAc;AAAA;AAAA,iBAGA,oBAAA,CACd,MAAA,sBACA,KAAA,uBACC,sBAAsB;;;KCjBb,oBAAA;EACV,SAAA;EACA,UAAA;EACA,SAAA,UAAmB,KAAK;AAAA;AAAA,iBAGJ,aAAA,CACpB,OAAA,UACA,OAAA,GAAS,oBAAA,GACR,OAAO;;;;cCbG,uBAAA;;;KCGD,kBAAA;EACV,SAAA;EACA,SAAA,UAAmB,KAAA;EACnB,SAAA;EACA,MAAA,GAAS,IAAA,CAAK,aAAA;AAAA;AAAA,iBAGM,WAAA,CACpB,OAAA,UACA,OAAA,GAAS,kBAAA,GACR,OAAO;;;iBCVM,oBAAA,CACd,MAAA,EAAQ,iBAAA,EACR,MAAA,GAAS,IAAA,CAAK,aAAA,qBACb,MAAA;EAAY,OAAA;EAAiB,SAAA;AAAA;AAAA,iBAQV,iBAAA,CACpB,MAAA,EAAQ,iBAAA,EACR,OAAA,GAAS,kBAAA;EACP,MAAA,GAAS,IAAA,CAAK,aAAA;AAAA,IAEf,OAAA;;;;KChBS,mBAAA;EACV,SAAA;EACA,EAAA;EACA,KAAA;AAAA;AAAA,KAGU,oBAAA;EACV,QAAA,EAAU,mBAAmB;AAAA;AAAA,KAGnB,wBAAA;EACV,SAAA,UAAmB,KAAK;EACxB,SAAA;AAAA;APd8B;AAEhC;;;;AAFgC,iBOsBV,iBAAA,CACpB,OAAA,UACA,OAAA,GAAS,wBAAA,GACR,OAAA,CAAQ,oBAAA"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/constants.ts","../src/runtime-control.ts","../src/runtime-env.ts","../src/org-worker-token.ts","../src/wait-for-health.ts","../src/runtime-constants.ts","../src/ping-project.ts","../src/ping-project-target.ts","../src/deploy-status.ts"],"mappings":";;;;cAAa,oBAAA;AAAA,cAEA,mBAAA;AAAA,cAEA,mBAAA;AAAA,cAEA,6BAAA;AAAA,cAEA,qCAAA;AAAA,cAEA,qBAAA;EAAA,SAGH,IAAA;EAAA,SAAA,IAAA;AAAA;AAAA,cAEG,4BAAA;EAAA,SAGH,IAAA;EAAA,SAAA,IAAA;AAAA;;;cCfG,sBAAA;EAAA;;;;;KAOD,yBAAA;EACV,SAAA;EACA,UAAA;EACA,eAAA;EACA,UAAA;EACA,QAAA;AAAA;AAAA,KAGU,qBAAA;EACV,SAAA,UAAmB,KAAK;EACxB,SAAA;EAEA,WAAA;AAAA;AAAA,iBAoCoB,oBAAA,CACpB,OAAA,UACA,KAAA,EAAO,yBAAA,EACP,OAAA,GAAS,qBAAA,GACR,OAAA;AAAA,iBAImB,uBAAA,CACpB,OAAA,UACA,KAAA;EAAS,SAAA;EAAmB,UAAA;AAAA,GAC5B,OAAA,GAAS,qBAAA,GACR,OAAO;AAAA,iBAIY,sBAAA,CACpB,OAAA,UACA,KAAA;EAAS,SAAA;EAAmB,UAAA;EAAoB,KAAA;AAAA,GAChD,OAAA,GAAS,qBAAA,GACR,OAAO;ADpEV;AAAA,iBCyEsB,iBAAA,CACpB,OAAA,UACA,OAAA,GAAS,qBAAA,GACR,OAAO;;;ADtFV;AAAA,cEyCa,yBAAA;;cAmBA,yBAAA;AAAA,KAED,iBAAA;EACV,KAAA;EACA,GAAA,EAAK,MAAA;EACL,IAAA;EACA,MAAA,SAAe,qBAAqB;AAAA;AF9DtC;;;;AAAA,iBEqEgB,oBAAA,CACd,OAAA,EAAS,cAAA,EACT,SAAA,EAAW,eAAA,IACX,QAAA,EAAU,sBAAA,EACV,QAAA,GAAW,MAAA,mBACV,iBAAA;;iBAsCa,eAAA,CACd,MAAA,EAAQ,MAAA,CAAO,UAAA,EACf,SAAA,EAAW,eAAA,IACX,QAAA,EAAU,sBAAA,EACV,QAAA,GAAW,MAAA,mBACV,MAAA;AAAA,iBA2Da,0BAAA,CACd,MAAA,GAAQ,MAAA,CAAO,UAAwB;;iBAezB,wBAAA,CACd,MAAA,GAAQ,MAAA,CAAO,UAAwB;;iBAczB,mBAAA,CAAoB,IAAY;AAAA,iBAQhC,kBAAA,CAAmB,GAAW;;iBAW9B,eAAA,CAAgB,GAA2B,EAAtB,MAAM;AAAA,iBAI3B,yBAAA,CACd,GAAA,GAAK,MAAA,CAAO,UAAwB,EACpC,QAAA;;;;;;;AF5OF;iBGWgB,kBAAA,CAAmB,MAAA,UAAgB,cAAsB;AAAA,KAI7D,sBAAA;EACV,cAAc;AAAA;AAAA,iBAGA,oBAAA,CACd,MAAA,sBACA,KAAA,uBACC,sBAAsB;;;KCjBb,oBAAA;EACV,SAAA;EACA,UAAA;EACA,SAAA,UAAmB,KAAK;AAAA;AAAA,iBAGJ,aAAA,CACpB,OAAA,UACA,OAAA,GAAS,oBAAA,GACR,OAAO;;;;cCbG,uBAAA;;;KCGD,kBAAA;EACV,SAAA;EACA,SAAA,UAAmB,KAAA;EACnB,SAAA;EACA,MAAA,GAAS,IAAA,CAAK,aAAA;AAAA;AAAA,iBAGM,WAAA,CACpB,OAAA,UACA,OAAA,GAAS,kBAAA,GACR,OAAO;;;iBCVM,oBAAA,CACd,MAAA,EAAQ,iBAAA,EACR,MAAA,GAAS,IAAA,CAAK,aAAA,qBACb,MAAA;EAAY,OAAA;EAAiB,SAAA;AAAA;AAAA,iBAQV,iBAAA,CACpB,MAAA,EAAQ,iBAAA,EACR,OAAA,GAAS,kBAAA;EACP,MAAA,GAAS,IAAA,CAAK,aAAA;AAAA,IAEf,OAAA;;;;KCZS,mBAAA;EACV,SAAA;EACA,EAAA;EACA,KAAA;AAAA;AAAA,KAGU,mBAAA;EACV,SAAA;EACA,UAAA;EACA,KAAA;EACA,EAAA;EACA,cAAA;EACA,KAAA;AAAA;AAAA,KAGU,oBAAA;EACV,QAAA,EAAU,mBAAA;EACV,QAAA,GAAW,mBAAmB;AAAA;AAAA,KAGpB,wBAAA;EACV,SAAA,UAAmB,KAAK;EACxB,SAAA;AAAA;AAAA,KAGU,0BAAA;EACV,SAAA,UAAmB,KAAK;EACxB,SAAA;EACA,UAAA;AAAA;;AR5BgD;AAElD;;;;iBQmCsB,iBAAA,CACpB,OAAA,UACA,OAAA,GAAS,wBAAA,GACR,OAAA,CAAQ,oBAAA;ARjCX;;;;;;;AAAA,iBQiEsB,mBAAA,CACpB,OAAA,UACA,OAAA,GAAS,0BAAA,GACR,OAAA,CAAQ,oBAAA;AAAA,KA+BC,mBAAA;EAAwB,EAAA;AAAA;EAAe,EAAA;EAAW,KAAA;AAAA;;;;;APxG9D;iBO+GgB,oBAAA,CAAqB,KAAA;EACnC,UAAA;EACA,YAAA,EAAc,oBAAA;AAAA,IACZ,GAAA,SAAY,mBAAA"}
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { a as ProjectRuntimeDatabase, c as ProjectRuntimeInput, d as encodeOrgArtifacts, f as parseOrgArtifactsFromEnv, i as ProjectRuntime, l as ProjectRuntimeReconcileInput, n as OrgProjectDeploy, o as ProjectRuntimeDestroyInput, r as ProjectPingTarget, s as ProjectRuntimeHosting, t as OrgArtifactSpec, u as ProjectRuntimeResult } from "./runtime-B0jF0oMv.mjs";
2
- import { a as WorkerRuntimeConfig, i as OrganizationHostingResult, n as HostingPlugin, o as WorkerRuntimeConfigError, r as OrganizationHostingInput, s as resolveWorkerRuntimeConfig, t as HostingOptions } from "./plugin-qEl5Sx4m.mjs";
1
+ import { a as ProjectRuntimeDatabase, c as ProjectRuntimeInput, d as ProjectRuntimeSleepInput, f as ProjectRuntimeWakeInput, i as ProjectRuntime, l as ProjectRuntimeReconcileInput, m as parseOrgArtifactsFromEnv, n as OrgProjectDeploy, o as ProjectRuntimeDestroyInput, p as encodeOrgArtifacts, r as ProjectPingTarget, s as ProjectRuntimeHosting, t as OrgArtifactSpec, u as ProjectRuntimeResult } from "./runtime-Cp906goK.mjs";
2
+ import { a as WorkerRuntimeConfig, i as OrganizationHostingResult, n as HostingPlugin, o as WorkerRuntimeConfigError, r as OrganizationHostingInput, s as resolveWorkerRuntimeConfig, t as HostingOptions } from "./plugin-eiAXqE7P.mjs";
3
3
 
4
4
  //#region src/constants.d.ts
5
5
  declare const PROJECT_SERVER_IMAGE = "keystroke/project-server:local";
@@ -16,6 +16,38 @@ declare const PROJECT_SERVER_DEPLOY_STATUS: {
16
16
  readonly port: 3000;
17
17
  };
18
18
  //#endregion
19
+ //#region src/runtime-control.d.ts
20
+ declare const PROJECT_SERVER_CONTROL: {
21
+ readonly load: "/control/projects/load";
22
+ readonly promote: "/control/projects/promote";
23
+ readonly unload: "/control/projects/unload";
24
+ readonly port: 3000;
25
+ };
26
+ type LoadProjectOnRuntimeInput = {
27
+ projectId: string;
28
+ artifactId: string;
29
+ artifactVersion: number;
30
+ storageKey: string;
31
+ activate?: boolean;
32
+ };
33
+ type RuntimeControlOptions = {
34
+ fetchImpl?: typeof fetch;
35
+ timeoutMs?: number; /** Bearer token — typically the org's WORKER_INTERNAL_TOKEN. */
36
+ workerToken?: string;
37
+ };
38
+ declare function loadProjectOnRuntime(baseUrl: string, input: LoadProjectOnRuntimeInput, options?: RuntimeControlOptions): Promise<void>;
39
+ declare function promoteProjectOnRuntime(baseUrl: string, input: {
40
+ projectId: string;
41
+ artifactId: string;
42
+ }, options?: RuntimeControlOptions): Promise<void>;
43
+ declare function unloadProjectOnRuntime(baseUrl: string, input: {
44
+ projectId: string;
45
+ artifactId: string;
46
+ force?: boolean;
47
+ }, options?: RuntimeControlOptions): Promise<void>;
48
+ /** True when the org runtime answers /health. */
49
+ declare function pingRuntimeHealth(baseUrl: string, options?: RuntimeControlOptions): Promise<boolean>;
50
+ //#endregion
19
51
  //#region src/runtime-env.d.ts
20
52
  /** Env keys that must never be forwarded into untrusted org workers. */
21
53
  declare const FORBIDDEN_WORKER_ENV_KEYS: readonly ["BETTER_AUTH_SECRET", "DATABASE_URL", "POSTHOG_API_KEY", "POSTGRES_PASSWORD", "PLATFORM_WORKER_TOKEN", "STRIPE_SECRET_KEY", "CREDENTIAL_ENCRYPTION_KEY"];
@@ -93,19 +125,57 @@ type ProjectDeployStatus = {
93
125
  ok: boolean;
94
126
  error?: string;
95
127
  };
128
+ type VersionDeployStatus = {
129
+ projectId: string;
130
+ artifactId: string;
131
+ state: "active" | "resident" | "indexed";
132
+ ok: boolean;
133
+ activeJobCount?: number;
134
+ error?: string;
135
+ };
96
136
  type DeployStatusResponse = {
97
137
  projects: ProjectDeployStatus[];
138
+ versions?: VersionDeployStatus[];
98
139
  };
99
140
  type FetchDeployStatusOptions = {
100
141
  fetchImpl?: typeof fetch;
101
142
  timeoutMs?: number;
102
143
  };
144
+ type WaitForDeployStatusOptions = {
145
+ fetchImpl?: typeof fetch;
146
+ timeoutMs?: number;
147
+ intervalMs?: number;
148
+ };
103
149
  /**
104
150
  * Ask a running org machine which projects bootstrapped and which failed.
105
- * Best-effort: returns undefined on any error so callers can fall back to
106
- * treating every project as healthy (preserving pre-existing behavior).
151
+ * Returns undefined when the endpoint is unreachable, non-OK, times out, or
152
+ * returns an unusable payload callers must treat that as non-affirmative
153
+ * health for every pending project (never promote on silence).
107
154
  */
108
155
  declare function fetchDeployStatus(baseUrl: string, options?: FetchDeployStatusOptions): Promise<DeployStatusResponse | undefined>;
156
+ /**
157
+ * Poll `/deploy-status` until the real worker returns a usable payload or the
158
+ * deadline expires. Retries only transient unavailability (network errors,
159
+ * non-2xx including early-health 404, timeouts, malformed bodies). Any valid
160
+ * `{ projects: [...] }` response — including empty or explicit failures — is
161
+ * terminal so genuine bootstrap errors surface immediately.
162
+ */
163
+ declare function waitForDeployStatus(baseUrl: string, options?: WaitForDeployStatusOptions): Promise<DeployStatusResponse | undefined>;
164
+ type DeployHealthVerdict = {
165
+ ok: true;
166
+ } | {
167
+ ok: false;
168
+ error: string;
169
+ };
170
+ /**
171
+ * Require an explicit `{ projectId, ok: true }` entry for each candidate.
172
+ * Missing entries, `ok: false`, empty `projects`, or an unavailable status
173
+ * response all fail closed for that project.
174
+ */
175
+ declare function classifyDeployHealth(input: {
176
+ projectIds: string[];
177
+ deployStatus: DeployStatusResponse | undefined;
178
+ }): Map<string, DeployHealthVerdict>;
109
179
  //#endregion
110
- export { DEV_PLATFORM_WORKER_TOKEN, type DeployStatusResponse, FORBIDDEN_WORKER_ENV_KEYS, type FetchDeployStatusOptions, type HostingOptions, type HostingPlugin, type OrgArtifactSpec, type OrgProjectDeploy, type OrganizationHostingInput, type OrganizationHostingResult, PROJECT_SERVER_DEPLOY_STATUS, PROJECT_SERVER_FRAMEWORK_NODE_MODULES, PROJECT_SERVER_FRAMEWORK_ROOT, PROJECT_SERVER_HEALTH, PROJECT_SERVER_IMAGE, PROJECT_SERVER_PORT, PROJECT_SERVER_ROOT, type PingProjectOptions, type ProjectDeployStatus, type ProjectPingTarget, type ProjectRuntime, type ProjectRuntimeDatabase, type ProjectRuntimeDestroyInput, type ProjectRuntimeHosting, type ProjectRuntimeInput, type ProjectRuntimeReconcileInput, type ProjectRuntimeResult, RUNTIME_PING_TIMEOUT_MS, type RuntimeLaunchSpec, type VerifiedOrgWorkerToken, type WaitForHealthOptions, type WorkerRuntimeConfig, WorkerRuntimeConfigError, buildRuntimeEnv, canPingProjectTarget, encodeOrgArtifacts, fetchDeployStatus, formatDockerEnv, mintOrgWorkerToken, parseOrgArtifactsFromEnv, pingProject, pingProjectTarget, resolvePlatformWorkerToken, resolveProjectServerImage, resolveRuntimeLaunch, resolveWorkerPlatformUrl, resolveWorkerRuntimeConfig, rewriteLoopbackHost, rewriteLoopbackUrl, verifyOrgWorkerToken, waitForHealth };
180
+ export { DEV_PLATFORM_WORKER_TOKEN, type DeployHealthVerdict, type DeployStatusResponse, FORBIDDEN_WORKER_ENV_KEYS, type FetchDeployStatusOptions, type HostingOptions, type HostingPlugin, type LoadProjectOnRuntimeInput, type OrgArtifactSpec, type OrgProjectDeploy, type OrganizationHostingInput, type OrganizationHostingResult, PROJECT_SERVER_CONTROL, PROJECT_SERVER_DEPLOY_STATUS, PROJECT_SERVER_FRAMEWORK_NODE_MODULES, PROJECT_SERVER_FRAMEWORK_ROOT, PROJECT_SERVER_HEALTH, PROJECT_SERVER_IMAGE, PROJECT_SERVER_PORT, PROJECT_SERVER_ROOT, type PingProjectOptions, type ProjectDeployStatus, type ProjectPingTarget, type ProjectRuntime, type ProjectRuntimeDatabase, type ProjectRuntimeDestroyInput, type ProjectRuntimeHosting, type ProjectRuntimeInput, type ProjectRuntimeReconcileInput, type ProjectRuntimeResult, type ProjectRuntimeSleepInput, type ProjectRuntimeWakeInput, RUNTIME_PING_TIMEOUT_MS, type RuntimeControlOptions, type RuntimeLaunchSpec, type VerifiedOrgWorkerToken, type VersionDeployStatus, type WaitForDeployStatusOptions, type WaitForHealthOptions, type WorkerRuntimeConfig, WorkerRuntimeConfigError, buildRuntimeEnv, canPingProjectTarget, classifyDeployHealth, encodeOrgArtifacts, fetchDeployStatus, formatDockerEnv, loadProjectOnRuntime, mintOrgWorkerToken, parseOrgArtifactsFromEnv, pingProject, pingProjectTarget, pingRuntimeHealth, promoteProjectOnRuntime, resolvePlatformWorkerToken, resolveProjectServerImage, resolveRuntimeLaunch, resolveWorkerPlatformUrl, resolveWorkerRuntimeConfig, rewriteLoopbackHost, rewriteLoopbackUrl, unloadProjectOnRuntime, verifyOrgWorkerToken, waitForDeployStatus, waitForHealth };
111
181
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/constants.ts","../src/runtime-env.ts","../src/org-worker-token.ts","../src/wait-for-health.ts","../src/runtime-constants.ts","../src/ping-project.ts","../src/ping-project-target.ts","../src/deploy-status.ts"],"mappings":";;;;cAAa,oBAAA;AAAA,cAEA,mBAAA;AAAA,cAEA,mBAAA;AAAA,cAEA,6BAAA;AAAA,cAEA,qCAAA;AAAA,cAEA,qBAAA;EAAA,SAGH,IAAA;EAAA,SAAA,IAAA;AAAA;AAAA,cAEG,4BAAA;EAAA,SAGH,IAAA;EAAA,SAAA,IAAA;AAAA;;;AAlBV;AAAA,cCyCa,yBAAA;;cAmBA,yBAAA;AAAA,KAED,iBAAA;EACV,KAAA;EACA,GAAA,EAAK,MAAA;EACL,IAAA;EACA,MAAA,SAAe,qBAAqB;AAAA;AD9DtC;;;;AAAA,iBCqEgB,oBAAA,CACd,OAAA,EAAS,cAAA,EACT,SAAA,EAAW,eAAA,IACX,QAAA,EAAU,sBAAA,EACV,QAAA,GAAW,MAAA,mBACV,iBAAA;;iBAsCa,eAAA,CACd,MAAA,EAAQ,MAAA,CAAO,UAAA,EACf,SAAA,EAAW,eAAA,IACX,QAAA,EAAU,sBAAA,EACV,QAAA,GAAW,MAAA,mBACV,MAAA;AAAA,iBA2Da,0BAAA,CACd,MAAA,GAAQ,MAAA,CAAO,UAAwB;;iBAezB,wBAAA,CACd,MAAA,GAAQ,MAAA,CAAO,UAAwB;;iBAczB,mBAAA,CAAoB,IAAY;AAAA,iBAQhC,kBAAA,CAAmB,GAAW;;iBAW9B,eAAA,CAAgB,GAA2B,EAAtB,MAAM;AAAA,iBAI3B,yBAAA,CACd,GAAA,GAAK,MAAA,CAAO,UAAwB,EACpC,QAAA;;;;;;;AD5OF;iBEWgB,kBAAA,CAAmB,MAAA,UAAgB,cAAsB;AAAA,KAI7D,sBAAA;EACV,cAAc;AAAA;AAAA,iBAGA,oBAAA,CACd,MAAA,sBACA,KAAA,uBACC,sBAAsB;;;KCjBb,oBAAA;EACV,SAAA;EACA,UAAA;EACA,SAAA,UAAmB,KAAK;AAAA;AAAA,iBAGJ,aAAA,CACpB,OAAA,UACA,OAAA,GAAS,oBAAA,GACR,OAAO;;;;cCbG,uBAAA;;;KCGD,kBAAA;EACV,SAAA;EACA,SAAA,UAAmB,KAAA;EACnB,SAAA;EACA,MAAA,GAAS,IAAA,CAAK,aAAA;AAAA;AAAA,iBAGM,WAAA,CACpB,OAAA,UACA,OAAA,GAAS,kBAAA,GACR,OAAO;;;iBCVM,oBAAA,CACd,MAAA,EAAQ,iBAAA,EACR,MAAA,GAAS,IAAA,CAAK,aAAA,qBACb,MAAA;EAAY,OAAA;EAAiB,SAAA;AAAA;AAAA,iBAQV,iBAAA,CACpB,MAAA,EAAQ,iBAAA,EACR,OAAA,GAAS,kBAAA;EACP,MAAA,GAAS,IAAA,CAAK,aAAA;AAAA,IAEf,OAAA;;;;KChBS,mBAAA;EACV,SAAA;EACA,EAAA;EACA,KAAA;AAAA;AAAA,KAGU,oBAAA;EACV,QAAA,EAAU,mBAAmB;AAAA;AAAA,KAGnB,wBAAA;EACV,SAAA,UAAmB,KAAK;EACxB,SAAA;AAAA;APd8B;AAEhC;;;;AAFgC,iBOsBV,iBAAA,CACpB,OAAA,UACA,OAAA,GAAS,wBAAA,GACR,OAAA,CAAQ,oBAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/constants.ts","../src/runtime-control.ts","../src/runtime-env.ts","../src/org-worker-token.ts","../src/wait-for-health.ts","../src/runtime-constants.ts","../src/ping-project.ts","../src/ping-project-target.ts","../src/deploy-status.ts"],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -4,6 +4,46 @@ import { n as parseOrgArtifactsFromEnv, t as encodeOrgArtifacts } from "./runtim
4
4
  /** Timeout for a single project runtime `/health` probe. */
5
5
  const RUNTIME_PING_TIMEOUT_MS = 15e3;
6
6
  //#endregion
7
+ //#region src/runtime-control.ts
8
+ const PROJECT_SERVER_CONTROL = {
9
+ load: "/control/projects/load",
10
+ promote: "/control/projects/promote",
11
+ unload: "/control/projects/unload",
12
+ port: PROJECT_SERVER_PORT
13
+ };
14
+ async function postControl(baseUrl, path, body, options = {}) {
15
+ const timeoutMs = options.timeoutMs ?? 15e3;
16
+ const headers = { "content-type": "application/json" };
17
+ if (options.workerToken) headers.authorization = `Bearer ${options.workerToken}`;
18
+ const response = await (options.fetchImpl ?? fetch)(new URL(path, baseUrl), {
19
+ method: "POST",
20
+ headers,
21
+ body: JSON.stringify(body),
22
+ signal: AbortSignal.timeout(timeoutMs)
23
+ });
24
+ const data = await response.json().catch(() => ({}));
25
+ if (!response.ok || data.ok === false) throw new Error(typeof data.error === "string" ? data.error : `Runtime control ${path} failed (${response.status})`);
26
+ return data;
27
+ }
28
+ async function loadProjectOnRuntime(baseUrl, input, options = {}) {
29
+ await postControl(baseUrl, PROJECT_SERVER_CONTROL.load, input, options);
30
+ }
31
+ async function promoteProjectOnRuntime(baseUrl, input, options = {}) {
32
+ await postControl(baseUrl, PROJECT_SERVER_CONTROL.promote, input, options);
33
+ }
34
+ async function unloadProjectOnRuntime(baseUrl, input, options = {}) {
35
+ await postControl(baseUrl, PROJECT_SERVER_CONTROL.unload, input, options);
36
+ }
37
+ /** True when the org runtime answers /health. */
38
+ async function pingRuntimeHealth(baseUrl, options = {}) {
39
+ const timeoutMs = options.timeoutMs ?? 15e3;
40
+ try {
41
+ return (await (options.fetchImpl ?? fetch)(new URL("/health", baseUrl), { signal: AbortSignal.timeout(timeoutMs) })).ok;
42
+ } catch {
43
+ return false;
44
+ }
45
+ }
46
+ //#endregion
7
47
  //#region src/ping-project.ts
8
48
  async function pingProject(baseUrl, options = {}) {
9
49
  const timeoutMs = options.timeoutMs ?? 15e3;
@@ -32,10 +72,14 @@ async function pingProjectTarget(target, options = {}) {
32
72
  }
33
73
  //#endregion
34
74
  //#region src/deploy-status.ts
75
+ const DEFAULT_WAIT_TIMEOUT_MS = 12e4;
76
+ const DEFAULT_WAIT_INTERVAL_MS = 500;
77
+ const REQUEST_TIMEOUT_MS = 2e3;
35
78
  /**
36
79
  * Ask a running org machine which projects bootstrapped and which failed.
37
- * Best-effort: returns undefined on any error so callers can fall back to
38
- * treating every project as healthy (preserving pre-existing behavior).
80
+ * Returns undefined when the endpoint is unreachable, non-OK, times out, or
81
+ * returns an unusable payload callers must treat that as non-affirmative
82
+ * health for every pending project (never promote on silence).
39
83
  */
40
84
  async function fetchDeployStatus(baseUrl, options = {}) {
41
85
  const timeoutMs = options.timeoutMs ?? 15e3;
@@ -50,7 +94,70 @@ async function fetchDeployStatus(baseUrl, options = {}) {
50
94
  return;
51
95
  }
52
96
  }
97
+ /**
98
+ * Poll `/deploy-status` until the real worker returns a usable payload or the
99
+ * deadline expires. Retries only transient unavailability (network errors,
100
+ * non-2xx including early-health 404, timeouts, malformed bodies). Any valid
101
+ * `{ projects: [...] }` response — including empty or explicit failures — is
102
+ * terminal so genuine bootstrap errors surface immediately.
103
+ */
104
+ async function waitForDeployStatus(baseUrl, options = {}) {
105
+ const timeoutMs = options.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS;
106
+ const intervalMs = options.intervalMs ?? DEFAULT_WAIT_INTERVAL_MS;
107
+ const deadline = Date.now() + timeoutMs;
108
+ while (Date.now() < deadline) {
109
+ const remainingMs = deadline - Date.now();
110
+ const requestTimeoutMs = Math.min(REQUEST_TIMEOUT_MS, Math.max(1, remainingMs));
111
+ const status = await fetchDeployStatus(baseUrl, {
112
+ fetchImpl: options.fetchImpl,
113
+ timeoutMs: requestTimeoutMs
114
+ });
115
+ if (status) return status;
116
+ if (Date.now() + intervalMs >= deadline) break;
117
+ await sleep(intervalMs);
118
+ }
119
+ }
120
+ function sleep(ms) {
121
+ return new Promise((resolve) => {
122
+ setTimeout(resolve, ms);
123
+ });
124
+ }
125
+ /**
126
+ * Require an explicit `{ projectId, ok: true }` entry for each candidate.
127
+ * Missing entries, `ok: false`, empty `projects`, or an unavailable status
128
+ * response all fail closed for that project.
129
+ */
130
+ function classifyDeployHealth(input) {
131
+ const verdicts = /* @__PURE__ */ new Map();
132
+ if (!input.deployStatus) {
133
+ for (const projectId of input.projectIds) verdicts.set(projectId, {
134
+ ok: false,
135
+ error: "Deploy health status unavailable"
136
+ });
137
+ return verdicts;
138
+ }
139
+ const byId = new Map(input.deployStatus.projects.filter((entry) => typeof entry?.projectId === "string" && entry.projectId.length > 0).map((entry) => [entry.projectId, entry]));
140
+ for (const projectId of input.projectIds) {
141
+ const entry = byId.get(projectId);
142
+ if (!entry) {
143
+ verdicts.set(projectId, {
144
+ ok: false,
145
+ error: "Deploy health status omitted project"
146
+ });
147
+ continue;
148
+ }
149
+ if (entry.ok !== true) {
150
+ verdicts.set(projectId, {
151
+ ok: false,
152
+ error: entry.error?.trim() || "Project failed to start"
153
+ });
154
+ continue;
155
+ }
156
+ verdicts.set(projectId, { ok: true });
157
+ }
158
+ return verdicts;
159
+ }
53
160
  //#endregion
54
- export { DEV_PLATFORM_WORKER_TOKEN, FORBIDDEN_WORKER_ENV_KEYS, PROJECT_SERVER_DEPLOY_STATUS, PROJECT_SERVER_FRAMEWORK_NODE_MODULES, PROJECT_SERVER_FRAMEWORK_ROOT, PROJECT_SERVER_HEALTH, PROJECT_SERVER_IMAGE, PROJECT_SERVER_PORT, PROJECT_SERVER_ROOT, RUNTIME_PING_TIMEOUT_MS, WorkerRuntimeConfigError, buildRuntimeEnv, canPingProjectTarget, encodeOrgArtifacts, fetchDeployStatus, formatDockerEnv, mintOrgWorkerToken, parseOrgArtifactsFromEnv, pingProject, pingProjectTarget, resolvePlatformWorkerToken, resolveProjectServerImage, resolveRuntimeLaunch, resolveWorkerPlatformUrl, resolveWorkerRuntimeConfig, rewriteLoopbackHost, rewriteLoopbackUrl, verifyOrgWorkerToken, waitForHealth };
161
+ export { DEV_PLATFORM_WORKER_TOKEN, FORBIDDEN_WORKER_ENV_KEYS, PROJECT_SERVER_CONTROL, PROJECT_SERVER_DEPLOY_STATUS, PROJECT_SERVER_FRAMEWORK_NODE_MODULES, PROJECT_SERVER_FRAMEWORK_ROOT, PROJECT_SERVER_HEALTH, PROJECT_SERVER_IMAGE, PROJECT_SERVER_PORT, PROJECT_SERVER_ROOT, RUNTIME_PING_TIMEOUT_MS, WorkerRuntimeConfigError, buildRuntimeEnv, canPingProjectTarget, classifyDeployHealth, encodeOrgArtifacts, fetchDeployStatus, formatDockerEnv, loadProjectOnRuntime, mintOrgWorkerToken, parseOrgArtifactsFromEnv, pingProject, pingProjectTarget, pingRuntimeHealth, promoteProjectOnRuntime, resolvePlatformWorkerToken, resolveProjectServerImage, resolveRuntimeLaunch, resolveWorkerPlatformUrl, resolveWorkerRuntimeConfig, rewriteLoopbackHost, rewriteLoopbackUrl, unloadProjectOnRuntime, verifyOrgWorkerToken, waitForDeployStatus, waitForHealth };
55
162
 
56
163
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/runtime-constants.ts","../src/ping-project.ts","../src/ping-project-target.ts","../src/deploy-status.ts"],"sourcesContent":["/** Timeout for a single project runtime `/health` probe. */\nexport const RUNTIME_PING_TIMEOUT_MS = 15_000;\n","import { PROJECT_SERVER_HEALTH } from \"./constants\";\nimport type { HostingPlugin } from \"./plugin\";\nimport { RUNTIME_PING_TIMEOUT_MS } from \"./runtime-constants\";\n\nexport type PingProjectOptions = {\n runtimeId?: string | null;\n fetchImpl?: typeof fetch;\n timeoutMs?: number;\n plugin?: Pick<HostingPlugin, \"pingRequestHeaders\">;\n};\n\nexport async function pingProject(\n baseUrl: string,\n options: PingProjectOptions = {},\n): Promise<boolean> {\n const timeoutMs = options.timeoutMs ?? RUNTIME_PING_TIMEOUT_MS;\n const headers = options.plugin?.pingRequestHeaders?.(options.runtimeId ?? null) ?? {};\n\n try {\n const response = await (options.fetchImpl ?? fetch)(\n new URL(PROJECT_SERVER_HEALTH.path, baseUrl),\n {\n signal: AbortSignal.timeout(timeoutMs),\n headers,\n },\n );\n\n return response.ok;\n } catch {\n return false;\n }\n}\n","import type { HostingPlugin } from \"./plugin\";\nimport type { ProjectPingTarget } from \"./runtime\";\nimport { pingProject, type PingProjectOptions } from \"./ping-project\";\n\nexport function canPingProjectTarget(\n target: ProjectPingTarget,\n plugin?: Pick<HostingPlugin, \"canPingTarget\">,\n): target is { baseUrl: string; runtimeId: string | null } {\n if (plugin?.canPingTarget) {\n return plugin.canPingTarget(target);\n }\n\n return !!target.baseUrl;\n}\n\nexport async function pingProjectTarget(\n target: ProjectPingTarget,\n options: PingProjectOptions & {\n plugin?: Pick<HostingPlugin, \"canPingTarget\" | \"pingRequestHeaders\">;\n } = {},\n): Promise<boolean> {\n if (!canPingProjectTarget(target, options.plugin)) {\n return false;\n }\n\n return pingProject(target.baseUrl, {\n ...options,\n runtimeId: target.runtimeId,\n });\n}\n","import { PROJECT_SERVER_DEPLOY_STATUS } from \"./constants\";\nimport { RUNTIME_PING_TIMEOUT_MS } from \"./runtime-constants\";\n\n/** Per-project bootstrap outcome reported by an org runtime machine. */\nexport type ProjectDeployStatus = {\n projectId: string;\n ok: boolean;\n error?: string;\n};\n\nexport type DeployStatusResponse = {\n projects: ProjectDeployStatus[];\n};\n\nexport type FetchDeployStatusOptions = {\n fetchImpl?: typeof fetch;\n timeoutMs?: number;\n};\n\n/**\n * Ask a running org machine which projects bootstrapped and which failed.\n * Best-effort: returns undefined on any error so callers can fall back to\n * treating every project as healthy (preserving pre-existing behavior).\n */\nexport async function fetchDeployStatus(\n baseUrl: string,\n options: FetchDeployStatusOptions = {},\n): Promise<DeployStatusResponse | undefined> {\n const timeoutMs = options.timeoutMs ?? RUNTIME_PING_TIMEOUT_MS;\n\n try {\n const response = await (options.fetchImpl ?? fetch)(\n new URL(PROJECT_SERVER_DEPLOY_STATUS.path, baseUrl),\n { signal: AbortSignal.timeout(timeoutMs) },\n );\n\n if (!response.ok) {\n return undefined;\n }\n\n const data = (await response.json()) as DeployStatusResponse;\n if (!data || !Array.isArray(data.projects)) {\n return undefined;\n }\n\n return data;\n } catch (error) {\n // Best-effort: fall back to promote-all, but log so a consistently\n // unreachable /deploy-status (port/6PN/version skew) is diagnosable rather\n // than silently re-creating the pre-fix behavior.\n console.warn(`[deploy-status] failed to fetch from ${baseUrl}:`, error);\n return undefined;\n }\n}\n"],"mappings":";;;;AACA,MAAa,0BAA0B;;;ACUvC,eAAsB,YACpB,SACA,UAA8B,CAAC,GACb;CAClB,MAAM,YAAY,QAAQ,aAAA;CAC1B,MAAM,UAAU,QAAQ,QAAQ,qBAAqB,QAAQ,aAAa,IAAI,KAAK,CAAC;CAEpF,IAAI;EASF,QAAO,OARiB,QAAQ,aAAa,OAC3C,IAAI,IAAI,sBAAsB,MAAM,OAAO,GAC3C;GACE,QAAQ,YAAY,QAAQ,SAAS;GACrC;EACF,CACF,GAEgB;CAClB,QAAQ;EACN,OAAO;CACT;AACF;;;AC3BA,SAAgB,qBACd,QACA,QACyD;CACzD,IAAI,QAAQ,eACV,OAAO,OAAO,cAAc,MAAM;CAGpC,OAAO,CAAC,CAAC,OAAO;AAClB;AAEA,eAAsB,kBACpB,QACA,UAEI,CAAC,GACa;CAClB,IAAI,CAAC,qBAAqB,QAAQ,QAAQ,MAAM,GAC9C,OAAO;CAGT,OAAO,YAAY,OAAO,SAAS;EACjC,GAAG;EACH,WAAW,OAAO;CACpB,CAAC;AACH;;;;;;;;ACLA,eAAsB,kBACpB,SACA,UAAoC,CAAC,GACM;CAC3C,MAAM,YAAY,QAAQ,aAAA;CAE1B,IAAI;EACF,MAAM,WAAW,OAAO,QAAQ,aAAa,OAC3C,IAAI,IAAI,6BAA6B,MAAM,OAAO,GAClD,EAAE,QAAQ,YAAY,QAAQ,SAAS,EAAE,CAC3C;EAEA,IAAI,CAAC,SAAS,IACZ;EAGF,MAAM,OAAQ,MAAM,SAAS,KAAK;EAClC,IAAI,CAAC,QAAQ,CAAC,MAAM,QAAQ,KAAK,QAAQ,GACvC;EAGF,OAAO;CACT,SAAS,OAAO;EAId,QAAQ,KAAK,wCAAwC,QAAQ,IAAI,KAAK;EACtE;CACF;AACF"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/runtime-constants.ts","../src/runtime-control.ts","../src/ping-project.ts","../src/ping-project-target.ts","../src/deploy-status.ts"],"sourcesContent":["/** Timeout for a single project runtime `/health` probe. */\nexport const RUNTIME_PING_TIMEOUT_MS = 15_000;\n","import { PROJECT_SERVER_PORT } from \"./constants\";\nimport { RUNTIME_PING_TIMEOUT_MS } from \"./runtime-constants\";\n\nexport const PROJECT_SERVER_CONTROL = {\n load: \"/control/projects/load\",\n promote: \"/control/projects/promote\",\n unload: \"/control/projects/unload\",\n port: PROJECT_SERVER_PORT,\n} as const;\n\nexport type LoadProjectOnRuntimeInput = {\n projectId: string;\n artifactId: string;\n artifactVersion: number;\n storageKey: string;\n activate?: boolean;\n};\n\nexport type RuntimeControlOptions = {\n fetchImpl?: typeof fetch;\n timeoutMs?: number;\n /** Bearer token — typically the org's WORKER_INTERNAL_TOKEN. */\n workerToken?: string;\n};\n\nasync function postControl<T>(\n baseUrl: string,\n path: string,\n body: unknown,\n options: RuntimeControlOptions = {},\n): Promise<T> {\n const timeoutMs = options.timeoutMs ?? RUNTIME_PING_TIMEOUT_MS;\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n };\n if (options.workerToken) {\n headers.authorization = `Bearer ${options.workerToken}`;\n }\n\n const response = await (options.fetchImpl ?? fetch)(new URL(path, baseUrl), {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(timeoutMs),\n });\n\n const data = (await response.json().catch(() => ({}))) as T & { ok?: boolean; error?: string };\n if (!response.ok || data.ok === false) {\n throw new Error(\n typeof data.error === \"string\"\n ? data.error\n : `Runtime control ${path} failed (${response.status})`,\n );\n }\n\n return data;\n}\n\nexport async function loadProjectOnRuntime(\n baseUrl: string,\n input: LoadProjectOnRuntimeInput,\n options: RuntimeControlOptions = {},\n): Promise<void> {\n await postControl(baseUrl, PROJECT_SERVER_CONTROL.load, input, options);\n}\n\nexport async function promoteProjectOnRuntime(\n baseUrl: string,\n input: { projectId: string; artifactId: string },\n options: RuntimeControlOptions = {},\n): Promise<void> {\n await postControl(baseUrl, PROJECT_SERVER_CONTROL.promote, input, options);\n}\n\nexport async function unloadProjectOnRuntime(\n baseUrl: string,\n input: { projectId: string; artifactId: string; force?: boolean },\n options: RuntimeControlOptions = {},\n): Promise<void> {\n await postControl(baseUrl, PROJECT_SERVER_CONTROL.unload, input, options);\n}\n\n/** True when the org runtime answers /health. */\nexport async function pingRuntimeHealth(\n baseUrl: string,\n options: RuntimeControlOptions = {},\n): Promise<boolean> {\n const timeoutMs = options.timeoutMs ?? RUNTIME_PING_TIMEOUT_MS;\n try {\n const response = await (options.fetchImpl ?? fetch)(new URL(\"/health\", baseUrl), {\n signal: AbortSignal.timeout(timeoutMs),\n });\n return response.ok;\n } catch {\n return false;\n }\n}\n","import { PROJECT_SERVER_HEALTH } from \"./constants\";\nimport type { HostingPlugin } from \"./plugin\";\nimport { RUNTIME_PING_TIMEOUT_MS } from \"./runtime-constants\";\n\nexport type PingProjectOptions = {\n runtimeId?: string | null;\n fetchImpl?: typeof fetch;\n timeoutMs?: number;\n plugin?: Pick<HostingPlugin, \"pingRequestHeaders\">;\n};\n\nexport async function pingProject(\n baseUrl: string,\n options: PingProjectOptions = {},\n): Promise<boolean> {\n const timeoutMs = options.timeoutMs ?? RUNTIME_PING_TIMEOUT_MS;\n const headers = options.plugin?.pingRequestHeaders?.(options.runtimeId ?? null) ?? {};\n\n try {\n const response = await (options.fetchImpl ?? fetch)(\n new URL(PROJECT_SERVER_HEALTH.path, baseUrl),\n {\n signal: AbortSignal.timeout(timeoutMs),\n headers,\n },\n );\n\n return response.ok;\n } catch {\n return false;\n }\n}\n","import type { HostingPlugin } from \"./plugin\";\nimport type { ProjectPingTarget } from \"./runtime\";\nimport { pingProject, type PingProjectOptions } from \"./ping-project\";\n\nexport function canPingProjectTarget(\n target: ProjectPingTarget,\n plugin?: Pick<HostingPlugin, \"canPingTarget\">,\n): target is { baseUrl: string; runtimeId: string | null } {\n if (plugin?.canPingTarget) {\n return plugin.canPingTarget(target);\n }\n\n return !!target.baseUrl;\n}\n\nexport async function pingProjectTarget(\n target: ProjectPingTarget,\n options: PingProjectOptions & {\n plugin?: Pick<HostingPlugin, \"canPingTarget\" | \"pingRequestHeaders\">;\n } = {},\n): Promise<boolean> {\n if (!canPingProjectTarget(target, options.plugin)) {\n return false;\n }\n\n return pingProject(target.baseUrl, {\n ...options,\n runtimeId: target.runtimeId,\n });\n}\n","import { PROJECT_SERVER_DEPLOY_STATUS } from \"./constants\";\nimport { RUNTIME_PING_TIMEOUT_MS } from \"./runtime-constants\";\n\nconst DEFAULT_WAIT_TIMEOUT_MS = 120_000;\nconst DEFAULT_WAIT_INTERVAL_MS = 500;\nconst REQUEST_TIMEOUT_MS = 2_000;\n\n/** Per-project bootstrap outcome reported by an org runtime machine. */\nexport type ProjectDeployStatus = {\n projectId: string;\n ok: boolean;\n error?: string;\n};\n\nexport type VersionDeployStatus = {\n projectId: string;\n artifactId: string;\n state: \"active\" | \"resident\" | \"indexed\";\n ok: boolean;\n activeJobCount?: number;\n error?: string;\n};\n\nexport type DeployStatusResponse = {\n projects: ProjectDeployStatus[];\n versions?: VersionDeployStatus[];\n};\n\nexport type FetchDeployStatusOptions = {\n fetchImpl?: typeof fetch;\n timeoutMs?: number;\n};\n\nexport type WaitForDeployStatusOptions = {\n fetchImpl?: typeof fetch;\n timeoutMs?: number;\n intervalMs?: number;\n};\n\n/**\n * Ask a running org machine which projects bootstrapped and which failed.\n * Returns undefined when the endpoint is unreachable, non-OK, times out, or\n * returns an unusable payload — callers must treat that as non-affirmative\n * health for every pending project (never promote on silence).\n */\nexport async function fetchDeployStatus(\n baseUrl: string,\n options: FetchDeployStatusOptions = {},\n): Promise<DeployStatusResponse | undefined> {\n const timeoutMs = options.timeoutMs ?? RUNTIME_PING_TIMEOUT_MS;\n\n try {\n const response = await (options.fetchImpl ?? fetch)(\n new URL(PROJECT_SERVER_DEPLOY_STATUS.path, baseUrl),\n { signal: AbortSignal.timeout(timeoutMs) },\n );\n\n if (!response.ok) {\n return undefined;\n }\n\n const data = (await response.json()) as DeployStatusResponse;\n if (!data || !Array.isArray(data.projects)) {\n return undefined;\n }\n\n return data;\n } catch (error) {\n console.warn(`[deploy-status] failed to fetch from ${baseUrl}:`, error);\n return undefined;\n }\n}\n\n/**\n * Poll `/deploy-status` until the real worker returns a usable payload or the\n * deadline expires. Retries only transient unavailability (network errors,\n * non-2xx including early-health 404, timeouts, malformed bodies). Any valid\n * `{ projects: [...] }` response — including empty or explicit failures — is\n * terminal so genuine bootstrap errors surface immediately.\n */\nexport async function waitForDeployStatus(\n baseUrl: string,\n options: WaitForDeployStatusOptions = {},\n): Promise<DeployStatusResponse | undefined> {\n const timeoutMs = options.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS;\n const intervalMs = options.intervalMs ?? DEFAULT_WAIT_INTERVAL_MS;\n const deadline = Date.now() + timeoutMs;\n\n while (Date.now() < deadline) {\n const remainingMs = deadline - Date.now();\n const requestTimeoutMs = Math.min(REQUEST_TIMEOUT_MS, Math.max(1, remainingMs));\n const status = await fetchDeployStatus(baseUrl, {\n fetchImpl: options.fetchImpl,\n timeoutMs: requestTimeoutMs,\n });\n if (status) {\n return status;\n }\n\n if (Date.now() + intervalMs >= deadline) {\n break;\n }\n await sleep(intervalMs);\n }\n\n return undefined;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n\nexport type DeployHealthVerdict = { ok: true } | { ok: false; error: string };\n\n/**\n * Require an explicit `{ projectId, ok: true }` entry for each candidate.\n * Missing entries, `ok: false`, empty `projects`, or an unavailable status\n * response all fail closed for that project.\n */\nexport function classifyDeployHealth(input: {\n projectIds: string[];\n deployStatus: DeployStatusResponse | undefined;\n}): Map<string, DeployHealthVerdict> {\n const verdicts = new Map<string, DeployHealthVerdict>();\n\n if (!input.deployStatus) {\n for (const projectId of input.projectIds) {\n verdicts.set(projectId, {\n ok: false,\n error: \"Deploy health status unavailable\",\n });\n }\n return verdicts;\n }\n\n const byId = new Map(\n input.deployStatus.projects\n .filter((entry) => typeof entry?.projectId === \"string\" && entry.projectId.length > 0)\n .map((entry) => [entry.projectId, entry]),\n );\n\n for (const projectId of input.projectIds) {\n const entry = byId.get(projectId);\n if (!entry) {\n verdicts.set(projectId, {\n ok: false,\n error: \"Deploy health status omitted project\",\n });\n continue;\n }\n if (entry.ok !== true) {\n verdicts.set(projectId, {\n ok: false,\n error: entry.error?.trim() || \"Project failed to start\",\n });\n continue;\n }\n verdicts.set(projectId, { ok: true });\n }\n\n return verdicts;\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import { a as ProjectRuntimeDatabase, c as ProjectRuntimeInput, d as encodeOrgArtifacts, f as parseOrgArtifactsFromEnv, i as ProjectRuntime, l as ProjectRuntimeReconcileInput, n as OrgProjectDeploy, o as ProjectRuntimeDestroyInput, r as ProjectPingTarget, s as ProjectRuntimeHosting, t as OrgArtifactSpec, u as ProjectRuntimeResult } from "./runtime-B0jF0oMv.cjs";
1
+ import { a as ProjectRuntimeDatabase, c as ProjectRuntimeInput, i as ProjectRuntime, l as ProjectRuntimeReconcileInput, m as parseOrgArtifactsFromEnv, n as OrgProjectDeploy, o as ProjectRuntimeDestroyInput, p as encodeOrgArtifacts, r as ProjectPingTarget, s as ProjectRuntimeHosting, t as OrgArtifactSpec, u as ProjectRuntimeResult } from "./runtime-Cp906goK.cjs";
2
2
  export { type OrgArtifactSpec, type OrgProjectDeploy, type ProjectPingTarget, type ProjectRuntime, type ProjectRuntimeDatabase, type ProjectRuntimeDestroyInput, type ProjectRuntimeHosting, type ProjectRuntimeInput, type ProjectRuntimeReconcileInput, type ProjectRuntimeResult, encodeOrgArtifacts, parseOrgArtifactsFromEnv };
@@ -1,2 +1,2 @@
1
- import { a as ProjectRuntimeDatabase, c as ProjectRuntimeInput, d as encodeOrgArtifacts, f as parseOrgArtifactsFromEnv, i as ProjectRuntime, l as ProjectRuntimeReconcileInput, n as OrgProjectDeploy, o as ProjectRuntimeDestroyInput, r as ProjectPingTarget, s as ProjectRuntimeHosting, t as OrgArtifactSpec, u as ProjectRuntimeResult } from "./runtime-B0jF0oMv.mjs";
1
+ import { a as ProjectRuntimeDatabase, c as ProjectRuntimeInput, i as ProjectRuntime, l as ProjectRuntimeReconcileInput, m as parseOrgArtifactsFromEnv, n as OrgProjectDeploy, o as ProjectRuntimeDestroyInput, p as encodeOrgArtifacts, r as ProjectPingTarget, s as ProjectRuntimeHosting, t as OrgArtifactSpec, u as ProjectRuntimeResult } from "./runtime-Cp906goK.mjs";
2
2
  export { type OrgArtifactSpec, type OrgProjectDeploy, type ProjectPingTarget, type ProjectRuntime, type ProjectRuntimeDatabase, type ProjectRuntimeDestroyInput, type ProjectRuntimeHosting, type ProjectRuntimeInput, type ProjectRuntimeReconcileInput, type ProjectRuntimeResult, encodeOrgArtifacts, parseOrgArtifactsFromEnv };
@@ -1,4 +1,4 @@
1
- import { i as ProjectRuntime, r as ProjectPingTarget } from "./runtime-B0jF0oMv.cjs";
1
+ import { i as ProjectRuntime, r as ProjectPingTarget } from "./runtime-Cp906goK.cjs";
2
2
 
3
3
  //#region src/worker-runtime-config.d.ts
4
4
  /** Resolved platform ↔ project-server worker auth contract. */
@@ -41,4 +41,4 @@ type HostingPlugin = {
41
41
  };
42
42
  //#endregion
43
43
  export { WorkerRuntimeConfig as a, OrganizationHostingResult as i, HostingPlugin as n, WorkerRuntimeConfigError as o, OrganizationHostingInput as r, resolveWorkerRuntimeConfig as s, HostingOptions as t };
44
- //# sourceMappingURL=plugin-is5oPt6x.d.cts.map
44
+ //# sourceMappingURL=plugin-4cuXkf42.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-is5oPt6x.d.cts","names":[],"sources":["../src/worker-runtime-config.ts","../src/plugin.ts"],"mappings":";;;;KAGY,mBAAA;yEAEV,WAAA,UAF6B;EAI7B,WAAW;AAAA;AAAA,cAGA,wBAAA,SAAiC,KAAK;EAAA,SACxC,OAAA;cAEG,OAAA;AAAA;;iBAWE,0BAAA,CACd,MAAA,GAAQ,MAAA,CAAO,UAAA,GACd,mBAAmB;;;KCvBV,cAAA;EACV,KAAA;EACA,GAAA,GAAM,MAAA,CAAO,UAAA;EACb,SAAA,UAAmB,KAAK;EACxB,eAAA;AAAA;AAAA,KAGU,wBAAA;EACV,cAAA;EACA,IAAI;AAAA;AAAA,KAGM,yBAAA;EACV,OAAO;AAAA;AAAA,KAGG,aAAA;EACV,IAAA,UDP6B;ECS7B,aAAA,EAAe,mBAAA;EACf,aAAA,IAAiB,cAAA;EACjB,qBAAA,EAAuB,KAAA,EAAO,wBAAA,GAA2B,OAAA,CAAQ,yBAAA;EACjE,uBAAA,EAAyB,MAAA,EAAQ,yBAAA,GAA4B,OAAA;EAC7D,aAAA,EACE,MAAA,EAAQ,iBAAA,GACP,MAAA;IAAY,OAAA;IAAiB,SAAA;EAAA;EAChC,kBAAA,EAAoB,SAAA,kBAA2B,MAAA;AAAA"}
1
+ {"version":3,"file":"plugin-4cuXkf42.d.cts","names":[],"sources":["../src/worker-runtime-config.ts","../src/plugin.ts"],"mappings":";;;;KAGY,mBAAA;yEAEV,WAAA,UAF6B;EAI7B,WAAW;AAAA;AAAA,cAGA,wBAAA,SAAiC,KAAK;EAAA,SACxC,OAAA;cAEG,OAAA;AAAA;;iBAWE,0BAAA,CACd,MAAA,GAAQ,MAAA,CAAO,UAAA,GACd,mBAAmB;;;KCvBV,cAAA;EACV,KAAA;EACA,GAAA,GAAM,MAAA,CAAO,UAAA;EACb,SAAA,UAAmB,KAAK;EACxB,eAAA;AAAA;AAAA,KAGU,wBAAA;EACV,cAAA;EACA,IAAI;AAAA;AAAA,KAGM,yBAAA;EACV,OAAO;AAAA;AAAA,KAGG,aAAA;EACV,IAAA,UDP6B;ECS7B,aAAA,EAAe,mBAAA;EACf,aAAA,IAAiB,cAAA;EACjB,qBAAA,EAAuB,KAAA,EAAO,wBAAA,GAA2B,OAAA,CAAQ,yBAAA;EACjE,uBAAA,EAAyB,MAAA,EAAQ,yBAAA,GAA4B,OAAA;EAC7D,aAAA,EACE,MAAA,EAAQ,iBAAA,GACP,MAAA;IAAY,OAAA;IAAiB,SAAA;EAAA;EAChC,kBAAA,EAAoB,SAAA,kBAA2B,MAAA;AAAA"}
@@ -1,4 +1,4 @@
1
- import { i as ProjectRuntime, r as ProjectPingTarget } from "./runtime-B0jF0oMv.mjs";
1
+ import { i as ProjectRuntime, r as ProjectPingTarget } from "./runtime-Cp906goK.mjs";
2
2
 
3
3
  //#region src/worker-runtime-config.d.ts
4
4
  /** Resolved platform ↔ project-server worker auth contract. */
@@ -41,4 +41,4 @@ type HostingPlugin = {
41
41
  };
42
42
  //#endregion
43
43
  export { WorkerRuntimeConfig as a, OrganizationHostingResult as i, HostingPlugin as n, WorkerRuntimeConfigError as o, OrganizationHostingInput as r, resolveWorkerRuntimeConfig as s, HostingOptions as t };
44
- //# sourceMappingURL=plugin-qEl5Sx4m.d.mts.map
44
+ //# sourceMappingURL=plugin-eiAXqE7P.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-qEl5Sx4m.d.mts","names":[],"sources":["../src/worker-runtime-config.ts","../src/plugin.ts"],"mappings":";;;;KAGY,mBAAA;yEAEV,WAAA,UAF6B;EAI7B,WAAW;AAAA;AAAA,cAGA,wBAAA,SAAiC,KAAK;EAAA,SACxC,OAAA;cAEG,OAAA;AAAA;;iBAWE,0BAAA,CACd,MAAA,GAAQ,MAAA,CAAO,UAAA,GACd,mBAAmB;;;KCvBV,cAAA;EACV,KAAA;EACA,GAAA,GAAM,MAAA,CAAO,UAAA;EACb,SAAA,UAAmB,KAAK;EACxB,eAAA;AAAA;AAAA,KAGU,wBAAA;EACV,cAAA;EACA,IAAI;AAAA;AAAA,KAGM,yBAAA;EACV,OAAO;AAAA;AAAA,KAGG,aAAA;EACV,IAAA,UDP6B;ECS7B,aAAA,EAAe,mBAAA;EACf,aAAA,IAAiB,cAAA;EACjB,qBAAA,EAAuB,KAAA,EAAO,wBAAA,GAA2B,OAAA,CAAQ,yBAAA;EACjE,uBAAA,EAAyB,MAAA,EAAQ,yBAAA,GAA4B,OAAA;EAC7D,aAAA,EACE,MAAA,EAAQ,iBAAA,GACP,MAAA;IAAY,OAAA;IAAiB,SAAA;EAAA;EAChC,kBAAA,EAAoB,SAAA,kBAA2B,MAAA;AAAA"}
1
+ {"version":3,"file":"plugin-eiAXqE7P.d.mts","names":[],"sources":["../src/worker-runtime-config.ts","../src/plugin.ts"],"mappings":";;;;KAGY,mBAAA;yEAEV,WAAA,UAF6B;EAI7B,WAAW;AAAA;AAAA,cAGA,wBAAA,SAAiC,KAAK;EAAA,SACxC,OAAA;cAEG,OAAA;AAAA;;iBAWE,0BAAA,CACd,MAAA,GAAQ,MAAA,CAAO,UAAA,GACd,mBAAmB;;;KCvBV,cAAA;EACV,KAAA;EACA,GAAA,GAAM,MAAA,CAAO,UAAA;EACb,SAAA,UAAmB,KAAK;EACxB,eAAA;AAAA;AAAA,KAGU,wBAAA;EACV,cAAA;EACA,IAAI;AAAA;AAAA,KAGM,yBAAA;EACV,OAAO;AAAA;AAAA,KAGG,aAAA;EACV,IAAA,UDP6B;ECS7B,aAAA,EAAe,mBAAA;EACf,aAAA,IAAiB,cAAA;EACjB,qBAAA,EAAuB,KAAA,EAAO,wBAAA,GAA2B,OAAA,CAAQ,yBAAA;EACjE,uBAAA,EAAyB,MAAA,EAAQ,yBAAA,GAA4B,OAAA;EAC7D,aAAA,EACE,MAAA,EAAQ,iBAAA,GACP,MAAA;IAAY,OAAA;IAAiB,SAAA;EAAA;EAChC,kBAAA,EAAoB,SAAA,kBAA2B,MAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-BmfsHLrk.mjs","names":[],"sources":["../src/runtime.ts"],"sourcesContent":["export type ProjectRuntimeDatabase = {\n host: string;\n port: number;\n database: string;\n user: string;\n password: string;\n searchPath: string;\n organizationId: string;\n /** @deprecated Per-project workers only — org workers omit this. */\n projectId?: string;\n};\n\nexport type OrgProjectDeploy = {\n projectId: string;\n name: string;\n artifactId: string;\n artifactStorageKey: string;\n artifactVersion: number;\n};\n\nexport type OrgArtifactSpec = {\n projectId: string;\n artifactStorageKey: string;\n artifactId: string;\n /** Monotonic per-project artifact version from the platform database. */\n artifactVersion: number;\n};\n\nexport type ProjectRuntimeHosting = {\n appName: string;\n};\n\nexport type ProjectRuntimeInput = {\n organizationId: string;\n projects: OrgProjectDeploy[];\n database: ProjectRuntimeDatabase;\n hosting?: ProjectRuntimeHosting;\n /** Extra env merged into the project server launch spec (e.g. from an llmProxy plugin). */\n env?: Record<string, string>;\n /** Previous runtime to retire after a successful cutover. */\n previousRuntimeId?: string | null;\n};\n\n/** Builds base64 `KEYSTROKE_ORG_ARTIFACTS` payload for org worker containers. */\nexport function encodeOrgArtifacts(artifacts: OrgArtifactSpec[]): string {\n return Buffer.from(JSON.stringify(artifacts)).toString(\"base64\");\n}\n\nexport function parseOrgArtifactsFromEnv(\n env: NodeJS.ProcessEnv = process.env,\n): OrgArtifactSpec[] | undefined {\n const raw = env.KEYSTROKE_ORG_ARTIFACTS?.trim();\n if (!raw) {\n return undefined;\n }\n\n const parsed = JSON.parse(Buffer.from(raw, \"base64\").toString(\"utf8\")) as unknown;\n if (!Array.isArray(parsed)) {\n throw new Error(\"KEYSTROKE_ORG_ARTIFACTS must decode to a JSON array\");\n }\n\n return parsed.map((entry) => {\n if (\n !entry ||\n typeof entry !== \"object\" ||\n typeof (entry as OrgArtifactSpec).projectId !== \"string\" ||\n typeof (entry as OrgArtifactSpec).artifactStorageKey !== \"string\" ||\n typeof (entry as OrgArtifactSpec).artifactId !== \"string\" ||\n typeof (entry as OrgArtifactSpec).artifactVersion !== \"number\"\n ) {\n throw new Error(\n \"KEYSTROKE_ORG_ARTIFACTS entries require projectId, artifactStorageKey, artifactId, and artifactVersion\",\n );\n }\n\n return entry as OrgArtifactSpec;\n });\n}\n\nexport type ProjectRuntimeDestroyInput = {\n runtimeId: string;\n hosting?: ProjectRuntimeHosting;\n};\n\nexport type ProjectRuntimeReconcileInput = {\n organizationId: string;\n /** The runtime to keep — every other runtime belonging to this org should be destroyed. */\n keepRuntimeId: string;\n hosting?: ProjectRuntimeHosting;\n};\n\nexport type ProjectRuntimeResult = {\n runtimeId: string;\n baseUrl: string;\n};\n\nexport type ProjectRuntime = {\n start(input: ProjectRuntimeInput): Promise<ProjectRuntimeResult>;\n destroy?(input: ProjectRuntimeDestroyInput): Promise<void>;\n /**\n * Sweep away every runtime for this org except `keepRuntimeId`, retrying\n * transient failures instead of giving up after one attempt. This catches\n * orphans that a single {@link destroy} call missed (e.g. a previous deploy's\n * destroy failed silently), not just the one runtime recorded as \"previous.\"\n */\n reconcile?(input: ProjectRuntimeReconcileInput): Promise<void>;\n};\n\nexport type ProjectPingTarget = {\n baseUrl: string | null;\n runtimeId: string | null;\n};\n"],"mappings":";;AA4CA,SAAgB,mBAAmB,WAAsC;CACvE,OAAO,OAAO,KAAK,KAAK,UAAU,SAAS,CAAC,EAAE,SAAS,QAAQ;AACjE;AAEA,SAAgB,yBACd,MAAyB,QAAQ,KACF;CAC/B,MAAM,MAAM,IAAI,yBAAyB,KAAK;CAC9C,IAAI,CAAC,KACH;CAGF,MAAM,SAAS,KAAK,MAAM,OAAO,KAAK,KAAK,QAAQ,EAAE,SAAS,MAAM,CAAC;CACrE,IAAI,CAAC,MAAM,QAAQ,MAAM,GACvB,MAAM,IAAI,MAAM,qDAAqD;CAGvE,OAAO,OAAO,KAAK,UAAU;EAC3B,IACE,CAAC,SACD,OAAO,UAAU,YACjB,OAAQ,MAA0B,cAAc,YAChD,OAAQ,MAA0B,uBAAuB,YACzD,OAAQ,MAA0B,eAAe,YACjD,OAAQ,MAA0B,oBAAoB,UAEtD,MAAM,IAAI,MACR,wGACF;EAGF,OAAO;CACT,CAAC;AACH"}
1
+ {"version":3,"file":"runtime-BmfsHLrk.mjs","names":[],"sources":["../src/runtime.ts"],"sourcesContent":["export type ProjectRuntimeDatabase = {\n host: string;\n port: number;\n database: string;\n user: string;\n password: string;\n searchPath: string;\n organizationId: string;\n /** @deprecated Per-project workers only — org workers omit this. */\n projectId?: string;\n};\n\nexport type OrgProjectDeploy = {\n projectId: string;\n name: string;\n artifactId: string;\n artifactStorageKey: string;\n artifactVersion: number;\n /** Require an affirmative artifact-specific health record before promotion. */\n requiresArtifactHealth?: boolean;\n};\n\nexport type OrgArtifactSpec = {\n projectId: string;\n artifactStorageKey: string;\n artifactId: string;\n /** Monotonic per-project artifact version from the platform database. */\n artifactVersion: number;\n};\n\nexport type ProjectRuntimeHosting = {\n appName: string;\n};\n\nexport type ProjectRuntimeInput = {\n organizationId: string;\n projects: OrgProjectDeploy[];\n database: ProjectRuntimeDatabase;\n hosting?: ProjectRuntimeHosting;\n /** Extra env merged into the project server launch spec (e.g. from an llmProxy plugin). */\n env?: Record<string, string>;\n /** Previous runtime to retire after a successful cutover. */\n previousRuntimeId?: string | null;\n};\n\n/** Builds base64 `KEYSTROKE_ORG_ARTIFACTS` payload for org worker containers. */\nexport function encodeOrgArtifacts(artifacts: OrgArtifactSpec[]): string {\n return Buffer.from(JSON.stringify(artifacts)).toString(\"base64\");\n}\n\nexport function parseOrgArtifactsFromEnv(\n env: NodeJS.ProcessEnv = process.env,\n): OrgArtifactSpec[] | undefined {\n const raw = env.KEYSTROKE_ORG_ARTIFACTS?.trim();\n if (!raw) {\n return undefined;\n }\n\n const parsed = JSON.parse(Buffer.from(raw, \"base64\").toString(\"utf8\")) as unknown;\n if (!Array.isArray(parsed)) {\n throw new Error(\"KEYSTROKE_ORG_ARTIFACTS must decode to a JSON array\");\n }\n\n return parsed.map((entry) => {\n if (\n !entry ||\n typeof entry !== \"object\" ||\n typeof (entry as OrgArtifactSpec).projectId !== \"string\" ||\n typeof (entry as OrgArtifactSpec).artifactStorageKey !== \"string\" ||\n typeof (entry as OrgArtifactSpec).artifactId !== \"string\" ||\n typeof (entry as OrgArtifactSpec).artifactVersion !== \"number\"\n ) {\n throw new Error(\n \"KEYSTROKE_ORG_ARTIFACTS entries require projectId, artifactStorageKey, artifactId, and artifactVersion\",\n );\n }\n\n return entry as OrgArtifactSpec;\n });\n}\n\nexport type ProjectRuntimeDestroyInput = {\n runtimeId: string;\n hosting?: ProjectRuntimeHosting;\n};\n\nexport type ProjectRuntimeReconcileInput = {\n organizationId: string;\n /** The runtime to keep — every other runtime belonging to this org should be destroyed. */\n keepRuntimeId: string;\n hosting?: ProjectRuntimeHosting;\n};\n\nexport type ProjectRuntimeResult = {\n runtimeId: string;\n baseUrl: string;\n};\n\nexport type ProjectRuntimeSleepInput = {\n runtimeId: string;\n hosting?: ProjectRuntimeHosting;\n};\n\nexport type ProjectRuntimeWakeInput = {\n runtimeId: string;\n hosting?: ProjectRuntimeHosting;\n};\n\nexport type ProjectRuntime = {\n start(input: ProjectRuntimeInput): Promise<ProjectRuntimeResult>;\n destroy?(input: ProjectRuntimeDestroyInput): Promise<void>;\n /**\n * Sweep away every runtime for this org except `keepRuntimeId`, retrying\n * transient failures instead of giving up after one attempt. This catches\n * orphans that a single {@link destroy} call missed (e.g. a previous deploy's\n * destroy failed silently), not just the one runtime recorded as \"previous.\"\n */\n reconcile?(input: ProjectRuntimeReconcileInput): Promise<void>;\n /**\n * Put an existing runtime to sleep without destroying it (e.g. Docker stop,\n * Fly suspend). Provider-specific; omit when unsupported.\n */\n sleep?(input: ProjectRuntimeSleepInput): Promise<void>;\n /**\n * Resume a sleeping runtime. May return a fresh {@link ProjectRuntimeResult}\n * when the base URL can change (e.g. Docker host port remap).\n */\n wake?(input: ProjectRuntimeWakeInput): Promise<ProjectRuntimeResult | void>;\n};\n\nexport type ProjectPingTarget = {\n baseUrl: string | null;\n runtimeId: string | null;\n};\n"],"mappings":";;AA8CA,SAAgB,mBAAmB,WAAsC;CACvE,OAAO,OAAO,KAAK,KAAK,UAAU,SAAS,CAAC,EAAE,SAAS,QAAQ;AACjE;AAEA,SAAgB,yBACd,MAAyB,QAAQ,KACF;CAC/B,MAAM,MAAM,IAAI,yBAAyB,KAAK;CAC9C,IAAI,CAAC,KACH;CAGF,MAAM,SAAS,KAAK,MAAM,OAAO,KAAK,KAAK,QAAQ,EAAE,SAAS,MAAM,CAAC;CACrE,IAAI,CAAC,MAAM,QAAQ,MAAM,GACvB,MAAM,IAAI,MAAM,qDAAqD;CAGvE,OAAO,OAAO,KAAK,UAAU;EAC3B,IACE,CAAC,SACD,OAAO,UAAU,YACjB,OAAQ,MAA0B,cAAc,YAChD,OAAQ,MAA0B,uBAAuB,YACzD,OAAQ,MAA0B,eAAe,YACjD,OAAQ,MAA0B,oBAAoB,UAEtD,MAAM,IAAI,MACR,wGACF;EAGF,OAAO;CACT,CAAC;AACH"}
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-CV7pSfMl.cjs","names":[],"sources":["../src/runtime.ts"],"sourcesContent":["export type ProjectRuntimeDatabase = {\n host: string;\n port: number;\n database: string;\n user: string;\n password: string;\n searchPath: string;\n organizationId: string;\n /** @deprecated Per-project workers only — org workers omit this. */\n projectId?: string;\n};\n\nexport type OrgProjectDeploy = {\n projectId: string;\n name: string;\n artifactId: string;\n artifactStorageKey: string;\n artifactVersion: number;\n};\n\nexport type OrgArtifactSpec = {\n projectId: string;\n artifactStorageKey: string;\n artifactId: string;\n /** Monotonic per-project artifact version from the platform database. */\n artifactVersion: number;\n};\n\nexport type ProjectRuntimeHosting = {\n appName: string;\n};\n\nexport type ProjectRuntimeInput = {\n organizationId: string;\n projects: OrgProjectDeploy[];\n database: ProjectRuntimeDatabase;\n hosting?: ProjectRuntimeHosting;\n /** Extra env merged into the project server launch spec (e.g. from an llmProxy plugin). */\n env?: Record<string, string>;\n /** Previous runtime to retire after a successful cutover. */\n previousRuntimeId?: string | null;\n};\n\n/** Builds base64 `KEYSTROKE_ORG_ARTIFACTS` payload for org worker containers. */\nexport function encodeOrgArtifacts(artifacts: OrgArtifactSpec[]): string {\n return Buffer.from(JSON.stringify(artifacts)).toString(\"base64\");\n}\n\nexport function parseOrgArtifactsFromEnv(\n env: NodeJS.ProcessEnv = process.env,\n): OrgArtifactSpec[] | undefined {\n const raw = env.KEYSTROKE_ORG_ARTIFACTS?.trim();\n if (!raw) {\n return undefined;\n }\n\n const parsed = JSON.parse(Buffer.from(raw, \"base64\").toString(\"utf8\")) as unknown;\n if (!Array.isArray(parsed)) {\n throw new Error(\"KEYSTROKE_ORG_ARTIFACTS must decode to a JSON array\");\n }\n\n return parsed.map((entry) => {\n if (\n !entry ||\n typeof entry !== \"object\" ||\n typeof (entry as OrgArtifactSpec).projectId !== \"string\" ||\n typeof (entry as OrgArtifactSpec).artifactStorageKey !== \"string\" ||\n typeof (entry as OrgArtifactSpec).artifactId !== \"string\" ||\n typeof (entry as OrgArtifactSpec).artifactVersion !== \"number\"\n ) {\n throw new Error(\n \"KEYSTROKE_ORG_ARTIFACTS entries require projectId, artifactStorageKey, artifactId, and artifactVersion\",\n );\n }\n\n return entry as OrgArtifactSpec;\n });\n}\n\nexport type ProjectRuntimeDestroyInput = {\n runtimeId: string;\n hosting?: ProjectRuntimeHosting;\n};\n\nexport type ProjectRuntimeReconcileInput = {\n organizationId: string;\n /** The runtime to keep — every other runtime belonging to this org should be destroyed. */\n keepRuntimeId: string;\n hosting?: ProjectRuntimeHosting;\n};\n\nexport type ProjectRuntimeResult = {\n runtimeId: string;\n baseUrl: string;\n};\n\nexport type ProjectRuntime = {\n start(input: ProjectRuntimeInput): Promise<ProjectRuntimeResult>;\n destroy?(input: ProjectRuntimeDestroyInput): Promise<void>;\n /**\n * Sweep away every runtime for this org except `keepRuntimeId`, retrying\n * transient failures instead of giving up after one attempt. This catches\n * orphans that a single {@link destroy} call missed (e.g. a previous deploy's\n * destroy failed silently), not just the one runtime recorded as \"previous.\"\n */\n reconcile?(input: ProjectRuntimeReconcileInput): Promise<void>;\n};\n\nexport type ProjectPingTarget = {\n baseUrl: string | null;\n runtimeId: string | null;\n};\n"],"mappings":";;AA4CA,SAAgB,mBAAmB,WAAsC;CACvE,OAAO,OAAO,KAAK,KAAK,UAAU,SAAS,CAAC,EAAE,SAAS,QAAQ;AACjE;AAEA,SAAgB,yBACd,MAAyB,QAAQ,KACF;CAC/B,MAAM,MAAM,IAAI,yBAAyB,KAAK;CAC9C,IAAI,CAAC,KACH;CAGF,MAAM,SAAS,KAAK,MAAM,OAAO,KAAK,KAAK,QAAQ,EAAE,SAAS,MAAM,CAAC;CACrE,IAAI,CAAC,MAAM,QAAQ,MAAM,GACvB,MAAM,IAAI,MAAM,qDAAqD;CAGvE,OAAO,OAAO,KAAK,UAAU;EAC3B,IACE,CAAC,SACD,OAAO,UAAU,YACjB,OAAQ,MAA0B,cAAc,YAChD,OAAQ,MAA0B,uBAAuB,YACzD,OAAQ,MAA0B,eAAe,YACjD,OAAQ,MAA0B,oBAAoB,UAEtD,MAAM,IAAI,MACR,wGACF;EAGF,OAAO;CACT,CAAC;AACH"}
1
+ {"version":3,"file":"runtime-CV7pSfMl.cjs","names":[],"sources":["../src/runtime.ts"],"sourcesContent":["export type ProjectRuntimeDatabase = {\n host: string;\n port: number;\n database: string;\n user: string;\n password: string;\n searchPath: string;\n organizationId: string;\n /** @deprecated Per-project workers only — org workers omit this. */\n projectId?: string;\n};\n\nexport type OrgProjectDeploy = {\n projectId: string;\n name: string;\n artifactId: string;\n artifactStorageKey: string;\n artifactVersion: number;\n /** Require an affirmative artifact-specific health record before promotion. */\n requiresArtifactHealth?: boolean;\n};\n\nexport type OrgArtifactSpec = {\n projectId: string;\n artifactStorageKey: string;\n artifactId: string;\n /** Monotonic per-project artifact version from the platform database. */\n artifactVersion: number;\n};\n\nexport type ProjectRuntimeHosting = {\n appName: string;\n};\n\nexport type ProjectRuntimeInput = {\n organizationId: string;\n projects: OrgProjectDeploy[];\n database: ProjectRuntimeDatabase;\n hosting?: ProjectRuntimeHosting;\n /** Extra env merged into the project server launch spec (e.g. from an llmProxy plugin). */\n env?: Record<string, string>;\n /** Previous runtime to retire after a successful cutover. */\n previousRuntimeId?: string | null;\n};\n\n/** Builds base64 `KEYSTROKE_ORG_ARTIFACTS` payload for org worker containers. */\nexport function encodeOrgArtifacts(artifacts: OrgArtifactSpec[]): string {\n return Buffer.from(JSON.stringify(artifacts)).toString(\"base64\");\n}\n\nexport function parseOrgArtifactsFromEnv(\n env: NodeJS.ProcessEnv = process.env,\n): OrgArtifactSpec[] | undefined {\n const raw = env.KEYSTROKE_ORG_ARTIFACTS?.trim();\n if (!raw) {\n return undefined;\n }\n\n const parsed = JSON.parse(Buffer.from(raw, \"base64\").toString(\"utf8\")) as unknown;\n if (!Array.isArray(parsed)) {\n throw new Error(\"KEYSTROKE_ORG_ARTIFACTS must decode to a JSON array\");\n }\n\n return parsed.map((entry) => {\n if (\n !entry ||\n typeof entry !== \"object\" ||\n typeof (entry as OrgArtifactSpec).projectId !== \"string\" ||\n typeof (entry as OrgArtifactSpec).artifactStorageKey !== \"string\" ||\n typeof (entry as OrgArtifactSpec).artifactId !== \"string\" ||\n typeof (entry as OrgArtifactSpec).artifactVersion !== \"number\"\n ) {\n throw new Error(\n \"KEYSTROKE_ORG_ARTIFACTS entries require projectId, artifactStorageKey, artifactId, and artifactVersion\",\n );\n }\n\n return entry as OrgArtifactSpec;\n });\n}\n\nexport type ProjectRuntimeDestroyInput = {\n runtimeId: string;\n hosting?: ProjectRuntimeHosting;\n};\n\nexport type ProjectRuntimeReconcileInput = {\n organizationId: string;\n /** The runtime to keep — every other runtime belonging to this org should be destroyed. */\n keepRuntimeId: string;\n hosting?: ProjectRuntimeHosting;\n};\n\nexport type ProjectRuntimeResult = {\n runtimeId: string;\n baseUrl: string;\n};\n\nexport type ProjectRuntimeSleepInput = {\n runtimeId: string;\n hosting?: ProjectRuntimeHosting;\n};\n\nexport type ProjectRuntimeWakeInput = {\n runtimeId: string;\n hosting?: ProjectRuntimeHosting;\n};\n\nexport type ProjectRuntime = {\n start(input: ProjectRuntimeInput): Promise<ProjectRuntimeResult>;\n destroy?(input: ProjectRuntimeDestroyInput): Promise<void>;\n /**\n * Sweep away every runtime for this org except `keepRuntimeId`, retrying\n * transient failures instead of giving up after one attempt. This catches\n * orphans that a single {@link destroy} call missed (e.g. a previous deploy's\n * destroy failed silently), not just the one runtime recorded as \"previous.\"\n */\n reconcile?(input: ProjectRuntimeReconcileInput): Promise<void>;\n /**\n * Put an existing runtime to sleep without destroying it (e.g. Docker stop,\n * Fly suspend). Provider-specific; omit when unsupported.\n */\n sleep?(input: ProjectRuntimeSleepInput): Promise<void>;\n /**\n * Resume a sleeping runtime. May return a fresh {@link ProjectRuntimeResult}\n * when the base URL can change (e.g. Docker host port remap).\n */\n wake?(input: ProjectRuntimeWakeInput): Promise<ProjectRuntimeResult | void>;\n};\n\nexport type ProjectPingTarget = {\n baseUrl: string | null;\n runtimeId: string | null;\n};\n"],"mappings":";;AA8CA,SAAgB,mBAAmB,WAAsC;CACvE,OAAO,OAAO,KAAK,KAAK,UAAU,SAAS,CAAC,EAAE,SAAS,QAAQ;AACjE;AAEA,SAAgB,yBACd,MAAyB,QAAQ,KACF;CAC/B,MAAM,MAAM,IAAI,yBAAyB,KAAK;CAC9C,IAAI,CAAC,KACH;CAGF,MAAM,SAAS,KAAK,MAAM,OAAO,KAAK,KAAK,QAAQ,EAAE,SAAS,MAAM,CAAC;CACrE,IAAI,CAAC,MAAM,QAAQ,MAAM,GACvB,MAAM,IAAI,MAAM,qDAAqD;CAGvE,OAAO,OAAO,KAAK,UAAU;EAC3B,IACE,CAAC,SACD,OAAO,UAAU,YACjB,OAAQ,MAA0B,cAAc,YAChD,OAAQ,MAA0B,uBAAuB,YACzD,OAAQ,MAA0B,eAAe,YACjD,OAAQ,MAA0B,oBAAoB,UAEtD,MAAM,IAAI,MACR,wGACF;EAGF,OAAO;CACT,CAAC;AACH"}
@@ -14,7 +14,8 @@ type OrgProjectDeploy = {
14
14
  name: string;
15
15
  artifactId: string;
16
16
  artifactStorageKey: string;
17
- artifactVersion: number;
17
+ artifactVersion: number; /** Require an affirmative artifact-specific health record before promotion. */
18
+ requiresArtifactHealth?: boolean;
18
19
  };
19
20
  type OrgArtifactSpec = {
20
21
  projectId: string;
@@ -49,6 +50,14 @@ type ProjectRuntimeResult = {
49
50
  runtimeId: string;
50
51
  baseUrl: string;
51
52
  };
53
+ type ProjectRuntimeSleepInput = {
54
+ runtimeId: string;
55
+ hosting?: ProjectRuntimeHosting;
56
+ };
57
+ type ProjectRuntimeWakeInput = {
58
+ runtimeId: string;
59
+ hosting?: ProjectRuntimeHosting;
60
+ };
52
61
  type ProjectRuntime = {
53
62
  start(input: ProjectRuntimeInput): Promise<ProjectRuntimeResult>;
54
63
  destroy?(input: ProjectRuntimeDestroyInput): Promise<void>;
@@ -59,11 +68,21 @@ type ProjectRuntime = {
59
68
  * destroy failed silently), not just the one runtime recorded as "previous."
60
69
  */
61
70
  reconcile?(input: ProjectRuntimeReconcileInput): Promise<void>;
71
+ /**
72
+ * Put an existing runtime to sleep without destroying it (e.g. Docker stop,
73
+ * Fly suspend). Provider-specific; omit when unsupported.
74
+ */
75
+ sleep?(input: ProjectRuntimeSleepInput): Promise<void>;
76
+ /**
77
+ * Resume a sleeping runtime. May return a fresh {@link ProjectRuntimeResult}
78
+ * when the base URL can change (e.g. Docker host port remap).
79
+ */
80
+ wake?(input: ProjectRuntimeWakeInput): Promise<ProjectRuntimeResult | void>;
62
81
  };
63
82
  type ProjectPingTarget = {
64
83
  baseUrl: string | null;
65
84
  runtimeId: string | null;
66
85
  };
67
86
  //#endregion
68
- export { ProjectRuntimeDatabase as a, ProjectRuntimeInput as c, encodeOrgArtifacts as d, parseOrgArtifactsFromEnv as f, ProjectRuntime as i, ProjectRuntimeReconcileInput as l, OrgProjectDeploy as n, ProjectRuntimeDestroyInput as o, ProjectPingTarget as r, ProjectRuntimeHosting as s, OrgArtifactSpec as t, ProjectRuntimeResult as u };
69
- //# sourceMappingURL=runtime-B0jF0oMv.d.cts.map
87
+ export { ProjectRuntimeDatabase as a, ProjectRuntimeInput as c, ProjectRuntimeSleepInput as d, ProjectRuntimeWakeInput as f, ProjectRuntime as i, ProjectRuntimeReconcileInput as l, parseOrgArtifactsFromEnv as m, OrgProjectDeploy as n, ProjectRuntimeDestroyInput as o, encodeOrgArtifacts as p, ProjectPingTarget as r, ProjectRuntimeHosting as s, OrgArtifactSpec as t, ProjectRuntimeResult as u };
88
+ //# sourceMappingURL=runtime-Cp906goK.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-Cp906goK.d.cts","names":[],"sources":["../src/runtime.ts"],"mappings":";KAAY,sBAAA;EACV,IAAA;EACA,IAAA;EACA,QAAA;EACA,IAAA;EACA,QAAA;EACA,UAAA;EACA,cAAA,UAHA;EAKA,SAAA;AAAA;AAAA,KAGU,gBAAA;EACV,SAAA;EACA,IAAA;EACA,UAAA;EACA,kBAAA;EACA,eAAA;EAEA,sBAAA;AAAA;AAAA,KAGU,eAAA;EACV,SAAA;EACA,kBAAA;EACA,UAAA,UANA;EAQA,eAAA;AAAA;AAAA,KAGU,qBAAA;EACV,OAAO;AAAA;AAAA,KAGG,mBAAA;EACV,cAAA;EACA,QAAA,EAAU,gBAAA;EACV,QAAA,EAAU,sBAAA;EACV,OAAA,GAAU,qBAAA,EAXK;EAaf,GAAA,GAAM,MAAA,kBAVI;EAYV,iBAAA;AAAA;;iBAIc,kBAAA,CAAmB,SAA4B,EAAjB,eAAe;AAAA,iBAI7C,wBAAA,CACd,GAAA,GAAK,MAAA,CAAO,UAAA,GACX,eAAe;AAAA,KA6BN,0BAAA;EACV,SAAA;EACA,OAAA,GAAU,qBAAqB;AAAA;AAAA,KAGrB,4BAAA;EACV,cAAA,UA/CY;EAiDZ,aAAA;EACA,OAAA,GAAU,qBAAqB;AAAA;AAAA,KAGrB,oBAAA;EACV,SAAA;EACA,OAAO;AAAA;AAAA,KAGG,wBAAA;EACV,SAAA;EACA,OAAA,GAAU,qBAAqB;AAAA;AAAA,KAGrB,uBAAA;EACV,SAAA;EACA,OAAA,GAAU,qBAAqB;AAAA;AAAA,KAGrB,cAAA;EACV,KAAA,CAAM,KAAA,EAAO,mBAAA,GAAsB,OAAA,CAAQ,oBAAA;EAC3C,OAAA,EAAS,KAAA,EAAO,0BAAA,GAA6B,OAAA;EA5D/B;;;;;;EAmEd,SAAA,EAAW,KAAA,EAAO,4BAAA,GAA+B,OAAA;EAjEhD;;AAAe;AA6BlB;EAyCE,KAAA,EAAO,KAAA,EAAO,wBAAA,GAA2B,OAAA;;;;;EAKzC,IAAA,EAAM,KAAA,EAAO,uBAAA,GAA0B,OAAA,CAAQ,oBAAA;AAAA;AAAA,KAGrC,iBAAA;EACV,OAAA;EACA,SAAS;AAAA"}
@@ -14,7 +14,8 @@ type OrgProjectDeploy = {
14
14
  name: string;
15
15
  artifactId: string;
16
16
  artifactStorageKey: string;
17
- artifactVersion: number;
17
+ artifactVersion: number; /** Require an affirmative artifact-specific health record before promotion. */
18
+ requiresArtifactHealth?: boolean;
18
19
  };
19
20
  type OrgArtifactSpec = {
20
21
  projectId: string;
@@ -49,6 +50,14 @@ type ProjectRuntimeResult = {
49
50
  runtimeId: string;
50
51
  baseUrl: string;
51
52
  };
53
+ type ProjectRuntimeSleepInput = {
54
+ runtimeId: string;
55
+ hosting?: ProjectRuntimeHosting;
56
+ };
57
+ type ProjectRuntimeWakeInput = {
58
+ runtimeId: string;
59
+ hosting?: ProjectRuntimeHosting;
60
+ };
52
61
  type ProjectRuntime = {
53
62
  start(input: ProjectRuntimeInput): Promise<ProjectRuntimeResult>;
54
63
  destroy?(input: ProjectRuntimeDestroyInput): Promise<void>;
@@ -59,11 +68,21 @@ type ProjectRuntime = {
59
68
  * destroy failed silently), not just the one runtime recorded as "previous."
60
69
  */
61
70
  reconcile?(input: ProjectRuntimeReconcileInput): Promise<void>;
71
+ /**
72
+ * Put an existing runtime to sleep without destroying it (e.g. Docker stop,
73
+ * Fly suspend). Provider-specific; omit when unsupported.
74
+ */
75
+ sleep?(input: ProjectRuntimeSleepInput): Promise<void>;
76
+ /**
77
+ * Resume a sleeping runtime. May return a fresh {@link ProjectRuntimeResult}
78
+ * when the base URL can change (e.g. Docker host port remap).
79
+ */
80
+ wake?(input: ProjectRuntimeWakeInput): Promise<ProjectRuntimeResult | void>;
62
81
  };
63
82
  type ProjectPingTarget = {
64
83
  baseUrl: string | null;
65
84
  runtimeId: string | null;
66
85
  };
67
86
  //#endregion
68
- export { ProjectRuntimeDatabase as a, ProjectRuntimeInput as c, encodeOrgArtifacts as d, parseOrgArtifactsFromEnv as f, ProjectRuntime as i, ProjectRuntimeReconcileInput as l, OrgProjectDeploy as n, ProjectRuntimeDestroyInput as o, ProjectPingTarget as r, ProjectRuntimeHosting as s, OrgArtifactSpec as t, ProjectRuntimeResult as u };
69
- //# sourceMappingURL=runtime-B0jF0oMv.d.mts.map
87
+ export { ProjectRuntimeDatabase as a, ProjectRuntimeInput as c, ProjectRuntimeSleepInput as d, ProjectRuntimeWakeInput as f, ProjectRuntime as i, ProjectRuntimeReconcileInput as l, parseOrgArtifactsFromEnv as m, OrgProjectDeploy as n, ProjectRuntimeDestroyInput as o, encodeOrgArtifacts as p, ProjectPingTarget as r, ProjectRuntimeHosting as s, OrgArtifactSpec as t, ProjectRuntimeResult as u };
88
+ //# sourceMappingURL=runtime-Cp906goK.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-Cp906goK.d.mts","names":[],"sources":["../src/runtime.ts"],"mappings":";KAAY,sBAAA;EACV,IAAA;EACA,IAAA;EACA,QAAA;EACA,IAAA;EACA,QAAA;EACA,UAAA;EACA,cAAA,UAHA;EAKA,SAAA;AAAA;AAAA,KAGU,gBAAA;EACV,SAAA;EACA,IAAA;EACA,UAAA;EACA,kBAAA;EACA,eAAA;EAEA,sBAAA;AAAA;AAAA,KAGU,eAAA;EACV,SAAA;EACA,kBAAA;EACA,UAAA,UANA;EAQA,eAAA;AAAA;AAAA,KAGU,qBAAA;EACV,OAAO;AAAA;AAAA,KAGG,mBAAA;EACV,cAAA;EACA,QAAA,EAAU,gBAAA;EACV,QAAA,EAAU,sBAAA;EACV,OAAA,GAAU,qBAAA,EAXK;EAaf,GAAA,GAAM,MAAA,kBAVI;EAYV,iBAAA;AAAA;;iBAIc,kBAAA,CAAmB,SAA4B,EAAjB,eAAe;AAAA,iBAI7C,wBAAA,CACd,GAAA,GAAK,MAAA,CAAO,UAAA,GACX,eAAe;AAAA,KA6BN,0BAAA;EACV,SAAA;EACA,OAAA,GAAU,qBAAqB;AAAA;AAAA,KAGrB,4BAAA;EACV,cAAA,UA/CY;EAiDZ,aAAA;EACA,OAAA,GAAU,qBAAqB;AAAA;AAAA,KAGrB,oBAAA;EACV,SAAA;EACA,OAAO;AAAA;AAAA,KAGG,wBAAA;EACV,SAAA;EACA,OAAA,GAAU,qBAAqB;AAAA;AAAA,KAGrB,uBAAA;EACV,SAAA;EACA,OAAA,GAAU,qBAAqB;AAAA;AAAA,KAGrB,cAAA;EACV,KAAA,CAAM,KAAA,EAAO,mBAAA,GAAsB,OAAA,CAAQ,oBAAA;EAC3C,OAAA,EAAS,KAAA,EAAO,0BAAA,GAA6B,OAAA;EA5D/B;;;;;;EAmEd,SAAA,EAAW,KAAA,EAAO,4BAAA,GAA+B,OAAA;EAjEhD;;AAAe;AA6BlB;EAyCE,KAAA,EAAO,KAAA,EAAO,wBAAA,GAA2B,OAAA;;;;;EAKzC,IAAA,EAAM,KAAA,EAAO,uBAAA,GAA0B,OAAA,CAAQ,oBAAA;AAAA;AAAA,KAGrC,iBAAA;EACV,OAAA;EACA,SAAS;AAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keystrokehq/hosting",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/keystrokehq/keystroke.git",
@@ -37,7 +37,7 @@
37
37
  },
38
38
  "dependencies": {
39
39
  "dockerode": "^4.0.9",
40
- "@keystrokehq/shared": "0.1.52"
40
+ "@keystrokehq/shared": "0.1.53"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/dockerode": "^3.3.47",
@@ -1 +0,0 @@
1
- {"version":3,"file":"runtime-B0jF0oMv.d.cts","names":[],"sources":["../src/runtime.ts"],"mappings":";KAAY,sBAAA;EACV,IAAA;EACA,IAAA;EACA,QAAA;EACA,IAAA;EACA,QAAA;EACA,UAAA;EACA,cAAA,UAHA;EAKA,SAAA;AAAA;AAAA,KAGU,gBAAA;EACV,SAAA;EACA,IAAA;EACA,UAAA;EACA,kBAAA;EACA,eAAA;AAAA;AAAA,KAGU,eAAA;EACV,SAAA;EACA,kBAAA;EACA,UAAA,UAPA;EASA,eAAA;AAAA;AAAA,KAGU,qBAAA;EACV,OAAO;AAAA;AAAA,KAGG,mBAAA;EACV,cAAA;EACA,QAAA,EAAU,gBAAA;EACV,QAAA,EAAU,sBAAA;EACV,OAAA,GAAU,qBAAA,EAXV;EAaA,GAAA,GAAM,MAAA,kBAbS;EAef,iBAAA;AAAA;;iBAIc,kBAAA,CAAmB,SAA4B,EAAjB,eAAe;AAAA,iBAI7C,wBAAA,CACd,GAAA,GAAK,MAAA,CAAO,UAAA,GACX,eAAe;AAAA,KA6BN,0BAAA;EACV,SAAA;EACA,OAAA,GAAU,qBAAqB;AAAA;AAAA,KAGrB,4BAAA;EACV,cAAA,UA/CM;EAiDN,aAAA;EACA,OAAA,GAAU,qBAAqB;AAAA;AAAA,KAGrB,oBAAA;EACV,SAAA;EACA,OAAO;AAAA;AAAA,KAGG,cAAA;EACV,KAAA,CAAM,KAAA,EAAO,mBAAA,GAAsB,OAAA,CAAQ,oBAAA;EAC3C,OAAA,EAAS,KAAA,EAAO,0BAAA,GAA6B,OAAA;EA5DvC;;;AAEW;AAInB;;EA6DE,SAAA,EAAW,KAAA,EAAO,4BAAA,GAA+B,OAAA;AAAA;AAAA,KAGvC,iBAAA;EACV,OAAA;EACA,SAAS;AAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"runtime-B0jF0oMv.d.mts","names":[],"sources":["../src/runtime.ts"],"mappings":";KAAY,sBAAA;EACV,IAAA;EACA,IAAA;EACA,QAAA;EACA,IAAA;EACA,QAAA;EACA,UAAA;EACA,cAAA,UAHA;EAKA,SAAA;AAAA;AAAA,KAGU,gBAAA;EACV,SAAA;EACA,IAAA;EACA,UAAA;EACA,kBAAA;EACA,eAAA;AAAA;AAAA,KAGU,eAAA;EACV,SAAA;EACA,kBAAA;EACA,UAAA,UAPA;EASA,eAAA;AAAA;AAAA,KAGU,qBAAA;EACV,OAAO;AAAA;AAAA,KAGG,mBAAA;EACV,cAAA;EACA,QAAA,EAAU,gBAAA;EACV,QAAA,EAAU,sBAAA;EACV,OAAA,GAAU,qBAAA,EAXV;EAaA,GAAA,GAAM,MAAA,kBAbS;EAef,iBAAA;AAAA;;iBAIc,kBAAA,CAAmB,SAA4B,EAAjB,eAAe;AAAA,iBAI7C,wBAAA,CACd,GAAA,GAAK,MAAA,CAAO,UAAA,GACX,eAAe;AAAA,KA6BN,0BAAA;EACV,SAAA;EACA,OAAA,GAAU,qBAAqB;AAAA;AAAA,KAGrB,4BAAA;EACV,cAAA,UA/CM;EAiDN,aAAA;EACA,OAAA,GAAU,qBAAqB;AAAA;AAAA,KAGrB,oBAAA;EACV,SAAA;EACA,OAAO;AAAA;AAAA,KAGG,cAAA;EACV,KAAA,CAAM,KAAA,EAAO,mBAAA,GAAsB,OAAA,CAAQ,oBAAA;EAC3C,OAAA,EAAS,KAAA,EAAO,0BAAA,GAA6B,OAAA;EA5DvC;;;AAEW;AAInB;;EA6DE,SAAA,EAAW,KAAA,EAAO,4BAAA,GAA+B,OAAA;AAAA;AAAA,KAGvC,iBAAA;EACV,OAAA;EACA,SAAS;AAAA"}