@coze-arch/cli 0.0.3 → 0.0.5

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/lib/cli.js CHANGED
@@ -4,6 +4,9 @@
4
4
  var commander = require('commander');
5
5
  var path = require('path');
6
6
  var fs = require('fs');
7
+ var debug = require('debug');
8
+ var node_http = require('node:http');
9
+ var node_https = require('node:https');
7
10
  var node_path = require('node:path');
8
11
  var node_fs = require('node:fs');
9
12
  var shelljs = require('shelljs');
@@ -129,6 +132,2214 @@ const generateTemplatesHelpText = () => {
129
132
  return lines.join('\n');
130
133
  };
131
134
 
135
+ var DEFAULT_SIZE = 10;
136
+ var DEFAULT_WAIT = 1000;
137
+ var stringifyBatch = function (list) {
138
+ return JSON.stringify({
139
+ ev_type: 'batch',
140
+ list: list,
141
+ });
142
+ };
143
+ function createBatchSender(config) {
144
+ var transport = config.transport;
145
+ var endpoint = config.endpoint, _a = config.size, size = _a === void 0 ? DEFAULT_SIZE : _a, _b = config.wait, wait = _b === void 0 ? DEFAULT_WAIT : _b;
146
+ var batch = [];
147
+ var tid = 0;
148
+ var fail;
149
+ var success;
150
+ var sender = {
151
+ getSize: function () {
152
+ return size;
153
+ },
154
+ getWait: function () {
155
+ return wait;
156
+ },
157
+ setSize: function (v) {
158
+ size = v;
159
+ },
160
+ setWait: function (v) {
161
+ wait = v;
162
+ },
163
+ getEndpoint: function () {
164
+ return endpoint;
165
+ },
166
+ setEndpoint: function (v) {
167
+ endpoint = v;
168
+ },
169
+ send: function (e) {
170
+ batch.push(e);
171
+ if (batch.length >= size) {
172
+ sendBatch.call(this);
173
+ }
174
+ clearTimeout(tid);
175
+ tid = setTimeout(sendBatch.bind(this), wait);
176
+ },
177
+ flush: function () {
178
+ clearTimeout(tid);
179
+ sendBatch.call(this);
180
+ },
181
+ getBatchData: function () {
182
+ return batch.length ? stringifyBatch(batch) : '';
183
+ },
184
+ clear: function () {
185
+ clearTimeout(tid);
186
+ batch = [];
187
+ },
188
+ fail: function (cb) {
189
+ fail = cb;
190
+ },
191
+ success: function (cb) {
192
+ success = cb;
193
+ },
194
+ };
195
+ function sendBatch() {
196
+ if (!batch.length) {
197
+ return;
198
+ }
199
+ var data = this.getBatchData();
200
+ transport.post({
201
+ url: endpoint,
202
+ data: data,
203
+ fail: function (err) {
204
+ fail && fail(err, data);
205
+ },
206
+ success: function () {
207
+ success && success(data);
208
+ },
209
+ });
210
+ batch = [];
211
+ }
212
+ return sender;
213
+ }
214
+
215
+ /*! *****************************************************************************
216
+ Copyright (c) Microsoft Corporation.
217
+
218
+ Permission to use, copy, modify, and/or distribute this software for any
219
+ purpose with or without fee is hereby granted.
220
+
221
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
222
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
223
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
224
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
225
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
226
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
227
+ PERFORMANCE OF THIS SOFTWARE.
228
+ ***************************************************************************** */
229
+
230
+ var __assign = function() {
231
+ __assign = Object.assign || function __assign(t) {
232
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
233
+ s = arguments[i];
234
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
235
+ }
236
+ return t;
237
+ };
238
+ return __assign.apply(this, arguments);
239
+ };
240
+
241
+ function __values(o) {
242
+ var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
243
+ if (m) return m.call(o);
244
+ if (o && typeof o.length === "number") return {
245
+ next: function () {
246
+ if (o && i >= o.length) o = void 0;
247
+ return { value: o && o[i++], done: !o };
248
+ }
249
+ };
250
+ throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
251
+ }
252
+
253
+ function __read(o, n) {
254
+ var m = typeof Symbol === "function" && o[Symbol.iterator];
255
+ if (!m) return o;
256
+ var i = m.call(o), r, ar = [], e;
257
+ try {
258
+ while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
259
+ }
260
+ catch (error) { e = { error: error }; }
261
+ finally {
262
+ try {
263
+ if (r && !r.done && (m = i["return"])) m.call(i);
264
+ }
265
+ finally { if (e) throw e.error; }
266
+ }
267
+ return ar;
268
+ }
269
+
270
+ function __spreadArray(to, from, pack) {
271
+ if (arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
272
+ if (ar || !(i in from)) {
273
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
274
+ ar[i] = from[i];
275
+ }
276
+ }
277
+ return to.concat(ar || Array.prototype.slice.call(from));
278
+ }
279
+
280
+ var noop$1 = function () { return ({}); };
281
+ function id(v) {
282
+ return v;
283
+ }
284
+
285
+ // eslint-disable-next-line @typescript-eslint/ban-types
286
+ function isObject(o) {
287
+ return typeof o === 'object' && o !== null;
288
+ }
289
+ var objProto = Object.prototype;
290
+ function isArray(o) {
291
+ return objProto.toString.call(o) === '[object Array]';
292
+ }
293
+ function isBoolean(o) {
294
+ return typeof o === 'boolean';
295
+ }
296
+ function isNumber(o) {
297
+ return typeof o === 'number';
298
+ }
299
+ function isString(o) {
300
+ return typeof o === 'string';
301
+ }
302
+
303
+ // 检查数组中是否有元素
304
+ function arrayIncludes(array, value) {
305
+ if (!isArray(array)) {
306
+ return false;
307
+ }
308
+ if (array.length === 0) {
309
+ return false;
310
+ }
311
+ var k = 0;
312
+ while (k < array.length) {
313
+ if (array[k] === value) {
314
+ return true;
315
+ }
316
+ k++;
317
+ }
318
+ return false;
319
+ }
320
+ var arrayRemove = function (arr, e) {
321
+ if (!isArray(arr)) {
322
+ return arr;
323
+ }
324
+ var i = arr.indexOf(e);
325
+ if (i >= 0) {
326
+ var arr_ = arr.slice();
327
+ arr_.splice(i, 1);
328
+ return arr_;
329
+ }
330
+ return arr;
331
+ };
332
+ /**
333
+ * 按路径访问对象属性
334
+ * @param target 待访问对象
335
+ * @param property 访问属性路径
336
+ * @param { (target: any, property: string): any } visitor 访问器
337
+ */
338
+ var safeVisit = function (target, path, visitor) {
339
+ var _a, _b;
340
+ var paths = path.split('.');
341
+ var _c = __read(paths), method = _c[0], rest = _c.slice(1);
342
+ while (target && rest.length > 0) {
343
+ target = target[method];
344
+ _a = rest, _b = __read(_a), method = _b[0], rest = _b.slice(1);
345
+ }
346
+ if (!target) {
347
+ return undefined;
348
+ }
349
+ return visitor(target, method);
350
+ };
351
+
352
+ function safeStringify(a) {
353
+ try {
354
+ return isString(a) ? a : JSON.stringify(a);
355
+ }
356
+ catch (err) {
357
+ return '[FAILED_TO_STRINGIFY]:' + String(err);
358
+ }
359
+ }
360
+
361
+ function createContextAgent() {
362
+ var context = {};
363
+ var stringified = {};
364
+ var contextAgent = {
365
+ set: function (k, v) {
366
+ context[k] = v;
367
+ stringified[k] = safeStringify(v);
368
+ return contextAgent;
369
+ },
370
+ merge: function (ctx) {
371
+ context = __assign(__assign({}, context), ctx);
372
+ Object.keys(ctx).forEach(function (key) {
373
+ stringified[key] = safeStringify(ctx[key]);
374
+ });
375
+ return contextAgent;
376
+ },
377
+ delete: function (k) {
378
+ delete context[k];
379
+ delete stringified[k];
380
+ return contextAgent;
381
+ },
382
+ clear: function () {
383
+ context = {};
384
+ stringified = {};
385
+ return contextAgent;
386
+ },
387
+ get: function (k) {
388
+ return stringified[k];
389
+ },
390
+ toString: function () {
391
+ return __assign({}, stringified);
392
+ },
393
+ };
394
+ return contextAgent;
395
+ }
396
+
397
+ var getPrintString = function () {
398
+ // @ts-expect-error
399
+ if (''.padStart) {
400
+ return function (str, prefixLength) {
401
+ if (prefixLength === void 0) { prefixLength = 8; }
402
+ return str.padStart(prefixLength, ' ');
403
+ };
404
+ }
405
+ return function (str) { return str; };
406
+ };
407
+ var printString = getPrintString();
408
+ var errCount = 0;
409
+ var error = function () {
410
+ var args = [];
411
+ for (var _i = 0; _i < arguments.length; _i++) {
412
+ args[_i] = arguments[_i];
413
+ }
414
+ // eslint-disable-next-line no-console
415
+ console.error.apply(console, __spreadArray(['[SDK]', Date.now(), printString("" + errCount++)], __read(args), false));
416
+ };
417
+ var warnCount = 0;
418
+ var warn = function () {
419
+ var args = [];
420
+ for (var _i = 0; _i < arguments.length; _i++) {
421
+ args[_i] = arguments[_i];
422
+ }
423
+ // eslint-disable-next-line no-console
424
+ console.warn.apply(console, __spreadArray(['[SDK]', Date.now(), printString("" + warnCount++)], __read(args), false));
425
+ };
426
+
427
+ var isHitBySampleRate = function (sampleRate) {
428
+ if (Math.random() < Number(sampleRate)) {
429
+ return true;
430
+ }
431
+ return false;
432
+ };
433
+ var isHitByRandom = function (random, sampleRate) {
434
+ if (random < Number(sampleRate)) {
435
+ return true;
436
+ }
437
+ return false;
438
+ };
439
+
440
+ var runProcessors = function (fns) {
441
+ return function (e) {
442
+ var r = e;
443
+ for (var i = 0; i < fns.length; i++) {
444
+ if (r) {
445
+ try {
446
+ r = fns[i](r);
447
+ }
448
+ catch (err) {
449
+ error(err);
450
+ }
451
+ }
452
+ else {
453
+ break;
454
+ }
455
+ }
456
+ return r;
457
+ };
458
+ };
459
+
460
+ /**
461
+ * 生成uuid
462
+ * stolen from https://github.com/kelektiv/node-uuid#readme uuid/v4
463
+ *
464
+ * @returns
465
+ */
466
+ function mathRNG() {
467
+ var rnds = new Array(16);
468
+ var r = 0;
469
+ for (var i = 0; i < 16; i++) {
470
+ if ((i & 0x03) === 0) {
471
+ r = Math.random() * 0x100000000;
472
+ }
473
+ rnds[i] = (r >>> ((i & 0x03) << 3)) & 0xff;
474
+ }
475
+ return rnds;
476
+ }
477
+ function bytesToUuid(buf) {
478
+ var byteToHex = [];
479
+ for (var index = 0; index < 256; ++index) {
480
+ byteToHex[index] = (index + 0x100).toString(16).substr(1);
481
+ }
482
+ var i = 0;
483
+ var bth = byteToHex;
484
+ // join used to fix memory issue caused by concatenation: https://bugs.chromium.org/p/v8/issues/detail?id=3175#c4
485
+ return [
486
+ bth[buf[i++]],
487
+ bth[buf[i++]],
488
+ bth[buf[i++]],
489
+ bth[buf[i++]],
490
+ '-',
491
+ bth[buf[i++]],
492
+ bth[buf[i++]],
493
+ '-',
494
+ bth[buf[i++]],
495
+ bth[buf[i++]],
496
+ '-',
497
+ bth[buf[i++]],
498
+ bth[buf[i++]],
499
+ '-',
500
+ bth[buf[i++]],
501
+ bth[buf[i++]],
502
+ bth[buf[i++]],
503
+ bth[buf[i++]],
504
+ bth[buf[i++]],
505
+ bth[buf[i++]],
506
+ ].join('');
507
+ }
508
+ function uuid() {
509
+ var rnds = mathRNG();
510
+ // Per 4.4, set bits for version and `clock_seq_hi_and_reserved`
511
+ rnds[6] = (rnds[6] & 0x0f) | 0x40;
512
+ rnds[8] = (rnds[8] & 0x3f) | 0x80;
513
+ return bytesToUuid(rnds);
514
+ }
515
+
516
+ function createDestroyAgent() {
517
+ var destroyed = false;
518
+ var data = {};
519
+ var removeTearDownGroup = function (tearDownGroup) {
520
+ tearDownGroup.length &&
521
+ tearDownGroup.forEach(function (v) {
522
+ try {
523
+ v();
524
+ // eslint-disable-next-line no-empty
525
+ }
526
+ catch (_a) { }
527
+ });
528
+ tearDownGroup.length = 0;
529
+ };
530
+ var removeByPluginName = function (pluginName) {
531
+ data[pluginName] &&
532
+ data[pluginName].forEach(function (e) {
533
+ removeTearDownGroup(e[1]);
534
+ });
535
+ data[pluginName] = undefined;
536
+ };
537
+ var removeByEvType = function (evType) {
538
+ Object.keys(data).forEach(function (k) {
539
+ data[k] &&
540
+ data[k].forEach(function (e) {
541
+ if (e[0] === evType) {
542
+ removeTearDownGroup(e[1]);
543
+ }
544
+ });
545
+ });
546
+ };
547
+ return {
548
+ /**
549
+ * register tearDownGroup for a single plugin.
550
+ */
551
+ set: function (pluginName, evType, tearDownGroup) {
552
+ if (data[pluginName])
553
+ data[pluginName].push([evType, tearDownGroup]);
554
+ else
555
+ data[pluginName] = [[evType, tearDownGroup]];
556
+ // auto remove tearDownGroup if destroyed
557
+ destroyed && removeTearDownGroup(tearDownGroup);
558
+ },
559
+ has: function (pluginName) {
560
+ return !!data[pluginName];
561
+ },
562
+ /**
563
+ * remove tearDownGroup for a single plugin.
564
+ */
565
+ remove: removeByPluginName,
566
+ /**
567
+ * remove tearDownGroup by event type
568
+ */
569
+ removeByEvType: removeByEvType,
570
+ /**
571
+ * clear all tearDownGroup
572
+ */
573
+ clear: function () {
574
+ destroyed = true;
575
+ Object.keys(data).forEach(function (k) {
576
+ removeByPluginName(k);
577
+ });
578
+ },
579
+ };
580
+ }
581
+
582
+ // max size of preStartQueue
583
+ var PRESTART_QUEUE_MAX_SIZE = 500;
584
+ // slice size to operate preStartQueue
585
+ var PRESTART_QUEUE_OP_SLICE_SIZE = 50;
586
+ function cachePreStartData(client, preStartQueue, processed) {
587
+ preStartQueue.push(processed);
588
+ if (preStartQueue.length < PRESTART_QUEUE_MAX_SIZE) {
589
+ return;
590
+ }
591
+ // delete some data to prevent OOM
592
+ var deleteData = preStartQueue.splice(0, PRESTART_QUEUE_OP_SLICE_SIZE);
593
+ if (client.savePreStartDataToDb) {
594
+ void client.savePreStartDataToDb(deleteData);
595
+ }
596
+ }
597
+ function consumePreStartData(client) {
598
+ var preStartQueue = client.getPreStartQueue();
599
+ preStartQueue.forEach(function (e) { return client.build(e); });
600
+ preStartQueue.length = 0;
601
+ }
602
+
603
+ var EVENTS = [
604
+ 'init',
605
+ 'start',
606
+ 'config',
607
+ 'beforeDestroy',
608
+ 'provide',
609
+ 'beforeReport',
610
+ 'report',
611
+ 'beforeBuild',
612
+ 'build',
613
+ 'beforeSend',
614
+ 'send',
615
+ 'beforeConfig',
616
+ ];
617
+
618
+ function createClient(creationConfig) {
619
+ var builder = creationConfig.builder, createSender = creationConfig.createSender, createDefaultConfig = creationConfig.createDefaultConfig, createConfigManager = creationConfig.createConfigManager, userConfigNormalizer = creationConfig.userConfigNormalizer, initConfigNormalizer = creationConfig.initConfigNormalizer, validateInitConfig = creationConfig.validateInitConfig;
620
+ var sender;
621
+ var configManager;
622
+ var handlers = {};
623
+ EVENTS.forEach(function (e) { return (handlers[e] = []); });
624
+ var inited = false;
625
+ var started = false;
626
+ var destroyed = false;
627
+ // 缓存 start 之前 build 的事件
628
+ var preStartQueue = [];
629
+ // 禁止通过 provide 挂载的字段名
630
+ var reservedNames = [];
631
+ var destroyAgent = createDestroyAgent();
632
+ var client = {
633
+ getBuilder: function () { return builder; },
634
+ getSender: function () { return sender; },
635
+ getPreStartQueue: function () { return preStartQueue; },
636
+ init: function (c) {
637
+ if (inited) {
638
+ warn('already inited');
639
+ return;
640
+ }
641
+ if (c && isObject(c) && validateInitConfig(c)) {
642
+ var defaultConfig = createDefaultConfig(c);
643
+ if (!defaultConfig) {
644
+ throw new Error('defaultConfig missing');
645
+ }
646
+ var initConfig = initConfigNormalizer(c);
647
+ configManager = createConfigManager(defaultConfig);
648
+ configManager.setConfig(initConfig);
649
+ configManager.onChange(function () {
650
+ handle('config');
651
+ });
652
+ sender = createSender(configManager.getConfig());
653
+ if (!sender) {
654
+ throw new Error('sender missing');
655
+ }
656
+ inited = true;
657
+ handle('init', true);
658
+ }
659
+ else {
660
+ throw new Error('invalid InitConfig, init failed');
661
+ }
662
+ },
663
+ set: function (c) {
664
+ if (!inited) {
665
+ return;
666
+ }
667
+ if (c && isObject(c)) {
668
+ handle('beforeConfig', false, c);
669
+ configManager === null || configManager === void 0 ? void 0 : configManager.setConfig(c);
670
+ }
671
+ },
672
+ config: function (c) {
673
+ if (!inited) {
674
+ return;
675
+ }
676
+ if (c && isObject(c)) {
677
+ handle('beforeConfig', false, c);
678
+ configManager === null || configManager === void 0 ? void 0 : configManager.setConfig(userConfigNormalizer(c));
679
+ }
680
+ return configManager === null || configManager === void 0 ? void 0 : configManager.getConfig();
681
+ },
682
+ provide: function (name, value) {
683
+ if (arrayIncludes(reservedNames, name)) {
684
+ warn("cannot provide " + name + ", reserved");
685
+ return;
686
+ }
687
+ client[name] = value;
688
+ handle('provide', false, name);
689
+ },
690
+ start: function () {
691
+ if (!inited) {
692
+ return;
693
+ }
694
+ if (started) {
695
+ return;
696
+ }
697
+ configManager === null || configManager === void 0 ? void 0 : configManager.onReady(function () {
698
+ started = true;
699
+ handle('start', true);
700
+ consumePreStartData(client);
701
+ });
702
+ },
703
+ report: function (data) {
704
+ if (!data) {
705
+ return;
706
+ }
707
+ var preReport = runProcessors(handlers['beforeReport'])(data);
708
+ if (!preReport) {
709
+ return;
710
+ }
711
+ var processed = runProcessors(handlers['report'])(preReport);
712
+ if (!processed) {
713
+ return;
714
+ }
715
+ if (started) {
716
+ this.build(processed);
717
+ }
718
+ else {
719
+ cachePreStartData(client, preStartQueue, processed);
720
+ }
721
+ },
722
+ build: function (data) {
723
+ if (!started) {
724
+ return;
725
+ }
726
+ var preBuild = runProcessors(handlers['beforeBuild'])(data);
727
+ if (!preBuild) {
728
+ return;
729
+ }
730
+ var built = builder.build(preBuild);
731
+ if (!built) {
732
+ return;
733
+ }
734
+ var processed = runProcessors(handlers['build'])(built);
735
+ if (!processed) {
736
+ return;
737
+ }
738
+ this.send(processed);
739
+ },
740
+ send: function (data) {
741
+ if (!started) {
742
+ return;
743
+ }
744
+ var processed = runProcessors(handlers['beforeSend'])(data);
745
+ if (processed) {
746
+ sender.send(processed);
747
+ handle('send', false, processed);
748
+ }
749
+ },
750
+ destroy: function () {
751
+ destroyAgent.clear();
752
+ destroyed = true;
753
+ preStartQueue.length = 0;
754
+ handle('beforeDestroy', true);
755
+ },
756
+ on: function (ev, handler) {
757
+ if ((ev === 'init' && inited) || (ev === 'start' && started) || (ev === 'beforeDestroy' && destroyed)) {
758
+ try {
759
+ ;
760
+ handler();
761
+ }
762
+ catch (_err) {
763
+ // ignore
764
+ }
765
+ }
766
+ else if (handlers[ev]) {
767
+ handlers[ev].push(handler);
768
+ }
769
+ },
770
+ off: function (ev, handler) {
771
+ if (handlers[ev])
772
+ handlers[ev] = arrayRemove(handlers[ev], handler);
773
+ },
774
+ destroyAgent: destroyAgent,
775
+ };
776
+ reservedNames = Object.keys(client);
777
+ return client;
778
+ function handle(ev, once) {
779
+ if (once === void 0) { once = false; }
780
+ var args = [];
781
+ for (var _i = 2; _i < arguments.length; _i++) {
782
+ args[_i - 2] = arguments[_i];
783
+ }
784
+ handlers[ev].forEach(function (f) {
785
+ try {
786
+ f.apply(void 0, __spreadArray([], __read(args), false));
787
+ }
788
+ catch (_err) {
789
+ // ignore
790
+ }
791
+ });
792
+ if (once) {
793
+ handlers[ev].length = 0;
794
+ }
795
+ }
796
+ }
797
+
798
+ var ContextPlugin = function (client) {
799
+ var contextAgent = createContextAgent();
800
+ client.provide('context', contextAgent);
801
+ client.on('report', function (ev) {
802
+ if (!ev.extra) {
803
+ ev.extra = {};
804
+ }
805
+ ev.extra.context = contextAgent.toString();
806
+ return ev;
807
+ });
808
+ };
809
+
810
+ function IntegrationPlugin(client, runAfterSetup) {
811
+ client.on('init', function () {
812
+ var nameList = [];
813
+ var applyIntegrations = function (integrations) {
814
+ integrations.forEach(function (integration) {
815
+ var integrationName = integration.name;
816
+ if (!arrayIncludes(nameList, integrationName)) {
817
+ nameList.push(integrationName);
818
+ integration.setup(client);
819
+ client.destroyAgent.set(integrationName, integrationName, [
820
+ function () {
821
+ nameList = arrayRemove(nameList, integrationName);
822
+ integration.tearDown && integration.tearDown();
823
+ },
824
+ ]);
825
+ }
826
+ });
827
+ };
828
+ client.provide('applyIntegrations', applyIntegrations);
829
+ var config = client.config();
830
+ if (config && config.integrations) {
831
+ applyIntegrations(config.integrations);
832
+ }
833
+ });
834
+ }
835
+
836
+ var DevtoolsPlugin = function (client) {
837
+ try {
838
+ if (typeof window === 'object' && isObject(window) && window.__SLARDAR_DEVTOOLS_GLOBAL_HOOK__) {
839
+ window.__SLARDAR_DEVTOOLS_GLOBAL_HOOK__.push(client);
840
+ }
841
+ }
842
+ catch (e) {
843
+ // ignore
844
+ }
845
+ };
846
+
847
+ var now = function () { return Date.now(); };
848
+
849
+ function getDefaultBrowser() {
850
+ if (typeof window === 'object' && isObject(window))
851
+ return window;
852
+ }
853
+
854
+ // 获取全局注册表
855
+ var getGlobalRegistry = function (global) {
856
+ if (!global)
857
+ return;
858
+ if (!global.__SLARDAR_REGISTRY__) {
859
+ global.__SLARDAR_REGISTRY__ = {
860
+ Slardar: {
861
+ plugins: [],
862
+ errors: [],
863
+ subject: {},
864
+ },
865
+ };
866
+ }
867
+ return global.__SLARDAR_REGISTRY__.Slardar;
868
+ };
869
+ var reportSelfError = function () {
870
+ var errorInfo = [];
871
+ for (var _i = 0; _i < arguments.length; _i++) {
872
+ errorInfo[_i] = arguments[_i];
873
+ }
874
+ var registry = getGlobalRegistry(getDefaultBrowser());
875
+ if (!registry)
876
+ return;
877
+ if (!registry.errors) {
878
+ registry.errors = [];
879
+ }
880
+ registry.errors.push(errorInfo);
881
+ };
882
+
883
+ /**
884
+ * Special support in Perfsee for analyzing call stacks of custom events and custom metrics.
885
+ */
886
+ // eslint-disable-next-line
887
+ var getStacksOnPerfsee = function (constructor) {
888
+ var _a;
889
+ // @ts-expect-error
890
+ if (typeof window !== 'object' || !window.__perfsee__) {
891
+ return;
892
+ }
893
+ var obj = {};
894
+ (_a = Error.captureStackTrace) === null || _a === void 0 ? void 0 : _a.call(Error, obj, constructor);
895
+ return obj.stack;
896
+ };
897
+
898
+ var CUSTOM_EV_TYPE = 'custom';
899
+
900
+ var CUSTOM_EVENT_TYPE = 'event';
901
+ var CUSTOM_LOG_TYPE = 'log';
902
+ var normalizeCustomEventData = function (raw) {
903
+ if (!raw || !isObject(raw)) {
904
+ return;
905
+ }
906
+ // name is required
907
+ if (!raw['name'] || !isString(raw['name'])) {
908
+ return;
909
+ }
910
+ var res = {
911
+ name: raw['name'],
912
+ type: CUSTOM_EVENT_TYPE,
913
+ };
914
+ if ('metrics' in raw && isObject(raw['metrics'])) {
915
+ var rMetrics = raw['metrics'];
916
+ var metrics = {};
917
+ for (var k in rMetrics) {
918
+ if (isNumber(rMetrics[k])) {
919
+ metrics[k] = rMetrics[k];
920
+ }
921
+ }
922
+ res.metrics = metrics;
923
+ }
924
+ if ('categories' in raw && isObject(raw['categories'])) {
925
+ var rCategories = raw['categories'];
926
+ var categories = {};
927
+ for (var k in rCategories) {
928
+ categories[k] = safeStringify(rCategories[k]);
929
+ }
930
+ res.categories = categories;
931
+ }
932
+ if ('attached_log' in raw && isString(raw['attached_log'])) {
933
+ res.attached_log = raw['attached_log'];
934
+ }
935
+ return res;
936
+ };
937
+ var normalizeCustomLogData = function (raw) {
938
+ if (!raw || !isObject(raw)) {
939
+ return;
940
+ }
941
+ // content is required
942
+ if (!raw['content'] || !isString(raw['content'])) {
943
+ return;
944
+ }
945
+ var rContent = raw['content'];
946
+ var res = {
947
+ content: safeStringify(rContent),
948
+ type: CUSTOM_LOG_TYPE,
949
+ level: 'info',
950
+ };
951
+ if ('level' in raw) {
952
+ res.level = raw['level'];
953
+ }
954
+ if ('extra' in raw && isObject(raw['extra'])) {
955
+ var rExtra = raw['extra'];
956
+ var metrics = {};
957
+ var categories = {};
958
+ for (var k in rExtra) {
959
+ if (isNumber(rExtra[k])) {
960
+ metrics[k] = rExtra[k];
961
+ }
962
+ else {
963
+ categories[k] = safeStringify(rExtra[k]);
964
+ }
965
+ }
966
+ res.metrics = metrics;
967
+ res.categories = categories;
968
+ }
969
+ if ('attached_log' in raw && isString(raw['attached_log'])) {
970
+ res.attached_log = raw['attached_log'];
971
+ }
972
+ return res;
973
+ };
974
+ var CustomPlugin = function (client) {
975
+ var sendEvent = function (data) {
976
+ var normalized = normalizeCustomEventData(data);
977
+ if (normalized) {
978
+ var stacks = getStacksOnPerfsee(sendEvent);
979
+ if (stacks) {
980
+ // @ts-expect-error
981
+ normalized.stacks = stacks;
982
+ }
983
+ client.report({
984
+ ev_type: CUSTOM_EV_TYPE,
985
+ payload: normalized,
986
+ extra: {
987
+ timestamp: now(),
988
+ },
989
+ });
990
+ }
991
+ };
992
+ var sendLog = function (data) {
993
+ var normalized = normalizeCustomLogData(data);
994
+ if (normalized) {
995
+ client.report({
996
+ ev_type: CUSTOM_EV_TYPE,
997
+ payload: normalized,
998
+ extra: {
999
+ timestamp: now(),
1000
+ },
1001
+ });
1002
+ }
1003
+ };
1004
+ client.provide('sendEvent', sendEvent);
1005
+ client.provide('sendLog', sendLog);
1006
+ };
1007
+
1008
+ /* eslint-disable @typescript-eslint/prefer-for-of */
1009
+ var withSampleRate = function (ev, sampleRate) {
1010
+ var common = ev.common || {};
1011
+ common.sample_rate = sampleRate;
1012
+ ev.common = common;
1013
+ return ev;
1014
+ };
1015
+ var hitFnWithRandom = function (preCalc, sampleRate, isHitBySampleRate, random, isHitByRandom) {
1016
+ return preCalc
1017
+ ? (function (h) { return function () {
1018
+ return h;
1019
+ }; })(isHitByRandom(random, sampleRate))
1020
+ : function () { return isHitBySampleRate(sampleRate); };
1021
+ };
1022
+ var parseValues = function (values, type) {
1023
+ return values.map(function (v) {
1024
+ switch (type) {
1025
+ case 'number':
1026
+ return Number(v);
1027
+ case 'boolean':
1028
+ return v === '1';
1029
+ case 'string': // default to string
1030
+ default:
1031
+ return String(v);
1032
+ }
1033
+ });
1034
+ };
1035
+ var checkVal = function (val, values, op) {
1036
+ switch (op) {
1037
+ case 'eq':
1038
+ return arrayIncludes(values, val);
1039
+ case 'neq':
1040
+ return !arrayIncludes(values, val);
1041
+ case 'gt':
1042
+ return val > values[0];
1043
+ case 'gte':
1044
+ return val >= values[0];
1045
+ case 'lt':
1046
+ return val < values[0];
1047
+ case 'lte':
1048
+ return val <= values[0];
1049
+ case 'regex':
1050
+ return Boolean(val.match(new RegExp(values.join('|'))));
1051
+ case 'not_regex':
1052
+ return !val.match(new RegExp(values.join('|')));
1053
+ default: {
1054
+ // unknown op
1055
+ return false;
1056
+ }
1057
+ }
1058
+ };
1059
+ var checkFilter = function (ev, field, op, values) {
1060
+ var val = safeVisit(ev, field, function (t, p) {
1061
+ return t[p];
1062
+ });
1063
+ if (val === undefined) {
1064
+ return false;
1065
+ }
1066
+ var field_type = isBoolean(val) ? 'bool' : isNumber(val) ? 'number' : 'string';
1067
+ return checkVal(val, parseValues(values, field_type), op);
1068
+ };
1069
+ var matchFilter = function (ev, filter) {
1070
+ try {
1071
+ return filter.type === 'rule'
1072
+ ? checkFilter(ev, filter.field, filter.op, filter.values)
1073
+ : filter.type === 'and'
1074
+ ? filter.children.every(function (f) { return matchFilter(ev, f); })
1075
+ : filter.children.some(function (f) { return matchFilter(ev, f); });
1076
+ }
1077
+ catch (e) {
1078
+ reportSelfError(e);
1079
+ return false;
1080
+ }
1081
+ };
1082
+ var getHitMap = function (rules, preCalcHit, baseRate, isHitBySampleRate, random, isHitByRandom) {
1083
+ var hitMap = {};
1084
+ Object.keys(rules).forEach(function (name) {
1085
+ var _a = rules[name], enable = _a.enable, sample_rate = _a.sample_rate, conditional_sample_rules = _a.conditional_sample_rules;
1086
+ if (enable) {
1087
+ hitMap[name] = {
1088
+ enable: enable,
1089
+ sample_rate: sample_rate,
1090
+ effectiveSampleRate: sample_rate * baseRate,
1091
+ hit: hitFnWithRandom(preCalcHit, sample_rate, isHitBySampleRate, random, isHitByRandom),
1092
+ };
1093
+ if (conditional_sample_rules) {
1094
+ hitMap[name].conditional_hit_rules = conditional_sample_rules.map(function (_a) {
1095
+ var s = _a.sample_rate, filter = _a.filter;
1096
+ return ({
1097
+ sample_rate: s,
1098
+ hit: hitFnWithRandom(preCalcHit, s, isHitBySampleRate, random, isHitByRandom),
1099
+ effectiveSampleRate: s * baseRate,
1100
+ filter: filter,
1101
+ });
1102
+ });
1103
+ }
1104
+ }
1105
+ else {
1106
+ hitMap[name] = {
1107
+ enable: enable,
1108
+ hit: function () {
1109
+ /* istanbul ignore next */
1110
+ return false;
1111
+ },
1112
+ sample_rate: 0,
1113
+ effectiveSampleRate: 0,
1114
+ };
1115
+ }
1116
+ });
1117
+ return hitMap;
1118
+ };
1119
+ var getSampler = function (userId, config, isHitBySampleRate, isHitByRandom, destroyFns) {
1120
+ if (!config)
1121
+ return id;
1122
+ // r的设计是为了允许外部传入随机数,用于彻底实现按用户采样
1123
+ var baseRate = config.sample_rate, include_users = config.include_users, sample_granularity = config.sample_granularity, rules = config.rules, _a = config.r, random = _a === void 0 ? Math.random() : _a;
1124
+ // 用户名单采样
1125
+ var userHit = arrayIncludes(include_users, userId);
1126
+ if (userHit) {
1127
+ return function (ev) { return withSampleRate(ev, 1); };
1128
+ }
1129
+ // should pre calculate hit
1130
+ var preCalcHit = sample_granularity === 'session';
1131
+ var baseHit = hitFnWithRandom(preCalcHit, baseRate, isHitBySampleRate, random, isHitByRandom);
1132
+ var hitMap = getHitMap(rules, preCalcHit, baseRate, isHitBySampleRate, random, isHitByRandom);
1133
+ return function (ev) {
1134
+ var _a;
1135
+ // 总采样必须命中才有后续
1136
+ if (!baseHit()) {
1137
+ preCalcHit && destroyFns[0]();
1138
+ return false;
1139
+ }
1140
+ // 未配置的事件类型
1141
+ if (!(ev.ev_type in hitMap)) {
1142
+ return withSampleRate(ev, baseRate);
1143
+ }
1144
+ // 忽略未开启的事件类型
1145
+ if (!hitMap[ev.ev_type].enable) {
1146
+ preCalcHit && destroyFns[1](ev.ev_type);
1147
+ return false;
1148
+ }
1149
+ // 跳过采样配置
1150
+ if ((_a = ev.common) === null || _a === void 0 ? void 0 : _a.sample_rate) {
1151
+ return ev;
1152
+ }
1153
+ var hitConfig = hitMap[ev.ev_type];
1154
+ var conditions = hitConfig.conditional_hit_rules;
1155
+ if (conditions) {
1156
+ // 先判断条件采样
1157
+ for (var i = 0; i < conditions.length; i++) {
1158
+ if (matchFilter(ev, conditions[i].filter)) {
1159
+ if (conditions[i].hit()) {
1160
+ return withSampleRate(ev, conditions[i].effectiveSampleRate);
1161
+ }
1162
+ // 条件匹配后不再搜索
1163
+ return false;
1164
+ }
1165
+ }
1166
+ }
1167
+ // 事件类型采样
1168
+ if (!hitConfig.hit()) {
1169
+ // not hit ev_type and no condition, destroy side effect
1170
+ !(conditions && conditions.length) && preCalcHit && destroyFns[1](ev.ev_type);
1171
+ return false;
1172
+ }
1173
+ // 事件类型默认采样已经命中
1174
+ return withSampleRate(ev, hitConfig.effectiveSampleRate);
1175
+ };
1176
+ };
1177
+ var SamplePlugin = function (client) {
1178
+ client.on('start', function () {
1179
+ var _a = client.config(), userId = _a.userId, sample = _a.sample;
1180
+ var destroyFns = [
1181
+ function () {
1182
+ client.destroy();
1183
+ },
1184
+ function (ev_type) {
1185
+ client.destroyAgent.removeByEvType(ev_type);
1186
+ },
1187
+ ];
1188
+ var sampler = getSampler(userId, sample, isHitBySampleRate, isHitByRandom, destroyFns);
1189
+ client.on('build', sampler);
1190
+ });
1191
+ };
1192
+
1193
+ var builder = {
1194
+ build: function (e) {
1195
+ return {
1196
+ ev_type: e.ev_type,
1197
+ payload: e.payload,
1198
+ common: __assign(__assign({}, (e.extra || {})), (e.overrides || {})),
1199
+ };
1200
+ },
1201
+ };
1202
+
1203
+ var REPORT_DOMAIN = "mon.zijieapi.com";
1204
+ var SDK_VERSION = "2.1.8" ;
1205
+ var SDK_NAME = 'SDK_BASE';
1206
+ var SETTINGS_PATH = '/monitor_web/settings/browser-settings';
1207
+ var BATCH_REPORT_PATH = "/monitor_browser/collect/batch/";
1208
+ var DEFAULT_SAMPLE_CONFIG = {
1209
+ sample_rate: 1,
1210
+ include_users: [],
1211
+ sample_granularity: 'session',
1212
+ rules: {},
1213
+ };
1214
+ var DEFAULT_SENDER_SIZE = 20;
1215
+ var DEFAULT_SAMPLE_GRANULARITY = 'session';
1216
+
1217
+ function normalizeStrictFields(config) {
1218
+ var e_1, _a;
1219
+ var strictFields = ['userId', 'deviceId', 'sessionId', 'env'];
1220
+ try {
1221
+ for (var strictFields_1 = __values(strictFields), strictFields_1_1 = strictFields_1.next(); !strictFields_1_1.done; strictFields_1_1 = strictFields_1.next()) {
1222
+ var k = strictFields_1_1.value;
1223
+ if (!config[k]) {
1224
+ delete config[k];
1225
+ }
1226
+ }
1227
+ }
1228
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
1229
+ finally {
1230
+ try {
1231
+ if (strictFields_1_1 && !strictFields_1_1.done && (_a = strictFields_1.return)) _a.call(strictFields_1);
1232
+ }
1233
+ finally { if (e_1) throw e_1.error; }
1234
+ }
1235
+ return config;
1236
+ }
1237
+ function normalizeInitConfig(config) {
1238
+ return normalizeStrictFields(__assign({}, config));
1239
+ }
1240
+ function validateInitConfig(config) {
1241
+ return isObject(config) && 'bid' in config && 'transport' in config;
1242
+ }
1243
+ function normalizeUserConfig(config) {
1244
+ return normalizeStrictFields(__assign({}, config));
1245
+ }
1246
+ function parseServerConfig(serverConfig) {
1247
+ if (!serverConfig) {
1248
+ return {};
1249
+ }
1250
+ var sample = serverConfig.sample, timestamp = serverConfig.timestamp, _a = serverConfig.quota_rate, quota_rate = _a === void 0 ? 1 : _a;
1251
+ if (!sample) {
1252
+ return {};
1253
+ }
1254
+ var sample_rate = sample.sample_rate, _b = sample.sample_granularity, sample_granularity = _b === void 0 ? DEFAULT_SAMPLE_GRANULARITY : _b, include_users = sample.include_users, _c = sample.rules, rules = _c === void 0 ? [] : _c;
1255
+ return {
1256
+ sample: {
1257
+ include_users: include_users,
1258
+ sample_rate: sample_rate * quota_rate,
1259
+ sample_granularity: sample_granularity,
1260
+ rules: rules.reduce(function (prev, cur) {
1261
+ var name = cur.name, enable = cur.enable, sample_rate = cur.sample_rate, conditional_sample_rules = cur.conditional_sample_rules;
1262
+ prev[name] = {
1263
+ enable: enable,
1264
+ sample_rate: sample_rate,
1265
+ conditional_sample_rules: conditional_sample_rules,
1266
+ };
1267
+ return prev;
1268
+ }, {}),
1269
+ },
1270
+ serverTimestamp: timestamp,
1271
+ };
1272
+ }
1273
+
1274
+ /* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
1275
+ var getReportUrl = function (domain, path) {
1276
+ if (path === void 0) { path = BATCH_REPORT_PATH; }
1277
+ return "" + (domain && domain.indexOf('//') >= 0 ? '' : 'https://') + domain + path;
1278
+ };
1279
+ var getSettingsUrl = function (domain, path) {
1280
+ if (path === void 0) { path = SETTINGS_PATH; }
1281
+ return "" + (domain && domain.indexOf('//') >= 0 ? '' : 'https://') + domain + path;
1282
+ };
1283
+ var getDefaultUserId = function () {
1284
+ return uuid();
1285
+ };
1286
+ var getDefaultDeviceId = function () {
1287
+ return uuid();
1288
+ };
1289
+ var getViewId = function (pid) { return pid + "_" + Date.now(); };
1290
+ var getDefaultSessionId = function () {
1291
+ return uuid();
1292
+ };
1293
+
1294
+ var createConfigManager = function (defaultConfig) {
1295
+ // the merged config
1296
+ var config = defaultConfig;
1297
+ // save it so we know when initConfig is set
1298
+ var initConfig;
1299
+ // save UserConfig so we can merge with priority
1300
+ var userConfig = {};
1301
+ // save the original server config, from get_setting response
1302
+ var serverConfig;
1303
+ // cache the parsed ServerConfig, used in merge
1304
+ var parsedServerConfig;
1305
+ var onReady = noop$1;
1306
+ // call when config changed
1307
+ var onChange = noop$1;
1308
+ return {
1309
+ getConfig: function () {
1310
+ return config;
1311
+ },
1312
+ setConfig: function (c) {
1313
+ userConfig = __assign(__assign({}, userConfig), (c || {}));
1314
+ updateConfig();
1315
+ if (!initConfig) {
1316
+ // handle init
1317
+ initConfig = c;
1318
+ if (config.useLocalConfig || !config.bid) {
1319
+ parsedServerConfig = {};
1320
+ onReady();
1321
+ }
1322
+ else {
1323
+ getServerConfig(config.transport, config.domain, config.bid, function (res) {
1324
+ serverConfig = res;
1325
+ handleServerConfig();
1326
+ });
1327
+ }
1328
+ }
1329
+ return config;
1330
+ },
1331
+ onChange: function (fn) {
1332
+ onChange = fn;
1333
+ },
1334
+ onReady: function (fn) {
1335
+ onReady = fn;
1336
+ if (parsedServerConfig) {
1337
+ onReady();
1338
+ }
1339
+ },
1340
+ };
1341
+ function updateConfig() {
1342
+ var newConfig = __assign(__assign(__assign({}, defaultConfig), (parsedServerConfig || {})), userConfig);
1343
+ newConfig.sample = mergeSampleConfig(mergeSampleConfig(defaultConfig.sample, parsedServerConfig === null || parsedServerConfig === void 0 ? void 0 : parsedServerConfig.sample), userConfig.sample);
1344
+ config = newConfig;
1345
+ onChange();
1346
+ }
1347
+ function handleServerConfig() {
1348
+ parsedServerConfig = parseServerConfig(serverConfig);
1349
+ updateConfig();
1350
+ onReady();
1351
+ }
1352
+ };
1353
+ function getServerConfig(transport, domain, bid, cb) {
1354
+ if (!transport.get) {
1355
+ return cb({});
1356
+ }
1357
+ transport.get({
1358
+ withCredentials: true,
1359
+ url: getSettingsUrl(domain) + "?bid=" + bid + "&store=1",
1360
+ success: function (res) {
1361
+ cb(res.data || {});
1362
+ },
1363
+ fail: function () {
1364
+ cb({ sample: { sample_rate: 0.001 } });
1365
+ },
1366
+ });
1367
+ }
1368
+ function mergeSampleConfig(a, b) {
1369
+ if (!a || !b)
1370
+ return a || b;
1371
+ var res = __assign(__assign({}, a), b);
1372
+ res.include_users = __spreadArray(__spreadArray([], __read((a.include_users || [])), false), __read((b.include_users || [])), false);
1373
+ res.rules = __spreadArray(__spreadArray([], __read(Object.keys(a.rules || {})), false), __read(Object.keys(b.rules || {})), false).reduce(function (obj, key) {
1374
+ var _a, _b;
1375
+ if (!(key in obj)) {
1376
+ if (key in (a.rules || {}) && key in (b.rules || {})) {
1377
+ obj[key] = __assign(__assign({}, a.rules[key]), b.rules[key]);
1378
+ obj[key].conditional_sample_rules = __spreadArray(__spreadArray([], __read((a.rules[key].conditional_sample_rules || [])), false), __read((b.rules[key].conditional_sample_rules || [])), false);
1379
+ }
1380
+ else {
1381
+ obj[key] = ((_a = a.rules) === null || _a === void 0 ? void 0 : _a[key]) || ((_b = b.rules) === null || _b === void 0 ? void 0 : _b[key]);
1382
+ }
1383
+ }
1384
+ return obj;
1385
+ }, {});
1386
+ return res;
1387
+ }
1388
+
1389
+ var addEnvToSendEvent = function (ev, config) {
1390
+ var _a = config || {}, version = _a.version, name = _a.name;
1391
+ var extra = {
1392
+ url: '',
1393
+ protocol: '',
1394
+ domain: '',
1395
+ path: '',
1396
+ query: '',
1397
+ timestamp: Date.now(),
1398
+ sdk_version: version || SDK_VERSION,
1399
+ sdk_name: name || SDK_NAME,
1400
+ };
1401
+ return __assign(__assign({}, ev), { extra: __assign(__assign({}, extra), (ev.extra || {})) });
1402
+ };
1403
+ var InjectEnvPlugin = function (client) {
1404
+ client.on('report', function (ev) {
1405
+ return addEnvToSendEvent(ev, client.config());
1406
+ });
1407
+ };
1408
+
1409
+ var addConfigToReportEvent = function (ev, config) {
1410
+ var extra = {};
1411
+ extra.bid = config.bid;
1412
+ extra.pid = config.pid;
1413
+ extra.view_id = config.viewId;
1414
+ extra.user_id = config.userId;
1415
+ extra.device_id = config.deviceId;
1416
+ extra.session_id = config.sessionId;
1417
+ extra.release = config.release;
1418
+ extra.env = config.env;
1419
+ return __assign(__assign({}, ev), { extra: __assign(__assign({}, extra), (ev.extra || {})) });
1420
+ };
1421
+ var InjectConfigPlugin = function (client) {
1422
+ client.on('beforeBuild', function (ev) {
1423
+ return addConfigToReportEvent(ev, client.config());
1424
+ });
1425
+ };
1426
+
1427
+ // createSender has side effects(register onClose behaviour)
1428
+ // so it must be create lazily
1429
+ function createSender(config) {
1430
+ return createBatchSender(config);
1431
+ }
1432
+
1433
+ /* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
1434
+ var getDefaultConfig = function (_c) { return ({
1435
+ bid: '',
1436
+ pid: '',
1437
+ viewId: getViewId('_'),
1438
+ userId: getDefaultUserId(),
1439
+ deviceId: getDefaultDeviceId(),
1440
+ sessionId: getDefaultSessionId(),
1441
+ domain: REPORT_DOMAIN,
1442
+ release: '',
1443
+ env: 'production',
1444
+ sample: DEFAULT_SAMPLE_CONFIG,
1445
+ plugins: {},
1446
+ transport: {
1447
+ get: noop$1,
1448
+ post: noop$1,
1449
+ },
1450
+ useLocalConfig: false,
1451
+ }); };
1452
+ var createMinimalClient = function (_a) {
1453
+ var _b = _a === void 0 ? {} : _a, _d = _b.createSender, createSender$1 = _d === void 0 ? function (config) {
1454
+ return createSender({
1455
+ size: DEFAULT_SENDER_SIZE,
1456
+ endpoint: getReportUrl(config.domain),
1457
+ transport: config.transport,
1458
+ });
1459
+ } : _d, _e = _b.builder, builder$1 = _e === void 0 ? builder : _e, _f = _b.createDefaultConfig, createDefaultConfig = _f === void 0 ? getDefaultConfig : _f;
1460
+ var client = createClient({
1461
+ validateInitConfig: validateInitConfig,
1462
+ initConfigNormalizer: normalizeInitConfig,
1463
+ userConfigNormalizer: normalizeUserConfig,
1464
+ createSender: createSender$1,
1465
+ builder: builder$1,
1466
+ createDefaultConfig: createDefaultConfig,
1467
+ createConfigManager: createConfigManager,
1468
+ });
1469
+ ContextPlugin(client);
1470
+ InjectConfigPlugin(client);
1471
+ InjectEnvPlugin(client);
1472
+ IntegrationPlugin(client);
1473
+ DevtoolsPlugin(client);
1474
+ return client;
1475
+ };
1476
+ var createBaseClient = function (config) {
1477
+ if (config === void 0) { config = {}; }
1478
+ var client = createMinimalClient(config);
1479
+ SamplePlugin(client);
1480
+ CustomPlugin(client);
1481
+ return client;
1482
+ };
1483
+
1484
+ var browserClient = createBaseClient();
1485
+
1486
+ /**
1487
+ * Node.js 环境的 HTTP Transport 实现
1488
+ * 使用原生 http/https 模块实现网络请求
1489
+ */
1490
+
1491
+
1492
+
1493
+ const log$2 = debug('slardar:transport');
1494
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
1495
+ const noop = () => {};
1496
+
1497
+
1498
+
1499
+
1500
+
1501
+
1502
+
1503
+
1504
+
1505
+
1506
+
1507
+ /**
1508
+ * 执行 HTTP/HTTPS 请求
1509
+ */
1510
+ function makeRequest(options) {
1511
+ const { url, method, data, success = noop, fail = noop, getResponseText = noop } = options;
1512
+
1513
+ log$2('Making %s request to %s', method, url);
1514
+ if (data) {
1515
+ log$2('Request data: %s', data.slice(0, 200));
1516
+ }
1517
+
1518
+ // 检查 URL 是否有效
1519
+ if (!url || typeof url !== 'string') {
1520
+ const error = new Error(`Invalid URL: ${url}`);
1521
+ log$2('Invalid URL provided: %o', url);
1522
+ fail(error);
1523
+ return;
1524
+ }
1525
+
1526
+ try {
1527
+ const urlObj = new URL(url);
1528
+ log$2('Parsed URL - protocol: %s, hostname: %s, port: %s, path: %s',
1529
+ urlObj.protocol, urlObj.hostname, urlObj.port, urlObj.pathname);
1530
+
1531
+ const isHttps = urlObj.protocol === 'https:';
1532
+ const request = isHttps ? node_https.request : node_http.request;
1533
+
1534
+ const req = request(
1535
+ {
1536
+ hostname: urlObj.hostname,
1537
+ port: urlObj.port,
1538
+ path: urlObj.pathname + urlObj.search,
1539
+ method,
1540
+ headers: {
1541
+ 'Content-Type': 'application/json',
1542
+ ...(data && { 'Content-Length': Buffer.byteLength(data) }),
1543
+ },
1544
+ },
1545
+ res => {
1546
+ log$2('Response callback triggered: status=%s', res.statusCode);
1547
+ let responseText = '';
1548
+
1549
+ res.on('data', chunk => {
1550
+ log$2('Response data chunk received: %s bytes', chunk.length);
1551
+ responseText += chunk.toString();
1552
+ });
1553
+
1554
+ res.on('end', () => {
1555
+ log$2('Response end event fired');
1556
+ log$2('Response received: status=%s, body=%s', res.statusCode, responseText.slice(0, 200));
1557
+ getResponseText(responseText);
1558
+
1559
+ try {
1560
+ if (res.statusCode && res.statusCode >= 400) {
1561
+ log$2('Request failed with status %s: %s', res.statusCode, responseText);
1562
+ fail(new Error(responseText || res.statusMessage || 'Request failed'));
1563
+ } else if (responseText) {
1564
+ // eslint-disable-next-line no-restricted-syntax -- Parsing trusted Slardar API responses
1565
+ const result = JSON.parse(responseText);
1566
+ log$2('Request succeeded');
1567
+ success(result);
1568
+ } else {
1569
+ log$2('Request succeeded with empty response');
1570
+ success({});
1571
+ }
1572
+ } catch (e) {
1573
+ log$2('Failed to parse response: %s', (e ).message);
1574
+ fail(e );
1575
+ }
1576
+ });
1577
+ },
1578
+ );
1579
+
1580
+ req.on('error', err => {
1581
+ log$2('Request error: %s', err.message);
1582
+ fail(err);
1583
+ });
1584
+
1585
+ req.on('timeout', () => {
1586
+ log$2('Request timeout');
1587
+ req.destroy();
1588
+ fail(new Error('Request timeout'));
1589
+ });
1590
+
1591
+ if (data) {
1592
+ req.write(data);
1593
+ }
1594
+
1595
+ req.end();
1596
+ } catch (e) {
1597
+ log$2('Exception during request: %s', (e ).message);
1598
+ fail(e );
1599
+ }
1600
+ }
1601
+
1602
+ /**
1603
+ * 创建 Node.js 环境的 Transport
1604
+ */
1605
+ function createNodeTransport() {
1606
+ return {
1607
+ get(options) {
1608
+ log$2('Transport GET called: %s', options.url);
1609
+ makeRequest({
1610
+ method: 'GET',
1611
+ ...options,
1612
+ });
1613
+ },
1614
+ post({ url, data }) {
1615
+ log$2('Transport POST called: %s', url);
1616
+ makeRequest({
1617
+ method: 'POST',
1618
+ url,
1619
+ data: data
1620
+ ? typeof data === 'string'
1621
+ ? data
1622
+ : JSON.stringify(data)
1623
+ : undefined,
1624
+ });
1625
+ },
1626
+ };
1627
+ }
1628
+
1629
+ function _nullishCoalesce$3(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain$6(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }/**
1630
+ * Slardar CLI Reporter 主类
1631
+ * 封装 @slardar/base 的初始化和上报逻辑
1632
+ */
1633
+
1634
+ // 创建 debug 实例
1635
+ // 使用方式: DEBUG=slardar:* your-cli-command
1636
+ const log$1 = debug('slardar:reporter');
1637
+
1638
+ /**
1639
+ * Slardar CLI Reporter
1640
+ * 提供简洁的 API 用于 CLI 工具的监控数据上报
1641
+ */
1642
+ class SlardarCLIReporter {constructor() { SlardarCLIReporter.prototype.__init.call(this);SlardarCLIReporter.prototype.__init2.call(this); }
1643
+ __init() {this.initialized = false;}
1644
+ __init2() {this.client = browserClient;}
1645
+
1646
+ /**
1647
+ * 初始化 Slardar Reporter
1648
+ * @param config 初始化配置
1649
+ *
1650
+ * @example
1651
+ * ```typescript
1652
+ * reporter.setup({
1653
+ * bid: 'my_cli_app',
1654
+ * release: '1.0.0',
1655
+ * env: 'production',
1656
+ * });
1657
+ * ```
1658
+ */
1659
+ setup(config) {
1660
+ if (this.initialized) {
1661
+ log$1('Already initialized, skipping setup');
1662
+ return;
1663
+ }
1664
+
1665
+ try {
1666
+ log$1('Initializing Slardar with config:', {
1667
+ bid: config.bid,
1668
+ release: config.release,
1669
+ env: config.env,
1670
+ userId: config.userId,
1671
+ projectId: config.projectId,
1672
+ });
1673
+
1674
+ const transport = createNodeTransport();
1675
+
1676
+ this.client.init({
1677
+ bid: config.bid,
1678
+ transport,
1679
+ userId: config.userId,
1680
+ pid: config.projectId, // projectId 映射到 slardar 的 pid 字段
1681
+ deviceId: config.deviceId,
1682
+ sessionId: config.sessionId,
1683
+ release: config.release,
1684
+ env: config.env,
1685
+ name: config.name,
1686
+ useLocalConfig: _nullishCoalesce$3(config.useLocalConfig, () => ( false)), // 默认使用服务端配置
1687
+ domain: config.domain,
1688
+ // 设置本地采样率为 100%,确保事件不被过滤
1689
+ sample: {
1690
+ sample_rate: 1, // 100% 采样率
1691
+ include_users: [], // 包含所有用户
1692
+ sample_granularity: 'session', // 会话级别采样
1693
+ rules: {}, // 采样规则(空表示使用默认规则)
1694
+ },
1695
+ });
1696
+
1697
+ // 添加 send 钩子来追踪事件上报
1698
+ this.client.on('send', (ev) => {
1699
+ log$1(
1700
+ 'send hook called for event: %s',
1701
+ (_optionalChain$6([ev, 'optionalAccess', _ => _.ev_type]) ) || 'unknown',
1702
+ );
1703
+ });
1704
+
1705
+ this.client.start();
1706
+ this.initialized = true;
1707
+ log$1('Slardar initialized successfully');
1708
+ } catch (error) {
1709
+ log$1('Failed to initialize:', error);
1710
+ }
1711
+ }
1712
+
1713
+ /**
1714
+ * 检查是否已初始化
1715
+ */
1716
+ ensureInitialized() {
1717
+ if (!this.initialized) {
1718
+ log$1('Not initialized, call setup() first');
1719
+ return false;
1720
+ }
1721
+ return true;
1722
+ }
1723
+
1724
+ /**
1725
+ * 上报自定义事件
1726
+ * @param name 事件名称
1727
+ * @param metrics 指标数据(数值类型)
1728
+ * @param categories 分类维度(字符串类型)
1729
+ */
1730
+ sendEvent(
1731
+ name,
1732
+ metrics,
1733
+ categories,
1734
+ ) {
1735
+ if (!this.ensureInitialized()) {
1736
+ return;
1737
+ }
1738
+
1739
+ try {
1740
+ log$1('Sending event:', { name, metrics, categories });
1741
+
1742
+ // 过滤掉 undefined 值以满足 Slardar 类型要求
1743
+ const cleanMetrics = metrics
1744
+ ? Object.fromEntries(
1745
+ Object.entries(metrics).filter(
1746
+ ([, value]) => value !== undefined,
1747
+ ),
1748
+ )
1749
+ : undefined;
1750
+
1751
+ const cleanCategories = categories
1752
+ ? Object.fromEntries(
1753
+ Object.entries(categories).filter(
1754
+ ([, value]) => value !== undefined,
1755
+ ),
1756
+ )
1757
+ : undefined;
1758
+
1759
+ _optionalChain$6([this, 'access', _2 => _2.client, 'access', _3 => _3.sendEvent, 'optionalCall', _4 => _4({
1760
+ name,
1761
+ metrics: cleanMetrics ,
1762
+ categories: cleanCategories ,
1763
+ })]);
1764
+ } catch (error) {
1765
+ log$1('Failed to send event:', error);
1766
+ }
1767
+ }
1768
+
1769
+ /**
1770
+ * 上报自定义日志
1771
+ * @param level 日志级别
1772
+ * @param message 日志消息
1773
+ * @param extra 额外的上下文信息
1774
+ */
1775
+ sendLog(
1776
+ level,
1777
+ message,
1778
+ extra,
1779
+ ) {
1780
+ if (!this.ensureInitialized()) {
1781
+ return;
1782
+ }
1783
+
1784
+ try {
1785
+ log$1('Sending log:', { level, message, extra });
1786
+ // 使用 sendEvent 发送日志事件
1787
+ this.sendEvent('cli_log', undefined, {
1788
+ level,
1789
+ message,
1790
+ ...extra,
1791
+ });
1792
+ } catch (error) {
1793
+ log$1('Failed to send log:', error);
1794
+ }
1795
+ }
1796
+
1797
+ /**
1798
+ * 上报 JS 错误(会显示在 Slardar JS 错误总览页面)
1799
+ * @param error Error 对象
1800
+ * @param extra 额外的上下文信息
1801
+ * @param source 错误来源信息
1802
+ */
1803
+ reportError(
1804
+ error,
1805
+ extra,
1806
+ source,
1807
+ ) {
1808
+ if (!this.ensureInitialized()) {
1809
+ return;
1810
+ }
1811
+
1812
+ try {
1813
+ const errorEvent = {
1814
+ ev_type: 'js_error' ,
1815
+ payload: {
1816
+ error: {
1817
+ name: error.name,
1818
+ message: error.message,
1819
+ stack: error.stack,
1820
+ },
1821
+ breadcrumbs: [], // CLI 环境暂不收集面包屑
1822
+ extra,
1823
+ source: source || { type: 'manual' }, // 默认为手动捕获
1824
+ },
1825
+ };
1826
+
1827
+ log$1('Reporting JS error:', {
1828
+ name: error.name,
1829
+ message: error.message.slice(0, 100),
1830
+ stack: _optionalChain$6([error, 'access', _5 => _5.stack, 'optionalAccess', _6 => _6.slice, 'call', _7 => _7(0, 200)]),
1831
+ extra,
1832
+ source,
1833
+ });
1834
+
1835
+ // 使用 Slardar 的 js_error 事件类型,这样会显示在 JS 错误总览页面
1836
+ this.client.report(errorEvent);
1837
+ log$1('JS error reported successfully');
1838
+ } catch (err) {
1839
+ log$1('Failed to report error:', err);
1840
+ }
1841
+ }
1842
+
1843
+ /**
1844
+ * 设置全局上下文
1845
+ */
1846
+ setContext(key, value) {
1847
+ if (!this.ensureInitialized()) {
1848
+ return;
1849
+ }
1850
+ _optionalChain$6([this, 'access', _8 => _8.client, 'access', _9 => _9.context, 'optionalAccess', _10 => _10.set, 'call', _11 => _11(key, value)]);
1851
+ }
1852
+
1853
+ /**
1854
+ * 批量设置全局上下文
1855
+ */
1856
+ mergeContext(context) {
1857
+ if (!this.ensureInitialized()) {
1858
+ return;
1859
+ }
1860
+ log$1('Merging context:', context);
1861
+ _optionalChain$6([this, 'access', _12 => _12.client, 'access', _13 => _13.context, 'optionalAccess', _14 => _14.merge, 'call', _15 => _15(context)]);
1862
+ }
1863
+
1864
+ /**
1865
+ * 删除全局上下文
1866
+ */
1867
+ deleteContext(key) {
1868
+ if (!this.ensureInitialized()) {
1869
+ return;
1870
+ }
1871
+ _optionalChain$6([this, 'access', _16 => _16.client, 'access', _17 => _17.context, 'optionalAccess', _18 => _18.delete, 'call', _19 => _19(key)]);
1872
+ }
1873
+
1874
+ /**
1875
+ * 清空全局上下文
1876
+ */
1877
+ clearContext() {
1878
+ if (!this.ensureInitialized()) {
1879
+ return;
1880
+ }
1881
+ _optionalChain$6([this, 'access', _20 => _20.client, 'access', _21 => _21.context, 'optionalAccess', _22 => _22.clear, 'call', _23 => _23()]);
1882
+ }
1883
+
1884
+ /**
1885
+ * 更新用户配置
1886
+ */
1887
+ updateConfig(
1888
+ config
1889
+
1890
+ ,
1891
+ ) {
1892
+ if (!this.ensureInitialized()) {
1893
+ return;
1894
+ }
1895
+
1896
+ try {
1897
+ log$1('Updating config:', config);
1898
+ this.client.config({
1899
+ userId: config.userId,
1900
+ release: config.release,
1901
+ sessionId: config.sessionId,
1902
+ env: config.env,
1903
+ });
1904
+ } catch (error) {
1905
+ log$1('Failed to update config:', error);
1906
+ }
1907
+ }
1908
+
1909
+ /**
1910
+ * 立即上报(不等待队列)
1911
+ * @param waitMs 等待时间(毫秒),默认 1500ms,用于确保设置请求完成和事件发送
1912
+ */
1913
+ async flush(waitMs = 1500) {
1914
+ if (!this.ensureInitialized()) {
1915
+ return;
1916
+ }
1917
+ log$1('Flushing Slardar data...');
1918
+ _optionalChain$6([this, 'access', _24 => _24.client, 'access', _25 => _25.getSender, 'optionalCall', _26 => _26(), 'optionalAccess', _27 => _27.flush, 'call', _28 => _28()]);
1919
+ log$1('Waiting %dms for events to be sent...', waitMs);
1920
+ await new Promise(resolve => setTimeout(resolve, waitMs));
1921
+ log$1('Slardar data flushed');
1922
+ }
1923
+
1924
+ /**
1925
+ * 获取原始 Slardar client(用于高级用法)
1926
+ */
1927
+ getRawClient() {
1928
+ return this.client;
1929
+ }
1930
+ }
1931
+
1932
+ /**
1933
+ * 默认导出的单例实例
1934
+ */
1935
+ const reporter = new SlardarCLIReporter();
1936
+
1937
+ function _optionalChain$5(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
1938
+
1939
+
1940
+
1941
+
1942
+
1943
+ /**
1944
+ * CLI 通用事件类型常量
1945
+ * 仅包含通用的 CLI 工具事件,不包含具体业务逻辑
1946
+ */
1947
+ const CLI_EVENTS = {
1948
+ /** CLI 启动 */
1949
+ CLI_START: 'cli_start',
1950
+ /** CLI 命令执行 */
1951
+ CLI_COMMAND: 'cli_command',
1952
+ /** CLI 命令完成 */
1953
+ CLI_COMMAND_COMPLETE: 'cli_command_complete',
1954
+ /** CLI 错误 */
1955
+ CLI_ERROR: 'cli_error',
1956
+ /** 网络请求 */
1957
+ NETWORK_REQUEST: 'network_request',
1958
+ /** 文件操作 */
1959
+ FILE_OPERATION: 'file_operation',
1960
+ } ;
1961
+
1962
+
1963
+
1964
+ /**
1965
+ * 事件构建器 - 提供更友好的事件创建接口
1966
+ */
1967
+ // eslint-disable-next-line @typescript-eslint/naming-convention
1968
+ const EventBuilder = {
1969
+ /**
1970
+ * 构建 CLI 启动事件
1971
+ */
1972
+ cliStart(metrics) {
1973
+ return {
1974
+ name: CLI_EVENTS.CLI_START,
1975
+ metrics: {
1976
+ duration: 0,
1977
+ ...metrics,
1978
+ },
1979
+ };
1980
+ },
1981
+
1982
+ /**
1983
+ * 构建 CLI 命令执行事件
1984
+ */
1985
+ cliCommand(
1986
+ command,
1987
+ options
1988
+
1989
+
1990
+
1991
+ ,
1992
+ ) {
1993
+ return {
1994
+ name: CLI_EVENTS.CLI_COMMAND,
1995
+ categories: {
1996
+ command,
1997
+ args: _optionalChain$5([options, 'optionalAccess', _ => _.args]),
1998
+ status: 'running' ,
1999
+ ..._optionalChain$5([options, 'optionalAccess', _2 => _2.categories]),
2000
+ },
2001
+ metrics: _optionalChain$5([options, 'optionalAccess', _3 => _3.metrics]),
2002
+ };
2003
+ },
2004
+
2005
+ /**
2006
+ * 构建 CLI 命令完成事件
2007
+ */
2008
+ cliCommandComplete(
2009
+ command,
2010
+ success,
2011
+ duration,
2012
+ options
2013
+
2014
+
2015
+
2016
+ ,
2017
+ ) {
2018
+ return {
2019
+ name: CLI_EVENTS.CLI_COMMAND_COMPLETE,
2020
+ categories: {
2021
+ command,
2022
+ args: _optionalChain$5([options, 'optionalAccess', _4 => _4.args]),
2023
+ status: success ? ('success' ) : ('fail' ),
2024
+ ..._optionalChain$5([options, 'optionalAccess', _5 => _5.categories]),
2025
+ },
2026
+ metrics: {
2027
+ duration,
2028
+ ...(_optionalChain$5([options, 'optionalAccess', _6 => _6.errorCode]) && { errorCode: options.errorCode }),
2029
+ },
2030
+ };
2031
+ },
2032
+
2033
+ /**
2034
+ * 构建网络请求事件
2035
+ */
2036
+ networkRequest(
2037
+ url,
2038
+ options
2039
+
2040
+
2041
+
2042
+
2043
+ ,
2044
+ ) {
2045
+ return {
2046
+ name: CLI_EVENTS.NETWORK_REQUEST,
2047
+ categories: {
2048
+ url,
2049
+ method: _optionalChain$5([options, 'optionalAccess', _7 => _7.method]) || 'GET',
2050
+ statusCode: _optionalChain$5([options, 'optionalAccess', _8 => _8.statusCode, 'optionalAccess', _9 => _9.toString, 'call', _10 => _10()]),
2051
+ status: _optionalChain$5([options, 'optionalAccess', _11 => _11.success]) ? ('success' ) : ('fail' ),
2052
+ },
2053
+ metrics: {
2054
+ duration: _optionalChain$5([options, 'optionalAccess', _12 => _12.duration]),
2055
+ },
2056
+ };
2057
+ },
2058
+
2059
+ /**
2060
+ * 构建文件操作事件
2061
+ */
2062
+ fileOperation(
2063
+ operation,
2064
+ filePath,
2065
+ options
2066
+
2067
+
2068
+
2069
+ ,
2070
+ ) {
2071
+ return {
2072
+ name: CLI_EVENTS.FILE_OPERATION,
2073
+ categories: {
2074
+ operation,
2075
+ filePath,
2076
+ status: _optionalChain$5([options, 'optionalAccess', _13 => _13.success]) ? ('success' ) : ('fail' ),
2077
+ },
2078
+ metrics: {
2079
+ duration: _optionalChain$5([options, 'optionalAccess', _14 => _14.duration]),
2080
+ fileSize: _optionalChain$5([options, 'optionalAccess', _15 => _15.fileSize]),
2081
+ },
2082
+ };
2083
+ },
2084
+
2085
+ /**
2086
+ * 构建错误事件
2087
+ */
2088
+ cliError(
2089
+ error,
2090
+ context,
2091
+ ) {
2092
+ return {
2093
+ name: CLI_EVENTS.CLI_ERROR,
2094
+ categories: {
2095
+ errorName: error.name,
2096
+ errorMessage: error.message,
2097
+ stack: error.stack || '',
2098
+ ...context,
2099
+ },
2100
+ metrics: {
2101
+ errorCode: 1,
2102
+ },
2103
+ };
2104
+ },
2105
+ };
2106
+
2107
+ var name = "@coze-arch/cli";
2108
+ var version = "0.0.5";
2109
+ var description = "coze coding devtools cli";
2110
+ var license = "MIT";
2111
+ var author = "fanwenjie.fe@bytedance.com";
2112
+ var maintainers = [
2113
+ ];
2114
+ var bin = {
2115
+ coze: "bin/main"
2116
+ };
2117
+ var files = [
2118
+ "lib",
2119
+ "bin",
2120
+ "lib/**/.npmrc",
2121
+ "!**/*.tsbuildinfo",
2122
+ "!**/*.map",
2123
+ "!**/node-compile-cache"
2124
+ ];
2125
+ var scripts = {
2126
+ prebuild: "tsx scripts/prebuild.ts",
2127
+ build: "tsx scripts/build.ts",
2128
+ create: "tsx scripts/create-template.ts",
2129
+ lint: "eslint ./ --cache",
2130
+ "pre-release": "tsx scripts/pre-release.ts",
2131
+ postpublish: "bash scripts/sync-npmmirror.sh",
2132
+ test: "vitest --run --passWithNoTests",
2133
+ "test:all": "bash scripts/test-coverage.sh",
2134
+ "test:cov": "vitest --run --passWithNoTests --coverage",
2135
+ "test:e2e": "NODE_ENV=test bash scripts/e2e.sh",
2136
+ "test:perf": "vitest bench --run --config vitest.perf.config.ts",
2137
+ "test:perf:compare": "bash scripts/compare-perf.sh",
2138
+ "test:perf:save": "bash scripts/run-perf-with-output.sh"
2139
+ };
2140
+ var dependencies = {
2141
+ "@iarna/toml": "^2.2.5",
2142
+ ajv: "^8.17.1",
2143
+ "ajv-formats": "^3.0.1",
2144
+ "change-case": "^5.4.4",
2145
+ commander: "~12.1.0",
2146
+ debug: "^4.3.7",
2147
+ ejs: "^3.1.10",
2148
+ "js-yaml": "^4.1.0",
2149
+ minimist: "^1.2.5",
2150
+ shelljs: "^0.10.0"
2151
+ };
2152
+ var devDependencies = {
2153
+ "@coze-arch/cli-logger": "workspace:*",
2154
+ "@coze-arch/cli-slardar": "workspace:*",
2155
+ "@coze-arch/eslint-config": "workspace:*",
2156
+ "@coze-arch/monorepo-kits": "workspace:*",
2157
+ "@coze-arch/rollup-config": "workspace:*",
2158
+ "@coze-arch/ts-config": "workspace:*",
2159
+ "@coze-arch/vitest-config": "workspace:*",
2160
+ "@coze-coding/lambda": "workspace:*",
2161
+ "@inquirer/prompts": "^3.2.0",
2162
+ "@playwright/test": "~1.55.0",
2163
+ "@types/debug": "^4.1.12",
2164
+ "@types/ejs": "^3.1.5",
2165
+ "@types/iarna__toml": "^2.0.5",
2166
+ "@types/js-yaml": "^4.0.9",
2167
+ "@types/minimatch": "^5.1.2",
2168
+ "@types/minimist": "^1.2.5",
2169
+ "@types/node": "^24",
2170
+ "@types/shelljs": "^0.10.0",
2171
+ "@vitest/coverage-v8": "~4.0.18",
2172
+ "json-schema-to-typescript": "^15.0.3",
2173
+ minimatch: "^10.0.1",
2174
+ playwright: "~1.55.0",
2175
+ rollup: "^4.41.1",
2176
+ sucrase: "^3.35.0",
2177
+ "tree-kill": "^1.2.2",
2178
+ tsx: "^4.20.6",
2179
+ "vite-tsconfig-paths": "^4.2.1",
2180
+ vitest: "~4.0.18"
2181
+ };
2182
+ var publishConfig = {
2183
+ access: "public",
2184
+ registry: "https://registry.npmjs.org"
2185
+ };
2186
+ var cozePublishConfig = {
2187
+ bin: {
2188
+ coze: "bin/main"
2189
+ }
2190
+ };
2191
+ var packageJson = {
2192
+ name: name,
2193
+ version: version,
2194
+ "private": false,
2195
+ description: description,
2196
+ license: license,
2197
+ author: author,
2198
+ maintainers: maintainers,
2199
+ bin: bin,
2200
+ files: files,
2201
+ scripts: scripts,
2202
+ dependencies: dependencies,
2203
+ devDependencies: devDependencies,
2204
+ publishConfig: publishConfig,
2205
+ cozePublishConfig: cozePublishConfig
2206
+ };
2207
+
2208
+ function _optionalChain$4(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }/**
2209
+ * Slardar 监控初始化和上报
2210
+ */
2211
+
2212
+ const log = debug('slardar:cli');
2213
+
2214
+ /**
2215
+ * 安全执行函数包装器
2216
+ * 捕获并静默处理所有错误,确保 Slardar 上报失败不影响 CLI 正常运行
2217
+ * 支持同步和异步函数
2218
+ */
2219
+ function safeRun(
2220
+ name,
2221
+ fn,
2222
+ ) {
2223
+ return ((...args) => {
2224
+ try {
2225
+ log('Calling Slardar function: %s', name);
2226
+ const result = fn(...args);
2227
+
2228
+ // 如果是 Promise,处理异步错误
2229
+ if (result instanceof Promise) {
2230
+ return result
2231
+ .then(res => {
2232
+ log('Slardar function %s completed', name);
2233
+ return res;
2234
+ })
2235
+ .catch(error => {
2236
+ log(
2237
+ 'Slardar function %s failed: %s',
2238
+ name,
2239
+ (error ).message,
2240
+ );
2241
+ void error;
2242
+ });
2243
+ }
2244
+
2245
+ log('Slardar function %s completed', name);
2246
+ return result;
2247
+ } catch (error) {
2248
+ // Slardar 上报失败不应影响 CLI 正常运行,但要记录错误
2249
+ log('Slardar function %s failed: %s', name, (error ).message);
2250
+ }
2251
+ }) ;
2252
+ }
2253
+
2254
+ /**
2255
+ * 初始化 Slardar Reporter
2256
+ */
2257
+ const initSlardar = safeRun('initSlardar', () => {
2258
+ reporter.setup({
2259
+ bid: 'coze_codign_cli',
2260
+ release: packageJson.version,
2261
+ env: process.env.NODE_ENV || 'production',
2262
+ userId: process.env.USER,
2263
+ projectId: process.env.COZE_PROJECT_ID || '',
2264
+ useLocalConfig: false, // 启用服务端采样率配置
2265
+ domain: 'mon.zijieapi.com', // Node.js 环境上报域名
2266
+ });
2267
+
2268
+ // 设置全局上下文
2269
+ reporter.mergeContext({
2270
+ platform: process.platform,
2271
+ nodeVersion: process.version,
2272
+ cliVersion: packageJson.version,
2273
+ });
2274
+ });
2275
+
2276
+ /**
2277
+ * 上报命令执行
2278
+ */
2279
+ const reportCommandStart = safeRun(
2280
+ 'reportCommandStart',
2281
+ (
2282
+ command,
2283
+ args,
2284
+ extraCategories,
2285
+ ) => {
2286
+ const event = EventBuilder.cliCommand(command, {
2287
+ args,
2288
+ categories: extraCategories,
2289
+ });
2290
+ reporter.sendEvent(event.name, event.metrics, event.categories);
2291
+ },
2292
+ );
2293
+
2294
+ /**
2295
+ * 上报命令完成
2296
+ */
2297
+ const reportCommandComplete = safeRun(
2298
+ 'reportCommandComplete',
2299
+ (
2300
+ command,
2301
+ success,
2302
+ duration,
2303
+ options
2304
+
2305
+
2306
+
2307
+
2308
+ ,
2309
+ ) => {
2310
+ const event = EventBuilder.cliCommandComplete(command, success, duration, {
2311
+ args: _optionalChain$4([options, 'optionalAccess', _ => _.args]),
2312
+ errorCode: _optionalChain$4([options, 'optionalAccess', _2 => _2.errorCode]),
2313
+ categories: {
2314
+ ...(_optionalChain$4([options, 'optionalAccess', _3 => _3.errorMessage]) && { errorMessage: options.errorMessage }),
2315
+ ..._optionalChain$4([options, 'optionalAccess', _4 => _4.categories]),
2316
+ },
2317
+ });
2318
+ reporter.sendEvent(event.name, event.metrics, event.categories);
2319
+ },
2320
+ );
2321
+
2322
+ /**
2323
+ * 上报错误(使用 JS 错误上报,会显示在 Slardar JS 错误总览页面)
2324
+ */
2325
+ const reportError = safeRun(
2326
+ 'reportError',
2327
+ (error, context) => {
2328
+ reporter.reportError(error, context, {
2329
+ type: 'cli',
2330
+ data: context,
2331
+ });
2332
+ },
2333
+ );
2334
+
2335
+ /**
2336
+ * 立即上报(在 CLI 退出前调用)
2337
+ * 等待 500ms 确保设置请求完成和事件发送
2338
+ */
2339
+ const flushSlardar = safeRun('flushSlardar', async () => {
2340
+ await reporter.flush();
2341
+ });
2342
+
132
2343
  function _nullishCoalesce$2(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain$3(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var LogLevel; (function (LogLevel) {
133
2344
  const ERROR = 0; LogLevel[LogLevel["ERROR"] = ERROR] = "ERROR";
134
2345
  const WARN = 1; LogLevel[LogLevel["WARN"] = WARN] = "WARN";
@@ -143,459 +2354,823 @@ function _nullishCoalesce$2(lhs, rhsFn) { if (lhs != null) { return lhs; } else
143
2354
 
144
2355
 
145
2356
 
146
- const LOG_LEVEL_MAP = {
147
- error: LogLevel.ERROR,
148
- warn: LogLevel.WARN,
149
- success: LogLevel.SUCCESS,
150
- info: LogLevel.INFO,
151
- verbose: LogLevel.VERBOSE,
2357
+ const LOG_LEVEL_MAP = {
2358
+ error: LogLevel.ERROR,
2359
+ warn: LogLevel.WARN,
2360
+ success: LogLevel.SUCCESS,
2361
+ info: LogLevel.INFO,
2362
+ verbose: LogLevel.VERBOSE,
2363
+ };
2364
+
2365
+ const COLOR_CODES = {
2366
+ reset: '\x1b[0m',
2367
+ red: '\x1b[31m',
2368
+ yellow: '\x1b[33m',
2369
+ green: '\x1b[32m',
2370
+ cyan: '\x1b[36m',
2371
+ gray: '\x1b[90m',
2372
+ };
2373
+
2374
+ class Logger {
2375
+
2376
+
2377
+
2378
+
2379
+ constructor(options = {}) {
2380
+ this.level = this.parseLogLevel(options.level);
2381
+ this.useColor = _nullishCoalesce$2(options.useColor, () => ( this.isColorSupported()));
2382
+ this.prefix = options.prefix;
2383
+ }
2384
+
2385
+ parseLogLevel(level) {
2386
+ if (level !== undefined) {
2387
+ return level;
2388
+ }
2389
+
2390
+ const envLevel = _optionalChain$3([process, 'access', _ => _.env, 'access', _2 => _2.LOG_LEVEL, 'optionalAccess', _3 => _3.toLowerCase, 'call', _4 => _4()]);
2391
+ if (envLevel && envLevel in LOG_LEVEL_MAP) {
2392
+ return LOG_LEVEL_MAP[envLevel];
2393
+ }
2394
+
2395
+ return LogLevel.INFO;
2396
+ }
2397
+
2398
+ isColorSupported() {
2399
+ // 简单检测:Node.js 环境且支持 TTY
2400
+ return (
2401
+ typeof process !== 'undefined' &&
2402
+ _optionalChain$3([process, 'access', _5 => _5.stdout, 'optionalAccess', _6 => _6.isTTY]) === true &&
2403
+ process.env.NO_COLOR === undefined
2404
+ );
2405
+ }
2406
+
2407
+ colorize(text, color) {
2408
+ if (!this.useColor) {
2409
+ return text;
2410
+ }
2411
+ return `${COLOR_CODES[color]}${text}${COLOR_CODES.reset}`;
2412
+ }
2413
+
2414
+ log(options
2415
+
2416
+
2417
+
2418
+
2419
+
2420
+ ) {
2421
+ if (options.level > this.level) {
2422
+ return;
2423
+ }
2424
+
2425
+ const icon = this.colorize(options.icon, options.color);
2426
+ const prefix = this.prefix ? `${icon} ${this.prefix}` : icon;
2427
+ console.log(prefix, options.message, ...(_nullishCoalesce$2(options.args, () => ( []))));
2428
+ }
2429
+
2430
+ error(message, ...args) {
2431
+ this.log({
2432
+ level: LogLevel.ERROR,
2433
+ icon: '✖',
2434
+ color: 'red',
2435
+ message,
2436
+ args,
2437
+ });
2438
+ }
2439
+
2440
+ warn(message, ...args) {
2441
+ this.log({
2442
+ level: LogLevel.WARN,
2443
+ icon: '⚠',
2444
+ color: 'yellow',
2445
+ message,
2446
+ args,
2447
+ });
2448
+ }
2449
+
2450
+ success(message, ...args) {
2451
+ this.log({
2452
+ level: LogLevel.SUCCESS,
2453
+ icon: '✓',
2454
+ color: 'green',
2455
+ message,
2456
+ args,
2457
+ });
2458
+ }
2459
+
2460
+ info(message, ...args) {
2461
+ this.log({
2462
+ level: LogLevel.INFO,
2463
+ icon: 'ℹ',
2464
+ color: 'cyan',
2465
+ message,
2466
+ args,
2467
+ });
2468
+ }
2469
+
2470
+ verbose(message, ...args) {
2471
+ this.log({
2472
+ level: LogLevel.VERBOSE,
2473
+ icon: '→',
2474
+ color: 'gray',
2475
+ message,
2476
+ args,
2477
+ });
2478
+ }
2479
+ }
2480
+
2481
+ // 创建 logger 实例的工厂函数
2482
+ const createLogger = (options = {}) =>
2483
+ new Logger(options);
2484
+
2485
+ // 导出默认实例
2486
+ const logger = createLogger();
2487
+
2488
+ /**
2489
+ * 时间统计工具
2490
+ */
2491
+ class TimeTracker {
2492
+
2493
+
2494
+
2495
+ constructor() {
2496
+ this.startTime = perf_hooks.performance.now();
2497
+ this.lastTime = this.startTime;
2498
+ }
2499
+
2500
+ /**
2501
+ * 记录阶段耗时
2502
+ * @param phaseName 阶段名称
2503
+ */
2504
+ logPhase(phaseName) {
2505
+ const now = perf_hooks.performance.now();
2506
+ const phaseTime = now - this.lastTime;
2507
+ this.lastTime = now;
2508
+
2509
+ logger.verbose(`⏱ ${phaseName}: ${phaseTime.toFixed(2)}ms`);
2510
+ }
2511
+
2512
+ /**
2513
+ * 记录总耗时
2514
+ */
2515
+ logTotal() {
2516
+ const totalTime = perf_hooks.performance.now() - this.startTime;
2517
+ logger.verbose(`⏱ Total time: ${totalTime.toFixed(2)}ms`);
2518
+ }
2519
+
2520
+ /**
2521
+ * 获取当前耗时(不输出日志)
2522
+ * @returns 从开始到现在的总耗时(毫秒)
2523
+ */
2524
+ getElapsedTime() {
2525
+ return perf_hooks.performance.now() - this.startTime;
2526
+ }
2527
+ }
2528
+
2529
+ /**
2530
+ * 获取模板配置文件路径
2531
+ * @returns templates.json 的绝对路径
2532
+ */
2533
+ const getTemplatesConfigPath = () => {
2534
+ const configPath = path.resolve(getTemplatesDir(), 'templates.json');
2535
+ logger.verbose(`Templates config path: ${configPath}`);
2536
+ logger.verbose(`Config file exists: ${fs.existsSync(configPath)}`);
2537
+ return configPath;
152
2538
  };
153
2539
 
154
- const COLOR_CODES = {
155
- reset: '\x1b[0m',
156
- red: '\x1b[31m',
157
- yellow: '\x1b[33m',
158
- green: '\x1b[32m',
159
- cyan: '\x1b[36m',
160
- gray: '\x1b[90m',
2540
+ /**
2541
+ * 获取模板目录路径
2542
+ * @returns __templates__ 目录的绝对路径
2543
+ */
2544
+ const getTemplatesDir = () => {
2545
+ const templatesDir = path.resolve(__dirname, './__templates__');
2546
+ logger.verbose(`Templates directory: ${templatesDir}`);
2547
+ logger.verbose(`Templates directory exists: ${fs.existsSync(templatesDir)}`);
2548
+ return templatesDir;
161
2549
  };
162
2550
 
163
- class Logger {
164
-
165
-
166
-
2551
+ /**
2552
+ * 加载模板配置文件
2553
+ * 支持 .ts 和 .js 文件(通过 sucrase 注册)
2554
+ *
2555
+ * @param templatePath - 模板目录路径
2556
+ * @returns 模板配置对象
2557
+ */
167
2558
 
168
- constructor(options = {}) {
169
- this.level = this.parseLogLevel(options.level);
170
- this.useColor = _nullishCoalesce$2(options.useColor, () => ( this.isColorSupported()));
171
- this.prefix = options.prefix;
172
- }
2559
+ const loadTemplateConfig = async (
2560
+ templatePath,
2561
+ ) => {
2562
+ logger.verbose(`Loading template config from: ${templatePath}`);
173
2563
 
174
- parseLogLevel(level) {
175
- if (level !== undefined) {
176
- return level;
177
- }
2564
+ const tsConfigPath = path.join(templatePath, 'template.config.ts');
2565
+ const jsConfigPath = path.join(templatePath, 'template.config.js');
178
2566
 
179
- const envLevel = _optionalChain$3([process, 'access', _ => _.env, 'access', _2 => _2.LOG_LEVEL, 'optionalAccess', _3 => _3.toLowerCase, 'call', _4 => _4()]);
180
- if (envLevel && envLevel in LOG_LEVEL_MAP) {
181
- return LOG_LEVEL_MAP[envLevel];
182
- }
2567
+ logger.verbose('Checking for config files:');
2568
+ logger.verbose(` - TypeScript: ${tsConfigPath}`);
2569
+ logger.verbose(` - JavaScript: ${jsConfigPath}`);
183
2570
 
184
- return LogLevel.INFO;
2571
+ let configPath;
2572
+
2573
+ const [tsExists, jsExists] = await Promise.all([
2574
+ fs$1.access(tsConfigPath).then(
2575
+ () => true,
2576
+ () => false,
2577
+ ),
2578
+ fs$1.access(jsConfigPath).then(
2579
+ () => true,
2580
+ () => false,
2581
+ ),
2582
+ ]);
2583
+
2584
+ logger.verbose('Config file existence check:');
2585
+ logger.verbose(` - template.config.ts: ${tsExists}`);
2586
+ logger.verbose(` - template.config.js: ${jsExists}`);
2587
+
2588
+ if (tsExists) {
2589
+ configPath = tsConfigPath;
2590
+ } else if (jsExists) {
2591
+ configPath = jsConfigPath;
2592
+ } else {
2593
+ throw new Error(
2594
+ `Template config not found in ${templatePath}.\n` +
2595
+ 'Expected: template.config.ts or template.config.js',
2596
+ );
185
2597
  }
186
2598
 
187
- isColorSupported() {
188
- // 简单检测:Node.js 环境且支持 TTY
189
- return (
190
- typeof process !== 'undefined' &&
191
- _optionalChain$3([process, 'access', _5 => _5.stdout, 'optionalAccess', _6 => _6.isTTY]) === true &&
192
- process.env.NO_COLOR === undefined
2599
+ logger.verbose(`Using config file: ${configPath}`);
2600
+
2601
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, security/detect-non-literal-require -- Sucrase handles .ts files at runtime, path is validated above
2602
+ const config = require(configPath);
2603
+
2604
+ logger.verbose('Template config loaded successfully');
2605
+
2606
+ return config.default || config;
2607
+ };
2608
+
2609
+ /**
2610
+ * 加载模板列表配置
2611
+ *
2612
+ * @param configPath - templates.json 配置文件路径
2613
+ * @returns 模板列表配置
2614
+ */
2615
+ const loadTemplatesConfig = async (
2616
+ configPath,
2617
+ ) => {
2618
+ logger.verbose(`Loading templates config from: ${configPath}`);
2619
+
2620
+ const content = await fs$1.readFile(configPath, 'utf-8');
2621
+ // eslint-disable-next-line no-restricted-syntax -- Static config file loaded at build time, safeJsonParse not needed
2622
+ const config = JSON.parse(content) ;
2623
+
2624
+ logger.verbose(
2625
+ `Found ${config.templates.length} templates: ${config.templates.map(t => t.name).join(', ')}`,
2626
+ );
2627
+
2628
+ return config;
2629
+ };
2630
+
2631
+ /**
2632
+ * 根据模板名称查找模板元信息
2633
+ *
2634
+ * @param templatesConfig - 模板列表配置
2635
+ * @param templateName - 模板名称
2636
+ * @returns 模板元信息
2637
+ */
2638
+ const findTemplate = (
2639
+ templatesConfig,
2640
+ templateName,
2641
+ ) => {
2642
+ const template = templatesConfig.templates.find(t => t.name === templateName);
2643
+
2644
+ if (!template) {
2645
+ const availableTemplates = templatesConfig.templates
2646
+ .map(t => t.name)
2647
+ .join(', ');
2648
+ throw new Error(
2649
+ `Template "${templateName}" not found.\n` +
2650
+ `Available templates: ${availableTemplates}\n` +
2651
+ 'Use --template <name> to specify a template.',
193
2652
  );
194
2653
  }
195
2654
 
196
- colorize(text, color) {
197
- if (!this.useColor) {
198
- return text;
199
- }
200
- return `${COLOR_CODES[color]}${text}${COLOR_CODES.reset}`;
2655
+ return template;
2656
+ };
2657
+
2658
+ /**
2659
+ * 获取模板的完整路径
2660
+ *
2661
+ * @param basePath - 模板目录(templates.json 所在目录)
2662
+ * @param templateMetadata - 模板元信息
2663
+ * @returns 模板完整路径
2664
+ */
2665
+ const getTemplatePath = async (
2666
+ basePath,
2667
+ templateMetadata,
2668
+ ) => {
2669
+ logger.verbose('Resolving template path:');
2670
+ logger.verbose(` - Base path: ${basePath}`);
2671
+ logger.verbose(` - Template location: ${templateMetadata.location}`);
2672
+
2673
+ // location 是相对于 templates.json 文件的路径
2674
+ const templatePath = path.join(basePath, templateMetadata.location);
2675
+
2676
+ logger.verbose(` - Resolved path: ${templatePath}`);
2677
+
2678
+ try {
2679
+ await fs$1.access(templatePath);
2680
+ logger.verbose(' - Template directory exists: ✓');
2681
+ // eslint-disable-next-line @coze-arch/use-error-in-catch -- Error handling done in throw statement
2682
+ } catch (e) {
2683
+ logger.error(' - Template directory does not exist: ✗');
2684
+ throw new Error(`Template directory not found: ${templatePath}`);
2685
+ }
2686
+
2687
+ return templatePath;
2688
+ };
2689
+
2690
+ /**
2691
+ * 对单个模板执行 pnpm install
2692
+ */
2693
+ const warmupTemplate = (templatePath, templateName) => {
2694
+ logger.info(`\nWarming up template: ${templateName}`);
2695
+ logger.info(` Path: ${templatePath}`);
2696
+
2697
+ // 检查是否存在 package.json
2698
+ const packageJsonPath = node_path.join(templatePath, 'package.json');
2699
+ // eslint-disable-next-line security/detect-non-literal-fs-filename
2700
+ if (!node_fs.existsSync(packageJsonPath)) {
2701
+ logger.info(` ⊘ Skipping ${templateName} (no package.json found)`);
2702
+ return;
2703
+ }
2704
+
2705
+ const result = shelljs.exec('pnpm install', {
2706
+ cwd: templatePath,
2707
+ silent: true,
2708
+ });
2709
+
2710
+ // 输出 stdout
2711
+ if (result.stdout) {
2712
+ process.stdout.write(result.stdout);
2713
+ }
2714
+
2715
+ // 输出 stderr
2716
+ if (result.stderr) {
2717
+ process.stderr.write(result.stderr);
2718
+ }
2719
+
2720
+ if (result.code === 0) {
2721
+ logger.success(` ✓ ${templateName} warmed up successfully`);
2722
+ } else {
2723
+ const errorMessage = [
2724
+ `pnpm install failed for ${templateName} with exit code ${result.code}`,
2725
+ result.stderr ? `\nStderr:\n${result.stderr}` : '',
2726
+ result.stdout ? `\nStdout:\n${result.stdout}` : '',
2727
+ ]
2728
+ .filter(Boolean)
2729
+ .join('');
2730
+
2731
+ throw new Error(errorMessage);
201
2732
  }
2733
+ };
202
2734
 
203
- log(options
2735
+ /**
2736
+ * 执行 warmup 命令的内部实现
2737
+ */
2738
+ const executeWarmup = async (options) => {
2739
+ const timer = new TimeTracker();
2740
+
2741
+ try {
2742
+ const { template: templateFilter } = options;
204
2743
 
2744
+ logger.info('Starting template warmup...');
2745
+ timer.logPhase('Initialization');
205
2746
 
2747
+ // 加载模板配置
2748
+ const configPath = getTemplatesConfigPath();
2749
+ const templatesConfig = await loadTemplatesConfig(configPath);
206
2750
 
2751
+ logger.verbose(
2752
+ `Found ${templatesConfig.templates.length} templates in config`,
2753
+ );
207
2754
 
2755
+ // 过滤模板
2756
+ const templatesToWarmup = templateFilter
2757
+ ? templatesConfig.templates.filter(t => t.name === templateFilter)
2758
+ : templatesConfig.templates;
208
2759
 
209
- ) {
210
- if (options.level > this.level) {
2760
+ if (templatesToWarmup.length === 0) {
2761
+ if (templateFilter) {
2762
+ logger.warn(`Template "${templateFilter}" not found`);
2763
+ logger.info(
2764
+ `Available templates: ${templatesConfig.templates.map(t => t.name).join(', ')}`,
2765
+ );
2766
+ } else {
2767
+ logger.warn('No templates found');
2768
+ }
211
2769
  return;
212
2770
  }
213
2771
 
214
- const icon = this.colorize(options.icon, options.color);
215
- const prefix = this.prefix ? `${icon} ${this.prefix}` : icon;
216
- console.log(prefix, options.message, ...(_nullishCoalesce$2(options.args, () => ( []))));
217
- }
2772
+ logger.info(
2773
+ `\nWill warm up ${templatesToWarmup.length} template(s): ${templatesToWarmup.map(t => t.name).join(', ')}`,
2774
+ );
218
2775
 
219
- error(message, ...args) {
220
- this.log({
221
- level: LogLevel.ERROR,
222
- icon: '✖',
223
- color: 'red',
224
- message,
225
- args,
226
- });
227
- }
2776
+ // 获取模板基础路径
2777
+ const basePath = configPath.replace(/\/templates\.json$/, '');
228
2778
 
229
- warn(message, ...args) {
230
- this.log({
231
- level: LogLevel.WARN,
232
- icon: '⚠',
233
- color: 'yellow',
234
- message,
235
- args,
236
- });
237
- }
2779
+ // 对每个模板执行 pnpm install
2780
+ for (const templateMetadata of templatesToWarmup) {
2781
+ const templatePath = await getTemplatePath(basePath, templateMetadata);
2782
+ warmupTemplate(templatePath, templateMetadata.name);
2783
+ timer.logPhase(`Warmup ${templateMetadata.name}`);
2784
+ }
238
2785
 
239
- success(message, ...args) {
240
- this.log({
241
- level: LogLevel.SUCCESS,
242
- icon: '✓',
243
- color: 'green',
244
- message,
245
- args,
246
- });
247
- }
2786
+ logger.success('\n✅ All templates warmed up successfully!');
2787
+ logger.info(
2788
+ '\nNext time you run `coze init`, it will be much faster as node_modules are pre-installed.',
2789
+ );
248
2790
 
249
- info(message, ...args) {
250
- this.log({
251
- level: LogLevel.INFO,
252
- icon: 'ℹ',
253
- color: 'cyan',
254
- message,
255
- args,
256
- });
2791
+ timer.logTotal();
2792
+ } catch (error) {
2793
+ timer.logTotal();
2794
+ logger.error('Failed to warmup templates:');
2795
+ logger.error(error instanceof Error ? error.message : String(error));
2796
+ process.exit(1);
257
2797
  }
2798
+ };
258
2799
 
259
- verbose(message, ...args) {
260
- this.log({
261
- level: LogLevel.VERBOSE,
262
- icon: '→',
263
- color: 'gray',
264
- message,
265
- args,
2800
+ /**
2801
+ * 注册 warmup 命令到 program
2802
+ */
2803
+ const registerCommand$4 = program => {
2804
+ program
2805
+ .command('warmup')
2806
+ .description('Pre-install dependencies for templates to speed up init')
2807
+ .option('-t, --template <name>', 'Warmup a specific template only')
2808
+ .action(async options => {
2809
+ await executeWarmup(options);
266
2810
  });
267
- }
268
- }
2811
+ };
2812
+
2813
+ // ABOUTME: This file implements the update command for coze CLI
2814
+ // ABOUTME: It wraps pnpm update/install to update package dependencies with logging support
269
2815
 
270
- // 创建 logger 实例的工厂函数
271
- const createLogger = (options = {}) =>
272
- new Logger(options);
273
2816
 
274
- // 导出默认实例
275
- const logger = createLogger();
276
2817
 
277
2818
  /**
278
- * 时间统计工具
2819
+ * 日志文件名常量
279
2820
  */
280
- class TimeTracker {
281
-
282
-
283
-
284
- constructor() {
285
- this.startTime = perf_hooks.performance.now();
286
- this.lastTime = this.startTime;
287
- }
2821
+ const LOG_FILE_NAME$1 = 'update.log';
288
2822
 
289
- /**
290
- * 记录阶段耗时
291
- * @param phaseName 阶段名称
292
- */
293
- logPhase(phaseName) {
294
- const now = perf_hooks.performance.now();
295
- const phaseTime = now - this.lastTime;
296
- this.lastTime = now;
2823
+ /**
2824
+ * 获取日志目录
2825
+ * 优先使用环境变量 COZE_LOG_DIR,否则使用 ~/.coze-logs
2826
+ */
2827
+ const getLogDir$1 = () =>
2828
+ process.env.COZE_LOG_DIR || path.join(os.homedir(), '.coze-logs');
297
2829
 
298
- logger.verbose(`⏱ ${phaseName}: ${phaseTime.toFixed(2)}ms`);
2830
+ /**
2831
+ * 解析日志文件路径
2832
+ * - 如果是绝对路径,直接使用
2833
+ * - 如果是相对路径,基于 getLogDir() + 相对路径
2834
+ * - 如果为空,使用 getLogDir() + LOG_FILE_NAME
2835
+ */
2836
+ const resolveLogFilePath$1 = (logFile) => {
2837
+ if (!logFile) {
2838
+ return path.join(getLogDir$1(), LOG_FILE_NAME$1);
299
2839
  }
300
2840
 
301
- /**
302
- * 记录总耗时
303
- */
304
- logTotal() {
305
- const totalTime = perf_hooks.performance.now() - this.startTime;
306
- logger.verbose(`⏱ Total time: ${totalTime.toFixed(2)}ms`);
2841
+ if (path.isAbsolute(logFile)) {
2842
+ return logFile;
307
2843
  }
308
2844
 
309
- /**
310
- * 获取当前耗时(不输出日志)
311
- * @returns 从开始到现在的总耗时(毫秒)
312
- */
313
- getElapsedTime() {
314
- return perf_hooks.performance.now() - this.startTime;
2845
+ return path.join(getLogDir$1(), logFile);
2846
+ };
2847
+
2848
+ /**
2849
+ * 创建日志写入流
2850
+ */
2851
+ const createLogStream$1 = (logFilePath) => {
2852
+ const logDir = path.dirname(logFilePath);
2853
+
2854
+ // 确保日志目录存在
2855
+ if (!fs.existsSync(logDir)) {
2856
+ fs.mkdirSync(logDir, { recursive: true });
315
2857
  }
316
- }
2858
+
2859
+ // 使用 'w' 标志覆盖之前的日志
2860
+ return fs.createWriteStream(logFilePath, { flags: 'w' });
2861
+ };
317
2862
 
318
2863
  /**
319
- * 获取模板配置文件路径
320
- * @returns templates.json 的绝对路径
2864
+ * 格式化时间戳
321
2865
  */
322
- const getTemplatesConfigPath = () => {
323
- const configPath = path.resolve(getTemplatesDir(), 'templates.json');
324
- logger.verbose(`Templates config path: ${configPath}`);
325
- logger.verbose(`Config file exists: ${fs.existsSync(configPath)}`);
326
- return configPath;
2866
+ const formatTimestamp = () => {
2867
+ const now = new Date();
2868
+ return now.toISOString();
327
2869
  };
328
2870
 
329
2871
  /**
330
- * 获取模板目录路径
331
- * @returns __templates__ 目录的绝对路径
2872
+ * 写入带时间戳的日志
332
2873
  */
333
- const getTemplatesDir = () => {
334
- const templatesDir = path.resolve(__dirname, './__templates__');
335
- logger.verbose(`Templates directory: ${templatesDir}`);
336
- logger.verbose(`Templates directory exists: ${fs.existsSync(templatesDir)}`);
337
- return templatesDir;
2874
+ const writeLogWithTimestamp = (stream, message) => {
2875
+ const timestamp = formatTimestamp();
2876
+ const lines = message.split('\n');
2877
+ lines.forEach(line => {
2878
+ if (line) {
2879
+ stream.write(`[${timestamp}] ${line}\n`);
2880
+ } else {
2881
+ stream.write('\n');
2882
+ }
2883
+ });
2884
+ // 确保数据写入磁盘
2885
+ stream.uncork();
2886
+ };
2887
+
2888
+ /**
2889
+ * 同时输出到控制台和日志文件
2890
+ */
2891
+ const logWithFile = (
2892
+ stream,
2893
+ level,
2894
+ message,
2895
+ ) => {
2896
+ // 输出到控制台
2897
+ switch (level) {
2898
+ case 'info':
2899
+ logger.info(message);
2900
+ break;
2901
+ case 'success':
2902
+ logger.success(message);
2903
+ break;
2904
+ case 'error':
2905
+ logger.error(message);
2906
+ break;
2907
+ default:
2908
+ logger.info(message);
2909
+ break;
2910
+ }
2911
+
2912
+ // 写入日志文件(带时间戳)
2913
+ writeLogWithTimestamp(stream, `[${level.toUpperCase()}] ${message}`);
338
2914
  };
339
2915
 
2916
+ // start_aigc
340
2917
  /**
341
- * 加载模板配置文件
342
- * 支持 .ts 和 .js 文件(通过 sucrase 注册)
343
- *
344
- * @param templatePath - 模板目录路径
345
- * @returns 模板配置对象
2918
+ * 构建 pnpm add 命令
346
2919
  */
2920
+ const buildPnpmCommand = (
2921
+ packageName,
2922
+ options
347
2923
 
348
- const loadTemplateConfig = async (
349
- templatePath,
350
- ) => {
351
- logger.verbose(`Loading template config from: ${templatePath}`);
352
2924
 
353
- const tsConfigPath = path.join(templatePath, 'template.config.ts');
354
- const jsConfigPath = path.join(templatePath, 'template.config.js');
355
2925
 
356
- logger.verbose('Checking for config files:');
357
- logger.verbose(` - TypeScript: ${tsConfigPath}`);
358
- logger.verbose(` - JavaScript: ${jsConfigPath}`);
359
2926
 
360
- let configPath;
2927
+ ,
2928
+ ) => {
2929
+ const { global, version, registry, extraArgs } = options;
361
2930
 
362
- const [tsExists, jsExists] = await Promise.all([
363
- fs$1.access(tsConfigPath).then(
364
- () => true,
365
- () => false,
366
- ),
367
- fs$1.access(jsConfigPath).then(
368
- () => true,
369
- () => false,
370
- ),
371
- ]);
2931
+ const parts = ['pnpm', 'add'];
372
2932
 
373
- logger.verbose('Config file existence check:');
374
- logger.verbose(` - template.config.ts: ${tsExists}`);
375
- logger.verbose(` - template.config.js: ${jsExists}`);
2933
+ // 添加全局标记
2934
+ if (global) {
2935
+ parts.push('-g');
2936
+ }
376
2937
 
377
- if (tsExists) {
378
- configPath = tsConfigPath;
379
- } else if (jsExists) {
380
- configPath = jsConfigPath;
2938
+ // 添加包名和版本
2939
+ if (version && version !== 'latest') {
2940
+ parts.push(`${packageName}@${version}`);
381
2941
  } else {
382
- throw new Error(
383
- `Template config not found in ${templatePath}.\n` +
384
- 'Expected: template.config.ts or template.config.js',
385
- );
2942
+ parts.push(`${packageName}@latest`);
386
2943
  }
387
2944
 
388
- logger.verbose(`Using config file: ${configPath}`);
389
-
390
- // eslint-disable-next-line @typescript-eslint/no-require-imports, security/detect-non-literal-require -- Sucrase handles .ts files at runtime, path is validated above
391
- const config = require(configPath);
2945
+ // 添加 registry
2946
+ if (registry) {
2947
+ parts.push(`--registry=${registry}`);
2948
+ }
392
2949
 
393
- logger.verbose('Template config loaded successfully');
2950
+ // 添加额外参数
2951
+ if (extraArgs.length > 0) {
2952
+ parts.push(...extraArgs);
2953
+ }
394
2954
 
395
- return config.default || config;
2955
+ return parts.join(' ');
396
2956
  };
2957
+ // end_aigc
397
2958
 
398
2959
  /**
399
- * 加载模板列表配置
400
- *
401
- * @param configPath - templates.json 配置文件路径
402
- * @returns 模板列表配置
2960
+ * 处理更新失败的错误
403
2961
  */
404
- const loadTemplatesConfig = async (
405
- configPath,
2962
+ const handleUpdateError = (
2963
+ error,
2964
+ packageName,
2965
+ options,
2966
+ cmdStartTime,
2967
+ logStream,
406
2968
  ) => {
407
- logger.verbose(`Loading templates config from: ${configPath}`);
408
-
409
- const content = await fs$1.readFile(configPath, 'utf-8');
410
- // eslint-disable-next-line no-restricted-syntax -- Static config file loaded at build time, safeJsonParse not needed
411
- const config = JSON.parse(content) ;
412
-
413
- logger.verbose(
414
- `Found ${config.templates.length} templates: ${config.templates.map(t => t.name).join(', ')}`,
415
- );
2969
+ const err = error instanceof Error ? error : new Error(String(error));
2970
+ logger.error('Failed to update package:');
2971
+ logger.error(err.message);
2972
+
2973
+ // 上报错误
2974
+ reportError(err, {
2975
+ command: 'update',
2976
+ packageName,
2977
+ type: 'execution_error',
2978
+ });
2979
+ reportCommandComplete('update', false, Date.now() - cmdStartTime, {
2980
+ args: JSON.stringify(
2981
+ typeof options === 'object' && options !== null
2982
+ ? { packageName, ...options }
2983
+ : { packageName },
2984
+ ),
2985
+ errorCode: 1,
2986
+ errorMessage: err.message,
2987
+ });
416
2988
 
417
- return config;
2989
+ // 写入错误到日志文件
2990
+ if (logStream) {
2991
+ writeLogWithTimestamp(logStream, `[ERROR] ${err.message}`);
2992
+ // 等待流关闭后再退出
2993
+ logStream.end(() => {
2994
+ flushSlardar()
2995
+ .then(() => {
2996
+ process.exit(1);
2997
+ })
2998
+ .catch(() => {
2999
+ // Catch any errors in the promise chain to prevent unhandled rejections
3000
+ process.exit(1);
3001
+ });
3002
+ });
3003
+ } else {
3004
+ flushSlardar()
3005
+ .then(() => {
3006
+ process.exit(1);
3007
+ })
3008
+ .catch(() => {
3009
+ // Catch any errors in the promise chain to prevent unhandled rejections
3010
+ process.exit(1);
3011
+ });
3012
+ }
418
3013
  };
419
3014
 
3015
+ // start_aigc
420
3016
  /**
421
- * 根据模板名称查找模板元信息
422
- *
423
- * @param templatesConfig - 模板列表配置
424
- * @param templateName - 模板名称
425
- * @returns 模板元信息
3017
+ * 执行 update 命令的内部实现
426
3018
  */
427
- const findTemplate = (
428
- templatesConfig,
429
- templateName,
430
- ) => {
431
- const template = templatesConfig.templates.find(t => t.name === templateName);
432
-
433
- if (!template) {
434
- const availableTemplates = templatesConfig.templates
435
- .map(t => t.name)
436
- .join(', ');
437
- throw new Error(
438
- `Template "${templateName}" not found.\n` +
439
- `Available templates: ${availableTemplates}\n` +
440
- 'Use --template <name> to specify a template.',
441
- );
442
- }
3019
+ const executeUpdate = (
3020
+ packageName,
3021
+ options
443
3022
 
444
- return template;
445
- };
446
3023
 
447
- /**
448
- * 获取模板的完整路径
449
- *
450
- * @param basePath - 模板目录(templates.json 所在目录)
451
- * @param templateMetadata - 模板元信息
452
- * @returns 模板完整路径
453
- */
454
- const getTemplatePath = async (
455
- basePath,
456
- templateMetadata,
457
- ) => {
458
- logger.verbose('Resolving template path:');
459
- logger.verbose(` - Base path: ${basePath}`);
460
- logger.verbose(` - Template location: ${templateMetadata.location}`);
461
3024
 
462
- // location 是相对于 templates.json 文件的路径
463
- const templatePath = path.join(basePath, templateMetadata.location);
464
3025
 
465
- logger.verbose(` - Resolved path: ${templatePath}`);
466
3026
 
467
- try {
468
- await fs$1.access(templatePath);
469
- logger.verbose(' - Template directory exists: ✓');
470
- // eslint-disable-next-line @coze-arch/use-error-in-catch -- Error handling done in throw statement
471
- } catch (e) {
472
- logger.error(' - Template directory does not exist: ✗');
473
- throw new Error(`Template directory not found: ${templatePath}`);
474
- }
475
3027
 
476
- return templatePath;
477
- };
3028
+ ,
3029
+ ) => {
3030
+ const cmdStartTime = Date.now();
3031
+ let logStream = null;
478
3032
 
479
- /**
480
- * 对单个模板执行 pnpm install
481
- */
482
- const warmupTemplate = (templatePath, templateName) => {
483
- logger.info(`\nWarming up template: ${templateName}`);
484
- logger.info(` Path: ${templatePath}`);
3033
+ try {
3034
+ const { global, cwd, version, registry, logFile, extraArgs } = options;
485
3035
 
486
- // 检查是否存在 package.json
487
- const packageJsonPath = node_path.join(templatePath, 'package.json');
488
- // eslint-disable-next-line security/detect-non-literal-fs-filename
489
- if (!node_fs.existsSync(packageJsonPath)) {
490
- logger.info(` ⊘ Skipping ${templateName} (no package.json found)`);
491
- return;
492
- }
3036
+ // 上报命令开始
3037
+ reportCommandStart('update', JSON.stringify({ packageName, ...options }));
493
3038
 
494
- const result = shelljs.exec('pnpm install', {
495
- cwd: templatePath,
496
- silent: true,
497
- });
3039
+ // 准备日志
3040
+ const logFilePath = resolveLogFilePath$1(logFile);
498
3041
 
499
- // 输出 stdout
500
- if (result.stdout) {
501
- process.stdout.write(result.stdout);
502
- }
3042
+ // 调试:确认日志路径
3043
+ logger.info(`Log file path resolved to: ${logFilePath}`);
503
3044
 
504
- // 输出 stderr
505
- if (result.stderr) {
506
- process.stderr.write(result.stderr);
507
- }
3045
+ logStream = createLogStream$1(logFilePath);
508
3046
 
509
- if (result.code === 0) {
510
- logger.success(` ✓ ${templateName} warmed up successfully`);
511
- } else {
512
- const errorMessage = [
513
- `pnpm install failed for ${templateName} with exit code ${result.code}`,
514
- result.stderr ? `\nStderr:\n${result.stderr}` : '',
515
- result.stdout ? `\nStdout:\n${result.stdout}` : '',
516
- ]
517
- .filter(Boolean)
518
- .join('');
3047
+ // 调试:确认流已创建
3048
+ logger.info('Log stream created successfully');
519
3049
 
520
- throw new Error(errorMessage);
521
- }
522
- };
3050
+ logWithFile(logStream, 'info', `Updating package: ${packageName}`);
523
3051
 
524
- /**
525
- * 执行 warmup 命令的内部实现
526
- */
527
- const executeWarmup = async (options) => {
528
- const timer = new TimeTracker();
3052
+ // 构建命令
3053
+ const command = buildPnpmCommand(packageName, {
3054
+ global,
3055
+ version,
3056
+ registry,
3057
+ extraArgs,
3058
+ });
529
3059
 
530
- try {
531
- const { template: templateFilter } = options;
3060
+ // 确定工作目录
3061
+ const workingDir = cwd
3062
+ ? path.isAbsolute(cwd)
3063
+ ? cwd
3064
+ : path.join(process.cwd(), cwd)
3065
+ : process.cwd();
532
3066
 
533
- logger.info('Starting template warmup...');
534
- timer.logPhase('Initialization');
3067
+ logWithFile(logStream, 'info', `Executing: ${command}`);
3068
+ logWithFile(logStream, 'info', `Working directory: ${workingDir}`);
3069
+ logWithFile(logStream, 'info', `Log file: ${logFilePath}`);
535
3070
 
536
- // 加载模板配置
537
- const configPath = getTemplatesConfigPath();
538
- const templatesConfig = await loadTemplatesConfig(configPath);
3071
+ // 记录命令开始时间
3072
+ writeLogWithTimestamp(logStream, '--- Command execution started ---');
539
3073
 
540
- logger.verbose(
541
- `Found ${templatesConfig.templates.length} templates in config`,
542
- );
3074
+ // 同步执行命令
3075
+ const result = shelljs.exec(command, {
3076
+ cwd: workingDir,
3077
+ silent: true, // 使用 silent 来捕获输出
3078
+ });
543
3079
 
544
- // 过滤模板
545
- const templatesToWarmup = templateFilter
546
- ? templatesConfig.templates.filter(t => t.name === templateFilter)
547
- : templatesConfig.templates;
3080
+ // 将输出写入控制台和日志文件(带时间戳)
3081
+ if (result.stdout) {
3082
+ process.stdout.write(result.stdout);
3083
+ writeLogWithTimestamp(logStream, result.stdout.trim());
3084
+ }
548
3085
 
549
- if (templatesToWarmup.length === 0) {
550
- if (templateFilter) {
551
- logger.warn(`Template "${templateFilter}" not found`);
552
- logger.info(
553
- `Available templates: ${templatesConfig.templates.map(t => t.name).join(', ')}`,
554
- );
555
- } else {
556
- logger.warn('No templates found');
557
- }
558
- return;
3086
+ if (result.stderr) {
3087
+ process.stderr.write(result.stderr);
3088
+ writeLogWithTimestamp(logStream, result.stderr.trim());
559
3089
  }
560
3090
 
561
- logger.info(
562
- `\nWill warm up ${templatesToWarmup.length} template(s): ${templatesToWarmup.map(t => t.name).join(', ')}`,
563
- );
3091
+ // 记录命令结束时间
3092
+ writeLogWithTimestamp(logStream, '--- Command execution ended ---');
564
3093
 
565
- // 获取模板基础路径
566
- const basePath = configPath.replace(/\/templates\.json$/, '');
3094
+ // 检查执行结果并记录到日志
3095
+ if (result.code === 0) {
3096
+ logWithFile(logStream, 'success', 'Package updated successfully');
3097
+ logWithFile(logStream, 'info', `Log file: ${logFilePath}`);
567
3098
 
568
- // 对每个模板执行 pnpm install
569
- for (const templateMetadata of templatesToWarmup) {
570
- const templatePath = await getTemplatePath(basePath, templateMetadata);
571
- warmupTemplate(templatePath, templateMetadata.name);
572
- timer.logPhase(`Warmup ${templateMetadata.name}`);
573
- }
3099
+ // 上报命令成功
3100
+ reportCommandComplete('update', true, Date.now() - cmdStartTime, {
3101
+ args: JSON.stringify({ packageName, ...options }),
3102
+ });
3103
+ // flush 由 main 函数统一处理
3104
+ } else {
3105
+ const errorMessage = `Command exited with code ${result.code}`;
3106
+ logWithFile(logStream, 'error', errorMessage);
3107
+ logWithFile(
3108
+ logStream,
3109
+ 'error',
3110
+ `Check log file for details: ${logFilePath}`,
3111
+ );
574
3112
 
575
- logger.success('\n✅ All templates warmed up successfully!');
576
- logger.info(
577
- '\nNext time you run `coze init`, it will be much faster as node_modules are pre-installed.',
578
- );
3113
+ // 上报命令失败
3114
+ reportError(new Error(errorMessage), {
3115
+ command: 'update',
3116
+ packageName,
3117
+ exitCode: String(result.code),
3118
+ });
3119
+ reportCommandComplete('update', false, Date.now() - cmdStartTime, {
3120
+ args: JSON.stringify({ packageName, ...options }),
3121
+ errorCode: result.code || 1,
3122
+ errorMessage,
3123
+ });
3124
+ }
579
3125
 
580
- timer.logTotal();
3126
+ // 关闭日志流并等待写入完成
3127
+ logStream.end(() => {
3128
+ // 流关闭后再退出进程
3129
+ if (result.code !== 0) {
3130
+ // flush 后再退出
3131
+ flushSlardar()
3132
+ .then(() => {
3133
+ process.exit(result.code || 1);
3134
+ })
3135
+ .catch(() => {
3136
+ // Catch any errors in the promise chain to prevent unhandled rejections
3137
+ process.exit(result.code || 1);
3138
+ });
3139
+ }
3140
+ // 成功时 flush 由 main 函数统一处理
3141
+ });
581
3142
  } catch (error) {
582
- timer.logTotal();
583
- logger.error('Failed to warmup templates:');
584
- logger.error(error instanceof Error ? error.message : String(error));
585
- process.exit(1);
3143
+ handleUpdateError(error, packageName, options, cmdStartTime, logStream);
586
3144
  }
587
3145
  };
3146
+ // end_aigc
588
3147
 
589
3148
  /**
590
- * 注册 warmup 命令到 program
3149
+ * 注册 update 命令到 program
591
3150
  */
592
- const registerCommand$4 = program => {
3151
+ const registerCommand$3 = program => {
593
3152
  program
594
- .command('warmup')
595
- .description('Pre-install dependencies for templates to speed up init')
596
- .option('-t, --template <name>', 'Warmup a specific template only')
597
- .action(async options => {
598
- await executeWarmup(options);
3153
+ .command('update <package>')
3154
+ .description('Update a package dependency')
3155
+ .option('-g, --global', 'Update package globally', false)
3156
+ .option('-c, --cwd <path>', 'Working directory for the update')
3157
+ .option(
3158
+ '--to <version>',
3159
+ 'Version to update to (default: latest)',
3160
+ 'latest',
3161
+ )
3162
+ .option('--registry <url>', 'Registry URL to use for the update')
3163
+ .option('--log-file <path>', 'Log file path')
3164
+ .allowUnknownOption() // 允许透传参数给 pnpm
3165
+ .action((packageName, options, command) => {
3166
+ // 收集所有未知选项作为额外参数
3167
+ const extraArgs = command.args.slice(1);
3168
+
3169
+ executeUpdate(packageName, {
3170
+ ...options,
3171
+ version: options.to, // 将 --to 映射到 version
3172
+ extraArgs,
3173
+ });
599
3174
  });
600
3175
  };
601
3176
 
@@ -979,7 +3554,9 @@ const rules = [
979
3554
  /**
980
3555
  * 执行 fix 命令的内部实现
981
3556
  */
982
- const executeFix = async (options = {}) => {
3557
+ const executeFix = async (
3558
+ options = {},
3559
+ ) => {
983
3560
  try {
984
3561
  const cwd = process.cwd();
985
3562
  const projectFolder = options.directory
@@ -1037,7 +3614,7 @@ const executeFix = async (options = {}) => {
1037
3614
  /**
1038
3615
  * 注册 fix 命令到 program
1039
3616
  */
1040
- const registerCommand$3 = program => {
3617
+ const registerCommand$2 = program => {
1041
3618
  program
1042
3619
  .command('fix')
1043
3620
  .description(
@@ -1056,13 +3633,13 @@ function _nullishCoalesce$1(lhs, rhsFn) { if (lhs != null) { return lhs; } else
1056
3633
  /**
1057
3634
  * 日志文件名常量
1058
3635
  */
1059
- const LOG_FILE_NAME$1 = 'dev.log';
3636
+ const LOG_FILE_NAME = 'dev.log';
1060
3637
 
1061
3638
  /**
1062
3639
  * 获取日志目录
1063
3640
  * 优先使用环境变量 COZE_LOG_DIR,否则使用 ~/.coze-logs
1064
3641
  */
1065
- const getLogDir$1 = () =>
3642
+ const getLogDir = () =>
1066
3643
  process.env.COZE_LOG_DIR || path.join(os.homedir(), '.coze-logs');
1067
3644
 
1068
3645
  /**
@@ -1071,22 +3648,22 @@ const getLogDir$1 = () =>
1071
3648
  * - 如果是相对路径,基于 getLogDir() + 相对路径
1072
3649
  * - 如果为空,使用 getLogDir() + LOG_FILE_NAME
1073
3650
  */
1074
- const resolveLogFilePath$1 = (logFile) => {
3651
+ const resolveLogFilePath = (logFile) => {
1075
3652
  if (!logFile) {
1076
- return path.join(getLogDir$1(), LOG_FILE_NAME$1);
3653
+ return path.join(getLogDir(), LOG_FILE_NAME);
1077
3654
  }
1078
3655
 
1079
3656
  if (path.isAbsolute(logFile)) {
1080
3657
  return logFile;
1081
3658
  }
1082
3659
 
1083
- return path.join(getLogDir$1(), logFile);
3660
+ return path.join(getLogDir(), logFile);
1084
3661
  };
1085
3662
 
1086
3663
  /**
1087
3664
  * 创建日志写入流
1088
3665
  */
1089
- const createLogStream$1 = (logFilePath) => {
3666
+ const createLogStream = (logFilePath) => {
1090
3667
  const logDir = path.dirname(logFilePath);
1091
3668
 
1092
3669
  // 确保日志目录存在
@@ -1101,13 +3678,19 @@ const createLogStream$1 = (logFilePath) => {
1101
3678
  /**
1102
3679
  * 执行命令的内部实现
1103
3680
  */
3681
+ // eslint-disable-next-line @coze-arch/max-line-per-function, max-lines-per-function
1104
3682
  const executeRun = async (
1105
3683
  commandName,
1106
3684
  options = {},
1107
3685
  ) => {
3686
+ const cmdStartTime = Date.now();
3687
+
1108
3688
  try {
1109
3689
  logger.info(`Running ${commandName} command...`);
1110
3690
 
3691
+ // 上报命令开始
3692
+ reportCommandStart(commandName, JSON.stringify(options));
3693
+
1111
3694
  // 1. 对于 build 命令,先执行 fix 以确保项目配置正确
1112
3695
  if (['dev', 'build'].includes(commandName)) {
1113
3696
  logger.info('\n🔧 Running fix command before build...\n');
@@ -1125,8 +3708,8 @@ const executeRun = async (
1125
3708
  const commandArgs = getCommandConfig(config, commandName);
1126
3709
 
1127
3710
  // 3. 准备日志
1128
- const logFilePath = resolveLogFilePath$1(options.logFile);
1129
- const logStream = createLogStream$1(logFilePath);
3711
+ const logFilePath = resolveLogFilePath(options.logFile);
3712
+ const logStream = createLogStream(logFilePath);
1130
3713
 
1131
3714
  // 4. 执行命令
1132
3715
  const commandString = commandArgs.join(' ');
@@ -1159,14 +3742,39 @@ const executeRun = async (
1159
3742
  logStream.end();
1160
3743
 
1161
3744
  if (code !== 0) {
1162
- logger.error(
1163
- `Command exited with code ${_nullishCoalesce$1(code, () => ( 'unknown'))}${signal ? ` and signal ${signal}` : ''}`,
1164
- );
3745
+ const errorMessage = `Command exited with code ${_nullishCoalesce$1(code, () => ( 'unknown'))}${signal ? ` and signal ${signal}` : ''}`;
3746
+ logger.error(errorMessage);
1165
3747
  logger.error(`Check log file for details: ${logFilePath}`);
1166
- process.exit(code || 1);
3748
+
3749
+ // 上报命令失败
3750
+ reportError(new Error(errorMessage), {
3751
+ command: commandName,
3752
+ exitCode: String(_nullishCoalesce$1(code, () => ( 'unknown'))),
3753
+ signal: _nullishCoalesce$1(signal, () => ( 'none')),
3754
+ logFile: logFilePath,
3755
+ });
3756
+ reportCommandComplete(commandName, false, Date.now() - cmdStartTime, {
3757
+ args: JSON.stringify(options),
3758
+ errorCode: _nullishCoalesce$1(code, () => ( 1)),
3759
+ errorMessage,
3760
+ });
3761
+ flushSlardar()
3762
+ .then(() => {
3763
+ process.exit(code || 1);
3764
+ })
3765
+ .catch(() => {
3766
+ // Catch any errors in the promise chain to prevent unhandled rejections
3767
+ process.exit(code || 1);
3768
+ });
1167
3769
  } else {
1168
3770
  logger.success('Command completed successfully');
1169
3771
  logger.info(`Log file: ${logFilePath}`);
3772
+
3773
+ // 上报命令成功
3774
+ reportCommandComplete(commandName, true, Date.now() - cmdStartTime, {
3775
+ args: JSON.stringify(options),
3776
+ });
3777
+ // flush 由 main 函数统一处理
1170
3778
  }
1171
3779
  });
1172
3780
 
@@ -1177,19 +3785,56 @@ const executeRun = async (
1177
3785
  logger.error(`Stack trace:\n${error.stack}`);
1178
3786
  }
1179
3787
  logStream.end();
1180
- process.exit(1);
3788
+
3789
+ // 上报错误
3790
+ reportError(error, {
3791
+ command: commandName,
3792
+ type: 'child_process_error',
3793
+ });
3794
+ reportCommandComplete(commandName, false, Date.now() - cmdStartTime, {
3795
+ args: JSON.stringify(options),
3796
+ errorCode: 1,
3797
+ errorMessage: error.message,
3798
+ });
3799
+ flushSlardar()
3800
+ .then(() => {
3801
+ process.exit(1);
3802
+ })
3803
+ .catch(() => {
3804
+ // Catch any errors in the promise chain to prevent unhandled rejections
3805
+ process.exit(1);
3806
+ });
1181
3807
  });
1182
3808
  } catch (error) {
3809
+ const err = error instanceof Error ? error : new Error(String(error));
1183
3810
  logger.error(`Failed to run ${commandName} command:`);
1184
- logger.error(error instanceof Error ? error.message : String(error));
1185
- process.exit(1);
3811
+ logger.error(err.message);
3812
+
3813
+ // 上报错误
3814
+ reportError(err, {
3815
+ command: commandName,
3816
+ type: 'execution_error',
3817
+ });
3818
+ reportCommandComplete(commandName, false, Date.now() - cmdStartTime, {
3819
+ args: JSON.stringify(options),
3820
+ errorCode: 1,
3821
+ errorMessage: err.message,
3822
+ });
3823
+ flushSlardar()
3824
+ .then(() => {
3825
+ process.exit(1);
3826
+ })
3827
+ .catch(() => {
3828
+ // Catch any errors in the promise chain to prevent unhandled rejections
3829
+ process.exit(1);
3830
+ });
1186
3831
  }
1187
3832
  };
1188
3833
 
1189
3834
  /**
1190
3835
  * 注册 dev/build/start 命令到 program
1191
3836
  */
1192
- const registerCommand$2 = program => {
3837
+ const registerCommand$1 = program => {
1193
3838
  // dev 命令
1194
3839
  program
1195
3840
  .command('dev')
@@ -1521,12 +4166,6 @@ const convertDotfileName = (filePath) => {
1521
4166
  // ABOUTME: Handles file content rendering, hook execution, and file writing
1522
4167
 
1523
4168
 
1524
-
1525
-
1526
-
1527
-
1528
-
1529
-
1530
4169
  // start_aigc
1531
4170
  /**
1532
4171
  * 执行文件渲染钩子
@@ -1677,11 +4316,6 @@ const writeRenderedFile = async (options
1677
4316
  // ABOUTME: Provides dry-run file collection and conflict checking
1678
4317
 
1679
4318
 
1680
-
1681
-
1682
-
1683
-
1684
-
1685
4319
  // start_aigc
1686
4320
  /**
1687
4321
  * 收集所有将要写入的文件路径(dry-run 阶段)
@@ -1722,13 +4356,44 @@ const collectFilesToRender = async (options
1722
4356
 
1723
4357
  return filesToWrite;
1724
4358
  };
4359
+
4360
+ /**
4361
+ * 检测文件冲突
4362
+ *
4363
+ * @param outputPath - 输出目录路径
4364
+ * @param filesToWrite - 将要写入的文件路径列表
4365
+ * @returns 冲突的文件路径列表
4366
+ */
4367
+ const detectFileConflicts = (
4368
+ outputPath,
4369
+ filesToWrite,
4370
+ ) => {
4371
+ logger.verbose('\nChecking for file conflicts...');
4372
+
4373
+ const conflicts = [];
4374
+
4375
+ for (const file of filesToWrite) {
4376
+ const fullPath = path.join(outputPath, file);
4377
+ if (fs.existsSync(fullPath)) {
4378
+ conflicts.push(file);
4379
+ logger.verbose(` ⚠ Conflict detected: ${file}`);
4380
+ }
4381
+ }
4382
+
4383
+ if (conflicts.length === 0) {
4384
+ logger.verbose(' ✓ No conflicts detected');
4385
+ } else {
4386
+ logger.verbose(` ⚠ ${conflicts.length} conflicts detected`);
4387
+ }
4388
+
4389
+ return conflicts;
4390
+ };
1725
4391
  // end_aigc
1726
4392
 
1727
4393
  // ABOUTME: Main file processing orchestration for template rendering
1728
4394
  // ABOUTME: Coordinates file system, rendering, and conflict detection layers
1729
4395
 
1730
4396
 
1731
-
1732
4397
  // start_aigc
1733
4398
  /**
1734
4399
  * 处理单个文件(准备 + 写入)
@@ -1790,7 +4455,7 @@ const processTemplateFiles = async (options
1790
4455
 
1791
4456
 
1792
4457
  ) => {
1793
- const { templatePath, outputPath, context, templateConfig} = options;
4458
+ const { templatePath, outputPath, context, templateConfig, force } = options;
1794
4459
  logger.verbose('Processing template files:');
1795
4460
  logger.verbose(` - Template path: ${templatePath}`);
1796
4461
  logger.verbose(` - Output path: ${outputPath}`);
@@ -1822,7 +4487,7 @@ const processTemplateFiles = async (options
1822
4487
  }
1823
4488
 
1824
4489
  // 阶段 2: Dry-run - 收集所有将要写入的文件
1825
- await collectFilesToRender({
4490
+ const filesToWrite = await collectFilesToRender({
1826
4491
  files,
1827
4492
  templatePath,
1828
4493
  context,
@@ -1830,7 +4495,20 @@ const processTemplateFiles = async (options
1830
4495
  });
1831
4496
 
1832
4497
  // 阶段 3: 冲突检测(force 为 true 时跳过)
1833
- {
4498
+ if (!force) {
4499
+ const conflicts = detectFileConflicts(outputPath, filesToWrite);
4500
+
4501
+ if (conflicts.length > 0) {
4502
+ // 有冲突,抛出详细的错误信息
4503
+ const conflictList = conflicts.map(f => ` - ${f}`).join('\n');
4504
+ throw new Error(
4505
+ `File conflicts detected in output directory: ${outputPath}\n\n` +
4506
+ `The following files already exist and would be overwritten:\n${conflictList}\n\n` +
4507
+ 'Please remove these files or use a different output directory.\n' +
4508
+ 'Or use --force to overwrite existing files.',
4509
+ );
4510
+ }
4511
+ } else {
1834
4512
  logger.verbose(
1835
4513
  ' - Force mode enabled, skipping conflict detection. Existing files will be overwritten.',
1836
4514
  );
@@ -1975,7 +4653,7 @@ const prepareOutputDirectory = (outputPath) => {
1975
4653
  const execute = async (
1976
4654
  options,
1977
4655
  ) => {
1978
- const { templateName, outputPath, command} = options;
4656
+ const { templateName, outputPath, command, force } = options;
1979
4657
 
1980
4658
  // 1. 加载模板
1981
4659
  const { templatePath } = await loadTemplateMetadata(templateName);
@@ -1999,7 +4677,9 @@ const execute = async (
1999
4677
  templatePath,
2000
4678
  outputPath: absoluteOutputPath,
2001
4679
  context,
2002
- templateConfig});
4680
+ templateConfig,
4681
+ force,
4682
+ });
2003
4683
 
2004
4684
  // 7. 执行 onAfterRender 钩子
2005
4685
  await executeAfterRenderHook(templateConfig, context, absoluteOutputPath);
@@ -2089,6 +4769,7 @@ const runGitCommand = (command, projectPath) => {
2089
4769
  const runGitInit = (projectPath) => {
2090
4770
  // 检查是否已存在 .git 目录
2091
4771
  const gitDir = path.join(projectPath, '.git');
4772
+
2092
4773
  if (fs.existsSync(gitDir)) {
2093
4774
  logger.info(
2094
4775
  '\n💡 Git repository already exists, skipping git initialization',
@@ -2102,9 +4783,8 @@ const runGitInit = (projectPath) => {
2102
4783
  logger.success('Git repository initialized successfully!');
2103
4784
  } catch (error) {
2104
4785
  // Git 初始化失败不应该导致整个流程失败
2105
- logger.warn(
2106
- `Git initialization failed: ${error instanceof Error ? error.message : String(error)}`,
2107
- );
4786
+ const errorMessage = error instanceof Error ? error.message : String(error);
4787
+ logger.warn(`Git initialization failed: ${errorMessage}`);
2108
4788
  logger.info('You can manually initialize git later with: git init');
2109
4789
  }
2110
4790
  };
@@ -2129,9 +4809,8 @@ const commitChanges = (projectPath) => {
2129
4809
  logger.success('Changes committed successfully!');
2130
4810
  } catch (error) {
2131
4811
  // Commit 失败不应该导致整个流程失败
2132
- logger.warn(
2133
- `Git commit failed: ${error instanceof Error ? error.message : String(error)}`,
2134
- );
4812
+ const errorMessage = error instanceof Error ? error.message : String(error);
4813
+ logger.warn(`Git commit failed: ${errorMessage}`);
2135
4814
  logger.info(
2136
4815
  'You can manually commit later with: git add --all && git commit -m "chore: init env"',
2137
4816
  );
@@ -2152,28 +4831,33 @@ const runDev = (projectPath) => {
2152
4831
 
2153
4832
  logger.info(`Executing: ${cliPath} dev in ${projectPath}`);
2154
4833
 
2155
- // 使用通用的后台执行函数启动开发服务器
2156
- // 调用 CLI 自己的 dev 命令
2157
- const pid = spawnDetached(process.argv[0], [cliPath, 'dev'], {
2158
- cwd: projectPath,
2159
- verbose: false, // 不输出额外的进程信息,由 logger 统一处理
2160
- });
4834
+ try {
4835
+ // 使用通用的后台执行函数启动开发服务器
4836
+ // 调用 CLI 自己的 dev 命令
4837
+ const pid = spawnDetached(process.argv[0], [cliPath, 'dev'], {
4838
+ cwd: projectPath,
4839
+ verbose: false, // 不输出额外的进程信息,由 logger 统一处理
4840
+ });
2161
4841
 
2162
- logger.success('Development server started in background!');
2163
- if (pid) {
2164
- logger.info(`Process ID: ${pid}`);
2165
- logger.info(
2166
- '\nThe dev server is running independently. You can close this terminal.',
2167
- );
2168
- logger.info(`To stop the server later, use: kill ${pid}`);
4842
+ if (pid) {
4843
+ logger.success('Development server started in background!');
4844
+ logger.info(`Process ID: ${pid}`);
4845
+ logger.info(
4846
+ '\nThe dev server is running independently. You can close this terminal.',
4847
+ );
4848
+ logger.info(`To stop the server later, use: kill ${pid}`);
4849
+ } else {
4850
+ logger.warn('Failed to get process ID for dev server');
4851
+ }
4852
+ } catch (error) {
4853
+ const errorMessage = error instanceof Error ? error.message : String(error);
4854
+ logger.error(`Failed to start dev server: ${errorMessage}`);
2169
4855
  }
2170
4856
  };
2171
4857
 
2172
4858
  /**
2173
- * 执行 init 命令的内部实现
4859
+ * Init 命令执行上下文
2174
4860
  */
2175
- const executeInit = async (
2176
- options
2177
4861
 
2178
4862
 
2179
4863
 
@@ -2181,280 +4865,183 @@ const executeInit = async (
2181
4865
 
2182
4866
 
2183
4867
 
2184
- ,
2185
- command,
2186
- ) => {
2187
- const timer = new TimeTracker();
2188
4868
 
2189
- try {
2190
- const {
2191
- template: templateName,
2192
- output: outputPath,
2193
- skipInstall,
2194
- skipGit,
2195
- skipCommit,
2196
- skipDev,
2197
- force,
2198
- } = options;
2199
-
2200
- logger.info(`Initializing project with template: ${templateName}`);
2201
- timer.logPhase('Initialization');
2202
4869
 
2203
- // 执行模板引擎,返回结果对象
2204
- const result = await execute({
2205
- templateName,
2206
- outputPath,
2207
- command,
2208
- force,
2209
- });
2210
- const { outputPath: absoluteOutputPath, templateConfig, context } = result;
2211
4870
 
2212
- timer.logPhase('Template engine execution');
2213
- logger.success('Project created successfully!');
2214
4871
 
2215
- // 检查是否存在 package.json
2216
- const packageJsonPath = path.join(absoluteOutputPath, 'package.json');
2217
- const hasPackageJson = fs.existsSync(packageJsonPath);
2218
4872
 
2219
- // 安装依赖(始终使用 pnpm install,利用缓存机制)
2220
- if (!skipInstall) {
2221
- if (hasPackageJson) {
2222
- runPnpmInstall(absoluteOutputPath);
2223
- timer.logPhase('Dependencies installation');
2224
- } else {
2225
- logger.info(
2226
- '\n💡 No package.json found, skipping dependency installation',
2227
- );
2228
- }
2229
- }
2230
4873
 
2231
- // 执行 onComplete 钩子(在 pnpm install 之后)
2232
- await executeCompleteHook(templateConfig, context, absoluteOutputPath);
2233
- timer.logPhase('Complete hook execution');
2234
4874
 
2235
- // 如果没有跳过 git,则初始化 git 仓库
2236
- if (!skipGit) {
2237
- runGitInit(absoluteOutputPath);
2238
- timer.logPhase('Git initialization');
2239
- }
2240
4875
 
2241
- // 如果没有跳过 commit,则提交初始化生成的文件
2242
- if (!skipCommit) {
2243
- commitChanges(absoluteOutputPath);
2244
- timer.logPhase('Git commit');
2245
- }
2246
4876
 
2247
- // 如果没有跳过 dev,则启动开发服务器
2248
- if (!skipDev) {
2249
- runDev(absoluteOutputPath);
2250
- timer.logPhase('Dev server startup');
2251
- } else {
2252
- // 只有跳过 dev 时才显示 Next steps
2253
- logger.info('\nNext steps:');
2254
- logger.info(` cd ${outputPath}`);
2255
- if (skipInstall && hasPackageJson) {
2256
- logger.info(' pnpm install');
2257
- }
2258
- if (skipGit) {
2259
- logger.info(' git init');
2260
- }
2261
- if (skipCommit) {
2262
- logger.info(' git add --all && git commit -m "chore: init env"');
2263
- }
2264
- logger.info(' coze dev');
2265
- }
2266
4877
 
2267
- // 输出总耗时
2268
- timer.logTotal();
2269
- } catch (error) {
2270
- timer.logTotal();
2271
- logger.error('Failed to initialize project:');
2272
- logger.error(error instanceof Error ? error.message : String(error));
2273
- process.exit(1);
2274
- }
2275
- };
2276
4878
 
2277
- /**
2278
- * 注册 init 命令到 program
2279
- */
2280
- const registerCommand$1 = program => {
2281
- program
2282
- .command('init')
2283
- .description('Initialize a new project from a template')
2284
- .argument('[directory]', 'Output directory for the project')
2285
- .requiredOption('-t, --template <name>', 'Template name')
2286
- .option('-o, --output <path>', 'Output directory', process.cwd())
2287
- .option('--skip-install', 'Skip automatic pnpm install', false)
2288
- .option('--skip-git', 'Skip automatic git initialization', false)
2289
- .option(
2290
- '--skip-commit',
2291
- 'Skip automatic git commit after initialization',
2292
- false,
2293
- )
2294
- .option('--skip-dev', 'Skip automatic dev server start', false)
2295
- .allowUnknownOption() // 允许透传参数
2296
- .action(async (directory, options, command) => {
2297
- // 位置参数优先级高于 --output 选项
2298
- const outputPath = _nullishCoalesce(directory, () => ( options.output));
2299
- // Always use force mode - overwrite existing files without conflict check
2300
- const force = true;
2301
- await executeInit({ ...options, output: outputPath, force }, command);
2302
- });
2303
- };
2304
4879
 
2305
- // ABOUTME: This file implements the update command for coze CLI
2306
- // ABOUTME: It wraps pnpm update/install to update package dependencies with logging support
2307
4880
 
2308
4881
 
2309
4882
 
2310
4883
 
2311
- /**
2312
- * 日志文件名常量
2313
- */
2314
- const LOG_FILE_NAME = 'update.log';
2315
4884
 
2316
- /**
2317
- * 获取日志目录
2318
- * 优先使用环境变量 COZE_LOG_DIR,否则使用 ~/.coze-logs
2319
- */
2320
- const getLogDir = () =>
2321
- process.env.COZE_LOG_DIR || path.join(os.homedir(), '.coze-logs');
2322
4885
 
2323
- /**
2324
- * 解析日志文件路径
2325
- * - 如果是绝对路径,直接使用
2326
- * - 如果是相对路径,基于 getLogDir() + 相对路径
2327
- * - 如果为空,使用 getLogDir() + LOG_FILE_NAME
2328
- */
2329
- const resolveLogFilePath = (logFile) => {
2330
- if (!logFile) {
2331
- return path.join(getLogDir(), LOG_FILE_NAME);
2332
- }
2333
4886
 
2334
- if (path.isAbsolute(logFile)) {
2335
- return logFile;
2336
- }
2337
4887
 
2338
- return path.join(getLogDir(), logFile);
2339
- };
4888
+
4889
+
4890
+
2340
4891
 
2341
4892
  /**
2342
- * 创建日志写入流
4893
+ * 步骤1: 执行模板引擎
2343
4894
  */
2344
- const createLogStream = (logFilePath) => {
2345
- const logDir = path.dirname(logFilePath);
4895
+ const executeTemplateEngineStep = async ctx => {
4896
+ logger.info(`Initializing project with template: ${ctx.templateName}`);
4897
+ ctx.timer.logPhase('Initialization');
4898
+
4899
+ const result = await execute({
4900
+ templateName: ctx.templateName,
4901
+ outputPath: ctx.outputPath,
4902
+ command: ctx.command,
4903
+ force: _nullishCoalesce(ctx.options.force, () => ( false)),
4904
+ });
2346
4905
 
2347
- // 确保日志目录存在
2348
- if (!fs.existsSync(logDir)) {
2349
- fs.mkdirSync(logDir, { recursive: true });
2350
- }
4906
+ // 保存结果到上下文
4907
+ ctx.absoluteOutputPath = result.outputPath;
4908
+ ctx.templateConfig = result.templateConfig;
4909
+ ctx.context = result.context;
2351
4910
 
2352
- // 使用 'w' 标志覆盖之前的日志
2353
- return fs.createWriteStream(logFilePath, { flags: 'w' });
4911
+ ctx.timer.logPhase('Template engine execution');
4912
+ logger.success('Project created successfully!');
2354
4913
  };
2355
4914
 
2356
4915
  /**
2357
- * 格式化时间戳
4916
+ * 步骤2: 检查 package.json 是否存在
2358
4917
  */
2359
- const formatTimestamp = () => {
2360
- const now = new Date();
2361
- return now.toISOString();
4918
+ const checkPackageJsonStep = ctx => {
4919
+ if (!ctx.absoluteOutputPath) {
4920
+ return;
4921
+ }
4922
+
4923
+ const packageJsonPath = path.join(ctx.absoluteOutputPath, 'package.json');
4924
+ ctx.hasPackageJson = fs.existsSync(packageJsonPath);
2362
4925
  };
2363
4926
 
2364
4927
  /**
2365
- * 写入带时间戳的日志
4928
+ * 步骤3: 安装依赖
2366
4929
  */
2367
- const writeLogWithTimestamp = (stream, message) => {
2368
- const timestamp = formatTimestamp();
2369
- const lines = message.split('\n');
2370
- lines.forEach(line => {
2371
- if (line) {
2372
- stream.write(`[${timestamp}] ${line}\n`);
4930
+ const installDependenciesStep = ctx => {
4931
+ if (!ctx.absoluteOutputPath) {
4932
+ return;
4933
+ }
4934
+
4935
+ const { skipInstall } = ctx.options;
4936
+
4937
+ if (!skipInstall) {
4938
+ if (ctx.hasPackageJson) {
4939
+ runPnpmInstall(ctx.absoluteOutputPath);
4940
+ ctx.timer.logPhase('Dependencies installation');
2373
4941
  } else {
2374
- stream.write('\n');
4942
+ logger.info(
4943
+ '\n💡 No package.json found, skipping dependency installation',
4944
+ );
2375
4945
  }
2376
- });
2377
- // 确保数据写入磁盘
2378
- stream.uncork();
4946
+ }
2379
4947
  };
2380
4948
 
2381
4949
  /**
2382
- * 同时输出到控制台和日志文件
4950
+ * 步骤4: 执行完成钩子
2383
4951
  */
2384
- const logWithFile = (
2385
- stream,
2386
- level,
2387
- message,
2388
- ) => {
2389
- // 输出到控制台
2390
- switch (level) {
2391
- case 'info':
2392
- logger.info(message);
2393
- break;
2394
- case 'success':
2395
- logger.success(message);
2396
- break;
2397
- case 'error':
2398
- logger.error(message);
2399
- break;
2400
- default:
2401
- logger.info(message);
2402
- break;
4952
+ const executeCompleteHookStep = async ctx => {
4953
+ if (!ctx.absoluteOutputPath || !ctx.templateConfig || !ctx.context) {
4954
+ return;
2403
4955
  }
2404
4956
 
2405
- // 写入日志文件(带时间戳)
2406
- writeLogWithTimestamp(stream, `[${level.toUpperCase()}] ${message}`);
4957
+ await executeCompleteHook(
4958
+ ctx.templateConfig,
4959
+ ctx.context,
4960
+ ctx.absoluteOutputPath,
4961
+ );
4962
+
4963
+ ctx.timer.logPhase('Complete hook execution');
2407
4964
  };
2408
4965
 
2409
- // start_aigc
2410
4966
  /**
2411
- * 构建 pnpm add 命令
4967
+ * 步骤5: 初始化 Git 仓库
2412
4968
  */
2413
- const buildPnpmCommand = (
2414
- packageName,
2415
- options
2416
-
4969
+ const gitInitStep = ctx => {
4970
+ if (!ctx.absoluteOutputPath) {
4971
+ return;
4972
+ }
2417
4973
 
4974
+ const { skipGit } = ctx.options;
2418
4975
 
4976
+ if (!skipGit) {
4977
+ runGitInit(ctx.absoluteOutputPath);
4978
+ ctx.timer.logPhase('Git initialization');
4979
+ }
4980
+ };
2419
4981
 
2420
- ,
2421
- ) => {
2422
- const { global, version, registry, extraArgs } = options;
4982
+ /**
4983
+ * 步骤6: 提交 Git 变更
4984
+ */
4985
+ const gitCommitStep = ctx => {
4986
+ if (!ctx.absoluteOutputPath) {
4987
+ return;
4988
+ }
2423
4989
 
2424
- const parts = ['pnpm', 'add'];
4990
+ const { skipCommit } = ctx.options;
2425
4991
 
2426
- // 添加全局标记
2427
- if (global) {
2428
- parts.push('-g');
4992
+ if (!skipCommit) {
4993
+ commitChanges(ctx.absoluteOutputPath);
4994
+ ctx.timer.logPhase('Git commit');
2429
4995
  }
4996
+ };
2430
4997
 
2431
- // 添加包名和版本
2432
- if (version && version !== 'latest') {
2433
- parts.push(`${packageName}@${version}`);
2434
- } else {
2435
- parts.push(`${packageName}@latest`);
4998
+ /**
4999
+ * 步骤7: 启动开发服务器
5000
+ */
5001
+ const startDevServerStep = ctx => {
5002
+ if (!ctx.absoluteOutputPath) {
5003
+ return;
2436
5004
  }
2437
5005
 
2438
- // 添加 registry
2439
- if (registry) {
2440
- parts.push(`--registry=${registry}`);
2441
- }
5006
+ const { skipDev, skipInstall, skipGit, skipCommit } = ctx.options;
2442
5007
 
2443
- // 添加额外参数
2444
- if (extraArgs.length > 0) {
2445
- parts.push(...extraArgs);
5008
+ if (!skipDev) {
5009
+ runDev(ctx.absoluteOutputPath);
5010
+ ctx.timer.logPhase('Dev server startup');
5011
+ } else {
5012
+ // 只有跳过 dev 时才显示 Next steps
5013
+ logger.info('\nNext steps:');
5014
+ logger.info(` cd ${ctx.outputPath}`);
5015
+ if (skipInstall && ctx.hasPackageJson) {
5016
+ logger.info(' pnpm install');
5017
+ }
5018
+ if (skipGit) {
5019
+ logger.info(' git init');
5020
+ }
5021
+ if (skipCommit) {
5022
+ logger.info(' git add --all && git commit -m "chore: init env"');
5023
+ }
5024
+ logger.info(' coze dev');
2446
5025
  }
2447
-
2448
- return parts.join(' ');
2449
5026
  };
2450
- // end_aigc
2451
5027
 
2452
- // start_aigc
2453
5028
  /**
2454
- * 执行 update 命令的内部实现
5029
+ * Init 命令的所有执行步骤
2455
5030
  */
2456
- const executeUpdate = (
2457
- packageName,
5031
+ const initSteps = [
5032
+ executeTemplateEngineStep,
5033
+ checkPackageJsonStep,
5034
+ installDependenciesStep,
5035
+ executeCompleteHookStep,
5036
+ gitInitStep,
5037
+ gitCommitStep,
5038
+ startDevServerStep,
5039
+ ];
5040
+
5041
+ /**
5042
+ * 执行 init 命令的内部实现
5043
+ */
5044
+ const executeInit = async (
2458
5045
  options
2459
5046
 
2460
5047
 
@@ -2462,155 +5049,132 @@ const executeUpdate = (
2462
5049
 
2463
5050
 
2464
5051
 
5052
+
2465
5053
  ,
5054
+ command,
2466
5055
  ) => {
2467
- let logStream = null;
5056
+ const timer = new TimeTracker();
5057
+ const cmdStartTime = Date.now();
5058
+
5059
+ // 初始化执行上下文
5060
+ const ctx = {
5061
+ options,
5062
+ command,
5063
+ timer,
5064
+ cmdStartTime,
5065
+ templateName: options.template,
5066
+ outputPath: options.output,
5067
+ };
2468
5068
 
2469
5069
  try {
2470
- const { global, cwd, version, registry, logFile, extraArgs } = options;
2471
-
2472
- // 准备日志
2473
- const logFilePath = resolveLogFilePath(logFile);
2474
-
2475
- // 调试:确认日志路径
2476
- logger.info(`Log file path resolved to: ${logFilePath}`);
2477
-
2478
- logStream = createLogStream(logFilePath);
5070
+ // 上报命令开始
5071
+ reportCommandStart(
5072
+ 'init',
5073
+ JSON.stringify({ template: ctx.templateName, output: ctx.outputPath }),
5074
+ { template: ctx.templateName },
5075
+ );
2479
5076
 
2480
- // 调试:确认流已创建
2481
- logger.info('Log stream created successfully');
5077
+ // 逐个执行步骤
5078
+ for (const step of initSteps) {
5079
+ await step(ctx);
5080
+ }
2482
5081
 
2483
- logWithFile(logStream, 'info', `Updating package: ${packageName}`);
5082
+ // 输出总耗时
5083
+ timer.logTotal();
2484
5084
 
2485
- // 构建命令
2486
- const command = buildPnpmCommand(packageName, {
2487
- global,
2488
- version,
2489
- registry,
2490
- extraArgs,
5085
+ // 上报命令成功
5086
+ reportCommandComplete('init', true, Date.now() - cmdStartTime, {
5087
+ args: JSON.stringify(options),
5088
+ categories: { template: ctx.templateName },
2491
5089
  });
5090
+ // flush 由 main 函数统一处理
5091
+ } catch (error) {
5092
+ timer.logTotal();
2492
5093
 
2493
- // 确定工作目录
2494
- const workingDir = cwd
2495
- ? path.isAbsolute(cwd)
2496
- ? cwd
2497
- : path.join(process.cwd(), cwd)
2498
- : process.cwd();
2499
-
2500
- logWithFile(logStream, 'info', `Executing: ${command}`);
2501
- logWithFile(logStream, 'info', `Working directory: ${workingDir}`);
2502
- logWithFile(logStream, 'info', `Log file: ${logFilePath}`);
2503
-
2504
- // 记录命令开始时间
2505
- writeLogWithTimestamp(logStream, '--- Command execution started ---');
2506
-
2507
- // 同步执行命令
2508
- const result = shelljs.exec(command, {
2509
- cwd: workingDir,
2510
- silent: true, // 使用 silent 来捕获输出
5094
+ // 上报错误
5095
+ const err = error instanceof Error ? error : new Error(String(error));
5096
+ reportError(err, {
5097
+ command: 'init',
5098
+ template: options.template,
5099
+ output: options.output,
2511
5100
  });
2512
5101
 
2513
- // 将输出写入控制台和日志文件(带时间戳)
2514
- if (result.stdout) {
2515
- process.stdout.write(result.stdout);
2516
- writeLogWithTimestamp(logStream, result.stdout.trim());
2517
- }
2518
-
2519
- if (result.stderr) {
2520
- process.stderr.write(result.stderr);
2521
- writeLogWithTimestamp(logStream, result.stderr.trim());
2522
- }
2523
-
2524
- // 记录命令结束时间
2525
- writeLogWithTimestamp(logStream, '--- Command execution ended ---');
2526
-
2527
- // 检查执行结果并记录到日志
2528
- if (result.code === 0) {
2529
- logWithFile(logStream, 'success', 'Package updated successfully');
2530
- logWithFile(logStream, 'info', `Log file: ${logFilePath}`);
2531
- } else {
2532
- logWithFile(
2533
- logStream,
2534
- 'error',
2535
- `Command exited with code ${result.code}`,
2536
- );
2537
- logWithFile(
2538
- logStream,
2539
- 'error',
2540
- `Check log file for details: ${logFilePath}`,
2541
- );
2542
- }
2543
-
2544
- // 关闭日志流并等待写入完成
2545
- logStream.end(() => {
2546
- // 流关闭后再退出进程
2547
- if (result.code !== 0) {
2548
- process.exit(result.code || 1);
2549
- }
5102
+ // 上报命令失败
5103
+ reportCommandComplete('init', false, Date.now() - cmdStartTime, {
5104
+ args: JSON.stringify(options),
5105
+ errorCode: 1,
5106
+ errorMessage: err.message,
5107
+ categories: { template: options.template },
2550
5108
  });
2551
- } catch (error) {
2552
- logger.error('Failed to update package:');
2553
- logger.error(error instanceof Error ? error.message : String(error));
5109
+ await flushSlardar();
2554
5110
 
2555
- // 写入错误到日志文件
2556
- if (logStream) {
2557
- writeLogWithTimestamp(
2558
- logStream,
2559
- `[ERROR] ${error instanceof Error ? error.message : String(error)}`,
2560
- );
2561
- // 等待流关闭后再退出
2562
- logStream.end(() => {
2563
- process.exit(1);
2564
- });
2565
- } else {
2566
- process.exit(1);
2567
- }
5111
+ logger.error('Failed to initialize project:');
5112
+ logger.error(err.message);
5113
+ process.exit(1);
2568
5114
  }
2569
5115
  };
2570
- // end_aigc
2571
5116
 
2572
5117
  /**
2573
- * 注册 update 命令到 program
5118
+ * 注册 init 命令到 program
2574
5119
  */
2575
5120
  const registerCommand = program => {
2576
5121
  program
2577
- .command('update <package>')
2578
- .description('Update a package dependency')
2579
- .option('-g, --global', 'Update package globally', false)
2580
- .option('-c, --cwd <path>', 'Working directory for the update')
5122
+ .command('init')
5123
+ .description('Initialize a new project from a template')
5124
+ .argument('[directory]', 'Output directory for the project')
5125
+ .requiredOption('-t, --template <name>', 'Template name')
5126
+ .option('-o, --output <path>', 'Output directory', process.cwd())
5127
+ .option('--skip-install', 'Skip automatic pnpm install', false)
5128
+ .option('--skip-git', 'Skip automatic git initialization', false)
2581
5129
  .option(
2582
- '--to <version>',
2583
- 'Version to update to (default: latest)',
2584
- 'latest',
5130
+ '--skip-commit',
5131
+ 'Skip automatic git commit after initialization',
5132
+ false,
2585
5133
  )
2586
- .option('--registry <url>', 'Registry URL to use for the update')
2587
- .option('--log-file <path>', 'Log file path')
2588
- .allowUnknownOption() // 允许透传参数给 pnpm
2589
- .action((packageName, options, command) => {
2590
- // 收集所有未知选项作为额外参数
2591
- const extraArgs = command.args.slice(1);
2592
-
2593
- executeUpdate(packageName, {
2594
- ...options,
2595
- version: options.to, // 将 --to 映射到 version
2596
- extraArgs,
2597
- });
5134
+ .option('--skip-dev', 'Skip automatic dev server start', false)
5135
+ .allowUnknownOption() // 允许透传参数
5136
+ .action(async (directory, options, command) => {
5137
+ // 位置参数优先级高于 --output 选项
5138
+ const outputPath = _nullishCoalesce(directory, () => ( options.output));
5139
+ // Always use force mode - overwrite existing files without conflict check
5140
+ const force = true;
5141
+ await executeInit({ ...options, output: outputPath, force }, command);
2598
5142
  });
2599
5143
  };
2600
5144
 
2601
- var version = "0.0.3";
2602
- var packageJson = {
2603
- version: version};
2604
-
2605
5145
  const commands = [
5146
+ registerCommand,
2606
5147
  registerCommand$1,
2607
- registerCommand$2,
2608
5148
  registerCommand$4,
5149
+ registerCommand$2,
2609
5150
  registerCommand$3,
2610
- registerCommand,
2611
5151
  ];
2612
5152
 
2613
- const main = () => {
5153
+ const main = async () => {
5154
+ // 初始化 Slardar 监控
5155
+ initSlardar();
5156
+
5157
+ // 监听未捕获的异常
5158
+ process.on('uncaughtException', async (error) => {
5159
+ reportError(error, {
5160
+ type: 'uncaughtException',
5161
+ context: 'global',
5162
+ });
5163
+ await flushSlardar();
5164
+ process.exit(1);
5165
+ });
5166
+
5167
+ // 监听未处理的 Promise rejection
5168
+ process.on('unhandledRejection', async (reason) => {
5169
+ const error = reason instanceof Error ? reason : new Error(String(reason));
5170
+ reportError(error, {
5171
+ type: 'unhandledRejection',
5172
+ context: 'global',
5173
+ });
5174
+ await flushSlardar();
5175
+ process.exit(1);
5176
+ });
5177
+
2614
5178
  const program = new commander.Command();
2615
5179
 
2616
5180
  program
@@ -2625,7 +5189,12 @@ const main = () => {
2625
5189
  // 在 help 输出中添加模板信息
2626
5190
  program.addHelpText('after', generateTemplatesHelpText());
2627
5191
 
2628
- program.parse(process.argv);
5192
+ // 使用 parseAsync 等待命令执行完成
5193
+ await program.parseAsync(process.argv);
5194
+
5195
+ // 统一在命令执行完成后 flush
5196
+ // 注意:如果命令中调用了 process.exit(),不会执行到这里
5197
+ await flushSlardar();
2629
5198
  };
2630
5199
 
2631
5200
  main();