funkophile 0.2.4 → 0.2.5

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
@@ -4,8 +4,10 @@ import { Action, createStore, Store } from "redux";
4
4
  import fs from "fs";
5
5
  import fse from "fs-extra";
6
6
  import { glob } from "glob";
7
+ import http from "http";
7
8
  import path from "path";
8
9
  import Promise from "bluebird";
10
+ import url from "url";
9
11
 
10
12
  export default (funkophileConfig: {
11
13
  mode: 'build' | 'watch';
@@ -13,6 +15,7 @@ export default (funkophileConfig: {
13
15
  options: {
14
16
  inFolder: string;
15
17
  outFolder: string;
18
+ port?: number;
16
19
  },
17
20
  encodings: Record<string, string[]>,
18
21
  inputs: Record<string, string>,
@@ -170,76 +173,218 @@ export default (funkophileConfig: {
170
173
  Object.keys(funkophileConfig.inputs).reduce((mm, inputKey) => {
171
174
  return {
172
175
  ...mm,
173
- [inputKey]: createSelector([(x) => x], (root) => root[inputKey]),
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
+ }),
174
187
  };
175
188
  }, {})
176
189
  );
177
190
 
191
+ // Start HTTP server in watch mode
192
+ let server: http.Server | null = null;
193
+ 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
+ });
271
+ }
272
+
178
273
  // Wait for all the file watchers to check in
179
274
  Promise.all(
180
275
  Object.keys(funkophileConfig.inputs).map((inputRuleKey) => {
181
- const p = path.resolve(
182
- `./${funkophileConfig.options.inFolder}/${
183
- funkophileConfig.inputs[inputRuleKey] || ""
184
- }`
185
- );
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()}`);
186
284
 
187
285
  return new Promise((fulfill, reject) => {
188
286
  if (funkophileConfig.mode === "build") {
189
- glob(p, {})
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() })
190
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);
191
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}`);
192
329
  dispatchUpsert(
193
330
  store,
194
331
  inputRuleKey,
195
- file,
332
+ absoluteFilePath,
196
333
  funkophileConfig.encodings
197
334
  );
198
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
+ });
199
384
  })
200
- .then(() => {
201
- fulfill();
202
- });
203
- } else if (funkophileConfig.mode === "watch") {
204
- chokidar
205
- .watch(p, {})
206
- .on("error", (error) => {
207
- logger.watchError(p);
208
- })
209
- .on("ready", () => {
210
- logger.watchReady(p);
211
- fulfill();
212
- })
213
- .on("add", (p) => {
214
- logger.watchAdd(p);
215
- dispatchUpsert(
216
- store,
217
- inputRuleKey,
218
- p,
219
- funkophileConfig.encodings
220
- );
221
- })
222
- .on("change", (p) => {
223
- logger.watchChange(p);
224
- dispatchUpsert(
225
- store,
226
- inputRuleKey,
227
- p,
228
- funkophileConfig.encodings
229
- );
230
- })
231
- .on("unlink", (p) => {
232
- logger.watchUnlink(p);
233
- store.dispatch({
234
- type: REMOVE,
235
- payload: {
236
- key: inputRuleKey,
237
- file: p,
238
- },
239
- });
240
- })
241
- .on("unlinkDir", (p) => {
242
- logger.watchUnlink(p);
385
+ .catch((error) => {
386
+ console.error(`[Funkophile] Error globbing for pattern ${globPattern}:`, error);
387
+ reject(error);
243
388
  });
244
389
  // .on('raw', (event, p, details) => { // internal
245
390
  // log('Raw event info:', event, p, details);
@@ -253,15 +398,57 @@ export default (funkophileConfig: {
253
398
  });
254
399
  })
255
400
  ).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
+
256
415
  // listen for changes to the store
257
416
  store.subscribe(() => {
258
417
  const s = store.getState();
418
+
419
+ // Skip processing during initial load
420
+ 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));
423
+ return;
424
+ }
259
425
 
260
426
  logger.stateChange();
261
- const outputs = finalSelector(s);
427
+ console.log('\u001b[36m\u001b[1m[Funkophile]\u001b[0m Processing state changes...');
428
+
429
+ let outputs;
430
+ try {
431
+ outputs = finalSelector(s);
432
+ console.log(`\u001b[36m\u001b[1m[Funkophile]\u001b[0m Generated ${Object.keys(outputs).length} outputs`);
433
+ } 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);
437
+ // Don't exit the process in watch mode, just log the error and continue
438
+ if (funkophileConfig.mode === 'build') {
439
+ process.exit(1);
440
+ } else {
441
+ console.log('\u001b[33m\u001b[1m[Funkophile]\u001b[0m Continuing to watch for changes despite error...');
442
+ // Reset previousState to empty to ensure we try processing again on next change
443
+ Object.keys(previousState).forEach(key => {
444
+ delete previousState[key];
445
+ });
446
+ return;
447
+ }
448
+ }
262
449
 
263
450
  if (outputPromise.isPending()) {
264
- console.log("cancelling previous write!");
451
+ console.log("\u001b[33m\u001b[1m[Funkophile]\u001b[0m Cancelling previous write operation!");
265
452
  outputPromise.cancel();
266
453
  }
267
454
 
@@ -273,6 +460,7 @@ export default (funkophileConfig: {
273
460
  if (!outputs[key]) {
274
461
  const file = funkophileConfig.options.outFolder + "/" + key;
275
462
  logger.removedFile(file);
463
+ console.log(`\u001b[31m\u001b[1m[Funkophile]\u001b[0m Removing file: ${file}`);
276
464
 
277
465
  try {
278
466
  fse.unlinkSync("./" + file);
@@ -280,14 +468,12 @@ export default (funkophileConfig: {
280
468
  "./" + file.substring(0, file.lastIndexOf("/"))
281
469
  );
282
470
  } catch (ex) {
283
- // console.error('inner', ex.message);
284
- // throw ex;
471
+ // 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);
285
473
  } finally {
286
- // console.log('finally');
287
- return;
474
+ delete previousState[key];
475
+ fulfill();
288
476
  }
289
- // delete previousState[key]
290
- // fulfill()
291
477
  } else {
292
478
  if (outputs[key] !== previousState[key]) {
293
479
  previousState[key] = outputs[key];
@@ -296,77 +482,146 @@ export default (funkophileConfig: {
296
482
  "./" + funkophileConfig.options.outFolder + "/" + key;
297
483
  const contents = outputs[key];
298
484
 
485
+ // console.log(`\u001b[32m\u001b[1m[Funkophile]\u001b[0m Writing file: ${relativeFilePath}`);
486
+
299
487
  if (typeof contents === "function") {
300
488
  logger.writingFunction(relativeFilePath);
301
489
  contents((err, res) => {
302
- fse.outputFile(relativeFilePath, res, fulfill);
303
- logger.writingString(relativeFilePath);
490
+ if (err) {
491
+ logger.writingError(relativeFilePath, err.message);
492
+ fulfill(); // Still fulfill to continue processing other files
493
+ } else {
494
+ fse.outputFile(relativeFilePath, res, (err) => {
495
+ if (err) {
496
+ logger.writingError(relativeFilePath, err.message);
497
+ fulfill(); // Still fulfill to continue processing other files
498
+ } else {
499
+ logger.writingString(relativeFilePath);
500
+ fulfill();
501
+ }
502
+ });
503
+ }
304
504
  });
305
505
  } else if (typeof contents === "string") {
306
- fse.outputFile(relativeFilePath, contents, fulfill);
307
- logger.writingString(relativeFilePath);
506
+ fse.outputFile(relativeFilePath, contents, (err) => {
507
+ if (err) {
508
+ logger.writingError(relativeFilePath, err.message);
509
+ fulfill();
510
+ } else {
511
+ logger.writingString(relativeFilePath);
512
+ fulfill();
513
+ }
514
+ });
308
515
  } else if (Buffer.isBuffer(contents)) {
309
- fse.outputFile(relativeFilePath, contents, fulfill);
310
- logger.writingString(relativeFilePath);
516
+ fse.outputFile(relativeFilePath, contents, (err) => {
517
+ if (err) {
518
+ logger.writingError(relativeFilePath, err.message);
519
+ fulfill();
520
+ } else {
521
+ logger.writingString(relativeFilePath);
522
+ fulfill();
523
+ }
524
+ });
311
525
  } else if (Array.isArray(contents)) {
312
526
  fse.outputFile(
313
527
  relativeFilePath,
314
528
  JSON.stringify(contents),
315
- fulfill
529
+ (err) => {
530
+ if (err) {
531
+ logger.writingError(relativeFilePath, err.message);
532
+ fulfill();
533
+ } else {
534
+ logger.writingString(relativeFilePath);
535
+ fulfill();
536
+ }
537
+ }
316
538
  );
317
- logger.writingString(relativeFilePath);
318
539
  } else if (typeof contents.then === "function") {
319
540
  logger.writingPromise(relativeFilePath);
320
541
  Promise.resolve(contents).then(
321
542
  function (value) {
322
543
  if (value instanceof Error) {
323
544
  logger.writingError(relativeFilePath, value.message);
545
+ fulfill();
324
546
  } else {
325
- fse.outputFile(relativeFilePath, value, fulfill);
326
- logger.writingString(relativeFilePath);
547
+ fse.outputFile(relativeFilePath, value, (err) => {
548
+ if (err) {
549
+ logger.writingError(relativeFilePath, err.message);
550
+ fulfill();
551
+ } else {
552
+ logger.writingString(relativeFilePath);
553
+ fulfill();
554
+ }
555
+ });
327
556
  }
328
557
  },
329
- function (value) {
330
- // not called
558
+ function (error) {
559
+ logger.writingError(relativeFilePath, error.message);
560
+ fulfill();
331
561
  }
332
562
  );
333
563
  } else {
334
564
  console.log(
335
- `I don't recognize what this is but I will try to write it to a file: ` +
336
- relativeFilePath,
337
- typeof contents,
338
- contents
565
+ `\u001b[33m\u001b[1m[Funkophile]\u001b[0m Unrecognized content type for ${relativeFilePath}, attempting to write:`,
566
+ typeof contents
339
567
  );
340
- fse.outputFile(relativeFilePath, contents, fulfill);
341
- logger.writingString(relativeFilePath);
568
+ fse.outputFile(relativeFilePath, contents, (err) => {
569
+ if (err) {
570
+ logger.writingError(relativeFilePath, err.message);
571
+ fulfill();
572
+ } else {
573
+ logger.writingString(relativeFilePath);
574
+ fulfill();
575
+ }
576
+ });
342
577
  }
343
578
  } else {
579
+ // console.log(`\u001b[90m\u001b[1m[Funkophile]\u001b[0m Skipping unchanged file: ${key}`);
344
580
  fulfill();
345
581
  }
346
582
  }
347
583
  });
348
584
  })
349
585
  ).then(() => {
586
+ // console.log('\u001b[36m\u001b[1m[Funkophile]\u001b[0m Cleaning empty folders...');
350
587
  cleanEmptyFoldersRecursively(funkophileConfig.options.outFolder);
351
588
 
352
589
  if (funkophileConfig.mode === "build") {
590
+ console.log('\u001b[32m\u001b[1m[Funkophile]\u001b[0m Build completed successfully!');
353
591
  logger.done();
354
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}/`);
355
597
  logger.waiting();
356
598
  } else {
357
- console.error(
358
- `The mode should be 'watch' or 'build', not "${funkophileConfig.mode}"`
599
+ throw (
600
+ `\u001b[31m\u001b[1m[Funkophile]\u001b[0m The mode should be 'watch' or 'build', not "${funkophileConfig.mode}"`
359
601
  );
360
- process.exit(-1);
602
+
361
603
  }
362
- });
604
+ })
605
+ // .catch((error) => {
606
+ // // console.error('\u001b[31m\u001b[1m[Funkophile]\u001b[0m Error during file operations:', error);
607
+ // });
363
608
  });
364
609
 
610
+ // console.log('\u001b[36m\u001b[1m[Funkophile]\u001b[0m Initializing store...');
365
611
  // lastly, turn the store `on`.
366
612
  // This is to prevent unecessary recomputations when initialy adding files to redux
367
613
  store.dispatch({
368
614
  type: INITIALIZE,
369
615
  payload: true,
370
616
  });
617
+ // console.log('\u001b[36m\u001b[1m[Funkophile]\u001b[0m Store initialized. Starting processing...');
618
+ });
619
+
620
+ // Handle process exit to close the server
621
+ process.on('SIGINT', () => {
622
+ if (server) {
623
+ server.close();
624
+ }
625
+ process.exit(0);
371
626
  });
372
627
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "funkophile",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "repository": "git@github.com:adamwong246/funkophile.git",
5
5
  "author": "adam wong <adamwong246@gmail.com>",
6
6
  "license": "MIT",
@@ -12,7 +12,8 @@
12
12
  "main": "./dist/esm.index.js",
13
13
  "types": "./dist/esm/index.d.ts",
14
14
  "scripts": {
15
- "transpile": "tsc --project tsconfig.json "
15
+ "transpile": "tsc --project tsconfig.json",
16
+ "dev": "tsc --watch --project tsconfig.json"
16
17
  },
17
18
  "dependencies": {
18
19
  "@tsconfig/node-lts-strictest-esm": "^18.12.1",
@@ -28,6 +29,9 @@
28
29
  "tsc": "^2.0.4",
29
30
  "typescript": "^5.8.2"
30
31
  },
32
+ "devDependencies": {
33
+ "chokidar-cli": "^3.0.0"
34
+ },
31
35
  "exports": {
32
36
  ".": {
33
37
  "types": "./dist/esm/index.d.ts",
package/tsconfig.json CHANGED
@@ -8,7 +8,8 @@
8
8
  "declaration": true,
9
9
  "strict": true,
10
10
  "rootDir": ".",
11
- "noImplicitAny": false
11
+ "noImplicitAny": false,
12
+ "useUnknownInCatchVariables": false
12
13
  },
13
14
  "include": ["*.ts"],
14
15
  "exclude": ["node_modules"]