@arela/uploader 0.0.1 → 0.0.3

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.
Files changed (2) hide show
  1. package/package.json +4 -3
  2. package/src/index.js +125 -34
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arela/uploader",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "CLI to upload files/directories to Arela",
5
5
  "bin": {
6
6
  "arela": "./src/index.js"
@@ -32,10 +32,11 @@
32
32
  "commander": "^13.1.0",
33
33
  "dotenv": "^16.5.0",
34
34
  "globby": "^14.1.0",
35
- "mime-types": "^3.0.1"
35
+ "mime-types": "^3.0.1",
36
+ "ora": "^8.2.0"
36
37
  },
37
38
  "devDependencies": {
38
39
  "@trivago/prettier-plugin-sort-imports": "^5.2.2",
39
40
  "prettier": "^3.5.3"
40
41
  }
41
- }
42
+ }
package/src/index.js CHANGED
@@ -5,6 +5,7 @@ import { config } from 'dotenv';
5
5
  import fs from 'fs';
6
6
  import { globby } from 'globby';
7
7
  import mime from 'mime-types';
8
+ import ora from 'ora';
8
9
  import path from 'path';
9
10
 
10
11
  config();
@@ -14,14 +15,31 @@ const program = new Command();
14
15
  const supabaseUrl = process.env.SUPABASE_URL;
15
16
  const supabaseKey = process.env.SUPABASE_KEY;
16
17
  const bucket = process.env.SUPABASE_BUCKET;
18
+ const basePath = process.env.UPLOAD_BASE_PATH;
19
+ const sources = process.env.UPLOAD_SOURCES?.split('|').map((s) => s.trim()).filter(Boolean);
17
20
 
18
21
  const supabase = createClient(supabaseUrl, supabaseKey);
19
22
 
23
+ const sendLogToSupabase = async ({ file, uploadPath, status, message }) => {
24
+ const { error } = await supabase.from('upload_logs').insert([
25
+ {
26
+ filename: path.basename(file),
27
+ path: uploadPath,
28
+ status,
29
+ message,
30
+ },
31
+ ]);
32
+
33
+ if (error) {
34
+ console.error(`⚠️ Error saving the log to Supabase: ${error.message}`);
35
+ }
36
+ };
37
+
20
38
  const checkCredentials = async () => {
21
39
  if (!supabaseUrl || !supabaseKey || !bucket) {
22
- console.error(
23
- '⚠️ Missing Supabase credentials. Please set SUPABASE_URL, SUPABASE_KEY, and SUPABASE_BUCKET in your environment variables.',
24
- );
40
+ console.error('⚠️ Missing Supabase credentials. Please set SUPABASE_URL, SUPABASE_KEY, and SUPABASE_BUCKET in your environment variables.');
41
+ writeLog('⚠️ Missing Supabase credentials.');
42
+ await sendLogToSupabase({ file: 'Error', uploadPath: 'Error', status: 'error', message: 'Missing Supabase credentials.' });
25
43
  process.exit(1);
26
44
  }
27
45
 
@@ -29,53 +47,126 @@ const checkCredentials = async () => {
29
47
  const { error } = await supabase.storage.from(bucket).list('');
30
48
  if (error) {
31
49
  console.error('⚠️ Error connecting to Supabase:', error.message);
50
+ writeLog(`⚠️ Error connecting to Supabase: ${error.message}`);
51
+ await sendLogToSupabase({ file: 'Error', uploadPath: 'Error', status: 'error', message: error.message });
32
52
  process.exit(1);
33
53
  }
34
54
  } catch (err) {
35
55
  console.error('⚠️ Error:', err.message);
56
+ writeLog(`⚠️ Error: ${err.message}`);
57
+ await sendLogToSupabase({ file: 'Error', uploadPath: 'Error', status: 'error', message: err.message });
36
58
  process.exit(1);
37
59
  }
38
60
  };
39
61
 
40
62
  await checkCredentials();
41
63
 
64
+ const fileExistsInBucket = async (pathInBucket) => {
65
+ const dir = path.dirname(pathInBucket);
66
+ const filename = path.basename(pathInBucket);
67
+ const { data, error } = await supabase.storage.from(bucket).list(dir === '.' ? '' : dir, { limit: 1000 });
68
+ if (error) {
69
+ console.error(`⚠️ Could not verify duplicate: ${error.message}`);
70
+ writeLog(`⚠️ Could not verify duplicate: ${error.message}`);
71
+ await sendLogToSupabase({ file: 'Error', uploadPath: 'Error', status: 'error', message: error.message });
72
+ return false;
73
+ }
74
+ return data.some((file) => file.name === filename);
75
+ };
76
+
77
+ const logFilePath = path.resolve(process.cwd(), 'upload.log');
78
+ const writeLog = (message) => {
79
+ const timestamp = new Date().toISOString();
80
+ fs.appendFileSync(logFilePath, `[${timestamp}] ${message}\n`);
81
+ };
82
+
83
+ const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
84
+
42
85
  program
43
86
  .name('supabase-uploader')
44
- .description('CLI to upload files/directories to Supabase Storage')
45
- .argument('<source>', 'File or directory to upload')
87
+ .description('CLI to upload folders from a base path to Supabase Storage')
46
88
  .option('-p, --prefix <prefix>', 'Prefix path in bucket', '')
47
- .action(async (source, options) => {
48
- try {
49
- const stats = fs.statSync(source);
50
- const files = stats.isDirectory()
51
- ? await globby([`${source}/**/*`], { onlyFiles: true })
52
- : [source];
53
-
54
- for (const file of files) {
55
- const content = fs.readFileSync(file);
56
- const relativePath = path.relative(source, file);
57
- const uploadPath = options.prefix
58
- ? path.join(options.prefix, relativePath)
59
- : relativePath;
60
-
61
- const contentType = mime.lookup(file) || 'application/octet-stream';
62
-
63
- const { data, error } = await supabase.storage
64
- .from(bucket)
65
- .upload(uploadPath.replace(/\\/g, '/'), content, {
66
- upsert: true,
67
- contentType,
68
- });
69
-
70
- if (error) {
71
- console.error(`❌ Failed to upload ${file}:`, error.message);
72
- } else {
73
- console.log(`✅ Uploaded ${file} -> ${uploadPath}`);
89
+ .action(async (options) => {
90
+ if (!basePath || !sources || sources.length === 0) {
91
+ console.error('⚠️ UPLOAD_BASE_PATH or UPLOAD_SOURCES not defined in environment variables.');
92
+ process.exit(1);
93
+ }
94
+
95
+ let globalSuccess = 0;
96
+ let globalFailure = 0;
97
+
98
+ for (const folder of sources) {
99
+ const sourcePath = path.join(basePath, folder);
100
+ console.log(`📂 Procesando carpeta: ${sourcePath}`);
101
+
102
+ try {
103
+ const stats = fs.statSync(sourcePath);
104
+ const files = stats.isDirectory() ? await globby([`${sourcePath}/**/*`], { onlyFiles: true }) : [sourcePath];
105
+
106
+ let successCount = 0;
107
+ let failureCount = 0;
108
+
109
+ for (const file of files) {
110
+ const content = fs.readFileSync(file);
111
+ const relativePath = path.relative(basePath, file).replace(/^[\\/]+/, '').replace(/\\/g, '/');
112
+ const uploadPath = options.prefix ? path.posix.join(options.prefix, relativePath) : relativePath;
113
+ const contentType = mime.lookup(file) || 'application/octet-stream';
114
+
115
+ const spinner = ora(`Checking ${file}...`).start();
116
+ const exists = await fileExistsInBucket(uploadPath);
117
+
118
+ if (exists) {
119
+ spinner.info(`⏭️ Skipped (already exists): ${file}`);
120
+ writeLog(`SKIPPED: ${file} -> ${uploadPath}`);
121
+ await sendLogToSupabase({ file, uploadPath, status: 'skipped', message: 'Already exists in bucket' });
122
+ continue;
123
+ }
124
+
125
+ try {
126
+ await delay(5000); // TODO: Remove this delay before production
127
+
128
+ const { error } = await supabase.storage.from(bucket).upload(uploadPath, content, {
129
+ upsert: true,
130
+ contentType,
131
+ });
132
+
133
+ if (error) {
134
+ failureCount++;
135
+ globalFailure++;
136
+ spinner.fail(`❌ Failed to upload ${file}: ${JSON.stringify(error, null, 2)}`);
137
+ writeLog(`ERROR: ${file} -> ${uploadPath} | ${error.message}`);
138
+ await sendLogToSupabase({ file, uploadPath, status: 'error', message: error.message });
139
+ } else {
140
+ successCount++;
141
+ globalSuccess++;
142
+ spinner.succeed(`✅ Uploaded ${file} -> ${uploadPath}`);
143
+ writeLog(`SUCCESS: ${file} -> ${uploadPath}`);
144
+ await sendLogToSupabase({ file, uploadPath, status: 'success', message: 'Uploaded successfully' });
145
+ }
146
+ } catch (err) {
147
+ spinner.fail(`❌ Error uploading ${file}: ${err.message}`);
148
+ writeLog(`❌ Error uploading ${file}: ${err.message}`);
149
+ }
74
150
  }
151
+
152
+ console.log(`\n📦 Upload Summary:`);
153
+ console.log(` ✅ Successfully uploaded files: ${successCount}`);
154
+ console.log(` ❌ Files with errors: ${failureCount}`);
155
+ console.log(` ⏭️ Files skipped (already exist): ${files.length - successCount - failureCount}`);
156
+ console.log(` 📜 Log file: ${logFilePath} \n`);
157
+
158
+ writeLog(`📦 Upload Summary for folder ${folder}: Success: ${successCount}, Errors: ${failureCount}, Skipped: ${files.length - successCount - failureCount}`);
159
+ } catch (err) {
160
+ console.error(`⚠️ Error processing folder ${folder}:`, err.message);
161
+ writeLog(`⚠️ Error processing folder ${folder}: ${err.message}`);
162
+ await sendLogToSupabase({ file: folder, uploadPath: folder, status: 'error', message: err.message });
75
163
  }
76
- } catch (err) {
77
- console.error('⚠️ Error:', err.message);
78
164
  }
165
+
166
+ console.log(`🎯 Upload completed.`);
167
+ console.log(` ✅ Total uploaded: ${globalSuccess}`);
168
+ console.log(` ❌ Total with errors: ${globalFailure}`);
169
+ console.log(` 📜 Log file: ${logFilePath}`);
79
170
  });
80
171
 
81
172
  program.parse();