thinkncollab-cli 0.0.21 → 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.
- package/bin/index.js +137 -50
- 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
|
-
|
|
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
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
hash =
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
193
|
+
const newItem = {
|
|
194
|
+
...item,
|
|
195
|
+
changed,
|
|
196
|
+
hash,
|
|
197
|
+
path: relativePath // Ensure consistent path
|
|
198
|
+
};
|
|
162
199
|
|
|
163
|
-
|
|
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
|
}
|
|
@@ -198,22 +236,59 @@ async function uploadFileSigned(filePath, folder, roomId, token, email) {
|
|
|
198
236
|
return cloudRes.data.secure_url;
|
|
199
237
|
}
|
|
200
238
|
|
|
201
|
-
async function uploadTree(fileTree, folderHex, roomId, token, email, parentPath = "") {
|
|
239
|
+
async function uploadTree(fileTree, folderHex, roomId, token, email, previousVersions, parentPath = "") {
|
|
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
|
-
const children = await uploadTree(node.children, folderHex, roomId, token, email, relativePath);
|
|
209
|
-
|
|
210
|
-
uploaded.push({
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
246
|
+
const children = await uploadTree(node.children, folderHex, roomId, token, email, previousVersions, relativePath);
|
|
247
|
+
|
|
248
|
+
uploaded.push({
|
|
249
|
+
...node,
|
|
250
|
+
children,
|
|
251
|
+
hash: node.hash,
|
|
252
|
+
path: relativePath
|
|
253
|
+
});
|
|
215
254
|
} else {
|
|
216
|
-
|
|
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
|
+
}
|
|
217
292
|
}
|
|
218
293
|
}
|
|
219
294
|
|
|
@@ -230,10 +305,6 @@ async function push(roomId, targetPath) {
|
|
|
230
305
|
}
|
|
231
306
|
const meta = JSON.parse(fs.readFileSync(tncMetaPath, "utf-8"));
|
|
232
307
|
const projectId = meta.projectId;
|
|
233
|
-
if (!projectId) {
|
|
234
|
-
console.error("❌ Project ID missing in metadata. Run 'tnc init' again.");
|
|
235
|
-
process.exit(1);
|
|
236
|
-
}
|
|
237
308
|
|
|
238
309
|
const stats = fs.statSync(targetPath);
|
|
239
310
|
const rootFolder = stats.isDirectory() ? targetPath : path.dirname(targetPath);
|
|
@@ -255,7 +326,16 @@ async function push(roomId, targetPath) {
|
|
|
255
326
|
}
|
|
256
327
|
|
|
257
328
|
const previousVersions = loadVersions();
|
|
329
|
+
console.log('📁 Previous versions:', Object.keys(previousVersions).length);
|
|
330
|
+
|
|
258
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
|
+
|
|
259
339
|
const hasChanges = contentWithChanges.some(item => item.changed || (item.children && item.children.some(c => c.changed)));
|
|
260
340
|
|
|
261
341
|
if (!hasChanges) {
|
|
@@ -267,10 +347,9 @@ async function push(roomId, targetPath) {
|
|
|
267
347
|
const folderHex = crypto.createHash("md5").update(path.basename(targetPath) + Date.now()).digest("hex");
|
|
268
348
|
|
|
269
349
|
console.log("🚀 Uploading to Cloudinary...");
|
|
270
|
-
const uploadedTree = await uploadTree(contentWithChanges, folderHex, roomId, token, email);
|
|
271
|
-
|
|
272
|
-
console.log("🗂️ Sending metadata:", { folderId: folderHex, content: uploadedTree, uploadedBy: email, projectId });
|
|
350
|
+
const uploadedTree = await uploadTree(contentWithChanges, folderHex, roomId, token, email, previousVersions);
|
|
273
351
|
|
|
352
|
+
console.log("🗂️ Sending metadata...");
|
|
274
353
|
await axios.post(
|
|
275
354
|
`${BASE_URL}/${roomId}/upload`,
|
|
276
355
|
{ folderId: folderHex, content: uploadedTree, uploadedBy: email, projectId },
|
|
@@ -279,16 +358,24 @@ async function push(roomId, targetPath) {
|
|
|
279
358
|
|
|
280
359
|
console.log("✅ Upload complete! Metadata stored successfully.");
|
|
281
360
|
|
|
282
|
-
// Save version hashes
|
|
283
|
-
const
|
|
284
|
-
const
|
|
361
|
+
// Save version hashes WITH URLs
|
|
362
|
+
const newVersionMap = {};
|
|
363
|
+
const flattenAndStore = (items) => {
|
|
285
364
|
for (const item of items) {
|
|
286
|
-
|
|
287
|
-
|
|
365
|
+
if (item.type === "file") {
|
|
366
|
+
newVersionMap[item.path] = {
|
|
367
|
+
hash: item.hash,
|
|
368
|
+
url: item.url, // ✅ Store URL locally
|
|
369
|
+
version: item.version || 1
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
if (item.children) flattenAndStore(item.children);
|
|
288
373
|
}
|
|
289
374
|
};
|
|
290
|
-
|
|
291
|
-
saveVersions(
|
|
375
|
+
flattenAndStore(uploadedTree);
|
|
376
|
+
saveVersions(newVersionMap);
|
|
377
|
+
|
|
378
|
+
console.log(`💾 Saved ${Object.keys(newVersionMap).length} files to .tncversions`);
|
|
292
379
|
|
|
293
380
|
} catch (err) {
|
|
294
381
|
console.error("❌ Upload failed:", err.response?.data || err.message);
|
|
@@ -334,4 +421,4 @@ async function main() {
|
|
|
334
421
|
}
|
|
335
422
|
}
|
|
336
423
|
|
|
337
|
-
main().catch(err => console.error(err));
|
|
424
|
+
main().catch(err => console.error(err));
|