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.
Files changed (2) hide show
  1. package/commands/pull.js +81 -96
  2. 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
- file.on('finish', () => {
41
- file.close();
42
- resolve(filePath);
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 itemPath = path.join(basePath, item.path || item.name);
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, basePath);
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
- // Check if file already exists and has same content
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}:`, err.message);
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(`🚀 Pulling files from room ${roomId}...`);
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
- // Use your existing endpoint
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
- // Handle different response formats
156
- let rootContent = folderData.rootContent || folderData.content || folderData;
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
- // Create backup of current versions file if it exists
159
+ // Backup versions file
169
160
  const versionsFile = path.join(process.cwd(), ".tncversions");
170
- if (fs.existsSync(versionsFile)) {
171
- const backupPath = path.join(process.cwd(), `.tncversions.backup.${Date.now()}`);
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(`💾 Backed up existing versions to: ${path.basename(backupPath)}`);
164
+ console.log(chalk.gray(`💾 Backup created: ${path.basename(backupPath)}`));
174
165
  }
175
166
 
176
- // Process and download files
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 (already exist)`);
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 local versions file with pulled data
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("❌ Pull failed with error:", err.message);
218
-
204
+ console.error(chalk.red(`❌ Pull failed: ${err.message}`));
205
+
219
206
  if (err.response) {
220
- console.error("📡 Server response status:", err.response.status);
221
- if (err.response.data) {
222
- if (typeof err.response.data === 'object') {
223
- console.error("📡 Server message:", err.response.data.message || err.response.data);
224
- } else {
225
- console.error("📡 Server response:", err.response.data);
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 received from server - check if backend is running on localhost:3001");
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.28",
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
+ }