funkophile 0.2.3 → 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/dev.js ADDED
@@ -0,0 +1,59 @@
1
+ import chokidar from 'chokidar';
2
+ import { exec } from 'child_process';
3
+ import { promisify } from 'util';
4
+ import { fileURLToPath } from 'url';
5
+ import { dirname } from 'path';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+
10
+ const execAsync = promisify(exec);
11
+
12
+ console.log('Watching for file changes...');
13
+
14
+ const watcher = chokidar.watch('**/*.ts', {
15
+ ignored: /node_modules/,
16
+ persistent: true,
17
+ ignoreInitial: true
18
+ });
19
+
20
+ let buildInProgress = false;
21
+
22
+ const runBuild = async () => {
23
+ if (buildInProgress) {
24
+ console.log('Build already in progress, skipping...');
25
+ return;
26
+ }
27
+
28
+ buildInProgress = true;
29
+ console.log('File changes detected. Rebuilding...');
30
+
31
+ try {
32
+ const { stdout, stderr } = await execAsync('yarn transpile');
33
+ if (stdout) console.log(stdout);
34
+ if (stderr) console.error(stderr);
35
+ console.log('Rebuild completed successfully!');
36
+ } catch (error) {
37
+ console.error('Build failed:', error);
38
+ } finally {
39
+ buildInProgress = false;
40
+ }
41
+ };
42
+
43
+ watcher.on('change', runBuild);
44
+ watcher.on('add', runBuild);
45
+ watcher.on('unlink', runBuild);
46
+
47
+ // Initial build
48
+ console.log('Performing initial build...');
49
+ execAsync('yarn transpile')
50
+ .then(({ stdout, stderr }) => {
51
+ if (stdout) console.log(stdout);
52
+ if (stderr) console.error(stderr);
53
+ console.log('Initial build completed!');
54
+ console.log('Watching for changes...');
55
+ })
56
+ .catch(error => {
57
+ console.error('Initial build failed:', error);
58
+ process.exit(1);
59
+ });
@@ -1,32 +1,83 @@
1
1
  import { createSelector } from "reselect";
2
+ import path from "path";
2
3
  export const contentsOfFiles = (selector) => {
3
4
  return createSelector([selector], (selected) => {
4
- return Object.keys(selected).reduce((mm, k) => mm + selected[k], "");
5
+ if (selected === undefined || selected === null) {
6
+ throw new Error(`contentsOfFiles: selected is ${selected}. Make sure the selector is pointing to valid state.`);
7
+ }
8
+ return Object.keys(selected).reduce((mm, k) => mm + (selected[k] || ""), "");
5
9
  });
6
10
  };
7
11
  export const contentOfFile = (selector) => {
8
12
  return createSelector([selector], (selected) => {
9
- try {
10
- return selected[Object.keys(selected)[0]];
13
+ if (selected === undefined || selected === null) {
14
+ throw new Error(`contentOfFile: selected is ${selected}. Make sure the selector is pointing to valid state.`);
11
15
  }
12
- catch (e) {
13
- console.error("error", e);
14
- console.error("selected", selected);
15
- console.error("selector", selector);
16
- process.exit(-1);
16
+ const keys = Object.keys(selected);
17
+ if (keys.length === 0) {
18
+ throw new Error(`contentOfFile: selected object is empty. No files found. This may be because the input pattern didn't match any files.`);
17
19
  }
20
+ return selected[keys[0]] || "";
18
21
  });
19
22
  };
20
23
  export const srcAndContentOfFile = (selector, key) => {
21
24
  return createSelector([selector], (selected) => {
25
+ if (selected === undefined || selected === null) {
26
+ throw new Error(`srcAndContentOfFile: selected is ${selected}. Make sure the selector is pointing to valid state.`);
27
+ }
28
+ const keys = Object.keys(selected);
29
+ if (keys.length === 0) {
30
+ throw new Error(`srcAndContentOfFile: selected object is empty. No files found. This may be because the input pattern didn't match any files.`);
31
+ }
32
+ // Try exact match first
33
+ let matchingKey = keys.find(k => k === key);
34
+ // If exact match not found, try to find by resolving to absolute path
35
+ if (!matchingKey) {
36
+ // Try to resolve the key to an absolute path
37
+ const resolvedKey = path.resolve(process.cwd(), key);
38
+ matchingKey = keys.find(k => k === resolvedKey);
39
+ }
40
+ // If still not found, try to find by basename
41
+ if (!matchingKey) {
42
+ const keyBasename = path.basename(key);
43
+ matchingKey = keys.find(k => path.basename(k) === keyBasename);
44
+ }
45
+ // If still not found, try to find by relative path
46
+ if (!matchingKey) {
47
+ const relativeKey = path.relative(process.cwd(), key);
48
+ matchingKey = keys.find(k => {
49
+ const kRelative = path.relative(process.cwd(), k);
50
+ return kRelative === relativeKey;
51
+ });
52
+ }
53
+ // If still not found, try to find by ending with the key
54
+ if (!matchingKey) {
55
+ matchingKey = keys.find(k => k.endsWith(key));
56
+ }
57
+ // If still not found, try to find by the key ending with the path
58
+ if (!matchingKey) {
59
+ matchingKey = keys.find(k => k.endsWith(key.replace('./', '')));
60
+ }
61
+ // If still not found, try to find by the key being a relative path that matches
62
+ if (!matchingKey) {
63
+ // Remove leading './' if present
64
+ const cleanKey = key.startsWith('./') ? key.slice(2) : key;
65
+ matchingKey = keys.find(k => k.endsWith(cleanKey));
66
+ }
67
+ if (!matchingKey) {
68
+ throw new Error(`srcAndContentOfFile: key "${key}" not found in selected object. Available keys: ${keys.join(', ')}`);
69
+ }
22
70
  return {
23
- src: key,
24
- content: selected[key],
71
+ src: matchingKey,
72
+ content: selected[matchingKey],
25
73
  };
26
74
  });
27
75
  };
28
76
  export const srcAndContentOfFiles = (selector) => {
29
77
  return createSelector([selector], (selected) => {
78
+ if (selected === undefined || selected === null) {
79
+ throw new Error(`srcAndContentOfFiles: selected is ${selected}. Make sure the selector is pointing to valid state.`);
80
+ }
30
81
  const keys = Object.keys(selected);
31
82
  return keys.map((key) => {
32
83
  return {
@@ -4,6 +4,7 @@ declare const _default: (funkophileConfig: {
4
4
  options: {
5
5
  inFolder: string;
6
6
  outFolder: string;
7
+ port?: number;
7
8
  };
8
9
  encodings: Record<string, string[]>;
9
10
  inputs: Record<string, string>;
package/dist/esm/index.js CHANGED
@@ -4,8 +4,10 @@ import { createStore } 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
  export default (funkophileConfig) => {
10
12
  Promise.config({
11
13
  cancellation: true,
@@ -77,6 +79,9 @@ export default (funkophileConfig) => {
77
79
  ...funkophileConfig.initialState,
78
80
  timestamp: Date.now(),
79
81
  }, action) => {
82
+ if (state === undefined) {
83
+ throw new Error("Redux state is undefined. This should never happen.");
84
+ }
80
85
  // console.log("\u001b[7m\u001b[35m ||| Redux recieved action \u001b[0m", action.type)
81
86
  if (!action.type.includes("@@redux")) {
82
87
  if (action.type === INITIALIZE) {
@@ -116,54 +121,188 @@ export default (funkophileConfig) => {
116
121
  const finalSelector = funkophileConfig.outputs(Object.keys(funkophileConfig.inputs).reduce((mm, inputKey) => {
117
122
  return {
118
123
  ...mm,
119
- [inputKey]: createSelector([(x) => x], (root) => root[inputKey]),
124
+ [inputKey]: createSelector([(x) => x], (root) => {
125
+ const result = root[inputKey];
126
+ if (result === undefined) {
127
+ throw new Error(`Input key "${inputKey}" is undefined in state. ` +
128
+ `This means no files were found for the pattern "${funkophileConfig.inputs[inputKey]}". ` +
129
+ `Available state keys: ${Object.keys(root).join(', ')}`);
130
+ }
131
+ return result;
132
+ }),
120
133
  };
121
134
  }, {}));
135
+ // Start HTTP server in watch mode
136
+ let server = null;
137
+ if (funkophileConfig.mode === "watch") {
138
+ const port = funkophileConfig.options.port || 8080;
139
+ server = http.createServer((req, res) => {
140
+ if (!req.url) {
141
+ res.statusCode = 400;
142
+ res.end('Bad Request');
143
+ return;
144
+ }
145
+ const parsedUrl = url.parse(req.url);
146
+ let pathname = parsedUrl.pathname;
147
+ // Default to index.html if the path ends with /
148
+ if (pathname && pathname.endsWith('/')) {
149
+ pathname += 'index.html';
150
+ }
151
+ // Remove leading slash
152
+ const filePath = pathname ? pathname.substring(1) : 'index.html';
153
+ // Construct the full path to the file
154
+ const fullPath = path.join(process.cwd(), funkophileConfig.options.outFolder, filePath);
155
+ // Check if file exists
156
+ fs.access(fullPath, fs.constants.F_OK, (err) => {
157
+ if (err) {
158
+ // Try with .html extension
159
+ const htmlPath = fullPath + '.html';
160
+ fs.access(htmlPath, fs.constants.F_OK, (htmlErr) => {
161
+ if (htmlErr) {
162
+ // File not found
163
+ res.statusCode = 404;
164
+ res.end('File not found');
165
+ }
166
+ else {
167
+ // Serve the .html file
168
+ fs.readFile(htmlPath, (readErr, data) => {
169
+ if (readErr) {
170
+ res.statusCode = 500;
171
+ res.end('Internal Server Error');
172
+ }
173
+ else {
174
+ res.setHeader('Content-Type', 'text/html');
175
+ res.end(data);
176
+ }
177
+ });
178
+ }
179
+ });
180
+ }
181
+ else {
182
+ // Serve the file
183
+ fs.readFile(fullPath, (readErr, data) => {
184
+ if (readErr) {
185
+ res.statusCode = 500;
186
+ res.end('Internal Server Error');
187
+ }
188
+ else {
189
+ // Set appropriate content type based on file extension
190
+ const ext = path.extname(fullPath).toLowerCase();
191
+ const contentTypes = {
192
+ '.html': 'text/html',
193
+ '.css': 'text/css',
194
+ '.js': 'application/javascript',
195
+ '.json': 'application/json',
196
+ '.png': 'image/png',
197
+ '.jpg': 'image/jpeg',
198
+ '.jpeg': 'image/jpeg',
199
+ '.gif': 'image/gif',
200
+ '.svg': 'image/svg+xml',
201
+ '.ico': 'image/x-icon'
202
+ };
203
+ res.setHeader('Content-Type', contentTypes[ext] || 'application/octet-stream');
204
+ res.end(data);
205
+ }
206
+ });
207
+ }
208
+ });
209
+ });
210
+ server.listen(port, () => {
211
+ console.log(`\u001b[36m\u001b[1m[Funkophile]\u001b[0m Server running at http://localhost:${port}/`);
212
+ });
213
+ }
122
214
  // Wait for all the file watchers to check in
123
215
  Promise.all(Object.keys(funkophileConfig.inputs).map((inputRuleKey) => {
124
- const p = path.resolve(`./${funkophileConfig.options.inFolder}/${funkophileConfig.inputs[inputRuleKey] || ""}`);
216
+ // Ensure the pattern includes the inFolder and is relative to the current working directory
217
+ // Also, make sure to handle patterns that might already include the inFolder
218
+ const pattern = funkophileConfig.inputs[inputRuleKey] || "";
219
+ // For glob, we want the pattern to be relative to process.cwd()
220
+ // Join inFolder and pattern using forward slashes
221
+ const globPattern = path.posix.join(funkophileConfig.options.inFolder, pattern);
222
+ // console.log(`[Funkophile] Looking for files with glob pattern: ${globPattern}`);
223
+ // console.log(`[Funkophile] Current working directory: ${process.cwd()}`);
125
224
  return new Promise((fulfill, reject) => {
126
225
  if (funkophileConfig.mode === "build") {
127
- glob(p, {})
226
+ // Use the glob pattern we constructed earlier
227
+ // console.log(`[Funkophile] Searching for files matching pattern: ${globPattern}`);
228
+ // console.log(`[Funkophile] Input rule key: ${inputRuleKey}`);
229
+ glob(globPattern, { cwd: process.cwd() })
128
230
  .then((files) => {
129
- files.forEach((file) => {
130
- dispatchUpsert(store, inputRuleKey, file, funkophileConfig.encodings);
131
- });
231
+ // console.log(`[Funkophile] Found ${files.length} files for ${inputRuleKey} (pattern: ${pattern}):`, files);
232
+ if (files.length === 0) {
233
+ console.warn(`No files found for input key "${inputRuleKey}" with pattern "${globPattern}"`);
234
+ // console.log(`[Funkophile] The glob pattern used was: ${globPattern}`);
235
+ }
236
+ else {
237
+ files.forEach((file) => {
238
+ // Make sure the file path is absolute
239
+ const absoluteFilePath = path.resolve(process.cwd(), file);
240
+ // console.log(`[Funkophile] Adding file to state for key ${inputRuleKey}: ${absoluteFilePath}`);
241
+ dispatchUpsert(store, inputRuleKey, absoluteFilePath, funkophileConfig.encodings);
242
+ });
243
+ }
132
244
  })
133
245
  .then(() => {
134
246
  fulfill();
247
+ })
248
+ .catch((error) => {
249
+ // console.error(`[Funkophile] Error globbing for pattern ${globPattern}:`, error);
250
+ reject(error);
135
251
  });
136
252
  }
137
253
  else if (funkophileConfig.mode === "watch") {
138
- chokidar
139
- .watch(p, {})
140
- .on("error", (error) => {
141
- logger.watchError(p);
142
- })
143
- .on("ready", () => {
144
- logger.watchReady(p);
145
- fulfill();
146
- })
147
- .on("add", (p) => {
148
- logger.watchAdd(p);
149
- dispatchUpsert(store, inputRuleKey, p, funkophileConfig.encodings);
150
- })
151
- .on("change", (p) => {
152
- logger.watchChange(p);
153
- dispatchUpsert(store, inputRuleKey, p, funkophileConfig.encodings);
154
- })
155
- .on("unlink", (p) => {
156
- logger.watchUnlink(p);
157
- store.dispatch({
158
- type: REMOVE,
159
- payload: {
160
- key: inputRuleKey,
161
- file: p,
162
- },
254
+ // Use the same glob pattern for watch mode
255
+ // console.log(`[Funkophile] Watching for files matching pattern: ${globPattern}`);
256
+ // First, use glob to find existing files to ensure they're added to the state
257
+ glob(globPattern, { cwd: process.cwd() })
258
+ .then((files) => {
259
+ // console.log(`[Funkophile] Found ${files.length} existing files for ${inputRuleKey} (pattern: ${pattern}):`, files);
260
+ files.forEach((file) => {
261
+ const absoluteFilePath = path.resolve(process.cwd(), file);
262
+ // console.log(`[Funkophile] Adding existing file to state for key ${inputRuleKey}: ${absoluteFilePath}`);
263
+ dispatchUpsert(store, inputRuleKey, absoluteFilePath, funkophileConfig.encodings);
264
+ });
265
+ // Now set up the watcher
266
+ const watcher = chokidar
267
+ .watch(globPattern, {
268
+ cwd: process.cwd(),
269
+ ignoreInitial: true // We've already handled initial files above
270
+ })
271
+ .on("error", (error) => {
272
+ logger.watchError(globPattern);
273
+ })
274
+ .on("ready", () => {
275
+ logger.watchReady(globPattern);
276
+ fulfill();
277
+ })
278
+ .on("add", (filePath) => {
279
+ logger.watchAdd(filePath);
280
+ const absoluteFilePath = path.resolve(process.cwd(), filePath);
281
+ dispatchUpsert(store, inputRuleKey, absoluteFilePath, funkophileConfig.encodings);
282
+ })
283
+ .on("change", (filePath) => {
284
+ logger.watchChange(filePath);
285
+ const absoluteFilePath = path.resolve(process.cwd(), filePath);
286
+ dispatchUpsert(store, inputRuleKey, absoluteFilePath, funkophileConfig.encodings);
287
+ })
288
+ .on("unlink", (filePath) => {
289
+ logger.watchUnlink(filePath);
290
+ const absoluteFilePath = path.resolve(process.cwd(), filePath);
291
+ store.dispatch({
292
+ type: REMOVE,
293
+ payload: {
294
+ key: inputRuleKey,
295
+ file: absoluteFilePath,
296
+ },
297
+ });
298
+ })
299
+ .on("unlinkDir", (filePath) => {
300
+ logger.watchUnlink(filePath);
163
301
  });
164
302
  })
165
- .on("unlinkDir", (p) => {
166
- logger.watchUnlink(p);
303
+ .catch((error) => {
304
+ console.error(`[Funkophile] Error globbing for pattern ${globPattern}:`, error);
305
+ reject(error);
167
306
  });
168
307
  // .on('raw', (event, p, details) => { // internal
169
308
  // log('Raw event info:', event, p, details);
@@ -175,13 +314,54 @@ export default (funkophileConfig) => {
175
314
  }
176
315
  });
177
316
  })).then(function () {
317
+ // console.log('\u001b[36m\u001b[1m[Funkophile]\u001b[0m All input files processed. Setting up store subscription...');
318
+ // Debug: log the current state after all files are processed
319
+ const currentState = store.getState();
320
+ console.log('\u001b[36m\u001b[1m[Funkophile]\u001b[0m Current state keys:', Object.keys(currentState));
321
+ // Log all input keys to see if they're present
322
+ Object.keys(funkophileConfig.inputs).forEach(inputKey => {
323
+ if (currentState[inputKey]) {
324
+ // console.log(`[Funkophile] Input key "${inputKey}" found in state with ${Object.keys(currentState[inputKey]).length} files`);
325
+ }
326
+ else {
327
+ throw (`Input key "${inputKey}" NOT found in state`);
328
+ }
329
+ });
178
330
  // listen for changes to the store
179
331
  store.subscribe(() => {
180
332
  const s = store.getState();
333
+ // Skip processing during initial load
334
+ if (s.initialLoad) {
335
+ console.log('\u001b[36m\u001b[1m[Funkophile]\u001b[0m Initial load in progress, skipping processing...');
336
+ console.log('\u001b[36m\u001b[1m[Funkophile]\u001b[0m State keys during initial load:', Object.keys(s));
337
+ return;
338
+ }
181
339
  logger.stateChange();
182
- const outputs = finalSelector(s);
340
+ console.log('\u001b[36m\u001b[1m[Funkophile]\u001b[0m Processing state changes...');
341
+ let outputs;
342
+ try {
343
+ outputs = finalSelector(s);
344
+ console.log(`\u001b[36m\u001b[1m[Funkophile]\u001b[0m Generated ${Object.keys(outputs).length} outputs`);
345
+ }
346
+ catch (error) {
347
+ console.error('\u001b[31m\u001b[1m[Funkophile]\u001b[0m FATAL: Error in output selector chain:');
348
+ console.error(' Error:', error.message);
349
+ console.error(' Stack:', error.stack);
350
+ // Don't exit the process in watch mode, just log the error and continue
351
+ if (funkophileConfig.mode === 'build') {
352
+ process.exit(1);
353
+ }
354
+ else {
355
+ console.log('\u001b[33m\u001b[1m[Funkophile]\u001b[0m Continuing to watch for changes despite error...');
356
+ // Reset previousState to empty to ensure we try processing again on next change
357
+ Object.keys(previousState).forEach(key => {
358
+ delete previousState[key];
359
+ });
360
+ return;
361
+ }
362
+ }
183
363
  if (outputPromise.isPending()) {
184
- console.log("cancelling previous write!");
364
+ console.log("\u001b[33m\u001b[1m[Funkophile]\u001b[0m Cancelling previous write operation!");
185
365
  outputPromise.cancel();
186
366
  }
187
367
  outputPromise = Promise.all(Array.from(new Set(Object.keys(previousState).concat(Object.keys(outputs)))).map((key) => {
@@ -189,90 +369,163 @@ export default (funkophileConfig) => {
189
369
  if (!outputs[key]) {
190
370
  const file = funkophileConfig.options.outFolder + "/" + key;
191
371
  logger.removedFile(file);
372
+ console.log(`\u001b[31m\u001b[1m[Funkophile]\u001b[0m Removing file: ${file}`);
192
373
  try {
193
374
  fse.unlinkSync("./" + file);
194
375
  cleanEmptyFoldersRecursively("./" + file.substring(0, file.lastIndexOf("/")));
195
376
  }
196
377
  catch (ex) {
197
- // console.error('inner', ex.message);
198
- // throw ex;
378
+ // Log error but don't fail the entire process
379
+ console.error(`\u001b[31m\u001b[1m[Funkophile]\u001b[0m Error removing file ${file}:`, ex.message);
199
380
  }
200
381
  finally {
201
- // console.log('finally');
202
- return;
382
+ delete previousState[key];
383
+ fulfill();
203
384
  }
204
- // delete previousState[key]
205
- // fulfill()
206
385
  }
207
386
  else {
208
387
  if (outputs[key] !== previousState[key]) {
209
388
  previousState[key] = outputs[key];
210
389
  const relativeFilePath = "./" + funkophileConfig.options.outFolder + "/" + key;
211
390
  const contents = outputs[key];
391
+ // console.log(`\u001b[32m\u001b[1m[Funkophile]\u001b[0m Writing file: ${relativeFilePath}`);
212
392
  if (typeof contents === "function") {
213
393
  logger.writingFunction(relativeFilePath);
214
394
  contents((err, res) => {
215
- fse.outputFile(relativeFilePath, res, fulfill);
216
- logger.writingString(relativeFilePath);
395
+ if (err) {
396
+ logger.writingError(relativeFilePath, err.message);
397
+ fulfill(); // Still fulfill to continue processing other files
398
+ }
399
+ else {
400
+ fse.outputFile(relativeFilePath, res, (err) => {
401
+ if (err) {
402
+ logger.writingError(relativeFilePath, err.message);
403
+ fulfill(); // Still fulfill to continue processing other files
404
+ }
405
+ else {
406
+ logger.writingString(relativeFilePath);
407
+ fulfill();
408
+ }
409
+ });
410
+ }
217
411
  });
218
412
  }
219
413
  else if (typeof contents === "string") {
220
- fse.outputFile(relativeFilePath, contents, fulfill);
221
- logger.writingString(relativeFilePath);
414
+ fse.outputFile(relativeFilePath, contents, (err) => {
415
+ if (err) {
416
+ logger.writingError(relativeFilePath, err.message);
417
+ fulfill();
418
+ }
419
+ else {
420
+ logger.writingString(relativeFilePath);
421
+ fulfill();
422
+ }
423
+ });
222
424
  }
223
425
  else if (Buffer.isBuffer(contents)) {
224
- fse.outputFile(relativeFilePath, contents, fulfill);
225
- logger.writingString(relativeFilePath);
426
+ fse.outputFile(relativeFilePath, contents, (err) => {
427
+ if (err) {
428
+ logger.writingError(relativeFilePath, err.message);
429
+ fulfill();
430
+ }
431
+ else {
432
+ logger.writingString(relativeFilePath);
433
+ fulfill();
434
+ }
435
+ });
226
436
  }
227
437
  else if (Array.isArray(contents)) {
228
- fse.outputFile(relativeFilePath, JSON.stringify(contents), fulfill);
229
- logger.writingString(relativeFilePath);
438
+ fse.outputFile(relativeFilePath, JSON.stringify(contents), (err) => {
439
+ if (err) {
440
+ logger.writingError(relativeFilePath, err.message);
441
+ fulfill();
442
+ }
443
+ else {
444
+ logger.writingString(relativeFilePath);
445
+ fulfill();
446
+ }
447
+ });
230
448
  }
231
449
  else if (typeof contents.then === "function") {
232
450
  logger.writingPromise(relativeFilePath);
233
451
  Promise.resolve(contents).then(function (value) {
234
452
  if (value instanceof Error) {
235
453
  logger.writingError(relativeFilePath, value.message);
454
+ fulfill();
236
455
  }
237
456
  else {
238
- fse.outputFile(relativeFilePath, value, fulfill);
239
- logger.writingString(relativeFilePath);
457
+ fse.outputFile(relativeFilePath, value, (err) => {
458
+ if (err) {
459
+ logger.writingError(relativeFilePath, err.message);
460
+ fulfill();
461
+ }
462
+ else {
463
+ logger.writingString(relativeFilePath);
464
+ fulfill();
465
+ }
466
+ });
240
467
  }
241
- }, function (value) {
242
- // not called
468
+ }, function (error) {
469
+ logger.writingError(relativeFilePath, error.message);
470
+ fulfill();
243
471
  });
244
472
  }
245
473
  else {
246
- console.log(`I don't recognize what this is but I will try to write it to a file: ` +
247
- relativeFilePath, typeof contents, contents);
248
- fse.outputFile(relativeFilePath, contents, fulfill);
249
- logger.writingString(relativeFilePath);
474
+ console.log(`\u001b[33m\u001b[1m[Funkophile]\u001b[0m Unrecognized content type for ${relativeFilePath}, attempting to write:`, typeof contents);
475
+ fse.outputFile(relativeFilePath, contents, (err) => {
476
+ if (err) {
477
+ logger.writingError(relativeFilePath, err.message);
478
+ fulfill();
479
+ }
480
+ else {
481
+ logger.writingString(relativeFilePath);
482
+ fulfill();
483
+ }
484
+ });
250
485
  }
251
486
  }
252
487
  else {
488
+ // console.log(`\u001b[90m\u001b[1m[Funkophile]\u001b[0m Skipping unchanged file: ${key}`);
253
489
  fulfill();
254
490
  }
255
491
  }
256
492
  });
257
493
  })).then(() => {
494
+ // console.log('\u001b[36m\u001b[1m[Funkophile]\u001b[0m Cleaning empty folders...');
258
495
  cleanEmptyFoldersRecursively(funkophileConfig.options.outFolder);
259
496
  if (funkophileConfig.mode === "build") {
497
+ console.log('\u001b[32m\u001b[1m[Funkophile]\u001b[0m Build completed successfully!');
260
498
  logger.done();
261
499
  }
262
500
  else if (funkophileConfig.mode === "watch") {
501
+ console.log('\u001b[36m\u001b[1m[Funkophile]\u001b[0m Watching for file changes...');
502
+ // Log the localhost URL if port is specified
503
+ const port = funkophileConfig.options.port || 8080;
504
+ console.log(`\u001b[36m\u001b[1m[Funkophile]\u001b[0m Serving at: http://localhost:${port}/`);
263
505
  logger.waiting();
264
506
  }
265
507
  else {
266
- console.error(`The mode should be 'watch' or 'build', not "${funkophileConfig.mode}"`);
267
- process.exit(-1);
508
+ throw (`\u001b[31m\u001b[1m[Funkophile]\u001b[0m The mode should be 'watch' or 'build', not "${funkophileConfig.mode}"`);
268
509
  }
269
510
  });
511
+ // .catch((error) => {
512
+ // // console.error('\u001b[31m\u001b[1m[Funkophile]\u001b[0m Error during file operations:', error);
513
+ // });
270
514
  });
515
+ // console.log('\u001b[36m\u001b[1m[Funkophile]\u001b[0m Initializing store...');
271
516
  // lastly, turn the store `on`.
272
517
  // This is to prevent unecessary recomputations when initialy adding files to redux
273
518
  store.dispatch({
274
519
  type: INITIALIZE,
275
520
  payload: true,
276
521
  });
522
+ // console.log('\u001b[36m\u001b[1m[Funkophile]\u001b[0m Store initialized. Starting processing...');
523
+ });
524
+ // Handle process exit to close the server
525
+ process.on('SIGINT', () => {
526
+ if (server) {
527
+ server.close();
528
+ }
529
+ process.exit(0);
277
530
  });
278
531
  };