@swimmingliu/autovpn 1.5.1 → 1.5.3
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/cli/main.js +12 -0
- package/dist/server/http.js +23 -0
- package/dist/server/runtime.js +56 -2
- package/dist/server/web-adapter.js +15 -3
- package/dist/web/renderer/i18n.js +1 -1
- package/package.json +4 -2
package/dist/cli/main.js
CHANGED
|
@@ -47,6 +47,15 @@ async function resolvePackageVersion(options) {
|
|
|
47
47
|
function hasFlag(argv, flag) {
|
|
48
48
|
return argv.includes(flag);
|
|
49
49
|
}
|
|
50
|
+
function hasProxyFlag(argv) {
|
|
51
|
+
return argv.some((value) => value === '--proxy' || value.startsWith('--proxy='));
|
|
52
|
+
}
|
|
53
|
+
function isPipelineProxyCommand(argv) {
|
|
54
|
+
if (!hasProxyFlag(argv)) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
return argv[0] === 'run' || (argv[0] === 'resume' && argv[1] === 'pipeline');
|
|
58
|
+
}
|
|
50
59
|
function eventOutputFormat(argv) {
|
|
51
60
|
return readOptionValue(argv, '--output') === 'human' ? 'human' : 'jsonl';
|
|
52
61
|
}
|
|
@@ -229,6 +238,9 @@ export async function runCliShell(argv, options = {}) {
|
|
|
229
238
|
});
|
|
230
239
|
return pythonFallbackBackend.executeCli(fallbackArgv);
|
|
231
240
|
};
|
|
241
|
+
if (isPipelineProxyCommand(normalizedArgv)) {
|
|
242
|
+
return runExplicitPythonFallback(normalizedArgv);
|
|
243
|
+
}
|
|
232
244
|
const nativeResult = await runNativeCommand(normalizedArgv, {
|
|
233
245
|
cwd,
|
|
234
246
|
env,
|
package/dist/server/http.js
CHANGED
|
@@ -2,6 +2,7 @@ import http from 'node:http';
|
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { fileURLToPath, URL } from 'node:url';
|
|
5
|
+
import QRCode from 'qrcode';
|
|
5
6
|
import { renderWebAdapterScript } from './web-adapter.js';
|
|
6
7
|
import { redactText } from '../runtime/redaction.js';
|
|
7
8
|
const SENSITIVE_KEYS = new Set([
|
|
@@ -139,6 +140,20 @@ export async function createAutoVpnServer(options) {
|
|
|
139
140
|
writeJson(response, 200, await options.runtime.loadState());
|
|
140
141
|
return;
|
|
141
142
|
}
|
|
143
|
+
if (request.method === 'POST' && url.pathname === '/api/profile') {
|
|
144
|
+
const body = await readJsonBody(request);
|
|
145
|
+
writeJson(response, 200, await options.runtime.saveProfile?.(body) ?? { ok: false, error: 'profile_save_unavailable' });
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (request.method === 'POST' && url.pathname === '/api/qr') {
|
|
149
|
+
const body = await readJsonBody(request);
|
|
150
|
+
const text = String(body.text ?? '');
|
|
151
|
+
writeJson(response, 200, {
|
|
152
|
+
ok: true,
|
|
153
|
+
dataUrl: await QRCode.toDataURL(text, { margin: 1, width: 220 })
|
|
154
|
+
});
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
142
157
|
if (request.method === 'GET' && url.pathname === '/api/events') {
|
|
143
158
|
response.writeHead(200, {
|
|
144
159
|
'Content-Type': 'text/event-stream; charset=utf-8',
|
|
@@ -165,6 +180,14 @@ export async function createAutoVpnServer(options) {
|
|
|
165
180
|
writeJson(response, 200, await options.runtime.stopRun?.() ?? { ok: true, requested: false });
|
|
166
181
|
return;
|
|
167
182
|
}
|
|
183
|
+
if (request.method === 'POST' && url.pathname === '/api/runs/retry-stage') {
|
|
184
|
+
const body = await readJsonBody(request);
|
|
185
|
+
writeJson(response, 202, await options.runtime.startRetry?.({
|
|
186
|
+
artifactDir: String(body.artifactDir ?? ''),
|
|
187
|
+
stage: String(body.stage ?? '')
|
|
188
|
+
}) ?? { ok: false, error: 'retry_stage_unavailable' });
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
168
191
|
if (request.method === 'GET' && !url.pathname.startsWith('/api/')) {
|
|
169
192
|
if (await serveRendererAsset(url, response)) {
|
|
170
193
|
return;
|
package/dist/server/runtime.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { artifactLatest, artifactList } from '../artifacts/list.js';
|
|
2
2
|
import { previewArtifact } from '../artifacts/preview.js';
|
|
3
|
-
import { profilePayload } from '../config/profile.js';
|
|
4
|
-
import { startDetachedRun as defaultStartDetachedRun, stopManagedJob as defaultStopManagedJob } from '../jobs/commands.js';
|
|
3
|
+
import { profilePayload, saveProfilePayload } from '../config/profile.js';
|
|
4
|
+
import { startDetachedRetry as defaultStartDetachedRetry, startDetachedRun as defaultStartDetachedRun, stopManagedJob as defaultStopManagedJob } from '../jobs/commands.js';
|
|
5
5
|
import { followLog as defaultFollowLog } from '../jobs/logs.js';
|
|
6
6
|
const DEPLOY_SECRET_KEYS = new Set([
|
|
7
7
|
'cloudflare_api_token',
|
|
@@ -30,6 +30,28 @@ export function sanitizeProfileForServer(profile) {
|
|
|
30
30
|
}
|
|
31
31
|
return safe;
|
|
32
32
|
}
|
|
33
|
+
function preserveRedactedSecrets(incoming, current) {
|
|
34
|
+
const merged = structuredClone(incoming ?? {});
|
|
35
|
+
const currentSources = (current.sources ?? {});
|
|
36
|
+
for (const [name, source] of Object.entries((merged.sources ?? {}))) {
|
|
37
|
+
const currentSource = currentSources[name] ?? {};
|
|
38
|
+
if (source && typeof source === 'object') {
|
|
39
|
+
for (const key of ['url', 'key']) {
|
|
40
|
+
if (source[key] === '<redacted>') {
|
|
41
|
+
source[key] = currentSource[key] ?? '';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const deploy = (merged.deploy ?? {});
|
|
47
|
+
const currentDeploy = (current.deploy ?? {});
|
|
48
|
+
for (const key of DEPLOY_SECRET_KEYS) {
|
|
49
|
+
if (deploy[key] === '<redacted>') {
|
|
50
|
+
deploy[key] = currentDeploy[key] ?? '';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return merged;
|
|
54
|
+
}
|
|
33
55
|
function normalizeLatestArtifact(projectRoot, env) {
|
|
34
56
|
const latest = artifactLatest(projectRoot, env);
|
|
35
57
|
if (!latest.ok || !latest.artifact_dir) {
|
|
@@ -61,6 +83,7 @@ export function createServerRuntime(options) {
|
|
|
61
83
|
let unsubscribeLogs;
|
|
62
84
|
const subscribers = new Set();
|
|
63
85
|
const startDetachedRun = options.startDetachedRun ?? defaultStartDetachedRun;
|
|
86
|
+
const startDetachedRetry = options.startDetachedRetry ?? defaultStartDetachedRetry;
|
|
64
87
|
const stopManagedJob = options.stopManagedJob ?? defaultStopManagedJob;
|
|
65
88
|
const followLog = options.followLog ?? defaultFollowLog;
|
|
66
89
|
function publish(event) {
|
|
@@ -112,6 +135,14 @@ export function createServerRuntime(options) {
|
|
|
112
135
|
deployment: (artifact?.deployment ?? {})
|
|
113
136
|
};
|
|
114
137
|
},
|
|
138
|
+
async saveProfile(profile) {
|
|
139
|
+
const current = profilePayload(options.projectRoot, options.env);
|
|
140
|
+
const merged = preserveRedactedSecrets(profile, current);
|
|
141
|
+
return {
|
|
142
|
+
ok: true,
|
|
143
|
+
profile: sanitizeProfileForServer(saveProfilePayload(options.projectRoot, merged, options.env))
|
|
144
|
+
};
|
|
145
|
+
},
|
|
115
146
|
async startRun(runOptions = {}) {
|
|
116
147
|
if (runState === 'running' || runState === 'stopping') {
|
|
117
148
|
return { ok: false, error: 'run_already_active' };
|
|
@@ -131,6 +162,29 @@ export function createServerRuntime(options) {
|
|
|
131
162
|
followJob(activeJobId);
|
|
132
163
|
return { ok: true, runId: activeJobId, job_id: activeJobId, status: job.status ?? 'running' };
|
|
133
164
|
},
|
|
165
|
+
async startRetry(retryOptions = {}) {
|
|
166
|
+
if (runState === 'running' || runState === 'stopping') {
|
|
167
|
+
return { ok: false, error: 'run_already_active' };
|
|
168
|
+
}
|
|
169
|
+
const artifactDir = String(retryOptions.artifactDir ?? '').trim();
|
|
170
|
+
const stage = String(retryOptions.stage ?? '').trim();
|
|
171
|
+
if (!artifactDir || !stage) {
|
|
172
|
+
return { ok: false, error: 'artifact_dir_and_stage_required' };
|
|
173
|
+
}
|
|
174
|
+
runState = 'running';
|
|
175
|
+
const job = await startDetachedRetry({
|
|
176
|
+
projectRoot: options.projectRoot,
|
|
177
|
+
artifactDir,
|
|
178
|
+
stage,
|
|
179
|
+
outputFormat: 'jsonl'
|
|
180
|
+
}, {
|
|
181
|
+
env: options.env,
|
|
182
|
+
cwd: options.projectRoot
|
|
183
|
+
});
|
|
184
|
+
activeJobId = String(job.job_id ?? '');
|
|
185
|
+
followJob(activeJobId);
|
|
186
|
+
return { ok: true, runId: activeJobId, job_id: activeJobId, status: job.status ?? 'running' };
|
|
187
|
+
},
|
|
134
188
|
async stopRun() {
|
|
135
189
|
if (runState !== 'running' || !activeJobId) {
|
|
136
190
|
return { ok: true, requested: false };
|
|
@@ -35,7 +35,10 @@ export function renderWebAdapterScript() {
|
|
|
35
35
|
const payload = await state();
|
|
36
36
|
return payload.profile || {};
|
|
37
37
|
},
|
|
38
|
-
saveProfile: async () => (
|
|
38
|
+
saveProfile: async (profile) => request('/api/profile', {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
body: JSON.stringify(profile || {})
|
|
41
|
+
}),
|
|
39
42
|
runPipeline: async (options = {}) => request('/api/runs', {
|
|
40
43
|
method: 'POST',
|
|
41
44
|
body: JSON.stringify({
|
|
@@ -50,7 +53,10 @@ export function renderWebAdapterScript() {
|
|
|
50
53
|
return { ok: true };
|
|
51
54
|
},
|
|
52
55
|
openPath: async () => ({ ok: false, error: 'open_path_unavailable_in_browser' }),
|
|
53
|
-
generateQr: async () => (
|
|
56
|
+
generateQr: async (text) => request('/api/qr', {
|
|
57
|
+
method: 'POST',
|
|
58
|
+
body: JSON.stringify({ text: String(text || '') })
|
|
59
|
+
}),
|
|
54
60
|
previewArtifact: async () => {
|
|
55
61
|
const payload = await state();
|
|
56
62
|
return payload.artifact ? { ok: true, ...payload.artifact } : { ok: false };
|
|
@@ -63,7 +69,13 @@ export function renderWebAdapterScript() {
|
|
|
63
69
|
const payload = await state();
|
|
64
70
|
return { ok: true, items: payload.retryArtifacts || [] };
|
|
65
71
|
},
|
|
66
|
-
retryStage: async () => (
|
|
72
|
+
retryStage: async (options = {}) => request('/api/runs/retry-stage', {
|
|
73
|
+
method: 'POST',
|
|
74
|
+
body: JSON.stringify({
|
|
75
|
+
artifactDir: String(options.artifactDir || ''),
|
|
76
|
+
stage: String(options.stage || '')
|
|
77
|
+
})
|
|
78
|
+
}),
|
|
67
79
|
copyText: async (text) => {
|
|
68
80
|
await navigator.clipboard.writeText(String(text || ''));
|
|
69
81
|
return { ok: true };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swimmingliu/autovpn",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.3",
|
|
4
4
|
"description": "npm wrapper for the AutoVPN headless CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -31,9 +31,11 @@
|
|
|
31
31
|
"license": "AGPL-3.0-only",
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^24.0.0",
|
|
34
|
+
"@types/qrcode": "^1.5.6",
|
|
34
35
|
"typescript": "^5.9.2"
|
|
35
36
|
},
|
|
36
37
|
"dependencies": {
|
|
37
|
-
"@iarna/toml": "^2.2.5"
|
|
38
|
+
"@iarna/toml": "^2.2.5",
|
|
39
|
+
"qrcode": "^1.5.4"
|
|
38
40
|
}
|
|
39
41
|
}
|