thinkncollab-cli 0.0.22 → 0.0.23

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/bin/index.js +117 -48
  2. package/package.json +1 -1
package/bin/index.js CHANGED
@@ -119,48 +119,86 @@ function scanFolder(folderPath, ignoreList, rootPath = folderPath) {
119
119
  /** ------------------ VERSIONING ------------------ **/
120
120
  function loadVersions() {
121
121
  if (!fs.existsSync(VERSION_FILE)) return {};
122
- return JSON.parse(fs.readFileSync(VERSION_FILE, "utf-8"));
122
+ const data = JSON.parse(fs.readFileSync(VERSION_FILE, "utf-8"));
123
+
124
+ // Backward compatibility: if old format (just strings), convert to new format
125
+ const converted = {};
126
+ for (const [path, value] of Object.entries(data)) {
127
+ if (typeof value === 'string') {
128
+ converted[path] = { hash: value, url: '', version: 1 };
129
+ } else {
130
+ converted[path] = value;
131
+ }
132
+ }
133
+ return converted;
123
134
  }
124
135
 
125
136
  function saveVersions(versionMap) {
126
137
  fs.writeFileSync(VERSION_FILE, JSON.stringify(versionMap, null, 2));
127
138
  }
128
139
 
129
- function computeHashCLI(filePath) {
130
- if (!fs.existsSync(filePath)) return null;
131
-
132
- const stats = fs.statSync(filePath);
133
- if (stats.isDirectory()) {
134
- const items = fs.readdirSync(filePath);
135
- const combined = items.map(name => computeHashCLI(path.join(filePath, name)) || "").join("");
136
- return crypto.createHash("sha256").update(filePath + combined).digest("hex");
137
- } else {
138
- const content = fs.readFileSync(filePath);
139
- return crypto.createHash("sha256").update(content).digest("hex");
140
+ function computeFileHash(filePath) {
141
+ try {
142
+ if (!fs.existsSync(filePath)) return null;
143
+
144
+ const stats = fs.statSync(filePath);
145
+ if (stats.isDirectory()) {
146
+ // For folders: hash of sorted file names + their hashes
147
+ const items = fs.readdirSync(filePath).sort();
148
+ const childrenHashes = items.map(name =>
149
+ computeFileHash(path.join(filePath, name))
150
+ ).filter(Boolean).join('|');
151
+
152
+ return crypto.createHash("sha256")
153
+ .update(`folder:${filePath}|${childrenHashes}`)
154
+ .digest("hex");
155
+ } else {
156
+ // For files: hash of content + file metadata
157
+ const content = fs.readFileSync(filePath);
158
+ const stats = fs.statSync(filePath);
159
+
160
+ return crypto.createHash("sha256")
161
+ .update(content)
162
+ .update(`size:${stats.size}|mtime:${stats.mtimeMs}`)
163
+ .digest("hex");
164
+ }
165
+ } catch (err) {
166
+ console.error(`❌ Error computing hash for ${filePath}:`, err.message);
167
+ return null;
140
168
  }
141
169
  }
142
170
 
143
171
  function checkChanges(fileTree, versionMap, rootPath = CWD) {
144
172
  return fileTree.map(item => {
145
173
  const fullPath = path.join(rootPath, item.path || item.name);
174
+ const relativePath = item.path || item.name;
175
+
146
176
  let hash = null;
147
-
148
- if (fs.existsSync(fullPath)) {
149
- if (fs.statSync(fullPath).isDirectory()) {
150
- const children = fs.readdirSync(fullPath).map(name => computeHashCLI(path.join(fullPath, name)) || "").join("");
151
- hash = crypto.createHash("sha256").update(fullPath + children).digest("hex");
152
- } else {
153
- const content = fs.readFileSync(fullPath);
154
- hash = crypto.createHash("sha256").update(content).digest("hex");
177
+ let changed = true;
178
+
179
+ try {
180
+ if (fs.existsSync(fullPath)) {
181
+ hash = computeFileHash(fullPath);
182
+
183
+ // Check against previous version
184
+ const prevVersion = versionMap[relativePath];
185
+ if (prevVersion && prevVersion.hash === hash) {
186
+ changed = false;
187
+ }
155
188
  }
189
+ } catch (err) {
190
+ console.error(`❌ Error checking changes for ${relativePath}:`, err.message);
156
191
  }
157
192
 
158
- const prevHash = versionMap[item.path || fullPath] || null;
159
- const changed = hash !== prevHash;
160
-
161
- const newItem = { ...item, changed, hash };
193
+ const newItem = {
194
+ ...item,
195
+ changed,
196
+ hash,
197
+ path: relativePath // Ensure consistent path
198
+ };
162
199
 
163
- if (item.type === "folder" && item.children.length > 0) {
200
+ // Recursively check children for folders
201
+ if (item.type === "folder" && item.children && item.children.length > 0) {
164
202
  newItem.children = checkChanges(item.children, versionMap, rootPath);
165
203
  newItem.changed = newItem.changed || newItem.children.some(c => c.changed);
166
204
  }
@@ -202,41 +240,61 @@ async function uploadTree(fileTree, folderHex, roomId, token, email, previousVer
202
240
  const uploaded = [];
203
241
 
204
242
  for (const node of fileTree) {
205
- const relativePath = path.join(parentPath, node.name).replace(/\\/g, "/");
243
+ const relativePath = node.path || path.join(parentPath, node.name).replace(/\\/g, "/");
206
244
 
207
245
  if (node.type === "folder") {
208
246
  const children = await uploadTree(node.children, folderHex, roomId, token, email, previousVersions, relativePath);
209
- const folderHash = crypto.createHash("sha256").update(relativePath + children.map(c => c.hash || "").join("")).digest("hex");
210
- uploaded.push({ ...node, children, hash: folderHash });
211
- } else if (node.changed && node.path) {
212
- try {
213
- const url = await uploadFileSigned(node.path, `tnc_uploads/${folderHex}`, roomId, token, email);
214
- console.log(`📦 Uploaded: ${relativePath} → ${url}`);
215
- uploaded.push({
216
- ...node,
217
- url,
218
- hash: node.hash
219
- });
220
- } catch (err) {
221
- console.error(`❌ Failed to upload ${relativePath}:`, err.message);
222
- uploaded.push({ ...node, hash: node.hash });
223
- }
224
- } else {
225
- // Unchanged file - use previous URL
226
- const prevFile = previousVersions[node.path];
247
+
227
248
  uploaded.push({
228
249
  ...node,
229
- url: prevFile?.url, // ✅ Use stored URL
250
+ children,
230
251
  hash: node.hash,
231
- version: prevFile?.version || 1
252
+ path: relativePath
232
253
  });
254
+ } else {
255
+ const prevFile = previousVersions[relativePath];
256
+
257
+ if (node.changed) {
258
+ try {
259
+ console.log(`📤 Uploading changed file: ${relativePath}`);
260
+ const url = await uploadFileSigned(node.path, `tnc_uploads/${folderHex}`, roomId, token, email);
261
+ console.log(`✅ Uploaded: ${relativePath}`);
262
+
263
+ uploaded.push({
264
+ ...node,
265
+ url,
266
+ hash: node.hash,
267
+ path: relativePath,
268
+ version: prevFile ? prevFile.version + 1 : 1
269
+ });
270
+ } catch (err) {
271
+ console.error(`❌ Failed to upload ${relativePath}:`, err.message);
272
+ // Fallback to previous version if upload fails
273
+ uploaded.push({
274
+ ...node,
275
+ url: prevFile?.url,
276
+ hash: node.hash,
277
+ path: relativePath,
278
+ version: prevFile?.version || 1
279
+ });
280
+ }
281
+ } else {
282
+ // Unchanged file - use previous URL and version
283
+ console.log(`📋 Using previous version for: ${relativePath}`);
284
+ uploaded.push({
285
+ ...node,
286
+ url: prevFile?.url,
287
+ hash: node.hash,
288
+ path: relativePath,
289
+ version: prevFile?.version || 1
290
+ });
291
+ }
233
292
  }
234
293
  }
235
294
 
236
295
  return uploaded;
237
296
  }
238
297
 
239
-
240
298
  /** ------------------ PUSH FUNCTION ------------------ **/
241
299
  async function push(roomId, targetPath) {
242
300
  const { token, email } = readToken();
@@ -268,7 +326,16 @@ async function push(roomId, targetPath) {
268
326
  }
269
327
 
270
328
  const previousVersions = loadVersions();
329
+ console.log('📁 Previous versions:', Object.keys(previousVersions).length);
330
+
271
331
  const contentWithChanges = checkChanges(content, previousVersions);
332
+
333
+ // Debug: Show what changed
334
+ const changedFiles = contentWithChanges.flatMap(item =>
335
+ item.changed ? [item.path] : []
336
+ );
337
+ console.log('🔄 Changed files:', changedFiles.length, changedFiles);
338
+
272
339
  const hasChanges = contentWithChanges.some(item => item.changed || (item.children && item.children.some(c => c.changed)));
273
340
 
274
341
  if (!hasChanges) {
@@ -308,6 +375,8 @@ async function push(roomId, targetPath) {
308
375
  flattenAndStore(uploadedTree);
309
376
  saveVersions(newVersionMap);
310
377
 
378
+ console.log(`💾 Saved ${Object.keys(newVersionMap).length} files to .tncversions`);
379
+
311
380
  } catch (err) {
312
381
  console.error("❌ Upload failed:", err.response?.data || err.message);
313
382
  }
@@ -352,4 +421,4 @@ async function main() {
352
421
  }
353
422
  }
354
423
 
355
- main().catch(err => console.error(err));
424
+ main().catch(err => console.error(err));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "thinkncollab-cli",
3
3
  "author": "Raman Singh",
4
- "version": "0.0.22",
4
+ "version": "0.0.23",
5
5
  "description": "CLI tool for ThinkNCollab",
6
6
  "main": "index.js",
7
7
  "bin": {