@kici-dev/compiler 0.1.21 → 0.1.23
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.js +25 -7
- package/dist/commands/compile.d.ts +6 -0
- package/dist/commands/compile.js +6 -3
- package/dist/commands/docs.d.ts +8 -8
- package/dist/commands/docs.js +35 -16
- package/dist/commands/held-run-client.d.ts +7 -2
- package/dist/commands/held-run-client.js +9 -3
- package/dist/commands/held-run-resolve.d.ts +5 -0
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +15 -7
- package/dist/commands/org.js +2 -2
- package/dist/commands/run-hold-watch.d.ts +57 -0
- package/dist/commands/run-hold-watch.js +87 -0
- package/dist/commands/run.d.ts +23 -0
- package/dist/commands/run.js +150 -17
- package/dist/commands/test.d.ts +14 -0
- package/dist/commands/types.d.ts +2 -0
- package/dist/commands/types.js +1 -1
- package/dist/fixtures/describe-event.d.ts +6 -0
- package/dist/fixtures/describe-event.js +18 -0
- package/dist/fixtures/picker.d.ts +19 -0
- package/dist/fixtures/picker.js +64 -0
- package/dist/llm-context/llms-architecture.txt +1440 -0
- package/dist/llm-context/llms-cli.txt +2386 -0
- package/dist/llm-context/llms-features.txt +2389 -0
- package/dist/llm-context/llms-full.txt +1304 -349
- package/dist/llm-context/llms-getting-started.txt +519 -0
- package/dist/llm-context/llms-patterns.txt +1324 -0
- package/dist/llm-context/llms-providers.txt +805 -0
- package/dist/llm-context/llms-sdk.txt +3725 -0
- package/dist/llm-context/llms.txt +15 -1
- package/dist/local-executor/index.js +40 -3
- package/dist/local-executor/job-runner.d.ts +2 -0
- package/dist/local-executor/job-runner.js +37 -4
- package/dist/local-executor/types.d.ts +2 -0
- package/dist/lockfile/generator.js +46 -20
- package/dist/remote/config.d.ts +2 -0
- package/dist/remote/config.js +1 -0
- package/dist/remote/platform-client.d.ts +12 -1
- package/dist/remote/uploader.js +1 -0
- package/dist/templates/package-json.js +1 -1
- package/dist/test-runner/rule-evaluator.d.ts +1 -1
- package/dist/test-runner/rule-evaluator.js +2 -1
- package/dist/test-runner/step-context.d.ts +1 -1
- package/dist/test-runner/step-context.js +8 -2
- package/dist/types.d.ts +15 -6
- package/package.json +4 -4
- package/sbom.spdx.json +35 -35
package/dist/commands/run.js
CHANGED
|
@@ -9,11 +9,19 @@ import { createOverlayTarball, getSizeWarning, uploadTarball } from "../remote/u
|
|
|
9
9
|
import { formatJsonResult } from "../remote/output/json.js";
|
|
10
10
|
import { formatJunitResult } from "../remote/output/junit.js";
|
|
11
11
|
import { formatErrorHighlight, formatMultiFixtureSummary, formatSummary } from "../remote/output/summary.js";
|
|
12
|
+
import { compileCommand } from "./compile.js";
|
|
12
13
|
import { compileFixtures, filterFixtures } from "../fixtures/compiler.js";
|
|
14
|
+
import { describeEvent } from "../fixtures/describe-event.js";
|
|
15
|
+
import { FixturePickerCancelledError, runFixturePicker } from "../fixtures/picker.js";
|
|
16
|
+
import { listHeldRunsForRun, resolveHeldRunContext } from "./held-run-client.js";
|
|
17
|
+
import { handleNewHolds } from "./run-hold-watch.js";
|
|
13
18
|
import path from "node:path";
|
|
14
19
|
import pc from "picocolors";
|
|
15
20
|
import { readFile, writeFile } from "node:fs/promises";
|
|
16
21
|
import { formatBytes, logger, toErrorMessage } from "@kici-dev/core";
|
|
22
|
+
import { coerceDispatchInputs, parseInputPairs } from "@kici-dev/engine";
|
|
23
|
+
import { normalizeRunsOnToMatchers } from "@kici-dev/engine/labels/compile";
|
|
24
|
+
import { confirm } from "@inquirer/prompts";
|
|
17
25
|
//#region src/commands/run.ts
|
|
18
26
|
/** Terminal run statuses returned by the Platform run-status snapshot. */
|
|
19
27
|
const TERMINAL_STATUSES = new Set([
|
|
@@ -22,9 +30,81 @@ const TERMINAL_STATUSES = new Set([
|
|
|
22
30
|
"cancelled",
|
|
23
31
|
"error"
|
|
24
32
|
]);
|
|
33
|
+
/**
|
|
34
|
+
* Compile `--target` selector strings into a {@link HostTargetSelector}. Each
|
|
35
|
+
* string becomes one AND value (its own include set); repeated values
|
|
36
|
+
* AND-combine. Returns undefined when no `--target` is given. Throws when
|
|
37
|
+
* `--target-allow-empty` is set without at least one `--target`.
|
|
38
|
+
*/
|
|
39
|
+
function buildTargetSelector(targets, allowEmpty) {
|
|
40
|
+
if (!targets || targets.length === 0) {
|
|
41
|
+
if (allowEmpty) throw new Error("--target-allow-empty requires at least one --target selector");
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
values: targets.map((t) => normalizeRunsOnToMatchers(t, "kici run --target")),
|
|
46
|
+
allowEmpty
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Look up the dispatch-trigger `inputs` descriptor for a workflow from a parsed
|
|
51
|
+
* inline lock file. When `workflowName` is given, only that workflow's dispatch
|
|
52
|
+
* triggers are considered; otherwise descriptors across all workflows are merged
|
|
53
|
+
* (best-effort fast-fail — the orchestrator re-validates against the matched
|
|
54
|
+
* workflow authoritatively). Returns undefined when no dispatch inputs declared.
|
|
55
|
+
*/
|
|
56
|
+
function lookupDispatchInputsDescriptor(inlineLockFile, workflowName) {
|
|
57
|
+
if (!inlineLockFile) return void 0;
|
|
58
|
+
let lock;
|
|
59
|
+
try {
|
|
60
|
+
lock = JSON.parse(inlineLockFile);
|
|
61
|
+
} catch {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const merged = {};
|
|
65
|
+
let found = false;
|
|
66
|
+
for (const wf of lock.workflows ?? []) {
|
|
67
|
+
if (workflowName && wf.name !== workflowName) continue;
|
|
68
|
+
for (const trigger of wf.triggers ?? []) if (trigger._type === "dispatch" && trigger.inputs) {
|
|
69
|
+
Object.assign(merged, trigger.inputs);
|
|
70
|
+
found = true;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return found ? merged : void 0;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Validate raw `--input KEY=VALUE` pairs against the (optional) lock descriptor
|
|
77
|
+
* and return the raw operator pairs verbatim. The CLI fast-fails on malformed /
|
|
78
|
+
* invalid input for UX, but forwards the **raw** strings — the orchestrator is
|
|
79
|
+
* authoritative and applies coercion + defaults exactly once.
|
|
80
|
+
*/
|
|
81
|
+
function buildDispatchInputs(pairs, descriptor) {
|
|
82
|
+
if (!pairs.length) return {};
|
|
83
|
+
const raw = parseInputPairs(pairs);
|
|
84
|
+
if (descriptor) {
|
|
85
|
+
const r = coerceDispatchInputs(raw, descriptor);
|
|
86
|
+
if ("error" in r) throw r.error;
|
|
87
|
+
}
|
|
88
|
+
return raw;
|
|
89
|
+
}
|
|
25
90
|
/** Interval between status/log polls while a run is active. */
|
|
26
91
|
const POLL_INTERVAL_MS = 750;
|
|
27
92
|
/**
|
|
93
|
+
* Recompile `.kici/workflows` → `kici.lock.json` before a remote run, mirroring
|
|
94
|
+
* `kici run local`. The orchestrator matches triggers and dispatches against the
|
|
95
|
+
* inline lock, so a stale lock would route an edited or newly-added workflow
|
|
96
|
+
* incorrectly. Returns false on a compile/validation error so the caller can
|
|
97
|
+
* abort before any upload or dispatch.
|
|
98
|
+
*/
|
|
99
|
+
async function compileBeforeRemoteRun(options) {
|
|
100
|
+
return compileCommand({
|
|
101
|
+
kiciDir: options.kiciDir ?? ".kici",
|
|
102
|
+
check: false,
|
|
103
|
+
verbose: options.debug ?? false,
|
|
104
|
+
quiet: Boolean(options.json || options.quiet)
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
28
108
|
* Run a workflow locally using the local executor.
|
|
29
109
|
* Thin wrapper that delegates to executeLocal from local-executor.
|
|
30
110
|
*
|
|
@@ -58,6 +138,7 @@ async function runRemoteCommand(fixture, options) {
|
|
|
58
138
|
if (!options.quiet) logger.info(pc.gray("Debug mode enabled"));
|
|
59
139
|
}
|
|
60
140
|
try {
|
|
141
|
+
buildTargetSelector(options.targets, options.targetAllowEmpty ?? false);
|
|
61
142
|
if (options.workflow && !fixture && !options.all) return await runDirectWorkflow(options.workflow, options);
|
|
62
143
|
const kiciDir = resolveKiciDir(options.kiciDir);
|
|
63
144
|
if (options.history) {
|
|
@@ -68,9 +149,20 @@ async function runRemoteCommand(fixture, options) {
|
|
|
68
149
|
return true;
|
|
69
150
|
}
|
|
70
151
|
const fixtures = await compileFixtures(path.join(kiciDir, "tests"));
|
|
71
|
-
if (!fixture && !options.all) return listFixtures(fixtures);
|
|
72
152
|
let selected;
|
|
73
|
-
if (options.
|
|
153
|
+
if (options.pick) {
|
|
154
|
+
if (fixtures.length === 0) return listFixtures(fixtures);
|
|
155
|
+
try {
|
|
156
|
+
selected = await runFixturePicker(fixtures);
|
|
157
|
+
} catch (err) {
|
|
158
|
+
if (err instanceof FixturePickerCancelledError) {
|
|
159
|
+
logger.info(pc.gray(err.message));
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
throw err;
|
|
163
|
+
}
|
|
164
|
+
} else if (!fixture && !options.all) return listFixtures(fixtures);
|
|
165
|
+
else if (options.all) selected = fixtures;
|
|
74
166
|
else selected = filterFixtures(fixtures, fixture);
|
|
75
167
|
if (selected.length === 0) {
|
|
76
168
|
logger.info(pc.yellow(`No fixtures matched: ${fixture ?? "(none)"}`));
|
|
@@ -111,17 +203,6 @@ function listFixtures(fixtures) {
|
|
|
111
203
|
return true;
|
|
112
204
|
}
|
|
113
205
|
/**
|
|
114
|
-
* Describe the event type from a fixture's trigger config.
|
|
115
|
-
*/
|
|
116
|
-
function describeEvent(event) {
|
|
117
|
-
if (!event || typeof event !== "object") return "unknown";
|
|
118
|
-
const e = event;
|
|
119
|
-
if (e._type === "push") return "push";
|
|
120
|
-
if (e._type === "pr") return `pr:${e.action ?? "open"}`;
|
|
121
|
-
if (typeof e._type === "string") return String(e._type);
|
|
122
|
-
return "custom";
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
206
|
* Resolve the authenticated Platform client and the run target (org + cluster).
|
|
126
207
|
*
|
|
127
208
|
* Org resolution: `--org` → `config.activeOrgId` → error.
|
|
@@ -161,6 +242,7 @@ function resolvePlatformContext(config, options) {
|
|
|
161
242
|
* Run fixtures remotely against the Platform.
|
|
162
243
|
*/
|
|
163
244
|
async function runFixturesRemotely(fixtures, options) {
|
|
245
|
+
if (!await compileBeforeRemoteRun(options)) return false;
|
|
164
246
|
const config = await loadGlobalConfig();
|
|
165
247
|
const ctx = resolvePlatformContext(config, options);
|
|
166
248
|
if (!ctx) return false;
|
|
@@ -295,7 +377,16 @@ async function runSingleFixture(fixture, ctx, options, config, history) {
|
|
|
295
377
|
workflowName: opts.workflowName,
|
|
296
378
|
inlineLockFile: overlay.inlineLockFile,
|
|
297
379
|
fullRepo: true,
|
|
298
|
-
...options.checkMode && { checkMode: options.checkMode }
|
|
380
|
+
...options.checkMode && { checkMode: options.checkMode },
|
|
381
|
+
...(() => {
|
|
382
|
+
const target = buildTargetSelector(options.targets, options.targetAllowEmpty ?? false);
|
|
383
|
+
return target ? { target } : {};
|
|
384
|
+
})(),
|
|
385
|
+
...(() => {
|
|
386
|
+
const descriptor = lookupDispatchInputsDescriptor(overlay.inlineLockFile, opts.workflowName);
|
|
387
|
+
const dispatchInputs = buildDispatchInputs(options.inputs ?? [], descriptor);
|
|
388
|
+
return Object.keys(dispatchInputs).length ? { dispatchInputs } : {};
|
|
389
|
+
})()
|
|
299
390
|
});
|
|
300
391
|
if (triggerResult.status === "rejected") {
|
|
301
392
|
if (!options.quiet) logger.info(pc.red(`Rejected: ${triggerResult.reason ?? "unknown reason"}`));
|
|
@@ -372,6 +463,8 @@ async function pollRunToCompletion(ctx, runId, fixtureId, options) {
|
|
|
372
463
|
process.on("SIGINT", cancelHandler);
|
|
373
464
|
try {
|
|
374
465
|
let lastStatus = null;
|
|
466
|
+
const seenHolds = /* @__PURE__ */ new Set();
|
|
467
|
+
let heldCtx;
|
|
375
468
|
while (!cancelled) {
|
|
376
469
|
const logs = await ctx.client.runLogs(ctx.orgId, runId, cursor, ctx.target);
|
|
377
470
|
for (const line of logs.lines) {
|
|
@@ -381,7 +474,9 @@ async function pollRunToCompletion(ctx, runId, fixtureId, options) {
|
|
|
381
474
|
}
|
|
382
475
|
cursor = logs.nextCursor;
|
|
383
476
|
lastStatus = await ctx.client.runStatus(ctx.orgId, runId, ctx.target);
|
|
384
|
-
|
|
477
|
+
const terminal = lastStatus.done || TERMINAL_STATUSES.has(lastStatus.status);
|
|
478
|
+
if (terminal && logs.done) return finishRun(fixtureId, runId, lastStatus, startTime, tailLines, options);
|
|
479
|
+
if (!terminal && !options.quiet) await watchRunHolds(runId, seenHolds, () => heldCtx, (c) => heldCtx = c, options);
|
|
385
480
|
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
386
481
|
}
|
|
387
482
|
return {
|
|
@@ -395,6 +490,34 @@ async function pollRunToCompletion(ctx, runId, fixtureId, options) {
|
|
|
395
490
|
process.removeListener("SIGINT", cancelHandler);
|
|
396
491
|
}
|
|
397
492
|
}
|
|
493
|
+
/**
|
|
494
|
+
* Fetch this run's pending holds and surface them to the operator. The held-run
|
|
495
|
+
* context is resolved lazily (cached across ticks via the getter/setter). A
|
|
496
|
+
* resolution / fetch failure is swallowed — hold-visibility is best-effort and
|
|
497
|
+
* must never abort the run watch.
|
|
498
|
+
*/
|
|
499
|
+
async function watchRunHolds(runId, seenHolds, getCtx, setCtx, options) {
|
|
500
|
+
try {
|
|
501
|
+
let ctx = getCtx();
|
|
502
|
+
if (ctx === void 0) {
|
|
503
|
+
ctx = await resolveHeldRunContext();
|
|
504
|
+
setCtx(ctx);
|
|
505
|
+
}
|
|
506
|
+
if (!ctx) return;
|
|
507
|
+
const holds = await listHeldRunsForRun(ctx, runId);
|
|
508
|
+
if (holds.length === 0) return;
|
|
509
|
+
await handleNewHolds({
|
|
510
|
+
holds,
|
|
511
|
+
seen: seenHolds,
|
|
512
|
+
isTty: Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
|
513
|
+
approveAll: Boolean(options.approveAll),
|
|
514
|
+
confirm: (message) => confirm({
|
|
515
|
+
message,
|
|
516
|
+
default: false
|
|
517
|
+
})
|
|
518
|
+
});
|
|
519
|
+
} catch {}
|
|
520
|
+
}
|
|
398
521
|
/** Build the final result + render the summary table for a completed run. */
|
|
399
522
|
function finishRun(fixtureId, runId, status, startTime, tailLines, options) {
|
|
400
523
|
const durationMs = Date.now() - startTime;
|
|
@@ -456,6 +579,7 @@ function buildEventFromFixture(opts) {
|
|
|
456
579
|
* Platform.
|
|
457
580
|
*/
|
|
458
581
|
async function runDirectWorkflow(workflowName, options) {
|
|
582
|
+
if (!await compileBeforeRemoteRun(options)) return false;
|
|
459
583
|
const ctx = resolvePlatformContext(await loadGlobalConfig(), options);
|
|
460
584
|
if (!ctx) return false;
|
|
461
585
|
if (options.json) options.quiet = true;
|
|
@@ -483,7 +607,16 @@ async function runDirectWorkflow(workflowName, options) {
|
|
|
483
607
|
workflowName,
|
|
484
608
|
inlineLockFile: overlay.inlineLockFile,
|
|
485
609
|
fullRepo: true,
|
|
486
|
-
...options.checkMode && { checkMode: options.checkMode }
|
|
610
|
+
...options.checkMode && { checkMode: options.checkMode },
|
|
611
|
+
...(() => {
|
|
612
|
+
const target = buildTargetSelector(options.targets, options.targetAllowEmpty ?? false);
|
|
613
|
+
return target ? { target } : {};
|
|
614
|
+
})(),
|
|
615
|
+
...(() => {
|
|
616
|
+
const descriptor = lookupDispatchInputsDescriptor(overlay.inlineLockFile, workflowName);
|
|
617
|
+
const dispatchInputs = buildDispatchInputs(options.inputs ?? [], descriptor);
|
|
618
|
+
return Object.keys(dispatchInputs).length ? { dispatchInputs } : {};
|
|
619
|
+
})()
|
|
487
620
|
});
|
|
488
621
|
if (!options.quiet) logger.info(pc.green(`Run started: ${triggerResult.runId}`));
|
|
489
622
|
if (options.wait === false) return triggerResult.status === "accepted";
|
|
@@ -511,6 +644,6 @@ function displayRemoteResults(results) {
|
|
|
511
644
|
logger.info("");
|
|
512
645
|
}
|
|
513
646
|
//#endregion
|
|
514
|
-
export { runLocalCommand, runRemoteCommand };
|
|
647
|
+
export { buildDispatchInputs, buildTargetSelector, lookupDispatchInputsDescriptor, runLocalCommand, runRemoteCommand };
|
|
515
648
|
|
|
516
649
|
//# sourceMappingURL=run.js.map
|
package/dist/commands/test.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ export interface TestOptions extends PayloadOptions {
|
|
|
19
19
|
export interface RemoteRunOptions extends TestOptions {
|
|
20
20
|
/** Run all available fixtures */
|
|
21
21
|
all?: boolean;
|
|
22
|
+
/** Interactively pick fixtures to run (multi-select checkbox). */
|
|
23
|
+
pick?: boolean;
|
|
22
24
|
/** Run matching fixtures concurrently */
|
|
23
25
|
parallel?: boolean;
|
|
24
26
|
/** Fire and forget (print runIds, don't stream) */
|
|
@@ -45,6 +47,18 @@ export interface RemoteRunOptions extends TestOptions {
|
|
|
45
47
|
* Defaults to `apply`.
|
|
46
48
|
*/
|
|
47
49
|
checkMode?: CheckMode;
|
|
50
|
+
/** `--target <selector>` values (repeatable), AND-combined into host narrowing. */
|
|
51
|
+
targets?: string[];
|
|
52
|
+
/** `--target-allow-empty`: a target that zeroes a runsOnAll job skips it instead of failing. */
|
|
53
|
+
targetAllowEmpty?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* `--approve-all` (alias `--yes`): auto-approve every approval gate this run
|
|
56
|
+
* holds on (run-scoped only — the run id this invocation dispatched). The
|
|
57
|
+
* operator must still be clause-eligible per hold; an ineligible hold blocks.
|
|
58
|
+
*/
|
|
59
|
+
approveAll?: boolean;
|
|
60
|
+
/** `--input KEY=VALUE` values (repeatable): typed workflow-dispatch inputs. */
|
|
61
|
+
inputs?: string[];
|
|
48
62
|
}
|
|
49
63
|
/** Result of a single remote fixture run */
|
|
50
64
|
export interface RemoteRunResult {
|
package/dist/commands/types.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export interface TypesOptions {
|
|
2
2
|
/** Path to .kici directory (defaults to .kici) */
|
|
3
3
|
kiciDir?: string;
|
|
4
|
+
/** Suppress the success line on stdout (so machine-readable output stays pure). */
|
|
5
|
+
quiet?: boolean;
|
|
4
6
|
}
|
|
5
7
|
/**
|
|
6
8
|
* Generate TypeScript declarations for environment secrets.
|
package/dist/commands/types.js
CHANGED
|
@@ -33,7 +33,7 @@ async function typesCommand(options = {}) {
|
|
|
33
33
|
await fs.mkdir(typesDir, { recursive: true });
|
|
34
34
|
const outputPath = path.join(typesDir, "secrets.d.ts");
|
|
35
35
|
await fs.writeFile(outputPath, dtsContent, "utf-8");
|
|
36
|
-
console.log(pc.green("Types generated") + pc.dim(` ${outputPath}`));
|
|
36
|
+
if (!options.quiet) console.log(pc.green("Types generated") + pc.dim(` ${outputPath}`));
|
|
37
37
|
return true;
|
|
38
38
|
} catch (err) {
|
|
39
39
|
if (err instanceof DashboardClientError) {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import "../chunk-BTugEXQM.js";
|
|
2
|
+
//#region src/fixtures/describe-event.ts
|
|
3
|
+
/**
|
|
4
|
+
* Describe the event type from a fixture's trigger config, for the fixtures
|
|
5
|
+
* table and the interactive picker.
|
|
6
|
+
*/
|
|
7
|
+
function describeEvent(event) {
|
|
8
|
+
if (!event || typeof event !== "object") return "unknown";
|
|
9
|
+
const e = event;
|
|
10
|
+
if (e._type === "push") return "push";
|
|
11
|
+
if (e._type === "pr") return `pr:${e.action ?? "open"}`;
|
|
12
|
+
if (typeof e._type === "string") return String(e._type);
|
|
13
|
+
return "custom";
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
export { describeEvent };
|
|
17
|
+
|
|
18
|
+
//# sourceMappingURL=describe-event.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive multi-select fixture picker for `kici run remote --pick`.
|
|
3
|
+
*
|
|
4
|
+
* Lists every available fixture with its source and event type, lets the user
|
|
5
|
+
* toggle one or more via a checkbox prompt, and returns the chosen fixtures.
|
|
6
|
+
* The caller feeds them into the standard remote-dispatch pipeline, so a picked
|
|
7
|
+
* run is identical to one selected by name / glob / --all.
|
|
8
|
+
*/
|
|
9
|
+
import type { CompiledFixture } from './compiler.js';
|
|
10
|
+
export declare class FixturePickerCancelledError extends Error {
|
|
11
|
+
constructor(message: string);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Run the interactive multi-select fixture picker.
|
|
15
|
+
*
|
|
16
|
+
* @throws FixturePickerCancelledError when stdin is not a TTY or the user aborts.
|
|
17
|
+
*/
|
|
18
|
+
export declare function runFixturePicker(fixtures: CompiledFixture[]): Promise<CompiledFixture[]>;
|
|
19
|
+
//# sourceMappingURL=picker.d.ts.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import "../chunk-BTugEXQM.js";
|
|
2
|
+
import { describeEvent } from "./describe-event.js";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import pc from "picocolors";
|
|
5
|
+
import { logger } from "@kici-dev/core";
|
|
6
|
+
import { checkbox } from "@inquirer/prompts";
|
|
7
|
+
//#region src/fixtures/picker.ts
|
|
8
|
+
/**
|
|
9
|
+
* Interactive multi-select fixture picker for `kici run remote --pick`.
|
|
10
|
+
*
|
|
11
|
+
* Lists every available fixture with its source and event type, lets the user
|
|
12
|
+
* toggle one or more via a checkbox prompt, and returns the chosen fixtures.
|
|
13
|
+
* The caller feeds them into the standard remote-dispatch pipeline, so a picked
|
|
14
|
+
* run is identical to one selected by name / glob / --all.
|
|
15
|
+
*/
|
|
16
|
+
var FixturePickerCancelledError = class extends Error {
|
|
17
|
+
constructor(message) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = "FixturePickerCancelledError";
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
function isStdinTty() {
|
|
23
|
+
return Boolean(process.stdin.isTTY);
|
|
24
|
+
}
|
|
25
|
+
/** Build a single fixture's display row: `id <source> <event type>`. */
|
|
26
|
+
function fixtureRow(f) {
|
|
27
|
+
const opts = typeof f.fixture.options === "function" ? null : f.fixture.options;
|
|
28
|
+
const eventType = opts?.event ? describeEvent(opts.event) : "(async)";
|
|
29
|
+
const source = path.relative(process.cwd(), f.sourceFile);
|
|
30
|
+
return `${f.id} ${pc.gray(source)} ${eventType}`;
|
|
31
|
+
}
|
|
32
|
+
function printFixtureList(fixtures) {
|
|
33
|
+
logger.info(pc.bold("Available fixtures:"));
|
|
34
|
+
for (const f of fixtures) logger.info(` ${pc.cyan(f.id)} — ${fixtureRow(f)}`);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Run the interactive multi-select fixture picker.
|
|
38
|
+
*
|
|
39
|
+
* @throws FixturePickerCancelledError when stdin is not a TTY or the user aborts.
|
|
40
|
+
*/
|
|
41
|
+
async function runFixturePicker(fixtures) {
|
|
42
|
+
if (!isStdinTty()) {
|
|
43
|
+
printFixtureList(fixtures);
|
|
44
|
+
throw new FixturePickerCancelledError("--pick requires an interactive terminal. Pass a fixture name instead.");
|
|
45
|
+
}
|
|
46
|
+
let chosenIds;
|
|
47
|
+
try {
|
|
48
|
+
chosenIds = await checkbox({
|
|
49
|
+
message: "Select fixtures to run",
|
|
50
|
+
choices: fixtures.map((f) => ({
|
|
51
|
+
name: fixtureRow(f),
|
|
52
|
+
value: f.id
|
|
53
|
+
})),
|
|
54
|
+
required: true
|
|
55
|
+
});
|
|
56
|
+
} catch (err) {
|
|
57
|
+
throw new FixturePickerCancelledError(`Picker cancelled: ${err instanceof Error ? err.message : String(err)}`);
|
|
58
|
+
}
|
|
59
|
+
return fixtures.filter((f) => chosenIds.includes(f.id));
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
export { FixturePickerCancelledError, runFixturePicker };
|
|
63
|
+
|
|
64
|
+
//# sourceMappingURL=picker.js.map
|