From af382e0a1bc498aab71bc76de5a0c8b28d6baab0 Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 6 Sep 2026 17:54:40 +0530 Subject: [PATCH 1/4] feat: allow admins to override the app update url from a system file The auto updater always fetched its manifest from the url baked into the build, so there was no way to point a prod or staging build at a local update feed for testing. Read an optional phoenix_override_config.json from a machine wide folder that only admin/root can write to, and prefer its app_update_url over brackets.config. The folder being admin owned is what makes the file trustworthy- a standard user, an extension or a project cannot forge it, so this cannot be used to redirect the updater at an attacker's server. windows: C:\Program Files\Phoenix Code Control\phoenix_override_config.json mac : /Library/Application Support/Phoenix Code Control/phoenix_override_config.json linux : /etc/phoenix-code-control/phoenix_override_config.json Only keys in OVERRIDABLE_KEYS are honoured, everything else in the file is ignored. Reads fail open- a missing or malformed file just means no override. Desktop only, the file is never read in the browser. Verified end to end on linux/electron: with the file in place the updater fetched its manifest from localhost and showed the served release notes, a non allowlisted key was dropped, and removing the file fell back to the baked in url. --- src/extensionsIntegrated/appUpdater/main.js | 7 +- .../appUpdater/update-electron.js | 7 +- src/utils/SystemConfigOverride.js | 126 ++++++++++++++++++ 3 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 src/utils/SystemConfigOverride.js diff --git a/src/extensionsIntegrated/appUpdater/main.js b/src/extensionsIntegrated/appUpdater/main.js index 2e6ca8c999..55fbb6b8be 100644 --- a/src/extensionsIntegrated/appUpdater/main.js +++ b/src/extensionsIntegrated/appUpdater/main.js @@ -43,6 +43,7 @@ define(function (require, exports, module) { StringUtils = require("utils/StringUtils"), NativeApp = require("utils/NativeApp"), BootGreetings = require("utils/BootGreetings"), + SystemConfigOverride = require("utils/SystemConfigOverride"), PreferencesManager = require("preferences/PreferencesManager"); // Reserve a slot in the boot-greeting coordinator so the tour can wait @@ -164,7 +165,11 @@ define(function (require, exports, module) { if(!updaterWindow){ updaterWindow = window.__TAURI__.window.WebviewWindow.getByLabel(TAURI_UPDATER_WINDOW_LABEL); } - const updateMetadata = await fetchJSON(brackets.config.app_update_url); + // an admin can point us at another update feed with the system wide + // phoenix_override_config.json, see utils/SystemConfigOverride.js + const overrideConfig = await SystemConfigOverride.getOverrides(); + const updateURL = overrideConfig.app_update_url || brackets.config.app_update_url; + const updateMetadata = await fetchJSON(updateURL); const phoenixBinaryVersion = await NodeUtils.getPhoenixBinaryVersion(); const phoenixLoadedAppVersion = Phoenix.metadata.apiVersion; if(semver.gt(updateMetadata.version, phoenixBinaryVersion)){ diff --git a/src/extensionsIntegrated/appUpdater/update-electron.js b/src/extensionsIntegrated/appUpdater/update-electron.js index 61f9cb275f..f9b094164d 100644 --- a/src/extensionsIntegrated/appUpdater/update-electron.js +++ b/src/extensionsIntegrated/appUpdater/update-electron.js @@ -38,6 +38,7 @@ define(function (require, exports, module) { TaskManager = require("features/TaskManager"), NativeApp = require("utils/NativeApp"), BootGreetings = require("utils/BootGreetings"), + SystemConfigOverride = require("utils/SystemConfigOverride"), PreferencesManager = require("preferences/PreferencesManager"); // Reserve a slot in the boot-greeting coordinator so the tour can wait @@ -123,7 +124,11 @@ define(function (require, exports, module) { updatePlatform: updatePlatformKey }; try{ - const updateMetadata = await fetchJSON(brackets.config.app_update_url); + // an admin can point us at another update feed with the system wide + // phoenix_override_config.json, see utils/SystemConfigOverride.js + const overrideConfig = await SystemConfigOverride.getOverrides(); + const updateURL = overrideConfig.app_update_url || brackets.config.app_update_url; + const updateMetadata = await fetchJSON(updateURL); // In Electron, binary version and loaded app version are always the same // since both are loaded at app start and only change after full restart const currentVersion = await window.electronAPI.getAppVersion(); diff --git a/src/utils/SystemConfigOverride.js b/src/utils/SystemConfigOverride.js new file mode 100644 index 0000000000..3c64ed935a --- /dev/null +++ b/src/utils/SystemConfigOverride.js @@ -0,0 +1,126 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2021 - present core.ai . All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + * + */ + +/** + * Reads `phoenix_override_config.json` from a machine wide, admin owned location and exposes the + * values in it as overrides for `brackets.config`. This is only available in the desktop app. + * + * The file lives in a directory that only an administrator/root can write to, so the presence of + * the file is itself the proof that a machine admin put it there. A standard user cannot create it, + * which is what stops a random extension or a downloaded project from pointing the app at an + * attacker controlled server. + * + * This is the shared "Phoenix Code Control" admin folder that machine wide policies should use going + * forward. It is a new folder, so it will not exist on already provisioned machines. The older AI + * disable policy still reads its own `Phoenix AI Control` folder(see phoenix-pro services/ai-control.js) + * and can be moved here later. + * + * Locations (create the directory as admin/root, then drop the file in): + * windows: C:\Program Files\Phoenix Code Control\phoenix_override_config.json + * mac : /Library/Application Support/Phoenix Code Control/phoenix_override_config.json + * linux : /etc/phoenix-code-control/phoenix_override_config.json + * + * File format - a flat json of `brackets.config` keys to override. Eg. to point the auto updater at + * a locally served update manifest: + * { + * "app_update_url": "http://localhost:8000/update-latest-experimental-build.json" + * } + * + * Only the keys listed in `OVERRIDABLE_KEYS` are honoured, everything else in the file is ignored. + */ + +/*global logger*/ + +define(function (require, exports, module) { + const OVERRIDE_FILE_NAME = "phoenix_override_config.json"; + + // Only these `brackets.config` keys can be overridden from the system file. Keep this list + // as small as possible, add a key only when there is a real need to override it on a machine. + const OVERRIDABLE_KEYS = [ + "app_update_url" + ]; + + /** + * Gets the platform specific path of the admin owned override file, or "" in non desktop builds. + * @returns {string} virtual path of the override file + */ + function _getOverrideFilePath() { + if (!Phoenix.isNativeApp) { + return ""; + } + let platformPath; + if (Phoenix.platform === "win") { + platformPath = `C:\\Program Files\\Phoenix Code Control\\${OVERRIDE_FILE_NAME}`; + } else if (Phoenix.platform === "mac") { + platformPath = `/Library/Application Support/Phoenix Code Control/${OVERRIDE_FILE_NAME}`; + } else if (Phoenix.platform === "linux") { + platformPath = `/etc/phoenix-code-control/${OVERRIDE_FILE_NAME}`; + } else { + console.error("System config override: unsupported platform", Phoenix.platform); + return ""; + } + return Phoenix.VFS.getTauriVirtualPath(platformPath); + } + + const OVERRIDE_FILE_PATH = _getOverrideFilePath(); + + let overridesPromise = null; + + /** + * Reads and parses the override file. The file is read once per app session and the result cached, + * so that an admin edit needs an app restart to take effect(same as any other boot config). + * Never rejects- a missing or malformed file just means "no overrides". + * @returns {Promise} the overridable key value pairs present in the file, may be empty + */ + function getOverrides() { + if (overridesPromise) { + return overridesPromise; + } + overridesPromise = (async function () { + if (!OVERRIDE_FILE_PATH) { + return {}; + } + const fileData = await Phoenix.VFS.readFileResolves(OVERRIDE_FILE_PATH, "utf8"); + if (fileData.error || !fileData.data) { + // the common case, no admin has placed an override file on this machine. + return {}; + } + try { + const rawOverrides = JSON.parse(fileData.data); + const overrides = {}; + for (const key of OVERRIDABLE_KEYS) { + if (rawOverrides[key] !== undefined) { + overrides[key] = rawOverrides[key]; + } + } + console.warn(`System config override in effect from ${OVERRIDE_FILE_PATH}:`, overrides); + return overrides; + } catch (e) { + console.error(`Error parsing system config override ${OVERRIDE_FILE_PATH}`, e); + logger.reportError(e, "Error parsing system config override file"); + return {}; + } + }()); + return overridesPromise; + } + + exports.OVERRIDE_FILE_PATH = OVERRIDE_FILE_PATH; + exports.getOverrides = getOverrides; +}); From 75bde0c8bc8e849bb5ad5abc28f51bb8f7629d75 Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 6 Sep 2026 17:55:11 +0530 Subject: [PATCH 2/4] docs: note the @INCLUDE_IN_API_DOCS marker in CLAUDE.md The marker is what makes a core module part of the public extension API docs, which is easy to add by reflex to something meant to stay internal. --- CLAUDE.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 9ffecaba2a..ddffdd6772 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -15,6 +15,12 @@ - No trailing whitespace. - Use `const` and `let` instead of `var`. +## Public API docs +- Core modules meant for extension authors must start with `// @INCLUDE_IN_API_DOCS`. Omit it for + internal modules — the marker is what makes a module public, so don't add it by reflex. +- If you add or change that marker, verify the docs still build with `npm run createJSDocs` (note it + also stages `docs/`). + ## Build artifacts — do not hand-edit - **`src/cacheManifest.json`** is a generated build artifact (gitignored, produced by `gulpfile.js/index.js`). It lists files + hashes for the service-worker cache. Never hand-edit or commit it — it is regenerated by the build, so edits are overwritten and won't be tracked anyway. When you add/remove/rename source files, just let the build regenerate it. From 4261f32242679fb287f0aadde143f72f6d4b62a5 Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 6 Sep 2026 18:24:35 +0530 Subject: [PATCH 3/4] feat: make the linux installer script url overridable too Overriding app_update_url alone was not enough to genuinely test an update on linux. Windows and mac download and install the artifact named in the manifest, but linux installs nothing from it- it pipes an installer script into bash and that script decides what to install. So detection used the overridden manifest while the install still fetched the production installer, which reported a fake version and then installed the real build. Add app_update_linux_installer_url to the allowlist and route the linux install through it, so every shipped platform can now be tested end to end. The url is taken as given. Whatever it points at is piped into bash anyway, so sanitising the url would buy nothing- it can only come from our own https constant or from a file that only root can create. Two guards so this cannot hurt the update path: - the config read is raced against a timeout. It is awaited at quit time, when the node process may already be gone, and a read that never settled would hang the quit rather than just the update. - the read is warmed at boot, because a window that only inherits an already scheduled update never runs an update check and would otherwise first touch the disk at quit time. Any failure reading the override falls back to the shipped default and the update proceeds as normal. Also add reference samples under appUpdater/unit-tests: an override config, a fake update manifest and a fake installer that just prints "update done". They point at each other over the dev server so they work as-is. --- .../linux-local-update-override-script.sh | 26 +++++++++ .../appUpdater/unit-tests/localOverride.json | 36 ++++++++++++ .../unit-tests/localUpdateManifest.json | 56 +++++++++++++++++++ .../appUpdater/update-electron.js | 55 +++++++++++++++--- src/utils/SystemConfigOverride.js | 18 +++++- 5 files changed, 181 insertions(+), 10 deletions(-) create mode 100755 src/extensionsIntegrated/appUpdater/unit-tests/linux-local-update-override-script.sh create mode 100644 src/extensionsIntegrated/appUpdater/unit-tests/localOverride.json create mode 100644 src/extensionsIntegrated/appUpdater/unit-tests/localUpdateManifest.json diff --git a/src/extensionsIntegrated/appUpdater/unit-tests/linux-local-update-override-script.sh b/src/extensionsIntegrated/appUpdater/unit-tests/linux-local-update-override-script.sh new file mode 100755 index 0000000000..85edbbb8fc --- /dev/null +++ b/src/extensionsIntegrated/appUpdater/unit-tests/linux-local-update-override-script.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# SAMPLE / REFERENCE ONLY - a fake linux installer for testing the admin update override. +# +# The real linux update runs: wget -qO- "$UPDATE_URL" | bash -s -- --upgrade +# so this stands in for updates.phcode.io/linux/installer.sh and installs nothing. +# +# Point app_update_linux_installer_url at it in phoenix_override_config.json (see +# localOverride.json in this folder), with the dev server serving the repo on :8000: +# http://localhost:8000/src/extensionsIntegrated/appUpdater/unit-tests/linux-local-update-override-script.sh +# +# Phoenix spawns this in an external terminal at app quit and waits for a keypress, so you +# get to read the output before the window closes. + +echo "================================================" +echo " update done" +echo "================================================" +echo "This is the LOCAL OVERRIDE installer, not the real one." +echo "If you are seeing this, app_update_linux_installer_url was honoured." +echo +echo "args passed by phoenix : $*" +echo "user : $(id -un)" +echo "date : $(date)" +echo +echo "Nothing was installed or changed." + +exit 0 diff --git a/src/extensionsIntegrated/appUpdater/unit-tests/localOverride.json b/src/extensionsIntegrated/appUpdater/unit-tests/localOverride.json new file mode 100644 index 0000000000..33ba394f71 --- /dev/null +++ b/src/extensionsIntegrated/appUpdater/unit-tests/localOverride.json @@ -0,0 +1,36 @@ +{ + "_readme": [ + "SAMPLE / REFERENCE ONLY - this file does nothing where it sits.", + "", + "To actually use it, copy it as admin/root into the machine wide control folder and", + "rename it to phoenix_override_config.json:", + " windows: C:\\Program Files\\Phoenix Code Control\\phoenix_override_config.json", + " mac : /Library/Application Support/Phoenix Code Control/phoenix_override_config.json", + " linux : /etc/phoenix-code-control/phoenix_override_config.json", + "", + "The folder MUST be admin/root owned. That a standard user cannot create the file is the", + "whole security model - see src/utils/SystemConfigOverride.js. Only keys listed in its", + "OVERRIDABLE_KEYS are honoured, anything else here (this _readme included) is ignored.", + "", + "The two urls below point at the sibling sample files in this folder, served by the dev", + "server straight out of the repo, so this works as-is with no extra setup:", + " localUpdateManifest.json - the fake update manifest", + " linux-local-update-override-script.sh - fake installer, just prints 'update done'", + "", + "Why both: on windows/mac the manifest's platform url is what gets downloaded and", + "installed, so app_update_url alone is enough there. Linux installs nothing from the", + "manifest - it pipes app_update_linux_installer_url into bash and that script decides", + "what to install - so a genuine linux test needs the second override too.", + "", + "Gotchas when testing:", + " - the file is read once per app session, so restart Phoenix after editing it.", + " - boot update checks are throttled to 24h (PH_LAST_UPDATE_CHECK_TIME view state), so", + " use Help > Check for Updates to see an override take effect immediately.", + " - the linux install only runs at app quit, in an external terminal." + ], + + "app_update_url": + "http://localhost:8000/src/extensionsIntegrated/appUpdater/unit-tests/localUpdateManifest.json", + "app_update_linux_installer_url": + "http://localhost:8000/src/extensionsIntegrated/appUpdater/unit-tests/linux-local-update-override-script.sh" +} diff --git a/src/extensionsIntegrated/appUpdater/unit-tests/localUpdateManifest.json b/src/extensionsIntegrated/appUpdater/unit-tests/localUpdateManifest.json new file mode 100644 index 0000000000..4d0655902c --- /dev/null +++ b/src/extensionsIntegrated/appUpdater/unit-tests/localUpdateManifest.json @@ -0,0 +1,56 @@ +{ + "_readme": [ + "SAMPLE / REFERENCE ONLY - the fake update manifest that app_update_url can point at.", + "Served by the dev server straight out of the repo:", + " http://localhost:8000/src/extensionsIntegrated/appUpdater/unit-tests/localUpdateManifest.json", + "", + "Only version, notes and platforms are read by the updater (see getUpdateDetails in", + "update-electron.js / main.js). Everything else here, including this _readme, is ignored.", + "", + "version : must be valid semver and GREATER than the installed build, or the app reports", + " 'no updates available'. 99.9.9 is used so it always wins.", + "notes : markdown, rendered as-is in the update dialog. Handy place to prove which", + " manifest was actually fetched.", + "", + "platform keys are `${os}-${arch}` where os is windows|darwin|linux. Careful: arch differs", + "by shell - electron uses node's process.arch (x64, arm64) while tauri uses os.arch()", + "(x86_64, aarch64), so the same machine wants a different key on each. All of them are", + "listed below so this file works either way. To see yours, run in the app:", + " await Phoenix.app.getPlatformArch()", + "", + "WARNING - the platform `url` is real on windows/mac: it is downloaded and run as an", + "installer. Linux ignores it entirely and pipes app_update_linux_installer_url into bash", + "instead. The urls below are deliberately dead ends so nothing can install by accident." + ], + + "version": "99.9.9", + "notes": "# Fake update from the LOCAL OVERRIDE manifest\n\nIf you can read this, the app fetched its update manifest from **localhost** and not from updates.phcode.io.\n\n- nothing here is real\n- nothing will be installed", + "pub_date": "2030-01-01T00:00:00Z", + + "platforms": { + "linux-x64": { + "signature": "", + "url": "http://localhost:8000/not-a-real-installer.tar.gz" + }, + "linux-arm64": { + "signature": "", + "url": "http://localhost:8000/not-a-real-installer.tar.gz" + }, + "darwin-x86_64": { + "signature": "", + "url": "http://localhost:8000/not-a-real-installer.tar.gz" + }, + "darwin-aarch64": { + "signature": "", + "url": "http://localhost:8000/not-a-real-installer.tar.gz" + }, + "windows-x86_64": { + "signature": "", + "url": "http://localhost:8000/not-a-real-installer.exe" + }, + "windows-aarch64": { + "signature": "", + "url": "http://localhost:8000/not-a-real-installer.exe" + } + } +} diff --git a/src/extensionsIntegrated/appUpdater/update-electron.js b/src/extensionsIntegrated/appUpdater/update-electron.js index f9b094164d..e9bc569602 100644 --- a/src/extensionsIntegrated/appUpdater/update-electron.js +++ b/src/extensionsIntegrated/appUpdater/update-electron.js @@ -278,23 +278,58 @@ define(function (require, exports, module) { } } + /** + * The installer script that performs the actual linux upgrade. Unlike windows and mac, linux does + * not download the build named in the update manifest- it pipes this script into bash and the + * script decides what to install. So this url, not the manifest, is what an admin has to point + * elsewhere to genuinely test an update on linux. + * @returns {string} + */ + function _getDefaultLinuxInstallerURL() { + const stageValue = Phoenix.config.environment; + console.log('Stage:', stageValue); + if(stageValue === 'dev' || stageValue === 'stage'){ + return "https://updates.phcode.io/linux/installer-latest-experimental-build.sh"; + } + return 'https://updates.phcode.io/linux/installer.sh'; + } + + /** + * The installer script url for the linux upgrade, preferring an admin override. + * + * The url is taken as given. Whatever it points at is downloaded and piped into bash anyway, + * so sanitising the url itself would buy nothing- anyone who can set it can just as easily + * serve any script they like. What makes it trustworthy is that only an admin/root can create + * the config file naming it, see utils/SystemConfigOverride.js. A url that does not work fails + * loudly through the installer's exit code rather than silently installing something else. + * + * This sits on the update path, so it never throws: a problem reading the override config + * falls back to the shipped default and the update proceeds as normal. + * @returns {Promise} always a usable url + */ + async function _resolveLinuxInstallerURL() { + try { + const overrideConfig = await SystemConfigOverride.getOverrides(); + return overrideConfig.app_update_linux_installer_url || _getDefaultLinuxInstallerURL(); + } catch (e) { + console.error("Error reading system config override, using the default installer", e); + return _getDefaultLinuxInstallerURL(); + } + } + /** * Launches the Linux updater using spawnProcess with streaming output + * @param {string} scriptUrl - installer script to pipe into bash * @param {function} onOutput - Callback for stdout/stderr lines * @returns {Promise} Resolves when update completes, rejects on error */ - function launchLinuxUpdater(onOutput) { + function launchLinuxUpdater(scriptUrl, onOutput) { return new Promise((resolve, reject) => { + console.log('Linux installer script:', scriptUrl); // Spawn the installer in an external terminal emulator so sudo / // interactive prompts work natively. The external terminal IS the // install UI — no internal dialog. Probes common terminals in // order; first hit wins. - const stageValue = Phoenix.config.environment; - console.log('Stage:', stageValue); - let scriptUrl = 'https://updates.phcode.io/linux/installer.sh'; - if(stageValue === 'dev' || stageValue === 'stage'){ - scriptUrl = "https://updates.phcode.io/linux/installer-latest-experimental-build.sh"; - } // Inner command run inside the spawned terminal: fetch installer // from $UPDATE_URL and pipe to bash, print exit code, pause so the @@ -355,7 +390,7 @@ define(function (require, exports, module) { await window.electronAPI.setUpdateScheduled(false); console.log("Launching external terminal for update"); try { - await launchLinuxUpdater(); + await launchLinuxUpdater(await _resolveLinuxInstallerURL()); Metrics.countEvent(Metrics.EVENT_TYPE.UPDATES, 'install', 'launched' + Phoenix.platform); // Success: the terminal is now the user's UI. Let the quit proceed. } catch (err) { @@ -395,6 +430,10 @@ define(function (require, exports, module) { _unblockUpdaterGate(); return; } + // Warm the system override cache at boot. A window that only inherits an already scheduled + // update never runs an update check, so without this its first read would happen at quit + // time- when the node process may already be gone. + SystemConfigOverride.getOverrides(); // Check if another window already scheduled an update (multi-window state persistence) // This ensures the quit handler is registered in this window too try { diff --git a/src/utils/SystemConfigOverride.js b/src/utils/SystemConfigOverride.js index 3c64ed935a..e3a8363b96 100644 --- a/src/utils/SystemConfigOverride.js +++ b/src/utils/SystemConfigOverride.js @@ -53,8 +53,14 @@ define(function (require, exports, module) { // Only these `brackets.config` keys can be overridden from the system file. Keep this list // as small as possible, add a key only when there is a real need to override it on a machine. + // reading this file must never be able to stall the app, see the race in getOverrides() + const READ_TIMEOUT_MS = 5000; + const OVERRIDABLE_KEYS = [ - "app_update_url" + "app_update_url", + // linux installs by piping an installer script into bash, so the manifest's downloadURL is + // not enough there- this is what actually decides which build gets installed on linux. + "app_update_linux_installer_url" ]; /** @@ -97,7 +103,15 @@ define(function (require, exports, module) { if (!OVERRIDE_FILE_PATH) { return {}; } - const fileData = await Phoenix.VFS.readFileResolves(OVERRIDE_FILE_PATH, "utf8"); + // Hard timeout. This is awaited on the app update path, including at quit time when + // the node process may already be gone, and a read that never settles would hang the + // quit. Timing out just means "no overrides", which is the normal case anyway. + const fileData = await Promise.race([ + Phoenix.VFS.readFileResolves(OVERRIDE_FILE_PATH, "utf8"), + new Promise((resolve) => { + setTimeout(() => resolve({error: new Error("timed out")}), READ_TIMEOUT_MS); + }) + ]); if (fileData.error || !fileData.data) { // the common case, no admin has placed an override file on this machine. return {}; From 97dd56163a7ee2e40a2447f8e3f4cc2175bf6c4c Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 6 Sep 2026 18:26:29 +0530 Subject: [PATCH 4/4] build: do not put shell scripts in the PWA cache The cache manifest builder throws on any extension it has not been told about, and appUpdater/unit-tests now carries a sample .sh installer. Shell scripts are never fetched by the app, so they belong in the disallowed list rather than the cache. --- gulpfile.js/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gulpfile.js/index.js b/gulpfile.js/index.js index 2da39623b6..14ca1b12c0 100644 --- a/gulpfile.js/index.js +++ b/gulpfile.js/index.js @@ -457,7 +457,7 @@ const ALLOWED_EXTENSIONS_TO_CACHE = ["js", "html", "htm", "xml", "xhtml", "mjs", "png", "svg", "jpg", "jpeg", "gif", "ico", "webp", "mustache", "md", "markdown"]; const DISALLOWED_EXTENSIONS_TO_CACHE = ["map", "nuspec", "partial", "pre", "post", - "webmanifest", "rb", "ts"]; + "webmanifest", "rb", "ts", "sh"]; // Ceiling for the PWA service worker cache, in KB. Dev builds ship unminified sources and keep the // phoenix-pro sources in dist, so they are legitimately larger than prod - dev gets the looser