mirror of
https://github.com/ilyhalight/voice-over-translation.git
synced 2026-10-19 07:43:33 +00:00
rafactor: added native ext store build & fixed env parsing
This commit is contained in:
parent
20e6f3db25
commit
73ed08be1a
21 changed files with 8229 additions and 6684 deletions
19
README-EN.md
19
README-EN.md
|
|
@ -33,6 +33,11 @@
|
|||
[badge-en]: https://img.shields.io/badge/lang-English%20%F0%9F%87%AC%F0%9F%87%A7-white
|
||||
[badge-ru]: https://img.shields.io/badge/%D1%8F%D0%B7%D1%8B%D0%BA-%D0%A0%D1%83%D1%81%D1%81%D0%BA%D0%B8%D0%B9%20%F0%9F%87%B7%F0%9F%87%BA-white
|
||||
|
||||
<!-- Stores -->
|
||||
|
||||
[vot-chrome-store]: https://chromewebstore.google.com/detail/dnioaagdjgpcokckfpokpndoblenmfcg
|
||||
[vot-firefox-store]: https://addons.mozilla.org/ru/firefox/addon/voice-over-translation/
|
||||
|
||||
<!-- Other -->
|
||||
|
||||
[vot-readme-ru]: README.md
|
||||
|
|
@ -88,6 +93,12 @@ Thanks to the **[Yandex.Translate][yatranslate-link]** and **[Yandex.Browser][ya
|
|||
|
||||
### Install Native Extension for Chrome / Chromium
|
||||
|
||||
#### From Chrome WebStore
|
||||
|
||||
Open [Chrome WebStore][vot-chrome-store] and click "Install"
|
||||
|
||||
#### From GitHub Releases
|
||||
|
||||
1. Open [Releases][vot-releases] and download `vot-extension-chrome-<version>.zip`
|
||||
2. Open your extensions page:
|
||||
- Chrome: `chrome://extensions`
|
||||
|
|
@ -99,7 +110,13 @@ Thanks to the **[Yandex.Translate][yatranslate-link]** and **[Yandex.Browser][ya
|
|||
|
||||
### Install Native Extension for Firefox
|
||||
|
||||
1. Open [Releases][vot-releases], click `vot-extension-firefox-<version>.xpi`, and confirm installation in Firefox
|
||||
#### From Firefox Add-ons
|
||||
|
||||
Open [Firefox Add-ons][vot-firefox-store] and click "Add to Firefox"
|
||||
|
||||
#### From GitHub Releases
|
||||
|
||||
Open [Releases][vot-releases], click `vot-extension-firefox-<version>.xpi`, and confirm installation in Firefox
|
||||
|
||||
## Features
|
||||
|
||||
|
|
|
|||
27
README.md
27
README.md
|
|
@ -33,6 +33,11 @@
|
|||
[badge-en]: https://img.shields.io/badge/lang-English%20%F0%9F%87%AC%F0%9F%87%A7-white
|
||||
[badge-ru]: https://img.shields.io/badge/%D1%8F%D0%B7%D1%8B%D0%BA-%D0%A0%D1%83%D1%81%D1%81%D0%BA%D0%B8%D0%B9%20%F0%9F%87%B7%F0%9F%87%BA-white
|
||||
|
||||
<!-- Stores -->
|
||||
|
||||
[vot-chrome-store]: https://chromewebstore.google.com/detail/dnioaagdjgpcokckfpokpndoblenmfcg
|
||||
[vot-firefox-store]: https://addons.mozilla.org/ru/firefox/addon/voice-over-translation/
|
||||
|
||||
<!-- Other -->
|
||||
|
||||
[vot-readme-ru]: README.md
|
||||
|
|
@ -88,18 +93,30 @@
|
|||
|
||||
### Установка нативного расширения для Chrome / Chromium
|
||||
|
||||
1. Откройте [Releases][vot-releases] и скачайте файл `vot-extension-chrome-<версия>.zip`
|
||||
2. Откройте страницу расширений:
|
||||
#### Из Chrome WebStore
|
||||
|
||||
Откройте [Chrome WebStore][vot-chrome-store] и нажмите «Установить»
|
||||
|
||||
#### Из GitHub Releases
|
||||
|
||||
2. Откройте [Releases][vot-releases] и скачайте файл `vot-extension-chrome-<версия>.zip`
|
||||
3. Откройте страницу расширений:
|
||||
- Chrome: `chrome://extensions`
|
||||
- Edge: `edge://extensions`
|
||||
- Brave: `brave://extensions`
|
||||
- Opera: `opera://extensions`
|
||||
3. Включите **«Режим разработчика»**
|
||||
4. Перетащите скачанный `.zip`-файл на страницу расширений
|
||||
4. Включите **«Режим разработчика»**
|
||||
5. Перетащите скачанный `.zip`-файл на страницу расширений
|
||||
|
||||
### Установка нативного расширения для Firefox
|
||||
|
||||
1. Откройте [Releases][vot-releases], нажмите на `vot-extension-firefox-<версия>.xpi` и подтвердите установку в Firefox
|
||||
#### Из Firefox Add-ons
|
||||
|
||||
Откройте [Firefox Add-ons][vot-firefox-store] и нажмите «Добавить в Firefox»
|
||||
|
||||
#### Из GitHub Releases
|
||||
|
||||
Откройте [Releases][vot-releases], нажмите на `vot-extension-firefox-<версия>.xpi` и подтвердите установку в Firefox
|
||||
|
||||
## Возможности
|
||||
|
||||
|
|
|
|||
24
changelog.md
24
changelog.md
|
|
@ -1,4 +1,26 @@
|
|||
# 1.11.6
|
||||
# 1.11.7
|
||||
|
||||
> [!NOTE]
|
||||
> В этой версии был изменен ID дополнений, если вы используете собранное расширение для браузера, вам прийдется самостоятельно переустановить расширение для обновления на новую версию
|
||||
|
||||
- Выбирать озвучку стало проще благодаря отображению активного типа голоса и понятным названиям обычных и «Живых» голосов
|
||||
- «Живые» голоса теперь доступны для перевода на русский с любого распознанного языка
|
||||
- Повторный вход после истечения авторизации стал понятнее и требует меньше лишних действий
|
||||
- Кнопку перевода теперь удобнее размещать по бокам плеера с предварительным просмотром позиции, учетом границ экрана и сохранением выбора
|
||||
- Перевод запускается надежнее при медленном соединении благодаря отображению времени ожидания и автоматическим повторным загрузкам, а запоздавшие ответы больше не сбивают текущий перевод
|
||||
- Субтитры стали чище и легче читаются за счет улучшенных переносов строк, пунктуации, подсветки слов и отображения встроенного форматирования
|
||||
- Виджет субтитров теперь можно свободнее размещать, а подсказки с переводом слов стали аккуратнее и удобнее для чтения
|
||||
- Громкость и отключение звука теперь синхронизируются корректнее, не изменяя сохраненную громкость YouTube
|
||||
- Перевод на YouTube больше не отключается из-за изменения таймкода в ссылке (#1736)
|
||||
- Перевод в Firefox больше не отключается после большой перемотки, если плеер заменяет видеоэлемент (#1752)
|
||||
- Смена качества и повторяющиеся события плеера больше не должны приводить к лишнему сбросу перевода
|
||||
- Добавлена поддержка `hot.noodlemagazine.com`
|
||||
- Исправлено центрирование текста в меню выбора голоса на Яндекс Диске
|
||||
- Повышена стабильность расширения в Chrome и Firefox, включая запуск, работу во встроенных плеерах и системные уведомления
|
||||
- Улучшена совместимость userscript с менеджерами пользовательских скриптов
|
||||
- В версиях для Chrome и Firefox теперь корректно отображается установленная версия расширения
|
||||
|
||||
# 1.11.6
|
||||
|
||||
- Восстановлена работа кнопки в Google Drive (#912)
|
||||
- Исправлена работа SAP Learning (#1683)
|
||||
|
|
|
|||
6
dist-ext/update.xml
Normal file
6
dist-ext/update.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
|
||||
<app appid='dnioaagdjgpcokckfpokpndoblenmfcg'>
|
||||
<updatecheck codebase='https://raw.githubusercontent.com/ilyhalight/voice-over-translation/master/dist-ext/vot-extension-chrome.zip' version='1.11.7' />
|
||||
</app>
|
||||
</gupdate>
|
||||
BIN
dist-ext/vot-1.11.7.xpi
Normal file
BIN
dist-ext/vot-1.11.7.xpi
Normal file
Binary file not shown.
Binary file not shown.
BIN
dist-ext/vot-extension-chrome.zip
Normal file
BIN
dist-ext/vot-extension-chrome.zip
Normal file
Binary file not shown.
Binary file not shown.
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"addons": {
|
||||
"vot-extension@firefox": {
|
||||
"updates": [
|
||||
{
|
||||
"version": "1.11.6.1",
|
||||
"update_link": "https://raw.githubusercontent.com/ilyhalight/voice-over-translation/master/dist-ext/vot-extension-firefox-1.11.6.xpi"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
"addons": {
|
||||
"vot-ext-store@firefox": {
|
||||
"updates": [
|
||||
{
|
||||
"version": "1.11.7",
|
||||
"update_link": "https://raw.githubusercontent.com/ilyhalight/voice-over-translation/master/dist-ext/vot-extension-firefox.xpi"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
123
dist/vot-min.user.js
vendored
123
dist/vot-min.user.js
vendored
File diff suppressed because one or more lines are too long
14344
dist/vot.user.js
vendored
14344
dist/vot.user.js
vendored
File diff suppressed because one or more lines are too long
|
|
@ -1,53 +0,0 @@
|
|||
import { defineManifest } from "@crxjs/vite-plugin";
|
||||
|
||||
import headers from "./src/headers.json";
|
||||
import {
|
||||
buildManifestChrome,
|
||||
type ChromeManifestOptions,
|
||||
type ExtensionHeaders,
|
||||
type ManifestPathStrategy,
|
||||
} from "./vite/vite.extension.manifest-helpers";
|
||||
|
||||
const typedHeaders = headers as unknown as ExtensionHeaders;
|
||||
|
||||
// CRXJS resolves from source (src/) paths, not dist output paths.
|
||||
// pathStrategy overrides the default dist-oriented path generation
|
||||
// so buildManifestChrome produces src paths directly.
|
||||
const srcPathStrategy: ManifestPathStrategy = {
|
||||
iconPathFn: (size) => `src/extension/icons/icon-${size}.png`,
|
||||
scriptPathFn: (distJs) => {
|
||||
const srcMap: Record<string, string> = {
|
||||
"bridge.js": "src/extension/bridge.ts",
|
||||
"prelude.js": "src/extension/prelude.iife.ts",
|
||||
"content.js": "src/index.ts",
|
||||
};
|
||||
return srcMap[distJs] ?? distJs;
|
||||
},
|
||||
background: {
|
||||
service_worker: "src/extension/background.ts",
|
||||
type: "module",
|
||||
},
|
||||
};
|
||||
|
||||
const chromeOptions: ChromeManifestOptions = {
|
||||
versionName: typedHeaders.version,
|
||||
};
|
||||
|
||||
// Build full Chrome manifest directly with source paths
|
||||
const manifest = buildManifestChrome({
|
||||
headers: typedHeaders,
|
||||
includeWorld: true,
|
||||
pathStrategy: srcPathStrategy,
|
||||
chromeOptions,
|
||||
}) as Parameters<typeof defineManifest>[0];
|
||||
|
||||
// Chrome side-load auto-update (not used for Chrome Web Store distribution)
|
||||
const GITHUB_DIST_EXT_RAW_BASE =
|
||||
"https://raw.githubusercontent.com/ilyhalight/voice-over-translation/master/dist-ext";
|
||||
manifest.update_url = `${GITHUB_DIST_EXT_RAW_BASE}/update.xml`;
|
||||
|
||||
// Public key for consistent extension ID (derived from keys/chrome-crx.pem)
|
||||
// Chrome calculates ID as first 32 chars of SHA256(publicKey), a-p mapped
|
||||
manifest.key = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3IITn/V9BfBfaSk1oNpgoUNADzm15zSyWJ/a+bSnp5SkGAziTle3efpSWFTzpfmUnN+LyQeuXsgGqLJ6N8BeXA4g/Gh+J3gcZAgeSOaWmI7zbaXKrAyUo6irOyXaXNr4z+EuH6hqxNd7N5gv/XjaE0fGd9brepbKUoImFa5GnBE/xT6hnLPjwMYYO0tYBi7Om1Z2+Em4SvwixIK42cGxt0v6RwcE3isJ9Yt3Hm28fndfwaN6UO9C5NTXrYrlO2ivDX5njhjnVUXTM4oyy0NifcibYl0e2Au2e5xDLq0R7EkfWuW4mF3xzQi3D+R1A3pqAnMUIWDKt5bu8BmlhGzc3wIDAQAB";
|
||||
|
||||
export default defineManifest(manifest);
|
||||
|
|
@ -11,6 +11,7 @@ Available (sub)domains:
|
|||
- `www.youtube.com`
|
||||
- `www.youtube-nocookie.com`
|
||||
- `www.youtubekids.com`
|
||||
- `music.youtube.com`
|
||||
|
||||
Available paths:
|
||||
|
||||
|
|
@ -992,6 +993,15 @@ Available (sub)domains:
|
|||
- `rt.com`
|
||||
- `www.rt.com`
|
||||
|
||||
## Noodlemagazine
|
||||
|
||||
Status: [✅] Working
|
||||
|
||||
Available (sub)domains:
|
||||
|
||||
- `noodlemagazine.com`
|
||||
- `hot.noodlemagazine.com`
|
||||
|
||||
## Bitview
|
||||
|
||||
Status: [✅] Working
|
||||
|
|
@ -1153,6 +1163,14 @@ Available (sub)domains:
|
|||
- `mediafile.cc`
|
||||
- `www.mediafile.cc`
|
||||
|
||||
## Skilljar
|
||||
|
||||
Status: [✅] Working
|
||||
|
||||
Available (sub)domains:
|
||||
|
||||
- `skilljar.com`
|
||||
|
||||
## Direct link to MP4/WEBM
|
||||
|
||||
Status: [✅] Working
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
- `www.youtube.com`
|
||||
- `www.youtube-nocookie.com`
|
||||
- `www.youtubekids.com`
|
||||
- `music.youtube.com`
|
||||
|
||||
Доступные пути:
|
||||
|
||||
|
|
@ -992,6 +993,15 @@
|
|||
- `rt.com`
|
||||
- `www.rt.com`
|
||||
|
||||
## Noodlemagazine
|
||||
|
||||
Статус: [✅] Работает
|
||||
|
||||
Доступные (под)домены:
|
||||
|
||||
- `noodlemagazine.com`
|
||||
- `hot.noodlemagazine.com`
|
||||
|
||||
## Bitview
|
||||
|
||||
Статус: [✅] Работает
|
||||
|
|
@ -1153,6 +1163,14 @@
|
|||
- `mediafile.cc`
|
||||
- `www.mediafile.cc`
|
||||
|
||||
## Skilljar
|
||||
|
||||
Статус: [✅] Работает
|
||||
|
||||
Доступные (под)домены:
|
||||
|
||||
- `skilljar.com`
|
||||
|
||||
## Direct link to MP4/WEBM
|
||||
|
||||
Статус: [✅] Работает
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "[VOT] - Voice Over Translation",
|
||||
"description": "A small extension that adds a Yandex Browser video translation to other browsers",
|
||||
"version": "1.11.6.3",
|
||||
"version": "1.11.7",
|
||||
"author": "Toil, SashaXser, MrSoczekXD, mynovelhost, sodapng",
|
||||
"namespace": "vot",
|
||||
"icon": "https://translate.yandex.ru/icons/favicon.ico",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import { loadEnv } from "vite";
|
||||
import { defineConstants, type ViteDefine } from "./define";
|
||||
import { rootDir } from "./paths";
|
||||
|
||||
export interface BuildEnvMeta {
|
||||
debug: boolean;
|
||||
|
|
@ -10,6 +12,28 @@ export interface BuildEnvMeta {
|
|||
crxjsBuild: boolean;
|
||||
}
|
||||
|
||||
export function getBuildConfig(mode: string) {
|
||||
const env = loadEnv(mode, rootDir, "");
|
||||
return {
|
||||
REPO_BRANCH: env.GITHUB_REF_NAME || env.REPO_BRANCH || "master",
|
||||
IS_STORE_BUILD: env.IS_STORE_BUILD === "true",
|
||||
CHROME_EXTENSION_ID: env.CHROME_EXTENSION_ID || "EXTENSION_ID",
|
||||
FIREFOX_ADDON_ID: env.FIREFOX_ADDON_ID || env.GECKO_ID || "vot-ext@firefox",
|
||||
FIREFOX_STRICT_MIN_VERSION:
|
||||
env.FIREFOX_STRICT_MIN_VERSION?.trim() || "140.0",
|
||||
FIREFOX_ANDROID_STRICT_MIN_VERSION:
|
||||
env.FIREFOX_ANDROID_STRICT_MIN_VERSION?.trim(),
|
||||
FIREFOX_ANDROID_STRICT_MAX_VERSION:
|
||||
env.FIREFOX_ANDROID_STRICT_MAX_VERSION?.trim(),
|
||||
FIREFOX_DATA_COLLECTION_REQUIRED: env.FIREFOX_DATA_COLLECTION_REQUIRED,
|
||||
FIREFOX_DATA_COLLECTION_OPTIONAL: env.FIREFOX_DATA_COLLECTION_OPTIONAL,
|
||||
REQUIRED_ANDROID_MIN_VERSION:
|
||||
env.REQUIRED_ANDROID_MIN_VERSION?.trim() || "142.0",
|
||||
} as const;
|
||||
}
|
||||
|
||||
export type BuildConfig = ReturnType<typeof getBuildConfig>;
|
||||
|
||||
export function buildDefine(env: BuildEnvMeta): ViteDefine {
|
||||
return {
|
||||
...defineConstants({
|
||||
|
|
|
|||
|
|
@ -1,16 +1,13 @@
|
|||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { COMPRESSION_LEVEL, zip } from "zip-a-folder";
|
||||
import type { BuildConfig } from "../env";
|
||||
import { distExtDir } from "../paths";
|
||||
|
||||
const GITHUB_DIST_EXT_RAW_BASE =
|
||||
"https://raw.githubusercontent.com/ilyhalight/voice-over-translation/master/dist-ext";
|
||||
const CHROME_CRX_RAW_URL = `${GITHUB_DIST_EXT_RAW_BASE}/vot-extension-chrome.zip`;
|
||||
|
||||
export function getChromeExtensionId(): string {
|
||||
return process.env.CHROME_EXTENSION_ID || "EXTENSION_ID";
|
||||
}
|
||||
|
||||
async function zipDir(
|
||||
sourceDirPath: string,
|
||||
outZipPath: string,
|
||||
|
|
@ -43,10 +40,13 @@ export async function writeChromeUpdatesManifest({
|
|||
return updatesManifestPath;
|
||||
}
|
||||
|
||||
export async function finalizeChromeBuild(headers: {
|
||||
version?: string;
|
||||
author?: string;
|
||||
}): Promise<void> {
|
||||
export async function finalizeChromeBuild(
|
||||
config: BuildConfig,
|
||||
headers: {
|
||||
version?: string;
|
||||
author?: string;
|
||||
},
|
||||
): Promise<void> {
|
||||
const chromeDir = path.join(distExtDir, "chrome");
|
||||
const version = String(headers.version || "");
|
||||
|
||||
|
|
@ -55,25 +55,24 @@ export async function finalizeChromeBuild(headers: {
|
|||
|
||||
const updatesPath = await writeChromeUpdatesManifest({
|
||||
version,
|
||||
extensionId: getChromeExtensionId(),
|
||||
extensionId: config.CHROME_EXTENSION_ID,
|
||||
});
|
||||
|
||||
console.log(`Chrome package: ${zipPath}`);
|
||||
console.log(`Chrome updates: ${updatesPath}`);
|
||||
}
|
||||
|
||||
export async function getChromeExtensionBuildEnv(): Promise<{
|
||||
export async function getChromeExtensionBuildEnv(config: BuildConfig): Promise<{
|
||||
headers: { version?: string; author?: string };
|
||||
locales: string[];
|
||||
branch: string;
|
||||
}> {
|
||||
const { getExtensionHeaders, getLocaleCodes, getRepoBranch } = await import(
|
||||
const { getExtensionHeaders, getLocaleCodes } = await import(
|
||||
"./firefox-pipeline"
|
||||
);
|
||||
const [headers, locales] = await Promise.all([
|
||||
getExtensionHeaders(),
|
||||
getLocaleCodes(),
|
||||
]);
|
||||
const branch = getRepoBranch();
|
||||
return { headers, locales, branch };
|
||||
return { headers, locales, branch: config.REPO_BRANCH };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import fs from "node:fs/promises";
|
|||
import path from "node:path";
|
||||
import { type UserConfig, build as viteBuild } from "vite";
|
||||
import { COMPRESSION_LEVEL, zip } from "zip-a-folder";
|
||||
import { type BuildEnvMeta, buildDefine } from "../env";
|
||||
import { type BuildConfig, type BuildEnvMeta, buildDefine } from "../env";
|
||||
import {
|
||||
distExtDir,
|
||||
outTmp,
|
||||
|
|
@ -111,14 +111,12 @@ export async function getLocaleCodes(): Promise<string[]> {
|
|||
return [...new Set(normalized)];
|
||||
}
|
||||
|
||||
export function getRepoBranch(): string {
|
||||
return process.env.GITHUB_REF_NAME || process.env.REPO_BRANCH || "master";
|
||||
}
|
||||
|
||||
export async function createExtensionBuildContext(): Promise<ExtensionBuildContext> {
|
||||
export async function createExtensionBuildContext(
|
||||
config: BuildConfig,
|
||||
): Promise<ExtensionBuildContext> {
|
||||
return {
|
||||
availableLocales: await getLocaleCodes(),
|
||||
repoBranch: getRepoBranch(),
|
||||
repoBranch: config.REPO_BRANCH,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -126,16 +124,17 @@ export async function getExtensionHeaders(): Promise<ExtensionHeaders> {
|
|||
return readJson<ExtensionHeaders>(path.join(srcDir, "headers.json"));
|
||||
}
|
||||
|
||||
export async function getFirefoxBuildEnv(): Promise<BuildEnvMeta> {
|
||||
export async function getFirefoxBuildEnv(
|
||||
config: BuildConfig,
|
||||
): Promise<BuildEnvMeta> {
|
||||
const headers = await getExtensionHeaders();
|
||||
const locales = await getLocaleCodes();
|
||||
const branch = getRepoBranch();
|
||||
|
||||
return {
|
||||
debug: false,
|
||||
isExtension: true,
|
||||
availableLocales: locales,
|
||||
repoBranch: branch,
|
||||
repoBranch: config.REPO_BRANCH,
|
||||
version: String(headers.version || ""),
|
||||
authors: String(headers.author || ""),
|
||||
crxjsBuild: false,
|
||||
|
|
@ -250,18 +249,6 @@ async function copyExtensionFiles(targetDir: string): Promise<void> {
|
|||
// Firefox manifest
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
function getFirefoxAddonId(): string {
|
||||
return (
|
||||
process.env.FIREFOX_ADDON_ID ||
|
||||
process.env.GECKO_ID ||
|
||||
"vot-extension@firefox"
|
||||
);
|
||||
}
|
||||
|
||||
function getFirefoxStrictMinVersion(): string {
|
||||
return process.env.FIREFOX_STRICT_MIN_VERSION || "140.0";
|
||||
}
|
||||
|
||||
function compareFirefoxVersions(left: string, right: string): number {
|
||||
const leftParts = left
|
||||
.split(".")
|
||||
|
|
@ -279,11 +266,13 @@ function compareFirefoxVersions(left: string, right: string): number {
|
|||
return 0;
|
||||
}
|
||||
|
||||
function getFirefoxAndroidSettings(): Record<string, string> {
|
||||
const strictMinVersion =
|
||||
process.env.FIREFOX_ANDROID_STRICT_MIN_VERSION?.trim();
|
||||
const strictMaxVersion =
|
||||
process.env.FIREFOX_ANDROID_STRICT_MAX_VERSION?.trim();
|
||||
function getFirefoxAndroidSettings(
|
||||
config: BuildConfig,
|
||||
): Record<string, string> {
|
||||
const {
|
||||
FIREFOX_ANDROID_STRICT_MIN_VERSION: strictMinVersion,
|
||||
FIREFOX_ANDROID_STRICT_MAX_VERSION: strictMaxVersion,
|
||||
} = config;
|
||||
|
||||
return {
|
||||
...(strictMinVersion ? { strict_min_version: strictMinVersion } : {}),
|
||||
|
|
@ -291,18 +280,20 @@ function getFirefoxAndroidSettings(): Record<string, string> {
|
|||
};
|
||||
}
|
||||
|
||||
function getFirefoxDataCollectionPermissions(): {
|
||||
const parseList = (raw: string): string[] =>
|
||||
String(raw)
|
||||
.split(",")
|
||||
.map((entry) => entry.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
function getFirefoxDataCollectionPermissions(config: BuildConfig): {
|
||||
required: string[];
|
||||
optional?: string[];
|
||||
} {
|
||||
const requiredRaw = process.env.FIREFOX_DATA_COLLECTION_REQUIRED;
|
||||
const optionalRaw = process.env.FIREFOX_DATA_COLLECTION_OPTIONAL;
|
||||
|
||||
const parseList = (raw: string): string[] =>
|
||||
String(raw)
|
||||
.split(",")
|
||||
.map((entry) => entry.trim())
|
||||
.filter(Boolean);
|
||||
const {
|
||||
FIREFOX_DATA_COLLECTION_REQUIRED: requiredRaw,
|
||||
FIREFOX_DATA_COLLECTION_OPTIONAL: optionalRaw,
|
||||
} = config;
|
||||
|
||||
const required = requiredRaw ? parseList(requiredRaw) : ["none"];
|
||||
const optional = optionalRaw ? parseList(optionalRaw) : null;
|
||||
|
|
@ -313,13 +304,12 @@ function getFirefoxDataCollectionPermissions(): {
|
|||
};
|
||||
}
|
||||
|
||||
function getFirefoxAndroidSettingsForDataCollectionPermissions(): Record<
|
||||
string,
|
||||
string
|
||||
> {
|
||||
const androidSettings = getFirefoxAndroidSettings();
|
||||
function getFirefoxAndroidSettingsForDataCollectionPermissions(
|
||||
config: BuildConfig,
|
||||
): Record<string, string> {
|
||||
const androidSettings = getFirefoxAndroidSettings(config);
|
||||
const androidMinVersion = androidSettings.strict_min_version?.trim();
|
||||
const requiredAndroidMinVersion = "142.0";
|
||||
const { REQUIRED_ANDROID_MIN_VERSION: requiredAndroidMinVersion } = config;
|
||||
|
||||
if (
|
||||
!androidMinVersion ||
|
||||
|
|
@ -338,15 +328,36 @@ function getFirefoxXpiRawUrl(): string {
|
|||
return `${GITHUB_DIST_EXT_RAW_BASE}/vot-extension-firefox.xpi`;
|
||||
}
|
||||
|
||||
function getFirefoxSpecificSettings(config: BuildConfig) {
|
||||
const dataCollectionPermissions = getFirefoxDataCollectionPermissions(config);
|
||||
|
||||
return {
|
||||
gecko: {
|
||||
id: config.FIREFOX_ADDON_ID,
|
||||
strict_min_version: config.FIREFOX_STRICT_MIN_VERSION,
|
||||
data_collection_permissions: dataCollectionPermissions,
|
||||
...(config.IS_STORE_BUILD
|
||||
? {}
|
||||
: { update_url: FIREFOX_UPDATES_MANIFEST_URL }),
|
||||
},
|
||||
gecko_android:
|
||||
dataCollectionPermissions.required.length ||
|
||||
dataCollectionPermissions.optional?.length
|
||||
? getFirefoxAndroidSettingsForDataCollectionPermissions(config)
|
||||
: getFirefoxAndroidSettings(config),
|
||||
};
|
||||
}
|
||||
|
||||
function buildManifestFirefox({
|
||||
headers,
|
||||
includeWorld,
|
||||
config,
|
||||
}: {
|
||||
headers: ExtensionHeaders;
|
||||
includeWorld: boolean;
|
||||
config: BuildConfig;
|
||||
}): Record<string, unknown> {
|
||||
const manifest = buildManifestChrome({ headers, includeWorld });
|
||||
const dataCollectionPermissions = getFirefoxDataCollectionPermissions();
|
||||
|
||||
const action = manifest.action as Record<string, unknown> | undefined;
|
||||
const defaultIcon = action?.default_icon as
|
||||
|
|
@ -361,19 +372,7 @@ function buildManifestFirefox({
|
|||
manifest.background = {
|
||||
scripts: ["background.js"],
|
||||
};
|
||||
manifest.browser_specific_settings = {
|
||||
gecko: {
|
||||
id: getFirefoxAddonId(),
|
||||
update_url: FIREFOX_UPDATES_MANIFEST_URL,
|
||||
strict_min_version: getFirefoxStrictMinVersion(),
|
||||
data_collection_permissions: dataCollectionPermissions,
|
||||
},
|
||||
gecko_android:
|
||||
dataCollectionPermissions.required.length ||
|
||||
dataCollectionPermissions.optional?.length
|
||||
? getFirefoxAndroidSettingsForDataCollectionPermissions()
|
||||
: getFirefoxAndroidSettings(),
|
||||
};
|
||||
manifest.browser_specific_settings = getFirefoxSpecificSettings(config);
|
||||
|
||||
return manifest;
|
||||
}
|
||||
|
|
@ -484,7 +483,10 @@ async function verifyManifest(
|
|||
return manifest;
|
||||
}
|
||||
|
||||
function verifyFirefoxManifestFields(manifest: Record<string, any>): void {
|
||||
function verifyFirefoxManifestFields(
|
||||
manifest: Record<string, any>,
|
||||
storeBuild: boolean,
|
||||
): void {
|
||||
const permissions = new Set(manifest.permissions || []);
|
||||
if (
|
||||
!permissions.has("declarativeNetRequestWithHostAccess") &&
|
||||
|
|
@ -501,12 +503,16 @@ function verifyFirefoxManifestFields(manifest: Record<string, any>): void {
|
|||
) {
|
||||
throw new Error("firefox: expected background.scripts[]");
|
||||
}
|
||||
const updateUrl = manifest.browser_specific_settings?.gecko?.update_url;
|
||||
if (
|
||||
manifest.browser_specific_settings?.gecko?.update_url !==
|
||||
FIREFOX_UPDATES_MANIFEST_URL
|
||||
storeBuild
|
||||
? updateUrl !== undefined
|
||||
: updateUrl !== FIREFOX_UPDATES_MANIFEST_URL
|
||||
) {
|
||||
throw new Error(
|
||||
`firefox: expected browser_specific_settings.gecko.update_url to be ${FIREFOX_UPDATES_MANIFEST_URL}`,
|
||||
storeBuild
|
||||
? "firefox: store manifest must not contain browser_specific_settings.gecko.update_url"
|
||||
: `firefox: expected browser_specific_settings.gecko.update_url to be ${FIREFOX_UPDATES_MANIFEST_URL}`,
|
||||
);
|
||||
}
|
||||
if (
|
||||
|
|
@ -692,10 +698,10 @@ async function verifyBodySerializationGuards(): Promise<void> {
|
|||
);
|
||||
}
|
||||
|
||||
export async function verifyFirefoxOutputs(): Promise<void> {
|
||||
export async function verifyFirefoxOutputs(config: BuildConfig): Promise<void> {
|
||||
const dir = path.join(distExtDir, "firefox");
|
||||
const manifest = await verifyManifest(path.join(dir, "manifest.json"));
|
||||
verifyFirefoxManifestFields(manifest);
|
||||
verifyFirefoxManifestFields(manifest, config.IS_STORE_BUILD);
|
||||
verifyContentScripts(manifest);
|
||||
|
||||
// File-level checks are independent — run in parallel.
|
||||
|
|
@ -722,7 +728,7 @@ export async function verifyFirefoxOutputs(): Promise<void> {
|
|||
// Firefox build pipeline
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
export async function finalizeFirefoxBuild(): Promise<void> {
|
||||
export async function finalizeFirefoxBuild(config: BuildConfig): Promise<void> {
|
||||
const headers = await getExtensionHeaders();
|
||||
const version = headers.version || DEFAULT_EXTENSION_VERSION;
|
||||
|
||||
|
|
@ -731,17 +737,17 @@ export async function finalizeFirefoxBuild(): Promise<void> {
|
|||
await copyExtensionFiles(outDir);
|
||||
await writeManifest(
|
||||
outDir,
|
||||
buildManifestFirefox({ headers, includeWorld: true }),
|
||||
buildManifestFirefox({ headers, includeWorld: true, config }),
|
||||
);
|
||||
|
||||
await zipDir(outDir, path.join(distExtDir, "vot-extension-firefox.xpi"));
|
||||
|
||||
const updatesPath = await writeFirefoxUpdatesManifest({
|
||||
version,
|
||||
addonId: getFirefoxAddonId(),
|
||||
addonId: config.FIREFOX_ADDON_ID,
|
||||
});
|
||||
|
||||
await verifyFirefoxOutputs();
|
||||
await verifyFirefoxOutputs(config);
|
||||
|
||||
console.log("Extension build complete:");
|
||||
console.log(`- Firefox: ${outDir}`);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { defineManifest } from "@crxjs/vite-plugin";
|
||||
|
||||
import headers from "../../../src/headers.json";
|
||||
import { getBuildConfig } from "../env";
|
||||
import {
|
||||
buildManifestChrome,
|
||||
type ChromeManifestOptions,
|
||||
|
|
@ -44,8 +45,18 @@ const manifest = buildManifestChrome({
|
|||
chromeOptions,
|
||||
}) as Record<string, unknown>;
|
||||
|
||||
export default defineManifest({
|
||||
...manifest,
|
||||
update_url: `${GITHUB_DIST_EXT_RAW_BASE}/update.xml`,
|
||||
key: CHROME_EXTENSION_KEY,
|
||||
} as Parameters<typeof defineManifest>[0]);
|
||||
export default defineManifest(({ mode }) => {
|
||||
const BUILD_COFNIG = getBuildConfig(mode);
|
||||
|
||||
const updateManifest = BUILD_COFNIG.IS_STORE_BUILD
|
||||
? {}
|
||||
: {
|
||||
update_url: `${GITHUB_DIST_EXT_RAW_BASE}/update.xml`,
|
||||
key: CHROME_EXTENSION_KEY,
|
||||
};
|
||||
|
||||
return {
|
||||
...manifest,
|
||||
...updateManifest,
|
||||
} as Parameters<typeof defineManifest>[0];
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
import { defineConfig, type Plugin } from "vite";
|
||||
import { buildDefine } from "./lib/env";
|
||||
import {
|
||||
type BuildConfig,
|
||||
buildDefine,
|
||||
getBuildConfig,
|
||||
isStoreBuild,
|
||||
} from "./lib/env";
|
||||
import {
|
||||
buildExtensionBundles,
|
||||
cleanupExtensionTmpDir,
|
||||
|
|
@ -29,19 +34,19 @@ function firefoxPipelineEntryPlugin(): Plugin {
|
|||
};
|
||||
}
|
||||
|
||||
function firefoxBuildPipelinePlugin(): Plugin {
|
||||
function firefoxBuildPipelinePlugin(config: BuildConfig): Plugin {
|
||||
return {
|
||||
name: "vot-firefox-build-pipeline",
|
||||
apply: "build",
|
||||
async closeBundle() {
|
||||
const [context, headers] = await Promise.all([
|
||||
createExtensionBuildContext(),
|
||||
createExtensionBuildContext(config),
|
||||
getExtensionHeaders(),
|
||||
]);
|
||||
|
||||
try {
|
||||
await buildExtensionBundles({ context, headers });
|
||||
await finalizeFirefoxBuild();
|
||||
await finalizeFirefoxBuild(config);
|
||||
} finally {
|
||||
await cleanupExtensionTmpDir();
|
||||
}
|
||||
|
|
@ -49,14 +54,21 @@ function firefoxBuildPipelinePlugin(): Plugin {
|
|||
};
|
||||
}
|
||||
|
||||
export default defineConfig(async () => {
|
||||
export default defineConfig(async ({ mode }) => {
|
||||
const buildConfig = getBuildConfig(mode);
|
||||
console.log(
|
||||
`Building extension with store mode? ${buildConfig.IS_STORE_BUILD}`,
|
||||
);
|
||||
const baseConfig = createBaseViteConfig({ cacheName: "firefox-extension" });
|
||||
const env = await getFirefoxBuildEnv();
|
||||
const env = await getFirefoxBuildEnv(buildConfig);
|
||||
|
||||
return {
|
||||
...baseConfig,
|
||||
define: buildDefine(env),
|
||||
plugins: [firefoxPipelineEntryPlugin(), firefoxBuildPipelinePlugin()],
|
||||
plugins: [
|
||||
firefoxPipelineEntryPlugin(),
|
||||
firefoxBuildPipelinePlugin(buildConfig),
|
||||
],
|
||||
build: {
|
||||
...baseConfig.build,
|
||||
outDir: distExtDir,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import path from "node:path";
|
||||
import { crx } from "@crxjs/vite-plugin";
|
||||
import { defineConfig, type Plugin } from "vite";
|
||||
import { buildDefine } from "./lib/env";
|
||||
import { type BuildConfig, buildDefine, getBuildConfig } from "./lib/env";
|
||||
import {
|
||||
finalizeChromeBuild,
|
||||
getChromeExtensionBuildEnv,
|
||||
|
|
@ -10,27 +10,32 @@ import manifest from "./lib/extension/manifest.config";
|
|||
import { distExtDir } from "./lib/paths";
|
||||
import { createBaseViteConfig } from "./lib/vite-base-config";
|
||||
|
||||
function chromePackagePlugin(headers: {
|
||||
version?: string;
|
||||
author?: string;
|
||||
}): Plugin {
|
||||
function chromePackagePlugin(
|
||||
config: BuildConfig,
|
||||
headers: {
|
||||
version?: string;
|
||||
author?: string;
|
||||
},
|
||||
): Plugin {
|
||||
return {
|
||||
name: "vot-chrome-package",
|
||||
apply: "build",
|
||||
enforce: "post",
|
||||
async closeBundle() {
|
||||
await finalizeChromeBuild(headers);
|
||||
await finalizeChromeBuild(config, headers);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default defineConfig(async () => {
|
||||
export default defineConfig(async ({ mode }) => {
|
||||
const buildConfig = getBuildConfig(mode);
|
||||
const baseConfig = createBaseViteConfig({ cacheName: "chrome-extension" });
|
||||
const { headers, locales, branch } = await getChromeExtensionBuildEnv();
|
||||
const { headers, locales, branch } =
|
||||
await getChromeExtensionBuildEnv(buildConfig);
|
||||
|
||||
return {
|
||||
...baseConfig,
|
||||
plugins: [crx({ manifest }), chromePackagePlugin(headers)],
|
||||
plugins: [crx({ manifest }), chromePackagePlugin(buildConfig, headers)],
|
||||
define: buildDefine({
|
||||
debug: false,
|
||||
isExtension: true,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue