@swimmingliu/autovpn 1.6.5 → 1.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/doctor/checks.js +17 -2
- package/dist/pipeline/deploy.js +27 -50
- package/dist/pipeline/orchestrator.js +6 -3
- package/dist/runtime/templates.js +37 -0
- package/dist/templates/share-worker/vpn.js +4813 -0
- package/dist/templates/vmess_node.js +27 -0
- package/dist/web/renderer/i18n.js +1 -1
- package/package.json +1 -1
package/dist/doctor/checks.js
CHANGED
|
@@ -5,6 +5,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
5
5
|
import { parse } from '@iarna/toml';
|
|
6
6
|
import { profileSummary } from '../config/profile.js';
|
|
7
7
|
import { resolveArtifactsRoot, resolveProfilePath } from '../runtime/paths.js';
|
|
8
|
+
import { resolveShareWorkerTemplatePath, resolveWorkerTemplatePath } from '../runtime/templates.js';
|
|
8
9
|
import { normalizeManagedToolCommandForSpawn, resolveManagedNpmTool } from '../runtime/managed-tools.js';
|
|
9
10
|
const JAVASCRIPT_OBFUSCATOR_VERSION = '5.4.3';
|
|
10
11
|
const WRANGLER_VERSION = '4.106.0';
|
|
@@ -201,13 +202,27 @@ export async function runDoctor(projectRoot, argv, env = process.env, options =
|
|
|
201
202
|
const configuredSources = sourceValues.filter((source) => source.enabled && source.url === 'set' && source.key === 'set');
|
|
202
203
|
const nodeToolChecks = await checkNodeTools(projectRoot, env, options);
|
|
203
204
|
const cloudflareChecks = await checkCloudflare(summary, deploy, env, projectRoot, options);
|
|
205
|
+
let workerTemplatePath = '';
|
|
206
|
+
let shareWorkerTemplatePath = '';
|
|
207
|
+
try {
|
|
208
|
+
workerTemplatePath = resolveWorkerTemplatePath(projectRoot);
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
workerTemplatePath = '';
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
shareWorkerTemplatePath = resolveShareWorkerTemplatePath(projectRoot, env);
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
shareWorkerTemplatePath = '';
|
|
218
|
+
}
|
|
204
219
|
const checks = [
|
|
205
220
|
check('node_version', 'pass', `Node ${process.versions.node}`, { required: '>=20' }),
|
|
206
221
|
check('project_root', 'pass', 'Project root resolved', { path: projectRoot }),
|
|
207
222
|
check('profile_path', pathWritable(profilePath) ? 'pass' : 'fail', pathWritable(profilePath) ? 'Profile is readable and writable' : 'Profile path is not writable', { profile_path: profilePath, exists: fs.existsSync(profilePath) }),
|
|
208
223
|
check('artifacts_root', pathWritable(artifactsRoot) ? 'pass' : 'fail', pathWritable(artifactsRoot) ? 'Artifacts root is writable' : 'Artifacts root is not writable', { path: artifactsRoot }),
|
|
209
|
-
check('worker_template',
|
|
210
|
-
check('share_worker_template',
|
|
224
|
+
check('worker_template', workerTemplatePath ? 'pass' : 'fail', workerTemplatePath ? 'Worker template exists' : 'Worker template is missing', { path: workerTemplatePath || path.join(projectRoot, 'templates', 'vmess_node.js') }),
|
|
225
|
+
check('share_worker_template', shareWorkerTemplatePath ? 'pass' : 'warn', shareWorkerTemplatePath ? 'Share worker template exists' : 'Share worker template is missing', { path: shareWorkerTemplatePath || path.join(projectRoot, 'templates', 'share-worker', 'vpn.js') }),
|
|
211
226
|
configuredSources.length
|
|
212
227
|
? check('sources', 'pass', 'At least one enabled source is configured', { configured_count: configuredSources.length })
|
|
213
228
|
: check('sources', 'warn', 'No enabled source has both URL and key configured', { configured_count: 0, key_state: 'missing' }),
|
package/dist/pipeline/deploy.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { spawn as defaultSpawn } from 'node:child_process';
|
|
3
|
-
import {
|
|
3
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { mergeProjectEnv } from '../runtime/env.js';
|
|
5
|
+
import { resolveShareWorkerTemplatePath } from '../runtime/templates.js';
|
|
5
6
|
import { normalizeManagedToolCommandForSpawn, resolveManagedNpmTool } from '../runtime/managed-tools.js';
|
|
6
7
|
const PYTHON_DEPLOY_HELPER = `
|
|
7
8
|
import json
|
|
@@ -47,6 +48,7 @@ json.dump(result, sys.stdout, ensure_ascii=False)
|
|
|
47
48
|
sys.stdout.write("\\n")
|
|
48
49
|
`;
|
|
49
50
|
const PAGES_PRODUCTION_BRANCH = 'main';
|
|
51
|
+
const CLOUDFLARE_API_MAX_ATTEMPTS = 3;
|
|
50
52
|
const FALLBACK_SUFFIX_PATTERN = /^(?<prefix>.+)-(?<suffix>\d+)$/;
|
|
51
53
|
const CLOUDFLARE_API_BASE_URL = 'https://api.cloudflare.com/client/v4';
|
|
52
54
|
const NETWORK_ERROR_MARKERS = [
|
|
@@ -354,51 +356,11 @@ function buildShareProjectUpdatePayload(sourceProject, runtimeEnv, options) {
|
|
|
354
356
|
deployment_configs: rewriteShareProjectSubValue(clonedConfigs, options)
|
|
355
357
|
};
|
|
356
358
|
}
|
|
357
|
-
async function fileExists(candidate) {
|
|
358
|
-
try {
|
|
359
|
-
await access(candidate);
|
|
360
|
-
return true;
|
|
361
|
-
}
|
|
362
|
-
catch {
|
|
363
|
-
return false;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
function expandHomePath(candidate) {
|
|
367
|
-
if (candidate === '~') {
|
|
368
|
-
return process.env.HOME || candidate;
|
|
369
|
-
}
|
|
370
|
-
if (candidate.startsWith('~/')) {
|
|
371
|
-
return process.env.HOME ? path.join(process.env.HOME, candidate.slice(2)) : candidate;
|
|
372
|
-
}
|
|
373
|
-
return candidate;
|
|
374
|
-
}
|
|
375
|
-
async function resolveShareProjectWorkerSourcePath(projectRoot, env) {
|
|
376
|
-
const candidates = [
|
|
377
|
-
clean(env.VPN_AUTOMATION_SHARE_WORKER_PATH),
|
|
378
|
-
path.join(projectRoot, 'electron', 'runtime', 'share-worker', 'vpn.js'),
|
|
379
|
-
path.join(projectRoot, 'templates', 'share-worker', 'vpn.js'),
|
|
380
|
-
path.join(path.dirname(projectRoot), 'cloudflarevpn', 'edgetunnel', 'vpn.js')
|
|
381
|
-
].filter(Boolean).map((candidate) => path.resolve(expandHomePath(candidate)));
|
|
382
|
-
const seen = new Set();
|
|
383
|
-
const uniqueCandidates = candidates.filter((candidate) => {
|
|
384
|
-
if (seen.has(candidate)) {
|
|
385
|
-
return false;
|
|
386
|
-
}
|
|
387
|
-
seen.add(candidate);
|
|
388
|
-
return true;
|
|
389
|
-
});
|
|
390
|
-
for (const candidate of uniqueCandidates) {
|
|
391
|
-
if (await fileExists(candidate)) {
|
|
392
|
-
return candidate;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
throw new Error(`share worker source not found; tried: ${uniqueCandidates.join(', ')}`);
|
|
396
|
-
}
|
|
397
359
|
function buildShareProjectBundleDir(projectRoot) {
|
|
398
360
|
return path.join(projectRoot, 'electron', 'runtime', 'share-worker', 'share_pages_bundle');
|
|
399
361
|
}
|
|
400
362
|
async function stageShareProjectWorkerBundle(projectRoot, env) {
|
|
401
|
-
const sourcePath =
|
|
363
|
+
const sourcePath = resolveShareWorkerTemplatePath(projectRoot, env);
|
|
402
364
|
const bundleDir = buildShareProjectBundleDir(projectRoot);
|
|
403
365
|
const workerEntry = path.join(bundleDir, '_worker.js');
|
|
404
366
|
await mkdir(bundleDir, { recursive: true });
|
|
@@ -508,12 +470,30 @@ export class CloudflareHttpClient {
|
|
|
508
470
|
this.headers = cloudflareHeaders(credentials);
|
|
509
471
|
this.fetchImpl = options.fetch ?? fetch;
|
|
510
472
|
}
|
|
473
|
+
async fetchWithTransientRetries(input, options, timeoutMs) {
|
|
474
|
+
for (let attempt = 1; attempt <= CLOUDFLARE_API_MAX_ATTEMPTS; attempt += 1) {
|
|
475
|
+
try {
|
|
476
|
+
return await this.fetchImpl(input, {
|
|
477
|
+
...options,
|
|
478
|
+
signal: options.signal ?? AbortSignal.timeout(timeoutMs)
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
catch (error) {
|
|
482
|
+
const transientFailure = error instanceof TypeError
|
|
483
|
+
|| (error instanceof Error && error.name === 'TimeoutError');
|
|
484
|
+
const retryable = transientFailure && !options.signal?.aborted;
|
|
485
|
+
if (!retryable || attempt === CLOUDFLARE_API_MAX_ATTEMPTS) {
|
|
486
|
+
throw error;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
throw new Error('Cloudflare request failed');
|
|
491
|
+
}
|
|
511
492
|
async apiRequest(pathname, options = {}) {
|
|
512
|
-
const response = await this.
|
|
493
|
+
const response = await this.fetchWithTransientRetries(`${CLOUDFLARE_API_BASE_URL}${pathname}`, {
|
|
513
494
|
...options,
|
|
514
|
-
headers: { ...this.headers, ...options.headers }
|
|
515
|
-
|
|
516
|
-
});
|
|
495
|
+
headers: { ...this.headers, ...options.headers }
|
|
496
|
+
}, 20_000);
|
|
517
497
|
if (!response.ok) {
|
|
518
498
|
const body = await responseBodyText(response);
|
|
519
499
|
throw new Error(`Cloudflare API request failed (${response.status}): ${body || response.statusText}`);
|
|
@@ -665,10 +645,7 @@ export class CloudflareHttpClient {
|
|
|
665
645
|
return this.apiRequest(`/accounts/${accountId}/pages/projects/${projectName}`, { method: 'DELETE' });
|
|
666
646
|
}
|
|
667
647
|
async verifyUrl(url, expectedFragment = '') {
|
|
668
|
-
const response = await this.
|
|
669
|
-
method: 'GET',
|
|
670
|
-
signal: AbortSignal.timeout(30_000)
|
|
671
|
-
});
|
|
648
|
+
const response = await this.fetchWithTransientRetries(url, { method: 'GET' }, 30_000);
|
|
672
649
|
if (!response.ok) {
|
|
673
650
|
const body = await responseBodyText(response);
|
|
674
651
|
throw new Error(`URL verification failed (${response.status}): ${body || response.statusText}`);
|
|
@@ -5,6 +5,7 @@ import { parse } from '@iarna/toml';
|
|
|
5
5
|
import { mergeProjectEnv } from '../runtime/env.js';
|
|
6
6
|
import { resolveArtifactsRoot, resolveProfilePath } from '../runtime/paths.js';
|
|
7
7
|
import { redactText } from '../runtime/redaction.js';
|
|
8
|
+
import { resolveWorkerTemplatePath } from '../runtime/templates.js';
|
|
8
9
|
import { fetchSourceLinksWithBackend } from './extract.js';
|
|
9
10
|
import { canonicalVmessKey, dedupeVmessLinksWithBackend, parseVmessLink } from './dedupe.js';
|
|
10
11
|
import { normalizeSpeedTestConfig, probeSpeedtestLinksInNode, selectSpeedtestCandidates, speedtestLinksWithBackend, testSpeedtestLinkInNode } from './speedtest.js';
|
|
@@ -650,7 +651,7 @@ export async function runNodePipeline(options, context = {}) {
|
|
|
650
651
|
await writeLines(artifactDir, 'vpn_node_emoji.txt', postprocessed.links);
|
|
651
652
|
await setStage('postprocess', 'success');
|
|
652
653
|
await setStage('render', 'running');
|
|
653
|
-
const template = await readFile(
|
|
654
|
+
const template = await readFile(resolveWorkerTemplatePath(projectRoot), 'utf8');
|
|
654
655
|
const rendered = await renderMainDataWithBackend({ template, links: postprocessed.links }, { cwd: projectRoot, env });
|
|
655
656
|
await setStage('render', 'success');
|
|
656
657
|
await setStage('obfuscate', 'running');
|
|
@@ -819,6 +820,8 @@ export async function retryNodePipelineStage(options, context = {}) {
|
|
|
819
820
|
await writeReport();
|
|
820
821
|
throw new Error('No links passed availability');
|
|
821
822
|
}
|
|
823
|
+
const availableLinkSet = new Set(availableLinks);
|
|
824
|
+
availabilityResults = availabilityResults.filter((result) => availableLinkSet.has(result.link));
|
|
822
825
|
await setStage('availability', 'success');
|
|
823
826
|
}
|
|
824
827
|
else if (isStageAtOrAfter(stage, 'postprocess')) {
|
|
@@ -861,7 +864,7 @@ export async function retryNodePipelineStage(options, context = {}) {
|
|
|
861
864
|
throw new Error('No postprocess output available to retry render');
|
|
862
865
|
}
|
|
863
866
|
await setStage('render', 'running');
|
|
864
|
-
const template = await readFile(
|
|
867
|
+
const template = await readFile(resolveWorkerTemplatePath(projectRoot), 'utf8');
|
|
865
868
|
const rendered = await renderMainDataWithBackend({ template, links: finalLinks }, { cwd: projectRoot, env });
|
|
866
869
|
await writeFile(path.join(retryArtifactDir, 'vmess_node.js'), rendered.rendered_source, 'utf8');
|
|
867
870
|
await setStage('render', 'success');
|
|
@@ -1185,7 +1188,7 @@ export async function resumeNodePipeline(options, context = {}) {
|
|
|
1185
1188
|
}
|
|
1186
1189
|
await setStage('postprocess', 'success');
|
|
1187
1190
|
await setStage('render', 'running');
|
|
1188
|
-
const template = await readFile(
|
|
1191
|
+
const template = await readFile(resolveWorkerTemplatePath(projectRoot), 'utf8');
|
|
1189
1192
|
const rendered = await renderMainDataWithBackend({ template, links: postprocessed.links }, { cwd: projectRoot, env });
|
|
1190
1193
|
await writeFile(path.join(artifactDir, 'vmess_node.js'), rendered.rendered_source, 'utf8');
|
|
1191
1194
|
await setStage('render', 'success');
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
const packagedWorkerTemplatePath = fileURLToPath(new URL('../templates/vmess_node.js', import.meta.url));
|
|
5
|
+
const packagedShareWorkerTemplatePath = fileURLToPath(new URL('../templates/share-worker/vpn.js', import.meta.url));
|
|
6
|
+
function expandHomePath(candidate, env) {
|
|
7
|
+
if (candidate === '~') {
|
|
8
|
+
return env.HOME || candidate;
|
|
9
|
+
}
|
|
10
|
+
if (candidate.startsWith('~/')) {
|
|
11
|
+
return env.HOME ? path.join(env.HOME, candidate.slice(2)) : candidate;
|
|
12
|
+
}
|
|
13
|
+
return candidate;
|
|
14
|
+
}
|
|
15
|
+
function firstExistingPath(candidates, message) {
|
|
16
|
+
const uniqueCandidates = [...new Set(candidates.filter(Boolean).map((candidate) => path.resolve(candidate)))];
|
|
17
|
+
const resolved = uniqueCandidates.find((candidate) => fs.existsSync(candidate));
|
|
18
|
+
if (resolved) {
|
|
19
|
+
return resolved;
|
|
20
|
+
}
|
|
21
|
+
throw new Error(`${message}; checked: ${uniqueCandidates.join(', ')}`);
|
|
22
|
+
}
|
|
23
|
+
export function resolveWorkerTemplatePath(projectRoot) {
|
|
24
|
+
return firstExistingPath([
|
|
25
|
+
path.join(projectRoot, 'templates', 'vmess_node.js'),
|
|
26
|
+
packagedWorkerTemplatePath
|
|
27
|
+
], 'Worker template is missing');
|
|
28
|
+
}
|
|
29
|
+
export function resolveShareWorkerTemplatePath(projectRoot, env = process.env) {
|
|
30
|
+
return firstExistingPath([
|
|
31
|
+
expandHomePath(String(env.VPN_AUTOMATION_SHARE_WORKER_PATH ?? '').trim(), env),
|
|
32
|
+
path.join(projectRoot, 'electron', 'runtime', 'share-worker', 'vpn.js'),
|
|
33
|
+
path.join(projectRoot, 'templates', 'share-worker', 'vpn.js'),
|
|
34
|
+
packagedShareWorkerTemplatePath,
|
|
35
|
+
path.join(path.dirname(projectRoot), 'cloudflarevpn', 'edgetunnel', 'vpn.js')
|
|
36
|
+
], 'Share Worker template is missing');
|
|
37
|
+
}
|