fenneckit 1.0.3 โ 1.2.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/README.md +262 -318
- package/bin/fenneckit +1 -1
- package/index.js +2 -2
- package/libs/fenneckit.js +1 -1
- package/libs/labs.js +393 -46
- package/package.json +4 -3
- package/utility/report.js +26 -0
package/bin/fenneckit
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
import '../libs/fenneckit.js'
|
package/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { newLabs
|
|
1
|
+
import { newLabs } from "./libs/labs.js";
|
|
2
2
|
import { testing } from "./libs/testing.js";
|
|
3
|
-
export { newLabs, testing
|
|
3
|
+
export { newLabs, testing };
|
package/libs/fenneckit.js
CHANGED
package/libs/labs.js
CHANGED
|
@@ -1,38 +1,306 @@
|
|
|
1
1
|
/*==================================
|
|
2
2
|
* Copyright 2026 lasith ruwantha amrwansha
|
|
3
|
-
* Written by 2026/09/
|
|
3
|
+
* Written by 2026/09/20
|
|
4
4
|
* Author: ruwantha amrwansha
|
|
5
5
|
* Library: FennecKit ๐ฆ
|
|
6
|
-
* Version: 1.1.0-beta (with clearStore feature)
|
|
7
6
|
*===================================*/
|
|
8
|
-
import {
|
|
7
|
+
import { createCipheriv, createDecipheriv, randomBytes, scryptSync, } from "crypto";
|
|
8
|
+
import report from "../utility/report.js";
|
|
9
9
|
import path from "path";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
10
|
+
class SecretVault {
|
|
11
|
+
vault = new Map();
|
|
12
|
+
masterKey;
|
|
13
|
+
cipher = "aes-256-gcm";
|
|
14
|
+
keyLength = 32;
|
|
15
|
+
ivLength = 16;
|
|
16
|
+
constructor(password = "fenneckit-default") {
|
|
17
|
+
this.masterKey = scryptSync(password, "salt", this.keyLength);
|
|
18
|
+
}
|
|
19
|
+
encrypt(data) {
|
|
20
|
+
const iv = randomBytes(this.ivLength);
|
|
21
|
+
const cipher = createCipheriv(this.cipher, this.masterKey, iv);
|
|
22
|
+
let encrypted = cipher.update(data, "utf8");
|
|
23
|
+
encrypted = Buffer.concat([encrypted, cipher.final()]);
|
|
24
|
+
const tag = cipher.getAuthTag();
|
|
25
|
+
return { encrypted, iv, tag };
|
|
26
|
+
}
|
|
27
|
+
decrypt(data, iv, tag) {
|
|
28
|
+
const decipher = createDecipheriv(this.cipher, this.masterKey, iv);
|
|
29
|
+
decipher.setAuthTag(tag);
|
|
30
|
+
let decrypted = decipher.update(data);
|
|
31
|
+
decrypted = Buffer.concat([decrypted, decipher.final()]);
|
|
32
|
+
return decrypted.toString("utf8");
|
|
33
|
+
}
|
|
34
|
+
set(key, value) {
|
|
35
|
+
const { encrypted, iv, tag } = this.encrypt(value);
|
|
36
|
+
const stored = Buffer.concat([iv, tag, encrypted]);
|
|
37
|
+
this.vault.set(key, stored);
|
|
38
|
+
}
|
|
39
|
+
get(key) {
|
|
40
|
+
const stored = this.vault.get(key);
|
|
41
|
+
if (!stored)
|
|
42
|
+
return undefined;
|
|
43
|
+
try {
|
|
44
|
+
const iv = stored.slice(0, this.ivLength);
|
|
45
|
+
const tag = stored.slice(this.ivLength, this.ivLength + 16);
|
|
46
|
+
const encrypted = stored.slice(this.ivLength + 16);
|
|
47
|
+
return this.decrypt(encrypted, iv, tag);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
console.error(`Failed to decrypt secret: ${key}`);
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
delete(key) {
|
|
55
|
+
this.vault.delete(key);
|
|
56
|
+
}
|
|
57
|
+
clear() {
|
|
58
|
+
this.vault.clear();
|
|
59
|
+
}
|
|
60
|
+
keys() {
|
|
61
|
+
return Array.from(this.vault.keys());
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
class NamespacedStore {
|
|
65
|
+
data = new Map();
|
|
66
|
+
name;
|
|
67
|
+
constructor(name) {
|
|
68
|
+
this.name = name;
|
|
69
|
+
}
|
|
70
|
+
set(key, value) {
|
|
71
|
+
this.data.set(key, value);
|
|
72
|
+
}
|
|
73
|
+
get(key) {
|
|
74
|
+
return this.data.get(key);
|
|
75
|
+
}
|
|
76
|
+
delete(key) {
|
|
77
|
+
this.data.delete(key);
|
|
78
|
+
}
|
|
79
|
+
clear(key) {
|
|
80
|
+
if (key) {
|
|
81
|
+
this.data.delete(key);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
this.data.clear();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
keys() {
|
|
88
|
+
return Array.from(this.data.keys());
|
|
89
|
+
}
|
|
90
|
+
entries() {
|
|
91
|
+
return Array.from(this.data.entries());
|
|
92
|
+
}
|
|
93
|
+
has(key) {
|
|
94
|
+
return this.data.has(key);
|
|
95
|
+
}
|
|
96
|
+
toJSON() {
|
|
97
|
+
const obj = {};
|
|
98
|
+
for (const [key, value] of this.data) {
|
|
99
|
+
obj[key] = value;
|
|
100
|
+
}
|
|
101
|
+
return obj;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
class AuditLog {
|
|
105
|
+
entries = [];
|
|
106
|
+
record(entry) {
|
|
107
|
+
this.entries.push(entry);
|
|
108
|
+
}
|
|
109
|
+
getLog() {
|
|
110
|
+
return [...this.entries];
|
|
111
|
+
}
|
|
112
|
+
getLast(n) {
|
|
113
|
+
return this.entries.slice(-n);
|
|
114
|
+
}
|
|
115
|
+
filterByKey(key) {
|
|
116
|
+
return this.entries.filter((e) => e.key === key);
|
|
117
|
+
}
|
|
118
|
+
filterByOperation(op) {
|
|
119
|
+
return this.entries.filter((e) => e.operation === op);
|
|
120
|
+
}
|
|
121
|
+
filterByNamespace(ns) {
|
|
122
|
+
return this.entries.filter((e) => e.namespace === ns);
|
|
123
|
+
}
|
|
124
|
+
export(format) {
|
|
125
|
+
if (format === "json") {
|
|
126
|
+
return JSON.stringify(this.entries, null, 2);
|
|
127
|
+
}
|
|
128
|
+
if (format === "csv") {
|
|
129
|
+
const headers = [
|
|
130
|
+
"timestamp",
|
|
131
|
+
"lab",
|
|
132
|
+
"test",
|
|
133
|
+
"operation",
|
|
134
|
+
"namespace",
|
|
135
|
+
"key",
|
|
136
|
+
"dataType",
|
|
137
|
+
"success",
|
|
138
|
+
].join(",");
|
|
139
|
+
const rows = this.entries.map((e) => [
|
|
140
|
+
e.timestamp,
|
|
141
|
+
e.lab,
|
|
142
|
+
e.test,
|
|
143
|
+
e.operation,
|
|
144
|
+
e.namespace || "",
|
|
145
|
+
e.key,
|
|
146
|
+
e.dataType,
|
|
147
|
+
e.success ? "true" : "false",
|
|
148
|
+
].join(","));
|
|
149
|
+
return [headers, ...rows].join("\n");
|
|
150
|
+
}
|
|
151
|
+
return "";
|
|
152
|
+
}
|
|
153
|
+
clear() {
|
|
154
|
+
this.entries = [];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
class HttpKit {
|
|
158
|
+
authHeader = null;
|
|
159
|
+
requestHistory = [];
|
|
160
|
+
baseHeaders = {
|
|
161
|
+
"Content-Type": "application/json",
|
|
162
|
+
"User-Agent": "FennecKit/1.2.0",
|
|
163
|
+
};
|
|
164
|
+
setAuth(type, credentials) {
|
|
165
|
+
this.authHeader = { type, value: credentials };
|
|
27
166
|
}
|
|
28
|
-
|
|
29
|
-
|
|
167
|
+
clearAuth() {
|
|
168
|
+
this.authHeader = null;
|
|
169
|
+
}
|
|
170
|
+
getAuthHeader() {
|
|
171
|
+
if (!this.authHeader)
|
|
172
|
+
return {};
|
|
173
|
+
if (this.authHeader.type === "bearer") {
|
|
174
|
+
return { Authorization: `Bearer ${this.authHeader.value}` };
|
|
175
|
+
}
|
|
176
|
+
else if (this.authHeader.type === "api-key") {
|
|
177
|
+
return { "X-API-Key": this.authHeader.value };
|
|
178
|
+
}
|
|
179
|
+
else if (this.authHeader.type === "basic") {
|
|
180
|
+
return { Authorization: `Basic ${this.authHeader.value}` };
|
|
181
|
+
}
|
|
182
|
+
return {};
|
|
183
|
+
}
|
|
184
|
+
async makeRequest(method, url, body, options = {}) {
|
|
185
|
+
const startTime = Date.now();
|
|
186
|
+
const requestId = `req_\( {Date.now()}_ \){Math.random().toString(36).substr(2, 9)}`;
|
|
187
|
+
const headers = {
|
|
188
|
+
...this.baseHeaders,
|
|
189
|
+
...this.getAuthHeader(),
|
|
190
|
+
...(options.headers || {}),
|
|
191
|
+
};
|
|
192
|
+
// Use AbortController for timeout (RequestInit does not support `timeout`)
|
|
193
|
+
const controller = new AbortController();
|
|
194
|
+
const timeoutMs = options.timeout ?? 30000;
|
|
195
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
196
|
+
const reqConfig = {
|
|
197
|
+
method,
|
|
198
|
+
headers,
|
|
199
|
+
signal: controller.signal,
|
|
200
|
+
redirect: options.followRedirects !== false ? "follow" : "manual",
|
|
201
|
+
};
|
|
202
|
+
if (body) {
|
|
203
|
+
reqConfig.body = typeof body === "string" ? body : JSON.stringify(body);
|
|
204
|
+
}
|
|
205
|
+
let lastError = null;
|
|
206
|
+
const maxRetries = options.retry?.max || 1;
|
|
207
|
+
const retryDelay = options.retry?.delay || 1000;
|
|
208
|
+
try {
|
|
209
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
210
|
+
try {
|
|
211
|
+
const response = await fetch(url, reqConfig);
|
|
212
|
+
const duration = Date.now() - startTime;
|
|
213
|
+
let data = null;
|
|
214
|
+
const contentType = response.headers.get("content-type");
|
|
215
|
+
if (contentType?.includes("application/json")) {
|
|
216
|
+
data = await response.json();
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
data = await response.text();
|
|
220
|
+
}
|
|
221
|
+
const httpResponse = {
|
|
222
|
+
status: response.status,
|
|
223
|
+
statusText: response.statusText,
|
|
224
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
225
|
+
data,
|
|
226
|
+
raw: response,
|
|
227
|
+
};
|
|
228
|
+
this.recordRequest({
|
|
229
|
+
id: requestId,
|
|
230
|
+
timestamp: new Date().toISOString(),
|
|
231
|
+
method,
|
|
232
|
+
url,
|
|
233
|
+
status: response.status,
|
|
234
|
+
duration,
|
|
235
|
+
headers,
|
|
236
|
+
requestBody: body,
|
|
237
|
+
responseBody: data,
|
|
238
|
+
});
|
|
239
|
+
return httpResponse;
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
lastError = error;
|
|
243
|
+
if (attempt < maxRetries - 1) {
|
|
244
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
finally {
|
|
250
|
+
clearTimeout(timeoutId);
|
|
251
|
+
}
|
|
252
|
+
const duration = Date.now() - startTime;
|
|
253
|
+
this.recordRequest({
|
|
254
|
+
id: requestId,
|
|
255
|
+
timestamp: new Date().toISOString(),
|
|
256
|
+
method,
|
|
257
|
+
url,
|
|
258
|
+
status: 0,
|
|
259
|
+
duration,
|
|
260
|
+
headers,
|
|
261
|
+
requestBody: body,
|
|
262
|
+
error: lastError?.message,
|
|
263
|
+
});
|
|
264
|
+
throw lastError || new Error(`HTTP request failed: ${method} ${url}`);
|
|
265
|
+
}
|
|
266
|
+
recordRequest(req) {
|
|
267
|
+
this.requestHistory.push(req);
|
|
268
|
+
}
|
|
269
|
+
async get(url, options) {
|
|
270
|
+
return this.makeRequest("GET", url, undefined, options);
|
|
271
|
+
}
|
|
272
|
+
async post(url, body, options) {
|
|
273
|
+
return this.makeRequest("POST", url, body, options);
|
|
274
|
+
}
|
|
275
|
+
async put(url, body, options) {
|
|
276
|
+
return this.makeRequest("PUT", url, body, options);
|
|
277
|
+
}
|
|
278
|
+
async patch(url, body, options) {
|
|
279
|
+
return this.makeRequest("PATCH", url, body, options);
|
|
280
|
+
}
|
|
281
|
+
async delete(url, options) {
|
|
282
|
+
return this.makeRequest("DELETE", url, undefined, options);
|
|
283
|
+
}
|
|
284
|
+
getRequestHistory() {
|
|
285
|
+
return [...this.requestHistory];
|
|
286
|
+
}
|
|
287
|
+
clearHistory() {
|
|
288
|
+
this.requestHistory = [];
|
|
289
|
+
}
|
|
290
|
+
getLastRequest() {
|
|
291
|
+
return this.requestHistory[this.requestHistory.length - 1];
|
|
30
292
|
}
|
|
31
293
|
}
|
|
32
294
|
export async function newLabs(name, fn, retCount = 0) {
|
|
33
295
|
let logs = `## ${name} ๐\n\n\`\`\`\n`;
|
|
34
296
|
let currentTest = "";
|
|
297
|
+
const Store = new Map();
|
|
298
|
+
const secretVault = new SecretVault();
|
|
299
|
+
const namespaces = new Map();
|
|
35
300
|
const temp = new Map();
|
|
301
|
+
const http = new HttpKit();
|
|
302
|
+
const audit = new AuditLog();
|
|
303
|
+
const ttlMap = new Map();
|
|
36
304
|
const Context = {
|
|
37
305
|
out: () => {
|
|
38
306
|
throw new Error("__Lab_Out__");
|
|
@@ -66,26 +334,91 @@ export async function newLabs(name, fn, retCount = 0) {
|
|
|
66
334
|
logs += `[๐ก] ${currentTest}: ${msg}\n`;
|
|
67
335
|
console.log(` โ ๏ธ [Warning] ${currentTest} -> ${msg}`);
|
|
68
336
|
},
|
|
337
|
+
// Store
|
|
69
338
|
setStore: (key, value) => {
|
|
70
339
|
logs += `[โ๏ธ] [STORE] set "${key}"\n`;
|
|
71
340
|
Store.set(key, value);
|
|
341
|
+
audit.record({
|
|
342
|
+
timestamp: new Date().toISOString(),
|
|
343
|
+
lab: name,
|
|
344
|
+
test: currentTest,
|
|
345
|
+
operation: "set",
|
|
346
|
+
key,
|
|
347
|
+
dataType: typeof value,
|
|
348
|
+
success: true,
|
|
349
|
+
});
|
|
72
350
|
},
|
|
73
351
|
getStore: (key) => {
|
|
74
352
|
logs += `[โ๏ธ] [STORE] get "${key}"\n`;
|
|
353
|
+
audit.record({
|
|
354
|
+
timestamp: new Date().toISOString(),
|
|
355
|
+
lab: name,
|
|
356
|
+
test: currentTest,
|
|
357
|
+
operation: "get",
|
|
358
|
+
key,
|
|
359
|
+
dataType: "unknown",
|
|
360
|
+
success: true,
|
|
361
|
+
});
|
|
75
362
|
return Store.get(key);
|
|
76
363
|
},
|
|
77
364
|
clearStore: (key) => {
|
|
78
365
|
if (key) {
|
|
79
366
|
logs += `[โ๏ธ] [STORE] cleared key "${key}"\n`;
|
|
80
|
-
console.log(` ๐งน [Store] Cleared: "${key}"`);
|
|
81
367
|
Store.delete(key);
|
|
82
368
|
}
|
|
83
369
|
else {
|
|
84
370
|
logs += `[โ๏ธ] [STORE] cleared all data\n`;
|
|
85
|
-
console.log(` ๐งน [Store] Cleared all Store data`);
|
|
86
371
|
Store.clear();
|
|
87
372
|
}
|
|
88
373
|
},
|
|
374
|
+
// Secrets
|
|
375
|
+
setSecret: (key, value) => {
|
|
376
|
+
logs += `[๐] [SECRET] set "${key}"\n`;
|
|
377
|
+
secretVault.set(key, value);
|
|
378
|
+
audit.record({
|
|
379
|
+
timestamp: new Date().toISOString(),
|
|
380
|
+
lab: name,
|
|
381
|
+
test: currentTest,
|
|
382
|
+
operation: "setSecret",
|
|
383
|
+
key,
|
|
384
|
+
dataType: "secret",
|
|
385
|
+
success: true,
|
|
386
|
+
});
|
|
387
|
+
},
|
|
388
|
+
getSecret: (key) => {
|
|
389
|
+
logs += `[๐] [SECRET] get "${key}"\n`;
|
|
390
|
+
audit.record({
|
|
391
|
+
timestamp: new Date().toISOString(),
|
|
392
|
+
lab: name,
|
|
393
|
+
test: currentTest,
|
|
394
|
+
operation: "getSecret",
|
|
395
|
+
key,
|
|
396
|
+
dataType: "secret",
|
|
397
|
+
success: true,
|
|
398
|
+
});
|
|
399
|
+
return secretVault.get(key);
|
|
400
|
+
},
|
|
401
|
+
clearSecret: (key) => {
|
|
402
|
+
if (key) {
|
|
403
|
+
logs += `[๐] [SECRET] cleared key "${key}"\n`;
|
|
404
|
+
secretVault.delete(key);
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
logs += `[๐] [SECRET] cleared all data\n`;
|
|
408
|
+
secretVault.clear();
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
// Namespaces
|
|
412
|
+
namespace: (ns) => {
|
|
413
|
+
if (!namespaces.has(ns)) {
|
|
414
|
+
namespaces.set(ns, new NamespacedStore(ns));
|
|
415
|
+
}
|
|
416
|
+
return namespaces.get(ns);
|
|
417
|
+
},
|
|
418
|
+
getNamespace: (ns) => {
|
|
419
|
+
return namespaces.get(ns);
|
|
420
|
+
},
|
|
421
|
+
// Temp
|
|
89
422
|
setTemp: (key, value) => {
|
|
90
423
|
logs += `[โฑ๏ธ] [TEMP] set "${key}"\n`;
|
|
91
424
|
temp.set(key, value);
|
|
@@ -94,6 +427,34 @@ export async function newLabs(name, fn, retCount = 0) {
|
|
|
94
427
|
logs += `[โฑ๏ธ] [TEMP] get "${key}"\n`;
|
|
95
428
|
return temp.get(key);
|
|
96
429
|
},
|
|
430
|
+
http,
|
|
431
|
+
audit,
|
|
432
|
+
// TTL
|
|
433
|
+
setStoreWithTTL: (key, value, ttlMs) => {
|
|
434
|
+
Context.setStore(key, value);
|
|
435
|
+
const existingTimer = ttlMap.get(key);
|
|
436
|
+
if (existingTimer)
|
|
437
|
+
clearTimeout(existingTimer);
|
|
438
|
+
const timer = setTimeout(() => {
|
|
439
|
+
Store.delete(key);
|
|
440
|
+
ttlMap.delete(key);
|
|
441
|
+
logs += `[โฐ] [TTL] expired key "${key}"\n`;
|
|
442
|
+
}, ttlMs);
|
|
443
|
+
ttlMap.set(key, timer);
|
|
444
|
+
},
|
|
445
|
+
setSecretWithTTL: (key, value, ttlMs) => {
|
|
446
|
+
Context.setSecret(key, value);
|
|
447
|
+
const existingTimer = ttlMap.get(`secret:${key}`);
|
|
448
|
+
if (existingTimer)
|
|
449
|
+
clearTimeout(existingTimer);
|
|
450
|
+
const timer = setTimeout(() => {
|
|
451
|
+
secretVault.delete(key);
|
|
452
|
+
ttlMap.delete(`secret:${key}`);
|
|
453
|
+
logs += `[โฐ] [TTL] expired secret "${key}"\n`;
|
|
454
|
+
}, ttlMs);
|
|
455
|
+
ttlMap.set(`secret:${key}`, timer);
|
|
456
|
+
},
|
|
457
|
+
// Testing
|
|
97
458
|
test: async (testName, testFn) => {
|
|
98
459
|
currentTest = testName;
|
|
99
460
|
try {
|
|
@@ -103,7 +464,7 @@ export async function newLabs(name, fn, retCount = 0) {
|
|
|
103
464
|
console.log(` ๐ [Bug Exception] in "${testName}":`, e.message || e);
|
|
104
465
|
throw e;
|
|
105
466
|
}
|
|
106
|
-
}
|
|
467
|
+
},
|
|
107
468
|
};
|
|
108
469
|
console.log(`\n๐ฌ [Lab Initiated]: ${name}`);
|
|
109
470
|
try {
|
|
@@ -113,36 +474,22 @@ export async function newLabs(name, fn, retCount = 0) {
|
|
|
113
474
|
if (err.message === "__Lab_Out__") {
|
|
114
475
|
logs += "```\n";
|
|
115
476
|
temp.clear();
|
|
116
|
-
|
|
477
|
+
ttlMap.forEach((timer) => clearTimeout(timer));
|
|
478
|
+
// Write report here
|
|
479
|
+
return;
|
|
117
480
|
}
|
|
118
481
|
else if (err.message === "__Restart__") {
|
|
482
|
+
// Fixed: was newLabsV12
|
|
119
483
|
return newLabs(name, fn, retCount + 1);
|
|
120
484
|
}
|
|
121
485
|
console.log(`โ [Lab Failed]: ${name}\n`);
|
|
122
|
-
await writeRepot(logs + "```\n");
|
|
123
486
|
temp.clear();
|
|
487
|
+
ttlMap.forEach((timer) => clearTimeout(timer));
|
|
124
488
|
return;
|
|
125
489
|
}
|
|
126
490
|
logs += "```\n";
|
|
491
|
+
await report(logs);
|
|
127
492
|
console.log(`โ
[Lab Success]: ${name} completed successfully.\n`);
|
|
128
493
|
temp.clear();
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
// Global Store hadels
|
|
132
|
-
export function clearStore(key) {
|
|
133
|
-
if (key) {
|
|
134
|
-
Store.delete(key);
|
|
135
|
-
console.log(`๐งน [Global] Cleared store key: "${key}"`);
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
Store.clear();
|
|
139
|
-
console.log(`๐งน [Global] Cleared all Store data`);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
export function getStoreState() {
|
|
143
|
-
return new Map(Store);
|
|
494
|
+
ttlMap.forEach((timer) => clearTimeout(timer));
|
|
144
495
|
}
|
|
145
|
-
process.on('exit', () => {
|
|
146
|
-
console.log("[โ] All Labs stopped");
|
|
147
|
-
Store.clear();
|
|
148
|
-
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fenneckit",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "A tiny, fast & adorable testing kit. Lightweight test framework with zero config โ easy to hold, easy to run.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"testing",
|
|
@@ -34,11 +34,12 @@
|
|
|
34
34
|
"build": "tsc",
|
|
35
35
|
"test": "cd test && node ../dist/libs/fenneckit.js"
|
|
36
36
|
},
|
|
37
|
-
"bin":{
|
|
38
|
-
|
|
37
|
+
"bin": {
|
|
38
|
+
"fenneckit": "./bin/fenneckit"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"chalk": "^6.0.0",
|
|
42
|
+
"crypto": "^1.0.1",
|
|
42
43
|
"fs-extra": "^11.4.0",
|
|
43
44
|
"path": "^0.12.7"
|
|
44
45
|
},
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { access, writeFile } from "fs/promises";
|
|
2
|
+
import path from "path";
|
|
3
|
+
const d = new Date();
|
|
4
|
+
const nowDate = d.toISOString().split('T')[0].replaceAll('-', '/');
|
|
5
|
+
const Store = new Map();
|
|
6
|
+
const headOfRepotPath = `
|
|
7
|
+
# FennecKit ๐ฆ Report
|
|
8
|
+
|
|
9
|
+
**๐ Version:** 1.2.0
|
|
10
|
+
|
|
11
|
+
**โฐ Date:** ${nowDate}
|
|
12
|
+
|
|
13
|
+
**โจ Features:** Zero Config โข Sequential Labs โข Inter-Lab Data Sharing โข Store Management
|
|
14
|
+
|
|
15
|
+
---\n\n
|
|
16
|
+
`;
|
|
17
|
+
export default async function report(data) {
|
|
18
|
+
const repotPath = path.join(process.cwd(), "fenneckit.md");
|
|
19
|
+
try {
|
|
20
|
+
await access(repotPath);
|
|
21
|
+
await writeFile(repotPath, data, { flag: 'a' });
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
await writeFile(repotPath, headOfRepotPath + data);
|
|
25
|
+
}
|
|
26
|
+
}
|