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 +59 -0
- package/dist/esm/funkophileHelpers.js +61 -10
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +312 -59
- package/funkophileHelpers.ts +72 -16
- package/index.ts +338 -89
- package/package.json +6 -2
- package/tsconfig.json +2 -1
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
|
-
|
|
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
|
-
|
|
10
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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:
|
|
24
|
-
content: selected[
|
|
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 {
|
package/dist/esm/index.d.ts
CHANGED
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) =>
|
|
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
|
-
|
|
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
|
|
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.
|
|
130
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
.
|
|
166
|
-
|
|
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
|
-
|
|
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("
|
|
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
|
-
//
|
|
198
|
-
|
|
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
|
-
|
|
202
|
-
|
|
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
|
-
|
|
216
|
-
|
|
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,
|
|
221
|
-
|
|
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,
|
|
225
|
-
|
|
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),
|
|
229
|
-
|
|
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,
|
|
239
|
-
|
|
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 (
|
|
242
|
-
|
|
468
|
+
}, function (error) {
|
|
469
|
+
logger.writingError(relativeFilePath, error.message);
|
|
470
|
+
fulfill();
|
|
243
471
|
});
|
|
244
472
|
}
|
|
245
473
|
else {
|
|
246
|
-
console.log(
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
-
|
|
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
|
};
|