rafactor: added native ext store build & fixed env parsing

This commit is contained in:
Toil 2026-07-22 17:33:44 +03:00
parent 20e6f3db25
commit 73ed08be1a
No known key found for this signature in database
GPG key ID: 57248E2819D32070
21 changed files with 8229 additions and 6684 deletions

View file

@ -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

View file

@ -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
## Возможности

View file

@ -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
View 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

Binary file not shown.

Binary file not shown.

View file

@ -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

File diff suppressed because one or more lines are too long

14344
dist/vot.user.js vendored

File diff suppressed because one or more lines are too long

View file

@ -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);

View file

@ -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

View file

@ -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
Статус: [✅] Работает

View file

@ -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",

View file

@ -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({

View file

@ -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 };
}

View file

@ -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}`);

View file

@ -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];
});

View file

@ -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,

View file

@ -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,