From 44f4b1d4c08014e01a3d075f729285ddc871185b Mon Sep 17 00:00:00 2001 From: XxUnkn0wnxX Date: Fri, 17 Apr 2026 03:13:42 +1000 Subject: [PATCH 01/11] repo: ignore local scratch and agent artifacts Fixes - ignore tmp/ scratch workspace in the repo root - ignore AGENTS.md session instructions file - ignore local Python virtualenvs under .venv/ Why - keeps local-only Codex and Python workflow files out of commits - prevents accidental staging of temporary or machine-specific files --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 48bef8880..bf0c17f46 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,8 @@ src/package-lock.json *.asar _* miniSrc/ +tmp/ +AGENTS.md +.venv/ -*.crswap # crostini tmp files \ No newline at end of file +*.crswap # crostini tmp files From 3268b0b76ea02ae89367bc1d531d3b2ebe8f2d68 Mon Sep 17 00:00:00 2001 From: XxUnkn0wnxX Date: Fri, 17 Apr 2026 03:55:18 +1000 Subject: [PATCH 02/11] fix: restore OpenAsar settings entry with BetterDiscord installed Fixes - replace the old footer-gated settings injection flow in `src/mainWindow.js` - add a hybrid settings hook that patches Discord's settings layout when available - keep a DOM fallback that matches the current BetterDiscord settings DOM - anchor insertion near `Developer` / `Log Out` when `Advanced` is absent Compatibility - avoid hard-coded creation of Discord-styled nodes by cloning native sidebar items - scope mutation observation to the settings area instead of the full document - preserve a fallback path when the internal layout module is unavailable Updater - prevent extra-suffixed local builds from overwriting themselves via `src/asarUpdate.js` - keep normal release-style auto-update behavior for upstream builds Verification - `node -c src/mainWindow.js` - `node -c src/asarUpdate.js` - packed local test archives under `tmp/openasar-test/app.asar` - confirmed local test builds now survive relaunch instead of being replaced on first launch --- WORKLOG.md | 19 ++++ src/asarUpdate.js | 5 +- src/mainWindow.js | 214 +++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 223 insertions(+), 15 deletions(-) create mode 100644 WORKLOG.md diff --git a/WORKLOG.md b/WORKLOG.md new file mode 100644 index 000000000..6ad0ed393 --- /dev/null +++ b/WORKLOG.md @@ -0,0 +1,19 @@ +# Work Log + +## 2026-04-17 + +### BetterDiscord settings compatibility + +- Investigated OpenAsar issue `#222`, PR `#225`, and the `TirOFlanc/OpenAsar` fork. +- Reviewed the local BetterDiscord codebase to understand how it patches Discord's settings UI. +- Identified the main failure mode in `src/mainWindow.js`: OpenAsar's settings item injection was incorrectly gated behind version/footer detection. +- Reworked the settings injection flow in `src/mainWindow.js` so version injection and menu item injection are independent. +- Kept the injected menu entry based on cloning native Discord sidebar items instead of creating fresh nodes with hard-coded Discord classes. +- Added a settings-area-scoped `MutationObserver` so the OpenAsar item can be restored after BetterDiscord or Discord rerenders the sidebar. +- Kept a slower interval fallback to retry injection without relying on a global full-page observer. +- Added a hybrid settings injection path that first tries to patch Discord's internal settings layout and then falls back to DOM injection. +- Matched the current BetterDiscord settings DOM where `Advanced` is absent and `Developer` / `Log Out` are the stable fallback anchors. +- Identified that local test builds were being overwritten on first launch by OpenAsar's self-updater. +- Updated `src/asarUpdate.js` so extra-suffixed local builds are treated as dev builds and skip self-replacement. +- Verified the updated file with `node -c src/mainWindow.js`. +- Verified the repo still packs locally by producing `tmp/pack-test/app.asar`. diff --git a/src/asarUpdate.js b/src/asarUpdate.js index fd4113281..449c1488f 100644 --- a/src/asarUpdate.js +++ b/src/asarUpdate.js @@ -12,9 +12,12 @@ const redirs = url => new Promise(res => get(url, r => { // Minimal wrapper arou module.exports = async () => { // (Try) update asar if (!oaVersion.includes('-')) return; + const versionParts = oaVersion.split('-'); + if (versionParts.length !== 2) return log('AsarUpdate', 'Skipping local build', oaVersion); + log('AsarUpdate', 'Updating...'); - const res = (await redirs(`https://github.com/GooseMod/OpenAsar/releases/download/${oaVersion.split('-')[0]}/app.asar`)); + const res = (await redirs(`https://github.com/GooseMod/OpenAsar/releases/download/${versionParts[0]}/app.asar`)); let data = []; res.on('data', d => { diff --git a/src/mainWindow.js b/src/mainWindow.js index 440bacbf4..c47937166 100644 --- a/src/mainWindow.js +++ b/src/mainWindow.js @@ -26,32 +26,218 @@ const themesync = async () => { }; // Settings injection -setInterval(() => { - const versionInfo = document.querySelector('[class*="sidebar"] [class*="compactInfo"]'); - if (!versionInfo || document.getElementById('openasar-ver')) return; +const openOpenAsarSettings = () => DiscordNative.ipc.send('DISCORD_UPDATED_QUOTES', 'o'); +const firstNode = (...getters) => { + for (const getter of getters) { + const node = getter(); + if (node) return node; + } +}; + +const sidebarHasSettingsMarkers = sidebar => !!sidebar && firstNode( + () => sidebar.querySelector('[data-settings-sidebar-item]'), + () => sidebar.querySelector('[data-list-item-id^="settings-sidebar___"]'), + () => sidebar.parentElement?.querySelector('.bd-version-info'), + () => sidebar.querySelector('[class*="compactInfo"]'), + () => sidebar.querySelector('[aria-label="User Settings"]'), + () => sidebar.querySelector('[aria-label="App Settings"]') +); + +const findSettingsSidebar = () => { + const candidates = [ + ...document.querySelectorAll('[data-list-id="settings-sidebar"]'), + ...document.querySelectorAll('[class*="sidebar"] [class*="nav"]') + ]; + + return candidates.find(sidebarHasSettingsMarkers); +}; + +const findVersionInfo = () => firstNode( + () => document.querySelector('.bd-version-info > div:nth-child(2)'), + () => document.querySelector('.bd-version-info'), + () => document.querySelector('[class*="sidebar"] [class*="compactInfo"]'), + () => [...document.querySelectorAll('[class*="sidebar"] [class*="info"] [class*="line"]')].find(x => x.textContent?.startsWith('Host ')) +); + +let webpackRequire; +const getWebpackRequire = () => { + if (webpackRequire) return webpackRequire; + const chunk = window.webpackChunkdiscord_app; + if (!chunk?.push) return; + + chunk.push([[`openasar_${Date.now()}`], {}, req => webpackRequire = req]); + chunk.pop?.(); + return webpackRequire; +}; + +const getWebpackModule = filter => { + const req = getWebpackRequire(); + if (!req?.c) return; + + for (const mod of Object.values(req.c)) { + const exp = mod?.exports; + if (!exp) continue; + + if (filter(exp)) return exp; + if (exp.default && filter(exp.default)) return exp.default; + + for (const nested of Object.values(exp)) { + if (nested && filter(nested)) return nested; + } + } +}; + +const getLayoutItemTitle = item => { + try { + const title = item?.useTitle?.(); + if (typeof title === 'string') return title; + if (typeof title?.props?.children === 'string') return title.props.children; + if (Array.isArray(title?.props?.children)) return title.props.children.join(''); + if (title && typeof title.toString === 'function') return String(title); + } catch { } + + return ''; +}; + +const patchSettingsLayoutItems = items => { + if (!Array.isArray(items) || items.some(x => x?.key === 'openasar_item' || getLayoutItemTitle(x) === 'OpenAsar')) return items; + + const logoutIndex = items.findIndex(x => x?.key?.includes?.('logout') || getLayoutItemTitle(x) === 'Log Out'); + const developerIndex = items.findIndex(x => x?.key?.includes?.('developer') || getLayoutItemTitle(x) === 'Developer'); + const anchorIndex = logoutIndex !== -1 ? logoutIndex : developerIndex; + if (anchorIndex === -1) return items; + + const template = items[anchorIndex]; + const openAsarItem = { + ...template, + key: 'openasar_item', + id: 'openasar_item', + useTitle: () => 'OpenAsar', + onClick: openOpenAsarSettings, + usePredicate: () => true, + useSearchTerms: () => ['openasar'], + buildLayout: () => [] + }; + + const insertAt = logoutIndex !== -1 ? logoutIndex : developerIndex + 1; + return [ + ...items.slice(0, insertAt), + openAsarItem, + ...items.slice(insertAt) + ]; +}; + +const patchSettingsLayout = () => { + const rootLayout = getWebpackModule(m => m?.key === '$Root' && typeof m.buildLayout === 'function'); + if (!rootLayout || rootLayout.__openasarPatched) return; + + const originalBuildLayout = rootLayout.buildLayout; + rootLayout.buildLayout = function(...args) { + const sections = originalBuildLayout.apply(this, args); + if (!Array.isArray(sections)) return sections; + + return sections.map(section => { + if (!section?.buildLayout || section.__openasarSectionPatched) return section; + + const originalSectionBuild = section.buildLayout; + section.buildLayout = function(...sectionArgs) { + return patchSettingsLayoutItems(originalSectionBuild.apply(this, sectionArgs)); + }; + section.__openasarSectionPatched = true; + return section; + }); + }; + rootLayout.__openasarPatched = true; +}; + +const findAdvancedItem = () => { + const sidebar = findSettingsSidebar(); + return firstNode( + () => sidebar?.querySelector('[data-list-item-id="settings-sidebar___advanced_sidebar_item"]'), + () => sidebar?.querySelector('[data-list-item-id*="advanced"]'), + () => sidebar?.querySelector('[data-settings-sidebar-item="advanced_panel"]')?.querySelector('[class*="item"]'), + () => sidebar?.querySelector('[data-settings-sidebar-item="developer_panel"] [role="listitem"]'), + () => sidebar?.querySelector('[data-list-item-id="settings-sidebar___developer_sidebar_item"]'), + () => sidebar?.querySelector('[data-list-item-id="settings-sidebar___logout_sidebar_item"]'), + () => { + const item = sidebar?.querySelector('[data-settings-sidebar-item="betterdiscord_settings_panel"]'); + return item?.previousElementSibling?.querySelector?.('[role="listitem"]') ?? item?.querySelector?.('[role="listitem"]'); + }, + () => document.querySelector('[data-tab-id="Advanced"]'), + () => [...(sidebar?.querySelectorAll('[class*="item"]') ?? [])].find(x => x.textContent === 'Advanced'), + () => [...document.querySelectorAll('[class*="item"]')].find(x => x.textContent === 'Advanced') + ); +}; + +const injectVersionInfo = () => { + if (document.getElementById('openasar-ver')) return; + + const versionInfo = findVersionInfo(); + if (!versionInfo) return; const oaVersionInfo = versionInfo.cloneNode(true); - const oaVersion = oaVersionInfo.children[0]; + const oaVersion = oaVersionInfo.children?.[0] ?? oaVersionInfo; oaVersion.id = 'openasar-ver'; oaVersion.textContent = 'OpenAsar ()'; - oaVersion.onclick = () => DiscordNative.ipc.send('DISCORD_UPDATED_QUOTES', 'o'); + oaVersion.onclick = openOpenAsarSettings; - oaVersionInfo.textContent = ''; - oaVersionInfo.appendChild(oaVersion); - versionInfo.parentElement.parentElement.lastElementChild.insertAdjacentElement('beforebegin', oaVersionInfo); + if (oaVersionInfo !== oaVersion) { + oaVersionInfo.textContent = ''; + oaVersionInfo.appendChild(oaVersion); + } + const versionTarget = versionInfo.parentElement?.parentElement?.lastElementChild; + if (versionTarget) versionTarget.insertAdjacentElement('beforebegin', oaVersionInfo); + else versionInfo.insertAdjacentElement('afterend', oaVersionInfo); +}; + +const injectSettingsItem = () => { if (document.getElementById('openasar-item')) return; - let advanced = document.querySelector('[data-list-item-id="settings-sidebar___advanced_sidebar_item"]'); - if (!advanced) advanced = document.querySelector('[class*="sidebar"] [class*="nav"] > [class*="section"]:nth-child(3) > :last-child'); - if (!advanced) advanced = [...document.querySelectorAll('[class*="item"]')].find(x => x.textContent === 'Advanced'); + + const advanced = findAdvancedItem(); + if (!advanced) return; const oaSetting = advanced.cloneNode(true); - oaSetting.querySelector('[class*="text"]').textContent = 'OpenAsar'; + const text = oaSetting.querySelector('[class*="text"]') ?? oaSetting; + oaSetting.id = 'openasar-item'; - oaSetting.onclick = oaVersion.onclick; + oaSetting.setAttribute('aria-label', 'OpenAsar'); + oaSetting.setAttribute('data-openasar', 'true'); + if (oaSetting.hasAttribute('data-list-item-id')) oaSetting.setAttribute('data-list-item-id', 'settings-sidebar___openasar_sidebar_item'); + if (oaSetting.hasAttribute('data-tab-id')) oaSetting.setAttribute('data-tab-id', 'OpenAsar'); + + text.textContent = 'OpenAsar'; + oaSetting.onclick = openOpenAsarSettings; advanced.insertAdjacentElement('afterend', oaSetting); -}, 800); +}; + +let settingsObserver; +let settingsObserverTarget; +const attachSettingsObserver = () => { + const sidebar = findSettingsSidebar(); + const nextTarget = sidebar?.closest('[class*="sidebar"]') ?? sidebar?.parentElement ?? sidebar; + if (!nextTarget || nextTarget === settingsObserverTarget) return; + + settingsObserver?.disconnect(); + settingsObserverTarget = nextTarget; + settingsObserver = new MutationObserver(() => { + injectVersionInfo(); + injectSettingsItem(); + attachSettingsObserver(); + }); + settingsObserver.observe(nextTarget, { childList: true, subtree: true }); +}; + +const syncSettingsInjection = () => { + patchSettingsLayout(); + injectVersionInfo(); + injectSettingsItem(); + attachSettingsObserver(); +}; + +setInterval(syncSettingsInjection, 1500); +syncSettingsInjection(); const injCSS = x => { const el = document.createElement('style'); From 5a0c2a3dad67c117cede60da460f87d63ef90aec Mon Sep 17 00:00:00 2001 From: XxUnkn0wnxX Date: Fri, 17 Apr 2026 04:00:34 +1000 Subject: [PATCH 03/11] build: add pack script flag to disable self-updates for local test builds Build - add `scripts/pack.js` to produce local `app.asar` builds from the repo - support `--version`, `--output`, and `--disable-autoupdate` build options - stamp `oaDisableAutoUpdate` into the packed build through `src/index.js` Updater - keep source behavior unchanged by default with self-updates still enabled - disable self-updates only when the build flag is explicitly passed - log when a build was packed with auto-update disabled Docs - record the local test build command in `WORKLOG.md` --- WORKLOG.md | 6 ++- scripts/pack.js | 118 ++++++++++++++++++++++++++++++++++++++++++++++ src/asarUpdate.js | 6 +-- src/index.js | 3 +- 4 files changed, 128 insertions(+), 5 deletions(-) create mode 100644 scripts/pack.js diff --git a/WORKLOG.md b/WORKLOG.md index 6ad0ed393..9526ffdea 100644 --- a/WORKLOG.md +++ b/WORKLOG.md @@ -14,6 +14,10 @@ - Added a hybrid settings injection path that first tries to patch Discord's internal settings layout and then falls back to DOM injection. - Matched the current BetterDiscord settings DOM where `Advanced` is absent and `Developer` / `Log Out` are the stable fallback anchors. - Identified that local test builds were being overwritten on first launch by OpenAsar's self-updater. -- Updated `src/asarUpdate.js` so extra-suffixed local builds are treated as dev builds and skip self-replacement. +- Added a build-time auto-update flag so local test archives can be packed with self-updates disabled intentionally. +- Kept the default source behavior with self-updates enabled unless the build flag is explicitly set. +- Added `scripts/pack.js` to build local `app.asar` files with options such as `--disable-autoupdate`. +- Local test build command: + `node scripts/pack.js --disable-autoupdate --version nightly-$(git rev-parse --short HEAD)-localtest --output tmp/openasar-build/app.asar` - Verified the updated file with `node -c src/mainWindow.js`. - Verified the repo still packs locally by producing `tmp/pack-test/app.asar`. diff --git a/scripts/pack.js b/scripts/pack.js new file mode 100644 index 000000000..1d5601d1f --- /dev/null +++ b/scripts/pack.js @@ -0,0 +1,118 @@ +const { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } = require('fs'); +const { join, resolve } = require('path'); +const { spawnSync } = require('child_process'); + +const root = resolve(__dirname, '..'); +const tmpRoot = join(root, 'tmp', 'pack-build'); + +const args = process.argv.slice(2); +let disableAutoUpdate = false; +let version; +let output = join(root, 'tmp', 'openasar-build', 'app.asar'); + +for (let i = 0; i < args.length; i++) { + const arg = args[i]; + + if (arg === '--disable-autoupdate') { + disableAutoUpdate = true; + continue; + } + + if (arg === '--version') { + version = args[++i]; + continue; + } + + if (arg === '--output') { + output = resolve(args[++i]); + continue; + } + + if (arg === '--help') { + console.log('Usage: node scripts/pack.js [--disable-autoupdate] [--version ] [--output ]'); + process.exit(0); + } + + throw new Error(`Unknown argument: ${arg}`); +} + +if (!version) { + const git = spawnSync('git', ['rev-parse', '--short', 'HEAD'], { + cwd: root, + encoding: 'utf8' + }); + + const shortSha = git.status === 0 ? git.stdout.trim() : 'local'; + version = `nightly-${shortSha}`; +} + +const stripCode = code => code + .replace(/(^| )\/\/.*$/gm, '') + .replaceAll('const ', 'const~') + .replaceAll('let ', 'let~') + .replaceAll('var ', 'var~') + .replaceAll('class ', 'class~') + .replace(/get [^=}]/g, _ => _.replaceAll(' ', '~')) + .replaceAll('delete ', 'delete~') + .replaceAll(' extends ', '~extends~') + .replaceAll('typeof ', 'typeof~') + .replaceAll(' of ', '~of~') + .replaceAll(' in ', '~in~') + .replaceAll('case ', 'case~') + .replaceAll('await ', 'await~') + .replaceAll('new ', 'new~') + .replaceAll('return ', 'return~') + .replaceAll('function ', 'function~') + .replaceAll('void ', 'void~') + .replaceAll('throw ', 'throw~') + .replaceAll('async ', 'async~') + .replaceAll('else ', 'else~') + .replace('/([0-9]+) files/', '/([0-9]+)~files/') + .replace(/((['"`])[\s\S]*?\2)|[ \n]/g, (_, g1) => g1 || '') + .replaceAll('~', ' ') + .replaceAll('? ?', '??'); + +const fixHtml = code => code + .replaceAll(' loop', '~loop') + .replaceAll(' autoplay', '~autoplay') + .replaceAll(' src', '~src') + .replaceAll(' id', '~id'); + +const stripJs = path => writeFileSync(path, stripCode(readFileSync(path, 'utf8'))); +const stripHtml = path => writeFileSync(path, stripCode(fixHtml(readFileSync(path, 'utf8')))); +const stripJson = path => { + const data = JSON.parse(readFileSync(path, 'utf8')); + if (data.description) delete data.description; + writeFileSync(path, JSON.stringify(data)); +}; + +const stripTree = dirPath => readdirSync(dirPath, { withFileTypes: true }).forEach(entry => { + const path = join(dirPath, entry.name); + + if (entry.isDirectory()) return stripTree(path); + if (entry.name.endsWith('.js')) return stripJs(path); + if (entry.name.endsWith('.json')) return stripJson(path); + if (entry.name.endsWith('.html')) return stripHtml(path); +}); + +rmSync(tmpRoot, { recursive: true, force: true }); +mkdirSync(tmpRoot, { recursive: true }); +cpSync(join(root, 'src'), join(tmpRoot, 'src'), { recursive: true }); + +const indexPath = join(tmpRoot, 'src', 'index.js'); +let indexCode = readFileSync(indexPath, 'utf8'); +indexCode = indexCode.replace("global.oaVersion = 'nightly';", `global.oaVersion = '${version}';`); +indexCode = indexCode.replace('', disableAutoUpdate ? 'true' : 'false'); +writeFileSync(indexPath, indexCode); + +stripTree(join(tmpRoot, 'src')); + +mkdirSync(resolve(output, '..'), { recursive: true }); +const asar = spawnSync('asar', ['pack', join(tmpRoot, 'src'), output], { + cwd: root, + stdio: 'inherit' +}); + +if (asar.status !== 0) process.exit(asar.status ?? 1); + +if (existsSync(output)) console.log(output); diff --git a/src/asarUpdate.js b/src/asarUpdate.js index 449c1488f..76fdc555f 100644 --- a/src/asarUpdate.js +++ b/src/asarUpdate.js @@ -11,13 +11,13 @@ const redirs = url => new Promise(res => get(url, r => { // Minimal wrapper arou })); module.exports = async () => { // (Try) update asar + if (global.oaDisableAutoUpdate) return log('AsarUpdate', 'Skipping build-configured auto-update disable'); if (!oaVersion.includes('-')) return; - const versionParts = oaVersion.split('-'); - if (versionParts.length !== 2) return log('AsarUpdate', 'Skipping local build', oaVersion); + const releaseChannel = oaVersion.split('-')[0]; log('AsarUpdate', 'Updating...'); - const res = (await redirs(`https://github.com/GooseMod/OpenAsar/releases/download/${versionParts[0]}/app.asar`)); + const res = (await redirs(`https://github.com/GooseMod/OpenAsar/releases/download/${releaseChannel}/app.asar`)); let data = []; res.on('data', d => { diff --git a/src/index.js b/src/index.js index 41fe9bde5..293a846e1 100644 --- a/src/index.js +++ b/src/index.js @@ -3,6 +3,7 @@ const { join } = require('path'); global.log = (area, ...args) => console.log(`[\x1b[38;2;88;101;242mOpenAsar\x1b[0m > ${area}]`, ...args); // Make log global for easy usage everywhere global.oaVersion = 'nightly'; +global.oaDisableAutoUpdate = '' === 'true'; log('Init', 'OpenAsar', oaVersion); @@ -37,4 +38,4 @@ if (process.argv.includes('--overlay-host')) { // If overlay require('discord_overlay2/standalone_host.js'); // Start overlay } else { require('./bootstrap')(); // Start bootstrap -} \ No newline at end of file +} From d9b75022dc4f71ecd0e89721c56c4988d49772cd Mon Sep 17 00:00:00 2001 From: XxUnkn0wnxX Date: Fri, 17 Apr 2026 04:08:02 +1000 Subject: [PATCH 04/11] fix: keep BetterDiscord compatibility on DOM-only settings injection Fixes - remove the experimental webpack settings layout patch from `src/mainWindow.js` - keep the BetterDiscord-aware DOM injection path that targets the current settings sidebar structure - retain the `Developer` / `Log Out` fallback anchors used by current Discord builds Reasoning - local testing showed the updater was the real source of the one-session regression - after disabling self-update in test builds, the DOM-only approach remained stable - keeping the simpler DOM-only path reduces maintenance and review risk versus the hybrid patch Docs - update `WORKLOG.md` to reflect that the final code path is DOM-only --- WORKLOG.md | 4 ++- src/mainWindow.js | 92 ----------------------------------------------- 2 files changed, 3 insertions(+), 93 deletions(-) diff --git a/WORKLOG.md b/WORKLOG.md index 9526ffdea..aec50c7aa 100644 --- a/WORKLOG.md +++ b/WORKLOG.md @@ -11,12 +11,14 @@ - Kept the injected menu entry based on cloning native Discord sidebar items instead of creating fresh nodes with hard-coded Discord classes. - Added a settings-area-scoped `MutationObserver` so the OpenAsar item can be restored after BetterDiscord or Discord rerenders the sidebar. - Kept a slower interval fallback to retry injection without relying on a global full-page observer. -- Added a hybrid settings injection path that first tries to patch Discord's internal settings layout and then falls back to DOM injection. +- Evaluated a hybrid settings injection path that patched Discord's internal settings layout alongside DOM injection. - Matched the current BetterDiscord settings DOM where `Advanced` is absent and `Developer` / `Log Out` are the stable fallback anchors. - Identified that local test builds were being overwritten on first launch by OpenAsar's self-updater. - Added a build-time auto-update flag so local test archives can be packed with self-updates disabled intentionally. - Kept the default source behavior with self-updates enabled unless the build flag is explicitly set. - Added `scripts/pack.js` to build local `app.asar` files with options such as `--disable-autoupdate`. +- Confirmed the DOM-only settings injection works once local test builds stop being overwritten by the self-updater. +- Reverted the experimental hybrid settings patch and kept the DOM-only fix for the final code path. - Local test build command: `node scripts/pack.js --disable-autoupdate --version nightly-$(git rev-parse --short HEAD)-localtest --output tmp/openasar-build/app.asar` - Verified the updated file with `node -c src/mainWindow.js`. diff --git a/src/mainWindow.js b/src/mainWindow.js index c47937166..fbe6a2abb 100644 --- a/src/mainWindow.js +++ b/src/mainWindow.js @@ -59,97 +59,6 @@ const findVersionInfo = () => firstNode( () => [...document.querySelectorAll('[class*="sidebar"] [class*="info"] [class*="line"]')].find(x => x.textContent?.startsWith('Host ')) ); -let webpackRequire; -const getWebpackRequire = () => { - if (webpackRequire) return webpackRequire; - const chunk = window.webpackChunkdiscord_app; - if (!chunk?.push) return; - - chunk.push([[`openasar_${Date.now()}`], {}, req => webpackRequire = req]); - chunk.pop?.(); - return webpackRequire; -}; - -const getWebpackModule = filter => { - const req = getWebpackRequire(); - if (!req?.c) return; - - for (const mod of Object.values(req.c)) { - const exp = mod?.exports; - if (!exp) continue; - - if (filter(exp)) return exp; - if (exp.default && filter(exp.default)) return exp.default; - - for (const nested of Object.values(exp)) { - if (nested && filter(nested)) return nested; - } - } -}; - -const getLayoutItemTitle = item => { - try { - const title = item?.useTitle?.(); - if (typeof title === 'string') return title; - if (typeof title?.props?.children === 'string') return title.props.children; - if (Array.isArray(title?.props?.children)) return title.props.children.join(''); - if (title && typeof title.toString === 'function') return String(title); - } catch { } - - return ''; -}; - -const patchSettingsLayoutItems = items => { - if (!Array.isArray(items) || items.some(x => x?.key === 'openasar_item' || getLayoutItemTitle(x) === 'OpenAsar')) return items; - - const logoutIndex = items.findIndex(x => x?.key?.includes?.('logout') || getLayoutItemTitle(x) === 'Log Out'); - const developerIndex = items.findIndex(x => x?.key?.includes?.('developer') || getLayoutItemTitle(x) === 'Developer'); - const anchorIndex = logoutIndex !== -1 ? logoutIndex : developerIndex; - if (anchorIndex === -1) return items; - - const template = items[anchorIndex]; - const openAsarItem = { - ...template, - key: 'openasar_item', - id: 'openasar_item', - useTitle: () => 'OpenAsar', - onClick: openOpenAsarSettings, - usePredicate: () => true, - useSearchTerms: () => ['openasar'], - buildLayout: () => [] - }; - - const insertAt = logoutIndex !== -1 ? logoutIndex : developerIndex + 1; - return [ - ...items.slice(0, insertAt), - openAsarItem, - ...items.slice(insertAt) - ]; -}; - -const patchSettingsLayout = () => { - const rootLayout = getWebpackModule(m => m?.key === '$Root' && typeof m.buildLayout === 'function'); - if (!rootLayout || rootLayout.__openasarPatched) return; - - const originalBuildLayout = rootLayout.buildLayout; - rootLayout.buildLayout = function(...args) { - const sections = originalBuildLayout.apply(this, args); - if (!Array.isArray(sections)) return sections; - - return sections.map(section => { - if (!section?.buildLayout || section.__openasarSectionPatched) return section; - - const originalSectionBuild = section.buildLayout; - section.buildLayout = function(...sectionArgs) { - return patchSettingsLayoutItems(originalSectionBuild.apply(this, sectionArgs)); - }; - section.__openasarSectionPatched = true; - return section; - }); - }; - rootLayout.__openasarPatched = true; -}; - const findAdvancedItem = () => { const sidebar = findSettingsSidebar(); return firstNode( @@ -230,7 +139,6 @@ const attachSettingsObserver = () => { }; const syncSettingsInjection = () => { - patchSettingsLayout(); injectVersionInfo(); injectSettingsItem(); attachSettingsObserver(); From 98a5cafcd664c0a6f81210445a1de9cfd79865b1 Mon Sep 17 00:00:00 2001 From: XxUnkn0wnxX Date: Fri, 17 Apr 2026 04:27:10 +1000 Subject: [PATCH 05/11] docs: move local work log into docs and link build docs Docs - add docs/build.md with local packing instructions based on the nightly workflow - add docs/changelog.md as the tracked location for the local OpenAsar change log - link both docs pages from README.md Cleanup - remove the root WORKLOG.md now that the changelog lives under docs/ Verification - confirmed the docs files exist in the repo tree - kept the change scoped to develop only --- README.md | 7 ++++++ docs/build.md | 40 +++++++++++++++++++++++++++++++++ WORKLOG.md => docs/changelog.md | 2 +- 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 docs/build.md rename WORKLOG.md => docs/changelog.md (99%) diff --git a/README.md b/README.md index 139af6698..f3185de1a 100644 --- a/README.md +++ b/README.md @@ -19,5 +19,12 @@ ## [Install Guide](https://github.com/GooseMod/OpenAsar/wiki/Install-Guide) +## Local Build +See [docs/build.md](docs/build.md) for local build instructions, including local test builds with `--disable-autoupdate`. + +## Changelog +See [docs/changelog.md](docs/changelog.md) for the current local work log / changelog. + ## Config + You can configure OpenAsar by clicking the "OpenAsar..." version info in the bottom of your settings sidebar, which will open the config window. diff --git a/docs/build.md b/docs/build.md new file mode 100644 index 000000000..9387c8d54 --- /dev/null +++ b/docs/build.md @@ -0,0 +1,40 @@ +# Local Build + +The GitHub nightly workflow builds OpenAsar by: + +- stamping a `nightly-` version into `src/index.js` +- stripping the `src/` tree with `node scripts/strip.js` +- packing the final archive with `asar pack` + +For local builds, use `scripts/pack.js`, which follows that same flow without modifying your working tree in place. + +## Requirements +- `node` +- `asar` + +Example install for `asar`: + +```bash +npm i -g asar +``` + +## Build With Normal Auto-Update Behavior +This keeps the default OpenAsar self-update behavior enabled. + +```bash +node scripts/pack.js --version nightly-$(git rev-parse --short HEAD) --output tmp/openasar-build/app.asar +``` + +## Build With Auto-Update Disabled +Use this for local testing when you do not want the built `app.asar` to replace itself with the upstream nightly release on launch. + +```bash +node scripts/pack.js --disable-autoupdate --version nightly-$(git rev-parse --short HEAD)-localtest --output tmp/openasar-build/app.asar +``` + +## Output +Both commands above produce: + +```text +tmp/openasar-build/app.asar +``` diff --git a/WORKLOG.md b/docs/changelog.md similarity index 99% rename from WORKLOG.md rename to docs/changelog.md index aec50c7aa..440296665 100644 --- a/WORKLOG.md +++ b/docs/changelog.md @@ -1,4 +1,4 @@ -# Work Log +# Changelog ## 2026-04-17 From ab31aa9da659a9d8502e3deaed9a5c5cb7af2740 Mon Sep 17 00:00:00 2001 From: XxUnkn0wnxX Date: Fri, 17 Apr 2026 04:29:04 +1000 Subject: [PATCH 06/11] docs: link changelog references to GitHub Docs - convert the issue, pull request, and fork references in docs/changelog.md to markdown links - keep the changelog text the same while making the source references clickable Verification - re-read docs/changelog.md after the patch to confirm the links were written correctly --- docs/changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.md b/docs/changelog.md index 440296665..d01f20f55 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -4,7 +4,7 @@ ### BetterDiscord settings compatibility -- Investigated OpenAsar issue `#222`, PR `#225`, and the `TirOFlanc/OpenAsar` fork. +- Investigated [OpenAsar issue #222](https://github.com/GooseMod/OpenAsar/issues/222), [PR #225](https://github.com/GooseMod/OpenAsar/pull/225), and the [TirOFlanc/OpenAsar fork](https://github.com/TirOFlanc/OpenAsar). - Reviewed the local BetterDiscord codebase to understand how it patches Discord's settings UI. - Identified the main failure mode in `src/mainWindow.js`: OpenAsar's settings item injection was incorrectly gated behind version/footer detection. - Reworked the settings injection flow in `src/mainWindow.js` so version injection and menu item injection are independent. From b7892f6b7add5adf2a4adfce32b0c01e8de2abc2 Mon Sep 17 00:00:00 2001 From: XxUnkn0wnxX Date: Fri, 17 Apr 2026 04:53:12 +1000 Subject: [PATCH 07/11] build: add fork-aware update channels and workflow variants Build - add update-repo stamping so packed builds can update from a specific GitHub repo - keep upstream as the default update source when no repo is stamped - extend scripts/pack.js with --update-repo alongside --disable-autoupdate Workflows - add a no-auto-update nightly variant that publishes nightly-no-autoupdate - add a fork-update nightly variant that publishes nightly-fork - keep both workflow variants close to the main nightly pipeline with matching smoke-test jobs - default the custom update repo workflow to XxUnkn0wnxX/OpenAsar for this fork Fixes - replace all update-repo placeholders during pack time instead of only the first match - fix the runtime fallback so stamped fork repos are preserved in packed builds Docs - document local build variants and the optional workflow channels - record the fork-update work and the two stamping bugs in docs/changelog.md Verification - ran node -c on src/index.js, src/asarUpdate.js, and scripts/pack.js - parsed both new workflow files as YAML - packed a fork-targeted test build into tmp/openasar-forktest/app.asar --- .../workflows/nightly-custom-update-repo.yml | 237 ++++++++++++++++++ .../workflows/nightly-disable-autoupdate.yml | 227 +++++++++++++++++ docs/build.md | 44 +++- docs/changelog.md | 11 +- scripts/pack.js | 11 +- src/asarUpdate.js | 3 +- src/index.js | 3 + 7 files changed, 532 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/nightly-custom-update-repo.yml create mode 100644 .github/workflows/nightly-disable-autoupdate.yml diff --git a/.github/workflows/nightly-custom-update-repo.yml b/.github/workflows/nightly-custom-update-repo.yml new file mode 100644 index 000000000..455821559 --- /dev/null +++ b/.github/workflows/nightly-custom-update-repo.yml @@ -0,0 +1,237 @@ +name: Nightly Custom Update Repo + +on: + workflow_dispatch: + +env: + UPDATE_REPO: 'XxUnkn0wnxX/OpenAsar' # Default fork repo for this checkout. + +jobs: + build: + name: Build + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Setup Node.js v20.x + uses: actions/setup-node@v2 + with: + node-version: 20.x + + - name: Validate update repo + run: | + if [ "${{ env.UPDATE_REPO }}" = "owner/repo" ]; then + echo "Set UPDATE_REPO to your fork before running this workflow." + exit 1 + fi + + - name: Pack fork-update asar + run: | + npm i -g asar + node scripts/pack.js --update-repo "${{ env.UPDATE_REPO }}" --version nightly-$(git rev-parse HEAD | cut -c 1-7) --output app.asar + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: built-asar-custom-update-repo + path: app.asar + retention-days: 1 + + test-linux-stable: + name: Test Linux Stable + + needs: build + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Retrieve artifact + uses: actions/download-artifact@v4 + with: + name: built-asar-custom-update-repo + path: artifact + + - name: Extract artifact + run: | + cp artifact/app.asar . + + - name: Download Client with OpenAsar + run: | + wget "https://discord.com/api/download/stable?platform=linux&format=tar.gz" -O discord.tar.gz + tar xf discord.tar.gz + + sed -r -i "s/const config=require\('.\/config'\);/console.log\('ABRA'\);app.exit\(\); /" app.asar + cp app.asar Discord/resources/app.asar + + # workaround https://github.com/electron/electron/issues/42510 + sudo chown root ./Discord/chrome-sandbox + sudo chmod 4755 ./Discord/chrome-sandbox + + - name: Check if Discord will startup + run: | + xvfb-run -e /dev/stdout ./Discord/Discord --enable-logging | grep -m1 "ABRA" + timeout-minutes: 3 + + test-linux-canary: + name: Test Linux Canary + + needs: build + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Retrieve artifact + uses: actions/download-artifact@v4 + with: + name: built-asar-custom-update-repo + path: artifact + + - name: Extract artifact + run: | + cp artifact/app.asar . + + - name: Download Client with OpenAsar + run: | + wget "https://discord.com/api/download/canary?platform=linux&format=tar.gz" -O discord.tar.gz + tar xf discord.tar.gz + + sed -r -i "s/const config=require\('.\/config'\);/console.log\('ABRA'\);app.exit\(\); /" app.asar + cp app.asar DiscordCanary/resources/app.asar + + # workaround https://github.com/electron/electron/issues/42510 + sudo chown root ./DiscordCanary/chrome-sandbox + sudo chmod 4755 ./DiscordCanary/chrome-sandbox + + - name: Check if Discord will startup + run: | + xvfb-run -e /dev/stdout ./DiscordCanary/DiscordCanary --enable-logging | grep -m1 "ABRA" + timeout-minutes: 3 + + test-win-stable: + name: Test Windows Stable + + needs: build + runs-on: windows-latest + + steps: + - uses: actions/checkout@v2 + + - name: Setup Node.js v20.x + uses: actions/setup-node@v2 + with: + node-version: 20.x + + - name: Retrieve artifact + uses: actions/download-artifact@v4 + with: + name: built-asar-custom-update-repo + path: artifact + + - name: Extract artifact + run: | + cp artifact/app.asar . + + - name: Setup Client + shell: bash + run: | + node scripts/downloadWin.js + tar xf client.tar + + # Install OpenAsar build and setup environment + sed -r -i "s/const config=require\('.\/config'\);/console.log\('ABRA'\);app.exit\(\); /" app.asar + sed -r -i "s/if\(next!=cur&&!options\?\.allowObsoleteHost\)/if\(false\) /" app.asar + cp -f app.asar files/resources/app.asar + mkdir discord + cp -rf files/ discord/app-1.0.0 + cd discord/app-1.0.0 + mkdir modules + + - name: Check if Discord will startup + run: | + cd discord/app-1.0.0 + ./Discord.exe | grep -m1 "ABRA" + timeout-minutes: 3 + shell: bash + + test-win-canary: + name: Test Windows Canary + + needs: build + runs-on: windows-latest + + steps: + - uses: actions/checkout@v2 + + - name: Setup Node.js v20.x + uses: actions/setup-node@v2 + with: + node-version: 20.x + + - name: Retrieve artifact + uses: actions/download-artifact@v4 + with: + name: built-asar-custom-update-repo + path: artifact + + - name: Extract artifact + run: | + cp artifact/app.asar . + + - name: Setup Client + shell: bash + run: | + node scripts/downloadWin.js canary + tar xf client.tar + + # Install OpenAsar build and setup environment + sed -r -i "s/const config=require\('.\/config'\);/console.log\('ABRA'\);app.exit\(\); /" app.asar + sed -r -i "s/if\(next!=cur&&!options\?\.allowObsoleteHost\)/if\(false\) /" app.asar + cp -f app.asar files/resources/app.asar + mkdir discord + cp -rf files/ discord/app-1.0.0 + cd discord/app-1.0.0 + mkdir modules + + - name: Check if Discord will startup + run: | + cd discord/app-1.0.0 + ./DiscordCanary.exe | grep -m1 "ABRA" + timeout-minutes: 3 + shell: bash + + release: + name: Release + needs: + - build + # - test-linux + # - test-win + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Retrieve artifact + uses: actions/download-artifact@v4 + with: + name: built-asar-custom-update-repo + path: artifact + + - name: Extract artifact + run: | + cp artifact/app.asar . + + - name: GitHub Release + run: | + git tag -d nightly-fork || true + git push origin --delete nightly-fork || true + git tag nightly-fork + git push origin nightly-fork + gh release delete ${{ env.VERSION }} -y || true + gh release create ${{ env.VERSION }} -t "Nightly Fork" -n "$(bash scripts/nightlyNotes.sh)" ${{ env.FILES }} + env: + GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' + VERSION: 'nightly-fork' + FILES: app.asar diff --git a/.github/workflows/nightly-disable-autoupdate.yml b/.github/workflows/nightly-disable-autoupdate.yml new file mode 100644 index 000000000..03d4af022 --- /dev/null +++ b/.github/workflows/nightly-disable-autoupdate.yml @@ -0,0 +1,227 @@ +name: Nightly Disable Auto-Update + +on: + workflow_dispatch: + +jobs: + build: + name: Build + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Setup Node.js v20.x + uses: actions/setup-node@v2 + with: + node-version: 20.x + + - name: Pack no-auto-update asar + run: | + npm i -g asar + node scripts/pack.js --disable-autoupdate --version nightly-$(git rev-parse HEAD | cut -c 1-7)-localtest --output app.asar + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: built-asar-no-autoupdate + path: app.asar + retention-days: 1 + + test-linux-stable: + name: Test Linux Stable + + needs: build + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Retrieve artifact + uses: actions/download-artifact@v4 + with: + name: built-asar-no-autoupdate + path: artifact + + - name: Extract artifact + run: | + cp artifact/app.asar . + + - name: Download Client with OpenAsar + run: | + wget "https://discord.com/api/download/stable?platform=linux&format=tar.gz" -O discord.tar.gz + tar xf discord.tar.gz + + sed -r -i "s/const config=require\('.\/config'\);/console.log\('ABRA'\);app.exit\(\); /" app.asar + cp app.asar Discord/resources/app.asar + + # workaround https://github.com/electron/electron/issues/42510 + sudo chown root ./Discord/chrome-sandbox + sudo chmod 4755 ./Discord/chrome-sandbox + + - name: Check if Discord will startup + run: | + xvfb-run -e /dev/stdout ./Discord/Discord --enable-logging | grep -m1 "ABRA" + timeout-minutes: 3 + + test-linux-canary: + name: Test Linux Canary + + needs: build + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Retrieve artifact + uses: actions/download-artifact@v4 + with: + name: built-asar-no-autoupdate + path: artifact + + - name: Extract artifact + run: | + cp artifact/app.asar . + + - name: Download Client with OpenAsar + run: | + wget "https://discord.com/api/download/canary?platform=linux&format=tar.gz" -O discord.tar.gz + tar xf discord.tar.gz + + sed -r -i "s/const config=require\('.\/config'\);/console.log\('ABRA'\);app.exit\(\); /" app.asar + cp app.asar DiscordCanary/resources/app.asar + + # workaround https://github.com/electron/electron/issues/42510 + sudo chown root ./DiscordCanary/chrome-sandbox + sudo chmod 4755 ./DiscordCanary/chrome-sandbox + + - name: Check if Discord will startup + run: | + xvfb-run -e /dev/stdout ./DiscordCanary/DiscordCanary --enable-logging | grep -m1 "ABRA" + timeout-minutes: 3 + + test-win-stable: + name: Test Windows Stable + + needs: build + runs-on: windows-latest + + steps: + - uses: actions/checkout@v2 + + - name: Setup Node.js v20.x + uses: actions/setup-node@v2 + with: + node-version: 20.x + + - name: Retrieve artifact + uses: actions/download-artifact@v4 + with: + name: built-asar-no-autoupdate + path: artifact + + - name: Extract artifact + run: | + cp artifact/app.asar . + + - name: Setup Client + shell: bash + run: | + node scripts/downloadWin.js + tar xf client.tar + + # Install OpenAsar build and setup environment + sed -r -i "s/const config=require\('.\/config'\);/console.log\('ABRA'\);app.exit\(\); /" app.asar + sed -r -i "s/if\(next!=cur&&!options\?\.allowObsoleteHost\)/if\(false\) /" app.asar + cp -f app.asar files/resources/app.asar + mkdir discord + cp -rf files/ discord/app-1.0.0 + cd discord/app-1.0.0 + mkdir modules + + - name: Check if Discord will startup + run: | + cd discord/app-1.0.0 + ./Discord.exe | grep -m1 "ABRA" + timeout-minutes: 3 + shell: bash + + test-win-canary: + name: Test Windows Canary + + needs: build + runs-on: windows-latest + + steps: + - uses: actions/checkout@v2 + + - name: Setup Node.js v20.x + uses: actions/setup-node@v2 + with: + node-version: 20.x + + - name: Retrieve artifact + uses: actions/download-artifact@v4 + with: + name: built-asar-no-autoupdate + path: artifact + + - name: Extract artifact + run: | + cp artifact/app.asar . + + - name: Setup Client + shell: bash + run: | + node scripts/downloadWin.js canary + tar xf client.tar + + # Install OpenAsar build and setup environment + sed -r -i "s/const config=require\('.\/config'\);/console.log\('ABRA'\);app.exit\(\); /" app.asar + sed -r -i "s/if\(next!=cur&&!options\?\.allowObsoleteHost\)/if\(false\) /" app.asar + cp -f app.asar files/resources/app.asar + mkdir discord + cp -rf files/ discord/app-1.0.0 + cd discord/app-1.0.0 + mkdir modules + + - name: Check if Discord will startup + run: | + cd discord/app-1.0.0 + ./DiscordCanary.exe | grep -m1 "ABRA" + timeout-minutes: 3 + shell: bash + + release: + name: Release + needs: + - build + # - test-linux + # - test-win + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Retrieve artifact + uses: actions/download-artifact@v4 + with: + name: built-asar-no-autoupdate + path: artifact + + - name: Extract artifact + run: | + cp artifact/app.asar . + + - name: GitHub Release + run: | + git tag -d nightly-no-autoupdate || true + git push origin --delete nightly-no-autoupdate || true + git tag nightly-no-autoupdate + git push origin nightly-no-autoupdate + gh release delete ${{ env.VERSION }} -y || true + gh release create ${{ env.VERSION }} -t "Nightly No Auto-Update" -n "$(bash scripts/nightlyNotes.sh)" ${{ env.FILES }} + env: + GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' + VERSION: 'nightly-no-autoupdate' + FILES: app.asar diff --git a/docs/build.md b/docs/build.md index 9387c8d54..816979d07 100644 --- a/docs/build.md +++ b/docs/build.md @@ -25,6 +25,19 @@ This keeps the default OpenAsar self-update behavior enabled. node scripts/pack.js --version nightly-$(git rev-parse --short HEAD) --output tmp/openasar-build/app.asar ``` +This build updates from the default upstream release repo: + +```text +GooseMod/OpenAsar +``` + +## Build With A Custom Update Repo +Use this when you want a build to self-update from your own fork releases instead of upstream. + +```bash +node scripts/pack.js --update-repo owner/repo --version nightly-$(git rev-parse --short HEAD) --output tmp/openasar-build/app.asar +``` + ## Build With Auto-Update Disabled Use this for local testing when you do not want the built `app.asar` to replace itself with the upstream nightly release on launch. @@ -33,8 +46,37 @@ node scripts/pack.js --disable-autoupdate --version nightly-$(git rev-parse --sh ``` ## Output -Both commands above produce: +All commands above produce: ```text tmp/openasar-build/app.asar ``` + +## Optional GitHub Workflows +The default upstream workflow remains in `.github/workflows/nightly.yml`. + +Additional opt-in workflow templates are included for forks: + +- `.github/workflows/nightly-disable-autoupdate.yml` +- `.github/workflows/nightly-custom-update-repo.yml` + +Both templates now mirror the main nightly pipeline structure more closely: + +- build an `app.asar` +- run the same Linux and Windows startup smoke tests +- publish a release in the repo where the workflow runs + +The custom update repo workflow is manual-only and requires editing: + +```text +UPDATE_REPO: 'owner/repo' +``` + +before use so releases point at the correct fork. + +The intended differences are: + +- `nightly-disable-autoupdate.yml`: packs with `--disable-autoupdate` +- `nightly-disable-autoupdate.yml`: publishes `nightly-no-autoupdate` +- `nightly-custom-update-repo.yml`: packs with `--update-repo owner/repo` +- `nightly-custom-update-repo.yml`: publishes `nightly-fork` diff --git a/docs/changelog.md b/docs/changelog.md index d01f20f55..19c1cedef 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -16,10 +16,19 @@ - Identified that local test builds were being overwritten on first launch by OpenAsar's self-updater. - Added a build-time auto-update flag so local test archives can be packed with self-updates disabled intentionally. - Kept the default source behavior with self-updates enabled unless the build flag is explicitly set. -- Added `scripts/pack.js` to build local `app.asar` files with options such as `--disable-autoupdate`. +- Added `scripts/pack.js` to build local `app.asar` files with options such as `--disable-autoupdate` and `--update-repo owner/repo`. - Confirmed the DOM-only settings injection works once local test builds stop being overwritten by the self-updater. - Reverted the experimental hybrid settings patch and kept the DOM-only fix for the final code path. - Local test build command: `node scripts/pack.js --disable-autoupdate --version nightly-$(git rev-parse --short HEAD)-localtest --output tmp/openasar-build/app.asar` +- Added optional fork-oriented GitHub workflow templates for no-auto-update artifacts and custom update repos while leaving the main upstream nightly workflow unchanged. +- Found and fixed a pack-time stamping bug where `scripts/pack.js` only replaced the first `` placeholder even though `src/index.js` used that placeholder more than once. +- Fixed that placeholder bug by changing the pack step to replace all `` occurrences so packed builds do not keep half-stamped fallback logic. +- Found and fixed a runtime fallback bug where the initial `oaUpdateRepo` comparison collapsed stamped fork builds back to `GooseMod/OpenAsar` instead of preserving the custom repo. +- Fixed that runtime bug by switching the fallback check to `stampedUpdateRepo.startsWith('<')`, so unpacked source still defaults upstream while packed builds keep the explicitly stamped fork repo. +- Verified the fork-update path by packing a test build with `--disable-autoupdate --update-repo XxUnkn0wnxX/OpenAsar` and confirming the stamped temp build source kept the custom repo value. +- Expanded the two optional fork workflows so they follow the main nightly pipeline shape more closely instead of only uploading artifacts. +- Matched the extra workflows to the main pipeline by adding the same Linux stable/canary and Windows stable/canary startup smoke-test jobs plus the same release structure. +- Kept the intended workflow differences narrow: one packs with `--disable-autoupdate` and publishes `nightly-no-autoupdate`, and the other packs with `--update-repo owner/repo` and publishes `nightly-fork`. - Verified the updated file with `node -c src/mainWindow.js`. - Verified the repo still packs locally by producing `tmp/pack-test/app.asar`. diff --git a/scripts/pack.js b/scripts/pack.js index 1d5601d1f..7fb9b83b5 100644 --- a/scripts/pack.js +++ b/scripts/pack.js @@ -7,6 +7,7 @@ const tmpRoot = join(root, 'tmp', 'pack-build'); const args = process.argv.slice(2); let disableAutoUpdate = false; +let updateRepo = 'GooseMod/OpenAsar'; let version; let output = join(root, 'tmp', 'openasar-build', 'app.asar'); @@ -23,19 +24,26 @@ for (let i = 0; i < args.length; i++) { continue; } + if (arg === '--update-repo') { + updateRepo = args[++i]; + continue; + } + if (arg === '--output') { output = resolve(args[++i]); continue; } if (arg === '--help') { - console.log('Usage: node scripts/pack.js [--disable-autoupdate] [--version ] [--output ]'); + console.log('Usage: node scripts/pack.js [--disable-autoupdate] [--update-repo ] [--version ] [--output ]'); process.exit(0); } throw new Error(`Unknown argument: ${arg}`); } +if (!/^[^/\s]+\/[^/\s]+$/.test(updateRepo)) throw new Error(`Invalid --update-repo value: ${updateRepo}`); + if (!version) { const git = spawnSync('git', ['rev-parse', '--short', 'HEAD'], { cwd: root, @@ -103,6 +111,7 @@ const indexPath = join(tmpRoot, 'src', 'index.js'); let indexCode = readFileSync(indexPath, 'utf8'); indexCode = indexCode.replace("global.oaVersion = 'nightly';", `global.oaVersion = '${version}';`); indexCode = indexCode.replace('', disableAutoUpdate ? 'true' : 'false'); +indexCode = indexCode.replaceAll('', updateRepo); writeFileSync(indexPath, indexCode); stripTree(join(tmpRoot, 'src')); diff --git a/src/asarUpdate.js b/src/asarUpdate.js index 76fdc555f..32da6c4fc 100644 --- a/src/asarUpdate.js +++ b/src/asarUpdate.js @@ -14,10 +14,11 @@ module.exports = async () => { // (Try) update asar if (global.oaDisableAutoUpdate) return log('AsarUpdate', 'Skipping build-configured auto-update disable'); if (!oaVersion.includes('-')) return; const releaseChannel = oaVersion.split('-')[0]; + const updateRepo = global.oaUpdateRepo || 'GooseMod/OpenAsar'; log('AsarUpdate', 'Updating...'); - const res = (await redirs(`https://github.com/GooseMod/OpenAsar/releases/download/${releaseChannel}/app.asar`)); + const res = (await redirs(`https://github.com/${updateRepo}/releases/download/${releaseChannel}/app.asar`)); let data = []; res.on('data', d => { diff --git a/src/index.js b/src/index.js index 293a846e1..904652622 100644 --- a/src/index.js +++ b/src/index.js @@ -2,8 +2,11 @@ const { join } = require('path'); global.log = (area, ...args) => console.log(`[\x1b[38;2;88;101;242mOpenAsar\x1b[0m > ${area}]`, ...args); // Make log global for easy usage everywhere +const defaultUpdateRepo = 'GooseMod/OpenAsar'; +const stampedUpdateRepo = ''; global.oaVersion = 'nightly'; global.oaDisableAutoUpdate = '' === 'true'; +global.oaUpdateRepo = stampedUpdateRepo.startsWith('<') ? defaultUpdateRepo : stampedUpdateRepo; log('Init', 'OpenAsar', oaVersion); From d4be23fd94149331a3a7dc737342e06f7befc503 Mon Sep 17 00:00:00 2001 From: XxUnkn0wnxX Date: Fri, 17 Apr 2026 04:58:54 +1000 Subject: [PATCH 08/11] ci: disable automatic workflow runs on the fork CI - change the main nightly workflow trigger from push-on-main to workflow_dispatch - keep the existing manual-only behavior across all workflow variants Verification - confirmed all workflow files now use workflow_dispatch - parsed every workflow file as YAML after the trigger change --- .github/workflows/nightly.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 93d393652..bcb196b05 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -1,12 +1,7 @@ name: Nightly on: - push: - branches: [ main ] - paths: - - 'src/**' - - 'scripts/**' - - '.github/workflows/**' + workflow_dispatch: jobs: build: From 20ff2b92d72fca438afe96b8f4ffadc540c5282f Mon Sep 17 00:00:00 2001 From: XxUnkn0wnxX Date: Fri, 17 Apr 2026 04:59:23 +1000 Subject: [PATCH 09/11] ci: preserve the disabled nightly push trigger as comments CI - keep workflow_dispatch as the active trigger for nightly.yml - restore the old push-on-main trigger as commented YAML instead of deleting it Verification - re-read nightly.yml after the patch - parsed all workflow files as YAML --- .github/workflows/nightly.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index bcb196b05..d19c4a874 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -2,6 +2,12 @@ name: Nightly on: workflow_dispatch: + # push: + # branches: [ main ] + # paths: + # - 'src/**' + # - 'scripts/**' + # - '.github/workflows/**' jobs: build: From 50ba307b707d57b22dc6669cb97097e73df5dd4f Mon Sep 17 00:00:00 2001 From: XxUnkn0wnxX Date: Fri, 17 Apr 2026 05:00:08 +1000 Subject: [PATCH 10/11] ci: auto-run the fork nightly workflow on main pushes CI - add a push trigger on main to nightly-custom-update-repo.yml - keep workflow_dispatch so the fork workflow can still be run manually - leave the other workflow variants manual-only Verification - re-read the workflow trigger block after the patch - parsed the custom workflow file as YAML --- .github/workflows/nightly-custom-update-repo.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/nightly-custom-update-repo.yml b/.github/workflows/nightly-custom-update-repo.yml index 455821559..6eb034c16 100644 --- a/.github/workflows/nightly-custom-update-repo.yml +++ b/.github/workflows/nightly-custom-update-repo.yml @@ -1,6 +1,12 @@ name: Nightly Custom Update Repo on: + push: + branches: [ main ] + paths: + - 'src/**' + - 'scripts/**' + - '.github/workflows/**' workflow_dispatch: env: From db50a917e238a8c2e001c0cf8d5ec162b8f5fcee Mon Sep 17 00:00:00 2001 From: XxUnkn0wnxX Date: Fri, 17 Apr 2026 05:12:22 +1000 Subject: [PATCH 11/11] ci: restore generic workflow defaults for the PR branch CI - restore nightly.yml to push-on-main behavior - return nightly-custom-update-repo.yml to manual-only dispatch - reset the custom update repo placeholder to owner/repo for upstream-safe defaults Verification - re-read both workflow files after the patch - parsed nightly.yml and nightly-custom-update-repo.yml as YAML --- .github/workflows/nightly-custom-update-repo.yml | 8 +------- .github/workflows/nightly.yml | 13 ++++++------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/.github/workflows/nightly-custom-update-repo.yml b/.github/workflows/nightly-custom-update-repo.yml index 6eb034c16..2de6d0bbf 100644 --- a/.github/workflows/nightly-custom-update-repo.yml +++ b/.github/workflows/nightly-custom-update-repo.yml @@ -1,16 +1,10 @@ name: Nightly Custom Update Repo on: - push: - branches: [ main ] - paths: - - 'src/**' - - 'scripts/**' - - '.github/workflows/**' workflow_dispatch: env: - UPDATE_REPO: 'XxUnkn0wnxX/OpenAsar' # Default fork repo for this checkout. + UPDATE_REPO: 'owner/repo' # Change this to your fork before using this workflow for releases. jobs: build: diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index d19c4a874..93d393652 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -1,13 +1,12 @@ name: Nightly on: - workflow_dispatch: - # push: - # branches: [ main ] - # paths: - # - 'src/**' - # - 'scripts/**' - # - '.github/workflows/**' + push: + branches: [ main ] + paths: + - 'src/**' + - 'scripts/**' + - '.github/workflows/**' jobs: build: