@kici-dev/compiler 0.1.26 → 0.1.27
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/cli-banner.d.ts +6 -3
- package/dist/cli-banner.js +13 -5
- package/dist/cli.js +66 -43
- package/dist/commands/check-mode.d.ts +1 -1
- package/dist/commands/compile.js +3 -3
- package/dist/commands/index.d.ts +5 -1
- package/dist/commands/index.js +6 -3
- package/dist/commands/init.js +1 -1
- package/dist/commands/local-trust-root.d.ts +6 -0
- package/dist/commands/local-trust-root.js +52 -0
- package/dist/commands/local.d.ts +39 -0
- package/dist/commands/local.js +137 -0
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +40 -0
- package/dist/commands/logout.js +7 -0
- package/dist/commands/preview.js +1 -1
- package/dist/commands/run-banner.d.ts +31 -0
- package/dist/commands/run-banner.js +25 -0
- package/dist/commands/run-routed.d.ts +53 -0
- package/dist/commands/run-routed.js +176 -0
- package/dist/commands/run.d.ts +0 -17
- package/dist/commands/run.js +7 -50
- package/dist/commands/secrets-list.d.ts +2 -2
- package/dist/commands/secrets-list.js +4 -4
- package/dist/commands/types.d.ts +3 -3
- package/dist/commands/types.js +4 -4
- package/dist/execution/executor.js +7 -7
- package/dist/generators/secrets-dts.d.ts +9 -9
- package/dist/generators/secrets-dts.js +12 -12
- package/dist/llm-context/llms-architecture.txt +2 -2
- package/dist/llm-context/llms-cli.txt +674 -340
- package/dist/llm-context/llms-features.txt +334 -336
- package/dist/llm-context/llms-full.txt +1207 -892
- package/dist/llm-context/llms-getting-started.txt +7 -15
- package/dist/llm-context/llms-patterns.txt +5 -23
- package/dist/llm-context/llms-providers.txt +10 -1
- package/dist/llm-context/llms-sdk.txt +12 -12
- package/dist/llm-context/llms.txt +5 -5
- package/dist/local-executor/secret-loader.d.ts +1 -1
- package/dist/local-plane/orchestrator-process.d.ts +65 -0
- package/dist/local-plane/orchestrator-process.js +115 -0
- package/dist/local-plane/paths.d.ts +39 -0
- package/dist/local-plane/paths.js +54 -0
- package/dist/local-plane/plane-manager.d.ts +151 -0
- package/dist/local-plane/plane-manager.js +340 -0
- package/dist/local-plane/plane-seed.d.ts +76 -0
- package/dist/local-plane/plane-seed.js +59 -0
- package/dist/local-plane/plane-trigger.d.ts +62 -0
- package/dist/local-plane/plane-trigger.js +97 -0
- package/dist/local-plane/platform-attach.d.ts +53 -0
- package/dist/local-plane/platform-attach.js +111 -0
- package/dist/local-plane/postgres.d.ts +44 -0
- package/dist/local-plane/postgres.js +165 -0
- package/dist/local-plane/resolve-plane.d.ts +37 -0
- package/dist/local-plane/resolve-plane.js +76 -0
- package/dist/local-plane/run-follow.d.ts +41 -0
- package/dist/local-plane/run-follow.js +104 -0
- package/dist/local-plane/scaler-config.d.ts +54 -0
- package/dist/local-plane/scaler-config.js +114 -0
- package/dist/local-plane/secret-seed.d.ts +59 -0
- package/dist/local-plane/secret-seed.js +112 -0
- package/dist/local-plane/source-provider.d.ts +43 -0
- package/dist/local-plane/source-provider.js +165 -0
- package/dist/local-plane/trusted-routing.d.ts +32 -0
- package/dist/local-plane/trusted-routing.js +47 -0
- package/dist/lockfile/generator.js +7 -7
- package/dist/remote/dashboard-client.d.ts +7 -7
- package/dist/remote/dashboard-client.js +7 -7
- package/dist/remote/platform-client.d.ts +1 -1
- package/dist/remote/secret-loader.d.ts +18 -0
- package/dist/{local-executor → remote}/secret-loader.js +4 -4
- package/dist/remote/secret-upload.d.ts +1 -1
- package/dist/remote/secret-upload.js +2 -2
- package/dist/remote/uploader.d.ts +1 -1
- package/dist/remote/uploader.js +2 -2
- package/dist/templates/agents-md.d.ts +1 -1
- package/dist/templates/agents-md.js +2 -2
- package/dist/templates/package-json.js +1 -1
- package/dist/test-runner/job-executor.js +1 -1
- package/dist/test-runner/runs-on-display.d.ts +9 -0
- package/dist/{local-executor → test-runner}/runs-on-display.js +1 -1
- package/dist/test-runner/step-context.d.ts +1 -1
- package/dist/test-runner/step-context.js +2 -2
- package/dist/types.d.ts +6 -6
- package/package.json +7 -4
- package/sbom.spdx.json +9884 -1459
- package/dist/local-executor/dag-scheduler.js +0 -183
- package/dist/local-executor/index.js +0 -378
- package/dist/local-executor/job-runner.js +0 -411
- package/dist/local-executor/materializer.js +0 -132
- package/dist/local-executor/output-streamer.js +0 -168
- package/dist/local-executor/payload-generator.js +0 -138
- package/dist/local-executor/picker.js +0 -109
- package/dist/local-executor/to-event-payload.js +0 -21
- package/dist/local-executor/types.js +0 -2
- package/dist/local-executor/workflow-lock.js +0 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import "../rolldown-runtime-ClRpJifh.js";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { AdminApiClient } from "@kici-dev/orchestrator";
|
|
4
|
+
//#region src/local-plane/plane-trigger.ts
|
|
5
|
+
/**
|
|
6
|
+
* Trigger an offline routed run against the local plane and discover its runId.
|
|
7
|
+
*
|
|
8
|
+
* A local repo has no forge to send a webhook, so this posts a GitHub-shaped
|
|
9
|
+
* synthetic push to the plane's provider-agnostic generic webhook route (the
|
|
10
|
+
* same wire shape the `kici-admin source trigger-local` path builds). The
|
|
11
|
+
* webhook is unauthenticated (the edge accepts it and returns 202); the run is
|
|
12
|
+
* created asynchronously, so this then polls the admin runs list — authenticated
|
|
13
|
+
* with the plane's bootstrap admin token — to resolve the created runId.
|
|
14
|
+
*
|
|
15
|
+
* The source's bundle hot-reload is debounced, so a first trigger can land
|
|
16
|
+
* before the plane has registered the (re-pointed) local source. This resends
|
|
17
|
+
* the webhook after a grace window until a run appears or the timeout elapses.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Build the GitHub-shaped webhook request the plane's local provider normalizer
|
|
21
|
+
* expects. Mirrors `buildLocalTriggerRequest` in the orchestrator's
|
|
22
|
+
* `cli/commands/local-trigger.ts` (kept local to honour the compiler-only scope
|
|
23
|
+
* of this phase — no orchestrator export is added).
|
|
24
|
+
*/
|
|
25
|
+
function buildLocalTriggerRequest(input) {
|
|
26
|
+
const body = JSON.stringify({
|
|
27
|
+
ref: input.ref,
|
|
28
|
+
after: input.sha,
|
|
29
|
+
repository: {
|
|
30
|
+
full_name: input.repoFullName,
|
|
31
|
+
default_branch: input.defaultBranch
|
|
32
|
+
},
|
|
33
|
+
...input.event === "dispatch" && {
|
|
34
|
+
action: input.action,
|
|
35
|
+
client_payload: input.clientPayload ?? {}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
return {
|
|
39
|
+
path: `/webhook/${input.orgId}/generic/${input.sourceId}`,
|
|
40
|
+
headers: {
|
|
41
|
+
"content-type": "application/json",
|
|
42
|
+
"x-event-type": input.event,
|
|
43
|
+
"x-delivery-id": randomUUID()
|
|
44
|
+
},
|
|
45
|
+
body
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** POST the trigger request to the plane. Returns the HTTP status. */
|
|
49
|
+
async function sendLocalTrigger(planeUrl, req) {
|
|
50
|
+
return (await fetch(`${planeUrl.replace(/\/$/, "")}${req.path}`, {
|
|
51
|
+
method: "POST",
|
|
52
|
+
headers: req.headers,
|
|
53
|
+
body: req.body
|
|
54
|
+
})).status;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Trigger the run and resolve its runId. Sends the synthetic push, then polls
|
|
58
|
+
* the admin runs list (`created_at > since`) for the newest run, resending the
|
|
59
|
+
* webhook past a grace window to absorb source hot-reload latency.
|
|
60
|
+
*/
|
|
61
|
+
async function triggerRun(planeUrl, adminToken, input, opts = {}) {
|
|
62
|
+
const client = opts.client ?? new AdminApiClient(planeUrl, adminToken);
|
|
63
|
+
const pollIntervalMs = opts.pollIntervalMs ?? 750;
|
|
64
|
+
const resendAfterMs = opts.resendAfterMs ?? 8e3;
|
|
65
|
+
const timeoutMs = opts.timeoutMs ?? 6e4;
|
|
66
|
+
const since = /* @__PURE__ */ new Date(Date.now() - 3e3);
|
|
67
|
+
const req = buildLocalTriggerRequest(input);
|
|
68
|
+
await sendLocalTrigger(planeUrl, req);
|
|
69
|
+
let lastSend = Date.now();
|
|
70
|
+
const deadline = Date.now() + timeoutMs;
|
|
71
|
+
while (Date.now() < deadline) {
|
|
72
|
+
const runId = await findLatestRunSince(client, since);
|
|
73
|
+
if (runId) return runId;
|
|
74
|
+
if (Date.now() - lastSend > resendAfterMs) {
|
|
75
|
+
await sendLocalTrigger(planeUrl, req);
|
|
76
|
+
lastSend = Date.now();
|
|
77
|
+
}
|
|
78
|
+
await sleep(pollIntervalMs);
|
|
79
|
+
}
|
|
80
|
+
throw new Error("offline run: no run appeared after triggering the local plane");
|
|
81
|
+
}
|
|
82
|
+
/** Return the newest run created after `since`, or null when none yet. */
|
|
83
|
+
async function findLatestRunSince(client, since) {
|
|
84
|
+
const qs = new URLSearchParams({
|
|
85
|
+
since: since.toISOString(),
|
|
86
|
+
limit: "5"
|
|
87
|
+
});
|
|
88
|
+
const { runs } = await client.get(`/api/v1/admin/runs?${qs}`);
|
|
89
|
+
return runs[0]?.runId ?? null;
|
|
90
|
+
}
|
|
91
|
+
function sleep(ms) {
|
|
92
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
export { buildLocalTriggerRequest, sendLocalTrigger, triggerRun };
|
|
96
|
+
|
|
97
|
+
//# sourceMappingURL=plane-trigger.js.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform attach helpers for the local dev plane. Pure HTTP/URL helpers with no
|
|
3
|
+
* plane side-effects: derive the orchestrator WS relay URL from the CLI's HTTPS
|
|
4
|
+
* API base, mint/revoke an org-scoped orchestrator key via the Platform API
|
|
5
|
+
* using the logged-in personal access token, and probe Platform reachability.
|
|
6
|
+
*
|
|
7
|
+
* The minted key (prefix `kici_ok_`) is what the plane presents on WS auth when
|
|
8
|
+
* it boots hybrid. It is org-scoped and created under the calling user's own
|
|
9
|
+
* `api_keys:write` permission — attach never mints anything the user could not
|
|
10
|
+
* mint from the dashboard.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Derive the orchestrator WS relay URL from the CLI's HTTPS Platform API base.
|
|
14
|
+
* `https://…` → `wss://…`, `http://…` → `ws://…`, then ensure a single trailing
|
|
15
|
+
* `/ws` path segment (idempotent when the base already ends in `/ws`).
|
|
16
|
+
*/
|
|
17
|
+
export declare function derivePlatformWsUrl(apiBase: string): string;
|
|
18
|
+
export interface MintedOrchestratorKey {
|
|
19
|
+
/** Raw orchestrator key (kici_ok_…) — present once. NEVER log this. */
|
|
20
|
+
key: string;
|
|
21
|
+
/** Platform api_keys id (for later revoke). */
|
|
22
|
+
keyId: string;
|
|
23
|
+
keyPrefix: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Mint an org-scoped orchestrator key via the Platform HTTP API using the PAT.
|
|
27
|
+
* `POST ${apiBase}/api/v1/orgs/${orgId}/orchestrator-keys`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function mintOrchestratorKey(args: {
|
|
30
|
+
apiBase: string;
|
|
31
|
+
pat: string;
|
|
32
|
+
orgId: string;
|
|
33
|
+
name?: string;
|
|
34
|
+
}): Promise<MintedOrchestratorKey>;
|
|
35
|
+
/**
|
|
36
|
+
* Best-effort revoke of a previously-minted orchestrator key on detach.
|
|
37
|
+
* `DELETE ${apiBase}/api/v1/orgs/${orgId}/orchestrator-keys/${keyId}`. Never
|
|
38
|
+
* throws — a failed revoke (offline, expired PAT) is non-fatal; the key is
|
|
39
|
+
* org-scoped and the local plane stops presenting it regardless.
|
|
40
|
+
*/
|
|
41
|
+
export declare function revokeOrchestratorKey(args: {
|
|
42
|
+
apiBase: string;
|
|
43
|
+
pat: string;
|
|
44
|
+
orgId: string;
|
|
45
|
+
keyId: string;
|
|
46
|
+
}): Promise<boolean>;
|
|
47
|
+
/**
|
|
48
|
+
* Bounded reachability probe against the Platform API base. Returns true when
|
|
49
|
+
* the base responds (any HTTP status — a reachable Platform, even a 401/404 on
|
|
50
|
+
* `/health`, proves the network path). A timeout / network error is false.
|
|
51
|
+
*/
|
|
52
|
+
export declare function probePlatformReachable(apiBase: string, timeoutMs?: number): Promise<boolean>;
|
|
53
|
+
//# sourceMappingURL=platform-attach.d.ts.map
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import "../rolldown-runtime-ClRpJifh.js";
|
|
2
|
+
import { toErrorMessage } from "@kici-dev/core";
|
|
3
|
+
//#region src/local-plane/platform-attach.ts
|
|
4
|
+
/**
|
|
5
|
+
* Platform attach helpers for the local dev plane. Pure HTTP/URL helpers with no
|
|
6
|
+
* plane side-effects: derive the orchestrator WS relay URL from the CLI's HTTPS
|
|
7
|
+
* API base, mint/revoke an org-scoped orchestrator key via the Platform API
|
|
8
|
+
* using the logged-in personal access token, and probe Platform reachability.
|
|
9
|
+
*
|
|
10
|
+
* The minted key (prefix `kici_ok_`) is what the plane presents on WS auth when
|
|
11
|
+
* it boots hybrid. It is org-scoped and created under the calling user's own
|
|
12
|
+
* `api_keys:write` permission — attach never mints anything the user could not
|
|
13
|
+
* mint from the dashboard.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Derive the orchestrator WS relay URL from the CLI's HTTPS Platform API base.
|
|
17
|
+
* `https://…` → `wss://…`, `http://…` → `ws://…`, then ensure a single trailing
|
|
18
|
+
* `/ws` path segment (idempotent when the base already ends in `/ws`).
|
|
19
|
+
*/
|
|
20
|
+
function derivePlatformWsUrl(apiBase) {
|
|
21
|
+
let url;
|
|
22
|
+
try {
|
|
23
|
+
url = new URL(apiBase);
|
|
24
|
+
} catch {
|
|
25
|
+
throw new Error(`Invalid Platform API base URL: ${apiBase}`);
|
|
26
|
+
}
|
|
27
|
+
if (url.protocol === "https:") url.protocol = "wss:";
|
|
28
|
+
else if (url.protocol === "http:") url.protocol = "ws:";
|
|
29
|
+
else throw new Error(`Platform API base must be http(s): ${apiBase}`);
|
|
30
|
+
const trimmed = url.pathname.replace(/\/+$/, "");
|
|
31
|
+
url.pathname = trimmed.endsWith("/ws") ? trimmed : `${trimmed}/ws`;
|
|
32
|
+
url.search = "";
|
|
33
|
+
url.hash = "";
|
|
34
|
+
return url.toString();
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Mint an org-scoped orchestrator key via the Platform HTTP API using the PAT.
|
|
38
|
+
* `POST ${apiBase}/api/v1/orgs/${orgId}/orchestrator-keys`.
|
|
39
|
+
*/
|
|
40
|
+
async function mintOrchestratorKey(args) {
|
|
41
|
+
const url = `${args.apiBase.replace(/\/+$/, "")}/api/v1/orgs/${encodeURIComponent(args.orgId)}/orchestrator-keys`;
|
|
42
|
+
let res;
|
|
43
|
+
try {
|
|
44
|
+
res = await fetch(url, {
|
|
45
|
+
method: "POST",
|
|
46
|
+
headers: {
|
|
47
|
+
Authorization: `Bearer ${args.pat}`,
|
|
48
|
+
"Content-Type": "application/json"
|
|
49
|
+
},
|
|
50
|
+
body: JSON.stringify({ name: args.name ?? `local dev plane (${orgHostLabel()})` })
|
|
51
|
+
});
|
|
52
|
+
} catch (err) {
|
|
53
|
+
throw new Error(`Could not reach the Platform to mint an orchestrator key: ${toErrorMessage(err)}`);
|
|
54
|
+
}
|
|
55
|
+
if (res.status === 401) throw new Error("Platform rejected the credentials (401). Run `kici login` to re-authenticate.");
|
|
56
|
+
if (res.status === 403) throw new Error(`Your account lacks permission to create orchestrator keys in org ${args.orgId} (needs the api_keys:write permission — an Owner can grant it).`);
|
|
57
|
+
if (!res.ok) throw new Error(`Platform returned ${res.status} minting the orchestrator key.`);
|
|
58
|
+
const body = await res.json();
|
|
59
|
+
if (!body.key || !body.id) throw new Error("Platform orchestrator-key response was missing the key or id.");
|
|
60
|
+
return {
|
|
61
|
+
key: body.key,
|
|
62
|
+
keyId: body.id,
|
|
63
|
+
keyPrefix: body.keyPrefix ?? "kici_ok_"
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Best-effort revoke of a previously-minted orchestrator key on detach.
|
|
68
|
+
* `DELETE ${apiBase}/api/v1/orgs/${orgId}/orchestrator-keys/${keyId}`. Never
|
|
69
|
+
* throws — a failed revoke (offline, expired PAT) is non-fatal; the key is
|
|
70
|
+
* org-scoped and the local plane stops presenting it regardless.
|
|
71
|
+
*/
|
|
72
|
+
async function revokeOrchestratorKey(args) {
|
|
73
|
+
const url = `${args.apiBase.replace(/\/+$/, "")}/api/v1/orgs/${encodeURIComponent(args.orgId)}/orchestrator-keys/${encodeURIComponent(args.keyId)}`;
|
|
74
|
+
try {
|
|
75
|
+
return (await fetch(url, {
|
|
76
|
+
method: "DELETE",
|
|
77
|
+
headers: { Authorization: `Bearer ${args.pat}` }
|
|
78
|
+
})).ok;
|
|
79
|
+
} catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Bounded reachability probe against the Platform API base. Returns true when
|
|
85
|
+
* the base responds (any HTTP status — a reachable Platform, even a 401/404 on
|
|
86
|
+
* `/health`, proves the network path). A timeout / network error is false.
|
|
87
|
+
*/
|
|
88
|
+
async function probePlatformReachable(apiBase, timeoutMs = 4e3) {
|
|
89
|
+
const controller = new AbortController();
|
|
90
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
91
|
+
try {
|
|
92
|
+
const url = `${apiBase.replace(/\/+$/, "")}/health`;
|
|
93
|
+
return (await fetch(url, { signal: controller.signal })).status > 0;
|
|
94
|
+
} catch {
|
|
95
|
+
return false;
|
|
96
|
+
} finally {
|
|
97
|
+
clearTimeout(timer);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/** A short, non-sensitive label for the minted key name (the machine host). */
|
|
101
|
+
function orgHostLabel() {
|
|
102
|
+
try {
|
|
103
|
+
return process.env.HOSTNAME || "kici-local";
|
|
104
|
+
} catch {
|
|
105
|
+
return "kici-local";
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
//#endregion
|
|
109
|
+
export { derivePlatformWsUrl, mintOrchestratorKey, probePlatformReachable, revokeOrchestratorKey };
|
|
110
|
+
|
|
111
|
+
//# sourceMappingURL=platform-attach.js.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Podman fallback Postgres image for the local dev plane. Pinned via
|
|
3
|
+
* `TS_CONST_PINS` in hack/containers-lock.ts (identity
|
|
4
|
+
* docker.io/library/postgres+alpine), so a lock bump rewrites it in place.
|
|
5
|
+
*/
|
|
6
|
+
export declare const PLANE_PG_IMAGE = "docker.io/library/postgres:18.4-alpine";
|
|
7
|
+
/** Name of the fallback podman Postgres container. */
|
|
8
|
+
export declare const PLANE_PG_CONTAINER = "kici-local-postgres";
|
|
9
|
+
export type PlanePgHandle = {
|
|
10
|
+
url: string;
|
|
11
|
+
kind: 'embedded' | 'podman';
|
|
12
|
+
stop(): Promise<void>;
|
|
13
|
+
};
|
|
14
|
+
declare let readyPoller: typeof defaultReadyPoller;
|
|
15
|
+
/** Test seam: override the podman readiness poller. */
|
|
16
|
+
export declare function __setReadyPollerForTest(fn: typeof readyPoller): void;
|
|
17
|
+
declare let embeddedDaemon: typeof defaultEmbeddedDaemon;
|
|
18
|
+
/** Test seam: override the embedded postmaster daemonizer. */
|
|
19
|
+
export declare function __setEmbeddedDaemonForTest(fn: typeof embeddedDaemon): void;
|
|
20
|
+
declare function defaultReadyPoller(_port: number): Promise<boolean>;
|
|
21
|
+
/** Whether a podman binary is available on PATH. */
|
|
22
|
+
export declare function isPodmanAvailable(): Promise<boolean>;
|
|
23
|
+
/**
|
|
24
|
+
* Start a detached embedded postmaster via `pg_ctl` so it survives the exit of
|
|
25
|
+
* this CLI process (embedded-postgres's in-process server is killed by its own
|
|
26
|
+
* exit hook, so it cannot back a warm plane).
|
|
27
|
+
*/
|
|
28
|
+
declare function defaultEmbeddedDaemon(port: number): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Stop the plane's Postgres by backend kind. Handle-independent so a separate
|
|
31
|
+
* CLI invocation (`kici local down`) can tear down what `up` started.
|
|
32
|
+
*/
|
|
33
|
+
export declare function stopPlanePostgres(kind: 'embedded' | 'podman'): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Provision the local dev plane's Postgres. Prefers the zero-dependency
|
|
36
|
+
* `embedded-postgres` binary (daemonized via pg_ctl so it stays warm); falls
|
|
37
|
+
* back to a podman Postgres container when the embedded binary is unavailable
|
|
38
|
+
* (or when forced via `forcePodman` / `KICI_LOCAL_PG_MODE=podman`).
|
|
39
|
+
*/
|
|
40
|
+
export declare function startPlanePostgres(opts?: {
|
|
41
|
+
forcePodman?: boolean;
|
|
42
|
+
}): Promise<PlanePgHandle>;
|
|
43
|
+
export {};
|
|
44
|
+
//# sourceMappingURL=postgres.d.ts.map
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import "../rolldown-runtime-ClRpJifh.js";
|
|
2
|
+
import { planePaths, planePorts } from "./paths.js";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
7
|
+
import os from "node:os";
|
|
8
|
+
import { $ } from "zx";
|
|
9
|
+
import EmbeddedPostgres from "embedded-postgres";
|
|
10
|
+
//#region src/local-plane/postgres.ts
|
|
11
|
+
/**
|
|
12
|
+
* Podman fallback Postgres image for the local dev plane. Pinned via
|
|
13
|
+
* `TS_CONST_PINS` in hack/containers-lock.ts (identity
|
|
14
|
+
* docker.io/library/postgres+alpine), so a lock bump rewrites it in place.
|
|
15
|
+
*/
|
|
16
|
+
const PLANE_PG_IMAGE = "docker.io/library/postgres:18.4-alpine";
|
|
17
|
+
/** Name of the fallback podman Postgres container. */
|
|
18
|
+
const PLANE_PG_CONTAINER = "kici-local-postgres";
|
|
19
|
+
let readyPoller = defaultReadyPoller;
|
|
20
|
+
/** Test seam: override the podman readiness poller. */
|
|
21
|
+
function __setReadyPollerForTest(fn) {
|
|
22
|
+
readyPoller = fn;
|
|
23
|
+
}
|
|
24
|
+
let embeddedDaemon = defaultEmbeddedDaemon;
|
|
25
|
+
/** Test seam: override the embedded postmaster daemonizer. */
|
|
26
|
+
function __setEmbeddedDaemonForTest(fn) {
|
|
27
|
+
embeddedDaemon = fn;
|
|
28
|
+
}
|
|
29
|
+
async function defaultReadyPoller(_port) {
|
|
30
|
+
for (let i = 0; i < 60; i++) try {
|
|
31
|
+
await $`podman exec ${PLANE_PG_CONTAINER} pg_isready -U kici`.quiet();
|
|
32
|
+
return true;
|
|
33
|
+
} catch {
|
|
34
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
/** Whether a podman binary is available on PATH. */
|
|
39
|
+
async function isPodmanAvailable() {
|
|
40
|
+
try {
|
|
41
|
+
await $`podman --version`.quiet();
|
|
42
|
+
return true;
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Resolve the platform-specific `pg_ctl` binary bundled with embedded-postgres.
|
|
49
|
+
* Mirrors the package's own platform → binary-package mapping, resolving the
|
|
50
|
+
* binary package through embedded-postgres's module context (pnpm isolates it
|
|
51
|
+
* as a transitive dependency, so it is not directly resolvable here) and
|
|
52
|
+
* computing the `native/bin/pg_ctl` path from the resolved package directory.
|
|
53
|
+
*/
|
|
54
|
+
function resolvePgCtl() {
|
|
55
|
+
const platform = os.platform();
|
|
56
|
+
const arch = os.arch();
|
|
57
|
+
const pkg = {
|
|
58
|
+
"darwin:arm64": "@embedded-postgres/darwin-arm64",
|
|
59
|
+
"darwin:x64": "@embedded-postgres/darwin-x64",
|
|
60
|
+
"linux:arm64": "@embedded-postgres/linux-arm64",
|
|
61
|
+
"linux:arm": "@embedded-postgres/linux-arm",
|
|
62
|
+
"linux:ia32": "@embedded-postgres/linux-ia32",
|
|
63
|
+
"linux:ppc64": "@embedded-postgres/linux-ppc64",
|
|
64
|
+
"linux:x64": "@embedded-postgres/linux-x64",
|
|
65
|
+
"win32:x64": "@embedded-postgres/windows-x64"
|
|
66
|
+
}[`${platform}:${arch}`];
|
|
67
|
+
if (!pkg) throw new Error(`unsupported platform for embedded Postgres: ${platform}/${arch}`);
|
|
68
|
+
const entry = createRequire(createRequire(import.meta.url).resolve("embedded-postgres")).resolve(pkg);
|
|
69
|
+
const binName = platform === "win32" ? "pg_ctl.exe" : "pg_ctl";
|
|
70
|
+
return path.resolve(path.dirname(entry), "..", "native", "bin", binName);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Initialise the embedded Postgres cluster + the `kici_local` database once. A
|
|
74
|
+
* short-lived in-process server is used only for the one-time bootstrap; the
|
|
75
|
+
* persistent postmaster is started separately (daemonized) so it outlives this
|
|
76
|
+
* CLI invocation.
|
|
77
|
+
*/
|
|
78
|
+
async function ensureEmbeddedCluster(port) {
|
|
79
|
+
const { pgData } = planePaths();
|
|
80
|
+
if (fs.existsSync(path.join(pgData, "PG_VERSION"))) return;
|
|
81
|
+
const pg = new EmbeddedPostgres({
|
|
82
|
+
databaseDir: pgData,
|
|
83
|
+
port,
|
|
84
|
+
user: "kici",
|
|
85
|
+
password: "kici",
|
|
86
|
+
persistent: true
|
|
87
|
+
});
|
|
88
|
+
await pg.initialise();
|
|
89
|
+
await pg.start();
|
|
90
|
+
await pg.createDatabase("kici_local").catch(() => {});
|
|
91
|
+
await pg.stop();
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Start a detached embedded postmaster via `pg_ctl` so it survives the exit of
|
|
95
|
+
* this CLI process (embedded-postgres's in-process server is killed by its own
|
|
96
|
+
* exit hook, so it cannot back a warm plane).
|
|
97
|
+
*/
|
|
98
|
+
async function defaultEmbeddedDaemon(port) {
|
|
99
|
+
const { pgData, logFile } = planePaths();
|
|
100
|
+
await $`${resolvePgCtl()} -D ${pgData} -o ${`-p ${port}`} -l ${`${logFile}.pg`} -w start`.quiet();
|
|
101
|
+
}
|
|
102
|
+
/** Stop the detached embedded postmaster (handle-independent, reads the data dir). */
|
|
103
|
+
async function stopEmbeddedDaemon() {
|
|
104
|
+
const { pgData } = planePaths();
|
|
105
|
+
if (!fs.existsSync(path.join(pgData, "postmaster.pid"))) return;
|
|
106
|
+
await $`${resolvePgCtl()} -D ${pgData} stop -m fast`.quiet().catch(() => {});
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Stop the plane's Postgres by backend kind. Handle-independent so a separate
|
|
110
|
+
* CLI invocation (`kici local down`) can tear down what `up` started.
|
|
111
|
+
*/
|
|
112
|
+
async function stopPlanePostgres(kind) {
|
|
113
|
+
if (kind === "podman") await $`podman rm -f ${PLANE_PG_CONTAINER}`.quiet().catch(() => {});
|
|
114
|
+
else await stopEmbeddedDaemon();
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Provision the local dev plane's Postgres. Prefers the zero-dependency
|
|
118
|
+
* `embedded-postgres` binary (daemonized via pg_ctl so it stays warm); falls
|
|
119
|
+
* back to a podman Postgres container when the embedded binary is unavailable
|
|
120
|
+
* (or when forced via `forcePodman` / `KICI_LOCAL_PG_MODE=podman`).
|
|
121
|
+
*/
|
|
122
|
+
async function startPlanePostgres(opts = {}) {
|
|
123
|
+
const { postgres: port } = planePorts();
|
|
124
|
+
const url = `postgres://kici:kici@127.0.0.1:${port}/kici_local`;
|
|
125
|
+
if (!(opts.forcePodman || process.env.KICI_LOCAL_PG_MODE === "podman")) try {
|
|
126
|
+
await ensureEmbeddedCluster(port);
|
|
127
|
+
await embeddedDaemon(port);
|
|
128
|
+
return {
|
|
129
|
+
url,
|
|
130
|
+
kind: "embedded",
|
|
131
|
+
stop: () => stopEmbeddedDaemon()
|
|
132
|
+
};
|
|
133
|
+
} catch {}
|
|
134
|
+
spawn("podman", [
|
|
135
|
+
"run",
|
|
136
|
+
"-d",
|
|
137
|
+
"--replace",
|
|
138
|
+
"--name",
|
|
139
|
+
PLANE_PG_CONTAINER,
|
|
140
|
+
"-p",
|
|
141
|
+
`127.0.0.1:${port}:5432`,
|
|
142
|
+
"-e",
|
|
143
|
+
"POSTGRES_USER=kici",
|
|
144
|
+
"-e",
|
|
145
|
+
"POSTGRES_PASSWORD=kici",
|
|
146
|
+
"-e",
|
|
147
|
+
"POSTGRES_DB=kici_local",
|
|
148
|
+
PLANE_PG_IMAGE
|
|
149
|
+
], {
|
|
150
|
+
stdio: "ignore",
|
|
151
|
+
detached: true
|
|
152
|
+
}).unref();
|
|
153
|
+
if (!await readyPoller(port)) throw new Error("local Postgres (podman) did not become ready");
|
|
154
|
+
return {
|
|
155
|
+
url,
|
|
156
|
+
kind: "podman",
|
|
157
|
+
stop: async () => {
|
|
158
|
+
await $`podman rm -f ${PLANE_PG_CONTAINER}`.quiet().catch(() => {});
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
//#endregion
|
|
163
|
+
export { PLANE_PG_CONTAINER, PLANE_PG_IMAGE, __setEmbeddedDaemonForTest, __setReadyPollerForTest, isPodmanAvailable, startPlanePostgres, stopPlanePostgres };
|
|
164
|
+
|
|
165
|
+
//# sourceMappingURL=postgres.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plane auto-selection for `kici run --local` (design §5).
|
|
3
|
+
*
|
|
4
|
+
* Rules:
|
|
5
|
+
* - `--offline` → force the independent (offline) plane.
|
|
6
|
+
* - `--connected` → require an attached plane; hard-error if not attached, and
|
|
7
|
+
* hard-error if the Platform is unreachable (the user explicitly demanded it).
|
|
8
|
+
* - neither flag → attached + Platform reachable → hybrid; attached but the
|
|
9
|
+
* Platform is unreachable → fall back to the independent plane with a loud
|
|
10
|
+
* banner; not attached → offline.
|
|
11
|
+
*
|
|
12
|
+
* The fallback is the "mid-session Platform-unreachable" guarantee: a `--local`
|
|
13
|
+
* run never fails just because the Platform is unreachable (unless `--connected`).
|
|
14
|
+
* It re-boots/keeps the independent plane and continues on local secrets +
|
|
15
|
+
* dev-signed identity.
|
|
16
|
+
*/
|
|
17
|
+
import { type PlaneStatus } from './plane-manager.js';
|
|
18
|
+
export interface ResolvePlaneFlags {
|
|
19
|
+
offline?: boolean;
|
|
20
|
+
connected?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export type ResolvedPlane = {
|
|
23
|
+
kind: 'offline';
|
|
24
|
+
plane: PlaneStatus;
|
|
25
|
+
} | {
|
|
26
|
+
kind: 'attached';
|
|
27
|
+
plane: PlaneStatus;
|
|
28
|
+
orgId: string;
|
|
29
|
+
} | {
|
|
30
|
+
kind: 'fallback';
|
|
31
|
+
plane: PlaneStatus;
|
|
32
|
+
reason: string;
|
|
33
|
+
} | {
|
|
34
|
+
error: string;
|
|
35
|
+
};
|
|
36
|
+
export declare function resolvePlaneForRun(flags: ResolvePlaneFlags): Promise<ResolvedPlane>;
|
|
37
|
+
//# sourceMappingURL=resolve-plane.d.ts.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import "../rolldown-runtime-ClRpJifh.js";
|
|
2
|
+
import { probePlatformReachable } from "./platform-attach.js";
|
|
3
|
+
import { planeUp, readAttachment, readPlatformToken } from "./plane-manager.js";
|
|
4
|
+
//#region src/local-plane/resolve-plane.ts
|
|
5
|
+
/**
|
|
6
|
+
* Plane auto-selection for `kici run --local` (design §5).
|
|
7
|
+
*
|
|
8
|
+
* Rules:
|
|
9
|
+
* - `--offline` → force the independent (offline) plane.
|
|
10
|
+
* - `--connected` → require an attached plane; hard-error if not attached, and
|
|
11
|
+
* hard-error if the Platform is unreachable (the user explicitly demanded it).
|
|
12
|
+
* - neither flag → attached + Platform reachable → hybrid; attached but the
|
|
13
|
+
* Platform is unreachable → fall back to the independent plane with a loud
|
|
14
|
+
* banner; not attached → offline.
|
|
15
|
+
*
|
|
16
|
+
* The fallback is the "mid-session Platform-unreachable" guarantee: a `--local`
|
|
17
|
+
* run never fails just because the Platform is unreachable (unless `--connected`).
|
|
18
|
+
* It re-boots/keeps the independent plane and continues on local secrets +
|
|
19
|
+
* dev-signed identity.
|
|
20
|
+
*/
|
|
21
|
+
/** Boot the hybrid plane from the durable attachment record + persisted token. */
|
|
22
|
+
async function bootAttached(attachment) {
|
|
23
|
+
const token = readPlatformToken();
|
|
24
|
+
if (!token) return { error: "The plane is attached but its Platform token is missing — run `kici local attach` to re-attach." };
|
|
25
|
+
return planeUp({ attach: {
|
|
26
|
+
platformWsUrl: attachment.platformWsUrl,
|
|
27
|
+
platformToken: token,
|
|
28
|
+
platformApiBase: attachment.platformApiBase,
|
|
29
|
+
orgId: attachment.orgId,
|
|
30
|
+
keyId: attachment.keyId
|
|
31
|
+
} });
|
|
32
|
+
}
|
|
33
|
+
async function resolvePlaneForRun(flags) {
|
|
34
|
+
const attachment = readAttachment();
|
|
35
|
+
if (flags.offline) return {
|
|
36
|
+
kind: "offline",
|
|
37
|
+
plane: await planeUp()
|
|
38
|
+
};
|
|
39
|
+
if (flags.connected) {
|
|
40
|
+
if (!attachment) return { error: "Not attached to the Platform. Run `kici local attach` first (or drop --connected)." };
|
|
41
|
+
if (!await probePlatformReachable(attachment.platformApiBase)) return { error: `Platform ${attachment.platformApiBase} is unreachable — cannot honor --connected.` };
|
|
42
|
+
const booted = await bootAttached(attachment);
|
|
43
|
+
if ("error" in booted) return booted;
|
|
44
|
+
return {
|
|
45
|
+
kind: "attached",
|
|
46
|
+
plane: booted,
|
|
47
|
+
orgId: attachment.orgId
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (!attachment) return {
|
|
51
|
+
kind: "offline",
|
|
52
|
+
plane: await planeUp()
|
|
53
|
+
};
|
|
54
|
+
if (await probePlatformReachable(attachment.platformApiBase)) {
|
|
55
|
+
const booted = await bootAttached(attachment);
|
|
56
|
+
if ("error" in booted) return {
|
|
57
|
+
kind: "fallback",
|
|
58
|
+
plane: await planeUp(),
|
|
59
|
+
reason: booted.error
|
|
60
|
+
};
|
|
61
|
+
return {
|
|
62
|
+
kind: "attached",
|
|
63
|
+
plane: booted,
|
|
64
|
+
orgId: attachment.orgId
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
kind: "fallback",
|
|
69
|
+
plane: await planeUp(),
|
|
70
|
+
reason: "Platform unreachable"
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
//#endregion
|
|
74
|
+
export { resolvePlaneForRun };
|
|
75
|
+
|
|
76
|
+
//# sourceMappingURL=resolve-plane.js.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Follow an offline routed run against the local plane by polling the
|
|
3
|
+
* orchestrator admin API (no SSE in independent mode). Streams step log lines as
|
|
4
|
+
* they arrive and resolves when the run reaches a terminal state.
|
|
5
|
+
*/
|
|
6
|
+
/** A minimal admin-read client (AdminApiClient.get) — injectable for tests. */
|
|
7
|
+
export interface RunFollowClient {
|
|
8
|
+
get<T>(path: string): Promise<T>;
|
|
9
|
+
}
|
|
10
|
+
/** Terminal outcome of a followed run. */
|
|
11
|
+
export interface RunOutcome {
|
|
12
|
+
runId: string;
|
|
13
|
+
status: string;
|
|
14
|
+
jobs: Array<{
|
|
15
|
+
name: string;
|
|
16
|
+
status: string;
|
|
17
|
+
durationMs?: number;
|
|
18
|
+
}>;
|
|
19
|
+
}
|
|
20
|
+
export interface FollowRunOptions {
|
|
21
|
+
onLine?: (line: string) => void;
|
|
22
|
+
quiet?: boolean;
|
|
23
|
+
client?: RunFollowClient;
|
|
24
|
+
pollIntervalMs?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Idle window: the max time with NO observed progress (no new log line, no run
|
|
27
|
+
* status change) before the follow gives up. Resets on every progress tick, so
|
|
28
|
+
* a legitimately long run (e.g. a full `deploy:stg`, tens of minutes of image
|
|
29
|
+
* builds + host mutation) never times out while it is actively advancing —
|
|
30
|
+
* only a genuinely stalled run does. Default 15 min. `timeoutMs` is a
|
|
31
|
+
* back-compat alias.
|
|
32
|
+
*/
|
|
33
|
+
idleTimeoutMs?: number;
|
|
34
|
+
/** @deprecated Back-compat alias for `idleTimeoutMs`. */
|
|
35
|
+
timeoutMs?: number;
|
|
36
|
+
/** Absolute cap regardless of progress (safety net against a runaway follow). Default 2 h. */
|
|
37
|
+
maxTotalMs?: number;
|
|
38
|
+
}
|
|
39
|
+
/** Poll a run to completion, streaming step logs. */
|
|
40
|
+
export declare function followRun(planeUrl: string, adminToken: string, runId: string, opts?: FollowRunOptions): Promise<RunOutcome>;
|
|
41
|
+
//# sourceMappingURL=run-follow.d.ts.map
|