源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
Dependency audit / audit (push) Canceled after 0s
CI / plan (push) Canceled after 0s
Gitoxide helper admission / macos-latest (push) Canceled after 0s
Gitoxide helper admission / ubuntu-latest (push) Canceled after 0s
Gitoxide helper admission / windows-latest (push) Canceled after 0s
Runtime Host peer admission / quality (push) Canceled after 0s
Windows recovery / windows_recovery (push) Canceled after 0s
CI / heavy (push) Canceled after 0s
CI / test (push) Canceled after 0s
Dependency audit / audit (push) Canceled after 0s
CI / plan (push) Canceled after 0s
Gitoxide helper admission / macos-latest (push) Canceled after 0s
Gitoxide helper admission / ubuntu-latest (push) Canceled after 0s
Gitoxide helper admission / windows-latest (push) Canceled after 0s
Runtime Host peer admission / quality (push) Canceled after 0s
Windows recovery / windows_recovery (push) Canceled after 0s
CI / heavy (push) Canceled after 0s
CI / test (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
#!/usr/bin/env node
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { createRequire } from 'node:module';
|
||||
import { dirname } from 'node:path';
|
||||
import { setTimeout as sleep } from 'node:timers/promises';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const DEFAULT_MAX_ATTEMPTS = 3;
|
||||
const DEFAULT_BASE_DELAY_MS = 1_000;
|
||||
const transientCodes = new Set([
|
||||
'ECONNRESET',
|
||||
'EAI_AGAIN',
|
||||
'ETIMEDOUT',
|
||||
'UND_ERR_BODY_TIMEOUT',
|
||||
'UND_ERR_CONNECT_TIMEOUT',
|
||||
'UND_ERR_HEADERS_TIMEOUT',
|
||||
'UND_ERR_SOCKET',
|
||||
]);
|
||||
const transientHttpStatuses = new Set([408, 425, 429, 500, 502, 503, 504]);
|
||||
|
||||
export function isTransientElectronInstallFailure(contextOutput) {
|
||||
return contextOutput.split(/\r?\n/).some((line) => {
|
||||
if (!line) return false;
|
||||
try {
|
||||
const context = JSON.parse(line);
|
||||
return (
|
||||
(context.kind === 'fetch' && transientCodes.has(context.code)) ||
|
||||
(context.kind === 'http' && transientHttpStatuses.has(context.status))
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function installWithRetry(
|
||||
runAttempt,
|
||||
{
|
||||
maxAttempts = DEFAULT_MAX_ATTEMPTS,
|
||||
baseDelayMs = DEFAULT_BASE_DELAY_MS,
|
||||
wait = sleep,
|
||||
warn = console.warn,
|
||||
} = {},
|
||||
) {
|
||||
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
||||
const result = await runAttempt(attempt);
|
||||
if (
|
||||
result.status === 0 ||
|
||||
typeof result.status !== 'number' ||
|
||||
result.signal ||
|
||||
result.spawnError ||
|
||||
!isTransientElectronInstallFailure(result.context ?? '')
|
||||
) {
|
||||
return result;
|
||||
}
|
||||
if (attempt === maxAttempts) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const delayMs = baseDelayMs * 2 ** (attempt - 1);
|
||||
warn(
|
||||
`[install-electron] transient download failure on attempt ${attempt}/${maxAttempts}; retrying in ${delayMs}ms`,
|
||||
);
|
||||
await wait(delayMs);
|
||||
}
|
||||
|
||||
throw new Error('Electron install retry loop ended unexpectedly');
|
||||
}
|
||||
|
||||
export function electronInstallerSpawnOptions(repositoryRoot) {
|
||||
return {
|
||||
cwd: repositoryRoot,
|
||||
encoding: 'utf8',
|
||||
stdio: ['inherit', 'inherit', 'pipe', 'pipe'],
|
||||
};
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// electron is declared by apps/desktop, not the workspace root, so anchor
|
||||
// resolution at that package. Resolving from this script (under scripts/)
|
||||
// only reaches electron when the installer hoists it to the root
|
||||
// node_modules; anchoring at apps/desktop works under any layout.
|
||||
const require = createRequire(new URL('../apps/desktop/package.json', import.meta.url));
|
||||
const installer = require.resolve('electron/install.js');
|
||||
const launcher = fileURLToPath(new URL('./run-electron-installer.cjs', import.meta.url));
|
||||
const repositoryRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
||||
|
||||
const result = await installWithRetry(() => {
|
||||
const child = spawnSync(
|
||||
process.execPath,
|
||||
[launcher, installer],
|
||||
electronInstallerSpawnOptions(repositoryRoot),
|
||||
);
|
||||
if (child.stderr) {
|
||||
process.stderr.write(child.stderr);
|
||||
}
|
||||
if (child.error) {
|
||||
const message = child.error.stack ?? child.error.message;
|
||||
process.stderr.write(`${message}\n`);
|
||||
return {
|
||||
status: child.status,
|
||||
signal: child.signal,
|
||||
spawnError: true,
|
||||
context: child.output?.[3] ?? '',
|
||||
stderr: `${child.stderr ?? ''}\n${message}`,
|
||||
};
|
||||
}
|
||||
return {
|
||||
status: child.status,
|
||||
signal: child.signal,
|
||||
context: child.output?.[3] ?? '',
|
||||
stderr: child.stderr ?? '',
|
||||
};
|
||||
});
|
||||
|
||||
if (result.signal) {
|
||||
process.kill(process.pid, result.signal);
|
||||
} else {
|
||||
process.exitCode = result.status ?? 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
||||
await main();
|
||||
}
|
||||
Reference in New Issue
Block a user