@cocreate/file 1.20.0 → 1.21.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/CHANGELOG.md +7 -0
- package/package.json +1 -1
- package/src/server.js +188 -85
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# [1.21.0](https://github.com/CoCreate-app/CoCreate-file/compare/v1.20.0...v1.21.0) (2025-11-16)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* enhance file processing by adding support for include/exclude filters and implementing dynamic data retrieval for various file types ([d8a2185](https://github.com/CoCreate-app/CoCreate-file/commit/d8a2185ce6f9554aa929f285e24526a281bc0b96))
|
|
7
|
+
|
|
1
8
|
# [1.20.0](https://github.com/CoCreate-app/CoCreate-file/compare/v1.19.6...v1.20.0) (2025-10-11)
|
|
2
9
|
|
|
3
10
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cocreate/file",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.21.0",
|
|
4
4
|
"description": "A versatile, configurable headless file uploader supporting local and server operations. Accessible via a JavaScript API and HTML5 attributes, it provides seamless file reading, writing, and uploading with fallbacks to the standard HTML5 file input API. Ideal for developers needing robust file management in headless environments.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"file-uploader",
|
package/src/server.js
CHANGED
|
@@ -4,6 +4,7 @@ const fs = require("fs");
|
|
|
4
4
|
const realpathAsync = fs.promises.realpath;
|
|
5
5
|
|
|
6
6
|
const path = require("path");
|
|
7
|
+
const { pathToFileURL } = require("url");
|
|
7
8
|
const mimeTypes = {
|
|
8
9
|
".aac": "audio/aac",
|
|
9
10
|
".abw": "application/x-abiword",
|
|
@@ -174,46 +175,60 @@ module.exports = async function file(
|
|
|
174
175
|
async function runDirectories() {
|
|
175
176
|
for (const directory of directories) {
|
|
176
177
|
const entry = directory.entry;
|
|
177
|
-
|
|
178
|
-
await runFiles(directory, entry, exclude);
|
|
178
|
+
await runFiles(directory, entry);
|
|
179
179
|
}
|
|
180
180
|
return;
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
async function runFiles(directory, entry,
|
|
183
|
+
async function runFiles(directory, entry, Path, directoryName) {
|
|
184
184
|
const entryPath = path.resolve(configDirectoryPath, entry);
|
|
185
185
|
let files = fs.readdirSync(entryPath);
|
|
186
|
-
|
|
186
|
+
let exclude = directory.exclude || [];
|
|
187
|
+
let include = directory.include || [];
|
|
187
188
|
for (let file of files) {
|
|
188
|
-
|
|
189
|
-
for (let i = 0; i < exclude.length; i++) {
|
|
190
|
-
if (file.includes(exclude[i])) {
|
|
191
|
-
skip = true;
|
|
192
|
-
break;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
if (skip) continue;
|
|
189
|
+
const filePath = path.resolve(entryPath, file);
|
|
196
190
|
|
|
197
191
|
let isDirectory;
|
|
198
192
|
let isSymlink = fs
|
|
199
|
-
.lstatSync(
|
|
193
|
+
.lstatSync(filePath)
|
|
200
194
|
.isSymbolicLink();
|
|
201
195
|
if (isSymlink) {
|
|
202
|
-
let symlinkPath = await realpathAsync(
|
|
196
|
+
let symlinkPath = await realpathAsync(filePath);
|
|
203
197
|
isDirectory =
|
|
204
198
|
fs.existsSync(symlinkPath) &&
|
|
205
199
|
fs.lstatSync(symlinkPath).isDirectory();
|
|
206
200
|
} else
|
|
207
201
|
isDirectory =
|
|
208
|
-
fs.existsSync(
|
|
209
|
-
fs.lstatSync(
|
|
202
|
+
fs.existsSync(filePath) &&
|
|
203
|
+
fs.lstatSync(filePath).isDirectory();
|
|
204
|
+
|
|
205
|
+
let skip = false;
|
|
206
|
+
for (let i = 0; i < exclude.length; i++) {
|
|
207
|
+
if (filePath.includes(exclude[i])) {
|
|
208
|
+
skip = true;
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
for (let i = 0; i < include.length; i++) {
|
|
214
|
+
if (filePath.includes(include[i])) {
|
|
215
|
+
skip = false;
|
|
216
|
+
break;
|
|
217
|
+
} else if (isDirectory) {
|
|
218
|
+
skip = "directory";
|
|
219
|
+
break;
|
|
220
|
+
} else {
|
|
221
|
+
skip = true;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (skip === true) continue;
|
|
210
226
|
|
|
211
227
|
let name = file;
|
|
212
228
|
let source = "";
|
|
213
229
|
|
|
214
230
|
for (let i = 0; i < match.length; i++) {
|
|
215
231
|
skip = true;
|
|
216
|
-
const filePath = path.resolve(entryPath, file);
|
|
217
232
|
if (filePath.startsWith(match[i])) {
|
|
218
233
|
skip = false;
|
|
219
234
|
break;
|
|
@@ -237,7 +252,7 @@ module.exports = async function file(
|
|
|
237
252
|
} else directoryName = "/";
|
|
238
253
|
}
|
|
239
254
|
|
|
240
|
-
if (exclude && exclude.includes(directoryName)) continue;
|
|
255
|
+
// if (exclude && exclude.includes(directoryName)) continue;
|
|
241
256
|
|
|
242
257
|
if (!Path) {
|
|
243
258
|
if (directoryName === "/") Path = directoryName;
|
|
@@ -248,13 +263,15 @@ module.exports = async function file(
|
|
|
248
263
|
if (Path === "/") pathname = Path + name;
|
|
249
264
|
else pathname = Path + "/" + name;
|
|
250
265
|
|
|
251
|
-
if (isDirectory)
|
|
252
|
-
|
|
266
|
+
if (isDirectory) {
|
|
267
|
+
mimeType = "text/directory";
|
|
268
|
+
} else {
|
|
253
269
|
source = await getSource(
|
|
254
270
|
`${entryPath}/${file}`,
|
|
255
271
|
mimeType,
|
|
256
272
|
isSymlink
|
|
257
273
|
);
|
|
274
|
+
}
|
|
258
275
|
|
|
259
276
|
let values = {
|
|
260
277
|
"{{name}}": name || "",
|
|
@@ -262,85 +279,120 @@ module.exports = async function file(
|
|
|
262
279
|
"{{directory}}": directoryName || "",
|
|
263
280
|
"{{path}}": Path || "",
|
|
264
281
|
"{{pathname}}": pathname,
|
|
265
|
-
"{{content-type}}": mimeType || ""
|
|
282
|
+
"{{content-type}}": mimeType || "",
|
|
266
283
|
};
|
|
267
284
|
|
|
268
|
-
let
|
|
269
|
-
|
|
270
|
-
if (!object.src) object.src = "{{source}}";
|
|
271
|
-
if (!object.directory) object.directory = "{{directory}}";
|
|
272
|
-
if (!object.path) object.path = "{{path}}";
|
|
273
|
-
if (!object.pathname) object.pathname = "{{pathname}}";
|
|
274
|
-
if (!object["content-type"])
|
|
275
|
-
object["content-type"] = "{{content-type}}";
|
|
276
|
-
if (
|
|
277
|
-
!object.public &&
|
|
278
|
-
object.public != false &&
|
|
279
|
-
object.public != "false"
|
|
280
|
-
)
|
|
281
|
-
object.public = "true";
|
|
282
|
-
|
|
283
|
-
let newObject = {
|
|
284
|
-
array: directory.array || "files",
|
|
285
|
-
object
|
|
285
|
+
let data = {
|
|
286
|
+
array: directory.array || "files"
|
|
286
287
|
};
|
|
287
288
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
289
|
+
let isData = false;
|
|
290
|
+
if ( typeof directory.$data === "string") {
|
|
291
|
+
if (isDirectory) {
|
|
292
|
+
skip = "directory";
|
|
293
|
+
} else {
|
|
294
|
+
isData = true;
|
|
295
|
+
data = await getData(
|
|
296
|
+
`${entryPath}/${file}`,
|
|
297
|
+
mimeType,
|
|
298
|
+
isSymlink
|
|
299
|
+
);
|
|
300
|
+
if (!data) continue
|
|
301
|
+
}
|
|
302
|
+
} else if ( typeof directory.object === "string") {
|
|
303
|
+
if (isDirectory) {
|
|
304
|
+
skip = "directory";
|
|
305
|
+
} else {
|
|
306
|
+
isData = true;
|
|
307
|
+
data.object = await getData(
|
|
308
|
+
`${entryPath}/${file}`,
|
|
309
|
+
mimeType,
|
|
310
|
+
isSymlink
|
|
299
311
|
);
|
|
300
|
-
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
|
|
312
|
+
if (!data.object) continue
|
|
313
|
+
}
|
|
314
|
+
} else if (typeof directory.object === "object" && directory.object !== null) {
|
|
315
|
+
data.array = directory.array || "files";
|
|
316
|
+
let object = { ...directory.object };
|
|
317
|
+
if (!object.name) object.name = "{{name}}";
|
|
318
|
+
if (!object.src) object.src = "{{source}}";
|
|
319
|
+
if (!object.directory) object.directory = "{{directory}}";
|
|
320
|
+
if (!object.path) object.path = "{{path}}";
|
|
321
|
+
if (!object.pathname) object.pathname = "{{pathname}}";
|
|
322
|
+
if (!object["content-type"])
|
|
323
|
+
object["content-type"] = "{{content-type}}";
|
|
324
|
+
if (
|
|
325
|
+
!object.public &&
|
|
326
|
+
object.public != false &&
|
|
327
|
+
object.public != "false"
|
|
328
|
+
)
|
|
329
|
+
object.public = "true";
|
|
330
|
+
|
|
331
|
+
data.object = object;
|
|
332
|
+
|
|
333
|
+
if (!data.object._id) {
|
|
334
|
+
data.$filter = {
|
|
335
|
+
query: {
|
|
336
|
+
pathname
|
|
337
|
+
}
|
|
338
|
+
};
|
|
304
339
|
}
|
|
305
|
-
}
|
|
306
340
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
341
|
+
if (
|
|
342
|
+
options.translate &&
|
|
343
|
+
mimeType === "text/html" &&
|
|
344
|
+
Array.isArray(directory.languages) &&
|
|
345
|
+
!object.translations
|
|
346
|
+
) {
|
|
347
|
+
try {
|
|
348
|
+
// Call your AI translation service
|
|
349
|
+
const translations = await options.translate(
|
|
350
|
+
Buffer.isBuffer(source) ? source.toString('utf-8') : source,
|
|
351
|
+
directory.languages
|
|
352
|
+
);
|
|
353
|
+
data.object.translations = translations;
|
|
354
|
+
} catch (err) {
|
|
355
|
+
console.error("Translation error:", err);
|
|
356
|
+
// Continue without translations
|
|
357
|
+
}
|
|
358
|
+
}
|
|
310
359
|
|
|
311
|
-
|
|
312
|
-
if (
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
if (
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
360
|
+
if (directory.storage) data.storage = directory.storage;
|
|
361
|
+
if (directory.database) data.database = directory.database;
|
|
362
|
+
if (directory.array) data.array = directory.array || "files";
|
|
363
|
+
|
|
364
|
+
for (const key of Object.keys(directory.object)) {
|
|
365
|
+
if (typeof directory.object[key] == "string") {
|
|
366
|
+
let variables = directory.object[key].match(
|
|
367
|
+
/{{([A-Za-z0-9_.,\[\]\-\/ ]*)}}/g
|
|
368
|
+
);
|
|
369
|
+
if (variables) {
|
|
370
|
+
for (let variable of variables) {
|
|
371
|
+
let replacement = values[variable];
|
|
372
|
+
if (key === 'src' && variable === '{{source}}' && Buffer.isBuffer(source)) {
|
|
373
|
+
replacement = `data:${mimeType};base64,${source.toString('base64')}`;
|
|
374
|
+
}
|
|
375
|
+
data.object[key] = data.object[
|
|
376
|
+
key
|
|
377
|
+
].replace(variable, replacement);
|
|
321
378
|
}
|
|
322
|
-
newObject.object[key] = newObject.object[
|
|
323
|
-
key
|
|
324
|
-
].replace(variable, replacement);
|
|
325
379
|
}
|
|
326
380
|
}
|
|
327
381
|
}
|
|
328
382
|
}
|
|
329
383
|
|
|
330
384
|
if (skip !== "directory") {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
);
|
|
343
|
-
|
|
385
|
+
response = await runStore(data);
|
|
386
|
+
if (isData) {
|
|
387
|
+
console.log(
|
|
388
|
+
`Saved: ${entryPath}/${file}`
|
|
389
|
+
);
|
|
390
|
+
} else {
|
|
391
|
+
console.log(
|
|
392
|
+
`Uploaded: ${entryPath}/${file}`,
|
|
393
|
+
`To: ${pathname}`
|
|
394
|
+
);
|
|
395
|
+
}
|
|
344
396
|
if (response.error) errorLog.push(response.error);
|
|
345
397
|
}
|
|
346
398
|
|
|
@@ -349,7 +401,7 @@ module.exports = async function file(
|
|
|
349
401
|
if (entry.endsWith("/")) newEntry = entry + name;
|
|
350
402
|
else newEntry = entry + "/" + name;
|
|
351
403
|
|
|
352
|
-
await runFiles(directory, newEntry,
|
|
404
|
+
await runFiles(directory, newEntry, pathname, name);
|
|
353
405
|
}
|
|
354
406
|
}
|
|
355
407
|
// if (errorLog.length)
|
|
@@ -389,6 +441,57 @@ module.exports = async function file(
|
|
|
389
441
|
}
|
|
390
442
|
}
|
|
391
443
|
|
|
444
|
+
async function getData(filePath, mimeType, isSymlink) {
|
|
445
|
+
let resolvedPath = filePath;
|
|
446
|
+
if (isSymlink) {
|
|
447
|
+
resolvedPath = await realpathAsync(filePath);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
try {
|
|
451
|
+
const fileMimeType = mimeTypes[path.extname(resolvedPath)] || "text/plain";
|
|
452
|
+
if (fileMimeType === "application/json") {
|
|
453
|
+
// Parse JSON files
|
|
454
|
+
return JSON.parse(fs.readFileSync(resolvedPath, "utf8"));
|
|
455
|
+
} else if (
|
|
456
|
+
fileMimeType === "application/javascript" ||
|
|
457
|
+
fileMimeType === "text/javascript"
|
|
458
|
+
) {
|
|
459
|
+
// Try CommonJS require first (fast path)
|
|
460
|
+
try {
|
|
461
|
+
// clear require cache to ensure fresh load
|
|
462
|
+
delete require.cache[require.resolve(resolvedPath)];
|
|
463
|
+
return require(resolvedPath);
|
|
464
|
+
} catch (err) {
|
|
465
|
+
// If require fails due to ESM syntax (export / import), fall back to dynamic import
|
|
466
|
+
const isESMSyntaxError =
|
|
467
|
+
err instanceof SyntaxError ||
|
|
468
|
+
/Unexpected token 'export'/.test(err.message) ||
|
|
469
|
+
/Cannot use import statement outside a module/.test(err.message) ||
|
|
470
|
+
/Unexpected token 'import'/.test(err.message);
|
|
471
|
+
|
|
472
|
+
if (isESMSyntaxError) {
|
|
473
|
+
try {
|
|
474
|
+
const module = await import(pathToFileURL(resolvedPath).href);
|
|
475
|
+
// return default export when present otherwise return full module
|
|
476
|
+
return module && module.default ? module.default : module;
|
|
477
|
+
} catch (impErr) {
|
|
478
|
+
console.error(`Failed to dynamic-import module: ${resolvedPath}`, impErr);
|
|
479
|
+
throw impErr;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
// rethrow original require error if it's not an ESM issue
|
|
483
|
+
throw err;
|
|
484
|
+
}
|
|
485
|
+
} else {
|
|
486
|
+
return fs.readFileSync(resolvedPath, "utf8");
|
|
487
|
+
}
|
|
488
|
+
} catch (error) {
|
|
489
|
+
console.error(`Failed to process file: ${resolvedPath}`, error);
|
|
490
|
+
return "";
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
}
|
|
494
|
+
|
|
392
495
|
/**
|
|
393
496
|
* Store files by config sources
|
|
394
497
|
**/
|