funkophile 0.2.5 → 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/index.ts CHANGED
@@ -1,446 +1,93 @@
1
- import chokidar from "chokidar";
2
- import { createSelector } from "reselect";
3
- import { Action, createStore, Store } from "redux";
4
- import fs from "fs";
5
- import fse from "fs-extra";
6
- import { glob } from "glob";
7
- import http from "http";
8
- import path from "path";
9
1
  import Promise from "bluebird";
10
- import url from "url";
11
-
12
- export default (funkophileConfig: {
13
- mode: 'build' | 'watch';
14
- initialState: any;
15
- options: {
16
- inFolder: string;
17
- outFolder: string;
18
- port?: number;
19
- },
20
- encodings: Record<string, string[]>,
21
- inputs: Record<string, string>,
22
- outputs: (x: any) => any;
23
- }) => {
2
+ import fse from "fs-extra";
24
3
 
25
- Promise.config({
26
- cancellation: true,
27
- });
4
+ import { Action, Store } from "redux";
5
+ import {
6
+ IConfig,
7
+ INITIALIZE,
8
+ cleanEmptyFoldersRecursively,
9
+ logDone,
10
+ logInputKeys,
11
+ logger,
12
+ makeFinalSelector,
13
+ makePromissesArray,
14
+ newStore,
15
+ previousState,
16
+ startServing,
17
+ } from "./utils";
28
18
 
29
- const INITIALIZE = "INITIALIZE";
30
- const UPSERT = "UPSERT";
31
- const REMOVE = "REMOVE";
19
+ Promise.config({
20
+ cancellation: true,
21
+ });
32
22
 
33
- const previousState: any = {};
23
+ export default (funkophileConfig: IConfig) => {
34
24
  let outputPromise = Promise.resolve();
25
+ const store: Store<any, Action<string>, any> = newStore(funkophileConfig);
26
+ const finalSelector = makeFinalSelector(funkophileConfig);
35
27
 
36
- const logger = {
37
- watchError: (p: string) => console.log("\u001b[7m ! \u001b[0m" + p),
38
- watchReady: (p: string) =>
39
- console.log("\u001b[7m\u001b[36m < \u001b[0m" + p),
40
- watchAdd: (p: string) =>
41
- console.log("\u001b[7m\u001b[34m + \u001b[0m./" + p),
42
- watchChange: (p: string) =>
43
- console.log("\u001b[7m\u001b[35m * \u001b[0m" + p),
44
- watchUnlink: (p: string) =>
45
- console.log("\u001b[7m\u001b[31m - \u001b[0m./" + p),
46
- stateChange: () =>
47
- console.log("\u001b[7m\u001b[31m --- Redux state changed --- \u001b[0m"),
48
- cleaningEmptyfolder: (p: string) =>
49
- console.log("\u001b[31m\u001b[7m XXX! \u001b[0m" + p),
50
- readingFile: (p: string) => console.log("\u001b[31m <-- \u001b[0m" + p),
51
- removedFile: (p: string) =>
52
- console.log("\u001b[31m\u001b[7m ??? \u001b[0m./" + p),
53
- writingString: (p: string) => console.log("\u001b[32m --> \u001b[0m" + p),
54
- writingFunction: (p: string) => console.log("\u001b[33m ... \u001b[0m" + p),
55
- writingPromise: (p: string) => console.log("\u001b[33m ... \u001b[0m" + p),
56
- writingError: (p: string, message: string) =>
57
- console.log("\u001b[31m !!! \u001b[0m" + p + " " + message),
58
-
59
- waiting: () =>
60
- console.log(
61
- "\u001b[7m Funkophile is done for now but waiting on changes...\u001b[0m "
62
- ),
63
- done: () => console.log("\u001b[7m Funkophile is done!\u001b[0m "),
64
- };
65
-
66
- function cleanEmptyFoldersRecursively(folder: string) {
67
- var isDir = fs.statSync(folder).isDirectory();
68
- if (!isDir) {
69
- return;
70
- }
71
- var files = fs.readdirSync(folder);
72
- if (files.length > 0) {
73
- files.forEach(function (file) {
74
- var fullPath = path.join(folder, file);
75
- });
76
-
77
- // re-evaluate files; after deleting subfolder
78
- // we may have parent folder empty now
79
- files = fs.readdirSync(folder);
80
- }
81
-
82
- if (files.length == 0) {
83
- logger.cleaningEmptyfolder(folder);
84
-
85
- fs.rmdirSync(folder);
86
- return;
87
- }
88
- }
89
-
90
- const dispatchUpsert = (
91
- store: Store,
92
- key: string,
93
- file: string,
94
- encodings: Record<string, string[]>
95
- ) => {
96
-
97
- const fileType: string = path.basename(file).split(".")[1];
98
-
99
- let encoding: BufferEncoding = Object.keys(encodings).find((e) => {
100
- return encodings[e].includes(fileType);
101
- }) as BufferEncoding;
102
-
103
- logger.readingFile(file);
104
- store.dispatch({
105
- type: UPSERT,
106
- payload: {
107
- key: key,
108
- // key: path.relative(process.cwd(), key),
109
- src: file,
110
- contents: fse.readFileSync(file, encoding),
111
- },
112
- });
113
- };
114
-
115
- function omit(key: string, obj: any) {
116
- const { [key]: omitted, ...rest } = obj;
117
- return rest;
118
- }
119
-
120
- const store: Store<any, Action<string>, any> = createStore(
121
- (
122
- state = {
123
- initialLoad: true,
124
- ...funkophileConfig.initialState,
125
- timestamp: Date.now(),
126
- },
127
- action,
128
- ) => {
129
- if (state === undefined) {
130
- throw new Error("Redux state is undefined. This should never happen.");
131
- }
132
- // console.log("\u001b[7m\u001b[35m ||| Redux recieved action \u001b[0m", action.type)
133
- if (!action.type.includes("@@redux")) {
134
- if (action.type === INITIALIZE) {
135
- return {
136
- ...state,
137
- initialLoad: false,
138
- timestamp: Date.now(),
139
- };
140
- } else if (action.type === UPSERT) {
141
- return {
142
- ...state,
143
- [action["payload"].key]: {
144
- // @ts-ignore
145
- ...state[action.payload.key],
146
- ...{
147
- [action["payload"].src]: action["payload"].contents,
148
- },
149
- },
150
- timestamp: Date.now(),
151
- };
152
- } else if (action.type === REMOVE) {
153
- return {
154
- ...state,
155
- [action["payload"].key]: omit(
156
- action["payload"].file,
157
- state[action["payload"].key]
158
- ),
159
- timestamp: Date.now(),
160
- };
161
- } else {
162
- console.error(
163
- "Redux was asked to handle an unknown action type: " + action.type
164
- );
165
- process.exit(-1);
166
- }
167
- // return state
168
- }
169
- }
170
- );
171
-
172
- const finalSelector = funkophileConfig.outputs(
173
- Object.keys(funkophileConfig.inputs).reduce((mm, inputKey) => {
174
- return {
175
- ...mm,
176
- [inputKey]: createSelector([(x) => x], (root) => {
177
- const result = root[inputKey];
178
- if (result === undefined) {
179
- throw new Error(
180
- `Input key "${inputKey}" is undefined in state. ` +
181
- `This means no files were found for the pattern "${funkophileConfig.inputs[inputKey]}". ` +
182
- `Available state keys: ${Object.keys(root).join(', ')}`
183
- );
184
- }
185
- return result;
186
- }),
187
- };
188
- }, {})
189
- );
190
-
191
- // Start HTTP server in watch mode
192
- let server: http.Server | null = null;
193
28
  if (funkophileConfig.mode === "watch") {
194
- const port = funkophileConfig.options.port || 8080;
195
- server = http.createServer((req, res) => {
196
- if (!req.url) {
197
- res.statusCode = 400;
198
- res.end('Bad Request');
199
- return;
200
- }
201
-
202
- const parsedUrl = url.parse(req.url);
203
- let pathname = parsedUrl.pathname;
204
-
205
- // Default to index.html if the path ends with /
206
- if (pathname && pathname.endsWith('/')) {
207
- pathname += 'index.html';
208
- }
209
-
210
- // Remove leading slash
211
- const filePath = pathname ? pathname.substring(1) : 'index.html';
212
-
213
- // Construct the full path to the file
214
- const fullPath = path.join(process.cwd(), funkophileConfig.options.outFolder, filePath);
215
-
216
- // Check if file exists
217
- fs.access(fullPath, fs.constants.F_OK, (err) => {
218
- if (err) {
219
- // Try with .html extension
220
- const htmlPath = fullPath + '.html';
221
- fs.access(htmlPath, fs.constants.F_OK, (htmlErr) => {
222
- if (htmlErr) {
223
- // File not found
224
- res.statusCode = 404;
225
- res.end('File not found');
226
- } else {
227
- // Serve the .html file
228
- fs.readFile(htmlPath, (readErr, data) => {
229
- if (readErr) {
230
- res.statusCode = 500;
231
- res.end('Internal Server Error');
232
- } else {
233
- res.setHeader('Content-Type', 'text/html');
234
- res.end(data);
235
- }
236
- });
237
- }
238
- });
239
- } else {
240
- // Serve the file
241
- fs.readFile(fullPath, (readErr, data) => {
242
- if (readErr) {
243
- res.statusCode = 500;
244
- res.end('Internal Server Error');
245
- } else {
246
- // Set appropriate content type based on file extension
247
- const ext = path.extname(fullPath).toLowerCase();
248
- const contentTypes: Record<string, string> = {
249
- '.html': 'text/html',
250
- '.css': 'text/css',
251
- '.js': 'application/javascript',
252
- '.json': 'application/json',
253
- '.png': 'image/png',
254
- '.jpg': 'image/jpeg',
255
- '.jpeg': 'image/jpeg',
256
- '.gif': 'image/gif',
257
- '.svg': 'image/svg+xml',
258
- '.ico': 'image/x-icon'
259
- };
260
- res.setHeader('Content-Type', contentTypes[ext] || 'application/octet-stream');
261
- res.end(data);
262
- }
263
- });
264
- }
265
- });
266
- });
267
-
268
- server.listen(port, () => {
269
- console.log(`\u001b[36m\u001b[1m[Funkophile]\u001b[0m Server running at http://localhost:${port}/`);
270
- });
29
+ startServing(funkophileConfig);
271
30
  }
272
31
 
273
32
  // Wait for all the file watchers to check in
274
33
  Promise.all(
275
- Object.keys(funkophileConfig.inputs).map((inputRuleKey) => {
276
- // Ensure the pattern includes the inFolder and is relative to the current working directory
277
- // Also, make sure to handle patterns that might already include the inFolder
278
- const pattern = funkophileConfig.inputs[inputRuleKey] || "";
279
- // For glob, we want the pattern to be relative to process.cwd()
280
- // Join inFolder and pattern using forward slashes
281
- const globPattern = path.posix.join(funkophileConfig.options.inFolder, pattern);
282
- // console.log(`[Funkophile] Looking for files with glob pattern: ${globPattern}`);
283
- // console.log(`[Funkophile] Current working directory: ${process.cwd()}`);
284
-
285
- return new Promise((fulfill, reject) => {
286
- if (funkophileConfig.mode === "build") {
287
- // Use the glob pattern we constructed earlier
288
- // console.log(`[Funkophile] Searching for files matching pattern: ${globPattern}`);
289
- // console.log(`[Funkophile] Input rule key: ${inputRuleKey}`);
290
-
291
- glob(globPattern, { cwd: process.cwd() })
292
- .then((files: string[]) => {
293
- // console.log(`[Funkophile] Found ${files.length} files for ${inputRuleKey} (pattern: ${pattern}):`, files);
294
- if (files.length === 0) {
295
- console.warn(`No files found for input key "${inputRuleKey}" with pattern "${globPattern}"`);
296
- // console.log(`[Funkophile] The glob pattern used was: ${globPattern}`);
297
- } else {
298
- files.forEach((file) => {
299
- // Make sure the file path is absolute
300
- const absoluteFilePath = path.resolve(process.cwd(), file);
301
- // console.log(`[Funkophile] Adding file to state for key ${inputRuleKey}: ${absoluteFilePath}`);
302
- dispatchUpsert(
303
- store,
304
- inputRuleKey,
305
- absoluteFilePath,
306
- funkophileConfig.encodings
307
- );
308
- });
309
- }
310
- })
311
- .then(() => {
312
- fulfill();
313
- })
314
- .catch((error) => {
315
- // console.error(`[Funkophile] Error globbing for pattern ${globPattern}:`, error);
316
- reject(error);
317
- });
318
- } else if (funkophileConfig.mode === "watch") {
319
- // Use the same glob pattern for watch mode
320
- // console.log(`[Funkophile] Watching for files matching pattern: ${globPattern}`);
321
-
322
- // First, use glob to find existing files to ensure they're added to the state
323
- glob(globPattern, { cwd: process.cwd() })
324
- .then((files: string[]) => {
325
- // console.log(`[Funkophile] Found ${files.length} existing files for ${inputRuleKey} (pattern: ${pattern}):`, files);
326
- files.forEach((file) => {
327
- const absoluteFilePath = path.resolve(process.cwd(), file);
328
- // console.log(`[Funkophile] Adding existing file to state for key ${inputRuleKey}: ${absoluteFilePath}`);
329
- dispatchUpsert(
330
- store,
331
- inputRuleKey,
332
- absoluteFilePath,
333
- funkophileConfig.encodings
334
- );
335
- });
336
-
337
- // Now set up the watcher
338
- const watcher = chokidar
339
- .watch(globPattern, {
340
- cwd: process.cwd(),
341
- ignoreInitial: true // We've already handled initial files above
342
- })
343
- .on("error", (error) => {
344
- logger.watchError(globPattern);
345
- })
346
- .on("ready", () => {
347
- logger.watchReady(globPattern);
348
- fulfill();
349
- })
350
- .on("add", (filePath) => {
351
- logger.watchAdd(filePath);
352
- const absoluteFilePath = path.resolve(process.cwd(), filePath);
353
- dispatchUpsert(
354
- store,
355
- inputRuleKey,
356
- absoluteFilePath,
357
- funkophileConfig.encodings
358
- );
359
- })
360
- .on("change", (filePath) => {
361
- logger.watchChange(filePath);
362
- const absoluteFilePath = path.resolve(process.cwd(), filePath);
363
- dispatchUpsert(
364
- store,
365
- inputRuleKey,
366
- absoluteFilePath,
367
- funkophileConfig.encodings
368
- );
369
- })
370
- .on("unlink", (filePath) => {
371
- logger.watchUnlink(filePath);
372
- const absoluteFilePath = path.resolve(process.cwd(), filePath);
373
- store.dispatch({
374
- type: REMOVE,
375
- payload: {
376
- key: inputRuleKey,
377
- file: absoluteFilePath,
378
- },
379
- });
380
- })
381
- .on("unlinkDir", (filePath) => {
382
- logger.watchUnlink(filePath);
383
- });
384
- })
385
- .catch((error) => {
386
- console.error(`[Funkophile] Error globbing for pattern ${globPattern}:`, error);
387
- reject(error);
388
- });
389
- // .on('raw', (event, p, details) => { // internal
390
- // log('Raw event info:', event, p, details);
391
- // })
392
- } else {
393
- console.error(
394
- `mode should be 'watch' or 'build', not "${funkophileConfig.mode}"`
395
- );
396
- process.exit(-1);
397
- }
398
- });
399
- })
34
+ makePromissesArray(funkophileConfig, store)
400
35
  ).then(function () {
401
- // console.log('\u001b[36m\u001b[1m[Funkophile]\u001b[0m All input files processed. Setting up store subscription...');
402
-
403
- // Debug: log the current state after all files are processed
404
- const currentState = store.getState();
405
- console.log('\u001b[36m\u001b[1m[Funkophile]\u001b[0m Current state keys:', Object.keys(currentState));
406
- // Log all input keys to see if they're present
407
- Object.keys(funkophileConfig.inputs).forEach(inputKey => {
408
- if (currentState[inputKey]) {
409
- // console.log(`[Funkophile] Input key "${inputKey}" found in state with ${Object.keys(currentState[inputKey]).length} files`);
410
- } else {
411
- throw(`Input key "${inputKey}" NOT found in state`);
412
- }
413
- });
414
-
415
- // listen for changes to the store
36
+ console.log(
37
+ "\u001b[32m\u001b[1m[Funkophile]\u001b[0m All input watchers are ready. Setting up store subscription..."
38
+ );
39
+
40
+ // Set up the store subscription BEFORE initializing to catch all changes
416
41
  store.subscribe(() => {
417
42
  const s = store.getState();
418
-
43
+ console.log(
44
+ `\u001b[36m\u001b[1m[Funkophile]\u001b[0m Store updated. initialLoad: ${s.initialLoad}, timestamp: ${s.timestamp}`
45
+ );
46
+
419
47
  // Skip processing during initial load
420
48
  if (s.initialLoad) {
421
- console.log('\u001b[36m\u001b[1m[Funkophile]\u001b[0m Initial load in progress, skipping processing...');
422
- console.log('\u001b[36m\u001b[1m[Funkophile]\u001b[0m State keys during initial load:', Object.keys(s));
49
+ console.log(
50
+ "\u001b[36m\u001b[1m[Funkophile]\u001b[0m Initial load in progress, skipping processing..."
51
+ );
52
+ console.log(
53
+ "\u001b[36m\u001b[1m[Funkophile]\u001b[0m State keys during initial load:",
54
+ Object.keys(s)
55
+ );
423
56
  return;
424
57
  }
425
58
 
426
59
  logger.stateChange();
427
- console.log('\u001b[36m\u001b[1m[Funkophile]\u001b[0m Processing state changes...');
428
-
60
+ console.log(
61
+ "\u001b[36m\u001b[1m[Funkophile]\u001b[0m Processing state changes..."
62
+ );
63
+ console.log(
64
+ "\u001b[36m\u001b[1m[Funkophile]\u001b[0m Current state keys:",
65
+ Object.keys(s)
66
+ );
67
+
429
68
  let outputs;
430
69
  try {
431
70
  outputs = finalSelector(s);
432
- console.log(`\u001b[36m\u001b[1m[Funkophile]\u001b[0m Generated ${Object.keys(outputs).length} outputs`);
71
+ console.log(
72
+ `\u001b[36m\u001b[1m[Funkophile]\u001b[0m Generated ${
73
+ Object.keys(outputs).length
74
+ } outputs`
75
+ );
433
76
  } catch (error) {
434
- console.error('\u001b[31m\u001b[1m[Funkophile]\u001b[0m FATAL: Error in output selector chain:');
435
- console.error(' Error:', error.message);
436
- console.error(' Stack:', error.stack);
77
+ console.error(
78
+ "\u001b[31m\u001b[1m[Funkophile]\u001b[0m FATAL: Error in output selector chain ? :"
79
+ );
80
+ console.error(" Error:", error.message);
81
+ console.error(" Stack:", error.stack);
437
82
  // Don't exit the process in watch mode, just log the error and continue
438
- if (funkophileConfig.mode === 'build') {
83
+ if (funkophileConfig.mode === "build") {
439
84
  process.exit(1);
440
85
  } else {
441
- console.log('\u001b[33m\u001b[1m[Funkophile]\u001b[0m Continuing to watch for changes despite error...');
86
+ console.log(
87
+ "\u001b[33m\u001b[1m[Funkophile]\u001b[0m Continuing to watch for changes despite error..."
88
+ );
442
89
  // Reset previousState to empty to ensure we try processing again on next change
443
- Object.keys(previousState).forEach(key => {
90
+ Object.keys(previousState).forEach((key) => {
444
91
  delete previousState[key];
445
92
  });
446
93
  return;
@@ -448,7 +95,9 @@ export default (funkophileConfig: {
448
95
  }
449
96
 
450
97
  if (outputPromise.isPending()) {
451
- console.log("\u001b[33m\u001b[1m[Funkophile]\u001b[0m Cancelling previous write operation!");
98
+ console.log(
99
+ "\u001b[33m\u001b[1m[Funkophile]\u001b[0m Cancelling previous write operation!"
100
+ );
452
101
  outputPromise.cancel();
453
102
  }
454
103
 
@@ -460,7 +109,9 @@ export default (funkophileConfig: {
460
109
  if (!outputs[key]) {
461
110
  const file = funkophileConfig.options.outFolder + "/" + key;
462
111
  logger.removedFile(file);
463
- console.log(`\u001b[31m\u001b[1m[Funkophile]\u001b[0m Removing file: ${file}`);
112
+ console.log(
113
+ `\u001b[31m\u001b[1m[Funkophile]\u001b[0m Removing file: ${file}`
114
+ );
464
115
 
465
116
  try {
466
117
  fse.unlinkSync("./" + file);
@@ -469,7 +120,10 @@ export default (funkophileConfig: {
469
120
  );
470
121
  } catch (ex) {
471
122
  // Log error but don't fail the entire process
472
- console.error(`\u001b[31m\u001b[1m[Funkophile]\u001b[0m Error removing file ${file}:`, ex.message);
123
+ console.error(
124
+ `\u001b[31m\u001b[1m[Funkophile]\u001b[0m Error removing file ${file}:`,
125
+ ex.message
126
+ );
473
127
  } finally {
474
128
  delete previousState[key];
475
129
  fulfill();
@@ -483,7 +137,7 @@ export default (funkophileConfig: {
483
137
  const contents = outputs[key];
484
138
 
485
139
  // console.log(`\u001b[32m\u001b[1m[Funkophile]\u001b[0m Writing file: ${relativeFilePath}`);
486
-
140
+
487
141
  if (typeof contents === "function") {
488
142
  logger.writingFunction(relativeFilePath);
489
143
  contents((err, res) => {
@@ -586,42 +240,36 @@ export default (funkophileConfig: {
586
240
  // console.log('\u001b[36m\u001b[1m[Funkophile]\u001b[0m Cleaning empty folders...');
587
241
  cleanEmptyFoldersRecursively(funkophileConfig.options.outFolder);
588
242
 
589
- if (funkophileConfig.mode === "build") {
590
- console.log('\u001b[32m\u001b[1m[Funkophile]\u001b[0m Build completed successfully!');
591
- logger.done();
592
- } else if (funkophileConfig.mode === "watch") {
593
- console.log('\u001b[36m\u001b[1m[Funkophile]\u001b[0m Watching for file changes...');
594
- // Log the localhost URL if port is specified
595
- const port = funkophileConfig.options.port || 8080;
596
- console.log(`\u001b[36m\u001b[1m[Funkophile]\u001b[0m Serving at: http://localhost:${port}/`);
597
- logger.waiting();
598
- } else {
599
- throw (
600
- `\u001b[31m\u001b[1m[Funkophile]\u001b[0m The mode should be 'watch' or 'build', not "${funkophileConfig.mode}"`
601
- );
602
-
603
- }
604
- })
243
+ logDone(funkophileConfig, currentState)
244
+ });
605
245
  // .catch((error) => {
606
246
  // // console.error('\u001b[31m\u001b[1m[Funkophile]\u001b[0m Error during file operations:', error);
607
247
  // });
608
248
  });
609
249
 
610
- // console.log('\u001b[36m\u001b[1m[Funkophile]\u001b[0m Initializing store...');
611
- // lastly, turn the store `on`.
612
- // This is to prevent unecessary recomputations when initialy adding files to redux
613
- store.dispatch({
614
- type: INITIALIZE,
615
- payload: true,
616
- });
617
- // console.log('\u001b[36m\u001b[1m[Funkophile]\u001b[0m Store initialized. Starting processing...');
618
- });
250
+ console.log(
251
+ "\u001b[32m\u001b[1m[Funkophile]\u001b[0m Dispatching INITIALIZE action to enable processing..."
252
+ );
253
+ // Debug: log the current state after all files are processed
254
+ const currentState = store.getState();
255
+ console.log(
256
+ "\u001b[36m\u001b[1m[Funkophile]\u001b[0m Current state keys:",
257
+ Object.keys(currentState)
258
+ );
259
+
260
+ logInputKeys(funkophileConfig, currentState);
619
261
 
620
- // Handle process exit to close the server
621
- process.on('SIGINT', () => {
622
- if (server) {
623
- server.close();
624
- }
625
- process.exit(0);
262
+ // Add a small delay to ensure all file operations are complete before initializing
263
+ setTimeout(() => {
264
+ // lastly, turn the store `on`.
265
+ // This is to prevent unecessary recomputations when initialy adding files to redux
266
+ store.dispatch({
267
+ type: INITIALIZE,
268
+ payload: true,
269
+ });
270
+ console.log(
271
+ "\u001b[32m\u001b[1m[Funkophile]\u001b[0m Store initialized. Ready to process changes!"
272
+ );
273
+ }, 100);
626
274
  });
627
275
  };
package/mocha.log ADDED
@@ -0,0 +1,59 @@
1
+ warning ../../package.json: No license field
2
+ $ mocha --loader=ts '**/*.test.ts'
3
+
4
+
5
+ Funkophile Core
6
+ makeStore
7
+ ✔ should create a store with empty state until it is initialized
8
+ omit
9
+ ✔ should remove the specified key from object
10
+ selectors
11
+ for the pages
12
+ ✔ contentsOfFiles should concatenate all file contents
13
+ ✔ contentOfFile should return first file content
14
+ ✔ srcAndContentOfFile should return src and content
15
+ ✔ srcAndContentOfFiles should return array of src/content pairs
16
+ for the posts
17
+ ✔ contentsOfFiles should concatenate all file contents
18
+ ✔ contentOfFile should return first file content
19
+ ✔ srcAndContentOfFile should return src and content
20
+ ✔ srcAndContentOfFiles should return array of src/content pairs
21
+ createInputSelectors
22
+ ✔ should create a selector for each input key
23
+ ✔ selectors should return the corresponding state slice
24
+ store actions
25
+ ✔ should handle INITIALIZE action
26
+ ✔ should handle UPSERT action
27
+ ✔ should handle REMOVE action
28
+ edge cases
29
+ An input selector returned a different result when passed same arguments.
30
+ This means your output selector will likely run more frequently than intended.
31
+ Avoid returning a new reference inside your input selector, e.g.
32
+ `createSelector([state => state.todos.map(todo => todo.id)], todoIds => todoIds.length)` {
33
+ arguments: [Arguments] {
34
+ '0': { timestamp: 1752731765346, posts: [Object], pages: [Object] }
35
+ },
36
+ firstInputs: [ {} ],
37
+ secondInputs: [ {} ],
38
+ stack: 'Error\n' +
39
+ ' at Object.runInputStabilityCheck (/Users/adam/Code/funkophile/node_modules/reselect/src/devModeChecks/inputStabilityCheck.ts:41:13)\n' +
40
+ ' at dependenciesChecker (/Users/adam/Code/funkophile/node_modules/reselect/src/createSelectorCreator.ts:432:31)\n' +
41
+ ' at memoized (/Users/adam/Code/funkophile/node_modules/reselect/src/weakMapMemoize.ts:228:21)\n' +
42
+ ' at Context.<anonymous> (/Users/adam/Code/funkophile/index.test.ts:193:14)\n' +
43
+ ' at callFn (/Users/adam/Code/funkophile/node_modules/mocha/lib/runnable.js:366:21)\n' +
44
+ ' at Runnable.run (/Users/adam/Code/funkophile/node_modules/mocha/lib/runnable.js:354:5)\n' +
45
+ ' at Runner.runTest (/Users/adam/Code/funkophile/node_modules/mocha/lib/runner.js:715:10)\n' +
46
+ ' at /Users/adam/Code/funkophile/node_modules/mocha/lib/runner.js:838:12\n' +
47
+ ' at next (/Users/adam/Code/funkophile/node_modules/mocha/lib/runner.js:630:14)\n' +
48
+ ' at /Users/adam/Code/funkophile/node_modules/mocha/lib/runner.js:640:7\n' +
49
+ ' at next (/Users/adam/Code/funkophile/node_modules/mocha/lib/runner.js:523:14)\n' +
50
+ ' at Immediate._onImmediate (/Users/adam/Code/funkophile/node_modules/mocha/lib/runner.js:608:5)\n' +
51
+ ' at process.processImmediate (node:internal/timers:478:21)'
52
+ }
53
+ ✔ should handle empty file contents
54
+ ✔ should handle Buffer content
55
+ ✔ should throw on invalid selector
56
+
57
+
58
+ 18 passing (10ms)
59
+