@arela/uploader 0.0.5 → 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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +122 -19
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arela/uploader",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "CLI to upload files/directories to Arela",
5
5
  "bin": {
6
6
  "arela": "./src/index.js"
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('|').map((s) => s.trim()).filter(Boolean);
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('⚠️ Missing Supabase credentials. Please set SUPABASE_URL, SUPABASE_KEY, and SUPABASE_BUCKET in your environment variables.');
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({ file: 'Error', uploadPath: 'Error', status: 'error', message: 'Missing Supabase credentials.' });
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({ file: 'Error', uploadPath: 'Error', status: 'error', message: error.message });
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({ file: 'Error', uploadPath: 'Error', status: 'error', message: err.message });
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.from(bucket).list(dir === '.' ? '' : dir, { limit: 1000 });
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({ file: 'Error', uploadPath: 'Error', status: 'error', message: error.message });
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,10 +136,13 @@ 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('⚠️ UPLOAD_BASE_PATH or UPLOAD_SOURCES not defined in environment variables.');
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
 
@@ -101,15 +152,39 @@ program
101
152
 
102
153
  try {
103
154
  const stats = fs.statSync(sourcePath);
104
- const files = stats.isDirectory() ? await globby([`${sourcePath}/**/*`], { onlyFiles: true }) : [sourcePath];
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 relativePath = path.relative(basePath, file).replace(/^[\\/]+/, '').replace(/\\/g, '/');
112
- const uploadPath = options.prefix ? path.posix.join(options.prefix, relativePath) : relativePath;
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({ file, uploadPath, status: 'skipped', message: 'Already exists in bucket' });
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.from(bucket).upload(uploadPath, content, {
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(`❌ Failed to upload ${file}: ${JSON.stringify(error, null, 2)}`);
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({ file, uploadPath, status: 'error', message: error.message });
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({ file, uploadPath, status: 'success', message: 'Uploaded successfully' });
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(` ⏭️ Files skipped (already exist): ${files.length - successCount - failureCount}`);
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(`📦 Upload Summary for folder ${folder}: Success: ${successCount}, Errors: ${failureCount}, Skipped: ${files.length - successCount - failureCount}`);
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({ file: folder, uploadPath: folder, status: 'error', message: err.message });
260
+ await sendLogToSupabase({
261
+ file: folder,
262
+ uploadPath: folder,
263
+ status: 'error',
264
+ message: err.message,
265
+ });
163
266
  }
164
267
  }
165
268