@kici-dev/compiler 0.1.22 → 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 +20 -6
- 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/org.js +2 -2
- package/dist/commands/run.d.ts +16 -1
- package/dist/commands/run.js +86 -14
- package/dist/commands/test.d.ts +4 -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 +976 -317
- 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 +13 -0
- 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 +36 -4
- package/dist/local-executor/types.d.ts +2 -0
- package/dist/lockfile/generator.js +13 -4
- package/dist/remote/platform-client.d.ts +6 -0
- 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 +7 -2
- package/dist/types.d.ts +6 -2
- package/package.json +4 -4
- package/sbom.spdx.json +35 -35
package/dist/cli.js
CHANGED
|
@@ -7,7 +7,7 @@ import { realpathSync } from "node:fs";
|
|
|
7
7
|
import { Argument, Command, Option } from "commander";
|
|
8
8
|
import pc from "picocolors";
|
|
9
9
|
//#region src/cli.ts
|
|
10
|
-
const version = "0.1.
|
|
10
|
+
const version = "0.1.23";
|
|
11
11
|
/**
|
|
12
12
|
* Build the kici Commander program with every command registered. Exported so
|
|
13
13
|
* the surface registry can walk the real command tree without parsing argv (no
|
|
@@ -44,7 +44,7 @@ function buildProgram() {
|
|
|
44
44
|
await fixtureCommand(event, options);
|
|
45
45
|
});
|
|
46
46
|
const runCommand = program.command("run").description("Execute workflows locally or remotely");
|
|
47
|
-
runCommand.command("local").argument("[event]", "Event type (e.g., push, pr:open, schedule) — optional with --pick").description("Execute workflows locally without orchestrator infrastructure").option("-p, --pick", "Interactively pick a workflow and trigger to simulate", false).option("--workflow <name>", "Run only the specified workflow").option("--job <name>", "Run only the specified job (and its dependencies)").option("--branch <name>", "Override detected git branch").option("--sha <hash>", "Override detected git SHA").option("--payload <path>", "Path to explicit event payload JSON file").option("--concurrency <n>", "Max parallel jobs (default: CPU cores)", parseInt).option("--keep-going", "Continue after job failure", false).option("--container", "Use Podman container isolation", false).option("--env <KEY=VALUE>", "Environment variable override (repeatable)", (val, prev) => [...prev, val], []).option("--quiet", "Suppress streaming output", false).option("--json", "Output structured JSON result", false).option("--junit <path>", "Output JUnit XML result").option("--files <path>", "Override changed file paths (repeatable, default: git diff)", (val, prev) => [...prev, val], []).option("--debug", "Verbose internals", false).option("--kici-dir <path>", "Path to .kici directory", ".kici").option("--in-place", "Run against the real working directory instead of an isolated tmp checkout", false).option("--keep", "Always retain the isolated tmp checkout (default: keep only on failure)", false).option("--check", "Run in check mode: report drift, change nothing", false).option("--fail-on-drift", "In check mode, exit non-zero if any step reports drift", false).action(async (event, options) => {
|
|
47
|
+
runCommand.command("local").argument("[event]", "Event type (e.g., push, pr:open, schedule) — optional with --pick").description("Execute workflows locally without orchestrator infrastructure").option("-p, --pick", "Interactively pick a workflow and trigger to simulate", false).option("--workflow <name>", "Run only the specified workflow").option("--job <name>", "Run only the specified job (and its dependencies)").option("--branch <name>", "Override detected git branch").option("--sha <hash>", "Override detected git SHA").option("--payload <path>", "Path to explicit event payload JSON file").option("--concurrency <n>", "Max parallel jobs (default: CPU cores)", parseInt).option("--keep-going", "Continue after job failure", false).option("--container", "Use Podman container isolation", false).option("--env <KEY=VALUE>", "Environment variable override (repeatable)", (val, prev) => [...prev, val], []).option("--input <KEY=VALUE>", "Typed workflow-dispatch input (repeatable)", (val, prev) => [...prev, val], []).option("--quiet", "Suppress streaming output", false).option("--json", "Output structured JSON result", false).option("--junit <path>", "Output JUnit XML result").option("--files <path>", "Override changed file paths (repeatable, default: git diff)", (val, prev) => [...prev, val], []).option("--debug", "Verbose internals", false).option("--kici-dir <path>", "Path to .kici directory", ".kici").option("--in-place", "Run against the real working directory instead of an isolated tmp checkout", false).option("--keep", "Always retain the isolated tmp checkout (default: keep only on failure)", false).option("--check", "Run in check mode: report drift, change nothing", false).option("--fail-on-drift", "In check mode, exit non-zero if any step reports drift", false).action(async (event, options) => {
|
|
48
48
|
if (options.pick && options.workflow) {
|
|
49
49
|
console.error("Error: --pick is mutually exclusive with --workflow.");
|
|
50
50
|
process.exit(2);
|
|
@@ -78,6 +78,7 @@ function buildProgram() {
|
|
|
78
78
|
keepGoing: options.keepGoing,
|
|
79
79
|
container: options.container,
|
|
80
80
|
env: options.env,
|
|
81
|
+
inputs: options.input,
|
|
81
82
|
quiet: options.quiet,
|
|
82
83
|
json: options.json,
|
|
83
84
|
junit: options.junit,
|
|
@@ -89,7 +90,19 @@ function buildProgram() {
|
|
|
89
90
|
});
|
|
90
91
|
process.exit(success ? 0 : 1);
|
|
91
92
|
});
|
|
92
|
-
runCommand.command("remote").argument("[fixture]", "Fixture name or glob pattern (omit to list available)").description("Execute fixtures remotely via orchestrator").option("--workflow <name>", "Run a specific workflow directly (bypass triggers)").option("--all", "Run all available fixtures", false).option("--parallel", "Run matching fixtures concurrently", false).option("--no-wait", "Fire and forget (print runIds, don't stream)").option("--quiet", "Suppress output except final result", false).option("--json", "Output structured JSON result", false).option("--junit <path>", "Output JUnit XML result").option("--history", "Show recent run history", false).option("--routing-key <key>", "Override routing key for this run").option("--org <id>", "Target organization (overrides the active org)").option("--orchestrator <name>", "Target orchestrator cluster (overrides the per-org default)").option("--debug", "Verbose internals", false).option("--kici-dir <path>", "Path to .kici directory", ".kici").option("--context <ctx.key=value>", "Inject a namespaced context secret, uploaded encrypted to the orchestrator (repeatable)", (val, prev) => [...prev, val], []).option("--env <KEY=VALUE>", "Provide a per-run secret (repeatable); uploaded encrypted to the orchestrator", (val, prev) => [...prev, val], []).option("--check", "Run in check mode: report drift, change nothing", false).option("--fail-on-drift", "In check mode, exit non-zero if any step reports drift", false).option("--target <selector>", "Narrow runsOnAll jobs to hosts matching this label selector (repeatable, AND-combined)", (val, prev) => [...prev, val], []).option("--target-allow-empty", "A --target that narrows a runsOnAll job to zero hosts skips it instead of failing", false).option("--approve-all, --yes", "Auto-approve every approval gate this run holds on (run-scoped; eligibility still enforced)", false).action(async (fixture, options) => {
|
|
93
|
+
runCommand.command("remote").argument("[fixture]", "Fixture name or glob pattern (omit to list available)").description("Execute fixtures remotely via orchestrator").option("--workflow <name>", "Run a specific workflow directly (bypass triggers)").option("--all", "Run all available fixtures", false).option("-p, --pick", "Interactively pick fixtures to run", false).option("--parallel", "Run matching fixtures concurrently", false).option("--no-wait", "Fire and forget (print runIds, don't stream)").option("--quiet", "Suppress output except final result", false).option("--json", "Output structured JSON result", false).option("--junit <path>", "Output JUnit XML result").option("--history", "Show recent run history", false).option("--routing-key <key>", "Override routing key for this run").option("--org <id>", "Target organization (overrides the active org)").option("--orchestrator <name>", "Target orchestrator cluster (overrides the per-org default)").option("--debug", "Verbose internals", false).option("--kici-dir <path>", "Path to .kici directory", ".kici").option("--context <ctx.key=value>", "Inject a namespaced context secret, uploaded encrypted to the orchestrator (repeatable)", (val, prev) => [...prev, val], []).option("--env <KEY=VALUE>", "Provide a per-run secret (repeatable); uploaded encrypted to the orchestrator", (val, prev) => [...prev, val], []).option("--check", "Run in check mode: report drift, change nothing", false).option("--fail-on-drift", "In check mode, exit non-zero if any step reports drift", false).option("--target <selector>", "Narrow runsOnAll jobs to hosts matching this label selector (repeatable, AND-combined)", (val, prev) => [...prev, val], []).option("--target-allow-empty", "A --target that narrows a runsOnAll job to zero hosts skips it instead of failing", false).option("--input <KEY=VALUE>", "Typed workflow-dispatch input (repeatable)", (val, prev) => [...prev, val], []).option("--approve-all, --yes", "Auto-approve every approval gate this run holds on (run-scoped; eligibility still enforced)", false).action(async (fixture, options) => {
|
|
94
|
+
if (options.pick && fixture) {
|
|
95
|
+
console.error("Error: --pick selects fixtures interactively; do not also pass a fixture name.");
|
|
96
|
+
process.exit(2);
|
|
97
|
+
}
|
|
98
|
+
if (options.pick && options.all) {
|
|
99
|
+
console.error("Error: --pick is mutually exclusive with --all.");
|
|
100
|
+
process.exit(2);
|
|
101
|
+
}
|
|
102
|
+
if (options.pick && options.workflow) {
|
|
103
|
+
console.error("Error: --pick is mutually exclusive with --workflow.");
|
|
104
|
+
process.exit(2);
|
|
105
|
+
}
|
|
93
106
|
const { runRemoteCommand } = await import("./commands/index.js");
|
|
94
107
|
const { resolveCheckMode } = await import("./commands/check-mode.js");
|
|
95
108
|
let checkMode;
|
|
@@ -108,7 +121,8 @@ function buildProgram() {
|
|
|
108
121
|
envFlags: options.env,
|
|
109
122
|
targets: options.target,
|
|
110
123
|
targetAllowEmpty: options.targetAllowEmpty,
|
|
111
|
-
approveAll: options.approveAll
|
|
124
|
+
approveAll: options.approveAll,
|
|
125
|
+
inputs: options.input
|
|
112
126
|
});
|
|
113
127
|
process.exit(success ? 0 : 1);
|
|
114
128
|
});
|
|
@@ -268,10 +282,10 @@ Environment variables:
|
|
|
268
282
|
const { docsCommand } = await import("./commands/index.js");
|
|
269
283
|
const success = await docsCommand({ open: options.open });
|
|
270
284
|
process.exit(success ? 0 : 1);
|
|
271
|
-
}).command("llm").description("Print
|
|
285
|
+
}).command("llm [topic]").description("Print KiCI LLM docs bundles. No topic prints the llms.txt index; <topic> prints a task bundle (e.g. sdk, cli, patterns, features, providers, architecture, getting-started); \"full\" prints the complete bundle.").option("--out <path>", "Write the bundle to a file instead of stdout").action(async (topic, options) => {
|
|
272
286
|
const { docsLlmCommand } = await import("./commands/index.js");
|
|
273
287
|
const success = await docsLlmCommand({
|
|
274
|
-
|
|
288
|
+
topic,
|
|
275
289
|
out: options.out
|
|
276
290
|
});
|
|
277
291
|
process.exit(success ? 0 : 1);
|
|
@@ -6,6 +6,12 @@ export interface CompileOptions {
|
|
|
6
6
|
check: boolean;
|
|
7
7
|
/** Verbose output */
|
|
8
8
|
verbose: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Suppress the success line on stdout (and the auto-types success line) so a
|
|
11
|
+
* caller emitting machine-readable output keeps stdout pure. Validation
|
|
12
|
+
* errors are still reported.
|
|
13
|
+
*/
|
|
14
|
+
quiet?: boolean;
|
|
9
15
|
}
|
|
10
16
|
/**
|
|
11
17
|
* Execute the compile command.
|
package/dist/commands/compile.js
CHANGED
|
@@ -76,7 +76,7 @@ async function compileCommand(options) {
|
|
|
76
76
|
const lockJson = serializeLockFile(generateLockFile(workflowsWithSource));
|
|
77
77
|
if (!options.check) {
|
|
78
78
|
await fs.writeFile(lockPath, lockJson, "utf-8");
|
|
79
|
-
logger.info(pc.green("✓") + ` Compiled workflows → .kici/kici.lock.json` + pc.dim(` (${workflowsWithSource.length} workflow${workflowsWithSource.length !== 1 ? "s" : ""})`));
|
|
79
|
+
if (!options.quiet) logger.info(pc.green("✓") + ` Compiled workflows → .kici/kici.lock.json` + pc.dim(` (${workflowsWithSource.length} workflow${workflowsWithSource.length !== 1 ? "s" : ""})`));
|
|
80
80
|
try {
|
|
81
81
|
const { loadGlobalConfig } = await import("../remote/config.js");
|
|
82
82
|
const config = await loadGlobalConfig();
|
|
@@ -84,12 +84,15 @@ async function compileCommand(options) {
|
|
|
84
84
|
const hasEndpoint = Boolean(config.platformEndpoint ?? config.endpoint);
|
|
85
85
|
if (hasToken && hasEndpoint && config.activeOrgId) {
|
|
86
86
|
const { typesCommand } = await import("./types.js");
|
|
87
|
-
await typesCommand({
|
|
87
|
+
await typesCommand({
|
|
88
|
+
kiciDir,
|
|
89
|
+
quiet: options.quiet
|
|
90
|
+
});
|
|
88
91
|
}
|
|
89
92
|
} catch {
|
|
90
93
|
logger.warn(pc.yellow("Could not refresh types (Platform unreachable). Compilation succeeded."));
|
|
91
94
|
}
|
|
92
|
-
} else logger.info(pc.green("✓") + ` Workflows are valid` + pc.dim(` (${workflowsWithSource.length} workflow${workflowsWithSource.length !== 1 ? "s" : ""})`));
|
|
95
|
+
} else if (!options.quiet) logger.info(pc.green("✓") + ` Workflows are valid` + pc.dim(` (${workflowsWithSource.length} workflow${workflowsWithSource.length !== 1 ? "s" : ""})`));
|
|
93
96
|
return true;
|
|
94
97
|
} catch (error) {
|
|
95
98
|
if (isCompilerError(error)) logger.error(formatError(error));
|
package/dist/commands/docs.d.ts
CHANGED
|
@@ -3,11 +3,11 @@ export interface DocsOptions {
|
|
|
3
3
|
open?: boolean;
|
|
4
4
|
}
|
|
5
5
|
export interface DocsLlmOptions {
|
|
6
|
-
/**
|
|
7
|
-
|
|
6
|
+
/** Task bundle id to print. Undefined → the llms.txt index; 'full' → llms-full.txt. */
|
|
7
|
+
topic?: string;
|
|
8
8
|
/** Override the bundled output destination (overrides default stdout). */
|
|
9
9
|
out?: string;
|
|
10
|
-
/** Override the directory holding llms
|
|
10
|
+
/** Override the directory holding the llms*.txt files (test seam). */
|
|
11
11
|
bundleDir?: string;
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
@@ -15,12 +15,12 @@ export interface DocsLlmOptions {
|
|
|
15
15
|
*/
|
|
16
16
|
export declare function docsCommand(options?: DocsOptions): Promise<boolean>;
|
|
17
17
|
/**
|
|
18
|
-
* Print
|
|
18
|
+
* Print a KiCI LLM docs bundle to stdout (or a file).
|
|
19
19
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* brief
|
|
20
|
+
* No topic prints the llms.txt index (a router listing every task bundle).
|
|
21
|
+
* A topic prints that task bundle (e.g. `sdk`, `cli`, `patterns`); `full`
|
|
22
|
+
* prints the everything-bundle. Pipe straight into a coding agent's context
|
|
23
|
+
* to brief it on KiCI authoring without an internet round-trip.
|
|
24
24
|
*/
|
|
25
25
|
export declare function docsLlmCommand(options?: DocsLlmOptions): Promise<boolean>;
|
|
26
26
|
//# sourceMappingURL=docs.d.ts.map
|
package/dist/commands/docs.js
CHANGED
|
@@ -2,12 +2,24 @@ import "../chunk-BTugEXQM.js";
|
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import pc from "picocolors";
|
|
5
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
5
|
+
import { readFile, readdir, writeFile } from "node:fs/promises";
|
|
6
6
|
import { logger, toErrorMessage } from "@kici-dev/core";
|
|
7
7
|
import open from "open";
|
|
8
8
|
//#region src/commands/docs.ts
|
|
9
9
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
10
|
const DOCS_HOME_URL = "https://kici.dev/docs/";
|
|
11
|
+
function bundleFilenameForTopic(topic) {
|
|
12
|
+
if (!topic) return "llms.txt";
|
|
13
|
+
if (topic === "full") return "llms-full.txt";
|
|
14
|
+
return `llms-${topic}.txt`;
|
|
15
|
+
}
|
|
16
|
+
async function availableTopics(bundleDir) {
|
|
17
|
+
try {
|
|
18
|
+
return [...(await readdir(bundleDir)).filter((n) => n.startsWith("llms-") && n.endsWith(".txt") && n !== "llms-full.txt").map((n) => n.slice(5, -4)).sort(), "full"];
|
|
19
|
+
} catch {
|
|
20
|
+
return ["full"];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
11
23
|
/**
|
|
12
24
|
* Open the published documentation site in the user's default browser.
|
|
13
25
|
*/
|
|
@@ -27,32 +39,39 @@ async function docsCommand(options = {}) {
|
|
|
27
39
|
}
|
|
28
40
|
}
|
|
29
41
|
/**
|
|
30
|
-
* Print
|
|
42
|
+
* Print a KiCI LLM docs bundle to stdout (or a file).
|
|
31
43
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* brief
|
|
44
|
+
* No topic prints the llms.txt index (a router listing every task bundle).
|
|
45
|
+
* A topic prints that task bundle (e.g. `sdk`, `cli`, `patterns`); `full`
|
|
46
|
+
* prints the everything-bundle. Pipe straight into a coding agent's context
|
|
47
|
+
* to brief it on KiCI authoring without an internet round-trip.
|
|
36
48
|
*/
|
|
37
49
|
async function docsLlmCommand(options = {}) {
|
|
38
|
-
const filename = options.
|
|
50
|
+
const filename = bundleFilenameForTopic(options.topic);
|
|
39
51
|
const bundleDir = options.bundleDir ?? path.join(__dirname, "..", "llm-context");
|
|
40
52
|
const bundlePath = path.join(bundleDir, filename);
|
|
53
|
+
let content;
|
|
41
54
|
try {
|
|
42
|
-
|
|
43
|
-
if (options.out) {
|
|
44
|
-
await writeFile(options.out, content, "utf-8");
|
|
45
|
-
logger.info(pc.gray(`Wrote ${filename} to ${options.out}`));
|
|
46
|
-
return true;
|
|
47
|
-
}
|
|
48
|
-
process.stdout.write(content);
|
|
49
|
-
if (!content.endsWith("\n")) process.stdout.write("\n");
|
|
50
|
-
return true;
|
|
55
|
+
content = await readFile(bundlePath, "utf-8");
|
|
51
56
|
} catch (error) {
|
|
57
|
+
if (options.topic && options.topic !== "full") {
|
|
58
|
+
const topics = await availableTopics(bundleDir);
|
|
59
|
+
logger.error(pc.red(`Unknown docs bundle "${options.topic}".`));
|
|
60
|
+
logger.info(pc.gray(`Available topics: ${topics.join(", ")} (no topic prints the index).`));
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
52
63
|
logger.error(pc.red(`Error reading bundled ${filename}: ${toErrorMessage(error)}`));
|
|
53
64
|
logger.info(pc.gray("The bundle ships with the @kici-dev/compiler package. If you built the package locally, run `pnpm build` in packages/compiler/."));
|
|
54
65
|
return false;
|
|
55
66
|
}
|
|
67
|
+
if (options.out) {
|
|
68
|
+
await writeFile(options.out, content, "utf-8");
|
|
69
|
+
logger.info(pc.gray(`Wrote ${filename} to ${options.out}`));
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
process.stdout.write(content);
|
|
73
|
+
if (!content.endsWith("\n")) process.stdout.write("\n");
|
|
74
|
+
return true;
|
|
56
75
|
}
|
|
57
76
|
//#endregion
|
|
58
77
|
export { docsCommand, docsLlmCommand };
|
package/dist/commands/org.js
CHANGED
|
@@ -65,9 +65,9 @@ async function orgListCommand() {
|
|
|
65
65
|
const isActive = org.id === config.activeOrgId;
|
|
66
66
|
const marker = isActive ? pc.green("* ") : " ";
|
|
67
67
|
const name = isActive ? pc.bold(org.displayName) : org.displayName;
|
|
68
|
-
const
|
|
68
|
+
const ownership = pc.gray(`(${org.isOwner ? "owner" : "member"})`);
|
|
69
69
|
const id = pc.gray(org.id);
|
|
70
|
-
console.log(` ${marker}${name.padEnd(nameWidth)} ${
|
|
70
|
+
console.log(` ${marker}${name.padEnd(nameWidth)} ${ownership} ${id}`);
|
|
71
71
|
}
|
|
72
72
|
console.log("");
|
|
73
73
|
return true;
|
package/dist/commands/run.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type HostTargetSelector, type InputsDescriptorMap } from '@kici-dev/engine';
|
|
2
2
|
import type { RunLocalOptions } from '../local-executor/types.js';
|
|
3
3
|
import type { RemoteRunOptions } from './test.js';
|
|
4
4
|
/**
|
|
@@ -8,6 +8,21 @@ import type { RemoteRunOptions } from './test.js';
|
|
|
8
8
|
* `--target-allow-empty` is set without at least one `--target`.
|
|
9
9
|
*/
|
|
10
10
|
export declare function buildTargetSelector(targets: string[] | undefined, allowEmpty: boolean): HostTargetSelector | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Look up the dispatch-trigger `inputs` descriptor for a workflow from a parsed
|
|
13
|
+
* inline lock file. When `workflowName` is given, only that workflow's dispatch
|
|
14
|
+
* triggers are considered; otherwise descriptors across all workflows are merged
|
|
15
|
+
* (best-effort fast-fail — the orchestrator re-validates against the matched
|
|
16
|
+
* workflow authoritatively). Returns undefined when no dispatch inputs declared.
|
|
17
|
+
*/
|
|
18
|
+
export declare function lookupDispatchInputsDescriptor(inlineLockFile: string | undefined, workflowName: string | undefined): InputsDescriptorMap | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Validate raw `--input KEY=VALUE` pairs against the (optional) lock descriptor
|
|
21
|
+
* and return the raw operator pairs verbatim. The CLI fast-fails on malformed /
|
|
22
|
+
* invalid input for UX, but forwards the **raw** strings — the orchestrator is
|
|
23
|
+
* authoritative and applies coercion + defaults exactly once.
|
|
24
|
+
*/
|
|
25
|
+
export declare function buildDispatchInputs(pairs: string[], descriptor: InputsDescriptorMap | undefined): Record<string, string>;
|
|
11
26
|
/**
|
|
12
27
|
* Run a workflow locally using the local executor.
|
|
13
28
|
* Thin wrapper that delegates to executeLocal from local-executor.
|
package/dist/commands/run.js
CHANGED
|
@@ -9,13 +9,17 @@ 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";
|
|
13
16
|
import { listHeldRunsForRun, resolveHeldRunContext } from "./held-run-client.js";
|
|
14
17
|
import { handleNewHolds } from "./run-hold-watch.js";
|
|
15
18
|
import path from "node:path";
|
|
16
19
|
import pc from "picocolors";
|
|
17
20
|
import { readFile, writeFile } from "node:fs/promises";
|
|
18
21
|
import { formatBytes, logger, toErrorMessage } from "@kici-dev/core";
|
|
22
|
+
import { coerceDispatchInputs, parseInputPairs } from "@kici-dev/engine";
|
|
19
23
|
import { normalizeRunsOnToMatchers } from "@kici-dev/engine/labels/compile";
|
|
20
24
|
import { confirm } from "@inquirer/prompts";
|
|
21
25
|
//#region src/commands/run.ts
|
|
@@ -42,9 +46,65 @@ function buildTargetSelector(targets, allowEmpty) {
|
|
|
42
46
|
allowEmpty
|
|
43
47
|
};
|
|
44
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
|
+
}
|
|
45
90
|
/** Interval between status/log polls while a run is active. */
|
|
46
91
|
const POLL_INTERVAL_MS = 750;
|
|
47
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
|
+
/**
|
|
48
108
|
* Run a workflow locally using the local executor.
|
|
49
109
|
* Thin wrapper that delegates to executeLocal from local-executor.
|
|
50
110
|
*
|
|
@@ -89,9 +149,20 @@ async function runRemoteCommand(fixture, options) {
|
|
|
89
149
|
return true;
|
|
90
150
|
}
|
|
91
151
|
const fixtures = await compileFixtures(path.join(kiciDir, "tests"));
|
|
92
|
-
if (!fixture && !options.all) return listFixtures(fixtures);
|
|
93
152
|
let selected;
|
|
94
|
-
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;
|
|
95
166
|
else selected = filterFixtures(fixtures, fixture);
|
|
96
167
|
if (selected.length === 0) {
|
|
97
168
|
logger.info(pc.yellow(`No fixtures matched: ${fixture ?? "(none)"}`));
|
|
@@ -132,17 +203,6 @@ function listFixtures(fixtures) {
|
|
|
132
203
|
return true;
|
|
133
204
|
}
|
|
134
205
|
/**
|
|
135
|
-
* Describe the event type from a fixture's trigger config.
|
|
136
|
-
*/
|
|
137
|
-
function describeEvent(event) {
|
|
138
|
-
if (!event || typeof event !== "object") return "unknown";
|
|
139
|
-
const e = event;
|
|
140
|
-
if (e._type === "push") return "push";
|
|
141
|
-
if (e._type === "pr") return `pr:${e.action ?? "open"}`;
|
|
142
|
-
if (typeof e._type === "string") return String(e._type);
|
|
143
|
-
return "custom";
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
206
|
* Resolve the authenticated Platform client and the run target (org + cluster).
|
|
147
207
|
*
|
|
148
208
|
* Org resolution: `--org` → `config.activeOrgId` → error.
|
|
@@ -182,6 +242,7 @@ function resolvePlatformContext(config, options) {
|
|
|
182
242
|
* Run fixtures remotely against the Platform.
|
|
183
243
|
*/
|
|
184
244
|
async function runFixturesRemotely(fixtures, options) {
|
|
245
|
+
if (!await compileBeforeRemoteRun(options)) return false;
|
|
185
246
|
const config = await loadGlobalConfig();
|
|
186
247
|
const ctx = resolvePlatformContext(config, options);
|
|
187
248
|
if (!ctx) return false;
|
|
@@ -320,6 +381,11 @@ async function runSingleFixture(fixture, ctx, options, config, history) {
|
|
|
320
381
|
...(() => {
|
|
321
382
|
const target = buildTargetSelector(options.targets, options.targetAllowEmpty ?? false);
|
|
322
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 } : {};
|
|
323
389
|
})()
|
|
324
390
|
});
|
|
325
391
|
if (triggerResult.status === "rejected") {
|
|
@@ -513,6 +579,7 @@ function buildEventFromFixture(opts) {
|
|
|
513
579
|
* Platform.
|
|
514
580
|
*/
|
|
515
581
|
async function runDirectWorkflow(workflowName, options) {
|
|
582
|
+
if (!await compileBeforeRemoteRun(options)) return false;
|
|
516
583
|
const ctx = resolvePlatformContext(await loadGlobalConfig(), options);
|
|
517
584
|
if (!ctx) return false;
|
|
518
585
|
if (options.json) options.quiet = true;
|
|
@@ -544,6 +611,11 @@ async function runDirectWorkflow(workflowName, options) {
|
|
|
544
611
|
...(() => {
|
|
545
612
|
const target = buildTargetSelector(options.targets, options.targetAllowEmpty ?? false);
|
|
546
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 } : {};
|
|
547
619
|
})()
|
|
548
620
|
});
|
|
549
621
|
if (!options.quiet) logger.info(pc.green(`Run started: ${triggerResult.runId}`));
|
|
@@ -572,6 +644,6 @@ function displayRemoteResults(results) {
|
|
|
572
644
|
logger.info("");
|
|
573
645
|
}
|
|
574
646
|
//#endregion
|
|
575
|
-
export { buildTargetSelector, runLocalCommand, runRemoteCommand };
|
|
647
|
+
export { buildDispatchInputs, buildTargetSelector, lookupDispatchInputsDescriptor, runLocalCommand, runRemoteCommand };
|
|
576
648
|
|
|
577
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) */
|
|
@@ -55,6 +57,8 @@ export interface RemoteRunOptions extends TestOptions {
|
|
|
55
57
|
* operator must still be clause-eligible per hold; an ineligible hold blocks.
|
|
56
58
|
*/
|
|
57
59
|
approveAll?: boolean;
|
|
60
|
+
/** `--input KEY=VALUE` values (repeatable): typed workflow-dispatch inputs. */
|
|
61
|
+
inputs?: string[];
|
|
58
62
|
}
|
|
59
63
|
/** Result of a single remote fixture run */
|
|
60
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
|