Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
2 changes: 1 addition & 1 deletion gulpfile.js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion src/extensionsIntegrated/appUpdater/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)){
Expand Down
Original file line number Diff line number Diff line change
@@ -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
36 changes: 36 additions & 0 deletions src/extensionsIntegrated/appUpdater/unit-tests/localOverride.json
Original file line number Diff line number Diff line change
@@ -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"
}
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
62 changes: 53 additions & 9 deletions src/extensionsIntegrated/appUpdater/update-electron.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
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
Expand Down Expand Up @@ -123,7 +124,11 @@
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();
Expand Down Expand Up @@ -273,23 +278,58 @@
}
}

/**
* 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() {

Check warning on line 288 in src/extensionsIntegrated/appUpdater/update-electron.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Move function '_getDefaultLinuxInstallerURL' to the outer scope.

See more on https://sonarcloud.io/project/issues?id=phcode-dev_phoenix&issues=AaB2y0JQp8EFLDUUjtVH&open=AaB2y0JQp8EFLDUUjtVH&pullRequest=3181
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<string>} 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) {

Check warning on line 326 in src/extensionsIntegrated/appUpdater/update-electron.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Move function 'launchLinuxUpdater' to the outer scope.

See more on https://sonarcloud.io/project/issues?id=phcode-dev_phoenix&issues=AaB2y0JQp8EFLDUUjtVI&open=AaB2y0JQp8EFLDUUjtVI&pullRequest=3181
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
Expand Down Expand Up @@ -350,7 +390,7 @@
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) {
Expand Down Expand Up @@ -390,6 +430,10 @@
_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 {
Expand Down
Loading
Loading