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.
Files changed (2) hide show
  1. package/commands/pull.js +90 -88
  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";
5
- import os from "os"; // Add this 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
- 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,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(`🚀 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
- // 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 || !folderData.rootContent) {
150
- console.log("❌ No content found in response");
142
+
143
+ if (!folderData) {
144
+ console.log(chalk.red("❌ No data found in response"));
151
145
  return;
152
146
  }
153
147
 
154
- console.log(`📦 Found version ${folderData.version} with ${folderData.rootContent.length} items`);
155
- console.log("");
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
- // Create backup of current versions file if it exists
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
- if (fs.existsSync(versionsFile)) {
160
- 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)) {
161
163
  fs.copyFileSync(versionsFile, backupPath);
162
- console.log(`💾 Backed up existing versions to: ${path.basename(backupPath)}`);
164
+ console.log(chalk.gray(`💾 Backup created: ${path.basename(backupPath)}`));
163
165
  }
164
166
 
165
- // Process and download files
166
- const result = await processFolderContent(folderData.rootContent, process.cwd());
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 (already exist)`);
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 local versions file with pulled data
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(folderData.rootContent);
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("❌ Pull failed with error:", err.message);
207
-
204
+ console.error(chalk.red(`❌ Pull failed: ${err.message}`));
205
+
208
206
  if (err.response) {
209
- console.error("📡 Server response:", {
210
- status: err.response.status,
211
- data: err.response.data
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 received from server");
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.27",
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
+ }