@kb-labs/devkit 1.0.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/.cursorrules +32 -0
- package/.github/CODEOWNERS +2 -0
- package/.github/actions/setup-node-pnpm/action.yml +47 -0
- package/.github/workflow-templates/ci.yml +13 -0
- package/.github/workflow-templates/drift-check.yml +10 -0
- package/.github/workflow-templates/profiles-validate.yml +16 -0
- package/.github/workflow-templates/release.yml +8 -0
- package/.github/workflows/ci-reusable.yml +131 -0
- package/.github/workflows/drift-check-reusable.yml +23 -0
- package/.github/workflows/fixtures.yml +74 -0
- package/.github/workflows/profiles-validate-reusable.yml +67 -0
- package/.github/workflows/release-reusable.yml +50 -0
- package/.vscode/settings.json +23 -0
- package/AGENTS.md +130 -0
- package/LICENSE +21 -0
- package/README.md +1542 -0
- package/agents/devkit-maintainer/context.globs +15 -0
- package/agents/devkit-maintainer/permissions.yml +17 -0
- package/agents/devkit-maintainer/prompt.md +28 -0
- package/agents/devkit-maintainer/runbook.md +31 -0
- package/agents/docs-crafter/prompt.md +24 -0
- package/agents/docs-crafter/runbook.md +18 -0
- package/agents/release-manager/context.globs +7 -0
- package/agents/release-manager/prompt.md +27 -0
- package/agents/release-manager/runbook.md +17 -0
- package/agents/test-generator/context.globs +7 -0
- package/agents/test-generator/prompt.md +27 -0
- package/agents/test-generator/runbook.md +18 -0
- package/bin/devkit-architecture.mjs +1225 -0
- package/bin/devkit-build-order.mjs +500 -0
- package/bin/devkit-check-build-readiness.mjs +222 -0
- package/bin/devkit-check-commands.mjs +394 -0
- package/bin/devkit-check-configs.mjs +461 -0
- package/bin/devkit-check-deprecated.mjs +532 -0
- package/bin/devkit-check-duplicates.mjs +431 -0
- package/bin/devkit-check-exports.mjs +561 -0
- package/bin/devkit-check-imports.mjs +712 -0
- package/bin/devkit-check-paths.mjs +670 -0
- package/bin/devkit-check-scripts.mjs +335 -0
- package/bin/devkit-check-structure.mjs +495 -0
- package/bin/devkit-check-types.mjs +450 -0
- package/bin/devkit-ci.mjs +261 -0
- package/bin/devkit-core-gate.mjs +225 -0
- package/bin/devkit-fix-deps.mjs +1169 -0
- package/bin/devkit-freshness.mjs +199 -0
- package/bin/devkit-health.mjs +489 -0
- package/bin/devkit-migrate-configs.mjs +370 -0
- package/bin/devkit-paths.mjs +192 -0
- package/bin/devkit-stats.mjs +453 -0
- package/bin/devkit-sync.mjs +12 -0
- package/bin/devkit-tsup-external.mjs +148 -0
- package/bin/devkit-types-audit.mjs +615 -0
- package/bin/devkit-types-order.mjs +637 -0
- package/bin/devkit-validate-naming.mjs +203 -0
- package/bin/devkit-visualize.mjs +452 -0
- package/bin/kb-devkit-qa-history.mjs +453 -0
- package/bin/kb-devkit-qa.mjs +915 -0
- package/eslint/node.js +134 -0
- package/eslint/react.js +260 -0
- package/package.json +186 -0
- package/prettier/index.json +10 -0
- package/sync/index.mjs +693 -0
- package/templates/configs/README.md +306 -0
- package/templates/configs/eslint.config.js +27 -0
- package/templates/configs/package.json.bin +25 -0
- package/templates/configs/package.json.lib +30 -0
- package/templates/configs/tsconfig.build.json +15 -0
- package/templates/configs/tsconfig.json +9 -0
- package/templates/configs/tsup.config.bin.ts +34 -0
- package/templates/configs/tsup.config.cli.ts +41 -0
- package/templates/configs/tsup.config.dual.ts +46 -0
- package/templates/configs/tsup.config.ts +36 -0
- package/templates/package-json/README.md +290 -0
- package/templates/package-json/package.json.bin.template +40 -0
- package/templates/package-json/package.json.template +45 -0
- package/tsconfig/base.json +21 -0
- package/tsconfig/cli.json +12 -0
- package/tsconfig/dist/__tests__/cache.spec.d.ts +6 -0
- package/tsconfig/dist/__tests__/cache.spec.d.ts.map +1 -0
- package/tsconfig/dist/__tests__/cache.spec.js +85 -0
- package/tsconfig/dist/__tests__/cache.spec.js.map +1 -0
- package/tsconfig/dist/__tests__/fs-atomic.spec.d.ts +6 -0
- package/tsconfig/dist/__tests__/fs-atomic.spec.d.ts.map +1 -0
- package/tsconfig/dist/__tests__/fs-atomic.spec.js +153 -0
- package/tsconfig/dist/__tests__/fs-atomic.spec.js.map +1 -0
- package/tsconfig/dist/__tests__/init-workspace.spec.d.ts +6 -0
- package/tsconfig/dist/__tests__/init-workspace.spec.d.ts.map +1 -0
- package/tsconfig/dist/__tests__/init-workspace.spec.js +99 -0
- package/tsconfig/dist/__tests__/init-workspace.spec.js.map +1 -0
- package/tsconfig/dist/__tests__/kb-error.spec.d.ts +6 -0
- package/tsconfig/dist/__tests__/kb-error.spec.d.ts.map +1 -0
- package/tsconfig/dist/__tests__/kb-error.spec.js +190 -0
- package/tsconfig/dist/__tests__/kb-error.spec.js.map +1 -0
- package/tsconfig/dist/__tests__/preset-lockfile.spec.d.ts +6 -0
- package/tsconfig/dist/__tests__/preset-lockfile.spec.d.ts.map +1 -0
- package/tsconfig/dist/__tests__/preset-lockfile.spec.js +142 -0
- package/tsconfig/dist/__tests__/preset-lockfile.spec.js.map +1 -0
- package/tsconfig/dist/__tests__/product-config-profiles.spec.d.ts +6 -0
- package/tsconfig/dist/__tests__/product-config-profiles.spec.d.ts.map +1 -0
- package/tsconfig/dist/__tests__/product-config-profiles.spec.js +100 -0
- package/tsconfig/dist/__tests__/product-config-profiles.spec.js.map +1 -0
- package/tsconfig/dist/__tests__/product-config.spec.d.ts +6 -0
- package/tsconfig/dist/__tests__/product-config.spec.d.ts.map +1 -0
- package/tsconfig/dist/__tests__/product-config.spec.js +298 -0
- package/tsconfig/dist/__tests__/product-config.spec.js.map +1 -0
- package/tsconfig/dist/__tests__/runtime.spec.d.ts +2 -0
- package/tsconfig/dist/__tests__/runtime.spec.d.ts.map +1 -0
- package/tsconfig/dist/__tests__/runtime.spec.js +127 -0
- package/tsconfig/dist/__tests__/runtime.spec.js.map +1 -0
- package/tsconfig/dist/__tests__/upsert-lockfile.spec.d.ts +6 -0
- package/tsconfig/dist/__tests__/upsert-lockfile.spec.d.ts.map +1 -0
- package/tsconfig/dist/__tests__/upsert-lockfile.spec.js +251 -0
- package/tsconfig/dist/__tests__/upsert-lockfile.spec.js.map +1 -0
- package/tsconfig/dist/__tests__/validate-config.spec.d.ts +2 -0
- package/tsconfig/dist/__tests__/validate-config.spec.d.ts.map +1 -0
- package/tsconfig/dist/__tests__/validate-config.spec.js +14 -0
- package/tsconfig/dist/__tests__/validate-config.spec.js.map +1 -0
- package/tsconfig/dist/api/init-workspace.d.ts +10 -0
- package/tsconfig/dist/api/init-workspace.d.ts.map +1 -0
- package/tsconfig/dist/api/init-workspace.js +191 -0
- package/tsconfig/dist/api/init-workspace.js.map +1 -0
- package/tsconfig/dist/api/product-config.d.ts +21 -0
- package/tsconfig/dist/api/product-config.d.ts.map +1 -0
- package/tsconfig/dist/api/product-config.js +192 -0
- package/tsconfig/dist/api/product-config.js.map +1 -0
- package/tsconfig/dist/api/read-config.d.ts +22 -0
- package/tsconfig/dist/api/read-config.d.ts.map +1 -0
- package/tsconfig/dist/api/read-config.js +105 -0
- package/tsconfig/dist/api/read-config.js.map +1 -0
- package/tsconfig/dist/api/upsert-lockfile.d.ts +10 -0
- package/tsconfig/dist/api/upsert-lockfile.d.ts.map +1 -0
- package/tsconfig/dist/api/upsert-lockfile.js +63 -0
- package/tsconfig/dist/api/upsert-lockfile.js.map +1 -0
- package/tsconfig/dist/cache/fs-cache.d.ts +38 -0
- package/tsconfig/dist/cache/fs-cache.d.ts.map +1 -0
- package/tsconfig/dist/cache/fs-cache.js +142 -0
- package/tsconfig/dist/cache/fs-cache.js.map +1 -0
- package/tsconfig/dist/errors/kb-error.d.ts +32 -0
- package/tsconfig/dist/errors/kb-error.d.ts.map +1 -0
- package/tsconfig/dist/errors/kb-error.js +54 -0
- package/tsconfig/dist/errors/kb-error.js.map +1 -0
- package/tsconfig/dist/fs/__tests__/fs.spec.d.ts +2 -0
- package/tsconfig/dist/fs/__tests__/fs.spec.d.ts.map +1 -0
- package/tsconfig/dist/fs/__tests__/fs.spec.js +22 -0
- package/tsconfig/dist/fs/__tests__/fs.spec.js.map +1 -0
- package/tsconfig/dist/fs/fs.d.ts +6 -0
- package/tsconfig/dist/fs/fs.d.ts.map +1 -0
- package/tsconfig/dist/fs/fs.js +12 -0
- package/tsconfig/dist/fs/fs.js.map +1 -0
- package/tsconfig/dist/fs/index.d.ts +2 -0
- package/tsconfig/dist/fs/index.d.ts.map +1 -0
- package/tsconfig/dist/fs/index.js +2 -0
- package/tsconfig/dist/fs/index.js.map +1 -0
- package/tsconfig/dist/hash/config-hash.d.ts +17 -0
- package/tsconfig/dist/hash/config-hash.d.ts.map +1 -0
- package/tsconfig/dist/hash/config-hash.js +55 -0
- package/tsconfig/dist/hash/config-hash.js.map +1 -0
- package/tsconfig/dist/index.d.ts +5 -0
- package/tsconfig/dist/index.d.ts.map +1 -0
- package/tsconfig/dist/index.js +5 -0
- package/tsconfig/dist/index.js.map +1 -0
- package/tsconfig/dist/lockfile/lockfile.d.ts +54 -0
- package/tsconfig/dist/lockfile/lockfile.d.ts.map +1 -0
- package/tsconfig/dist/lockfile/lockfile.js +141 -0
- package/tsconfig/dist/lockfile/lockfile.js.map +1 -0
- package/tsconfig/dist/logging/__tests__/logger.spec.d.ts +2 -0
- package/tsconfig/dist/logging/__tests__/logger.spec.d.ts.map +1 -0
- package/tsconfig/dist/logging/__tests__/logger.spec.js +65 -0
- package/tsconfig/dist/logging/__tests__/logger.spec.js.map +1 -0
- package/tsconfig/dist/logging/__tests__/redaction.spec.d.ts +2 -0
- package/tsconfig/dist/logging/__tests__/redaction.spec.d.ts.map +1 -0
- package/tsconfig/dist/logging/__tests__/redaction.spec.js +34 -0
- package/tsconfig/dist/logging/__tests__/redaction.spec.js.map +1 -0
- package/tsconfig/dist/logging/__tests__/sinks-and-env.spec.d.ts +2 -0
- package/tsconfig/dist/logging/__tests__/sinks-and-env.spec.d.ts.map +1 -0
- package/tsconfig/dist/logging/__tests__/sinks-and-env.spec.js +90 -0
- package/tsconfig/dist/logging/__tests__/sinks-and-env.spec.js.map +1 -0
- package/tsconfig/dist/logging/index.d.ts +6 -0
- package/tsconfig/dist/logging/index.d.ts.map +1 -0
- package/tsconfig/dist/logging/index.js +6 -0
- package/tsconfig/dist/logging/index.js.map +1 -0
- package/tsconfig/dist/logging/logger.d.ts +9 -0
- package/tsconfig/dist/logging/logger.d.ts.map +1 -0
- package/tsconfig/dist/logging/logger.js +101 -0
- package/tsconfig/dist/logging/logger.js.map +1 -0
- package/tsconfig/dist/logging/redaction.d.ts +7 -0
- package/tsconfig/dist/logging/redaction.d.ts.map +1 -0
- package/tsconfig/dist/logging/redaction.js +23 -0
- package/tsconfig/dist/logging/redaction.js.map +1 -0
- package/tsconfig/dist/logging/sinks/json.d.ts +4 -0
- package/tsconfig/dist/logging/sinks/json.d.ts.map +1 -0
- package/tsconfig/dist/logging/sinks/json.js +23 -0
- package/tsconfig/dist/logging/sinks/json.js.map +1 -0
- package/tsconfig/dist/logging/sinks/stdout.d.ts +3 -0
- package/tsconfig/dist/logging/sinks/stdout.d.ts.map +1 -0
- package/tsconfig/dist/logging/sinks/stdout.js +24 -0
- package/tsconfig/dist/logging/sinks/stdout.js.map +1 -0
- package/tsconfig/dist/logging/types/index.d.ts +2 -0
- package/tsconfig/dist/logging/types/index.d.ts.map +1 -0
- package/tsconfig/dist/logging/types/index.js +2 -0
- package/tsconfig/dist/logging/types/index.js.map +1 -0
- package/tsconfig/dist/logging/types/types.d.ts +37 -0
- package/tsconfig/dist/logging/types/types.d.ts.map +1 -0
- package/tsconfig/dist/logging/types/types.js +2 -0
- package/tsconfig/dist/logging/types/types.js.map +1 -0
- package/tsconfig/dist/merge/layered-merge.d.ts +16 -0
- package/tsconfig/dist/merge/layered-merge.d.ts.map +1 -0
- package/tsconfig/dist/merge/layered-merge.js +97 -0
- package/tsconfig/dist/merge/layered-merge.js.map +1 -0
- package/tsconfig/dist/preset/resolve-preset.d.ts +29 -0
- package/tsconfig/dist/preset/resolve-preset.d.ts.map +1 -0
- package/tsconfig/dist/preset/resolve-preset.js +104 -0
- package/tsconfig/dist/preset/resolve-preset.js.map +1 -0
- package/tsconfig/dist/repo/__tests__/repo.spec.d.ts +2 -0
- package/tsconfig/dist/repo/__tests__/repo.spec.d.ts.map +1 -0
- package/tsconfig/dist/repo/__tests__/repo.spec.js +25 -0
- package/tsconfig/dist/repo/__tests__/repo.spec.js.map +1 -0
- package/tsconfig/dist/repo/index.d.ts +2 -0
- package/tsconfig/dist/repo/index.d.ts.map +1 -0
- package/tsconfig/dist/repo/index.js +2 -0
- package/tsconfig/dist/repo/index.js.map +1 -0
- package/tsconfig/dist/repo/repo.d.ts +6 -0
- package/tsconfig/dist/repo/repo.d.ts.map +1 -0
- package/tsconfig/dist/repo/repo.js +25 -0
- package/tsconfig/dist/repo/repo.js.map +1 -0
- package/tsconfig/dist/runtime/index.d.ts +2 -0
- package/tsconfig/dist/runtime/index.d.ts.map +1 -0
- package/tsconfig/dist/runtime/index.js +2 -0
- package/tsconfig/dist/runtime/index.js.map +1 -0
- package/tsconfig/dist/runtime/runtime.d.ts +46 -0
- package/tsconfig/dist/runtime/runtime.d.ts.map +1 -0
- package/tsconfig/dist/runtime/runtime.js +126 -0
- package/tsconfig/dist/runtime/runtime.js.map +1 -0
- package/tsconfig/dist/tsconfig.tools.tsbuildinfo +1 -0
- package/tsconfig/dist/tsconfig.tsbuildinfo +1 -0
- package/tsconfig/dist/types/index.d.ts +2 -0
- package/tsconfig/dist/types/index.d.ts.map +1 -0
- package/tsconfig/dist/types/index.js +2 -0
- package/tsconfig/dist/types/index.js.map +1 -0
- package/tsconfig/dist/types/init.d.ts +34 -0
- package/tsconfig/dist/types/init.d.ts.map +1 -0
- package/tsconfig/dist/types/init.js +6 -0
- package/tsconfig/dist/types/init.js.map +1 -0
- package/tsconfig/dist/types/preset.d.ts +27 -0
- package/tsconfig/dist/types/preset.d.ts.map +1 -0
- package/tsconfig/dist/types/preset.js +6 -0
- package/tsconfig/dist/types/preset.js.map +1 -0
- package/tsconfig/dist/types/types.d.ts +6 -0
- package/tsconfig/dist/types/types.d.ts.map +1 -0
- package/tsconfig/dist/types/types.js +2 -0
- package/tsconfig/dist/types/types.js.map +1 -0
- package/tsconfig/dist/utils/__tests__/env.spec.d.ts +2 -0
- package/tsconfig/dist/utils/__tests__/env.spec.d.ts.map +1 -0
- package/tsconfig/dist/utils/__tests__/env.spec.js +33 -0
- package/tsconfig/dist/utils/__tests__/env.spec.js.map +1 -0
- package/tsconfig/dist/utils/env.d.ts +7 -0
- package/tsconfig/dist/utils/env.d.ts.map +1 -0
- package/tsconfig/dist/utils/env.js +25 -0
- package/tsconfig/dist/utils/env.js.map +1 -0
- package/tsconfig/dist/utils/fs-atomic.d.ts +15 -0
- package/tsconfig/dist/utils/fs-atomic.d.ts.map +1 -0
- package/tsconfig/dist/utils/fs-atomic.js +45 -0
- package/tsconfig/dist/utils/fs-atomic.js.map +1 -0
- package/tsconfig/dist/utils/index.d.ts +3 -0
- package/tsconfig/dist/utils/index.d.ts.map +1 -0
- package/tsconfig/dist/utils/index.js +3 -0
- package/tsconfig/dist/utils/index.js.map +1 -0
- package/tsconfig/dist/utils/paths.d.ts +21 -0
- package/tsconfig/dist/utils/paths.d.ts.map +1 -0
- package/tsconfig/dist/utils/paths.js +32 -0
- package/tsconfig/dist/utils/paths.js.map +1 -0
- package/tsconfig/dist/utils/product-normalize.d.ts +27 -0
- package/tsconfig/dist/utils/product-normalize.d.ts.map +1 -0
- package/tsconfig/dist/utils/product-normalize.js +45 -0
- package/tsconfig/dist/utils/product-normalize.js.map +1 -0
- package/tsconfig/dist/validation/validate-config.d.ts +7 -0
- package/tsconfig/dist/validation/validate-config.d.ts.map +1 -0
- package/tsconfig/dist/validation/validate-config.js +22 -0
- package/tsconfig/dist/validation/validate-config.js.map +1 -0
- package/tsconfig/lib.json +13 -0
- package/tsconfig/node.json +12 -0
- package/tsconfig/react-app.json +8 -0
- package/tsconfig/react-lib.json +8 -0
- package/tsconfig/test.json +15 -0
- package/tsup/bin.js +172 -0
- package/tsup/dual.js +155 -0
- package/tsup/external-sync.mjs +41 -0
- package/tsup/external.mjs +109 -0
- package/tsup/node.js +65 -0
- package/tsup/react-lib.js +18 -0
- package/tsup/sdk.js +118 -0
- package/vite/react-app.js +50 -0
- package/vitest/node.js +37 -0
- package/vitest/react.js +39 -0
- package/vitest/vitest-setup.ts +9 -0
|
@@ -0,0 +1,915 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* KB Labs Quality Assurance Runner
|
|
5
|
+
*
|
|
6
|
+
* Runs comprehensive checks across entire monorepo:
|
|
7
|
+
* 1. Build all packages in correct layer order (incremental)
|
|
8
|
+
* 2. Run linter on all packages (with smart caching)
|
|
9
|
+
* 3. Run type-check on all packages (with smart caching)
|
|
10
|
+
* 4. Run tests on all packages (with smart caching)
|
|
11
|
+
*
|
|
12
|
+
* Features:
|
|
13
|
+
* - Incremental builds (skips packages where src/ hasn't changed)
|
|
14
|
+
* - Smart caching (skips lint/type-check/test for unchanged packages)
|
|
15
|
+
* - Per-package and per-repo filtering
|
|
16
|
+
* - Baseline regression detection for all 4 check types
|
|
17
|
+
* - JSON output for CI/CD and agents
|
|
18
|
+
*
|
|
19
|
+
* Usage:
|
|
20
|
+
* npx kb-devkit-qa # Run on all packages (with caching)
|
|
21
|
+
* npx kb-devkit-qa --json # JSON output for agents
|
|
22
|
+
* npx kb-devkit-qa --package=@kb-labs/cli-core # Run on specific package
|
|
23
|
+
* npx kb-devkit-qa --repo=kb-labs-core # Run on entire repo
|
|
24
|
+
* npx kb-devkit-qa --scope=workflow # Run on packages matching scope
|
|
25
|
+
* npx kb-devkit-qa --no-cache # Disable smart caching
|
|
26
|
+
* npx kb-devkit-qa --skip-build # Skip build phase
|
|
27
|
+
* npx kb-devkit-qa --skip-lint # Skip lint phase
|
|
28
|
+
* npx kb-devkit-qa --skip-types # Skip type-check phase
|
|
29
|
+
* npx kb-devkit-qa --skip-tests # Skip test phase
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { execSync } from 'child_process'
|
|
33
|
+
import { readFileSync, existsSync, statSync, readdirSync, writeFileSync, mkdirSync } from 'fs'
|
|
34
|
+
import { join } from 'path'
|
|
35
|
+
import { createHash } from 'crypto'
|
|
36
|
+
|
|
37
|
+
// Parse args
|
|
38
|
+
const args = process.argv.slice(2)
|
|
39
|
+
const jsonMode = args.includes('--json')
|
|
40
|
+
const skipBuild = args.includes('--skip-build')
|
|
41
|
+
const skipLint = args.includes('--skip-lint')
|
|
42
|
+
const skipTypes = args.includes('--skip-types')
|
|
43
|
+
const skipTests = args.includes('--skip-tests')
|
|
44
|
+
const noCache = args.includes('--no-cache') // NEW: Disable caching
|
|
45
|
+
|
|
46
|
+
// NEW: Parse filter options
|
|
47
|
+
const packageFilter = args.find(arg => arg.startsWith('--package='))?.split('=')[1]
|
|
48
|
+
const repoFilter = args.find(arg => arg.startsWith('--repo='))?.split('=')[1]
|
|
49
|
+
const scopeFilter = args.find(arg => arg.startsWith('--scope='))?.split('=')[1]
|
|
50
|
+
|
|
51
|
+
// Cache directory
|
|
52
|
+
const CACHE_DIR = '.qa-cache'
|
|
53
|
+
const CACHE_FILE = join(CACHE_DIR, 'package-hashes.json')
|
|
54
|
+
|
|
55
|
+
// ANSI colors (disabled in JSON mode)
|
|
56
|
+
const colors = jsonMode ? {
|
|
57
|
+
reset: '', bright: '', red: '', green: '', yellow: '', blue: '', cyan: '', gray: '',
|
|
58
|
+
} : {
|
|
59
|
+
reset: '\x1b[0m',
|
|
60
|
+
bright: '\x1b[1m',
|
|
61
|
+
red: '\x1b[31m',
|
|
62
|
+
green: '\x1b[32m',
|
|
63
|
+
yellow: '\x1b[33m',
|
|
64
|
+
blue: '\x1b[34m',
|
|
65
|
+
cyan: '\x1b[36m',
|
|
66
|
+
gray: '\x1b[90m',
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function log(msg, color = 'reset') {
|
|
70
|
+
if (!jsonMode) {
|
|
71
|
+
console.log(`${colors[color]}${msg}${colors.reset}`)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function header(msg) {
|
|
76
|
+
if (!jsonMode) {
|
|
77
|
+
log(`\n${colors.bright}${colors.cyan}${msg}${colors.reset}\n`)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const results = {
|
|
82
|
+
build: { passed: [], failed: [], skipped: [], errors: {} },
|
|
83
|
+
lint: { passed: [], failed: [], skipped: [], errors: {} },
|
|
84
|
+
typeCheck: { passed: [], failed: [], skipped: [], errors: {} },
|
|
85
|
+
test: { passed: [], failed: [], skipped: [], errors: {} },
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Get repo name from package path
|
|
89
|
+
function getRepoFromPackage(pkg) {
|
|
90
|
+
// Extract repo from path: /path/to/kb-labs-REPO/packages/...
|
|
91
|
+
const match = pkg.path.match(/\/(kb-labs-[^/]+)/)
|
|
92
|
+
return match ? match[1] : null
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Load package hash cache
|
|
96
|
+
function loadCache() {
|
|
97
|
+
if (!existsSync(CACHE_FILE)) {
|
|
98
|
+
return {}
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
return JSON.parse(readFileSync(CACHE_FILE, 'utf-8'))
|
|
102
|
+
} catch (err) {
|
|
103
|
+
return {}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Save package hash cache
|
|
108
|
+
function saveCache(cache) {
|
|
109
|
+
if (!existsSync(CACHE_DIR)) {
|
|
110
|
+
mkdirSync(CACHE_DIR, { recursive: true })
|
|
111
|
+
}
|
|
112
|
+
writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2))
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Calculate hash for package sources
|
|
116
|
+
function calculatePackageHash(pkgPath) {
|
|
117
|
+
const srcDir = join(pkgPath, 'src')
|
|
118
|
+
const pkgJson = join(pkgPath, 'package.json')
|
|
119
|
+
|
|
120
|
+
const hash = createHash('sha256')
|
|
121
|
+
|
|
122
|
+
// Hash all files in src/ if it exists
|
|
123
|
+
if (existsSync(srcDir)) {
|
|
124
|
+
function hashDirectory(dir) {
|
|
125
|
+
try {
|
|
126
|
+
const files = readdirSync(dir, { withFileTypes: true })
|
|
127
|
+
for (const file of files) {
|
|
128
|
+
const fullPath = join(dir, file.name)
|
|
129
|
+
if (file.isDirectory()) {
|
|
130
|
+
hashDirectory(fullPath)
|
|
131
|
+
} else {
|
|
132
|
+
const content = readFileSync(fullPath, 'utf-8')
|
|
133
|
+
hash.update(content)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
} catch (err) {
|
|
137
|
+
// Ignore permission errors
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
hashDirectory(srcDir)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Hash package.json (dependencies/scripts)
|
|
144
|
+
if (existsSync(pkgJson)) {
|
|
145
|
+
const content = readFileSync(pkgJson, 'utf-8')
|
|
146
|
+
hash.update(content)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Return null only if neither src nor package.json exist
|
|
150
|
+
if (!existsSync(srcDir) && !existsSync(pkgJson)) {
|
|
151
|
+
return null
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return hash.digest('hex')
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Check if package sources have changed since last QA run
|
|
158
|
+
function hasPackageChanged(pkgName, pkgPath, cache) {
|
|
159
|
+
if (noCache) {return true} // Skip cache if --no-cache flag
|
|
160
|
+
|
|
161
|
+
const currentHash = calculatePackageHash(pkgPath)
|
|
162
|
+
if (!currentHash) {return true} // Nothing to hash, always run
|
|
163
|
+
|
|
164
|
+
const cachedHash = cache[pkgName]
|
|
165
|
+
return currentHash !== cachedHash
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Get all workspace packages (with optional filtering)
|
|
169
|
+
function getWorkspacePackages() {
|
|
170
|
+
try {
|
|
171
|
+
const output = execSync('pnpm list -r --depth -1 --json', {
|
|
172
|
+
encoding: 'utf-8',
|
|
173
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
174
|
+
})
|
|
175
|
+
let packages = JSON.parse(output)
|
|
176
|
+
.filter(pkg => pkg.name?.startsWith('@kb-labs/'))
|
|
177
|
+
.map(pkg => ({ name: pkg.name, path: pkg.path }))
|
|
178
|
+
|
|
179
|
+
// Apply filters
|
|
180
|
+
if (packageFilter) {
|
|
181
|
+
packages = packages.filter(p => p.name === packageFilter)
|
|
182
|
+
if (packages.length === 0) {
|
|
183
|
+
log(`โ Package not found: ${packageFilter}`, 'red')
|
|
184
|
+
process.exit(1)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (repoFilter) {
|
|
189
|
+
packages = packages.filter(p => getRepoFromPackage(p) === repoFilter)
|
|
190
|
+
if (packages.length === 0) {
|
|
191
|
+
log(`โ No packages found in repo: ${repoFilter}`, 'red')
|
|
192
|
+
process.exit(1)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (scopeFilter) {
|
|
197
|
+
packages = packages.filter(p => p.name.includes(scopeFilter))
|
|
198
|
+
if (packages.length === 0) {
|
|
199
|
+
log(`โ No packages found matching scope: ${scopeFilter}`, 'red')
|
|
200
|
+
process.exit(1)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return packages
|
|
205
|
+
} catch (err) {
|
|
206
|
+
log('Failed to get workspace packages', 'red')
|
|
207
|
+
process.exit(1)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Get latest modification time in directory recursively
|
|
212
|
+
function getLatestMtime(dir) {
|
|
213
|
+
if (!existsSync(dir)) {return 0}
|
|
214
|
+
|
|
215
|
+
let latest = 0
|
|
216
|
+
try {
|
|
217
|
+
const files = readdirSync(dir, { withFileTypes: true })
|
|
218
|
+
for (const file of files) {
|
|
219
|
+
const fullPath = join(dir, file.name)
|
|
220
|
+
if (file.isDirectory()) {
|
|
221
|
+
const dirMtime = getLatestMtime(fullPath)
|
|
222
|
+
if (dirMtime > latest) {latest = dirMtime}
|
|
223
|
+
} else {
|
|
224
|
+
const stat = statSync(fullPath)
|
|
225
|
+
if (stat.mtimeMs > latest) {latest = stat.mtimeMs}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
} catch (err) {
|
|
229
|
+
// Ignore permission errors
|
|
230
|
+
}
|
|
231
|
+
return latest
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Check if package needs rebuild
|
|
235
|
+
function needsRebuild(pkgPath) {
|
|
236
|
+
const srcDir = join(pkgPath, 'src')
|
|
237
|
+
const distDir = join(pkgPath, 'dist')
|
|
238
|
+
|
|
239
|
+
// If dist doesn't exist, needs build
|
|
240
|
+
if (!existsSync(distDir)) {return true}
|
|
241
|
+
|
|
242
|
+
// If src doesn't exist, skip (might be types-only package)
|
|
243
|
+
if (!existsSync(srcDir)) {return false}
|
|
244
|
+
|
|
245
|
+
// Compare modification times
|
|
246
|
+
const srcMtime = getLatestMtime(srcDir)
|
|
247
|
+
const distMtime = getLatestMtime(distDir)
|
|
248
|
+
|
|
249
|
+
// If src is newer than dist, needs rebuild
|
|
250
|
+
return srcMtime > distMtime
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Build all packages in layer order
|
|
254
|
+
async function buildAllLayers() {
|
|
255
|
+
header('๐จ Building all packages in correct dependency order...')
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
// Generate build order
|
|
259
|
+
log('๐ Calculating build order...', 'gray')
|
|
260
|
+
let buildOrder = execSync('npx kb-devkit-build-order --layers', {
|
|
261
|
+
encoding: 'utf-8',
|
|
262
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
// Strip ANSI codes
|
|
266
|
+
buildOrder = buildOrder.replace(/\x1b\[[0-9;]*m/g, '')
|
|
267
|
+
|
|
268
|
+
// Parse layers
|
|
269
|
+
const layerMatches = buildOrder.matchAll(/Layer (\d+).*?:\s+((?:@kb-labs\/[\w-]+\s*)+)/gs)
|
|
270
|
+
const layers = []
|
|
271
|
+
|
|
272
|
+
for (const match of layerMatches) {
|
|
273
|
+
const layerNum = parseInt(match[1])
|
|
274
|
+
const packages = match[2]
|
|
275
|
+
.trim()
|
|
276
|
+
.split(/\s+/)
|
|
277
|
+
.filter(pkg => pkg.startsWith('@kb-labs/'))
|
|
278
|
+
layers.push({ num: layerNum, packages })
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
log(`Found ${layers.length} layers to build\n`, 'gray')
|
|
282
|
+
|
|
283
|
+
// Get package paths for timestamp checking
|
|
284
|
+
const allPackages = getWorkspacePackages()
|
|
285
|
+
const pkgPathMap = new Map(allPackages.map(p => [p.name, p.path]))
|
|
286
|
+
const filteredPackageNames = new Set(allPackages.map(p => p.name))
|
|
287
|
+
|
|
288
|
+
// Build each layer
|
|
289
|
+
for (const layer of layers) {
|
|
290
|
+
// Filter layer packages based on current filters
|
|
291
|
+
const layerPackagesToBuild = layer.packages.filter(pkg => filteredPackageNames.has(pkg))
|
|
292
|
+
|
|
293
|
+
if (layerPackagesToBuild.length === 0) {
|
|
294
|
+
continue // Skip empty layers
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
log(`๐จ Building Layer ${layer.num}/${layers.length} (${layerPackagesToBuild.length} packages)...`, 'cyan')
|
|
298
|
+
|
|
299
|
+
for (const pkg of layerPackagesToBuild) {
|
|
300
|
+
const pkgPath = pkgPathMap.get(pkg)
|
|
301
|
+
|
|
302
|
+
// Check if rebuild is needed
|
|
303
|
+
if (pkgPath && !needsRebuild(pkgPath)) {
|
|
304
|
+
results.build.skipped.push(pkg)
|
|
305
|
+
if (!jsonMode) {process.stdout.write(`${colors.gray}-${colors.reset}`)}
|
|
306
|
+
continue
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
try {
|
|
310
|
+
execSync(`pnpm --filter ${pkg} run build`, {
|
|
311
|
+
encoding: 'utf-8',
|
|
312
|
+
stdio: 'pipe',
|
|
313
|
+
})
|
|
314
|
+
results.build.passed.push(pkg)
|
|
315
|
+
if (!jsonMode) {process.stdout.write(`${colors.green}.${colors.reset}`)}
|
|
316
|
+
} catch (err) {
|
|
317
|
+
results.build.failed.push(pkg)
|
|
318
|
+
results.build.errors[pkg] = err.stderr || err.stdout || err.message
|
|
319
|
+
if (!jsonMode) {process.stdout.write(`${colors.red}F${colors.reset}`)}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (!jsonMode) {console.log('')}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
log(`\nโ
Build complete: ${results.build.passed.length} passed, ${results.build.failed.length} failed, ${results.build.skipped.length} skipped (up-to-date)\n`, 'green')
|
|
326
|
+
|
|
327
|
+
} catch (err) {
|
|
328
|
+
log('โ Build order calculation failed', 'red')
|
|
329
|
+
throw err
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Run command on all packages
|
|
334
|
+
async function runOnAllPackages(command, label, resultKey, cache) {
|
|
335
|
+
header(`${label}...`)
|
|
336
|
+
|
|
337
|
+
const packages = getWorkspacePackages()
|
|
338
|
+
log(`Running on ${packages.length} packages\n`, 'gray')
|
|
339
|
+
|
|
340
|
+
let cacheHits = 0
|
|
341
|
+
|
|
342
|
+
for (const pkg of packages) {
|
|
343
|
+
// NEW: Check if package has changed
|
|
344
|
+
const changed = hasPackageChanged(pkg.name, pkg.path, cache)
|
|
345
|
+
|
|
346
|
+
if (!changed && !noCache) {
|
|
347
|
+
// Skip unchanged package
|
|
348
|
+
results[resultKey].skipped.push(pkg.name)
|
|
349
|
+
cacheHits++
|
|
350
|
+
if (!jsonMode) {process.stdout.write(`${colors.gray}-${colors.reset}`)}
|
|
351
|
+
continue
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
try {
|
|
355
|
+
execSync(`pnpm --filter ${pkg.name} ${command}`, {
|
|
356
|
+
encoding: 'utf-8',
|
|
357
|
+
stdio: 'pipe',
|
|
358
|
+
})
|
|
359
|
+
results[resultKey].passed.push(pkg.name)
|
|
360
|
+
if (!jsonMode) {process.stdout.write(`${colors.green}.${colors.reset}`)}
|
|
361
|
+
|
|
362
|
+
// NEW: Update cache with current hash
|
|
363
|
+
const currentHash = calculatePackageHash(pkg.path)
|
|
364
|
+
if (currentHash) {
|
|
365
|
+
cache[pkg.name] = currentHash
|
|
366
|
+
}
|
|
367
|
+
} catch (err) {
|
|
368
|
+
// Check if script doesn't exist
|
|
369
|
+
if (err.message.includes('missing script')) {
|
|
370
|
+
results[resultKey].skipped.push(pkg.name)
|
|
371
|
+
if (!jsonMode) {process.stdout.write(`${colors.gray}-${colors.reset}`)}
|
|
372
|
+
} else {
|
|
373
|
+
results[resultKey].failed.push(pkg.name)
|
|
374
|
+
results[resultKey].errors[pkg.name] = err.stderr || err.stdout || err.message
|
|
375
|
+
if (!jsonMode) {process.stdout.write(`${colors.red}F${colors.reset}`)}
|
|
376
|
+
|
|
377
|
+
// Cache hash even for failed packages โ if code hasn't changed, no point re-running
|
|
378
|
+
const currentHash = calculatePackageHash(pkg.path)
|
|
379
|
+
if (currentHash) {
|
|
380
|
+
cache[pkg.name] = currentHash
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (!jsonMode) {console.log('')}
|
|
387
|
+
|
|
388
|
+
const skippedByCache = cacheHits
|
|
389
|
+
const totalSkipped = results[resultKey].skipped.length
|
|
390
|
+
|
|
391
|
+
if (skippedByCache > 0) {
|
|
392
|
+
log(`\nโ
${label} complete: ${results[resultKey].passed.length} passed, ${results[resultKey].failed.length} failed, ${totalSkipped} skipped (${skippedByCache} unchanged)\n`, 'green')
|
|
393
|
+
} else {
|
|
394
|
+
log(`\nโ
${label} complete: ${results[resultKey].passed.length} passed, ${results[resultKey].failed.length} failed, ${totalSkipped} skipped\n`, 'green')
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Load all baselines if they exist
|
|
399
|
+
function loadBaselines() {
|
|
400
|
+
const baselines = {
|
|
401
|
+
build: null,
|
|
402
|
+
lint: null,
|
|
403
|
+
typeCheck: null,
|
|
404
|
+
test: null,
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Load build baseline
|
|
408
|
+
const buildPath = '.baseline/build-failures.json'
|
|
409
|
+
if (existsSync(buildPath)) {
|
|
410
|
+
try {
|
|
411
|
+
baselines.build = JSON.parse(readFileSync(buildPath, 'utf-8'))
|
|
412
|
+
} catch (err) {
|
|
413
|
+
// Ignore parsing errors
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Load lint baseline (aggregated)
|
|
418
|
+
const lintPath = '.baseline/lint-errors-aggregated.json'
|
|
419
|
+
if (existsSync(lintPath)) {
|
|
420
|
+
try {
|
|
421
|
+
baselines.lint = JSON.parse(readFileSync(lintPath, 'utf-8'))
|
|
422
|
+
} catch (err) {
|
|
423
|
+
// Ignore parsing errors
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Load type-check baseline
|
|
428
|
+
const typesPath = '.baseline/type-errors.json'
|
|
429
|
+
if (existsSync(typesPath)) {
|
|
430
|
+
try {
|
|
431
|
+
baselines.typeCheck = JSON.parse(readFileSync(typesPath, 'utf-8'))
|
|
432
|
+
} catch (err) {
|
|
433
|
+
// Ignore parsing errors
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Load test baseline
|
|
438
|
+
const testPath = '.baseline/test-results.json'
|
|
439
|
+
if (existsSync(testPath)) {
|
|
440
|
+
try {
|
|
441
|
+
baselines.test = JSON.parse(readFileSync(testPath, 'utf-8'))
|
|
442
|
+
} catch (err) {
|
|
443
|
+
// Ignore parsing errors
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return baselines
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Calculate diff with baselines for all check types
|
|
451
|
+
function calculateDiff(baselines) {
|
|
452
|
+
if (!baselines) {return null}
|
|
453
|
+
|
|
454
|
+
return {
|
|
455
|
+
build: calculateCheckDiff('build', baselines.build),
|
|
456
|
+
lint: calculateCheckDiff('lint', baselines.lint),
|
|
457
|
+
typeCheck: calculateCheckDiff('typeCheck', baselines.typeCheck),
|
|
458
|
+
test: calculateCheckDiff('test', baselines.test),
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Calculate diff for a specific check type
|
|
463
|
+
function calculateCheckDiff(checkKey, baseline) {
|
|
464
|
+
if (!baseline) {return null}
|
|
465
|
+
|
|
466
|
+
const currentFailed = new Set(results[checkKey].failed)
|
|
467
|
+
let baselineFailed = new Set()
|
|
468
|
+
const perPackage = []
|
|
469
|
+
|
|
470
|
+
// Extract baseline failures based on format
|
|
471
|
+
if (checkKey === 'build' && baseline.summary) {
|
|
472
|
+
// build-failures.json format: { packages: [{name, path, reason, errors}], summary: {...} }
|
|
473
|
+
if (baseline.packages && Array.isArray(baseline.packages)) {
|
|
474
|
+
// New format: has per-package data
|
|
475
|
+
baselineFailed = new Set(baseline.packages.map(pkg => pkg.name))
|
|
476
|
+
} else if (baseline.failures) {
|
|
477
|
+
// Old format: just array of package names
|
|
478
|
+
baselineFailed = new Set(baseline.failures || [])
|
|
479
|
+
}
|
|
480
|
+
} else if (checkKey === 'lint' && baseline.packages) {
|
|
481
|
+
// lint-errors-aggregated.json format: { packages: [{name, errors, warnings}] }
|
|
482
|
+
baselineFailed = new Set(
|
|
483
|
+
baseline.packages
|
|
484
|
+
.filter(pkg => pkg.errors > 0 || pkg.warnings > 0)
|
|
485
|
+
.map(pkg => pkg.name)
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
// Per-package comparison for lint
|
|
489
|
+
const allPackages = new Set([...currentFailed, ...baselineFailed])
|
|
490
|
+
for (const pkgName of allPackages) {
|
|
491
|
+
const baselinePkg = baseline.packages.find(p => p.name === pkgName)
|
|
492
|
+
const baselineErrors = baselinePkg ? (baselinePkg.errors + baselinePkg.warnings) : 0
|
|
493
|
+
const currentFailing = currentFailed.has(pkgName) ? 1 : 0
|
|
494
|
+
|
|
495
|
+
if (baselineErrors > 0 || currentFailing > 0) {
|
|
496
|
+
perPackage.push({
|
|
497
|
+
package: pkgName,
|
|
498
|
+
baseline: baselineErrors,
|
|
499
|
+
current: currentFailing ? '1+' : 0, // We don't have exact counts from current run
|
|
500
|
+
isRegression: currentFailing > 0 && baselineErrors === 0
|
|
501
|
+
})
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
} else if (checkKey === 'typeCheck' && baseline.packages) {
|
|
505
|
+
// type-errors.json format: { packages: [{name, errors, warnings, coverage}] }
|
|
506
|
+
baselineFailed = new Set(
|
|
507
|
+
baseline.packages
|
|
508
|
+
.filter(pkg => pkg.errors > 0)
|
|
509
|
+
.map(pkg => pkg.name)
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
// Per-package comparison for type-check
|
|
513
|
+
const allPackages = new Set([...currentFailed, ...baselineFailed])
|
|
514
|
+
for (const pkgName of allPackages) {
|
|
515
|
+
const baselinePkg = baseline.packages.find(p => p.name === pkgName)
|
|
516
|
+
const baselineErrors = baselinePkg ? baselinePkg.errors : 0
|
|
517
|
+
const currentFailing = currentFailed.has(pkgName) ? 1 : 0
|
|
518
|
+
|
|
519
|
+
if (baselineErrors > 0 || currentFailing > 0) {
|
|
520
|
+
perPackage.push({
|
|
521
|
+
package: pkgName,
|
|
522
|
+
baseline: baselineErrors,
|
|
523
|
+
current: currentFailing ? '1+' : 0,
|
|
524
|
+
isRegression: currentFailing > 0 && baselineErrors === 0
|
|
525
|
+
})
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
} else if (checkKey === 'test' && baseline.summary) {
|
|
529
|
+
// test-results.json format: { summary: {total, passed, failed}, packages: [{name, tests, passed, failed, status}] }
|
|
530
|
+
if (baseline.packages && Array.isArray(baseline.packages)) {
|
|
531
|
+
// New format: has per-package data
|
|
532
|
+
baselineFailed = new Set(
|
|
533
|
+
baseline.packages
|
|
534
|
+
.filter(pkg => pkg.failed > 0)
|
|
535
|
+
.map(pkg => pkg.name)
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
// Per-package comparison for tests
|
|
539
|
+
const allPackages = new Set([...currentFailed, ...baselineFailed])
|
|
540
|
+
for (const pkgName of allPackages) {
|
|
541
|
+
const baselinePkg = baseline.packages.find(p => p.name === pkgName)
|
|
542
|
+
const baselineFailed = baselinePkg ? baselinePkg.failed : 0
|
|
543
|
+
const currentFailing = currentFailed.has(pkgName) ? 1 : 0
|
|
544
|
+
|
|
545
|
+
if (baselineFailed > 0 || currentFailing > 0) {
|
|
546
|
+
perPackage.push({
|
|
547
|
+
package: pkgName,
|
|
548
|
+
baseline: baselineFailed,
|
|
549
|
+
current: currentFailing ? '1+' : 0,
|
|
550
|
+
isRegression: currentFailing > 0 && baselineFailed === 0
|
|
551
|
+
})
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
} else {
|
|
555
|
+
// Old format: no per-package data
|
|
556
|
+
baselineFailed = new Set() // No per-package data
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const newFailures = [...currentFailed].filter(pkg => !baselineFailed.has(pkg))
|
|
561
|
+
const fixed = [...baselineFailed].filter(pkg => !currentFailed.has(pkg))
|
|
562
|
+
const stillFailing = [...currentFailed].filter(pkg => baselineFailed.has(pkg))
|
|
563
|
+
|
|
564
|
+
return {
|
|
565
|
+
newFailures,
|
|
566
|
+
fixed,
|
|
567
|
+
stillFailing,
|
|
568
|
+
summary: {
|
|
569
|
+
baseline: baselineFailed.size,
|
|
570
|
+
current: currentFailed.size,
|
|
571
|
+
delta: currentFailed.size - baselineFailed.size,
|
|
572
|
+
},
|
|
573
|
+
perPackage: perPackage.sort((a, b) => {
|
|
574
|
+
// Sort regressions first, then by baseline errors
|
|
575
|
+
if (a.isRegression !== b.isRegression) {return a.isRegression ? -1 : 1}
|
|
576
|
+
return (typeof b.baseline === 'number' ? b.baseline : 0) - (typeof a.baseline === 'number' ? a.baseline : 0)
|
|
577
|
+
})
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Group results by repo
|
|
582
|
+
function groupByRepo() {
|
|
583
|
+
const packages = getWorkspacePackages()
|
|
584
|
+
const byRepo = {}
|
|
585
|
+
|
|
586
|
+
// Helper to get status for a package in a check
|
|
587
|
+
const getStatus = (checkKey, pkgName) => {
|
|
588
|
+
if (results[checkKey].passed.includes(pkgName)) {return 'passed'}
|
|
589
|
+
if (results[checkKey].failed.includes(pkgName)) {return 'failed'}
|
|
590
|
+
if (results[checkKey].skipped.includes(pkgName)) {return 'skipped'}
|
|
591
|
+
return 'not-run'
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Process all packages
|
|
595
|
+
for (const pkg of packages) {
|
|
596
|
+
const repo = getRepoFromPackage(pkg)
|
|
597
|
+
if (!repo) {continue}
|
|
598
|
+
|
|
599
|
+
// Initialize repo if needed
|
|
600
|
+
if (!byRepo[repo]) {
|
|
601
|
+
byRepo[repo] = {
|
|
602
|
+
repo,
|
|
603
|
+
packages: [],
|
|
604
|
+
summary: {
|
|
605
|
+
build: { passed: 0, failed: 0, skipped: 0 },
|
|
606
|
+
lint: { passed: 0, failed: 0, skipped: 0 },
|
|
607
|
+
typeCheck: { passed: 0, failed: 0, skipped: 0 },
|
|
608
|
+
test: { passed: 0, failed: 0, skipped: 0 },
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Add package with statuses
|
|
614
|
+
const pkgData = {
|
|
615
|
+
name: pkg.name,
|
|
616
|
+
build: getStatus('build', pkg.name),
|
|
617
|
+
lint: getStatus('lint', pkg.name),
|
|
618
|
+
typeCheck: getStatus('typeCheck', pkg.name),
|
|
619
|
+
test: getStatus('test', pkg.name),
|
|
620
|
+
}
|
|
621
|
+
byRepo[repo].packages.push(pkgData)
|
|
622
|
+
|
|
623
|
+
// Update summary counts
|
|
624
|
+
for (const checkKey of ['build', 'lint', 'typeCheck', 'test']) {
|
|
625
|
+
const status = pkgData[checkKey]
|
|
626
|
+
if (status === 'passed') {byRepo[repo].summary[checkKey].passed++}
|
|
627
|
+
else if (status === 'failed') {byRepo[repo].summary[checkKey].failed++}
|
|
628
|
+
else if (status === 'skipped') {byRepo[repo].summary[checkKey].skipped++}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
return byRepo
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// Print JSON report for agents
|
|
636
|
+
function printJsonReport(baselines, diff) {
|
|
637
|
+
// Group by repo if filtering is active
|
|
638
|
+
const byRepo = (packageFilter || repoFilter || scopeFilter) ? groupByRepo() : null
|
|
639
|
+
|
|
640
|
+
const report = {
|
|
641
|
+
status: (results.build.failed.length + results.lint.failed.length +
|
|
642
|
+
results.typeCheck.failed.length + results.test.failed.length) === 0 ? 'passed' : 'failed',
|
|
643
|
+
timestamp: new Date().toISOString(),
|
|
644
|
+
filter: {
|
|
645
|
+
package: packageFilter || null,
|
|
646
|
+
repo: repoFilter || null,
|
|
647
|
+
scope: scopeFilter || null,
|
|
648
|
+
},
|
|
649
|
+
summary: {
|
|
650
|
+
build: {
|
|
651
|
+
passed: results.build.passed.length,
|
|
652
|
+
failed: results.build.failed.length,
|
|
653
|
+
skipped: results.build.skipped.length,
|
|
654
|
+
},
|
|
655
|
+
lint: {
|
|
656
|
+
passed: results.lint.passed.length,
|
|
657
|
+
failed: results.lint.failed.length,
|
|
658
|
+
skipped: results.lint.skipped.length,
|
|
659
|
+
},
|
|
660
|
+
typeCheck: {
|
|
661
|
+
passed: results.typeCheck.passed.length,
|
|
662
|
+
failed: results.typeCheck.failed.length,
|
|
663
|
+
skipped: results.typeCheck.skipped.length,
|
|
664
|
+
},
|
|
665
|
+
test: {
|
|
666
|
+
passed: results.test.passed.length,
|
|
667
|
+
failed: results.test.failed.length,
|
|
668
|
+
skipped: results.test.skipped.length,
|
|
669
|
+
},
|
|
670
|
+
},
|
|
671
|
+
failures: {
|
|
672
|
+
build: results.build.failed,
|
|
673
|
+
lint: results.lint.failed,
|
|
674
|
+
typeCheck: results.typeCheck.failed,
|
|
675
|
+
test: results.test.failed,
|
|
676
|
+
},
|
|
677
|
+
errors: {
|
|
678
|
+
build: results.build.errors,
|
|
679
|
+
lint: results.lint.errors,
|
|
680
|
+
typeCheck: results.typeCheck.errors,
|
|
681
|
+
test: results.test.errors,
|
|
682
|
+
},
|
|
683
|
+
baseline: diff ? {
|
|
684
|
+
build: diff.build,
|
|
685
|
+
lint: diff.lint,
|
|
686
|
+
typeCheck: diff.typeCheck,
|
|
687
|
+
test: diff.test,
|
|
688
|
+
} : null,
|
|
689
|
+
byRepo: byRepo || null,
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
console.log(JSON.stringify(report, null, 2))
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// Print human-readable report
|
|
696
|
+
function printHumanReport(baselines, diff) {
|
|
697
|
+
header('๐ QA Summary Report')
|
|
698
|
+
|
|
699
|
+
const checks = [
|
|
700
|
+
{ name: 'Build', key: 'build', icon: '๐จ' },
|
|
701
|
+
{ name: 'Lint', key: 'lint', icon: '๐' },
|
|
702
|
+
{ name: 'Type Check', key: 'typeCheck', icon: '๐' },
|
|
703
|
+
{ name: 'Tests', key: 'test', icon: '๐งช' },
|
|
704
|
+
]
|
|
705
|
+
|
|
706
|
+
let totalPassed = 0
|
|
707
|
+
let totalFailed = 0
|
|
708
|
+
let totalSkipped = 0
|
|
709
|
+
let hasFailures = false
|
|
710
|
+
|
|
711
|
+
for (const check of checks) {
|
|
712
|
+
const result = results[check.key]
|
|
713
|
+
const passed = result.passed.length
|
|
714
|
+
const failed = result.failed.length
|
|
715
|
+
const skipped = result.skipped.length
|
|
716
|
+
const total = passed + failed + skipped
|
|
717
|
+
|
|
718
|
+
totalPassed += passed
|
|
719
|
+
totalFailed += failed
|
|
720
|
+
totalSkipped += skipped
|
|
721
|
+
|
|
722
|
+
if (failed > 0) {hasFailures = true}
|
|
723
|
+
|
|
724
|
+
const status = failed > 0 ? `${colors.red}โ` : `${colors.green}โ
`
|
|
725
|
+
const percentage = total > 0 ? Math.round((passed / (total - skipped)) * 100) : 100
|
|
726
|
+
|
|
727
|
+
log(`${status} ${check.icon} ${check.name.padEnd(12)} ${passed}/${total - skipped} passed (${percentage}%)${colors.reset}`)
|
|
728
|
+
|
|
729
|
+
// Show failed packages
|
|
730
|
+
if (failed > 0) {
|
|
731
|
+
log(` ${colors.red}Failed packages:${colors.reset}`, 'red')
|
|
732
|
+
for (const pkg of result.failed.slice(0, 10)) {
|
|
733
|
+
log(` - ${pkg}`, 'red')
|
|
734
|
+
}
|
|
735
|
+
if (failed > 10) {
|
|
736
|
+
log(` ... and ${failed - 10} more`, 'gray')
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// Show baseline diff if available
|
|
742
|
+
if (diff) {
|
|
743
|
+
log('')
|
|
744
|
+
log('๐ Baseline Comparison:', 'cyan')
|
|
745
|
+
|
|
746
|
+
const checkTypes = [
|
|
747
|
+
{ key: 'build', label: 'Build', icon: '๐จ' },
|
|
748
|
+
{ key: 'lint', label: 'Lint', icon: '๐' },
|
|
749
|
+
{ key: 'typeCheck', label: 'Type Check', icon: '๐' },
|
|
750
|
+
{ key: 'test', label: 'Tests', icon: '๐งช' },
|
|
751
|
+
]
|
|
752
|
+
|
|
753
|
+
for (const checkType of checkTypes) {
|
|
754
|
+
const checkDiff = diff[checkType.key]
|
|
755
|
+
if (!checkDiff) {continue}
|
|
756
|
+
|
|
757
|
+
log(`\n${checkType.icon} ${checkType.label}:`, 'cyan')
|
|
758
|
+
log(` Baseline: ${checkDiff.summary.baseline} failed`, 'gray')
|
|
759
|
+
log(` Current: ${checkDiff.summary.current} failed`, 'gray')
|
|
760
|
+
|
|
761
|
+
if (checkDiff.summary.delta > 0) {
|
|
762
|
+
log(` โ +${checkDiff.summary.delta} NEW failures (regression!)`, 'red')
|
|
763
|
+
} else if (checkDiff.summary.delta < 0) {
|
|
764
|
+
log(` โ
${Math.abs(checkDiff.summary.delta)} failures FIXED (improvement!)`, 'green')
|
|
765
|
+
} else {
|
|
766
|
+
log(` โก๏ธ No change`, 'yellow')
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
if (checkDiff.newFailures.length > 0) {
|
|
770
|
+
log(` ๐ New failures (${checkDiff.newFailures.length}):`, 'red')
|
|
771
|
+
for (const pkg of checkDiff.newFailures.slice(0, 3)) {
|
|
772
|
+
log(` - ${pkg}`, 'red')
|
|
773
|
+
}
|
|
774
|
+
if (checkDiff.newFailures.length > 3) {
|
|
775
|
+
log(` ... and ${checkDiff.newFailures.length - 3} more`, 'gray')
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
if (checkDiff.fixed.length > 0) {
|
|
780
|
+
log(` โ
Fixed (${checkDiff.fixed.length}):`, 'green')
|
|
781
|
+
for (const pkg of checkDiff.fixed.slice(0, 3)) {
|
|
782
|
+
log(` - ${pkg}`, 'green')
|
|
783
|
+
}
|
|
784
|
+
if (checkDiff.fixed.length > 3) {
|
|
785
|
+
log(` ... and ${checkDiff.fixed.length - 3} more`, 'gray')
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
log('')
|
|
792
|
+
log('โ'.repeat(60), 'gray')
|
|
793
|
+
log(`Total: ${totalPassed} passed, ${totalFailed} failed, ${totalSkipped} skipped`, 'bright')
|
|
794
|
+
log('')
|
|
795
|
+
|
|
796
|
+
if (hasFailures) {
|
|
797
|
+
log('โ QA checks failed. See details above.', 'red')
|
|
798
|
+
process.exit(1)
|
|
799
|
+
} else {
|
|
800
|
+
log('โ
All QA checks passed!', 'green')
|
|
801
|
+
process.exit(0)
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// Main
|
|
806
|
+
async function main() {
|
|
807
|
+
if (!jsonMode) {
|
|
808
|
+
let header = `\n${colors.bright}${colors.blue}๐ KB Labs QA Runner${colors.reset}`
|
|
809
|
+
|
|
810
|
+
// Show filter info
|
|
811
|
+
if (packageFilter) {
|
|
812
|
+
header += `\n${colors.yellow}๐ฆ Filtered: --package=${packageFilter}${colors.reset}`
|
|
813
|
+
} else if (repoFilter) {
|
|
814
|
+
header += `\n${colors.yellow}๐ Filtered: --repo=${repoFilter}${colors.reset}`
|
|
815
|
+
} else if (scopeFilter) {
|
|
816
|
+
header += `\n${colors.yellow}๐ Filtered: --scope=${scopeFilter}${colors.reset}`
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
if (noCache) {
|
|
820
|
+
header += `\n${colors.yellow}๐ซ Cache disabled (--no-cache)${colors.reset}`
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
log(header + '\n')
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
try {
|
|
827
|
+
// NEW: Load cache at the start
|
|
828
|
+
const cache = loadCache()
|
|
829
|
+
|
|
830
|
+
// 1. Build (in correct order)
|
|
831
|
+
if (!skipBuild) {
|
|
832
|
+
await buildAllLayers()
|
|
833
|
+
} else {
|
|
834
|
+
log('โญ๏ธ Skipping build (--skip-build)', 'yellow')
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// 2. Lint
|
|
838
|
+
if (!skipLint) {
|
|
839
|
+
await runOnAllPackages('run lint', '๐ Running linter', 'lint', cache)
|
|
840
|
+
} else {
|
|
841
|
+
log('โญ๏ธ Skipping lint (--skip-lint)', 'yellow')
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// 3. Type check
|
|
845
|
+
if (!skipTypes) {
|
|
846
|
+
await runOnAllPackages('run type-check', '๐ Running type check', 'typeCheck', cache)
|
|
847
|
+
} else {
|
|
848
|
+
log('โญ๏ธ Skipping type-check (--skip-types)', 'yellow')
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// 4. Tests
|
|
852
|
+
if (!skipTests) {
|
|
853
|
+
await runOnAllPackages('run test', '๐งช Running tests', 'test', cache)
|
|
854
|
+
} else {
|
|
855
|
+
log('โญ๏ธ Skipping tests (--skip-tests)', 'yellow')
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// NEW: Save cache at the end
|
|
859
|
+
if (!noCache) {
|
|
860
|
+
saveCache(cache)
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// Save last run results so core:gate can read fresh data without re-running
|
|
864
|
+
try {
|
|
865
|
+
const lastRunPath = join(CACHE_DIR, 'last-run.json')
|
|
866
|
+
writeFileSync(lastRunPath, JSON.stringify({
|
|
867
|
+
timestamp: new Date().toISOString(),
|
|
868
|
+
git: (() => {
|
|
869
|
+
try {
|
|
870
|
+
const commit = execSync('git rev-parse HEAD', { encoding: 'utf-8', stdio: ['pipe','pipe','ignore'] }).trim()
|
|
871
|
+
return { commit }
|
|
872
|
+
} catch { return {} }
|
|
873
|
+
})(),
|
|
874
|
+
failedPackages: {
|
|
875
|
+
build: results.build.failed,
|
|
876
|
+
lint: results.lint.failed,
|
|
877
|
+
typeCheck: results.typeCheck.failed,
|
|
878
|
+
test: results.test.failed,
|
|
879
|
+
}
|
|
880
|
+
}, null, 2))
|
|
881
|
+
} catch { /* non-critical */ }
|
|
882
|
+
|
|
883
|
+
// Load baselines and calculate diff
|
|
884
|
+
const baselines = loadBaselines()
|
|
885
|
+
const diff = calculateDiff(baselines)
|
|
886
|
+
|
|
887
|
+
// Print report
|
|
888
|
+
if (jsonMode) {
|
|
889
|
+
printJsonReport(baselines, diff)
|
|
890
|
+
} else {
|
|
891
|
+
printHumanReport(baselines, diff)
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// Auto-save QA results to history
|
|
895
|
+
if (!jsonMode) {
|
|
896
|
+
log('\n๐พ Saving QA results to history...', 'cyan')
|
|
897
|
+
try {
|
|
898
|
+
execSync('pnpm qa:save', { stdio: 'inherit' })
|
|
899
|
+
log('โ
Results saved successfully\n', 'green')
|
|
900
|
+
} catch (err) {
|
|
901
|
+
log('โ ๏ธ Failed to save results (this is non-critical)', 'yellow')
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
} catch (err) {
|
|
906
|
+
if (jsonMode) {
|
|
907
|
+
console.log(JSON.stringify({ status: 'error', message: err.message }, null, 2))
|
|
908
|
+
} else {
|
|
909
|
+
log(`\nโ QA runner failed: ${err.message}`, 'red')
|
|
910
|
+
}
|
|
911
|
+
process.exit(1)
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
main()
|