@skaile/provider-k8s 0.1.0-beta.0 → 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/CHANGELOG.md CHANGED
@@ -1,5 +1,56 @@
1
1
  # @skaile/provider-k8s
2
2
 
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#54](https://github.com/skaile-ai/workspaces/pull/54) [`7bdaf11`](https://github.com/skaile-ai/workspaces/commit/7bdaf115c251136288a94a7f880b3a0f52145be8) Thanks [@mortegro](https://github.com/mortegro)! - Initial release.
8
+
9
+ Five plugins for `@skaile/workspaces`' `pluginRegistry`, each exporting
10
+ `register(pluginRegistry)`:
11
+
12
+ - `@skaile/provider-fly` — Fly Machines deploy target (`tlsTermination: edge`,
13
+ `buildStrategy: remote`).
14
+ - `@skaile/provider-vercel-sandbox` — Vercel Sandbox deploy target
15
+ (`tlsTermination: edge`, `buildStrategy: remote`).
16
+ - `@skaile/provider-k8s` — Kubernetes deploy target, ClusterIP + port-forward
17
+ only in 0.1.0 (`tlsTermination: edge`, `buildStrategy: local`).
18
+ - `@skaile/connector-redis` — Redis connector (deps `ioredis`).
19
+ - `@skaile/connector-yjs` — Yjs CRDT connector (deps `yjs`).
20
+ </content>
21
+
22
+ ### Patch Changes
23
+
24
+ - [`ce3f33f`](https://github.com/skaile-ai/workspaces/commit/ce3f33f5b85f81d1c80689a99a8e63cf0c2afc07) Thanks [@mortegro](https://github.com/mortegro)! - Fix deploy-handle readiness timeout + dedupe deploy-target helpers.
25
+
26
+ - **Fix:** `DeployHandle.waitReady(timeoutMs)` silently ignored its `timeoutMs`
27
+ argument in the fly / k8s / vercel-sandbox targets, always using a hard-coded
28
+ internal deadline. The caller's timeout is now honored.
29
+ - **Fix (`@skaile/provider-fly`):** `deleteMachine` swallowed _all_ errors while
30
+ only intending to ignore a 404, so `stop()` could falsely report success on a
31
+ still-running (still-billing) machine. Non-404 errors now propagate.
32
+ - **Added (`@skaile/workspaces/plugin-registry`):** shared deploy helpers —
33
+ `buildStrategySchema` / `BuildStrategy`, abort-aware `sleep`, generic
34
+ `pollUntil`, and the `makeDeployHandle` factory — replacing the per-provider
35
+ copies of the sleep loop, handle wrapper, and build-strategy enum.
36
+
37
+ ## 0.1.0-beta.1
38
+
39
+ ### Patch Changes
40
+
41
+ - [`ce3f33f`](https://github.com/skaile-ai/workspaces/commit/ce3f33f5b85f81d1c80689a99a8e63cf0c2afc07) Thanks [@mortegro](https://github.com/mortegro)! - Fix deploy-handle readiness timeout + dedupe deploy-target helpers.
42
+
43
+ - **Fix:** `DeployHandle.waitReady(timeoutMs)` silently ignored its `timeoutMs`
44
+ argument in the fly / k8s / vercel-sandbox targets, always using a hard-coded
45
+ internal deadline. The caller's timeout is now honored.
46
+ - **Fix (`@skaile/provider-fly`):** `deleteMachine` swallowed _all_ errors while
47
+ only intending to ignore a 404, so `stop()` could falsely report success on a
48
+ still-running (still-billing) machine. Non-404 errors now propagate.
49
+ - **Added (`@skaile/workspaces/plugin-registry`):** shared deploy helpers —
50
+ `buildStrategySchema` / `BuildStrategy`, abort-aware `sleep`, generic
51
+ `pollUntil`, and the `makeDeployHandle` factory — replacing the per-provider
52
+ copies of the sleep loop, handle wrapper, and build-strategy enum.
53
+
3
54
  ## 0.1.0-beta.0
4
55
 
5
56
  ### Minor Changes
package/dist/index.js CHANGED
@@ -1,4 +1,9 @@
1
1
  // src/target.ts
2
+ import {
3
+ buildStrategySchema,
4
+ makeDeployHandle,
5
+ pollUntil
6
+ } from "@skaile/workspaces/plugin-registry";
2
7
  import * as z from "zod";
3
8
 
4
9
  // src/sdk-types.ts
@@ -23,7 +28,7 @@ var k8sConfigSchema = z.object({
23
28
  // are documented as future work.
24
29
  serviceType: z.literal("ClusterIP").default("ClusterIP"),
25
30
  localForwardPort: z.number().int().positive().optional(),
26
- buildStrategy: z.enum(["local", "remote", "managed"]).default("local")
31
+ buildStrategy: buildStrategySchema.default("local")
27
32
  }).strict();
28
33
  var k8sPayloadSchema = z.object({
29
34
  namespace: z.string(),
@@ -33,20 +38,6 @@ var k8sPayloadSchema = z.object({
33
38
  });
34
39
  var DEFAULT_READY_TIMEOUT_MS = 12e4;
35
40
  var DEFAULT_POLL_INTERVAL_MS = 1e3;
36
- async function sleep(ms, signal) {
37
- return await new Promise((resolve, reject) => {
38
- const timer = setTimeout(() => {
39
- signal.removeEventListener("abort", onAbort);
40
- resolve();
41
- }, ms);
42
- const onAbort = () => {
43
- clearTimeout(timer);
44
- reject(new Error("aborted while polling pod phase"));
45
- };
46
- if (signal.aborted) onAbort();
47
- else signal.addEventListener("abort", onAbort);
48
- });
49
- }
50
41
  async function makeApi(loadSdk) {
51
42
  const k8s = await loadSdk();
52
43
  const kc = new k8s.KubeConfig();
@@ -54,26 +45,15 @@ async function makeApi(loadSdk) {
54
45
  return kc.makeApiClient(k8s.CoreV1Api);
55
46
  }
56
47
  function buildHandle(api, payload, config, ctx, ready, initialState) {
57
- let state = initialState;
58
48
  const forwardPort = payload.localForwardPort ?? config.port;
59
- return {
49
+ return makeDeployHandle({
60
50
  // Reached over a local port-forward tunnel to the ClusterIP service.
61
51
  wsUrl: `ws://127.0.0.1:${forwardPort}`,
62
- wsAuth: ctx.wsAuth ? "<redacted>" : void 0,
63
- get state() {
64
- return state;
65
- },
52
+ ctx,
66
53
  payload,
67
- async waitReady(timeoutMs) {
68
- try {
69
- await ready();
70
- state = "ready";
71
- } catch (err) {
72
- state = "errored";
73
- throw err;
74
- }
75
- void timeoutMs;
76
- },
54
+ initialState,
55
+ defaultReadyTimeoutMs: DEFAULT_READY_TIMEOUT_MS,
56
+ ready,
77
57
  async health() {
78
58
  try {
79
59
  const pod = await api.readNamespacedPod({
@@ -87,14 +67,12 @@ function buildHandle(api, payload, config, ctx, ready, initialState) {
87
67
  }
88
68
  },
89
69
  async stop() {
90
- state = "stopping";
91
70
  await api.deleteNamespacedService({ name: payload.serviceName, namespace: payload.namespace }).catch(() => {
92
71
  });
93
72
  await api.deleteNamespacedPod({ name: payload.podName, namespace: payload.namespace }).catch(() => {
94
73
  });
95
- state = "stopped";
96
74
  }
97
- };
75
+ });
98
76
  }
99
77
  function makeK8sDeployTarget(opts = {}) {
100
78
  const loadSdk = opts.loadSdk ?? defaultSdkLoader;
@@ -146,17 +124,13 @@ function makeK8sDeployTarget(opts = {}) {
146
124
  serviceName,
147
125
  localForwardPort: config.localForwardPort
148
126
  };
149
- const ready = async () => {
150
- const deadline = Date.now() + DEFAULT_READY_TIMEOUT_MS;
151
- for (; ; ) {
152
- const pod = await api.readNamespacedPod({ name: podName, namespace: config.namespace });
153
- if (pod.status?.phase === "Running") return;
154
- if (Date.now() > deadline) {
155
- throw new Error(`timed out waiting for pod ${podName} to reach Running`);
156
- }
157
- await sleep(pollIntervalMs, ctx.signal);
158
- }
159
- };
127
+ const ready = (timeoutMs, signal) => pollUntil({
128
+ check: async () => (await api.readNamespacedPod({ name: podName, namespace: config.namespace })).status?.phase === "Running",
129
+ timeoutMs,
130
+ intervalMs: pollIntervalMs,
131
+ signal,
132
+ timeoutMessage: `timed out waiting for pod ${podName} to reach Running`
133
+ });
160
134
  return buildHandle(api, payload, config, ctx, ready, "starting");
161
135
  },
162
136
  async restore(payload, ctx) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/target.ts","../src/sdk-types.ts","../src/index.ts"],"sourcesContent":["/**\n * `k8sDeployTarget` — deploy a workspace as a Pod + ClusterIP Service in\n * Kubernetes, reached via `kubectl port-forward`. `tlsTermination: \"edge\"`\n * (TLS terminates at the cluster ingress; v0.1.0 reaches the service over a\n * local port-forward tunnel).\n *\n * `buildStrategy` defaults to `local` — k8s is bring-your-own-image, so the\n * operator supplies `config.image`. `@kubernetes/client-node` is an *optional*\n * peer, lazy-loaded inside `create()`/`restore()` via an injectable loader so\n * the package builds standalone and the test stays hermetic.\n */\n\nimport type { DeployContext, DeployHandle, DeployTarget } from \"@skaile/workspaces/plugin-registry\";\nimport * as z from \"zod\";\nimport { type CoreV1Api, defaultSdkLoader, type SdkLoader } from \"./sdk-types.js\";\n\nconst k8sConfigSchema = z\n .object({\n namespace: z.string().default(\"default\"),\n image: z.string().min(1),\n port: z.number().int().positive().default(8080),\n serviceAccount: z.string().optional(),\n env: z.record(z.string(), z.string()).default({}),\n // v0.1.0 ships ClusterIP + port-forward only; NodePort/LoadBalancer/Ingress\n // are documented as future work.\n serviceType: z.literal(\"ClusterIP\").default(\"ClusterIP\"),\n localForwardPort: z.number().int().positive().optional(),\n buildStrategy: z.enum([\"local\", \"remote\", \"managed\"]).default(\"local\"),\n })\n .strict();\n\ntype K8sConfig = z.infer<typeof k8sConfigSchema>;\n\nconst k8sPayloadSchema = z.object({\n namespace: z.string(),\n podName: z.string(),\n serviceName: z.string(),\n localForwardPort: z.number().optional(),\n});\n\ntype K8sPayload = z.infer<typeof k8sPayloadSchema>;\n\nconst DEFAULT_READY_TIMEOUT_MS = 120_000;\nconst DEFAULT_POLL_INTERVAL_MS = 1_000;\n\n/** Injectable seams so tests never load the real `@kubernetes/client-node`. */\nexport interface K8sTargetOptions {\n loadSdk?: SdkLoader;\n pollIntervalMs?: number;\n}\n\nasync function sleep(ms: number, signal: AbortSignal): Promise<void> {\n return await new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => {\n signal.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n const onAbort = () => {\n clearTimeout(timer);\n reject(new Error(\"aborted while polling pod phase\"));\n };\n if (signal.aborted) onAbort();\n else signal.addEventListener(\"abort\", onAbort);\n });\n}\n\nasync function makeApi(loadSdk: SdkLoader): Promise<CoreV1Api> {\n const k8s = await loadSdk();\n const kc = new k8s.KubeConfig();\n kc.loadFromDefault();\n return kc.makeApiClient(k8s.CoreV1Api);\n}\n\nfunction buildHandle(\n api: CoreV1Api,\n payload: K8sPayload,\n config: Pick<K8sConfig, \"port\">,\n ctx: DeployContext,\n ready: () => Promise<void>,\n initialState: DeployHandle[\"state\"],\n): DeployHandle<K8sPayload> {\n let state: DeployHandle[\"state\"] = initialState;\n const forwardPort = payload.localForwardPort ?? config.port;\n return {\n // Reached over a local port-forward tunnel to the ClusterIP service.\n wsUrl: `ws://127.0.0.1:${forwardPort}`,\n wsAuth: ctx.wsAuth ? \"<redacted>\" : undefined,\n get state() {\n return state;\n },\n payload,\n async waitReady(timeoutMs?: number) {\n try {\n await ready();\n state = \"ready\";\n } catch (err) {\n state = \"errored\";\n throw err;\n }\n void timeoutMs;\n },\n async health() {\n try {\n const pod = await api.readNamespacedPod({\n name: payload.podName,\n namespace: payload.namespace,\n });\n const healthy = pod.status?.phase === \"Running\";\n return healthy\n ? { healthy }\n : { healthy, detail: `pod ${payload.podName} phase=${pod.status?.phase ?? \"unknown\"}` };\n } catch (err) {\n return { healthy: false, detail: (err as Error).message };\n }\n },\n async stop() {\n state = \"stopping\";\n // Idempotent teardown: ignore 404s when the objects are already gone.\n await api\n .deleteNamespacedService({ name: payload.serviceName, namespace: payload.namespace })\n .catch(() => {});\n await api\n .deleteNamespacedPod({ name: payload.podName, namespace: payload.namespace })\n .catch(() => {});\n state = \"stopped\";\n },\n };\n}\n\n/** Build the Kubernetes deploy target with optional injected seams. */\nexport function makeK8sDeployTarget(\n opts: K8sTargetOptions = {},\n): DeployTarget<K8sConfig, K8sPayload> {\n const loadSdk = opts.loadSdk ?? defaultSdkLoader;\n const pollIntervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n\n return {\n id: \"k8s\",\n displayName: \"Kubernetes (ClusterIP + port-forward)\",\n apiVersion: 1,\n tlsTermination: \"edge\",\n configSchema: k8sConfigSchema as unknown as z.ZodType<K8sConfig>,\n payloadSchema: k8sPayloadSchema,\n\n async create(config, ctx) {\n const api = await makeApi(loadSdk);\n const podName = `skaile-ws-${ctx.workspaceId}`;\n const serviceName = `skaile-ws-${ctx.workspaceId}-svc`;\n const image = config.image ?? ctx.imageRef;\n if (!image) throw new Error(\"k8s deploy requires an image (buildStrategy is local)\");\n\n const podBody = {\n metadata: { name: podName, namespace: config.namespace, labels: { app: podName } },\n spec: {\n serviceAccountName: config.serviceAccount,\n containers: [\n {\n name: \"workspace\",\n image,\n ports: [{ containerPort: config.port }],\n env: Object.entries({ ...config.env, ...ctx.env }).map(([name, value]) => ({\n name,\n value,\n })),\n },\n ],\n },\n };\n await api.createNamespacedPod({ namespace: config.namespace, body: podBody });\n\n const serviceBody = {\n metadata: { name: serviceName, namespace: config.namespace },\n spec: {\n type: config.serviceType,\n selector: { app: podName },\n ports: [{ port: config.port, targetPort: config.port }],\n },\n };\n await api.createNamespacedService({ namespace: config.namespace, body: serviceBody });\n ctx.log.info(\"k8s pod + service created\", { podName, serviceName });\n\n const payload: K8sPayload = {\n namespace: config.namespace,\n podName,\n serviceName,\n localForwardPort: config.localForwardPort,\n };\n const ready = async () => {\n const deadline = Date.now() + DEFAULT_READY_TIMEOUT_MS;\n for (;;) {\n const pod = await api.readNamespacedPod({ name: podName, namespace: config.namespace });\n if (pod.status?.phase === \"Running\") return;\n if (Date.now() > deadline) {\n throw new Error(`timed out waiting for pod ${podName} to reach Running`);\n }\n await sleep(pollIntervalMs, ctx.signal);\n }\n };\n return buildHandle(api, payload, config, ctx, ready, \"starting\");\n },\n\n async restore(payload, ctx) {\n const api = await makeApi(loadSdk);\n const pod = await api.readNamespacedPod({\n name: payload.podName,\n namespace: payload.namespace,\n });\n if (pod.status?.phase !== \"Running\") {\n throw new Error(\n `k8s pod ${payload.podName} is not running (phase=${pod.status?.phase ?? \"unknown\"})`,\n );\n }\n ctx.log.info(\"re-attached to k8s pod\", { podName: payload.podName });\n return buildHandle(api, payload, { port: 8080 }, ctx, async () => {}, \"ready\");\n },\n };\n}\n\n/** Default Kubernetes deploy target registered by `register()`. */\nexport const k8sDeployTarget = makeK8sDeployTarget();\n","/**\n * Minimal local interface for `@kubernetes/client-node` — only the members this\n * provider actually calls.\n *\n * The SDK is an *optional* peer dependency, lazy-loaded via a string-literal\n * dynamic `import()`. Typing against this minimal surface (rather than the real\n * package's types) keeps the package building + typechecking standalone\n * offline; the host project supplies the real SDK at runtime.\n */\n\n/** A `V1Pod`-ish object (subset we read). */\nexport interface K8sPod {\n metadata?: { name?: string; namespace?: string };\n status?: { phase?: string };\n}\n\n/** A `V1Service`-ish object (subset we read). */\nexport interface K8sService {\n metadata?: { name?: string; namespace?: string };\n}\n\n/** `CoreV1Api` subset. */\nexport interface CoreV1Api {\n createNamespacedPod(arg: { namespace: string; body: unknown }): Promise<K8sPod>;\n readNamespacedPod(arg: { name: string; namespace: string }): Promise<K8sPod>;\n deleteNamespacedPod(arg: { name: string; namespace: string }): Promise<unknown>;\n createNamespacedService(arg: { namespace: string; body: unknown }): Promise<K8sService>;\n deleteNamespacedService(arg: { name: string; namespace: string }): Promise<unknown>;\n}\n\n/** `KubeConfig` subset. */\nexport interface KubeConfig {\n loadFromDefault(): void;\n // biome-ignore lint/suspicious/noExplicitAny: api ctor passed through opaquely\n makeApiClient(api: any): CoreV1Api;\n}\n\n/** The `@kubernetes/client-node` module surface we depend on. */\nexport interface K8sModule {\n KubeConfig: new () => KubeConfig;\n CoreV1Api: unknown;\n}\n\n/** Loader seam: returns the SDK module. Default does a real dynamic import. */\nexport type SdkLoader = () => Promise<K8sModule>;\n\n/**\n * Default loader: lazy `import(\"@kubernetes/client-node\")`. The string literal\n * keeps bundlers from eagerly resolving the optional peer; only evaluated when\n * `create()`/`restore()` run.\n */\nexport const defaultSdkLoader: SdkLoader = async () => {\n const mod = (await import(\"@kubernetes/client-node\" as string)) as unknown as K8sModule;\n if (!mod?.KubeConfig) {\n throw new Error(\n \"@kubernetes/client-node is not installed. Run `skaile plugin install @skaile/provider-k8s` and add @kubernetes/client-node to your project.\",\n );\n }\n return mod;\n};\n","/**\n * `@skaile/provider-k8s` — Kubernetes deploy target for `@skaile/workspaces`.\n *\n * v0.1.0 ships ClusterIP + port-forward only. Requires the optional peer\n * `@kubernetes/client-node` to be installed in the host project (lazy-loaded at\n * runtime). Install via `skaile plugin install @skaile/provider-k8s`.\n */\n\nimport type { PluginRegistry } from \"@skaile/workspaces/plugin-registry\";\nimport { k8sDeployTarget } from \"./target.js\";\n\nexport { k8sDeployTarget, makeK8sDeployTarget } from \"./target.js\";\nexport type { K8sTargetOptions } from \"./target.js\";\nexport type { CoreV1Api, K8sModule, KubeConfig, SdkLoader } from \"./sdk-types.js\";\n\n/** Register this plugin's deploy target with a registry. */\nexport function register(registry: PluginRegistry): void {\n registry.register(\"deployTarget\", k8sDeployTarget);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/target.ts","../src/sdk-types.ts","../src/index.ts"],"sourcesContent":["/**\n * `k8sDeployTarget` — deploy a workspace as a Pod + ClusterIP Service in\n * Kubernetes, reached via `kubectl port-forward`. `tlsTermination: \"edge\"`\n * (TLS terminates at the cluster ingress; v0.1.0 reaches the service over a\n * local port-forward tunnel).\n *\n * `buildStrategy` defaults to `local` — k8s is bring-your-own-image, so the\n * operator supplies `config.image`. `@kubernetes/client-node` is an *optional*\n * peer, lazy-loaded inside `create()`/`restore()` via an injectable loader so\n * the package builds standalone and the test stays hermetic.\n */\n\nimport {\n buildStrategySchema,\n type DeployContext,\n type DeployHandle,\n type DeployTarget,\n makeDeployHandle,\n pollUntil,\n} from \"@skaile/workspaces/plugin-registry\";\nimport * as z from \"zod\";\nimport { type CoreV1Api, defaultSdkLoader, type SdkLoader } from \"./sdk-types.js\";\n\nconst k8sConfigSchema = z\n .object({\n namespace: z.string().default(\"default\"),\n image: z.string().min(1),\n port: z.number().int().positive().default(8080),\n serviceAccount: z.string().optional(),\n env: z.record(z.string(), z.string()).default({}),\n // v0.1.0 ships ClusterIP + port-forward only; NodePort/LoadBalancer/Ingress\n // are documented as future work.\n serviceType: z.literal(\"ClusterIP\").default(\"ClusterIP\"),\n localForwardPort: z.number().int().positive().optional(),\n buildStrategy: buildStrategySchema.default(\"local\"),\n })\n .strict();\n\ntype K8sConfig = z.infer<typeof k8sConfigSchema>;\n\nconst k8sPayloadSchema = z.object({\n namespace: z.string(),\n podName: z.string(),\n serviceName: z.string(),\n localForwardPort: z.number().optional(),\n});\n\ntype K8sPayload = z.infer<typeof k8sPayloadSchema>;\n\nconst DEFAULT_READY_TIMEOUT_MS = 120_000;\nconst DEFAULT_POLL_INTERVAL_MS = 1_000;\n\n/** Injectable seams so tests never load the real `@kubernetes/client-node`. */\nexport interface K8sTargetOptions {\n loadSdk?: SdkLoader;\n pollIntervalMs?: number;\n}\n\nasync function makeApi(loadSdk: SdkLoader): Promise<CoreV1Api> {\n const k8s = await loadSdk();\n const kc = new k8s.KubeConfig();\n kc.loadFromDefault();\n return kc.makeApiClient(k8s.CoreV1Api);\n}\n\n/**\n * Build the per-target seams `makeDeployHandle` drives. `health` reads the pod\n * phase; `stop` does idempotent teardown only — the factory writes state.\n */\nfunction buildHandle(\n api: CoreV1Api,\n payload: K8sPayload,\n config: Pick<K8sConfig, \"port\">,\n ctx: DeployContext,\n ready: (timeoutMs: number, signal: AbortSignal) => Promise<void>,\n initialState: DeployHandle[\"state\"],\n): DeployHandle<K8sPayload> {\n const forwardPort = payload.localForwardPort ?? config.port;\n return makeDeployHandle<K8sPayload>({\n // Reached over a local port-forward tunnel to the ClusterIP service.\n wsUrl: `ws://127.0.0.1:${forwardPort}`,\n ctx,\n payload,\n initialState,\n defaultReadyTimeoutMs: DEFAULT_READY_TIMEOUT_MS,\n ready,\n async health() {\n try {\n const pod = await api.readNamespacedPod({\n name: payload.podName,\n namespace: payload.namespace,\n });\n const healthy = pod.status?.phase === \"Running\";\n return healthy\n ? { healthy }\n : { healthy, detail: `pod ${payload.podName} phase=${pod.status?.phase ?? \"unknown\"}` };\n } catch (err) {\n return { healthy: false, detail: (err as Error).message };\n }\n },\n async stop() {\n // Idempotent teardown: ignore 404s when the objects are already gone.\n await api\n .deleteNamespacedService({ name: payload.serviceName, namespace: payload.namespace })\n .catch(() => {});\n await api\n .deleteNamespacedPod({ name: payload.podName, namespace: payload.namespace })\n .catch(() => {});\n },\n });\n}\n\n/** Build the Kubernetes deploy target with optional injected seams. */\nexport function makeK8sDeployTarget(\n opts: K8sTargetOptions = {},\n): DeployTarget<K8sConfig, K8sPayload> {\n const loadSdk = opts.loadSdk ?? defaultSdkLoader;\n const pollIntervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n\n return {\n id: \"k8s\",\n displayName: \"Kubernetes (ClusterIP + port-forward)\",\n apiVersion: 1,\n tlsTermination: \"edge\",\n configSchema: k8sConfigSchema as unknown as z.ZodType<K8sConfig>,\n payloadSchema: k8sPayloadSchema,\n\n async create(config, ctx) {\n const api = await makeApi(loadSdk);\n const podName = `skaile-ws-${ctx.workspaceId}`;\n const serviceName = `skaile-ws-${ctx.workspaceId}-svc`;\n const image = config.image ?? ctx.imageRef;\n if (!image) throw new Error(\"k8s deploy requires an image (buildStrategy is local)\");\n\n const podBody = {\n metadata: { name: podName, namespace: config.namespace, labels: { app: podName } },\n spec: {\n serviceAccountName: config.serviceAccount,\n containers: [\n {\n name: \"workspace\",\n image,\n ports: [{ containerPort: config.port }],\n env: Object.entries({ ...config.env, ...ctx.env }).map(([name, value]) => ({\n name,\n value,\n })),\n },\n ],\n },\n };\n await api.createNamespacedPod({ namespace: config.namespace, body: podBody });\n\n const serviceBody = {\n metadata: { name: serviceName, namespace: config.namespace },\n spec: {\n type: config.serviceType,\n selector: { app: podName },\n ports: [{ port: config.port, targetPort: config.port }],\n },\n };\n await api.createNamespacedService({ namespace: config.namespace, body: serviceBody });\n ctx.log.info(\"k8s pod + service created\", { podName, serviceName });\n\n const payload: K8sPayload = {\n namespace: config.namespace,\n podName,\n serviceName,\n localForwardPort: config.localForwardPort,\n };\n const ready = (timeoutMs: number, signal: AbortSignal) =>\n pollUntil({\n check: async () =>\n (await api.readNamespacedPod({ name: podName, namespace: config.namespace })).status\n ?.phase === \"Running\",\n timeoutMs,\n intervalMs: pollIntervalMs,\n signal,\n timeoutMessage: `timed out waiting for pod ${podName} to reach Running`,\n });\n return buildHandle(api, payload, config, ctx, ready, \"starting\");\n },\n\n async restore(payload, ctx) {\n const api = await makeApi(loadSdk);\n const pod = await api.readNamespacedPod({\n name: payload.podName,\n namespace: payload.namespace,\n });\n if (pod.status?.phase !== \"Running\") {\n throw new Error(\n `k8s pod ${payload.podName} is not running (phase=${pod.status?.phase ?? \"unknown\"})`,\n );\n }\n ctx.log.info(\"re-attached to k8s pod\", { podName: payload.podName });\n return buildHandle(api, payload, { port: 8080 }, ctx, async () => {}, \"ready\");\n },\n };\n}\n\n/** Default Kubernetes deploy target registered by `register()`. */\nexport const k8sDeployTarget = makeK8sDeployTarget();\n","/**\n * Minimal local interface for `@kubernetes/client-node` — only the members this\n * provider actually calls.\n *\n * The SDK is an *optional* peer dependency, lazy-loaded via a string-literal\n * dynamic `import()`. Typing against this minimal surface (rather than the real\n * package's types) keeps the package building + typechecking standalone\n * offline; the host project supplies the real SDK at runtime.\n */\n\n/** A `V1Pod`-ish object (subset we read). */\nexport interface K8sPod {\n metadata?: { name?: string; namespace?: string };\n status?: { phase?: string };\n}\n\n/** A `V1Service`-ish object (subset we read). */\nexport interface K8sService {\n metadata?: { name?: string; namespace?: string };\n}\n\n/** `CoreV1Api` subset. */\nexport interface CoreV1Api {\n createNamespacedPod(arg: { namespace: string; body: unknown }): Promise<K8sPod>;\n readNamespacedPod(arg: { name: string; namespace: string }): Promise<K8sPod>;\n deleteNamespacedPod(arg: { name: string; namespace: string }): Promise<unknown>;\n createNamespacedService(arg: { namespace: string; body: unknown }): Promise<K8sService>;\n deleteNamespacedService(arg: { name: string; namespace: string }): Promise<unknown>;\n}\n\n/** `KubeConfig` subset. */\nexport interface KubeConfig {\n loadFromDefault(): void;\n // biome-ignore lint/suspicious/noExplicitAny: api ctor passed through opaquely\n makeApiClient(api: any): CoreV1Api;\n}\n\n/** The `@kubernetes/client-node` module surface we depend on. */\nexport interface K8sModule {\n KubeConfig: new () => KubeConfig;\n CoreV1Api: unknown;\n}\n\n/** Loader seam: returns the SDK module. Default does a real dynamic import. */\nexport type SdkLoader = () => Promise<K8sModule>;\n\n/**\n * Default loader: lazy `import(\"@kubernetes/client-node\")`. The string literal\n * keeps bundlers from eagerly resolving the optional peer; only evaluated when\n * `create()`/`restore()` run.\n */\nexport const defaultSdkLoader: SdkLoader = async () => {\n const mod = (await import(\"@kubernetes/client-node\" as string)) as unknown as K8sModule;\n if (!mod?.KubeConfig) {\n throw new Error(\n \"@kubernetes/client-node is not installed. Run `skaile plugin install @skaile/provider-k8s` and add @kubernetes/client-node to your project.\",\n );\n }\n return mod;\n};\n","/**\n * `@skaile/provider-k8s` — Kubernetes deploy target for `@skaile/workspaces`.\n *\n * v0.1.0 ships ClusterIP + port-forward only. Requires the optional peer\n * `@kubernetes/client-node` to be installed in the host project (lazy-loaded at\n * runtime). Install via `skaile plugin install @skaile/provider-k8s`.\n */\n\nimport type { PluginRegistry } from \"@skaile/workspaces/plugin-registry\";\nimport { k8sDeployTarget } from \"./target.js\";\n\nexport { k8sDeployTarget, makeK8sDeployTarget } from \"./target.js\";\nexport type { K8sTargetOptions } from \"./target.js\";\nexport type { CoreV1Api, K8sModule, KubeConfig, SdkLoader } from \"./sdk-types.js\";\n\n/** Register this plugin's deploy target with a registry. */\nexport function register(registry: PluginRegistry): void {\n registry.register(\"deployTarget\", k8sDeployTarget);\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skaile/provider-k8s",
3
- "version": "0.1.0-beta.0",
3
+ "version": "0.1.0",
4
4
  "description": "Kubernetes (ClusterIP + port-forward) deploy target for @skaile/workspaces",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -31,7 +31,7 @@
31
31
  }
32
32
  },
33
33
  "devDependencies": {
34
- "@skaile/workspaces": "^0.22.0-beta.0"
34
+ "@skaile/workspaces": "^0.22.0"
35
35
  },
36
36
  "scripts": {
37
37
  "build": "tsup",