@swimmingliu/autovpn 1.6.6 → 1.6.7
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/README.md +7 -54
- package/dist/backend/select-backend.js +0 -3
- package/dist/cli/commands/index.js +4 -0
- package/dist/cli/main.js +7 -25
- package/dist/cli/native-commands.js +1 -0
- package/dist/jobs/commands.js +13 -0
- package/dist/jobs/process.js +92 -10
- package/dist/pipeline/availability.js +0 -96
- package/dist/pipeline/dedupe.js +1 -69
- package/dist/pipeline/deploy.js +138 -251
- package/dist/pipeline/extract.js +0 -80
- package/dist/pipeline/obfuscate.js +1 -80
- package/dist/pipeline/orchestrator.js +105 -127
- package/dist/pipeline/postprocess.js +1 -85
- package/dist/pipeline/proxy-runtime.js +5 -0
- package/dist/pipeline/render.js +1 -73
- package/dist/pipeline/speedtest.js +89 -100
- package/dist/runtime/paths.js +11 -2
- package/dist/web/renderer/i18n.js +1 -1
- package/package.json +3 -4
- package/dist/backend/python-backend.js +0 -242
- package/lib/cache.mjs +0 -14
- package/lib/errors.mjs +0 -11
- package/lib/install-python-cli.mjs +0 -63
- package/lib/runner.mjs +0 -113
|
@@ -285,32 +285,28 @@ async function restoreResumeSpeedResults(artifactDir, eventLog, passedLinks) {
|
|
|
285
285
|
}
|
|
286
286
|
async function forEachWithConcurrency(items, concurrency, mapper) {
|
|
287
287
|
let nextIndex = 0;
|
|
288
|
+
let failed = false;
|
|
289
|
+
let firstError;
|
|
288
290
|
const workerCount = Math.max(1, Math.min(Math.max(1, Math.floor(concurrency)), items.length));
|
|
289
|
-
|
|
290
|
-
while (nextIndex < items.length) {
|
|
291
|
+
const workers = Array.from({ length: workerCount }, async () => {
|
|
292
|
+
while (nextIndex < items.length && !failed) {
|
|
291
293
|
const index = nextIndex;
|
|
292
294
|
nextIndex += 1;
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
if (active >= limit) {
|
|
303
|
-
await new Promise((resolve) => queue.push(resolve));
|
|
304
|
-
}
|
|
305
|
-
active += 1;
|
|
306
|
-
try {
|
|
307
|
-
return await task();
|
|
308
|
-
}
|
|
309
|
-
finally {
|
|
310
|
-
active -= 1;
|
|
311
|
-
queue.shift()?.();
|
|
295
|
+
try {
|
|
296
|
+
await mapper(items[index]);
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
if (!failed) {
|
|
300
|
+
failed = true;
|
|
301
|
+
firstError = error;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
312
304
|
}
|
|
313
|
-
};
|
|
305
|
+
});
|
|
306
|
+
await Promise.allSettled(workers);
|
|
307
|
+
if (failed) {
|
|
308
|
+
throw firstError;
|
|
309
|
+
}
|
|
314
310
|
}
|
|
315
311
|
async function speedtestResumeStateFromEventLog(eventLog) {
|
|
316
312
|
const probes = new Map();
|
|
@@ -418,98 +414,6 @@ export async function runNodePipeline(options, context = {}) {
|
|
|
418
414
|
let availabilityResults = [];
|
|
419
415
|
let availableLinks = [];
|
|
420
416
|
const streamedDeduped = new Set();
|
|
421
|
-
const speedTasks = [];
|
|
422
|
-
const availabilityTasks = [];
|
|
423
|
-
const limitSpeedtest = createLimiter(Number(speedConfig.concurrency ?? 1));
|
|
424
|
-
const limitAvailability = createLimiter(Number(speedConfig.concurrency ?? 1));
|
|
425
|
-
let speedCompleted = 0;
|
|
426
|
-
let availabilityCompleted = 0;
|
|
427
|
-
let speedArtifactFlush = Promise.resolve();
|
|
428
|
-
let availabilityArtifactFlush = Promise.resolve();
|
|
429
|
-
const flushStreamingSpeedArtifacts = async () => {
|
|
430
|
-
const resultSnapshot = [...speedResults];
|
|
431
|
-
const linkSnapshot = [...passedSpeedLinks];
|
|
432
|
-
speedArtifactFlush = speedArtifactFlush.catch(() => undefined).then(async () => {
|
|
433
|
-
summary.counts.speedtest_links = linkSnapshot.length;
|
|
434
|
-
await writeLines(artifactDir, 'vpn_node_speedtest.txt', linkSnapshot);
|
|
435
|
-
await writeJson(artifactDir, 'vpn_node_speedtest_report.json', resultSnapshot);
|
|
436
|
-
await writeReport();
|
|
437
|
-
});
|
|
438
|
-
await speedArtifactFlush;
|
|
439
|
-
};
|
|
440
|
-
const flushStreamingAvailabilityArtifacts = async () => {
|
|
441
|
-
const resultSnapshot = [...availabilityResults];
|
|
442
|
-
const linkSnapshot = resultSnapshot.filter((result) => result.all_passed).map((result) => result.link);
|
|
443
|
-
availabilityArtifactFlush = availabilityArtifactFlush.catch(() => undefined).then(async () => {
|
|
444
|
-
summary.counts.availability_links = linkSnapshot.length;
|
|
445
|
-
await writeLines(artifactDir, 'vpn_node_availability.txt', linkSnapshot);
|
|
446
|
-
await writeJson(artifactDir, 'vpn_node_availability_report.json', resultSnapshot);
|
|
447
|
-
await writeReport();
|
|
448
|
-
});
|
|
449
|
-
await availabilityArtifactFlush;
|
|
450
|
-
};
|
|
451
|
-
const runStreamingAvailability = (speedResult) => {
|
|
452
|
-
const task = limitAvailability(async () => {
|
|
453
|
-
const results = context.stages?.availability
|
|
454
|
-
? await context.stages.availability([speedResult], profile.speed_test ?? {}, runtimePath, profile.availability_targets)
|
|
455
|
-
: await checkLinkAvailabilityBatchWithBackend({
|
|
456
|
-
results: [speedResult],
|
|
457
|
-
config: profile.speed_test ?? {},
|
|
458
|
-
runtime_path: runtimePath,
|
|
459
|
-
targets: profile.availability_targets
|
|
460
|
-
}, { cwd: projectRoot, env: runtimeStageEnv });
|
|
461
|
-
for (const result of results) {
|
|
462
|
-
availabilityResults.push(result);
|
|
463
|
-
availabilityCompleted += 1;
|
|
464
|
-
await flushStreamingAvailabilityArtifacts();
|
|
465
|
-
emit('availability_link_result', {
|
|
466
|
-
completed: availabilityCompleted,
|
|
467
|
-
total: passedSpeedLinks.length,
|
|
468
|
-
link: result.link,
|
|
469
|
-
all_passed: result.all_passed,
|
|
470
|
-
provider_results: result.provider_results
|
|
471
|
-
});
|
|
472
|
-
}
|
|
473
|
-
});
|
|
474
|
-
availabilityTasks.push(task);
|
|
475
|
-
task.catch(() => undefined);
|
|
476
|
-
};
|
|
477
|
-
const runStreamingSpeedtest = (link) => {
|
|
478
|
-
const task = limitSpeedtest(async () => {
|
|
479
|
-
const result = context.stages?.speedtestLink
|
|
480
|
-
? await context.stages.speedtestLink(link, profile.speed_test ?? {}, runtimePath)
|
|
481
|
-
: await testSpeedtestLinkInNode({ link, config: profile.speed_test, runtime_path: runtimePath }, {
|
|
482
|
-
cwd: projectRoot,
|
|
483
|
-
env: runtimeStageEnv
|
|
484
|
-
});
|
|
485
|
-
speedResults.push(result);
|
|
486
|
-
speedCompleted += 1;
|
|
487
|
-
const passed = result.reachable && result.average_download_mb_s >= speedConfig.min_download_mb_s;
|
|
488
|
-
emit('speedtest_result', {
|
|
489
|
-
completed: speedCompleted,
|
|
490
|
-
total: dedupedLinks.length,
|
|
491
|
-
link: result.link,
|
|
492
|
-
reachable: result.reachable,
|
|
493
|
-
average_download_mb_s: result.average_download_mb_s,
|
|
494
|
-
latency_ms: result.latency_ms,
|
|
495
|
-
passed_threshold: passed,
|
|
496
|
-
error: result.error ?? ''
|
|
497
|
-
});
|
|
498
|
-
emit('log', {
|
|
499
|
-
message: `[speedtest] ${speedCompleted}/${dedupedLinks.length} reachable=${result.reachable} speed=${result.average_download_mb_s}MB/s`
|
|
500
|
-
});
|
|
501
|
-
if (passed) {
|
|
502
|
-
passedSpeedLinks.push(result.link);
|
|
503
|
-
await flushStreamingSpeedArtifacts();
|
|
504
|
-
runStreamingAvailability(result);
|
|
505
|
-
}
|
|
506
|
-
else if (useStreamingStages) {
|
|
507
|
-
await flushStreamingSpeedArtifacts();
|
|
508
|
-
}
|
|
509
|
-
});
|
|
510
|
-
speedTasks.push(task);
|
|
511
|
-
task.catch(() => undefined);
|
|
512
|
-
};
|
|
513
417
|
const onExtractedLinks = async (links) => {
|
|
514
418
|
for (const link of links) {
|
|
515
419
|
rawLinks.push(link);
|
|
@@ -519,7 +423,6 @@ export async function runNodePipeline(options, context = {}) {
|
|
|
519
423
|
}
|
|
520
424
|
streamedDeduped.add(key);
|
|
521
425
|
dedupedLinks.push(link);
|
|
522
|
-
runStreamingSpeedtest(link);
|
|
523
426
|
}
|
|
524
427
|
};
|
|
525
428
|
await setStage('extract', 'running');
|
|
@@ -594,8 +497,81 @@ export async function runNodePipeline(options, context = {}) {
|
|
|
594
497
|
emit('log', {
|
|
595
498
|
message: `[speedtest] runtime_core=${requestedRuntime === 'direct' ? 'direct' : 'mihomo'} probe_url=${speedConfig.probe_url}`
|
|
596
499
|
});
|
|
597
|
-
|
|
598
|
-
|
|
500
|
+
const probes = context.stages?.speedtestProbe
|
|
501
|
+
? await context.stages.speedtestProbe(dedupedLinks, profile.speed_test ?? {}, runtimePath)
|
|
502
|
+
: await probeSpeedtestLinksInNode({ links: dedupedLinks, config: profile.speed_test, runtime_path: runtimePath }, {
|
|
503
|
+
cwd: projectRoot,
|
|
504
|
+
env: runtimeStageEnv
|
|
505
|
+
});
|
|
506
|
+
for (let index = 0; index < probes.length; index += 1) {
|
|
507
|
+
const result = probes[index];
|
|
508
|
+
emit('log', { message: `[speedtest:probe] ${index + 1}/${dedupedLinks.length} reachable=${result.reachable} latency=${result.latency_ms}ms` });
|
|
509
|
+
emit('speedtest_probe_result', {
|
|
510
|
+
completed: index + 1,
|
|
511
|
+
total: dedupedLinks.length,
|
|
512
|
+
link: result.link,
|
|
513
|
+
reachable: result.reachable,
|
|
514
|
+
latency_ms: result.latency_ms,
|
|
515
|
+
error: result.error ?? ''
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
const candidateLinks = selectSpeedtestCandidates(probes, speedConfig.max_download_candidates);
|
|
519
|
+
const reachableCount = probes.filter((probe) => probe.reachable).length;
|
|
520
|
+
emit('log', { message: `[speedtest] selected ${candidateLinks.length}/${reachableCount} reachable links for full download test` });
|
|
521
|
+
emit('speedtest_selected', {
|
|
522
|
+
total_links: dedupedLinks.length,
|
|
523
|
+
reachable_count: reachableCount,
|
|
524
|
+
candidate_count: candidateLinks.length
|
|
525
|
+
});
|
|
526
|
+
const probeByLink = new Map(probes.map((probe) => [probe.link, probe]));
|
|
527
|
+
const resultByLink = new Map();
|
|
528
|
+
for (const probe of probes) {
|
|
529
|
+
if (!probe.reachable) {
|
|
530
|
+
resultByLink.set(probe.link, {
|
|
531
|
+
link: probe.link,
|
|
532
|
+
reachable: false,
|
|
533
|
+
average_download_mb_s: 0,
|
|
534
|
+
latency_ms: probe.latency_ms,
|
|
535
|
+
error: probe.error ?? ''
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
let speedCompleted = 0;
|
|
540
|
+
await forEachWithConcurrency(candidateLinks, speedConfig.concurrency, async (link) => {
|
|
541
|
+
const result = context.stages?.speedtestLink
|
|
542
|
+
? await context.stages.speedtestLink(link, profile.speed_test ?? {}, runtimePath)
|
|
543
|
+
: await testSpeedtestLinkInNode({ link, config: profile.speed_test, runtime_path: runtimePath }, {
|
|
544
|
+
cwd: projectRoot,
|
|
545
|
+
env: runtimeStageEnv
|
|
546
|
+
});
|
|
547
|
+
if (result.reachable && result.latency_ms <= 0) {
|
|
548
|
+
result.latency_ms = probeByLink.get(result.link)?.latency_ms ?? 0;
|
|
549
|
+
}
|
|
550
|
+
resultByLink.set(result.link, result);
|
|
551
|
+
speedCompleted += 1;
|
|
552
|
+
const passed = result.reachable && result.average_download_mb_s >= speedConfig.min_download_mb_s;
|
|
553
|
+
emit('speedtest_result', {
|
|
554
|
+
completed: speedCompleted,
|
|
555
|
+
total: candidateLinks.length,
|
|
556
|
+
link: result.link,
|
|
557
|
+
reachable: result.reachable,
|
|
558
|
+
average_download_mb_s: result.average_download_mb_s,
|
|
559
|
+
latency_ms: result.latency_ms,
|
|
560
|
+
passed_threshold: passed,
|
|
561
|
+
error: result.error ?? ''
|
|
562
|
+
});
|
|
563
|
+
emit('log', {
|
|
564
|
+
message: `[speedtest] ${speedCompleted}/${candidateLinks.length} reachable=${result.reachable} speed=${result.average_download_mb_s}MB/s`
|
|
565
|
+
});
|
|
566
|
+
});
|
|
567
|
+
speedResults = [
|
|
568
|
+
...probes.filter((probe) => !probe.reachable).map((probe) => resultByLink.get(probe.link)),
|
|
569
|
+
...candidateLinks.map((link) => resultByLink.get(link)).filter((result) => Boolean(result))
|
|
570
|
+
];
|
|
571
|
+
passedSpeedLinks = candidateLinks.filter((link) => {
|
|
572
|
+
const result = resultByLink.get(link);
|
|
573
|
+
return Boolean(result?.reachable && result.average_download_mb_s >= speedConfig.min_download_mb_s);
|
|
574
|
+
});
|
|
599
575
|
}
|
|
600
576
|
else {
|
|
601
577
|
speedResults = context.stages?.speedtest
|
|
@@ -622,20 +598,22 @@ export async function runNodePipeline(options, context = {}) {
|
|
|
622
598
|
}
|
|
623
599
|
const speedResultByLink = new Map(speedResults.map((result) => [result.link, result]));
|
|
624
600
|
const candidateSpeedResults = passedSpeedLinks.map((link) => speedResultByLink.get(link)).filter((result) => Boolean(result));
|
|
625
|
-
availabilityResults =
|
|
626
|
-
?
|
|
627
|
-
:
|
|
628
|
-
|
|
629
|
-
:
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
targets: profile.availability_targets
|
|
634
|
-
}, { cwd: projectRoot, env: runtimeStageEnv });
|
|
601
|
+
availabilityResults = context.stages?.availability
|
|
602
|
+
? await context.stages.availability(candidateSpeedResults, profile.speed_test ?? {}, runtimePath, profile.availability_targets)
|
|
603
|
+
: await checkLinkAvailabilityBatchWithBackend({
|
|
604
|
+
results: candidateSpeedResults,
|
|
605
|
+
config: profile.speed_test ?? {},
|
|
606
|
+
runtime_path: runtimePath,
|
|
607
|
+
targets: profile.availability_targets
|
|
608
|
+
}, { cwd: projectRoot, env: runtimeStageEnv });
|
|
635
609
|
availableLinks = availabilityResults.filter((result) => result.all_passed).map((result) => result.link);
|
|
636
610
|
summary.counts.availability_links = availableLinks.length;
|
|
637
611
|
await writeLines(artifactDir, 'vpn_node_availability.txt', availableLinks);
|
|
638
612
|
await writeJson(artifactDir, 'vpn_node_availability_report.json', availabilityResults);
|
|
613
|
+
if (passedSpeedLinks.length > 0 && availableLinks.length === 0) {
|
|
614
|
+
await setStage('availability', 'failed');
|
|
615
|
+
throw new Error('No links passed availability');
|
|
616
|
+
}
|
|
639
617
|
await setStage('availability', 'success', !useStreamingStages);
|
|
640
618
|
await setStage('postprocess', 'running');
|
|
641
619
|
const countryLookup = context.stages?.countryLookup ?? defaultCountryFor;
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import { spawn as defaultSpawn } from 'node:child_process';
|
|
3
|
-
import { mergeProjectEnv } from '../runtime/env.js';
|
|
4
1
|
const EMOJI_MAP = {
|
|
5
2
|
AE: '🇦🇪',
|
|
6
3
|
AR: '🇦🇷',
|
|
@@ -46,30 +43,6 @@ const DEFAULT_FILTERS = {
|
|
|
46
43
|
per_country_limit: {}
|
|
47
44
|
};
|
|
48
45
|
const LEADING_EMOJI_AND_COUNTRY_PATTERN = /^(?:\S+\s+)?([A-Za-z]{2})\s+(.*)$/;
|
|
49
|
-
const PYTHON_POSTPROCESS_HELPER = `
|
|
50
|
-
import json
|
|
51
|
-
import sys
|
|
52
|
-
from vpn_automation.config.models import FilterConfig
|
|
53
|
-
from vpn_automation.pipeline.postprocess import decorate_link_with_country, select_links_by_country_limit
|
|
54
|
-
|
|
55
|
-
payload = json.load(sys.stdin)
|
|
56
|
-
filters = payload.get("filters")
|
|
57
|
-
ranked_links = payload.get("ranked_links") or []
|
|
58
|
-
filter_config = FilterConfig() if filters is None else FilterConfig(
|
|
59
|
-
excluded_country_codes=list(filters.get("excluded_country_codes") or []),
|
|
60
|
-
per_country_limit=dict(filters.get("per_country_limit") or {}),
|
|
61
|
-
)
|
|
62
|
-
selected = select_links_by_country_limit(
|
|
63
|
-
[(item["link"], {}, item.get("country_code") or item.get("countryCode") or "") for item in ranked_links],
|
|
64
|
-
filter_config,
|
|
65
|
-
)
|
|
66
|
-
country_by_link = {
|
|
67
|
-
item["link"]: item.get("country_code") or item.get("countryCode") or ""
|
|
68
|
-
for item in ranked_links
|
|
69
|
-
}
|
|
70
|
-
json.dump({"links": [decorate_link_with_country(link, country_by_link[link]) for link in selected]}, sys.stdout, ensure_ascii=False)
|
|
71
|
-
sys.stdout.write("\\n")
|
|
72
|
-
`;
|
|
73
46
|
export function parseVmessLink(link) {
|
|
74
47
|
const encoded = link.replace(/^vmess:\/\//, '');
|
|
75
48
|
const padded = encoded + '='.repeat((4 - (encoded.length % 4)) % 4);
|
|
@@ -149,64 +122,7 @@ export function runPostprocess(input) {
|
|
|
149
122
|
links: selected.map((link) => decorateLinkWithCountry(link, countries.get(link) ?? ''))
|
|
150
123
|
};
|
|
151
124
|
}
|
|
152
|
-
export function selectPipelineStageBackend(stage, env = process.env) {
|
|
153
|
-
void stage;
|
|
154
|
-
void env;
|
|
155
|
-
return 'node';
|
|
156
|
-
}
|
|
157
|
-
async function defaultResolvePythonCli(env) {
|
|
158
|
-
// @ts-expect-error Phase 1 runner remains plain ESM JavaScript.
|
|
159
|
-
const runner = await import('../../lib/runner.mjs');
|
|
160
|
-
return runner.resolveOrInstallPythonCli({ env });
|
|
161
|
-
}
|
|
162
|
-
function pythonCommandFor(resolved) {
|
|
163
|
-
const command = resolved.command;
|
|
164
|
-
const name = path.basename(command).toLowerCase();
|
|
165
|
-
if (['autovpn', 'autovpn.exe'].includes(name)) {
|
|
166
|
-
const executable = process.platform === 'win32' ? 'python.exe' : 'python';
|
|
167
|
-
return path.join(path.dirname(command), executable);
|
|
168
|
-
}
|
|
169
|
-
return process.platform === 'win32' ? 'python.exe' : 'python3';
|
|
170
|
-
}
|
|
171
|
-
async function postprocessWithPython(input, options) {
|
|
172
|
-
const env = mergeProjectEnv(options.cwd ?? process.cwd(), options.env ?? process.env);
|
|
173
|
-
const resolved = options.resolvePythonCli ? await options.resolvePythonCli() : await defaultResolvePythonCli(env);
|
|
174
|
-
const helperInput = { ...input, filters: resolveFilters(input.filters) };
|
|
175
|
-
const child = (options.spawn ?? defaultSpawn)(pythonCommandFor(resolved), ['-c', PYTHON_POSTPROCESS_HELPER], {
|
|
176
|
-
cwd: options.cwd ?? process.cwd(),
|
|
177
|
-
env,
|
|
178
|
-
stdio: ['pipe', 'pipe', 'pipe']
|
|
179
|
-
});
|
|
180
|
-
let stdout = '';
|
|
181
|
-
let stderr = '';
|
|
182
|
-
child.stdout?.on('data', (chunk) => {
|
|
183
|
-
stdout += String(chunk);
|
|
184
|
-
});
|
|
185
|
-
child.stderr?.on('data', (chunk) => {
|
|
186
|
-
stderr += String(chunk);
|
|
187
|
-
});
|
|
188
|
-
const completion = new Promise((resolve, reject) => {
|
|
189
|
-
child.on('error', reject);
|
|
190
|
-
child.on('close', (code) => {
|
|
191
|
-
if (code !== 0) {
|
|
192
|
-
reject(new Error(`Python postprocess backend failed with exit code ${code}: ${stderr.trim()}`));
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
try {
|
|
196
|
-
resolve(JSON.parse(stdout));
|
|
197
|
-
}
|
|
198
|
-
catch (error) {
|
|
199
|
-
reject(new Error(`Python postprocess backend returned invalid JSON: ${error instanceof Error ? error.message : String(error)}`));
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
});
|
|
203
|
-
child.stdin?.write(JSON.stringify(helperInput));
|
|
204
|
-
child.stdin?.end();
|
|
205
|
-
return completion;
|
|
206
|
-
}
|
|
207
125
|
export async function postprocessLinksWithBackend(input, options = {}) {
|
|
208
|
-
|
|
209
|
-
return options.pythonPostprocess ? options.pythonPostprocess(input) : postprocessWithPython(input, options);
|
|
210
|
-
}
|
|
126
|
+
void options;
|
|
211
127
|
return runPostprocess(input);
|
|
212
128
|
}
|
|
@@ -274,6 +274,11 @@ export async function openMihomoRuntime(link, options = {}) {
|
|
|
274
274
|
child.off('error', onStartupError);
|
|
275
275
|
child.on('error', () => { });
|
|
276
276
|
await close();
|
|
277
|
+
if (error instanceof Error
|
|
278
|
+
&& error.name !== 'AbortError'
|
|
279
|
+
&& /^proxy port \d+ did not open in time$/.test(error.message)) {
|
|
280
|
+
error.code = 'AUTOVPN_INTERNAL_TIMEOUT';
|
|
281
|
+
}
|
|
277
282
|
throw error;
|
|
278
283
|
}
|
|
279
284
|
return {
|
package/dist/pipeline/render.js
CHANGED
|
@@ -1,20 +1,4 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import { spawn as defaultSpawn } from 'node:child_process';
|
|
3
|
-
import { mergeProjectEnv } from '../runtime/env.js';
|
|
4
1
|
export const MAIN_DATA_PLACEHOLDER = '__MAIN_DATA__';
|
|
5
|
-
const PYTHON_RENDER_HELPER = `
|
|
6
|
-
import json
|
|
7
|
-
import sys
|
|
8
|
-
from vpn_automation.pipeline.render import replace_main_data
|
|
9
|
-
|
|
10
|
-
payload = json.load(sys.stdin)
|
|
11
|
-
json.dump(
|
|
12
|
-
{"rendered_source": replace_main_data(payload["template"], list(payload.get("links") or []))},
|
|
13
|
-
sys.stdout,
|
|
14
|
-
ensure_ascii=False,
|
|
15
|
-
)
|
|
16
|
-
sys.stdout.write("\\n")
|
|
17
|
-
`;
|
|
18
2
|
export function replaceMainData(template, links) {
|
|
19
3
|
const occurrences = template.split(MAIN_DATA_PLACEHOLDER).length - 1;
|
|
20
4
|
if (occurrences !== 1) {
|
|
@@ -27,63 +11,7 @@ export function runRender(input) {
|
|
|
27
11
|
rendered_source: replaceMainData(input.template, input.links ?? [])
|
|
28
12
|
};
|
|
29
13
|
}
|
|
30
|
-
export function selectPipelineStageBackend(stage, env = process.env) {
|
|
31
|
-
void stage;
|
|
32
|
-
void env;
|
|
33
|
-
return 'node';
|
|
34
|
-
}
|
|
35
|
-
async function defaultResolvePythonCli(env) {
|
|
36
|
-
// @ts-expect-error Phase 1 runner remains plain ESM JavaScript.
|
|
37
|
-
const runner = await import('../../lib/runner.mjs');
|
|
38
|
-
return runner.resolveOrInstallPythonCli({ env });
|
|
39
|
-
}
|
|
40
|
-
function pythonCommandFor(resolved) {
|
|
41
|
-
const command = resolved.command;
|
|
42
|
-
const name = path.basename(command).toLowerCase();
|
|
43
|
-
if (['autovpn', 'autovpn.exe'].includes(name)) {
|
|
44
|
-
const executable = process.platform === 'win32' ? 'python.exe' : 'python';
|
|
45
|
-
return path.join(path.dirname(command), executable);
|
|
46
|
-
}
|
|
47
|
-
return process.platform === 'win32' ? 'python.exe' : 'python3';
|
|
48
|
-
}
|
|
49
|
-
async function renderWithPython(input, options) {
|
|
50
|
-
const env = mergeProjectEnv(options.cwd ?? process.cwd(), options.env ?? process.env);
|
|
51
|
-
const resolved = options.resolvePythonCli ? await options.resolvePythonCli() : await defaultResolvePythonCli(env);
|
|
52
|
-
const child = (options.spawn ?? defaultSpawn)(pythonCommandFor(resolved), ['-c', PYTHON_RENDER_HELPER], {
|
|
53
|
-
cwd: options.cwd ?? process.cwd(),
|
|
54
|
-
env,
|
|
55
|
-
stdio: ['pipe', 'pipe', 'pipe']
|
|
56
|
-
});
|
|
57
|
-
let stdout = '';
|
|
58
|
-
let stderr = '';
|
|
59
|
-
child.stdout?.on('data', (chunk) => {
|
|
60
|
-
stdout += String(chunk);
|
|
61
|
-
});
|
|
62
|
-
child.stderr?.on('data', (chunk) => {
|
|
63
|
-
stderr += String(chunk);
|
|
64
|
-
});
|
|
65
|
-
const completion = new Promise((resolve, reject) => {
|
|
66
|
-
child.on('error', reject);
|
|
67
|
-
child.on('close', (code) => {
|
|
68
|
-
if (code !== 0) {
|
|
69
|
-
reject(new Error(`Python render backend failed with exit code ${code}: ${stderr.trim()}`));
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
try {
|
|
73
|
-
resolve(JSON.parse(stdout));
|
|
74
|
-
}
|
|
75
|
-
catch (error) {
|
|
76
|
-
reject(new Error(`Python render backend returned invalid JSON: ${error instanceof Error ? error.message : String(error)}`));
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
child.stdin?.write(JSON.stringify(input));
|
|
81
|
-
child.stdin?.end();
|
|
82
|
-
return completion;
|
|
83
|
-
}
|
|
84
14
|
export async function renderMainDataWithBackend(input, options = {}) {
|
|
85
|
-
|
|
86
|
-
return options.pythonRender ? options.pythonRender(input) : renderWithPython(input, options);
|
|
87
|
-
}
|
|
15
|
+
void options;
|
|
88
16
|
return runRender(input);
|
|
89
17
|
}
|