@keystrokehq/hosting 0.0.112 → 0.1.0
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/index.cjs +13 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -5
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +5 -5
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +14 -12
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -42,8 +42,13 @@ const RUNTIME_ENV_KEYS = [
|
|
|
42
42
|
"POSTGRES_SSL",
|
|
43
43
|
"KEYSTROKE_SCHEDULER_MODULE"
|
|
44
44
|
];
|
|
45
|
-
/** Provider
|
|
46
|
-
const
|
|
45
|
+
/** Provider and gateway env forwarded from the platform into project servers. */
|
|
46
|
+
const PROVIDER_ENV_KEYS = [
|
|
47
|
+
"ANTHROPIC_API_KEY",
|
|
48
|
+
"OPENAI_API_KEY",
|
|
49
|
+
"AI_GATEWAY_API_KEY",
|
|
50
|
+
"AI_GATEWAY_BASE_URL"
|
|
51
|
+
];
|
|
47
52
|
/** Shared dev token when `PLATFORM_WORKER_TOKEN` is unset (non-production only). */
|
|
48
53
|
const DEV_PLATFORM_WORKER_TOKEN = "dev-platform-worker-token";
|
|
49
54
|
const PROJECT_DATABASE_ENV_KEYS = [
|
|
@@ -70,18 +75,18 @@ function projectDatabaseRuntimeEnv(database) {
|
|
|
70
75
|
* per-project presigned URL to that deploy's built `dist` — there is no base
|
|
71
76
|
* image, so it is required.
|
|
72
77
|
*/
|
|
73
|
-
function resolveRuntimeLaunch(options, artifactUrl, database) {
|
|
78
|
+
function resolveRuntimeLaunch(options, artifactUrl, database, extraEnv) {
|
|
74
79
|
if (!artifactUrl) throw new Error("Cannot start a project server without an artifact URL");
|
|
75
80
|
const env = options.env ?? process.env;
|
|
76
81
|
return {
|
|
77
82
|
image: resolveProjectServerImage(env, options.image),
|
|
78
|
-
env: buildRuntimeEnv(env, artifactUrl, database),
|
|
83
|
+
env: buildRuntimeEnv(env, artifactUrl, database, extraEnv),
|
|
79
84
|
port: PROJECT_SERVER_PORT,
|
|
80
85
|
health: PROJECT_SERVER_HEALTH
|
|
81
86
|
};
|
|
82
87
|
}
|
|
83
88
|
/** Env vars passed into the project server container/machine. */
|
|
84
|
-
function buildRuntimeEnv(source, artifactUrl, database) {
|
|
89
|
+
function buildRuntimeEnv(source, artifactUrl, database, extraEnv) {
|
|
85
90
|
const mode = source.KEYSTROKE_MODE ?? "worker";
|
|
86
91
|
const entries = new Map([
|
|
87
92
|
["PORT", String(PROJECT_SERVER_PORT)],
|
|
@@ -101,7 +106,7 @@ function buildRuntimeEnv(source, artifactUrl, database) {
|
|
|
101
106
|
}
|
|
102
107
|
entries.set(key, value);
|
|
103
108
|
}
|
|
104
|
-
|
|
109
|
+
for (const key of PROVIDER_ENV_KEYS) {
|
|
105
110
|
const value = source[key]?.trim();
|
|
106
111
|
if (value) entries.set(key, value);
|
|
107
112
|
}
|
|
@@ -113,14 +118,11 @@ function buildRuntimeEnv(source, artifactUrl, database) {
|
|
|
113
118
|
const worker = resolveWorkerRuntimeConfig(source);
|
|
114
119
|
entries.set("PLATFORM_URL", worker.platformUrl);
|
|
115
120
|
entries.set("WORKER_INTERNAL_TOKEN", worker.workerToken);
|
|
116
|
-
entries.set("AI_GATEWAY_BASE_URL", resolveLlmGatewayBaseUrl(worker.platformUrl, database.projectId));
|
|
117
121
|
}
|
|
118
122
|
for (const [key, value] of Object.entries(projectDatabaseRuntimeEnv(database))) entries.set(key, value);
|
|
123
|
+
if (extraEnv) for (const [key, value] of Object.entries(extraEnv)) entries.set(key, value);
|
|
119
124
|
return Object.fromEntries(entries);
|
|
120
125
|
}
|
|
121
|
-
function resolveLlmGatewayBaseUrl(platformUrl, projectId) {
|
|
122
|
-
return `${platformUrl.replace(/\/$/, "")}/internal/projects/${encodeURIComponent(projectId)}/llm`;
|
|
123
|
-
}
|
|
124
126
|
function resolvePlatformWorkerToken(source = process.env) {
|
|
125
127
|
const explicit = source.PLATFORM_WORKER_TOKEN?.trim() ?? source.WORKER_INTERNAL_TOKEN?.trim();
|
|
126
128
|
if (explicit) return explicit;
|
|
@@ -207,7 +209,7 @@ const MINIO_DOCKER_NETWORK = process.env.MINIO_DOCKER_NETWORK ?? "keystroke";
|
|
|
207
209
|
function createDockerRuntime(options = {}) {
|
|
208
210
|
const docker = options.docker ?? new dockerode.default();
|
|
209
211
|
return { async start(input) {
|
|
210
|
-
const launch = resolveRuntimeLaunch(options, input.artifactUrl, input.database);
|
|
212
|
+
const launch = resolveRuntimeLaunch(options, input.artifactUrl, input.database, input.env);
|
|
211
213
|
await ensureImage(docker, launch.image);
|
|
212
214
|
await removeExistingContainer(docker, input.projectId);
|
|
213
215
|
const container = await docker.createContainer({
|
|
@@ -344,7 +346,6 @@ exports.formatDockerEnv = formatDockerEnv;
|
|
|
344
346
|
exports.pingProject = pingProject;
|
|
345
347
|
exports.pingProjectTarget = pingProjectTarget;
|
|
346
348
|
exports.projectDatabaseRuntimeEnv = projectDatabaseRuntimeEnv;
|
|
347
|
-
exports.resolveLlmGatewayBaseUrl = resolveLlmGatewayBaseUrl;
|
|
348
349
|
exports.resolvePlatformWorkerToken = resolvePlatformWorkerToken;
|
|
349
350
|
exports.resolveProjectServerImage = resolveProjectServerImage;
|
|
350
351
|
exports.resolveRuntimeLaunch = resolveRuntimeLaunch;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["Docker"],"sources":["../src/constants.ts","../src/runtime-env.ts","../src/worker-runtime-config.ts","../src/wait-for-health.ts","../src/docker/create-docker-runtime.ts","../src/docker/plugin.ts","../src/default-plugin.ts","../src/runtime-constants.ts","../src/ping-project.ts","../src/ping-project-target.ts"],"sourcesContent":["export const PROJECT_SERVER_IMAGE = \"keystroke/project-server:local\";\n\nexport const PROJECT_SERVER_PORT = 3000;\n\nexport const PROJECT_SERVER_ROOT = \"/app\";\n\nexport const PROJECT_SERVER_FRAMEWORK_ROOT = \"/opt/keystroke\";\n\nexport const PROJECT_SERVER_FRAMEWORK_NODE_MODULES = `${PROJECT_SERVER_FRAMEWORK_ROOT}/node_modules`;\n\nexport const PROJECT_SERVER_HEALTH = {\n path: \"/health\",\n port: PROJECT_SERVER_PORT,\n} as const;\n","import type { ProjectRuntimeDatabase } from \"./runtime\";\nimport {\n PROJECT_SERVER_FRAMEWORK_NODE_MODULES,\n PROJECT_SERVER_HEALTH,\n PROJECT_SERVER_IMAGE,\n PROJECT_SERVER_PORT,\n PROJECT_SERVER_ROOT,\n} from \"./constants\";\nimport type { HostingOptions } from \"./plugin\";\nimport { resolveWorkerRuntimeConfig } from \"./worker-runtime-config\";\n\nconst RUNTIME_ENV_KEYS = [\n \"BETTER_AUTH_SECRET\",\n \"PUBLIC_SERVER_URL\",\n \"PUBLIC_WEB_URL\",\n \"POSTGRES_SSL\",\n // Scheduler backend selector (forwarded so the container consumer matches the\n // platform producer).\n \"KEYSTROKE_SCHEDULER_MODULE\",\n] as const;\n\n/** Provider keys for non-worker runtimes only — hosted workers use the platform LLM proxy. */\nconst LOCAL_PROVIDER_ENV_KEYS = [\"ANTHROPIC_API_KEY\", \"OPENAI_API_KEY\"] as const;\n\n/** Shared dev token when `PLATFORM_WORKER_TOKEN` is unset (non-production only). */\nexport const DEV_PLATFORM_WORKER_TOKEN = \"dev-platform-worker-token\";\n\nexport const PROJECT_DATABASE_ENV_KEYS = [\n \"POSTGRES_HOST\",\n \"POSTGRES_PORT\",\n \"POSTGRES_DB\",\n \"POSTGRES_USER\",\n \"POSTGRES_PASSWORD\",\n \"DATABASE_SEARCH_PATH\",\n] as const;\n\nexport type RuntimeLaunchSpec = {\n image: string;\n env: Record<string, string>;\n port: number;\n health: typeof PROJECT_SERVER_HEALTH;\n};\n\n/** Per-project postgres env injected into every project server container/machine. */\nexport function projectDatabaseRuntimeEnv(\n database: ProjectRuntimeDatabase,\n): Record<(typeof PROJECT_DATABASE_ENV_KEYS)[number], string> {\n return {\n POSTGRES_HOST: rewriteLoopbackHost(database.host),\n POSTGRES_PORT: String(database.port),\n POSTGRES_DB: database.database,\n POSTGRES_USER: database.user,\n POSTGRES_PASSWORD: database.password,\n DATABASE_SEARCH_PATH: database.searchPath,\n };\n}\n\n/**\n * Builds the launch spec for a single project server. `artifactUrl` is the\n * per-project presigned URL to that deploy's built `dist` — there is no base\n * image, so it is required.\n */\nexport function resolveRuntimeLaunch(\n options: HostingOptions,\n artifactUrl: string,\n database: ProjectRuntimeDatabase,\n): RuntimeLaunchSpec {\n if (!artifactUrl) {\n throw new Error(\"Cannot start a project server without an artifact URL\");\n }\n\n const env = options.env ?? process.env;\n\n return {\n image: resolveProjectServerImage(env, options.image),\n env: buildRuntimeEnv(env, artifactUrl, database),\n port: PROJECT_SERVER_PORT,\n health: PROJECT_SERVER_HEALTH,\n };\n}\n\n/** Env vars passed into the project server container/machine. */\nexport function buildRuntimeEnv(\n source: NodeJS.ProcessEnv,\n artifactUrl: string,\n database: ProjectRuntimeDatabase,\n): Record<string, string> {\n const mode = source.KEYSTROKE_MODE ?? \"worker\";\n const entries = new Map<string, string>([\n // Container bind port — not derived from PUBLIC_SERVER_URL (external URL metadata).\n [\"PORT\", String(PROJECT_SERVER_PORT)],\n [\"KEYSTROKE_ROOT\", PROJECT_SERVER_ROOT],\n [\"KEYSTROKE_RUNTIME_NODE_MODULES\", PROJECT_SERVER_FRAMEWORK_NODE_MODULES],\n [\"ARTIFACT_URL\", artifactUrl],\n [\"KEYSTROKE_MODE\", mode],\n [\"PROJECT_ID\", database.projectId],\n [\"ORGANIZATION_ID\", database.organizationId],\n [\"TENANT_ID\", database.organizationId],\n ]);\n\n for (const key of RUNTIME_ENV_KEYS) {\n const value = source[key]?.trim();\n if (!value) {\n if (key === \"POSTGRES_SSL\") {\n throw new Error(\"POSTGRES_SSL is required\");\n }\n continue;\n }\n entries.set(key, value);\n }\n\n if (mode !== \"worker\") {\n for (const key of LOCAL_PROVIDER_ENV_KEYS) {\n const value = source[key]?.trim();\n if (value) {\n entries.set(key, value);\n }\n }\n }\n\n if (mode !== \"worker\") {\n const credentialsKey = source.CREDENTIALS_ENCRYPTION_KEY;\n if (credentialsKey) {\n entries.set(\"CREDENTIALS_ENCRYPTION_KEY\", credentialsKey);\n }\n }\n\n if (mode === \"worker\") {\n const worker = resolveWorkerRuntimeConfig(source);\n entries.set(\"PLATFORM_URL\", worker.platformUrl);\n entries.set(\"WORKER_INTERNAL_TOKEN\", worker.workerToken);\n entries.set(\n \"AI_GATEWAY_BASE_URL\",\n resolveLlmGatewayBaseUrl(worker.platformUrl, database.projectId),\n );\n }\n\n for (const [key, value] of Object.entries(projectDatabaseRuntimeEnv(database))) {\n entries.set(key, value);\n }\n\n return Object.fromEntries(entries);\n}\n\nexport function resolveLlmGatewayBaseUrl(platformUrl: string, projectId: string): string {\n return `${platformUrl.replace(/\\/$/, \"\")}/internal/projects/${encodeURIComponent(projectId)}/llm`;\n}\n\nexport function resolvePlatformWorkerToken(\n source: NodeJS.ProcessEnv = process.env,\n): string | undefined {\n const explicit = source.PLATFORM_WORKER_TOKEN?.trim() ?? source.WORKER_INTERNAL_TOKEN?.trim();\n if (explicit) {\n return explicit;\n }\n\n if (source.NODE_ENV === \"production\") {\n return undefined;\n }\n\n return DEV_PLATFORM_WORKER_TOKEN;\n}\n\n/** Platform API URL reachable from a project-server container. */\nconst DEV_PLATFORM_URL = \"http://localhost:3002\";\n\nexport function resolveWorkerPlatformUrl(\n source: NodeJS.ProcessEnv = process.env,\n): string | undefined {\n const raw =\n source.PLATFORM_URL?.trim() ??\n source.PUBLIC_PLATFORM_URL?.trim() ??\n (source.NODE_ENV === \"production\" ? undefined : DEV_PLATFORM_URL);\n if (!raw) {\n return undefined;\n }\n\n return rewriteLoopbackUrl(raw);\n}\n\n/** Rewrite loopback hosts so containers can reach Postgres and other host services. */\nexport function rewriteLoopbackHost(host: string): string {\n if (host === \"localhost\" || host === \"127.0.0.1\") {\n return \"host.docker.internal\";\n }\n\n return host;\n}\n\nexport function rewriteLoopbackUrl(url: string): string {\n try {\n const parsed = new URL(url);\n parsed.hostname = rewriteLoopbackHost(parsed.hostname);\n return parsed.toString().replace(/\\/$/, \"\");\n } catch {\n return url;\n }\n}\n\n/** dockerode expects `KEY=value` strings. */\nexport function formatDockerEnv(env: Record<string, string>): string[] {\n return Object.entries(env).map(([key, value]) => `${key}=${value}`);\n}\n\nexport function resolveProjectServerImage(\n env: NodeJS.ProcessEnv = process.env,\n override?: string,\n): string {\n return override ?? env.PROJECT_DOCKER_IMAGE ?? env.TENANT_DOCKER_IMAGE ?? PROJECT_SERVER_IMAGE;\n}\n","import { resolvePlatformWorkerToken, resolveWorkerPlatformUrl } from \"./runtime-env\";\n\n/** Resolved platform ↔ project-server worker auth contract. */\nexport type WorkerRuntimeConfig = {\n /** Platform API base URL reachable from the project-server runtime. */\n platformUrl: string;\n /** Bearer token for platform `/internal` routes. */\n workerToken: string;\n};\n\nexport class WorkerRuntimeConfigError extends Error {\n readonly missing: readonly string[];\n\n constructor(missing: string[]) {\n super(\n `Worker runtime config incomplete (missing: ${missing.join(\", \")}). ` +\n \"In production set PLATFORM_WORKER_TOKEN and PUBLIC_PLATFORM_URL (or PLATFORM_URL).\",\n );\n this.name = \"WorkerRuntimeConfigError\";\n this.missing = missing;\n }\n}\n\n/** Fail fast when the platform cannot start worker-mode project servers. */\nexport function resolveWorkerRuntimeConfig(\n source: NodeJS.ProcessEnv = process.env,\n): WorkerRuntimeConfig {\n const platformUrl = resolveWorkerPlatformUrl(source);\n const workerToken = resolvePlatformWorkerToken(source);\n const missing: string[] = [];\n\n if (!platformUrl) {\n missing.push(\"PUBLIC_PLATFORM_URL or PLATFORM_URL\");\n }\n\n if (!workerToken) {\n missing.push(\"PLATFORM_WORKER_TOKEN\");\n }\n\n if (missing.length > 0) {\n throw new WorkerRuntimeConfigError(missing);\n }\n\n return { platformUrl: platformUrl!, workerToken: workerToken! };\n}\n","import { PROJECT_SERVER_HEALTH } from \"./constants\";\n\nconst DEFAULT_TIMEOUT_MS = 120_000;\nconst DEFAULT_INTERVAL_MS = 500;\n\nexport type WaitForHealthOptions = {\n timeoutMs?: number;\n intervalMs?: number;\n fetchImpl?: typeof fetch;\n};\n\nexport async function waitForHealth(\n baseUrl: string,\n options: WaitForHealthOptions = {},\n): Promise<void> {\n const fetchImpl = options.fetchImpl ?? fetch;\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;\n const deadline = Date.now() + timeoutMs;\n const url = new URL(PROJECT_SERVER_HEALTH.path, baseUrl);\n\n while (Date.now() < deadline) {\n try {\n const response = await fetchImpl(url, { signal: AbortSignal.timeout(2_000) });\n if (response.ok) {\n return;\n }\n } catch {\n // retry until timeout\n }\n\n await sleep(intervalMs);\n }\n\n throw new Error(`Project server health check timed out after ${timeoutMs}ms (${url})`);\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n","import 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 DockerPluginOptions = HostingOptions & {\n docker?: Docker;\n};\n\nconst MINIO_DOCKER_NETWORK = process.env.MINIO_DOCKER_NETWORK ?? \"keystroke\";\n\nexport function createDockerRuntime(options: DockerPluginOptions = {}): ProjectRuntime {\n const docker = options.docker ?? new Docker();\n\n return {\n async start(input) {\n const launch = resolveRuntimeLaunch(options, input.artifactUrl, input.database);\n await ensureImage(docker, launch.image);\n await removeExistingContainer(docker, input.projectId);\n\n const container = await docker.createContainer({\n Image: launch.image,\n name: containerName(input.projectId),\n Labels: {\n \"keystroke.project.id\": input.projectId,\n \"keystroke.project.name\": input.name,\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 },\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 project 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}\n\nfunction containerName(projectId: string): string {\n return `keystroke-project-${projectId}`;\n}\n\nasync function removeExistingContainer(docker: Docker, projectId: string): Promise<void> {\n try {\n const existing = docker.getContainer(containerName(projectId));\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","/** 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"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["Docker"],"sources":["../src/constants.ts","../src/runtime-env.ts","../src/worker-runtime-config.ts","../src/wait-for-health.ts","../src/docker/create-docker-runtime.ts","../src/docker/plugin.ts","../src/default-plugin.ts","../src/runtime-constants.ts","../src/ping-project.ts","../src/ping-project-target.ts"],"sourcesContent":["export const PROJECT_SERVER_IMAGE = \"keystroke/project-server:local\";\n\nexport const PROJECT_SERVER_PORT = 3000;\n\nexport const PROJECT_SERVER_ROOT = \"/app\";\n\nexport const PROJECT_SERVER_FRAMEWORK_ROOT = \"/opt/keystroke\";\n\nexport const PROJECT_SERVER_FRAMEWORK_NODE_MODULES = `${PROJECT_SERVER_FRAMEWORK_ROOT}/node_modules`;\n\nexport const PROJECT_SERVER_HEALTH = {\n path: \"/health\",\n port: PROJECT_SERVER_PORT,\n} as const;\n","import type { ProjectRuntimeDatabase } from \"./runtime\";\nimport {\n PROJECT_SERVER_FRAMEWORK_NODE_MODULES,\n PROJECT_SERVER_HEALTH,\n PROJECT_SERVER_IMAGE,\n PROJECT_SERVER_PORT,\n PROJECT_SERVER_ROOT,\n} from \"./constants\";\nimport type { HostingOptions } from \"./plugin\";\nimport { resolveWorkerRuntimeConfig } from \"./worker-runtime-config\";\n\nconst RUNTIME_ENV_KEYS = [\n \"BETTER_AUTH_SECRET\",\n \"PUBLIC_SERVER_URL\",\n \"PUBLIC_WEB_URL\",\n \"POSTGRES_SSL\",\n // Scheduler backend selector (forwarded so the container consumer matches the\n // platform producer).\n \"KEYSTROKE_SCHEDULER_MODULE\",\n] as const;\n\n/** Provider and gateway env forwarded from the platform into project servers. */\nconst PROVIDER_ENV_KEYS = [\n \"ANTHROPIC_API_KEY\",\n \"OPENAI_API_KEY\",\n \"AI_GATEWAY_API_KEY\",\n \"AI_GATEWAY_BASE_URL\",\n] as const;\n\n/** Shared dev token when `PLATFORM_WORKER_TOKEN` is unset (non-production only). */\nexport const DEV_PLATFORM_WORKER_TOKEN = \"dev-platform-worker-token\";\n\nexport const PROJECT_DATABASE_ENV_KEYS = [\n \"POSTGRES_HOST\",\n \"POSTGRES_PORT\",\n \"POSTGRES_DB\",\n \"POSTGRES_USER\",\n \"POSTGRES_PASSWORD\",\n \"DATABASE_SEARCH_PATH\",\n] as const;\n\nexport type RuntimeLaunchSpec = {\n image: string;\n env: Record<string, string>;\n port: number;\n health: typeof PROJECT_SERVER_HEALTH;\n};\n\n/** Per-project postgres env injected into every project server container/machine. */\nexport function projectDatabaseRuntimeEnv(\n database: ProjectRuntimeDatabase,\n): Record<(typeof PROJECT_DATABASE_ENV_KEYS)[number], string> {\n return {\n POSTGRES_HOST: rewriteLoopbackHost(database.host),\n POSTGRES_PORT: String(database.port),\n POSTGRES_DB: database.database,\n POSTGRES_USER: database.user,\n POSTGRES_PASSWORD: database.password,\n DATABASE_SEARCH_PATH: database.searchPath,\n };\n}\n\n/**\n * Builds the launch spec for a single project server. `artifactUrl` is the\n * per-project presigned URL to that deploy's built `dist` — there is no base\n * image, so it is required.\n */\nexport function resolveRuntimeLaunch(\n options: HostingOptions,\n artifactUrl: string,\n database: ProjectRuntimeDatabase,\n extraEnv?: Record<string, string>,\n): RuntimeLaunchSpec {\n if (!artifactUrl) {\n throw new Error(\"Cannot start a project server without an artifact URL\");\n }\n\n const env = options.env ?? process.env;\n\n return {\n image: resolveProjectServerImage(env, options.image),\n env: buildRuntimeEnv(env, artifactUrl, database, extraEnv),\n port: PROJECT_SERVER_PORT,\n health: PROJECT_SERVER_HEALTH,\n };\n}\n\n/** Env vars passed into the project server container/machine. */\nexport function buildRuntimeEnv(\n source: NodeJS.ProcessEnv,\n artifactUrl: string,\n database: ProjectRuntimeDatabase,\n extraEnv?: Record<string, string>,\n): Record<string, string> {\n const mode = source.KEYSTROKE_MODE ?? \"worker\";\n const entries = new Map<string, string>([\n // Container bind port — not derived from PUBLIC_SERVER_URL (external URL metadata).\n [\"PORT\", String(PROJECT_SERVER_PORT)],\n [\"KEYSTROKE_ROOT\", PROJECT_SERVER_ROOT],\n [\"KEYSTROKE_RUNTIME_NODE_MODULES\", PROJECT_SERVER_FRAMEWORK_NODE_MODULES],\n [\"ARTIFACT_URL\", artifactUrl],\n [\"KEYSTROKE_MODE\", mode],\n [\"PROJECT_ID\", database.projectId],\n [\"ORGANIZATION_ID\", database.organizationId],\n [\"TENANT_ID\", database.organizationId],\n ]);\n\n for (const key of RUNTIME_ENV_KEYS) {\n const value = source[key]?.trim();\n if (!value) {\n if (key === \"POSTGRES_SSL\") {\n throw new Error(\"POSTGRES_SSL is required\");\n }\n continue;\n }\n entries.set(key, value);\n }\n\n for (const key of PROVIDER_ENV_KEYS) {\n const value = source[key]?.trim();\n if (value) {\n entries.set(key, value);\n }\n }\n\n if (mode !== \"worker\") {\n const credentialsKey = source.CREDENTIALS_ENCRYPTION_KEY;\n if (credentialsKey) {\n entries.set(\"CREDENTIALS_ENCRYPTION_KEY\", credentialsKey);\n }\n }\n\n if (mode === \"worker\") {\n const worker = resolveWorkerRuntimeConfig(source);\n entries.set(\"PLATFORM_URL\", worker.platformUrl);\n entries.set(\"WORKER_INTERNAL_TOKEN\", worker.workerToken);\n }\n\n for (const [key, value] of Object.entries(projectDatabaseRuntimeEnv(database))) {\n entries.set(key, value);\n }\n\n if (extraEnv) {\n for (const [key, value] of Object.entries(extraEnv)) {\n entries.set(key, value);\n }\n }\n\n return Object.fromEntries(entries);\n}\n\nexport function resolvePlatformWorkerToken(\n source: NodeJS.ProcessEnv = process.env,\n): string | undefined {\n const explicit = source.PLATFORM_WORKER_TOKEN?.trim() ?? source.WORKER_INTERNAL_TOKEN?.trim();\n if (explicit) {\n return explicit;\n }\n\n if (source.NODE_ENV === \"production\") {\n return undefined;\n }\n\n return DEV_PLATFORM_WORKER_TOKEN;\n}\n\n/** Platform API URL reachable from a project-server container. */\nconst DEV_PLATFORM_URL = \"http://localhost:3002\";\n\nexport function resolveWorkerPlatformUrl(\n source: NodeJS.ProcessEnv = process.env,\n): string | undefined {\n const raw =\n source.PLATFORM_URL?.trim() ??\n source.PUBLIC_PLATFORM_URL?.trim() ??\n (source.NODE_ENV === \"production\" ? undefined : DEV_PLATFORM_URL);\n if (!raw) {\n return undefined;\n }\n\n return rewriteLoopbackUrl(raw);\n}\n\n/** Rewrite loopback hosts so containers can reach Postgres and other host services. */\nexport function rewriteLoopbackHost(host: string): string {\n if (host === \"localhost\" || host === \"127.0.0.1\") {\n return \"host.docker.internal\";\n }\n\n return host;\n}\n\nexport function rewriteLoopbackUrl(url: string): string {\n try {\n const parsed = new URL(url);\n parsed.hostname = rewriteLoopbackHost(parsed.hostname);\n return parsed.toString().replace(/\\/$/, \"\");\n } catch {\n return url;\n }\n}\n\n/** dockerode expects `KEY=value` strings. */\nexport function formatDockerEnv(env: Record<string, string>): string[] {\n return Object.entries(env).map(([key, value]) => `${key}=${value}`);\n}\n\nexport function resolveProjectServerImage(\n env: NodeJS.ProcessEnv = process.env,\n override?: string,\n): string {\n return override ?? env.PROJECT_DOCKER_IMAGE ?? env.TENANT_DOCKER_IMAGE ?? PROJECT_SERVER_IMAGE;\n}\n","import { resolvePlatformWorkerToken, resolveWorkerPlatformUrl } from \"./runtime-env\";\n\n/** Resolved platform ↔ project-server worker auth contract. */\nexport type WorkerRuntimeConfig = {\n /** Platform API base URL reachable from the project-server runtime. */\n platformUrl: string;\n /** Bearer token for platform `/internal` routes. */\n workerToken: string;\n};\n\nexport class WorkerRuntimeConfigError extends Error {\n readonly missing: readonly string[];\n\n constructor(missing: string[]) {\n super(\n `Worker runtime config incomplete (missing: ${missing.join(\", \")}). ` +\n \"In production set PLATFORM_WORKER_TOKEN and PUBLIC_PLATFORM_URL (or PLATFORM_URL).\",\n );\n this.name = \"WorkerRuntimeConfigError\";\n this.missing = missing;\n }\n}\n\n/** Fail fast when the platform cannot start worker-mode project servers. */\nexport function resolveWorkerRuntimeConfig(\n source: NodeJS.ProcessEnv = process.env,\n): WorkerRuntimeConfig {\n const platformUrl = resolveWorkerPlatformUrl(source);\n const workerToken = resolvePlatformWorkerToken(source);\n const missing: string[] = [];\n\n if (!platformUrl) {\n missing.push(\"PUBLIC_PLATFORM_URL or PLATFORM_URL\");\n }\n\n if (!workerToken) {\n missing.push(\"PLATFORM_WORKER_TOKEN\");\n }\n\n if (missing.length > 0) {\n throw new WorkerRuntimeConfigError(missing);\n }\n\n return { platformUrl: platformUrl!, workerToken: workerToken! };\n}\n","import { PROJECT_SERVER_HEALTH } from \"./constants\";\n\nconst DEFAULT_TIMEOUT_MS = 120_000;\nconst DEFAULT_INTERVAL_MS = 500;\n\nexport type WaitForHealthOptions = {\n timeoutMs?: number;\n intervalMs?: number;\n fetchImpl?: typeof fetch;\n};\n\nexport async function waitForHealth(\n baseUrl: string,\n options: WaitForHealthOptions = {},\n): Promise<void> {\n const fetchImpl = options.fetchImpl ?? fetch;\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;\n const deadline = Date.now() + timeoutMs;\n const url = new URL(PROJECT_SERVER_HEALTH.path, baseUrl);\n\n while (Date.now() < deadline) {\n try {\n const response = await fetchImpl(url, { signal: AbortSignal.timeout(2_000) });\n if (response.ok) {\n return;\n }\n } catch {\n // retry until timeout\n }\n\n await sleep(intervalMs);\n }\n\n throw new Error(`Project server health check timed out after ${timeoutMs}ms (${url})`);\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n","import 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 DockerPluginOptions = HostingOptions & {\n docker?: Docker;\n};\n\nconst MINIO_DOCKER_NETWORK = process.env.MINIO_DOCKER_NETWORK ?? \"keystroke\";\n\nexport function createDockerRuntime(options: DockerPluginOptions = {}): ProjectRuntime {\n const docker = options.docker ?? new Docker();\n\n return {\n async start(input) {\n const launch = resolveRuntimeLaunch(options, input.artifactUrl, input.database, input.env);\n await ensureImage(docker, launch.image);\n await removeExistingContainer(docker, input.projectId);\n\n const container = await docker.createContainer({\n Image: launch.image,\n name: containerName(input.projectId),\n Labels: {\n \"keystroke.project.id\": input.projectId,\n \"keystroke.project.name\": input.name,\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 },\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 project 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}\n\nfunction containerName(projectId: string): string {\n return `keystroke-project-${projectId}`;\n}\n\nasync function removeExistingContainer(docker: Docker, projectId: string): Promise<void> {\n try {\n const existing = docker.getContainer(containerName(projectId));\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","/** 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"],"mappings":"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"}
|
package/dist/index.d.cts
CHANGED
|
@@ -31,7 +31,8 @@ type ProjectRuntimeInput = {
|
|
|
31
31
|
artifactId: string;
|
|
32
32
|
artifactUrl: string;
|
|
33
33
|
database: ProjectRuntimeDatabase;
|
|
34
|
-
hosting?: ProjectRuntimeHosting; /**
|
|
34
|
+
hosting?: ProjectRuntimeHosting; /** Extra env merged into the project server launch spec (e.g. from an llmProxy plugin). */
|
|
35
|
+
env?: Record<string, string>; /** Previous runtime to retire after a successful cutover. */
|
|
35
36
|
previousRuntimeId?: string | null;
|
|
36
37
|
};
|
|
37
38
|
type ProjectRuntimeDestroyInput = {
|
|
@@ -121,10 +122,9 @@ declare function projectDatabaseRuntimeEnv(database: ProjectRuntimeDatabase): Re
|
|
|
121
122
|
* per-project presigned URL to that deploy's built `dist` — there is no base
|
|
122
123
|
* image, so it is required.
|
|
123
124
|
*/
|
|
124
|
-
declare function resolveRuntimeLaunch(options: HostingOptions, artifactUrl: string, database: ProjectRuntimeDatabase): RuntimeLaunchSpec;
|
|
125
|
+
declare function resolveRuntimeLaunch(options: HostingOptions, artifactUrl: string, database: ProjectRuntimeDatabase, extraEnv?: Record<string, string>): RuntimeLaunchSpec;
|
|
125
126
|
/** Env vars passed into the project server container/machine. */
|
|
126
|
-
declare function buildRuntimeEnv(source: NodeJS.ProcessEnv, artifactUrl: string, database: ProjectRuntimeDatabase): Record<string, string>;
|
|
127
|
-
declare function resolveLlmGatewayBaseUrl(platformUrl: string, projectId: string): string;
|
|
127
|
+
declare function buildRuntimeEnv(source: NodeJS.ProcessEnv, artifactUrl: string, database: ProjectRuntimeDatabase, extraEnv?: Record<string, string>): Record<string, string>;
|
|
128
128
|
declare function resolvePlatformWorkerToken(source?: NodeJS.ProcessEnv): string | undefined;
|
|
129
129
|
declare function resolveWorkerPlatformUrl(source?: NodeJS.ProcessEnv): string | undefined;
|
|
130
130
|
/** Rewrite loopback hosts so containers can reach Postgres and other host services. */
|
|
@@ -164,5 +164,5 @@ declare function pingProjectTarget(target: ProjectPingTarget, options?: PingProj
|
|
|
164
164
|
plugin?: Pick<HostingPlugin, "canPingTarget" | "pingRequestHeaders">;
|
|
165
165
|
}): Promise<boolean>;
|
|
166
166
|
//#endregion
|
|
167
|
-
export { DEV_PLATFORM_WORKER_TOKEN, type DockerPluginOptions, type HostingOptions, type HostingPlugin, type OrganizationHostingInput, type OrganizationHostingResult, PROJECT_DATABASE_ENV_KEYS, PROJECT_SERVER_FRAMEWORK_NODE_MODULES, PROJECT_SERVER_FRAMEWORK_ROOT, PROJECT_SERVER_HEALTH, PROJECT_SERVER_IMAGE, PROJECT_SERVER_PORT, PROJECT_SERVER_ROOT, type PingProjectOptions, type ProjectPingTarget, type ProjectRuntime, type ProjectRuntimeDatabase, type ProjectRuntimeDestroyInput, type ProjectRuntimeHosting, type ProjectRuntimeInput, type ProjectRuntimeResult, RUNTIME_PING_TIMEOUT_MS, type RuntimeLaunchSpec, type WaitForHealthOptions, type WorkerRuntimeConfig, WorkerRuntimeConfigError, buildRuntimeEnv, canPingProjectTarget, defaultHostingPlugin, dockerPlugin, formatDockerEnv, pingProject, pingProjectTarget, projectDatabaseRuntimeEnv,
|
|
167
|
+
export { DEV_PLATFORM_WORKER_TOKEN, type DockerPluginOptions, type HostingOptions, type HostingPlugin, type OrganizationHostingInput, type OrganizationHostingResult, PROJECT_DATABASE_ENV_KEYS, PROJECT_SERVER_FRAMEWORK_NODE_MODULES, PROJECT_SERVER_FRAMEWORK_ROOT, PROJECT_SERVER_HEALTH, PROJECT_SERVER_IMAGE, PROJECT_SERVER_PORT, PROJECT_SERVER_ROOT, type PingProjectOptions, type ProjectPingTarget, type ProjectRuntime, type ProjectRuntimeDatabase, type ProjectRuntimeDestroyInput, type ProjectRuntimeHosting, type ProjectRuntimeInput, type ProjectRuntimeResult, RUNTIME_PING_TIMEOUT_MS, type RuntimeLaunchSpec, type WaitForHealthOptions, type WorkerRuntimeConfig, WorkerRuntimeConfigError, buildRuntimeEnv, canPingProjectTarget, defaultHostingPlugin, dockerPlugin, formatDockerEnv, pingProject, pingProjectTarget, projectDatabaseRuntimeEnv, resolvePlatformWorkerToken, resolveProjectServerImage, resolveRuntimeLaunch, resolveWorkerPlatformUrl, resolveWorkerRuntimeConfig, rewriteLoopbackHost, rewriteLoopbackUrl, waitForHealth };
|
|
168
168
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/constants.ts","../src/runtime.ts","../src/worker-runtime-config.ts","../src/plugin.ts","../src/docker/create-docker-runtime.ts","../src/docker/plugin.ts","../src/default-plugin.ts","../src/runtime-env.ts","../src/wait-for-health.ts","../src/runtime-constants.ts","../src/ping-project.ts","../src/ping-project-target.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;;;KCbE,sBAAA;EACV,IAAA;EACA,IAAA;EACA,QAAA;EACA,IAAA;EACA,QAAA;EACA,UAAA;EACA,SAAA;EACA,cAAA;AAAA;AAAA,KAGU,qBAAA;EACV,OAAO;AAAA;AAAA,KAGG,mBAAA;EACV,SAAA;EACA,IAAA;EACA,UAAA;EACA,WAAA;EACA,QAAA,EAAU,sBAAA;EACV,OAAA,GAAU,
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/constants.ts","../src/runtime.ts","../src/worker-runtime-config.ts","../src/plugin.ts","../src/docker/create-docker-runtime.ts","../src/docker/plugin.ts","../src/default-plugin.ts","../src/runtime-env.ts","../src/wait-for-health.ts","../src/runtime-constants.ts","../src/ping-project.ts","../src/ping-project-target.ts"],"mappings":"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"}
|
package/dist/index.d.mts
CHANGED
|
@@ -31,7 +31,8 @@ type ProjectRuntimeInput = {
|
|
|
31
31
|
artifactId: string;
|
|
32
32
|
artifactUrl: string;
|
|
33
33
|
database: ProjectRuntimeDatabase;
|
|
34
|
-
hosting?: ProjectRuntimeHosting; /**
|
|
34
|
+
hosting?: ProjectRuntimeHosting; /** Extra env merged into the project server launch spec (e.g. from an llmProxy plugin). */
|
|
35
|
+
env?: Record<string, string>; /** Previous runtime to retire after a successful cutover. */
|
|
35
36
|
previousRuntimeId?: string | null;
|
|
36
37
|
};
|
|
37
38
|
type ProjectRuntimeDestroyInput = {
|
|
@@ -121,10 +122,9 @@ declare function projectDatabaseRuntimeEnv(database: ProjectRuntimeDatabase): Re
|
|
|
121
122
|
* per-project presigned URL to that deploy's built `dist` — there is no base
|
|
122
123
|
* image, so it is required.
|
|
123
124
|
*/
|
|
124
|
-
declare function resolveRuntimeLaunch(options: HostingOptions, artifactUrl: string, database: ProjectRuntimeDatabase): RuntimeLaunchSpec;
|
|
125
|
+
declare function resolveRuntimeLaunch(options: HostingOptions, artifactUrl: string, database: ProjectRuntimeDatabase, extraEnv?: Record<string, string>): RuntimeLaunchSpec;
|
|
125
126
|
/** Env vars passed into the project server container/machine. */
|
|
126
|
-
declare function buildRuntimeEnv(source: NodeJS.ProcessEnv, artifactUrl: string, database: ProjectRuntimeDatabase): Record<string, string>;
|
|
127
|
-
declare function resolveLlmGatewayBaseUrl(platformUrl: string, projectId: string): string;
|
|
127
|
+
declare function buildRuntimeEnv(source: NodeJS.ProcessEnv, artifactUrl: string, database: ProjectRuntimeDatabase, extraEnv?: Record<string, string>): Record<string, string>;
|
|
128
128
|
declare function resolvePlatformWorkerToken(source?: NodeJS.ProcessEnv): string | undefined;
|
|
129
129
|
declare function resolveWorkerPlatformUrl(source?: NodeJS.ProcessEnv): string | undefined;
|
|
130
130
|
/** Rewrite loopback hosts so containers can reach Postgres and other host services. */
|
|
@@ -164,5 +164,5 @@ declare function pingProjectTarget(target: ProjectPingTarget, options?: PingProj
|
|
|
164
164
|
plugin?: Pick<HostingPlugin, "canPingTarget" | "pingRequestHeaders">;
|
|
165
165
|
}): Promise<boolean>;
|
|
166
166
|
//#endregion
|
|
167
|
-
export { DEV_PLATFORM_WORKER_TOKEN, type DockerPluginOptions, type HostingOptions, type HostingPlugin, type OrganizationHostingInput, type OrganizationHostingResult, PROJECT_DATABASE_ENV_KEYS, PROJECT_SERVER_FRAMEWORK_NODE_MODULES, PROJECT_SERVER_FRAMEWORK_ROOT, PROJECT_SERVER_HEALTH, PROJECT_SERVER_IMAGE, PROJECT_SERVER_PORT, PROJECT_SERVER_ROOT, type PingProjectOptions, type ProjectPingTarget, type ProjectRuntime, type ProjectRuntimeDatabase, type ProjectRuntimeDestroyInput, type ProjectRuntimeHosting, type ProjectRuntimeInput, type ProjectRuntimeResult, RUNTIME_PING_TIMEOUT_MS, type RuntimeLaunchSpec, type WaitForHealthOptions, type WorkerRuntimeConfig, WorkerRuntimeConfigError, buildRuntimeEnv, canPingProjectTarget, defaultHostingPlugin, dockerPlugin, formatDockerEnv, pingProject, pingProjectTarget, projectDatabaseRuntimeEnv,
|
|
167
|
+
export { DEV_PLATFORM_WORKER_TOKEN, type DockerPluginOptions, type HostingOptions, type HostingPlugin, type OrganizationHostingInput, type OrganizationHostingResult, PROJECT_DATABASE_ENV_KEYS, PROJECT_SERVER_FRAMEWORK_NODE_MODULES, PROJECT_SERVER_FRAMEWORK_ROOT, PROJECT_SERVER_HEALTH, PROJECT_SERVER_IMAGE, PROJECT_SERVER_PORT, PROJECT_SERVER_ROOT, type PingProjectOptions, type ProjectPingTarget, type ProjectRuntime, type ProjectRuntimeDatabase, type ProjectRuntimeDestroyInput, type ProjectRuntimeHosting, type ProjectRuntimeInput, type ProjectRuntimeResult, RUNTIME_PING_TIMEOUT_MS, type RuntimeLaunchSpec, type WaitForHealthOptions, type WorkerRuntimeConfig, WorkerRuntimeConfigError, buildRuntimeEnv, canPingProjectTarget, defaultHostingPlugin, dockerPlugin, formatDockerEnv, pingProject, pingProjectTarget, projectDatabaseRuntimeEnv, resolvePlatformWorkerToken, resolveProjectServerImage, resolveRuntimeLaunch, resolveWorkerPlatformUrl, resolveWorkerRuntimeConfig, rewriteLoopbackHost, rewriteLoopbackUrl, waitForHealth };
|
|
168
168
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/constants.ts","../src/runtime.ts","../src/worker-runtime-config.ts","../src/plugin.ts","../src/docker/create-docker-runtime.ts","../src/docker/plugin.ts","../src/default-plugin.ts","../src/runtime-env.ts","../src/wait-for-health.ts","../src/runtime-constants.ts","../src/ping-project.ts","../src/ping-project-target.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;;;KCbE,sBAAA;EACV,IAAA;EACA,IAAA;EACA,QAAA;EACA,IAAA;EACA,QAAA;EACA,UAAA;EACA,SAAA;EACA,cAAA;AAAA;AAAA,KAGU,qBAAA;EACV,OAAO;AAAA;AAAA,KAGG,mBAAA;EACV,SAAA;EACA,IAAA;EACA,UAAA;EACA,WAAA;EACA,QAAA,EAAU,sBAAA;EACV,OAAA,GAAU,
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/constants.ts","../src/runtime.ts","../src/worker-runtime-config.ts","../src/plugin.ts","../src/docker/create-docker-runtime.ts","../src/docker/plugin.ts","../src/default-plugin.ts","../src/runtime-env.ts","../src/wait-for-health.ts","../src/runtime-constants.ts","../src/ping-project.ts","../src/ping-project-target.ts"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -18,8 +18,13 @@ const RUNTIME_ENV_KEYS = [
|
|
|
18
18
|
"POSTGRES_SSL",
|
|
19
19
|
"KEYSTROKE_SCHEDULER_MODULE"
|
|
20
20
|
];
|
|
21
|
-
/** Provider
|
|
22
|
-
const
|
|
21
|
+
/** Provider and gateway env forwarded from the platform into project servers. */
|
|
22
|
+
const PROVIDER_ENV_KEYS = [
|
|
23
|
+
"ANTHROPIC_API_KEY",
|
|
24
|
+
"OPENAI_API_KEY",
|
|
25
|
+
"AI_GATEWAY_API_KEY",
|
|
26
|
+
"AI_GATEWAY_BASE_URL"
|
|
27
|
+
];
|
|
23
28
|
/** Shared dev token when `PLATFORM_WORKER_TOKEN` is unset (non-production only). */
|
|
24
29
|
const DEV_PLATFORM_WORKER_TOKEN = "dev-platform-worker-token";
|
|
25
30
|
const PROJECT_DATABASE_ENV_KEYS = [
|
|
@@ -46,18 +51,18 @@ function projectDatabaseRuntimeEnv(database) {
|
|
|
46
51
|
* per-project presigned URL to that deploy's built `dist` — there is no base
|
|
47
52
|
* image, so it is required.
|
|
48
53
|
*/
|
|
49
|
-
function resolveRuntimeLaunch(options, artifactUrl, database) {
|
|
54
|
+
function resolveRuntimeLaunch(options, artifactUrl, database, extraEnv) {
|
|
50
55
|
if (!artifactUrl) throw new Error("Cannot start a project server without an artifact URL");
|
|
51
56
|
const env = options.env ?? process.env;
|
|
52
57
|
return {
|
|
53
58
|
image: resolveProjectServerImage(env, options.image),
|
|
54
|
-
env: buildRuntimeEnv(env, artifactUrl, database),
|
|
59
|
+
env: buildRuntimeEnv(env, artifactUrl, database, extraEnv),
|
|
55
60
|
port: PROJECT_SERVER_PORT,
|
|
56
61
|
health: PROJECT_SERVER_HEALTH
|
|
57
62
|
};
|
|
58
63
|
}
|
|
59
64
|
/** Env vars passed into the project server container/machine. */
|
|
60
|
-
function buildRuntimeEnv(source, artifactUrl, database) {
|
|
65
|
+
function buildRuntimeEnv(source, artifactUrl, database, extraEnv) {
|
|
61
66
|
const mode = source.KEYSTROKE_MODE ?? "worker";
|
|
62
67
|
const entries = new Map([
|
|
63
68
|
["PORT", String(PROJECT_SERVER_PORT)],
|
|
@@ -77,7 +82,7 @@ function buildRuntimeEnv(source, artifactUrl, database) {
|
|
|
77
82
|
}
|
|
78
83
|
entries.set(key, value);
|
|
79
84
|
}
|
|
80
|
-
|
|
85
|
+
for (const key of PROVIDER_ENV_KEYS) {
|
|
81
86
|
const value = source[key]?.trim();
|
|
82
87
|
if (value) entries.set(key, value);
|
|
83
88
|
}
|
|
@@ -89,14 +94,11 @@ function buildRuntimeEnv(source, artifactUrl, database) {
|
|
|
89
94
|
const worker = resolveWorkerRuntimeConfig(source);
|
|
90
95
|
entries.set("PLATFORM_URL", worker.platformUrl);
|
|
91
96
|
entries.set("WORKER_INTERNAL_TOKEN", worker.workerToken);
|
|
92
|
-
entries.set("AI_GATEWAY_BASE_URL", resolveLlmGatewayBaseUrl(worker.platformUrl, database.projectId));
|
|
93
97
|
}
|
|
94
98
|
for (const [key, value] of Object.entries(projectDatabaseRuntimeEnv(database))) entries.set(key, value);
|
|
99
|
+
if (extraEnv) for (const [key, value] of Object.entries(extraEnv)) entries.set(key, value);
|
|
95
100
|
return Object.fromEntries(entries);
|
|
96
101
|
}
|
|
97
|
-
function resolveLlmGatewayBaseUrl(platformUrl, projectId) {
|
|
98
|
-
return `${platformUrl.replace(/\/$/, "")}/internal/projects/${encodeURIComponent(projectId)}/llm`;
|
|
99
|
-
}
|
|
100
102
|
function resolvePlatformWorkerToken(source = process.env) {
|
|
101
103
|
const explicit = source.PLATFORM_WORKER_TOKEN?.trim() ?? source.WORKER_INTERNAL_TOKEN?.trim();
|
|
102
104
|
if (explicit) return explicit;
|
|
@@ -183,7 +185,7 @@ const MINIO_DOCKER_NETWORK = process.env.MINIO_DOCKER_NETWORK ?? "keystroke";
|
|
|
183
185
|
function createDockerRuntime(options = {}) {
|
|
184
186
|
const docker = options.docker ?? new Docker();
|
|
185
187
|
return { async start(input) {
|
|
186
|
-
const launch = resolveRuntimeLaunch(options, input.artifactUrl, input.database);
|
|
188
|
+
const launch = resolveRuntimeLaunch(options, input.artifactUrl, input.database, input.env);
|
|
187
189
|
await ensureImage(docker, launch.image);
|
|
188
190
|
await removeExistingContainer(docker, input.projectId);
|
|
189
191
|
const container = await docker.createContainer({
|
|
@@ -302,6 +304,6 @@ async function pingProjectTarget(target, options = {}) {
|
|
|
302
304
|
});
|
|
303
305
|
}
|
|
304
306
|
//#endregion
|
|
305
|
-
export { DEV_PLATFORM_WORKER_TOKEN, PROJECT_DATABASE_ENV_KEYS, 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, defaultHostingPlugin, dockerPlugin, formatDockerEnv, pingProject, pingProjectTarget, projectDatabaseRuntimeEnv,
|
|
307
|
+
export { DEV_PLATFORM_WORKER_TOKEN, PROJECT_DATABASE_ENV_KEYS, 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, defaultHostingPlugin, dockerPlugin, formatDockerEnv, pingProject, pingProjectTarget, projectDatabaseRuntimeEnv, resolvePlatformWorkerToken, resolveProjectServerImage, resolveRuntimeLaunch, resolveWorkerPlatformUrl, resolveWorkerRuntimeConfig, rewriteLoopbackHost, rewriteLoopbackUrl, waitForHealth };
|
|
306
308
|
|
|
307
309
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/constants.ts","../src/runtime-env.ts","../src/worker-runtime-config.ts","../src/wait-for-health.ts","../src/docker/create-docker-runtime.ts","../src/docker/plugin.ts","../src/default-plugin.ts","../src/runtime-constants.ts","../src/ping-project.ts","../src/ping-project-target.ts"],"sourcesContent":["export const PROJECT_SERVER_IMAGE = \"keystroke/project-server:local\";\n\nexport const PROJECT_SERVER_PORT = 3000;\n\nexport const PROJECT_SERVER_ROOT = \"/app\";\n\nexport const PROJECT_SERVER_FRAMEWORK_ROOT = \"/opt/keystroke\";\n\nexport const PROJECT_SERVER_FRAMEWORK_NODE_MODULES = `${PROJECT_SERVER_FRAMEWORK_ROOT}/node_modules`;\n\nexport const PROJECT_SERVER_HEALTH = {\n path: \"/health\",\n port: PROJECT_SERVER_PORT,\n} as const;\n","import type { ProjectRuntimeDatabase } from \"./runtime\";\nimport {\n PROJECT_SERVER_FRAMEWORK_NODE_MODULES,\n PROJECT_SERVER_HEALTH,\n PROJECT_SERVER_IMAGE,\n PROJECT_SERVER_PORT,\n PROJECT_SERVER_ROOT,\n} from \"./constants\";\nimport type { HostingOptions } from \"./plugin\";\nimport { resolveWorkerRuntimeConfig } from \"./worker-runtime-config\";\n\nconst RUNTIME_ENV_KEYS = [\n \"BETTER_AUTH_SECRET\",\n \"PUBLIC_SERVER_URL\",\n \"PUBLIC_WEB_URL\",\n \"POSTGRES_SSL\",\n // Scheduler backend selector (forwarded so the container consumer matches the\n // platform producer).\n \"KEYSTROKE_SCHEDULER_MODULE\",\n] as const;\n\n/** Provider keys for non-worker runtimes only — hosted workers use the platform LLM proxy. */\nconst LOCAL_PROVIDER_ENV_KEYS = [\"ANTHROPIC_API_KEY\", \"OPENAI_API_KEY\"] as const;\n\n/** Shared dev token when `PLATFORM_WORKER_TOKEN` is unset (non-production only). */\nexport const DEV_PLATFORM_WORKER_TOKEN = \"dev-platform-worker-token\";\n\nexport const PROJECT_DATABASE_ENV_KEYS = [\n \"POSTGRES_HOST\",\n \"POSTGRES_PORT\",\n \"POSTGRES_DB\",\n \"POSTGRES_USER\",\n \"POSTGRES_PASSWORD\",\n \"DATABASE_SEARCH_PATH\",\n] as const;\n\nexport type RuntimeLaunchSpec = {\n image: string;\n env: Record<string, string>;\n port: number;\n health: typeof PROJECT_SERVER_HEALTH;\n};\n\n/** Per-project postgres env injected into every project server container/machine. */\nexport function projectDatabaseRuntimeEnv(\n database: ProjectRuntimeDatabase,\n): Record<(typeof PROJECT_DATABASE_ENV_KEYS)[number], string> {\n return {\n POSTGRES_HOST: rewriteLoopbackHost(database.host),\n POSTGRES_PORT: String(database.port),\n POSTGRES_DB: database.database,\n POSTGRES_USER: database.user,\n POSTGRES_PASSWORD: database.password,\n DATABASE_SEARCH_PATH: database.searchPath,\n };\n}\n\n/**\n * Builds the launch spec for a single project server. `artifactUrl` is the\n * per-project presigned URL to that deploy's built `dist` — there is no base\n * image, so it is required.\n */\nexport function resolveRuntimeLaunch(\n options: HostingOptions,\n artifactUrl: string,\n database: ProjectRuntimeDatabase,\n): RuntimeLaunchSpec {\n if (!artifactUrl) {\n throw new Error(\"Cannot start a project server without an artifact URL\");\n }\n\n const env = options.env ?? process.env;\n\n return {\n image: resolveProjectServerImage(env, options.image),\n env: buildRuntimeEnv(env, artifactUrl, database),\n port: PROJECT_SERVER_PORT,\n health: PROJECT_SERVER_HEALTH,\n };\n}\n\n/** Env vars passed into the project server container/machine. */\nexport function buildRuntimeEnv(\n source: NodeJS.ProcessEnv,\n artifactUrl: string,\n database: ProjectRuntimeDatabase,\n): Record<string, string> {\n const mode = source.KEYSTROKE_MODE ?? \"worker\";\n const entries = new Map<string, string>([\n // Container bind port — not derived from PUBLIC_SERVER_URL (external URL metadata).\n [\"PORT\", String(PROJECT_SERVER_PORT)],\n [\"KEYSTROKE_ROOT\", PROJECT_SERVER_ROOT],\n [\"KEYSTROKE_RUNTIME_NODE_MODULES\", PROJECT_SERVER_FRAMEWORK_NODE_MODULES],\n [\"ARTIFACT_URL\", artifactUrl],\n [\"KEYSTROKE_MODE\", mode],\n [\"PROJECT_ID\", database.projectId],\n [\"ORGANIZATION_ID\", database.organizationId],\n [\"TENANT_ID\", database.organizationId],\n ]);\n\n for (const key of RUNTIME_ENV_KEYS) {\n const value = source[key]?.trim();\n if (!value) {\n if (key === \"POSTGRES_SSL\") {\n throw new Error(\"POSTGRES_SSL is required\");\n }\n continue;\n }\n entries.set(key, value);\n }\n\n if (mode !== \"worker\") {\n for (const key of LOCAL_PROVIDER_ENV_KEYS) {\n const value = source[key]?.trim();\n if (value) {\n entries.set(key, value);\n }\n }\n }\n\n if (mode !== \"worker\") {\n const credentialsKey = source.CREDENTIALS_ENCRYPTION_KEY;\n if (credentialsKey) {\n entries.set(\"CREDENTIALS_ENCRYPTION_KEY\", credentialsKey);\n }\n }\n\n if (mode === \"worker\") {\n const worker = resolveWorkerRuntimeConfig(source);\n entries.set(\"PLATFORM_URL\", worker.platformUrl);\n entries.set(\"WORKER_INTERNAL_TOKEN\", worker.workerToken);\n entries.set(\n \"AI_GATEWAY_BASE_URL\",\n resolveLlmGatewayBaseUrl(worker.platformUrl, database.projectId),\n );\n }\n\n for (const [key, value] of Object.entries(projectDatabaseRuntimeEnv(database))) {\n entries.set(key, value);\n }\n\n return Object.fromEntries(entries);\n}\n\nexport function resolveLlmGatewayBaseUrl(platformUrl: string, projectId: string): string {\n return `${platformUrl.replace(/\\/$/, \"\")}/internal/projects/${encodeURIComponent(projectId)}/llm`;\n}\n\nexport function resolvePlatformWorkerToken(\n source: NodeJS.ProcessEnv = process.env,\n): string | undefined {\n const explicit = source.PLATFORM_WORKER_TOKEN?.trim() ?? source.WORKER_INTERNAL_TOKEN?.trim();\n if (explicit) {\n return explicit;\n }\n\n if (source.NODE_ENV === \"production\") {\n return undefined;\n }\n\n return DEV_PLATFORM_WORKER_TOKEN;\n}\n\n/** Platform API URL reachable from a project-server container. */\nconst DEV_PLATFORM_URL = \"http://localhost:3002\";\n\nexport function resolveWorkerPlatformUrl(\n source: NodeJS.ProcessEnv = process.env,\n): string | undefined {\n const raw =\n source.PLATFORM_URL?.trim() ??\n source.PUBLIC_PLATFORM_URL?.trim() ??\n (source.NODE_ENV === \"production\" ? undefined : DEV_PLATFORM_URL);\n if (!raw) {\n return undefined;\n }\n\n return rewriteLoopbackUrl(raw);\n}\n\n/** Rewrite loopback hosts so containers can reach Postgres and other host services. */\nexport function rewriteLoopbackHost(host: string): string {\n if (host === \"localhost\" || host === \"127.0.0.1\") {\n return \"host.docker.internal\";\n }\n\n return host;\n}\n\nexport function rewriteLoopbackUrl(url: string): string {\n try {\n const parsed = new URL(url);\n parsed.hostname = rewriteLoopbackHost(parsed.hostname);\n return parsed.toString().replace(/\\/$/, \"\");\n } catch {\n return url;\n }\n}\n\n/** dockerode expects `KEY=value` strings. */\nexport function formatDockerEnv(env: Record<string, string>): string[] {\n return Object.entries(env).map(([key, value]) => `${key}=${value}`);\n}\n\nexport function resolveProjectServerImage(\n env: NodeJS.ProcessEnv = process.env,\n override?: string,\n): string {\n return override ?? env.PROJECT_DOCKER_IMAGE ?? env.TENANT_DOCKER_IMAGE ?? PROJECT_SERVER_IMAGE;\n}\n","import { resolvePlatformWorkerToken, resolveWorkerPlatformUrl } from \"./runtime-env\";\n\n/** Resolved platform ↔ project-server worker auth contract. */\nexport type WorkerRuntimeConfig = {\n /** Platform API base URL reachable from the project-server runtime. */\n platformUrl: string;\n /** Bearer token for platform `/internal` routes. */\n workerToken: string;\n};\n\nexport class WorkerRuntimeConfigError extends Error {\n readonly missing: readonly string[];\n\n constructor(missing: string[]) {\n super(\n `Worker runtime config incomplete (missing: ${missing.join(\", \")}). ` +\n \"In production set PLATFORM_WORKER_TOKEN and PUBLIC_PLATFORM_URL (or PLATFORM_URL).\",\n );\n this.name = \"WorkerRuntimeConfigError\";\n this.missing = missing;\n }\n}\n\n/** Fail fast when the platform cannot start worker-mode project servers. */\nexport function resolveWorkerRuntimeConfig(\n source: NodeJS.ProcessEnv = process.env,\n): WorkerRuntimeConfig {\n const platformUrl = resolveWorkerPlatformUrl(source);\n const workerToken = resolvePlatformWorkerToken(source);\n const missing: string[] = [];\n\n if (!platformUrl) {\n missing.push(\"PUBLIC_PLATFORM_URL or PLATFORM_URL\");\n }\n\n if (!workerToken) {\n missing.push(\"PLATFORM_WORKER_TOKEN\");\n }\n\n if (missing.length > 0) {\n throw new WorkerRuntimeConfigError(missing);\n }\n\n return { platformUrl: platformUrl!, workerToken: workerToken! };\n}\n","import { PROJECT_SERVER_HEALTH } from \"./constants\";\n\nconst DEFAULT_TIMEOUT_MS = 120_000;\nconst DEFAULT_INTERVAL_MS = 500;\n\nexport type WaitForHealthOptions = {\n timeoutMs?: number;\n intervalMs?: number;\n fetchImpl?: typeof fetch;\n};\n\nexport async function waitForHealth(\n baseUrl: string,\n options: WaitForHealthOptions = {},\n): Promise<void> {\n const fetchImpl = options.fetchImpl ?? fetch;\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;\n const deadline = Date.now() + timeoutMs;\n const url = new URL(PROJECT_SERVER_HEALTH.path, baseUrl);\n\n while (Date.now() < deadline) {\n try {\n const response = await fetchImpl(url, { signal: AbortSignal.timeout(2_000) });\n if (response.ok) {\n return;\n }\n } catch {\n // retry until timeout\n }\n\n await sleep(intervalMs);\n }\n\n throw new Error(`Project server health check timed out after ${timeoutMs}ms (${url})`);\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n","import 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 DockerPluginOptions = HostingOptions & {\n docker?: Docker;\n};\n\nconst MINIO_DOCKER_NETWORK = process.env.MINIO_DOCKER_NETWORK ?? \"keystroke\";\n\nexport function createDockerRuntime(options: DockerPluginOptions = {}): ProjectRuntime {\n const docker = options.docker ?? new Docker();\n\n return {\n async start(input) {\n const launch = resolveRuntimeLaunch(options, input.artifactUrl, input.database);\n await ensureImage(docker, launch.image);\n await removeExistingContainer(docker, input.projectId);\n\n const container = await docker.createContainer({\n Image: launch.image,\n name: containerName(input.projectId),\n Labels: {\n \"keystroke.project.id\": input.projectId,\n \"keystroke.project.name\": input.name,\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 },\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 project 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}\n\nfunction containerName(projectId: string): string {\n return `keystroke-project-${projectId}`;\n}\n\nasync function removeExistingContainer(docker: Docker, projectId: string): Promise<void> {\n try {\n const existing = docker.getContainer(containerName(projectId));\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","/** 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"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/constants.ts","../src/runtime-env.ts","../src/worker-runtime-config.ts","../src/wait-for-health.ts","../src/docker/create-docker-runtime.ts","../src/docker/plugin.ts","../src/default-plugin.ts","../src/runtime-constants.ts","../src/ping-project.ts","../src/ping-project-target.ts"],"sourcesContent":["export const PROJECT_SERVER_IMAGE = \"keystroke/project-server:local\";\n\nexport const PROJECT_SERVER_PORT = 3000;\n\nexport const PROJECT_SERVER_ROOT = \"/app\";\n\nexport const PROJECT_SERVER_FRAMEWORK_ROOT = \"/opt/keystroke\";\n\nexport const PROJECT_SERVER_FRAMEWORK_NODE_MODULES = `${PROJECT_SERVER_FRAMEWORK_ROOT}/node_modules`;\n\nexport const PROJECT_SERVER_HEALTH = {\n path: \"/health\",\n port: PROJECT_SERVER_PORT,\n} as const;\n","import type { ProjectRuntimeDatabase } from \"./runtime\";\nimport {\n PROJECT_SERVER_FRAMEWORK_NODE_MODULES,\n PROJECT_SERVER_HEALTH,\n PROJECT_SERVER_IMAGE,\n PROJECT_SERVER_PORT,\n PROJECT_SERVER_ROOT,\n} from \"./constants\";\nimport type { HostingOptions } from \"./plugin\";\nimport { resolveWorkerRuntimeConfig } from \"./worker-runtime-config\";\n\nconst RUNTIME_ENV_KEYS = [\n \"BETTER_AUTH_SECRET\",\n \"PUBLIC_SERVER_URL\",\n \"PUBLIC_WEB_URL\",\n \"POSTGRES_SSL\",\n // Scheduler backend selector (forwarded so the container consumer matches the\n // platform producer).\n \"KEYSTROKE_SCHEDULER_MODULE\",\n] as const;\n\n/** Provider and gateway env forwarded from the platform into project servers. */\nconst PROVIDER_ENV_KEYS = [\n \"ANTHROPIC_API_KEY\",\n \"OPENAI_API_KEY\",\n \"AI_GATEWAY_API_KEY\",\n \"AI_GATEWAY_BASE_URL\",\n] as const;\n\n/** Shared dev token when `PLATFORM_WORKER_TOKEN` is unset (non-production only). */\nexport const DEV_PLATFORM_WORKER_TOKEN = \"dev-platform-worker-token\";\n\nexport const PROJECT_DATABASE_ENV_KEYS = [\n \"POSTGRES_HOST\",\n \"POSTGRES_PORT\",\n \"POSTGRES_DB\",\n \"POSTGRES_USER\",\n \"POSTGRES_PASSWORD\",\n \"DATABASE_SEARCH_PATH\",\n] as const;\n\nexport type RuntimeLaunchSpec = {\n image: string;\n env: Record<string, string>;\n port: number;\n health: typeof PROJECT_SERVER_HEALTH;\n};\n\n/** Per-project postgres env injected into every project server container/machine. */\nexport function projectDatabaseRuntimeEnv(\n database: ProjectRuntimeDatabase,\n): Record<(typeof PROJECT_DATABASE_ENV_KEYS)[number], string> {\n return {\n POSTGRES_HOST: rewriteLoopbackHost(database.host),\n POSTGRES_PORT: String(database.port),\n POSTGRES_DB: database.database,\n POSTGRES_USER: database.user,\n POSTGRES_PASSWORD: database.password,\n DATABASE_SEARCH_PATH: database.searchPath,\n };\n}\n\n/**\n * Builds the launch spec for a single project server. `artifactUrl` is the\n * per-project presigned URL to that deploy's built `dist` — there is no base\n * image, so it is required.\n */\nexport function resolveRuntimeLaunch(\n options: HostingOptions,\n artifactUrl: string,\n database: ProjectRuntimeDatabase,\n extraEnv?: Record<string, string>,\n): RuntimeLaunchSpec {\n if (!artifactUrl) {\n throw new Error(\"Cannot start a project server without an artifact URL\");\n }\n\n const env = options.env ?? process.env;\n\n return {\n image: resolveProjectServerImage(env, options.image),\n env: buildRuntimeEnv(env, artifactUrl, database, extraEnv),\n port: PROJECT_SERVER_PORT,\n health: PROJECT_SERVER_HEALTH,\n };\n}\n\n/** Env vars passed into the project server container/machine. */\nexport function buildRuntimeEnv(\n source: NodeJS.ProcessEnv,\n artifactUrl: string,\n database: ProjectRuntimeDatabase,\n extraEnv?: Record<string, string>,\n): Record<string, string> {\n const mode = source.KEYSTROKE_MODE ?? \"worker\";\n const entries = new Map<string, string>([\n // Container bind port — not derived from PUBLIC_SERVER_URL (external URL metadata).\n [\"PORT\", String(PROJECT_SERVER_PORT)],\n [\"KEYSTROKE_ROOT\", PROJECT_SERVER_ROOT],\n [\"KEYSTROKE_RUNTIME_NODE_MODULES\", PROJECT_SERVER_FRAMEWORK_NODE_MODULES],\n [\"ARTIFACT_URL\", artifactUrl],\n [\"KEYSTROKE_MODE\", mode],\n [\"PROJECT_ID\", database.projectId],\n [\"ORGANIZATION_ID\", database.organizationId],\n [\"TENANT_ID\", database.organizationId],\n ]);\n\n for (const key of RUNTIME_ENV_KEYS) {\n const value = source[key]?.trim();\n if (!value) {\n if (key === \"POSTGRES_SSL\") {\n throw new Error(\"POSTGRES_SSL is required\");\n }\n continue;\n }\n entries.set(key, value);\n }\n\n for (const key of PROVIDER_ENV_KEYS) {\n const value = source[key]?.trim();\n if (value) {\n entries.set(key, value);\n }\n }\n\n if (mode !== \"worker\") {\n const credentialsKey = source.CREDENTIALS_ENCRYPTION_KEY;\n if (credentialsKey) {\n entries.set(\"CREDENTIALS_ENCRYPTION_KEY\", credentialsKey);\n }\n }\n\n if (mode === \"worker\") {\n const worker = resolveWorkerRuntimeConfig(source);\n entries.set(\"PLATFORM_URL\", worker.platformUrl);\n entries.set(\"WORKER_INTERNAL_TOKEN\", worker.workerToken);\n }\n\n for (const [key, value] of Object.entries(projectDatabaseRuntimeEnv(database))) {\n entries.set(key, value);\n }\n\n if (extraEnv) {\n for (const [key, value] of Object.entries(extraEnv)) {\n entries.set(key, value);\n }\n }\n\n return Object.fromEntries(entries);\n}\n\nexport function resolvePlatformWorkerToken(\n source: NodeJS.ProcessEnv = process.env,\n): string | undefined {\n const explicit = source.PLATFORM_WORKER_TOKEN?.trim() ?? source.WORKER_INTERNAL_TOKEN?.trim();\n if (explicit) {\n return explicit;\n }\n\n if (source.NODE_ENV === \"production\") {\n return undefined;\n }\n\n return DEV_PLATFORM_WORKER_TOKEN;\n}\n\n/** Platform API URL reachable from a project-server container. */\nconst DEV_PLATFORM_URL = \"http://localhost:3002\";\n\nexport function resolveWorkerPlatformUrl(\n source: NodeJS.ProcessEnv = process.env,\n): string | undefined {\n const raw =\n source.PLATFORM_URL?.trim() ??\n source.PUBLIC_PLATFORM_URL?.trim() ??\n (source.NODE_ENV === \"production\" ? undefined : DEV_PLATFORM_URL);\n if (!raw) {\n return undefined;\n }\n\n return rewriteLoopbackUrl(raw);\n}\n\n/** Rewrite loopback hosts so containers can reach Postgres and other host services. */\nexport function rewriteLoopbackHost(host: string): string {\n if (host === \"localhost\" || host === \"127.0.0.1\") {\n return \"host.docker.internal\";\n }\n\n return host;\n}\n\nexport function rewriteLoopbackUrl(url: string): string {\n try {\n const parsed = new URL(url);\n parsed.hostname = rewriteLoopbackHost(parsed.hostname);\n return parsed.toString().replace(/\\/$/, \"\");\n } catch {\n return url;\n }\n}\n\n/** dockerode expects `KEY=value` strings. */\nexport function formatDockerEnv(env: Record<string, string>): string[] {\n return Object.entries(env).map(([key, value]) => `${key}=${value}`);\n}\n\nexport function resolveProjectServerImage(\n env: NodeJS.ProcessEnv = process.env,\n override?: string,\n): string {\n return override ?? env.PROJECT_DOCKER_IMAGE ?? env.TENANT_DOCKER_IMAGE ?? PROJECT_SERVER_IMAGE;\n}\n","import { resolvePlatformWorkerToken, resolveWorkerPlatformUrl } from \"./runtime-env\";\n\n/** Resolved platform ↔ project-server worker auth contract. */\nexport type WorkerRuntimeConfig = {\n /** Platform API base URL reachable from the project-server runtime. */\n platformUrl: string;\n /** Bearer token for platform `/internal` routes. */\n workerToken: string;\n};\n\nexport class WorkerRuntimeConfigError extends Error {\n readonly missing: readonly string[];\n\n constructor(missing: string[]) {\n super(\n `Worker runtime config incomplete (missing: ${missing.join(\", \")}). ` +\n \"In production set PLATFORM_WORKER_TOKEN and PUBLIC_PLATFORM_URL (or PLATFORM_URL).\",\n );\n this.name = \"WorkerRuntimeConfigError\";\n this.missing = missing;\n }\n}\n\n/** Fail fast when the platform cannot start worker-mode project servers. */\nexport function resolveWorkerRuntimeConfig(\n source: NodeJS.ProcessEnv = process.env,\n): WorkerRuntimeConfig {\n const platformUrl = resolveWorkerPlatformUrl(source);\n const workerToken = resolvePlatformWorkerToken(source);\n const missing: string[] = [];\n\n if (!platformUrl) {\n missing.push(\"PUBLIC_PLATFORM_URL or PLATFORM_URL\");\n }\n\n if (!workerToken) {\n missing.push(\"PLATFORM_WORKER_TOKEN\");\n }\n\n if (missing.length > 0) {\n throw new WorkerRuntimeConfigError(missing);\n }\n\n return { platformUrl: platformUrl!, workerToken: workerToken! };\n}\n","import { PROJECT_SERVER_HEALTH } from \"./constants\";\n\nconst DEFAULT_TIMEOUT_MS = 120_000;\nconst DEFAULT_INTERVAL_MS = 500;\n\nexport type WaitForHealthOptions = {\n timeoutMs?: number;\n intervalMs?: number;\n fetchImpl?: typeof fetch;\n};\n\nexport async function waitForHealth(\n baseUrl: string,\n options: WaitForHealthOptions = {},\n): Promise<void> {\n const fetchImpl = options.fetchImpl ?? fetch;\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;\n const deadline = Date.now() + timeoutMs;\n const url = new URL(PROJECT_SERVER_HEALTH.path, baseUrl);\n\n while (Date.now() < deadline) {\n try {\n const response = await fetchImpl(url, { signal: AbortSignal.timeout(2_000) });\n if (response.ok) {\n return;\n }\n } catch {\n // retry until timeout\n }\n\n await sleep(intervalMs);\n }\n\n throw new Error(`Project server health check timed out after ${timeoutMs}ms (${url})`);\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n","import 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 DockerPluginOptions = HostingOptions & {\n docker?: Docker;\n};\n\nconst MINIO_DOCKER_NETWORK = process.env.MINIO_DOCKER_NETWORK ?? \"keystroke\";\n\nexport function createDockerRuntime(options: DockerPluginOptions = {}): ProjectRuntime {\n const docker = options.docker ?? new Docker();\n\n return {\n async start(input) {\n const launch = resolveRuntimeLaunch(options, input.artifactUrl, input.database, input.env);\n await ensureImage(docker, launch.image);\n await removeExistingContainer(docker, input.projectId);\n\n const container = await docker.createContainer({\n Image: launch.image,\n name: containerName(input.projectId),\n Labels: {\n \"keystroke.project.id\": input.projectId,\n \"keystroke.project.name\": input.name,\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 },\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 project 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}\n\nfunction containerName(projectId: string): string {\n return `keystroke-project-${projectId}`;\n}\n\nasync function removeExistingContainer(docker: Docker, projectId: string): Promise<void> {\n try {\n const existing = docker.getContainer(containerName(projectId));\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","/** 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"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@keystrokehq/hosting",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/dallinbentley/keystroke.git",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"typescript": "^6.0.3",
|
|
38
38
|
"vitest": "^4.1.7",
|
|
39
39
|
"@keystrokehq/oxlint-config": "0.0.3",
|
|
40
|
-
"@keystrokehq/shared": "0.0.
|
|
40
|
+
"@keystrokehq/shared": "0.0.62",
|
|
41
41
|
"@keystrokehq/tsconfig": "0.0.3",
|
|
42
42
|
"@keystrokehq/tsdown-config": "0.0.3",
|
|
43
43
|
"@keystrokehq/vitest-config": "0.0.4"
|