funkophile 0.2.5 → 1.0.1
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 +52 -39
- 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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "funkophile",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"repository": "git@github.com:adamwong246/funkophile.git",
|
|
5
5
|
"author": "adam wong <adamwong246@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -9,24 +9,40 @@
|
|
|
9
9
|
"static site generator"
|
|
10
10
|
],
|
|
11
11
|
"type": "module",
|
|
12
|
-
"
|
|
12
|
+
"test": "mocha",
|
|
13
|
+
"module": "./dist/esm/index.js",
|
|
13
14
|
"types": "./dist/esm/index.d.ts",
|
|
15
|
+
"mocha": {
|
|
16
|
+
"node-option": [
|
|
17
|
+
"import=tsx"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
14
20
|
"scripts": {
|
|
15
|
-
"
|
|
16
|
-
"dev": "
|
|
21
|
+
"build": "tsx build.ts",
|
|
22
|
+
"dev": "tsx --watch dev.ts",
|
|
23
|
+
"test": "mocha index.test.ts",
|
|
24
|
+
"typecheck": "tsc --noEmit"
|
|
17
25
|
},
|
|
18
26
|
"dependencies": {
|
|
19
27
|
"@tsconfig/node-lts-strictest-esm": "^18.12.1",
|
|
20
28
|
"@types/bluebird": "^3.5.42",
|
|
29
|
+
"@types/chai": "^5.2.2",
|
|
21
30
|
"@types/chokidar": "^2.1.7",
|
|
22
31
|
"@types/fs-extra": "^11.0.4",
|
|
32
|
+
"@types/mocha": "^10.0.10",
|
|
23
33
|
"@types/redux": "^3.6.0",
|
|
24
34
|
"@types/reselect": "^2.2.0",
|
|
25
35
|
"bluebird": "3.7.2",
|
|
36
|
+
"chai": "^5.2.1",
|
|
26
37
|
"chokidar": "^4.0.3",
|
|
38
|
+
"esbuild": "^0.25.0",
|
|
27
39
|
"fs-extra": "^11.3.0",
|
|
28
40
|
"glob": "11.0.1",
|
|
41
|
+
"mocha": "^11.7.1",
|
|
42
|
+
"mocha-junit-reporter": "^2.2.0",
|
|
43
|
+
"ts-node": "^10.9.1",
|
|
29
44
|
"tsc": "^2.0.4",
|
|
45
|
+
"tsx": "^4.20.6",
|
|
30
46
|
"typescript": "^5.8.2"
|
|
31
47
|
},
|
|
32
48
|
"devDependencies": {
|
|
@@ -34,12 +50,15 @@
|
|
|
34
50
|
},
|
|
35
51
|
"exports": {
|
|
36
52
|
".": {
|
|
37
|
-
"
|
|
38
|
-
|
|
53
|
+
"import": {
|
|
54
|
+
"default": "./dist/esm/index.js"
|
|
55
|
+
},
|
|
39
56
|
"default": "./dist/esm/index.js"
|
|
40
57
|
},
|
|
41
58
|
"./funkophileHelpers": {
|
|
42
|
-
"
|
|
59
|
+
"types": "./dist/esm/funkophileHelpers.d.ts",
|
|
60
|
+
"import": "./dist/esm/funkophileHelpers.js",
|
|
61
|
+
"default": "./dist/esm/funkophileHelpers.js"
|
|
43
62
|
}
|
|
44
63
|
}
|
|
45
64
|
}
|
package/test-utils.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { AppState, FileContents, FunkophileConfig } from './core';
|
|
2
|
+
|
|
3
|
+
export const createMockState = (partial?: Partial<AppState>): AppState => ({
|
|
4
|
+
timestamp: Date.now(),
|
|
5
|
+
posts: {
|
|
6
|
+
'post1.md': '# Post 1',
|
|
7
|
+
'post2.md': '# Post 2',
|
|
8
|
+
'post33.md': 'hello world'
|
|
9
|
+
},
|
|
10
|
+
pages: {
|
|
11
|
+
'about.md': '# About'
|
|
12
|
+
},
|
|
13
|
+
...partial
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export const createMockConfig = (partial?: Partial<FunkophileConfig>): FunkophileConfig => ({
|
|
17
|
+
mode: 'build',
|
|
18
|
+
initialState: {
|
|
19
|
+
posts: {
|
|
20
|
+
'post1.md': '# Post 1',
|
|
21
|
+
'post2.md': '# Post 2'
|
|
22
|
+
},
|
|
23
|
+
pages: {
|
|
24
|
+
'about.md': '# About'
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
options: {
|
|
28
|
+
inFolder: 'src',
|
|
29
|
+
outFolder: 'dist'
|
|
30
|
+
},
|
|
31
|
+
encodings: {
|
|
32
|
+
utf8: ['md', 'txt']
|
|
33
|
+
},
|
|
34
|
+
inputs: {
|
|
35
|
+
posts: 'posts/**/*.md',
|
|
36
|
+
pages: 'pages/**/*.md'
|
|
37
|
+
},
|
|
38
|
+
outputs: () => ({}),
|
|
39
|
+
...partial
|
|
40
|
+
});
|
package/tsconfig.json
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
|
+
|
|
4
|
+
"allowJs": true,
|
|
5
|
+
|
|
3
6
|
"module": "ESNext",
|
|
4
7
|
"target": "ESNext",
|
|
5
8
|
"moduleResolution": "node",
|
|
@@ -9,8 +12,17 @@
|
|
|
9
12
|
"strict": true,
|
|
10
13
|
"rootDir": ".",
|
|
11
14
|
"noImplicitAny": false,
|
|
12
|
-
"useUnknownInCatchVariables": false
|
|
15
|
+
"useUnknownInCatchVariables": false,
|
|
16
|
+
"emitDeclarationOnly": true,
|
|
17
|
+
"skipLibCheck": true
|
|
13
18
|
},
|
|
14
|
-
"include": [
|
|
15
|
-
|
|
16
|
-
|
|
19
|
+
"include": [
|
|
20
|
+
"index.ts",
|
|
21
|
+
"funkophileHelpers.ts",
|
|
22
|
+
"utils.ts"
|
|
23
|
+
],
|
|
24
|
+
"exclude": [
|
|
25
|
+
"node_modules",
|
|
26
|
+
"dist"
|
|
27
|
+
]
|
|
28
|
+
}
|
package/utils.ts
ADDED
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import fse from "fs-extra";
|
|
3
|
+
import http from "http";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { Action, createStore, Store } from "redux";
|
|
6
|
+
import { createSelector } from "reselect";
|
|
7
|
+
import url from "url";
|
|
8
|
+
import { glob } from "glob";
|
|
9
|
+
import chokidar from "chokidar";
|
|
10
|
+
|
|
11
|
+
export type IConfig = {
|
|
12
|
+
mode: "build" | "watch";
|
|
13
|
+
initialState: any;
|
|
14
|
+
options: {
|
|
15
|
+
inFolder: string;
|
|
16
|
+
outFolder: string;
|
|
17
|
+
port?: number;
|
|
18
|
+
};
|
|
19
|
+
encodings: Record<string, string[]>;
|
|
20
|
+
inputs: Record<string, string>;
|
|
21
|
+
outputs: (x: any) => any;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const INITIALIZE = "INITIALIZE";
|
|
25
|
+
export const UPSERT = "UPSERT";
|
|
26
|
+
export const REMOVE = "REMOVE";
|
|
27
|
+
|
|
28
|
+
export const previousState: any = {};
|
|
29
|
+
|
|
30
|
+
export const logger = {
|
|
31
|
+
watchError: (p: string) => console.log("\u001b[7m ! \u001b[0m" + p),
|
|
32
|
+
watchReady: (p: string) =>
|
|
33
|
+
console.log("\u001b[7m\u001b[36m < \u001b[0m" + p),
|
|
34
|
+
watchAdd: (p: string) =>
|
|
35
|
+
console.log("\u001b[7m\u001b[34m + \u001b[0m./" + p),
|
|
36
|
+
watchChange: (p: string) =>
|
|
37
|
+
console.log("\u001b[7m\u001b[35m * \u001b[0m" + p),
|
|
38
|
+
watchUnlink: (p: string) =>
|
|
39
|
+
console.log("\u001b[7m\u001b[31m - \u001b[0m./" + p),
|
|
40
|
+
stateChange: () =>
|
|
41
|
+
console.log("\u001b[7m\u001b[31m --- Redux state changed --- \u001b[0m"),
|
|
42
|
+
cleaningEmptyfolder: (p: string) =>
|
|
43
|
+
console.log("\u001b[31m\u001b[7m XXX! \u001b[0m" + p),
|
|
44
|
+
readingFile: (p: string) => console.log("\u001b[31m <-- \u001b[0m" + p),
|
|
45
|
+
removedFile: (p: string) =>
|
|
46
|
+
console.log("\u001b[31m\u001b[7m ??? \u001b[0m./" + p),
|
|
47
|
+
writingString: (p: string) => console.log("\u001b[32m --> \u001b[0m" + p),
|
|
48
|
+
writingFunction: (p: string) => console.log("\u001b[33m ... \u001b[0m" + p),
|
|
49
|
+
writingPromise: (p: string) => console.log("\u001b[33m ... \u001b[0m" + p),
|
|
50
|
+
writingError: (p: string, message: string) =>
|
|
51
|
+
console.log("\u001b[31m !!! \u001b[0m" + p + " " + message),
|
|
52
|
+
|
|
53
|
+
waiting: () =>
|
|
54
|
+
console.log(
|
|
55
|
+
"\u001b[7m Funkophile is done for now but waiting on changes...\u001b[0m "
|
|
56
|
+
),
|
|
57
|
+
done: () => console.log("\u001b[7m Funkophile is done!\u001b[0m "),
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export function cleanEmptyFoldersRecursively(folder: string) {
|
|
61
|
+
var isDir = fs.statSync(folder).isDirectory();
|
|
62
|
+
if (!isDir) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
var files = fs.readdirSync(folder);
|
|
66
|
+
if (files.length > 0) {
|
|
67
|
+
files.forEach(function (file) {
|
|
68
|
+
var fullPath = path.join(folder, file);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// re-evaluate files; after deleting subfolder
|
|
72
|
+
// we may have parent folder empty now
|
|
73
|
+
files = fs.readdirSync(folder);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (files.length == 0) {
|
|
77
|
+
logger.cleaningEmptyfolder(folder);
|
|
78
|
+
|
|
79
|
+
fs.rmdirSync(folder);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const dispatchUpsert = (
|
|
85
|
+
store: Store,
|
|
86
|
+
key: string,
|
|
87
|
+
file: string,
|
|
88
|
+
encodings: Record<string, string[]>
|
|
89
|
+
) => {
|
|
90
|
+
const fileType: string = path.basename(file).split(".")[1];
|
|
91
|
+
|
|
92
|
+
let encoding: BufferEncoding = Object.keys(encodings).find((e) => {
|
|
93
|
+
return encodings[e].includes(fileType);
|
|
94
|
+
}) as BufferEncoding;
|
|
95
|
+
|
|
96
|
+
logger.readingFile(file);
|
|
97
|
+
store.dispatch({
|
|
98
|
+
type: UPSERT,
|
|
99
|
+
payload: {
|
|
100
|
+
key: key,
|
|
101
|
+
// key: path.relative(process.cwd(), key),
|
|
102
|
+
src: file,
|
|
103
|
+
contents: fse.readFileSync(file, encoding),
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export function omit(key: string, obj: any) {
|
|
109
|
+
const { [key]: omitted, ...rest } = obj;
|
|
110
|
+
return rest;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function newStore(funkophileConfig): Store<any, Action<string>, any> {
|
|
114
|
+
const initialInputState = Object.keys(funkophileConfig.inputs).reduce(
|
|
115
|
+
(state, inputKey) => {
|
|
116
|
+
state[inputKey] = {};
|
|
117
|
+
return state;
|
|
118
|
+
},
|
|
119
|
+
{} as Record<string, any>
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
return createStore(
|
|
123
|
+
(
|
|
124
|
+
state = {
|
|
125
|
+
initialLoad: true,
|
|
126
|
+
...initialInputState,
|
|
127
|
+
...funkophileConfig.initialState,
|
|
128
|
+
timestamp: Date.now(),
|
|
129
|
+
},
|
|
130
|
+
action
|
|
131
|
+
) => {
|
|
132
|
+
if (state === undefined) {
|
|
133
|
+
throw new Error("Redux state is undefined. This should never happen.");
|
|
134
|
+
}
|
|
135
|
+
console.log(
|
|
136
|
+
`\u001b[35m\u001b[1m[Funkophile]\u001b[0m Redux received action: ${action.type}`
|
|
137
|
+
);
|
|
138
|
+
if (!action.type.includes("@@redux")) {
|
|
139
|
+
if (action.type === INITIALIZE) {
|
|
140
|
+
console.log(
|
|
141
|
+
`\u001b[35m\u001b[1m[Funkophile]\u001b[0m INITIALIZE action - setting initialLoad to false`
|
|
142
|
+
);
|
|
143
|
+
return {
|
|
144
|
+
...state,
|
|
145
|
+
initialLoad: false,
|
|
146
|
+
timestamp: Date.now(),
|
|
147
|
+
};
|
|
148
|
+
} else if (action.type === UPSERT) {
|
|
149
|
+
console.log(
|
|
150
|
+
`\u001b[35m\u001b[1m[Funkophile]\u001b[0m UPSERT action for key: ${action["payload"].key}, file: ${action["payload"].src}`
|
|
151
|
+
);
|
|
152
|
+
return {
|
|
153
|
+
...state,
|
|
154
|
+
[action["payload"].key]: {
|
|
155
|
+
// @ts-ignore
|
|
156
|
+
...state[action.payload.key],
|
|
157
|
+
...{
|
|
158
|
+
[action["payload"].src]: action["payload"].contents,
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
timestamp: Date.now(),
|
|
162
|
+
};
|
|
163
|
+
} else if (action.type === REMOVE) {
|
|
164
|
+
console.log(
|
|
165
|
+
`\u001b[35m\u001b[1m[Funkophile]\u001b[0m REMOVE action for key: ${action["payload"].key}, file: ${action["payload"].file}`
|
|
166
|
+
);
|
|
167
|
+
// Ensure the key exists before trying to omit from it
|
|
168
|
+
const currentKeyState = state[action["payload"].key] || {};
|
|
169
|
+
return {
|
|
170
|
+
...state,
|
|
171
|
+
[action["payload"].key]: omit(
|
|
172
|
+
action["payload"].file,
|
|
173
|
+
currentKeyState
|
|
174
|
+
),
|
|
175
|
+
timestamp: Date.now(),
|
|
176
|
+
};
|
|
177
|
+
} else {
|
|
178
|
+
console.error(
|
|
179
|
+
"Redux was asked to handle an unknown action type: " + action.type
|
|
180
|
+
);
|
|
181
|
+
process.exit(-1);
|
|
182
|
+
}
|
|
183
|
+
// return state
|
|
184
|
+
}
|
|
185
|
+
return state;
|
|
186
|
+
}
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function makeFinalSelector(funkophileConfig) {
|
|
191
|
+
return funkophileConfig.outputs(
|
|
192
|
+
Object.keys(funkophileConfig.inputs).reduce((mm, inputKey) => {
|
|
193
|
+
return {
|
|
194
|
+
...mm,
|
|
195
|
+
[inputKey]: createSelector([(x) => x], (root) => {
|
|
196
|
+
// The input key should always be present now, even if it's an empty object
|
|
197
|
+
const result = root[inputKey];
|
|
198
|
+
// If result is undefined, it's a programming error since we initialize all input keys
|
|
199
|
+
if (result === undefined) {
|
|
200
|
+
console.warn(
|
|
201
|
+
`\u001b[33m\u001b[1m[Funkophile]\u001b[0m Input key "${inputKey}" is undefined in state, which shouldn't happen. Using empty object.`
|
|
202
|
+
);
|
|
203
|
+
return {};
|
|
204
|
+
}
|
|
205
|
+
return result;
|
|
206
|
+
}),
|
|
207
|
+
};
|
|
208
|
+
}, {})
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function startServing(funkophileConfig): void {
|
|
213
|
+
const port = funkophileConfig.options.port || 8080;
|
|
214
|
+
const server = http.createServer((req, res) => {
|
|
215
|
+
if (!req.url) {
|
|
216
|
+
res.statusCode = 400;
|
|
217
|
+
res.end("Bad Request");
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const parsedUrl = url.parse(req.url);
|
|
222
|
+
let pathname = parsedUrl.pathname;
|
|
223
|
+
|
|
224
|
+
// Default to index.html if the path ends with /
|
|
225
|
+
if (pathname && pathname.endsWith("/")) {
|
|
226
|
+
pathname += "index.html";
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Remove leading slash
|
|
230
|
+
const filePath = pathname ? pathname.substring(1) : "index.html";
|
|
231
|
+
|
|
232
|
+
// Construct the full path to the file
|
|
233
|
+
const fullPath = path.join(
|
|
234
|
+
process.cwd(),
|
|
235
|
+
funkophileConfig.options.outFolder,
|
|
236
|
+
filePath
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
// Check if file exists
|
|
240
|
+
fs.access(fullPath, fs.constants.F_OK, (err) => {
|
|
241
|
+
if (err) {
|
|
242
|
+
// Try with .html extension
|
|
243
|
+
const htmlPath = fullPath + ".html";
|
|
244
|
+
fs.access(htmlPath, fs.constants.F_OK, (htmlErr) => {
|
|
245
|
+
if (htmlErr) {
|
|
246
|
+
// File not found
|
|
247
|
+
res.statusCode = 404;
|
|
248
|
+
res.end("File not found");
|
|
249
|
+
} else {
|
|
250
|
+
// Serve the .html file
|
|
251
|
+
fs.readFile(htmlPath, (readErr, data) => {
|
|
252
|
+
if (readErr) {
|
|
253
|
+
res.statusCode = 500;
|
|
254
|
+
res.end("Internal Server Error");
|
|
255
|
+
} else {
|
|
256
|
+
res.setHeader("Content-Type", "text/html");
|
|
257
|
+
res.end(data);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
} else {
|
|
263
|
+
// Serve the file
|
|
264
|
+
fs.readFile(fullPath, (readErr, data) => {
|
|
265
|
+
if (readErr) {
|
|
266
|
+
res.statusCode = 500;
|
|
267
|
+
res.end("Internal Server Error");
|
|
268
|
+
} else {
|
|
269
|
+
// Set appropriate content type based on file extension
|
|
270
|
+
const ext = path.extname(fullPath).toLowerCase();
|
|
271
|
+
const contentTypes: Record<string, string> = {
|
|
272
|
+
".html": "text/html",
|
|
273
|
+
".css": "text/css",
|
|
274
|
+
".js": "application/javascript",
|
|
275
|
+
".json": "application/json",
|
|
276
|
+
".png": "image/png",
|
|
277
|
+
".jpg": "image/jpeg",
|
|
278
|
+
".jpeg": "image/jpeg",
|
|
279
|
+
".gif": "image/gif",
|
|
280
|
+
".svg": "image/svg+xml",
|
|
281
|
+
".ico": "image/x-icon",
|
|
282
|
+
};
|
|
283
|
+
res.setHeader(
|
|
284
|
+
"Content-Type",
|
|
285
|
+
contentTypes[ext] || "application/octet-stream"
|
|
286
|
+
);
|
|
287
|
+
res.end(data);
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
server.listen(port, () => {
|
|
295
|
+
console.log(
|
|
296
|
+
`\u001b[36m\u001b[1m[Funkophile]\u001b[0m Server running at http://localhost:${port}/`
|
|
297
|
+
);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// Handle process exit to close the server
|
|
301
|
+
process.on("SIGINT", () => {
|
|
302
|
+
if (server) {
|
|
303
|
+
server.close();
|
|
304
|
+
}
|
|
305
|
+
// process.exit(0);
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Log all input keys to see if they're present
|
|
310
|
+
export function logInputKeys(funkophileConfig, currentState) {
|
|
311
|
+
Object.keys(funkophileConfig.inputs).forEach((inputKey) => {
|
|
312
|
+
if (currentState[inputKey]) {
|
|
313
|
+
console.log(
|
|
314
|
+
`\u001b[36m\u001b[1m[Funkophile]\u001b[0m Input key "${inputKey}" found in state with ${
|
|
315
|
+
Object.keys(currentState[inputKey]).length
|
|
316
|
+
} files`
|
|
317
|
+
);
|
|
318
|
+
// Only log file names if there are files to avoid cluttering the output
|
|
319
|
+
if (Object.keys(currentState[inputKey]).length > 0) {
|
|
320
|
+
console.log(
|
|
321
|
+
`\u001b[36m\u001b[1m[Funkophile]\u001b[0m Files for "${inputKey}":`,
|
|
322
|
+
Object.keys(currentState[inputKey])
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
} else {
|
|
326
|
+
console.warn(
|
|
327
|
+
`\u001b[33m\u001b[1m[Funkophile]\u001b[0m Input key "${inputKey}" NOT found in state`
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export function logDone(funkophileConfig, currentState) {
|
|
334
|
+
if (funkophileConfig.mode === "build") {
|
|
335
|
+
console.log(
|
|
336
|
+
"\u001b[32m\u001b[1m[Funkophile]\u001b[0m Build completed successfully!"
|
|
337
|
+
);
|
|
338
|
+
logger.done();
|
|
339
|
+
} else if (funkophileConfig.mode === "watch") {
|
|
340
|
+
console.log(
|
|
341
|
+
"\u001b[36m\u001b[1m[Funkophile]\u001b[0m Watching for file changes..."
|
|
342
|
+
);
|
|
343
|
+
// Log the localhost URL if port is specified
|
|
344
|
+
const port = funkophileConfig.options.port || 8080;
|
|
345
|
+
console.log(
|
|
346
|
+
`\u001b[36m\u001b[1m[Funkophile]\u001b[0m Serving at: http://localhost:${port}/`
|
|
347
|
+
);
|
|
348
|
+
logger.waiting();
|
|
349
|
+
} else {
|
|
350
|
+
throw `\u001b[31m\u001b[1m[Funkophile]\u001b[0m The mode should be 'watch' or 'build', not "${funkophileConfig.mode}"`;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
export function makePromissesArray(funkophileConfig, store) {
|
|
355
|
+
return Object.keys(funkophileConfig.inputs).map((inputRuleKey) => {
|
|
356
|
+
// Ensure the pattern includes the inFolder and is relative to the current working directory
|
|
357
|
+
// Also, make sure to handle patterns that might already include the inFolder
|
|
358
|
+
const pattern = funkophileConfig.inputs[inputRuleKey] || "";
|
|
359
|
+
// For glob, we want the pattern to be relative to process.cwd()
|
|
360
|
+
// Join inFolder and pattern using forward slashes
|
|
361
|
+
const globPattern = path.posix.join(
|
|
362
|
+
funkophileConfig.options.inFolder,
|
|
363
|
+
pattern
|
|
364
|
+
);
|
|
365
|
+
// console.log(`[Funkophile] Looking for files with glob pattern: ${globPattern}`);
|
|
366
|
+
// console.log(`[Funkophile] Current working directory: ${process.cwd()}`);
|
|
367
|
+
|
|
368
|
+
return new Promise<void>((fulfill, reject) => {
|
|
369
|
+
if (funkophileConfig.mode === "build") {
|
|
370
|
+
// Use the glob pattern we constructed earlier
|
|
371
|
+
// console.log(`[Funkophile] Searching for files matching pattern: ${globPattern}`);
|
|
372
|
+
// console.log(`[Funkophile] Input rule key: ${inputRuleKey}`);
|
|
373
|
+
|
|
374
|
+
glob(globPattern, { cwd: process.cwd() })
|
|
375
|
+
.then((files: string[]) => {
|
|
376
|
+
// console.log(`[Funkophile] Found ${files.length} files for ${inputRuleKey} (pattern: ${pattern}):`, files);
|
|
377
|
+
if (files.length === 0) {
|
|
378
|
+
console.warn(
|
|
379
|
+
`No files found for input key "${inputRuleKey}" with pattern "${globPattern}"`
|
|
380
|
+
);
|
|
381
|
+
// Even if no files are found, the key is already initialized in the state
|
|
382
|
+
// No need to dispatch anything
|
|
383
|
+
} else {
|
|
384
|
+
files.forEach((file) => {
|
|
385
|
+
// Make sure the file path is absolute
|
|
386
|
+
const absoluteFilePath = path.resolve(process.cwd(), file);
|
|
387
|
+
// console.log(`[Funkophile] Adding file to state for key ${inputRuleKey}: ${absoluteFilePath}`);
|
|
388
|
+
dispatchUpsert(
|
|
389
|
+
store,
|
|
390
|
+
inputRuleKey,
|
|
391
|
+
absoluteFilePath,
|
|
392
|
+
funkophileConfig.encodings
|
|
393
|
+
);
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
})
|
|
397
|
+
.then(() => {
|
|
398
|
+
fulfill();
|
|
399
|
+
})
|
|
400
|
+
.catch((error) => {
|
|
401
|
+
// console.error(`[Funkophile] Error globbing for pattern ${globPattern}:`, error);
|
|
402
|
+
reject(error);
|
|
403
|
+
});
|
|
404
|
+
} else if (funkophileConfig.mode === "watch") {
|
|
405
|
+
console.log(
|
|
406
|
+
`\u001b[36m\u001b[1m[Funkophile]\u001b[0m Setting up watcher for pattern: ${globPattern}`
|
|
407
|
+
);
|
|
408
|
+
console.log(
|
|
409
|
+
`\u001b[36m\u001b[1m[Funkophile]\u001b[0m Current working directory: ${process.cwd()}`
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
// First, process initial files using glob to ensure all files are loaded
|
|
413
|
+
glob(globPattern, { cwd: process.cwd() })
|
|
414
|
+
.then((files: string[]) => {
|
|
415
|
+
console.log(
|
|
416
|
+
`\u001b[36m\u001b[1m[Funkophile]\u001b[0m Found ${files.length} initial files for ${inputRuleKey}`
|
|
417
|
+
);
|
|
418
|
+
files.forEach((file) => {
|
|
419
|
+
const absoluteFilePath = path.resolve(process.cwd(), file);
|
|
420
|
+
console.log(
|
|
421
|
+
`\u001b[32m\u001b[1m[Funkophile]\u001b[0m Adding initial file: ${file}`
|
|
422
|
+
);
|
|
423
|
+
dispatchUpsert(
|
|
424
|
+
store,
|
|
425
|
+
inputRuleKey,
|
|
426
|
+
absoluteFilePath,
|
|
427
|
+
funkophileConfig.encodings
|
|
428
|
+
);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// Now set up the watcher
|
|
432
|
+
const watcher = chokidar
|
|
433
|
+
.watch(globPattern, {
|
|
434
|
+
cwd: process.cwd(),
|
|
435
|
+
ignoreInitial: true, // We've already processed initial files
|
|
436
|
+
persistent: true,
|
|
437
|
+
usePolling: false,
|
|
438
|
+
interval: 100,
|
|
439
|
+
binaryInterval: 300,
|
|
440
|
+
alwaysStat: false,
|
|
441
|
+
depth: 99,
|
|
442
|
+
awaitWriteFinish: {
|
|
443
|
+
stabilityThreshold: 50,
|
|
444
|
+
pollInterval: 10,
|
|
445
|
+
},
|
|
446
|
+
})
|
|
447
|
+
.on("error", (error) => {
|
|
448
|
+
console.error(
|
|
449
|
+
`\u001b[31m\u001b[1m[Funkophile]\u001b[0m Watcher error for pattern ${globPattern}:`,
|
|
450
|
+
error
|
|
451
|
+
);
|
|
452
|
+
logger.watchError(globPattern);
|
|
453
|
+
})
|
|
454
|
+
.on("add", (filePath) => {
|
|
455
|
+
console.log(
|
|
456
|
+
`\u001b[32m\u001b[1m[Funkophile]\u001b[0m File added: ${filePath}`
|
|
457
|
+
);
|
|
458
|
+
logger.watchAdd(filePath);
|
|
459
|
+
const absoluteFilePath = path.resolve(process.cwd(), filePath);
|
|
460
|
+
console.log(
|
|
461
|
+
`\u001b[32m\u001b[1m[Funkophile]\u001b[0m Dispatching UPSERT for key: ${inputRuleKey}, file: ${absoluteFilePath}`
|
|
462
|
+
);
|
|
463
|
+
dispatchUpsert(
|
|
464
|
+
store,
|
|
465
|
+
inputRuleKey,
|
|
466
|
+
absoluteFilePath,
|
|
467
|
+
funkophileConfig.encodings
|
|
468
|
+
);
|
|
469
|
+
})
|
|
470
|
+
.on("change", (filePath) => {
|
|
471
|
+
console.log(
|
|
472
|
+
`\u001b[33m\u001b[1m[Funkophile]\u001b[0m File changed: ${filePath}`
|
|
473
|
+
);
|
|
474
|
+
logger.watchChange(filePath);
|
|
475
|
+
const absoluteFilePath = path.resolve(process.cwd(), filePath);
|
|
476
|
+
console.log(
|
|
477
|
+
`\u001b[33m\u001b[1m[Funkophile]\u001b[0m Dispatching UPSERT for key: ${inputRuleKey}, file: ${absoluteFilePath}`
|
|
478
|
+
);
|
|
479
|
+
dispatchUpsert(
|
|
480
|
+
store,
|
|
481
|
+
inputRuleKey,
|
|
482
|
+
absoluteFilePath,
|
|
483
|
+
funkophileConfig.encodings
|
|
484
|
+
);
|
|
485
|
+
})
|
|
486
|
+
.on("unlink", (filePath) => {
|
|
487
|
+
console.log(
|
|
488
|
+
`\u001b[31m\u001b[1m[Funkophile]\u001b[0m File removed: ${filePath}`
|
|
489
|
+
);
|
|
490
|
+
logger.watchUnlink(filePath);
|
|
491
|
+
const absoluteFilePath = path.resolve(process.cwd(), filePath);
|
|
492
|
+
console.log(
|
|
493
|
+
`\u001b[31m\u001b[1m[Funkophile]\u001b[0m Dispatching REMOVE for key: ${inputRuleKey}, file: ${absoluteFilePath}`
|
|
494
|
+
);
|
|
495
|
+
store.dispatch({
|
|
496
|
+
type: REMOVE,
|
|
497
|
+
payload: {
|
|
498
|
+
key: inputRuleKey,
|
|
499
|
+
file: absoluteFilePath,
|
|
500
|
+
},
|
|
501
|
+
});
|
|
502
|
+
})
|
|
503
|
+
.on("unlinkDir", (filePath) => {
|
|
504
|
+
console.log(
|
|
505
|
+
`\u001b[31m\u001b[1m[Funkophile]\u001b[0m Directory removed: ${filePath}`
|
|
506
|
+
);
|
|
507
|
+
logger.watchUnlink(filePath);
|
|
508
|
+
})
|
|
509
|
+
.on("raw", (event, path, details) => {
|
|
510
|
+
console.log(
|
|
511
|
+
`\u001b[90m\u001b[1m[Funkophile]\u001b[0m Raw event: ${event} for path: ${path}`
|
|
512
|
+
);
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
console.log(
|
|
516
|
+
`\u001b[32m\u001b[1m[Funkophile]\u001b[0m Watcher is ready for pattern: ${globPattern}`
|
|
517
|
+
);
|
|
518
|
+
logger.watchReady(globPattern);
|
|
519
|
+
fulfill();
|
|
520
|
+
})
|
|
521
|
+
.catch((error) => {
|
|
522
|
+
console.error(
|
|
523
|
+
`\u001b[31m\u001b[1m[Funkophile]\u001b[0m Error processing initial files for pattern ${globPattern}:`,
|
|
524
|
+
error
|
|
525
|
+
);
|
|
526
|
+
reject(error);
|
|
527
|
+
});
|
|
528
|
+
// .on('raw', (event, p, details) => { // internal
|
|
529
|
+
// log('Raw event info:', event, p, details);
|
|
530
|
+
// })
|
|
531
|
+
} else {
|
|
532
|
+
console.error(
|
|
533
|
+
`mode should be 'watch' or 'build', not "${funkophileConfig.mode}"`
|
|
534
|
+
);
|
|
535
|
+
process.exit(-1);
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
});
|
|
539
|
+
}
|