@cocreate/file 1.19.6 → 1.20.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 CHANGED
@@ -1,3 +1,18 @@
1
+ # [1.20.0](https://github.com/CoCreate-app/CoCreate-file/compare/v1.19.6...v1.20.0) (2025-10-11)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * add error handling for translation service in file function ([262dc66](https://github.com/CoCreate-app/CoCreate-file/commit/262dc66d57c13a8948ef1afa863413c203f965bc))
7
+ * update import statement and modify exit behavior in server function ([6e72fde](https://github.com/CoCreate-app/CoCreate-file/commit/6e72fdec3867c031e342b3faef598b661f43a620))
8
+
9
+
10
+ ### Features
11
+
12
+ * add options parameter to file function for enhanced translation support ([7fac13d](https://github.com/CoCreate-app/CoCreate-file/commit/7fac13d10b8abd40584d5abc6b89d110f84db288))
13
+ * enhance file handling by supporting base64 encoding for binary sources and improving file reading logic ([192337c](https://github.com/CoCreate-app/CoCreate-file/commit/192337c3a44d412f8a8d2c6bb1172451661eebf5))
14
+ * enhance file processing with support for base64 and binary types, and add variable processing functionality ([eabfddf](https://github.com/CoCreate-app/CoCreate-file/commit/eabfddf2e1c36e7abd330369c1f28f90290cc1b8))
15
+
1
16
  ## [1.19.6](https://github.com/CoCreate-app/CoCreate-file/compare/v1.19.5...v1.19.6) (2025-09-07)
2
17
 
3
18
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocreate/file",
3
- "version": "1.19.6",
3
+ "version": "1.20.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/client.js CHANGED
@@ -28,7 +28,7 @@ import Elements from "@cocreate/elements";
28
28
  import Actions from "@cocreate/actions";
29
29
  import { render } from "@cocreate/render";
30
30
  import { queryElements } from "@cocreate/utils";
31
- import "@cocreate/element-prototype";
31
+ import { setValue } from "@cocreate/element-prototype";
32
32
 
33
33
  const inputs = new Map();
34
34
  const Files = new Map();
package/src/server.js CHANGED
@@ -83,7 +83,12 @@ const mimeTypes = {
83
83
  ".7z": "application/x-7z-compressed"
84
84
  };
85
85
 
86
- module.exports = async function file(CoCreateConfig, configPath, match) {
86
+ module.exports = async function file(
87
+ CoCreateConfig,
88
+ configPath,
89
+ match,
90
+ options
91
+ ) {
87
92
  let directories = CoCreateConfig.directories;
88
93
  let sources = CoCreateConfig.sources;
89
94
  let configDirectoryPath = path.dirname(configPath);
@@ -253,7 +258,7 @@ module.exports = async function file(CoCreateConfig, configPath, match) {
253
258
 
254
259
  let values = {
255
260
  "{{name}}": name || "",
256
- "{{source}}": source || "",
261
+ "{{source}}": Buffer.isBuffer(source) ? `data:${mimeType};base64,${source.toString('base64')}` : source || "",
257
262
  "{{directory}}": directoryName || "",
258
263
  "{{path}}": Path || "",
259
264
  "{{pathname}}": pathname,
@@ -280,6 +285,25 @@ module.exports = async function file(CoCreateConfig, configPath, match) {
280
285
  object
281
286
  };
282
287
 
288
+ if (
289
+ options.translate &&
290
+ mimeType === "text/html" &&
291
+ Array.isArray(directory.languages) &&
292
+ !object.translations
293
+ ) {
294
+ try {
295
+ // Call your AI translation service
296
+ const translations = await options.translate(
297
+ Buffer.isBuffer(source) ? source.toString('utf-8') : source,
298
+ directory.languages
299
+ );
300
+ newObject.object.translations = translations;
301
+ } catch (err) {
302
+ console.error("Translation error:", err);
303
+ // Continue without translations
304
+ }
305
+ }
306
+
283
307
  if (directory.storage) newObject.storage = directory.storage;
284
308
  if (directory.database) newObject.database = directory.database;
285
309
  if (directory.array) newObject.array = directory.array || "files";
@@ -291,9 +315,13 @@ module.exports = async function file(CoCreateConfig, configPath, match) {
291
315
  );
292
316
  if (variables) {
293
317
  for (let variable of variables) {
318
+ let replacement = values[variable];
319
+ if (key === 'src' && variable === '{{source}}' && Buffer.isBuffer(source)) {
320
+ replacement = `data:${mimeType};base64,${source.toString('base64')}`;
321
+ }
294
322
  newObject.object[key] = newObject.object[
295
323
  key
296
- ].replace(variable, values[variable]);
324
+ ].replace(variable, replacement);
297
325
  }
298
326
  }
299
327
  }
@@ -328,144 +356,137 @@ module.exports = async function file(CoCreateConfig, configPath, match) {
328
356
  // console.log(...errorLog)
329
357
  }
330
358
 
331
- async function getSource(path, mimeType, isSymlink) {
332
- let readType = "utf8";
333
- if (mimeType === "image/svg+xml") {
334
- readType = "utf8";
335
- } else if (/^(image|audio|video)\/[-+.\w]+/.test(mimeType)) {
336
- readType = "base64";
337
- }
359
+ async function getSource(filePath, mimeType, isSymlink) {
360
+ // 1. UPDATED: Includes standard font types and uses simpler matching
361
+ const base64MimeTypes = /^(image|audio|video|font\/(woff2?|ttf|otf|eot)|application\/vnd\.ms-fontobject|application\/x-font-.*|application\/octet-stream)/;
338
362
 
339
- if (isSymlink) path = await realpathAsync(path);
363
+ // We only care if it needs to be Base64-encoded for a Data URI.
364
+ const needsBase64 = base64MimeTypes.test(mimeType);
340
365
 
341
- let binary = fs.readFileSync(path);
342
- let content = new Buffer.from(binary).toString(readType);
366
+ let resolvedPath = filePath;
367
+ if (isSymlink) {
368
+ // Use promises for realpath
369
+ resolvedPath = await realpathAsync(filePath);
370
+ }
343
371
 
344
- return content;
372
+ // 2. READ: Always read the file as a raw Buffer (omitting encoding)
373
+ // This gives us the raw bytes, which is the safest start for any file.
374
+ let fileBuffer;
375
+ try {
376
+ fileBuffer = await fs.promises.readFile(resolvedPath);
377
+ } catch (error) {
378
+ console.error(`Error reading file: ${resolvedPath}`, error);
379
+ return ""; // Return empty string or handle error as appropriate
380
+ }
381
+
382
+ if (needsBase64) {
383
+ // 3. RETURN BUFFER: Return the raw buffer for binary files.
384
+ return fileBuffer;
385
+ } else {
386
+ // 4. HANDLE TEXT/OTHER:
387
+ // For files not intended for Base64, convert the Buffer to a string using 'utf8'.
388
+ return fileBuffer.toString('utf8');
389
+ }
345
390
  }
346
391
 
347
392
  /**
348
393
  * Store files by config sources
349
394
  **/
350
395
  async function runSources() {
351
- let updatedSources = [];
396
+ let newConfig = require(configPath);
352
397
 
353
398
  for (let i = 0; i < sources.length; i++) {
354
- const { array, object } = sources[i];
355
-
356
- let source = { ...sources[i] };
357
- let keys = new Map();
358
- let response = {};
359
- let isMatch = false;
360
-
361
- try {
362
- if (array) {
363
- if (!object) object = {};
364
- else
365
- for (const key of Object.keys(object)) {
366
- if (typeof object[key] != "string") continue;
367
-
368
- let variables = object[key].match(
369
- /{{([A-Za-z0-9_.,\[\]\-\/ ]*)}}/g
370
- );
371
- if (variables) {
372
- let originalValue = object[key];
373
- keys.set(key, originalValue);
374
- let value = "";
375
- for (let variable of variables) {
376
- let entry = /{{\s*([\w\W]+)\s*}}/g.exec(
377
- variable
378
- );
379
- entry = entry[1].trim();
380
- if (entry) {
381
- if (!fs.existsSync(entry)) continue;
382
-
383
- if (!isMatch) {
384
- const filePath = path.resolve(
385
- configDirectoryPath,
386
- entry
387
- );
388
- for (
389
- let i = 0;
390
- i < match.length;
391
- i++
392
- ) {
393
- if (
394
- filePath.startsWith(
395
- match[i]
396
- )
397
- ) {
398
- console.log(
399
- "Source saved",
400
- sources[i]
401
- );
402
- isMatch = true;
403
- break;
404
- }
405
- }
406
- }
407
-
408
- let read_type = "utf8";
409
- const fileExtension =
410
- path.extname(entry);
411
- let mime_type =
412
- mimeTypes[fileExtension] ||
413
- "text/html";
414
-
415
- if (
416
- /^(image|audio|video)\/[-+.\w]+/.test(
417
- mime_type
418
- )
419
- ) {
420
- read_type = "base64";
421
- }
422
-
423
- let binary = fs.readFileSync(entry);
424
- let content = new Buffer.from(
425
- binary
426
- ).toString(read_type);
427
- if (content) value += content;
428
- // object[key] = object[key].replace(variable, content);
429
- }
430
- }
431
- object[key] = value;
432
- }
399
+ let data = sources[i];
400
+
401
+ // Handle string values
402
+ if (typeof data === "string") {
403
+ let {value, filePath } = await processVariables(data);
404
+ let response = await runStore(value);
405
+ if (response && response.object && response.object[0]) {
406
+ updateFilePath(filePath, response); // Call the new function to update the file path
407
+ }
408
+ } else if (data.array && data.object) {
409
+ if (typeof data.object === "string") {
410
+ let {value, filePath } = await processVariables(data.object);
411
+ if (value) {
412
+ let response = await runStore(value);
413
+ if (response && response.object && response.object[0]) {
414
+ updateFilePath(filePath, response.object);
433
415
  }
434
-
435
- let data = { array, object };
436
- if (!object._id && object.pathname)
437
- data.$filter = {
438
- query: {
439
- $or: [{ pathname: object.pathname }]
440
- }
441
- };
442
-
443
- if (match.length && isMatch)
444
- response = await runStore(data);
416
+ }
417
+ } else if (typeof data.object === "object" && data.object !== null) {
418
+ for (const key in data) {
419
+ let {value } = await processVariables(data[key]);
420
+ if (data) {
421
+ data.object[key] = value;
422
+ }
423
+ }
424
+ let response = await runStore(data);
425
+ if (response && response.object && response.object[0] && response.object[0]._id) {
426
+ newConfig.sources[i].object._id = response.object[0]._id;
427
+ }
445
428
  }
446
- } catch (err) {
447
- console.log(err);
448
- process.exit();
449
429
  }
450
430
 
451
- if (
452
- response.object &&
453
- response.object[0] &&
454
- response.object[0]._id
455
- ) {
456
- source.object._id = response.object[0]._id;
457
- }
431
+ }
458
432
 
459
- for (const [key, value] of keys) {
460
- source.object[key] = value;
461
- }
433
+ return newConfig;
434
+ }
435
+
436
+ async function processVariables(value) {
437
+ let variableMatch = /{{\s*([\w\W]+)\s*}}/g.exec(value);
438
+ if (!variableMatch) return { value, filePath: null };
439
+
440
+ let entry = variableMatch[1].trim();
441
+ if (!fs.existsSync(entry)) return { value, filePath: null };
462
442
 
463
- updatedSources.push(source);
443
+ const filePath = path.resolve(configDirectoryPath, entry);
444
+
445
+ // Check if the file path matches any of the provided match patterns
446
+ let isMatched = match.some((pattern) => filePath.startsWith(pattern));
447
+ if (!isMatched) return { value, filePath: null };
448
+
449
+ // Read the file as is
450
+ let content;
451
+ try {
452
+ const fileMimeType = mimeTypes[path.extname(entry)] || "text/plain";
453
+
454
+ if (fileMimeType === "application/json") {
455
+ // Parse JSON files
456
+ content = JSON.parse(fs.readFileSync(filePath, "utf8"));
457
+ } else if (fileMimeType === "application/javascript" || fileMimeType === "text/javascript") {
458
+ // For JavaScript files, require the file to execute exports
459
+ content = require(filePath);
460
+ } else {
461
+ // For plain strings, read as UTF-8 without conversion
462
+ content = fs.readFileSync(filePath, "utf8");
463
+ }
464
+ } catch (error) {
465
+ console.error(`Failed to process file: ${filePath}`, error);
466
+ return { value, filePath: null };
464
467
  }
465
468
 
466
- return updatedSources;
469
+ return { value: content, filePath };
470
+ }
471
+
472
+ /**
473
+ * Updates the file at the given file path with the provided data.
474
+ * The data is saved as a JSON string.
475
+ *
476
+ * @param {string} filePath - The path of the file to update.
477
+ * @param {object} data - The data to write to the file.
478
+ */
479
+ function updateFilePath(filePath, data) {
480
+ try {
481
+ const jsonData = JSON.stringify(data, null, 4); // Format JSON with indentation
482
+ fs.writeFileSync(filePath, jsonData, "utf8");
483
+ console.log(`File updated successfully at: ${filePath}`);
484
+ } catch (error) {
485
+ console.error(`Failed to update file at: ${filePath}`, error);
486
+ }
467
487
  }
468
488
 
489
+
469
490
  async function runStore(data) {
470
491
  try {
471
492
  let response;
@@ -493,42 +514,22 @@ module.exports = async function file(CoCreateConfig, configPath, match) {
493
514
  }
494
515
 
495
516
  async function run() {
496
- if (directories) await runDirectories();
517
+ if (directories) {
518
+ await runDirectories();
519
+ }
497
520
 
498
521
  if (sources && sources.length) {
499
- let sources = await runSources();
500
- let newConfig = { ...CoCreateConfig };
501
- if (directories && directories.length)
502
- newConfig.directories = directories;
503
-
504
- newConfig.sources = sources;
505
-
506
- if (newConfig.repositories)
507
- newConfig.repositories.forEach((obj) => {
508
- for (const key in obj) {
509
- if (!["path", "repo", "exclude"].includes(key)) {
510
- delete obj[key];
511
- }
512
- }
513
- });
514
-
515
- delete newConfig.url;
516
- delete newConfig.broadcast;
517
-
522
+ let newConfig = await runSources();
518
523
  fs.writeFileSync(
519
524
  configPath,
520
525
  `module.exports = ${JSON.stringify(newConfig, null, 4)};`
521
526
  );
522
527
  }
523
-
524
- if (!match.length) {
525
- console.log("upload complete!");
526
-
527
- setTimeout(function () {
528
- process.exit();
529
- }, 2000);
530
- }
531
528
  }
532
529
 
533
530
  await run();
531
+ // Only exit if not in watch mode
532
+ if (!process.argv.includes("--watch") && !process.argv.includes("-w")) {
533
+ process.exit();
534
+ }
534
535
  };