Skip to content
Open
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
11 changes: 7 additions & 4 deletions apps/desktop/app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,13 @@ Packaging notes:
Externalized dependencies are loaded from this app directory at runtime, so a
root-level `patch-package` patch does not modify the copy installed here.
After Electron Builder installs appDir dependencies, `yarn install-app-deps`
copies the patched `electron-updater` files from the workspace dependency into
the packaged runtime dependency and verifies both copies before packaging
continues. The root-level `patch-package` patch remains the single source of
truth.
dynamically intersects every committed root patch with package instances found
recursively under appDir `node_modules`. It applies the complete matching patch
to every unpatched instance and then verifies the complete patch in reverse
before packaging continues. The command is idempotent and fails if a dependency
is partially patched, has drifted, or is on a different version. Runtime lookup
never falls back to workspace `node_modules`; committed root patch files are the
single source of truth, with no package, file, or marker allowlist to maintain.

Security scanner caveat:

Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"clean:build": "rimraf ./build-electron && rimraf ./app/build && rimraf ./app/dist && rimraf ./node_modules/.cache",
"start": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" yarn dev",
"start:rspack": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" yarn dev:rspack",
"install-app-deps": "electron-builder install-app-deps && node scripts/apply-runtime-patches.js && node scripts/verify-runtime-patches.js",
"install-app-deps": "electron-builder install-app-deps && node scripts/apply-runtime-patches.js && node scripts/audit-packaged-runtime-patches.js",
"dev": "npx concurrently \"yarn build:main:dev\" \"yarn dev:renderer\" \"cross-env LAUNCH_ELECTRON=true node scripts/dev.js\"",
"dev:rspack": "npx concurrently \"yarn build:main:dev\" \"yarn dev:renderer:rspack\" \"cross-env LAUNCH_ELECTRON=true node scripts/dev.js\"",
"dev:main": "electron --inspect=5858 --remote-debugging-port=9222 --remote-debugging-address=127.0.0.1 app/dist/app.js",
Expand Down
211 changes: 155 additions & 56 deletions apps/desktop/scripts/apply-runtime-patches.js
Original file line number Diff line number Diff line change
@@ -1,74 +1,173 @@
const assert = require('assert');
const fs = require('fs');
const path = require('path');

const {
electronUpdaterRuntimePatchFiles,
} = require('./electron-updater-runtime-patch-files');
PATCH_STATE,
classifyPatchState,
discoverPackagedRuntimePatchTargets,
findMatchingPatchDescriptor,
normalizeGitPath,
runGitApply,
} = require('./audit-packaged-runtime-patches');

function failPatch(message) {
process.stderr.write(`${message}\n`);
process.exit(1);
}
const PATCH_ACTION = {
alreadyPatched: 'ALREADY_PATCHED',
applied: 'APPLIED',
};

function readPackageMetadata(packageJsonPath, description) {
if (!fs.existsSync(packageJsonPath)) {
failPatch(`${description} is missing: ${packageJsonPath}.`);
function processPackageInstance({
packageInstance,
patchesByPackageName,
repositoryRoot,
}) {
const { packagePatches, patchDescriptor } = findMatchingPatchDescriptor(
packageInstance,
patchesByPackageName,
);
const relativePackageRoot = normalizeGitPath(
path.relative(repositoryRoot, packageInstance.packageRoot),
);
if (!patchDescriptor) {
return {
failure: `${packageInstance.packageName}@${packageInstance.version} at ${relativePackageRoot} does not match committed patch version(s): ${packagePatches
.map((candidate) => candidate.version)
.join(', ')}.`,
};
}
try {
return JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
} catch (error) {
failPatch(
`${description} is invalid: ${
error instanceof Error ? error.message : String(error)
}`,
);

const patchOptions = {
packageName: packageInstance.packageName,
packageRoot: packageInstance.packageRoot,
patchFilePath: patchDescriptor.patchFilePath,
repositoryRoot,
};
const initialState = classifyPatchState(patchOptions);
if (initialState.state === PATCH_STATE.patched) {
return {
result: {
...packageInstance,
action: PATCH_ACTION.alreadyPatched,
patchFilePath: patchDescriptor.patchFilePath,
},
};
}
if (initialState.state === PATCH_STATE.drifted) {
return {
failure: `${packageInstance.packageName}@${packageInstance.version} at ${relativePackageRoot} is partially patched or has drifted from ${path.relative(
repositoryRoot,
patchDescriptor.patchFilePath,
)}.`,
};
}

const applyResult = runGitApply(patchOptions);
if (applyResult.error) {
throw applyResult.error;
}
if (!applyResult.ok) {
return {
failure: `Failed to apply ${path.relative(
repositoryRoot,
patchDescriptor.patchFilePath,
)} to ${relativePackageRoot}: ${applyResult.output || 'git apply failed'}.`,
};
}

const appliedState = classifyPatchState(patchOptions);
if (appliedState.state !== PATCH_STATE.patched) {
return {
failure: `${packageInstance.packageName}@${packageInstance.version} at ${relativePackageRoot} did not pass full patch verification after apply.`,
};
}
return {
result: {
...packageInstance,
action: PATCH_ACTION.applied,
patchFilePath: patchDescriptor.patchFilePath,
},
};
}

const desktopPackageRoot = path.join(__dirname, '..');
const runtimePackageRoot = path.join(
desktopPackageRoot,
'app/node_modules/electron-updater',
);
const runtimePackageJsonPath = path.join(runtimePackageRoot, 'package.json');
let workspacePackageJsonPath;
try {
workspacePackageJsonPath = require.resolve('electron-updater/package.json', {
paths: [desktopPackageRoot],
});
} catch {
failPatch(
`Workspace electron-updater dependency cannot be resolved from ${desktopPackageRoot}. Run yarn install first.`,
function applyPackagedRuntimePatches({
patchesRoot,
repositoryRoot,
runtimeNodeModulesRoot,
}) {
assert(
fs.existsSync(runtimeNodeModulesRoot),
`Runtime node_modules is missing: ${runtimeNodeModulesRoot}. Run electron-builder install-app-deps first.`,
);
}
const workspacePackageRoot = path.dirname(workspacePackageJsonPath);
const workspacePackage = readPackageMetadata(
workspacePackageJsonPath,
'Workspace electron-updater package metadata',
);
const runtimePackage = readPackageMetadata(
runtimePackageJsonPath,
'Runtime electron-updater package metadata',
);

if (runtimePackage.version !== workspacePackage.version) {
failPatch(
`electron-updater version mismatch: runtime=${runtimePackage.version}, workspace=${workspacePackage.version}.`,
const { packageInstances, patchDescriptors, patchesByPackageName } =
discoverPackagedRuntimePatchTargets({
patchesRoot,
runtimeNodeModulesRoot,
});
const failures = [];
const results = [];

for (const packageInstance of packageInstances) {
const { failure, result } = processPackageInstance({
packageInstance,
patchesByPackageName,
repositoryRoot,
});
if (failure) {
failures.push(failure);
} else if (result) {
results.push(result);
}
}

assert(
failures.length === 0,
`Packaged runtime patch application failed:\n${failures
.map((failure) => `- ${failure}`)
.join('\n')}`,
);

return {
packagedPatchCount: results.length,
rootPatchCount: patchDescriptors.length,
results,
};
}

for (const relativePath of electronUpdaterRuntimePatchFiles) {
const sourcePath = path.join(workspacePackageRoot, relativePath);
const destinationPath = path.join(runtimePackageRoot, relativePath);
if (!fs.existsSync(sourcePath)) {
failPatch(`Workspace electron-updater file is missing: ${sourcePath}.`);
}
if (!fs.existsSync(destinationPath)) {
failPatch(`Runtime electron-updater file is missing: ${destinationPath}.`);
function main() {
const repositoryRoot = path.resolve(__dirname, '../../..');
try {
const summary = applyPackagedRuntimePatches({
patchesRoot: path.join(repositoryRoot, 'patches'),
repositoryRoot,
runtimeNodeModulesRoot: path.join(
repositoryRoot,
'apps/desktop/app/node_modules',
),
});
for (const result of summary.results) {
process.stdout.write(
`${result.action} ${result.packageName}@${result.version} (${normalizeGitPath(
path.relative(repositoryRoot, result.packageRoot),
)})\n`,
);
}
process.stdout.write(
`Processed ${summary.packagedPatchCount} packaged runtime patch instance(s) from ${summary.rootPatchCount} committed patch(es).\n`,
);
} catch (error) {
process.stderr.write(
`${error instanceof Error ? error.message : String(error)}\n`,
);
process.exitCode = 1;
}
fs.copyFileSync(sourcePath, destinationPath);
}

process.stdout.write(
`Applied electron-updater ${runtimePackage.version} runtime patch.\n`,
);
if (require.main === module) {
main();
}

module.exports = {
PATCH_ACTION,
applyPackagedRuntimePatches,
};
Loading
Loading