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.
- package/bin/index.js +61 -45
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
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
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
|
286
|
-
const
|
|
294
|
+
// Save version hashes WITH URLs
|
|
295
|
+
const newVersionMap = {};
|
|
296
|
+
const flattenAndStore = (items) => {
|
|
287
297
|
for (const item of items) {
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
|
|
293
|
-
saveVersions(
|
|
308
|
+
flattenAndStore(uploadedTree);
|
|
309
|
+
saveVersions(newVersionMap);
|
|
294
310
|
|
|
295
311
|
} catch (err) {
|
|
296
312
|
console.error("❌ Upload failed:", err.response?.data || err.message);
|