mirror of
https://github.com/OpenSelena/omniget.git
synced 2026-11-04 08:10:56 +00:00
feat: add version bumping script and i18n key generator
This commit is contained in:
1 parent
b06b7d1d35
commit
f288acaa83
24 files changed
+1032
-69
No files matched your search
@@ -145,9 +145,6 @@ jobs:
|
||||
--arg d "$PUB_DATE" \
|
||||
'{version: $v, notes: "", pub_date: $d, platforms: {}}')
|
||||
|
||||
ASSETS=$(gh release view "$TAG" --repo "$REPO" --json assets -q '.assets[].name')
|
||||
TMPDIR=$(mktemp -d)
|
||||
|
||||
declare -A PLATFORM_MAP=(
|
||||
["aarch64.app.tar.gz.sig"]="darwin-aarch64"
|
||||
["x64.app.tar.gz.sig"]="darwin-x86_64"
|
||||
@@ -156,13 +153,44 @@ jobs:
|
||||
["x64-setup.exe.sig"]="windows-x86_64"
|
||||
)
|
||||
|
||||
ASSETS=""
|
||||
for attempt in $(seq 1 10); do
|
||||
ASSETS=$(gh release view "$TAG" --repo "$REPO" --json assets -q '.assets[].name' || true)
|
||||
MISSING=0
|
||||
for SUFFIX in "${!PLATFORM_MAP[@]}"; do
|
||||
if ! echo "$ASSETS" | grep -q "${SUFFIX}$"; then
|
||||
MISSING=$((MISSING + 1))
|
||||
fi
|
||||
done
|
||||
if [ "$MISSING" -eq 0 ]; then
|
||||
echo "All expected .sig assets are visible (attempt $attempt)."
|
||||
break
|
||||
fi
|
||||
echo "Attempt $attempt: $MISSING .sig asset(s) not yet visible, retrying in 30s..."
|
||||
sleep 30
|
||||
done
|
||||
|
||||
TMPDIR=$(mktemp -d)
|
||||
|
||||
for SUFFIX in "${!PLATFORM_MAP[@]}"; do
|
||||
PLATFORM="${PLATFORM_MAP[$SUFFIX]}"
|
||||
SIG_FILE=$(echo "$ASSETS" | grep "${SUFFIX}$" || true)
|
||||
|
||||
if [ -n "$SIG_FILE" ]; then
|
||||
BUNDLE="${SIG_FILE%.sig}"
|
||||
gh release download "$TAG" --repo "$REPO" --pattern "$SIG_FILE" --dir "$TMPDIR" --clobber
|
||||
DOWNLOADED=0
|
||||
for dl_attempt in $(seq 1 10); do
|
||||
if gh release download "$TAG" --repo "$REPO" --pattern "$SIG_FILE" --dir "$TMPDIR" --clobber; then
|
||||
DOWNLOADED=1
|
||||
break
|
||||
fi
|
||||
echo "Download attempt $dl_attempt for $SIG_FILE failed, retrying in 30s..."
|
||||
sleep 30
|
||||
done
|
||||
if [ "$DOWNLOADED" -ne 1 ]; then
|
||||
echo "Failed to download $SIG_FILE after 10 attempts" >&2
|
||||
exit 1
|
||||
fi
|
||||
SIG_CONTENT=$(cat "$TMPDIR/$SIG_FILE")
|
||||
URL="https://github.com/${REPO}/releases/download/${TAG}/${BUNDLE}"
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ body {
|
||||
}
|
||||
|
||||
.header-left { display: flex; align-items: center; gap: 8px; }
|
||||
.header-toggles { display: flex; align-items: center; gap: 10px; }
|
||||
.logo { width: 16px; height: 16px; }
|
||||
.title { font-weight: 600; font-size: 14px; }
|
||||
|
||||
|
||||
@@ -12,10 +12,16 @@
|
||||
<img src="../icons/active-16.png" alt="" class="logo">
|
||||
<span class="title">OmniGet</span>
|
||||
</div>
|
||||
<label class="toggle" title="Media detection">
|
||||
<input type="checkbox" id="sniffer-toggle" role="switch" aria-label="Media detection">
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
<div class="header-toggles">
|
||||
<label class="toggle" title="Open app when downloading">
|
||||
<input type="checkbox" id="open-app-toggle" role="switch" aria-label="Open app when downloading">
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
<label class="toggle" title="Media detection">
|
||||
<input type="checkbox" id="sniffer-toggle" role="switch" aria-label="Media detection">
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</header>
|
||||
<div id="content" class="content">
|
||||
<div class="state-empty">
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { loadOpenAppState, isOpenAppEnabled, setOpenAppEnabled } from "../src/open-app-toggle.js";
|
||||
|
||||
const APP_URL = "https://github.com/tonhowtf/omniget/releases/latest";
|
||||
|
||||
const SVG = {
|
||||
@@ -11,15 +13,24 @@ const SVG = {
|
||||
|
||||
let currentData = null;
|
||||
let pageTitle = "";
|
||||
let pageThumbnail = "";
|
||||
|
||||
async function init() {
|
||||
const toggle = document.getElementById("sniffer-toggle");
|
||||
const openAppToggle = document.getElementById("open-app-toggle");
|
||||
|
||||
try {
|
||||
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||
pageTitle = tab?.title || "";
|
||||
pageThumbnail = tab?.favIconUrl || "";
|
||||
} catch {}
|
||||
|
||||
await loadOpenAppState();
|
||||
openAppToggle.checked = isOpenAppEnabled();
|
||||
openAppToggle.addEventListener("change", () => {
|
||||
setOpenAppEnabled(openAppToggle.checked);
|
||||
});
|
||||
|
||||
chrome.runtime.sendMessage({ type: "getDetectedMedia" }, (response) => {
|
||||
if (!response) return;
|
||||
currentData = response;
|
||||
@@ -309,9 +320,11 @@ function sendToApp(url, platform, mediaEntry) {
|
||||
const msg = { type: "sendToOmniGet", url, platform };
|
||||
|
||||
if (pageTitle) msg.title = pageTitle;
|
||||
if (pageThumbnail) msg.thumbnail = pageThumbnail;
|
||||
if (currentData?.tabUrl) msg.referer = currentData.tabUrl;
|
||||
if (mediaEntry?.mediaType) msg.mediaType = mediaEntry.mediaType;
|
||||
if (mediaEntry?.contentType) msg.contentType = mediaEntry.contentType;
|
||||
msg.openApp = isOpenAppEnabled();
|
||||
|
||||
if (mediaEntry?.requestHeaders) {
|
||||
const skip = ["host", "connection", "accept-encoding", "sec-ch-ua", "sec-ch-ua-mobile", "sec-ch-ua-platform", "sec-fetch-dest", "sec-fetch-mode", "sec-fetch-site", "upgrade-insecure-requests"];
|
||||
@@ -344,6 +357,7 @@ async function sendBatch(groups) {
|
||||
if (currentData?.tabUrl) msg.referer = currentData.tabUrl;
|
||||
if (group.master.mediaType) msg.mediaType = group.master.mediaType;
|
||||
if (group.master.contentType) msg.contentType = group.master.contentType;
|
||||
msg.openApp = isOpenAppEnabled();
|
||||
|
||||
try {
|
||||
await new Promise((resolve) => {
|
||||
|
||||
@@ -156,9 +156,11 @@ async function handleSendToApp(msg) {
|
||||
const platform = msg.platform || "generic";
|
||||
|
||||
let pageTitle = "";
|
||||
let pageThumbnail = "";
|
||||
try {
|
||||
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||
pageTitle = tab?.title || "";
|
||||
pageThumbnail = tab?.favIconUrl || "";
|
||||
} catch {}
|
||||
|
||||
let cookies = null;
|
||||
@@ -204,9 +206,12 @@ async function handleSendToApp(msg) {
|
||||
if (msg.referer) message.referer = msg.referer;
|
||||
if (msg.title) message.title = msg.title;
|
||||
else if (pageTitle) message.title = pageTitle;
|
||||
if (msg.thumbnail) message.thumbnail = msg.thumbnail;
|
||||
else if (pageThumbnail) message.thumbnail = pageThumbnail;
|
||||
if (msg.mediaType) message.mediaType = msg.mediaType;
|
||||
if (msg.contentType) message.contentType = msg.contentType;
|
||||
if (msg.headers) message.headers = msg.headers;
|
||||
if (typeof msg.openApp === "boolean") message.openApp = msg.openApp;
|
||||
message.pageUrl = msg.referer || "";
|
||||
message.userAgent = navigator.userAgent;
|
||||
|
||||
|
||||
@@ -71,6 +71,18 @@ const PLATFORM_CDN_HOSTS = [
|
||||
"tiktokcdn-us.com",
|
||||
];
|
||||
|
||||
const BLOCKED_PATH_PATTERNS = [
|
||||
/\/analytics(\/|\?|$)/i,
|
||||
/\/pixel(\/|\?|\.|$)/i,
|
||||
/\/collect(\/|\?|$)/i,
|
||||
/\/beacon(\/|\?|$)/i,
|
||||
/\/telemetry(\/|\?|$)/i,
|
||||
/\/track(\/|\?|$|er)/i,
|
||||
/1x1\.gif$/i,
|
||||
/\/spacer\.gif$/i,
|
||||
/\/transparent\.gif$/i,
|
||||
];
|
||||
|
||||
const MIN_CONTENT_LENGTH = 50 * 1024;
|
||||
|
||||
const detectedMedia = new Map();
|
||||
@@ -96,6 +108,14 @@ function isBlockedHost(url) {
|
||||
} catch { return false; }
|
||||
}
|
||||
|
||||
function isBlockedPath(url) {
|
||||
try {
|
||||
const u = new URL(url);
|
||||
const target = u.pathname + u.search;
|
||||
return BLOCKED_PATH_PATTERNS.some(rx => rx.test(target));
|
||||
} catch { return false; }
|
||||
}
|
||||
|
||||
function isMediaByExtension(url) {
|
||||
try {
|
||||
const path = new URL(url).pathname.toLowerCase();
|
||||
@@ -168,16 +188,18 @@ function isPlatformCdnFragment(url, contentType, contentLength) {
|
||||
} catch { return false; }
|
||||
}
|
||||
|
||||
const storageArea = chrome.storage.session ?? chrome.storage.local;
|
||||
|
||||
function persistMedia(tabId) {
|
||||
const media = detectedMedia.get(tabId);
|
||||
if (!media || media.size === 0) return;
|
||||
const arr = Array.from(media.entries());
|
||||
chrome.storage.session.set({ [`media_${tabId}`]: arr }).catch(() => {});
|
||||
storageArea.set({ [`media_${tabId}`]: arr }).catch(() => {});
|
||||
}
|
||||
|
||||
export async function restoreMedia() {
|
||||
try {
|
||||
const data = await chrome.storage.session.get(null);
|
||||
const data = await storageArea.get(null);
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
if (!key.startsWith("media_")) continue;
|
||||
const tabId = parseInt(key.replace("media_", ""));
|
||||
@@ -214,7 +236,10 @@ export function registerSnifferListeners(onMediaDetected) {
|
||||
if (details.statusCode < 200 || details.statusCode >= 300) return;
|
||||
|
||||
const url = details.url;
|
||||
if (isBlockedHost(url)) return;
|
||||
if (isBlockedHost(url) || isBlockedPath(url)) {
|
||||
pendingRequests.delete(details.requestId);
|
||||
return;
|
||||
}
|
||||
|
||||
const contentType = getContentType(details.responseHeaders);
|
||||
const contentLength = getContentLength(details.responseHeaders);
|
||||
@@ -280,13 +305,13 @@ export function registerSnifferListeners(onMediaDetected) {
|
||||
|
||||
chrome.tabs.onRemoved.addListener((tabId) => {
|
||||
detectedMedia.delete(tabId);
|
||||
chrome.storage.session.remove(`media_${tabId}`).catch(() => {});
|
||||
storageArea.remove(`media_${tabId}`).catch(() => {});
|
||||
});
|
||||
|
||||
chrome.tabs.onUpdated.addListener((tabId, changeInfo) => {
|
||||
if (changeInfo.url) {
|
||||
detectedMedia.delete(tabId);
|
||||
chrome.storage.session.remove(`media_${tabId}`).catch(() => {});
|
||||
storageArea.remove(`media_${tabId}`).catch(() => {});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
const STORAGE_KEY = "omniget_open_app_on_download";
|
||||
|
||||
let openApp = true;
|
||||
|
||||
export async function loadOpenAppState() {
|
||||
const result = await chrome.storage.local.get(STORAGE_KEY);
|
||||
openApp = result[STORAGE_KEY] !== false;
|
||||
return openApp;
|
||||
}
|
||||
|
||||
export function isOpenAppEnabled() {
|
||||
return openApp;
|
||||
}
|
||||
|
||||
export async function setOpenAppEnabled(value) {
|
||||
openApp = value;
|
||||
await chrome.storage.local.set({ [STORAGE_KEY]: value });
|
||||
}
|
||||
@@ -37,6 +37,7 @@ body {
|
||||
}
|
||||
|
||||
.header-left { display: flex; align-items: center; gap: 8px; }
|
||||
.header-toggles { display: flex; align-items: center; gap: 10px; }
|
||||
.logo { width: 16px; height: 16px; }
|
||||
.title { font-weight: 600; font-size: 14px; }
|
||||
|
||||
|
||||
@@ -12,10 +12,16 @@
|
||||
<img src="../icons/active-16.png" alt="" class="logo">
|
||||
<span class="title">OmniGet</span>
|
||||
</div>
|
||||
<label class="toggle" title="Media detection">
|
||||
<input type="checkbox" id="sniffer-toggle" role="switch" aria-label="Media detection">
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
<div class="header-toggles">
|
||||
<label class="toggle" title="Open app when downloading">
|
||||
<input type="checkbox" id="open-app-toggle" role="switch" aria-label="Open app when downloading">
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
<label class="toggle" title="Media detection">
|
||||
<input type="checkbox" id="sniffer-toggle" role="switch" aria-label="Media detection">
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</header>
|
||||
<div id="content" class="content">
|
||||
<div class="state-empty">
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { loadOpenAppState, isOpenAppEnabled, setOpenAppEnabled } from "../src/open-app-toggle.js";
|
||||
|
||||
const APP_URL = "https://github.com/tonhowtf/omniget/releases/latest";
|
||||
|
||||
const SVG = {
|
||||
@@ -11,15 +13,24 @@ const SVG = {
|
||||
|
||||
let currentData = null;
|
||||
let pageTitle = "";
|
||||
let pageThumbnail = "";
|
||||
|
||||
async function init() {
|
||||
const toggle = document.getElementById("sniffer-toggle");
|
||||
const openAppToggle = document.getElementById("open-app-toggle");
|
||||
|
||||
try {
|
||||
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||
pageTitle = tab?.title || "";
|
||||
pageThumbnail = tab?.favIconUrl || "";
|
||||
} catch {}
|
||||
|
||||
await loadOpenAppState();
|
||||
openAppToggle.checked = isOpenAppEnabled();
|
||||
openAppToggle.addEventListener("change", () => {
|
||||
setOpenAppEnabled(openAppToggle.checked);
|
||||
});
|
||||
|
||||
chrome.runtime.sendMessage({ type: "getDetectedMedia" }, (response) => {
|
||||
if (!response) return;
|
||||
currentData = response;
|
||||
@@ -309,9 +320,11 @@ function sendToApp(url, platform, mediaEntry) {
|
||||
const msg = { type: "sendToOmniGet", url, platform };
|
||||
|
||||
if (pageTitle) msg.title = pageTitle;
|
||||
if (pageThumbnail) msg.thumbnail = pageThumbnail;
|
||||
if (currentData?.tabUrl) msg.referer = currentData.tabUrl;
|
||||
if (mediaEntry?.mediaType) msg.mediaType = mediaEntry.mediaType;
|
||||
if (mediaEntry?.contentType) msg.contentType = mediaEntry.contentType;
|
||||
msg.openApp = isOpenAppEnabled();
|
||||
|
||||
if (mediaEntry?.requestHeaders) {
|
||||
const skip = ["host", "connection", "accept-encoding", "sec-ch-ua", "sec-ch-ua-mobile", "sec-ch-ua-platform", "sec-fetch-dest", "sec-fetch-mode", "sec-fetch-site", "upgrade-insecure-requests"];
|
||||
@@ -344,6 +357,7 @@ async function sendBatch(groups) {
|
||||
if (currentData?.tabUrl) msg.referer = currentData.tabUrl;
|
||||
if (group.master.mediaType) msg.mediaType = group.master.mediaType;
|
||||
if (group.master.contentType) msg.contentType = group.master.contentType;
|
||||
msg.openApp = isOpenAppEnabled();
|
||||
|
||||
try {
|
||||
await new Promise((resolve) => {
|
||||
|
||||
@@ -156,9 +156,11 @@ async function handleSendToApp(msg) {
|
||||
const platform = msg.platform || "generic";
|
||||
|
||||
let pageTitle = "";
|
||||
let pageThumbnail = "";
|
||||
try {
|
||||
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||
pageTitle = tab?.title || "";
|
||||
pageThumbnail = tab?.favIconUrl || "";
|
||||
} catch {}
|
||||
|
||||
let cookies = null;
|
||||
@@ -204,9 +206,12 @@ async function handleSendToApp(msg) {
|
||||
if (msg.referer) message.referer = msg.referer;
|
||||
if (msg.title) message.title = msg.title;
|
||||
else if (pageTitle) message.title = pageTitle;
|
||||
if (msg.thumbnail) message.thumbnail = msg.thumbnail;
|
||||
else if (pageThumbnail) message.thumbnail = pageThumbnail;
|
||||
if (msg.mediaType) message.mediaType = msg.mediaType;
|
||||
if (msg.contentType) message.contentType = msg.contentType;
|
||||
if (msg.headers) message.headers = msg.headers;
|
||||
if (typeof msg.openApp === "boolean") message.openApp = msg.openApp;
|
||||
message.pageUrl = msg.referer || "";
|
||||
message.userAgent = navigator.userAgent;
|
||||
|
||||
|
||||
@@ -71,6 +71,18 @@ const PLATFORM_CDN_HOSTS = [
|
||||
"tiktokcdn-us.com",
|
||||
];
|
||||
|
||||
const BLOCKED_PATH_PATTERNS = [
|
||||
/\/analytics(\/|\?|$)/i,
|
||||
/\/pixel(\/|\?|\.|$)/i,
|
||||
/\/collect(\/|\?|$)/i,
|
||||
/\/beacon(\/|\?|$)/i,
|
||||
/\/telemetry(\/|\?|$)/i,
|
||||
/\/track(\/|\?|$|er)/i,
|
||||
/1x1\.gif$/i,
|
||||
/\/spacer\.gif$/i,
|
||||
/\/transparent\.gif$/i,
|
||||
];
|
||||
|
||||
const MIN_CONTENT_LENGTH = 50 * 1024;
|
||||
|
||||
const detectedMedia = new Map();
|
||||
@@ -96,6 +108,14 @@ function isBlockedHost(url) {
|
||||
} catch { return false; }
|
||||
}
|
||||
|
||||
function isBlockedPath(url) {
|
||||
try {
|
||||
const u = new URL(url);
|
||||
const target = u.pathname + u.search;
|
||||
return BLOCKED_PATH_PATTERNS.some(rx => rx.test(target));
|
||||
} catch { return false; }
|
||||
}
|
||||
|
||||
function isMediaByExtension(url) {
|
||||
try {
|
||||
const path = new URL(url).pathname.toLowerCase();
|
||||
@@ -168,16 +188,18 @@ function isPlatformCdnFragment(url, contentType, contentLength) {
|
||||
} catch { return false; }
|
||||
}
|
||||
|
||||
const storageArea = chrome.storage.session ?? chrome.storage.local;
|
||||
|
||||
function persistMedia(tabId) {
|
||||
const media = detectedMedia.get(tabId);
|
||||
if (!media || media.size === 0) return;
|
||||
const arr = Array.from(media.entries());
|
||||
chrome.storage.local.set({ [`media_${tabId}`]: arr }).catch(() => {});
|
||||
storageArea.set({ [`media_${tabId}`]: arr }).catch(() => {});
|
||||
}
|
||||
|
||||
export async function restoreMedia() {
|
||||
try {
|
||||
const data = await chrome.storage.local.get(null);
|
||||
const data = await storageArea.get(null);
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
if (!key.startsWith("media_")) continue;
|
||||
const tabId = parseInt(key.replace("media_", ""));
|
||||
@@ -214,7 +236,10 @@ export function registerSnifferListeners(onMediaDetected) {
|
||||
if (details.statusCode < 200 || details.statusCode >= 300) return;
|
||||
|
||||
const url = details.url;
|
||||
if (isBlockedHost(url)) return;
|
||||
if (isBlockedHost(url) || isBlockedPath(url)) {
|
||||
pendingRequests.delete(details.requestId);
|
||||
return;
|
||||
}
|
||||
|
||||
const contentType = getContentType(details.responseHeaders);
|
||||
const contentLength = getContentLength(details.responseHeaders);
|
||||
@@ -280,13 +305,13 @@ export function registerSnifferListeners(onMediaDetected) {
|
||||
|
||||
chrome.tabs.onRemoved.addListener((tabId) => {
|
||||
detectedMedia.delete(tabId);
|
||||
chrome.storage.local.remove(`media_${tabId}`).catch(() => {});
|
||||
storageArea.remove(`media_${tabId}`).catch(() => {});
|
||||
});
|
||||
|
||||
chrome.tabs.onUpdated.addListener((tabId, changeInfo) => {
|
||||
if (changeInfo.url) {
|
||||
detectedMedia.delete(tabId);
|
||||
chrome.storage.local.remove(`media_${tabId}`).catch(() => {});
|
||||
storageArea.remove(`media_${tabId}`).catch(() => {});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
const STORAGE_KEY = "omniget_open_app_on_download";
|
||||
|
||||
let openApp = true;
|
||||
|
||||
export async function loadOpenAppState() {
|
||||
const result = await chrome.storage.local.get(STORAGE_KEY);
|
||||
openApp = result[STORAGE_KEY] !== false;
|
||||
return openApp;
|
||||
}
|
||||
|
||||
export function isOpenAppEnabled() {
|
||||
return openApp;
|
||||
}
|
||||
|
||||
export async function setOpenAppEnabled(value) {
|
||||
openApp = value;
|
||||
await chrome.storage.local.set({ [STORAGE_KEY]: value });
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const CHROME = path.join(__dirname, "chrome");
|
||||
const FIREFOX = path.join(__dirname, "firefox");
|
||||
|
||||
const SHARED = [
|
||||
"src/action-click.js",
|
||||
"src/action-feedback.js",
|
||||
"src/action-title.js",
|
||||
"src/background.js",
|
||||
"src/context-menu.js",
|
||||
"src/cookies.js",
|
||||
"src/detect.js",
|
||||
"src/media-sniffer.js",
|
||||
"src/open-app-toggle.js",
|
||||
"src/sniffer-toggle.js",
|
||||
"popup/popup.js",
|
||||
"popup/popup.css",
|
||||
"popup/popup.html",
|
||||
"pages/error.html",
|
||||
"pages/error.css",
|
||||
"pages/error.js",
|
||||
"pages/error-content.js",
|
||||
];
|
||||
|
||||
const SHARED_DIRS = ["_locales", "icons"];
|
||||
|
||||
function copyFile(rel) {
|
||||
const from = path.join(CHROME, rel);
|
||||
const to = path.join(FIREFOX, rel);
|
||||
if (!fs.existsSync(from)) {
|
||||
console.warn(`[skip] missing in chrome: ${rel}`);
|
||||
return { changed: false, same: false, skipped: true };
|
||||
}
|
||||
fs.mkdirSync(path.dirname(to), { recursive: true });
|
||||
const a = fs.readFileSync(from);
|
||||
if (fs.existsSync(to)) {
|
||||
const b = fs.readFileSync(to);
|
||||
if (a.equals(b)) return { changed: false, same: true, skipped: false };
|
||||
}
|
||||
fs.writeFileSync(to, a);
|
||||
return { changed: true, same: false, skipped: false };
|
||||
}
|
||||
|
||||
function copyDirRecursive(relDir) {
|
||||
const from = path.join(CHROME, relDir);
|
||||
const to = path.join(FIREFOX, relDir);
|
||||
if (!fs.existsSync(from)) return { changed: 0, same: 0 };
|
||||
let changed = 0;
|
||||
let same = 0;
|
||||
const walk = (srcDir, dstDir) => {
|
||||
fs.mkdirSync(dstDir, { recursive: true });
|
||||
for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
|
||||
const srcPath = path.join(srcDir, entry.name);
|
||||
const dstPath = path.join(dstDir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
walk(srcPath, dstPath);
|
||||
} else if (entry.isFile()) {
|
||||
const a = fs.readFileSync(srcPath);
|
||||
if (fs.existsSync(dstPath)) {
|
||||
const b = fs.readFileSync(dstPath);
|
||||
if (a.equals(b)) {
|
||||
same++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
fs.writeFileSync(dstPath, a);
|
||||
changed++;
|
||||
}
|
||||
}
|
||||
};
|
||||
walk(from, to);
|
||||
return { changed, same };
|
||||
}
|
||||
|
||||
let totalChanged = 0;
|
||||
let totalSame = 0;
|
||||
|
||||
for (const rel of SHARED) {
|
||||
const r = copyFile(rel);
|
||||
if (r.changed) {
|
||||
console.log(`[copy] ${rel}`);
|
||||
totalChanged++;
|
||||
} else if (r.same) {
|
||||
totalSame++;
|
||||
}
|
||||
}
|
||||
|
||||
for (const rel of SHARED_DIRS) {
|
||||
const r = copyDirRecursive(rel);
|
||||
if (r.changed > 0) console.log(`[copy] ${rel}/ (${r.changed} file(s))`);
|
||||
totalChanged += r.changed;
|
||||
totalSame += r.same;
|
||||
}
|
||||
|
||||
console.log(`\nSynced chrome → firefox: ${totalChanged} changed, ${totalSame} already identical.`);
|
||||
console.log(`Note: manifest.json is intentionally per-browser and not synced.`);
|
||||
+9
-2
@@ -10,7 +10,12 @@
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"tauri": "tauri"
|
||||
"tauri": "tauri",
|
||||
"version:set": "node scripts/bump-version.js",
|
||||
"generate:i18n-keys": "node scripts/generate-i18n-keys.js",
|
||||
"extension:sync": "node browser-extension/sync.mjs",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest"
|
||||
},
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
@@ -29,9 +34,11 @@
|
||||
"@sveltejs/kit": "^2.9.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||
"@tauri-apps/cli": "^2",
|
||||
"@types/node": "^25.6.0",
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
"typescript": "~5.6.2",
|
||||
"vite": "^6.0.3"
|
||||
"vite": "^6.0.3",
|
||||
"vitest": "^4.1.4"
|
||||
}
|
||||
}
|
||||
Generated
+276
-19
@@ -38,16 +38,19 @@ importers:
|
||||
devDependencies:
|
||||
'@sveltejs/adapter-static':
|
||||
specifier: ^3.0.6
|
||||
version: 3.0.10(@sveltejs/kit@2.51.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.51.0)(vite@6.4.1))(svelte@5.51.0)(typescript@5.6.3)(vite@6.4.1))
|
||||
version: 3.0.10(@sveltejs/kit@2.51.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.51.0)(vite@6.4.1(@types/node@25.6.0)))(svelte@5.51.0)(typescript@5.6.3)(vite@6.4.1(@types/node@25.6.0)))
|
||||
'@sveltejs/kit':
|
||||
specifier: ^2.9.0
|
||||
version: 2.51.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.51.0)(vite@6.4.1))(svelte@5.51.0)(typescript@5.6.3)(vite@6.4.1)
|
||||
version: 2.51.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.51.0)(vite@6.4.1(@types/node@25.6.0)))(svelte@5.51.0)(typescript@5.6.3)(vite@6.4.1(@types/node@25.6.0))
|
||||
'@sveltejs/vite-plugin-svelte':
|
||||
specifier: ^5.0.0
|
||||
version: 5.1.1(svelte@5.51.0)(vite@6.4.1)
|
||||
version: 5.1.1(svelte@5.51.0)(vite@6.4.1(@types/node@25.6.0))
|
||||
'@tauri-apps/cli':
|
||||
specifier: ^2
|
||||
version: 2.10.0
|
||||
'@types/node':
|
||||
specifier: ^25.6.0
|
||||
version: 25.6.0
|
||||
svelte:
|
||||
specifier: ^5.0.0
|
||||
version: 5.51.0
|
||||
@@ -59,7 +62,10 @@ importers:
|
||||
version: 5.6.3
|
||||
vite:
|
||||
specifier: ^6.0.3
|
||||
version: 6.4.1
|
||||
version: 6.4.1(@types/node@25.6.0)
|
||||
vitest:
|
||||
specifier: ^4.1.4
|
||||
version: 4.1.4(@types/node@25.6.0)(vite@6.4.1(@types/node@25.6.0))
|
||||
|
||||
packages:
|
||||
|
||||
@@ -528,15 +534,53 @@ packages:
|
||||
'@tauri-apps/plugin-updater@2.10.0':
|
||||
resolution: {integrity: sha512-ljN8jPlnT0aSn8ecYhuBib84alxfMx6Hc8vJSKMJyzGbTPFZAC44T2I1QNFZssgWKrAlofvJqCC6Rr472JWfkQ==}
|
||||
|
||||
'@types/chai@5.2.3':
|
||||
resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
|
||||
|
||||
'@types/cookie@0.6.0':
|
||||
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
|
||||
|
||||
'@types/deep-eql@4.0.2':
|
||||
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
|
||||
|
||||
'@types/estree@1.0.8':
|
||||
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
||||
|
||||
'@types/node@25.6.0':
|
||||
resolution: {integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==}
|
||||
|
||||
'@types/trusted-types@2.0.7':
|
||||
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
|
||||
|
||||
'@vitest/expect@4.1.4':
|
||||
resolution: {integrity: sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==}
|
||||
|
||||
'@vitest/mocker@4.1.4':
|
||||
resolution: {integrity: sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==}
|
||||
peerDependencies:
|
||||
msw: ^2.4.9
|
||||
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
peerDependenciesMeta:
|
||||
msw:
|
||||
optional: true
|
||||
vite:
|
||||
optional: true
|
||||
|
||||
'@vitest/pretty-format@4.1.4':
|
||||
resolution: {integrity: sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==}
|
||||
|
||||
'@vitest/runner@4.1.4':
|
||||
resolution: {integrity: sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==}
|
||||
|
||||
'@vitest/snapshot@4.1.4':
|
||||
resolution: {integrity: sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==}
|
||||
|
||||
'@vitest/spy@4.1.4':
|
||||
resolution: {integrity: sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==}
|
||||
|
||||
'@vitest/utils@4.1.4':
|
||||
resolution: {integrity: sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==}
|
||||
|
||||
acorn@8.15.0:
|
||||
resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
@@ -546,10 +590,18 @@ packages:
|
||||
resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
assertion-error@2.0.1:
|
||||
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
axobject-query@4.1.0:
|
||||
resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
chai@6.2.2:
|
||||
resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
chokidar@4.0.3:
|
||||
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
|
||||
engines: {node: '>= 14.16.0'}
|
||||
@@ -558,6 +610,9 @@ packages:
|
||||
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
convert-source-map@2.0.0:
|
||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||
|
||||
cookie@0.6.0:
|
||||
resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@@ -578,6 +633,9 @@ packages:
|
||||
devalue@5.6.2:
|
||||
resolution: {integrity: sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==}
|
||||
|
||||
es-module-lexer@2.0.0:
|
||||
resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==}
|
||||
|
||||
esbuild@0.25.12:
|
||||
resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -589,6 +647,13 @@ packages:
|
||||
esrap@2.2.3:
|
||||
resolution: {integrity: sha512-8fOS+GIGCQZl/ZIlhl59htOlms6U8NvX6ZYgYHpRU/b6tVSh3uHkOHZikl3D4cMbYM0JlpBe+p/BkZEi8J9XIQ==}
|
||||
|
||||
estree-walker@3.0.3:
|
||||
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
|
||||
|
||||
expect-type@1.3.0:
|
||||
resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
fdir@6.5.0:
|
||||
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
@@ -632,6 +697,12 @@ packages:
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
obug@2.1.1:
|
||||
resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==}
|
||||
|
||||
pathe@2.0.3:
|
||||
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
|
||||
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
@@ -659,6 +730,9 @@ packages:
|
||||
set-cookie-parser@3.0.1:
|
||||
resolution: {integrity: sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q==}
|
||||
|
||||
siginfo@2.0.0:
|
||||
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
|
||||
|
||||
sirv@3.0.2:
|
||||
resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -667,6 +741,12 @@ packages:
|
||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
stackback@0.0.2:
|
||||
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
||||
|
||||
std-env@4.0.0:
|
||||
resolution: {integrity: sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==}
|
||||
|
||||
svelte-check@4.4.0:
|
||||
resolution: {integrity: sha512-gB3FdEPb8tPO3Y7Dzc6d/Pm/KrXAhK+0Fk+LkcysVtupvAh6Y/IrBCEZNupq57oh0hcwlxCUamu/rq7GtvfSEg==}
|
||||
engines: {node: '>= 18.0.0'}
|
||||
@@ -684,10 +764,21 @@ packages:
|
||||
peerDependencies:
|
||||
svelte: '>=3.49.0'
|
||||
|
||||
tinybench@2.9.0:
|
||||
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
|
||||
|
||||
tinyexec@1.1.1:
|
||||
resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
tinyglobby@0.2.15:
|
||||
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
tinyrainbow@3.1.0:
|
||||
resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
totalist@3.0.1:
|
||||
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -697,6 +788,9 @@ packages:
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
undici-types@7.19.2:
|
||||
resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==}
|
||||
|
||||
vite@6.4.1:
|
||||
resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==}
|
||||
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
||||
@@ -745,6 +839,52 @@ packages:
|
||||
vite:
|
||||
optional: true
|
||||
|
||||
vitest@4.1.4:
|
||||
resolution: {integrity: sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==}
|
||||
engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@edge-runtime/vm': '*'
|
||||
'@opentelemetry/api': ^1.9.0
|
||||
'@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
|
||||
'@vitest/browser-playwright': 4.1.4
|
||||
'@vitest/browser-preview': 4.1.4
|
||||
'@vitest/browser-webdriverio': 4.1.4
|
||||
'@vitest/coverage-istanbul': 4.1.4
|
||||
'@vitest/coverage-v8': 4.1.4
|
||||
'@vitest/ui': 4.1.4
|
||||
happy-dom: '*'
|
||||
jsdom: '*'
|
||||
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
peerDependenciesMeta:
|
||||
'@edge-runtime/vm':
|
||||
optional: true
|
||||
'@opentelemetry/api':
|
||||
optional: true
|
||||
'@types/node':
|
||||
optional: true
|
||||
'@vitest/browser-playwright':
|
||||
optional: true
|
||||
'@vitest/browser-preview':
|
||||
optional: true
|
||||
'@vitest/browser-webdriverio':
|
||||
optional: true
|
||||
'@vitest/coverage-istanbul':
|
||||
optional: true
|
||||
'@vitest/coverage-v8':
|
||||
optional: true
|
||||
'@vitest/ui':
|
||||
optional: true
|
||||
happy-dom:
|
||||
optional: true
|
||||
jsdom:
|
||||
optional: true
|
||||
|
||||
why-is-node-running@2.3.0:
|
||||
resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
|
||||
engines: {node: '>=8'}
|
||||
hasBin: true
|
||||
|
||||
zimmerframe@1.1.4:
|
||||
resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==}
|
||||
|
||||
@@ -934,15 +1074,15 @@ snapshots:
|
||||
dependencies:
|
||||
acorn: 8.15.0
|
||||
|
||||
'@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.51.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.51.0)(vite@6.4.1))(svelte@5.51.0)(typescript@5.6.3)(vite@6.4.1))':
|
||||
'@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.51.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.51.0)(vite@6.4.1(@types/node@25.6.0)))(svelte@5.51.0)(typescript@5.6.3)(vite@6.4.1(@types/node@25.6.0)))':
|
||||
dependencies:
|
||||
'@sveltejs/kit': 2.51.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.51.0)(vite@6.4.1))(svelte@5.51.0)(typescript@5.6.3)(vite@6.4.1)
|
||||
'@sveltejs/kit': 2.51.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.51.0)(vite@6.4.1(@types/node@25.6.0)))(svelte@5.51.0)(typescript@5.6.3)(vite@6.4.1(@types/node@25.6.0))
|
||||
|
||||
'@sveltejs/kit@2.51.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.51.0)(vite@6.4.1))(svelte@5.51.0)(typescript@5.6.3)(vite@6.4.1)':
|
||||
'@sveltejs/kit@2.51.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.51.0)(vite@6.4.1(@types/node@25.6.0)))(svelte@5.51.0)(typescript@5.6.3)(vite@6.4.1(@types/node@25.6.0))':
|
||||
dependencies:
|
||||
'@standard-schema/spec': 1.1.0
|
||||
'@sveltejs/acorn-typescript': 1.0.9(acorn@8.15.0)
|
||||
'@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.51.0)(vite@6.4.1)
|
||||
'@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.51.0)(vite@6.4.1(@types/node@25.6.0))
|
||||
'@types/cookie': 0.6.0
|
||||
acorn: 8.15.0
|
||||
cookie: 0.6.0
|
||||
@@ -955,29 +1095,29 @@ snapshots:
|
||||
set-cookie-parser: 3.0.1
|
||||
sirv: 3.0.2
|
||||
svelte: 5.51.0
|
||||
vite: 6.4.1
|
||||
vite: 6.4.1(@types/node@25.6.0)
|
||||
optionalDependencies:
|
||||
typescript: 5.6.3
|
||||
|
||||
'@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.51.0)(vite@6.4.1))(svelte@5.51.0)(vite@6.4.1)':
|
||||
'@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.51.0)(vite@6.4.1(@types/node@25.6.0)))(svelte@5.51.0)(vite@6.4.1(@types/node@25.6.0))':
|
||||
dependencies:
|
||||
'@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.51.0)(vite@6.4.1)
|
||||
'@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.51.0)(vite@6.4.1(@types/node@25.6.0))
|
||||
debug: 4.4.3
|
||||
svelte: 5.51.0
|
||||
vite: 6.4.1
|
||||
vite: 6.4.1(@types/node@25.6.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.51.0)(vite@6.4.1)':
|
||||
'@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.51.0)(vite@6.4.1(@types/node@25.6.0))':
|
||||
dependencies:
|
||||
'@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.51.0)(vite@6.4.1))(svelte@5.51.0)(vite@6.4.1)
|
||||
'@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.51.0)(vite@6.4.1(@types/node@25.6.0)))(svelte@5.51.0)(vite@6.4.1(@types/node@25.6.0))
|
||||
debug: 4.4.3
|
||||
deepmerge: 4.3.1
|
||||
kleur: 4.1.5
|
||||
magic-string: 0.30.21
|
||||
svelte: 5.51.0
|
||||
vite: 6.4.1
|
||||
vitefu: 1.1.1(vite@6.4.1)
|
||||
vite: 6.4.1(@types/node@25.6.0)
|
||||
vitefu: 1.1.1(vite@6.4.1(@types/node@25.6.0))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -1056,24 +1196,82 @@ snapshots:
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.10.1
|
||||
|
||||
'@types/chai@5.2.3':
|
||||
dependencies:
|
||||
'@types/deep-eql': 4.0.2
|
||||
assertion-error: 2.0.1
|
||||
|
||||
'@types/cookie@0.6.0': {}
|
||||
|
||||
'@types/deep-eql@4.0.2': {}
|
||||
|
||||
'@types/estree@1.0.8': {}
|
||||
|
||||
'@types/node@25.6.0':
|
||||
dependencies:
|
||||
undici-types: 7.19.2
|
||||
|
||||
'@types/trusted-types@2.0.7': {}
|
||||
|
||||
'@vitest/expect@4.1.4':
|
||||
dependencies:
|
||||
'@standard-schema/spec': 1.1.0
|
||||
'@types/chai': 5.2.3
|
||||
'@vitest/spy': 4.1.4
|
||||
'@vitest/utils': 4.1.4
|
||||
chai: 6.2.2
|
||||
tinyrainbow: 3.1.0
|
||||
|
||||
'@vitest/mocker@4.1.4(vite@6.4.1(@types/node@25.6.0))':
|
||||
dependencies:
|
||||
'@vitest/spy': 4.1.4
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.21
|
||||
optionalDependencies:
|
||||
vite: 6.4.1(@types/node@25.6.0)
|
||||
|
||||
'@vitest/pretty-format@4.1.4':
|
||||
dependencies:
|
||||
tinyrainbow: 3.1.0
|
||||
|
||||
'@vitest/runner@4.1.4':
|
||||
dependencies:
|
||||
'@vitest/utils': 4.1.4
|
||||
pathe: 2.0.3
|
||||
|
||||
'@vitest/snapshot@4.1.4':
|
||||
dependencies:
|
||||
'@vitest/pretty-format': 4.1.4
|
||||
'@vitest/utils': 4.1.4
|
||||
magic-string: 0.30.21
|
||||
pathe: 2.0.3
|
||||
|
||||
'@vitest/spy@4.1.4': {}
|
||||
|
||||
'@vitest/utils@4.1.4':
|
||||
dependencies:
|
||||
'@vitest/pretty-format': 4.1.4
|
||||
convert-source-map: 2.0.0
|
||||
tinyrainbow: 3.1.0
|
||||
|
||||
acorn@8.15.0: {}
|
||||
|
||||
aria-query@5.3.2: {}
|
||||
|
||||
assertion-error@2.0.1: {}
|
||||
|
||||
axobject-query@4.1.0: {}
|
||||
|
||||
chai@6.2.2: {}
|
||||
|
||||
chokidar@4.0.3:
|
||||
dependencies:
|
||||
readdirp: 4.1.2
|
||||
|
||||
clsx@2.1.1: {}
|
||||
|
||||
convert-source-map@2.0.0: {}
|
||||
|
||||
cookie@0.6.0: {}
|
||||
|
||||
debug@4.4.3:
|
||||
@@ -1084,6 +1282,8 @@ snapshots:
|
||||
|
||||
devalue@5.6.2: {}
|
||||
|
||||
es-module-lexer@2.0.0: {}
|
||||
|
||||
esbuild@0.25.12:
|
||||
optionalDependencies:
|
||||
'@esbuild/aix-ppc64': 0.25.12
|
||||
@@ -1119,6 +1319,12 @@ snapshots:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
estree-walker@3.0.3:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.8
|
||||
|
||||
expect-type@1.3.0: {}
|
||||
|
||||
fdir@6.5.0(picomatch@4.0.3):
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.3
|
||||
@@ -1146,6 +1352,10 @@ snapshots:
|
||||
|
||||
nanoid@3.3.11: {}
|
||||
|
||||
obug@2.1.1: {}
|
||||
|
||||
pathe@2.0.3: {}
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
|
||||
picomatch@4.0.3: {}
|
||||
@@ -1195,6 +1405,8 @@ snapshots:
|
||||
|
||||
set-cookie-parser@3.0.1: {}
|
||||
|
||||
siginfo@2.0.0: {}
|
||||
|
||||
sirv@3.0.2:
|
||||
dependencies:
|
||||
'@polka/url': 1.0.0-next.29
|
||||
@@ -1203,6 +1415,10 @@ snapshots:
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
|
||||
stackback@0.0.2: {}
|
||||
|
||||
std-env@4.0.0: {}
|
||||
|
||||
svelte-check@4.4.0(picomatch@4.0.3)(svelte@5.51.0)(typescript@5.6.3):
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
@@ -1240,16 +1456,24 @@ snapshots:
|
||||
'@sveltekit-i18n/parser-default': 1.1.1
|
||||
svelte: 5.51.0
|
||||
|
||||
tinybench@2.9.0: {}
|
||||
|
||||
tinyexec@1.1.1: {}
|
||||
|
||||
tinyglobby@0.2.15:
|
||||
dependencies:
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
picomatch: 4.0.3
|
||||
|
||||
tinyrainbow@3.1.0: {}
|
||||
|
||||
totalist@3.0.1: {}
|
||||
|
||||
typescript@5.6.3: {}
|
||||
|
||||
vite@6.4.1:
|
||||
undici-types@7.19.2: {}
|
||||
|
||||
vite@6.4.1(@types/node@25.6.0):
|
||||
dependencies:
|
||||
esbuild: 0.25.12
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
@@ -1258,10 +1482,43 @@ snapshots:
|
||||
rollup: 4.57.1
|
||||
tinyglobby: 0.2.15
|
||||
optionalDependencies:
|
||||
'@types/node': 25.6.0
|
||||
fsevents: 2.3.3
|
||||
|
||||
vitefu@1.1.1(vite@6.4.1):
|
||||
vitefu@1.1.1(vite@6.4.1(@types/node@25.6.0)):
|
||||
optionalDependencies:
|
||||
vite: 6.4.1
|
||||
vite: 6.4.1(@types/node@25.6.0)
|
||||
|
||||
vitest@4.1.4(@types/node@25.6.0)(vite@6.4.1(@types/node@25.6.0)):
|
||||
dependencies:
|
||||
'@vitest/expect': 4.1.4
|
||||
'@vitest/mocker': 4.1.4(vite@6.4.1(@types/node@25.6.0))
|
||||
'@vitest/pretty-format': 4.1.4
|
||||
'@vitest/runner': 4.1.4
|
||||
'@vitest/snapshot': 4.1.4
|
||||
'@vitest/spy': 4.1.4
|
||||
'@vitest/utils': 4.1.4
|
||||
es-module-lexer: 2.0.0
|
||||
expect-type: 1.3.0
|
||||
magic-string: 0.30.21
|
||||
obug: 2.1.1
|
||||
pathe: 2.0.3
|
||||
picomatch: 4.0.3
|
||||
std-env: 4.0.0
|
||||
tinybench: 2.9.0
|
||||
tinyexec: 1.1.1
|
||||
tinyglobby: 0.2.15
|
||||
tinyrainbow: 3.1.0
|
||||
vite: 6.4.1(@types/node@25.6.0)
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
'@types/node': 25.6.0
|
||||
transitivePeerDependencies:
|
||||
- msw
|
||||
|
||||
why-is-node-running@2.3.0:
|
||||
dependencies:
|
||||
siginfo: 2.0.0
|
||||
stackback: 0.0.2
|
||||
|
||||
zimmerframe@1.1.4: {}
|
||||
@@ -0,0 +1,197 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { execSync } from "child_process";
|
||||
|
||||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const arg = process.argv[2];
|
||||
|
||||
if (!arg) {
|
||||
console.error("Usage: node scripts/bump-version.js <major|minor|patch|X.Y.Z[-suffix]>");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function readCurrentVersion() {
|
||||
const pkg = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
|
||||
return pkg.version;
|
||||
}
|
||||
|
||||
function bump(current, kind) {
|
||||
const match = current.match(/^(\d+)\.(\d+)\.(\d+)(?:-([\w.]+))?$/);
|
||||
if (!match) {
|
||||
console.error(`Cannot parse current version: ${current}`);
|
||||
process.exit(1);
|
||||
}
|
||||
let [, maj, min, patch] = match;
|
||||
maj = Number(maj);
|
||||
min = Number(min);
|
||||
patch = Number(patch);
|
||||
if (kind === "major") {
|
||||
maj += 1;
|
||||
min = 0;
|
||||
patch = 0;
|
||||
} else if (kind === "minor") {
|
||||
min += 1;
|
||||
patch = 0;
|
||||
} else if (kind === "patch") {
|
||||
patch += 1;
|
||||
} else {
|
||||
console.error(`Unknown bump kind: ${kind}`);
|
||||
process.exit(1);
|
||||
}
|
||||
return `${maj}.${min}.${patch}`;
|
||||
}
|
||||
|
||||
const current = readCurrentVersion();
|
||||
let version;
|
||||
if (["major", "minor", "patch"].includes(arg)) {
|
||||
version = bump(current, arg);
|
||||
} else if (/^\d+\.\d+\.\d+(-[\w.]+)?$/.test(arg)) {
|
||||
version = arg;
|
||||
} else {
|
||||
console.error(`Invalid version: ${arg}`);
|
||||
console.error("Expected: major | minor | patch | X.Y.Z[-suffix]");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`Bumping ${current} → ${version}`);
|
||||
|
||||
const changed = [];
|
||||
|
||||
function writeJson(filePath, mutator) {
|
||||
const raw = fs.readFileSync(filePath, "utf8");
|
||||
const data = JSON.parse(raw);
|
||||
mutator(data);
|
||||
const out = JSON.stringify(data, null, 2) + (raw.endsWith("\n") ? "\n" : "");
|
||||
fs.writeFileSync(filePath, out);
|
||||
changed.push(path.relative(root, filePath));
|
||||
}
|
||||
|
||||
function writeText(filePath, mutator) {
|
||||
const original = fs.readFileSync(filePath, "utf8");
|
||||
const updated = mutator(original);
|
||||
if (updated !== original) {
|
||||
fs.writeFileSync(filePath, updated);
|
||||
changed.push(path.relative(root, filePath));
|
||||
}
|
||||
}
|
||||
|
||||
writeJson(path.join(root, "package.json"), (p) => {
|
||||
p.version = version;
|
||||
});
|
||||
|
||||
const pkgLockPath = path.join(root, "package-lock.json");
|
||||
if (fs.existsSync(pkgLockPath)) {
|
||||
writeJson(pkgLockPath, (p) => {
|
||||
p.version = version;
|
||||
if (p.packages && p.packages[""]) {
|
||||
p.packages[""].version = version;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const cargoTomlPath = path.join(root, "src-tauri", "Cargo.toml");
|
||||
writeText(cargoTomlPath, (content) => {
|
||||
const lines = content.split("\n");
|
||||
let inPackage = false;
|
||||
let replaced = false;
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
const sectionMatch = line.match(/^\[([^\]]+)\]\s*$/);
|
||||
if (sectionMatch) {
|
||||
inPackage = sectionMatch[1].trim() === "package";
|
||||
continue;
|
||||
}
|
||||
if (inPackage && /^version\s*=\s*"[^"]+"/.test(line)) {
|
||||
lines[i] = line.replace(/^version\s*=\s*"[^"]+"/, `version = "${version}"`);
|
||||
replaced = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!replaced) {
|
||||
console.error("Failed to update version in Cargo.toml");
|
||||
process.exit(1);
|
||||
}
|
||||
return lines.join("\n");
|
||||
});
|
||||
|
||||
const cargoLockPath = path.join(root, "src-tauri", "Cargo.lock");
|
||||
if (fs.existsSync(cargoLockPath)) {
|
||||
writeText(cargoLockPath, (content) => {
|
||||
return content.replace(
|
||||
/(name = "omniget"\nversion = ")([^"]+)(")/,
|
||||
(_, a, _b, c) => `${a}${version}${c}`
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const tauriConfPath = path.join(root, "src-tauri", "tauri.conf.json");
|
||||
writeJson(tauriConfPath, (conf) => {
|
||||
conf.version = version;
|
||||
});
|
||||
|
||||
const metainfoPath = path.join(root, "flatpak", "wtf.tonho.omniget.metainfo.xml");
|
||||
writeText(metainfoPath, (content) => {
|
||||
let updated = content.replace(
|
||||
/(raw\.githubusercontent\.com\/tonhowtf\/omniget\/v)(\d+\.\d+\.\d+(?:-[\w.]+)?)(\/)/g,
|
||||
(_, a, _b, c) => `${a}${version}${c}`
|
||||
);
|
||||
return updated;
|
||||
});
|
||||
|
||||
const changelogStorePath = path.join(root, "src", "lib", "stores", "changelog-store.svelte.ts");
|
||||
if (fs.existsSync(changelogStorePath)) {
|
||||
writeText(changelogStorePath, (content) =>
|
||||
content.replace(
|
||||
/(currentVersion\s*=\s*")[^"]+(";)/g,
|
||||
(_, a, c) => `${a}${version}${c}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const aboutProjectPath = path.join(root, "src", "routes", "about", "project", "+page.svelte");
|
||||
if (fs.existsSync(aboutProjectPath)) {
|
||||
writeText(aboutProjectPath, (content) =>
|
||||
content.replace(
|
||||
/(const\s+APP_VERSION\s*=\s*")[^"]+(";)/,
|
||||
(_, a, c) => `${a}${version}${c}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (changed.length === 0) {
|
||||
console.error("No files changed — aborting.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("Updated files:");
|
||||
for (const file of changed) {
|
||||
console.log(` - ${file}`);
|
||||
}
|
||||
|
||||
function hasBinary(name) {
|
||||
const probe = process.platform === "win32" ? "where" : "which";
|
||||
try {
|
||||
execSync(`${probe} ${name}`, { stdio: "ignore" });
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const metainfoAbs = path.join(root, "flatpak", "wtf.tonho.omniget.metainfo.xml");
|
||||
if (hasBinary("appstreamcli")) {
|
||||
try {
|
||||
execSync(`appstreamcli validate "${metainfoAbs}"`, { stdio: "inherit" });
|
||||
console.log("appstreamcli validate: OK");
|
||||
} catch {
|
||||
console.error("appstreamcli validate failed — aborting.");
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
console.warn("appstreamcli not found in PATH — skipping metainfo validation.");
|
||||
}
|
||||
|
||||
console.log(`v${version}`);
|
||||
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const i18nDir = path.join(__dirname, "..", "src", "lib", "i18n");
|
||||
const enPath = path.join(i18nDir, "en.json");
|
||||
const outputFile = path.join(i18nDir, "keys.ts");
|
||||
|
||||
function extractKeys(obj, prefix = "") {
|
||||
const keys = [];
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
const fullKey = prefix ? `${prefix}.${key}` : key;
|
||||
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
||||
keys.push(...extractKeys(value, fullKey));
|
||||
} else {
|
||||
keys.push(fullKey);
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
function generateKeysFile() {
|
||||
if (!fs.existsSync(enPath)) {
|
||||
console.error("Error: en.json not found at", enPath);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const enContent = JSON.parse(fs.readFileSync(enPath, "utf-8"));
|
||||
const keys = extractKeys(enContent).sort();
|
||||
|
||||
const grouped = {};
|
||||
for (const key of keys) {
|
||||
const category = key.split(".")[0];
|
||||
if (!grouped[category]) grouped[category] = [];
|
||||
grouped[category].push(key);
|
||||
}
|
||||
|
||||
let output = `/**
|
||||
* i18n Translation Keys Reference
|
||||
*
|
||||
* AUTO-GENERATED FILE
|
||||
* Run: pnpm generate:i18n-keys
|
||||
*
|
||||
* This file provides autocomplete hints for translation keys.
|
||||
* Import the \`t\` function from '$lib/i18n' to use translations.
|
||||
*
|
||||
* Usage:
|
||||
* \`\`\`svelte
|
||||
* <script>
|
||||
* import { t } from '$lib/i18n';
|
||||
* </script>
|
||||
*
|
||||
* <h1>{\$t('app.name')}</h1>
|
||||
* <p>{\$t('queue.items', { count: 5 })}</p>
|
||||
* \`\`\`
|
||||
*/
|
||||
|
||||
export type TranslationKeys =\n`;
|
||||
|
||||
const categories = Object.keys(grouped).sort();
|
||||
for (let i = 0; i < categories.length; i++) {
|
||||
const category = categories[i];
|
||||
const categoryKeys = grouped[category];
|
||||
|
||||
for (let j = 0; j < categoryKeys.length; j++) {
|
||||
const key = categoryKeys[j];
|
||||
output += ` | '${key}'\n`;
|
||||
}
|
||||
|
||||
if (i < categories.length - 1) {
|
||||
output += "\n";
|
||||
}
|
||||
}
|
||||
|
||||
output += " // Allows dynamic/computed keys while preserving autocomplete for known keys\n";
|
||||
output += " | (string & {});\n";
|
||||
|
||||
fs.writeFileSync(outputFile, output, "utf-8");
|
||||
console.log(`Generated ${keys.length} translation keys in keys.ts`);
|
||||
|
||||
const otherLocales = fs
|
||||
.readdirSync(i18nDir)
|
||||
.filter((f) => f.endsWith(".json") && f !== "en.json");
|
||||
|
||||
let hadMismatch = false;
|
||||
const enKeySet = new Set(keys);
|
||||
for (const file of otherLocales) {
|
||||
const p = path.join(i18nDir, file);
|
||||
try {
|
||||
const content = JSON.parse(fs.readFileSync(p, "utf-8"));
|
||||
const localeKeys = new Set(extractKeys(content));
|
||||
const missing = [...enKeySet].filter((k) => !localeKeys.has(k));
|
||||
const extra = [...localeKeys].filter((k) => !enKeySet.has(k));
|
||||
if (missing.length > 0 || extra.length > 0) {
|
||||
hadMismatch = true;
|
||||
console.warn(`[${file}] missing: ${missing.length}, extra: ${extra.length}`);
|
||||
}
|
||||
} catch (err) {
|
||||
hadMismatch = true;
|
||||
console.warn(`[${file}] failed to parse: ${err.message}`);
|
||||
}
|
||||
}
|
||||
if (!hadMismatch) {
|
||||
console.log(`All ${otherLocales.length} other locales in sync with en.json`);
|
||||
}
|
||||
}
|
||||
|
||||
generateKeysFile();
|
||||
@@ -8,6 +8,12 @@
|
||||
--padding: 10px;
|
||||
--border-radius: 11px;
|
||||
--sidebar-width: 80px;
|
||||
--bottom-nav-height: 44px;
|
||||
--mobile-nav-clearance: calc(var(--bottom-nav-height) + env(safe-area-inset-bottom, 0px) + var(--padding));
|
||||
--safe-area-top: env(safe-area-inset-top, 0px);
|
||||
--safe-area-right: env(safe-area-inset-right, 0px);
|
||||
--safe-area-bottom: env(safe-area-inset-bottom, 0px);
|
||||
--safe-area-left: env(safe-area-inset-left, 0px);
|
||||
|
||||
--font-system: 'DM Sans', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
--font-mono: 'IBM Plex Mono', 'DejaVu Sans Mono', 'Liberation Mono', 'Noto Sans Mono', 'Ubuntu Mono', 'Courier New', monospace;
|
||||
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
declare const __COMMIT_HASH__: string;
|
||||
declare const __GIT_BRANCH__: string;
|
||||
declare const __APP_VERSION__: string;
|
||||
declare const __BUILD_DATE__: string;
|
||||
|
||||
declare namespace App {}
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||
<title>OmniGet</title>
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
|
||||
const invokeMock = vi.fn();
|
||||
|
||||
vi.mock("@tauri-apps/api/core", () => ({
|
||||
invoke: (cmd: string, args: unknown) => invokeMock(cmd, args),
|
||||
}));
|
||||
|
||||
import { pluginInvoke } from "./plugin-invoke";
|
||||
|
||||
describe("pluginInvoke", () => {
|
||||
beforeEach(() => {
|
||||
invokeMock.mockReset();
|
||||
});
|
||||
|
||||
it("routes through the plugin_command Tauri handler", async () => {
|
||||
invokeMock.mockResolvedValueOnce({ ok: true });
|
||||
await pluginInvoke("courses", "hotmart_login", { email: "e", password: "p" });
|
||||
expect(invokeMock).toHaveBeenCalledTimes(1);
|
||||
expect(invokeMock.mock.calls[0][0]).toBe("plugin_command");
|
||||
});
|
||||
|
||||
it("passes pluginId, command, and args in the payload", async () => {
|
||||
invokeMock.mockResolvedValueOnce(undefined);
|
||||
await pluginInvoke("telegram", "list_chats", { limit: 20 });
|
||||
expect(invokeMock.mock.calls[0][1]).toEqual({
|
||||
pluginId: "telegram",
|
||||
command: "list_chats",
|
||||
args: { limit: 20 },
|
||||
});
|
||||
});
|
||||
|
||||
it("defaults args to an empty object when not provided", async () => {
|
||||
invokeMock.mockResolvedValueOnce("ok");
|
||||
await pluginInvoke("convert", "list_formats");
|
||||
expect(invokeMock.mock.calls[0][1]).toEqual({
|
||||
pluginId: "convert",
|
||||
command: "list_formats",
|
||||
args: {},
|
||||
});
|
||||
});
|
||||
|
||||
it("propagates the resolved value", async () => {
|
||||
invokeMock.mockResolvedValueOnce({ success: true, count: 3 });
|
||||
const result = await pluginInvoke<{ success: boolean; count: number }>(
|
||||
"courses",
|
||||
"list",
|
||||
);
|
||||
expect(result).toEqual({ success: true, count: 3 });
|
||||
});
|
||||
|
||||
it("propagates rejection from invoke", async () => {
|
||||
invokeMock.mockRejectedValueOnce(new Error("plugin not loaded"));
|
||||
await expect(pluginInvoke("missing", "cmd")).rejects.toThrow(
|
||||
"plugin not loaded",
|
||||
);
|
||||
});
|
||||
});
|
||||
+56
-25
@@ -1,32 +1,63 @@
|
||||
import { defineConfig } from "vite";
|
||||
import { sveltekit } from "@sveltejs/kit/vite";
|
||||
import { execSync } from "child_process";
|
||||
import { readFileSync } from "fs";
|
||||
|
||||
// @ts-expect-error process is a nodejs global
|
||||
const host = process.env.TAURI_DEV_HOST;
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig(async () => ({
|
||||
plugins: [sveltekit()],
|
||||
function getGitInfo() {
|
||||
try {
|
||||
return {
|
||||
hash: execSync("git rev-parse HEAD").toString().trim(),
|
||||
branch: execSync("git rev-parse --abbrev-ref HEAD").toString().trim(),
|
||||
};
|
||||
} catch {
|
||||
return { hash: "unknown", branch: "unknown" };
|
||||
}
|
||||
}
|
||||
|
||||
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
||||
//
|
||||
// 1. prevent Vite from obscuring rust errors
|
||||
clearScreen: false,
|
||||
// 2. tauri expects a fixed port, fail if that port is not available
|
||||
server: {
|
||||
port: 1420,
|
||||
strictPort: true,
|
||||
host: host || false,
|
||||
hmr: host
|
||||
? {
|
||||
protocol: "ws",
|
||||
host,
|
||||
port: 1421,
|
||||
}
|
||||
: undefined,
|
||||
watch: {
|
||||
// 3. tell Vite to ignore watching `src-tauri`
|
||||
ignored: ["**/src-tauri/**"],
|
||||
function getVersion() {
|
||||
try {
|
||||
return JSON.parse(readFileSync("package.json", "utf8")).version;
|
||||
} catch {
|
||||
return "0.0.0";
|
||||
}
|
||||
}
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig(async () => {
|
||||
const gitInfo = getGitInfo();
|
||||
|
||||
return {
|
||||
plugins: [sveltekit()],
|
||||
|
||||
define: {
|
||||
__COMMIT_HASH__: JSON.stringify(gitInfo.hash),
|
||||
__GIT_BRANCH__: JSON.stringify(gitInfo.branch),
|
||||
__APP_VERSION__: JSON.stringify(getVersion()),
|
||||
__BUILD_DATE__: JSON.stringify(new Date().toISOString().split("T")[0]),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
||||
//
|
||||
// 1. prevent Vite from obscuring rust errors
|
||||
clearScreen: false,
|
||||
// 2. tauri expects a fixed port, fail if that port is not available
|
||||
server: {
|
||||
port: 1420,
|
||||
strictPort: true,
|
||||
host: host || false,
|
||||
hmr: host
|
||||
? {
|
||||
protocol: "ws",
|
||||
host,
|
||||
port: 1421,
|
||||
}
|
||||
: undefined,
|
||||
watch: {
|
||||
// 3. tell Vite to ignore watching `src-tauri`
|
||||
ignored: ["**/src-tauri/**"],
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
import { defineConfig } from "vitest/config";
|
||||
import path from "path";
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
include: ["src/**/*.test.ts"],
|
||||
environment: "node",
|
||||
globals: false,
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
$lib: path.resolve(__dirname, "src/lib"),
|
||||
$components: path.resolve(__dirname, "src/components"),
|
||||
},
|
||||
},
|
||||
define: {
|
||||
__COMMIT_HASH__: JSON.stringify("test-commit"),
|
||||
__GIT_BRANCH__: JSON.stringify("test-branch"),
|
||||
__APP_VERSION__: JSON.stringify("0.0.0-test"),
|
||||
__BUILD_DATE__: JSON.stringify("2026-04-13"),
|
||||
},
|
||||
});
|
||||
Reference in new issue
Block a user