thinkncollab-cli 0.0.28 → 0.0.30
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/commands/pull.js +81 -96
- package/package.json +3 -2
package/commands/pull.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
1
2
|
import fs from "fs";
|
|
2
3
|
import path from "path";
|
|
3
4
|
import axios from "axios";
|
|
4
5
|
import https from "https";
|
|
6
|
+
import http from "http";
|
|
5
7
|
import os from "os";
|
|
8
|
+
import chalk from "chalk";
|
|
6
9
|
|
|
7
10
|
const RC_FILE = path.join(os.homedir(), ".tncrc");
|
|
8
|
-
const CWD = process.cwd();
|
|
9
11
|
|
|
10
12
|
/** ------------------ TOKEN UTILS ------------------ **/
|
|
11
13
|
function readToken() {
|
|
12
14
|
if (!fs.existsSync(RC_FILE)) {
|
|
13
|
-
console.error("❌ Not logged in. Run 'tnc login' first.");
|
|
15
|
+
console.error(chalk.red("❌ Not logged in. Run 'tnc login' first."));
|
|
14
16
|
process.exit(1);
|
|
15
17
|
}
|
|
16
18
|
const data = JSON.parse(fs.readFileSync(RC_FILE));
|
|
@@ -21,31 +23,27 @@ function readToken() {
|
|
|
21
23
|
async function downloadFile(url, filePath) {
|
|
22
24
|
return new Promise((resolve, reject) => {
|
|
23
25
|
const fileDir = path.dirname(filePath);
|
|
24
|
-
|
|
25
|
-
// Create directory if it doesn't exist
|
|
26
|
-
if (!fs.existsSync(fileDir)) {
|
|
27
|
-
fs.mkdirSync(fileDir, { recursive: true });
|
|
28
|
-
}
|
|
26
|
+
if (!fs.existsSync(fileDir)) fs.mkdirSync(fileDir, { recursive: true });
|
|
29
27
|
|
|
30
28
|
const file = fs.createWriteStream(filePath);
|
|
31
|
-
|
|
32
|
-
https.get(url, (response) => {
|
|
33
|
-
if (response.statusCode !== 200) {
|
|
34
|
-
reject(new Error(`Failed to download: ${response.statusCode}`));
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
response.pipe(file);
|
|
29
|
+
const client = url.startsWith("https") ? https : http;
|
|
39
30
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
31
|
+
client
|
|
32
|
+
.get(url, (response) => {
|
|
33
|
+
if (response.statusCode !== 200) {
|
|
34
|
+
reject(new Error(`Failed to download: ${response.statusCode}`));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
response.pipe(file);
|
|
38
|
+
file.on("finish", () => {
|
|
39
|
+
file.close();
|
|
40
|
+
resolve(filePath);
|
|
41
|
+
});
|
|
42
|
+
})
|
|
43
|
+
.on("error", (err) => {
|
|
44
|
+
fs.unlink(filePath, () => {});
|
|
45
|
+
reject(err);
|
|
43
46
|
});
|
|
44
|
-
|
|
45
|
-
}).on('error', (err) => {
|
|
46
|
-
fs.unlink(filePath, () => {}); // Delete the file on error
|
|
47
|
-
reject(err);
|
|
48
|
-
});
|
|
49
47
|
});
|
|
50
48
|
}
|
|
51
49
|
|
|
@@ -56,44 +54,44 @@ async function processFolderContent(content, basePath = "") {
|
|
|
56
54
|
let errorCount = 0;
|
|
57
55
|
|
|
58
56
|
for (const item of content) {
|
|
59
|
-
const
|
|
57
|
+
const relPath = item.path ? item.path.replace(/^[\\/]+/, "") : item.name;
|
|
58
|
+
const itemPath = path.join(basePath, relPath);
|
|
60
59
|
|
|
61
60
|
if (item.type === "folder") {
|
|
62
|
-
// Create directory
|
|
63
61
|
if (!fs.existsSync(itemPath)) {
|
|
64
62
|
fs.mkdirSync(itemPath, { recursive: true });
|
|
65
|
-
console.log(`📁 Created directory: ${itemPath}`);
|
|
63
|
+
console.log(chalk.cyan(`📁 Created directory: ${itemPath}`));
|
|
66
64
|
}
|
|
67
65
|
|
|
68
|
-
// Process children recursively
|
|
69
66
|
if (item.children && item.children.length > 0) {
|
|
70
|
-
const result = await processFolderContent(item.children,
|
|
67
|
+
const result = await processFolderContent(item.children, itemPath);
|
|
71
68
|
downloadedCount += result.downloadedCount;
|
|
72
69
|
skippedCount += result.skippedCount;
|
|
73
70
|
errorCount += result.errorCount;
|
|
74
71
|
}
|
|
75
72
|
} else if (item.type === "file" && item.url) {
|
|
76
73
|
try {
|
|
77
|
-
|
|
74
|
+
fs.mkdirSync(path.dirname(itemPath), { recursive: true });
|
|
75
|
+
|
|
78
76
|
if (fs.existsSync(itemPath)) {
|
|
79
77
|
const existingStats = fs.statSync(itemPath);
|
|
80
78
|
if (existingStats.size === item.size) {
|
|
81
|
-
console.log(`✅ Already exists: ${itemPath}`);
|
|
79
|
+
console.log(chalk.gray(`✅ Already exists: ${itemPath}`));
|
|
82
80
|
skippedCount++;
|
|
83
81
|
continue;
|
|
84
82
|
}
|
|
85
83
|
}
|
|
86
84
|
|
|
87
|
-
console.log(`📥 Downloading: ${itemPath}`);
|
|
85
|
+
console.log(chalk.yellow(`📥 Downloading: ${itemPath}`));
|
|
88
86
|
await downloadFile(item.url, itemPath);
|
|
89
|
-
console.log(`✅ Downloaded: ${itemPath}`);
|
|
87
|
+
console.log(chalk.green(`✅ Downloaded: ${itemPath}`));
|
|
90
88
|
downloadedCount++;
|
|
91
89
|
} catch (err) {
|
|
92
|
-
console.error(`❌ Failed to download ${itemPath}
|
|
90
|
+
console.error(chalk.red(`❌ Failed to download ${itemPath}: ${err.message}`));
|
|
93
91
|
errorCount++;
|
|
94
92
|
}
|
|
95
93
|
} else if (item.type === "file" && !item.url) {
|
|
96
|
-
console.log(`⚠️ No URL available for: ${itemPath}`);
|
|
94
|
+
console.log(chalk.magenta(`⚠️ No URL available for: ${itemPath}`));
|
|
97
95
|
skippedCount++;
|
|
98
96
|
}
|
|
99
97
|
}
|
|
@@ -105,130 +103,117 @@ async function processFolderContent(content, basePath = "") {
|
|
|
105
103
|
export default async function pull(roomId, version = "latest") {
|
|
106
104
|
try {
|
|
107
105
|
const { token, email } = readToken();
|
|
108
|
-
|
|
109
106
|
const tncMetaPath = path.join(process.cwd(), ".tnc", ".tncmeta.json");
|
|
107
|
+
|
|
110
108
|
if (!fs.existsSync(tncMetaPath)) {
|
|
111
|
-
console.error("❌ Project not initialized. Run 'tnc init' first.");
|
|
109
|
+
console.error(chalk.red("❌ Project not initialized. Run 'tnc init' first."));
|
|
112
110
|
return;
|
|
113
111
|
}
|
|
114
112
|
|
|
115
113
|
const meta = JSON.parse(fs.readFileSync(tncMetaPath, "utf-8"));
|
|
116
114
|
const projectId = meta.projectId;
|
|
117
115
|
|
|
118
|
-
console.log(
|
|
116
|
+
console.log(chalk.blue(`\n🚀 Pulling files from room ${roomId}...`));
|
|
119
117
|
console.log(`📋 Project: ${projectId}`);
|
|
120
|
-
console.log(`🎯 Version: ${version}`);
|
|
121
|
-
console.log("");
|
|
118
|
+
console.log(`🎯 Version: ${version}\n`);
|
|
122
119
|
|
|
123
|
-
|
|
124
|
-
let url = `http://localhost:3001/folder/${roomId}/download`;
|
|
120
|
+
let url = `http://localhost:3001/folders/${roomId}/download`;
|
|
125
121
|
const params = { projectId };
|
|
126
|
-
|
|
122
|
+
|
|
127
123
|
if (version !== "latest") {
|
|
128
124
|
const versionNum = parseInt(version);
|
|
129
125
|
if (isNaN(versionNum)) {
|
|
130
|
-
console.error("❌ Version must be a number");
|
|
126
|
+
console.error(chalk.red("❌ Version must be a number"));
|
|
131
127
|
return;
|
|
132
128
|
}
|
|
133
129
|
params.version = versionNum;
|
|
134
130
|
}
|
|
135
131
|
|
|
136
|
-
console.log("🔍 Making API request with:", { roomId, projectId, version });
|
|
137
|
-
console.log("🌐 API URL:", url);
|
|
138
|
-
|
|
132
|
+
console.log(chalk.gray("🔍 Making API request with:"), { roomId, projectId, version });
|
|
133
|
+
console.log(chalk.gray("🌐 API URL:"), url);
|
|
134
|
+
|
|
139
135
|
const response = await axios.get(url, {
|
|
140
136
|
params,
|
|
141
|
-
headers: {
|
|
142
|
-
authorization: `Bearer ${token}`,
|
|
143
|
-
email: email
|
|
144
|
-
}
|
|
137
|
+
headers: { authorization: `Bearer ${token}`, email },
|
|
145
138
|
});
|
|
146
139
|
|
|
147
|
-
console.log("✅ API Response received");
|
|
140
|
+
console.log(chalk.green("✅ API Response received"));
|
|
148
141
|
const folderData = response.data;
|
|
149
|
-
|
|
142
|
+
|
|
150
143
|
if (!folderData) {
|
|
151
|
-
console.log("❌ No data found in response");
|
|
144
|
+
console.log(chalk.red("❌ No data found in response"));
|
|
152
145
|
return;
|
|
153
146
|
}
|
|
154
147
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
let folderVersion = folderData.version || 1;
|
|
148
|
+
const rootContent = folderData.rootContent || folderData.content || folderData;
|
|
149
|
+
const folderVersion = folderData.version || 1;
|
|
158
150
|
|
|
159
151
|
if (!rootContent || !Array.isArray(rootContent)) {
|
|
160
|
-
console.log("❌ No valid content found in response");
|
|
152
|
+
console.log(chalk.red("❌ No valid content found in response"));
|
|
161
153
|
console.log("📋 Response structure:", Object.keys(folderData));
|
|
162
154
|
return;
|
|
163
155
|
}
|
|
164
156
|
|
|
165
|
-
console.log(`📦 Found version ${folderVersion} with ${rootContent.length} items`);
|
|
166
|
-
console.log("");
|
|
157
|
+
console.log(chalk.cyan(`📦 Found version ${folderVersion} with ${rootContent.length} items\n`));
|
|
167
158
|
|
|
168
|
-
//
|
|
159
|
+
// Backup versions file
|
|
169
160
|
const versionsFile = path.join(process.cwd(), ".tncversions");
|
|
170
|
-
|
|
171
|
-
|
|
161
|
+
const backupPath = path.join(process.cwd(), `.tncversions.backup`);
|
|
162
|
+
if (fs.existsSync(versionsFile) && !fs.existsSync(backupPath)) {
|
|
172
163
|
fs.copyFileSync(versionsFile, backupPath);
|
|
173
|
-
console.log(`💾
|
|
164
|
+
console.log(chalk.gray(`💾 Backup created: ${path.basename(backupPath)}`));
|
|
174
165
|
}
|
|
175
166
|
|
|
176
|
-
//
|
|
167
|
+
// Download files
|
|
177
168
|
const result = await processFolderContent(rootContent, process.cwd());
|
|
178
169
|
|
|
179
|
-
console.log("");
|
|
180
|
-
console.log("📊 Download Summary
|
|
181
|
-
console.log("
|
|
170
|
+
console.log("\n" + "=".repeat(35));
|
|
171
|
+
console.log(chalk.bold("\n📊 Download Summary"));
|
|
172
|
+
console.log("=".repeat(35));
|
|
182
173
|
console.log(`✅ Downloaded: ${result.downloadedCount} files`);
|
|
183
|
-
console.log(`📋 Skipped: ${result.skippedCount} files
|
|
174
|
+
console.log(`📋 Skipped: ${result.skippedCount} files`);
|
|
184
175
|
console.log(`❌ Errors: ${result.errorCount} files`);
|
|
185
|
-
|
|
176
|
+
|
|
186
177
|
if (result.errorCount > 0) {
|
|
187
|
-
console.log("");
|
|
188
|
-
console.log("⚠️ Some files failed to download. Check the errors above.");
|
|
178
|
+
console.log(chalk.yellow("\n⚠️ Some files failed to download. Check above logs."));
|
|
189
179
|
}
|
|
190
180
|
|
|
191
|
-
// Update
|
|
181
|
+
// Update .tncversions
|
|
192
182
|
const newVersionMap = {};
|
|
193
183
|
const flattenContent = (items) => {
|
|
194
184
|
for (const item of items) {
|
|
195
185
|
if (item.type === "file") {
|
|
196
186
|
newVersionMap[item.path] = {
|
|
197
|
-
hash: item.contentHash || item.hash,
|
|
198
|
-
url: item.url,
|
|
187
|
+
hash: item.contentHash || item.hash || "",
|
|
188
|
+
url: item.url || null,
|
|
199
189
|
version: item.version || 1,
|
|
200
|
-
size: item.size || 0
|
|
190
|
+
size: item.size || 0,
|
|
201
191
|
};
|
|
202
192
|
}
|
|
203
|
-
if (item.children)
|
|
204
|
-
flattenContent(item.children);
|
|
205
|
-
}
|
|
193
|
+
if (item.children) flattenContent(item.children);
|
|
206
194
|
}
|
|
207
195
|
};
|
|
208
|
-
|
|
196
|
+
|
|
209
197
|
flattenContent(rootContent);
|
|
210
198
|
fs.writeFileSync(versionsFile, JSON.stringify(newVersionMap, null, 2));
|
|
211
|
-
|
|
212
|
-
console.log("");
|
|
213
|
-
console.log(`💾 Updated local versions file with ${Object.keys(newVersionMap).length} files`);
|
|
214
|
-
console.log(`🎉 Pull completed successfully!`);
|
|
215
199
|
|
|
200
|
+
console.log("");
|
|
201
|
+
console.log(chalk.green(`💾 Updated local versions file with ${Object.keys(newVersionMap).length} files`));
|
|
202
|
+
console.log(chalk.bold.green(`🎉 Pull completed successfully!\n`));
|
|
216
203
|
} catch (err) {
|
|
217
|
-
console.error(
|
|
218
|
-
|
|
204
|
+
console.error(chalk.red(`❌ Pull failed: ${err.message}`));
|
|
205
|
+
|
|
219
206
|
if (err.response) {
|
|
220
|
-
console.error(
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
227
|
-
}
|
|
207
|
+
console.error(chalk.red(`📡 Server status: ${err.response.status}`));
|
|
208
|
+
const msg =
|
|
209
|
+
typeof err.response.data === "object"
|
|
210
|
+
? err.response.data.message || JSON.stringify(err.response.data)
|
|
211
|
+
: err.response.data;
|
|
212
|
+
console.error(chalk.red("📡 Server message:"), msg);
|
|
228
213
|
} else if (err.request) {
|
|
229
|
-
console.error("🌐 No response
|
|
214
|
+
console.error(chalk.yellow("🌐 No response from server — check backend (localhost:3001)."));
|
|
230
215
|
} else {
|
|
231
|
-
console.error("🔧 Configuration error:", err.message);
|
|
216
|
+
console.error(chalk.red("🔧 Configuration error:"), err.message);
|
|
232
217
|
}
|
|
233
218
|
}
|
|
234
|
-
}
|
|
219
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thinkncollab-cli",
|
|
3
3
|
"author": "Raman Singh",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.30",
|
|
5
5
|
"description": "CLI tool for ThinkNCollab",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"bin": {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"type": "module",
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"axios": "^1.12.2",
|
|
16
|
+
"chalk": "^5.6.2",
|
|
16
17
|
"inquirer": "^9.3.8"
|
|
17
18
|
}
|
|
18
|
-
}
|
|
19
|
+
}
|