mirror of
https://github.com/Foundry376/Mailspring.git
synced 2026-06-24 12:21:36 +02:00
4f09302077
* Upgrade Electron from 39.2.7 to 41.0.2 and address deprecations
- Bump `electron` from 39.2.7 → 41.0.2 in package.json
- Electron 40 deprecated direct clipboard API access from renderer
processes. Migrate all renderer-side `clipboard.writeText()` calls
(imported from 'electron') to the standard Web API
`navigator.clipboard.writeText()` across 9 files:
app/src/components/evented-iframe.tsx
app/src/components/participants-text-field.tsx
app/internal_packages/activity/lib/dashboard/share-button.tsx
app/internal_packages/main-calendar/lib/core/event-attendees-input.tsx
app/internal_packages/message-list/lib/message-controls.tsx
app/internal_packages/onboarding/lib/oauth-signin-page.tsx
app/internal_packages/thread-list/lib/thread-list-context-menu.ts
app/internal_packages/thread-sharing/lib/copy-button.tsx
app/internal_packages/thread-sharing/lib/thread-sharing-button.tsx
- Keep Electron clipboard.read() in composer-editor.tsx for
platform-specific format reads (public.file-url, FileNameW,
text/uri-list) which have no navigator.clipboard equivalent
- Main-process clipboard usage in application.ts is unchanged (not
subject to the renderer deprecation)
- No action needed for Electron 41 breaking changes: the PDF
WebContents change does not affect this app (PDFs are rendered via
pdf.js in a separate BrowserWindow, not via MimeHandlerViewGuest),
and the cookie change-event cause update does not affect this app
(no cookie change listeners present)
https://claude.ai/code/session_01WBGx8XkrU7X1FhuXGdMygt
* Update package-lock.json for Electron 41.0.2 upgrade
Regenerate lock file to satisfy npm ci after bumping electron from
39.2.7 to 41.0.2. Also updates transitive deps @types/node and
undici-types to versions compatible with Node 24 (bundled in Electron 41).
https://claude.ai/code/session_01WBGx8XkrU7X1FhuXGdMygt
* Fix unhandled Promise rejections and dead clipboard test spies
navigator.clipboard.writeText() is async; the previous change left all
call sites fire-and-forget. Two problems fixed:
1. All 8 non-CopyButton call sites now chain .catch() so failures are
logged to the console instead of producing unhandled rejection
warnings (e.g. when context menu callbacks fire after the window
loses focus).
2. copy-button.tsx previously showed "Copied" immediately regardless of
whether the write succeeded. The UI update and 2-second timer are now
moved into .then() so the label only changes on success. A truthy
sentinel (true) is assigned to _timeout before the async call to
block re-entry while the write is in-flight; clearTimeout(true) is a
safe no-op so componentWillUnmount is unaffected. On failure _timeout
is reset to null so the user can retry.
3. master-before-each.ts: remove dead spyOn(electron.clipboard, ...)
stubs (production code no longer calls electron.clipboard.writeText
from renderer) and replace with spyOn(navigator.clipboard, ...)
stubs that return resolved Promises, matching the async API contract.
https://claude.ai/code/session_01WBGx8XkrU7X1FhuXGdMygt
* Fix node-gyp config so better-sqlite3 finds its prebuilt for Electron 41
Two related problems:
1. app/package.json pinned electron to "39.2.7" in its resolutions field
while the root package.json (and npm_config_target in postinstall) was
already at "41.0.2". The stale pin would override the correct version in
the app's dependency subtree. Updated to "41.0.2" to stay in sync.
2. The electron npm environment in postinstall.js did not set
npm_config_node_gyp. Without it, npm falls back to its own bundled
node-gyp, which is typically older and does not recognise Electron 41 /
Node.js 22 headers. prebuild-install (used by better-sqlite3) calls
node-gyp when resolving the prebuilt binary URL; if it gets the wrong
version it fails to match the ABI and either falls back to a slow
source compile or errors out. Pointing npm_config_node_gyp at the
project's node-gyp v12 binary ensures the correct headers are fetched
and the prebuilt .node file is used.
https://claude.ai/code/session_01WBGx8XkrU7X1FhuXGdMygt
* Fix
---------
Co-authored-by: Claude <noreply@anthropic.com>
179 lines
6.2 KiB
JavaScript
179 lines
6.2 KiB
JavaScript
#!/usr/bin/env node
|
|
/* eslint global-require: 0 */
|
|
/* eslint quote-props: 0 */
|
|
const path = require('path');
|
|
const https = require('https');
|
|
const fs = require('fs');
|
|
const rimraf = require('rimraf');
|
|
const targz = require('targz');
|
|
const { safeExec } = require('./utils/child-process-wrapper.js');
|
|
const { execSync } = require('child_process');
|
|
|
|
const appDependencies = require('../app/package.json').dependencies;
|
|
const rootDependencies = require('../package.json').dependencies;
|
|
const npmElectronTarget = rootDependencies.electron;
|
|
const npmEnvs = {
|
|
system: process.env,
|
|
electron: Object.assign({}, process.env, {
|
|
npm_config_target: npmElectronTarget,
|
|
npm_config_arch: process.env.OVERRIDE_TO_INTEL ? 'x64' : process.arch,
|
|
npm_config_target_arch: process.env.OVERRIDE_TO_INTEL ? 'x64' : process.arch,
|
|
npm_config_disturl: 'https://electronjs.org/headers',
|
|
npm_config_runtime: 'electron',
|
|
}),
|
|
};
|
|
|
|
function npm(cmd, options) {
|
|
const { cwd, env } = Object.assign({ cwd: '.', env: 'system' }, options);
|
|
|
|
return new Promise((resolve, reject) => {
|
|
console.log(
|
|
`\n-- Running npm ${cmd} in ${cwd} with ${env} config (arch=${npmEnvs[env].npm_config_target_arch}) --`
|
|
);
|
|
|
|
safeExec(
|
|
`npm ${cmd}`,
|
|
{
|
|
cwd: path.resolve(__dirname, '..', cwd),
|
|
env: npmEnvs[env],
|
|
},
|
|
(err, stdout) => {
|
|
return err ? reject(err) : resolve(stdout);
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
function getMailsyncURL(callback) {
|
|
const distKey = `${process.platform}-${process.arch}`;
|
|
const distDir = {
|
|
'darwin-x64': 'osx',
|
|
'darwin-arm64': 'osx',
|
|
'win32-x64': 'win-ia32', // At this time, Mailsync is still 32-bit
|
|
'win32-ia32': 'win-ia32',
|
|
'linux-x64': 'linux',
|
|
'linux-arm64': 'linux-arm64',
|
|
'linux-ia32': null,
|
|
}[distKey];
|
|
|
|
if (!distDir) {
|
|
console.error(
|
|
`\nSorry, a Mailspring Mailsync build for your machine (${distKey}) is not yet available.`
|
|
);
|
|
return;
|
|
}
|
|
|
|
const out = execSync('git submodule status ./mailsync');
|
|
const [_, hash] = /[\+-]([A-Za-z0-9]{8})/.exec(out.toString());
|
|
callback(
|
|
`https://mailspring-builds.s3.amazonaws.com/mailsync/${hash}/${distDir}/mailsync.tar.gz`
|
|
);
|
|
}
|
|
|
|
function downloadMailsync() {
|
|
getMailsyncURL(distS3URL => {
|
|
https.get(distS3URL, response => {
|
|
if (response.statusCode === 200) {
|
|
response.pipe(fs.createWriteStream(`app/mailsync.tar.gz`));
|
|
response.on('end', () => {
|
|
console.log(
|
|
`\nDownloaded Mailsync prebuilt binary from ${distS3URL} to ./app/mailsync.tar.gz.`
|
|
);
|
|
targz.decompress(
|
|
{
|
|
src: `app/mailsync.tar.gz`,
|
|
dest: 'app/',
|
|
},
|
|
err => {
|
|
if (!err) {
|
|
console.log(`\nUnpackaged Mailsync into ./app.`);
|
|
} else {
|
|
console.error(`\nEncountered an error unpacking: ${err}`);
|
|
}
|
|
}
|
|
);
|
|
});
|
|
} else {
|
|
console.error(
|
|
`Sorry, an error occurred while fetching the Mailspring Mailsync build for your machine\n(${distS3URL})\n`
|
|
);
|
|
if (process.env.CI) {
|
|
throw new Error('Mailsync build not available.');
|
|
}
|
|
response.pipe(process.stderr);
|
|
response.on('end', () => console.error('\n'));
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// For speed, we cache app/node_modules. However, we need to
|
|
// be sure to do a full rebuild of native node modules when the
|
|
// Electron version changes. To do this we check a marker file.
|
|
const appPath = path.resolve(__dirname, '..', 'app');
|
|
const appModulesPath = path.resolve(appPath, 'node_modules');
|
|
const cacheVersionPath = path.join(appModulesPath, '.postinstall-target-version');
|
|
const cacheElectronTarget =
|
|
fs.existsSync(cacheVersionPath) && fs.readFileSync(cacheVersionPath).toString();
|
|
|
|
if (cacheElectronTarget !== npmElectronTarget) {
|
|
console.log(
|
|
`\n-- Clearing app/node_modules (${cacheElectronTarget} !== ${npmElectronTarget}) --`
|
|
);
|
|
rimraf.sync(appModulesPath);
|
|
}
|
|
|
|
// Audit is emitted with npm ls, no need to run it on EVERY command which is an odd default
|
|
|
|
async function sqliteMissingNanosleep() {
|
|
return new Promise(resolve => {
|
|
const sqliteLibDir = path.join(appModulesPath, 'better-sqlite3', 'build', 'Release');
|
|
const staticLib = path.join(sqliteLibDir, 'sqlite3.a');
|
|
const sharedLib = path.join(sqliteLibDir, 'better_sqlite3.node');
|
|
|
|
// Check the static lib first (build-from-source), then the prebuilt .node binary
|
|
const target = fs.existsSync(staticLib) ? staticLib : sharedLib;
|
|
safeExec(`nm '${target}' | grep nanosleep`, { ignoreStderr: true }, (err, resp) => {
|
|
resolve(resp === '');
|
|
});
|
|
});
|
|
}
|
|
|
|
async function run() {
|
|
// run `npm install` in ./app with Electron NPM config
|
|
await npm(`install --no-audit`, { cwd: './app', env: 'electron' });
|
|
|
|
// run `npm dedupe` in ./app with Electron NPM config
|
|
await npm(`dedupe --no-audit`, { cwd: './app', env: 'electron' });
|
|
|
|
// run `npm ls` in ./app - detects missing peer dependencies, etc.
|
|
await npm(`ls`, { cwd: './app', env: 'electron' });
|
|
|
|
// if SQlite was not built with HAVE_NANOSLEEP, do not ship this build! We need nanosleep
|
|
// support so that multiple processes can connect to the sqlite file at the same time.
|
|
// Without it, transactions only retry every 1 sec instead of every 10ms, leading to
|
|
// awful db lock contention. https://github.com/WiseLibs/better-sqlite3/issues/597
|
|
if (['linux', 'darwin'].includes(process.platform) && (await sqliteMissingNanosleep())) {
|
|
console.error(`better-sqlite compiled without -HAVE_NANOSLEEP, do not ship this build!`);
|
|
process.exit(1001);
|
|
}
|
|
|
|
// write the marker with the electron version
|
|
fs.writeFileSync(cacheVersionPath, npmElectronTarget);
|
|
|
|
// if the user hasn't cloned the mailsync module, download
|
|
// the binary for their operating system that was shipped to S3.
|
|
if (!fs.existsSync('./mailsync/build.sh')) {
|
|
console.log(`\n-- Downloading the last released version of Mailspring mailsync --`);
|
|
downloadMailsync();
|
|
} else {
|
|
console.log(
|
|
`\n-- You have the Mailspring mailsync submodule. If you'd prefer ` +
|
|
`to develop with a pre-built binary, remove the submodule and re-run ` +
|
|
`'npm run postinstall' to download the latest binary for your machine. --`
|
|
);
|
|
}
|
|
}
|
|
|
|
run();
|