@kici-dev/compiler 0.1.26 → 0.2.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/auth/headless-detect.d.ts +14 -5
- package/dist/auth/headless-detect.js +50 -9
- package/dist/cli-banner.d.ts +6 -3
- package/dist/cli-banner.js +13 -5
- package/dist/cli.js +180 -48
- package/dist/commands/check-mode.d.ts +1 -1
- package/dist/commands/compile.js +31 -7
- package/dist/commands/diagnostics.js +14 -2
- package/dist/commands/doctor.d.ts +64 -0
- package/dist/commands/doctor.js +413 -0
- package/dist/commands/endpoints.js +6 -0
- package/dist/commands/fixture.js +13 -0
- package/dist/commands/hook.js +3 -2
- package/dist/commands/index.d.ts +13 -1
- package/dist/commands/index.js +10 -3
- package/dist/commands/init-host-os.d.ts +34 -0
- package/dist/commands/init-host-os.js +44 -0
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.js +160 -37
- 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 +65 -0
- package/dist/commands/local.js +206 -0
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +41 -1
- package/dist/commands/logout.js +7 -0
- package/dist/commands/notifications.d.ts +61 -0
- package/dist/commands/notifications.js +274 -0
- package/dist/commands/preview.js +9 -2
- 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 +185 -0
- package/dist/commands/run.d.ts +0 -17
- package/dist/commands/run.js +44 -58
- package/dist/commands/runs/artifacts/download.d.ts +24 -0
- package/dist/commands/runs/artifacts/download.js +154 -0
- package/dist/commands/runs/artifacts/list.d.ts +5 -0
- package/dist/commands/runs/artifacts/list.js +47 -0
- package/dist/commands/runs/cancel.js +2 -2
- package/dist/commands/runs/list.js +8 -2
- 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/commands/verify-attestation.d.ts +3 -1
- package/dist/commands/verify-attestation.js +18 -7
- package/dist/errors/formatter.d.ts +14 -1
- package/dist/errors/formatter.js +13 -3
- package/dist/errors/index.d.ts +2 -1
- package/dist/errors/index.js +3 -2
- package/dist/errors/source-location.d.ts +20 -0
- package/dist/errors/source-location.js +45 -0
- package/dist/execution/executor.js +32 -12
- package/dist/fixtures/defaults/index.js +2 -1
- package/dist/generators/secrets-dts.d.ts +9 -9
- package/dist/generators/secrets-dts.js +12 -12
- package/dist/llm-context/llms-architecture.txt +50 -21
- package/dist/llm-context/llms-cli.txt +2667 -1454
- package/dist/llm-context/llms-features.txt +484 -414
- package/dist/llm-context/llms-full.txt +6703 -4687
- package/dist/llm-context/llms-getting-started.txt +311 -55
- package/dist/llm-context/llms-patterns.txt +17 -29
- package/dist/llm-context/llms-providers.txt +10 -1
- package/dist/llm-context/llms-sdk-runtime.txt +2013 -0
- package/dist/llm-context/llms-sdk.txt +596 -2157
- package/dist/llm-context/llms.txt +31 -17
- package/dist/local-plane/orchestrator-process.d.ts +70 -0
- package/dist/local-plane/orchestrator-process.js +129 -0
- package/dist/local-plane/os-mismatch-hint.d.ts +16 -0
- package/dist/local-plane/os-mismatch-hint.js +34 -0
- package/dist/local-plane/paths.d.ts +39 -0
- package/dist/local-plane/paths.js +54 -0
- package/dist/local-plane/plane-liveness.d.ts +68 -0
- package/dist/local-plane/plane-liveness.js +145 -0
- package/dist/local-plane/plane-manager.d.ts +193 -0
- package/dist/local-plane/plane-manager.js +480 -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 +71 -0
- package/dist/local-plane/plane-trigger.js +110 -0
- package/dist/local-plane/platform-attach.d.ts +53 -0
- package/dist/local-plane/platform-attach.js +107 -0
- package/dist/local-plane/port-holder.d.ts +78 -0
- package/dist/local-plane/port-holder.js +227 -0
- package/dist/local-plane/postgres.d.ts +56 -0
- package/dist/local-plane/postgres.js +187 -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 +61 -0
- package/dist/local-plane/run-follow.js +151 -0
- package/dist/local-plane/scaler-config.d.ts +68 -0
- package/dist/local-plane/scaler-config.js +132 -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 +159 -0
- package/dist/local-plane/trusted-routing.d.ts +32 -0
- package/dist/local-plane/trusted-routing.js +47 -0
- package/dist/lockfile/generator.d.ts +14 -1
- package/dist/lockfile/generator.js +103 -37
- package/dist/lockfile/index.d.ts +3 -1
- package/dist/lockfile/index.js +3 -2
- package/dist/lockfile/purity-diagnostics.d.ts +31 -0
- package/dist/lockfile/purity-diagnostics.js +52 -0
- package/dist/postinstall.js +2 -1
- package/dist/remote/artifact-extract.d.ts +8 -0
- package/dist/remote/artifact-extract.js +58 -0
- package/dist/remote/config.d.ts +21 -3
- package/dist/remote/config.js +24 -5
- package/dist/remote/dashboard-client.d.ts +46 -9
- package/dist/remote/dashboard-client.js +54 -11
- package/dist/remote/fs-case.d.ts +21 -0
- package/dist/remote/fs-case.js +45 -0
- package/dist/remote/history.d.ts +9 -3
- package/dist/remote/history.js +79 -10
- package/dist/remote/local-repo-identity.d.ts +1 -1
- package/dist/remote/local-repo-identity.js +2 -1
- package/dist/remote/notifications-client.d.ts +149 -0
- package/dist/remote/notifications-client.js +103 -0
- package/dist/remote/oauth.d.ts +13 -0
- package/dist/remote/oauth.js +31 -5
- package/dist/remote/output/summary.js +76 -14
- package/dist/remote/platform-client.d.ts +1 -1
- package/dist/remote/render.js +30 -9
- package/dist/{local-executor → remote}/secret-loader.d.ts +1 -1
- 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 +7 -6
- package/dist/templates/agents-md.d.ts +1 -1
- package/dist/templates/agents-md.js +8 -8
- package/dist/templates/index.d.ts +1 -1
- package/dist/templates/index.js +2 -2
- package/dist/templates/package-json.d.ts +15 -0
- package/dist/templates/package-json.js +26 -5
- package/dist/test-runner/dry-run.d.ts +2 -1
- package/dist/test-runner/dry-run.js +18 -1
- package/dist/test-runner/event-types.d.ts +2 -0
- package/dist/test-runner/event-types.js +3 -0
- package/dist/test-runner/job-executor.d.ts +1 -0
- package/dist/test-runner/job-executor.js +82 -44
- package/dist/test-runner/output-formatter.d.ts +5 -2
- package/dist/test-runner/output-formatter.js +6 -4
- package/dist/test-runner/payload-builder.d.ts +7 -1
- package/dist/test-runner/payload-builder.js +11 -2
- package/dist/test-runner/rule-evaluator.d.ts +7 -3
- package/dist/test-runner/rule-evaluator.js +13 -12
- package/dist/{local-executor → test-runner}/runs-on-display.js +1 -1
- package/dist/test-runner/step-context.d.ts +23 -5
- package/dist/test-runner/step-context.js +29 -119
- package/dist/types.d.ts +26 -7
- package/dist/types.js +4 -2
- package/dist/validation/index.d.ts +2 -0
- package/dist/validation/index.js +2 -1
- package/dist/validation/typecheck.d.ts +28 -0
- package/dist/validation/typecheck.js +110 -0
- package/dist/validation/validator.d.ts +9 -4
- package/dist/validation/validator.js +38 -44
- package/package.json +13 -13
- package/sbom.spdx.json +9311 -1528
- package/dist/assets/api-TJJVHrjC.json +0 -118
- package/dist/assets/descriptor-BTtjzN9L.json +0 -1382
- package/dist/assets/package-BpQF9kR8.json +0 -74
- package/dist/assets/package-Ceo2h27X.json +0 -89
- package/dist/assets/source_context-D0atuL28.json +0 -20
- package/dist/assets/type-BFqO8SCZ.json +0 -202
- package/dist/commands/cancel.d.ts +0 -22
- package/dist/commands/detect-package-manager.d.ts +0 -42
- package/dist/commands/held-run-resolve.d.ts +0 -50
- package/dist/commands/status.d.ts +0 -34
- package/dist/commands/test.d.ts +0 -88
- package/dist/errors/codes.d.ts +0 -25
- package/dist/local-executor/dag-scheduler.d.ts +0 -44
- package/dist/local-executor/dag-scheduler.js +0 -183
- package/dist/local-executor/index.d.ts +0 -23
- package/dist/local-executor/index.js +0 -378
- package/dist/local-executor/job-runner.d.ts +0 -47
- package/dist/local-executor/job-runner.js +0 -411
- package/dist/local-executor/materializer.d.ts +0 -44
- package/dist/local-executor/materializer.js +0 -132
- package/dist/local-executor/output-streamer.d.ts +0 -31
- package/dist/local-executor/output-streamer.js +0 -168
- package/dist/local-executor/payload-generator.d.ts +0 -16
- package/dist/local-executor/payload-generator.js +0 -138
- package/dist/local-executor/picker.d.ts +0 -33
- package/dist/local-executor/picker.js +0 -109
- package/dist/local-executor/to-event-payload.d.ts +0 -16
- package/dist/local-executor/to-event-payload.js +0 -21
- package/dist/local-executor/types.d.ts +0 -93
- package/dist/local-executor/types.js +0 -2
- package/dist/local-executor/workflow-lock.d.ts +0 -82
- package/dist/local-executor/workflow-lock.js +0 -0
- package/dist/package-F7UXSDHW.json +0 -74
- package/dist/remote/client.d.ts +0 -210
- package/dist/remote/observer.d.ts +0 -81
- package/dist/test-runner/summary.d.ts +0 -7
- /package/dist/{local-executor → test-runner}/runs-on-display.d.ts +0 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seed the local `file://` source the plane dispatches an offline routed run
|
|
3
|
+
* through. Idempotent: a stable `kici-local` source is created once and its
|
|
4
|
+
* `repoBasePath` is re-pointed at the current workdir on every run (the isolated
|
|
5
|
+
* profile materializes a fresh tmp clone each time). The org is the independent
|
|
6
|
+
* orchestrator's `__default__` sentinel — the orchestrator DB has no org table,
|
|
7
|
+
* so seeding an org is just passing this id.
|
|
8
|
+
*/
|
|
9
|
+
/** Independent-mode default org sentinel used by the local plane. */
|
|
10
|
+
export declare const LOCAL_ORG_ID = "__default__";
|
|
11
|
+
/** Stable name of the plane's local source (reused + re-pointed across runs). */
|
|
12
|
+
export declare const LOCAL_SOURCE_NAME = "kici-local";
|
|
13
|
+
/** Minimal surface of AdminApiClient plane-seed needs (for test injection). */
|
|
14
|
+
export interface PlaneSeedClient {
|
|
15
|
+
listGenericSources(orgId: string, includeDeleted?: boolean): Promise<{
|
|
16
|
+
sources: Array<{
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
provider_type?: string;
|
|
20
|
+
}>;
|
|
21
|
+
}>;
|
|
22
|
+
createGenericSource(data: {
|
|
23
|
+
orgId: string;
|
|
24
|
+
name: string;
|
|
25
|
+
providerType?: string;
|
|
26
|
+
verificationMethod?: string;
|
|
27
|
+
localConfig?: {
|
|
28
|
+
repoBasePath: string;
|
|
29
|
+
cloneUrlBase?: string;
|
|
30
|
+
inPlace?: boolean;
|
|
31
|
+
};
|
|
32
|
+
}): Promise<{
|
|
33
|
+
source: {
|
|
34
|
+
id: string;
|
|
35
|
+
name: string;
|
|
36
|
+
};
|
|
37
|
+
}>;
|
|
38
|
+
updateGenericSource(id: string, data: {
|
|
39
|
+
localConfig?: {
|
|
40
|
+
repoBasePath: string;
|
|
41
|
+
cloneUrlBase?: string;
|
|
42
|
+
inPlace?: boolean;
|
|
43
|
+
} | null;
|
|
44
|
+
}): Promise<{
|
|
45
|
+
source: {
|
|
46
|
+
id: string;
|
|
47
|
+
name: string;
|
|
48
|
+
};
|
|
49
|
+
}>;
|
|
50
|
+
}
|
|
51
|
+
/** Result of seeding: the org + the (created or reused) source. */
|
|
52
|
+
export interface SeededSource {
|
|
53
|
+
orgId: string;
|
|
54
|
+
sourceId: string;
|
|
55
|
+
sourceName: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Ensure the plane has a local `file://` source pointed at `repoDir`. Creates
|
|
59
|
+
* the `kici-local` source on first use; on subsequent runs it re-points the
|
|
60
|
+
* existing source at the new workdir (idempotent reuse). Returns the org +
|
|
61
|
+
* source ids the trigger uses.
|
|
62
|
+
*/
|
|
63
|
+
export declare function ensureLocalSource(planeUrl: string, adminToken: string, opts: {
|
|
64
|
+
repoDir: string;
|
|
65
|
+
orgId?: string;
|
|
66
|
+
name?: string;
|
|
67
|
+
/**
|
|
68
|
+
* In-place profile: `repoDir` is the operator's real working tree, so the
|
|
69
|
+
* plane skips the source-pack `__build__` job and each job runs the tree
|
|
70
|
+
* directly. Always written (true/false) so a subsequent isolated run resets
|
|
71
|
+
* a previously in-place source.
|
|
72
|
+
*/
|
|
73
|
+
inPlace?: boolean;
|
|
74
|
+
client?: PlaneSeedClient;
|
|
75
|
+
}): Promise<SeededSource>;
|
|
76
|
+
//# sourceMappingURL=plane-seed.d.ts.map
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import "../rolldown-runtime-ClRpJifh.js";
|
|
2
|
+
import { AdminApiClient } from "@kici-dev/orchestrator";
|
|
3
|
+
//#region src/local-plane/plane-seed.ts
|
|
4
|
+
/**
|
|
5
|
+
* Seed the local `file://` source the plane dispatches an offline routed run
|
|
6
|
+
* through. Idempotent: a stable `kici-local` source is created once and its
|
|
7
|
+
* `repoBasePath` is re-pointed at the current workdir on every run (the isolated
|
|
8
|
+
* profile materializes a fresh tmp clone each time). The org is the independent
|
|
9
|
+
* orchestrator's `__default__` sentinel — the orchestrator DB has no org table,
|
|
10
|
+
* so seeding an org is just passing this id.
|
|
11
|
+
*/
|
|
12
|
+
/** Independent-mode default org sentinel used by the local plane. */
|
|
13
|
+
const LOCAL_ORG_ID = "__default__";
|
|
14
|
+
/** Stable name of the plane's local source (reused + re-pointed across runs). */
|
|
15
|
+
const LOCAL_SOURCE_NAME = "kici-local";
|
|
16
|
+
/**
|
|
17
|
+
* Ensure the plane has a local `file://` source pointed at `repoDir`. Creates
|
|
18
|
+
* the `kici-local` source on first use; on subsequent runs it re-points the
|
|
19
|
+
* existing source at the new workdir (idempotent reuse). Returns the org +
|
|
20
|
+
* source ids the trigger uses.
|
|
21
|
+
*/
|
|
22
|
+
async function ensureLocalSource(planeUrl, adminToken, opts) {
|
|
23
|
+
const orgId = opts.orgId ?? "__default__";
|
|
24
|
+
const name = opts.name ?? "kici-local";
|
|
25
|
+
const inPlace = opts.inPlace === true;
|
|
26
|
+
const client = opts.client ?? new AdminApiClient(planeUrl, adminToken);
|
|
27
|
+
const { sources } = await client.listGenericSources(orgId);
|
|
28
|
+
const existing = sources.find((s) => s.name === name && s.provider_type === "local");
|
|
29
|
+
if (existing) {
|
|
30
|
+
const updated = await client.updateGenericSource(existing.id, { localConfig: {
|
|
31
|
+
repoBasePath: opts.repoDir,
|
|
32
|
+
inPlace
|
|
33
|
+
} });
|
|
34
|
+
return {
|
|
35
|
+
orgId,
|
|
36
|
+
sourceId: updated.source.id,
|
|
37
|
+
sourceName: updated.source.name
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const created = await client.createGenericSource({
|
|
41
|
+
orgId,
|
|
42
|
+
name,
|
|
43
|
+
providerType: "local",
|
|
44
|
+
verificationMethod: "none",
|
|
45
|
+
localConfig: {
|
|
46
|
+
repoBasePath: opts.repoDir,
|
|
47
|
+
inPlace
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
return {
|
|
51
|
+
orgId,
|
|
52
|
+
sourceId: created.source.id,
|
|
53
|
+
sourceName: created.source.name
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
export { LOCAL_ORG_ID, LOCAL_SOURCE_NAME, ensureLocalSource };
|
|
58
|
+
|
|
59
|
+
//# sourceMappingURL=plane-seed.js.map
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trigger an offline routed run against the local plane and discover its runId.
|
|
3
|
+
*
|
|
4
|
+
* A local repo has no forge to send a webhook, so this posts a GitHub-shaped
|
|
5
|
+
* synthetic push to the plane's provider-agnostic generic webhook route (the
|
|
6
|
+
* same wire shape the `kici-admin source trigger-local` path builds). The
|
|
7
|
+
* webhook is unauthenticated (the edge accepts it and returns 202); the run is
|
|
8
|
+
* created asynchronously, so this then polls the admin runs list — authenticated
|
|
9
|
+
* with the plane's bootstrap admin token — to resolve the created runId.
|
|
10
|
+
*
|
|
11
|
+
* The source's bundle hot-reload is debounced, so a first trigger can land
|
|
12
|
+
* before the plane has registered the (re-pointed) local source. This resends
|
|
13
|
+
* the webhook after a grace window until a run appears or the timeout elapses.
|
|
14
|
+
*/
|
|
15
|
+
/** A minimal admin-read client (AdminApiClient.get) — injectable for tests. */
|
|
16
|
+
export interface RunDiscoveryClient {
|
|
17
|
+
get<T>(path: string): Promise<T>;
|
|
18
|
+
}
|
|
19
|
+
export interface LocalTriggerInput {
|
|
20
|
+
orgId: string;
|
|
21
|
+
sourceId: string;
|
|
22
|
+
repoFullName: string;
|
|
23
|
+
event: 'push' | 'pull_request' | 'dispatch';
|
|
24
|
+
ref: string;
|
|
25
|
+
sha: string;
|
|
26
|
+
defaultBranch: string;
|
|
27
|
+
/**
|
|
28
|
+
* Dispatch event action (`dispatch()` `types` matcher key). Only used when
|
|
29
|
+
* `event === 'dispatch'`; the local normalizer reads it from `payload.action`,
|
|
30
|
+
* so exactly one `types`-filtered workflow matches.
|
|
31
|
+
*/
|
|
32
|
+
action?: string;
|
|
33
|
+
/** Dispatch `client_payload` (steps read `ctx.rawPayload.client_payload`). */
|
|
34
|
+
clientPayload?: unknown;
|
|
35
|
+
}
|
|
36
|
+
export interface LocalTriggerRequest {
|
|
37
|
+
path: string;
|
|
38
|
+
headers: Record<string, string>;
|
|
39
|
+
body: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Build the GitHub-shaped webhook request the plane's local provider normalizer
|
|
43
|
+
* expects. Mirrors `buildLocalTriggerRequest` in the orchestrator's
|
|
44
|
+
* `cli/commands/local-trigger.ts` (kept local to honour the compiler-only scope
|
|
45
|
+
* of this phase — no orchestrator export is added).
|
|
46
|
+
*/
|
|
47
|
+
export declare function buildLocalTriggerRequest(input: LocalTriggerInput): LocalTriggerRequest;
|
|
48
|
+
/** Structured result of posting a local trigger to the plane. */
|
|
49
|
+
export interface LocalTriggerResponse {
|
|
50
|
+
status: number;
|
|
51
|
+
/** Routing-key-scoped delivery id echoed by the plane, or null if absent. */
|
|
52
|
+
deliveryId: string | null;
|
|
53
|
+
}
|
|
54
|
+
/** POST the trigger request to the plane. Returns the status + delivery id. */
|
|
55
|
+
export declare function sendLocalTrigger(planeUrl: string, req: LocalTriggerRequest): Promise<LocalTriggerResponse>;
|
|
56
|
+
export interface TriggerRunOptions {
|
|
57
|
+
client?: RunDiscoveryClient;
|
|
58
|
+
pollIntervalMs?: number;
|
|
59
|
+
resendAfterMs?: number;
|
|
60
|
+
timeoutMs?: number;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Trigger the run and resolve its runId. Sends the synthetic push, then polls
|
|
64
|
+
* the admin runs list filtered by this webhook's routing-key-scoped delivery id
|
|
65
|
+
* (`?deliveryId=`), resending the webhook past a grace window to absorb source
|
|
66
|
+
* hot-reload latency. Correlating on the exact delivery id — rather than the
|
|
67
|
+
* newest run in a time window — guarantees the follower attaches to the run
|
|
68
|
+
* THIS invocation created, even when another local run lands concurrently.
|
|
69
|
+
*/
|
|
70
|
+
export declare function triggerRun(planeUrl: string, adminToken: string, input: LocalTriggerInput, opts?: TriggerRunOptions): Promise<string>;
|
|
71
|
+
//# sourceMappingURL=plane-trigger.d.ts.map
|
|
@@ -0,0 +1,110 @@
|
|
|
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 status + delivery id. */
|
|
49
|
+
async function sendLocalTrigger(planeUrl, req) {
|
|
50
|
+
const res = await fetch(`${planeUrl.replace(/\/$/, "")}${req.path}`, {
|
|
51
|
+
method: "POST",
|
|
52
|
+
headers: req.headers,
|
|
53
|
+
body: req.body
|
|
54
|
+
});
|
|
55
|
+
let deliveryId = null;
|
|
56
|
+
try {
|
|
57
|
+
const parsed = await res.json();
|
|
58
|
+
if (typeof parsed.deliveryId === "string") deliveryId = parsed.deliveryId;
|
|
59
|
+
} catch {}
|
|
60
|
+
return {
|
|
61
|
+
status: res.status,
|
|
62
|
+
deliveryId
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Trigger the run and resolve its runId. Sends the synthetic push, then polls
|
|
67
|
+
* the admin runs list filtered by this webhook's routing-key-scoped delivery id
|
|
68
|
+
* (`?deliveryId=`), resending the webhook past a grace window to absorb source
|
|
69
|
+
* hot-reload latency. Correlating on the exact delivery id — rather than the
|
|
70
|
+
* newest run in a time window — guarantees the follower attaches to the run
|
|
71
|
+
* THIS invocation created, even when another local run lands concurrently.
|
|
72
|
+
*/
|
|
73
|
+
async function triggerRun(planeUrl, adminToken, input, opts = {}) {
|
|
74
|
+
const client = opts.client ?? new AdminApiClient(planeUrl, adminToken);
|
|
75
|
+
const pollIntervalMs = opts.pollIntervalMs ?? 750;
|
|
76
|
+
const resendAfterMs = opts.resendAfterMs ?? 8e3;
|
|
77
|
+
const timeoutMs = opts.timeoutMs ?? 6e4;
|
|
78
|
+
const req = buildLocalTriggerRequest(input);
|
|
79
|
+
let deliveryId = (await sendLocalTrigger(planeUrl, req)).deliveryId;
|
|
80
|
+
let lastSend = Date.now();
|
|
81
|
+
const deadline = Date.now() + timeoutMs;
|
|
82
|
+
while (Date.now() < deadline) {
|
|
83
|
+
if (deliveryId) {
|
|
84
|
+
const runId = await findRunByDelivery(client, deliveryId);
|
|
85
|
+
if (runId) return runId;
|
|
86
|
+
}
|
|
87
|
+
if (Date.now() - lastSend > resendAfterMs) {
|
|
88
|
+
deliveryId ??= (await sendLocalTrigger(planeUrl, req)).deliveryId;
|
|
89
|
+
lastSend = Date.now();
|
|
90
|
+
}
|
|
91
|
+
await sleep(pollIntervalMs);
|
|
92
|
+
}
|
|
93
|
+
throw new Error("offline run: no run appeared after triggering the local plane");
|
|
94
|
+
}
|
|
95
|
+
/** Return the run created by this webhook delivery, or null when none yet. */
|
|
96
|
+
async function findRunByDelivery(client, deliveryId) {
|
|
97
|
+
const qs = new URLSearchParams({
|
|
98
|
+
deliveryId,
|
|
99
|
+
limit: "1"
|
|
100
|
+
});
|
|
101
|
+
const { runs } = await client.get(`/api/v1/admin/runs?${qs}`);
|
|
102
|
+
return runs[0]?.runId ?? null;
|
|
103
|
+
}
|
|
104
|
+
function sleep(ms) {
|
|
105
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
106
|
+
}
|
|
107
|
+
//#endregion
|
|
108
|
+
export { buildLocalTriggerRequest, sendLocalTrigger, triggerRun };
|
|
109
|
+
|
|
110
|
+
//# 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,107 @@
|
|
|
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
|
+
return process.env.HOSTNAME || "kici-local";
|
|
103
|
+
}
|
|
104
|
+
//#endregion
|
|
105
|
+
export { derivePlatformWsUrl, mintOrchestratorKey, probePlatformReachable, revokeOrchestratorKey };
|
|
106
|
+
|
|
107
|
+
//# sourceMappingURL=platform-attach.js.map
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/** Command runner, injectable so each platform branch is unit-testable anywhere. */
|
|
2
|
+
export type ExecFn = (cmd: string, args: string[]) => Promise<{
|
|
3
|
+
stdout: string;
|
|
4
|
+
exitCode: number;
|
|
5
|
+
}>;
|
|
6
|
+
/**
|
|
7
|
+
* Command line of process `pid`, or null when it cannot be determined.
|
|
8
|
+
*
|
|
9
|
+
* Linux exposes it as a NUL-separated `/proc` file; every other platform is
|
|
10
|
+
* asked through the same injectable `ExecFn` the port discovery above uses.
|
|
11
|
+
*
|
|
12
|
+
* **A null means "cannot tell", never "not that process."** Callers must treat
|
|
13
|
+
* it as an absence of evidence — the only safe reading when the answer decides
|
|
14
|
+
* whether to signal a pid.
|
|
15
|
+
*/
|
|
16
|
+
export declare function processCommandLine(pid: number, opts?: {
|
|
17
|
+
exec?: ExecFn;
|
|
18
|
+
platform?: NodeJS.Platform;
|
|
19
|
+
readProc?: (path: string) => string;
|
|
20
|
+
}): Promise<string | null>;
|
|
21
|
+
/**
|
|
22
|
+
* Whether the port can be bound on loopback. Probes `127.0.0.1` because that is
|
|
23
|
+
* the address the CLI talks to; a listener bound to `0.0.0.0` still makes this
|
|
24
|
+
* bind fail with EADDRINUSE, so there is no false "free".
|
|
25
|
+
*/
|
|
26
|
+
export declare function isPortFree(port: number): Promise<boolean>;
|
|
27
|
+
/**
|
|
28
|
+
* Parse `ss -lptnH` rows. The process name is deliberately NOT matched — a Node
|
|
29
|
+
* listener reports as `MainThread` — so only `pid=N` is extracted. A row for a
|
|
30
|
+
* socket this user does not own carries no `users:(…)` field at all and yields
|
|
31
|
+
* null, which is what sends the caller to the lsof fallback.
|
|
32
|
+
*/
|
|
33
|
+
export declare function parseSsOutput(stdout: string, port: number): number | null;
|
|
34
|
+
/**
|
|
35
|
+
* Parse `lsof -Fpn` field output, which is a stateful stream: each `p<pid>` line
|
|
36
|
+
* precedes the `n<address>` lines belonging to that pid.
|
|
37
|
+
*/
|
|
38
|
+
export declare function parseLsofOutput(stdout: string, port: number): number | null;
|
|
39
|
+
/** Parse `netstat -ano -p tcp`: LISTENING rows only, pid in the last column. */
|
|
40
|
+
export declare function parseNetstatOutput(stdout: string, port: number): number | null;
|
|
41
|
+
/**
|
|
42
|
+
* Pid of the process listening on `port`, or null when it cannot be determined.
|
|
43
|
+
* Each platform runs the UNFILTERED listing and matches the port here rather
|
|
44
|
+
* than passing a filter expression to the tool, whose syntax is the fragile
|
|
45
|
+
* part. On Linux a null parse falls through to lsof as well as a non-zero exit,
|
|
46
|
+
* because ss omits the owning-process field for sockets this user does not own.
|
|
47
|
+
*/
|
|
48
|
+
export declare function findPortHolderPid(port: number, opts?: {
|
|
49
|
+
exec?: ExecFn;
|
|
50
|
+
platform?: NodeJS.Platform;
|
|
51
|
+
}): Promise<number | null>;
|
|
52
|
+
/** Signal sender, injectable so the escalation windows are unit-testable. */
|
|
53
|
+
export type KillFn = (pid: number, signal: NodeJS.Signals | 0) => void;
|
|
54
|
+
/**
|
|
55
|
+
* Poll until process `pid` has exited, sending SIGTERM first and escalating to
|
|
56
|
+
* SIGKILL past the grace window. `process.kill(pid, 0)` throws ESRCH once the
|
|
57
|
+
* process is gone. On Windows every signal terminates unconditionally, so the
|
|
58
|
+
* escalation collapses into a single hard kill there.
|
|
59
|
+
*
|
|
60
|
+
* Resolving does NOT prove the process died: a pid that survives SIGKILL (a
|
|
61
|
+
* zombie awaiting reaping, or a task wedged in uninterruptible I/O) is given
|
|
62
|
+
* `hardKillWaitMs` and then abandoned, because a teardown command that hangs
|
|
63
|
+
* forever is worse than one that reports the port is still held. Callers decide
|
|
64
|
+
* the outcome from the port itself — `waitForPortFree` — never from this
|
|
65
|
+
* resolving.
|
|
66
|
+
*/
|
|
67
|
+
export declare function terminatePid(pid: number, graceMs?: number, opts?: {
|
|
68
|
+
hardKillWaitMs?: number;
|
|
69
|
+
kill?: KillFn;
|
|
70
|
+
}): Promise<void>;
|
|
71
|
+
/**
|
|
72
|
+
* Poll until the port is bindable. This, not `terminatePid` resolving, is what
|
|
73
|
+
* decides a teardown: `terminatePid` abandons a pid that survives SIGKILL, and
|
|
74
|
+
* even a process that did exit can leave its listening socket lingering a moment
|
|
75
|
+
* longer on some platforms.
|
|
76
|
+
*/
|
|
77
|
+
export declare function waitForPortFree(port: number, timeoutMs?: number): Promise<boolean>;
|
|
78
|
+
//# sourceMappingURL=port-holder.d.ts.map
|