shennian 0.2.88 → 0.2.90
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/assets/wechat-channel/macos/manifest.json +22 -0
- package/dist/assets/wechat-channel/macos/shennian-wechat-channel-helper +0 -0
- package/dist/bin/shennian.js +1 -1
- package/dist/publish-build-manifest.json +548 -0
- package/dist/scripts/wechat-rpa-confirmation.mjs +5 -97
- package/dist/src/agent-env.js +4 -105
- package/dist/src/agents/adapter.d.ts +6 -0
- package/dist/src/agents/adapter.js +1 -19
- package/dist/src/agents/claude.js +8 -305
- package/dist/src/agents/codex-control.d.ts +35 -0
- package/dist/src/agents/codex-control.js +2 -0
- package/dist/src/agents/codex-utils.js +7 -200
- package/dist/src/agents/codex.d.ts +8 -0
- package/dist/src/agents/codex.js +15 -863
- package/dist/src/agents/command-spec.js +2 -413
- package/dist/src/agents/config-status.js +1 -226
- package/dist/src/agents/cursor.js +1 -249
- package/dist/src/agents/custom.js +4 -271
- package/dist/src/agents/detect.js +1 -56
- package/dist/src/agents/external-channel-instructions.js +10 -94
- package/dist/src/agents/gemini.js +1 -173
- package/dist/src/agents/manager.js +13 -157
- package/dist/src/agents/model-registry/cache.js +1 -37
- package/dist/src/agents/model-registry/discovery.js +2 -187
- package/dist/src/agents/model-registry/parsers.js +4 -447
- package/dist/src/agents/model-registry/runner.js +1 -30
- package/dist/src/agents/model-registry/service.js +1 -78
- package/dist/src/agents/model-registry/types.js +1 -8
- package/dist/src/agents/model-registry.js +1 -18
- package/dist/src/agents/openclaw.js +2 -275
- package/dist/src/agents/opencode.js +1 -231
- package/dist/src/agents/pi-context.js +12 -217
- package/dist/src/agents/pi.js +14 -723
- package/dist/src/agents/platform-instructions.js +9 -54
- package/dist/src/channels/base.d.ts +4 -1
- package/dist/src/channels/base.js +1 -3
- package/dist/src/channels/registry.js +1 -30
- package/dist/src/channels/reply-split.js +10 -89
- package/dist/src/channels/runtime.d.ts +1 -0
- package/dist/src/channels/runtime.js +5 -533
- package/dist/src/channels/secret-registry.d.ts +1 -0
- package/dist/src/channels/secret-registry.js +1 -46
- package/dist/src/channels/websocket.js +8 -378
- package/dist/src/channels/wechat-channel/anchor.d.ts +10 -0
- package/dist/src/channels/wechat-channel/anchor.js +1 -0
- package/dist/src/channels/wechat-channel/client.d.ts +74 -0
- package/dist/src/channels/wechat-channel/client.js +1 -0
- package/dist/src/channels/wechat-channel/cooldown.d.ts +15 -0
- package/dist/src/channels/wechat-channel/cooldown.js +1 -0
- package/dist/src/channels/wechat-channel/fingerprint.d.ts +28 -0
- package/dist/src/channels/wechat-channel/fingerprint.js +1 -0
- package/dist/src/channels/wechat-channel/helper-assets.d.ts +37 -0
- package/dist/src/channels/wechat-channel/helper-assets.js +1 -0
- package/dist/src/channels/wechat-channel/helper-client.d.ts +25 -0
- package/dist/src/channels/wechat-channel/helper-client.js +3 -0
- package/dist/src/channels/wechat-channel/helper-protocol.d.ts +84 -0
- package/dist/src/channels/wechat-channel/helper-protocol.js +1 -0
- package/dist/src/channels/wechat-channel/index.d.ts +17 -0
- package/dist/src/channels/wechat-channel/index.js +1 -0
- package/dist/src/channels/wechat-channel/ledger.d.ts +33 -0
- package/dist/src/channels/wechat-channel/ledger.js +1 -0
- package/dist/src/channels/wechat-channel/media-resolver.d.ts +32 -0
- package/dist/src/channels/wechat-channel/media-resolver.js +1 -0
- package/dist/src/channels/wechat-channel/message-key.d.ts +19 -0
- package/dist/src/channels/wechat-channel/message-key.js +1 -0
- package/dist/src/channels/wechat-channel/observer.d.ts +64 -0
- package/dist/src/channels/wechat-channel/observer.js +1 -0
- package/dist/src/channels/wechat-channel/outbound-ledger.d.ts +69 -0
- package/dist/src/channels/wechat-channel/outbound-ledger.js +2 -0
- package/dist/src/channels/wechat-channel/outbound-sender.d.ts +26 -0
- package/dist/src/channels/wechat-channel/outbound-sender.js +1 -0
- package/dist/src/channels/wechat-channel/preflight.d.ts +37 -0
- package/dist/src/channels/wechat-channel/preflight.js +1 -0
- package/dist/src/channels/wechat-channel/runner.d.ts +34 -0
- package/dist/src/channels/wechat-channel/runner.js +1 -0
- package/dist/src/channels/wechat-channel/runtime.d.ts +45 -0
- package/dist/src/channels/wechat-channel/runtime.js +1 -0
- package/dist/src/channels/wechat-channel/scheduler.d.ts +35 -0
- package/dist/src/channels/wechat-channel/scheduler.js +1 -0
- package/dist/src/channels/wechat-rpa/macos-flow.js +1 -96
- package/dist/src/channels/wechat-rpa/macos.js +6 -48
- package/dist/src/channels/wechat-rpa/normalizer.js +7 -127
- package/dist/src/channels/wechat-rpa.d.ts +21 -0
- package/dist/src/channels/wechat-rpa.js +6 -1022
- package/dist/src/channels/wecom.js +4 -357
- package/dist/src/commands/agent.js +6 -131
- package/dist/src/commands/daemon-windows.js +8 -48
- package/dist/src/commands/daemon.js +19 -1013
- package/dist/src/commands/external-attachments.js +1 -51
- package/dist/src/commands/external.js +1 -137
- package/dist/src/commands/manager.js +2 -389
- package/dist/src/commands/pair-qr.js +1 -6
- package/dist/src/commands/pair.js +9 -287
- package/dist/src/commands/tools.js +1 -34
- package/dist/src/commands/upgrade.js +1 -198
- package/dist/src/config/index.js +1 -35
- package/dist/src/daemon-log.js +6 -58
- package/dist/src/env-path.js +1 -64
- package/dist/src/fs/boundary.js +1 -126
- package/dist/src/fs/handler.js +1 -130
- package/dist/src/fs/security.js +1 -32
- package/dist/src/fs/text-decoder.d.ts +10 -0
- package/dist/src/fs/text-decoder.js +1 -0
- package/dist/src/index.js +2 -404
- package/dist/src/log-reporter.js +1 -16
- package/dist/src/manager/prompt.js +29 -34
- package/dist/src/manager/registry.js +2 -269
- package/dist/src/manager/runtime.js +19 -1003
- package/dist/src/native-fusion/config.js +1 -5
- package/dist/src/native-fusion/opencode-parser.js +3 -123
- package/dist/src/native-fusion/parser-common.js +8 -264
- package/dist/src/native-fusion/parsers.js +8 -729
- package/dist/src/native-fusion/service.d.ts +10 -0
- package/dist/src/native-fusion/service.js +2 -198
- package/dist/src/native-fusion/state.js +1 -22
- package/dist/src/native-fusion/types.js +1 -1
- package/dist/src/region.js +1 -88
- package/dist/src/relay/client.js +1 -343
- package/dist/src/session/archive-zip.js +1 -220
- package/dist/src/session/handlers/agent-config.js +1 -150
- package/dist/src/session/handlers/agents.js +1 -55
- package/dist/src/session/handlers/chat.js +2 -733
- package/dist/src/session/handlers/control.js +1 -55
- package/dist/src/session/handlers/fs.js +1 -747
- package/dist/src/session/handlers/session-refresh.js +1 -35
- package/dist/src/session/handlers/skills.js +1 -121
- package/dist/src/session/handlers/title.js +1 -60
- package/dist/src/session/handlers/tool-detail.d.ts +3 -0
- package/dist/src/session/handlers/tool-detail.js +1 -0
- package/dist/src/session/manager.d.ts +3 -0
- package/dist/src/session/manager.js +1 -261
- package/dist/src/session/projection.js +1 -54
- package/dist/src/session/queue.js +4 -317
- package/dist/src/session/remote-attachments.js +1 -72
- package/dist/src/session/store.js +3 -109
- package/dist/src/session/types.d.ts +4 -0
- package/dist/src/session/types.js +1 -4
- package/dist/src/skills/registry.js +15 -148
- package/dist/src/skills/setup.js +1 -101
- package/dist/src/tools/markdown-to-pdf.js +10 -346
- package/dist/src/upgrade/engine.js +3 -347
- package/package.json +3 -2
- package/dist/scripts/wechat-rpa-download-candidates.mjs +0 -105
package/dist/src/relay/client.js
CHANGED
|
@@ -1,343 +1 @@
|
|
|
1
|
-
|
|
2
|
-
// @test src/__tests__/e2e-relay.ts
|
|
3
|
-
import WebSocket from 'ws';
|
|
4
|
-
import { generateTraceId } from '../log-reporter.js';
|
|
5
|
-
const HEARTBEAT_TIMEOUT = 90_000;
|
|
6
|
-
const MAX_RECONNECT_DELAY = 30_000;
|
|
7
|
-
const BASE_RECONNECT_DELAY = 1_000;
|
|
8
|
-
const JITTER_FACTOR = 0.2;
|
|
9
|
-
const PING_INTERVAL = 20_000;
|
|
10
|
-
const PONG_TIMEOUT = 10_000;
|
|
11
|
-
const CONNECT_TIMEOUT = 15_000;
|
|
12
|
-
const MAX_SEND_BUFFER = 1000;
|
|
13
|
-
export class CliRelayClient {
|
|
14
|
-
options;
|
|
15
|
-
ws = null;
|
|
16
|
-
state = 'disconnected';
|
|
17
|
-
reconnectAttempts = 0;
|
|
18
|
-
heartbeatTimer = null;
|
|
19
|
-
reconnectTimer = null;
|
|
20
|
-
connectTimer = null;
|
|
21
|
-
pingTimer = null;
|
|
22
|
-
pongTimer = null;
|
|
23
|
-
disposed = false;
|
|
24
|
-
closeTrigger;
|
|
25
|
-
lastSocketError;
|
|
26
|
-
/** Buffered agent events awaiting server ack, keyed by event id */
|
|
27
|
-
sendBuffer = new Map();
|
|
28
|
-
pendingAcks = new Map();
|
|
29
|
-
pendingRequests = new Map();
|
|
30
|
-
constructor(options) {
|
|
31
|
-
this.options = options;
|
|
32
|
-
}
|
|
33
|
-
connect() {
|
|
34
|
-
if (this.disposed)
|
|
35
|
-
return;
|
|
36
|
-
// Always clean up stale connection before creating a new one
|
|
37
|
-
if (this.ws) {
|
|
38
|
-
this.cleanup(false);
|
|
39
|
-
}
|
|
40
|
-
this.state = 'connecting';
|
|
41
|
-
const sep = this.options.serverUrl.includes('?') ? '&' : '?';
|
|
42
|
-
const vParam = this.options.cliVersion ? `&v=${encodeURIComponent(this.options.cliVersion)}` : '';
|
|
43
|
-
const agentsParam = this.options.agentList?.length
|
|
44
|
-
? `&agents=${encodeURIComponent(this.options.agentList.join(','))}`
|
|
45
|
-
: '';
|
|
46
|
-
const url = `${this.options.serverUrl}${sep}token=${encodeURIComponent(this.options.machineToken)}${vParam}${agentsParam}`;
|
|
47
|
-
const ws = new WebSocket(url);
|
|
48
|
-
this.ws = ws;
|
|
49
|
-
// Guard against connections that never open (DNS hang, TCP timeout, etc.)
|
|
50
|
-
this.connectTimer = setTimeout(() => {
|
|
51
|
-
if (this.ws === ws && this.state === 'connecting') {
|
|
52
|
-
this.closeTrigger = 'connect-timeout';
|
|
53
|
-
ws.terminate();
|
|
54
|
-
}
|
|
55
|
-
}, CONNECT_TIMEOUT);
|
|
56
|
-
ws.on('open', () => {
|
|
57
|
-
if (this.ws !== ws)
|
|
58
|
-
return;
|
|
59
|
-
this.clearConnectTimer();
|
|
60
|
-
this.state = 'connected';
|
|
61
|
-
this.reconnectAttempts = 0;
|
|
62
|
-
this.startHeartbeat();
|
|
63
|
-
this.startPing(ws);
|
|
64
|
-
this.flushSendBuffer();
|
|
65
|
-
this.options.onConnected?.();
|
|
66
|
-
});
|
|
67
|
-
ws.on('pong', () => {
|
|
68
|
-
if (this.ws !== ws)
|
|
69
|
-
return;
|
|
70
|
-
this.clearPongTimer();
|
|
71
|
-
this.resetHeartbeat();
|
|
72
|
-
});
|
|
73
|
-
ws.on('message', (data) => {
|
|
74
|
-
if (this.ws !== ws)
|
|
75
|
-
return;
|
|
76
|
-
let frame;
|
|
77
|
-
try {
|
|
78
|
-
frame = JSON.parse(data.toString());
|
|
79
|
-
}
|
|
80
|
-
catch {
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
this.handleFrame(frame);
|
|
84
|
-
});
|
|
85
|
-
ws.on('close', (code, reason) => {
|
|
86
|
-
if (this.ws !== ws)
|
|
87
|
-
return;
|
|
88
|
-
const phase = this.state;
|
|
89
|
-
const info = {
|
|
90
|
-
code,
|
|
91
|
-
reason: reason.toString(),
|
|
92
|
-
error: this.lastSocketError,
|
|
93
|
-
phase,
|
|
94
|
-
trigger: this.closeTrigger ?? 'socket-close',
|
|
95
|
-
reconnectAttempt: this.reconnectAttempts + 1,
|
|
96
|
-
};
|
|
97
|
-
this.cleanup(false);
|
|
98
|
-
this.options.onDisconnected?.(info);
|
|
99
|
-
this.scheduleReconnect();
|
|
100
|
-
});
|
|
101
|
-
ws.on('error', (error) => {
|
|
102
|
-
this.lastSocketError = error.message;
|
|
103
|
-
this.closeTrigger = 'socket-error';
|
|
104
|
-
ws.close();
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
disconnect() {
|
|
108
|
-
this.disposed = true;
|
|
109
|
-
this.cleanup(true);
|
|
110
|
-
}
|
|
111
|
-
sendRes(res) {
|
|
112
|
-
if (this.state !== 'connected' || !this.ws)
|
|
113
|
-
return;
|
|
114
|
-
if (!res.traceId)
|
|
115
|
-
res.traceId = generateTraceId();
|
|
116
|
-
this.ws.send(JSON.stringify(res));
|
|
117
|
-
}
|
|
118
|
-
sendEvent(event) {
|
|
119
|
-
if (this.state !== 'connected' || !this.ws)
|
|
120
|
-
return;
|
|
121
|
-
if (!event.traceId)
|
|
122
|
-
event.traceId = generateTraceId();
|
|
123
|
-
this.ws.send(JSON.stringify(event));
|
|
124
|
-
}
|
|
125
|
-
sendReq(req, timeoutMs = 60_000) {
|
|
126
|
-
if (this.state !== 'connected' || !this.ws) {
|
|
127
|
-
return Promise.reject(new Error('Relay is not connected'));
|
|
128
|
-
}
|
|
129
|
-
if (!req.traceId)
|
|
130
|
-
req.traceId = generateTraceId();
|
|
131
|
-
return new Promise((resolve, reject) => {
|
|
132
|
-
const existing = this.pendingRequests.get(req.id);
|
|
133
|
-
if (existing) {
|
|
134
|
-
clearTimeout(existing.timer);
|
|
135
|
-
existing.reject(new Error('Superseded by a newer relay request'));
|
|
136
|
-
}
|
|
137
|
-
const timer = setTimeout(() => {
|
|
138
|
-
this.pendingRequests.delete(req.id);
|
|
139
|
-
reject(new Error('Relay request timed out'));
|
|
140
|
-
}, timeoutMs);
|
|
141
|
-
this.pendingRequests.set(req.id, { resolve, reject, timer });
|
|
142
|
-
this.ws?.send(JSON.stringify(req));
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
sendBufferedEvent(event, timeoutMs = 120_000) {
|
|
146
|
-
if (!event.traceId)
|
|
147
|
-
event.traceId = generateTraceId();
|
|
148
|
-
if (!event.id) {
|
|
149
|
-
this.sendEvent(event);
|
|
150
|
-
return Promise.resolve();
|
|
151
|
-
}
|
|
152
|
-
const eventId = event.id;
|
|
153
|
-
return new Promise((resolve, reject) => {
|
|
154
|
-
const existing = this.pendingAcks.get(eventId);
|
|
155
|
-
if (existing) {
|
|
156
|
-
clearTimeout(existing.timer);
|
|
157
|
-
existing.reject(new Error('Superseded by a newer buffered event'));
|
|
158
|
-
}
|
|
159
|
-
const timer = setTimeout(() => {
|
|
160
|
-
this.pendingAcks.delete(eventId);
|
|
161
|
-
this.sendBuffer.delete(eventId);
|
|
162
|
-
reject(new Error('Relay event acknowledgement timed out'));
|
|
163
|
-
}, timeoutMs);
|
|
164
|
-
this.pendingAcks.set(eventId, { resolve, reject, timer });
|
|
165
|
-
this.bufferEvent(event);
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
/**
|
|
169
|
-
* Send an agent event with at-least-once delivery guarantee.
|
|
170
|
-
* The event is buffered until the server acknowledges it.
|
|
171
|
-
* On reconnect, all unacked events are resent in order.
|
|
172
|
-
*/
|
|
173
|
-
sendAgentEvent(event) {
|
|
174
|
-
void this.sendBufferedEvent(event).catch(() => { });
|
|
175
|
-
}
|
|
176
|
-
getState() {
|
|
177
|
-
return this.state;
|
|
178
|
-
}
|
|
179
|
-
handleFrame(frame) {
|
|
180
|
-
if (frame.type === 'event' && frame.event === 'tick') {
|
|
181
|
-
this.resetHeartbeat();
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
if (frame.type === 'res' && frame.id) {
|
|
185
|
-
// Server ack for a buffered agent event — remove from buffer
|
|
186
|
-
this.sendBuffer.delete(frame.id);
|
|
187
|
-
const pending = this.pendingAcks.get(frame.id);
|
|
188
|
-
if (pending) {
|
|
189
|
-
this.pendingAcks.delete(frame.id);
|
|
190
|
-
clearTimeout(pending.timer);
|
|
191
|
-
if (frame.ok)
|
|
192
|
-
pending.resolve();
|
|
193
|
-
else
|
|
194
|
-
pending.reject(new Error(frame.error ?? 'Relay event failed'));
|
|
195
|
-
}
|
|
196
|
-
const pendingRequest = this.pendingRequests.get(frame.id);
|
|
197
|
-
if (pendingRequest) {
|
|
198
|
-
this.pendingRequests.delete(frame.id);
|
|
199
|
-
clearTimeout(pendingRequest.timer);
|
|
200
|
-
pendingRequest.resolve(frame);
|
|
201
|
-
}
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
if (frame.type === 'req') {
|
|
205
|
-
this.options.onReq?.(frame);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
/** Resend all buffered agent events after reconnect, in insertion order */
|
|
209
|
-
flushSendBuffer() {
|
|
210
|
-
if (this.sendBuffer.size === 0 || !this.ws)
|
|
211
|
-
return;
|
|
212
|
-
for (const event of this.sendBuffer.values()) {
|
|
213
|
-
if (this.state !== 'connected' || !this.ws)
|
|
214
|
-
break;
|
|
215
|
-
this.ws.send(JSON.stringify(event));
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
bufferEvent(event) {
|
|
219
|
-
if (!event.id) {
|
|
220
|
-
this.sendEvent(event);
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
this.sendBuffer.set(event.id, event);
|
|
224
|
-
if (this.sendBuffer.size > MAX_SEND_BUFFER) {
|
|
225
|
-
const oldest = this.sendBuffer.keys().next().value;
|
|
226
|
-
if (oldest) {
|
|
227
|
-
this.sendBuffer.delete(oldest);
|
|
228
|
-
const pending = this.pendingAcks.get(oldest);
|
|
229
|
-
if (pending) {
|
|
230
|
-
this.pendingAcks.delete(oldest);
|
|
231
|
-
clearTimeout(pending.timer);
|
|
232
|
-
pending.reject(new Error('Relay send buffer overflow'));
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
if (this.state === 'connected' && this.ws) {
|
|
237
|
-
this.ws.send(JSON.stringify(event));
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
clearConnectTimer() {
|
|
241
|
-
if (this.connectTimer !== null) {
|
|
242
|
-
clearTimeout(this.connectTimer);
|
|
243
|
-
this.connectTimer = null;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
startPing(ws) {
|
|
247
|
-
this.clearPing();
|
|
248
|
-
this.pingTimer = setInterval(() => {
|
|
249
|
-
if (this.ws !== ws || this.state !== 'connected')
|
|
250
|
-
return;
|
|
251
|
-
// Set pong timeout before sending ping
|
|
252
|
-
this.pongTimer = setTimeout(() => {
|
|
253
|
-
// No pong received — connection is dead, force close
|
|
254
|
-
this.closeTrigger = 'pong-timeout';
|
|
255
|
-
ws.close();
|
|
256
|
-
}, PONG_TIMEOUT);
|
|
257
|
-
try {
|
|
258
|
-
ws.ping();
|
|
259
|
-
}
|
|
260
|
-
catch {
|
|
261
|
-
this.clearPongTimer();
|
|
262
|
-
this.closeTrigger = 'socket-error';
|
|
263
|
-
ws.close();
|
|
264
|
-
}
|
|
265
|
-
}, PING_INTERVAL);
|
|
266
|
-
}
|
|
267
|
-
clearPing() {
|
|
268
|
-
if (this.pingTimer !== null) {
|
|
269
|
-
clearInterval(this.pingTimer);
|
|
270
|
-
this.pingTimer = null;
|
|
271
|
-
}
|
|
272
|
-
this.clearPongTimer();
|
|
273
|
-
}
|
|
274
|
-
clearPongTimer() {
|
|
275
|
-
if (this.pongTimer !== null) {
|
|
276
|
-
clearTimeout(this.pongTimer);
|
|
277
|
-
this.pongTimer = null;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
startHeartbeat() {
|
|
281
|
-
this.clearHeartbeat();
|
|
282
|
-
this.heartbeatTimer = setTimeout(() => {
|
|
283
|
-
this.closeTrigger = 'heartbeat-timeout';
|
|
284
|
-
this.ws?.close();
|
|
285
|
-
}, HEARTBEAT_TIMEOUT);
|
|
286
|
-
}
|
|
287
|
-
resetHeartbeat() {
|
|
288
|
-
this.startHeartbeat();
|
|
289
|
-
}
|
|
290
|
-
clearHeartbeat() {
|
|
291
|
-
if (this.heartbeatTimer !== null) {
|
|
292
|
-
clearTimeout(this.heartbeatTimer);
|
|
293
|
-
this.heartbeatTimer = null;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
scheduleReconnect() {
|
|
297
|
-
if (this.disposed)
|
|
298
|
-
return;
|
|
299
|
-
const base = Math.min(BASE_RECONNECT_DELAY * Math.pow(2, this.reconnectAttempts), MAX_RECONNECT_DELAY);
|
|
300
|
-
const jitter = base * JITTER_FACTOR * (Math.random() * 2 - 1);
|
|
301
|
-
const delay = Math.round(base + jitter);
|
|
302
|
-
this.reconnectAttempts++;
|
|
303
|
-
this.reconnectTimer = setTimeout(() => {
|
|
304
|
-
this.reconnectTimer = null;
|
|
305
|
-
this.connect();
|
|
306
|
-
}, delay);
|
|
307
|
-
}
|
|
308
|
-
cleanup(rejectAll) {
|
|
309
|
-
this.state = 'disconnected';
|
|
310
|
-
this.closeTrigger = undefined;
|
|
311
|
-
this.lastSocketError = undefined;
|
|
312
|
-
this.clearConnectTimer();
|
|
313
|
-
this.clearHeartbeat();
|
|
314
|
-
this.clearPing();
|
|
315
|
-
if (this.reconnectTimer !== null) {
|
|
316
|
-
clearTimeout(this.reconnectTimer);
|
|
317
|
-
this.reconnectTimer = null;
|
|
318
|
-
}
|
|
319
|
-
if (this.ws) {
|
|
320
|
-
const ws = this.ws;
|
|
321
|
-
this.ws = null;
|
|
322
|
-
ws.removeAllListeners();
|
|
323
|
-
try {
|
|
324
|
-
ws.close();
|
|
325
|
-
}
|
|
326
|
-
catch {
|
|
327
|
-
// already closed
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
for (const [id, pending] of this.pendingRequests) {
|
|
331
|
-
clearTimeout(pending.timer);
|
|
332
|
-
pending.reject(new Error('Relay client disconnected'));
|
|
333
|
-
this.pendingRequests.delete(id);
|
|
334
|
-
}
|
|
335
|
-
if (rejectAll) {
|
|
336
|
-
for (const [id, pending] of this.pendingAcks) {
|
|
337
|
-
clearTimeout(pending.timer);
|
|
338
|
-
pending.reject(new Error('Relay client disconnected'));
|
|
339
|
-
this.pendingAcks.delete(id);
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
}
|
|
1
|
+
import d from"ws";import{generateTraceId as c}from"../log-reporter.js";const a=9e4,u=3e4,g=1e3,f=.2,m=2e4,p=1e4,T=15e3,w=1e3;class k{options;ws=null;state="disconnected";reconnectAttempts=0;heartbeatTimer=null;reconnectTimer=null;connectTimer=null;pingTimer=null;pongTimer=null;disposed=!1;closeTrigger;lastSocketError;sendBuffer=new Map;pendingAcks=new Map;pendingRequests=new Map;constructor(e){this.options=e}connect(){if(this.disposed)return;this.ws&&this.cleanup(!1),this.state="connecting";const e=this.options.serverUrl.includes("?")?"&":"?",t=this.options.cliVersion?`&v=${encodeURIComponent(this.options.cliVersion)}`:"",s=this.options.agentList?.length?`&agents=${encodeURIComponent(this.options.agentList.join(","))}`:"",r=`${this.options.serverUrl}${e}token=${encodeURIComponent(this.options.machineToken)}${t}${s}`,i=new d(r);this.ws=i,this.connectTimer=setTimeout(()=>{this.ws===i&&this.state==="connecting"&&(this.closeTrigger="connect-timeout",i.terminate())},T),i.on("open",()=>{this.ws===i&&(this.clearConnectTimer(),this.state="connected",this.reconnectAttempts=0,this.startHeartbeat(),this.startPing(i),this.flushSendBuffer(),this.options.onConnected?.())}),i.on("pong",()=>{this.ws===i&&(this.clearPongTimer(),this.resetHeartbeat())}),i.on("message",n=>{if(this.ws!==i)return;let o;try{o=JSON.parse(n.toString())}catch{return}this.handleFrame(o)}),i.on("close",(n,o)=>{if(this.ws!==i)return;const h=this.state,l={code:n,reason:o.toString(),error:this.lastSocketError,phase:h,trigger:this.closeTrigger??"socket-close",reconnectAttempt:this.reconnectAttempts+1};this.cleanup(!1),this.options.onDisconnected?.(l),this.scheduleReconnect()}),i.on("error",n=>{this.lastSocketError=n.message,this.closeTrigger="socket-error",i.close()})}disconnect(){this.disposed=!0,this.cleanup(!0)}sendRes(e){this.state!=="connected"||!this.ws||(e.traceId||(e.traceId=c()),this.ws.send(JSON.stringify(e)))}sendEvent(e){this.state!=="connected"||!this.ws||(e.traceId||(e.traceId=c()),this.ws.send(JSON.stringify(e)))}sendReq(e,t=6e4){return this.state!=="connected"||!this.ws?Promise.reject(new Error("Relay is not connected")):(e.traceId||(e.traceId=c()),new Promise((s,r)=>{const i=this.pendingRequests.get(e.id);i&&(clearTimeout(i.timer),i.reject(new Error("Superseded by a newer relay request")));const n=setTimeout(()=>{this.pendingRequests.delete(e.id),r(new Error("Relay request timed out"))},t);this.pendingRequests.set(e.id,{resolve:s,reject:r,timer:n}),this.ws?.send(JSON.stringify(e))}))}sendBufferedEvent(e,t=12e4){if(e.traceId||(e.traceId=c()),!e.id)return this.sendEvent(e),Promise.resolve();const s=e.id;return new Promise((r,i)=>{const n=this.pendingAcks.get(s);n&&(clearTimeout(n.timer),n.reject(new Error("Superseded by a newer buffered event")));const o=setTimeout(()=>{this.pendingAcks.delete(s),this.sendBuffer.delete(s),i(new Error("Relay event acknowledgement timed out"))},t);this.pendingAcks.set(s,{resolve:r,reject:i,timer:o}),this.bufferEvent(e)})}sendAgentEvent(e){this.sendBufferedEvent(e).catch(()=>{})}getState(){return this.state}handleFrame(e){if(e.type==="event"&&e.event==="tick"){this.resetHeartbeat();return}if(e.type==="res"&&e.id){this.sendBuffer.delete(e.id);const t=this.pendingAcks.get(e.id);t&&(this.pendingAcks.delete(e.id),clearTimeout(t.timer),e.ok?t.resolve():t.reject(new Error(e.error??"Relay event failed")));const s=this.pendingRequests.get(e.id);s&&(this.pendingRequests.delete(e.id),clearTimeout(s.timer),s.resolve(e));return}e.type==="req"&&this.options.onReq?.(e)}flushSendBuffer(){if(!(this.sendBuffer.size===0||!this.ws))for(const e of this.sendBuffer.values()){if(this.state!=="connected"||!this.ws)break;this.ws.send(JSON.stringify(e))}}bufferEvent(e){if(!e.id){this.sendEvent(e);return}if(this.sendBuffer.set(e.id,e),this.sendBuffer.size>w){const t=this.sendBuffer.keys().next().value;if(t){this.sendBuffer.delete(t);const s=this.pendingAcks.get(t);s&&(this.pendingAcks.delete(t),clearTimeout(s.timer),s.reject(new Error("Relay send buffer overflow")))}}this.state==="connected"&&this.ws&&this.ws.send(JSON.stringify(e))}clearConnectTimer(){this.connectTimer!==null&&(clearTimeout(this.connectTimer),this.connectTimer=null)}startPing(e){this.clearPing(),this.pingTimer=setInterval(()=>{if(!(this.ws!==e||this.state!=="connected")){this.pongTimer=setTimeout(()=>{this.closeTrigger="pong-timeout",e.close()},p);try{e.ping()}catch{this.clearPongTimer(),this.closeTrigger="socket-error",e.close()}}},m)}clearPing(){this.pingTimer!==null&&(clearInterval(this.pingTimer),this.pingTimer=null),this.clearPongTimer()}clearPongTimer(){this.pongTimer!==null&&(clearTimeout(this.pongTimer),this.pongTimer=null)}startHeartbeat(){this.clearHeartbeat(),this.heartbeatTimer=setTimeout(()=>{this.closeTrigger="heartbeat-timeout",this.ws?.close()},a)}resetHeartbeat(){this.startHeartbeat()}clearHeartbeat(){this.heartbeatTimer!==null&&(clearTimeout(this.heartbeatTimer),this.heartbeatTimer=null)}scheduleReconnect(){if(this.disposed)return;const e=Math.min(g*Math.pow(2,this.reconnectAttempts),u),t=e*f*(Math.random()*2-1),s=Math.round(e+t);this.reconnectAttempts++,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.connect()},s)}cleanup(e){if(this.state="disconnected",this.closeTrigger=void 0,this.lastSocketError=void 0,this.clearConnectTimer(),this.clearHeartbeat(),this.clearPing(),this.reconnectTimer!==null&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.ws){const t=this.ws;this.ws=null,t.removeAllListeners();try{t.close()}catch{}}for(const[t,s]of this.pendingRequests)clearTimeout(s.timer),s.reject(new Error("Relay client disconnected")),this.pendingRequests.delete(t);if(e)for(const[t,s]of this.pendingAcks)clearTimeout(s.timer),s.reject(new Error("Relay client disconnected")),this.pendingAcks.delete(t)}}export{k as CliRelayClient};
|
|
@@ -1,220 +1 @@
|
|
|
1
|
-
|
|
2
|
-
// @test src/__tests__/archive-zip.test.ts
|
|
3
|
-
import fs from 'node:fs';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
const DEFAULT_MAX_FILES = 5000;
|
|
6
|
-
const DEFAULT_MAX_TOTAL_SIZE = 1024 * 1024 * 1024;
|
|
7
|
-
const ZIP_VERSION_NEEDED = 20;
|
|
8
|
-
const ZIP_UTF8_FLAG = 0x0800;
|
|
9
|
-
const ZIP_STORE_METHOD = 0;
|
|
10
|
-
const CHUNK_SIZE = 1024 * 1024;
|
|
11
|
-
const crcTable = new Uint32Array(256);
|
|
12
|
-
for (let index = 0; index < 256; index += 1) {
|
|
13
|
-
let value = index;
|
|
14
|
-
for (let bit = 0; bit < 8; bit += 1) {
|
|
15
|
-
value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;
|
|
16
|
-
}
|
|
17
|
-
crcTable[index] = value >>> 0;
|
|
18
|
-
}
|
|
19
|
-
function updateCrc32(crc, buffer) {
|
|
20
|
-
let next = crc;
|
|
21
|
-
for (let index = 0; index < buffer.length; index += 1) {
|
|
22
|
-
next = crcTable[(next ^ buffer[index]) & 0xff] ^ (next >>> 8);
|
|
23
|
-
}
|
|
24
|
-
return next >>> 0;
|
|
25
|
-
}
|
|
26
|
-
function dosDateTime(mtimeMs) {
|
|
27
|
-
const date = new Date(mtimeMs || Date.now());
|
|
28
|
-
const year = Math.max(1980, Math.min(2107, date.getFullYear()));
|
|
29
|
-
return {
|
|
30
|
-
date: ((year - 1980) << 9) | ((date.getMonth() + 1) << 5) | date.getDate(),
|
|
31
|
-
time: (date.getHours() << 11) | (date.getMinutes() << 5) | Math.floor(date.getSeconds() / 2),
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
function writeUInt32(buffer, value, offset) {
|
|
35
|
-
buffer.writeUInt32LE(value >>> 0, offset);
|
|
36
|
-
}
|
|
37
|
-
function writeLocalHeader(fd, entry) {
|
|
38
|
-
const name = Buffer.from(entry.name, 'utf8');
|
|
39
|
-
const { date, time } = dosDateTime(entry.mtimeMs);
|
|
40
|
-
const header = Buffer.alloc(30);
|
|
41
|
-
writeUInt32(header, 0x04034b50, 0);
|
|
42
|
-
header.writeUInt16LE(ZIP_VERSION_NEEDED, 4);
|
|
43
|
-
header.writeUInt16LE(ZIP_UTF8_FLAG, 6);
|
|
44
|
-
header.writeUInt16LE(ZIP_STORE_METHOD, 8);
|
|
45
|
-
header.writeUInt16LE(time, 10);
|
|
46
|
-
header.writeUInt16LE(date, 12);
|
|
47
|
-
writeUInt32(header, entry.crc32, 14);
|
|
48
|
-
writeUInt32(header, entry.size, 18);
|
|
49
|
-
writeUInt32(header, entry.size, 22);
|
|
50
|
-
header.writeUInt16LE(name.length, 26);
|
|
51
|
-
header.writeUInt16LE(0, 28);
|
|
52
|
-
fs.writeSync(fd, header);
|
|
53
|
-
fs.writeSync(fd, name);
|
|
54
|
-
}
|
|
55
|
-
function writeCentralDirectoryHeader(fd, entry) {
|
|
56
|
-
const name = Buffer.from(entry.name, 'utf8');
|
|
57
|
-
const { date, time } = dosDateTime(entry.mtimeMs);
|
|
58
|
-
const header = Buffer.alloc(46);
|
|
59
|
-
writeUInt32(header, 0x02014b50, 0);
|
|
60
|
-
header.writeUInt16LE(0x031e, 4);
|
|
61
|
-
header.writeUInt16LE(ZIP_VERSION_NEEDED, 6);
|
|
62
|
-
header.writeUInt16LE(ZIP_UTF8_FLAG, 8);
|
|
63
|
-
header.writeUInt16LE(ZIP_STORE_METHOD, 10);
|
|
64
|
-
header.writeUInt16LE(time, 12);
|
|
65
|
-
header.writeUInt16LE(date, 14);
|
|
66
|
-
writeUInt32(header, entry.crc32, 16);
|
|
67
|
-
writeUInt32(header, entry.size, 20);
|
|
68
|
-
writeUInt32(header, entry.size, 24);
|
|
69
|
-
header.writeUInt16LE(name.length, 28);
|
|
70
|
-
header.writeUInt16LE(0, 30);
|
|
71
|
-
header.writeUInt16LE(0, 32);
|
|
72
|
-
header.writeUInt16LE(0, 34);
|
|
73
|
-
header.writeUInt16LE(0, 36);
|
|
74
|
-
writeUInt32(header, entry.isDir ? 0x10 : 0, 38);
|
|
75
|
-
writeUInt32(header, entry.offset, 42);
|
|
76
|
-
fs.writeSync(fd, header);
|
|
77
|
-
fs.writeSync(fd, name);
|
|
78
|
-
}
|
|
79
|
-
function writeEndOfCentralDirectory(fd, entryCount, centralSize, centralOffset) {
|
|
80
|
-
const header = Buffer.alloc(22);
|
|
81
|
-
writeUInt32(header, 0x06054b50, 0);
|
|
82
|
-
header.writeUInt16LE(0, 4);
|
|
83
|
-
header.writeUInt16LE(0, 6);
|
|
84
|
-
header.writeUInt16LE(entryCount, 8);
|
|
85
|
-
header.writeUInt16LE(entryCount, 10);
|
|
86
|
-
writeUInt32(header, centralSize, 12);
|
|
87
|
-
writeUInt32(header, centralOffset, 16);
|
|
88
|
-
header.writeUInt16LE(0, 20);
|
|
89
|
-
fs.writeSync(fd, header);
|
|
90
|
-
}
|
|
91
|
-
function normalizeZipName(relativePath, isDir) {
|
|
92
|
-
const normalized = relativePath.split(path.sep).join('/');
|
|
93
|
-
return isDir && !normalized.endsWith('/') ? `${normalized}/` : normalized;
|
|
94
|
-
}
|
|
95
|
-
function collectEntries(sourceDir, options) {
|
|
96
|
-
const sourceStat = fs.statSync(sourceDir);
|
|
97
|
-
const rootName = normalizeZipName(path.basename(sourceDir) || 'folder', true);
|
|
98
|
-
const entries = [{
|
|
99
|
-
name: rootName,
|
|
100
|
-
sourcePath: null,
|
|
101
|
-
crc32: 0,
|
|
102
|
-
size: 0,
|
|
103
|
-
isDir: true,
|
|
104
|
-
mtimeMs: sourceStat.mtimeMs,
|
|
105
|
-
}];
|
|
106
|
-
let fileCount = 0;
|
|
107
|
-
let totalSize = 0;
|
|
108
|
-
const visit = (dirPath) => {
|
|
109
|
-
const raw = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
110
|
-
for (const dirent of raw) {
|
|
111
|
-
const fullPath = path.join(dirPath, dirent.name);
|
|
112
|
-
const relative = path.relative(sourceDir, fullPath);
|
|
113
|
-
if (!relative || relative.startsWith('..') || path.isAbsolute(relative))
|
|
114
|
-
continue;
|
|
115
|
-
const stat = fs.lstatSync(fullPath);
|
|
116
|
-
if (stat.isSymbolicLink())
|
|
117
|
-
continue;
|
|
118
|
-
if (stat.isDirectory()) {
|
|
119
|
-
entries.push({
|
|
120
|
-
name: path.posix.join(rootName, normalizeZipName(relative, true)),
|
|
121
|
-
sourcePath: null,
|
|
122
|
-
crc32: 0,
|
|
123
|
-
size: 0,
|
|
124
|
-
isDir: true,
|
|
125
|
-
mtimeMs: stat.mtimeMs,
|
|
126
|
-
});
|
|
127
|
-
visit(fullPath);
|
|
128
|
-
continue;
|
|
129
|
-
}
|
|
130
|
-
if (!stat.isFile())
|
|
131
|
-
continue;
|
|
132
|
-
fileCount += 1;
|
|
133
|
-
if (fileCount > options.maxFiles)
|
|
134
|
-
throw new Error(`Too many files: ${fileCount}`);
|
|
135
|
-
totalSize += stat.size;
|
|
136
|
-
if (totalSize > options.maxTotalSize) {
|
|
137
|
-
throw new Error(`Folder too large: ${totalSize} bytes`);
|
|
138
|
-
}
|
|
139
|
-
entries.push({
|
|
140
|
-
name: path.posix.join(rootName, normalizeZipName(relative, false)),
|
|
141
|
-
sourcePath: fullPath,
|
|
142
|
-
crc32: computeFileCrc32(fullPath),
|
|
143
|
-
size: stat.size,
|
|
144
|
-
isDir: false,
|
|
145
|
-
mtimeMs: stat.mtimeMs,
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
};
|
|
149
|
-
visit(sourceDir);
|
|
150
|
-
return entries;
|
|
151
|
-
}
|
|
152
|
-
function computeFileCrc32(filePath) {
|
|
153
|
-
const fd = fs.openSync(filePath, 'r');
|
|
154
|
-
const buffer = Buffer.alloc(CHUNK_SIZE);
|
|
155
|
-
let crc = 0xffffffff;
|
|
156
|
-
try {
|
|
157
|
-
while (true) {
|
|
158
|
-
const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, null);
|
|
159
|
-
if (bytesRead <= 0)
|
|
160
|
-
break;
|
|
161
|
-
crc = updateCrc32(crc, buffer.subarray(0, bytesRead));
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
finally {
|
|
165
|
-
fs.closeSync(fd);
|
|
166
|
-
}
|
|
167
|
-
return (crc ^ 0xffffffff) >>> 0;
|
|
168
|
-
}
|
|
169
|
-
function writeFileData(fd, filePath) {
|
|
170
|
-
const sourceFd = fs.openSync(filePath, 'r');
|
|
171
|
-
const buffer = Buffer.alloc(CHUNK_SIZE);
|
|
172
|
-
try {
|
|
173
|
-
while (true) {
|
|
174
|
-
const bytesRead = fs.readSync(sourceFd, buffer, 0, buffer.length, null);
|
|
175
|
-
if (bytesRead <= 0)
|
|
176
|
-
break;
|
|
177
|
-
fs.writeSync(fd, buffer.subarray(0, bytesRead));
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
finally {
|
|
181
|
-
fs.closeSync(sourceFd);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
export function createZipArchive(sourceDir, outputPath, options = {}) {
|
|
185
|
-
const sourceStat = fs.statSync(sourceDir);
|
|
186
|
-
if (!sourceStat.isDirectory())
|
|
187
|
-
throw new Error('Not a directory');
|
|
188
|
-
const limits = {
|
|
189
|
-
maxFiles: options.maxFiles ?? DEFAULT_MAX_FILES,
|
|
190
|
-
maxTotalSize: options.maxTotalSize ?? DEFAULT_MAX_TOTAL_SIZE,
|
|
191
|
-
};
|
|
192
|
-
const scanned = collectEntries(sourceDir, limits);
|
|
193
|
-
const zipEntries = [];
|
|
194
|
-
let fileCount = 0;
|
|
195
|
-
let totalSize = 0;
|
|
196
|
-
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
197
|
-
const fd = fs.openSync(outputPath, 'w');
|
|
198
|
-
try {
|
|
199
|
-
for (const entry of scanned) {
|
|
200
|
-
const offset = fs.fstatSync(fd).size;
|
|
201
|
-
const nextEntry = { ...entry, offset };
|
|
202
|
-
writeLocalHeader(fd, nextEntry);
|
|
203
|
-
if (nextEntry.sourcePath) {
|
|
204
|
-
writeFileData(fd, nextEntry.sourcePath);
|
|
205
|
-
fileCount += 1;
|
|
206
|
-
totalSize += nextEntry.size;
|
|
207
|
-
}
|
|
208
|
-
zipEntries.push(nextEntry);
|
|
209
|
-
}
|
|
210
|
-
const centralOffset = fs.fstatSync(fd).size;
|
|
211
|
-
for (const entry of zipEntries)
|
|
212
|
-
writeCentralDirectoryHeader(fd, entry);
|
|
213
|
-
const centralSize = fs.fstatSync(fd).size - centralOffset;
|
|
214
|
-
writeEndOfCentralDirectory(fd, zipEntries.length, centralSize, centralOffset);
|
|
215
|
-
}
|
|
216
|
-
finally {
|
|
217
|
-
fs.closeSync(fd);
|
|
218
|
-
}
|
|
219
|
-
return { outputPath, fileCount, totalSize };
|
|
220
|
-
}
|
|
1
|
+
import c from"node:fs";import u from"node:path";const p=5e3,M=1024*1024*1024,L=20,U=2048,h=0,x=1024*1024,z=new Uint32Array(256);for(let i=0;i<256;i+=1){let t=i;for(let n=0;n<8;n+=1)t=t&1?3988292384^t>>>1:t>>>1;z[i]=t>>>0}function F(i,t){let n=i;for(let o=0;o<t.length;o+=1)n=z[(n^t[o])&255]^n>>>8;return n>>>0}function b(i){const t=new Date(i||Date.now());return{date:Math.max(1980,Math.min(2107,t.getFullYear()))-1980<<9|t.getMonth()+1<<5|t.getDate(),time:t.getHours()<<11|t.getMinutes()<<5|Math.floor(t.getSeconds()/2)}}function s(i,t,n){i.writeUInt32LE(t>>>0,n)}function D(i,t){const n=Buffer.from(t.name,"utf8"),{date:o,time:r}=b(t.mtimeMs),e=Buffer.alloc(30);s(e,67324752,0),e.writeUInt16LE(L,4),e.writeUInt16LE(U,6),e.writeUInt16LE(h,8),e.writeUInt16LE(r,10),e.writeUInt16LE(o,12),s(e,t.crc32,14),s(e,t.size,18),s(e,t.size,22),e.writeUInt16LE(n.length,26),e.writeUInt16LE(0,28),c.writeSync(i,e),c.writeSync(i,n)}function T(i,t){const n=Buffer.from(t.name,"utf8"),{date:o,time:r}=b(t.mtimeMs),e=Buffer.alloc(46);s(e,33639248,0),e.writeUInt16LE(798,4),e.writeUInt16LE(L,6),e.writeUInt16LE(U,8),e.writeUInt16LE(h,10),e.writeUInt16LE(r,12),e.writeUInt16LE(o,14),s(e,t.crc32,16),s(e,t.size,20),s(e,t.size,24),e.writeUInt16LE(n.length,28),e.writeUInt16LE(0,30),e.writeUInt16LE(0,32),e.writeUInt16LE(0,34),e.writeUInt16LE(0,36),s(e,t.isDir?16:0,38),s(e,t.offset,42),c.writeSync(i,e),c.writeSync(i,n)}function g(i,t,n,o){const r=Buffer.alloc(22);s(r,101010256,0),r.writeUInt16LE(0,4),r.writeUInt16LE(0,6),r.writeUInt16LE(t,8),r.writeUInt16LE(t,10),s(r,n,12),s(r,o,16),r.writeUInt16LE(0,20),c.writeSync(i,r)}function d(i,t){const n=i.split(u.sep).join("/");return t&&!n.endsWith("/")?`${n}/`:n}function _(i,t){const n=c.statSync(i),o=d(u.basename(i)||"folder",!0),r=[{name:o,sourcePath:null,crc32:0,size:0,isDir:!0,mtimeMs:n.mtimeMs}];let e=0,m=0;const E=S=>{const a=c.readdirSync(S,{withFileTypes:!0});for(const I of a){const w=u.join(S,I.name),f=u.relative(i,w);if(!f||f.startsWith("..")||u.isAbsolute(f))continue;const l=c.lstatSync(w);if(!l.isSymbolicLink()){if(l.isDirectory()){r.push({name:u.posix.join(o,d(f,!0)),sourcePath:null,crc32:0,size:0,isDir:!0,mtimeMs:l.mtimeMs}),E(w);continue}if(l.isFile()){if(e+=1,e>t.maxFiles)throw new Error(`Too many files: ${e}`);if(m+=l.size,m>t.maxTotalSize)throw new Error(`Folder too large: ${m} bytes`);r.push({name:u.posix.join(o,d(f,!1)),sourcePath:w,crc32:A(w),size:l.size,isDir:!1,mtimeMs:l.mtimeMs})}}}};return E(i),r}function A(i){const t=c.openSync(i,"r"),n=Buffer.alloc(x);let o=4294967295;try{for(;;){const r=c.readSync(t,n,0,n.length,null);if(r<=0)break;o=F(o,n.subarray(0,r))}}finally{c.closeSync(t)}return(o^4294967295)>>>0}function B(i,t){const n=c.openSync(t,"r"),o=Buffer.alloc(x);try{for(;;){const r=c.readSync(n,o,0,o.length,null);if(r<=0)break;c.writeSync(i,o.subarray(0,r))}}finally{c.closeSync(n)}}function C(i,t,n={}){if(!c.statSync(i).isDirectory())throw new Error("Not a directory");const r={maxFiles:n.maxFiles??p,maxTotalSize:n.maxTotalSize??M},e=_(i,r),m=[];let E=0,S=0;c.mkdirSync(u.dirname(t),{recursive:!0});const a=c.openSync(t,"w");try{for(const f of e){const l=c.fstatSync(a).size,y={...f,offset:l};D(a,y),y.sourcePath&&(B(a,y.sourcePath),E+=1,S+=y.size),m.push(y)}const I=c.fstatSync(a).size;for(const f of m)T(a,f);const w=c.fstatSync(a).size-I;g(a,m.length,w,I)}finally{c.closeSync(a)}return{outputPath:t,fileCount:E,totalSize:S}}export{C as createZipArchive};
|