fenneckit 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/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2026 lasith ruwantha amrwansha
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the β€œSoftware”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED β€œAS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,603 @@
1
+ # 🦊 FennecKit: Lab-Based Testing & Development Utility
2
+
3
+ ![logo](./fenneckit.png)
4
+
5
+ **English**: A Complete Storage System for Practical and Seamless Data Analysis
6
+
7
+ **Zero Config** β€’ Sequential Labs β€’ Inter-Lab Data Sharing (within the same file) β€’ Store Management
8
+
9
+ ---
10
+
11
+ ## 🎯 What is FennecKit?
12
+
13
+ FennecKit is a lightweight, **zero-config** testing & development utility built around the concept of **Labs**.
14
+
15
+ ### What is a "Lab"?
16
+
17
+ A **Lab** is a collection of sequential tasks (a workflow) that together accomplish one objective.
18
+
19
+ **Examples**:
20
+ - User Registration Lab β†’ create user β†’ validate β†’ save to DB β†’ send email
21
+ - Payment Processing Lab β†’ validate β†’ charge β†’ generate invoice
22
+ - API Testing Lab β†’ hit endpoints β†’ verify responses β†’ check side effects
23
+
24
+ ```typescript
25
+ // Lab = Collection of sequential tasks
26
+ await newLabs("User Registration Lab", async (kit) => {
27
+ await kit.test("Validate Email", async () => {
28
+ kit.done("Email validated");
29
+ });
30
+
31
+ await kit.test("Save to Database", async () => {
32
+ kit.done("User saved");
33
+ });
34
+
35
+ await kit.test("Send Verification Email", async () => {
36
+ kit.done("Email sent");
37
+ });
38
+ });
39
+ ```
40
+
41
+ ---
42
+
43
+ ## ⚑ Zero Config & Runner
44
+
45
+ FennecKit requires **no configuration files**.
46
+
47
+ ### How to run
48
+
49
+ ```bash
50
+ # Run a specific lab file
51
+ npx fenneckit lab.js
52
+
53
+ # Or just
54
+ npx fenneckit
55
+
56
+ # The runner automatically finds all *.labs.js / *.labs.ts files
57
+ # and executes them one after another (sequentially)
58
+ ```
59
+
60
+ **What the runner does**:
61
+ 1. Discovers lab files in the current directory (and subdirectories if configured)
62
+ 2. Runs each file **one by one**
63
+ 3. Inside each file, Labs run in the order they are written
64
+ 4. Generates a report (`fenneckit.md`) after execution
65
+
66
+ > **Important limitation**
67
+ > Data sharing (`setStore` / `getStore`) only works **inside the same file**.
68
+ > Different lab files **cannot** share STORE or TEMP data with each other.
69
+
70
+ ---
71
+
72
+ ## 🌳 Data Sharing Hierarchy (Tree Structure)
73
+
74
+ ```
75
+ πŸ“ FennecKit Execution
76
+ β”‚
77
+ β”œβ”€ πŸ“„ file1.labs.js (STORE Instance #1)
78
+ β”‚ β”‚
79
+ β”‚ β”œβ”€ πŸ”¬ Lab 1 (User Registration)
80
+ β”‚ β”‚ β”œβ”€ πŸ“ Test 1: Create User
81
+ β”‚ β”‚ β”‚ β”œβ”€ STORE: {"userId": "123"} βœ… Shared with Lab 2
82
+ β”‚ β”‚ β”‚ └─ TEMP: {"token": "abc"} ❌ Only here
83
+ β”‚ β”‚ β”‚
84
+ β”‚ β”‚ └─ πŸ“ Test 2: Send Email
85
+ β”‚ β”‚ └─ Can access STORE from Test 1
86
+ β”‚ β”‚
87
+ β”‚ β”œβ”€ πŸ”¬ Lab 2 (Authentication)
88
+ β”‚ β”‚ β”œβ”€ πŸ“ Test 1: Generate Token
89
+ β”‚ β”‚ β”‚ β”œβ”€ STORE: {"userId": "123"} βœ… From Lab 1
90
+ β”‚ β”‚ β”‚ └─ TEMP: {"token": "new"} ❌ Only here
91
+ β”‚ β”‚ β”‚
92
+ β”‚ β”‚ └─ πŸ“ Test 2: Verify Token
93
+ β”‚ β”‚ └─ Can access STORE from Labs 1 & 2
94
+ β”‚ β”‚
95
+ β”‚ └─ πŸ”¬ Lab 3 (Cleanup)
96
+ β”‚ └─ File STORE cleared when execution ends
97
+ β”‚
98
+ β”œβ”€ πŸ“„ file2.labs.js (STORE Instance #2 - ISOLATED)
99
+ β”‚ β”‚
100
+ β”‚ β”œβ”€ πŸ”¬ Lab 1
101
+ β”‚ β”‚ └─ ❌ CANNOT access file1.labs.js STORE
102
+ β”‚ β”‚
103
+ β”‚ └─ πŸ”¬ Lab 2
104
+ β”‚ └─ ❌ CANNOT access file1.labs.js STORE
105
+ β”‚
106
+ └─ πŸ“„ file3.labs.js (STORE Instance #3 - ISOLATED)
107
+ └─ ❌ Isolated from file1.labs.js and file2.labs.js
108
+ ```
109
+
110
+ ### Understanding the Hierarchy
111
+
112
+ **πŸ”΄ Level 1: Different Files = NO Data Sharing**
113
+ ```
114
+ file1.labs.js ← STORE Instance #1 (isolated)
115
+ file2.labs.js ← STORE Instance #2 (isolated)
116
+ file3.labs.js ← STORE Instance #3 (isolated)
117
+
118
+ ❌ file1's STORE β‰  file2's STORE β‰  file3's STORE
119
+ ```
120
+
121
+ **🟑 Level 2: Same File, Different Labs = STORE Sharing**
122
+ ```
123
+ file1.labs.js
124
+ β”œβ”€ Lab 1: setStore("userId", "123")
125
+ β”œβ”€ Lab 2: getStore("userId") βœ… Can access
126
+ └─ Lab 3: getStore("userId") βœ… Can still access
127
+ ```
128
+
129
+ **🟒 Level 3: Same Lab, Different Tests = STORE + TEMP Sharing**
130
+ ```
131
+ Lab 1 (User Registration)
132
+ β”œβ”€ Test 1:
133
+ β”‚ β”œβ”€ setStore("userId", "123") βœ… Shared with other tests
134
+ β”‚ └─ setTemp("token", "abc") βœ… Shared with other tests in Lab 1
135
+ β”‚
136
+ β”œβ”€ Test 2:
137
+ β”‚ β”œβ”€ getStore("userId") βœ… Works (from Test 1)
138
+ β”‚ └─ getTemp("token") βœ… Works (from Test 1)
139
+ β”‚
140
+ └─ Lab 1 ends β†’ TEMP cleared, STORE remains
141
+ ```
142
+
143
+ ---
144
+
145
+ ## πŸ”— Data Communication Between Labs (Inter-Lab Communication)
146
+
147
+ ### The Problem with Jest / Vitest
148
+
149
+ In Jest and Vitest every test is isolated. You cannot pass data from one test to another.
150
+
151
+ ### FennecKit Solution – Three Storage Levels
152
+
153
+ ```
154
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
155
+ β”‚ FennecKit Storage System (Per File) β”‚
156
+ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
157
+ β”‚ β”‚
158
+ β”‚ LEVEL 1: FILE SCOPE (Entire File) β”‚
159
+ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
160
+ β”‚ β”‚ STORE (Global - Persistent within file) β”‚ β”‚
161
+ β”‚ β”‚ β”œβ”€ Shared across ALL Labs in this file β”‚ β”‚
162
+ β”‚ β”‚ β”œβ”€ Available until file execution ends β”‚ β”‚
163
+ β”‚ β”‚ β”œβ”€ Can be manually cleared with clearStore() β”‚ β”‚
164
+ β”‚ β”‚ └─ Example: userId, authToken, orderData β”‚ β”‚
165
+ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
166
+ β”‚ β”‚
167
+ β”‚ LEVEL 2: LAB SCOPE (Single Lab) β”‚
168
+ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
169
+ β”‚ β”‚ STORE (Available to this and following Labs) β”‚ β”‚
170
+ β”‚ β”‚ └─ Set in Lab 1, used in Lab 2, Lab 3, etc β”‚ β”‚
171
+ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
172
+ β”‚ β”‚
173
+ β”‚ LEVEL 3: LAB-LOCAL SCOPE (Single Lab Only) β”‚
174
+ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
175
+ β”‚ β”‚ TEMP (Local - Auto-Cleaned) β”‚ β”‚
176
+ β”‚ β”‚ β”œβ”€ Only available inside current Lab β”‚ β”‚
177
+ β”‚ β”‚ β”œβ”€ Automatically cleared when Lab ends β”‚ β”‚
178
+ β”‚ β”‚ β”œβ”€ Cannot be manually cleared β”‚ β”‚
179
+ β”‚ β”‚ └─ Example: timestamps, temp calculations β”‚ β”‚
180
+ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
181
+ β”‚ β”‚
182
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
183
+ ```
184
+
185
+ ### Storage Demo (same file)
186
+
187
+ ```typescript
188
+ // ========== LAB 1: User Registration ==========
189
+ await newLabs("User Registration", async (kit) => {
190
+ await kit.test("Create User", async () => {
191
+ const userId = "user_123";
192
+ const email = "john@example.com";
193
+
194
+ // STORE β†’ available to all Labs in this file
195
+ kit.setStore("userId", userId);
196
+ kit.setStore("userEmail", email);
197
+
198
+ // TEMP β†’ only for this Lab
199
+ kit.setTemp("tempToken", "abc123");
200
+
201
+ kit.done("User created");
202
+ });
203
+ });
204
+
205
+ // ========== LAB 2: Authentication ==========
206
+ await newLabs("Authentication", async (kit) => {
207
+ await kit.test("Generate Token", async () => {
208
+ const userId = kit.getStore("userId"); // βœ… works (from Lab 1)
209
+ const email = kit.getStore("userEmail"); // βœ… works (from Lab 1)
210
+ const token = kit.getTemp("tempToken"); // ❌ undefined (cleared after Lab 1)
211
+
212
+ kit.done(`Token generated for: ${email}`);
213
+ });
214
+ });
215
+ ```
216
+
217
+ ---
218
+
219
+ ## 🧹 NEW: clearStore() Feature (v1.1.0)
220
+
221
+ Manually clear Store data between Labs for better state management and security.
222
+
223
+ ### Two Ways to Clear
224
+
225
+ #### 1. Clear specific key (inside Lab)
226
+ ```typescript
227
+ await newLabs("My Lab", async (kit) => {
228
+ await kit.test("Store sensitive data", async () => {
229
+ kit.setStore("authToken", "secret123");
230
+ kit.done("Token stored");
231
+ });
232
+
233
+ await kit.test("Cleanup", async () => {
234
+ kit.clearStore("authToken"); // Remove only this key
235
+ kit.done("Token cleared");
236
+ });
237
+ });
238
+ ```
239
+
240
+ #### 2. Clear all data (global)
241
+ ```typescript
242
+ import { newLabs, clearStore } from "fenneckit";
243
+
244
+ await newLabs("Lab 1", async (kit) => {
245
+ await kit.test("Setup", async () => {
246
+ kit.setStore("data1", "value1");
247
+ kit.setStore("data2", "value2");
248
+ kit.done("Stored");
249
+ });
250
+ });
251
+
252
+ clearStore(); // Clear all STORE data globally
253
+
254
+ await newLabs("Lab 2", async (kit) => {
255
+ await kit.test("Fresh Start", async () => {
256
+ const data = kit.getStore("data1"); // undefined
257
+ kit.done("Fresh state");
258
+ });
259
+ });
260
+ ```
261
+
262
+ ### When to Use clearStore()
263
+
264
+ | Scenario | Method | Why |
265
+ |----------|--------|-----|
266
+ | Remove sensitive data | `clearStore("token")` | Security |
267
+ | Free memory | `clearStore("largeObject")` | Performance |
268
+ | Test isolation | `clearStore()` | Prevent data leakage |
269
+ | Between phases | `clearStore("tempData")` | Clean state |
270
+
271
+ ---
272
+
273
+ ## πŸ“Š Storage Lifecycle (per file)
274
+
275
+ ```
276
+ FILE EXECUTION START
277
+ β”‚
278
+ β”œβ”€ Lab 1
279
+ β”‚ β”œβ”€ setStore(...) β†’ kept across all labs
280
+ β”‚ β”œβ”€ setTemp(...) β†’ kept only for this Lab
281
+ β”‚ β”œβ”€ clearStore(...) β†’ optionally remove keys
282
+ β”‚ └─ Lab ends β†’ TEMP cleared, STORE remains (unless cleared)
283
+ β”‚
284
+ β”œβ”€ Lab 2
285
+ β”‚ β”œβ”€ getStore(...) β†’ works (if not cleared)
286
+ β”‚ β”œβ”€ getTemp(...) β†’ undefined (cleared after Lab 1)
287
+ β”‚ β”œβ”€ clearStore(...) β†’ can clear for next labs
288
+ β”‚ └─ Lab ends β†’ TEMP cleared, STORE remains (unless cleared)
289
+ β”‚
290
+ └─ FILE END β†’ STORE completely cleared
291
+ ```
292
+
293
+ **Remember**: This lifecycle is **per file**.
294
+ Running `npx fenneckit fileA.labs.js` and then `npx fenneckit fileB.labs.js` gives two completely separate STORE instances.
295
+
296
+ ---
297
+
298
+ ## πŸ’‘ Real-World Example (Single File)
299
+
300
+ ```typescript
301
+ // order-pipeline.labs.js
302
+
303
+ import { newLabs, clearStore } from "fenneckit";
304
+
305
+ // LAB 1
306
+ await newLabs("Order Validation", async (kit) => {
307
+ await kit.test("Check Product Stock", async () => {
308
+ kit.setStore("productId", "prod_456");
309
+ kit.setStore("stockAvailable", 50);
310
+ kit.setTemp("validationTime", Date.now());
311
+ kit.done("Product stock verified");
312
+ });
313
+ });
314
+
315
+ // LAB 2
316
+ await newLabs("Payment Processing", async (kit) => {
317
+ await kit.test("Charge Customer Card", async () => {
318
+ const productId = kit.getStore("productId"); // βœ… From Lab 1
319
+ const chargeId = `charge_${Date.now()}`;
320
+
321
+ kit.setStore("chargeId", chargeId);
322
+ kit.setStore("orderStatus", "paid");
323
+ kit.setTemp("transactionId", chargeId);
324
+
325
+ kit.done(`Payment charged: ${chargeId}`);
326
+ });
327
+ });
328
+
329
+ // Clear sensitive payment data before shipping
330
+ clearStore("chargeId");
331
+
332
+ // LAB 3
333
+ await newLabs("Shipping & Notification", async (kit) => {
334
+ await kit.test("Create Shipping Label", async () => {
335
+ const status = kit.getStore("orderStatus"); // βœ… Available
336
+ const chargeId = kit.getStore("chargeId"); // ❌ Cleared
337
+ const tempTx = kit.getTemp("transactionId"); // ❌ Undefined
338
+
339
+ if (status === "paid") {
340
+ const tracking = `TRACK_${Date.now()}`;
341
+ kit.setStore("trackingNumber", tracking);
342
+ kit.done(`Shipping label created: ${tracking}`);
343
+ }
344
+ });
345
+
346
+ await kit.test("Send Notification Email", async () => {
347
+ const tracking = kit.getStore("trackingNumber");
348
+ kit.done(`Email sent with tracking: ${tracking}`);
349
+ });
350
+ });
351
+ ```
352
+
353
+ Run it:
354
+
355
+ ```bash
356
+ npx fenneckit order-pipeline.labs.js
357
+ ```
358
+
359
+ ---
360
+
361
+ ## πŸ› οΈ LabContext Methods
362
+
363
+ ```typescript
364
+ interface LabContext {
365
+ // Testing & Flow
366
+ test(name: string, fn: () => Promise<any>): Promise<any>
367
+ done(msg: string): void
368
+ err(msg: string): void // stops the Lab
369
+ flatErr(msg: string): void // continues
370
+ log(msg: string): void
371
+ warning(msg: string): void // warning
372
+
373
+ // Flow control
374
+ out(): void // exit Lab immediately
375
+ ret(): void // restart Lab
376
+
377
+ // Persistent (across Labs in same file)
378
+ setStore(key: string, value: any): void
379
+ getStore(key: string): any
380
+ clearStore(key?: string): void // NEW: clear specific key or all
381
+
382
+ // Temporary (Lab-local only)
383
+ setTemp(key: string, value: any): void
384
+ getTemp(key: string): any
385
+ }
386
+ ```
387
+
388
+ ---
389
+
390
+ ## πŸš€ Quick Start
391
+
392
+ ### 1. Create a lab file
393
+
394
+ ```bash
395
+ # example.labs.js
396
+ ```
397
+
398
+ ```typescript
399
+ import { newLabs } from "fenneckit";
400
+
401
+ await newLabs("User Registration Workflow", async (kit) => {
402
+ kit.log("Starting user registration...");
403
+
404
+ await kit.test("Create User", async () => {
405
+ const id = "user_" + Date.now();
406
+ kit.setStore("userId", id);
407
+ kit.done(`User created: ${id}`);
408
+ });
409
+
410
+ await kit.test("Send Welcome Email", async () => {
411
+ const id = kit.getStore("userId");
412
+ kit.done(`Email sent to user: ${id}`);
413
+ });
414
+ });
415
+ ```
416
+
417
+ ### 2. Run
418
+
419
+ ```bash
420
+ npx fenneckit example.labs.js
421
+ ```
422
+
423
+ ### 3. Check report
424
+
425
+ ```bash
426
+ cat fenneckit.md
427
+ ```
428
+
429
+ ---
430
+
431
+ ## πŸ“‹ setStore vs setTemp vs clearStore
432
+
433
+ | Scenario | Use | Why |
434
+ |----------|-----|-----|
435
+ | Pass data between Labs | `setStore` | Survives Lab end |
436
+ | Performance timing | `setTemp` | Only needed inside one Lab |
437
+ | Auth token / DB connection | `setStore` | Needed by multiple Labs |
438
+ | Temporary calculation | `setTemp` | Auto-cleaned |
439
+ | Remove sensitive data | `clearStore` | Security |
440
+ | Reset before next phase | `clearStore` | Fresh state |
441
+ | Cross-file sharing | ❌ Impossible | STORE is scoped to one file only |
442
+
443
+ ---
444
+
445
+ ## 🎯 Best Practices
446
+
447
+ 1. **Clear names**
448
+ ```typescript
449
+ kit.setStore("userId", id);
450
+ kit.setStore("authToken", token);
451
+ ```
452
+
453
+ 2. **Always check before use**
454
+ ```typescript
455
+ const userId = kit.getStore("userId");
456
+ if (!userId) {
457
+ kit.err("userId missing from previous Lab!");
458
+ }
459
+ ```
460
+
461
+ 3. **Clear sensitive data**
462
+ ```typescript
463
+ kit.clearStore("password");
464
+ kit.clearStore("creditCard");
465
+ ```
466
+
467
+ 4. **One concern per Lab**
468
+ Keep each Lab focused. Use STORE to pass only the necessary data.
469
+
470
+ 5. **Do not rely on cross-file data**
471
+ If you need data from another file, write it to disk or a database yourself.
472
+
473
+ ---
474
+
475
+ ## πŸ”„ Complete Multi-Lab Example (Payment β†’ Invoice)
476
+
477
+ ```typescript
478
+ import { newLabs, clearStore } from "fenneckit";
479
+
480
+ await newLabs("Payment Validation", async (kit) => {
481
+ await kit.test("Validate Payment Details", async () => {
482
+ kit.setStore("customerId", "cust_123");
483
+ kit.setStore("amount", 299.99);
484
+ kit.setStore("currency", "USD");
485
+ kit.setTemp("validatedAt", Date.now());
486
+ kit.done("Payment validated: 299.99 USD");
487
+ });
488
+ });
489
+
490
+ await newLabs("Process Charge", async (kit) => {
491
+ await kit.test("Charge Card", async () => {
492
+ const amount = kit.getStore("amount");
493
+ const chargeId = `charge_${Date.now()}`;
494
+ kit.setStore("chargeId", chargeId);
495
+ kit.setStore("chargedAt", new Date().toISOString());
496
+ kit.done(`Charged: ${chargeId} for ${amount}`);
497
+ });
498
+ });
499
+
500
+ // Clear payment details (security)
501
+ clearStore("chargeId");
502
+
503
+ await newLabs("Generate Invoice", async (kit) => {
504
+ await kit.test("Create Invoice PDF", async () => {
505
+ const invoiceId = `inv_${Date.now()}`;
506
+ kit.setStore("invoiceId", invoiceId);
507
+ kit.done(`Invoice created: ${invoiceId}`);
508
+ });
509
+
510
+ await kit.test("Send Invoice Email", async () => {
511
+ const invoiceId = kit.getStore("invoiceId");
512
+ const customerId = kit.getStore("customerId");
513
+ kit.done(`Invoice ${invoiceId} emailed to ${customerId}`);
514
+ });
515
+ });
516
+ ```
517
+
518
+ Run:
519
+
520
+ ```bash
521
+ npx fenneckit payment-pipeline.labs.js
522
+ ```
523
+
524
+ ---
525
+
526
+ ## πŸ“ž Troubleshooting
527
+
528
+ **Q: Lab 2 cannot see Lab 1 data?**
529
+ A: You used `setTemp`. Switch to `setStore`.
530
+
531
+ **Q: I cleared data but it's still there?**
532
+ A: Make sure you're using `clearStore()` correctly. Check key name.
533
+
534
+ **Q: Can STORE survive across different files?**
535
+ A: No. Each file execution has its own isolated STORE.
536
+ `npx fenneckit a.labs.js` and `npx fenneckit b.labs.js` do not share data.
537
+
538
+ **Q: How is TEMP cleaned?**
539
+ A: Automatically when the Lab finishes. No manual cleanup needed.
540
+
541
+ **Q: How do I run multiple files?**
542
+ A:
543
+ ```bash
544
+ npx fenneckit file1.labs.js
545
+ npx fenneckit file2.labs.js
546
+ # or let the runner discover all *.labs.* files
547
+ npx fenneckit
548
+ ```
549
+
550
+ ---
551
+
552
+ ## 🎨 Color / Status Legend
553
+
554
+ - `βœ…` / `🟒` Success
555
+ - `❌` / `πŸ”΄` Failed (Lab continues)
556
+ - `πŸ›‘` Error (Lab stops)
557
+ - `⚠️` / `🟑` Warning
558
+ - `πŸ“` / `βšͺ` Info
559
+ - `βš™οΈ` Store operation
560
+ - `⏱️` Temp operation
561
+ - `🧹` Clear operation
562
+
563
+ ---
564
+
565
+ ## πŸ’ͺ Perfect For
566
+
567
+ - Backend API testing (Express, Fastify, etc.)
568
+ - Database migration validation
569
+ - CLI tool workflows
570
+ - Microservice chaining
571
+ - Pre-deployment smoke checks
572
+ - Development-time sanity tests
573
+ - State management testing
574
+ - Data pipeline validation
575
+
576
+ ---
577
+
578
+ ## πŸ“ Version History
579
+
580
+ ### v1.0.0-beta (Current)
581
+ - ✨ Added `clearStore()` for store management
582
+ - 🌳 Improved data sharing hierarchy documentation
583
+ - 🧹 Better state cleanup capabilities
584
+
585
+ ### v1.0.0 (Initial Release)
586
+ - Core Lab functionality
587
+ - STORE & TEMP storage
588
+ - Zero config runner
589
+ - Report generation
590
+
591
+ ---
592
+
593
+ ## πŸ“ License
594
+
595
+ **Copyright Β© 2026 Lasith Ruwantha Amrwansha**
596
+ Written: 2026/09/17
597
+ Updated: 2026/09/20
598
+ Author: Ruwantha Amrwansha
599
+ Library: FennecKit 🦊
600
+
601
+ ---
602
+
603
+ **Happy Testing! 🦊⚑**
package/fenneckit.png ADDED
Binary file
package/index.js ADDED
@@ -0,0 +1,3 @@
1
+ import { newLabs, getStoreState, clearStore, } from "./libs/labs.js";
2
+ import { testing } from "./libs/testing.js";
3
+ export { newLabs, testing, getStoreState, clearStore };
@@ -0,0 +1,118 @@
1
+ /*==================================
2
+ * Copyright 2026 lasith ruwantha amrwansha
3
+ * Written by 2026/09/17
4
+ * Author: ruwantha amrwansha
5
+ * Library: FennecKit 🦊
6
+ *===================================*/
7
+ import { readdir, unlink } from "fs/promises";
8
+ import { execSync } from "child_process";
9
+ import chalk from "chalk";
10
+ async function LabsFile(path) {
11
+ const labs = [];
12
+ try {
13
+ const files = await readdir(path, { withFileTypes: true });
14
+ for (const file of files) {
15
+ if (file.name.endsWith(".labs.js")) {
16
+ labs.push(file.name);
17
+ }
18
+ else if (file.isDirectory()) {
19
+ if (file.name === "node_modules" || file.name.startsWith("."))
20
+ continue;
21
+ let x = await LabsFile(`${path}/${file.name}`);
22
+ x = x.map((e) => `${file.name}/${e}`);
23
+ labs.push(...x);
24
+ }
25
+ }
26
+ return labs;
27
+ }
28
+ catch (error) {
29
+ throw new Error("Can't find Labs File", { cause: error });
30
+ }
31
+ }
32
+ function Runner(path) {
33
+ try {
34
+ console.log(chalk.cyan(`\nπŸ”¬ Running: ${path}`));
35
+ execSync(`node ${path}`, {
36
+ stdio: "inherit",
37
+ timeout: 60000
38
+ });
39
+ console.log(chalk.green(`βœ… Finished: ${path}\n`));
40
+ return true;
41
+ }
42
+ catch (error) {
43
+ const errMessage = error instanceof Error ? error.message : String(error);
44
+ console.error(chalk.red(`\n❌ Failed: ${path}`));
45
+ console.error(chalk.red(` Error: ${errMessage}\n`));
46
+ return false;
47
+ }
48
+ }
49
+ async function fenneckit() {
50
+ const args = process.argv.slice(2);
51
+ if (args.includes("--help") || args.includes("-h")) {
52
+ console.log(`
53
+ FennecKit 🦊
54
+
55
+ Usage:
56
+ npx fenneckit Run all *.labs.js files
57
+ npx fenneckit <file.labs.js> Run single file
58
+ npx fenneckit --help Show help
59
+ npx fenneckit --version Show version
60
+ `);
61
+ return;
62
+ }
63
+ if (args.includes("--version") || args.includes("-v")) {
64
+ console.log("FennecKit 🦊 v1.1.0-beta");
65
+ return;
66
+ }
67
+ const singleFile = args.find(a => !a.startsWith("-"));
68
+ if (singleFile) {
69
+ Runner(singleFile);
70
+ return;
71
+ }
72
+ const path = process.cwd();
73
+ const labs = await LabsFile(path);
74
+ if (labs.length === 0) {
75
+ console.log(chalk.yellow("\n⚠️ No .labs.js files found.\n"));
76
+ return;
77
+ }
78
+ console.log(chalk.blue(`\nπŸ“Š Found ${labs.length} lab file(s)\n`));
79
+ let passed = 0;
80
+ let failed = 0;
81
+ for (const file of labs) {
82
+ const success = Runner(file);
83
+ if (success)
84
+ passed++;
85
+ else
86
+ failed++;
87
+ }
88
+ console.log(chalk.blue(`\n${'='.repeat(50)}`));
89
+ console.log(chalk.blue(`πŸ“‹ Test Summary:`));
90
+ console.log(chalk.green(` βœ… Passed: ${passed}`));
91
+ if (failed > 0)
92
+ console.log(chalk.red(` ❌ Failed: ${failed}`));
93
+ console.log(chalk.blue(`${'='.repeat(50)}\n`));
94
+ }
95
+ function banner() {
96
+ const neon = chalk.hex("#1F51FF");
97
+ console.log(neon(`
98
+ 8888888888 888 d8P d8b 888
99
+ 888 888 d8P Y8P 888
100
+ 888 888 d8P 888
101
+ 8888888 .d88b. 88888b. 88888b. .d88b. .d8888b 888d88K 888 888888
102
+ 888 d8P Y8b 888 "88b 888 "88b d8P Y8b d88P" 8888888b 888 888
103
+ 888 88888888 888 888 888 888 88888888 888 888 Y88b 888 888
104
+ 888 Y8b. 888 888 888 888 Y8b. Y8b. 888 Y88b 888 Y8b.
105
+ 888 "Y8888 888 888 888 888 "Y8888 "Y8888P 888 Y88b 888 "Y888
106
+ `));
107
+ }
108
+ async function deleteOldReport() {
109
+ try {
110
+ await unlink("./fenneckit.md");
111
+ }
112
+ catch {
113
+ }
114
+ }
115
+ console.log("\n");
116
+ banner();
117
+ deleteOldReport();
118
+ fenneckit();
package/libs/labs.js ADDED
@@ -0,0 +1,148 @@
1
+ /*==================================
2
+ * Copyright 2026 lasith ruwantha amrwansha
3
+ * Written by 2026/09/17
4
+ * Author: ruwantha amrwansha
5
+ * Library: FennecKit 🦊
6
+ * Version: 1.1.0-beta (with clearStore feature)
7
+ *===================================*/
8
+ import { access, writeFile } from "fs/promises";
9
+ import path from "path";
10
+ const d = new Date();
11
+ const nowDate = d.toISOString().split('T')[0].replaceAll('-', '/');
12
+ const Store = new Map();
13
+ const headOfRepotPath = `
14
+ # FennecKit 🦊 Report
15
+
16
+ **πŸš€ Version:** 1.1.0-beta
17
+ **⏰ Date:** ${nowDate}
18
+ **✨ Features:** Zero Config β€’ Sequential Labs β€’ Inter-Lab Data Sharing β€’ Store Management
19
+
20
+ ---\n\n
21
+ `;
22
+ async function writeRepot(data) {
23
+ const repotPath = path.join(process.cwd(), "fenneckit.md");
24
+ try {
25
+ await access(repotPath);
26
+ await writeFile(repotPath, data, { flag: 'a' });
27
+ }
28
+ catch {
29
+ await writeFile(repotPath, headOfRepotPath + data);
30
+ }
31
+ }
32
+ export async function newLabs(name, fn, retCount = 0) {
33
+ let logs = `## ${name} 🌏\n\n\`\`\`\n`;
34
+ let currentTest = "";
35
+ const temp = new Map();
36
+ const Context = {
37
+ out: () => {
38
+ throw new Error("__Lab_Out__");
39
+ },
40
+ ret: () => {
41
+ if (retCount < 3) {
42
+ throw new Error("__Restart__");
43
+ }
44
+ else {
45
+ Context.err("Restart limit exceeded (max 3 attempts)");
46
+ }
47
+ },
48
+ flatErr: (msg) => {
49
+ logs += `[πŸ”΄] ${currentTest}: ${msg}\n`;
50
+ console.log(` ❌ [Failed] ${currentTest} -> ${msg}`);
51
+ },
52
+ err: (msg) => {
53
+ logs += `[πŸ”΄] ${currentTest}: ${msg}\n`;
54
+ console.log(` πŸ›‘ [Error] ${currentTest} -> ${msg}`);
55
+ Context.out();
56
+ },
57
+ done: (msg) => {
58
+ logs += `[🟒] ${currentTest}: ${msg}\n`;
59
+ console.log(` βœ… [Done] ${currentTest} -> ${msg}`);
60
+ },
61
+ log: (msg) => {
62
+ logs += `[βšͺ] ${currentTest}: ${msg}\n`;
63
+ console.log(` πŸ“ [Info] ${currentTest} -> ${msg}`);
64
+ },
65
+ warning: (msg) => {
66
+ logs += `[🟑] ${currentTest}: ${msg}\n`;
67
+ console.log(` ⚠️ [Warning] ${currentTest} -> ${msg}`);
68
+ },
69
+ setStore: (key, value) => {
70
+ logs += `[βš™οΈ] [STORE] set "${key}"\n`;
71
+ Store.set(key, value);
72
+ },
73
+ getStore: (key) => {
74
+ logs += `[βš™οΈ] [STORE] get "${key}"\n`;
75
+ return Store.get(key);
76
+ },
77
+ clearStore: (key) => {
78
+ if (key) {
79
+ logs += `[βš™οΈ] [STORE] cleared key "${key}"\n`;
80
+ console.log(` 🧹 [Store] Cleared: "${key}"`);
81
+ Store.delete(key);
82
+ }
83
+ else {
84
+ logs += `[βš™οΈ] [STORE] cleared all data\n`;
85
+ console.log(` 🧹 [Store] Cleared all Store data`);
86
+ Store.clear();
87
+ }
88
+ },
89
+ setTemp: (key, value) => {
90
+ logs += `[⏱️] [TEMP] set "${key}"\n`;
91
+ temp.set(key, value);
92
+ },
93
+ getTemp: (key) => {
94
+ logs += `[⏱️] [TEMP] get "${key}"\n`;
95
+ return temp.get(key);
96
+ },
97
+ test: async (testName, testFn) => {
98
+ currentTest = testName;
99
+ try {
100
+ return await testFn();
101
+ }
102
+ catch (e) {
103
+ console.log(` πŸ› [Bug Exception] in "${testName}":`, e.message || e);
104
+ throw e;
105
+ }
106
+ }
107
+ };
108
+ console.log(`\nπŸ”¬ [Lab Initiated]: ${name}`);
109
+ try {
110
+ await fn(Context);
111
+ }
112
+ catch (err) {
113
+ if (err.message === "__Lab_Out__") {
114
+ logs += "```\n";
115
+ temp.clear();
116
+ return await writeRepot(logs);
117
+ }
118
+ else if (err.message === "__Restart__") {
119
+ return newLabs(name, fn, retCount + 1);
120
+ }
121
+ console.log(`❌ [Lab Failed]: ${name}\n`);
122
+ await writeRepot(logs + "```\n");
123
+ temp.clear();
124
+ return;
125
+ }
126
+ logs += "```\n";
127
+ console.log(`βœ… [Lab Success]: ${name} completed successfully.\n`);
128
+ temp.clear();
129
+ await writeRepot(logs);
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);
144
+ }
145
+ process.on('exit', () => {
146
+ console.log("[β›”] All Labs stopped");
147
+ Store.clear();
148
+ });
@@ -0,0 +1,44 @@
1
+ /*==================================
2
+ * Copyright 2026 lasith ruwantha amrwansha
3
+ * Written by 2026/09/17
4
+ * Author: ruwantha amrwansha
5
+ * Library: FennecKit 🦊
6
+ *===================================*/
7
+ import chalk from "chalk";
8
+ // Development-time message utility
9
+ class messages {
10
+ static error = (msg) => {
11
+ return this.brakOf(msg);
12
+ };
13
+ static brakOf(msg) {
14
+ console.log(chalk.bgRed.black("[error]:"), msg);
15
+ process.exit(0);
16
+ }
17
+ static succes(msg) {
18
+ console.log(chalk.bgGreen.black("[success]:"), msg);
19
+ }
20
+ static log(msg) {
21
+ console.log(chalk.bgWhite.black("[logs]:"), msg);
22
+ }
23
+ static worring(msg) {
24
+ console.log(chalk.bgYellow.black("[worring]:"), msg);
25
+ }
26
+ }
27
+ /*
28
+ * Single-level testing toolkit for quick development-time utility checks
29
+ */
30
+ export async function testing(fn) {
31
+ const context = {
32
+ succes: (msg) => messages.succes(msg),
33
+ err: (msg) => messages.error(msg),
34
+ log: (msg) => messages.log(msg),
35
+ worrn: (msg) => messages.worring(msg)
36
+ };
37
+ try {
38
+ const result = await fn(context);
39
+ return result;
40
+ }
41
+ catch (error) {
42
+ console.error(chalk.bgRed.black("[exception]:"), error);
43
+ }
44
+ }
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "fenneckit",
3
+ "version": "1.0.0",
4
+ "description": "A tiny, fast & adorable testing kit. Lightweight test framework with zero config – easy to hold, easy to run.",
5
+ "keywords": [
6
+ "testing",
7
+ "test",
8
+ "libray",
9
+ "esay",
10
+ "poupler",
11
+ "fenneckit",
12
+ "vtest",
13
+ "developer",
14
+ "tyne",
15
+ "units",
16
+ "labs",
17
+ "lab",
18
+ "lab-test"
19
+ ],
20
+ "homepage": "https://github.com/Ruwantha-OFFICIAL/fenneckit#readme",
21
+ "bugs": {
22
+ "url": "https://github.com/Ruwantha-OFFICIAL/fenneckit/issues"
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/Ruwantha-OFFICIAL/fenneckit.git"
27
+ },
28
+ "license": "MIT",
29
+ "author": "lasith ruwantha amrwansha",
30
+ "type": "module",
31
+ "main": "index.js",
32
+ "scripts": {
33
+ "eslint": "eslint .",
34
+ "build": "tsc",
35
+ "test": "cd test && node ../dist/libs/fenneckit.js"
36
+ },
37
+ "dependencies": {
38
+ "chalk": "^6.0.0",
39
+ "fs-extra": "^11.4.0",
40
+ "path": "^0.12.7"
41
+ },
42
+ "devDependencies": {
43
+ "@eslint/js": "^10.0.1",
44
+ "@types/node": "^22.20.3",
45
+ "eslint": "^10.10.0",
46
+ "globals": "^17.12.0",
47
+ "jiti": "^2.7.0",
48
+ "tsx": "^4.23.13",
49
+ "typescript": "^6.0.3",
50
+ "typescript-eslint": "^8.70.0"
51
+ }
52
+ }