thinkncollab-cli 0.0.1-0.1
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/.ignoretnc +2 -0
- package/.vscode/geekload-types.d.ts +455 -0
- package/Readme.md +313 -0
- package/bin/index.js +245 -0
- package/bin/tnc.js +21 -0
- package/lib/api.js +28 -0
- package/lib/scanner.js +27 -0
- package/lib/uploader.js +5 -0
- package/package.json +18 -0
package/.ignoretnc
ADDED
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
declare class GroupClass {
|
|
2
|
+
/**
|
|
3
|
+
* Execute test group
|
|
4
|
+
*
|
|
5
|
+
* @param scenario Name or identifier of scenario function
|
|
6
|
+
* @param users Amount of virtual users
|
|
7
|
+
* @param profile Duration string ('5s', '1m 30s' etc) or Profile object
|
|
8
|
+
*/
|
|
9
|
+
run(scenario: any, users: number, profile: any): void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
declare class ConfigClass {
|
|
13
|
+
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Configuration of project
|
|
18
|
+
*/
|
|
19
|
+
declare const config: ConfigClass;
|
|
20
|
+
|
|
21
|
+
declare class StageClass {
|
|
22
|
+
/**
|
|
23
|
+
* Execute test stage
|
|
24
|
+
*
|
|
25
|
+
* @param title Title of stage
|
|
26
|
+
* @param users Amount of virtual users
|
|
27
|
+
* @param groups Array of groups
|
|
28
|
+
*/
|
|
29
|
+
run(title: string, users: number, groups: GroupClass[]): void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Stage of test with set of Groups
|
|
34
|
+
*/
|
|
35
|
+
declare const Stage: StageClass;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Group of test with set of Scenarios
|
|
39
|
+
*/
|
|
40
|
+
declare const Group: GroupClass;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Class representing BaseHttpRequest.
|
|
44
|
+
*/
|
|
45
|
+
declare class BaseHttpRequest {
|
|
46
|
+
/**
|
|
47
|
+
* Create a http GET request.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} path - Relative path to target point.
|
|
50
|
+
* @returns {HttpRequest} - The created HttpRequest.
|
|
51
|
+
*/
|
|
52
|
+
get(path: string): HttpRequest;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Create a http PUT request.
|
|
56
|
+
*
|
|
57
|
+
* @param {string} path - Relative path to target point.
|
|
58
|
+
* @returns {HttpRequest} - The created HttpRequest.
|
|
59
|
+
*/
|
|
60
|
+
put(path: string): HttpRequest;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Create a http POST request.
|
|
64
|
+
*
|
|
65
|
+
* @param {string} path - Relative path to target point.
|
|
66
|
+
* @returns {HttpRequest} - The created HttpRequest.
|
|
67
|
+
*/
|
|
68
|
+
post(path: string): HttpRequest;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Create a http PATH request.
|
|
72
|
+
*
|
|
73
|
+
* @param {string} path - Relative path to target point.
|
|
74
|
+
* @returns {HttpRequest} - The created HttpRequest.
|
|
75
|
+
*/
|
|
76
|
+
patch(path: string): HttpRequest;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Create a http DELETE request.
|
|
80
|
+
*
|
|
81
|
+
* @param {string} path - Relative path to target point.
|
|
82
|
+
* @returns {HttpRequest} - The created HttpRequest.
|
|
83
|
+
*/
|
|
84
|
+
delete(path: string): HttpRequest;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Create a http OPTIONS request.
|
|
88
|
+
*
|
|
89
|
+
* @param {string} path - Relative path to target point.
|
|
90
|
+
* @returns {HttpRequest} - The created HttpRequest.
|
|
91
|
+
*/
|
|
92
|
+
options(path: string): HttpRequest;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Create a http HEAD request.
|
|
96
|
+
*
|
|
97
|
+
* @param {string} path - Relative path to target point.
|
|
98
|
+
* @returns {HttpRequest} - The created HttpRequest.
|
|
99
|
+
*/
|
|
100
|
+
head(path: string): HttpRequest;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Body of Request's response.
|
|
105
|
+
*/
|
|
106
|
+
declare class BodyClass {
|
|
107
|
+
asText: string;
|
|
108
|
+
asObject: object;
|
|
109
|
+
asBytes: number[]
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Result of HttpRequest's execution.
|
|
114
|
+
*/
|
|
115
|
+
declare class HttpResult {
|
|
116
|
+
statusCode: number
|
|
117
|
+
body: BodyClass
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Class representing HttpRequest.
|
|
122
|
+
*/
|
|
123
|
+
declare class HttpRequest extends BaseHttpRequest {
|
|
124
|
+
/**
|
|
125
|
+
* Add header to request.
|
|
126
|
+
*
|
|
127
|
+
* @param {string} name - Name of the header.
|
|
128
|
+
* @param {string} value - Value of the header.
|
|
129
|
+
* @returns {HttpRequest} - The modified HttpRequest.
|
|
130
|
+
*/
|
|
131
|
+
header(name: string, value: string): HttpRequest;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Add cookie to request.
|
|
135
|
+
*
|
|
136
|
+
* @param {string} name - Name of the cookie.
|
|
137
|
+
* @param {string} value - Value of the cookie.
|
|
138
|
+
* @returns {HttpRequest} - The modified HttpRequest.
|
|
139
|
+
*/
|
|
140
|
+
cookie(name: string, value: string): HttpRequest;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Add query parameter to request url.
|
|
144
|
+
*
|
|
145
|
+
* @param {string} name - Name of the query parameter.
|
|
146
|
+
* @param {string} value - Value of the query parameter.
|
|
147
|
+
* @returns {HttpRequest} - The modified HttpRequest.
|
|
148
|
+
*/
|
|
149
|
+
query(name: string, value: string): HttpRequest;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Set body value.
|
|
153
|
+
*
|
|
154
|
+
* @param {any} object - Body value.
|
|
155
|
+
* @returns {HttpRequest} - The modified HttpRequest.
|
|
156
|
+
*/
|
|
157
|
+
body(object: any): HttpRequest;
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Execute request.
|
|
161
|
+
*
|
|
162
|
+
* @returns {HttpResult} - The HttpResult.
|
|
163
|
+
*/
|
|
164
|
+
sync(): HttpResult;
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Execute request checks.
|
|
168
|
+
*
|
|
169
|
+
* @param {CheckExpression} check - Check object.
|
|
170
|
+
* @returns {HttpRequest} - The modified HttpRequest.
|
|
171
|
+
*/
|
|
172
|
+
then(check: CheckExpression): HttpRequest;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Execute request checks.
|
|
176
|
+
*
|
|
177
|
+
* @param {() => any} check - Check function.
|
|
178
|
+
* @returns {HttpRequest} - The modified HttpRequest.
|
|
179
|
+
*/
|
|
180
|
+
then(check: () => any): HttpRequest;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Class representing HttpConnection.
|
|
185
|
+
*/
|
|
186
|
+
declare class HttpConnection extends BaseHttpRequest {
|
|
187
|
+
/**
|
|
188
|
+
* Open connection to server.
|
|
189
|
+
*
|
|
190
|
+
* @returns {HttpConnection} - The HttpConnection.
|
|
191
|
+
*/
|
|
192
|
+
connect(): HttpConnection;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Create a HTTP connection.
|
|
197
|
+
*
|
|
198
|
+
* @param {string} url - Url of server.
|
|
199
|
+
* @param {Options} options - Optional options object.
|
|
200
|
+
* @returns {HttpConnection} - The HttpConnection.
|
|
201
|
+
*/
|
|
202
|
+
declare function http(url: string, options?: Options): HttpConnection;
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Class representing Http2Connection.
|
|
206
|
+
*/
|
|
207
|
+
declare class Http2Connection extends HttpConnection {
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Create a HTTP2 connection.
|
|
212
|
+
*
|
|
213
|
+
* @param {string} url - Url of server.
|
|
214
|
+
* @param {Options} options - Optional options object.
|
|
215
|
+
* @returns {Http2Connection} - The Http2Connection.
|
|
216
|
+
*/
|
|
217
|
+
declare function http2(url: string, options?: Options): Http2Connection;
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Class representing WebSocketConnection.
|
|
221
|
+
*/
|
|
222
|
+
declare class WebSocketConnection {
|
|
223
|
+
/**
|
|
224
|
+
* Send text message.
|
|
225
|
+
*
|
|
226
|
+
* @param {string} value - Text message.
|
|
227
|
+
*/
|
|
228
|
+
sendText(value: string): void;
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Send array message.
|
|
232
|
+
*
|
|
233
|
+
* @param {byte[]} value - Byte array.
|
|
234
|
+
*/
|
|
235
|
+
sendArray(value: number[]): void;
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Receive a text message.
|
|
239
|
+
*
|
|
240
|
+
* @returns {string} - The received text message.
|
|
241
|
+
*/
|
|
242
|
+
receiveText(): string;
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Receive a byte array.
|
|
246
|
+
*
|
|
247
|
+
* @returns {byte[]} - The received byte array.
|
|
248
|
+
*/
|
|
249
|
+
receiveArray(): number[];
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Create a WebSocket connection.
|
|
254
|
+
*
|
|
255
|
+
* @param {string} url - Url of server.
|
|
256
|
+
* @param {Options} options - Optional options object.
|
|
257
|
+
* @returns {WebSocketConnection} - The WebSocketConnection.
|
|
258
|
+
*/
|
|
259
|
+
declare function websocket(url: string, options?: Options): WebSocketConnection;
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Create a Swagger connection.
|
|
263
|
+
*
|
|
264
|
+
* @param {string} url - Url to Swagger definition.
|
|
265
|
+
* @param {Options} options - Optional options object.
|
|
266
|
+
* @returns {SwaggerConnection} - The SwaggerConnection.
|
|
267
|
+
*/
|
|
268
|
+
declare function swagger(url: string, options?: Options): SwaggerConnection;
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Class representing SwaggerConnection.
|
|
272
|
+
*/
|
|
273
|
+
declare class SwaggerConnection {
|
|
274
|
+
/**
|
|
275
|
+
* Print loaded definition to console.
|
|
276
|
+
*/
|
|
277
|
+
show(): void;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Class representing Options.
|
|
282
|
+
*/
|
|
283
|
+
declare class Options {
|
|
284
|
+
cacheConnections: true;
|
|
285
|
+
connectionTimeout: '1m';
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Change global options.
|
|
290
|
+
*
|
|
291
|
+
* @param {Options} value - Options object.
|
|
292
|
+
*/
|
|
293
|
+
declare function setOptions(value: Options): void;
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Pause scenario execution.
|
|
297
|
+
*
|
|
298
|
+
* @param {string} minDuration - Minimum duration of pause ('5ms', '1s 150ms', etc).
|
|
299
|
+
* @param {string} maxDuration - Maximum duration of pause ('5ms', '1s 150ms', etc).
|
|
300
|
+
*/
|
|
301
|
+
declare function pause(minDuration: string, maxDuration: string): void;
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Test log.
|
|
305
|
+
*/
|
|
306
|
+
declare const Log: LogClass;
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Class representing LogClass.
|
|
310
|
+
*/
|
|
311
|
+
declare class LogClass {
|
|
312
|
+
/**
|
|
313
|
+
* Post message to test log.
|
|
314
|
+
*
|
|
315
|
+
* @param {string} text - Text of the message.
|
|
316
|
+
*/
|
|
317
|
+
message(text: string): void;
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Post warning message to test log.
|
|
321
|
+
*
|
|
322
|
+
* @param {string} text - Text of the message.
|
|
323
|
+
*/
|
|
324
|
+
warning(text: string): void;
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Post error message to test log.
|
|
328
|
+
*
|
|
329
|
+
* @param {string} text - Text of the message.
|
|
330
|
+
*/
|
|
331
|
+
error(text: string): void;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Class representing CheckExpression.
|
|
336
|
+
*/
|
|
337
|
+
declare class CheckExpression {
|
|
338
|
+
/**
|
|
339
|
+
* Checking for equality of the current and reference values.
|
|
340
|
+
*
|
|
341
|
+
* @param {any} value - Reference value.
|
|
342
|
+
* @returns {CheckExpression} - The CheckExpression.
|
|
343
|
+
*/
|
|
344
|
+
equal(value: any): CheckExpression;
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Checking that the current value is greater than the reference value.
|
|
348
|
+
*
|
|
349
|
+
* @param {any} value - Reference value.
|
|
350
|
+
* @returns {CheckExpression} - The CheckExpression.
|
|
351
|
+
*/
|
|
352
|
+
great(value: any): CheckExpression;
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Checking that the current value is less than the reference value.
|
|
356
|
+
*
|
|
357
|
+
* @param {any} value - Reference value.
|
|
358
|
+
* @returns {CheckExpression} - The CheckExpression.
|
|
359
|
+
*/
|
|
360
|
+
less(value: any): CheckExpression;
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Checking that the current value contains the reference value.
|
|
364
|
+
*
|
|
365
|
+
* @param {any} value - Reference value.
|
|
366
|
+
* @returns {CheckExpression} - The CheckExpression.
|
|
367
|
+
*/
|
|
368
|
+
contains(value: any): CheckExpression;
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Checking that the reference value contains the current value.
|
|
372
|
+
*
|
|
373
|
+
* @param {any} value - Reference value.
|
|
374
|
+
* @returns {CheckExpression} - The CheckExpression.
|
|
375
|
+
*/
|
|
376
|
+
isContained(value: any): CheckExpression;
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Checking that the reference value exists.
|
|
380
|
+
*
|
|
381
|
+
* @returns {CheckExpression} - The CheckExpression.
|
|
382
|
+
*/
|
|
383
|
+
exists(): CheckExpression;
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Inverts the result of the check.
|
|
387
|
+
*
|
|
388
|
+
* @returns {CheckExpression} - The CheckExpression.
|
|
389
|
+
*/
|
|
390
|
+
not(): CheckExpression;
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Setting a custom message to display a checking error.
|
|
394
|
+
*
|
|
395
|
+
* @param {string} text - Custom message text.
|
|
396
|
+
* @returns {CheckExpression} - The CheckExpression.
|
|
397
|
+
*/
|
|
398
|
+
message(text: string): CheckExpression;
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Saves the received value in the "Session" object with the specified name.
|
|
402
|
+
*
|
|
403
|
+
* @param {string} name - Value name.
|
|
404
|
+
* @returns {CheckExpression} - The CheckExpression.
|
|
405
|
+
*/
|
|
406
|
+
store(name: string): CheckExpression;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* The source of the value of the response status code for its verification.
|
|
411
|
+
*
|
|
412
|
+
* @returns {CheckExpression} - The CheckExpression.
|
|
413
|
+
*/
|
|
414
|
+
declare function statusCode(): CheckExpression;
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Selector for checking values obtained by Regular expression from body.
|
|
418
|
+
*
|
|
419
|
+
* @param {string | RegExp} expression - Regular expression.
|
|
420
|
+
* @param {number} group - Index of RegExp group. 0 by default.
|
|
421
|
+
* @param {number} index - Index of occurrence of expression. 0 by default.
|
|
422
|
+
* @returns {CheckExpression} - The CheckExpression.
|
|
423
|
+
*/
|
|
424
|
+
declare function regexp(expression: any, group: number, index: number): CheckExpression;
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Selector for checking values obtained by xPath expression from body.
|
|
428
|
+
*
|
|
429
|
+
* @param {string} expression - xPath expression.
|
|
430
|
+
* @returns {CheckExpression} - The CheckExpression.
|
|
431
|
+
*/
|
|
432
|
+
declare function xPath(expression: string): CheckExpression;
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Source of text for checking it.
|
|
436
|
+
*
|
|
437
|
+
* @returns {CheckExpression} - The CheckExpression.
|
|
438
|
+
*/
|
|
439
|
+
declare function text(): CheckExpression;
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Selector for checking cookies value obtained by name.
|
|
443
|
+
*
|
|
444
|
+
* @param {string} name - Name of cookie.
|
|
445
|
+
* @returns {CheckExpression} - The CheckExpression.
|
|
446
|
+
*/
|
|
447
|
+
declare function cookie(name: string): CheckExpression;
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Selector for checking headers value obtained by name.
|
|
451
|
+
*
|
|
452
|
+
* @param {string} name - Name of header.
|
|
453
|
+
* @returns {CheckExpression} - The CheckExpression.
|
|
454
|
+
*/
|
|
455
|
+
declare function header(name: string): CheckExpression;
|
package/Readme.md
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# 🧠 ThinkNCollab CLI
|
|
2
|
+
|
|
3
|
+
A powerful command-line interface for seamless collaboration with **ThinkNCollab** — push files, manage rooms, and collaborate directly from your terminal.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🚀 Quick Start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Install the CLI globally
|
|
11
|
+
npm install -g @thinkncollab/tnc-cli
|
|
12
|
+
|
|
13
|
+
# Login to your ThinkNCollab account
|
|
14
|
+
tnc-cli login
|
|
15
|
+
|
|
16
|
+
# Push files to a room
|
|
17
|
+
tnc-cli push --room <roomId> <path>
|
|
18
|
+
|
|
19
|
+
# Logout
|
|
20
|
+
tnc-cli logout
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 🔐 Authentication
|
|
26
|
+
|
|
27
|
+
### **Login Command**
|
|
28
|
+
|
|
29
|
+
Authenticate with your ThinkNCollab account to enable CLI access:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
tnc-cli login
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### **What Happens During Login**
|
|
36
|
+
|
|
37
|
+
- Opens a secure browser window to ThinkNCollab’s authentication page
|
|
38
|
+
- Completes OAuth2 authentication flow
|
|
39
|
+
- Creates an encrypted `.tncrc` configuration file in your home directory
|
|
40
|
+
- Stores secure tokens for future CLI sessions
|
|
41
|
+
|
|
42
|
+
### **Manual Authentication**
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
tnc-cli login --token YOUR_AUTH_TOKEN
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### **Verify Authentication**
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
tnc-cli whoami
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### **Logout**
|
|
55
|
+
|
|
56
|
+
Clear stored credentials:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
tnc-cli logout
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 📦 File Operations
|
|
65
|
+
|
|
66
|
+
### **Push Command**
|
|
67
|
+
|
|
68
|
+
Push files or directories to ThinkNCollab rooms:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
tnc-cli push --room <roomId> <path>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
#### **Syntax**
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
tnc-cli push --room ROOM_ID PATH [ADDITIONAL_PATHS...]
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
#### **Examples**
|
|
81
|
+
|
|
82
|
+
| Action | Command |
|
|
83
|
+
|--------|----------|
|
|
84
|
+
| Push a single file | `tnc-cli push --room 64a1b2c3d4e5f6a1b2c3d4e5 document.pdf` |
|
|
85
|
+
| Push entire folder | `tnc-cli push --room 64a1b2c3d4e5f6a1b2c3d4e5 ./src/` |
|
|
86
|
+
| Push multiple items | `tnc-cli push --room 64a1b2c3d4e5f6a1b2c3d4e5 file1.js assets/ components/` |
|
|
87
|
+
| Push current directory | `tnc-cli push --room 64a1b2c3d4e5f6a1b2c3d4e5 .` |
|
|
88
|
+
|
|
89
|
+
#### **Options**
|
|
90
|
+
|
|
91
|
+
| Option | Short | Description |
|
|
92
|
+
|---------|--------|-------------|
|
|
93
|
+
| `--room` | `-r` | **Required:** Target room ID |
|
|
94
|
+
| `--message` | `-m` | Commit message describing changes |
|
|
95
|
+
| `--force` | `-f` | Force push (overwrite conflicts) |
|
|
96
|
+
| `--dry-run` | — | Preview files before pushing |
|
|
97
|
+
| `--exclude` | — | Additional patterns to exclude |
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 🧩 Room Management
|
|
102
|
+
|
|
103
|
+
| Command | Description |
|
|
104
|
+
|----------|--------------|
|
|
105
|
+
| `tnc-cli rooms list` | List accessible rooms |
|
|
106
|
+
| `tnc-cli rooms info <id>` | Show details for a specific room |
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## 🚫 File Ignoring
|
|
111
|
+
|
|
112
|
+
Use a `.ignoretnc` file in your project root to exclude files/folders during push.
|
|
113
|
+
|
|
114
|
+
### **Example `.ignoretnc`**
|
|
115
|
+
|
|
116
|
+
```text
|
|
117
|
+
# Dependencies
|
|
118
|
+
node_modules/
|
|
119
|
+
vendor/
|
|
120
|
+
bower_components/
|
|
121
|
+
|
|
122
|
+
# Build outputs
|
|
123
|
+
/dist
|
|
124
|
+
/build
|
|
125
|
+
/.next
|
|
126
|
+
/out
|
|
127
|
+
|
|
128
|
+
# Environment
|
|
129
|
+
.env
|
|
130
|
+
.env.local
|
|
131
|
+
.env.production
|
|
132
|
+
.env.development
|
|
133
|
+
|
|
134
|
+
# Logs
|
|
135
|
+
*.log
|
|
136
|
+
npm-debug.log*
|
|
137
|
+
yarn-debug.log*
|
|
138
|
+
|
|
139
|
+
# Temporary / OS
|
|
140
|
+
*.tmp
|
|
141
|
+
.DS_Store
|
|
142
|
+
Thumbs.db
|
|
143
|
+
|
|
144
|
+
# IDE
|
|
145
|
+
.vscode/
|
|
146
|
+
.idea/
|
|
147
|
+
|
|
148
|
+
# Test
|
|
149
|
+
*.test.js
|
|
150
|
+
*.spec.js
|
|
151
|
+
/coverage/
|
|
152
|
+
|
|
153
|
+
# Large assets
|
|
154
|
+
*.psd
|
|
155
|
+
*.ai
|
|
156
|
+
*.sketch
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### **Pattern Rules**
|
|
160
|
+
|
|
161
|
+
| Type | Example | Description |
|
|
162
|
+
|------|----------|-------------|
|
|
163
|
+
| Directory | `dist/` | Ignore whole directory |
|
|
164
|
+
| File Extension | `*.log` | Ignore all `.log` files |
|
|
165
|
+
| Specific File | `secret.env` | Ignore single file |
|
|
166
|
+
| Wildcard | `test-*.js` | Match name patterns |
|
|
167
|
+
| Negation | `!keep.js` | Include despite other rules |
|
|
168
|
+
| Comment | `# comment` | Ignored by parser |
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## ⚙️ Configuration
|
|
173
|
+
|
|
174
|
+
After login, an encrypted `.tncrc` file is created in your home directory.
|
|
175
|
+
|
|
176
|
+
### **Example `.tncrc`**
|
|
177
|
+
|
|
178
|
+
```json
|
|
179
|
+
{
|
|
180
|
+
"user": {
|
|
181
|
+
"id": "encrypted_user_id",
|
|
182
|
+
"email": "encrypted_email",
|
|
183
|
+
"name": "encrypted_display_name"
|
|
184
|
+
},
|
|
185
|
+
"auth": {
|
|
186
|
+
"token": "encrypted_jwt_token",
|
|
187
|
+
"refreshToken": "encrypted_refresh_token",
|
|
188
|
+
"expires": "2025-12-31T23:59:59Z"
|
|
189
|
+
},
|
|
190
|
+
"workspace": {
|
|
191
|
+
"id": "encrypted_workspace_id",
|
|
192
|
+
"name": "encrypted_workspace_name"
|
|
193
|
+
},
|
|
194
|
+
"settings": {
|
|
195
|
+
"defaultRoom": "optional_default_room_id",
|
|
196
|
+
"autoSync": false
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### **Environment Variables**
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
export TNC_API_TOKEN="your_api_token"
|
|
205
|
+
export TNC_API_URL="https://api.thinkncollab.com"
|
|
206
|
+
export TNC_DEFAULT_ROOM="your_default_room_id"
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## ⚡ Advanced Usage
|
|
212
|
+
|
|
213
|
+
### **Batch Push**
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
tnc-cli push --room room1,room2,room3 ./shared-assets/
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### **CI/CD Integration**
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
tnc-cli login --token $TNC_DEPLOY_TOKEN
|
|
223
|
+
tnc-cli push --room $PRODUCTION_ROOM ./dist/ --message "Build ${CI_COMMIT_SHA}"
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### **Watch for Changes (Experimental)**
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
tnc-cli watch --room 64a1b2c3d4e5f6a1b2c3d4e5 ./src/
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## 🧰 Troubleshooting
|
|
235
|
+
|
|
236
|
+
### **Authentication Issues**
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
tnc-cli logout
|
|
240
|
+
tnc-cli login
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
- Ensure valid token and room access
|
|
244
|
+
- Token may need refresh or rotation
|
|
245
|
+
|
|
246
|
+
### **Permission Errors**
|
|
247
|
+
|
|
248
|
+
- Confirm write access to target room
|
|
249
|
+
- Check if the room ID is active
|
|
250
|
+
|
|
251
|
+
### **File Size Limits**
|
|
252
|
+
|
|
253
|
+
| Type | Limit |
|
|
254
|
+
|------|--------|
|
|
255
|
+
| Individual File | 100 MB |
|
|
256
|
+
| Total Push | 1 GB |
|
|
257
|
+
|
|
258
|
+
### **Debug Mode**
|
|
259
|
+
|
|
260
|
+
Enable detailed logs:
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
tnc-cli --debug push --room 64a1b2c3d4e5f6a1b2c3d4e5 ./path/
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## 🔒 Security Guidelines
|
|
269
|
+
|
|
270
|
+
- **Never share** your `.tncrc` file — it stores encrypted tokens
|
|
271
|
+
- **Never commit** `.tncrc` to Git or any version control
|
|
272
|
+
- Use `.ignoretnc` to exclude sensitive files
|
|
273
|
+
- Rotate API tokens regularly
|
|
274
|
+
- Validate room access before pushing confidential data
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## 💡 Best Practices
|
|
279
|
+
|
|
280
|
+
- Use environment variables for automated environments
|
|
281
|
+
- Review `.ignoretnc` before each push
|
|
282
|
+
- Run `--dry-run` to preview changes
|
|
283
|
+
- Monitor push logs for unexpected files
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## 🧭 Command Reference
|
|
288
|
+
|
|
289
|
+
| Command | Description |
|
|
290
|
+
|----------|-------------|
|
|
291
|
+
| `tnc-cli login` | Authenticate with ThinkNCollab |
|
|
292
|
+
| `tnc-cli logout` | Clear credentials |
|
|
293
|
+
| `tnc-cli whoami` | Show current user info |
|
|
294
|
+
| `tnc-cli push --room <id> <path>` | Push files/folders to a room |
|
|
295
|
+
| `tnc-cli rooms list` | List all accessible rooms |
|
|
296
|
+
| `tnc-cli rooms info <id>` | Show room details |
|
|
297
|
+
| `tnc-cli --version` | Show CLI version |
|
|
298
|
+
| `tnc-cli --help` | Show help information |
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## 🧩 Resources & Support
|
|
303
|
+
|
|
304
|
+
- 📘 **Documentation:** [docs.thinkncollab.com/cli](https://docs.thinkncollab.com/cli)
|
|
305
|
+
- 🐙 **GitHub Issues:** [ThinkNCollab Repository](https://github.com/thinkncollab)
|
|
306
|
+
- ✉️ **Email:** support@thinkncollab.com
|
|
307
|
+
- 💬 **Community:** Join our [ThinkNCollab Discord](https://discord.gg/thinkncollab)
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## 📄 License
|
|
312
|
+
|
|
313
|
+
MIT License – see `LICENSE` file for details.
|
package/bin/index.js
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import inquirer from "inquirer";
|
|
3
|
+
import axios from "axios";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import os from "os";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import crypto from "crypto";
|
|
8
|
+
import FormData from "form-data";
|
|
9
|
+
|
|
10
|
+
const RC_FILE = path.join(os.homedir(), ".tncrc");
|
|
11
|
+
const BASE_URL = "https://thinkncollab.com/rooms";
|
|
12
|
+
|
|
13
|
+
// LOGOUT
|
|
14
|
+
async function logout() {
|
|
15
|
+
try {
|
|
16
|
+
if (fs.existsSync(RC_FILE)) {
|
|
17
|
+
// Remove the .tncrc file safely
|
|
18
|
+
await fs.promises.rm(RC_FILE, { force: true });
|
|
19
|
+
console.log("✅ Logged out successfully. Local credentials removed.");
|
|
20
|
+
} else {
|
|
21
|
+
console.log("ℹ️ No active session found.");
|
|
22
|
+
}
|
|
23
|
+
} catch (err) {
|
|
24
|
+
console.error("❌ Error during logout:", err.message);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** ========== LOGIN ========== **/
|
|
29
|
+
async function login() {
|
|
30
|
+
const answers = await inquirer.prompt([
|
|
31
|
+
{ type: "input", name: "email", message: "Email:" },
|
|
32
|
+
{ type: "password", name: "password", message: "Password:" }
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
console.log("🔐 Logging in...");
|
|
37
|
+
const res = await axios.post("https://thinkncollab.com/login", {
|
|
38
|
+
email: answers.email,
|
|
39
|
+
password: answers.password
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const { token, email } = res.data;
|
|
43
|
+
fs.writeFileSync(RC_FILE, JSON.stringify({ token, email }, null, 2));
|
|
44
|
+
console.log(`✅ Login successful! Token saved to ${RC_FILE}`);
|
|
45
|
+
} catch (err) {
|
|
46
|
+
console.error("❌ Login failed:", err.response?.data?.message || err.message);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function readToken() {
|
|
51
|
+
if (!fs.existsSync(RC_FILE)) {
|
|
52
|
+
console.error("❌ Not logged in. Run 'tnc login' first.");
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
const data = JSON.parse(fs.readFileSync(RC_FILE));
|
|
56
|
+
return { token: data.token, email: data.email };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** ========== IGNORE HANDLING ========== **/
|
|
60
|
+
function loadIgnore(folderPath) {
|
|
61
|
+
const ignoreFile = path.join(folderPath, ".ignoretnc");
|
|
62
|
+
if (!fs.existsSync(ignoreFile)) return [];
|
|
63
|
+
return fs
|
|
64
|
+
.readFileSync(ignoreFile, "utf-8")
|
|
65
|
+
.split("\n")
|
|
66
|
+
.map(line => line.trim())
|
|
67
|
+
.filter(line => line && !line.startsWith("#"));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function shouldIgnore(relativePath, ignoreList) {
|
|
71
|
+
return ignoreList.some(pattern => {
|
|
72
|
+
if (pattern.endsWith("/**")) {
|
|
73
|
+
const folder = pattern.slice(0, -3);
|
|
74
|
+
return relativePath === folder || relativePath.startsWith(folder + path.sep);
|
|
75
|
+
}
|
|
76
|
+
if (pattern.startsWith("*.")) {
|
|
77
|
+
return relativePath.endsWith(pattern.slice(1));
|
|
78
|
+
}
|
|
79
|
+
return relativePath === pattern;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** ========== SCAN FOLDER ========== **/
|
|
84
|
+
function scanFolder(folderPath, ignoreList, rootPath = folderPath) {
|
|
85
|
+
const items = fs.readdirSync(folderPath, { withFileTypes: true });
|
|
86
|
+
const result = [];
|
|
87
|
+
for (const item of items) {
|
|
88
|
+
const fullPath = path.join(folderPath, item.name);
|
|
89
|
+
const relativePath = path.relative(rootPath, fullPath);
|
|
90
|
+
|
|
91
|
+
if (shouldIgnore(relativePath, ignoreList)) {
|
|
92
|
+
console.log("⚠️ Ignored:", relativePath);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (item.isDirectory()) {
|
|
97
|
+
result.push({
|
|
98
|
+
name: item.name,
|
|
99
|
+
type: "folder",
|
|
100
|
+
children: scanFolder(fullPath, ignoreList, rootPath)
|
|
101
|
+
});
|
|
102
|
+
} else {
|
|
103
|
+
const stats = fs.statSync(fullPath);
|
|
104
|
+
result.push({
|
|
105
|
+
name: item.name,
|
|
106
|
+
type: "file",
|
|
107
|
+
path: fullPath,
|
|
108
|
+
size: stats.size
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** ========== CLOUDINARY UPLOAD (SIGNED) ========== **/
|
|
116
|
+
async function uploadFileSigned(filePath, folder, roomId, token, email) {
|
|
117
|
+
const filename = path.basename(filePath);
|
|
118
|
+
|
|
119
|
+
const sigRes = await axios.post(
|
|
120
|
+
`${BASE_URL}/${roomId}/get-upload-signature`,
|
|
121
|
+
{ filename, folder, roomId },
|
|
122
|
+
{ headers: { authorization: `Bearer ${token}`, email } }
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const { signature, timestamp, api_key, cloud_name } = sigRes.data;
|
|
126
|
+
|
|
127
|
+
const formData = new FormData();
|
|
128
|
+
formData.append("file", fs.createReadStream(filePath));
|
|
129
|
+
formData.append("folder", folder);
|
|
130
|
+
formData.append("public_id", filename);
|
|
131
|
+
formData.append("timestamp", timestamp);
|
|
132
|
+
formData.append("signature", signature);
|
|
133
|
+
formData.append("api_key", api_key);
|
|
134
|
+
|
|
135
|
+
const cloudRes = await axios.post(
|
|
136
|
+
`https://api.cloudinary.com/v1_1/${cloud_name}/auto/upload`,
|
|
137
|
+
formData,
|
|
138
|
+
{ headers: formData.getHeaders() }
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
return cloudRes.data.secure_url;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function uploadTree(fileTree, folderHex, roomId, token, email, parentPath = "") {
|
|
145
|
+
const uploaded = [];
|
|
146
|
+
|
|
147
|
+
for (const node of fileTree) {
|
|
148
|
+
const relativePath = path.join(parentPath, node.name).replace(/\\/g, "/");
|
|
149
|
+
|
|
150
|
+
if (node.type === "folder") {
|
|
151
|
+
const children = await uploadTree(node.children, folderHex, roomId, token, email, relativePath);
|
|
152
|
+
uploaded.push({
|
|
153
|
+
name: node.name,
|
|
154
|
+
type: "folder",
|
|
155
|
+
children
|
|
156
|
+
});
|
|
157
|
+
} else {
|
|
158
|
+
const url = await uploadFileSigned(node.path, `tnc_uploads/${folderHex}`, roomId, token, email);
|
|
159
|
+
console.log(`📦 Uploaded: ${relativePath} → ${url}`);
|
|
160
|
+
|
|
161
|
+
uploaded.push({
|
|
162
|
+
name: node.name,
|
|
163
|
+
type: "file",
|
|
164
|
+
path: relativePath,
|
|
165
|
+
size: node.size,
|
|
166
|
+
url // ✅ send top-level URL now
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return uploaded;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** ========== PUSH FUNCTION ========== **/
|
|
175
|
+
async function push(roomId, targetPath) {
|
|
176
|
+
const { token, email } = readToken();
|
|
177
|
+
const stats = fs.statSync(targetPath);
|
|
178
|
+
const rootFolder = stats.isDirectory() ? targetPath : path.dirname(targetPath);
|
|
179
|
+
const ignoreList = loadIgnore(rootFolder);
|
|
180
|
+
|
|
181
|
+
let content;
|
|
182
|
+
if (stats.isDirectory()) {
|
|
183
|
+
content = scanFolder(targetPath, ignoreList);
|
|
184
|
+
} else {
|
|
185
|
+
const relativePath = path.basename(targetPath);
|
|
186
|
+
content = shouldIgnore(relativePath, ignoreList)
|
|
187
|
+
? []
|
|
188
|
+
: [{ name: relativePath, type: "file", path: targetPath, size: stats.size }];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (!content.length) {
|
|
192
|
+
console.log("⚠️ Nothing to upload (all ignored).");
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
const folderHex = crypto.createHash("md5").update(path.basename(targetPath) + Date.now()).digest("hex");
|
|
198
|
+
|
|
199
|
+
console.log("🚀 Uploading to Cloudinary...");
|
|
200
|
+
const uploadedTree = await uploadTree(content, folderHex, roomId, token, email);
|
|
201
|
+
|
|
202
|
+
console.log("🗂️ Sending metadata to backend...");
|
|
203
|
+
await axios.post(
|
|
204
|
+
`${BASE_URL}/${roomId}/upload`,
|
|
205
|
+
{ folderId: folderHex, content: uploadedTree, uploadedBy: email },
|
|
206
|
+
{ headers: { authorization: `Bearer ${token}`, email } }
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
console.log("✅ Upload complete! Metadata stored successfully.");
|
|
210
|
+
} catch (err) {
|
|
211
|
+
console.error("❌ Upload failed:", err.response?.data || err.message);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** ========== CLI HANDLER ========== **/
|
|
216
|
+
const args = process.argv.slice(2);
|
|
217
|
+
|
|
218
|
+
switch (args[0]) {
|
|
219
|
+
case "login":
|
|
220
|
+
login();
|
|
221
|
+
break;
|
|
222
|
+
|
|
223
|
+
case "push": {
|
|
224
|
+
const roomIndex = args.indexOf("--room");
|
|
225
|
+
if (roomIndex === -1 || !args[roomIndex + 1] || !args[roomIndex + 2]) {
|
|
226
|
+
console.error("Usage: tnc push --room <roomId> <file-or-folder-path>");
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
const roomId = args[roomIndex + 1];
|
|
230
|
+
const targetPath = args[roomIndex + 2];
|
|
231
|
+
push(roomId, targetPath);
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
case "logout": {
|
|
235
|
+
logout();
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
default:
|
|
240
|
+
console.log("✅ TNC CLI ready!");
|
|
241
|
+
console.log("Commands:");
|
|
242
|
+
console.log(" tnc login");
|
|
243
|
+
console.log(" tnc push --room <roomId> <path>");
|
|
244
|
+
console.log(" tnc logout");
|
|
245
|
+
}
|
package/bin/tnc.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { program } from "commander";
|
|
3
|
+
import { pushToRoom } from "../lib/api.js";
|
|
4
|
+
|
|
5
|
+
program
|
|
6
|
+
.name("tnc")
|
|
7
|
+
.description("ThinkNCollab CLI tool")
|
|
8
|
+
.version("0.1.0");
|
|
9
|
+
|
|
10
|
+
program
|
|
11
|
+
.command("push")
|
|
12
|
+
.option("--room <roomId>", "Room ID to push files")
|
|
13
|
+
.action(async (opts) => {
|
|
14
|
+
if (!opts.room) {
|
|
15
|
+
console.error("❌ Please provide a room id using --room <id>");
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
await pushToRoom(opts.room);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
program.parse();
|
package/lib/api.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { scanDirectory } from "./scanner.js";
|
|
2
|
+
import { uploadFile } from "./uploader.js";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
|
|
6
|
+
export async function pushToRoom(roomId) {
|
|
7
|
+
const currentDir = process.cwd();
|
|
8
|
+
console.log(`📂 Scanning directory: ${currentDir}`);
|
|
9
|
+
|
|
10
|
+
const files = scanDirectory(currentDir);
|
|
11
|
+
|
|
12
|
+
const uploadedFiles = [];
|
|
13
|
+
for (let file of files) {
|
|
14
|
+
const url = await uploadFile(file);
|
|
15
|
+
uploadedFiles.push({
|
|
16
|
+
name: file.name,
|
|
17
|
+
path: file.path,
|
|
18
|
+
size: file.size,
|
|
19
|
+
url,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Instead of real backend, save JSON locally for now
|
|
24
|
+
const outputPath = path.join(currentDir, `tnc_snapshot_${roomId}.json`);
|
|
25
|
+
fs.writeFileSync(outputPath, JSON.stringify(uploadedFiles, null, 2));
|
|
26
|
+
|
|
27
|
+
console.log(`✅ Snapshot saved at ${outputPath}`);
|
|
28
|
+
}
|
package/lib/scanner.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
export function scanDirectory(dirPath) {
|
|
5
|
+
const result = [];
|
|
6
|
+
|
|
7
|
+
function walk(dir) {
|
|
8
|
+
const files = fs.readdirSync(dir);
|
|
9
|
+
for (let file of files) {
|
|
10
|
+
const fullPath = path.join(dir, file);
|
|
11
|
+
const stat = fs.statSync(fullPath);
|
|
12
|
+
|
|
13
|
+
if (stat.isDirectory()) {
|
|
14
|
+
walk(fullPath);
|
|
15
|
+
} else {
|
|
16
|
+
result.push({
|
|
17
|
+
name: file,
|
|
18
|
+
path: fullPath,
|
|
19
|
+
size: stat.size,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
walk(dirPath);
|
|
26
|
+
return result;
|
|
27
|
+
}
|
package/lib/uploader.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "thinkncollab-cli",
|
|
3
|
+
"author": "Raman Singh",
|
|
4
|
+
"version": "0.0.10.1",
|
|
5
|
+
"description": "CLI tool for ThinkNCollab",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"tnc-cli": "./bin/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node bin/index.js"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"axios": "^1.12.2",
|
|
16
|
+
"inquirer": "^9.3.8"
|
|
17
|
+
}
|
|
18
|
+
}
|