@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 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 { generateClientFiles, getClientGeneratorFingerprint } from "@livon/client/generate";
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.1",
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.1",
21
- "@livon/config": "0.27.0-rc.1"
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.1"
31
+ "@livon/config": "0.27.0-rc.2"
32
32
  },
33
33
  "scripts": {
34
34
  "build": "rslib build",