Merge pull request #737 from webadderallorg/chore/ci-quality-gates

ci: add pull-request quality checks
This commit is contained in:
PTMH-NMH
2026-07-11 02:53:53 +07:00
committed by GitHub
13 changed files with 135 additions and 69 deletions
-15
View File
@@ -1,15 +0,0 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react-hooks/recommended",
],
ignorePatterns: ["dist", ".eslintrc.cjs"],
parser: "@typescript-eslint/parser",
plugins: ["react-refresh"],
rules: {
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
},
};
+25
View File
@@ -0,0 +1,25 @@
* text=auto eol=lf
*.bat text eol=crlf
*.cmd text eol=crlf
*.dll binary
*.exe binary
*.gif binary
*.icns binary
*.ico binary
*.jpeg binary
*.jpg binary
*.mov binary
*.mp3 binary
*.mp4 binary
*.node binary
*.pdf binary
*.png binary
*.ttf binary
*.wasm binary
*.wav binary
*.webm binary
*.woff binary
*.woff2 binary
*.zip binary
+65
View File
@@ -0,0 +1,65 @@
name: Code Quality
on:
pull_request:
types: [opened, reopened, synchronize, ready_for_review]
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
quality:
name: Repository quality
runs-on: ubuntu-latest
timeout-minutes: 20
env:
CI: true
steps:
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '22'
cache: npm
cache-dependency-path: package-lock.json
- name: Install dependencies
id: install
run: npm ci --ignore-scripts
- name: Typecheck
if: ${{ !cancelled() && steps.install.outcome == 'success' }}
run: npx tsc --noEmit
- name: Lint
if: ${{ !cancelled() && steps.install.outcome == 'success' }}
run: npm run lint
- name: Check formatting (advisory)
if: ${{ !cancelled() && steps.install.outcome == 'success' }}
continue-on-error: true
run: npm run format:check
- name: Test
if: ${{ !cancelled() && steps.install.outcome == 'success' }}
run: npm test
# Main currently has known locale-parity debt covered by PR #710. Keep the
# check visible without blocking unrelated PRs; make it required once that
# existing translation PR (or an equivalent fix) lands.
- name: Check translations (advisory)
if: ${{ !cancelled() && steps.install.outcome == 'success' }}
continue-on-error: true
run: npm run i18n:check
+14 -34
View File
@@ -1,7 +1,18 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.13/schema.json",
"vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true },
"files": { "ignoreUnknown": false },
"files": {
"ignoreUnknown": false,
"includes": [
"**",
"!!**/dist",
"!!**/dist-electron",
"!!**/dist-ssr",
"!!**/release",
"!!**/.tmp",
"!!**/electron/native/**/build"
]
},
"formatter": {
"enabled": true,
"indentStyle": "tab",
@@ -92,42 +103,11 @@
"noWith": "error",
"useGetterReturn": "error"
}
},
"includes": ["**", "**/dist", "**/.eslintrc.cjs", "**", "**/dist", "**/.eslintrc.cjs"]
}
},
"javascript": { "formatter": { "quoteStyle": "double" } },
"css": { "parser": { "tailwindDirectives": true } },
"css": { "parser": { "cssModules": true, "tailwindDirectives": true } },
"overrides": [
{
"includes": ["*.ts", "*.tsx", "*.mts", "*.cts"],
"linter": {
"rules": {
"complexity": { "noArguments": "error" },
"correctness": {
"noConstAssign": "off",
"noGlobalObjectCalls": "off",
"noInvalidBuiltinInstantiation": "off",
"noInvalidConstructorSuper": "off",
"noSetterReturn": "off",
"noUndeclaredVariables": "off",
"noUnreachable": "off",
"noUnreachableSuper": "off"
},
"style": { "useConst": "error" },
"suspicious": {
"noDuplicateClassMembers": "off",
"noDuplicateObjectKeys": "off",
"noDuplicateParameters": "off",
"noFunctionAssign": "off",
"noImportAssign": "off",
"noRedeclare": "off",
"noUnsafeNegation": "off",
"noVar": "error",
"useGetterReturn": "off"
}
}
}
},
{
"includes": ["*.ts", "*.tsx", "*.mts", "*.cts"],
"linter": {
+8
View File
@@ -27,6 +27,14 @@ describe("Windows native helper path resolution", () => {
await fs.rm(tempRoot, { recursive: true, force: true });
});
it("resolves a requested platform tag independently of the host platform", async () => {
const { getNativeArchTag } = await import("./binaries");
expect(getNativeArchTag("win32")).toBe(
process.arch === "arm64" ? "win32-arm64" : "win32-x64",
);
});
it("prefers the branch-staged helper over a stale local CMake build in dev", async () => {
const buildOutputPath = path.join(
appPath,
+11 -8
View File
@@ -29,24 +29,27 @@ export function getNativeCaptureHelperSourcePath(): string {
return resolveUnpackedAppPath("electron", "native", "ScreenCaptureKitRecorder.swift");
}
export function getNativeArchTag(): string {
if (process.platform === "darwin") {
export function getNativeArchTag(platform: NodeJS.Platform = process.platform): string {
if (platform === "darwin") {
return process.arch === "arm64" ? "darwin-arm64" : "darwin-x64";
}
if (process.platform === "win32") {
if (platform === "win32") {
return process.arch === "arm64" ? "win32-arm64" : "win32-x64";
}
if (process.platform === "linux") {
if (platform === "linux") {
return process.arch === "arm64" ? "linux-arm64" : "linux-x64";
}
return `${process.platform}-${process.arch}`;
return `${platform}-${process.arch}`;
}
export function getPrebundledNativeHelperPath(binaryName: string): string {
return resolveUnpackedAppPath("electron", "native", "bin", getNativeArchTag(), binaryName);
export function getPrebundledNativeHelperPath(
binaryName: string,
archTag = getNativeArchTag(),
): string {
return resolveUnpackedAppPath("electron", "native", "bin", archTag, binaryName);
}
export function resolvePreferredWindowsNativeHelperPath(
@@ -61,7 +64,7 @@ export function resolvePreferredWindowsNativeHelperPath(
"Release",
binaryName,
);
const prebundledPath = getPrebundledNativeHelperPath(binaryName);
const prebundledPath = getPrebundledNativeHelperPath(binaryName, getNativeArchTag("win32"));
if (app.isPackaged && existsSync(prebundledPath)) {
return prebundledPath;
+1 -1
View File
@@ -62,7 +62,7 @@ export function registerAssetHandlers() {
await fs.writeFile(thumbPath, jpegData)
})
// Keep the queue moving even if one fails
thumbGenerationQueue = generation.catch(() => {})
thumbGenerationQueue = generation.catch(() => undefined)
await generation
return { success: true, data: jpegData! }
+3 -2
View File
@@ -18,9 +18,10 @@
"dev": "vite --config vite.config.ts",
"postinstall": "node scripts/postinstall.mjs",
"build": "npm run build:platform-native-helpers && tsc && vite build --config vite.config.ts && npm run normalize:electron-main-cjs && npm run smoke:electron-main-cjs && electron-builder",
"lint": "biome check .",
"lint:fix": "biome check --write .",
"lint": "biome lint .",
"lint:fix": "biome lint --write .",
"format": "biome format --write .",
"format:check": "biome format .",
"preview": "vite preview --config vite.config.ts",
"rebuild:native": "node ./node_modules/@electron/rebuild/lib/cli.js --force --only uiohook-napi",
"build:native-helpers": "node scripts/build-native-helpers.mjs",
+1 -1
View File
@@ -80,7 +80,7 @@ export const SourceSelectorContent = ({
windowSources = [],
selectedSource = "Screen",
loading = false,
onSourceSelect = () => {},
onSourceSelect = () => undefined,
}: Pick<SourceSelectorProps, "screenSources" | "windowSources" | "selectedSource" | "loading" | "onSourceSelect">) => {
const t = useScopedT("launch");
const renderSourceItem = (source: DesktopSource, index: number) => {
@@ -1,8 +1,8 @@
import { createContext, useContext } from "react";
import { createContext, type MouseEvent, useContext } from "react";
interface HudInteractionContextType {
onMouseEnter: () => void;
onMouseLeave: (event: any) => void;
onMouseLeave: (event: MouseEvent<HTMLDivElement>) => void;
}
export const HudInteractionContext = createContext<HudInteractionContextType | null>(null);
@@ -169,7 +169,6 @@ import {
import { clampFocusToStage as clampFocusToStageUtil } from "./videoPlayback/focusUtils";
import {
layoutVideoContent as layoutVideoContentUtil,
scalePreviewBorderRadius,
} from "./videoPlayback/layoutUtils";
import { updateOverlayIndicator } from "./videoPlayback/overlayUtils";
import { createVideoEventHandlers } from "./videoPlayback/videoEventHandlers";
@@ -6,7 +6,7 @@ type WaveformWorkerRequest = {
interface WorkerContext {
onmessage: (e: MessageEvent<WaveformWorkerRequest>) => void;
postMessage: (message: any, transfer?: Transferable[]) => void;
postMessage: (message: unknown, transfer?: Transferable[]) => void;
}
const workerScope = self as unknown as WorkerContext;
+4 -4
View File
@@ -67,7 +67,7 @@ describe("StreamingVideoDecoder local media loading", () => {
const decoder = new StreamingVideoDecoder();
await decoder.loadMetadata("http://127.0.0.1:43123/video?path=%2Ftmp%2Fcapture.mp4");
expect((window as any).electronAPI.readLocalFile).not.toHaveBeenCalled();
expect(window.electronAPI.readLocalFile).not.toHaveBeenCalled();
expect(mockDemuxerLoad).toHaveBeenCalledWith(
"http://127.0.0.1:43123/video?path=%2Ftmp%2Fcapture.mp4",
);
@@ -77,7 +77,7 @@ describe("StreamingVideoDecoder local media loading", () => {
const decoder = new StreamingVideoDecoder();
await decoder.loadMetadata("/tmp/capture.mp4");
expect((window as any).electronAPI.getLocalMediaUrl).toHaveBeenCalledWith(
expect(window.electronAPI.getLocalMediaUrl).toHaveBeenCalledWith(
"/tmp/capture.mp4",
);
expect(mockDemuxerLoad).toHaveBeenCalledWith(
@@ -90,7 +90,7 @@ describe("StreamingVideoDecoder local media loading", () => {
mockDemuxerLoad
.mockRejectedValueOnce(new Error("get_media_info failed: Failed after 3 attempts"))
.mockResolvedValueOnce(undefined);
(window as any).electronAPI.readLocalFile = vi.fn(async () => ({
window.electronAPI.readLocalFile = vi.fn(async () => ({
success: true,
data: new Uint8Array([1, 2, 3]),
}));
@@ -103,7 +103,7 @@ describe("StreamingVideoDecoder local media loading", () => {
"http://127.0.0.1:4321/video?path=%2Ftmp%2Ffallback.mp4",
);
expect(mockDemuxerLoad.mock.calls[1]?.[0]).toBeInstanceOf(File);
expect((window as any).electronAPI.readLocalFile).toHaveBeenCalledWith(
expect(window.electronAPI.readLocalFile).toHaveBeenCalledWith(
"/tmp/fallback.mp4",
);
});