@port-labs/jq-node-bindings 0.0.15-dev2 → 1.0.0
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/jq.js +0 -2
- package/lib/templateAsync.js +0 -1
- package/package.json +2 -2
- package/src/binding.cc +118 -91
- package/test/template.test.js +0 -1
package/lib/jq.js
CHANGED
|
@@ -15,7 +15,6 @@ class JqExecCompileError extends JqExecError {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
const exec = (object, filter, {enableEnv = false, throwOnError = false} = {}) => {
|
|
18
|
-
console.log("exec",object,filter)
|
|
19
18
|
try {
|
|
20
19
|
const data = nativeJq.execSync(JSON.stringify(object), formatFilter(filter, {enableEnv}))
|
|
21
20
|
|
|
@@ -29,7 +28,6 @@ const exec = (object, filter, {enableEnv = false, throwOnError = false} = {}) =>
|
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
const execAsync = async (object, filter, {enableEnv = false, throwOnError = false} = {}) => {
|
|
32
|
-
console.log(object,filter)
|
|
33
31
|
try {
|
|
34
32
|
const data = await nativeJq.execAsync(JSON.stringify(object), formatFilter(filter, {enableEnv}))
|
|
35
33
|
return data?.value;
|
package/lib/templateAsync.js
CHANGED
|
@@ -119,7 +119,6 @@ const renderRecursivelyAsync = async(inputJson, template, execOptions = {}) => {
|
|
|
119
119
|
`Evaluated object key should be undefined, null or string. Original key: ${key}, evaluated to: ${JSON.stringify(evaluatedKey)}`,
|
|
120
120
|
);
|
|
121
121
|
}
|
|
122
|
-
console.log("evaluatedKey",evaluatedKey,value)
|
|
123
122
|
return evaluatedKey ? [[evaluatedKey, await renderRecursivelyAsync(inputJson, value, execOptions)]] : [];
|
|
124
123
|
});
|
|
125
124
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@port-labs/jq-node-bindings",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "v1.0.0",
|
|
4
4
|
"description": "Node.js bindings for JQ",
|
|
5
|
-
"jq-node-bindings": "0.0
|
|
5
|
+
"jq-node-bindings": "1.0.0",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"configure": "node-gyp configure",
|
package/src/binding.cc
CHANGED
|
@@ -9,23 +9,40 @@
|
|
|
9
9
|
#include "src/binding.h"
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
#ifdef DEBUG_MODE
|
|
13
|
-
static bool debug_enabled = true;
|
|
14
|
-
#else
|
|
15
|
-
static bool debug_enabled = false;
|
|
16
|
-
#endif
|
|
12
|
+
// #ifdef DEBUG_MODE
|
|
13
|
+
// static bool debug_enabled = true;
|
|
14
|
+
// #else
|
|
15
|
+
// static bool debug_enabled = false;
|
|
16
|
+
// #endif
|
|
17
|
+
|
|
18
|
+
// #define DEBUG_LOG(fmt, ...) \
|
|
19
|
+
// do { if (debug_enabled) printf("[DEBUG] " fmt "\n", ##__VA_ARGS__); } while (0)
|
|
17
20
|
|
|
18
|
-
#define
|
|
19
|
-
|
|
21
|
+
// #define ASYNC_DEBUG_LOG(work, fmt, ...) \
|
|
22
|
+
// do { if (debug_enabled) printf("[DEBUG][ASYNC][%p] " fmt "\n", (jv*)work, ##__VA_ARGS__); } while (0)
|
|
20
23
|
|
|
21
|
-
#define
|
|
22
|
-
|
|
24
|
+
// #define CACHE_DEBUG_LOG(cache, fmt, ...) \
|
|
25
|
+
// do { if (debug_enabled) printf("[DEBUG][CACHE][%p] " fmt "\n", (void*)cache, ##__VA_ARGS__); } while (0)
|
|
23
26
|
|
|
24
|
-
#define
|
|
25
|
-
|
|
27
|
+
// #define WRAPPER_DEBUG_LOG(wrapper, fmt, ...) \
|
|
28
|
+
// do { if (debug_enabled) printf("[DEBUG][WRAPPER:%p] " fmt "\n", (void*)wrapper, ##__VA_ARGS__); } while (0)
|
|
29
|
+
#ifdef ENABLE_DEBUG // We'll use ENABLE_DEBUG as our flag name
|
|
30
|
+
#define DEBUG_ENABLED 1
|
|
31
|
+
#else
|
|
32
|
+
#define DEBUG_ENABLED 0
|
|
33
|
+
#endif
|
|
26
34
|
|
|
27
|
-
#
|
|
28
|
-
|
|
35
|
+
#ifdef ENABLE_DEBUG
|
|
36
|
+
#define DEBUG_LOG(fmt, ...) fprintf(stderr, "[DEBUG] " fmt "\n", ##__VA_ARGS__)
|
|
37
|
+
#define ASYNC_DEBUG_LOG(work, fmt, ...) fprintf(stderr, "[DEBUG][ASYNC][%p] " fmt "\n", (void*)work, ##__VA_ARGS__)
|
|
38
|
+
#define CACHE_DEBUG_LOG(cache, fmt, ...) fprintf(stderr, "[DEBUG][CACHE][%p] " fmt "\n", (void*)cache, ##__VA_ARGS__)
|
|
39
|
+
#define WRAPPER_DEBUG_LOG(wrapper, fmt, ...) fprintf(stderr, "[DEBUG][WRAPPER][%p] " fmt "\n", (void*)wrapper, ##__VA_ARGS__)
|
|
40
|
+
#else
|
|
41
|
+
#define DEBUG_LOG(fmt, ...) ((void)0)
|
|
42
|
+
#define ASYNC_DEBUG_LOG(work, fmt, ...) ((void)0)
|
|
43
|
+
#define CACHE_DEBUG_LOG(cache, fmt, ...) ((void)0)
|
|
44
|
+
#define WRAPPER_DEBUG_LOG(wrapper, fmt, ...) ((void)0)
|
|
45
|
+
#endif
|
|
29
46
|
|
|
30
47
|
static size_t global_cache_size = 100;
|
|
31
48
|
|
|
@@ -81,7 +98,6 @@ struct JqFilterWrapper {
|
|
|
81
98
|
public:
|
|
82
99
|
std::string filter_name;
|
|
83
100
|
std::list<JqFilterWrapper*>::iterator cache_pos;
|
|
84
|
-
|
|
85
101
|
/* init mutex and set filter_name */
|
|
86
102
|
explicit JqFilterWrapper(jq_state* jq_, std::string filter_name_) :
|
|
87
103
|
filter_name(filter_name_),
|
|
@@ -124,7 +140,7 @@ private:
|
|
|
124
140
|
pthread_mutex_t cache_mutex;
|
|
125
141
|
std::list<JqFilterWrapper*> item_list;
|
|
126
142
|
std::unordered_map<KEY_T, JqFilterWrapper*> item_map;
|
|
127
|
-
std::unordered_map<JqFilterWrapper*,
|
|
143
|
+
std::unordered_map<JqFilterWrapper*, size_t> item_refcnt;
|
|
128
144
|
|
|
129
145
|
size_t cache_size;
|
|
130
146
|
|
|
@@ -139,8 +155,8 @@ private:
|
|
|
139
155
|
auto last_it = item_list.end();
|
|
140
156
|
last_it--;
|
|
141
157
|
JqFilterWrapper* wrapper = *last_it;
|
|
142
|
-
CACHE_DEBUG_LOG((void*)wrapper, "Examining wrapper: name='%s',
|
|
143
|
-
if(
|
|
158
|
+
CACHE_DEBUG_LOG((void*)wrapper, "Examining wrapper: name='%s', refcnt=%zu", wrapper->filter_name.c_str(), item_refcnt[wrapper]);
|
|
159
|
+
if(item_refcnt[wrapper]>0){
|
|
144
160
|
CACHE_DEBUG_LOG((void*)wrapper, "Wrapper is busy, skipping");
|
|
145
161
|
break;
|
|
146
162
|
}
|
|
@@ -152,7 +168,7 @@ private:
|
|
|
152
168
|
CACHE_DEBUG_LOG((void*)wrapper, "Removing wrapper from cache");
|
|
153
169
|
item_map.erase(wrapper->filter_name);
|
|
154
170
|
};
|
|
155
|
-
|
|
171
|
+
item_refcnt.erase(wrapper);
|
|
156
172
|
item_list.pop_back();
|
|
157
173
|
CACHE_DEBUG_LOG((void*)wrapper, "Deleting wrapper");
|
|
158
174
|
delete wrapper;
|
|
@@ -171,20 +187,20 @@ public:
|
|
|
171
187
|
//clear cache
|
|
172
188
|
pthread_mutex_destroy(&cache_mutex);
|
|
173
189
|
}
|
|
174
|
-
void
|
|
190
|
+
void inc_refcnt(JqFilterWrapper* val){
|
|
175
191
|
CACHE_DEBUG_LOG((void*)val, "Incrementing refcnt for wrapper:%p", (void*)val);
|
|
176
|
-
|
|
192
|
+
item_refcnt[val]++;
|
|
177
193
|
}
|
|
178
|
-
void
|
|
194
|
+
void dec_refcnt(JqFilterWrapper* val){
|
|
179
195
|
pthread_mutex_lock(&cache_mutex);
|
|
180
196
|
CACHE_DEBUG_LOG((void*)val, "Decrementing refcnt for wrapper:%p", (void*)val);
|
|
181
|
-
|
|
197
|
+
item_refcnt[val]--;
|
|
182
198
|
pthread_mutex_unlock(&cache_mutex);
|
|
183
199
|
}
|
|
184
200
|
void put(const KEY_T &key, JqFilterWrapper* val) {
|
|
185
201
|
CACHE_DEBUG_LOG((void*)val, "Putting key='%s' wrapper:%p", key.c_str(), (void*)val);
|
|
186
202
|
pthread_mutex_lock(&cache_mutex);
|
|
187
|
-
|
|
203
|
+
inc_refcnt(val);
|
|
188
204
|
CACHE_DEBUG_LOG((void*)val, "Got cache lock for put operation");
|
|
189
205
|
|
|
190
206
|
auto it = item_map.find(key);
|
|
@@ -217,9 +233,9 @@ public:
|
|
|
217
233
|
item_list.erase(wrapper->cache_pos);
|
|
218
234
|
item_list.push_front(wrapper);
|
|
219
235
|
wrapper->cache_pos = item_list.begin();
|
|
220
|
-
|
|
221
|
-
CACHE_DEBUG_LOG((void*)wrapper, "Cache hit for jq wrapper,pointer=%p,name=%s",
|
|
222
|
-
(void*)wrapper, wrapper->filter_name.c_str());
|
|
236
|
+
inc_refcnt(wrapper);
|
|
237
|
+
CACHE_DEBUG_LOG((void*)wrapper, "Cache hit for jq wrapper,pointer=%p,name=%s,refcnt=%zu",
|
|
238
|
+
(void*)wrapper, wrapper->filter_name.c_str(),item_refcnt[wrapper]);
|
|
223
239
|
pthread_mutex_unlock(&cache_mutex);
|
|
224
240
|
CACHE_DEBUG_LOG((void*)wrapper, "Released cache lock after get");
|
|
225
241
|
return wrapper;
|
|
@@ -412,13 +428,13 @@ napi_value ExecSync(napi_env env, napi_callback_info info) {
|
|
|
412
428
|
napi_throw_error(env, nullptr, err_msg_conversion.c_str());
|
|
413
429
|
jv_free(result);
|
|
414
430
|
wrapper->unlock();
|
|
415
|
-
cache.
|
|
431
|
+
cache.dec_refcnt(wrapper);
|
|
416
432
|
return nullptr;
|
|
417
433
|
}
|
|
418
434
|
|
|
419
435
|
jv_free(result);
|
|
420
436
|
wrapper->unlock();
|
|
421
|
-
cache.
|
|
437
|
+
cache.dec_refcnt(wrapper);
|
|
422
438
|
return ret;
|
|
423
439
|
}
|
|
424
440
|
|
|
@@ -430,7 +446,8 @@ struct AsyncWork {
|
|
|
430
446
|
napi_deferred deferred;
|
|
431
447
|
napi_async_work async_work;
|
|
432
448
|
/* output */
|
|
433
|
-
|
|
449
|
+
bool is_undefined;
|
|
450
|
+
std::string result;
|
|
434
451
|
std::string error;
|
|
435
452
|
bool success;
|
|
436
453
|
};
|
|
@@ -466,8 +483,8 @@ void ExecuteAsync(napi_env env, void* data) {
|
|
|
466
483
|
work->error = "Invalid JSON input";
|
|
467
484
|
work->success = false;
|
|
468
485
|
jv_free(input);
|
|
486
|
+
cache.dec_refcnt(wrapper);
|
|
469
487
|
wrapper->unlock();
|
|
470
|
-
cache.set_item_in_use_false(wrapper);
|
|
471
488
|
|
|
472
489
|
return;
|
|
473
490
|
}
|
|
@@ -476,14 +493,43 @@ void ExecuteAsync(napi_env env, void* data) {
|
|
|
476
493
|
ASYNC_DEBUG_LOG(work, "jq execution started");
|
|
477
494
|
|
|
478
495
|
jv result=jq_next(wrapper->get_jq());
|
|
479
|
-
|
|
480
|
-
|
|
496
|
+
if(jv_get_kind(result) == JV_KIND_INVALID){
|
|
497
|
+
jv msg = jv_invalid_get_msg(jv_copy(result));
|
|
498
|
+
|
|
499
|
+
if (jv_get_kind(msg) == JV_KIND_STRING) {
|
|
500
|
+
work->error = std::string("jq: error: ") + jv_string_value(msg);
|
|
501
|
+
jv_free(msg);
|
|
502
|
+
work->success=false;
|
|
503
|
+
}else{
|
|
504
|
+
work->is_undefined = true;
|
|
505
|
+
work->success=true;
|
|
506
|
+
}
|
|
507
|
+
}else{
|
|
508
|
+
jv dump = jv_dump_string(result, JV_PRINT_INVALID);
|
|
509
|
+
if(jv_is_valid(dump)){
|
|
510
|
+
work->result = jv_string_value(dump);
|
|
511
|
+
work->success = true;
|
|
512
|
+
}else{
|
|
513
|
+
ASYNC_DEBUG_LOG(work, "failed to get result");
|
|
514
|
+
work->error = "failed to get result";
|
|
515
|
+
work->success = false;
|
|
516
|
+
}
|
|
517
|
+
jv_free(dump);
|
|
518
|
+
}
|
|
481
519
|
wrapper->unlock();
|
|
520
|
+
cache.dec_refcnt(wrapper);
|
|
482
521
|
|
|
483
|
-
|
|
484
|
-
ASYNC_DEBUG_LOG(work, "jq execution finished - got result, %p", work->result);
|
|
522
|
+
ASYNC_DEBUG_LOG(work, "jq execution finished - got result, %s", work->result.c_str());
|
|
485
523
|
}
|
|
486
524
|
|
|
525
|
+
void reject_with_error_message(napi_env env, napi_deferred deferred, std::string error_message){
|
|
526
|
+
napi_value error;
|
|
527
|
+
napi_create_string_utf8(env, error_message.c_str(), NAPI_AUTO_LENGTH, &error);
|
|
528
|
+
napi_value error_obj;
|
|
529
|
+
napi_create_object(env, &error_obj);
|
|
530
|
+
napi_set_named_property(env, error_obj, "message", error);
|
|
531
|
+
napi_reject_deferred(env, deferred, error_obj);
|
|
532
|
+
}
|
|
487
533
|
|
|
488
534
|
void CompleteAsync(napi_env env, napi_status status, void* data) {
|
|
489
535
|
AsyncWork* work = static_cast<AsyncWork*>(data);
|
|
@@ -491,69 +537,50 @@ void CompleteAsync(napi_env env, napi_status status, void* data) {
|
|
|
491
537
|
|
|
492
538
|
auto cleanup = [&]() {
|
|
493
539
|
if (!cleanup_done) {
|
|
494
|
-
DEBUG_LOG("Freeing result, %p,refcnt %zu", work->result, jv_get_refcnt(work->result));
|
|
495
|
-
jv_free(work->result);
|
|
496
540
|
napi_delete_async_work(env, work->async_work);
|
|
497
541
|
ASYNC_DEBUG_LOG(work, "Deleting AsyncWork");
|
|
498
542
|
delete work;
|
|
499
543
|
cleanup_done = true;
|
|
500
544
|
}
|
|
501
545
|
};
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
546
|
+
|
|
547
|
+
if(status != napi_ok || !work->success){
|
|
548
|
+
std::string error_message = work->error;
|
|
549
|
+
if(error_message == ""){
|
|
550
|
+
error_message = "Got error from async work";
|
|
551
|
+
}
|
|
552
|
+
reject_with_error_message(env, work->deferred, error_message);
|
|
505
553
|
cleanup();
|
|
506
554
|
return;
|
|
507
555
|
}
|
|
508
556
|
napi_handle_scope scope;
|
|
509
|
-
|
|
557
|
+
status = napi_open_handle_scope(env, &scope);
|
|
510
558
|
if (status != napi_ok) {
|
|
511
|
-
|
|
512
|
-
napi_reject_deferred(env, work->deferred, nullptr);
|
|
559
|
+
reject_with_error_message(env, work->deferred, "Failed to create handle scope");
|
|
513
560
|
cleanup();
|
|
514
561
|
return;
|
|
515
562
|
}
|
|
516
563
|
|
|
517
|
-
if (work->success) {
|
|
518
564
|
napi_value ret;
|
|
519
565
|
|
|
520
566
|
status=napi_create_object(env, &ret);
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
567
|
+
|
|
568
|
+
jv result_jv;
|
|
569
|
+
if(work->is_undefined){
|
|
570
|
+
result_jv = jv_invalid();
|
|
571
|
+
}else{
|
|
572
|
+
result_jv= jv_parse(work->result.c_str());
|
|
527
573
|
}
|
|
528
|
-
DEBUG_LOG("[COMPLETE ASYNC][%p] result %p", work, work->result);
|
|
529
574
|
std::string err_msg_conversion;
|
|
530
|
-
bool success = jv_object_to_napi("value", env,
|
|
531
|
-
|
|
532
|
-
napi_value error;
|
|
533
|
-
napi_create_string_utf8(env, err_msg_conversion.c_str(), NAPI_AUTO_LENGTH, &error);
|
|
534
|
-
|
|
535
|
-
napi_value error_obj;
|
|
536
|
-
napi_create_object(env, &error_obj);
|
|
537
|
-
napi_set_named_property(env, error_obj, "message", error);
|
|
538
|
-
// napi_create_error(env, nullptr, error, &error_obj);
|
|
539
|
-
napi_reject_deferred(env, work->deferred, error_obj);
|
|
575
|
+
bool success = jv_object_to_napi("value", env, result_jv, ret,err_msg_conversion);
|
|
576
|
+
jv_free(result_jv);
|
|
540
577
|
|
|
541
|
-
|
|
578
|
+
if(!success){
|
|
579
|
+
reject_with_error_message(env, work->deferred, err_msg_conversion);
|
|
542
580
|
napi_close_handle_scope(env, scope);
|
|
543
581
|
return;
|
|
544
582
|
}
|
|
545
583
|
napi_resolve_deferred(env, work->deferred, ret);
|
|
546
|
-
} else {
|
|
547
|
-
napi_value error;
|
|
548
|
-
napi_create_string_utf8(env, work->error.c_str(), NAPI_AUTO_LENGTH, &error);
|
|
549
|
-
|
|
550
|
-
napi_value error_obj;
|
|
551
|
-
napi_create_object(env, &error_obj);
|
|
552
|
-
napi_set_named_property(env, error_obj, "message", error);
|
|
553
|
-
|
|
554
|
-
// napi_create_error(env, nullptr, error, &error_obj);
|
|
555
|
-
napi_reject_deferred(env, work->deferred, error_obj);
|
|
556
|
-
}
|
|
557
584
|
cleanup();
|
|
558
585
|
napi_close_handle_scope(env, scope);
|
|
559
586
|
}
|
|
@@ -598,26 +625,26 @@ napi_value ExecAsync(napi_env env, napi_callback_info info) {
|
|
|
598
625
|
return promise;
|
|
599
626
|
}
|
|
600
627
|
|
|
601
|
-
napi_value SetDebugMode(napi_env env, napi_callback_info info) {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
628
|
+
// napi_value SetDebugMode(napi_env env, napi_callback_info info) {
|
|
629
|
+
// size_t argc = 1;
|
|
630
|
+
// napi_value args[1];
|
|
631
|
+
// bool enable;
|
|
605
632
|
|
|
606
|
-
|
|
633
|
+
// napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
|
|
607
634
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
635
|
+
// if (argc < 1) {
|
|
636
|
+
// napi_throw_type_error(env, nullptr, "Wrong number of arguments");
|
|
637
|
+
// return nullptr;
|
|
638
|
+
// }
|
|
612
639
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
640
|
+
// napi_get_value_bool(env, args[0], &enable);
|
|
641
|
+
// debug_enabled = enable;
|
|
642
|
+
// DEBUG_LOG("Debug mode %s", enable ? "enabled" : "disabled");
|
|
616
643
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
}
|
|
644
|
+
// napi_value result;
|
|
645
|
+
// napi_get_boolean(env, debug_enabled, &result);
|
|
646
|
+
// return result;
|
|
647
|
+
// }
|
|
621
648
|
|
|
622
649
|
// napi_value GetCacheStats(napi_env env, napi_callback_info info) {
|
|
623
650
|
// napi_value result;
|
|
@@ -663,16 +690,16 @@ napi_value SetCacheSize(napi_env env, napi_callback_info info) {
|
|
|
663
690
|
}
|
|
664
691
|
|
|
665
692
|
napi_value Init(napi_env env, napi_value exports) {
|
|
666
|
-
napi_value exec_sync, exec_async,
|
|
693
|
+
napi_value exec_sync, exec_async, cache_size_fn,cache_stats_fn;
|
|
667
694
|
|
|
668
695
|
napi_create_function(env, "execSync", NAPI_AUTO_LENGTH, ExecSync, nullptr, &exec_sync);
|
|
669
696
|
napi_create_function(env, "execAsync", NAPI_AUTO_LENGTH, ExecAsync, nullptr, &exec_async);
|
|
670
|
-
napi_create_function(env, "setDebugMode", NAPI_AUTO_LENGTH, SetDebugMode, nullptr, &debug_fn);
|
|
697
|
+
// napi_create_function(env, "setDebugMode", NAPI_AUTO_LENGTH, SetDebugMode, nullptr, &debug_fn);
|
|
671
698
|
napi_create_function(env, "setCacheSize", NAPI_AUTO_LENGTH, SetCacheSize, nullptr, &cache_size_fn);
|
|
672
699
|
// napi_create_function(env, "getCacheStats", NAPI_AUTO_LENGTH, GetCacheStats, nullptr, &cache_stats_fn);
|
|
673
700
|
napi_set_named_property(env, exports, "execSync", exec_sync);
|
|
674
701
|
napi_set_named_property(env, exports, "execAsync", exec_async);
|
|
675
|
-
napi_set_named_property(env, exports, "setDebugMode", debug_fn);
|
|
702
|
+
// napi_set_named_property(env, exports, "setDebugMode", debug_fn);
|
|
676
703
|
napi_set_named_property(env, exports, "setCacheSize", cache_size_fn);
|
|
677
704
|
// napi_set_named_property(env, exports, "getCacheStats", cache_stats_fn);
|
|
678
705
|
return exports;
|
package/test/template.test.js
CHANGED