thinkncollab-cli 0.0.27 → 0.0.29
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 +90 -88
- 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";
|
|
5
|
-
import
|
|
6
|
+
import http from "http";
|
|
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,113 +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
|
-
// Get folder data from server
|
|
124
120
|
let url = `http://localhost:3001/folder/${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
|
-
|
|
132
|
+
console.log(chalk.gray("🔍 Making API request with:"), { roomId, projectId, version });
|
|
133
|
+
console.log(chalk.gray("🌐 API URL:"), url);
|
|
134
|
+
|
|
138
135
|
const response = await axios.get(url, {
|
|
139
136
|
params,
|
|
140
|
-
headers: {
|
|
141
|
-
authorization: `Bearer ${token}`,
|
|
142
|
-
email: email
|
|
143
|
-
}
|
|
137
|
+
headers: { authorization: `Bearer ${token}`, email },
|
|
144
138
|
});
|
|
145
139
|
|
|
146
|
-
console.log("✅ API Response received");
|
|
140
|
+
console.log(chalk.green("✅ API Response received"));
|
|
147
141
|
const folderData = response.data;
|
|
148
|
-
|
|
149
|
-
if (!folderData
|
|
150
|
-
console.log("❌ No
|
|
142
|
+
|
|
143
|
+
if (!folderData) {
|
|
144
|
+
console.log(chalk.red("❌ No data found in response"));
|
|
151
145
|
return;
|
|
152
146
|
}
|
|
153
147
|
|
|
154
|
-
|
|
155
|
-
|
|
148
|
+
const rootContent = folderData.rootContent || folderData.content || folderData;
|
|
149
|
+
const folderVersion = folderData.version || 1;
|
|
150
|
+
|
|
151
|
+
if (!rootContent || !Array.isArray(rootContent)) {
|
|
152
|
+
console.log(chalk.red("❌ No valid content found in response"));
|
|
153
|
+
console.log("📋 Response structure:", Object.keys(folderData));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
156
|
|
|
157
|
-
|
|
157
|
+
console.log(chalk.cyan(`📦 Found version ${folderVersion} with ${rootContent.length} items\n`));
|
|
158
|
+
|
|
159
|
+
// Backup versions file
|
|
158
160
|
const versionsFile = path.join(process.cwd(), ".tncversions");
|
|
159
|
-
|
|
160
|
-
|
|
161
|
+
const backupPath = path.join(process.cwd(), `.tncversions.backup`);
|
|
162
|
+
if (fs.existsSync(versionsFile) && !fs.existsSync(backupPath)) {
|
|
161
163
|
fs.copyFileSync(versionsFile, backupPath);
|
|
162
|
-
console.log(`💾
|
|
164
|
+
console.log(chalk.gray(`💾 Backup created: ${path.basename(backupPath)}`));
|
|
163
165
|
}
|
|
164
166
|
|
|
165
|
-
//
|
|
166
|
-
const result = await processFolderContent(
|
|
167
|
+
// Download files
|
|
168
|
+
const result = await processFolderContent(rootContent, process.cwd());
|
|
167
169
|
|
|
168
|
-
console.log("");
|
|
169
|
-
console.log("📊 Download Summary
|
|
170
|
-
console.log("
|
|
170
|
+
console.log("\n" + "=".repeat(35));
|
|
171
|
+
console.log(chalk.bold("\n📊 Download Summary"));
|
|
172
|
+
console.log("=".repeat(35));
|
|
171
173
|
console.log(`✅ Downloaded: ${result.downloadedCount} files`);
|
|
172
|
-
console.log(`📋 Skipped: ${result.skippedCount} files
|
|
174
|
+
console.log(`📋 Skipped: ${result.skippedCount} files`);
|
|
173
175
|
console.log(`❌ Errors: ${result.errorCount} files`);
|
|
174
|
-
|
|
176
|
+
|
|
175
177
|
if (result.errorCount > 0) {
|
|
176
|
-
console.log("");
|
|
177
|
-
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."));
|
|
178
179
|
}
|
|
179
180
|
|
|
180
|
-
// Update
|
|
181
|
+
// Update .tncversions
|
|
181
182
|
const newVersionMap = {};
|
|
182
183
|
const flattenContent = (items) => {
|
|
183
184
|
for (const item of items) {
|
|
184
185
|
if (item.type === "file") {
|
|
185
186
|
newVersionMap[item.path] = {
|
|
186
|
-
hash: item.contentHash || item.hash,
|
|
187
|
-
url: item.url,
|
|
187
|
+
hash: item.contentHash || item.hash || "",
|
|
188
|
+
url: item.url || null,
|
|
188
189
|
version: item.version || 1,
|
|
189
|
-
size: item.size || 0
|
|
190
|
+
size: item.size || 0,
|
|
190
191
|
};
|
|
191
192
|
}
|
|
192
|
-
if (item.children)
|
|
193
|
-
flattenContent(item.children);
|
|
194
|
-
}
|
|
193
|
+
if (item.children) flattenContent(item.children);
|
|
195
194
|
}
|
|
196
195
|
};
|
|
197
|
-
|
|
198
|
-
flattenContent(
|
|
196
|
+
|
|
197
|
+
flattenContent(rootContent);
|
|
199
198
|
fs.writeFileSync(versionsFile, JSON.stringify(newVersionMap, null, 2));
|
|
200
|
-
|
|
201
|
-
console.log("");
|
|
202
|
-
console.log(`💾 Updated local versions file with ${Object.keys(newVersionMap).length} files`);
|
|
203
|
-
console.log(`🎉 Pull completed successfully!`);
|
|
204
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`));
|
|
205
203
|
} catch (err) {
|
|
206
|
-
console.error(
|
|
207
|
-
|
|
204
|
+
console.error(chalk.red(`❌ Pull failed: ${err.message}`));
|
|
205
|
+
|
|
208
206
|
if (err.response) {
|
|
209
|
-
console.error(
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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);
|
|
213
213
|
} else if (err.request) {
|
|
214
|
-
console.error("🌐 No response
|
|
214
|
+
console.error(chalk.yellow("🌐 No response from server — check backend (localhost:3001)."));
|
|
215
|
+
} else {
|
|
216
|
+
console.error(chalk.red("🔧 Configuration error:"), err.message);
|
|
215
217
|
}
|
|
216
218
|
}
|
|
217
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.29",
|
|
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
|
+
}
|