@kb-labs/quality-entry 2.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +120 -0
- package/dist/cli/commands/build-order.d.ts +16 -0
- package/dist/cli/commands/build-order.js +113 -0
- package/dist/cli/commands/build-order.js.map +1 -0
- package/dist/cli/commands/check-builds.d.ts +10 -0
- package/dist/cli/commands/check-builds.js +93 -0
- package/dist/cli/commands/check-builds.js.map +1 -0
- package/dist/cli/commands/check-tests.d.ts +10 -0
- package/dist/cli/commands/check-tests.js +114 -0
- package/dist/cli/commands/check-tests.js.map +1 -0
- package/dist/cli/commands/check-types.d.ts +10 -0
- package/dist/cli/commands/check-types.js +108 -0
- package/dist/cli/commands/check-types.js.map +1 -0
- package/dist/cli/commands/cycles.d.ts +17 -0
- package/dist/cli/commands/cycles.js +85 -0
- package/dist/cli/commands/cycles.js.map +1 -0
- package/dist/cli/commands/dead-code.d.ts +10 -0
- package/dist/cli/commands/dead-code.js +217 -0
- package/dist/cli/commands/dead-code.js.map +1 -0
- package/dist/cli/commands/fix-deps.d.ts +28 -0
- package/dist/cli/commands/fix-deps.js +389 -0
- package/dist/cli/commands/fix-deps.js.map +1 -0
- package/dist/cli/commands/flags.d.ts +344 -0
- package/dist/cli/commands/flags.js +298 -0
- package/dist/cli/commands/flags.js.map +1 -0
- package/dist/cli/commands/health.d.ts +10 -0
- package/dist/cli/commands/health.js +210 -0
- package/dist/cli/commands/health.js.map +1 -0
- package/dist/cli/commands/index.d.ts +14 -0
- package/dist/cli/commands/index.js +1747 -0
- package/dist/cli/commands/index.js.map +1 -0
- package/dist/cli/commands/stats.d.ts +10 -0
- package/dist/cli/commands/stats.js +282 -0
- package/dist/cli/commands/stats.js.map +1 -0
- package/dist/cli/commands/visualize.d.ts +26 -0
- package/dist/cli/commands/visualize.js +210 -0
- package/dist/cli/commands/visualize.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +666 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.d.ts +168 -0
- package/dist/manifest.js +666 -0
- package/dist/manifest.js.map +1 -0
- package/dist/rest/handlers/build-order-handler.d.ts +17 -0
- package/dist/rest/handlers/build-order-handler.js +28 -0
- package/dist/rest/handlers/build-order-handler.js.map +1 -0
- package/dist/rest/handlers/builds-handler.d.ts +12 -0
- package/dist/rest/handlers/builds-handler.js +17 -0
- package/dist/rest/handlers/builds-handler.js.map +1 -0
- package/dist/rest/handlers/cycles-handler.d.ts +13 -0
- package/dist/rest/handlers/cycles-handler.js +23 -0
- package/dist/rest/handlers/cycles-handler.js.map +1 -0
- package/dist/rest/handlers/dependencies-handler.d.ts +14 -0
- package/dist/rest/handlers/dependencies-handler.js +19 -0
- package/dist/rest/handlers/dependencies-handler.js.map +1 -0
- package/dist/rest/handlers/graph-handler.d.ts +30 -0
- package/dist/rest/handlers/graph-handler.js +155 -0
- package/dist/rest/handlers/graph-handler.js.map +1 -0
- package/dist/rest/handlers/health-handler.d.ts +15 -0
- package/dist/rest/handlers/health-handler.js +19 -0
- package/dist/rest/handlers/health-handler.js.map +1 -0
- package/dist/rest/handlers/stale-handler.d.ts +30 -0
- package/dist/rest/handlers/stale-handler.js +20 -0
- package/dist/rest/handlers/stale-handler.js.map +1 -0
- package/dist/rest/handlers/stats-handler.d.ts +16 -0
- package/dist/rest/handlers/stats-handler.js +29 -0
- package/dist/rest/handlers/stats-handler.js.map +1 -0
- package/dist/rest/handlers/tests-handler.d.ts +14 -0
- package/dist/rest/handlers/tests-handler.js +19 -0
- package/dist/rest/handlers/tests-handler.js.map +1 -0
- package/dist/rest/handlers/types-handler.d.ts +12 -0
- package/dist/rest/handlers/types-handler.js +17 -0
- package/dist/rest/handlers/types-handler.js.map +1 -0
- package/package.json +98 -0
package/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# @kb-labs/plugin-template-cli
|
|
2
|
+
|
|
3
|
+
Reference CLI/REST/Studio plugin package for KB Labs Plugin Template.
|
|
4
|
+
|
|
5
|
+
## Vision & Purpose
|
|
6
|
+
|
|
7
|
+
**@kb-labs/plugin-template-cli** is the canonical example plugin package used by `@kb-labs/plugin-template`.
|
|
8
|
+
It shows how to implement a plugin that exposes:
|
|
9
|
+
|
|
10
|
+
- a **CLI command** (Hello),
|
|
11
|
+
- a **REST handler**, and
|
|
12
|
+
- a **Studio widget**,
|
|
13
|
+
|
|
14
|
+
all driven by a single manifest and contracts package.
|
|
15
|
+
|
|
16
|
+
## Package Status
|
|
17
|
+
|
|
18
|
+
- **Version**: 0.1.0
|
|
19
|
+
- **Stage**: Stable (template)
|
|
20
|
+
- **Status**: Reference Implementation ✅
|
|
21
|
+
|
|
22
|
+
## Architecture
|
|
23
|
+
|
|
24
|
+
### High-Level Overview
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
plugin-cli
|
|
28
|
+
│
|
|
29
|
+
├──► contracts (from @kb-labs/plugin-template-contracts)
|
|
30
|
+
├──► shared (constants/helpers)
|
|
31
|
+
├──► domain (Greeting entity and invariants)
|
|
32
|
+
├──► application (use-cases: create greeting, etc.)
|
|
33
|
+
├──► cli (Hello command wiring)
|
|
34
|
+
├──► rest (Hello REST handler + schema)
|
|
35
|
+
└──► studio (Hello Studio widget)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Key Components
|
|
39
|
+
|
|
40
|
+
- `src/domain/`: `Greeting` entity and domain rules
|
|
41
|
+
- `src/application/`: use-cases that orchestrate domain logic
|
|
42
|
+
- `src/cli/commands/hello/*`: CLI command implementation
|
|
43
|
+
- `src/rest/handlers/hello-handler.ts`: REST handler bound to manifest
|
|
44
|
+
- `src/studio/widgets/hello-widget.tsx`: Studio widget implementation
|
|
45
|
+
- `src/manifest.v2.ts`: Plugin manifest v2 (CLI/REST/Studio wiring)
|
|
46
|
+
|
|
47
|
+
## Features
|
|
48
|
+
|
|
49
|
+
- **Single-source manifest** for CLI/REST/Studio surfaces
|
|
50
|
+
- **Layered architecture** (shared → domain → application → interface)
|
|
51
|
+
- **Type-safe contracts** via `@kb-labs/plugin-template-contracts`
|
|
52
|
+
- **Hello-world flow** demonstrating end-to-end plugin wiring
|
|
53
|
+
|
|
54
|
+
## Exports
|
|
55
|
+
|
|
56
|
+
From `src/index.ts`:
|
|
57
|
+
|
|
58
|
+
- `manifest`: Plugin Manifest V2
|
|
59
|
+
- All public surfaces:
|
|
60
|
+
- CLI command exports
|
|
61
|
+
- domain/application/shared re-exports
|
|
62
|
+
|
|
63
|
+
## Dependencies
|
|
64
|
+
|
|
65
|
+
### Runtime
|
|
66
|
+
|
|
67
|
+
- `@kb-labs/setup-operations`: reusable setup operations
|
|
68
|
+
- `@kb-labs/plugin-manifest`: manifest types and helpers
|
|
69
|
+
- `@kb-labs/plugin-template-contracts`: public contracts for this template plugin
|
|
70
|
+
- `@kb-labs/shared-cli-ui`: shared CLI UI helpers
|
|
71
|
+
- `react`, `react-dom`, `zod`
|
|
72
|
+
|
|
73
|
+
### Development
|
|
74
|
+
|
|
75
|
+
- `@kb-labs/devkit`: shared TS/ESLint/Vitest/TSUP presets
|
|
76
|
+
- `typescript`, `tsup`, `vitest`, `rimraf`
|
|
77
|
+
|
|
78
|
+
## Scripts
|
|
79
|
+
|
|
80
|
+
From `kb-labs-plugin-template` repo root:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
pnpm install
|
|
84
|
+
pnpm --filter @kb-labs/plugin-template-cli build
|
|
85
|
+
pnpm --filter @kb-labs/plugin-template-cli test
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
To run sandboxes, see the root `README.md` (`pnpm sandbox:cli`, `sandbox:rest`, `sandbox:studio`).
|
|
89
|
+
|
|
90
|
+
## Command Implementation
|
|
91
|
+
|
|
92
|
+
This template demonstrates **three different approaches** to implementing CLI commands:
|
|
93
|
+
|
|
94
|
+
1. **High-level wrapper (`defineCommand`)** - Recommended for most cases
|
|
95
|
+
2. **Low-level atomic tools** - For maximum control
|
|
96
|
+
3. **Hybrid approach** - Combining both
|
|
97
|
+
|
|
98
|
+
See [`COMMAND_IMPLEMENTATION_GUIDE.md`](./COMMAND_IMPLEMENTATION_GUIDE.md) for detailed explanations and examples.
|
|
99
|
+
|
|
100
|
+
### Quick Start
|
|
101
|
+
|
|
102
|
+
The `template:hello` command in `src/cli/commands/hello/run.ts` shows all three approaches with working code examples. The default implementation uses Approach 1 (`defineCommand`), which provides:
|
|
103
|
+
|
|
104
|
+
- ✅ Zero-boilerplate flag validation
|
|
105
|
+
- ✅ Automatic analytics integration
|
|
106
|
+
- ✅ Structured logging
|
|
107
|
+
- ✅ Error handling
|
|
108
|
+
- ✅ Timing tracking
|
|
109
|
+
- ✅ JSON output mode
|
|
110
|
+
|
|
111
|
+
## Customising for Your Plugin
|
|
112
|
+
|
|
113
|
+
When using this as a starting point:
|
|
114
|
+
|
|
115
|
+
- Rename the package in `package.json` (e.g. `@kb-labs/my-plugin-cli`)
|
|
116
|
+
- Update manifest IDs and the contracts package
|
|
117
|
+
- Replace the Hello flow with your own domain, use-cases, and surfaces
|
|
118
|
+
- Choose the command implementation approach that fits your needs (see `COMMAND_IMPLEMENTATION_GUIDE.md`)
|
|
119
|
+
|
|
120
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as _kb_labs_shared_command_kit from '@kb-labs/shared-command-kit';
|
|
2
|
+
import { TopologicalSortResult } from '@kb-labs/quality-core/graph';
|
|
3
|
+
|
|
4
|
+
type BuildOrderFlags = {
|
|
5
|
+
package?: string;
|
|
6
|
+
layers?: boolean;
|
|
7
|
+
script?: boolean;
|
|
8
|
+
json?: boolean;
|
|
9
|
+
argv?: string[];
|
|
10
|
+
};
|
|
11
|
+
type BuildOrderInput = BuildOrderFlags & {
|
|
12
|
+
argv?: string[];
|
|
13
|
+
};
|
|
14
|
+
declare const _default: _kb_labs_shared_command_kit.CommandHandlerV3<unknown, BuildOrderInput, TopologicalSortResult>;
|
|
15
|
+
|
|
16
|
+
export { _default as default };
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { defineCommand } from '@kb-labs/sdk';
|
|
2
|
+
import { buildDependencyGraph, getBuildOrderForPackage, topologicalSort } from '@kb-labs/quality-core/graph';
|
|
3
|
+
|
|
4
|
+
// src/cli/commands/build-order.ts
|
|
5
|
+
var build_order_default = defineCommand({
|
|
6
|
+
id: "quality:build-order",
|
|
7
|
+
description: "Calculate build order using topological sort",
|
|
8
|
+
handler: {
|
|
9
|
+
async execute(ctx, input) {
|
|
10
|
+
const { ui } = ctx;
|
|
11
|
+
const flags = input.flags ?? input;
|
|
12
|
+
const graph = buildDependencyGraph(ctx.cwd);
|
|
13
|
+
let result;
|
|
14
|
+
if (flags.package) {
|
|
15
|
+
result = getBuildOrderForPackage(graph, flags.package);
|
|
16
|
+
} else {
|
|
17
|
+
result = topologicalSort(graph);
|
|
18
|
+
}
|
|
19
|
+
if (result.circular.length > 0) {
|
|
20
|
+
ui?.error?.(
|
|
21
|
+
`Found ${result.circular.length} circular dependencies. Build order cannot be determined.`
|
|
22
|
+
);
|
|
23
|
+
outputCircularDependencies(result.circular, ui);
|
|
24
|
+
return { exitCode: 1, result };
|
|
25
|
+
}
|
|
26
|
+
outputBuildOrder(result, flags, ui);
|
|
27
|
+
return { exitCode: 0, result };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
function outputBuildOrder(result, flags, ui) {
|
|
32
|
+
if (flags.json) {
|
|
33
|
+
ui?.json?.(result);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (flags.script) {
|
|
37
|
+
ui?.write?.("#!/bin/bash");
|
|
38
|
+
ui?.write?.("# Generated build script");
|
|
39
|
+
ui?.write?.("set -e");
|
|
40
|
+
ui?.write?.("");
|
|
41
|
+
for (let i = 0; i < result.layers.length; i++) {
|
|
42
|
+
const layer = result.layers[i];
|
|
43
|
+
if (!layer) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
ui?.write?.(`# Layer ${i + 1} (${layer.length} packages)`);
|
|
47
|
+
for (const pkg of layer) {
|
|
48
|
+
ui?.write?.(`pnpm --filter "${pkg}" run build`);
|
|
49
|
+
}
|
|
50
|
+
ui?.write?.("");
|
|
51
|
+
}
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const sections = [];
|
|
55
|
+
if (flags.layers) {
|
|
56
|
+
const layerItems = [];
|
|
57
|
+
for (let i = 0; i < result.layers.length; i++) {
|
|
58
|
+
const layer = result.layers[i];
|
|
59
|
+
if (!layer) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
layerItems.push(`Layer ${i + 1}: ${layer.length} packages (can build in parallel)`);
|
|
63
|
+
for (const pkg of layer) {
|
|
64
|
+
layerItems.push(` \u2022 ${pkg}`);
|
|
65
|
+
}
|
|
66
|
+
layerItems.push("");
|
|
67
|
+
}
|
|
68
|
+
sections.push({ header: "Build Layers", items: layerItems });
|
|
69
|
+
} else {
|
|
70
|
+
const orderItems = result.sorted.map((pkg, idx) => `${idx + 1}. ${pkg}`);
|
|
71
|
+
sections.push({ header: "Build Order", items: orderItems });
|
|
72
|
+
}
|
|
73
|
+
sections.push({
|
|
74
|
+
header: "Summary",
|
|
75
|
+
items: [
|
|
76
|
+
`Total packages: ${result.sorted.length}`,
|
|
77
|
+
`Build layers: ${result.layers.length}`,
|
|
78
|
+
`Circular dependencies: ${result.circular.length}`
|
|
79
|
+
]
|
|
80
|
+
});
|
|
81
|
+
const title = flags.package ? `\u{1F4E6} Build Order for ${flags.package}` : "\u{1F4E6} Monorepo Build Order";
|
|
82
|
+
ui?.success?.("Build order calculated successfully", {
|
|
83
|
+
title,
|
|
84
|
+
sections
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
function outputCircularDependencies(cycles, ui) {
|
|
88
|
+
const sections = [];
|
|
89
|
+
for (let i = 0; i < cycles.length; i++) {
|
|
90
|
+
const cycle = cycles[i];
|
|
91
|
+
if (!cycle) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
sections.push({
|
|
95
|
+
header: `Cycle ${i + 1}`,
|
|
96
|
+
items: cycle.map((pkg, idx) => {
|
|
97
|
+
if (idx === cycle.length - 1) {
|
|
98
|
+
const firstPkg = cycle[0];
|
|
99
|
+
return ` ${pkg} \u2192 ${firstPkg ?? "?"} (circular!)`;
|
|
100
|
+
}
|
|
101
|
+
return ` ${pkg} \u2192`;
|
|
102
|
+
})
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
ui?.error?.("Circular dependencies detected", {
|
|
106
|
+
title: "\u26A0\uFE0F Circular Dependencies",
|
|
107
|
+
sections
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export { build_order_default as default };
|
|
112
|
+
//# sourceMappingURL=build-order.js.map
|
|
113
|
+
//# sourceMappingURL=build-order.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/cli/commands/build-order.ts"],"names":[],"mappings":"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","file":"build-order.js","sourcesContent":["/**\n * quality:build-order - Calculate build order and dependency layers\n *\n * Uses topological sort to determine correct build order.\n * Shows build layers where each layer can build in parallel.\n */\n\nimport { defineCommand, type PluginContextV3 } from '@kb-labs/sdk';\nimport {\n buildDependencyGraph,\n topologicalSort,\n getBuildOrderForPackage,\n type TopologicalSortResult,\n} from '@kb-labs/quality-core/graph';\n\ntype BuildOrderFlags = {\n package?: string;\n layers?: boolean;\n script?: boolean;\n json?: boolean;\n argv?: string[];\n};\n\ntype BuildOrderInput = BuildOrderFlags & { argv?: string[] };\n\ntype BuildOrderCommandResult = {\n exitCode: number;\n result?: TopologicalSortResult;\n};\n\nexport default defineCommand({\n id: 'quality:build-order',\n description: 'Calculate build order using topological sort',\n\n handler: {\n async execute(ctx: PluginContextV3, input: BuildOrderInput): Promise<BuildOrderCommandResult> {\n const { ui } = ctx;\n\n // V3: Flags come in input.flags object (not auto-merged)\n const flags = (input as any).flags ?? input;\n\n // Build dependency graph\n const graph = buildDependencyGraph(ctx.cwd);\n\n // Calculate build order\n let result: TopologicalSortResult;\n if (flags.package) {\n result = getBuildOrderForPackage(graph, flags.package);\n } else {\n result = topologicalSort(graph);\n }\n\n // Check for circular dependencies\n if (result.circular.length > 0) {\n ui?.error?.(\n `Found ${result.circular.length} circular dependencies. Build order cannot be determined.`\n );\n outputCircularDependencies(result.circular, ui);\n return { exitCode: 1, result };\n }\n\n // Output results\n outputBuildOrder(result, flags, ui);\n\n return { exitCode: 0, result };\n },\n },\n});\n\n/**\n * Output build order results\n */\nfunction outputBuildOrder(result: TopologicalSortResult, flags: any, ui: any) {\n if (flags.json) {\n ui?.json?.(result);\n return;\n }\n\n if (flags.script) {\n // Output as shell script\n ui?.write?.('#!/bin/bash');\n ui?.write?.('# Generated build script');\n ui?.write?.('set -e');\n ui?.write?.('');\n\n for (let i = 0; i < result.layers.length; i++) {\n const layer = result.layers[i];\n if (!layer) {continue;}\n ui?.write?.(`# Layer ${i + 1} (${layer.length} packages)`);\n for (const pkg of layer) {\n ui?.write?.(`pnpm --filter \"${pkg}\" run build`);\n }\n ui?.write?.('');\n }\n return;\n }\n\n // Build sections\n const sections: Array<{ header: string; items: string[] }> = [];\n\n if (flags.layers) {\n // Show build layers\n const layerItems: string[] = [];\n for (let i = 0; i < result.layers.length; i++) {\n const layer = result.layers[i];\n if (!layer) {continue;}\n layerItems.push(`Layer ${i + 1}: ${layer.length} packages (can build in parallel)`);\n for (const pkg of layer) {\n layerItems.push(` • ${pkg}`);\n }\n layerItems.push('');\n }\n sections.push({ header: 'Build Layers', items: layerItems });\n } else {\n // Show sequential order\n const orderItems = result.sorted.map((pkg, idx) => `${idx + 1}. ${pkg}`);\n sections.push({ header: 'Build Order', items: orderItems });\n }\n\n // Summary\n sections.push({\n header: 'Summary',\n items: [\n `Total packages: ${result.sorted.length}`,\n `Build layers: ${result.layers.length}`,\n `Circular dependencies: ${result.circular.length}`,\n ],\n });\n\n const title = flags.package\n ? `📦 Build Order for ${flags.package}`\n : '📦 Monorepo Build Order';\n\n ui?.success?.('Build order calculated successfully', {\n title,\n sections,\n });\n}\n\n/**\n * Output circular dependencies\n */\nfunction outputCircularDependencies(cycles: string[][], ui: any) {\n const sections: Array<{ header: string; items: string[] }> = [];\n\n for (let i = 0; i < cycles.length; i++) {\n const cycle = cycles[i];\n if (!cycle) {continue;}\n sections.push({\n header: `Cycle ${i + 1}`,\n items: cycle.map((pkg, idx) => {\n if (idx === cycle.length - 1) {\n const firstPkg = cycle[0];\n return ` ${pkg} → ${firstPkg ?? '?'} (circular!)`;\n }\n return ` ${pkg} →`;\n }),\n });\n }\n\n ui?.error?.('Circular dependencies detected', {\n title: '⚠️ Circular Dependencies',\n sections,\n });\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as _kb_labs_shared_command_kit from '@kb-labs/shared-command-kit';
|
|
2
|
+
import { BuildCheckResult } from '@kb-labs/quality-contracts';
|
|
3
|
+
import { CheckBuildsFlags } from './flags.js';
|
|
4
|
+
|
|
5
|
+
type CheckBuildsInput = CheckBuildsFlags & {
|
|
6
|
+
argv?: string[];
|
|
7
|
+
};
|
|
8
|
+
declare const _default: _kb_labs_shared_command_kit.CommandHandlerV3<unknown, CheckBuildsInput, BuildCheckResult>;
|
|
9
|
+
|
|
10
|
+
export { _default as default };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { defineCommand } from '@kb-labs/sdk';
|
|
2
|
+
import { CACHE_KEYS } from '@kb-labs/quality-contracts';
|
|
3
|
+
import { checkBuilds } from '@kb-labs/quality-core/builds';
|
|
4
|
+
|
|
5
|
+
// src/cli/commands/check-builds.ts
|
|
6
|
+
var check_builds_default = defineCommand({
|
|
7
|
+
id: "quality:check-builds",
|
|
8
|
+
description: "Check build status across monorepo",
|
|
9
|
+
handler: {
|
|
10
|
+
async execute(ctx, input) {
|
|
11
|
+
const { ui, platform } = ctx;
|
|
12
|
+
const flags = input.flags ?? input;
|
|
13
|
+
const cacheKey = `${CACHE_KEYS.BUILDS}:${flags.package || "all"}`;
|
|
14
|
+
if (!flags.refresh) {
|
|
15
|
+
const cached = await platform.cache.get(cacheKey);
|
|
16
|
+
if (cached) {
|
|
17
|
+
outputBuildCheck({ ...cached, cached: true }, flags, ui);
|
|
18
|
+
return { exitCode: cached.failing > 0 ? 1 : 0, result: cached };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const result = await checkBuilds(ctx.cwd, {
|
|
22
|
+
packageFilter: flags.package,
|
|
23
|
+
timeout: flags.timeout ? Number(flags.timeout) : 3e4
|
|
24
|
+
});
|
|
25
|
+
await platform.cache.set(cacheKey, result, 10 * 60 * 1e3);
|
|
26
|
+
await platform.analytics.track("quality:check-builds", {
|
|
27
|
+
totalPackages: result.totalPackages,
|
|
28
|
+
passing: result.passing,
|
|
29
|
+
failing: result.failing,
|
|
30
|
+
staleBuilds: result.staleBuilds.length,
|
|
31
|
+
duration: result.duration,
|
|
32
|
+
packageSpecific: !!flags.package
|
|
33
|
+
});
|
|
34
|
+
outputBuildCheck({ ...result, cached: false }, flags, ui);
|
|
35
|
+
return {
|
|
36
|
+
exitCode: result.failing > 0 ? 1 : 0,
|
|
37
|
+
result
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
function outputBuildCheck(result, flags, ui) {
|
|
43
|
+
if (flags.json) {
|
|
44
|
+
ui?.json?.(result);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const sections = [];
|
|
48
|
+
const statusIcon = result.failing === 0 ? "\u2705" : "\u274C";
|
|
49
|
+
const statusText = result.failing === 0 ? "All builds passing" : "Build failures detected";
|
|
50
|
+
const statusItems = [
|
|
51
|
+
`${statusIcon} ${statusText}`,
|
|
52
|
+
`Passing: ${result.passing} package(s)`,
|
|
53
|
+
result.failing > 0 ? `Failing: ${result.failing} package(s)` : null
|
|
54
|
+
].filter(Boolean);
|
|
55
|
+
sections.push({ header: "Build Status", items: statusItems });
|
|
56
|
+
if (result.failures.length > 0) {
|
|
57
|
+
const failureItems = [];
|
|
58
|
+
for (const failure of result.failures.slice(0, 5)) {
|
|
59
|
+
failureItems.push(`\u2022 ${failure.package}`);
|
|
60
|
+
const firstLine = failure.error.split("\n")[0];
|
|
61
|
+
failureItems.push(` ${firstLine?.substring(0, 80) || "Build failed"}`);
|
|
62
|
+
}
|
|
63
|
+
if (result.failures.length > 5) {
|
|
64
|
+
failureItems.push(`... and ${result.failures.length - 5} more failures`);
|
|
65
|
+
}
|
|
66
|
+
sections.push({ header: "\u274C Failed Packages", items: failureItems });
|
|
67
|
+
}
|
|
68
|
+
if (result.staleBuilds.length > 0) {
|
|
69
|
+
const staleItems = result.staleBuilds.slice(0, 5).map((s) => `\u2022 ${s.package}`);
|
|
70
|
+
if (result.staleBuilds.length > 5) {
|
|
71
|
+
staleItems.push(`... and ${result.staleBuilds.length - 5} more`);
|
|
72
|
+
}
|
|
73
|
+
sections.push({
|
|
74
|
+
header: "\u26A0\uFE0F Stale Builds (dist/ older than src/)",
|
|
75
|
+
items: staleItems
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
const summaryItems = [
|
|
79
|
+
`Total packages: ${result.totalPackages}`,
|
|
80
|
+
`Duration: ${(result.duration / 1e3).toFixed(1)}s`,
|
|
81
|
+
result.cached ? "\u{1F4BE} Cached (use --refresh to recheck)" : "\u{1F504} Fresh check"
|
|
82
|
+
];
|
|
83
|
+
sections.push({ header: "Summary", items: summaryItems });
|
|
84
|
+
const title = result.failing === 0 ? "\u2705 All Builds Passing" : `\u274C ${result.failing} Build Failure(s) Detected`;
|
|
85
|
+
ui?.success?.("Build check completed", {
|
|
86
|
+
title,
|
|
87
|
+
sections
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export { check_builds_default as default };
|
|
92
|
+
//# sourceMappingURL=check-builds.js.map
|
|
93
|
+
//# sourceMappingURL=check-builds.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/cli/commands/check-builds.ts"],"names":[],"mappings":"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","file":"check-builds.js","sourcesContent":["/**\n * quality:check-builds - Check build status across monorepo\n *\n * Analyzes build status including:\n * - Build failures with error messages\n * - Stale builds (dist/ older than src/)\n * - Build duration tracking\n */\n\nimport { defineCommand, type PluginContextV3 } from '@kb-labs/sdk';\nimport type { BuildCheckResult } from '@kb-labs/quality-contracts';\nimport { CACHE_KEYS } from '@kb-labs/quality-contracts';\nimport { checkBuilds } from '@kb-labs/quality-core/builds';\nimport type { CheckBuildsFlags } from './flags.js';\n\n// Input type with backward compatibility\ntype CheckBuildsInput = CheckBuildsFlags & { argv?: string[] };\n\ntype CheckBuildsCommandResult = {\n exitCode: number;\n result?: BuildCheckResult;\n meta?: Record<string, unknown>;\n};\n\nexport default defineCommand({\n id: 'quality:check-builds',\n description: 'Check build status across monorepo',\n\n handler: {\n async execute(\n ctx: PluginContextV3,\n input: CheckBuildsInput\n ): Promise<CheckBuildsCommandResult> {\n const { ui, platform } = ctx;\n\n // V3: Flags come in input.flags object (not auto-merged)\n const flags = (input as any).flags ?? input;\n\n // Check cache unless refresh requested\n const cacheKey = `${CACHE_KEYS.BUILDS}:${flags.package || 'all'}`;\n\n if (!flags.refresh) {\n const cached = await platform.cache.get<BuildCheckResult>(cacheKey);\n if (cached) {\n outputBuildCheck({ ...cached, cached: true }, flags, ui);\n return { exitCode: cached.failing > 0 ? 1 : 0, result: cached };\n }\n }\n\n // Run build check\n const result = await checkBuilds(ctx.cwd, {\n packageFilter: flags.package,\n timeout: flags.timeout ? Number(flags.timeout) : 30000,\n });\n\n // Cache results for 10 minutes\n await platform.cache.set(cacheKey, result, 10 * 60 * 1000);\n\n // Track analytics\n await platform.analytics.track('quality:check-builds', {\n totalPackages: result.totalPackages,\n passing: result.passing,\n failing: result.failing,\n staleBuilds: result.staleBuilds.length,\n duration: result.duration,\n packageSpecific: !!flags.package,\n });\n\n // Output results\n outputBuildCheck({ ...result, cached: false }, flags, ui);\n\n return {\n exitCode: result.failing > 0 ? 1 : 0,\n result,\n };\n },\n },\n});\n\n/**\n * Output build check results\n */\nfunction outputBuildCheck(\n result: BuildCheckResult & { cached?: boolean },\n flags: any,\n ui: any\n) {\n if (flags.json) {\n ui?.json?.(result);\n return;\n }\n\n // Build sections\n const sections: Array<{ header: string; items: string[] }> = [];\n\n // Status section\n const statusIcon = result.failing === 0 ? '✅' : '❌';\n const statusText = result.failing === 0 ? 'All builds passing' : 'Build failures detected';\n const statusItems = [\n `${statusIcon} ${statusText}`,\n `Passing: ${result.passing} package(s)`,\n result.failing > 0 ? `Failing: ${result.failing} package(s)` : null,\n ].filter(Boolean) as string[];\n\n sections.push({ header: 'Build Status', items: statusItems });\n\n // Failed packages\n if (result.failures.length > 0) {\n const failureItems: string[] = [];\n for (const failure of result.failures.slice(0, 5)) {\n failureItems.push(`• ${failure.package}`);\n // Show first line of error\n const firstLine = failure.error.split('\\n')[0];\n failureItems.push(` ${firstLine?.substring(0, 80) || 'Build failed'}`);\n }\n\n if (result.failures.length > 5) {\n failureItems.push(`... and ${result.failures.length - 5} more failures`);\n }\n\n sections.push({ header: '❌ Failed Packages', items: failureItems });\n }\n\n // Stale builds\n if (result.staleBuilds.length > 0) {\n const staleItems = result.staleBuilds.slice(0, 5).map((s) => `• ${s.package}`);\n\n if (result.staleBuilds.length > 5) {\n staleItems.push(`... and ${result.staleBuilds.length - 5} more`);\n }\n\n sections.push({\n header: '⚠️ Stale Builds (dist/ older than src/)',\n items: staleItems,\n });\n }\n\n // Summary\n const summaryItems = [\n `Total packages: ${result.totalPackages}`,\n `Duration: ${(result.duration / 1000).toFixed(1)}s`,\n result.cached ? '💾 Cached (use --refresh to recheck)' : '🔄 Fresh check',\n ];\n\n sections.push({ header: 'Summary', items: summaryItems });\n\n const title =\n result.failing === 0\n ? '✅ All Builds Passing'\n : `❌ ${result.failing} Build Failure(s) Detected`;\n\n ui?.success?.('Build check completed', {\n title,\n sections,\n });\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as _kb_labs_shared_command_kit from '@kb-labs/shared-command-kit';
|
|
2
|
+
import { TestRunResult } from '@kb-labs/quality-contracts';
|
|
3
|
+
import { CheckTestsFlags } from './flags.js';
|
|
4
|
+
|
|
5
|
+
type CheckTestsInput = CheckTestsFlags & {
|
|
6
|
+
argv?: string[];
|
|
7
|
+
};
|
|
8
|
+
declare const _default: _kb_labs_shared_command_kit.CommandHandlerV3<unknown, CheckTestsInput, TestRunResult>;
|
|
9
|
+
|
|
10
|
+
export { _default as default };
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { defineCommand } from '@kb-labs/sdk';
|
|
2
|
+
import { runTests } from '@kb-labs/quality-core/tests';
|
|
3
|
+
import { CACHE_KEYS } from '@kb-labs/quality-contracts';
|
|
4
|
+
|
|
5
|
+
// src/cli/commands/check-tests.ts
|
|
6
|
+
var check_tests_default = defineCommand({
|
|
7
|
+
id: "quality:check-tests",
|
|
8
|
+
description: "Run tests and track coverage across monorepo",
|
|
9
|
+
handler: {
|
|
10
|
+
async execute(ctx, input) {
|
|
11
|
+
const { ui, platform } = ctx;
|
|
12
|
+
const flags = input.flags ?? input;
|
|
13
|
+
const cacheKey = `${CACHE_KEYS.TESTS}:${flags.package || "all"}`;
|
|
14
|
+
if (!flags.refresh) {
|
|
15
|
+
const cached = await platform.cache.get(cacheKey);
|
|
16
|
+
if (cached) {
|
|
17
|
+
outputTestResults({ ...cached, cached: true }, flags, ui);
|
|
18
|
+
return { exitCode: cached.failing > 0 ? 1 : 0, result: cached };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const result = await runTests(ctx.cwd, {
|
|
22
|
+
packageFilter: flags.package,
|
|
23
|
+
timeout: flags.timeout ? Number(flags.timeout) : 6e4,
|
|
24
|
+
withCoverage: flags["with-coverage"],
|
|
25
|
+
coverageOnly: flags["coverage-only"]
|
|
26
|
+
});
|
|
27
|
+
await platform.cache.set(cacheKey, result, 5 * 60 * 1e3);
|
|
28
|
+
await platform.analytics.track("quality:check-tests", {
|
|
29
|
+
packageFilter: flags.package || "all",
|
|
30
|
+
totalPackages: result.totalPackages,
|
|
31
|
+
passing: result.passing,
|
|
32
|
+
failing: result.failing,
|
|
33
|
+
withCoverage: flags["with-coverage"],
|
|
34
|
+
cached: false
|
|
35
|
+
});
|
|
36
|
+
outputTestResults({ ...result, cached: false }, flags, ui);
|
|
37
|
+
return {
|
|
38
|
+
exitCode: result.failing > 0 ? 1 : 0,
|
|
39
|
+
result,
|
|
40
|
+
meta: { cached: false }
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
function outputTestResults(result, flags, ui) {
|
|
46
|
+
if (flags.json) {
|
|
47
|
+
ui?.json?.(result);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const sections = [];
|
|
51
|
+
const statusIcon = result.failing === 0 ? "\u2705" : "\u274C";
|
|
52
|
+
const statusText = result.failing === 0 ? "All tests passing" : "Test failures detected";
|
|
53
|
+
const statusItems = [
|
|
54
|
+
`${statusIcon} ${statusText}`,
|
|
55
|
+
`Total packages: ${result.totalPackages}`,
|
|
56
|
+
`Passing: ${result.passing}`,
|
|
57
|
+
result.failing > 0 ? `Failing: ${result.failing}` : null,
|
|
58
|
+
result.skipped > 0 ? `Skipped: ${result.skipped} (no test script)` : null
|
|
59
|
+
].filter(Boolean);
|
|
60
|
+
sections.push({ header: "Test Status", items: statusItems });
|
|
61
|
+
if (result.summary.totalTests > 0) {
|
|
62
|
+
const summaryItems2 = [
|
|
63
|
+
`Total tests: ${result.summary.totalTests}`,
|
|
64
|
+
`Passed: ${result.summary.passedTests}`,
|
|
65
|
+
result.summary.failedTests > 0 ? `Failed: ${result.summary.failedTests}` : null
|
|
66
|
+
].filter(Boolean);
|
|
67
|
+
sections.push({ header: "Test Summary", items: summaryItems2 });
|
|
68
|
+
}
|
|
69
|
+
if (result.failures.length > 0) {
|
|
70
|
+
const failureItems = [];
|
|
71
|
+
for (const failure of result.failures.slice(0, 5)) {
|
|
72
|
+
failureItems.push(`\u274C ${failure.package}`);
|
|
73
|
+
if (failure.failedTests && failure.totalTests) {
|
|
74
|
+
failureItems.push(` ${failure.failedTests}/${failure.totalTests} tests failed`);
|
|
75
|
+
}
|
|
76
|
+
const firstLine = failure.error.split("\n")[0];
|
|
77
|
+
failureItems.push(` ${firstLine?.substring(0, 80) || "Test failed"}`);
|
|
78
|
+
}
|
|
79
|
+
if (result.failures.length > 5) {
|
|
80
|
+
failureItems.push(`... and ${result.failures.length - 5} more failures`);
|
|
81
|
+
}
|
|
82
|
+
sections.push({ header: "Failed Packages", items: failureItems });
|
|
83
|
+
}
|
|
84
|
+
if (result.coverage.packages.length > 0) {
|
|
85
|
+
const coverageItems = [];
|
|
86
|
+
coverageItems.push(`Average coverage: ${result.coverage.avgCoverage.toFixed(1)}%`);
|
|
87
|
+
coverageItems.push("");
|
|
88
|
+
const topPackages = result.coverage.packages.sort((a, b) => b.lines - a.lines).slice(0, 5);
|
|
89
|
+
for (const pkg of topPackages) {
|
|
90
|
+
coverageItems.push(`${pkg.name}`);
|
|
91
|
+
coverageItems.push(
|
|
92
|
+
` Lines: ${pkg.lines.toFixed(1)}% | Statements: ${pkg.statements.toFixed(1)}% | Functions: ${pkg.functions.toFixed(1)}% | Branches: ${pkg.branches.toFixed(1)}%`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
if (result.coverage.packages.length > 5) {
|
|
96
|
+
coverageItems.push(`... and ${result.coverage.packages.length - 5} more packages`);
|
|
97
|
+
}
|
|
98
|
+
sections.push({ header: "Coverage", items: coverageItems });
|
|
99
|
+
}
|
|
100
|
+
const summaryItems = [
|
|
101
|
+
`Duration: ${(result.duration / 1e3).toFixed(1)}s`,
|
|
102
|
+
result.cached ? "\u{1F4BE} Cached (use --refresh to re-run)" : "\u{1F504} Fresh run"
|
|
103
|
+
];
|
|
104
|
+
sections.push({ header: "Summary", items: summaryItems });
|
|
105
|
+
const title = result.failing === 0 ? "\u2705 All Tests Passed" : `\u274C ${result.failing} Package(s) with Test Failures`;
|
|
106
|
+
ui?.success?.("Test run completed", {
|
|
107
|
+
title,
|
|
108
|
+
sections
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export { check_tests_default as default };
|
|
113
|
+
//# sourceMappingURL=check-tests.js.map
|
|
114
|
+
//# sourceMappingURL=check-tests.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/cli/commands/check-tests.ts"],"names":["summaryItems"],"mappings":"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","file":"check-tests.js","sourcesContent":["/**\n * quality:check-tests - Test execution and coverage tracking\n *\n * Runs tests across monorepo packages and collects coverage statistics.\n */\n\nimport { defineCommand, type PluginContextV3 } from '@kb-labs/sdk';\nimport { runTests } from '@kb-labs/quality-core/tests';\nimport { CACHE_KEYS, type TestRunResult } from '@kb-labs/quality-contracts';\nimport { type CheckTestsFlags } from './flags.js';\n\n// Input type with backward compatibility\ntype CheckTestsInput = CheckTestsFlags & { argv?: string[] };\n\ntype CheckTestsCommandResult = {\n exitCode: number;\n result?: TestRunResult;\n meta?: Record<string, unknown>;\n};\n\nexport default defineCommand({\n id: 'quality:check-tests',\n description: 'Run tests and track coverage across monorepo',\n\n handler: {\n async execute(\n ctx: PluginContextV3,\n input: CheckTestsInput\n ): Promise<CheckTestsCommandResult> {\n const { ui, platform } = ctx;\n\n // V3: Flags come in input.flags object (not auto-merged)\n const flags = (input as any).flags ?? input;\n\n const cacheKey = `${CACHE_KEYS.TESTS}:${flags.package || 'all'}`;\n\n // Check cache unless refresh requested\n if (!flags.refresh) {\n const cached = await platform.cache.get<TestRunResult>(cacheKey);\n if (cached) {\n outputTestResults({ ...cached, cached: true }, flags, ui);\n return { exitCode: cached.failing > 0 ? 1 : 0, result: cached };\n }\n }\n\n // Run tests\n const result = await runTests(ctx.cwd, {\n packageFilter: flags.package,\n timeout: flags.timeout ? Number(flags.timeout) : 60000,\n withCoverage: flags['with-coverage'],\n coverageOnly: flags['coverage-only'],\n });\n\n // Cache result (5 min TTL - tests are slower)\n await platform.cache.set(cacheKey, result, 5 * 60 * 1000);\n\n // Track analytics\n await platform.analytics.track('quality:check-tests', {\n packageFilter: flags.package || 'all',\n totalPackages: result.totalPackages,\n passing: result.passing,\n failing: result.failing,\n withCoverage: flags['with-coverage'],\n cached: false,\n });\n\n // Output results\n outputTestResults({ ...result, cached: false }, flags, ui);\n\n return {\n exitCode: result.failing > 0 ? 1 : 0,\n result,\n meta: { cached: false },\n };\n },\n },\n});\n\n/**\n * Output test results\n */\nfunction outputTestResults(\n result: TestRunResult & { cached: boolean },\n flags: CheckTestsInput,\n ui: any\n): void {\n if (flags.json) {\n ui?.json?.(result);\n return;\n }\n\n // Build sections\n const sections: Array<{ header: string; items: string[] }> = [];\n\n // Status section\n const statusIcon = result.failing === 0 ? '✅' : '❌';\n const statusText = result.failing === 0 ? 'All tests passing' : 'Test failures detected';\n const statusItems = [\n `${statusIcon} ${statusText}`,\n `Total packages: ${result.totalPackages}`,\n `Passing: ${result.passing}`,\n result.failing > 0 ? `Failing: ${result.failing}` : null,\n result.skipped > 0 ? `Skipped: ${result.skipped} (no test script)` : null,\n ].filter(Boolean) as string[];\n\n sections.push({ header: 'Test Status', items: statusItems });\n\n // Test summary\n if (result.summary.totalTests > 0) {\n const summaryItems = [\n `Total tests: ${result.summary.totalTests}`,\n `Passed: ${result.summary.passedTests}`,\n result.summary.failedTests > 0 ? `Failed: ${result.summary.failedTests}` : null,\n ].filter(Boolean) as string[];\n\n sections.push({ header: 'Test Summary', items: summaryItems });\n }\n\n // Failed packages\n if (result.failures.length > 0) {\n const failureItems: string[] = [];\n for (const failure of result.failures.slice(0, 5)) {\n failureItems.push(`❌ ${failure.package}`);\n if (failure.failedTests && failure.totalTests) {\n failureItems.push(` ${failure.failedTests}/${failure.totalTests} tests failed`);\n }\n // Show first line of error\n const firstLine = failure.error.split('\\n')[0];\n failureItems.push(` ${firstLine?.substring(0, 80) || 'Test failed'}`);\n }\n\n if (result.failures.length > 5) {\n failureItems.push(`... and ${result.failures.length - 5} more failures`);\n }\n\n sections.push({ header: 'Failed Packages', items: failureItems });\n }\n\n // Coverage section (if available)\n if (result.coverage.packages.length > 0) {\n const coverageItems: string[] = [];\n coverageItems.push(`Average coverage: ${result.coverage.avgCoverage.toFixed(1)}%`);\n coverageItems.push('');\n\n const topPackages = result.coverage.packages\n .sort((a, b) => b.lines - a.lines)\n .slice(0, 5);\n\n for (const pkg of topPackages) {\n coverageItems.push(`${pkg.name}`);\n coverageItems.push(\n ` Lines: ${pkg.lines.toFixed(1)}% | Statements: ${pkg.statements.toFixed(1)}% | Functions: ${pkg.functions.toFixed(1)}% | Branches: ${pkg.branches.toFixed(1)}%`\n );\n }\n\n if (result.coverage.packages.length > 5) {\n coverageItems.push(`... and ${result.coverage.packages.length - 5} more packages`);\n }\n\n sections.push({ header: 'Coverage', items: coverageItems });\n }\n\n // Summary\n const summaryItems = [\n `Duration: ${(result.duration / 1000).toFixed(1)}s`,\n result.cached ? '💾 Cached (use --refresh to re-run)' : '🔄 Fresh run',\n ];\n\n sections.push({ header: 'Summary', items: summaryItems });\n\n const title =\n result.failing === 0\n ? '✅ All Tests Passed'\n : `❌ ${result.failing} Package(s) with Test Failures`;\n\n ui?.success?.('Test run completed', {\n title,\n sections,\n });\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as _kb_labs_shared_command_kit from '@kb-labs/shared-command-kit';
|
|
2
|
+
import { TypeAnalysisResult } from '@kb-labs/quality-contracts';
|
|
3
|
+
import { CheckTypesFlags } from './flags.js';
|
|
4
|
+
|
|
5
|
+
type CheckTypesInput = CheckTypesFlags & {
|
|
6
|
+
argv?: string[];
|
|
7
|
+
};
|
|
8
|
+
declare const _default: _kb_labs_shared_command_kit.CommandHandlerV3<unknown, CheckTypesInput, TypeAnalysisResult>;
|
|
9
|
+
|
|
10
|
+
export { _default as default };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { defineCommand } from '@kb-labs/sdk';
|
|
2
|
+
import { analyzeTypes } from '@kb-labs/quality-core/types';
|
|
3
|
+
import { CACHE_KEYS } from '@kb-labs/quality-contracts';
|
|
4
|
+
|
|
5
|
+
// src/cli/commands/check-types.ts
|
|
6
|
+
var check_types_default = defineCommand({
|
|
7
|
+
id: "quality:check-types",
|
|
8
|
+
description: "Analyze TypeScript type safety across monorepo",
|
|
9
|
+
handler: {
|
|
10
|
+
async execute(ctx, input) {
|
|
11
|
+
const { ui, platform } = ctx;
|
|
12
|
+
const flags = input.flags ?? input;
|
|
13
|
+
const cacheKey = `${CACHE_KEYS.TYPE_ANALYSIS}:${flags.package || "all"}`;
|
|
14
|
+
if (!flags.refresh) {
|
|
15
|
+
const cached = await platform.cache.get(cacheKey);
|
|
16
|
+
if (cached) {
|
|
17
|
+
outputTypeAnalysis({ ...cached, cached: true }, flags, ui);
|
|
18
|
+
return { exitCode: cached.totalErrors > 0 ? 1 : 0, result: cached };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const result = await analyzeTypes(ctx.cwd, {
|
|
22
|
+
packageFilter: flags.package,
|
|
23
|
+
errorsOnly: flags["errors-only"]
|
|
24
|
+
});
|
|
25
|
+
await platform.cache.set(cacheKey, result, 10 * 60 * 1e3);
|
|
26
|
+
await platform.analytics.track("quality:check-types", {
|
|
27
|
+
packageFilter: flags.package || "all",
|
|
28
|
+
totalPackages: result.totalPackages,
|
|
29
|
+
totalErrors: result.totalErrors,
|
|
30
|
+
avgCoverage: result.avgCoverage,
|
|
31
|
+
cached: false
|
|
32
|
+
});
|
|
33
|
+
outputTypeAnalysis({ ...result, cached: false }, flags, ui);
|
|
34
|
+
return {
|
|
35
|
+
exitCode: result.totalErrors > 0 ? 1 : 0,
|
|
36
|
+
result,
|
|
37
|
+
meta: { cached: false }
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
function outputTypeAnalysis(result, flags, ui) {
|
|
43
|
+
if (flags.json) {
|
|
44
|
+
ui?.json?.(result);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const errorsOnly = flags["errors-only"] || false;
|
|
48
|
+
const sections = [];
|
|
49
|
+
const statusIcon = result.totalErrors === 0 ? "\u2705" : "\u274C";
|
|
50
|
+
const statusText = result.totalErrors === 0 ? "All packages passed type checks" : "Type errors detected";
|
|
51
|
+
const statusItems = [
|
|
52
|
+
`${statusIcon} ${statusText}`,
|
|
53
|
+
`Analyzed: ${result.totalPackages} package(s)`,
|
|
54
|
+
`Errors: ${result.totalErrors}`,
|
|
55
|
+
`Warnings: ${result.totalWarnings}`,
|
|
56
|
+
`Avg Coverage: ${result.avgCoverage.toFixed(1)}%`
|
|
57
|
+
];
|
|
58
|
+
sections.push({ header: "Type Safety Status", items: statusItems });
|
|
59
|
+
if (result.packagesWithErrors > 0) {
|
|
60
|
+
const errorItems = [];
|
|
61
|
+
const packagesToShow = result.packages.filter((pkg) => !errorsOnly || pkg.errors > 0).slice(0, 10);
|
|
62
|
+
for (const pkg of packagesToShow) {
|
|
63
|
+
const status = pkg.errors > 0 ? "\u274C" : "\u2705";
|
|
64
|
+
errorItems.push(`${status} ${pkg.name}`);
|
|
65
|
+
const details = [
|
|
66
|
+
pkg.errors > 0 ? `${pkg.errors} error(s)` : null,
|
|
67
|
+
pkg.warnings > 0 ? `${pkg.warnings} warning(s)` : null,
|
|
68
|
+
`${pkg.coverage.toFixed(1)}% coverage`,
|
|
69
|
+
pkg.anyCount > 0 ? `${pkg.anyCount} any` : null,
|
|
70
|
+
pkg.tsIgnoreCount > 0 ? `${pkg.tsIgnoreCount} @ts-ignore` : null
|
|
71
|
+
].filter(Boolean).join(", ");
|
|
72
|
+
errorItems.push(` ${details}`);
|
|
73
|
+
}
|
|
74
|
+
if (result.packages.length > 10) {
|
|
75
|
+
errorItems.push(`... and ${result.packages.length - 10} more packages`);
|
|
76
|
+
}
|
|
77
|
+
sections.push({
|
|
78
|
+
header: `Packages with Type Errors (${result.packagesWithErrors})`,
|
|
79
|
+
items: errorItems
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
const excellent = result.packages.filter((p) => p.coverage >= 90).length;
|
|
83
|
+
const good = result.packages.filter((p) => p.coverage >= 70 && p.coverage < 90).length;
|
|
84
|
+
const poor = result.packages.filter((p) => p.coverage < 70).length;
|
|
85
|
+
if (result.packages.length > 0) {
|
|
86
|
+
const coverageItems = [
|
|
87
|
+
`\u2705 Excellent (\u226590%): ${excellent} package(s)`,
|
|
88
|
+
`\u26A0\uFE0F Good (70-90%): ${good} package(s)`,
|
|
89
|
+
poor > 0 ? `\u274C Poor (<70%): ${poor} package(s)` : null
|
|
90
|
+
].filter(Boolean);
|
|
91
|
+
sections.push({ header: "Type Coverage Distribution", items: coverageItems });
|
|
92
|
+
}
|
|
93
|
+
const summaryItems = [
|
|
94
|
+
`Total packages: ${result.totalPackages}`,
|
|
95
|
+
`Duration: ${(result.duration / 1e3).toFixed(1)}s`,
|
|
96
|
+
result.cached ? "\u{1F4BE} Cached (use --refresh to recheck)" : "\u{1F504} Fresh analysis"
|
|
97
|
+
];
|
|
98
|
+
sections.push({ header: "Summary", items: summaryItems });
|
|
99
|
+
const title = result.totalErrors === 0 ? "\u2705 All Type Checks Passed" : `\u274C ${result.packagesWithErrors} Package(s) with Type Errors`;
|
|
100
|
+
ui?.success?.("Type analysis completed", {
|
|
101
|
+
title,
|
|
102
|
+
sections
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export { check_types_default as default };
|
|
107
|
+
//# sourceMappingURL=check-types.js.map
|
|
108
|
+
//# sourceMappingURL=check-types.js.map
|