diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml new file mode 100644 index 00000000..0e330045 --- /dev/null +++ b/.github/workflows/typecheck.yml @@ -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 + + 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 + + - 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 + + - 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\`\`\`` + }); diff --git a/application/package.json b/application/package.json index 853a4d3c..b6b07217 100644 --- a/application/package.json +++ b/application/package.json @@ -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": { diff --git a/application/src/electron/handlers/handler.ddl.ts b/application/src/electron/handlers/handler.ddl.ts index df16d4af..3c8bead6 100644 --- a/application/src/electron/handlers/handler.ddl.ts +++ b/application/src/electron/handlers/handler.ddl.ts @@ -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'; diff --git a/application/src/electron/handlers/helpers.app/library.ts b/application/src/electron/handlers/helpers.app/library.ts index be49f880..48baaf7a 100644 --- a/application/src/electron/handlers/helpers.app/library.ts +++ b/application/src/electron/handlers/helpers.app/library.ts @@ -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 { diff --git a/application/src/electron/handlers/library-handlers.ts b/application/src/electron/handlers/library-handlers.ts index ccb1743d..4b1eaf61 100644 --- a/application/src/electron/handlers/library-handlers.ts +++ b/application/src/electron/handlers/library-handlers.ts @@ -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, diff --git a/application/src/electron/handlers/redistributable-handlers.ts b/application/src/electron/handlers/redistributable-handlers.ts index ff2db4bc..1b0158e3 100644 --- a/application/src/electron/handlers/redistributable-handlers.ts +++ b/application/src/electron/handlers/redistributable-handlers.ts @@ -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'; diff --git a/application/src/electron/manager/manager.addon.ts b/application/src/electron/manager/manager.addon.ts index 616d3554..bbde36fe 100644 --- a/application/src/electron/manager/manager.addon.ts +++ b/application/src/electron/manager/manager.addon.ts @@ -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({ diff --git a/application/src/electron/server/AddonConnection.ts b/application/src/electron/server/AddonConnection.ts index e70cef89..ebd8f837 100644 --- a/application/src/electron/server/AddonConnection.ts +++ b/application/src/electron/server/AddonConnection.ts @@ -1,5 +1,5 @@ import wsLib from 'ws'; -import { +import type { ClientSentEventTypes, OGIAddonConfiguration, OGIAddonEvent, @@ -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; + public configTemplate: ConfigurationFile | undefined; public filePath: string | undefined; public addonLink: string | undefined; public eventsAvailable: OGIAddonEvent[] = []; - constructor(ws: wsLib.WebSocket) { + constructor(ws: InstanceType) { this.ws = ws; } @@ -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': { @@ -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) @@ -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', @@ -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' ); @@ -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; } @@ -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' ); @@ -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', @@ -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 @@ -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[] = []; @@ -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[]; } @@ -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() ); diff --git a/application/src/electron/server/DeferrableTask.ts b/application/src/electron/server/DeferrableTask.ts index b6111268..c651db6b 100644 --- a/application/src/electron/server/DeferrableTask.ts +++ b/application/src/electron/server/DeferrableTask.ts @@ -16,7 +16,7 @@ export class DeferrableTask { private task: () => Promise; 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[] = []; @@ -35,7 +35,7 @@ export class DeferrableTask { 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; } diff --git a/application/src/electron/server/addon-server.ts b/application/src/electron/server/addon-server.ts index be55ed80..37d2d15b 100644 --- a/application/src/electron/server/addon-server.ts +++ b/application/src/electron/server/addon-server.ts @@ -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 }); @@ -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) => { diff --git a/application/src/electron/server/api/addons.ts b/application/src/electron/server/api/addons.ts index 8ec6c664..b6297d5b 100644 --- a/application/src/electron/server/api/addons.ts +++ b/application/src/electron/server/api/addons.ts @@ -1,6 +1,9 @@ import { z } from 'zod'; import { clients } from '../addon-server.js'; import { DeferrableTask } from '../DeferrableTask.js'; + +// Invariant: clients only contains connections with addonInfo set (added in AddonConnection.authenticate). +// We still guard below for robustness in case the map is accessed before auth or after disconnect. import sanitize from 'sanitize-html'; import { type Procedure, @@ -13,7 +16,8 @@ import * as fs from 'fs/promises'; import { join } from 'path'; import { restartAddonServer } from '../../handlers/handler.addon.js'; import { __dirname } from '../../manager/manager.paths.js'; -import { StoreData, ZodLibraryInfo } from 'ogi-addon'; +import type { StoreData } from 'ogi-addon'; +import { ZodLibraryInfo } from 'ogi-addon'; const procedures: Record> = { // Get all addon info @@ -22,10 +26,12 @@ const procedures: Record> = { .handler(async () => { let info = []; for (const client of clients.values()) { - info.push({ - ...client.addonInfo, - configTemplate: client.configTemplate, - }); + if (client.addonInfo) { + info.push({ + ...client.addonInfo, + configTemplate: client.configTemplate, + }); + } } return new ProcedureJSON(200, info); }), @@ -41,6 +47,7 @@ const procedures: Record> = { .handler(async (input) => { const client = clients.get(input.addonID); if (!client) return new ProcedureError(404, 'Client not found'); + if (!client.addonInfo) return new ProcedureError(400, 'Client has no addon info'); const response = await client.sendEventMessage({ event: 'config-update', @@ -71,6 +78,7 @@ const procedures: Record> = { .handler(async (input) => { const client = clients.get(input.addonID); if (!client) return new ProcedureError(404, 'Client not found'); + if (!client.addonInfo) return new ProcedureError(400, 'Client has no addon info'); if (!client.eventsAvailable.includes('search')) { return new ProcedureError(400, 'Client does not support search'); } @@ -103,6 +111,7 @@ const procedures: Record> = { .handler(async (input) => { const client = clients.get(input.addonID); if (!client) return new ProcedureError(404, 'Client not found'); + if (!client.addonInfo) return new ProcedureError(400, 'Client has no addon info'); if (!client.eventsAvailable.includes('library-search')) { return new ProcedureError( @@ -134,6 +143,7 @@ const procedures: Record> = { .handler(async (input) => { const client = clients.get(input.addonID); if (!client) return new ProcedureError(404, 'Client not found'); + if (!client.addonInfo) return new ProcedureError(400, 'Client has no addon info'); if (!client.eventsAvailable.includes('request-dl')) { return new ProcedureError(400, 'Client does not support request-dl'); @@ -160,6 +170,7 @@ const procedures: Record> = { .handler(async (input) => { const client = clients.get(input.addonID); if (!client) return new ProcedureError(404, 'Client not found'); + if (!client.addonInfo) return new ProcedureError(400, 'Client has no addon info'); if (!client.eventsAvailable.includes('catalog')) { return new ProcedureError(400, 'Client does not support catalog'); @@ -207,6 +218,7 @@ const procedures: Record> = { console.error('Client not found'); return new ProcedureError(404, 'Client not found'); } + if (!client.addonInfo) return new ProcedureError(400, 'Client has no addon info'); if (!client.eventsAvailable.includes('setup')) { console.error('Client does not support setup'); @@ -247,7 +259,7 @@ const procedures: Record> = { .handler(async (input) => { const clientsWithStorefront = Array.from(clients.values()).filter( (client) => - client.addonInfo.storefronts.includes(input.storefront) && + client.addonInfo?.storefronts.includes(input.storefront) && client.eventsAvailable.includes('game-details') ); if (clientsWithStorefront.length === 0) @@ -308,6 +320,7 @@ const procedures: Record> = { .handler(async (input) => { const client = clients.get(input.addonID); if (!client) return new ProcedureError(404, 'Client not found'); + if (!client.addonInfo) return new ProcedureError(400, 'Client has no addon info'); if (!client.addonLink || client.addonLink.startsWith('local:')) { return new ProcedureError( 400, @@ -378,6 +391,7 @@ const procedures: Record> = { .handler(async (input) => { const client = clients.get(input.addonID); if (!client) return new ProcedureError(404, 'Client not found'); + if (!client.addonInfo) return new ProcedureError(400, 'Client has no addon info'); const deferrableTask = new DeferrableTask(async () => { const data = await client.sendEventMessage({ @@ -408,7 +422,7 @@ const procedures: Record> = { .handler(async (input) => { const clientsWithStorefront = Array.from(clients.values()).filter( (client) => - client.addonInfo.storefronts.includes(input.storefront) && + client.addonInfo?.storefronts.includes(input.storefront) && client.eventsAvailable.includes('check-for-updates') ); if (clientsWithStorefront.length === 0) @@ -425,6 +439,7 @@ const procedures: Record> = { } const client = clientsWithStorefront[0]; + if (!client.addonInfo) return new ProcedureError(400, 'Client has no addon info'); const deferrableTask = new DeferrableTask(async () => { const data = await client.sendEventMessage({ event: 'check-for-updates', diff --git a/application/src/electron/startup.ts b/application/src/electron/startup.ts index 3b3133ae..851c1b25 100644 --- a/application/src/electron/startup.ts +++ b/application/src/electron/startup.ts @@ -11,7 +11,7 @@ import { mkdirSync, rmSync, } from 'original-fs'; -import { LibraryInfo } from 'ogi-addon'; +import type { LibraryInfo } from 'ogi-addon'; import { app, BrowserWindow } from 'electron'; import { sendNotification } from './main.js'; import semver from 'semver'; diff --git a/application/src/electron/tsconfig.json b/application/src/electron/tsconfig.json index 138eea6a..951b6a58 100644 --- a/application/src/electron/tsconfig.json +++ b/application/src/electron/tsconfig.json @@ -2,14 +2,19 @@ "compileOnSave": true, "compilerOptions": { "outDir": "../../build", + "baseUrl": ".", "typeRoots": ["node_modules/@types"], - "target": "ESNext", + "target": "ES2022", "allowJs": true, "module": "ESNext", "sourceMap": true, "stripInternal": true, - "lib": ["ESNext", "dom"], - "moduleResolution": "Bundler", + "lib": ["ES2022", "dom"], + "moduleResolution": "bundler", + "paths": { + "ogi-addon/config": ["../../node_modules/ogi-addon/build/config/Configuration.d.mts"], + "ogi-addon": ["../../node_modules/ogi-addon/build/main.d.mts"] + }, "noImplicitAny": true, "strictNullChecks": true, "strictFunctionTypes": true, diff --git a/application/src/frontend/global.d.ts b/application/src/frontend/global.d.ts index 2b06c3a3..0b1c7e16 100644 --- a/application/src/frontend/global.d.ts +++ b/application/src/frontend/global.d.ts @@ -3,6 +3,7 @@ type AxiosResponse = import('axios').AxiosResponse; type AxiosRequestConfig = import('axios').AxiosRequestConfig; type LibraryInfo = import('ogi-addon').LibraryInfo; +type ConfigurationFile = Record; type $AddTorrentOrMagnet = import('real-debrid-js').$AddTorrentOrMagnet; type $Hosts = import('real-debrid-js').$Hosts; type $UnrestrictLink = import('real-debrid-js').$UnrestrictLink; diff --git a/application/src/frontend/lib/downloads/services/RequestService.ts b/application/src/frontend/lib/downloads/services/RequestService.ts index d2765c82..35ced801 100644 --- a/application/src/frontend/lib/downloads/services/RequestService.ts +++ b/application/src/frontend/lib/downloads/services/RequestService.ts @@ -77,6 +77,7 @@ export class RequestService extends BaseService { const updatedResult = { ...response, addonSource: result.addonSource, + addonName: result.addonName, capsuleImage: result.capsuleImage, coverImage: result.coverImage, storefront: result.storefront, diff --git a/application/src/frontend/managers/Debug.svelte b/application/src/frontend/managers/Debug.svelte index 98b14e36..378cf242 100644 --- a/application/src/frontend/managers/Debug.svelte +++ b/application/src/frontend/managers/Debug.svelte @@ -8,7 +8,10 @@ import ButtonModal from '../components/modal/ButtonModal.svelte'; import { createNotification, notificationHistory } from '../store'; import CheckboxModal from '../components/modal/CheckboxModal.svelte'; - import type { ConfigurationFile } from 'ogi-addon/config'; + import { + ConfigurationBuilder, + type ConfigurationFile, + } from 'ogi-addon/config'; import HeaderModal from '../components/modal/HeaderModal.svelte'; let showDebugModal = $state(false); @@ -20,31 +23,33 @@ let showEventsPerSec = $state(false); let showNotificationSideView = $state(false); let showInsertAppModal = $state(false); + // Build test config with real StringOption instances (matches production shape) const optionConfig: { config: ConfigurationFile; id: string; name: string; description: string; } = { - config: { - // @ts-expect-error - options is a valid property for a string option if a choice. - t: { - name: 'test-options', - displayName: 'Test Options', - description: 'This is a test options modal', - defaultValue: '', - type: 'string', - }, - // @ts-expect-error - options is a valid property for a string option if a choice. - t: { - name: 'test-option-2', - displayName: 'Test Options', - description: 'This is a test options modal', - // @ts-expect-error - options is a valid property for a string option if a choice. - allowedValues: ['test-option-1', 'test-option-2', 'test-option-3'], - type: 'string', - }, - }, + config: new ConfigurationBuilder() + .addStringOption((option) => + option + .setName('test-options') + .setDisplayName('Test Options') + .setDescription('This is a test options modal') + .setDefaultValue('') + ) + .addStringOption((option) => + option + .setName('test-option-2') + .setDisplayName('Test Options') + .setDescription('This is a test options modal') + .setAllowedValues([ + 'test-option-1', + 'test-option-2', + 'test-option-3', + ]) + ) + .build(false), id: 'test-options', name: 'Test Options', description: 'This is a test options modal', diff --git a/application/src/frontend/managers/GamepadManager.ts b/application/src/frontend/managers/GamepadManager.ts index 01d6bd61..22d98f33 100644 --- a/application/src/frontend/managers/GamepadManager.ts +++ b/application/src/frontend/managers/GamepadManager.ts @@ -539,20 +539,6 @@ export class GamepadNavigator { // Focus the element first - Steam keyboard will inject text here element.focus(); - // Get input properties for the keyboard - const previousText = element.value || ''; - const title = - element.placeholder || - element.getAttribute('aria-label') || - element.name || - 'Enter text'; - const maxChars = - element instanceof HTMLInputElement - ? element.maxLength > 0 - ? element.maxLength - : 500 - : 2000; - try { // Try to open Steam keyboard overlay // The keyboard injects text directly into the focused input diff --git a/application/src/frontend/views/CommunityAddonsList.svelte b/application/src/frontend/views/CommunityAddonsList.svelte index b562c165..c9a98f45 100644 --- a/application/src/frontend/views/CommunityAddonsList.svelte +++ b/application/src/frontend/views/CommunityAddonsList.svelte @@ -226,16 +226,10 @@ @apply flex flex-col w-full h-full; } - .loading-container, - .error-container { + .loading-container { @apply flex items-center justify-center w-full h-full; } - .loading-text, - .error-text { - @apply text-lg text-gray-600; - } - .addon-grid { @apply flex flex-col w-full gap-4 py-6 px-0 overflow-y-auto; max-height: calc(100vh - 200px); diff --git a/application/src/frontend/views/FocusedAddonView.svelte b/application/src/frontend/views/FocusedAddonView.svelte index 5e48524a..e74ace5f 100644 --- a/application/src/frontend/views/FocusedAddonView.svelte +++ b/application/src/frontend/views/FocusedAddonView.svelte @@ -369,6 +369,7 @@ await runTask( { addonSource: selectedAddon.id, + addonName: selectedAddon.name, manifest: manifest, name: actionOption.displayName, downloadType: 'task' as const, diff --git a/application/tsconfig.addonserver.json b/application/tsconfig.addonserver.json index 4c83d808..e53620c3 100644 --- a/application/tsconfig.addonserver.json +++ b/application/tsconfig.addonserver.json @@ -2,12 +2,17 @@ "compileOnSave": true, "compilerOptions": { "outDir": "./build-addons", - "target": "ES2015", - "module": "commonjs", + "baseUrl": ".", + "target": "ES2022", + "module": "ESNext", "sourceMap": true, "stripInternal": true, - "lib": ["es2015", "esnext", "dom"], - "moduleResolution": "node", + "lib": ["es2022", "esnext", "dom"], + "moduleResolution": "bundler", + "paths": { + "ogi-addon/config": ["./node_modules/ogi-addon/build/config/Configuration.d.mts"], + "ogi-addon": ["./node_modules/ogi-addon/build/main.d.mts"] + }, "noImplicitAny": true, "strictNullChecks": true, "strictFunctionTypes": true, diff --git a/application/typings/ogi-addon.d.ts b/application/typings/ogi-addon.d.ts new file mode 100644 index 00000000..c9ee4683 --- /dev/null +++ b/application/typings/ogi-addon.d.ts @@ -0,0 +1,5 @@ +declare module 'ogi-addon/config' { + export interface ConfigurationFile { + [key: string]: any; + } +} diff --git a/package.json b/package.json index 6c88fc97..b607e334 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,14 @@ "private": true, "scripts": { "release": "cd packages/ogi-addon && bun run release && cd ../real-debrid && bun run release", - "dev": "cd application && bun run electron-dev" + "dev": "cd application && bun run electron-dev", + "typecheck": "bun run build:typecheck-deps && bun run typecheck:ogi-addon && bun run typecheck:real-debrid && bun run typecheck:application && bun run typecheck:web && bun run typecheck:test-addon", + "build:typecheck-deps": "cd packages/ogi-addon && bun run build && cd ../real-debrid && bun run build", + "typecheck:application": "cd application && bun run typecheck", + "typecheck:web": "cd web && bun run typecheck", + "typecheck:ogi-addon": "cd packages/ogi-addon && bun run typecheck", + "typecheck:real-debrid": "cd packages/real-debrid && bun run typecheck", + "typecheck:test-addon": "cd test-addon && bun run typecheck" }, "workspaces": [ "packages/*", diff --git a/packages/real-debrid/package.json b/packages/real-debrid/package.json index ad37c4da..0e624ec1 100644 --- a/packages/real-debrid/package.json +++ b/packages/real-debrid/package.json @@ -29,7 +29,8 @@ "auto-build": "tsc -w", "build": "tsdown --config tsdown.config.js", "test": "bun test ./tests/**/*.test.ts", - "release": "bun run build && npm publish" + "release": "bun run build && npm publish", + "typecheck": "tsc --noEmit" }, "devDependencies": { "@types/node": "^20.14.12", diff --git a/packages/real-debrid/tsconfig.json b/packages/real-debrid/tsconfig.json index 6191a813..c62fef6c 100644 --- a/packages/real-debrid/tsconfig.json +++ b/packages/real-debrid/tsconfig.json @@ -2,6 +2,7 @@ "compilerOptions": { // project options "lib": ["ESNext"], // specifies which default set of type definitions to use ("DOM", "ES6", etc) + "types": ["node"], "removeComments": true, // Strips all comments from TypeScript files when converting into JavaScript- you rarely read compiled code so this saves space "target": "ESNext", // Target environment. Most modern browsers support ES6, but you may want to set it to newer or older. (defaults to ES3) "module": "ESNext", diff --git a/test-addon/package.json b/test-addon/package.json index 8271e87c..99bea3f7 100644 --- a/test-addon/package.json +++ b/test-addon/package.json @@ -2,6 +2,9 @@ "name": "test-addon", "module": "main.ts", "type": "module", + "scripts": { + "typecheck": "tsc --noEmit" + }, "dependencies": { "ogi-addon": "workspace:*" }, diff --git a/web/package.json b/web/package.json index afbc8d1b..cfda7eff 100644 --- a/web/package.json +++ b/web/package.json @@ -7,7 +7,8 @@ "start": "astro dev", "build": "astro check && astro build", "preview": "astro preview", - "astro": "astro" + "astro": "astro", + "typecheck": "astro check" }, "dependencies": { "@astrojs/check": "^0.9.2",