@arela/uploader 0.0.2 → 0.0.4
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/package.json +4 -3
- package/src/index.js +120 -56
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arela/uploader",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
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
|
-
|
|
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,80 +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
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
commonParts.push(segment);
|
|
52
|
-
} else {
|
|
53
|
-
break;
|
|
54
|
-
}
|
|
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;
|
|
55
73
|
}
|
|
74
|
+
return data.some((file) => file.name === filename);
|
|
75
|
+
};
|
|
56
76
|
|
|
57
|
-
|
|
58
|
-
|
|
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
|
+
};
|
|
59
82
|
|
|
60
|
-
// const
|
|
83
|
+
// const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
61
84
|
|
|
62
85
|
program
|
|
63
86
|
.name('supabase-uploader')
|
|
64
|
-
.description('CLI to upload
|
|
65
|
-
.argument('<source>', 'Glob pattern of files to upload')
|
|
87
|
+
.description('CLI to upload folders from a base path to Supabase Storage')
|
|
66
88
|
.option('-p, --prefix <prefix>', 'Prefix path in bucket', '')
|
|
67
|
-
.action(async (
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (files.length === 0) {
|
|
73
|
-
console.error('⚠️ No matching files found for pattern:', source);
|
|
74
|
-
process.exit(1);
|
|
75
|
-
}
|
|
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
|
+
}
|
|
76
94
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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(`📂 Processing folder: ${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
|
+
}
|
|
101
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 });
|
|
102
163
|
}
|
|
103
|
-
} catch (err) {
|
|
104
|
-
console.error('⚠️ Error:', err.message);
|
|
105
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}`);
|
|
106
170
|
});
|
|
107
171
|
|
|
108
172
|
program.parse();
|