thinkncollab-cli 0.0.22 → 0.0.24
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 +121 -48
- package/commands/status.js +284 -0
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import path from "path";
|
|
|
7
7
|
import crypto from "crypto";
|
|
8
8
|
import FormData from "form-data";
|
|
9
9
|
import projectInit from "../commands/init.js";
|
|
10
|
+
import Status from "../commands/status.js";
|
|
10
11
|
|
|
11
12
|
const RC_FILE = path.join(os.homedir(), ".tncrc");
|
|
12
13
|
const VERSION_FILE = path.join(process.cwd(), ".tncversions");
|
|
@@ -119,48 +120,86 @@ function scanFolder(folderPath, ignoreList, rootPath = folderPath) {
|
|
|
119
120
|
/** ------------------ VERSIONING ------------------ **/
|
|
120
121
|
function loadVersions() {
|
|
121
122
|
if (!fs.existsSync(VERSION_FILE)) return {};
|
|
122
|
-
|
|
123
|
+
const data = JSON.parse(fs.readFileSync(VERSION_FILE, "utf-8"));
|
|
124
|
+
|
|
125
|
+
// Backward compatibility: if old format (just strings), convert to new format
|
|
126
|
+
const converted = {};
|
|
127
|
+
for (const [path, value] of Object.entries(data)) {
|
|
128
|
+
if (typeof value === 'string') {
|
|
129
|
+
converted[path] = { hash: value, url: '', version: 1 };
|
|
130
|
+
} else {
|
|
131
|
+
converted[path] = value;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return converted;
|
|
123
135
|
}
|
|
124
136
|
|
|
125
137
|
function saveVersions(versionMap) {
|
|
126
138
|
fs.writeFileSync(VERSION_FILE, JSON.stringify(versionMap, null, 2));
|
|
127
139
|
}
|
|
128
140
|
|
|
129
|
-
function
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
141
|
+
function computeFileHash(filePath) {
|
|
142
|
+
try {
|
|
143
|
+
if (!fs.existsSync(filePath)) return null;
|
|
144
|
+
|
|
145
|
+
const stats = fs.statSync(filePath);
|
|
146
|
+
if (stats.isDirectory()) {
|
|
147
|
+
// For folders: hash of sorted file names + their hashes
|
|
148
|
+
const items = fs.readdirSync(filePath).sort();
|
|
149
|
+
const childrenHashes = items.map(name =>
|
|
150
|
+
computeFileHash(path.join(filePath, name))
|
|
151
|
+
).filter(Boolean).join('|');
|
|
152
|
+
|
|
153
|
+
return crypto.createHash("sha256")
|
|
154
|
+
.update(`folder:${filePath}|${childrenHashes}`)
|
|
155
|
+
.digest("hex");
|
|
156
|
+
} else {
|
|
157
|
+
// For files: hash of content + file metadata
|
|
158
|
+
const content = fs.readFileSync(filePath);
|
|
159
|
+
const stats = fs.statSync(filePath);
|
|
160
|
+
|
|
161
|
+
return crypto.createHash("sha256")
|
|
162
|
+
.update(content)
|
|
163
|
+
.update(`size:${stats.size}|mtime:${stats.mtimeMs}`)
|
|
164
|
+
.digest("hex");
|
|
165
|
+
}
|
|
166
|
+
} catch (err) {
|
|
167
|
+
console.error(`❌ Error computing hash for ${filePath}:`, err.message);
|
|
168
|
+
return null;
|
|
140
169
|
}
|
|
141
170
|
}
|
|
142
171
|
|
|
143
172
|
function checkChanges(fileTree, versionMap, rootPath = CWD) {
|
|
144
173
|
return fileTree.map(item => {
|
|
145
174
|
const fullPath = path.join(rootPath, item.path || item.name);
|
|
175
|
+
const relativePath = item.path || item.name;
|
|
176
|
+
|
|
146
177
|
let hash = null;
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
hash =
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
178
|
+
let changed = true;
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
if (fs.existsSync(fullPath)) {
|
|
182
|
+
hash = computeFileHash(fullPath);
|
|
183
|
+
|
|
184
|
+
// Check against previous version
|
|
185
|
+
const prevVersion = versionMap[relativePath];
|
|
186
|
+
if (prevVersion && prevVersion.hash === hash) {
|
|
187
|
+
changed = false;
|
|
188
|
+
}
|
|
155
189
|
}
|
|
190
|
+
} catch (err) {
|
|
191
|
+
console.error(`❌ Error checking changes for ${relativePath}:`, err.message);
|
|
156
192
|
}
|
|
157
193
|
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
194
|
+
const newItem = {
|
|
195
|
+
...item,
|
|
196
|
+
changed,
|
|
197
|
+
hash,
|
|
198
|
+
path: relativePath // Ensure consistent path
|
|
199
|
+
};
|
|
162
200
|
|
|
163
|
-
|
|
201
|
+
// Recursively check children for folders
|
|
202
|
+
if (item.type === "folder" && item.children && item.children.length > 0) {
|
|
164
203
|
newItem.children = checkChanges(item.children, versionMap, rootPath);
|
|
165
204
|
newItem.changed = newItem.changed || newItem.children.some(c => c.changed);
|
|
166
205
|
}
|
|
@@ -202,41 +241,61 @@ async function uploadTree(fileTree, folderHex, roomId, token, email, previousVer
|
|
|
202
241
|
const uploaded = [];
|
|
203
242
|
|
|
204
243
|
for (const node of fileTree) {
|
|
205
|
-
const relativePath = path.join(parentPath, node.name).replace(/\\/g, "/");
|
|
244
|
+
const relativePath = node.path || path.join(parentPath, node.name).replace(/\\/g, "/");
|
|
206
245
|
|
|
207
246
|
if (node.type === "folder") {
|
|
208
247
|
const children = await uploadTree(node.children, folderHex, roomId, token, email, previousVersions, relativePath);
|
|
209
|
-
|
|
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
|
-
}
|
|
224
|
-
} else {
|
|
225
|
-
// Unchanged file - use previous URL
|
|
226
|
-
const prevFile = previousVersions[node.path];
|
|
248
|
+
|
|
227
249
|
uploaded.push({
|
|
228
250
|
...node,
|
|
229
|
-
|
|
251
|
+
children,
|
|
230
252
|
hash: node.hash,
|
|
231
|
-
|
|
253
|
+
path: relativePath
|
|
232
254
|
});
|
|
255
|
+
} else {
|
|
256
|
+
const prevFile = previousVersions[relativePath];
|
|
257
|
+
|
|
258
|
+
if (node.changed) {
|
|
259
|
+
try {
|
|
260
|
+
console.log(`📤 Uploading changed file: ${relativePath}`);
|
|
261
|
+
const url = await uploadFileSigned(node.path, `tnc_uploads/${folderHex}`, roomId, token, email);
|
|
262
|
+
console.log(`✅ Uploaded: ${relativePath}`);
|
|
263
|
+
|
|
264
|
+
uploaded.push({
|
|
265
|
+
...node,
|
|
266
|
+
url,
|
|
267
|
+
hash: node.hash,
|
|
268
|
+
path: relativePath,
|
|
269
|
+
version: prevFile ? prevFile.version + 1 : 1
|
|
270
|
+
});
|
|
271
|
+
} catch (err) {
|
|
272
|
+
console.error(`❌ Failed to upload ${relativePath}:`, err.message);
|
|
273
|
+
// Fallback to previous version if upload fails
|
|
274
|
+
uploaded.push({
|
|
275
|
+
...node,
|
|
276
|
+
url: prevFile?.url,
|
|
277
|
+
hash: node.hash,
|
|
278
|
+
path: relativePath,
|
|
279
|
+
version: prevFile?.version || 1
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
} else {
|
|
283
|
+
// Unchanged file - use previous URL and version
|
|
284
|
+
console.log(`📋 Using previous version for: ${relativePath}`);
|
|
285
|
+
uploaded.push({
|
|
286
|
+
...node,
|
|
287
|
+
url: prevFile?.url,
|
|
288
|
+
hash: node.hash,
|
|
289
|
+
path: relativePath,
|
|
290
|
+
version: prevFile?.version || 1
|
|
291
|
+
});
|
|
292
|
+
}
|
|
233
293
|
}
|
|
234
294
|
}
|
|
235
295
|
|
|
236
296
|
return uploaded;
|
|
237
297
|
}
|
|
238
298
|
|
|
239
|
-
|
|
240
299
|
/** ------------------ PUSH FUNCTION ------------------ **/
|
|
241
300
|
async function push(roomId, targetPath) {
|
|
242
301
|
const { token, email } = readToken();
|
|
@@ -268,7 +327,16 @@ async function push(roomId, targetPath) {
|
|
|
268
327
|
}
|
|
269
328
|
|
|
270
329
|
const previousVersions = loadVersions();
|
|
330
|
+
console.log('📁 Previous versions:', Object.keys(previousVersions).length);
|
|
331
|
+
|
|
271
332
|
const contentWithChanges = checkChanges(content, previousVersions);
|
|
333
|
+
|
|
334
|
+
// Debug: Show what changed
|
|
335
|
+
const changedFiles = contentWithChanges.flatMap(item =>
|
|
336
|
+
item.changed ? [item.path] : []
|
|
337
|
+
);
|
|
338
|
+
console.log('🔄 Changed files:', changedFiles.length, changedFiles);
|
|
339
|
+
|
|
272
340
|
const hasChanges = contentWithChanges.some(item => item.changed || (item.children && item.children.some(c => c.changed)));
|
|
273
341
|
|
|
274
342
|
if (!hasChanges) {
|
|
@@ -308,6 +376,8 @@ async function push(roomId, targetPath) {
|
|
|
308
376
|
flattenAndStore(uploadedTree);
|
|
309
377
|
saveVersions(newVersionMap);
|
|
310
378
|
|
|
379
|
+
console.log(`💾 Saved ${Object.keys(newVersionMap).length} files to .tncversions`);
|
|
380
|
+
|
|
311
381
|
} catch (err) {
|
|
312
382
|
console.error("❌ Upload failed:", err.response?.data || err.message);
|
|
313
383
|
}
|
|
@@ -337,6 +407,9 @@ async function main() {
|
|
|
337
407
|
await push(roomId, targetPath);
|
|
338
408
|
break;
|
|
339
409
|
}
|
|
410
|
+
case "status":
|
|
411
|
+
await Status();
|
|
412
|
+
break;
|
|
340
413
|
|
|
341
414
|
case "init":
|
|
342
415
|
await projectInit();
|
|
@@ -352,4 +425,4 @@ async function main() {
|
|
|
352
425
|
}
|
|
353
426
|
}
|
|
354
427
|
|
|
355
|
-
main().catch(err => console.error(err));
|
|
428
|
+
main().catch(err => console.error(err));
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import crypto from "crypto";
|
|
5
|
+
|
|
6
|
+
const VERSION_FILE = path.join(process.cwd(), ".tncversions");
|
|
7
|
+
const CWD = process.cwd();
|
|
8
|
+
|
|
9
|
+
/** ------------------ IGNORE HANDLING ------------------ **/
|
|
10
|
+
function loadIgnore(folderPath) {
|
|
11
|
+
const ignoreFile = path.join(folderPath, ".ignoretnc");
|
|
12
|
+
if (!fs.existsSync(ignoreFile)) return [];
|
|
13
|
+
return fs
|
|
14
|
+
.readFileSync(ignoreFile, "utf-8")
|
|
15
|
+
.split("\n")
|
|
16
|
+
.map(line => line.trim())
|
|
17
|
+
.filter(line => line && !line.startsWith("#"));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function shouldIgnore(relativePath, ignoreList) {
|
|
21
|
+
return ignoreList.some(pattern => {
|
|
22
|
+
if (pattern.endsWith("/**")) {
|
|
23
|
+
const folder = pattern.slice(0, -3);
|
|
24
|
+
return relativePath === folder || relativePath.startsWith(folder + path.sep);
|
|
25
|
+
}
|
|
26
|
+
if (pattern.startsWith("*.")) {
|
|
27
|
+
return relativePath.endsWith(pattern.slice(1));
|
|
28
|
+
}
|
|
29
|
+
return relativePath === pattern;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** ------------------ SCAN FOLDER ------------------ **/
|
|
34
|
+
function scanFolder(folderPath, ignoreList, rootPath = folderPath) {
|
|
35
|
+
const items = fs.readdirSync(folderPath, { withFileTypes: true });
|
|
36
|
+
const result = [];
|
|
37
|
+
for (const item of items) {
|
|
38
|
+
const fullPath = path.join(folderPath, item.name);
|
|
39
|
+
const relativePath = path.relative(rootPath, fullPath);
|
|
40
|
+
|
|
41
|
+
if (shouldIgnore(relativePath, ignoreList)) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (item.isDirectory()) {
|
|
46
|
+
result.push({
|
|
47
|
+
name: item.name,
|
|
48
|
+
type: "folder",
|
|
49
|
+
children: scanFolder(fullPath, ignoreList, rootPath),
|
|
50
|
+
path: relativePath
|
|
51
|
+
});
|
|
52
|
+
} else {
|
|
53
|
+
const stats = fs.statSync(fullPath);
|
|
54
|
+
result.push({
|
|
55
|
+
name: item.name,
|
|
56
|
+
type: "file",
|
|
57
|
+
path: relativePath,
|
|
58
|
+
size: stats.size
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** ------------------ VERSIONING ------------------ **/
|
|
66
|
+
function loadVersions() {
|
|
67
|
+
if (!fs.existsSync(VERSION_FILE)) return {};
|
|
68
|
+
const data = JSON.parse(fs.readFileSync(VERSION_FILE, "utf-8"));
|
|
69
|
+
|
|
70
|
+
// Backward compatibility: if old format (just strings), convert to new format
|
|
71
|
+
const converted = {};
|
|
72
|
+
for (const [path, value] of Object.entries(data)) {
|
|
73
|
+
if (typeof value === 'string') {
|
|
74
|
+
converted[path] = { hash: value, url: '', version: 1 };
|
|
75
|
+
} else {
|
|
76
|
+
converted[path] = value;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return converted;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function computeFileHash(filePath) {
|
|
83
|
+
try {
|
|
84
|
+
if (!fs.existsSync(filePath)) return null;
|
|
85
|
+
|
|
86
|
+
const stats = fs.statSync(filePath);
|
|
87
|
+
if (stats.isDirectory()) {
|
|
88
|
+
// For folders: hash of sorted file names + their hashes
|
|
89
|
+
const items = fs.readdirSync(filePath).sort();
|
|
90
|
+
const childrenHashes = items.map(name =>
|
|
91
|
+
computeFileHash(path.join(filePath, name))
|
|
92
|
+
).filter(Boolean).join('|');
|
|
93
|
+
|
|
94
|
+
return crypto.createHash("sha256")
|
|
95
|
+
.update(`folder:${filePath}|${childrenHashes}`)
|
|
96
|
+
.digest("hex");
|
|
97
|
+
} else {
|
|
98
|
+
// For files: hash of content + file metadata
|
|
99
|
+
const content = fs.readFileSync(filePath);
|
|
100
|
+
const stats = fs.statSync(filePath);
|
|
101
|
+
|
|
102
|
+
return crypto.createHash("sha256")
|
|
103
|
+
.update(content)
|
|
104
|
+
.update(`size:${stats.size}|mtime:${stats.mtimeMs}`)
|
|
105
|
+
.digest("hex");
|
|
106
|
+
}
|
|
107
|
+
} catch (err) {
|
|
108
|
+
console.error(`❌ Error computing hash for ${filePath}:`, err.message);
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function checkChanges(fileTree, versionMap, rootPath = CWD) {
|
|
114
|
+
return fileTree.map(item => {
|
|
115
|
+
const fullPath = path.join(rootPath, item.path || item.name);
|
|
116
|
+
const relativePath = item.path || item.name;
|
|
117
|
+
|
|
118
|
+
let hash = null;
|
|
119
|
+
let changed = true;
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
if (fs.existsSync(fullPath)) {
|
|
123
|
+
hash = computeFileHash(fullPath);
|
|
124
|
+
|
|
125
|
+
// Check against previous version
|
|
126
|
+
const prevVersion = versionMap[relativePath];
|
|
127
|
+
if (prevVersion && prevVersion.hash === hash) {
|
|
128
|
+
changed = false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
} catch (err) {
|
|
132
|
+
console.error(`❌ Error checking changes for ${relativePath}:`, err.message);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const newItem = {
|
|
136
|
+
...item,
|
|
137
|
+
changed,
|
|
138
|
+
hash,
|
|
139
|
+
path: relativePath
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// Recursively check children for folders
|
|
143
|
+
if (item.type === "folder" && item.children && item.children.length > 0) {
|
|
144
|
+
newItem.children = checkChanges(item.children, versionMap, rootPath);
|
|
145
|
+
newItem.changed = newItem.changed || newItem.children.some(c => c.changed);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return newItem;
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** ------------------ STATUS FUNCTION ------------------ **/
|
|
153
|
+
export default async function status() {
|
|
154
|
+
try {
|
|
155
|
+
const tncMetaPath = path.join(process.cwd(), ".tnc", ".tncmeta.json");
|
|
156
|
+
if (!fs.existsSync(tncMetaPath)) {
|
|
157
|
+
console.error("❌ Project not initialized. Run 'tnc init' first.");
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const meta = JSON.parse(fs.readFileSync(tncMetaPath, "utf-8"));
|
|
162
|
+
const projectId = meta.projectId;
|
|
163
|
+
|
|
164
|
+
console.log("📊 TNC Project Status");
|
|
165
|
+
console.log("=====================");
|
|
166
|
+
console.log(`Project ID: ${projectId}`);
|
|
167
|
+
console.log(`Directory: ${process.cwd()}`);
|
|
168
|
+
console.log("");
|
|
169
|
+
|
|
170
|
+
// Check if versions file exists
|
|
171
|
+
if (!fs.existsSync(VERSION_FILE)) {
|
|
172
|
+
console.log("📭 No previous versions found. Run 'tnc push' first.");
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const previousVersions = loadVersions();
|
|
177
|
+
const totalFiles = Object.keys(previousVersions).length;
|
|
178
|
+
|
|
179
|
+
console.log(`📁 Total tracked files: ${totalFiles}`);
|
|
180
|
+
console.log("");
|
|
181
|
+
|
|
182
|
+
// Scan current directory for changes
|
|
183
|
+
const ignoreList = loadIgnore(process.cwd());
|
|
184
|
+
const currentContent = scanFolder(process.cwd(), ignoreList);
|
|
185
|
+
const contentWithChanges = checkChanges(currentContent, previousVersions);
|
|
186
|
+
|
|
187
|
+
// Analyze changes
|
|
188
|
+
let changedCount = 0;
|
|
189
|
+
let newCount = 0;
|
|
190
|
+
let unchangedCount = 0;
|
|
191
|
+
let ignoredCount = 0;
|
|
192
|
+
|
|
193
|
+
const flattenAndAnalyze = (items) => {
|
|
194
|
+
for (const item of items) {
|
|
195
|
+
if (item.type === "file") {
|
|
196
|
+
if (!previousVersions[item.path]) {
|
|
197
|
+
newCount++;
|
|
198
|
+
} else if (item.changed) {
|
|
199
|
+
changedCount++;
|
|
200
|
+
} else {
|
|
201
|
+
unchangedCount++;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (item.children) {
|
|
205
|
+
flattenAndAnalyze(item.children);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
flattenAndAnalyze(contentWithChanges);
|
|
211
|
+
|
|
212
|
+
console.log("🔄 Change Summary:");
|
|
213
|
+
console.log(` ✅ Unchanged files: ${unchangedCount}`);
|
|
214
|
+
console.log(` 📝 Modified files: ${changedCount}`);
|
|
215
|
+
console.log(` 🆕 New files: ${newCount}`);
|
|
216
|
+
console.log("");
|
|
217
|
+
|
|
218
|
+
// Show detailed changes
|
|
219
|
+
if (changedCount > 0 || newCount > 0) {
|
|
220
|
+
console.log("📋 Detailed Changes:");
|
|
221
|
+
console.log("-------------------");
|
|
222
|
+
|
|
223
|
+
const showChanges = (items, indent = "") => {
|
|
224
|
+
for (const item of items) {
|
|
225
|
+
if (item.type === "file") {
|
|
226
|
+
if (!previousVersions[item.path]) {
|
|
227
|
+
console.log(`${indent}🆕 ${item.path} (new)`);
|
|
228
|
+
} else if (item.changed) {
|
|
229
|
+
console.log(`${indent}📝 ${item.path} (modified)`);
|
|
230
|
+
}
|
|
231
|
+
} else if (item.type === "folder") {
|
|
232
|
+
const hasFileChanges = item.children && item.children.some(child =>
|
|
233
|
+
child.type === 'file' && (!previousVersions[child.path] || child.changed)
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
if (hasFileChanges) {
|
|
237
|
+
console.log(`${indent}📁 ${item.path}/`);
|
|
238
|
+
if (item.children) {
|
|
239
|
+
showChanges(item.children, indent + " ");
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
showChanges(contentWithChanges);
|
|
247
|
+
} else {
|
|
248
|
+
console.log("🎉 No changes detected. Everything is up to date!");
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
console.log("");
|
|
252
|
+
|
|
253
|
+
// Show version info
|
|
254
|
+
const versions = Object.values(previousVersions);
|
|
255
|
+
if (versions.length > 0) {
|
|
256
|
+
const maxVersion = Math.max(...versions.map(v => v.version || 1));
|
|
257
|
+
console.log(`🏷️ Latest version: ${maxVersion}`);
|
|
258
|
+
|
|
259
|
+
// Show file type breakdown
|
|
260
|
+
const extensions = {};
|
|
261
|
+
Object.keys(previousVersions).forEach(filePath => {
|
|
262
|
+
const ext = path.extname(filePath) || 'no extension';
|
|
263
|
+
extensions[ext] = (extensions[ext] || 0) + 1;
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
console.log("");
|
|
267
|
+
console.log("📄 File Types:");
|
|
268
|
+
Object.entries(extensions)
|
|
269
|
+
.sort(([,a], [,b]) => b - a)
|
|
270
|
+
.forEach(([ext, count]) => {
|
|
271
|
+
console.log(` ${ext || '(no ext)'}: ${count} files`);
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Show last push info if available
|
|
276
|
+
const versionFileStats = fs.statSync(VERSION_FILE);
|
|
277
|
+
console.log("");
|
|
278
|
+
console.log(`⏰ Last push: ${versionFileStats.mtime.toLocaleString()}`);
|
|
279
|
+
|
|
280
|
+
} catch (err) {
|
|
281
|
+
console.error("❌ Error checking status:", err.message);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|