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/.mocharc.json +3 -0
- package/.vscode/settings.json +6 -0
- package/README.md +72 -40
- package/build.ts +23 -0
- package/coverage/clover.xml +6 -0
- package/coverage/coverage-final.json +1 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +101 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +196 -0
- package/coverage/lcov.info +0 -0
- package/dev.ts +26 -0
- package/dist/esm/funkophileHelpers.js +99 -82
- package/dist/esm/funkophileHelpers.js.map +7 -0
- package/dist/esm/index.js +634 -508
- package/dist/esm/index.js.map +7 -0
- package/funkophileHelpers.ts +22 -3
- package/index.test.ts +210 -0
- package/index.ts +98 -450
- package/mocha.log +59 -0
- package/package.json +26 -7
- package/test-utils.ts +40 -0
- package/tsconfig.json +16 -4
- package/utils.ts +539 -0
- package/dev.js +0 -59
- package/dist/esm/funkophileHelpers.d.ts +0 -108
- package/dist/esm/index.d.ts +0 -13
- package/yarn-error.log +0 -107
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
|
|
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
|
-
|
|
26
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
19
|
+
Promise.config({
|
|
20
|
+
cancellation: true,
|
|
21
|
+
});
|
|
32
22
|
|
|
33
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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(
|
|
422
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
435
|
-
|
|
436
|
-
|
|
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 ===
|
|
83
|
+
if (funkophileConfig.mode === "build") {
|
|
439
84
|
process.exit(1);
|
|
440
85
|
} else {
|
|
441
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
590
|
-
|
|
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
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
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
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
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
|
+
|