@arela/uploader 0.0.4 → 0.0.6
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 +1 -1
- package/src/index.js +123 -20
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -16,10 +16,19 @@ const supabaseUrl = process.env.SUPABASE_URL;
|
|
|
16
16
|
const supabaseKey = process.env.SUPABASE_KEY;
|
|
17
17
|
const bucket = process.env.SUPABASE_BUCKET;
|
|
18
18
|
const basePath = process.env.UPLOAD_BASE_PATH;
|
|
19
|
-
const sources = process.env.UPLOAD_SOURCES?.split('|')
|
|
19
|
+
const sources = process.env.UPLOAD_SOURCES?.split('|')
|
|
20
|
+
.map((s) => s.trim())
|
|
21
|
+
.filter(Boolean);
|
|
20
22
|
|
|
21
23
|
const supabase = createClient(supabaseUrl, supabaseKey);
|
|
22
24
|
|
|
25
|
+
const sanitizePath = (path) =>
|
|
26
|
+
path
|
|
27
|
+
.replace(/[\\?%*:|"<>[\]~]/g, '-')
|
|
28
|
+
.replace(/ +/g, ' ')
|
|
29
|
+
.replace(/^\.+/, '')
|
|
30
|
+
.replace(/\/+/g, '/');
|
|
31
|
+
|
|
23
32
|
const sendLogToSupabase = async ({ file, uploadPath, status, message }) => {
|
|
24
33
|
const { error } = await supabase.from('upload_logs').insert([
|
|
25
34
|
{
|
|
@@ -37,9 +46,16 @@ const sendLogToSupabase = async ({ file, uploadPath, status, message }) => {
|
|
|
37
46
|
|
|
38
47
|
const checkCredentials = async () => {
|
|
39
48
|
if (!supabaseUrl || !supabaseKey || !bucket) {
|
|
40
|
-
console.error(
|
|
49
|
+
console.error(
|
|
50
|
+
'⚠️ Missing Supabase credentials. Please set SUPABASE_URL, SUPABASE_KEY, and SUPABASE_BUCKET in your environment variables.',
|
|
51
|
+
);
|
|
41
52
|
writeLog('⚠️ Missing Supabase credentials.');
|
|
42
|
-
await sendLogToSupabase({
|
|
53
|
+
await sendLogToSupabase({
|
|
54
|
+
file: 'Error',
|
|
55
|
+
uploadPath: 'Error',
|
|
56
|
+
status: 'error',
|
|
57
|
+
message: 'Missing Supabase credentials.',
|
|
58
|
+
});
|
|
43
59
|
process.exit(1);
|
|
44
60
|
}
|
|
45
61
|
|
|
@@ -48,13 +64,23 @@ const checkCredentials = async () => {
|
|
|
48
64
|
if (error) {
|
|
49
65
|
console.error('⚠️ Error connecting to Supabase:', error.message);
|
|
50
66
|
writeLog(`⚠️ Error connecting to Supabase: ${error.message}`);
|
|
51
|
-
await sendLogToSupabase({
|
|
67
|
+
await sendLogToSupabase({
|
|
68
|
+
file: 'Error',
|
|
69
|
+
uploadPath: 'Error',
|
|
70
|
+
status: 'error',
|
|
71
|
+
message: error.message,
|
|
72
|
+
});
|
|
52
73
|
process.exit(1);
|
|
53
74
|
}
|
|
54
75
|
} catch (err) {
|
|
55
76
|
console.error('⚠️ Error:', err.message);
|
|
56
77
|
writeLog(`⚠️ Error: ${err.message}`);
|
|
57
|
-
await sendLogToSupabase({
|
|
78
|
+
await sendLogToSupabase({
|
|
79
|
+
file: 'Error',
|
|
80
|
+
uploadPath: 'Error',
|
|
81
|
+
status: 'error',
|
|
82
|
+
message: err.message,
|
|
83
|
+
});
|
|
58
84
|
process.exit(1);
|
|
59
85
|
}
|
|
60
86
|
};
|
|
@@ -64,11 +90,18 @@ await checkCredentials();
|
|
|
64
90
|
const fileExistsInBucket = async (pathInBucket) => {
|
|
65
91
|
const dir = path.dirname(pathInBucket);
|
|
66
92
|
const filename = path.basename(pathInBucket);
|
|
67
|
-
const { data, error } = await supabase.storage
|
|
93
|
+
const { data, error } = await supabase.storage
|
|
94
|
+
.from(bucket)
|
|
95
|
+
.list(dir === '.' ? '' : dir, { limit: 1000 });
|
|
68
96
|
if (error) {
|
|
69
97
|
console.error(`⚠️ Could not verify duplicate: ${error.message}`);
|
|
70
98
|
writeLog(`⚠️ Could not verify duplicate: ${error.message}`);
|
|
71
|
-
await sendLogToSupabase({
|
|
99
|
+
await sendLogToSupabase({
|
|
100
|
+
file: 'Error',
|
|
101
|
+
uploadPath: 'Error',
|
|
102
|
+
status: 'error',
|
|
103
|
+
message: error.message,
|
|
104
|
+
});
|
|
72
105
|
return false;
|
|
73
106
|
}
|
|
74
107
|
return data.some((file) => file.name === filename);
|
|
@@ -80,6 +113,21 @@ const writeLog = (message) => {
|
|
|
80
113
|
fs.appendFileSync(logFilePath, `[${timestamp}] ${message}\n`);
|
|
81
114
|
};
|
|
82
115
|
|
|
116
|
+
const getProcessedPaths = () => {
|
|
117
|
+
const lines = fs.existsSync(logFilePath)
|
|
118
|
+
? fs.readFileSync(logFilePath, 'utf-8').split('\n')
|
|
119
|
+
: [];
|
|
120
|
+
const processed = new Set();
|
|
121
|
+
for (const line of lines) {
|
|
122
|
+
const match = line.match(/(SUCCESS|SKIPPED): .*? -> (.+)/);
|
|
123
|
+
if (match) {
|
|
124
|
+
const [, , path] = match;
|
|
125
|
+
processed.add(path.trim());
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return processed;
|
|
129
|
+
};
|
|
130
|
+
|
|
83
131
|
// const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
84
132
|
|
|
85
133
|
program
|
|
@@ -88,28 +136,55 @@ program
|
|
|
88
136
|
.option('-p, --prefix <prefix>', 'Prefix path in bucket', '')
|
|
89
137
|
.action(async (options) => {
|
|
90
138
|
if (!basePath || !sources || sources.length === 0) {
|
|
91
|
-
console.error(
|
|
139
|
+
console.error(
|
|
140
|
+
'⚠️ UPLOAD_BASE_PATH or UPLOAD_SOURCES not defined in environment variables.',
|
|
141
|
+
);
|
|
92
142
|
process.exit(1);
|
|
93
143
|
}
|
|
94
144
|
|
|
145
|
+
const processedPaths = getProcessedPaths();
|
|
95
146
|
let globalSuccess = 0;
|
|
96
147
|
let globalFailure = 0;
|
|
97
148
|
|
|
98
149
|
for (const folder of sources) {
|
|
99
|
-
const sourcePath = path.
|
|
150
|
+
const sourcePath = path.resolve(basePath, folder).replace(/\\/g, '/');
|
|
100
151
|
console.log(`📂 Processing folder: ${sourcePath}`);
|
|
101
152
|
|
|
102
153
|
try {
|
|
103
154
|
const stats = fs.statSync(sourcePath);
|
|
104
|
-
const files = stats.isDirectory()
|
|
155
|
+
const files = stats.isDirectory()
|
|
156
|
+
? await globby([`${sourcePath}/**/*`], { onlyFiles: true })
|
|
157
|
+
: [sourcePath];
|
|
105
158
|
|
|
106
159
|
let successCount = 0;
|
|
107
160
|
let failureCount = 0;
|
|
108
161
|
|
|
109
162
|
for (const file of files) {
|
|
110
163
|
const content = fs.readFileSync(file);
|
|
111
|
-
const
|
|
112
|
-
|
|
164
|
+
const relativePathRaw = path
|
|
165
|
+
.relative(basePath, file)
|
|
166
|
+
.replace(/^[\\/]+/, '')
|
|
167
|
+
.replace(/\\/g, '/');
|
|
168
|
+
const uploadPathRaw = options.prefix
|
|
169
|
+
? path.posix.join(options.prefix, relativePathRaw)
|
|
170
|
+
: relativePathRaw;
|
|
171
|
+
const uploadPath = sanitizePath(uploadPathRaw);
|
|
172
|
+
|
|
173
|
+
if (uploadPath !== uploadPathRaw) {
|
|
174
|
+
writeLog(`SANITIZED: ${uploadPathRaw} → ${uploadPath}`);
|
|
175
|
+
await sendLogToSupabase({
|
|
176
|
+
file,
|
|
177
|
+
uploadPath: uploadPathRaw,
|
|
178
|
+
status: 'sanitized',
|
|
179
|
+
message: `Sanitized to ${uploadPath}`,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (processedPaths.has(uploadPath)) {
|
|
184
|
+
ora().info(`⏭️ Already processed (log): ${file}`);
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
|
|
113
188
|
const contentType = mime.lookup(file) || 'application/octet-stream';
|
|
114
189
|
|
|
115
190
|
const spinner = ora(`Checking ${file}...`).start();
|
|
@@ -118,14 +193,21 @@ program
|
|
|
118
193
|
if (exists) {
|
|
119
194
|
spinner.info(`⏭️ Skipped (already exists): ${file}`);
|
|
120
195
|
writeLog(`SKIPPED: ${file} -> ${uploadPath}`);
|
|
121
|
-
await sendLogToSupabase({
|
|
196
|
+
await sendLogToSupabase({
|
|
197
|
+
file,
|
|
198
|
+
uploadPath,
|
|
199
|
+
status: 'skipped',
|
|
200
|
+
message: 'Already exists in bucket',
|
|
201
|
+
});
|
|
122
202
|
continue;
|
|
123
203
|
}
|
|
124
204
|
|
|
125
205
|
try {
|
|
126
206
|
// await delay(5000); // TODO: Remove this delay before production
|
|
127
207
|
|
|
128
|
-
const { error } = await supabase.storage
|
|
208
|
+
const { error } = await supabase.storage
|
|
209
|
+
.from(bucket)
|
|
210
|
+
.upload(uploadPath, content, {
|
|
129
211
|
upsert: true,
|
|
130
212
|
contentType,
|
|
131
213
|
});
|
|
@@ -133,15 +215,27 @@ program
|
|
|
133
215
|
if (error) {
|
|
134
216
|
failureCount++;
|
|
135
217
|
globalFailure++;
|
|
136
|
-
spinner.fail(
|
|
218
|
+
spinner.fail(
|
|
219
|
+
`❌ Failed to upload ${file}: ${JSON.stringify(error, null, 2)}`,
|
|
220
|
+
);
|
|
137
221
|
writeLog(`ERROR: ${file} -> ${uploadPath} | ${error.message}`);
|
|
138
|
-
await sendLogToSupabase({
|
|
222
|
+
await sendLogToSupabase({
|
|
223
|
+
file,
|
|
224
|
+
uploadPath,
|
|
225
|
+
status: 'error',
|
|
226
|
+
message: error.message,
|
|
227
|
+
});
|
|
139
228
|
} else {
|
|
140
229
|
successCount++;
|
|
141
230
|
globalSuccess++;
|
|
142
231
|
spinner.succeed(`✅ Uploaded ${file} -> ${uploadPath}`);
|
|
143
232
|
writeLog(`SUCCESS: ${file} -> ${uploadPath}`);
|
|
144
|
-
await sendLogToSupabase({
|
|
233
|
+
await sendLogToSupabase({
|
|
234
|
+
file,
|
|
235
|
+
uploadPath,
|
|
236
|
+
status: 'success',
|
|
237
|
+
message: 'Uploaded successfully',
|
|
238
|
+
});
|
|
145
239
|
}
|
|
146
240
|
} catch (err) {
|
|
147
241
|
spinner.fail(`❌ Error uploading ${file}: ${err.message}`);
|
|
@@ -152,14 +246,23 @@ program
|
|
|
152
246
|
console.log(`\n📦 Upload Summary:`);
|
|
153
247
|
console.log(` ✅ Successfully uploaded files: ${successCount}`);
|
|
154
248
|
console.log(` ❌ Files with errors: ${failureCount}`);
|
|
155
|
-
console.log(
|
|
249
|
+
console.log(
|
|
250
|
+
` ⏭️ Files skipped (already exist): ${files.length - successCount - failureCount}`,
|
|
251
|
+
);
|
|
156
252
|
console.log(` 📜 Log file: ${logFilePath} \n`);
|
|
157
253
|
|
|
158
|
-
writeLog(
|
|
254
|
+
writeLog(
|
|
255
|
+
`📦 Upload Summary for folder ${folder}: Success: ${successCount}, Errors: ${failureCount}, Skipped: ${files.length - successCount - failureCount}`,
|
|
256
|
+
);
|
|
159
257
|
} catch (err) {
|
|
160
258
|
console.error(`⚠️ Error processing folder ${folder}:`, err.message);
|
|
161
259
|
writeLog(`⚠️ Error processing folder ${folder}: ${err.message}`);
|
|
162
|
-
await sendLogToSupabase({
|
|
260
|
+
await sendLogToSupabase({
|
|
261
|
+
file: folder,
|
|
262
|
+
uploadPath: folder,
|
|
263
|
+
status: 'error',
|
|
264
|
+
message: err.message,
|
|
265
|
+
});
|
|
163
266
|
}
|
|
164
267
|
}
|
|
165
268
|
|