Skip to content

Commit c5a4422

Browse files
surajssdclaude
andauthored
build(lint): add ESLint flat configs for both workspaces (kaito-project#320)
Signed-off-by: Suraj Deshmukh <suraj.deshmukh@microsoft.com> Co-authored-by: Claude Opus 4 <noreply@anthropic.com>
1 parent aa14a55 commit c5a4422

40 files changed

Lines changed: 474 additions & 261 deletions

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
run: bun ci
3333

3434
- name: Verify generated versions are up to date
35-
run: bun run verify-versions
35+
run: make verify-versions
3636

3737
- name: Build frontend
3838
run: bun run build:frontend

.github/workflows/test.yml

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,23 +29,54 @@ jobs:
2929
run: make compile
3030

3131
- name: Run backend tests with coverage
32+
shell: bash
3233
run: |
33-
cd backend
34-
echo "## 🧪 Backend Test Coverage" >> $GITHUB_STEP_SUMMARY
35-
echo "" >> $GITHUB_STEP_SUMMARY
36-
echo '```' >> $GITHUB_STEP_SUMMARY
37-
bun test --coverage 2>&1 | tail -20 >> $GITHUB_STEP_SUMMARY
38-
echo '```' >> $GITHUB_STEP_SUMMARY
39-
echo "" >> $GITHUB_STEP_SUMMARY
34+
set -o pipefail
35+
rc=0
36+
make test-coverage-backend 2>&1 | tee /tmp/backend-cov.log || rc=$?
37+
{
38+
echo "## 🧪 Backend Test Coverage"
39+
echo ""
40+
echo '```'
41+
tail -20 /tmp/backend-cov.log
42+
echo '```'
43+
echo ""
44+
} >> "$GITHUB_STEP_SUMMARY"
45+
exit $rc
4046
4147
- name: Run frontend tests with coverage
48+
shell: bash
4249
run: |
43-
cd frontend
44-
echo "## 🎨 Frontend Test Coverage" >> $GITHUB_STEP_SUMMARY
45-
echo "" >> $GITHUB_STEP_SUMMARY
46-
echo '```' >> $GITHUB_STEP_SUMMARY
47-
bun run test:coverage 2>&1 | tail -30 >> $GITHUB_STEP_SUMMARY
48-
echo '```' >> $GITHUB_STEP_SUMMARY
50+
set -o pipefail
51+
rc=0
52+
make test-coverage-frontend 2>&1 | tee /tmp/frontend-cov.log || rc=$?
53+
{
54+
echo "## 🎨 Frontend Test Coverage"
55+
echo ""
56+
echo '```'
57+
tail -30 /tmp/frontend-cov.log
58+
echo '```'
59+
echo ""
60+
} >> "$GITHUB_STEP_SUMMARY"
61+
exit $rc
62+
63+
web-ui-lint:
64+
runs-on: ubuntu-latest
65+
66+
steps:
67+
- name: Checkout repository
68+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
69+
70+
- name: Setup Bun
71+
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
72+
with:
73+
bun-version: latest
74+
75+
- name: Install dependencies
76+
run: bun ci
77+
78+
- name: Lint (frontend + backend)
79+
run: make lint
4980

5081
controller-manifest-check:
5182
runs-on: ubuntu-latest

Makefile

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: install dev dev-frontend dev-backend build compile lint test clean help providers-test verify-versions test-verify-versions
1+
.PHONY: install dev dev-frontend dev-backend build compile lint test test-coverage test-coverage-backend test-coverage-frontend clean help providers-test verify-versions test-verify-versions
22
.PHONY: controller-build controller-docker-build controller-install controller-deploy controller-generate generate-deploy-manifests
33
.PHONY: model-downloader-docker-build setup-gateway cleanup-gateway
44

@@ -41,6 +41,7 @@ help:
4141
@echo " compile-windows Cross-compile for Windows (x64)"
4242
@echo " lint Run linters"
4343
@echo " test Run tests"
44+
@echo " test-coverage Run tests with coverage (frontend + backend)"
4445
@echo " clean Remove build artifacts and node_modules"
4546
@echo ""
4647
@echo "Controller Targets:"
@@ -125,6 +126,18 @@ lint:
125126
test: verify-versions
126127
bun run test
127128

129+
# Testing with coverage (CI entrypoint). Coverage prints to stdout;
130+
# GitHub step-summary formatting lives in the workflow, not here.
131+
# Uses `cd <ws> && bun run test:coverage` (not `bun run --filter`) so output
132+
# stays unprefixed and the coverage tables render cleanly in the CI summary.
133+
test-coverage: verify-versions test-coverage-backend test-coverage-frontend
134+
135+
test-coverage-backend:
136+
cd backend && bun run test:coverage
137+
138+
test-coverage-frontend:
139+
cd frontend && bun run test:coverage
140+
128141
# Clean build artifacts
129142
clean:
130143
rm -rf node_modules frontend/node_modules backend/node_modules shared/node_modules

backend/eslint.config.mjs

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,33 @@
1-
// Flat ESLint config (ESLint v9+). Replaces the old .eslintrc + `--ext` flow,
2-
// which ESLint v9 removed. TypeScript is linted via the typescript-eslint
3-
// plugin's `flat/recommended` preset, which bundles the parser, the plugin, and
4-
// a non-type-checked rule set (fast, low false-positive noise).
5-
import tseslint from '@typescript-eslint/eslint-plugin';
6-
import tsparser from '@typescript-eslint/parser';
1+
// ESLint flat config for the backend (Bun + Hono + TypeScript).
2+
//
3+
// ESLint 9+ no longer reads `.eslintrc.*`; this flat config is the replacement.
4+
// The package is CommonJS (no `"type": "module"`), so this file uses the `.mjs`
5+
// extension to be loaded as ESM.
6+
import tseslint from '@typescript-eslint/eslint-plugin'
77

88
export default [
9-
// Only application source is linted (mirrors the previous `eslint src` scope).
10-
{
11-
ignores: ['dist/**', 'node_modules/**', '*.config.*', 'coverage/**'],
12-
},
9+
// Build artifacts, compiled binaries, and generated output are never linted.
10+
{ ignores: ['dist/**', 'build/**', 'coverage/**'] },
1311

14-
// typescript-eslint's flat/recommended turns off core rules that clash with
15-
// TS (e.g. no-undef), wires up the parser, and enables the recommended rules.
12+
// typescript-eslint's recommended flat config wires up the TS parser, the
13+
// `@typescript-eslint` plugin, and a sensible rule set. It also turns off
14+
// core rules (e.g. `no-undef`) that TypeScript already enforces, so we don't
15+
// need the `globals` package for Node/Bun globals like `process` or `Bun`.
1616
...tseslint.configs['flat/recommended'],
1717

1818
{
19-
files: ['src/**/*.ts'],
20-
languageOptions: {
21-
parser: tsparser,
22-
ecmaVersion: 'latest',
23-
sourceType: 'module',
24-
},
19+
files: ['**/*.ts'],
2520
rules: {
26-
// Pre-existing debt: this codebase predates any ESLint config (the v8→v10
27-
// bump first introduced one), so `recommended` surfaces ~120 historical
28-
// violations of these two rules — none in newly-written code. Demote them
29-
// to warnings so lint is green and CI-usable today, while still surfacing
30-
// the backlog for incremental burndown. Promote back to "error" once the
31-
// existing hits are cleared.
32-
'@typescript-eslint/no-explicit-any': 'warn',
33-
'@typescript-eslint/no-unused-vars': 'warn',
21+
// Allow intentionally-unused identifiers when prefixed with `_`
22+
// (e.g. unused function params kept for signature/positional reasons).
23+
'@typescript-eslint/no-unused-vars': [
24+
'error',
25+
{
26+
argsIgnorePattern: '^_',
27+
varsIgnorePattern: '^_',
28+
caughtErrorsIgnorePattern: '^_',
29+
},
30+
],
3431
},
3532
},
36-
];
33+
]

backend/src/hono-app.test.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
1+
import { describe, test, expect, afterEach } from 'bun:test';
22
import app, { parseCorsOrigin } from './hono-app';
33
import { kubernetesService } from './services/kubernetes';
44
import { configService } from './services/config';
55
import { authService } from './services/auth';
6+
import { helmService } from './services/helm';
67
import { mockServiceMethod } from './test/helpers';
78
import { mockDeployment } from './test/fixtures';
89
import { HTTPException } from 'hono/http-exception';
@@ -1288,10 +1289,21 @@ describe('Hono Routes', () => {
12881289

12891290
describe('Installation Routes', () => {
12901291
test('GET /api/installation/helm/status returns helm status', async () => {
1291-
const res = await app.request('/api/installation/helm/status');
1292-
expect(res.status).toBe(200);
1293-
const data = await res.json();
1294-
expect(data.available).toBeDefined();
1292+
// Mock the helm CLI probe so the test never spawns the real `helm`
1293+
// binary, which hangs on CI runners where helm is absent. Mirrors the
1294+
// pattern used throughout installation.test.ts.
1295+
const restore = mockServiceMethod(helmService, 'checkHelmAvailable', async () => ({
1296+
available: true,
1297+
version: 'v3.14.0',
1298+
}));
1299+
try {
1300+
const res = await app.request('/api/installation/helm/status');
1301+
expect(res.status).toBe(200);
1302+
const data = await res.json();
1303+
expect(data.available).toBeDefined();
1304+
} finally {
1305+
restore();
1306+
}
12951307
});
12961308
});
12971309

backend/src/lib/kubeconfig.test.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
22
import * as k8s from '@kubernetes/client-node';
3-
import { kubeConfigToBunTls, BunTlsHttpLibrary, makeApiClient } from './kubeconfig';
3+
import type * as https from 'node:https';
4+
import { kubeConfigToBunTls, BunTlsHttpLibrary, makeApiClient, type BunTlsOptions } from './kubeconfig';
45

56
/**
67
* Regression guards for the Bun TLS shim (`kubeConfigToBunTls`,
@@ -47,8 +48,8 @@ function makeKubeConfig(opts: KcOpts = {}): k8s.KubeConfig {
4748
}
4849

4950
kc.loadFromOptions({
50-
clusters: [cluster as any],
51-
users: [user as any],
51+
clusters: [cluster as unknown as k8s.Cluster],
52+
users: [user as unknown as k8s.User],
5253
contexts: [{ name: 'test-ctx', cluster: 'test-cluster', user: 'test-user' }],
5354
currentContext: 'test-ctx',
5455
});
@@ -102,7 +103,7 @@ describe('kubeConfigToBunTls', () => {
102103

103104
describe('BunTlsHttpLibrary.send', () => {
104105
const realFetch = globalThis.fetch;
105-
let calls: Array<{ url: string; options: any }>;
106+
let calls: Array<{ url: string; options: RequestInit & { tls?: BunTlsOptions } }>;
106107

107108
beforeEach(() => {
108109
calls = [];
@@ -112,7 +113,7 @@ describe('BunTlsHttpLibrary.send', () => {
112113
});
113114

114115
function stubFetch(status = 200, body = '{"ok":true}') {
115-
globalThis.fetch = (async (url: any, options: any) => {
116+
globalThis.fetch = (async (url: RequestInfo | URL, options: RequestInit & { tls?: BunTlsOptions }) => {
116117
calls.push({ url: String(url), options });
117118
return new Response(body, { status, headers: { 'content-type': 'application/json' } });
118119
}) as typeof fetch;
@@ -168,7 +169,7 @@ describe('BunTlsHttpLibrary.send', () => {
168169
const kc = makeKubeConfig({ withClientCert: true });
169170
let applyCount = 0;
170171
const orig = kc.applyToHTTPSOptions.bind(kc);
171-
kc.applyToHTTPSOptions = (async (opts: any) => {
172+
kc.applyToHTTPSOptions = (async (opts: https.RequestOptions) => {
172173
applyCount += 1;
173174
return orig(opts);
174175
}) as typeof kc.applyToHTTPSOptions;

backend/src/lib/kubeconfig.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as k8s from '@kubernetes/client-node';
2+
import type * as https from 'node:https';
23
import logger from './logger';
34

45
/**
@@ -23,10 +24,19 @@ export function loadKubeConfig(): k8s.KubeConfig {
2324
if (process.env.AUTH_ENABLED?.toLowerCase() === 'true' || process.env.AUTH_ENABLED === '1') {
2425
const currentUser = kc.getCurrentUser();
2526
if (currentUser) {
26-
(currentUser as any).certData = undefined;
27-
(currentUser as any).certFile = undefined;
28-
(currentUser as any).keyData = undefined;
29-
(currentUser as any).keyFile = undefined;
27+
// The k8s User fields are declared `readonly`, but we must clear the
28+
// client certificates before any API client is created. Cast to a
29+
// writable view of just those fields instead of using `any`.
30+
const writableUser = currentUser as {
31+
certData?: string;
32+
certFile?: string;
33+
keyData?: string;
34+
keyFile?: string;
35+
};
36+
writableUser.certData = undefined;
37+
writableUser.certFile = undefined;
38+
writableUser.keyData = undefined;
39+
writableUser.keyFile = undefined;
3040
logger.debug('Stripped client certificates from kubeconfig (AUTH_ENABLED)');
3141
}
3242
}
@@ -92,7 +102,7 @@ export async function kubeConfigToBunTls(kc: k8s.KubeConfig): Promise<BunTlsOpti
92102
servername?: string;
93103
rejectUnauthorized?: boolean;
94104
} = {};
95-
await kc.applyToHTTPSOptions(httpsOptions as any);
105+
await kc.applyToHTTPSOptions(httpsOptions as https.RequestOptions);
96106

97107
const tls: BunTlsOptions = {};
98108
if (httpsOptions.ca) tls.ca = httpsOptions.ca;

backend/src/lib/logger.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,5 @@
11
import pino from 'pino';
22

3-
// Check if running in compiled binary mode
4-
const isCompiled = (): boolean => {
5-
try {
6-
return import.meta.dir?.includes('/$bunfs/') || process.env.BUN_SELF_EXECUTABLE !== undefined;
7-
} catch {
8-
return false;
9-
}
10-
};
11-
123
// Use simple JSON logging in production/compiled mode
134
// pino-pretty transport doesn't work in compiled binaries
145
export const logger = pino({

backend/src/routes/autoscaler.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
import { Hono } from 'hono';
22
import { autoscalerService } from '../services/autoscaler';
3-
import { kubernetesService } from '../services/kubernetes';
4-
import { zValidator } from '@hono/zod-validator';
5-
import { z } from 'zod';
63
import logger from '../lib/logger';
74

85
const autoscaler = new Hono()

backend/src/routes/costs.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,6 @@ export const costsRoutes = new Hono()
131131
);
132132

133133
if (result.success && result.price) {
134-
const totalGpus = gpuCount * replicas;
135134
const hourlyPrice = result.price.hourlyPrice * replicas; // Full VM cost per replica
136135
const monthlyPrice = hourlyPrice * 730;
137136

0 commit comments

Comments
 (0)