@cocreate/file 1.19.5 → 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,27 @@
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
+
16
+ ## [1.19.6](https://github.com/CoCreate-app/CoCreate-file/compare/v1.19.5...v1.19.6) (2025-09-07)
17
+
18
+
19
+ ### Bug Fixes
20
+
21
+ * correct exclusion logic to check each item in the exclude array ([1fae391](https://github.com/CoCreate-app/CoCreate-file/commit/1fae391f4333813aa30805e87ba7ea28fefda5d3))
22
+ * ensure match is an array by wrapping non-array values ([5c67ec5](https://github.com/CoCreate-app/CoCreate-file/commit/5c67ec592f0217199b2ce965d14e377d6faa9a5b))
23
+ * ensure match is an array of directory paths and await run function ([eb47cf0](https://github.com/CoCreate-app/CoCreate-file/commit/eb47cf00c672ac5110293b3dab38fd8186bb8d64))
24
+
1
25
  ## [1.19.5](https://github.com/CoCreate-app/CoCreate-file/compare/v1.19.4...v1.19.5) (2025-05-01)
2
26
 
3
27
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocreate/file",
3
- "version": "1.19.5",
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,13 +83,21 @@ 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);
90
95
 
91
- if (match && !Array.isArray(match)) match = [match];
92
- else if (!match) match = [];
96
+ if (match && !Array.isArray(match)) {
97
+ match = [match];
98
+ } else if (!match) {
99
+ match = [];
100
+ }
93
101
 
94
102
  let config = await Config(
95
103
  {
@@ -179,7 +187,7 @@ module.exports = async function file(CoCreateConfig, configPath, match) {
179
187
  for (let file of files) {
180
188
  let skip = false;
181
189
  for (let i = 0; i < exclude.length; i++) {
182
- if (file.includes(exclude)) {
190
+ if (file.includes(exclude[i])) {
183
191
  skip = true;
184
192
  break;
185
193
  }
@@ -250,7 +258,7 @@ module.exports = async function file(CoCreateConfig, configPath, match) {
250
258
 
251
259
  let values = {
252
260
  "{{name}}": name || "",
253
- "{{source}}": source || "",
261
+ "{{source}}": Buffer.isBuffer(source) ? `data:${mimeType};base64,${source.toString('base64')}` : source || "",
254
262
  "{{directory}}": directoryName || "",
255
263
  "{{path}}": Path || "",
256
264
  "{{pathname}}": pathname,
@@ -277,6 +285,25 @@ module.exports = async function file(CoCreateConfig, configPath, match) {
277
285
  object
278
286
  };
279
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
+
280
307
  if (directory.storage) newObject.storage = directory.storage;
281
308
  if (directory.database) newObject.database = directory.database;
282
309
  if (directory.array) newObject.array = directory.array || "files";
@@ -288,9 +315,13 @@ module.exports = async function file(CoCreateConfig, configPath, match) {
288
315
  );
289
316
  if (variables) {
290
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
+ }
291
322
  newObject.object[key] = newObject.object[
292
323
  key
293
- ].replace(variable, values[variable]);
324
+ ].replace(variable, replacement);
294
325
  }
295
326
  }
296
327
  }
@@ -325,144 +356,137 @@ module.exports = async function file(CoCreateConfig, configPath, match) {
325
356
  // console.log(...errorLog)
326
357
  }
327
358
 
328
- async function getSource(path, mimeType, isSymlink) {
329
- let readType = "utf8";
330
- if (mimeType === "image/svg+xml") {
331
- readType = "utf8";
332
- } else if (/^(image|audio|video)\/[-+.\w]+/.test(mimeType)) {
333
- readType = "base64";
334
- }
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)/;
335
362
 
336
- 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);
337
365
 
338
- let binary = fs.readFileSync(path);
339
- 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
+ }
340
371
 
341
- 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
+ }
342
390
  }
343
391
 
344
392
  /**
345
393
  * Store files by config sources
346
394
  **/
347
395
  async function runSources() {
348
- let updatedSources = [];
396
+ let newConfig = require(configPath);
349
397
 
350
398
  for (let i = 0; i < sources.length; i++) {
351
- const { array, object } = sources[i];
352
-
353
- let source = { ...sources[i] };
354
- let keys = new Map();
355
- let response = {};
356
- let isMatch = false;
357
-
358
- try {
359
- if (array) {
360
- if (!object) object = {};
361
- else
362
- for (const key of Object.keys(object)) {
363
- if (typeof object[key] != "string") continue;
364
-
365
- let variables = object[key].match(
366
- /{{([A-Za-z0-9_.,\[\]\-\/ ]*)}}/g
367
- );
368
- if (variables) {
369
- let originalValue = object[key];
370
- keys.set(key, originalValue);
371
- let value = "";
372
- for (let variable of variables) {
373
- let entry = /{{\s*([\w\W]+)\s*}}/g.exec(
374
- variable
375
- );
376
- entry = entry[1].trim();
377
- if (entry) {
378
- if (!fs.existsSync(entry)) continue;
379
-
380
- if (!isMatch) {
381
- const filePath = path.resolve(
382
- configDirectoryPath,
383
- entry
384
- );
385
- for (
386
- let i = 0;
387
- i < match.length;
388
- i++
389
- ) {
390
- if (
391
- filePath.startsWith(
392
- match[i]
393
- )
394
- ) {
395
- console.log(
396
- "Source saved",
397
- sources[i]
398
- );
399
- isMatch = true;
400
- break;
401
- }
402
- }
403
- }
404
-
405
- let read_type = "utf8";
406
- const fileExtension =
407
- path.extname(entry);
408
- let mime_type =
409
- mimeTypes[fileExtension] ||
410
- "text/html";
411
-
412
- if (
413
- /^(image|audio|video)\/[-+.\w]+/.test(
414
- mime_type
415
- )
416
- ) {
417
- read_type = "base64";
418
- }
419
-
420
- let binary = fs.readFileSync(entry);
421
- let content = new Buffer.from(
422
- binary
423
- ).toString(read_type);
424
- if (content) value += content;
425
- // object[key] = object[key].replace(variable, content);
426
- }
427
- }
428
- object[key] = value;
429
- }
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);
430
415
  }
431
-
432
- let data = { array, object };
433
- if (!object._id && object.pathname)
434
- data.$filter = {
435
- query: {
436
- $or: [{ pathname: object.pathname }]
437
- }
438
- };
439
-
440
- if (match.length && isMatch)
441
- 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
+ }
442
428
  }
443
- } catch (err) {
444
- console.log(err);
445
- process.exit();
446
429
  }
447
430
 
448
- if (
449
- response.object &&
450
- response.object[0] &&
451
- response.object[0]._id
452
- ) {
453
- source.object._id = response.object[0]._id;
454
- }
431
+ }
455
432
 
456
- for (const [key, value] of keys) {
457
- source.object[key] = value;
458
- }
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 };
442
+
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 };
459
448
 
460
- updatedSources.push(source);
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 };
461
467
  }
462
468
 
463
- return updatedSources;
469
+ return { value: content, filePath };
464
470
  }
465
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
+ }
487
+ }
488
+
489
+
466
490
  async function runStore(data) {
467
491
  try {
468
492
  let response;
@@ -490,42 +514,22 @@ module.exports = async function file(CoCreateConfig, configPath, match) {
490
514
  }
491
515
 
492
516
  async function run() {
493
- if (directories) await runDirectories();
517
+ if (directories) {
518
+ await runDirectories();
519
+ }
494
520
 
495
521
  if (sources && sources.length) {
496
- let sources = await runSources();
497
- let newConfig = { ...CoCreateConfig };
498
- if (directories && directories.length)
499
- newConfig.directories = directories;
500
-
501
- newConfig.sources = sources;
502
-
503
- if (newConfig.repositories)
504
- newConfig.repositories.forEach((obj) => {
505
- for (const key in obj) {
506
- if (!["path", "repo", "exclude"].includes(key)) {
507
- delete obj[key];
508
- }
509
- }
510
- });
511
-
512
- delete newConfig.url;
513
- delete newConfig.broadcast;
514
-
522
+ let newConfig = await runSources();
515
523
  fs.writeFileSync(
516
524
  configPath,
517
525
  `module.exports = ${JSON.stringify(newConfig, null, 4)};`
518
526
  );
519
527
  }
520
-
521
- if (!match.length) {
522
- console.log("upload complete!");
523
-
524
- setTimeout(function () {
525
- process.exit();
526
- }, 2000);
527
- }
528
528
  }
529
529
 
530
- run();
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
+ }
531
535
  };