thinkncollab-cli 0.0.20 → 0.0.22

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 +61 -45
  2. package/package.json +1 -1
package/bin/index.js CHANGED
@@ -13,9 +13,6 @@ const VERSION_FILE = path.join(process.cwd(), ".tncversions");
13
13
  const BASE_URL = "http://localhost:3001/rooms";
14
14
  const CWD = process.cwd();
15
15
 
16
-
17
-
18
-
19
16
  /** ------------------ LOGIN ------------------ **/
20
17
  async function login() {
21
18
  const answers = await inquirer.prompt([
@@ -131,10 +128,11 @@ function saveVersions(versionMap) {
131
128
 
132
129
  function computeHashCLI(filePath) {
133
130
  if (!fs.existsSync(filePath)) return null;
131
+
134
132
  const stats = fs.statSync(filePath);
135
133
  if (stats.isDirectory()) {
136
134
  const items = fs.readdirSync(filePath);
137
- const combined = items.map(name => computeHashCLI(path.join(filePath, name))).join("");
135
+ const combined = items.map(name => computeHashCLI(path.join(filePath, name)) || "").join("");
138
136
  return crypto.createHash("sha256").update(filePath + combined).digest("hex");
139
137
  } else {
140
138
  const content = fs.readFileSync(filePath);
@@ -145,11 +143,22 @@ function computeHashCLI(filePath) {
145
143
  function checkChanges(fileTree, versionMap, rootPath = CWD) {
146
144
  return fileTree.map(item => {
147
145
  const fullPath = path.join(rootPath, item.path || item.name);
148
- const hash = computeHashCLI(fullPath);
146
+ 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");
155
+ }
156
+ }
157
+
149
158
  const prevHash = versionMap[item.path || fullPath] || null;
150
159
  const changed = hash !== prevHash;
151
160
 
152
- let newItem = { ...item, changed, hash };
161
+ const newItem = { ...item, changed, hash };
153
162
 
154
163
  if (item.type === "folder" && item.children.length > 0) {
155
164
  newItem.children = checkChanges(item.children, versionMap, rootPath);
@@ -189,38 +198,45 @@ async function uploadFileSigned(filePath, folder, roomId, token, email) {
189
198
  return cloudRes.data.secure_url;
190
199
  }
191
200
 
192
- async function uploadTree(fileTree, folderHex, roomId, token, email, parentPath = "") {
201
+ async function uploadTree(fileTree, folderHex, roomId, token, email, previousVersions, parentPath = "") {
193
202
  const uploaded = [];
194
203
 
195
204
  for (const node of fileTree) {
196
205
  const relativePath = path.join(parentPath, node.name).replace(/\\/g, "/");
197
206
 
198
207
  if (node.type === "folder") {
199
- const children = await uploadTree(node.children, folderHex, roomId, token, email, relativePath);
200
- uploaded.push({
201
- name: node.name,
202
- type: "folder",
203
- path: relativePath,
204
- children
205
- });
206
- } else if (node.changed) {
207
- const url = await uploadFileSigned(node.path, `tnc_uploads/${folderHex}`, roomId, token, email);
208
- console.log(`📦 Uploaded: ${relativePath} → ${url}`);
209
- uploaded.push({
210
- name: node.name,
211
- type: "file",
212
- path: relativePath,
213
- size: node.size,
214
- url
215
- });
208
+ 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
+ }
216
224
  } else {
217
- uploaded.push(node); // unchanged, no upload
225
+ // Unchanged file - use previous URL
226
+ const prevFile = previousVersions[node.path];
227
+ uploaded.push({
228
+ ...node,
229
+ url: prevFile?.url, // ✅ Use stored URL
230
+ hash: node.hash,
231
+ version: prevFile?.version || 1
232
+ });
218
233
  }
219
234
  }
220
235
 
221
236
  return uploaded;
222
237
  }
223
238
 
239
+
224
240
  /** ------------------ PUSH FUNCTION ------------------ **/
225
241
  async function push(roomId, targetPath) {
226
242
  const { token, email } = readToken();
@@ -231,6 +247,7 @@ async function push(roomId, targetPath) {
231
247
  }
232
248
  const meta = JSON.parse(fs.readFileSync(tncMetaPath, "utf-8"));
233
249
  const projectId = meta.projectId;
250
+
234
251
  const stats = fs.statSync(targetPath);
235
252
  const rootFolder = stats.isDirectory() ? targetPath : path.dirname(targetPath);
236
253
  const ignoreList = loadIgnore(rootFolder);
@@ -263,34 +280,33 @@ async function push(roomId, targetPath) {
263
280
  const folderHex = crypto.createHash("md5").update(path.basename(targetPath) + Date.now()).digest("hex");
264
281
 
265
282
  console.log("🚀 Uploading to Cloudinary...");
266
- const uploadedTree = await uploadTree(contentWithChanges, folderHex, roomId, token, email);
267
-
268
- console.log("🗂️ Sending metadata:", {
269
- folderId: folderHex,
270
- content: uploadedTree,
271
- uploadedBy: email,
272
- projectId
273
- });
283
+ const uploadedTree = await uploadTree(contentWithChanges, folderHex, roomId, token, email, previousVersions);
274
284
 
285
+ console.log("🗂️ Sending metadata...");
275
286
  await axios.post(
276
- `${BASE_URL}/${roomId}/upload`,
277
- { folderId: folderHex, content: uploadedTree, uploadedBy: email, projectId },
278
- { headers: { authorization: `Bearer ${token}`, email } }
279
- );
280
-
287
+ `${BASE_URL}/${roomId}/upload`,
288
+ { folderId: folderHex, content: uploadedTree, uploadedBy: email, projectId },
289
+ { headers: { authorization: `Bearer ${token}`, email } }
290
+ );
281
291
 
282
292
  console.log("✅ Upload complete! Metadata stored successfully.");
283
293
 
284
- // Save version hashes
285
- const flattenVersionMap = {};
286
- const flatten = items => {
294
+ // Save version hashes WITH URLs
295
+ const newVersionMap = {};
296
+ const flattenAndStore = (items) => {
287
297
  for (const item of items) {
288
- flattenVersionMap[item.path || item.name] = item.hash;
289
- if (item.children) flatten(item.children);
298
+ if (item.type === "file") {
299
+ newVersionMap[item.path] = {
300
+ hash: item.hash,
301
+ url: item.url, // ✅ Store URL locally
302
+ version: item.version || 1
303
+ };
304
+ }
305
+ if (item.children) flattenAndStore(item.children);
290
306
  }
291
307
  };
292
- flatten(contentWithChanges);
293
- saveVersions(flattenVersionMap);
308
+ flattenAndStore(uploadedTree);
309
+ saveVersions(newVersionMap);
294
310
 
295
311
  } catch (err) {
296
312
  console.error("❌ Upload failed:", err.response?.data || err.message);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "thinkncollab-cli",
3
3
  "author": "Raman Singh",
4
- "version": "0.0.20",
4
+ "version": "0.0.22",
5
5
  "description": "CLI tool for ThinkNCollab",
6
6
  "main": "index.js",
7
7
  "bin": {