Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
31 changes: 29 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ on:
branches: [main]
pull_request:

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}

jobs:
build:
quality:
name: Lint, typecheck, test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -25,5 +30,27 @@ jobs:
- name: Typecheck
run: bun run compile

- name: Test
run: bun run test

build:
name: Build (${{ matrix.browser }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- browser: chrome
command: build
- browser: firefox
command: build:firefox
steps:
- uses: actions/checkout@v4

- uses: oven-sh/setup-bun@v2

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

- name: Build extension
run: bun run build
run: bun run ${{ matrix.command }}
6 changes: 6 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,15 @@ jobs:
- name: Install dependencies
run: bun install --frozen-lockfile

- name: Lint
run: bun run check

- name: Typecheck
run: bun run compile

- name: Test
run: bun run test

- name: Zip extension
run: bun run zip

Expand Down
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,15 @@ Know another tracker that works with the Steam profile URL trick? [Open a tracke

## Developers

Trackers live in `src/trackers/catalog.ts`. PRs welcome.
The source layout follows the features of the extension:

- `src/trackers/` — tracker catalog, URL building, and preferences. New trackers go in `src/trackers/catalog.ts`.
- `src/tracker-menu/` — the menu injected into Steam profile sidebars.
- `src/popup/` — the toolbar popup (React).
- `src/steam/` — Steam profile URL parsing and match patterns.
- `src/i18n/` — supported locales and runtime translations.
- `src/entrypoints/` — thin WXT wiring for the background, content script, and popup.

Common commands: `bun run dev` (live reload), `bun run test` (Vitest), `bun run check` (lint), `bun run compile` (typecheck).

PRs welcome.
14 changes: 1 addition & 13 deletions biome.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,5 @@
"defineBackground",
"defineContentScript"
]
},
"overrides": [
{
"includes": ["src/**"],
"linter": {
"rules": {
"style": {
"useFilenamingConvention": "off"
}
}
}
}
]
}
}
56 changes: 56 additions & 0 deletions bun.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
"json:sort": "bun run scripts/json-sort.ts",
"prepare": "husky",
"release": "bumpp",
"test": "vitest run",
"test:watch": "vitest",
"zip": "wxt zip",
"zip:firefox": "wxt zip -b firefox"
},
Expand All @@ -39,13 +41,15 @@
"@types/sharp": "^0.32.0",
"@wxt-dev/module-react": "^1.1.5",
"bumpp": "^11.1.0",
"happy-dom": "^20.10.2",
"husky": "^9.1.7",
"postcss-rem-to-responsive-pixel": "^7.0.4",
"sharp": "^0.34.5",
"sort-package-json": "^4.0.0",
"tailwindcss": "^4.3.0",
"typescript": "^5.9.3",
"ultracite": "7.8.2",
"vitest": "^4.1.8",
"wxt": "^0.20.26"
}
}
3 changes: 0 additions & 3 deletions public/_locales/de/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@
"extDescription": {
"message": "CS2-Statistik-Tracker von Steam-Profilen öffnen"
},
"popupSubtitle": {
"message": "Wähle, welche Statistik-Seiten auf Steam-Profilen erscheinen."
},
"openSource": {
"message": "Open Source"
},
Expand Down
3 changes: 0 additions & 3 deletions public/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@
"extDescription": {
"message": "Open CS2 stat trackers from Steam profiles"
},
"popupSubtitle": {
"message": "Choose which stat sites appear on Steam profiles."
},
"openSource": {
"message": "Open source"
},
Expand Down
3 changes: 0 additions & 3 deletions public/_locales/es/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@
"extDescription": {
"message": "Abre rastreadores de estadísticas de CS2 desde perfiles de Steam"
},
"popupSubtitle": {
"message": "Elige qué sitios de estadísticas aparecen en los perfiles de Steam."
},
"openSource": {
"message": "Código abierto"
},
Expand Down
3 changes: 0 additions & 3 deletions public/_locales/fr/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@
"extDescription": {
"message": "Ouvrir les trackers de stats CS2 depuis les profils Steam"
},
"popupSubtitle": {
"message": "Choisissez quels sites de stats apparaissent sur les profils Steam."
},
"openSource": {
"message": "Open source"
},
Expand Down
3 changes: 0 additions & 3 deletions public/_locales/pt_BR/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@
"extDescription": {
"message": "Abra rastreadores de estatísticas do CS2 a partir de perfis Steam"
},
"popupSubtitle": {
"message": "Escolha quais sites de estatísticas aparecem nos perfis Steam."
},
"openSource": {
"message": "Código aberto"
},
Expand Down
3 changes: 0 additions & 3 deletions public/_locales/ru/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@
"extDescription": {
"message": "Открывайте CS2-трекеры статистики со страниц профилей Steam"
},
"popupSubtitle": {
"message": "Выберите, какие сайты статистики показывать в профилях Steam."
},
"openSource": {
"message": "Открытый код"
},
Expand Down
3 changes: 0 additions & 3 deletions public/_locales/zh_CN/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@
"extDescription": {
"message": "从 Steam 个人资料打开 CS2 战绩追踪网站"
},
"popupSubtitle": {
"message": "选择在 Steam 个人资料中显示哪些战绩网站。"
},
"openSource": {
"message": "开源"
},
Expand Down
15 changes: 0 additions & 15 deletions public/wxt.svg

This file was deleted.

56 changes: 42 additions & 14 deletions scripts/check-locales.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,66 @@
#!/usr/bin/env bun
/** Verify every locale has the same keys and $n placeholders as the base. */
import { readdir, readFile } from "node:fs/promises";
import { join } from "node:path";

const LOCALES_DIR = "public/_locales";
const BASE_LOCALE = "en";
const SUBSTITUTION_RE = /\$\d+/g;

const baseMessages = JSON.parse(
await readFile(join(LOCALES_DIR, BASE_LOCALE, "messages.json"), "utf8")
) as Record<string, unknown>;
type Messages = Record<string, { message: string }>;

async function loadMessages(locale: string): Promise<Messages> {
const raw = await readFile(
join(LOCALES_DIR, locale, "messages.json"),
"utf8"
);
return JSON.parse(raw) as Messages;
}

function substitutionTokens(message: string): string {
return [...message.matchAll(SUBSTITUTION_RE)]
.map((match) => match[0])
.sort()
.join(",");
}

const baseMessages = await loadMessages(BASE_LOCALE);
const baseKeys = new Set(Object.keys(baseMessages));
const locales = await readdir(LOCALES_DIR);
const locales = (await readdir(LOCALES_DIR)).sort();

let failed = false;

for (const locale of locales.sort()) {
function fail(message: string): void {
console.error(message);
failed = true;
}

for (const locale of locales) {
if (locale === BASE_LOCALE) {
continue;
}

const messages = JSON.parse(
await readFile(join(LOCALES_DIR, locale, "messages.json"), "utf8")
) as Record<string, unknown>;

const messages = await loadMessages(locale);
const keys = new Set(Object.keys(messages));

for (const key of baseKeys) {
if (!keys.has(key)) {
console.error(`${locale}: missing key "${key}"`);
failed = true;
fail(`${locale}: missing key "${key}"`);
continue;
}

const expected = substitutionTokens(baseMessages[key].message);
const actual = substitutionTokens(messages[key].message);
if (expected !== actual) {
fail(
`${locale}: key "${key}" placeholders [${actual}] do not match ${BASE_LOCALE} [${expected}]`
);
}
}

for (const key of keys) {
if (!baseKeys.has(key)) {
console.error(`${locale}: extra key "${key}"`);
failed = true;
fail(`${locale}: extra key "${key}"`);
}
}
}
Expand All @@ -43,4 +69,6 @@ if (failed) {
process.exit(1);
}

console.log(`All ${locales.length - 1} locale files match ${BASE_LOCALE} keys`);
console.log(
`All ${locales.length - 1} locale files match ${BASE_LOCALE} keys and placeholders`
);
53 changes: 11 additions & 42 deletions scripts/json-sort.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,16 @@
#!/usr/bin/env bun
/**
* Sort all workspace package.json files.
* Discovers packages/* and apps/* dynamically.
* Usage: bun run scripts/json-sort.ts
*/
import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
/** Sort package.json with sort-package-json. */
import { readFileSync, writeFileSync } from "node:fs";
import sortPackageJson from "sort-package-json";
import { fromRoot } from "./lib/paths.ts";

const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const file = fromRoot("package.json");
const original = readFileSync(file, "utf8");
const sorted = sortPackageJson(original);

function packageJsonPaths(base: string): string[] {
const paths = [join(base, "package.json")];
for (const dir of ["packages", "apps"]) {
const dirPath = join(base, dir);
if (!existsSync(dirPath)) {
continue;
}
for (const entry of readdirSync(dirPath, { withFileTypes: true })) {
if (entry.isDirectory()) {
paths.push(join(dirPath, entry.name, "package.json"));
}
}
}
return paths;
if (sorted === original) {
console.log("package.json already sorted");
} else {
writeFileSync(file, sorted);
console.log("package.json sorted");
}

let exitCode = 0;
for (const file of packageJsonPaths(root)) {
try {
const original = readFileSync(file, "utf8");
const sorted = sortPackageJson(original);
if (sorted === original) {
console.log(`${file} was already sorted.`);
} else {
writeFileSync(file, sorted);
console.log(`${file} is sorted!`);
}
} catch (error) {
console.error(`Failed to sort ${file}:`, error);
exitCode = 1;
}
}

process.exit(exitCode);
1 change: 0 additions & 1 deletion src/assets/react.svg

This file was deleted.

13 changes: 11 additions & 2 deletions src/entrypoints/background.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import { handleExtensionInstalled } from "@/preferences/storage";
import {
normalizeStoredPreferences,
seedDefaultPreferences,
} from "@/trackers/preferences";

export default defineBackground(() => {
browser.runtime.onInstalled.addListener(({ reason }) => {
handleExtensionInstalled(reason);
if (reason === "install") {
seedDefaultPreferences();
return;
}
if (reason === "update") {
normalizeStoredPreferences();
}
});
});
4 changes: 2 additions & 2 deletions src/entrypoints/popup/main.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import { PopupApp } from "@/popup/app";
import "@/assets/tailwind.css";

document.documentElement.classList.add("bg-black");
Expand All @@ -13,6 +13,6 @@ if (!root) {

ReactDOM.createRoot(root).render(
<React.StrictMode>
<App />
<PopupApp />
</React.StrictMode>
);
Loading
Loading