@livon/cli 0.27.0-rc.1 → 0.27.0-rc.2
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/index.cjs +1 -603
- package/dist/index.js +1 -574
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -1,603 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __webpack_require__ = {};
|
|
3
|
-
(()=>{
|
|
4
|
-
__webpack_require__.n = (module)=>{
|
|
5
|
-
var getter = module && module.__esModule ? ()=>module['default'] : ()=>module;
|
|
6
|
-
__webpack_require__.d(getter, {
|
|
7
|
-
a: getter
|
|
8
|
-
});
|
|
9
|
-
return getter;
|
|
10
|
-
};
|
|
11
|
-
})();
|
|
12
|
-
(()=>{
|
|
13
|
-
__webpack_require__.d = (exports1, definition)=>{
|
|
14
|
-
for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
15
|
-
enumerable: true,
|
|
16
|
-
get: definition[key]
|
|
17
|
-
});
|
|
18
|
-
};
|
|
19
|
-
})();
|
|
20
|
-
(()=>{
|
|
21
|
-
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
22
|
-
})();
|
|
23
|
-
var __webpack_exports__ = {};
|
|
24
|
-
const generate_namespaceObject = require("@livon/client/generate");
|
|
25
|
-
const external_node_child_process_namespaceObject = require("node:child_process");
|
|
26
|
-
const external_node_crypto_namespaceObject = require("node:crypto");
|
|
27
|
-
const external_node_fs_namespaceObject = require("node:fs");
|
|
28
|
-
const external_node_path_namespaceObject = require("node:path");
|
|
29
|
-
var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
|
|
30
|
-
const external_ws_namespaceObject = require("ws");
|
|
31
|
-
var external_ws_default = /*#__PURE__*/ __webpack_require__.n(external_ws_namespaceObject);
|
|
32
|
-
const external_msgpackr_namespaceObject = require("msgpackr");
|
|
33
|
-
const RETRY_RESET_AFTER_CONNECTION = 'livon.retry.reset_after_connection';
|
|
34
|
-
const createDefaultOptions = ()=>({
|
|
35
|
-
endpoint: '',
|
|
36
|
-
port: void 0,
|
|
37
|
-
out: '',
|
|
38
|
-
poll: void 0,
|
|
39
|
-
timeout: void 0,
|
|
40
|
-
event: '$explain',
|
|
41
|
-
method: 'POST',
|
|
42
|
-
headers: {},
|
|
43
|
-
payload: void 0
|
|
44
|
-
});
|
|
45
|
-
const readOptionValue = ({ argv, index, arg })=>{
|
|
46
|
-
if (arg.includes('=')) return {
|
|
47
|
-
nextIndex: index + 1,
|
|
48
|
-
value: arg.split('=').slice(1).join('=')
|
|
49
|
-
};
|
|
50
|
-
const value = argv[index + 1];
|
|
51
|
-
if (!value || value.startsWith('-')) return {
|
|
52
|
-
nextIndex: index + 1,
|
|
53
|
-
value: void 0
|
|
54
|
-
};
|
|
55
|
-
return {
|
|
56
|
-
nextIndex: index + 2,
|
|
57
|
-
value
|
|
58
|
-
};
|
|
59
|
-
};
|
|
60
|
-
const readCliArgs = ({ argv, index, options })=>{
|
|
61
|
-
const arg = argv[index];
|
|
62
|
-
if (!arg) return {
|
|
63
|
-
options,
|
|
64
|
-
command: []
|
|
65
|
-
};
|
|
66
|
-
if ('--' === arg) return {
|
|
67
|
-
options,
|
|
68
|
-
command: argv.slice(index + 1)
|
|
69
|
-
};
|
|
70
|
-
if (!arg.startsWith('-')) return {
|
|
71
|
-
options,
|
|
72
|
-
command: argv.slice(index)
|
|
73
|
-
};
|
|
74
|
-
if ('--no-event' === arg) return readCliArgs({
|
|
75
|
-
argv,
|
|
76
|
-
index: index + 1,
|
|
77
|
-
options: {
|
|
78
|
-
...options,
|
|
79
|
-
event: void 0,
|
|
80
|
-
method: 'GET'
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
if (arg.startsWith('--endpoint')) {
|
|
84
|
-
const { value, nextIndex } = readOptionValue({
|
|
85
|
-
argv,
|
|
86
|
-
index,
|
|
87
|
-
arg
|
|
88
|
-
});
|
|
89
|
-
return readCliArgs({
|
|
90
|
-
argv,
|
|
91
|
-
index: nextIndex,
|
|
92
|
-
options: {
|
|
93
|
-
...options,
|
|
94
|
-
endpoint: value ?? ''
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
if (arg.startsWith('--out')) {
|
|
99
|
-
const { value, nextIndex } = readOptionValue({
|
|
100
|
-
argv,
|
|
101
|
-
index,
|
|
102
|
-
arg
|
|
103
|
-
});
|
|
104
|
-
return readCliArgs({
|
|
105
|
-
argv,
|
|
106
|
-
index: nextIndex,
|
|
107
|
-
options: {
|
|
108
|
-
...options,
|
|
109
|
-
out: value ?? ''
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
if (arg.startsWith('--poll')) {
|
|
114
|
-
const { value, nextIndex } = readOptionValue({
|
|
115
|
-
argv,
|
|
116
|
-
index,
|
|
117
|
-
arg
|
|
118
|
-
});
|
|
119
|
-
return readCliArgs({
|
|
120
|
-
argv,
|
|
121
|
-
index: nextIndex,
|
|
122
|
-
options: {
|
|
123
|
-
...options,
|
|
124
|
-
poll: value ? Number(value) : void 0
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
if (arg.startsWith('--timeout')) {
|
|
129
|
-
const { value, nextIndex } = readOptionValue({
|
|
130
|
-
argv,
|
|
131
|
-
index,
|
|
132
|
-
arg
|
|
133
|
-
});
|
|
134
|
-
if (value) {
|
|
135
|
-
const parsed = Number(value);
|
|
136
|
-
if (!Number.isFinite(parsed) || parsed <= 0) throw new Error(`Invalid --timeout value: ${value}`);
|
|
137
|
-
return readCliArgs({
|
|
138
|
-
argv,
|
|
139
|
-
index: nextIndex,
|
|
140
|
-
options: {
|
|
141
|
-
...options,
|
|
142
|
-
timeout: parsed
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
return readCliArgs({
|
|
147
|
-
argv,
|
|
148
|
-
index: nextIndex,
|
|
149
|
-
options
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
if (arg.startsWith('--port')) {
|
|
153
|
-
const { value, nextIndex } = readOptionValue({
|
|
154
|
-
argv,
|
|
155
|
-
index,
|
|
156
|
-
arg
|
|
157
|
-
});
|
|
158
|
-
if (value) {
|
|
159
|
-
const parsed = Number(value);
|
|
160
|
-
if (!Number.isFinite(parsed) || parsed <= 0) throw new Error(`Invalid --port value: ${value}`);
|
|
161
|
-
return readCliArgs({
|
|
162
|
-
argv,
|
|
163
|
-
index: nextIndex,
|
|
164
|
-
options: {
|
|
165
|
-
...options,
|
|
166
|
-
port: parsed
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
return readCliArgs({
|
|
171
|
-
argv,
|
|
172
|
-
index: nextIndex,
|
|
173
|
-
options
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
if (arg.startsWith('--event')) {
|
|
177
|
-
const { value, nextIndex } = readOptionValue({
|
|
178
|
-
argv,
|
|
179
|
-
index,
|
|
180
|
-
arg
|
|
181
|
-
});
|
|
182
|
-
return readCliArgs({
|
|
183
|
-
argv,
|
|
184
|
-
index: nextIndex,
|
|
185
|
-
options: {
|
|
186
|
-
...options,
|
|
187
|
-
event: value,
|
|
188
|
-
method: 'POST'
|
|
189
|
-
}
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
if (arg.startsWith('--method')) {
|
|
193
|
-
const { value, nextIndex } = readOptionValue({
|
|
194
|
-
argv,
|
|
195
|
-
index,
|
|
196
|
-
arg
|
|
197
|
-
});
|
|
198
|
-
return readCliArgs({
|
|
199
|
-
argv,
|
|
200
|
-
index: nextIndex,
|
|
201
|
-
options: {
|
|
202
|
-
...options,
|
|
203
|
-
method: value && 'GET' === value.toUpperCase() ? 'GET' : 'POST'
|
|
204
|
-
}
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
if (arg.startsWith('--header')) {
|
|
208
|
-
const { value, nextIndex } = readOptionValue({
|
|
209
|
-
argv,
|
|
210
|
-
index,
|
|
211
|
-
arg
|
|
212
|
-
});
|
|
213
|
-
if (value) {
|
|
214
|
-
const [key, ...rest] = value.split(':');
|
|
215
|
-
if (key && rest.length > 0) return readCliArgs({
|
|
216
|
-
argv,
|
|
217
|
-
index: nextIndex,
|
|
218
|
-
options: {
|
|
219
|
-
...options,
|
|
220
|
-
headers: {
|
|
221
|
-
...options.headers,
|
|
222
|
-
[key.trim()]: rest.join(':').trim()
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
return readCliArgs({
|
|
228
|
-
argv,
|
|
229
|
-
index: nextIndex,
|
|
230
|
-
options
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
if (arg.startsWith('--payload')) {
|
|
234
|
-
const { value, nextIndex } = readOptionValue({
|
|
235
|
-
argv,
|
|
236
|
-
index,
|
|
237
|
-
arg
|
|
238
|
-
});
|
|
239
|
-
if (value) try {
|
|
240
|
-
return readCliArgs({
|
|
241
|
-
argv,
|
|
242
|
-
index: nextIndex,
|
|
243
|
-
options: {
|
|
244
|
-
...options,
|
|
245
|
-
payload: JSON.parse(value)
|
|
246
|
-
}
|
|
247
|
-
});
|
|
248
|
-
} catch (error) {
|
|
249
|
-
throw new Error(`Invalid JSON for --payload: ${error instanceof Error ? error.message : String(error)}`);
|
|
250
|
-
}
|
|
251
|
-
return readCliArgs({
|
|
252
|
-
argv,
|
|
253
|
-
index: nextIndex,
|
|
254
|
-
options
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
return readCliArgs({
|
|
258
|
-
argv,
|
|
259
|
-
index: index + 1,
|
|
260
|
-
options
|
|
261
|
-
});
|
|
262
|
-
};
|
|
263
|
-
const readCliInput = (argv)=>{
|
|
264
|
-
const parsed = readCliArgs({
|
|
265
|
-
argv,
|
|
266
|
-
index: 0,
|
|
267
|
-
options: createDefaultOptions()
|
|
268
|
-
});
|
|
269
|
-
const options = {
|
|
270
|
-
...parsed.options
|
|
271
|
-
};
|
|
272
|
-
if (!options.endpoint && options.port) options.endpoint = `ws://127.0.0.1:${options.port}/ws`;
|
|
273
|
-
if (!options.endpoint) throw new Error('Missing required --endpoint or --port');
|
|
274
|
-
if (options.port) options.endpoint = applyPortToEndpoint(options.endpoint, options.port);
|
|
275
|
-
if (!options.out) throw new Error('Missing required --out');
|
|
276
|
-
if (void 0 === options.event) throw new Error('Missing required --event for websocket mode.');
|
|
277
|
-
return {
|
|
278
|
-
options,
|
|
279
|
-
command: parsed.command
|
|
280
|
-
};
|
|
281
|
-
};
|
|
282
|
-
const applyPortToEndpoint = (endpoint, port)=>{
|
|
283
|
-
const url = new URL(endpoint);
|
|
284
|
-
if ('ws:' !== url.protocol && 'wss:' !== url.protocol) throw new Error('Endpoint must be ws:// or wss:// for websocket mode.');
|
|
285
|
-
url.port = String(port);
|
|
286
|
-
if (!url.pathname || '/' === url.pathname) url.pathname = '/ws';
|
|
287
|
-
return url.toString();
|
|
288
|
-
};
|
|
289
|
-
const hashAst = (ast)=>(0, external_node_crypto_namespaceObject.createHash)('sha256').update(JSON.stringify(ast)).digest('hex');
|
|
290
|
-
const compactMetadata = (metadata)=>{
|
|
291
|
-
if (!metadata) return;
|
|
292
|
-
return Object.keys(metadata).length > 0 ? metadata : void 0;
|
|
293
|
-
};
|
|
294
|
-
const compactContext = (context)=>{
|
|
295
|
-
if (!context || 0 === Object.keys(context).length) return;
|
|
296
|
-
return context;
|
|
297
|
-
};
|
|
298
|
-
const encodePayload = (value)=>(0, external_msgpackr_namespaceObject.pack)(value);
|
|
299
|
-
const decodePayload = (payload)=>payload ? (0, external_msgpackr_namespaceObject.unpack)(payload) : void 0;
|
|
300
|
-
const isRecord = (value)=>'object' == typeof value && null !== value && !Array.isArray(value);
|
|
301
|
-
const binaryFromSocketData = (data)=>{
|
|
302
|
-
if (Array.isArray(data)) return new Uint8Array(Buffer.concat(data));
|
|
303
|
-
if ('string' == typeof data) throw new Error('Expected binary WebSocket payload.');
|
|
304
|
-
if (data instanceof ArrayBuffer) return new Uint8Array(data);
|
|
305
|
-
if (Buffer.isBuffer(data)) return new Uint8Array(data);
|
|
306
|
-
if (ArrayBuffer.isView(data)) return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
307
|
-
return new Uint8Array(Buffer.from(data));
|
|
308
|
-
};
|
|
309
|
-
const ensureEvent = (value)=>{
|
|
310
|
-
if (!value) throw new Error('Missing required --event for websocket mode.');
|
|
311
|
-
return value;
|
|
312
|
-
};
|
|
313
|
-
const buildWireEnvelope = (input)=>{
|
|
314
|
-
const metadata = compactMetadata(input.metadata);
|
|
315
|
-
const context = compactContext(input.context);
|
|
316
|
-
const base = {
|
|
317
|
-
event: input.event,
|
|
318
|
-
metadata,
|
|
319
|
-
context: context ? encodePayload(context) : void 0
|
|
320
|
-
};
|
|
321
|
-
return {
|
|
322
|
-
...base,
|
|
323
|
-
payload: encodePayload(input.payload)
|
|
324
|
-
};
|
|
325
|
-
};
|
|
326
|
-
const DEFAULT_TIMEOUT_MS = 30000;
|
|
327
|
-
const CLIENT_GENERATOR_HASH = (0, generate_namespaceObject.getClientGeneratorFingerprint)();
|
|
328
|
-
const fetchAst = async (options, etag)=>{
|
|
329
|
-
const endpoint = options.endpoint.trim();
|
|
330
|
-
if (!endpoint.startsWith('ws://') && !endpoint.startsWith('wss://')) throw new Error('Endpoint must be ws:// or wss:// for $explain.');
|
|
331
|
-
return new Promise((resolve, reject)=>{
|
|
332
|
-
const ws = new (external_ws_default())(endpoint, {
|
|
333
|
-
headers: options.headers
|
|
334
|
-
});
|
|
335
|
-
let resolved = false;
|
|
336
|
-
let hadConnection = false;
|
|
337
|
-
const timeoutMs = options.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
338
|
-
const timeout = setTimeout(()=>{
|
|
339
|
-
if (!resolved) {
|
|
340
|
-
resolved = true;
|
|
341
|
-
ws.close();
|
|
342
|
-
reject(new Error('Timed out waiting for $explain response.'));
|
|
343
|
-
}
|
|
344
|
-
}, timeoutMs);
|
|
345
|
-
const finish = (result)=>{
|
|
346
|
-
if (resolved) return;
|
|
347
|
-
resolved = true;
|
|
348
|
-
clearTimeout(timeout);
|
|
349
|
-
ws.close();
|
|
350
|
-
resolve(result);
|
|
351
|
-
};
|
|
352
|
-
const fail = (error)=>{
|
|
353
|
-
if (resolved) return;
|
|
354
|
-
resolved = true;
|
|
355
|
-
clearTimeout(timeout);
|
|
356
|
-
ws.close();
|
|
357
|
-
const retryAware = error;
|
|
358
|
-
if (hadConnection) retryAware[RETRY_RESET_AFTER_CONNECTION] = true;
|
|
359
|
-
reject(error);
|
|
360
|
-
};
|
|
361
|
-
ws.on('error', (error)=>{
|
|
362
|
-
fail(error);
|
|
363
|
-
});
|
|
364
|
-
ws.on('open', ()=>{
|
|
365
|
-
hadConnection = true;
|
|
366
|
-
const eventName = ensureEvent(options.event);
|
|
367
|
-
const request = buildWireEnvelope({
|
|
368
|
-
event: eventName,
|
|
369
|
-
payload: options.payload ?? null,
|
|
370
|
-
metadata: etag ? {
|
|
371
|
-
ifNoneMatch: etag
|
|
372
|
-
} : void 0
|
|
373
|
-
});
|
|
374
|
-
ws.send((0, external_msgpackr_namespaceObject.pack)(request));
|
|
375
|
-
});
|
|
376
|
-
ws.on('message', (data)=>{
|
|
377
|
-
let parsed;
|
|
378
|
-
try {
|
|
379
|
-
parsed = (0, external_msgpackr_namespaceObject.unpack)(binaryFromSocketData(data));
|
|
380
|
-
} catch {
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
if (!isRecord(parsed)) return;
|
|
384
|
-
const envelope = parsed;
|
|
385
|
-
if (envelope.event && envelope.event !== options.event) return;
|
|
386
|
-
if (envelope.error) {
|
|
387
|
-
const decoded = decodePayload(envelope.error);
|
|
388
|
-
const message = decoded && 'object' == typeof decoded && 'message' in decoded ? String(decoded.message ?? 'Explain error') : 'Explain error';
|
|
389
|
-
fail(new Error(message));
|
|
390
|
-
return;
|
|
391
|
-
}
|
|
392
|
-
const payload = envelope.payload ? decodePayload(envelope.payload) : parsed;
|
|
393
|
-
if (!payload || 'object' != typeof payload) return;
|
|
394
|
-
const response = payload;
|
|
395
|
-
if (response.notModified) return void finish({
|
|
396
|
-
notModified: true,
|
|
397
|
-
etag: response.etag ?? etag
|
|
398
|
-
});
|
|
399
|
-
if ('ast' in response) finish({
|
|
400
|
-
ast: response.ast,
|
|
401
|
-
checksum: response.checksum,
|
|
402
|
-
schemaVersion: response.schemaVersion,
|
|
403
|
-
generatedAt: response.generatedAt,
|
|
404
|
-
etag: response.etag ?? response.checksum ?? etag
|
|
405
|
-
});
|
|
406
|
-
});
|
|
407
|
-
});
|
|
408
|
-
};
|
|
409
|
-
const resolveOutputPaths = (out)=>{
|
|
410
|
-
const isFile = external_node_path_default().extname(out).length > 0;
|
|
411
|
-
const outDir = isFile ? external_node_path_default().dirname(out) : out;
|
|
412
|
-
const astFile = external_node_path_default().join(outDir, 'ast.ts');
|
|
413
|
-
const clientFile = isFile ? out : external_node_path_default().join(outDir, 'client.ts');
|
|
414
|
-
const checksumFile = external_node_path_default().join(outDir, '.livon.client.checksum');
|
|
415
|
-
return {
|
|
416
|
-
outDir,
|
|
417
|
-
astFile,
|
|
418
|
-
clientFile,
|
|
419
|
-
checksumFile
|
|
420
|
-
};
|
|
421
|
-
};
|
|
422
|
-
const readCachedChecksum = async (checksumFile)=>{
|
|
423
|
-
const raw = (await external_node_fs_namespaceObject.promises.readFile(checksumFile, 'utf8').catch(()=>'')).trim();
|
|
424
|
-
if (!raw) return {};
|
|
425
|
-
if (raw.startsWith('{')) try {
|
|
426
|
-
const parsed = JSON.parse(raw);
|
|
427
|
-
const generatorHash = 'string' == typeof parsed.generatorHash ? parsed.generatorHash : void 0;
|
|
428
|
-
const etag = 'string' == typeof parsed.etag ? parsed.etag : void 0;
|
|
429
|
-
return {
|
|
430
|
-
generatorHash,
|
|
431
|
-
etag
|
|
432
|
-
};
|
|
433
|
-
} catch {}
|
|
434
|
-
const legacyVersionSeparator = raw.indexOf(':');
|
|
435
|
-
if (raw.startsWith('client-generator-') && legacyVersionSeparator > 0) {
|
|
436
|
-
const etag = raw.slice(legacyVersionSeparator + 1).trim();
|
|
437
|
-
return {
|
|
438
|
-
etag: etag || void 0
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
return {
|
|
442
|
-
etag: raw
|
|
443
|
-
};
|
|
444
|
-
};
|
|
445
|
-
const writeClientFiles = async (ast, options, meta)=>{
|
|
446
|
-
const { outDir, astFile, clientFile, checksumFile } = resolveOutputPaths(options.out);
|
|
447
|
-
await external_node_fs_namespaceObject.promises.mkdir(outDir, {
|
|
448
|
-
recursive: true
|
|
449
|
-
});
|
|
450
|
-
const previous = await readCachedChecksum(checksumFile);
|
|
451
|
-
const checksum = meta?.checksum ?? hashAst(ast);
|
|
452
|
-
const etagBase = (meta?.etag ?? checksum).trim();
|
|
453
|
-
const hasSameGenerator = previous.generatorHash === CLIENT_GENERATOR_HASH;
|
|
454
|
-
const hasSameEtag = previous.etag === etagBase;
|
|
455
|
-
if (hasSameGenerator && hasSameEtag) return {
|
|
456
|
-
updated: false,
|
|
457
|
-
checksum,
|
|
458
|
-
etag: etagBase,
|
|
459
|
-
schemaVersion: meta?.schemaVersion,
|
|
460
|
-
generatedAt: meta?.generatedAt
|
|
461
|
-
};
|
|
462
|
-
const generated = (0, generate_namespaceObject.generateClientFiles)({
|
|
463
|
-
ast: ast
|
|
464
|
-
});
|
|
465
|
-
const astSource = generated.files[generated.astFile];
|
|
466
|
-
const clientSource = generated.files[generated.clientFile];
|
|
467
|
-
if (!astSource || !clientSource) throw new Error('Generated client sources were empty.');
|
|
468
|
-
await external_node_fs_namespaceObject.promises.writeFile(astFile, astSource, 'utf8');
|
|
469
|
-
await external_node_fs_namespaceObject.promises.writeFile(clientFile, clientSource, 'utf8');
|
|
470
|
-
await external_node_fs_namespaceObject.promises.writeFile(checksumFile, JSON.stringify({
|
|
471
|
-
generatorHash: CLIENT_GENERATOR_HASH,
|
|
472
|
-
etag: etagBase
|
|
473
|
-
}), 'utf8');
|
|
474
|
-
return {
|
|
475
|
-
updated: true,
|
|
476
|
-
checksum,
|
|
477
|
-
etag: etagBase,
|
|
478
|
-
schemaVersion: meta?.schemaVersion,
|
|
479
|
-
generatedAt: meta?.generatedAt
|
|
480
|
-
};
|
|
481
|
-
};
|
|
482
|
-
const startCommandRuntime = ({ command })=>{
|
|
483
|
-
const [commandName, ...commandArgs] = command;
|
|
484
|
-
if (!commandName) throw new Error('Missing command to run after livon sync.');
|
|
485
|
-
const child = (0, external_node_child_process_namespaceObject.spawn)(commandName, commandArgs, {
|
|
486
|
-
stdio: 'inherit',
|
|
487
|
-
env: process.env
|
|
488
|
-
});
|
|
489
|
-
const stopChild = ()=>{
|
|
490
|
-
if (child.killed) return;
|
|
491
|
-
child.kill('SIGTERM');
|
|
492
|
-
};
|
|
493
|
-
process.on('exit', stopChild);
|
|
494
|
-
process.on('SIGINT', ()=>{
|
|
495
|
-
stopChild();
|
|
496
|
-
process.exit(130);
|
|
497
|
-
});
|
|
498
|
-
process.on('SIGTERM', ()=>{
|
|
499
|
-
stopChild();
|
|
500
|
-
process.exit(143);
|
|
501
|
-
});
|
|
502
|
-
const waitForExit = new Promise((resolve, reject)=>{
|
|
503
|
-
child.on('error', (error)=>{
|
|
504
|
-
reject(error);
|
|
505
|
-
});
|
|
506
|
-
child.on('exit', (code, signal)=>{
|
|
507
|
-
const exitCode = 'number' == typeof code ? code : signal ? 1 : 0;
|
|
508
|
-
if (0 !== exitCode) console.error(`livon: linked command exited with code ${exitCode}`);
|
|
509
|
-
resolve(exitCode);
|
|
510
|
-
process.exit(exitCode);
|
|
511
|
-
});
|
|
512
|
-
});
|
|
513
|
-
return {
|
|
514
|
-
waitForExit
|
|
515
|
-
};
|
|
516
|
-
};
|
|
517
|
-
const run = async ()=>{
|
|
518
|
-
const cli = readCliInput(process.argv.slice(2));
|
|
519
|
-
const options = cli.options;
|
|
520
|
-
const commandRuntimeInput = cli.command.length > 0 ? {
|
|
521
|
-
command: cli.command
|
|
522
|
-
} : void 0;
|
|
523
|
-
let commandRuntime;
|
|
524
|
-
const ensureCommandRuntime = ()=>{
|
|
525
|
-
if (!commandRuntimeInput || commandRuntime) return;
|
|
526
|
-
commandRuntime = startCommandRuntime(commandRuntimeInput);
|
|
527
|
-
};
|
|
528
|
-
const execute = async ()=>{
|
|
529
|
-
const { checksumFile } = resolveOutputPaths(options.out);
|
|
530
|
-
const cached = await readCachedChecksum(checksumFile);
|
|
531
|
-
const useCachedEtag = cached.generatorHash === CLIENT_GENERATOR_HASH;
|
|
532
|
-
const cachedEtag = useCachedEtag ? cached.etag : void 0;
|
|
533
|
-
const result = await fetchAst(options, cachedEtag);
|
|
534
|
-
if (result.notModified) return;
|
|
535
|
-
if (void 0 === result.ast) throw new Error('Explain response missing AST.');
|
|
536
|
-
const writeResult = await writeClientFiles(result.ast, options, {
|
|
537
|
-
checksum: result.checksum,
|
|
538
|
-
etag: result.etag,
|
|
539
|
-
schemaVersion: result.schemaVersion,
|
|
540
|
-
generatedAt: result.generatedAt
|
|
541
|
-
});
|
|
542
|
-
if (writeResult.updated) {
|
|
543
|
-
const meta = [];
|
|
544
|
-
if (writeResult.schemaVersion) meta.push(`schema ${writeResult.schemaVersion}`);
|
|
545
|
-
if (writeResult.generatedAt) meta.push(`generated ${writeResult.generatedAt}`);
|
|
546
|
-
const metaInfo = meta.length > 0 ? `, ${meta.join(', ')}` : '';
|
|
547
|
-
console.log(`livon: client updated (checksum ${writeResult.checksum}${metaInfo})`);
|
|
548
|
-
}
|
|
549
|
-
};
|
|
550
|
-
const withRetry = async (action)=>{
|
|
551
|
-
const maxAttempts = 20;
|
|
552
|
-
const baseDelay = 250;
|
|
553
|
-
const runAttempt = async (attempt, resetApplied)=>{
|
|
554
|
-
try {
|
|
555
|
-
await action();
|
|
556
|
-
return;
|
|
557
|
-
} catch (error) {
|
|
558
|
-
const retryAware = error;
|
|
559
|
-
const shouldReset = Boolean(retryAware?.[RETRY_RESET_AFTER_CONNECTION]) && !resetApplied;
|
|
560
|
-
const nextAttempt = shouldReset ? 1 : attempt + 1;
|
|
561
|
-
const nextResetApplied = shouldReset ? true : resetApplied;
|
|
562
|
-
if (nextAttempt >= maxAttempts) throw new Error('livon: giving up after repeated retries');
|
|
563
|
-
const wait = baseDelay * Math.min(nextAttempt, 10);
|
|
564
|
-
console.warn(`livon: attempt ${nextAttempt}/${maxAttempts} failed: ${error instanceof Error ? error.message : String(error)} – retrying in ${wait}ms`);
|
|
565
|
-
await new Promise((resolve)=>setTimeout(resolve, wait));
|
|
566
|
-
await runAttempt(nextAttempt, nextResetApplied);
|
|
567
|
-
}
|
|
568
|
-
};
|
|
569
|
-
await runAttempt(0, false);
|
|
570
|
-
};
|
|
571
|
-
if (options.poll && options.poll > 0) {
|
|
572
|
-
let inFlight = false;
|
|
573
|
-
const tick = async ()=>{
|
|
574
|
-
if (inFlight) return;
|
|
575
|
-
inFlight = true;
|
|
576
|
-
try {
|
|
577
|
-
await withRetry(execute);
|
|
578
|
-
ensureCommandRuntime();
|
|
579
|
-
} catch (error) {
|
|
580
|
-
console.error('livon: poll error', error);
|
|
581
|
-
} finally{
|
|
582
|
-
inFlight = false;
|
|
583
|
-
setTimeout(tick, options.poll);
|
|
584
|
-
}
|
|
585
|
-
};
|
|
586
|
-
await tick();
|
|
587
|
-
return;
|
|
588
|
-
}
|
|
589
|
-
await withRetry(execute);
|
|
590
|
-
ensureCommandRuntime();
|
|
591
|
-
if (commandRuntime) {
|
|
592
|
-
const commandExitCode = await commandRuntime.waitForExit;
|
|
593
|
-
process.exit(commandExitCode);
|
|
594
|
-
}
|
|
595
|
-
};
|
|
596
|
-
run().catch((error)=>{
|
|
597
|
-
console.error(error);
|
|
598
|
-
process.exit(1);
|
|
599
|
-
});
|
|
600
|
-
for(var __rspack_i in __webpack_exports__)exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
601
|
-
Object.defineProperty(exports, '__esModule', {
|
|
602
|
-
value: true
|
|
603
|
-
});
|
|
1
|
+
"use strict";const __rslib_import_meta_url__="u"<typeof document?new(require("url".replace("",""))).URL("file:"+__filename).href:document.currentScript&&document.currentScript.src||new URL("main.js",document.baseURI).href;var __webpack_require__={};__webpack_require__.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return __webpack_require__.d(t,{a:t}),t},__webpack_require__.d=(e,t)=>{for(var r in t)__webpack_require__.o(t,r)&&!__webpack_require__.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},__webpack_require__.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t);var __webpack_exports__={};const generate_namespaceObject=require("@livon/client/generate"),external_node_child_process_namespaceObject=require("node:child_process"),external_node_crypto_namespaceObject=require("node:crypto"),external_node_fs_namespaceObject=require("node:fs"),external_node_path_namespaceObject=require("node:path");var external_node_path_default=__webpack_require__.n(external_node_path_namespaceObject);const external_ws_namespaceObject=require("ws");var external_ws_default=__webpack_require__.n(external_ws_namespaceObject);const external_msgpackr_namespaceObject=require("msgpackr"),RETRY_RESET_AFTER_CONNECTION="livon.retry.reset_after_connection",createDefaultOptions=()=>({endpoint:"",port:void 0,out:"",poll:void 0,timeout:void 0,event:"$explain",method:"POST",headers:{},payload:void 0}),readOptionValue=({argv:e,index:t,arg:r})=>{if(r.includes("="))return{nextIndex:t+1,value:r.split("=").slice(1).join("=")};let a=e[t+1];return!a||a.startsWith("-")?{nextIndex:t+1,value:void 0}:{nextIndex:t+2,value:a}},readCliArgs=({argv:e,index:t,options:r})=>{let a=e[t];if(!a)return{options:r,command:[]};if("--"===a)return{options:r,command:e.slice(t+1)};if(!a.startsWith("-"))return{options:r,command:e.slice(t)};if("--no-event"===a)return readCliArgs({argv:e,index:t+1,options:{...r,event:void 0,method:"GET"}});if(a.startsWith("--endpoint")){let{value:n,nextIndex:i}=readOptionValue({argv:e,index:t,arg:a});return readCliArgs({argv:e,index:i,options:{...r,endpoint:n??""}})}if(a.startsWith("--out")){let{value:n,nextIndex:i}=readOptionValue({argv:e,index:t,arg:a});return readCliArgs({argv:e,index:i,options:{...r,out:n??""}})}if(a.startsWith("--poll")){let{value:n,nextIndex:i}=readOptionValue({argv:e,index:t,arg:a});return readCliArgs({argv:e,index:i,options:{...r,poll:n?Number(n):void 0}})}if(a.startsWith("--timeout")){let{value:n,nextIndex:i}=readOptionValue({argv:e,index:t,arg:a});if(n){let t=Number(n);if(!Number.isFinite(t)||t<=0)throw Error(`Invalid --timeout value: ${n}`);return readCliArgs({argv:e,index:i,options:{...r,timeout:t}})}return readCliArgs({argv:e,index:i,options:r})}if(a.startsWith("--port")){let{value:n,nextIndex:i}=readOptionValue({argv:e,index:t,arg:a});if(n){let t=Number(n);if(!Number.isFinite(t)||t<=0)throw Error(`Invalid --port value: ${n}`);return readCliArgs({argv:e,index:i,options:{...r,port:t}})}return readCliArgs({argv:e,index:i,options:r})}if(a.startsWith("--event")){let{value:n,nextIndex:i}=readOptionValue({argv:e,index:t,arg:a});return readCliArgs({argv:e,index:i,options:{...r,event:n,method:"POST"}})}if(a.startsWith("--method")){let{value:n,nextIndex:i}=readOptionValue({argv:e,index:t,arg:a});return readCliArgs({argv:e,index:i,options:{...r,method:n&&"GET"===n.toUpperCase()?"GET":"POST"}})}if(a.startsWith("--header")){let{value:n,nextIndex:i}=readOptionValue({argv:e,index:t,arg:a});if(n){let[t,...a]=n.split(":");if(t&&a.length>0)return readCliArgs({argv:e,index:i,options:{...r,headers:{...r.headers,[t.trim()]:a.join(":").trim()}}})}return readCliArgs({argv:e,index:i,options:r})}if(a.startsWith("--payload")){let{value:n,nextIndex:i}=readOptionValue({argv:e,index:t,arg:a});if(n)try{return readCliArgs({argv:e,index:i,options:{...r,payload:JSON.parse(n)}})}catch(e){throw Error(`Invalid JSON for --payload: ${e instanceof Error?e.message:String(e)}`)}return readCliArgs({argv:e,index:i,options:r})}return readCliArgs({argv:e,index:t+1,options:r})},readCliInput=e=>{let t=readCliArgs({argv:e,index:0,options:createDefaultOptions()}),r={...t.options};if(!r.endpoint&&r.port&&(r.endpoint=`ws://127.0.0.1:${r.port}/ws`),!r.endpoint)throw Error("Missing required --endpoint or --port");if(r.port&&(r.endpoint=applyPortToEndpoint(r.endpoint,r.port)),!r.out)throw Error("Missing required --out");if(void 0===r.event)throw Error("Missing required --event for websocket mode.");return{options:r,command:t.command}},applyPortToEndpoint=(e,t)=>{let r=new URL(e);if("ws:"!==r.protocol&&"wss:"!==r.protocol)throw Error("Endpoint must be ws:// or wss:// for websocket mode.");return r.port=String(t),r.pathname&&"/"!==r.pathname||(r.pathname="/ws"),r.toString()},hashAst=e=>(0,external_node_crypto_namespaceObject.createHash)("sha256").update(JSON.stringify(e)).digest("hex"),compactMetadata=e=>{if(e)return Object.keys(e).length>0?e:void 0},compactContext=e=>{if(e&&0!==Object.keys(e).length)return e},encodePayload=e=>(0,external_msgpackr_namespaceObject.pack)(e),decodePayload=e=>e?(0,external_msgpackr_namespaceObject.unpack)(e):void 0,isRecord=e=>"object"==typeof e&&null!==e&&!Array.isArray(e),binaryFromSocketData=e=>{if(Array.isArray(e))return new Uint8Array(Buffer.concat(e));if("string"==typeof e)throw Error("Expected binary WebSocket payload.");return e instanceof ArrayBuffer||Buffer.isBuffer(e)?new Uint8Array(e):ArrayBuffer.isView(e)?new Uint8Array(e.buffer,e.byteOffset,e.byteLength):new Uint8Array(Buffer.from(e))},ensureEvent=e=>{if(!e)throw Error("Missing required --event for websocket mode.");return e},buildWireEnvelope=e=>{let t=compactMetadata(e.metadata),r=compactContext(e.context);return{...{event:e.event,metadata:t,context:r?encodePayload(r):void 0},payload:encodePayload(e.payload)}},DEFAULT_TIMEOUT_MS=3e4,CLIENT_GENERATOR_HASH=(0,generate_namespaceObject.getClientGeneratorFingerprint)(),fetchAst=async(e,t)=>{let r=e.endpoint.trim();if(!r.startsWith("ws://")&&!r.startsWith("wss://"))throw Error("Endpoint must be ws:// or wss:// for $explain.");return new Promise((a,n)=>{let i=new(external_ws_default())(r,{headers:e.headers}),o=!1,s=!1,l=setTimeout(()=>{o||(o=!0,i.close(),n(Error("Timed out waiting for $explain response.")))},e.timeout??3e4),c=e=>{o||(o=!0,clearTimeout(l),i.close(),a(e))},d=e=>{o||(o=!0,clearTimeout(l),i.close(),s&&(e[RETRY_RESET_AFTER_CONNECTION]=!0),n(e))};i.on("error",e=>{d(e)}),i.on("open",()=>{s=!0;let r=buildWireEnvelope({event:ensureEvent(e.event),payload:e.payload??null,metadata:t?{ifNoneMatch:t}:void 0});i.send((0,external_msgpackr_namespaceObject.pack)(r))}),i.on("message",r=>{let a;try{a=(0,external_msgpackr_namespaceObject.unpack)(binaryFromSocketData(r))}catch{return}if(!isRecord(a))return;let n=a;if(n.event&&n.event!==e.event)return;if(n.error){let e=decodePayload(n.error);d(Error(e&&"object"==typeof e&&"message"in e?String(e.message??"Explain error"):"Explain error"));return}let i=n.payload?decodePayload(n.payload):a;!i||"object"!=typeof i||(i.notModified?c({notModified:!0,etag:i.etag??t}):"ast"in i&&c({ast:i.ast,checksum:i.checksum,schemaVersion:i.schemaVersion,generatedAt:i.generatedAt,etag:i.etag??i.checksum??t}))})})},resolveOutputPaths=e=>{let t=external_node_path_default().extname(e).length>0,r=t?external_node_path_default().dirname(e):e,a=external_node_path_default().join(r,"ast.ts"),n=t?e:external_node_path_default().join(r,"client.ts"),i=external_node_path_default().join(r,".livon.client.checksum");return{outDir:r,astFile:a,clientFile:n,checksumFile:i}},readCachedChecksum=async e=>{let t=(await external_node_fs_namespaceObject.promises.readFile(e,"utf8").catch(()=>"")).trim();if(!t)return{};if(t.startsWith("{"))try{let e=JSON.parse(t),r="string"==typeof e.generatorHash?e.generatorHash:void 0,a="string"==typeof e.etag?e.etag:void 0;return{generatorHash:r,etag:a}}catch{}let r=t.indexOf(":");return t.startsWith("client-generator-")&&r>0?{etag:t.slice(r+1).trim()||void 0}:{etag:t}},writeClientFiles=async(e,t,r)=>{let{outDir:a,astFile:n,clientFile:i,checksumFile:o}=resolveOutputPaths(t.out);await external_node_fs_namespaceObject.promises.mkdir(a,{recursive:!0});let s=await readCachedChecksum(o),l=r?.checksum??hashAst(e),c=(r?.etag??l).trim(),d=s.generatorHash===CLIENT_GENERATOR_HASH,p=s.etag===c;if(d&&p)return{updated:!1,checksum:l,etag:c,schemaVersion:r?.schemaVersion,generatedAt:r?.generatedAt};let u=(0,generate_namespaceObject.generateClientFiles)({ast:e}),_=u.files[u.astFile],m=u.files[u.clientFile];if(!_||!m)throw Error("Generated client sources were empty.");return await external_node_fs_namespaceObject.promises.writeFile(n,_,"utf8"),await external_node_fs_namespaceObject.promises.writeFile(i,m,"utf8"),await external_node_fs_namespaceObject.promises.writeFile(o,JSON.stringify({generatorHash:CLIENT_GENERATOR_HASH,etag:c}),"utf8"),{updated:!0,checksum:l,etag:c,schemaVersion:r?.schemaVersion,generatedAt:r?.generatedAt}},startCommandRuntime=({command:e})=>{let[t,...r]=e;if(!t)throw Error("Missing command to run after livon sync.");let a=(0,external_node_child_process_namespaceObject.spawn)(t,r,{stdio:"inherit",env:process.env}),n=()=>{a.killed||a.kill("SIGTERM")};return process.on("exit",n),process.on("SIGINT",()=>{n(),process.exit(130)}),process.on("SIGTERM",()=>{n(),process.exit(143)}),{waitForExit:new Promise((e,t)=>{a.on("error",e=>{t(e)}),a.on("exit",(t,r)=>{let a="number"==typeof t?t:+!!r;0!==a&&console.error(`livon: linked command exited with code ${a}`),e(a),process.exit(a)})})}},run=async()=>{let e,t=readCliInput(process.argv.slice(2)),r=t.options,a=t.command.length>0?{command:t.command}:void 0,n=()=>{a&&!e&&(e=startCommandRuntime(a))},i=async()=>{let{checksumFile:e}=resolveOutputPaths(r.out),t=await readCachedChecksum(e),a=t.generatorHash===CLIENT_GENERATOR_HASH?t.etag:void 0,n=await fetchAst(r,a);if(n.notModified)return;if(void 0===n.ast)throw Error("Explain response missing AST.");let i=await writeClientFiles(n.ast,r,{checksum:n.checksum,etag:n.etag,schemaVersion:n.schemaVersion,generatedAt:n.generatedAt});if(i.updated){let e=[];i.schemaVersion&&e.push(`schema ${i.schemaVersion}`),i.generatedAt&&e.push(`generated ${i.generatedAt}`);let t=e.length>0?`, ${e.join(", ")}`:"";console.log(`livon: client updated (checksum ${i.checksum}${t})`)}},o=async e=>{let t=async(r,a)=>{try{await e();return}catch(o){let e=!!o?.[RETRY_RESET_AFTER_CONNECTION]&&!a,n=e?1:r+1;if(n>=20)throw Error("livon: giving up after repeated retries");let i=250*Math.min(n,10);console.warn(`livon: attempt ${n}/20 failed: ${o instanceof Error?o.message:String(o)} – retrying in ${i}ms`),await new Promise(e=>setTimeout(e,i)),await t(n,!!e||a)}};await t(0,!1)};if(r.poll&&r.poll>0){let e=!1,t=async()=>{if(!e){e=!0;try{await o(i),n()}catch(e){console.error("livon: poll error",e)}finally{e=!1,setTimeout(t,r.poll)}}};await t();return}if(await o(i),n(),e){let t=await e.waitForExit;process.exit(t)}};for(var __rspack_i in run().catch(e=>{console.error(e),process.exit(1)}),__webpack_exports__)exports[__rspack_i]=__webpack_exports__[__rspack_i];Object.defineProperty(exports,"__esModule",{value:!0});
|
package/dist/index.js
CHANGED
|
@@ -1,574 +1 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { spawn } from "node:child_process";
|
|
3
|
-
import { createHash } from "node:crypto";
|
|
4
|
-
import { promises } from "node:fs";
|
|
5
|
-
import node_path from "node:path";
|
|
6
|
-
import ws_0 from "ws";
|
|
7
|
-
import { pack, unpack } from "msgpackr";
|
|
8
|
-
const RETRY_RESET_AFTER_CONNECTION = 'livon.retry.reset_after_connection';
|
|
9
|
-
const createDefaultOptions = ()=>({
|
|
10
|
-
endpoint: '',
|
|
11
|
-
port: void 0,
|
|
12
|
-
out: '',
|
|
13
|
-
poll: void 0,
|
|
14
|
-
timeout: void 0,
|
|
15
|
-
event: '$explain',
|
|
16
|
-
method: 'POST',
|
|
17
|
-
headers: {},
|
|
18
|
-
payload: void 0
|
|
19
|
-
});
|
|
20
|
-
const readOptionValue = ({ argv, index, arg })=>{
|
|
21
|
-
if (arg.includes('=')) return {
|
|
22
|
-
nextIndex: index + 1,
|
|
23
|
-
value: arg.split('=').slice(1).join('=')
|
|
24
|
-
};
|
|
25
|
-
const value = argv[index + 1];
|
|
26
|
-
if (!value || value.startsWith('-')) return {
|
|
27
|
-
nextIndex: index + 1,
|
|
28
|
-
value: void 0
|
|
29
|
-
};
|
|
30
|
-
return {
|
|
31
|
-
nextIndex: index + 2,
|
|
32
|
-
value
|
|
33
|
-
};
|
|
34
|
-
};
|
|
35
|
-
const readCliArgs = ({ argv, index, options })=>{
|
|
36
|
-
const arg = argv[index];
|
|
37
|
-
if (!arg) return {
|
|
38
|
-
options,
|
|
39
|
-
command: []
|
|
40
|
-
};
|
|
41
|
-
if ('--' === arg) return {
|
|
42
|
-
options,
|
|
43
|
-
command: argv.slice(index + 1)
|
|
44
|
-
};
|
|
45
|
-
if (!arg.startsWith('-')) return {
|
|
46
|
-
options,
|
|
47
|
-
command: argv.slice(index)
|
|
48
|
-
};
|
|
49
|
-
if ('--no-event' === arg) return readCliArgs({
|
|
50
|
-
argv,
|
|
51
|
-
index: index + 1,
|
|
52
|
-
options: {
|
|
53
|
-
...options,
|
|
54
|
-
event: void 0,
|
|
55
|
-
method: 'GET'
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
if (arg.startsWith('--endpoint')) {
|
|
59
|
-
const { value, nextIndex } = readOptionValue({
|
|
60
|
-
argv,
|
|
61
|
-
index,
|
|
62
|
-
arg
|
|
63
|
-
});
|
|
64
|
-
return readCliArgs({
|
|
65
|
-
argv,
|
|
66
|
-
index: nextIndex,
|
|
67
|
-
options: {
|
|
68
|
-
...options,
|
|
69
|
-
endpoint: value ?? ''
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
if (arg.startsWith('--out')) {
|
|
74
|
-
const { value, nextIndex } = readOptionValue({
|
|
75
|
-
argv,
|
|
76
|
-
index,
|
|
77
|
-
arg
|
|
78
|
-
});
|
|
79
|
-
return readCliArgs({
|
|
80
|
-
argv,
|
|
81
|
-
index: nextIndex,
|
|
82
|
-
options: {
|
|
83
|
-
...options,
|
|
84
|
-
out: value ?? ''
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
if (arg.startsWith('--poll')) {
|
|
89
|
-
const { value, nextIndex } = readOptionValue({
|
|
90
|
-
argv,
|
|
91
|
-
index,
|
|
92
|
-
arg
|
|
93
|
-
});
|
|
94
|
-
return readCliArgs({
|
|
95
|
-
argv,
|
|
96
|
-
index: nextIndex,
|
|
97
|
-
options: {
|
|
98
|
-
...options,
|
|
99
|
-
poll: value ? Number(value) : void 0
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
if (arg.startsWith('--timeout')) {
|
|
104
|
-
const { value, nextIndex } = readOptionValue({
|
|
105
|
-
argv,
|
|
106
|
-
index,
|
|
107
|
-
arg
|
|
108
|
-
});
|
|
109
|
-
if (value) {
|
|
110
|
-
const parsed = Number(value);
|
|
111
|
-
if (!Number.isFinite(parsed) || parsed <= 0) throw new Error(`Invalid --timeout value: ${value}`);
|
|
112
|
-
return readCliArgs({
|
|
113
|
-
argv,
|
|
114
|
-
index: nextIndex,
|
|
115
|
-
options: {
|
|
116
|
-
...options,
|
|
117
|
-
timeout: parsed
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
return readCliArgs({
|
|
122
|
-
argv,
|
|
123
|
-
index: nextIndex,
|
|
124
|
-
options
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
if (arg.startsWith('--port')) {
|
|
128
|
-
const { value, nextIndex } = readOptionValue({
|
|
129
|
-
argv,
|
|
130
|
-
index,
|
|
131
|
-
arg
|
|
132
|
-
});
|
|
133
|
-
if (value) {
|
|
134
|
-
const parsed = Number(value);
|
|
135
|
-
if (!Number.isFinite(parsed) || parsed <= 0) throw new Error(`Invalid --port value: ${value}`);
|
|
136
|
-
return readCliArgs({
|
|
137
|
-
argv,
|
|
138
|
-
index: nextIndex,
|
|
139
|
-
options: {
|
|
140
|
-
...options,
|
|
141
|
-
port: parsed
|
|
142
|
-
}
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
return readCliArgs({
|
|
146
|
-
argv,
|
|
147
|
-
index: nextIndex,
|
|
148
|
-
options
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
if (arg.startsWith('--event')) {
|
|
152
|
-
const { value, nextIndex } = readOptionValue({
|
|
153
|
-
argv,
|
|
154
|
-
index,
|
|
155
|
-
arg
|
|
156
|
-
});
|
|
157
|
-
return readCliArgs({
|
|
158
|
-
argv,
|
|
159
|
-
index: nextIndex,
|
|
160
|
-
options: {
|
|
161
|
-
...options,
|
|
162
|
-
event: value,
|
|
163
|
-
method: 'POST'
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
if (arg.startsWith('--method')) {
|
|
168
|
-
const { value, nextIndex } = readOptionValue({
|
|
169
|
-
argv,
|
|
170
|
-
index,
|
|
171
|
-
arg
|
|
172
|
-
});
|
|
173
|
-
return readCliArgs({
|
|
174
|
-
argv,
|
|
175
|
-
index: nextIndex,
|
|
176
|
-
options: {
|
|
177
|
-
...options,
|
|
178
|
-
method: value && 'GET' === value.toUpperCase() ? 'GET' : 'POST'
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
if (arg.startsWith('--header')) {
|
|
183
|
-
const { value, nextIndex } = readOptionValue({
|
|
184
|
-
argv,
|
|
185
|
-
index,
|
|
186
|
-
arg
|
|
187
|
-
});
|
|
188
|
-
if (value) {
|
|
189
|
-
const [key, ...rest] = value.split(':');
|
|
190
|
-
if (key && rest.length > 0) return readCliArgs({
|
|
191
|
-
argv,
|
|
192
|
-
index: nextIndex,
|
|
193
|
-
options: {
|
|
194
|
-
...options,
|
|
195
|
-
headers: {
|
|
196
|
-
...options.headers,
|
|
197
|
-
[key.trim()]: rest.join(':').trim()
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
return readCliArgs({
|
|
203
|
-
argv,
|
|
204
|
-
index: nextIndex,
|
|
205
|
-
options
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
if (arg.startsWith('--payload')) {
|
|
209
|
-
const { value, nextIndex } = readOptionValue({
|
|
210
|
-
argv,
|
|
211
|
-
index,
|
|
212
|
-
arg
|
|
213
|
-
});
|
|
214
|
-
if (value) try {
|
|
215
|
-
return readCliArgs({
|
|
216
|
-
argv,
|
|
217
|
-
index: nextIndex,
|
|
218
|
-
options: {
|
|
219
|
-
...options,
|
|
220
|
-
payload: JSON.parse(value)
|
|
221
|
-
}
|
|
222
|
-
});
|
|
223
|
-
} catch (error) {
|
|
224
|
-
throw new Error(`Invalid JSON for --payload: ${error instanceof Error ? error.message : String(error)}`);
|
|
225
|
-
}
|
|
226
|
-
return readCliArgs({
|
|
227
|
-
argv,
|
|
228
|
-
index: nextIndex,
|
|
229
|
-
options
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
return readCliArgs({
|
|
233
|
-
argv,
|
|
234
|
-
index: index + 1,
|
|
235
|
-
options
|
|
236
|
-
});
|
|
237
|
-
};
|
|
238
|
-
const readCliInput = (argv)=>{
|
|
239
|
-
const parsed = readCliArgs({
|
|
240
|
-
argv,
|
|
241
|
-
index: 0,
|
|
242
|
-
options: createDefaultOptions()
|
|
243
|
-
});
|
|
244
|
-
const options = {
|
|
245
|
-
...parsed.options
|
|
246
|
-
};
|
|
247
|
-
if (!options.endpoint && options.port) options.endpoint = `ws://127.0.0.1:${options.port}/ws`;
|
|
248
|
-
if (!options.endpoint) throw new Error('Missing required --endpoint or --port');
|
|
249
|
-
if (options.port) options.endpoint = applyPortToEndpoint(options.endpoint, options.port);
|
|
250
|
-
if (!options.out) throw new Error('Missing required --out');
|
|
251
|
-
if (void 0 === options.event) throw new Error('Missing required --event for websocket mode.');
|
|
252
|
-
return {
|
|
253
|
-
options,
|
|
254
|
-
command: parsed.command
|
|
255
|
-
};
|
|
256
|
-
};
|
|
257
|
-
const applyPortToEndpoint = (endpoint, port)=>{
|
|
258
|
-
const url = new URL(endpoint);
|
|
259
|
-
if ('ws:' !== url.protocol && 'wss:' !== url.protocol) throw new Error('Endpoint must be ws:// or wss:// for websocket mode.');
|
|
260
|
-
url.port = String(port);
|
|
261
|
-
if (!url.pathname || '/' === url.pathname) url.pathname = '/ws';
|
|
262
|
-
return url.toString();
|
|
263
|
-
};
|
|
264
|
-
const hashAst = (ast)=>createHash('sha256').update(JSON.stringify(ast)).digest('hex');
|
|
265
|
-
const compactMetadata = (metadata)=>{
|
|
266
|
-
if (!metadata) return;
|
|
267
|
-
return Object.keys(metadata).length > 0 ? metadata : void 0;
|
|
268
|
-
};
|
|
269
|
-
const compactContext = (context)=>{
|
|
270
|
-
if (!context || 0 === Object.keys(context).length) return;
|
|
271
|
-
return context;
|
|
272
|
-
};
|
|
273
|
-
const encodePayload = (value)=>pack(value);
|
|
274
|
-
const decodePayload = (payload)=>payload ? unpack(payload) : void 0;
|
|
275
|
-
const isRecord = (value)=>'object' == typeof value && null !== value && !Array.isArray(value);
|
|
276
|
-
const binaryFromSocketData = (data)=>{
|
|
277
|
-
if (Array.isArray(data)) return new Uint8Array(Buffer.concat(data));
|
|
278
|
-
if ('string' == typeof data) throw new Error('Expected binary WebSocket payload.');
|
|
279
|
-
if (data instanceof ArrayBuffer) return new Uint8Array(data);
|
|
280
|
-
if (Buffer.isBuffer(data)) return new Uint8Array(data);
|
|
281
|
-
if (ArrayBuffer.isView(data)) return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
282
|
-
return new Uint8Array(Buffer.from(data));
|
|
283
|
-
};
|
|
284
|
-
const ensureEvent = (value)=>{
|
|
285
|
-
if (!value) throw new Error('Missing required --event for websocket mode.');
|
|
286
|
-
return value;
|
|
287
|
-
};
|
|
288
|
-
const buildWireEnvelope = (input)=>{
|
|
289
|
-
const metadata = compactMetadata(input.metadata);
|
|
290
|
-
const context = compactContext(input.context);
|
|
291
|
-
const base = {
|
|
292
|
-
event: input.event,
|
|
293
|
-
metadata,
|
|
294
|
-
context: context ? encodePayload(context) : void 0
|
|
295
|
-
};
|
|
296
|
-
return {
|
|
297
|
-
...base,
|
|
298
|
-
payload: encodePayload(input.payload)
|
|
299
|
-
};
|
|
300
|
-
};
|
|
301
|
-
const DEFAULT_TIMEOUT_MS = 30000;
|
|
302
|
-
const CLIENT_GENERATOR_HASH = getClientGeneratorFingerprint();
|
|
303
|
-
const fetchAst = async (options, etag)=>{
|
|
304
|
-
const endpoint = options.endpoint.trim();
|
|
305
|
-
if (!endpoint.startsWith('ws://') && !endpoint.startsWith('wss://')) throw new Error('Endpoint must be ws:// or wss:// for $explain.');
|
|
306
|
-
return new Promise((resolve, reject)=>{
|
|
307
|
-
const ws = new ws_0(endpoint, {
|
|
308
|
-
headers: options.headers
|
|
309
|
-
});
|
|
310
|
-
let resolved = false;
|
|
311
|
-
let hadConnection = false;
|
|
312
|
-
const timeoutMs = options.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
313
|
-
const timeout = setTimeout(()=>{
|
|
314
|
-
if (!resolved) {
|
|
315
|
-
resolved = true;
|
|
316
|
-
ws.close();
|
|
317
|
-
reject(new Error('Timed out waiting for $explain response.'));
|
|
318
|
-
}
|
|
319
|
-
}, timeoutMs);
|
|
320
|
-
const finish = (result)=>{
|
|
321
|
-
if (resolved) return;
|
|
322
|
-
resolved = true;
|
|
323
|
-
clearTimeout(timeout);
|
|
324
|
-
ws.close();
|
|
325
|
-
resolve(result);
|
|
326
|
-
};
|
|
327
|
-
const fail = (error)=>{
|
|
328
|
-
if (resolved) return;
|
|
329
|
-
resolved = true;
|
|
330
|
-
clearTimeout(timeout);
|
|
331
|
-
ws.close();
|
|
332
|
-
const retryAware = error;
|
|
333
|
-
if (hadConnection) retryAware[RETRY_RESET_AFTER_CONNECTION] = true;
|
|
334
|
-
reject(error);
|
|
335
|
-
};
|
|
336
|
-
ws.on('error', (error)=>{
|
|
337
|
-
fail(error);
|
|
338
|
-
});
|
|
339
|
-
ws.on('open', ()=>{
|
|
340
|
-
hadConnection = true;
|
|
341
|
-
const eventName = ensureEvent(options.event);
|
|
342
|
-
const request = buildWireEnvelope({
|
|
343
|
-
event: eventName,
|
|
344
|
-
payload: options.payload ?? null,
|
|
345
|
-
metadata: etag ? {
|
|
346
|
-
ifNoneMatch: etag
|
|
347
|
-
} : void 0
|
|
348
|
-
});
|
|
349
|
-
ws.send(pack(request));
|
|
350
|
-
});
|
|
351
|
-
ws.on('message', (data)=>{
|
|
352
|
-
let parsed;
|
|
353
|
-
try {
|
|
354
|
-
parsed = unpack(binaryFromSocketData(data));
|
|
355
|
-
} catch {
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
if (!isRecord(parsed)) return;
|
|
359
|
-
const envelope = parsed;
|
|
360
|
-
if (envelope.event && envelope.event !== options.event) return;
|
|
361
|
-
if (envelope.error) {
|
|
362
|
-
const decoded = decodePayload(envelope.error);
|
|
363
|
-
const message = decoded && 'object' == typeof decoded && 'message' in decoded ? String(decoded.message ?? 'Explain error') : 'Explain error';
|
|
364
|
-
fail(new Error(message));
|
|
365
|
-
return;
|
|
366
|
-
}
|
|
367
|
-
const payload = envelope.payload ? decodePayload(envelope.payload) : parsed;
|
|
368
|
-
if (!payload || 'object' != typeof payload) return;
|
|
369
|
-
const response = payload;
|
|
370
|
-
if (response.notModified) return void finish({
|
|
371
|
-
notModified: true,
|
|
372
|
-
etag: response.etag ?? etag
|
|
373
|
-
});
|
|
374
|
-
if ('ast' in response) finish({
|
|
375
|
-
ast: response.ast,
|
|
376
|
-
checksum: response.checksum,
|
|
377
|
-
schemaVersion: response.schemaVersion,
|
|
378
|
-
generatedAt: response.generatedAt,
|
|
379
|
-
etag: response.etag ?? response.checksum ?? etag
|
|
380
|
-
});
|
|
381
|
-
});
|
|
382
|
-
});
|
|
383
|
-
};
|
|
384
|
-
const resolveOutputPaths = (out)=>{
|
|
385
|
-
const isFile = node_path.extname(out).length > 0;
|
|
386
|
-
const outDir = isFile ? node_path.dirname(out) : out;
|
|
387
|
-
const astFile = node_path.join(outDir, 'ast.ts');
|
|
388
|
-
const clientFile = isFile ? out : node_path.join(outDir, 'client.ts');
|
|
389
|
-
const checksumFile = node_path.join(outDir, '.livon.client.checksum');
|
|
390
|
-
return {
|
|
391
|
-
outDir,
|
|
392
|
-
astFile,
|
|
393
|
-
clientFile,
|
|
394
|
-
checksumFile
|
|
395
|
-
};
|
|
396
|
-
};
|
|
397
|
-
const readCachedChecksum = async (checksumFile)=>{
|
|
398
|
-
const raw = (await promises.readFile(checksumFile, 'utf8').catch(()=>'')).trim();
|
|
399
|
-
if (!raw) return {};
|
|
400
|
-
if (raw.startsWith('{')) try {
|
|
401
|
-
const parsed = JSON.parse(raw);
|
|
402
|
-
const generatorHash = 'string' == typeof parsed.generatorHash ? parsed.generatorHash : void 0;
|
|
403
|
-
const etag = 'string' == typeof parsed.etag ? parsed.etag : void 0;
|
|
404
|
-
return {
|
|
405
|
-
generatorHash,
|
|
406
|
-
etag
|
|
407
|
-
};
|
|
408
|
-
} catch {}
|
|
409
|
-
const legacyVersionSeparator = raw.indexOf(':');
|
|
410
|
-
if (raw.startsWith('client-generator-') && legacyVersionSeparator > 0) {
|
|
411
|
-
const etag = raw.slice(legacyVersionSeparator + 1).trim();
|
|
412
|
-
return {
|
|
413
|
-
etag: etag || void 0
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
|
-
return {
|
|
417
|
-
etag: raw
|
|
418
|
-
};
|
|
419
|
-
};
|
|
420
|
-
const writeClientFiles = async (ast, options, meta)=>{
|
|
421
|
-
const { outDir, astFile, clientFile, checksumFile } = resolveOutputPaths(options.out);
|
|
422
|
-
await promises.mkdir(outDir, {
|
|
423
|
-
recursive: true
|
|
424
|
-
});
|
|
425
|
-
const previous = await readCachedChecksum(checksumFile);
|
|
426
|
-
const checksum = meta?.checksum ?? hashAst(ast);
|
|
427
|
-
const etagBase = (meta?.etag ?? checksum).trim();
|
|
428
|
-
const hasSameGenerator = previous.generatorHash === CLIENT_GENERATOR_HASH;
|
|
429
|
-
const hasSameEtag = previous.etag === etagBase;
|
|
430
|
-
if (hasSameGenerator && hasSameEtag) return {
|
|
431
|
-
updated: false,
|
|
432
|
-
checksum,
|
|
433
|
-
etag: etagBase,
|
|
434
|
-
schemaVersion: meta?.schemaVersion,
|
|
435
|
-
generatedAt: meta?.generatedAt
|
|
436
|
-
};
|
|
437
|
-
const generated = generateClientFiles({
|
|
438
|
-
ast: ast
|
|
439
|
-
});
|
|
440
|
-
const astSource = generated.files[generated.astFile];
|
|
441
|
-
const clientSource = generated.files[generated.clientFile];
|
|
442
|
-
if (!astSource || !clientSource) throw new Error('Generated client sources were empty.');
|
|
443
|
-
await promises.writeFile(astFile, astSource, 'utf8');
|
|
444
|
-
await promises.writeFile(clientFile, clientSource, 'utf8');
|
|
445
|
-
await promises.writeFile(checksumFile, JSON.stringify({
|
|
446
|
-
generatorHash: CLIENT_GENERATOR_HASH,
|
|
447
|
-
etag: etagBase
|
|
448
|
-
}), 'utf8');
|
|
449
|
-
return {
|
|
450
|
-
updated: true,
|
|
451
|
-
checksum,
|
|
452
|
-
etag: etagBase,
|
|
453
|
-
schemaVersion: meta?.schemaVersion,
|
|
454
|
-
generatedAt: meta?.generatedAt
|
|
455
|
-
};
|
|
456
|
-
};
|
|
457
|
-
const startCommandRuntime = ({ command })=>{
|
|
458
|
-
const [commandName, ...commandArgs] = command;
|
|
459
|
-
if (!commandName) throw new Error('Missing command to run after livon sync.');
|
|
460
|
-
const child = spawn(commandName, commandArgs, {
|
|
461
|
-
stdio: 'inherit',
|
|
462
|
-
env: process.env
|
|
463
|
-
});
|
|
464
|
-
const stopChild = ()=>{
|
|
465
|
-
if (child.killed) return;
|
|
466
|
-
child.kill('SIGTERM');
|
|
467
|
-
};
|
|
468
|
-
process.on('exit', stopChild);
|
|
469
|
-
process.on('SIGINT', ()=>{
|
|
470
|
-
stopChild();
|
|
471
|
-
process.exit(130);
|
|
472
|
-
});
|
|
473
|
-
process.on('SIGTERM', ()=>{
|
|
474
|
-
stopChild();
|
|
475
|
-
process.exit(143);
|
|
476
|
-
});
|
|
477
|
-
const waitForExit = new Promise((resolve, reject)=>{
|
|
478
|
-
child.on('error', (error)=>{
|
|
479
|
-
reject(error);
|
|
480
|
-
});
|
|
481
|
-
child.on('exit', (code, signal)=>{
|
|
482
|
-
const exitCode = 'number' == typeof code ? code : signal ? 1 : 0;
|
|
483
|
-
if (0 !== exitCode) console.error(`livon: linked command exited with code ${exitCode}`);
|
|
484
|
-
resolve(exitCode);
|
|
485
|
-
process.exit(exitCode);
|
|
486
|
-
});
|
|
487
|
-
});
|
|
488
|
-
return {
|
|
489
|
-
waitForExit
|
|
490
|
-
};
|
|
491
|
-
};
|
|
492
|
-
const run = async ()=>{
|
|
493
|
-
const cli = readCliInput(process.argv.slice(2));
|
|
494
|
-
const options = cli.options;
|
|
495
|
-
const commandRuntimeInput = cli.command.length > 0 ? {
|
|
496
|
-
command: cli.command
|
|
497
|
-
} : void 0;
|
|
498
|
-
let commandRuntime;
|
|
499
|
-
const ensureCommandRuntime = ()=>{
|
|
500
|
-
if (!commandRuntimeInput || commandRuntime) return;
|
|
501
|
-
commandRuntime = startCommandRuntime(commandRuntimeInput);
|
|
502
|
-
};
|
|
503
|
-
const execute = async ()=>{
|
|
504
|
-
const { checksumFile } = resolveOutputPaths(options.out);
|
|
505
|
-
const cached = await readCachedChecksum(checksumFile);
|
|
506
|
-
const useCachedEtag = cached.generatorHash === CLIENT_GENERATOR_HASH;
|
|
507
|
-
const cachedEtag = useCachedEtag ? cached.etag : void 0;
|
|
508
|
-
const result = await fetchAst(options, cachedEtag);
|
|
509
|
-
if (result.notModified) return;
|
|
510
|
-
if (void 0 === result.ast) throw new Error('Explain response missing AST.');
|
|
511
|
-
const writeResult = await writeClientFiles(result.ast, options, {
|
|
512
|
-
checksum: result.checksum,
|
|
513
|
-
etag: result.etag,
|
|
514
|
-
schemaVersion: result.schemaVersion,
|
|
515
|
-
generatedAt: result.generatedAt
|
|
516
|
-
});
|
|
517
|
-
if (writeResult.updated) {
|
|
518
|
-
const meta = [];
|
|
519
|
-
if (writeResult.schemaVersion) meta.push(`schema ${writeResult.schemaVersion}`);
|
|
520
|
-
if (writeResult.generatedAt) meta.push(`generated ${writeResult.generatedAt}`);
|
|
521
|
-
const metaInfo = meta.length > 0 ? `, ${meta.join(', ')}` : '';
|
|
522
|
-
console.log(`livon: client updated (checksum ${writeResult.checksum}${metaInfo})`);
|
|
523
|
-
}
|
|
524
|
-
};
|
|
525
|
-
const withRetry = async (action)=>{
|
|
526
|
-
const maxAttempts = 20;
|
|
527
|
-
const baseDelay = 250;
|
|
528
|
-
const runAttempt = async (attempt, resetApplied)=>{
|
|
529
|
-
try {
|
|
530
|
-
await action();
|
|
531
|
-
return;
|
|
532
|
-
} catch (error) {
|
|
533
|
-
const retryAware = error;
|
|
534
|
-
const shouldReset = Boolean(retryAware?.[RETRY_RESET_AFTER_CONNECTION]) && !resetApplied;
|
|
535
|
-
const nextAttempt = shouldReset ? 1 : attempt + 1;
|
|
536
|
-
const nextResetApplied = shouldReset ? true : resetApplied;
|
|
537
|
-
if (nextAttempt >= maxAttempts) throw new Error('livon: giving up after repeated retries');
|
|
538
|
-
const wait = baseDelay * Math.min(nextAttempt, 10);
|
|
539
|
-
console.warn(`livon: attempt ${nextAttempt}/${maxAttempts} failed: ${error instanceof Error ? error.message : String(error)} – retrying in ${wait}ms`);
|
|
540
|
-
await new Promise((resolve)=>setTimeout(resolve, wait));
|
|
541
|
-
await runAttempt(nextAttempt, nextResetApplied);
|
|
542
|
-
}
|
|
543
|
-
};
|
|
544
|
-
await runAttempt(0, false);
|
|
545
|
-
};
|
|
546
|
-
if (options.poll && options.poll > 0) {
|
|
547
|
-
let inFlight = false;
|
|
548
|
-
const tick = async ()=>{
|
|
549
|
-
if (inFlight) return;
|
|
550
|
-
inFlight = true;
|
|
551
|
-
try {
|
|
552
|
-
await withRetry(execute);
|
|
553
|
-
ensureCommandRuntime();
|
|
554
|
-
} catch (error) {
|
|
555
|
-
console.error('livon: poll error', error);
|
|
556
|
-
} finally{
|
|
557
|
-
inFlight = false;
|
|
558
|
-
setTimeout(tick, options.poll);
|
|
559
|
-
}
|
|
560
|
-
};
|
|
561
|
-
await tick();
|
|
562
|
-
return;
|
|
563
|
-
}
|
|
564
|
-
await withRetry(execute);
|
|
565
|
-
ensureCommandRuntime();
|
|
566
|
-
if (commandRuntime) {
|
|
567
|
-
const commandExitCode = await commandRuntime.waitForExit;
|
|
568
|
-
process.exit(commandExitCode);
|
|
569
|
-
}
|
|
570
|
-
};
|
|
571
|
-
run().catch((error)=>{
|
|
572
|
-
console.error(error);
|
|
573
|
-
process.exit(1);
|
|
574
|
-
});
|
|
1
|
+
import{generateClientFiles as e,getClientGeneratorFingerprint as t}from"@livon/client/generate";import{spawn as r}from"node:child_process";import{createHash as i}from"node:crypto";import{promises as o}from"node:fs";import n from"node:path";import a from"ws";import{pack as s,unpack as l}from"msgpackr";let d="livon.retry.reset_after_connection",c=({argv:e,index:t,arg:r})=>{if(r.includes("="))return{nextIndex:t+1,value:r.split("=").slice(1).join("=")};let i=e[t+1];return!i||i.startsWith("-")?{nextIndex:t+1,value:void 0}:{nextIndex:t+2,value:i}},p=({argv:e,index:t,options:r})=>{let i=e[t];if(!i)return{options:r,command:[]};if("--"===i)return{options:r,command:e.slice(t+1)};if(!i.startsWith("-"))return{options:r,command:e.slice(t)};if("--no-event"===i)return p({argv:e,index:t+1,options:{...r,event:void 0,method:"GET"}});if(i.startsWith("--endpoint")){let{value:o,nextIndex:n}=c({argv:e,index:t,arg:i});return p({argv:e,index:n,options:{...r,endpoint:o??""}})}if(i.startsWith("--out")){let{value:o,nextIndex:n}=c({argv:e,index:t,arg:i});return p({argv:e,index:n,options:{...r,out:o??""}})}if(i.startsWith("--poll")){let{value:o,nextIndex:n}=c({argv:e,index:t,arg:i});return p({argv:e,index:n,options:{...r,poll:o?Number(o):void 0}})}if(i.startsWith("--timeout")){let{value:o,nextIndex:n}=c({argv:e,index:t,arg:i});if(o){let t=Number(o);if(!Number.isFinite(t)||t<=0)throw Error(`Invalid --timeout value: ${o}`);return p({argv:e,index:n,options:{...r,timeout:t}})}return p({argv:e,index:n,options:r})}if(i.startsWith("--port")){let{value:o,nextIndex:n}=c({argv:e,index:t,arg:i});if(o){let t=Number(o);if(!Number.isFinite(t)||t<=0)throw Error(`Invalid --port value: ${o}`);return p({argv:e,index:n,options:{...r,port:t}})}return p({argv:e,index:n,options:r})}if(i.startsWith("--event")){let{value:o,nextIndex:n}=c({argv:e,index:t,arg:i});return p({argv:e,index:n,options:{...r,event:o,method:"POST"}})}if(i.startsWith("--method")){let{value:o,nextIndex:n}=c({argv:e,index:t,arg:i});return p({argv:e,index:n,options:{...r,method:o&&"GET"===o.toUpperCase()?"GET":"POST"}})}if(i.startsWith("--header")){let{value:o,nextIndex:n}=c({argv:e,index:t,arg:i});if(o){let[t,...i]=o.split(":");if(t&&i.length>0)return p({argv:e,index:n,options:{...r,headers:{...r.headers,[t.trim()]:i.join(":").trim()}}})}return p({argv:e,index:n,options:r})}if(i.startsWith("--payload")){let{value:o,nextIndex:n}=c({argv:e,index:t,arg:i});if(o)try{return p({argv:e,index:n,options:{...r,payload:JSON.parse(o)}})}catch(e){throw Error(`Invalid JSON for --payload: ${e instanceof Error?e.message:String(e)}`)}return p({argv:e,index:n,options:r})}return p({argv:e,index:t+1,options:r})},m=e=>e?l(e):void 0,u=t(),f=async(e,t)=>{let r=e.endpoint.trim();if(!r.startsWith("ws://")&&!r.startsWith("wss://"))throw Error("Endpoint must be ws:// or wss:// for $explain.");return new Promise((i,o)=>{let n=new a(r,{headers:e.headers}),c=!1,p=!1,u=setTimeout(()=>{c||(c=!0,n.close(),o(Error("Timed out waiting for $explain response.")))},e.timeout??3e4),f=e=>{c||(c=!0,clearTimeout(u),n.close(),i(e))},h=e=>{c||(c=!0,clearTimeout(u),n.close(),p&&(e[d]=!0),o(e))};n.on("error",e=>{h(e)}),n.on("open",()=>{var r;let i,o;p=!0;let a=(i=(e=>{if(e)return Object.keys(e).length>0?e:void 0})((r={event:(e=>{if(!e)throw Error("Missing required --event for websocket mode.");return e})(e.event),payload:e.payload??null,metadata:t?{ifNoneMatch:t}:void 0}).metadata),o=(e=>{if(e&&0!==Object.keys(e).length)return e})(r.context),{...{event:r.event,metadata:i,context:o?s(o):void 0},payload:s(r.payload)});n.send(s(a))}),n.on("message",r=>{let i,o;try{i=l((e=>{if(Array.isArray(e))return new Uint8Array(Buffer.concat(e));if("string"==typeof e)throw Error("Expected binary WebSocket payload.");return e instanceof ArrayBuffer||Buffer.isBuffer(e)?new Uint8Array(e):ArrayBuffer.isView(e)?new Uint8Array(e.buffer,e.byteOffset,e.byteLength):new Uint8Array(Buffer.from(e))})(r))}catch{return}if(!("object"==typeof(o=i)&&null!==o&&!Array.isArray(o)))return;let n=i;if(n.event&&n.event!==e.event)return;if(n.error){let e=m(n.error);h(Error(e&&"object"==typeof e&&"message"in e?String(e.message??"Explain error"):"Explain error"));return}let a=n.payload?m(n.payload):i;!a||"object"!=typeof a||(a.notModified?f({notModified:!0,etag:a.etag??t}):"ast"in a&&f({ast:a.ast,checksum:a.checksum,schemaVersion:a.schemaVersion,generatedAt:a.generatedAt,etag:a.etag??a.checksum??t}))})})},h=e=>{let t=n.extname(e).length>0,r=t?n.dirname(e):e,i=n.join(r,"ast.ts"),o=t?e:n.join(r,"client.ts"),a=n.join(r,".livon.client.checksum");return{outDir:r,astFile:i,clientFile:o,checksumFile:a}},g=async e=>{let t=(await o.readFile(e,"utf8").catch(()=>"")).trim();if(!t)return{};if(t.startsWith("{"))try{let e=JSON.parse(t),r="string"==typeof e.generatorHash?e.generatorHash:void 0,i="string"==typeof e.etag?e.etag:void 0;return{generatorHash:r,etag:i}}catch{}let r=t.indexOf(":");return t.startsWith("client-generator-")&&r>0?{etag:t.slice(r+1).trim()||void 0}:{etag:t}},w=async(t,r,n)=>{let{outDir:a,astFile:s,clientFile:l,checksumFile:d}=h(r.out);await o.mkdir(a,{recursive:!0});let c=await g(d),p=n?.checksum??i("sha256").update(JSON.stringify(t)).digest("hex"),m=(n?.etag??p).trim(),f=c.generatorHash===u,w=c.etag===m;if(f&&w)return{updated:!1,checksum:p,etag:m,schemaVersion:n?.schemaVersion,generatedAt:n?.generatedAt};let v=e({ast:t}),y=v.files[v.astFile],x=v.files[v.clientFile];if(!y||!x)throw Error("Generated client sources were empty.");return await o.writeFile(s,y,"utf8"),await o.writeFile(l,x,"utf8"),await o.writeFile(d,JSON.stringify({generatorHash:u,etag:m}),"utf8"),{updated:!0,checksum:p,etag:m,schemaVersion:n?.schemaVersion,generatedAt:n?.generatedAt}};(async()=>{let e,t=(e=>{let t=p({argv:e,index:0,options:{endpoint:"",port:void 0,out:"",poll:void 0,timeout:void 0,event:"$explain",method:"POST",headers:{},payload:void 0}}),r={...t.options};if(!r.endpoint&&r.port&&(r.endpoint=`ws://127.0.0.1:${r.port}/ws`),!r.endpoint)throw Error("Missing required --endpoint or --port");if(r.port&&(r.endpoint=((e,t)=>{let r=new URL(e);if("ws:"!==r.protocol&&"wss:"!==r.protocol)throw Error("Endpoint must be ws:// or wss:// for websocket mode.");return r.port=String(t),r.pathname&&"/"!==r.pathname||(r.pathname="/ws"),r.toString()})(r.endpoint,r.port)),!r.out)throw Error("Missing required --out");if(void 0===r.event)throw Error("Missing required --event for websocket mode.");return{options:r,command:t.command}})(process.argv.slice(2)),i=t.options,o=t.command.length>0?{command:t.command}:void 0,n=()=>{o&&!e&&(e=(({command:e})=>{let[t,...i]=e;if(!t)throw Error("Missing command to run after livon sync.");let o=r(t,i,{stdio:"inherit",env:process.env}),n=()=>{o.killed||o.kill("SIGTERM")};return process.on("exit",n),process.on("SIGINT",()=>{n(),process.exit(130)}),process.on("SIGTERM",()=>{n(),process.exit(143)}),{waitForExit:new Promise((e,t)=>{o.on("error",e=>{t(e)}),o.on("exit",(t,r)=>{let i="number"==typeof t?t:+!!r;0!==i&&console.error(`livon: linked command exited with code ${i}`),e(i),process.exit(i)})})}})(o))},a=async()=>{let{checksumFile:e}=h(i.out),t=await g(e),r=t.generatorHash===u?t.etag:void 0,o=await f(i,r);if(o.notModified)return;if(void 0===o.ast)throw Error("Explain response missing AST.");let n=await w(o.ast,i,{checksum:o.checksum,etag:o.etag,schemaVersion:o.schemaVersion,generatedAt:o.generatedAt});if(n.updated){let e=[];n.schemaVersion&&e.push(`schema ${n.schemaVersion}`),n.generatedAt&&e.push(`generated ${n.generatedAt}`);let t=e.length>0?`, ${e.join(", ")}`:"";console.log(`livon: client updated (checksum ${n.checksum}${t})`)}},s=async e=>{let t=async(r,i)=>{try{await e();return}catch(a){let e=!!a?.[d]&&!i,o=e?1:r+1;if(o>=20)throw Error("livon: giving up after repeated retries");let n=250*Math.min(o,10);console.warn(`livon: attempt ${o}/20 failed: ${a instanceof Error?a.message:String(a)} – retrying in ${n}ms`),await new Promise(e=>setTimeout(e,n)),await t(o,!!e||i)}};await t(0,!1)};if(i.poll&&i.poll>0){let e=!1,t=async()=>{if(!e){e=!0;try{await s(a),n()}catch(e){console.error("livon: poll error",e)}finally{e=!1,setTimeout(t,i.poll)}}};await t();return}if(await s(a),n(),e){let t=await e.waitForExit;process.exit(t)}})().catch(e=>{console.error(e),process.exit(1)});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livon/cli",
|
|
3
|
-
"version": "0.27.0-rc.
|
|
3
|
+
"version": "0.27.0-rc.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"msgpackr": "latest",
|
|
19
19
|
"ws": "latest",
|
|
20
|
-
"@livon/client": "0.27.0-rc.
|
|
21
|
-
"@livon/config": "0.27.0-rc.
|
|
20
|
+
"@livon/client": "0.27.0-rc.2",
|
|
21
|
+
"@livon/config": "0.27.0-rc.2"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@rslib/core": "latest",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"@types/node": "latest",
|
|
29
29
|
"eslint": "9.0.0",
|
|
30
30
|
"typescript": "latest",
|
|
31
|
-
"@livon/config": "0.27.0-rc.
|
|
31
|
+
"@livon/config": "0.27.0-rc.2"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
34
|
"build": "rslib build",
|