@kici-dev/compiler 0.5.0 → 0.6.1
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 +46 -8
- package/dist/commands/approve.d.ts +12 -0
- package/dist/commands/approve.js +5 -2
- package/dist/commands/compile.js +4 -2
- package/dist/commands/doctor.js +2 -2
- package/dist/commands/endpoints.js +4 -6
- package/dist/commands/feedback.d.ts +53 -0
- package/dist/commands/feedback.js +142 -0
- package/dist/commands/held-run-client.d.ts +21 -1
- package/dist/commands/held-run-client.js +34 -15
- package/dist/commands/hook.js +22 -20
- package/dist/commands/index.d.ts +4 -0
- package/dist/commands/index.js +3 -1
- package/dist/commands/init.d.ts +9 -2
- package/dist/commands/init.js +43 -16
- package/dist/commands/login.js +1 -1
- package/dist/commands/orchestrators.js +3 -2
- package/dist/commands/reject.d.ts +12 -0
- package/dist/commands/reject.js +5 -2
- package/dist/commands/report/collect.d.ts +82 -0
- package/dist/commands/report/collect.js +234 -0
- package/dist/commands/report/identity.d.ts +48 -0
- package/dist/commands/report/identity.js +49 -0
- package/dist/commands/report/index.d.ts +63 -0
- package/dist/commands/report/index.js +119 -0
- package/dist/commands/report/upload.d.ts +38 -0
- package/dist/commands/report/upload.js +64 -0
- package/dist/commands/run-hold-watch.js +2 -2
- package/dist/commands/run.js +6 -3
- package/dist/commands/runs/show.js +80 -1
- package/dist/commands/types.js +51 -8
- package/dist/execution/sdk-alias.js +4 -2
- package/dist/fixtures/compiler.js +2 -1
- package/dist/format.js +3 -3
- package/dist/generators/secrets-dts.d.ts +8 -2
- package/dist/generators/secrets-dts.js +3 -2
- package/dist/hooks/installer.js +2 -1
- package/dist/llm-context/llms-architecture.txt +35 -13
- package/dist/llm-context/llms-cli-remote.txt +2347 -0
- package/dist/llm-context/llms-cli.txt +284 -2470
- package/dist/llm-context/llms-features-execution.txt +2028 -0
- package/dist/llm-context/llms-features.txt +298 -1483
- package/dist/llm-context/llms-full.txt +6153 -4639
- package/dist/llm-context/llms-getting-started.txt +292 -12
- package/dist/llm-context/llms-patterns.txt +176 -1
- package/dist/llm-context/llms-providers.txt +11 -27
- package/dist/llm-context/llms-sdk-runtime.txt +25 -4
- package/dist/llm-context/llms-sdk.txt +31 -1
- package/dist/llm-context/llms.txt +33 -18
- package/dist/local-plane/paths.d.ts +15 -0
- package/dist/local-plane/paths.js +22 -1
- package/dist/local-plane/plane-manager.js +2 -2
- package/dist/local-plane/port-holder.js +1 -1
- package/dist/local-plane/postgres.d.ts +3 -16
- package/dist/local-plane/postgres.js +10 -15
- package/dist/lockfile/generator.d.ts +12 -0
- package/dist/lockfile/generator.js +47 -14
- package/dist/postinstall.js +2 -1
- package/dist/remote/config.d.ts +2 -15
- package/dist/remote/config.js +2 -16
- package/dist/remote/dashboard-client.d.ts +39 -0
- package/dist/remote/dashboard-client.js +41 -0
- package/dist/remote/oauth.js +7 -5
- package/dist/remote/uploader.js +2 -2
- package/dist/templates/package-json.js +1 -1
- package/dist/test-runner/dry-run.js +4 -2
- package/dist/test-runner/git-detector.js +2 -1
- package/dist/test-runner/job-executor.js +2 -1
- package/dist/test-runner/payload-builder.js +11 -17
- package/dist/types.d.ts +33 -3
- package/dist/validation/validator.js +23 -6
- package/package.json +16 -11
- package/sbom.spdx.json +953 -901
package/dist/commands/init.js
CHANGED
|
@@ -76,7 +76,8 @@ async function initCommand(options = {}) {
|
|
|
76
76
|
logger.info(pc.gray("Writing .kiciignore"));
|
|
77
77
|
await writeFile(kiciIgnorePath, kiciIgnoreTemplate, "utf-8");
|
|
78
78
|
}
|
|
79
|
-
|
|
79
|
+
await writeKiciGitignore(kiciDir);
|
|
80
|
+
const devMode = await detectDevelopmentMode();
|
|
80
81
|
const mode = await resolveScaffoldMode(options);
|
|
81
82
|
const useVerdaccio = devMode;
|
|
82
83
|
if (mode.kind === "integrate") {
|
|
@@ -204,7 +205,8 @@ async function detectDefaultBranch() {
|
|
|
204
205
|
async function detectDevelopmentMode() {
|
|
205
206
|
if (process.env.KICI_DEV === "true") return true;
|
|
206
207
|
try {
|
|
207
|
-
const
|
|
208
|
+
const rootPkgPath = path.resolve(process.cwd(), "package.json");
|
|
209
|
+
const content = await readFile(rootPkgPath, "utf-8");
|
|
208
210
|
return JSON.parse(content).kici?.development === true;
|
|
209
211
|
} catch {
|
|
210
212
|
return false;
|
|
@@ -234,7 +236,8 @@ const VERDACCIO_NPMRC = "@kici-dev:registry=http://verdaccio.local:4873\n";
|
|
|
234
236
|
async function writeTsConfigAndTypesDir(kiciDir) {
|
|
235
237
|
logger.info(pc.gray("Writing .kici/tsconfig.json"));
|
|
236
238
|
await writeFile(path.join(kiciDir, "tsconfig.json"), await generateTsConfig(), "utf-8");
|
|
237
|
-
|
|
239
|
+
const typesDir = path.join(kiciDir, "types");
|
|
240
|
+
await mkdir(typesDir, { recursive: true });
|
|
238
241
|
logger.info(pc.gray("Created .kici/types/ for generated type declarations"));
|
|
239
242
|
}
|
|
240
243
|
/** Run the given package manager's install in `dir`, with pnpm's build-gate flag. */
|
|
@@ -363,6 +366,28 @@ async function generateTsConfig() {
|
|
|
363
366
|
baseConfig.compilerOptions.paths = typeScriptPaths;
|
|
364
367
|
return JSON.stringify(baseConfig, null, 2) + "\n";
|
|
365
368
|
}
|
|
369
|
+
/** The single entry `kici init` scaffolds into `.kici/.gitignore`. */
|
|
370
|
+
const KICI_GITIGNORE_TEMPLATE = `# Generated type declarations (\`kici types\` / authenticated \`kici compile\`).
|
|
371
|
+
# A snapshot of one org's secret keys — a local development aid, not source.
|
|
372
|
+
types/
|
|
373
|
+
`;
|
|
374
|
+
/**
|
|
375
|
+
* Scaffold `.kici/.gitignore` so the generated `types/` declarations stay
|
|
376
|
+
* untracked. Never overwrites an existing file — the customer may have edited
|
|
377
|
+
* it. Deliberately ignores only `types/`, NOT `kici.lock.json` (the
|
|
378
|
+
* orchestrator fetches the lock from the repo, so it genuinely is source).
|
|
379
|
+
*
|
|
380
|
+
* @param kiciDir - The resolved `.kici` directory path.
|
|
381
|
+
*/
|
|
382
|
+
async function writeKiciGitignore(kiciDir) {
|
|
383
|
+
const gitignorePath = path.join(kiciDir, ".gitignore");
|
|
384
|
+
if (await checkExists(gitignorePath)) {
|
|
385
|
+
logger.info(pc.gray("Skipping .kici/.gitignore (already exists)"));
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
logger.info(pc.gray("Writing .kici/.gitignore"));
|
|
389
|
+
await writeFile(gitignorePath, KICI_GITIGNORE_TEMPLATE, "utf-8");
|
|
390
|
+
}
|
|
366
391
|
/**
|
|
367
392
|
* Update .gitignore with .kici/ entries
|
|
368
393
|
*
|
|
@@ -504,17 +529,18 @@ async function writeAgentsMd(kiciDir) {
|
|
|
504
529
|
async function offerHookInstallation(useVerdaccio) {
|
|
505
530
|
const tools = await detectHookTools();
|
|
506
531
|
let selectedTool;
|
|
507
|
-
if (tools.length === 0)
|
|
508
|
-
message: "No pre-commit tool found. Install husky for git hooks?",
|
|
509
|
-
default: false
|
|
510
|
-
})) {
|
|
532
|
+
if (tools.length === 0) {
|
|
511
533
|
if (!await confirm({
|
|
512
|
-
message: "
|
|
534
|
+
message: "No pre-commit tool found. Install husky for git hooks?",
|
|
513
535
|
default: false
|
|
514
|
-
}))
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
536
|
+
})) {
|
|
537
|
+
if (!await confirm({
|
|
538
|
+
message: "Add kici compile to .git/hooks/pre-commit instead?",
|
|
539
|
+
default: false
|
|
540
|
+
})) return;
|
|
541
|
+
selectedTool = "git";
|
|
542
|
+
} else selectedTool = "husky";
|
|
543
|
+
} else if (tools.length === 1) {
|
|
518
544
|
if (!await confirm({
|
|
519
545
|
message: `Found ${tools[0].name}. Add kici compile hook?`,
|
|
520
546
|
default: true
|
|
@@ -528,11 +554,12 @@ async function offerHookInstallation(useVerdaccio) {
|
|
|
528
554
|
}))
|
|
529
555
|
});
|
|
530
556
|
const result = await installHook(selectedTool, { useVerdaccio });
|
|
531
|
-
if (result.success)
|
|
532
|
-
|
|
533
|
-
|
|
557
|
+
if (result.success) {
|
|
558
|
+
if (result.action === "skipped") logger.info(pc.yellow(`Hook already installed: ${result.message}`));
|
|
559
|
+
else logger.info(pc.green(`Hook installed: ${result.message}`));
|
|
560
|
+
} else logger.warn(pc.yellow(`Warning: ${result.message}`));
|
|
534
561
|
}
|
|
535
562
|
//#endregion
|
|
536
|
-
export { initCommand };
|
|
563
|
+
export { initCommand, writeKiciGitignore };
|
|
537
564
|
|
|
538
565
|
//# sourceMappingURL=init.js.map
|
package/dist/commands/login.js
CHANGED
|
@@ -31,7 +31,7 @@ async function promptInput(prompt) {
|
|
|
31
31
|
function checkPatExpiry(expiresAt) {
|
|
32
32
|
const expiryDate = new Date(expiresAt);
|
|
33
33
|
const now = /* @__PURE__ */ new Date();
|
|
34
|
-
const daysUntilExpiry = (expiryDate.getTime() - now.getTime()) /
|
|
34
|
+
const daysUntilExpiry = (expiryDate.getTime() - now.getTime()) / 864e5;
|
|
35
35
|
if (daysUntilExpiry <= 7 && daysUntilExpiry > 0) {
|
|
36
36
|
console.log(pc.yellow(`\n Warning: your personal access token expires in ${Math.ceil(daysUntilExpiry)} day(s) (${expiryDate.toLocaleDateString()}).`));
|
|
37
37
|
console.log(pc.yellow(" Run `kici login` again to refresh it."));
|
|
@@ -102,10 +102,11 @@ async function orchestratorsUseCommand(clusterName, options) {
|
|
|
102
102
|
}
|
|
103
103
|
return false;
|
|
104
104
|
}
|
|
105
|
-
|
|
105
|
+
const defaultClusters = {
|
|
106
106
|
...ctx.config.defaultClusters ?? {},
|
|
107
107
|
[ctx.orgId]: clusterName
|
|
108
|
-
}
|
|
108
|
+
};
|
|
109
|
+
await mergeGlobalConfig({ defaultClusters });
|
|
109
110
|
console.log(pc.green(`Default orchestrator for ${ctx.orgId} set to: ${clusterName}`));
|
|
110
111
|
return true;
|
|
111
112
|
}
|
|
@@ -11,6 +11,18 @@ export interface RejectOptions {
|
|
|
11
11
|
job?: string;
|
|
12
12
|
/** Match a step-scoped hold by its step index. */
|
|
13
13
|
step?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Match one hold by its own id. The escape hatch for an ambiguity nothing
|
|
16
|
+
* else resolves; the error listing prints the ids when it needs to.
|
|
17
|
+
*/
|
|
18
|
+
hold?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Narrow to holds of one type (`reviewer` / `timer` / `concurrency` /
|
|
21
|
+
* `security`). A job carrying an SDK `requireApproval` AND a security-typed
|
|
22
|
+
* context gate has two pending holds under one job name, and this is what
|
|
23
|
+
* separates them.
|
|
24
|
+
*/
|
|
25
|
+
holdType?: string;
|
|
14
26
|
/** Required rejection reason. */
|
|
15
27
|
reason?: string;
|
|
16
28
|
}
|
package/dist/commands/reject.js
CHANGED
|
@@ -26,9 +26,12 @@ async function rejectCommand(runId, options = {}) {
|
|
|
26
26
|
}
|
|
27
27
|
const ctx = await resolveHeldRunContext();
|
|
28
28
|
if (!ctx) return false;
|
|
29
|
-
const
|
|
29
|
+
const holds = await listHeldRunsForRun(ctx, runId);
|
|
30
|
+
const resolution = resolveHeldRunId(holds, {
|
|
30
31
|
job: options.job,
|
|
31
|
-
step: options.step
|
|
32
|
+
step: options.step,
|
|
33
|
+
holdId: options.hold,
|
|
34
|
+
holdType: options.holdType
|
|
32
35
|
});
|
|
33
36
|
if (!resolution.ok) {
|
|
34
37
|
logger.error(pc.red(resolution.error));
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the `kici report` bundle.
|
|
3
|
+
*
|
|
4
|
+
* Writes the same archive layout the orchestrator's debug bundle uses, so
|
|
5
|
+
* `kici-admin inspect-bundle` reads either one, but collects from the workflow
|
|
6
|
+
* author's side: their config, their project, and the run they are reporting
|
|
7
|
+
* about — rather than an orchestrator's own logs and state.
|
|
8
|
+
*
|
|
9
|
+
* Every collector runs through `runCollector`, which turns a throw into a
|
|
10
|
+
* recorded `error` entry instead of losing the whole bundle. That matters twice
|
|
11
|
+
* over: a customer with one broken collector still gets a shareable report, and
|
|
12
|
+
* the per-collector outcome is itself a product feature — the manifest's
|
|
13
|
+
* `collectionReport` tells the reader which sections are trustworthy.
|
|
14
|
+
*/
|
|
15
|
+
import { z } from 'zod';
|
|
16
|
+
import type { ProbeOutcome } from '../doctor.js';
|
|
17
|
+
/** Outcome of one collector. */
|
|
18
|
+
export declare const CollectionStatus: z.ZodEnum<{
|
|
19
|
+
empty: "empty";
|
|
20
|
+
error: "error";
|
|
21
|
+
ok: "ok";
|
|
22
|
+
skipped: "skipped";
|
|
23
|
+
}>;
|
|
24
|
+
export type CollectionStatus = z.infer<typeof CollectionStatus>;
|
|
25
|
+
export interface CollectionEntry {
|
|
26
|
+
collector: string;
|
|
27
|
+
status: CollectionStatus;
|
|
28
|
+
note?: string;
|
|
29
|
+
}
|
|
30
|
+
/** Bundle-format version, bumped when the archive layout changes. */
|
|
31
|
+
export declare const REPORT_BUNDLE_VERSION = "1.0";
|
|
32
|
+
export interface ReportManifest {
|
|
33
|
+
version: string;
|
|
34
|
+
generated_at: string;
|
|
35
|
+
bundle_id: string;
|
|
36
|
+
node_version: string;
|
|
37
|
+
platform: string;
|
|
38
|
+
metadata: Record<string, string>;
|
|
39
|
+
redacted: boolean;
|
|
40
|
+
collectionReport: CollectionEntry[];
|
|
41
|
+
}
|
|
42
|
+
/** One run's diagnostic material, as the dashboard client returns it. */
|
|
43
|
+
export interface RunMaterial {
|
|
44
|
+
detail: unknown;
|
|
45
|
+
logs: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Injectable seams, mirroring `DoctorDeps`, so every path is unit-testable
|
|
49
|
+
* without real IO or network.
|
|
50
|
+
*/
|
|
51
|
+
export interface ReportBundleDeps {
|
|
52
|
+
loadConfig: () => Promise<Record<string, unknown>>;
|
|
53
|
+
probe: () => Promise<ProbeOutcome | null>;
|
|
54
|
+
readProject: (kiciDir: string) => Promise<Record<string, unknown>>;
|
|
55
|
+
fetchRun: (runId: string) => Promise<RunMaterial>;
|
|
56
|
+
now: () => Date;
|
|
57
|
+
}
|
|
58
|
+
export interface ReportBundleOptions {
|
|
59
|
+
outputPath: string;
|
|
60
|
+
kiciDir: string;
|
|
61
|
+
runId?: string;
|
|
62
|
+
metadata: Record<string, string>;
|
|
63
|
+
/** When false the bundle is assembled without scrubbing free text. */
|
|
64
|
+
redact: boolean;
|
|
65
|
+
deps?: Partial<ReportBundleDeps>;
|
|
66
|
+
}
|
|
67
|
+
export interface ReportBundleResult {
|
|
68
|
+
path: string;
|
|
69
|
+
sha256: string;
|
|
70
|
+
bundleId: string;
|
|
71
|
+
fileCount: number;
|
|
72
|
+
collectionReport: CollectionEntry[];
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Collect and write one report bundle.
|
|
76
|
+
*
|
|
77
|
+
* The body stays a narrative caller: each section is one `runCollector` call
|
|
78
|
+
* whose result is appended to the archive, so adding a collector never grows
|
|
79
|
+
* the control flow.
|
|
80
|
+
*/
|
|
81
|
+
export declare function createReportBundle(options: ReportBundleOptions): Promise<ReportBundleResult>;
|
|
82
|
+
//# sourceMappingURL=collect.d.ts.map
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import "../../rolldown-runtime-ClRpJifh.js";
|
|
2
|
+
import { collectIdentity } from "./identity.js";
|
|
3
|
+
import * as path$1 from "node:path";
|
|
4
|
+
import * as fs$1 from "node:fs";
|
|
5
|
+
import * as os$1 from "node:os";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
8
|
+
import { ZipArchive } from "archiver";
|
|
9
|
+
import { redactConfig, scrubText } from "@kici-dev/core/diagnostics-redaction";
|
|
10
|
+
//#region src/commands/report/collect.ts
|
|
11
|
+
/**
|
|
12
|
+
* Build the `kici report` bundle.
|
|
13
|
+
*
|
|
14
|
+
* Writes the same archive layout the orchestrator's debug bundle uses, so
|
|
15
|
+
* `kici-admin inspect-bundle` reads either one, but collects from the workflow
|
|
16
|
+
* author's side: their config, their project, and the run they are reporting
|
|
17
|
+
* about — rather than an orchestrator's own logs and state.
|
|
18
|
+
*
|
|
19
|
+
* Every collector runs through `runCollector`, which turns a throw into a
|
|
20
|
+
* recorded `error` entry instead of losing the whole bundle. That matters twice
|
|
21
|
+
* over: a customer with one broken collector still gets a shareable report, and
|
|
22
|
+
* the per-collector outcome is itself a product feature — the manifest's
|
|
23
|
+
* `collectionReport` tells the reader which sections are trustworthy.
|
|
24
|
+
*/
|
|
25
|
+
/** Outcome of one collector. */
|
|
26
|
+
const CollectionStatus = z.enum([
|
|
27
|
+
"ok",
|
|
28
|
+
"empty",
|
|
29
|
+
"error",
|
|
30
|
+
"skipped"
|
|
31
|
+
]);
|
|
32
|
+
/** Bundle-format version, bumped when the archive layout changes. */
|
|
33
|
+
const REPORT_BUNDLE_VERSION = "1.0";
|
|
34
|
+
/**
|
|
35
|
+
* Read the project's own KiCI state: which workflows exist and what the lock
|
|
36
|
+
* file says. Deliberately reads metadata rather than workflow source — the
|
|
37
|
+
* source is the customer's code, and a diagnostic bundle should not ship it.
|
|
38
|
+
*/
|
|
39
|
+
async function defaultReadProject(kiciDir) {
|
|
40
|
+
const workflowsDir = path$1.join(kiciDir, "workflows");
|
|
41
|
+
const workflows = fs$1.existsSync(workflowsDir) ? fs$1.readdirSync(workflowsDir).sort() : [];
|
|
42
|
+
const lockPath = path$1.join(kiciDir, "kici.lock.json");
|
|
43
|
+
let lock = null;
|
|
44
|
+
if (fs$1.existsSync(lockPath)) {
|
|
45
|
+
const parsed = JSON.parse(fs$1.readFileSync(lockPath, "utf-8"));
|
|
46
|
+
lock = {
|
|
47
|
+
schemaVersion: parsed.schemaVersion ?? null,
|
|
48
|
+
generatedAt: parsed.generatedAt ?? null,
|
|
49
|
+
workflowCount: Array.isArray(parsed.workflows) ? parsed.workflows.length : null
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
kiciDir,
|
|
54
|
+
exists: fs$1.existsSync(kiciDir),
|
|
55
|
+
workflows,
|
|
56
|
+
lock
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const DEFAULT_DEPS = {
|
|
60
|
+
loadConfig: async () => {
|
|
61
|
+
const { loadGlobalConfig } = await import("../../remote/config.js");
|
|
62
|
+
return await loadGlobalConfig();
|
|
63
|
+
},
|
|
64
|
+
probe: async () => {
|
|
65
|
+
const { loadGlobalConfig } = await import("../../remote/config.js");
|
|
66
|
+
const { DashboardClient, DashboardClientError } = await import("../../remote/dashboard-client.js");
|
|
67
|
+
const config = await loadGlobalConfig();
|
|
68
|
+
if (!config.activeOrgId || !(config.pat ?? config.token)) return null;
|
|
69
|
+
try {
|
|
70
|
+
return {
|
|
71
|
+
ok: true,
|
|
72
|
+
infra: await DashboardClient.fromConfig(config).getInfrastructure()
|
|
73
|
+
};
|
|
74
|
+
} catch (err) {
|
|
75
|
+
if (err instanceof DashboardClientError) return {
|
|
76
|
+
ok: false,
|
|
77
|
+
kind: err.kind,
|
|
78
|
+
message: err.message
|
|
79
|
+
};
|
|
80
|
+
throw err;
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
readProject: defaultReadProject,
|
|
84
|
+
fetchRun: async (runId) => {
|
|
85
|
+
const { loadGlobalConfig } = await import("../../remote/config.js");
|
|
86
|
+
const { DashboardClient } = await import("../../remote/dashboard-client.js");
|
|
87
|
+
const detail = await DashboardClient.fromConfig(await loadGlobalConfig()).getRunDetail(runId);
|
|
88
|
+
return {
|
|
89
|
+
detail,
|
|
90
|
+
logs: JSON.stringify(detail, null, 2)
|
|
91
|
+
};
|
|
92
|
+
},
|
|
93
|
+
now: () => /* @__PURE__ */ new Date()
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Run one collector, recording its outcome instead of letting it abort the
|
|
97
|
+
* bundle. `empty` and `error` are the two signals the dev-side gap filer reads.
|
|
98
|
+
*/
|
|
99
|
+
async function runCollector(report, collector, fn, scrub) {
|
|
100
|
+
try {
|
|
101
|
+
const value = await fn();
|
|
102
|
+
const isEmpty = value === null || value === void 0 || Array.isArray(value) && value.length === 0 || typeof value === "string" && value.length === 0;
|
|
103
|
+
report.push({
|
|
104
|
+
collector,
|
|
105
|
+
status: isEmpty ? "empty" : "ok"
|
|
106
|
+
});
|
|
107
|
+
return value;
|
|
108
|
+
} catch (err) {
|
|
109
|
+
report.push({
|
|
110
|
+
collector,
|
|
111
|
+
status: "error",
|
|
112
|
+
note: scrub(err instanceof Error ? err.message : String(err))
|
|
113
|
+
});
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/** Machine facts about the host the report was produced on. */
|
|
118
|
+
function systemInfo() {
|
|
119
|
+
return {
|
|
120
|
+
platform: os$1.platform(),
|
|
121
|
+
release: os$1.release(),
|
|
122
|
+
arch: os$1.arch(),
|
|
123
|
+
nodeVersion: process.version,
|
|
124
|
+
cpuCount: os$1.cpus().length,
|
|
125
|
+
totalMemory: os$1.totalmem(),
|
|
126
|
+
freeMemory: os$1.freemem(),
|
|
127
|
+
hostname: os$1.hostname()
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/** sha256 over the finished file, so the customer can quote a digest. */
|
|
131
|
+
async function digestOf(file) {
|
|
132
|
+
const hash = createHash("sha256");
|
|
133
|
+
await new Promise((resolve, reject) => {
|
|
134
|
+
const stream = fs$1.createReadStream(file);
|
|
135
|
+
stream.on("data", (chunk) => hash.update(chunk));
|
|
136
|
+
stream.on("end", resolve);
|
|
137
|
+
stream.on("error", reject);
|
|
138
|
+
});
|
|
139
|
+
return hash.digest("hex");
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Collect and write one report bundle.
|
|
143
|
+
*
|
|
144
|
+
* The body stays a narrative caller: each section is one `runCollector` call
|
|
145
|
+
* whose result is appended to the archive, so adding a collector never grows
|
|
146
|
+
* the control flow.
|
|
147
|
+
*/
|
|
148
|
+
async function createReportBundle(options) {
|
|
149
|
+
const deps = {
|
|
150
|
+
...DEFAULT_DEPS,
|
|
151
|
+
...options.deps
|
|
152
|
+
};
|
|
153
|
+
const scrub = options.redact ? scrubText : (s) => s;
|
|
154
|
+
const report = [];
|
|
155
|
+
const bundleId = randomUUID().replace(/-/g, "").slice(0, 24);
|
|
156
|
+
const dir = path$1.dirname(options.outputPath);
|
|
157
|
+
if (!fs$1.existsSync(dir)) fs$1.mkdirSync(dir, { recursive: true });
|
|
158
|
+
const output = fs$1.createWriteStream(options.outputPath);
|
|
159
|
+
const archive = new ZipArchive({ zlib: { level: 6 } });
|
|
160
|
+
const finalized = new Promise((resolve, reject) => {
|
|
161
|
+
output.on("close", resolve);
|
|
162
|
+
archive.on("error", reject);
|
|
163
|
+
output.on("error", reject);
|
|
164
|
+
});
|
|
165
|
+
archive.pipe(output);
|
|
166
|
+
let fileCount = 0;
|
|
167
|
+
const append = (body, name) => {
|
|
168
|
+
archive.append(scrub(JSON.stringify(body, null, 2)), { name });
|
|
169
|
+
fileCount += 1;
|
|
170
|
+
};
|
|
171
|
+
let probe;
|
|
172
|
+
try {
|
|
173
|
+
probe = await deps.probe();
|
|
174
|
+
report.push(probe === null ? {
|
|
175
|
+
collector: "probe",
|
|
176
|
+
status: "skipped",
|
|
177
|
+
note: "not authenticated"
|
|
178
|
+
} : {
|
|
179
|
+
collector: "probe",
|
|
180
|
+
status: "ok"
|
|
181
|
+
});
|
|
182
|
+
} catch (err) {
|
|
183
|
+
report.push({
|
|
184
|
+
collector: "probe",
|
|
185
|
+
status: "error",
|
|
186
|
+
note: scrub(err instanceof Error ? err.message : String(err))
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
const identity = await runCollector(report, "identity", () => Promise.resolve(collectIdentity(probe ?? null)), scrub);
|
|
190
|
+
if (identity) append(identity, "identity.json");
|
|
191
|
+
const config = await runCollector(report, "config", () => deps.loadConfig(), scrub);
|
|
192
|
+
if (config !== void 0) append(options.redact ? redactConfig(config) : config, "config/config.json");
|
|
193
|
+
const project = await runCollector(report, "project", () => deps.readProject(options.kiciDir), scrub);
|
|
194
|
+
if (project !== void 0) append(project, "project/project.json");
|
|
195
|
+
const system = await runCollector(report, "system", () => Promise.resolve(systemInfo()), scrub);
|
|
196
|
+
if (system !== void 0) append(system, "system/info.json");
|
|
197
|
+
if (options.runId) {
|
|
198
|
+
const run = await runCollector(report, "run", () => deps.fetchRun(options.runId), scrub);
|
|
199
|
+
if (run !== void 0) {
|
|
200
|
+
append(run.detail, `runs/${options.runId}/detail.json`);
|
|
201
|
+
archive.append(scrub(run.logs), { name: `runs/${options.runId}/logs.txt` });
|
|
202
|
+
fileCount += 1;
|
|
203
|
+
}
|
|
204
|
+
} else report.push({
|
|
205
|
+
collector: "run",
|
|
206
|
+
status: "skipped",
|
|
207
|
+
note: "no --run given"
|
|
208
|
+
});
|
|
209
|
+
const manifest = {
|
|
210
|
+
version: "1.0",
|
|
211
|
+
generated_at: deps.now().toISOString(),
|
|
212
|
+
bundle_id: bundleId,
|
|
213
|
+
node_version: process.version,
|
|
214
|
+
platform: process.platform,
|
|
215
|
+
metadata: options.metadata,
|
|
216
|
+
redacted: options.redact,
|
|
217
|
+
collectionReport: report
|
|
218
|
+
};
|
|
219
|
+
archive.append(scrub(JSON.stringify(manifest, null, 2)), { name: "manifest.json" });
|
|
220
|
+
fileCount += 1;
|
|
221
|
+
await archive.finalize();
|
|
222
|
+
await finalized;
|
|
223
|
+
return {
|
|
224
|
+
path: options.outputPath,
|
|
225
|
+
sha256: await digestOf(options.outputPath),
|
|
226
|
+
bundleId,
|
|
227
|
+
fileCount,
|
|
228
|
+
collectionReport: report
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
//#endregion
|
|
232
|
+
export { CollectionStatus, REPORT_BUNDLE_VERSION, createReportBundle };
|
|
233
|
+
|
|
234
|
+
//# sourceMappingURL=collect.js.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version and environment context for an issue report.
|
|
3
|
+
*
|
|
4
|
+
* A report without version context costs a support round-trip before anyone can
|
|
5
|
+
* even reproduce the problem, so this is collected first and never fails: the
|
|
6
|
+
* client-side half always resolves, and the component versions are folded in
|
|
7
|
+
* only when the authenticated probe actually reached the Platform.
|
|
8
|
+
*/
|
|
9
|
+
import type { ProbeOutcome } from '../doctor.js';
|
|
10
|
+
/** One orchestrator the caller's org has connected, and what it is running. */
|
|
11
|
+
export interface OrchestratorIdentity {
|
|
12
|
+
clusterName: string;
|
|
13
|
+
version: string | null;
|
|
14
|
+
mode: string | null;
|
|
15
|
+
connected: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface ReportIdentity {
|
|
18
|
+
kiciCliVersion: string;
|
|
19
|
+
nodeVersion: string;
|
|
20
|
+
platform: string;
|
|
21
|
+
arch: string;
|
|
22
|
+
/** The wire protocol this CLI speaks. */
|
|
23
|
+
protocolVersion: number;
|
|
24
|
+
/**
|
|
25
|
+
* Every orchestrator the probe saw, not just the first: a report about a
|
|
26
|
+
* routing problem is usually about which of several answered.
|
|
27
|
+
*/
|
|
28
|
+
orchestrators: OrchestratorIdentity[];
|
|
29
|
+
/**
|
|
30
|
+
* The newest KiCI version the Platform knows about, when it told us. This is
|
|
31
|
+
* an upgrade hint, NOT the Platform's own version — the diagnostics response
|
|
32
|
+
* carries no such field, and inventing one would put a fabricated value in a
|
|
33
|
+
* bundle a human will read as fact.
|
|
34
|
+
*/
|
|
35
|
+
latestKnownVersion?: string;
|
|
36
|
+
/** Why component versions are missing, when the probe could not run. */
|
|
37
|
+
probeError?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Build the identity block.
|
|
41
|
+
*
|
|
42
|
+
* `probe` is the same `ProbeOutcome` `kici doctor` already performs — one
|
|
43
|
+
* authenticated infrastructure read — passed in rather than re-fetched so a
|
|
44
|
+
* report never issues a second round trip for data the caller may already
|
|
45
|
+
* hold, and so every path is testable without network.
|
|
46
|
+
*/
|
|
47
|
+
export declare function collectIdentity(probe: ProbeOutcome | null): ReportIdentity;
|
|
48
|
+
//# sourceMappingURL=identity.d.ts.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import "../../rolldown-runtime-ClRpJifh.js";
|
|
2
|
+
import { PROTOCOL_VERSION } from "@kici-dev/engine";
|
|
3
|
+
//#region src/commands/report/identity.ts
|
|
4
|
+
/**
|
|
5
|
+
* Version and environment context for an issue report.
|
|
6
|
+
*
|
|
7
|
+
* A report without version context costs a support round-trip before anyone can
|
|
8
|
+
* even reproduce the problem, so this is collected first and never fails: the
|
|
9
|
+
* client-side half always resolves, and the component versions are folded in
|
|
10
|
+
* only when the authenticated probe actually reached the Platform.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Build the identity block.
|
|
14
|
+
*
|
|
15
|
+
* `probe` is the same `ProbeOutcome` `kici doctor` already performs — one
|
|
16
|
+
* authenticated infrastructure read — passed in rather than re-fetched so a
|
|
17
|
+
* report never issues a second round trip for data the caller may already
|
|
18
|
+
* hold, and so every path is testable without network.
|
|
19
|
+
*/
|
|
20
|
+
function collectIdentity(probe) {
|
|
21
|
+
const identity = {
|
|
22
|
+
kiciCliVersion: "0.6.1",
|
|
23
|
+
nodeVersion: process.version,
|
|
24
|
+
platform: process.platform,
|
|
25
|
+
arch: process.arch,
|
|
26
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
27
|
+
orchestrators: []
|
|
28
|
+
};
|
|
29
|
+
if (probe === null) {
|
|
30
|
+
identity.probeError = "not authenticated; component versions unavailable";
|
|
31
|
+
return identity;
|
|
32
|
+
}
|
|
33
|
+
if (!probe.ok) {
|
|
34
|
+
identity.probeError = `${probe.kind}: ${probe.message}`;
|
|
35
|
+
return identity;
|
|
36
|
+
}
|
|
37
|
+
identity.orchestrators = probe.infra.orchestrators.map((o) => ({
|
|
38
|
+
clusterName: o.clusterName ?? "(unnamed)",
|
|
39
|
+
version: o.version ?? null,
|
|
40
|
+
mode: o.mode ?? null,
|
|
41
|
+
connected: o.connected
|
|
42
|
+
}));
|
|
43
|
+
if (probe.infra.latestVersion) identity.latestKnownVersion = probe.infra.latestVersion;
|
|
44
|
+
return identity;
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
export { collectIdentity };
|
|
48
|
+
|
|
49
|
+
//# sourceMappingURL=identity.js.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `kici report` — produce a redacted diagnostic bundle to share when reporting
|
|
3
|
+
* an issue, and optionally upload it privately to KiCI.
|
|
4
|
+
*
|
|
5
|
+
* Writing and sending are separate acts. The default writes a ZIP next to you
|
|
6
|
+
* and tells you where it is, so you can open it and see exactly what you would
|
|
7
|
+
* be sharing; `--upload` is the explicit second step.
|
|
8
|
+
*/
|
|
9
|
+
import { type ReportBundleDeps } from './collect.js';
|
|
10
|
+
import { type UploadDeps } from './upload.js';
|
|
11
|
+
export interface ReportOptions {
|
|
12
|
+
output?: string;
|
|
13
|
+
run?: string;
|
|
14
|
+
/** Repeatable `key=value` pairs. */
|
|
15
|
+
metadata: string[];
|
|
16
|
+
redact: boolean;
|
|
17
|
+
upload?: boolean;
|
|
18
|
+
message?: string;
|
|
19
|
+
email?: string;
|
|
20
|
+
kiciDir: string;
|
|
21
|
+
deps?: Partial<ReportBundleDeps>;
|
|
22
|
+
uploadDeps?: UploadDeps;
|
|
23
|
+
}
|
|
24
|
+
export interface ReportListOptions {
|
|
25
|
+
json?: boolean;
|
|
26
|
+
deps?: {
|
|
27
|
+
listIssueReports: () => Promise<{
|
|
28
|
+
reports: ReportRow[];
|
|
29
|
+
}>;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export interface ReportRow {
|
|
33
|
+
ref: string;
|
|
34
|
+
bundleId: string;
|
|
35
|
+
byteSize: number;
|
|
36
|
+
status: string;
|
|
37
|
+
createdAt: string;
|
|
38
|
+
userId: string;
|
|
39
|
+
message: string | null;
|
|
40
|
+
}
|
|
41
|
+
export interface ReportWithdrawOptions {
|
|
42
|
+
ref: string;
|
|
43
|
+
deps?: {
|
|
44
|
+
withdrawIssueReport: (ref: string) => Promise<{
|
|
45
|
+
ref: string;
|
|
46
|
+
deleted: boolean;
|
|
47
|
+
}>;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Parse repeatable `--metadata key=value` into a record.
|
|
52
|
+
*
|
|
53
|
+
* The accumulator is null-prototyped so a `__proto__=x` pair is stored as an
|
|
54
|
+
* ordinary key rather than silently vanishing into the prototype — a dropped
|
|
55
|
+
* pair in a diagnostic bundle is a lie about what the reporter attached.
|
|
56
|
+
*/
|
|
57
|
+
export declare function parseMetadata(pairs: string[]): Record<string, string>;
|
|
58
|
+
/** Default bundle path: timestamped, in the working directory. */
|
|
59
|
+
export declare function defaultOutputPath(now: Date): string;
|
|
60
|
+
export declare function reportCommand(options: ReportOptions): Promise<boolean>;
|
|
61
|
+
export declare function reportListCommand(options: ReportListOptions): Promise<boolean>;
|
|
62
|
+
export declare function reportWithdrawCommand(options: ReportWithdrawOptions): Promise<boolean>;
|
|
63
|
+
//# sourceMappingURL=index.d.ts.map
|