Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
7afb094
ci: add project-wide typecheck workflow for main and PRs
nat-openclaw Feb 3, 2026
21e5ad7
Merge branch 'main' into feat/add-project-wide-type-check
Nat3z Feb 3, 2026
087bb26
Remove duplicate 'typecheck' script from package.json
Nat3z Feb 3, 2026
f4454f8
Fix formatting in package.json for release-beta script
Nat3z Feb 3, 2026
56a4b5f
fix(typecheck): resolve CI typecheck failures
nat-openclaw Feb 3, 2026
74615d6
fix(typecheck): fix addonName field mapping in RequestService
Feb 3, 2026
2f05adc
fix(typecheck): resolve Svelte type errors
nat-openclaw Feb 3, 2026
1c5c243
fix(typecheck): resolve Svelte type errors and improve tsconfig modul…
nat-openclaw Feb 3, 2026
40c21ba
fix(typecheck): resolve all TypeScript module resolution and top-leve…
nat-openclaw Feb 3, 2026
1a1b7d8
ci: add typecheck comment posting script and GitHub Actions workflow
nat-openclaw Feb 3, 2026
5e56d03
docs: add typecheck script documentation
nat-openclaw Feb 3, 2026
5bcdfde
Delete .github/workflows/typecheck.yml
Nat3z Feb 3, 2026
9d91eeb
Ci/typecheck comment (#58)
Nat3z Feb 3, 2026
1e6d847
Enable concurrency for typecheck workflow
Nat3z Feb 3, 2026
653b1df
Ci/typecheck comment fix (#59)
Nat3z Feb 3, 2026
1f08fc4
fix(ci): address CodeRabbit review - improve typecheck workflow and f…
Feb 4, 2026
cd5a47f
ci: combine typecheck and comment workflows
Feb 4, 2026
ae8e622
ci: fix typecheck reporting logic and match flex pattern
nat-openclaw Feb 4, 2026
784f6a6
ci: verify gpg signing with new key
nat-openclaw Feb 4, 2026
653dbff
ci: strip ansi codes from typecheck results
nat-openclaw Feb 4, 2026
17290c4
Delete TYPECHECK_README.md
Nat3z Feb 4, 2026
163be73
Delete post-typecheck-comment.sh
Nat3z Feb 4, 2026
4fc07f1
chore: improve typecheck workflow (main-only, pipefail, error regex, …
nat-openclaw Feb 4, 2026
bf31d04
Update typecheck.yml
nat-openclaw Feb 4, 2026
e2d2bc0
Delete scripts/typecheck-with-comment.sh
Nat3z Feb 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions .github/workflows/typecheck.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Unified typecheck workflow matching Flex pattern
name: Typecheck

on:
push:
branches: ["main"]
pull_request:
branches: ["**"]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
typecheck:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write

Comment thread
coderabbitai[bot] marked this conversation as resolved.
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: "1"

- name: Cache dependencies
uses: actions/cache@v4
with:
path: |
~/.bun/install/cache
node_modules
application/node_modules
packages/*/node_modules
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb', '**/bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-

- name: Install dependencies
run: bun install --frozen-lockfile

- name: Run typecheck
id: typecheck
run: |
set -o pipefail
bun run typecheck 2>&1 | tee typecheck-output.txt

Comment thread
coderabbitai[bot] marked this conversation as resolved.
- name: Extract errors
if: always()
id: errors
run: |
if [ -f typecheck-output.txt ] && grep -qE 'error TS|Error:' typecheck-output.txt; then
echo "has_errors=true" >> $GITHUB_OUTPUT
# Strip ANSI escape codes; capture lines with real errors (not e.g. "0 errors")
sed -r "s/\x1B\[([0-9]{1,3}(;[0-9]{1,2})?)?[mGK]//g" typecheck-output.txt | grep -E 'error TS|Error:' > errors.txt || true
else
echo "has_errors=false" >> $GITHUB_OUTPUT
fi
Comment thread
coderabbitai[bot] marked this conversation as resolved.

- name: Upload typecheck results
if: always()
uses: actions/upload-artifact@v4
with:
name: typecheck-results
path: errors.txt
if-no-files-found: ignore

- name: Post review on errors
if: steps.errors.outputs.has_errors == 'true' && github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const MAX_BODY = 60000;
let errors = '';
if (fs.existsSync('errors.txt')) {
errors = fs.readFileSync('errors.txt', 'utf8');
}
if (errors.length > MAX_BODY) {
errors = errors.slice(0, MAX_BODY) + '\n...truncated';
}
const pullNumber = context.eventName === 'workflow_run'
? (context.payload.workflow_run?.pull_requests?.length > 0
? context.payload.workflow_run.pull_requests[0].number
: null)
: context.issue.number;
if (pullNumber == null) {
console.log('Skipping review: no PR number available');
return;
}
await github.rest.pulls.createReview({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pullNumber,
event: 'REQUEST_CHANGES',
body: `❌ **Typecheck Failed**\n\nPlease fix the following type errors:\n\n\`\`\`\n${errors}\n\`\`\``
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
4 changes: 4 additions & 0 deletions application/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@
"electron-pack": "electron-builder",
"electron-pack:linux": "electron-builder -l",
"check": "svelte-check --tsconfig ./tsconfig.svelte.json",
"typecheck": "bun run typecheck:svelte && bun run typecheck:electron && bun run typecheck:addonserver",
"typecheck:svelte": "svelte-check --tsconfig ./tsconfig.svelte.json",
"typecheck:electron": "tsc -p tsconfig.electron.json --noEmit",
"typecheck:addonserver": "tsc -p tsconfig.addonserver.json --noEmit",
"rebuild": "npm rebuild"
},
"dependencies": {
Expand Down
2 changes: 1 addition & 1 deletion application/src/electron/handlers/handler.ddl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ipcMain, BrowserWindow } from 'electron';
import * as fs from 'fs';
import { rm as rmAsync } from 'fs/promises';
import { sendNotification } from '../main.js';
import axios, { AxiosError, AxiosResponse } from 'axios';
import axios, { AxiosError, type AxiosResponse } from 'axios';
import { dirname } from 'path';
import { DOWNLOAD_QUEUE } from '../manager/manager.queue.js';
import { Readable } from 'stream';
Expand Down
2 changes: 1 addition & 1 deletion application/src/electron/handlers/helpers.app/library.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/
import { join } from 'path';
import * as fs from 'fs';
import { LibraryInfo } from 'ogi-addon';
import type { LibraryInfo } from 'ogi-addon';
import { __dirname } from '../../manager/manager.paths.js';

export function getLibraryPath(appID: number): string {
Expand Down
2 changes: 1 addition & 1 deletion application/src/electron/handlers/library-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/
import { ipcMain } from 'electron';
import { exec } from 'child_process';
import { LibraryInfo } from 'ogi-addon';
import type { LibraryInfo } from 'ogi-addon';
import { isLinux } from './helpers.app/platform.js';
import {
getSteamAppIdWithFallback,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { spawn } from 'child_process';
import axios from 'axios';
import { join, dirname, basename } from 'path';
import * as fs from 'fs';
import { LibraryInfo } from 'ogi-addon';
import type { LibraryInfo } from 'ogi-addon';
import { isLinux, getProtonPrefixPath } from './helpers.app/platform.js';
import { getSteamAppIdWithFallback } from './helpers.app/steam.js';
import { loadLibraryInfo, saveLibraryInfo } from './helpers.app/library.js';
Expand Down
3 changes: 2 additions & 1 deletion application/src/electron/manager/manager.addon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,10 @@ export async function startAddon(
console.error(e);

// write to the run-crash.log file
const errorMessage = e instanceof Error ? e.message : String(e);
await writeFile(
join(addonPath, 'run-crash.log'),
stripAnsiCodes(e.message)
stripAnsiCodes(errorMessage)
);

sendNotification({
Expand Down
39 changes: 21 additions & 18 deletions application/src/electron/server/AddonConnection.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import wsLib from 'ws';
import {
import type {
ClientSentEventTypes,
OGIAddonConfiguration,
OGIAddonEvent,
Expand All @@ -20,13 +20,13 @@ import {
import { DeferrableTask, DeferredTasks } from './DeferrableTask.js';

export class AddonConnection {
public addonInfo: OGIAddonConfiguration;
public ws: wsLib.WebSocket;
public configTemplate: ConfigurationFile;
public addonInfo: OGIAddonConfiguration | undefined;
public ws: InstanceType<typeof wsLib>;
public configTemplate: ConfigurationFile | undefined;
public filePath: string | undefined;
public addonLink: string | undefined;
public eventsAvailable: OGIAddonEvent[] = [];
constructor(ws: wsLib.WebSocket) {
constructor(ws: InstanceType<typeof wsLib>) {
this.ws = ws;
}

Expand All @@ -38,7 +38,7 @@ export class AddonConnection {
resolve(false);
}, 1000);

this.ws.on('message', async (message) => {
this.ws.on('message', async (message: string | Buffer) => {
const data: WebsocketMessageClient = JSON.parse(message.toString());
switch (data.event) {
case 'notification': {
Expand All @@ -49,7 +49,8 @@ export class AddonConnection {
clearTimeout(authenticationTimeout);

// authentication
this.addonInfo = data.args;
const addonInfo = data.args as OGIAddonConfiguration;
this.addonInfo = addonInfo;
if (
isSecurityCheckEnabled &&
(!data.args.secret || data.args.secret !== addonSecret)
Expand All @@ -65,7 +66,7 @@ export class AddonConnection {
break;
}

// if (this.addonInfo.version !== ogiAddonVERSION) {
// if (addonInfo.version !== ogiAddonVERSION) {
// sendNotification({
// type: 'error',
// message: 'Client attempted to authenticate with an addon version that is not compatible with the OGI Addon Server',
Expand All @@ -76,7 +77,7 @@ export class AddonConnection {
// resolve(false)
// break;
// }
if (clients.has(this.addonInfo.id)) {
if (clients.has(addonInfo.id)) {
console.error(
'Client attempted to authenticate with an ID that is already in use'
);
Expand All @@ -88,7 +89,8 @@ export class AddonConnection {
break;
}
console.log('Client authenticated:', data.args.name);
sendIPCMessage('addon-connected', this.addonInfo.id);
clients.set(addonInfo.id, this);
sendIPCMessage('addon-connected', addonInfo.id);
resolve(true);
break;
}
Expand Down Expand Up @@ -140,7 +142,7 @@ export class AddonConnection {
);
return;
}
if (deferredTask.addonOwner !== this.addonInfo.id) {
if (deferredTask.addonOwner !== this.addonInfo!.id) {
console.error(
'Client attempted to send defer-update with an ID that does not belong to them'
);
Expand Down Expand Up @@ -253,7 +255,7 @@ export class AddonConnection {
if (!task) {
task = new DeferrableTask(async () => {
return null;
}, this.addonInfo.id);
}, this.addonInfo!.id);
DeferredTasks.getTasks()[data.args.id] = task;
// sendNotification({
// type: 'info',
Expand Down Expand Up @@ -305,7 +307,7 @@ export class AddonConnection {
// query all of the clients for the app details
const clientsWithStorefront = Array.from(clients.values()).filter(
(client) =>
client.addonInfo.storefronts.includes(storefront) &&
client.addonInfo?.storefronts.includes(storefront) &&
client.eventsAvailable.includes('game-details')
);
// find a storefront that gives app details that isn't undefined
Expand Down Expand Up @@ -363,7 +365,7 @@ export class AddonConnection {
}: ClientSentEventTypes['search-app-name'] = data.args;
const clientsWithStorefront = Array.from(clients.values()).filter(
(client) =>
client.addonInfo.storefronts.includes(storefront) &&
client.addonInfo?.storefronts.includes(storefront) &&
client.eventsAvailable.includes('library-search')
);
const searchResult: StoreData[] = [];
Expand Down Expand Up @@ -398,7 +400,7 @@ export class AddonConnection {
'Setting events-available to',
data.args.value,
'for addon',
this.addonInfo.id
this.addonInfo!.id
);
this.eventsAvailable = data.args.value as OGIAddonEvent[];
}
Expand All @@ -416,18 +418,19 @@ export class AddonConnection {
message.id = Math.random().toString(36).substring(7);
}
return new Promise((resolve, reject) => {
this.ws.send(JSON.stringify(message), (err) => {
this.ws.send(JSON.stringify(message), (err: Error | null | undefined) => {
if (err) {
reject(err);
}
});
if (expectResponse) {
const waitResponse = () => {
if (this.ws.readyState === wsLib.CLOSED) {
// CLOSED state is 3
if (this.ws.readyState === 3) {
reject('Websocket closed');
return;
}
this.ws.once('message', (messageRaw) => {
this.ws.once('message', (messageRaw: string | Buffer) => {
const messageFromClient: WebsocketMessageClient = JSON.parse(
'' + messageRaw.toString()
);
Expand Down
4 changes: 2 additions & 2 deletions application/src/electron/server/DeferrableTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class DeferrableTask<T> {
private task: () => Promise<any>;
public finished: boolean = false;

public data: T | null;
public data: T | null = null;
public id: string = Math.random().toString(36).substring(7);
public addonOwner = '';
public logs: string[] = [];
Expand All @@ -35,7 +35,7 @@ export class DeferrableTask<T> {
this.data = safeSerialize(result);
console.log('task finished', this.id);
} catch (error) {
this.failed = error;
this.failed = error instanceof Error ? error.message : String(error);
this.data = null;
this.finished = true;
}
Expand Down
10 changes: 5 additions & 5 deletions application/src/electron/server/addon-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import addonProcedures from './api/addons.js';
import deferProcedures from './api/defer.js';
import { AddonConnection } from './AddonConnection.js';
import { AddonServer } from './serve.js';
import { sendIPCMessage } from '../main.js';
const app = express();
const server = http.createServer(app);
const wss = new WebSocketServer({ server });
Expand All @@ -20,12 +19,13 @@ wss.on('connection', async (ws) => {
if (!connected) return;

ws.on('close', () => {
console.log('Client disconnected', connection.addonInfo.id);
clients.delete(connection.addonInfo.id);
console.log('Client disconnected', connection.addonInfo?.id);
if (connection.addonInfo) {
clients.delete(connection.addonInfo.id);
}
});

clients.set(connection.addonInfo.id, connection);
await sendIPCMessage('addon-connected', connection.addonInfo.id);
// Client is registered in AddonConnection.authenticate (clients.set + sendIPCMessage)
});

app.all('*', (_, res, next) => {
Expand Down
Loading