Skip to content

Commit 01b4029

Browse files
authored
feat(webext): add browser extension (#37)
1 parent 09e8fe2 commit 01b4029

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+5053
-229
lines changed

.github/workflows/autofix.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ jobs:
3030
- name: 🔠 Fix lint errors
3131
run: pnpm lint --fix
3232

33-
# - name: 🧪 Update unit test snapshots
34-
# run: pnpm test:unit -u
33+
- name: 🧪 Update unit test snapshots
34+
run: pnpm test:unit -u
3535

3636
# - name: 🏃 Update component test snapshots
3737
# run: pnpm test:nuxt -u

.github/workflows/ci.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,16 @@ jobs:
4343
- name: 💪 Type check
4444
run: pnpm test:types
4545

46-
# - name: 🧪 Unit test
47-
# run: pnpm test:unit
46+
- name: 🧪 Unit test
47+
run: pnpm test:unit
4848

4949
# - name: 🏃 Component tests
5050
# run: pnpm test:nuxt
5151

5252
# browser:
5353
# runs-on: ubuntu-latest
5454
# container:
55-
# image: mcr.microsoft.com/playwright:v1.48.0-focal
55+
# image: mcr.microsoft.com/playwright:v1.49.1-noble
5656

5757
# steps:
5858
# - uses: actions/checkout@v4

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ node_modules
1212
# Logs
1313
logs
1414
*.log
15+
test-results
1516

1617
# Misc
1718
.DS_Store
@@ -22,3 +23,7 @@ logs
2223
.env
2324
.env.*
2425
!.env.example
26+
27+
# ES Lint
28+
.eslintcache
29+

README.md

+7
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,20 @@
1010

1111
- [👉  Check it out](https://unsight.dev/)
1212

13+
<!--
14+
TODO: add listing on app stores
15+
16+
A browser extension (work in progress) that enhances GitHub issue pages by adding a "Similar Issues" section to the issue header. This feature helps users discover related issues more easily.
17+
-->
18+
1319
## ✨ Features
1420

1521
- Built on [Nuxt](https://nuxt.com/)
1622
- [Nitro server API routes](https://nuxt.com/docs/guide/concepts/server-engine#server-engine)
1723
- [GitHub API](https://docs.github.com/en/rest) and a [GitHub App](https://docs.github.com/en/apps/creating-github-apps)
1824
- [UnoCSS](https://unocss.dev/)
1925
- Deployed on [Cloudflare](https://cloudflare.com/) with [NuxtHub](https://hub.nuxt.com/), using [Workers AI](https://developers.cloudflare.com/workers-ai/#_top), [Workers KV](https://developers.cloudflare.com/kv/#_top) and [Vectorize](http://developers.cloudflare.com/vectorize/)
26+
- Browser extension coming soon!
2027

2128
## 🛝 Try it out locally
2229

eslint.config.js

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import withNuxt from './packages/web/.nuxt/eslint.config.mjs'
44

55
export default withNuxt(antfu()).append(
66
{
7+
files: ['packages/web/**/*.ts'],
78
rules: {
89
'no-console': 'off',
910
},

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
"packageManager": "[email protected]",
66
"scripts": {
77
"dev": "pnpm --filter web dev",
8-
"lint": "eslint .",
8+
"lint": "eslint --cache .",
99
"test:types": "pnpm -r test:types",
10+
"test:unit": "pnpm -r test:unit",
1011
"postinstall": "simple-git-hooks"
1112
},
1213
"devDependencies": {

packages/webext/.env.example

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
VITE_UNSIGHT_DOT_DEV_BASE_URL="http://localhost:3000"

packages/webext/.gitignore

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# auto-generated by vite plugins
2+
src/auto-imports.d.ts
3+
src/components.d.ts
4+
5+
# Browser extension build artifacts
6+
extension
7+
!extension/icon.svg
8+
!extension/icon-512.png

packages/webext/LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2021 Anthony Fu
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

packages/webext/README.md

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# unsight.dev browser extension
2+
3+
A browser extension (work in progress) that enhances GitHub issue pages by adding a "Similar Issues" section to the issue header. This feature helps users discover related issues more easily.
4+
5+
## Installation
6+
7+
From the root of the monorepo, run:
8+
9+
```bash
10+
pnpm install
11+
```
12+
13+
### Development
14+
15+
To start the development server, run:
16+
17+
```bash
18+
pnpm -F webext dev
19+
pnpm -F web dev # to run the API locally
20+
```
21+
22+
or
23+
24+
```bash
25+
pnpm dev # to run both the API and the extension
26+
```
27+
28+
When building the project locally copy the .env.example file to .env.local and fill in the required values:
29+
30+
```bash
31+
VITE_UNSIGHT_DOT_DEV_BASE_URL="http://localhost:3000"
32+
```
33+
34+
Then **load extension in browser with the `extension/` folder**.
35+
36+
For Firefox developers, you can run the following command instead:
37+
38+
```bash
39+
pnpm -F web dev
40+
pnpm -F webext dev-firefox
41+
```
42+
43+
`web-ext` auto reload the extension when `extension/` files changed.
44+
45+
> While Vite handles HMR automatically in the most of the case, [Extensions Reloader](https://chrome.google.com/webstore/detail/fimgfedafeadlieiabdeeaodndnlbhid) is still recommended for cleaner hard reloading.
46+
47+
### Build
48+
49+
To build the extension, run
50+
51+
```bash
52+
pnpm -F webext build
53+
```
54+
55+
ensure you have a .env file for the production build or pass the values as environment variables.
56+
57+
And then pack files under `extension`, you can upload `extension.crx` or `extension.xpi` to appropriate extension store.
58+
59+
## Credits
60+
61+
Thanks to [Anthony Fu](https://github.com/antfu) for the web browser extension template [vitesse-webext](https://github.com/antfu-collective/vitesse-webext) that this project was originally built with.
2.28 KB
Loading
Loading

packages/webext/modules.d.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
declare module 'vue' {
2+
interface ComponentCustomProperties {
3+
$app: {
4+
context: string
5+
}
6+
}
7+
}
8+
9+
// https://stackoverflow.com/a/64189046/479957
10+
export {}

packages/webext/package.json

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
{
2+
"name": "webext",
3+
"displayName": "unsight.dev browser extension",
4+
"version": "0.0.1",
5+
"private": true,
6+
"description": "unsight.dev browser extension",
7+
"scripts": {
8+
"dev": "pnpm run clear && cross-env NODE_ENV=development run-p dev:*",
9+
"dev-firefox": "pnpm run clear && cross-env NODE_ENV=development EXTENSION=firefox run-p dev:*",
10+
"dev:prepare": "esno scripts/prepare.ts",
11+
"dev:background": "pnpm run build:background -- --mode development",
12+
"dev:web": "vite",
13+
"dev:js": "pnpm run build:js -- --mode development",
14+
"build": "cross-env NODE_ENV=production run-s clear build:web build:prepare build:background build:js",
15+
"build:prepare": "esno scripts/prepare.ts",
16+
"build:background": "vite build --config vite.config.background.mts",
17+
"build:web": "vite build",
18+
"build:js": "vite build --config vite.config.content.mts",
19+
"pack": "cross-env NODE_ENV=production run-p pack:*",
20+
"pack:zip": "rimraf extension.zip && jszip-cli add extension/* -o ./extension.zip",
21+
"pack:crx": "crx pack extension -o ./extension.crx",
22+
"pack:xpi": "cross-env WEB_EXT_ARTIFACTS_DIR=./ web-ext build --source-dir ./extension --filename extension.xpi --overwrite-dest",
23+
"start:chromium": "web-ext run --source-dir ./extension --target=chromium",
24+
"start:firefox": "web-ext run --source-dir ./extension --target=firefox-desktop",
25+
"clear": "rimraf --glob extension/dist extension/manifest.json extension.*",
26+
"test:unit": "vitest test",
27+
"test:e2e": "playwright test",
28+
"test:types": "pnpm build && tsc --noEmit"
29+
},
30+
"dependencies": {
31+
"@github/relative-time-element": "^4.4.4",
32+
"@vueuse/shared": "^12.0.0"
33+
},
34+
"devDependencies": {
35+
"@ffflorian/jszip-cli": "^3.8.2",
36+
"@iconify/json": "^2.2.287",
37+
"@playwright/test": "^1.49.1",
38+
"@types/node": "^22.10.2",
39+
"@types/webextension-polyfill": "^0.12.1",
40+
"@unocss/reset": "^0.65.2",
41+
"@vitejs/plugin-vue": "^5.2.1",
42+
"@vue/test-utils": "^2.4.6",
43+
"@vueuse/core": "^12.0.0",
44+
"chokidar": "^4.0.3",
45+
"cross-env": "^7.0.3",
46+
"crx": "^5.0.1",
47+
"esno": "^4.8.0",
48+
"jsdom": "^25.0.1",
49+
"kolorist": "^1.8.0",
50+
"npm-run-all": "^4.1.5",
51+
"rimraf": "^6.0.1",
52+
"typescript": "5.6.3",
53+
"unocss": "^0.65.2",
54+
"unplugin-auto-import": "^0.19.0",
55+
"unplugin-icons": "^0.22.0",
56+
"unplugin-vue-components": "^0.28.0",
57+
"vite": "^5.4.11",
58+
"vitest": "^2.1.8",
59+
"vue": "^3.5.13",
60+
"web-ext": "^8.3.0",
61+
"webext-bridge": "^6.0.1",
62+
"webextension-polyfill": "^0.12.0"
63+
}
64+
}

packages/webext/playwright.config.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* @see {@link https://playwright.dev/docs/chrome-extensions Chrome extensions | Playwright}
3+
*/
4+
import { defineConfig } from '@playwright/test'
5+
6+
export default defineConfig({
7+
testDir: './tests/e2e',
8+
retries: 2,
9+
webServer: {
10+
command: 'pnpm dev',
11+
// start e2e test after the Vite server is fully prepared
12+
url: 'http://localhost:3303/popup/main.ts',
13+
reuseExistingServer: true,
14+
},
15+
})

packages/webext/scripts/manifest.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import fs from 'node:fs/promises'
2+
import { getManifest } from '../src/manifest'
3+
import { log, r } from './utils'
4+
5+
export async function writeManifest() {
6+
await fs.writeFile(r('extension/manifest.json'), JSON.stringify(await getManifest(), null, 2))
7+
log('PRE', 'write manifest.json')
8+
}
9+
10+
writeManifest()

packages/webext/scripts/prepare.ts

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// generate stub index.html files for dev entry
2+
import { execSync } from 'node:child_process'
3+
import fs from 'node:fs/promises'
4+
import chokidar from 'chokidar'
5+
import { isDev, log, port, r } from './utils'
6+
7+
/**
8+
* Stub index.html to use Vite in development
9+
*/
10+
async function stubIndexHtml() {
11+
const views = ['options', 'popup', 'sidepanel']
12+
13+
for (const view of views) {
14+
await fs.mkdir(r(`extension/dist/${view}`), { recursive: true })
15+
let data = await fs.readFile(r(`src/${view}/index.html`), 'utf-8')
16+
data = data
17+
.replace('"./main.ts"', `"http://localhost:${port}/${view}/main.ts"`)
18+
.replace('<div id="app"></div>', '<div id="app">Vite server did not start</div>')
19+
await fs.writeFile(r(`extension/dist/${view}/index.html`), data, 'utf-8')
20+
log('PRE', `stub ${view}`)
21+
}
22+
}
23+
24+
function writeManifest() {
25+
execSync('npx esno ./scripts/manifest.ts', { stdio: 'inherit' })
26+
}
27+
28+
writeManifest()
29+
30+
if (isDev) {
31+
stubIndexHtml()
32+
chokidar.watch(r('src/**/*.html'))
33+
.on('change', () => {
34+
stubIndexHtml()
35+
})
36+
chokidar.watch([r('src/manifest.ts'), r('package.json')])
37+
.on('change', () => {
38+
writeManifest()
39+
})
40+
}

packages/webext/scripts/utils.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { resolve } from 'node:path'
2+
import process from 'node:process'
3+
import { bgCyan, black } from 'kolorist'
4+
5+
export const port = Number(process.env.PORT || '') || 3303
6+
export const r = (...args: string[]) => resolve(__dirname, '..', ...args)
7+
export const isDev = process.env.NODE_ENV !== 'production'
8+
export const isFirefox = process.env.EXTENSION === 'firefox'
9+
10+
export function log(name: string, message: string) {
11+
console.log(black(bgCyan(` ${name} `)), message)
12+
}

packages/webext/shim.d.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import type { ProtocolWithReturn } from 'webext-bridge'
2+
3+
declare module 'webext-bridge' {
4+
export interface ProtocolMap {
5+
// define message protocol types
6+
// see https://github.com/antfu/webext-bridge#type-safe-protocols
7+
'tab-prev': { title: string | undefined }
8+
'get-current-tab': ProtocolWithReturn<{ tabId: number }, { title?: string }>
9+
}
10+
}

packages/webext/src/assets/logo.svg

+3
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { isFirefox, isForbiddenUrl } from '~/env'
2+
3+
// Firefox fetch files from cache instead of reloading changes from disk,
4+
// hmr will not work as Chromium based browser
5+
browser.webNavigation.onCommitted.addListener(({ tabId, frameId, url }) => {
6+
// Filter out non main window events.
7+
if (frameId !== 0)
8+
return
9+
10+
if (isForbiddenUrl(url))
11+
return
12+
13+
// inject the latest scripts
14+
browser.tabs.executeScript(tabId, {
15+
file: `${isFirefox ? '' : '.'}/dist/contentScripts/index.global.js`,
16+
runAt: 'document_end',
17+
}).catch(error => console.error(error))
18+
})

0 commit comments

Comments
 (0)