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.
- package/bin/index.js +117 -48
- 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
|
}
|
|
@@ -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
|
-
|
|
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
|
-
|
|
250
|
+
children,
|
|
230
251
|
hash: node.hash,
|
|
231
|
-
|
|
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));
|