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 +7 -0
- package/README.md +603 -0
- package/fenneckit.png +0 -0
- package/index.js +3 -0
- package/libs/fenneckit.js +118 -0
- package/libs/labs.js +148 -0
- package/libs/testing.js +44 -0
- package/package.json +52 -0
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
|
+

|
|
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,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
|
+
});
|
package/libs/testing.js
ADDED
|
@@ -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
|
+
}
|