jw-automator 4.0.1 → 6.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.
@@ -21,7 +21,7 @@ const Automator = require('jw-automator');
21
21
 
22
22
  // Create automator with file-based storage
23
23
  const automator = new Automator({
24
- storage: Automator.storage.file('./my-actions.json')
24
+ storage: Automator.storage.file('./my-tasks.json')
25
25
  });
26
26
 
27
27
  // Register a function
@@ -29,8 +29,8 @@ automator.addFunction('sayHello', function(payload) {
29
29
  console.log(`Hello, ${payload.name}!`);
30
30
  });
31
31
 
32
- // Add a repeating action
33
- automator.addAction({
32
+ // Add a repeating task
33
+ automator.addTask({
34
34
  name: 'Greeting',
35
35
  cmd: 'sayHello',
36
36
  date: new Date(Date.now() + 2000), // Start in 2 seconds
@@ -43,8 +43,8 @@ automator.addAction({
43
43
  });
44
44
 
45
45
  // Listen to events
46
- automator.on('action', (event) => {
47
- console.log(`Action executed: ${event.name}`);
46
+ automator.on('task', (event) => {
47
+ console.log(`Task executed: ${event.name}`);
48
48
  });
49
49
 
50
50
  // Start the scheduler
@@ -66,7 +66,7 @@ node app.js
66
66
  ### Daily Task at Specific Time
67
67
 
68
68
  ```javascript
69
- automator.addAction({
69
+ automator.addTask({
70
70
  name: 'Daily Backup',
71
71
  cmd: 'runBackup',
72
72
  date: new Date('2025-05-01T02:00:00'), // 2:00 AM
@@ -80,7 +80,7 @@ automator.addAction({
80
80
  ### Weekday Task
81
81
 
82
82
  ```javascript
83
- automator.addAction({
83
+ automator.addTask({
84
84
  name: 'Weekday Reminder',
85
85
  cmd: 'sendReminder',
86
86
  date: new Date('2025-05-01T09:00:00'), // 9:00 AM
@@ -94,7 +94,7 @@ automator.addAction({
94
94
  ### Every N Minutes
95
95
 
96
96
  ```javascript
97
- automator.addAction({
97
+ automator.addTask({
98
98
  name: 'Health Check',
99
99
  cmd: 'checkHealth',
100
100
  date: new Date(),
@@ -108,7 +108,7 @@ automator.addAction({
108
108
  ### Limited Run Count
109
109
 
110
110
  ```javascript
111
- automator.addAction({
111
+ automator.addTask({
112
112
  name: 'Startup Sequence',
113
113
  cmd: 'initSystem',
114
114
  date: new Date(),
@@ -123,7 +123,7 @@ automator.addAction({
123
123
  ### End Date
124
124
 
125
125
  ```javascript
126
- automator.addAction({
126
+ automator.addTask({
127
127
  name: 'Summer Sprinklers',
128
128
  cmd: 'waterLawn',
129
129
  date: new Date('2025-06-01T06:00:00'),
@@ -143,8 +143,8 @@ Automator v4 manages offline catch-up using the `catchUpWindow` property, which
143
143
 
144
144
  ### Smart Defaults for `catchUpWindow`
145
145
 
146
- - **Recurring Actions:** If `catchUpWindow` is not specified, it defaults to the **duration of the action's recurrence interval**. This ensures short delays are recovered, but long outages don't cause a "thundering herd."
147
- - **One-Time Actions:** If `catchUpWindow` is not specified and the action has no `repeat` property, it defaults to **`0`** (skip if missed).
146
+ - **Recurring Tasks:** If `catchUpWindow` is not specified, it defaults to the **duration of the task's recurrence interval**. This ensures short delays are recovered, but long outages don't cause a "thundering herd."
147
+ - **One-Time Tasks:** If `catchUpWindow` is not specified and the task has no `repeat` property, it defaults to **`0`** (skip if missed).
148
148
 
149
149
  ### Explicit Control
150
150
 
@@ -157,7 +157,7 @@ You can explicitly set `catchUpWindow`:
157
157
  #### Example: Critical Task (unlimited catch-up)
158
158
 
159
159
  ```javascript
160
- automator.addAction({
160
+ automator.addTask({
161
161
  name: 'Critical Task',
162
162
  cmd: 'criticalTask',
163
163
  date: new Date('2025-05-01T10:00:00'),
@@ -171,7 +171,7 @@ If the system is offline from 10:00 to 13:00, it will execute the 10:00, 11:00,
171
171
  #### Example: Animation Frame (skip missed)
172
172
 
173
173
  ```javascript
174
- automator.addAction({
174
+ automator.addTask({
175
175
  name: 'Animation Frame',
176
176
  cmd: 'updateAnimation',
177
177
  date: new Date(),
@@ -194,7 +194,7 @@ Preview what will happen in the future:
194
194
  const tomorrow = new Date();
195
195
  tomorrow.setDate(tomorrow.getDate() + 1);
196
196
 
197
- const events = automator.getActionsInRange(new Date(), tomorrow);
197
+ const events = automator.getTasksInRange(new Date(), tomorrow);
198
198
 
199
199
  console.log(`${events.length} events scheduled in next 24 hours`);
200
200
 
@@ -207,35 +207,50 @@ events.slice(0, 5).forEach(event => {
207
207
 
208
208
  ## Storage Options
209
209
 
210
- ### File Storage
210
+ ### File-Based Persistence
211
211
 
212
212
  ```javascript
213
213
  const automator = new Automator({
214
- storage: Automator.storage.file('./actions.json')
214
+ storageFile: './tasks.json',
215
+ autoSave: true, // default: true
216
+ saveInterval: 15000 // default: 15000ms (15 seconds)
215
217
  });
216
218
  ```
217
219
 
218
- ### Memory Storage (No Persistence)
220
+ **Moratorium-Based Persistence:**
221
+ - CRUD operations (add/update/remove) save immediately and start a moratorium period
222
+ - Task execution marks state as dirty and saves if moratorium has expired
223
+ - `saveInterval` sets the moratorium period (minimum cooling time between saves)
224
+ - Reduces disk wear from task execution while ensuring CRUD changes persist immediately
225
+ - `stop()` always saves immediately if dirty, ignoring any active moratorium
226
+
227
+ ### Memory-Only Mode (No Persistence)
219
228
 
220
229
  ```javascript
221
230
  const automator = new Automator({
222
- storage: Automator.storage.memory()
231
+ // No storageFile = memory-only mode
223
232
  });
224
233
  ```
225
234
 
226
- ### Custom Storage
235
+ State is lost when the process ends.
236
+
237
+ ### Custom Storage (Database, Cloud, etc.)
238
+
239
+ For custom persistence, use `getTasks()` and event listeners:
227
240
 
228
241
  ```javascript
229
- const automator = new Automator({
230
- storage: {
231
- load: function() {
232
- // Load from database, cloud, etc.
233
- return { actions: [...] };
234
- },
235
- save: function(state) {
236
- // Save to database, cloud, etc.
237
- }
238
- }
242
+ const automator = new Automator(); // Memory-only
243
+
244
+ // Load from custom source
245
+ automator.seed(async (auto) => {
246
+ const tasks = await loadFromDatabase();
247
+ tasks.forEach(task => auto.addTask(task));
248
+ });
249
+
250
+ // Save on updates
251
+ automator.on('update', async () => {
252
+ const tasks = automator.getTasks();
253
+ await saveToDatabase(tasks);
239
254
  });
240
255
  ```
241
256
 
@@ -249,17 +264,17 @@ automator.on('ready', () => {
249
264
  console.log('Scheduler started');
250
265
  });
251
266
 
252
- // Action executed
253
- automator.on('action', (event) => {
254
- console.log('Action:', event.name);
267
+ // Task executed
268
+ automator.on('task', (event) => {
269
+ console.log('Task:', event.name);
255
270
  console.log('Scheduled:', event.scheduledTime);
256
271
  console.log('Actual:', event.actualTime);
257
272
  console.log('Count:', event.count);
258
273
  });
259
274
 
260
- // Action added/updated/removed
275
+ // Task added/updated/removed
261
276
  automator.on('update', (event) => {
262
- console.log('Update:', event.operation, event.actionId);
277
+ console.log('Update:', event.operation, event.taskId);
263
278
  });
264
279
 
265
280
  // Errors
@@ -275,12 +290,12 @@ automator.on('warning', (event) => {
275
290
 
276
291
  ---
277
292
 
278
- ## Managing Actions
293
+ ## Managing Tasks
279
294
 
280
295
  ### Add
281
296
 
282
297
  ```javascript
283
- const id = automator.addAction({
298
+ const id = automator.addTask({
284
299
  name: 'My Task',
285
300
  cmd: 'myCommand',
286
301
  date: new Date(),
@@ -292,13 +307,13 @@ const id = automator.addAction({
292
307
 
293
308
  ```javascript
294
309
  // By ID
295
- automator.updateActionByID(id, {
310
+ automator.updateTaskByID(id, {
296
311
  name: 'Updated Task',
297
312
  repeat: { type: 'hour', interval: 2 }
298
313
  });
299
314
 
300
315
  // By name
301
- automator.updateActionByName('My Task', {
316
+ automator.updateTaskByName('My Task', {
302
317
  payload: { updated: true }
303
318
  });
304
319
  ```
@@ -307,26 +322,26 @@ automator.updateActionByName('My Task', {
307
322
 
308
323
  ```javascript
309
324
  // By ID
310
- automator.removeActionByID(id);
325
+ automator.removeTaskByID(id);
311
326
 
312
327
  // By name
313
- automator.removeActionByName('My Task');
328
+ automator.removeTaskByName('My Task');
314
329
  ```
315
330
 
316
331
  ### Query
317
332
 
318
333
  ```javascript
319
- // All actions
320
- const all = automator.getActions();
334
+ // All tasks
335
+ const all = automator.getTasks();
321
336
 
322
337
  // By name
323
- const tasks = automator.getActionsByName('My Task');
338
+ const tasks = automator.getTasksByName('My Task');
324
339
 
325
340
  // By ID
326
- const task = automator.getActionByID(id);
341
+ const task = automator.getTaskByID(id);
327
342
 
328
343
  // Description
329
- const desc = automator.describeAction(id);
344
+ const desc = automator.describeTask(id);
330
345
  console.log(desc);
331
346
  ```
332
347
 
@@ -334,12 +349,12 @@ console.log(desc);
334
349
 
335
350
  ## DST Handling
336
351
 
337
- For actions that run during daylight saving time transitions:
352
+ For tasks that run during daylight saving time transitions:
338
353
 
339
354
  ### Fall Back (Repeated Hour)
340
355
 
341
356
  ```javascript
342
- automator.addAction({
357
+ automator.addTask({
343
358
  name: 'DST Aware',
344
359
  cmd: 'task',
345
360
  date: new Date('2025-11-02T01:30:00'), // Falls in repeated hour
@@ -1,8 +1,8 @@
1
1
  # Automator Defensive Defaults Strategy
2
2
 
3
- The Automator's design philosophy for handling action specifications is **"Fail loudly, run defensively."**
3
+ The Automator's design philosophy for handling task specifications is **"Fail loudly, run defensively."**
4
4
 
5
- The goal is to maximize system robustness. Rather than rejecting an action with minor errors or missing properties, the Automator will make reasonable, "defensive" assumptions to coerce the action into a valid, runnable state.
5
+ The goal is to maximize system robustness. Rather than rejecting a task with minor errors or missing properties, the Automator will make reasonable, "defensive" assumptions to coerce the task into a valid, runnable state.
6
6
 
7
7
  However, these corrections are never silent. Whenever a default is applied or a value is coerced due to invalid input, the Automator emits either a `warning` or `debug` event. This ensures that the developer is always aware of any assumptions the system has made on their behalf.
8
8
 
@@ -10,14 +10,14 @@ However, these corrections are never silent. Whenever a default is applied or a
10
10
 
11
11
  ### Property Default and Coercion Rules
12
12
 
13
- The following rules are applied when an action is added via `addAction()` or updated via `updateAction...()`.
13
+ The following rules are applied when a task is added via `addTask()` or updated via `updateTask...()`.
14
14
 
15
15
  #### **`cmd`**
16
- - **Rule:** An action without a command is not runnable.
17
- - **Behavior:** Throws a hard `Error` if missing. This is the primary exception to the "run defensively" rule, as the action's intent cannot be determined.
16
+ - **Rule:** A task without a command is not runnable.
17
+ - **Behavior:** Throws a hard `Error` if missing. This is the primary exception to the "run defensively" rule, as the task's intent cannot be determined.
18
18
 
19
19
  #### **`date`** (Start Time)
20
- - **Rule:** An action needs a starting time.
20
+ - **Rule:** A task needs a starting time.
21
21
  - **Default:** If `date` is not provided, it defaults to **5 seconds in the future** from the time it was added.
22
22
  - **Event:** `debug`
23
23
 
@@ -32,15 +32,15 @@ The following rules are applied when an action is added via `addAction()` or upd
32
32
  2. **Legacy `unBuffered`:** If `catchUpWindow` is absent but the legacy `unBuffered` property is present, it is mapped for backwards compatibility:
33
33
  - `unBuffered: true` maps to `catchUpWindow: 0` (no catch-up).
34
34
  - `unBuffered: false` maps to `catchUpWindow: "unlimited"`.
35
- 3. **Smart Default (Recurring):** If the action has a `repeat` property, `catchUpWindow` defaults to the **duration of the repeat interval** (e.g., an hourly action gets a 1-hour catch-up window).
36
- 4. **Smart Default (One-Time):** If the action does not have a `repeat` property, `catchUpWindow` defaults to **`0`** (no catch-up).
35
+ 3. **Smart Default (Recurring):** If the task has a `repeat` property, `catchUpWindow` defaults to the **duration of the repeat interval** (e.g., an hourly task gets a 1-hour catch-up window).
36
+ 4. **Smart Default (One-Time):** If the task does not have a `repeat` property, `catchUpWindow` defaults to **`0`** (no catch-up).
37
37
 
38
38
  ---
39
39
 
40
40
  ### Recurrence Rules (`repeat.*`)
41
41
 
42
42
  #### **`repeat.type`**
43
- - **Rule:** The recurrence `type` is fundamental to the action's behavior and must be a valid string (e.g., 'hour', 'day', 'week').
43
+ - **Rule:** The recurrence `type` is fundamental to the task's behavior and must be a valid string (e.g., 'hour', 'day', 'week').
44
44
  - **Behavior:** Throws a hard `Error` if missing or invalid. Unlike other properties, the ambiguity of an invalid type is considered a fatal error, as the user's intent cannot be safely determined.
45
45
 
46
46
  #### **`repeat.interval`**
@@ -8,7 +8,7 @@ const Automator = require('../index');
8
8
 
9
9
  // Create automator with file-based persistence
10
10
  const automator = new Automator({
11
- storage: Automator.storage.file('./example-actions.json'),
11
+ storageFile: './example-actions.json',
12
12
  autoSave: true,
13
13
  saveInterval: 5000
14
14
  });
@@ -36,7 +36,7 @@ automator.addFunction('weeklyBackup', function(payload) {
36
36
  // Listen to events
37
37
  automator.on('ready', () => {
38
38
  console.log('Automator started!');
39
- console.log('Current actions:', automator.getActions().length);
39
+ console.log('Current actions:', automator.getTasks().length);
40
40
  });
41
41
 
42
42
  automator.on('action', (event) => {
@@ -51,11 +51,11 @@ automator.on('error', (event) => {
51
51
  });
52
52
 
53
53
  // Seed initial actions (runs only on first use)
54
- automator.seed((auto) => {
54
+ const seedResult = automator.seed((auto) => {
55
55
  console.log('Seeding initial actions...\n');
56
56
 
57
57
  // 1. Every 10 seconds - demo message
58
- auto.addAction({
58
+ const result1 = auto.addTask({
59
59
  name: 'Demo Message',
60
60
  cmd: 'logMessage',
61
61
  date: new Date(Date.now() + 5000), // Start in 5 seconds
@@ -68,9 +68,12 @@ automator.seed((auto) => {
68
68
  dstPolicy: 'once'
69
69
  }
70
70
  });
71
+ if (!result1.success) {
72
+ console.error('Failed to add demo task:', result1.error);
73
+ }
71
74
 
72
75
  // 2. Daily morning routine at 7:00 AM
73
- auto.addAction({
76
+ const result2 = auto.addTask({
74
77
  name: 'Morning Routine',
75
78
  cmd: 'morningRoutine',
76
79
  date: new Date(new Date().setHours(7, 0, 0, 0)),
@@ -81,13 +84,16 @@ automator.seed((auto) => {
81
84
  dstPolicy: 'once'
82
85
  }
83
86
  });
87
+ if (!result2.success) {
88
+ console.error('Failed to add morning routine:', result2.error);
89
+ }
84
90
 
85
91
  // 3. Weekly backup every Sunday at 2:00 AM
86
92
  const nextSunday = new Date();
87
93
  nextSunday.setDate(nextSunday.getDate() + (7 - nextSunday.getDay()) % 7);
88
94
  nextSunday.setHours(2, 0, 0, 0);
89
95
 
90
- auto.addAction({
96
+ const result3 = auto.addTask({
91
97
  name: 'Weekly Backup',
92
98
  cmd: 'weeklyBackup',
93
99
  date: nextSunday,
@@ -98,14 +104,23 @@ automator.seed((auto) => {
98
104
  dstPolicy: 'once'
99
105
  }
100
106
  });
107
+ if (!result3.success) {
108
+ console.error('Failed to add weekly backup:', result3.error);
109
+ }
101
110
  });
102
111
 
112
+ if (seedResult.seeded) {
113
+ console.log('✅ Database seeded successfully\n');
114
+ } else {
115
+ console.log('ℹ️ Database already populated - seeding skipped\n');
116
+ }
117
+
103
118
  // Demonstrate simulation - what will happen in the next 24 hours?
104
119
  console.log('\n=== Simulation: Next 24 hours ===');
105
120
  const now = new Date();
106
121
  const tomorrow = new Date(now.getTime() + 24 * 60 * 60 * 1000);
107
122
 
108
- const futureEvents = automator.getActionsInRange(now, tomorrow);
123
+ const futureEvents = automator.getTasksInRange(now, tomorrow);
109
124
  console.log(`${futureEvents.length} events scheduled in the next 24 hours\n`);
110
125
 
111
126
  futureEvents.slice(0, 10).forEach((event, i) => {
@@ -117,8 +132,8 @@ if (futureEvents.length > 10) {
117
132
  }
118
133
 
119
134
  console.log('\n=== Current Actions ===');
120
- automator.getActions().forEach((action) => {
121
- console.log(automator.describeAction(action.id));
135
+ automator.getTasks().forEach((action) => {
136
+ console.log(automator.describeTask(action.id));
122
137
  console.log('---');
123
138
  });
124
139
 
@@ -8,7 +8,7 @@ const Automator = require('../index');
8
8
 
9
9
  // Create automator (in-memory, no persistence)
10
10
  const automator = new Automator({
11
- storage: Automator.storage.memory()
11
+ // No storageFile = memory-only mode
12
12
  });
13
13
 
14
14
  // Register a function
@@ -17,7 +17,7 @@ automator.addFunction('sayHello', function() {
17
17
  });
18
18
 
19
19
  // Add an action that runs every 3 seconds, 5 times
20
- automator.addAction({
20
+ automator.addTask({
21
21
  cmd: 'sayHello',
22
22
  date: new Date(Date.now() + 1000), // Start in 1 second
23
23
  repeat: {
@@ -9,7 +9,7 @@ const Automator = require('../index');
9
9
 
10
10
  // Create automator
11
11
  const automator = new Automator({
12
- storage: Automator.storage.memory() // Use memory storage for this example
12
+ // Memory-only mode (no persistence)
13
13
  });
14
14
 
15
15
  // Simulated sensor data
@@ -65,7 +65,7 @@ automator.addFunction('dailyReport', function() {
65
65
  // Add sensor reading actions
66
66
 
67
67
  // 1. Temperature reading every 5 seconds (for 1 minute demo)
68
- automator.addAction({
68
+ automator.addTask({
69
69
  name: 'Temperature Reading',
70
70
  cmd: 'readTemperature',
71
71
  date: new Date(Date.now() + 2000),
@@ -78,7 +78,7 @@ automator.addAction({
78
78
  });
79
79
 
80
80
  // 2. Humidity reading every 10 seconds
81
- automator.addAction({
81
+ automator.addTask({
82
82
  name: 'Humidity Reading',
83
83
  cmd: 'readHumidity',
84
84
  date: new Date(Date.now() + 3000),
@@ -91,7 +91,7 @@ automator.addAction({
91
91
  });
92
92
 
93
93
  // 3. Full sensor sweep every 30 seconds
94
- automator.addAction({
94
+ automator.addTask({
95
95
  name: 'Full Sensor Sweep',
96
96
  cmd: 'fullSensorSweep',
97
97
  date: new Date(Date.now() + 5000),
@@ -108,7 +108,7 @@ const tomorrow6AM = new Date();
108
108
  tomorrow6AM.setDate(tomorrow6AM.getDate() + 1);
109
109
  tomorrow6AM.setHours(6, 0, 0, 0);
110
110
 
111
- automator.addAction({
111
+ automator.addTask({
112
112
  name: 'Daily Report',
113
113
  cmd: 'dailyReport',
114
114
  date: tomorrow6AM,
@@ -141,7 +141,7 @@ setTimeout(() => {
141
141
  automator.stop();
142
142
 
143
143
  console.log('\nFinal action summary:');
144
- automator.getActions().forEach(action => {
144
+ automator.getTasks().forEach(action => {
145
145
  console.log(`- ${action.name}: ${action.count} executions`);
146
146
  });
147
147
 
@@ -22,7 +22,7 @@ if (fs.existsSync(STORAGE_FILE)) {
22
22
 
23
23
  // Create automator with file-based persistence
24
24
  const automator = new Automator({
25
- storage: Automator.storage.file(STORAGE_FILE),
25
+ storageFile: STORAGE_FILE,
26
26
  autoSave: true,
27
27
  saveInterval: 3000
28
28
  });
@@ -49,7 +49,7 @@ automator.addFunction('flexibleTask', function(payload, event) {
49
49
  // Listen to events
50
50
  automator.on('ready', () => {
51
51
  console.log('\n=== Automator Ready ===');
52
- console.log(`Actions loaded: ${automator.getActions().length}`);
52
+ console.log(`Actions loaded: ${automator.getTasks().length}`);
53
53
  });
54
54
 
55
55
  // SEED: Initialize actions (runs only on first use)
@@ -58,7 +58,7 @@ const didSeed = automator.seed((auto) => {
58
58
  console.log('Creating initial system tasks...\n');
59
59
 
60
60
  // Task 1: Critical billing task - NEVER miss an execution
61
- auto.addAction({
61
+ auto.addTask({
62
62
  name: 'Billing Task',
63
63
  cmd: 'criticalTask',
64
64
  date: new Date(Date.now() + 2000),
@@ -72,7 +72,7 @@ const didSeed = automator.seed((auto) => {
72
72
  });
73
73
 
74
74
  // Task 2: Real-time alert - only relevant "now"
75
- auto.addAction({
75
+ auto.addTask({
76
76
  name: 'Realtime Alert',
77
77
  cmd: 'realtimeAlert',
78
78
  date: new Date(Date.now() + 5000),
@@ -86,7 +86,7 @@ const didSeed = automator.seed((auto) => {
86
86
  });
87
87
 
88
88
  // Task 3: Flexible sensor reading - tolerate brief lag
89
- auto.addAction({
89
+ auto.addTask({
90
90
  name: 'Sensor Reading',
91
91
  cmd: 'flexibleTask',
92
92
  date: new Date(Date.now() + 8000),
@@ -109,7 +109,7 @@ if (didSeed) {
109
109
  }
110
110
 
111
111
  console.log('\n=== Current Schedule ===');
112
- automator.getActions().forEach((action) => {
112
+ automator.getTasks().forEach((action) => {
113
113
  console.log(`\n${action.name}:`);
114
114
  console.log(` catchUpWindow: ${action.catchUpWindow === "unlimited" ? '"unlimited"' : action.catchUpWindow + 'ms'}`);
115
115
  console.log(` Next run: ${action.date.toLocaleTimeString()}`);
package/index.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "jw-automator",
3
- "version": "4.0.1",
4
- "description": "A resilient, local-time, 1-second precision automation scheduler for Node.js with smart defensive defaults and clear error handling",
3
+ "version": "6.0.0",
4
+ "description": "A resilient, local-time, 1-second precision automation scheduler for Node.js with result-based error handling and defensive defaults",
5
5
  "main": "index.js",
6
6
  "files": [
7
7
  "src/",