@dutodev/cli 1.1.0 → 1.3.0
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/README.md +1 -0
- package/dist/chunk-4SIBC52A.js +571 -0
- package/dist/{fullscreen-ECEH3R2V.js → fullscreen-AI6PC7RL.js} +11 -5
- package/dist/index.js +81 -77
- package/package.json +1 -1
- package/dist/chunk-76YCOMXH.js +0 -111
package/README.md
CHANGED
|
@@ -203,3 +203,4 @@ The page has the list on the left and the details on the right, with the tabs **
|
|
|
203
203
|
|
|
204
204
|
- Config lives in `~/.duto/config.json` (mode 600). `DUTO_HOME` changes that folder.
|
|
205
205
|
- `DUTO_API_URL` points the CLI at another server (default: `https://duto.dev`).
|
|
206
|
+
- Once a day, duto asks npm for the latest version and shows a line when there is a newer one. `DUTO_NO_UPDATE_CHECK=1` turns that off.
|
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// ../shared/src/names.ts
|
|
4
|
+
var EMAIL_SENDING_SUBDOMAINS = ["send"];
|
|
5
|
+
var RESERVED_NAMES = [
|
|
6
|
+
"www",
|
|
7
|
+
"api",
|
|
8
|
+
"app",
|
|
9
|
+
"dash",
|
|
10
|
+
"admin",
|
|
11
|
+
"docs",
|
|
12
|
+
"status",
|
|
13
|
+
"mail",
|
|
14
|
+
"custom",
|
|
15
|
+
"cname",
|
|
16
|
+
"duto",
|
|
17
|
+
"auth",
|
|
18
|
+
"login",
|
|
19
|
+
"signup",
|
|
20
|
+
"invite",
|
|
21
|
+
"static",
|
|
22
|
+
"assets",
|
|
23
|
+
"cdn",
|
|
24
|
+
"help",
|
|
25
|
+
"support",
|
|
26
|
+
"blog",
|
|
27
|
+
...EMAIL_SENDING_SUBDOMAINS
|
|
28
|
+
];
|
|
29
|
+
var MAX_NAME = 40;
|
|
30
|
+
var FORMAT = /^[a-z0-9]+$/;
|
|
31
|
+
function normalizeName(input) {
|
|
32
|
+
return input.trim().toLowerCase();
|
|
33
|
+
}
|
|
34
|
+
function validateName(name, max = MAX_NAME) {
|
|
35
|
+
if (name.length === 0 || name.length > max) return { ok: false, reason: "length" };
|
|
36
|
+
if (!FORMAT.test(name)) return { ok: false, reason: "format" };
|
|
37
|
+
if (RESERVED_NAMES.includes(name)) return { ok: false, reason: "reserved" };
|
|
38
|
+
return { ok: true };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ../shared/src/places.ts
|
|
42
|
+
var PLACES = {
|
|
43
|
+
AAE: ["Annaba", "Algeria"],
|
|
44
|
+
ABJ: ["Abidjan", "C\xF4te d\u2019Ivoire"],
|
|
45
|
+
ABQ: ["Albuquerque", "United States"],
|
|
46
|
+
ACC: ["Accra", "Ghana"],
|
|
47
|
+
ACX: ["Xingyi", "China"],
|
|
48
|
+
ADB: ["Izmir", "Turkey"],
|
|
49
|
+
ADD: ["Addis Ababa", "Ethiopia"],
|
|
50
|
+
ADL: ["Adelaide", "Australia"],
|
|
51
|
+
AGR: ["Agra", "India"],
|
|
52
|
+
AIP: ["Jalandhar", "India"],
|
|
53
|
+
AKL: ["Auckland", "New Zealand"],
|
|
54
|
+
AKX: ["Aktobe", "Kazakhstan"],
|
|
55
|
+
ALA: ["Almaty", "Kazakhstan"],
|
|
56
|
+
ALG: ["Algiers", "Algeria"],
|
|
57
|
+
AMD: ["Ahmedabad", "India"],
|
|
58
|
+
AMM: ["Amman", "Jordan"],
|
|
59
|
+
AMS: ["Amsterdam", "Netherlands"],
|
|
60
|
+
ANC: ["Anchorage", "United States"],
|
|
61
|
+
ARI: ["Arica", "Chile"],
|
|
62
|
+
ARN: ["Stockholm", "Sweden"],
|
|
63
|
+
ARU: ["Aracatuba", "Brazil"],
|
|
64
|
+
ASK: ["Yamoussoukro", "C\xF4te d\u2019Ivoire"],
|
|
65
|
+
ASU: ["Asunci\xF3n", "Paraguay"],
|
|
66
|
+
ATH: ["Athens", "Greece"],
|
|
67
|
+
ATL: ["Atlanta", "United States"],
|
|
68
|
+
AUS: ["Austin", "United States"],
|
|
69
|
+
AVA: ["Anshun", "China"],
|
|
70
|
+
BAH: ["Manama", "Bahrain"],
|
|
71
|
+
BAQ: ["Barranquilla", "Colombia"],
|
|
72
|
+
BBI: ["Bhubaneswar", "India"],
|
|
73
|
+
BCN: ["Barcelona", "Spain"],
|
|
74
|
+
BDQ: ["Jamnagar", "India"],
|
|
75
|
+
BEG: ["Belgrade", "Serbia"],
|
|
76
|
+
BEL: ["Bel\xE9m", "Brazil"],
|
|
77
|
+
BEY: ["Beirut", "Lebanon"],
|
|
78
|
+
BGI: ["Bridgetown", "Barbados"],
|
|
79
|
+
BGR: ["Bangor", "United States"],
|
|
80
|
+
BGW: ["Baghdad", "Iraq"],
|
|
81
|
+
BKK: ["Bangkok", "Thailand"],
|
|
82
|
+
BLR: ["Bangalore", "India"],
|
|
83
|
+
BNA: ["Nashville", "United States"],
|
|
84
|
+
BNE: ["Brisbane", "Australia"],
|
|
85
|
+
BOD: ["Bordeaux", "France"],
|
|
86
|
+
BOG: ["Bogot\xE1", "Colombia"],
|
|
87
|
+
BOM: ["Mumbai", "India"],
|
|
88
|
+
BOS: ["Boston", "United States"],
|
|
89
|
+
BRU: ["Brussels", "Belgium"],
|
|
90
|
+
BSB: ["Brasilia", "Brazil"],
|
|
91
|
+
BSR: ["Basra", "Iraq"],
|
|
92
|
+
BTS: ["Bratislava", "Slovakia"],
|
|
93
|
+
BUD: ["Budapest", "Hungary"],
|
|
94
|
+
BWN: ["Bandar Seri Begawan", "Brunei"],
|
|
95
|
+
CAI: ["Cairo", "Egypt"],
|
|
96
|
+
CAN: ["Guangzhou", "China"],
|
|
97
|
+
CAW: ["Campos dos Goytacazes", "Brazil"],
|
|
98
|
+
CBR: ["Canberra", "Australia"],
|
|
99
|
+
CCU: ["Kolkata", "India"],
|
|
100
|
+
CDG: ["Paris", "France"],
|
|
101
|
+
CEB: ["Cebu", "Philippines"],
|
|
102
|
+
CFC: ["Cacador", "Brazil"],
|
|
103
|
+
CGB: ["Cuiaba", "Brazil"],
|
|
104
|
+
CGD: ["Changde", "China"],
|
|
105
|
+
CGK: ["Jakarta", "Indonesia"],
|
|
106
|
+
CGO: ["Zhengzhou", "China"],
|
|
107
|
+
CGP: ["Chittagong", "Bangladesh"],
|
|
108
|
+
CGY: ["Cagayan de Oro", "Philippines"],
|
|
109
|
+
CHC: ["Christchurch", "New Zealand"],
|
|
110
|
+
CJB: ["Coimbatore", "India"],
|
|
111
|
+
CKG: ["Chongqing", "China"],
|
|
112
|
+
CLE: ["Cleveland", "United States"],
|
|
113
|
+
CLO: ["Cali", "Colombia"],
|
|
114
|
+
CLT: ["Charlotte", "United States"],
|
|
115
|
+
CMB: ["Colombo", "Sri Lanka"],
|
|
116
|
+
CMH: ["Columbus", "United States"],
|
|
117
|
+
CNF: ["Belo Horizonte", "Brazil"],
|
|
118
|
+
CNN: ["Kannur", "India"],
|
|
119
|
+
CNX: ["Chiang Mai", "Thailand"],
|
|
120
|
+
COK: ["Kochi", "India"],
|
|
121
|
+
COR: ["C\xF3rdoba", "Argentina"],
|
|
122
|
+
CPH: ["Copenhagen", "Denmark"],
|
|
123
|
+
CPT: ["Cape Town", "South Africa"],
|
|
124
|
+
CRK: ["Tarlac City", "Philippines"],
|
|
125
|
+
CSX: ["Changsha", "China"],
|
|
126
|
+
CTU: ["Chengdu", "China"],
|
|
127
|
+
CVG: ["Cincinnati", "United States"],
|
|
128
|
+
CWB: ["Curitiba", "Brazil"],
|
|
129
|
+
CZL: ["Constantine", "Algeria"],
|
|
130
|
+
CZX: ["Changzhou", "China"],
|
|
131
|
+
DAC: ["Dhaka", "Bangladesh"],
|
|
132
|
+
DAD: ["Da Nang", "Vietnam"],
|
|
133
|
+
DAR: ["Dar es Salaam", "Tanzania"],
|
|
134
|
+
DEL: ["New Delhi", "India"],
|
|
135
|
+
DEN: ["Denver", "United States"],
|
|
136
|
+
DFW: ["Dallas", "United States"],
|
|
137
|
+
DKR: ["Dakar", "Senegal"],
|
|
138
|
+
DLA: ["Douala", "Cameroon"],
|
|
139
|
+
DLC: ["Dalian", "China"],
|
|
140
|
+
DME: ["Moscow", "Russia"],
|
|
141
|
+
DMM: ["Dammam", "Saudi Arabia"],
|
|
142
|
+
DOH: ["Doha", "Qatar"],
|
|
143
|
+
DPS: ["Denpasar", "Indonesia"],
|
|
144
|
+
DTW: ["Detroit", "United States"],
|
|
145
|
+
DUB: ["Dublin", "Ireland"],
|
|
146
|
+
DUR: ["Durban", "South Africa"],
|
|
147
|
+
DUS: ["D\xFCsseldorf", "Germany"],
|
|
148
|
+
DXB: ["Dubai", "United Arab Emirates"],
|
|
149
|
+
DYU: ["Dushanbe", "Tajikistan"],
|
|
150
|
+
EBB: ["Kampala", "Uganda"],
|
|
151
|
+
EBL: ["Erbil", "Iraq"],
|
|
152
|
+
EVN: ["Yerevan", "Armenia"],
|
|
153
|
+
EWR: ["Newark", "United States"],
|
|
154
|
+
EZE: ["Buenos Aires", "Argentina"],
|
|
155
|
+
FCO: ["Rome", "Italy"],
|
|
156
|
+
FLN: ["Florianopolis", "Brazil"],
|
|
157
|
+
FOC: ["Fuzhou", "China"],
|
|
158
|
+
FOR: ["Fortaleza", "Brazil"],
|
|
159
|
+
FRA: ["Frankfurt", "Germany"],
|
|
160
|
+
FRU: ["Bishkek", "Kyrgyzstan"],
|
|
161
|
+
FSD: ["Sioux Falls", "United States"],
|
|
162
|
+
FUK: ["Fukuoka", "Japan"],
|
|
163
|
+
FUO: ["Foshan", "China"],
|
|
164
|
+
GBE: ["Gaborone", "Botswana"],
|
|
165
|
+
GDL: ["Guadalajara", "Mexico"],
|
|
166
|
+
GEO: ["Georgetown", "Guyana"],
|
|
167
|
+
GIG: ["Rio de Janeiro", "Brazil"],
|
|
168
|
+
GND: ["St. George's", "Grenada"],
|
|
169
|
+
GOT: ["Gothenburg", "Sweden"],
|
|
170
|
+
GRU: ["S\xE3o Paulo", "Brazil"],
|
|
171
|
+
GUA: ["Guatemala City", "Guatemala"],
|
|
172
|
+
GUM: ["Hagatna", "Guam"],
|
|
173
|
+
GVA: ["Geneva", "Switzerland"],
|
|
174
|
+
GYD: ["Baku", "Azerbaijan"],
|
|
175
|
+
GYE: ["Guayaquil", "Ecuador"],
|
|
176
|
+
GYN: ["Goiania", "Brazil"],
|
|
177
|
+
HAK: ["Chengmai", "China"],
|
|
178
|
+
HAM: ["Hamburg", "Germany"],
|
|
179
|
+
HAN: ["Hanoi", "Vietnam"],
|
|
180
|
+
HBA: ["Hobart", "Australia"],
|
|
181
|
+
HEL: ["Helsinki", "Finland"],
|
|
182
|
+
HFA: ["Haifa", "Israel"],
|
|
183
|
+
HGH: ["Shaoxing", "China"],
|
|
184
|
+
HKG: ["Hong Kong", null],
|
|
185
|
+
HNL: ["Honolulu", "United States"],
|
|
186
|
+
HRE: ["Harare", "Zimbabwe"],
|
|
187
|
+
HYD: ["Hyderabad", "India"],
|
|
188
|
+
HYN: ["Taizhou", "China"],
|
|
189
|
+
IAD: ["Ashburn", "United States"],
|
|
190
|
+
IAH: ["Houston", "United States"],
|
|
191
|
+
ICN: ["Seoul", "South Korea"],
|
|
192
|
+
IND: ["Indianapolis", "United States"],
|
|
193
|
+
ISB: ["Islamabad", "Pakistan"],
|
|
194
|
+
IST: ["Istanbul", "Turkey"],
|
|
195
|
+
ISU: ["Sulaymaniyah", "Iraq"],
|
|
196
|
+
IXC: ["Chandigarh", "India"],
|
|
197
|
+
JAI: ["\u201CJaipur\u201D", "India"],
|
|
198
|
+
JAX: ["Jacksonville", "United States"],
|
|
199
|
+
JDO: ["Juazeiro do Norte", "Brazil"],
|
|
200
|
+
JED: ["Jeddah", "Saudi Arabia"],
|
|
201
|
+
JHB: ["Johor Bahru", "Malaysia"],
|
|
202
|
+
JIB: ["Djibouti", "Djibouti"],
|
|
203
|
+
JNB: ["Johannesburg", "South Africa"],
|
|
204
|
+
JOG: ["Yogyakarta", "Indonesia"],
|
|
205
|
+
JOI: ["Joinville", "Brazil"],
|
|
206
|
+
JRG: ["Sambalpur", "India"],
|
|
207
|
+
JXG: ["Jiaxing", "China"],
|
|
208
|
+
KBP: ["Kyiv", "Ukraine"],
|
|
209
|
+
KCH: ["Kuching", "Malaysia"],
|
|
210
|
+
KEF: ["Reykjav\xEDk", "Iceland"],
|
|
211
|
+
KGL: ["Kigali", "Rwanda"],
|
|
212
|
+
KHH: ["Kaohsiung City", null],
|
|
213
|
+
KHI: ["Karachi", "Pakistan"],
|
|
214
|
+
KHN: ["Nanchang", "China"],
|
|
215
|
+
KIN: ["Kingston", "Jamaica"],
|
|
216
|
+
KIV: ["Chi\u0219in\u0103u", "Moldova"],
|
|
217
|
+
KIX: ["Osaka", "Japan"],
|
|
218
|
+
KMG: ["Kunming", "China"],
|
|
219
|
+
KNU: ["Kanpur", "India"],
|
|
220
|
+
KTM: ["Kathmandu", "Nepal"],
|
|
221
|
+
KUL: ["Kuala Lumpur", "Malaysia"],
|
|
222
|
+
KWE: ["Guiyang", "China"],
|
|
223
|
+
KWI: ["Kuwait City", "Kuwait"],
|
|
224
|
+
LAD: ["Luanda", "Angola"],
|
|
225
|
+
LAS: ["Las Vegas", "United States"],
|
|
226
|
+
LAX: ["Los Angeles", "United States"],
|
|
227
|
+
LCA: ["Nicosia", "Cyprus"],
|
|
228
|
+
LED: ["Saint Petersburg", "Russia"],
|
|
229
|
+
LHE: ["Lahore", "Pakistan"],
|
|
230
|
+
LHR: ["London", "United Kingdom"],
|
|
231
|
+
LHW: ["Lanzhou", "China"],
|
|
232
|
+
LIM: ["Lima", "Peru"],
|
|
233
|
+
LIS: ["Lisbon", "Portugal"],
|
|
234
|
+
LJU: ["Ljubljana", "Slovenia"],
|
|
235
|
+
LLK: ["Astara", "Azerbaijan"],
|
|
236
|
+
LLW: ["Lilongwe", "Malawi"],
|
|
237
|
+
LOS: ["Lagos", "Nigeria"],
|
|
238
|
+
LPB: ["La Paz", "Bolivia"],
|
|
239
|
+
LUH: ["Ludhiana", "India"],
|
|
240
|
+
LUN: ["Lusaka", "Zambia"],
|
|
241
|
+
LUX: ["Luxembourg City", "Luxembourg"],
|
|
242
|
+
LYA: ["Luoyang", "China"],
|
|
243
|
+
LYS: ["Lyon", "France"],
|
|
244
|
+
MAA: ["Chennai", "India"],
|
|
245
|
+
MAD: ["Madrid", "Spain"],
|
|
246
|
+
MAN: ["Manchester", "United Kingdom"],
|
|
247
|
+
MAO: ["Manaus", "Brazil"],
|
|
248
|
+
MBA: ["Mombasa", "Kenya"],
|
|
249
|
+
MCI: ["Kansas City", "United States"],
|
|
250
|
+
MCT: ["Muscat", "Oman"],
|
|
251
|
+
MDE: ["Medell\xEDn", "Colombia"],
|
|
252
|
+
MEL: ["Melbourne", "Australia"],
|
|
253
|
+
MEM: ["Memphis", "United States"],
|
|
254
|
+
MEX: ["Mexico City", "Mexico"],
|
|
255
|
+
MFM: ["Macau", null],
|
|
256
|
+
MIA: ["Miami", "United States"],
|
|
257
|
+
MLA: ["Valletta", "Malta"],
|
|
258
|
+
MLE: ["Male", "Maldives"],
|
|
259
|
+
MLG: ["Malang", "Indonesia"],
|
|
260
|
+
MNL: ["Manila", "Philippines"],
|
|
261
|
+
MPM: ["Maputo", "Mozambique"],
|
|
262
|
+
MRS: ["Marseille", "France"],
|
|
263
|
+
MRU: ["Port Louis", "Mauritius"],
|
|
264
|
+
MSP: ["Minneapolis", "United States"],
|
|
265
|
+
MSQ: ["Minsk", "Belarus"],
|
|
266
|
+
MUC: ["Munich", "Germany"],
|
|
267
|
+
MXP: ["Milan", "Italy"],
|
|
268
|
+
NAG: ["Nagpur", "India"],
|
|
269
|
+
NBO: ["Nairobi", "Kenya"],
|
|
270
|
+
NJF: ["Najaf", "Iraq"],
|
|
271
|
+
NOU: ["Noumea", "New Caledonia"],
|
|
272
|
+
NQN: ["Neuquen", "Argentina"],
|
|
273
|
+
NQZ: ["Astana", "Kazakhstan"],
|
|
274
|
+
NRT: ["Tokyo", "Japan"],
|
|
275
|
+
NVT: ["Timbo", "Brazil"],
|
|
276
|
+
OKA: ["Naha", "Japan"],
|
|
277
|
+
OKC: ["Oklahoma City", "United States"],
|
|
278
|
+
OMA: ["Omaha", "United States"],
|
|
279
|
+
ORD: ["Chicago", "United States"],
|
|
280
|
+
ORF: ["Norfolk", "United States"],
|
|
281
|
+
ORN: ["Oran", "Algeria"],
|
|
282
|
+
OSL: ["Oslo", "Norway"],
|
|
283
|
+
OTP: ["Bucharest", "Romania"],
|
|
284
|
+
OUA: ["Ouagadougou", "Burkina Faso"],
|
|
285
|
+
PAT: ["Patna", "India"],
|
|
286
|
+
PBH: ["Thimphu", "Bhutan"],
|
|
287
|
+
PBM: ["Paramaribo", "Suriname"],
|
|
288
|
+
PDX: ["Portland", "United States"],
|
|
289
|
+
PER: ["Perth", "Australia"],
|
|
290
|
+
PHL: ["Philadelphia", "United States"],
|
|
291
|
+
PHX: ["Phoenix", "United States"],
|
|
292
|
+
PIT: ["Pittsburgh", "United States"],
|
|
293
|
+
PKX: ["Beijing", "China"],
|
|
294
|
+
PMO: ["Palermo", "Italy"],
|
|
295
|
+
PMW: ["Palmas", "Brazil"],
|
|
296
|
+
PNH: ["Phnom Penh", "Cambodia"],
|
|
297
|
+
PNQ: ["Pune", "India"],
|
|
298
|
+
POA: ["Porto Alegre", "Brazil"],
|
|
299
|
+
POS: ["Port of Spain", "Trinidad & Tobago"],
|
|
300
|
+
PPT: ["Tahiti", "French Polynesia"],
|
|
301
|
+
PRG: ["Prague", "Czechia"],
|
|
302
|
+
PRN: ["Prishtine", "Kosovo"],
|
|
303
|
+
PTY: ["Panama City", "Panama"],
|
|
304
|
+
QRO: ["Queretaro", "Mexico"],
|
|
305
|
+
QWJ: ["Americana", "Brazil"],
|
|
306
|
+
RAO: ["Ribeirao Preto", "Brazil"],
|
|
307
|
+
RDU: ["Durham", "United States"],
|
|
308
|
+
REC: ["Recife", "Brazil"],
|
|
309
|
+
RIC: ["Richmond", "United States"],
|
|
310
|
+
RIX: ["Riga", "Latvia"],
|
|
311
|
+
RUH: ["Riyadh", "Saudi Arabia"],
|
|
312
|
+
RUN: ["Saint-Denis", "R\xE9union"],
|
|
313
|
+
SAN: ["San Diego", "United States"],
|
|
314
|
+
SAP: ["San Pedro Sula", "Honduras"],
|
|
315
|
+
SAT: ["San Antonio", "United States"],
|
|
316
|
+
SCL: ["Santiago", "Chile"],
|
|
317
|
+
SDQ: ["Santo Domingo", "Dominican Republic"],
|
|
318
|
+
SEA: ["Seattle", "United States"],
|
|
319
|
+
SFO: ["San Francisco", "United States"],
|
|
320
|
+
SGN: ["Ho Chi Minh City", "Vietnam"],
|
|
321
|
+
SHA: ["Shanghai", "China"],
|
|
322
|
+
SIN: ["Singapore", "Singapore"],
|
|
323
|
+
SJC: ["San Jose", "United States"],
|
|
324
|
+
SJK: ["S\xE3o Jos\xE9 dos Campos", "Brazil"],
|
|
325
|
+
SJO: ["San Jos\xE9", "Costa Rica"],
|
|
326
|
+
SJP: ["S\xE3o Jos\xE9 do Rio Preto", "Brazil"],
|
|
327
|
+
SJU: ["San Juan", "Puerto Rico"],
|
|
328
|
+
SJW: ["Hengshui", "China"],
|
|
329
|
+
SKG: ["Thessaloniki", "Greece"],
|
|
330
|
+
SKP: ["Skopje", "Macedonia"],
|
|
331
|
+
SLC: ["Salt Lake City", "United States"],
|
|
332
|
+
SMF: ["Sacramento", "United States"],
|
|
333
|
+
SOD: ["Sorocaba", "Brazil"],
|
|
334
|
+
SOF: ["Sofia", "Bulgaria"],
|
|
335
|
+
SSA: ["Salvador", "Brazil"],
|
|
336
|
+
STI: ["Santiago de los Caballeros", "Dominican Republic"],
|
|
337
|
+
STL: ["St. Louis", "United States"],
|
|
338
|
+
STR: ["Stuttgart", "Germany"],
|
|
339
|
+
SUV: ["Suva", "Fiji"],
|
|
340
|
+
SYD: ["Sydney", "Australia"],
|
|
341
|
+
SZX: ["Shenzhen", "China"],
|
|
342
|
+
TAO: ["Qingdao", "China"],
|
|
343
|
+
TBS: ["Tbilisi", "Georgia"],
|
|
344
|
+
TEN: ["Tongren", "China"],
|
|
345
|
+
TGU: ["Tegucigalpa", "Honduras"],
|
|
346
|
+
TIA: ["Tirana", "Albania"],
|
|
347
|
+
TLH: ["Tallahassee", "United States"],
|
|
348
|
+
TLL: ["Tallinn", "Estonia"],
|
|
349
|
+
TLV: ["Tel Aviv", "Israel"],
|
|
350
|
+
TNA: ["Jinan", "China"],
|
|
351
|
+
TNR: ["Antananarivo", "Madagascar"],
|
|
352
|
+
TPA: ["Tampa", "United States"],
|
|
353
|
+
TPE: ["Taipei", null],
|
|
354
|
+
TUN: ["Tunis", "Tunisia"],
|
|
355
|
+
TXL: ["Berlin", "Germany"],
|
|
356
|
+
TYN: ["Yangquan", "China"],
|
|
357
|
+
UDI: ["Uberlandia", "Brazil"],
|
|
358
|
+
UDR: ["Udaipur", "India"],
|
|
359
|
+
UIO: ["Quito", "Ecuador"],
|
|
360
|
+
ULN: ["Ulaanbaatar", "Mongolia"],
|
|
361
|
+
URT: ["Surat Thani", "Thailand"],
|
|
362
|
+
VCP: ["Campinas", "Brazil"],
|
|
363
|
+
VIE: ["Vienna", "Austria"],
|
|
364
|
+
VIX: ["Vitoria", "Brazil"],
|
|
365
|
+
VNO: ["Vilnius", "Lithuania"],
|
|
366
|
+
VTE: ["Vientiane", "Laos"],
|
|
367
|
+
WAW: ["Warsaw", "Poland"],
|
|
368
|
+
WDH: ["Windhoek", "Namibia"],
|
|
369
|
+
WLG: ["Wellington", "New Zealand"],
|
|
370
|
+
WRO: ["Wroclaw", "Poland"],
|
|
371
|
+
XAP: ["Chapeco", "Brazil"],
|
|
372
|
+
XFN: ["Xiangyang", "China"],
|
|
373
|
+
XIY: ["Baoji", "China"],
|
|
374
|
+
XNH: ["Nasiriyah", "Iraq"],
|
|
375
|
+
YHZ: ["Halifax", "Canada"],
|
|
376
|
+
YUL: ["Montr\xE9al", "Canada"],
|
|
377
|
+
YVR: ["Vancouver", "Canada"],
|
|
378
|
+
YWG: ["Winnipeg", "Canada"],
|
|
379
|
+
YXE: ["Saskatoon", "Canada"],
|
|
380
|
+
YYC: ["Calgary", "Canada"],
|
|
381
|
+
YYZ: ["Toronto", "Canada"],
|
|
382
|
+
ZAG: ["Zagreb", "Croatia"],
|
|
383
|
+
ZDM: ["Ramallah", null],
|
|
384
|
+
ZRH: ["Zurich", "Switzerland"]
|
|
385
|
+
};
|
|
386
|
+
function placeOf(colo) {
|
|
387
|
+
const entry = colo ? PLACES[colo] : void 0;
|
|
388
|
+
return entry ? { city: entry[0], country: entry[1] } : null;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// ../shared/src/cliVersion.ts
|
|
392
|
+
var CLI_VERSION_HEADER = "x-duto-cli-version";
|
|
393
|
+
var CLI_USER_AGENT = "duto-cli";
|
|
394
|
+
var CLI_UPDATE_COMMAND = "npm i -g @dutodev/cli";
|
|
395
|
+
var VERSION = /^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/;
|
|
396
|
+
function parts(version) {
|
|
397
|
+
const m = VERSION.exec(version.trim());
|
|
398
|
+
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
|
399
|
+
}
|
|
400
|
+
function compareVersions(a, b) {
|
|
401
|
+
const x = parts(a);
|
|
402
|
+
const y = parts(b);
|
|
403
|
+
if (!x || !y) return null;
|
|
404
|
+
for (let i = 0; i < 3; i++) {
|
|
405
|
+
const d = (x[i] ?? 0) - (y[i] ?? 0);
|
|
406
|
+
if (d !== 0) return d;
|
|
407
|
+
}
|
|
408
|
+
return 0;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// src/config.ts
|
|
412
|
+
import { randomUUID } from "crypto";
|
|
413
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
414
|
+
import { homedir } from "os";
|
|
415
|
+
import { join } from "path";
|
|
416
|
+
function dutoHome() {
|
|
417
|
+
return process.env.DUTO_HOME || join(homedir(), ".duto");
|
|
418
|
+
}
|
|
419
|
+
function configPath() {
|
|
420
|
+
return join(dutoHome(), "config.json");
|
|
421
|
+
}
|
|
422
|
+
function apiUrl() {
|
|
423
|
+
return (process.env.DUTO_API_URL || "https://duto.dev").replace(/\/+$/, "");
|
|
424
|
+
}
|
|
425
|
+
function saveConfig(c) {
|
|
426
|
+
mkdirSync(dutoHome(), { recursive: true, mode: 448 });
|
|
427
|
+
const path = configPath();
|
|
428
|
+
writeFileSync(path, JSON.stringify(c, null, 2) + "\n", { mode: 384 });
|
|
429
|
+
chmodSync(path, 384);
|
|
430
|
+
}
|
|
431
|
+
function loadConfig() {
|
|
432
|
+
const path = configPath();
|
|
433
|
+
if (existsSync(path)) {
|
|
434
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
435
|
+
if (parsed.deviceId) return parsed;
|
|
436
|
+
const fixed = { ...parsed, deviceId: randomUUID() };
|
|
437
|
+
saveConfig(fixed);
|
|
438
|
+
return fixed;
|
|
439
|
+
}
|
|
440
|
+
const fresh = { deviceId: randomUUID() };
|
|
441
|
+
saveConfig(fresh);
|
|
442
|
+
return fresh;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// src/updates.ts
|
|
446
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
447
|
+
import { join as join2 } from "path";
|
|
448
|
+
var LATEST_URL = "https://registry.npmjs.org/@dutodev/cli/latest";
|
|
449
|
+
var CHECK_EVERY_MS = 24 * 60 * 60 * 1e3;
|
|
450
|
+
var TIMEOUT_MS = 3e3;
|
|
451
|
+
async function fetchLatest(fetchImpl) {
|
|
452
|
+
const res = await fetchImpl(LATEST_URL, { headers: { accept: "application/json" }, signal: AbortSignal.timeout(TIMEOUT_MS) });
|
|
453
|
+
if (!res.ok) return null;
|
|
454
|
+
const { version } = await res.json();
|
|
455
|
+
return typeof version === "string" && compareVersions(version, version) === 0 ? version : null;
|
|
456
|
+
}
|
|
457
|
+
async function availableUpdate(o) {
|
|
458
|
+
const cached = o.read();
|
|
459
|
+
let latest = cached?.latest ?? null;
|
|
460
|
+
if (!cached || o.now - cached.checkedAt >= CHECK_EVERY_MS) {
|
|
461
|
+
latest = await fetchLatest(o.fetchImpl ?? fetch).catch(() => null) ?? latest;
|
|
462
|
+
o.write({ checkedAt: o.now, latest });
|
|
463
|
+
}
|
|
464
|
+
const diff = latest ? compareVersions(latest, o.current) : null;
|
|
465
|
+
return diff !== null && diff > 0 ? latest : null;
|
|
466
|
+
}
|
|
467
|
+
var cachePath = () => join2(dutoHome(), "update.json");
|
|
468
|
+
function readUpdateCache() {
|
|
469
|
+
try {
|
|
470
|
+
const c = JSON.parse(readFileSync2(cachePath(), "utf8"));
|
|
471
|
+
return typeof c.checkedAt === "number" ? { checkedAt: c.checkedAt, latest: typeof c.latest === "string" ? c.latest : null } : null;
|
|
472
|
+
} catch {
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
function writeUpdateCache(cache) {
|
|
477
|
+
try {
|
|
478
|
+
mkdirSync2(dutoHome(), { recursive: true, mode: 448 });
|
|
479
|
+
writeFileSync2(cachePath(), JSON.stringify(cache) + "\n", { mode: 384 });
|
|
480
|
+
} catch {
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
function checkForUpdate(current) {
|
|
485
|
+
if (process.env.DUTO_NO_UPDATE_CHECK) return Promise.resolve(null);
|
|
486
|
+
return availableUpdate({ current, now: Date.now(), read: readUpdateCache, write: writeUpdateCache }).catch(() => null);
|
|
487
|
+
}
|
|
488
|
+
function updateLine(latest) {
|
|
489
|
+
return `duto ${latest} is available. Update: ${CLI_UPDATE_COMMAND}`;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// src/ui/theme.ts
|
|
493
|
+
var C = {
|
|
494
|
+
signal: "#2dd4bf",
|
|
495
|
+
muted: "#8a8f8b",
|
|
496
|
+
strong: "#f3f4f1",
|
|
497
|
+
ok: "#4ade80",
|
|
498
|
+
warn: "#facc15",
|
|
499
|
+
err: "#f87171"
|
|
500
|
+
};
|
|
501
|
+
var TUNNEL_COLORS = ["#93c5fd", "#c4b5fd", "#facc15", "#4ade80", "#f9a8d4", "#fdba74", "#67e8f9"];
|
|
502
|
+
function tunnelColor(index) {
|
|
503
|
+
return TUNNEL_COLORS[index % TUNNEL_COLORS.length];
|
|
504
|
+
}
|
|
505
|
+
function fit(text, width) {
|
|
506
|
+
if (width <= 0) return "";
|
|
507
|
+
return text.length > width ? `${text.slice(0, Math.max(0, width - 1))}\u2026` : text.padEnd(width);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// src/session/bodyNote.ts
|
|
511
|
+
var TRUNCATED_NOTE = "Body cut at 64 KB.";
|
|
512
|
+
function bodyNote(r, side) {
|
|
513
|
+
if (side === "req") {
|
|
514
|
+
if (r.reqBodyBytes === 0) return "No body.";
|
|
515
|
+
if (r.reqBody === null && !r.reqTruncated) return "Binary body; not stored.";
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
if (r.bytes === 0) return "No body.";
|
|
519
|
+
if (r.resBody === null) return "Binary body; not stored.";
|
|
520
|
+
return null;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// src/ui/format.ts
|
|
524
|
+
function statusColor(status) {
|
|
525
|
+
if (status === null) return "muted";
|
|
526
|
+
if (status === 101) return "signal";
|
|
527
|
+
if (status >= 500) return "err";
|
|
528
|
+
if (status >= 400) return "warn";
|
|
529
|
+
if (status >= 300) return "muted";
|
|
530
|
+
return "ok";
|
|
531
|
+
}
|
|
532
|
+
function formatDuration(ms) {
|
|
533
|
+
return ms < 1e3 ? `${Math.round(ms)}ms` : `${(ms / 1e3).toFixed(1)}s`;
|
|
534
|
+
}
|
|
535
|
+
function formatBytes(n) {
|
|
536
|
+
if (n < 1024) return `${n} B`;
|
|
537
|
+
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
|
|
538
|
+
return `${(n / 1024 / 1024).toFixed(1)} MB`;
|
|
539
|
+
}
|
|
540
|
+
function formatTime(ms) {
|
|
541
|
+
const d = new Date(ms);
|
|
542
|
+
return [d.getHours(), d.getMinutes(), d.getSeconds()].map((n) => String(n).padStart(2, "0")).join(":");
|
|
543
|
+
}
|
|
544
|
+
function localLabel(t) {
|
|
545
|
+
return t.host ? `localhost:${t.port} (Host: ${t.host})` : `localhost:${t.port}`;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
export {
|
|
549
|
+
MAX_NAME,
|
|
550
|
+
normalizeName,
|
|
551
|
+
validateName,
|
|
552
|
+
placeOf,
|
|
553
|
+
CLI_VERSION_HEADER,
|
|
554
|
+
CLI_USER_AGENT,
|
|
555
|
+
dutoHome,
|
|
556
|
+
apiUrl,
|
|
557
|
+
saveConfig,
|
|
558
|
+
loadConfig,
|
|
559
|
+
C,
|
|
560
|
+
tunnelColor,
|
|
561
|
+
fit,
|
|
562
|
+
TRUNCATED_NOTE,
|
|
563
|
+
bodyNote,
|
|
564
|
+
statusColor,
|
|
565
|
+
formatDuration,
|
|
566
|
+
formatBytes,
|
|
567
|
+
formatTime,
|
|
568
|
+
localLabel,
|
|
569
|
+
checkForUpdate,
|
|
570
|
+
updateLine
|
|
571
|
+
};
|
|
@@ -12,8 +12,9 @@ import {
|
|
|
12
12
|
normalizeName,
|
|
13
13
|
statusColor,
|
|
14
14
|
tunnelColor,
|
|
15
|
+
updateLine,
|
|
15
16
|
validateName
|
|
16
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-4SIBC52A.js";
|
|
17
18
|
|
|
18
19
|
// src/ui/fullscreen.tsx
|
|
19
20
|
import { render } from "ink";
|
|
@@ -284,7 +285,7 @@ var KEYS = [
|
|
|
284
285
|
["tab", "focus"],
|
|
285
286
|
["q", "quit"]
|
|
286
287
|
];
|
|
287
|
-
function App({ session, version, system, onExit, inspector = null, inspectorError }) {
|
|
288
|
+
function App({ session, version, system, onExit, inspector = null, inspectorError, update = null }) {
|
|
288
289
|
const subState = useCallback((fn) => session.subscribe(fn), [session]);
|
|
289
290
|
const subRecords = useCallback((fn) => session.records.subscribe(fn), [session]);
|
|
290
291
|
const state = useSyncExternalStore(subState, () => session.getState());
|
|
@@ -435,7 +436,7 @@ function App({ session, version, system, onExit, inspector = null, inspectorErro
|
|
|
435
436
|
});
|
|
436
437
|
const live = state.tunnels.filter((t) => t.status === "live").length;
|
|
437
438
|
const conn = state.connection === "online" ? { text: "connected \u25CF", color: C.signal } : state.connection === "connecting" ? { text: "connecting\u2026", color: C.muted } : { text: "reconnecting\u2026", color: C.warn };
|
|
438
|
-
const listHeight = Math.max(3, rows - state.tunnels.length - (inspector ? 10 : 9));
|
|
439
|
+
const listHeight = Math.max(3, rows - state.tunnels.length - (inspector ? 10 : 9) - (update ? 1 : 0));
|
|
439
440
|
const detailRecord = mode.kind === "detail" ? all.find((r) => r.id === mode.id) : void 0;
|
|
440
441
|
const qrTunnel = mode.kind === "qr" ? state.tunnels.find((t) => t.key === mode.key) : void 0;
|
|
441
442
|
return /* @__PURE__ */ jsxs6(Box5, { flexDirection: "column", width, children: [
|
|
@@ -460,6 +461,7 @@ function App({ session, version, system, onExit, inspector = null, inspectorErro
|
|
|
460
461
|
] }),
|
|
461
462
|
/* @__PURE__ */ jsx6(Text6, { color: conn.color, children: conn.text })
|
|
462
463
|
] }),
|
|
464
|
+
update && /* @__PURE__ */ jsx6(Text6, { color: C.warn, children: updateLine(update) }),
|
|
463
465
|
/* @__PURE__ */ jsx6(Text6, { children: " " }),
|
|
464
466
|
/* @__PURE__ */ jsx6(TunnelTable, { tunnels: state.tunnels, selected: selTun, focused: focus === "tunnels" }),
|
|
465
467
|
inspector && /* @__PURE__ */ jsx6(Text6, { color: C.muted, children: `inspector ${inspector.url}` }),
|
|
@@ -548,7 +550,7 @@ var realSystem = {
|
|
|
548
550
|
|
|
549
551
|
// src/ui/fullscreen.tsx
|
|
550
552
|
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
551
|
-
function runFullscreen(session, specs, version, inspector, inspectorError) {
|
|
553
|
+
function runFullscreen(session, specs, version, inspector, inspectorError, update = Promise.resolve(null)) {
|
|
552
554
|
return withFullscreen(
|
|
553
555
|
process.stdout,
|
|
554
556
|
(signal) => new Promise((resolve) => {
|
|
@@ -559,7 +561,11 @@ function runFullscreen(session, specs, version, inspector, inspectorError) {
|
|
|
559
561
|
app.unmount();
|
|
560
562
|
resolve(code);
|
|
561
563
|
};
|
|
562
|
-
const
|
|
564
|
+
const view = (latest) => /* @__PURE__ */ jsx7(App, { session, version, system: realSystem, inspector, inspectorError, update: latest, onExit: () => finish(0) });
|
|
565
|
+
const app = render(view(null), { exitOnCtrlC: false });
|
|
566
|
+
void update.then((latest) => {
|
|
567
|
+
if (latest && !done) app.rerender(view(latest));
|
|
568
|
+
});
|
|
563
569
|
signal.addEventListener("abort", () => void session.stop().then(() => finish(0)), { once: true });
|
|
564
570
|
session.start(specs).catch(() => void 0);
|
|
565
571
|
})
|
package/dist/index.js
CHANGED
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
C,
|
|
4
|
+
CLI_USER_AGENT,
|
|
5
|
+
CLI_VERSION_HEADER,
|
|
4
6
|
MAX_NAME,
|
|
5
7
|
TRUNCATED_NOTE,
|
|
8
|
+
apiUrl,
|
|
6
9
|
bodyNote,
|
|
10
|
+
checkForUpdate,
|
|
11
|
+
dutoHome,
|
|
7
12
|
formatBytes,
|
|
8
13
|
formatDuration,
|
|
9
14
|
formatTime,
|
|
15
|
+
loadConfig,
|
|
10
16
|
localLabel,
|
|
11
17
|
normalizeName,
|
|
18
|
+
placeOf,
|
|
19
|
+
saveConfig,
|
|
12
20
|
statusColor,
|
|
21
|
+
updateLine,
|
|
13
22
|
validateName
|
|
14
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-4SIBC52A.js";
|
|
15
24
|
|
|
16
25
|
// src/ui/brand.ts
|
|
17
26
|
function colorOn(stream = process.stdout) {
|
|
@@ -37,9 +46,12 @@ var warn = (msg) => `${paint("warn", "\u26A0")} ${msg}`;
|
|
|
37
46
|
|
|
38
47
|
// src/index.ts
|
|
39
48
|
import { hostname as hostname2 } from "os";
|
|
40
|
-
import { join as
|
|
49
|
+
import { join as join5, relative } from "path";
|
|
41
50
|
import cac from "cac";
|
|
42
51
|
|
|
52
|
+
// src/version.ts
|
|
53
|
+
var CLI_VERSION = "1.3.0";
|
|
54
|
+
|
|
43
55
|
// src/api.ts
|
|
44
56
|
var DutoApiError = class extends Error {
|
|
45
57
|
constructor(message2, status, code) {
|
|
@@ -51,6 +63,7 @@ var DutoApiError = class extends Error {
|
|
|
51
63
|
status;
|
|
52
64
|
code;
|
|
53
65
|
};
|
|
66
|
+
var BOOKMARK_HEADER = "x-d1-bookmark";
|
|
54
67
|
var MESSAGES = {
|
|
55
68
|
unauthorized: "This machine is not logged in. Run: duto login",
|
|
56
69
|
suspended: "Your account is suspended.",
|
|
@@ -59,6 +72,7 @@ var MESSAGES = {
|
|
|
59
72
|
};
|
|
60
73
|
function createApiClient(o) {
|
|
61
74
|
const doFetch = o.fetchImpl ?? fetch;
|
|
75
|
+
let bookmark = null;
|
|
62
76
|
async function call(method, path, body) {
|
|
63
77
|
let res;
|
|
64
78
|
try {
|
|
@@ -67,13 +81,16 @@ function createApiClient(o) {
|
|
|
67
81
|
headers: {
|
|
68
82
|
...o.token === void 0 ? {} : { authorization: `Bearer ${o.token}` },
|
|
69
83
|
...body === void 0 ? {} : { "content-type": "application/json" },
|
|
70
|
-
|
|
84
|
+
...bookmark === null ? {} : { [BOOKMARK_HEADER]: bookmark },
|
|
85
|
+
"user-agent": CLI_USER_AGENT,
|
|
86
|
+
[CLI_VERSION_HEADER]: CLI_VERSION
|
|
71
87
|
},
|
|
72
88
|
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
73
89
|
});
|
|
74
90
|
} catch {
|
|
75
91
|
throw new DutoApiError(MESSAGES.network, 0, "network");
|
|
76
92
|
}
|
|
93
|
+
bookmark = res.headers.get(BOOKMARK_HEADER) ?? bookmark;
|
|
77
94
|
return res;
|
|
78
95
|
}
|
|
79
96
|
async function fail3(res) {
|
|
@@ -113,8 +130,8 @@ function createApiClient(o) {
|
|
|
113
130
|
|
|
114
131
|
// src/cloudflared.ts
|
|
115
132
|
import { createHash } from "crypto";
|
|
116
|
-
import { chmodSync
|
|
117
|
-
import { join
|
|
133
|
+
import { chmodSync, existsSync, mkdirSync, mkdtempSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
134
|
+
import { join } from "path";
|
|
118
135
|
import { extract } from "tar";
|
|
119
136
|
|
|
120
137
|
// src/checksums.ts
|
|
@@ -127,40 +144,6 @@ var CHECKSUMS = {
|
|
|
127
144
|
"cloudflared-windows-amd64.exe": "2493dbc168923bb3e59e5e7716f71deced92992eea9acd6e9d74f84ac7751343"
|
|
128
145
|
};
|
|
129
146
|
|
|
130
|
-
// src/config.ts
|
|
131
|
-
import { randomUUID } from "crypto";
|
|
132
|
-
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
133
|
-
import { homedir } from "os";
|
|
134
|
-
import { join } from "path";
|
|
135
|
-
function dutoHome() {
|
|
136
|
-
return process.env.DUTO_HOME || join(homedir(), ".duto");
|
|
137
|
-
}
|
|
138
|
-
function configPath() {
|
|
139
|
-
return join(dutoHome(), "config.json");
|
|
140
|
-
}
|
|
141
|
-
function apiUrl() {
|
|
142
|
-
return (process.env.DUTO_API_URL || "https://duto.dev").replace(/\/+$/, "");
|
|
143
|
-
}
|
|
144
|
-
function saveConfig(c) {
|
|
145
|
-
mkdirSync(dutoHome(), { recursive: true, mode: 448 });
|
|
146
|
-
const path = configPath();
|
|
147
|
-
writeFileSync(path, JSON.stringify(c, null, 2) + "\n", { mode: 384 });
|
|
148
|
-
chmodSync(path, 384);
|
|
149
|
-
}
|
|
150
|
-
function loadConfig() {
|
|
151
|
-
const path = configPath();
|
|
152
|
-
if (existsSync(path)) {
|
|
153
|
-
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
154
|
-
if (parsed.deviceId) return parsed;
|
|
155
|
-
const fixed = { ...parsed, deviceId: randomUUID() };
|
|
156
|
-
saveConfig(fixed);
|
|
157
|
-
return fixed;
|
|
158
|
-
}
|
|
159
|
-
const fresh = { deviceId: randomUUID() };
|
|
160
|
-
saveConfig(fresh);
|
|
161
|
-
return fresh;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
147
|
// src/cloudflared.ts
|
|
165
148
|
function assetFor(platform, arch) {
|
|
166
149
|
const key = `${platform}/${arch}`;
|
|
@@ -185,11 +168,11 @@ function downloadUrl(asset, version = CLOUDFLARED_VERSION) {
|
|
|
185
168
|
async function ensureCloudflared(opts = {}) {
|
|
186
169
|
const platform = opts.platform ?? process.platform;
|
|
187
170
|
const { asset, kind } = assetFor(platform, opts.arch ?? process.arch);
|
|
188
|
-
const binDir =
|
|
189
|
-
const target =
|
|
190
|
-
if (
|
|
171
|
+
const binDir = join(dutoHome(), "bin");
|
|
172
|
+
const target = join(binDir, `cloudflared-${CLOUDFLARED_VERSION}${platform === "win32" ? ".exe" : ""}`);
|
|
173
|
+
if (existsSync(target)) return target;
|
|
191
174
|
opts.onDownload?.();
|
|
192
|
-
|
|
175
|
+
mkdirSync(binDir, { recursive: true });
|
|
193
176
|
const res = await (opts.fetchImpl ?? fetch)(downloadUrl(asset));
|
|
194
177
|
if (!res.ok) throw new Error(`Could not download cloudflared (HTTP ${res.status}).`);
|
|
195
178
|
const bytes = Buffer.from(await res.arrayBuffer());
|
|
@@ -199,22 +182,22 @@ async function ensureCloudflared(opts = {}) {
|
|
|
199
182
|
rmSync(target, { force: true });
|
|
200
183
|
throw new Error("cloudflared checksum mismatch. Try again; if it keeps failing, update duto.");
|
|
201
184
|
}
|
|
202
|
-
const tmp = mkdtempSync(
|
|
185
|
+
const tmp = mkdtempSync(join(binDir, ".download-"));
|
|
203
186
|
try {
|
|
204
187
|
if (kind === "tgz") {
|
|
205
|
-
const tgz =
|
|
206
|
-
|
|
188
|
+
const tgz = join(tmp, asset);
|
|
189
|
+
writeFileSync(tgz, bytes);
|
|
207
190
|
await extract({ file: tgz, cwd: tmp });
|
|
208
|
-
renameSync(
|
|
191
|
+
renameSync(join(tmp, "cloudflared"), target);
|
|
209
192
|
} else {
|
|
210
|
-
const file =
|
|
211
|
-
|
|
193
|
+
const file = join(tmp, "cloudflared");
|
|
194
|
+
writeFileSync(file, bytes);
|
|
212
195
|
renameSync(file, target);
|
|
213
196
|
}
|
|
214
197
|
} finally {
|
|
215
198
|
rmSync(tmp, { recursive: true, force: true });
|
|
216
199
|
}
|
|
217
|
-
if (platform !== "win32")
|
|
200
|
+
if (platform !== "win32") chmodSync(target, 493);
|
|
218
201
|
return target;
|
|
219
202
|
}
|
|
220
203
|
async function ensureCloudflaredWithProgress(write, opts = {}) {
|
|
@@ -236,9 +219,9 @@ async function ensureCloudflaredWithProgress(write, opts = {}) {
|
|
|
236
219
|
}
|
|
237
220
|
|
|
238
221
|
// src/session/allowlist.ts
|
|
239
|
-
import { mkdirSync as
|
|
222
|
+
import { mkdirSync as mkdirSync2, readFileSync, writeFileSync as writeFileSync2 } from "fs";
|
|
240
223
|
import { BlockList, isIP } from "net";
|
|
241
|
-
import { dirname, join as
|
|
224
|
+
import { dirname, join as join2 } from "path";
|
|
242
225
|
|
|
243
226
|
// src/session/allowlist-snapshot.ts
|
|
244
227
|
var ALLOW_SNAPSHOT = {
|
|
@@ -307,7 +290,7 @@ function blockListOf(cidrs) {
|
|
|
307
290
|
}
|
|
308
291
|
function readCache(file) {
|
|
309
292
|
try {
|
|
310
|
-
const c = JSON.parse(
|
|
293
|
+
const c = JSON.parse(readFileSync(file, "utf8"));
|
|
311
294
|
if (typeof c?.fetchedAt === "number" && c.lists && typeof c.lists === "object") return c;
|
|
312
295
|
} catch {
|
|
313
296
|
}
|
|
@@ -324,7 +307,7 @@ async function fetchRanges(name, f) {
|
|
|
324
307
|
return out.map((x) => x.includes("/") ? x : `${x}/${splitCidr(x).prefix}`);
|
|
325
308
|
}
|
|
326
309
|
async function resolveAllow(entries, o = {}) {
|
|
327
|
-
const cacheFile = o.cacheFile ??
|
|
310
|
+
const cacheFile = o.cacheFile ?? join2(dutoHome(), "allowlist.json");
|
|
328
311
|
const f = o.fetch ?? fetch;
|
|
329
312
|
const now = (o.now ?? Date.now)();
|
|
330
313
|
const parsed = entries.map((raw) => ({ raw, e: parseAllowEntry(raw) }));
|
|
@@ -347,8 +330,8 @@ async function resolveAllow(entries, o = {}) {
|
|
|
347
330
|
const everyListFromNetwork = names.length > 0 && Object.keys(net).length === names.length;
|
|
348
331
|
if (everyListFromNetwork) {
|
|
349
332
|
try {
|
|
350
|
-
|
|
351
|
-
|
|
333
|
+
mkdirSync2(dirname(cacheFile), { recursive: true });
|
|
334
|
+
writeFileSync2(cacheFile, JSON.stringify({ fetchedAt: now, lists: net }));
|
|
352
335
|
} catch {
|
|
353
336
|
}
|
|
354
337
|
}
|
|
@@ -370,8 +353,8 @@ async function resolveAllow(entries, o = {}) {
|
|
|
370
353
|
}
|
|
371
354
|
|
|
372
355
|
// src/session/config.ts
|
|
373
|
-
import { existsSync as
|
|
374
|
-
import { dirname as dirname2, join as
|
|
356
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
357
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
375
358
|
import { isMap, isScalar, isSeq, LineCounter, parseDocument } from "yaml";
|
|
376
359
|
var ConfigError = class extends Error {
|
|
377
360
|
constructor(message2, line, file = CONFIG_FILES[0]) {
|
|
@@ -517,13 +500,13 @@ function parseConfig(text, file = CONFIG_FILES[0], env = process.env) {
|
|
|
517
500
|
function findConfig(cwd) {
|
|
518
501
|
let dir = cwd;
|
|
519
502
|
for (; ; ) {
|
|
520
|
-
const present = CONFIG_FILES.filter((f) =>
|
|
503
|
+
const present = CONFIG_FILES.filter((f) => existsSync2(join3(dir, f)));
|
|
521
504
|
if (present.length > 1) throw new ConfigError(`${CONFIG_FILES.join(" and ")} in the same folder. Delete one of them.`, void 0, "");
|
|
522
505
|
if (present.length === 1) {
|
|
523
|
-
const path =
|
|
524
|
-
return { path, tunnels: parseConfig(
|
|
506
|
+
const path = join3(dir, present[0]);
|
|
507
|
+
return { path, tunnels: parseConfig(readFileSync2(path, "utf8"), present[0]) };
|
|
525
508
|
}
|
|
526
|
-
if (
|
|
509
|
+
if (existsSync2(join3(dir, ".git"))) return null;
|
|
527
510
|
const up = dirname2(dir);
|
|
528
511
|
if (up === dir) return null;
|
|
529
512
|
dir = up;
|
|
@@ -540,7 +523,7 @@ async function browserLogin(o) {
|
|
|
540
523
|
const start = await api.startLogin({
|
|
541
524
|
deviceId: o.deviceId,
|
|
542
525
|
deviceName: hostname(),
|
|
543
|
-
cliVersion: "1.
|
|
526
|
+
cliVersion: "1.3.0",
|
|
544
527
|
nodeVersion: process.version,
|
|
545
528
|
os: process.platform,
|
|
546
529
|
arch: process.arch
|
|
@@ -616,6 +599,22 @@ function startSpinner(label, stream = process.stderr) {
|
|
|
616
599
|
};
|
|
617
600
|
}
|
|
618
601
|
|
|
602
|
+
// src/inspector/edge.ts
|
|
603
|
+
var RAY_COLO = /-([A-Za-z]{3})$/;
|
|
604
|
+
function edgeOf(headers) {
|
|
605
|
+
const name = Object.keys(headers).find((k) => k.toLowerCase() === "cf-ray");
|
|
606
|
+
const code = name ? RAY_COLO.exec(headers[name] ?? "")?.[1] : void 0;
|
|
607
|
+
if (!code) return null;
|
|
608
|
+
const colo = code.toUpperCase();
|
|
609
|
+
const place = placeOf(colo);
|
|
610
|
+
return { colo, city: place?.city ?? null, country: place?.country ?? null };
|
|
611
|
+
}
|
|
612
|
+
function edgeLabel(edge) {
|
|
613
|
+
if (!edge) return "";
|
|
614
|
+
const place = [edge.city, edge.country].filter(Boolean).join(", ");
|
|
615
|
+
return place ? "via " + edge.colo + " (" + place + ")" : "via " + edge.colo;
|
|
616
|
+
}
|
|
617
|
+
|
|
619
618
|
// src/inspector/jsonView.ts
|
|
620
619
|
function countLabel(n, one, many) {
|
|
621
620
|
return n + " " + (n === 1 ? one : many);
|
|
@@ -796,6 +795,7 @@ th { color: ${JSON_KEY}; font-weight: 400; white-space: nowrap; }
|
|
|
796
795
|
`;
|
|
797
796
|
var SCRIPT = String.raw`"use strict";
|
|
798
797
|
var countLabel = (${countLabel.toString()});
|
|
798
|
+
var edgeLabel = (${edgeLabel.toString()});
|
|
799
799
|
var jsonSyntaxError = (${jsonSyntaxError.toString()});
|
|
800
800
|
var jsonTokens = (${jsonTokens.toString()});
|
|
801
801
|
var jsonIndent = (${jsonIndent.toString()});
|
|
@@ -969,6 +969,7 @@ function renderDetail() {
|
|
|
969
969
|
if (duration(d)) parts.push(duration(d));
|
|
970
970
|
parts.push(size(d.bytes));
|
|
971
971
|
parts.push(d.tunnel);
|
|
972
|
+
if (d.edge) parts.push(edgeLabel(d.edge));
|
|
972
973
|
if (d.replayOf !== undefined) parts.push("↻ replay of #" + d.replayOf);
|
|
973
974
|
head.appendChild(el("span", "mut", " · " + parts.join(" · ")));
|
|
974
975
|
if (d.error) head.appendChild(el("div", "msg err", d.error));
|
|
@@ -1362,6 +1363,7 @@ function detail(r, session) {
|
|
|
1362
1363
|
}
|
|
1363
1364
|
return {
|
|
1364
1365
|
...listItem(r),
|
|
1366
|
+
edge: edgeOf(r.reqHeaders),
|
|
1365
1367
|
reqHeaders: r.reqHeaders,
|
|
1366
1368
|
resHeaders: r.resHeaders,
|
|
1367
1369
|
reqBody: r.reqBody,
|
|
@@ -2225,13 +2227,13 @@ async function startProxy(o) {
|
|
|
2225
2227
|
|
|
2226
2228
|
// src/session/orphans.ts
|
|
2227
2229
|
import { spawnSync } from "child_process";
|
|
2228
|
-
import { existsSync as
|
|
2229
|
-
import { join as
|
|
2230
|
-
var fileOf = (dir, cfPid) =>
|
|
2230
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync3, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
2231
|
+
import { join as join4 } from "path";
|
|
2232
|
+
var fileOf = (dir, cfPid) => join4(dir, `cloudflared-${cfPid}.json`);
|
|
2231
2233
|
function recordCloudflared(dir, cfPid, sessionPid = process.pid) {
|
|
2232
2234
|
try {
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
+
mkdirSync3(dir, { recursive: true });
|
|
2236
|
+
writeFileSync3(fileOf(dir, cfPid), JSON.stringify({ sessionPid, cfPid, at: Date.now() }));
|
|
2235
2237
|
} catch {
|
|
2236
2238
|
}
|
|
2237
2239
|
}
|
|
@@ -2249,7 +2251,7 @@ var realProc = {
|
|
|
2249
2251
|
},
|
|
2250
2252
|
isCloudflared(pid) {
|
|
2251
2253
|
try {
|
|
2252
|
-
if (process.platform === "linux") return
|
|
2254
|
+
if (process.platform === "linux") return readFileSync3(`/proc/${pid}/cmdline`, "utf8").includes("cloudflared");
|
|
2253
2255
|
if (process.platform === "darwin") return spawnSync("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf8" }).stdout.includes("cloudflared");
|
|
2254
2256
|
} catch {
|
|
2255
2257
|
return false;
|
|
@@ -2264,14 +2266,14 @@ var realProc = {
|
|
|
2264
2266
|
}
|
|
2265
2267
|
};
|
|
2266
2268
|
function reapOrphans(dir, proc = realProc) {
|
|
2267
|
-
if (!
|
|
2269
|
+
if (!existsSync3(dir)) return 0;
|
|
2268
2270
|
let killed = 0;
|
|
2269
2271
|
for (const name of readdirSync(dir)) {
|
|
2270
2272
|
if (!/^cloudflared-\d+\.json$/.test(name)) continue;
|
|
2271
|
-
const path =
|
|
2273
|
+
const path = join4(dir, name);
|
|
2272
2274
|
let rec;
|
|
2273
2275
|
try {
|
|
2274
|
-
rec = JSON.parse(
|
|
2276
|
+
rec = JSON.parse(readFileSync3(path, "utf8"));
|
|
2275
2277
|
} catch {
|
|
2276
2278
|
rmSync2(path, { force: true });
|
|
2277
2279
|
continue;
|
|
@@ -2662,13 +2664,14 @@ async function runSession(specs, source, flags) {
|
|
|
2662
2664
|
const config = loadConfig();
|
|
2663
2665
|
if (!config.authtoken) fail2(LOGIN_HINT);
|
|
2664
2666
|
const api = createApiClient({ baseUrl: apiUrl(), token: config.authtoken });
|
|
2667
|
+
const update = checkForUpdate(CLI_VERSION);
|
|
2665
2668
|
let cloudflaredPath;
|
|
2666
2669
|
try {
|
|
2667
2670
|
cloudflaredPath = process.env.DUTO_CLOUDFLARED || await ensureCloudflaredWithProgress((s) => process.stdout.write(s));
|
|
2668
2671
|
} catch (err) {
|
|
2669
2672
|
fail2(err instanceof Error ? err.message : String(err));
|
|
2670
2673
|
}
|
|
2671
|
-
const session = new Session({ api, deviceId: config.deviceId, deviceName: hostname2(), cloudflaredPath, source, runDir:
|
|
2674
|
+
const session = new Session({ api, deviceId: config.deviceId, deviceName: hostname2(), cloudflaredPath, source, runDir: join5(dutoHome(), "run") });
|
|
2672
2675
|
let inspector = null;
|
|
2673
2676
|
let inspectorError;
|
|
2674
2677
|
if (flags.inspect !== false) {
|
|
@@ -2679,14 +2682,15 @@ async function runSession(specs, source, flags) {
|
|
|
2679
2682
|
}
|
|
2680
2683
|
}
|
|
2681
2684
|
if (wantsUi(flags)) {
|
|
2682
|
-
const { runFullscreen } = await import("./fullscreen-
|
|
2683
|
-
const code = await runFullscreen(session, specs,
|
|
2685
|
+
const { runFullscreen } = await import("./fullscreen-AI6PC7RL.js");
|
|
2686
|
+
const code = await runFullscreen(session, specs, CLI_VERSION, inspector, inspectorError, update);
|
|
2684
2687
|
await inspector?.close().catch(() => void 0);
|
|
2685
2688
|
process.exit(code);
|
|
2686
2689
|
}
|
|
2687
|
-
console.log(`${mark()} ${paint("muted", `${
|
|
2690
|
+
console.log(`${mark()} ${paint("muted", `${CLI_VERSION} \xB7 ${source} \xB7 Ctrl+C to stop`)}`);
|
|
2688
2691
|
if (inspector) console.log(paint("muted", `inspector: ${inspector.openUrl}`));
|
|
2689
2692
|
if (inspectorError) console.log(warn(`inspector failed to start: ${inspectorError}`));
|
|
2693
|
+
void update.then((latest) => latest && console.log(warn(updateLine(latest))));
|
|
2690
2694
|
const stopLog = runPlain(session, (l) => console.log(l));
|
|
2691
2695
|
const stopped = new Promise((resolve) => {
|
|
2692
2696
|
const onSignal = () => {
|
|
@@ -2812,7 +2816,7 @@ cli.command("status", "Show your account and live tunnels").action(async () => {
|
|
|
2812
2816
|
}
|
|
2813
2817
|
});
|
|
2814
2818
|
cli.help();
|
|
2815
|
-
cli.version(
|
|
2819
|
+
cli.version(CLI_VERSION);
|
|
2816
2820
|
try {
|
|
2817
2821
|
cli.parse(process.argv, { run: false });
|
|
2818
2822
|
await cli.runMatchedCommand();
|
package/package.json
CHANGED
package/dist/chunk-76YCOMXH.js
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// ../shared/src/names.ts
|
|
4
|
-
var EMAIL_SENDING_SUBDOMAINS = ["send"];
|
|
5
|
-
var RESERVED_NAMES = [
|
|
6
|
-
"www",
|
|
7
|
-
"api",
|
|
8
|
-
"app",
|
|
9
|
-
"dash",
|
|
10
|
-
"admin",
|
|
11
|
-
"docs",
|
|
12
|
-
"status",
|
|
13
|
-
"mail",
|
|
14
|
-
"custom",
|
|
15
|
-
"cname",
|
|
16
|
-
"duto",
|
|
17
|
-
"auth",
|
|
18
|
-
"login",
|
|
19
|
-
"signup",
|
|
20
|
-
"invite",
|
|
21
|
-
"static",
|
|
22
|
-
"assets",
|
|
23
|
-
"cdn",
|
|
24
|
-
"help",
|
|
25
|
-
"support",
|
|
26
|
-
"blog",
|
|
27
|
-
...EMAIL_SENDING_SUBDOMAINS
|
|
28
|
-
];
|
|
29
|
-
var MAX_NAME = 40;
|
|
30
|
-
var FORMAT = /^[a-z0-9]+$/;
|
|
31
|
-
function normalizeName(input) {
|
|
32
|
-
return input.trim().toLowerCase();
|
|
33
|
-
}
|
|
34
|
-
function validateName(name, max = MAX_NAME) {
|
|
35
|
-
if (name.length === 0 || name.length > max) return { ok: false, reason: "length" };
|
|
36
|
-
if (!FORMAT.test(name)) return { ok: false, reason: "format" };
|
|
37
|
-
if (RESERVED_NAMES.includes(name)) return { ok: false, reason: "reserved" };
|
|
38
|
-
return { ok: true };
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// src/ui/theme.ts
|
|
42
|
-
var C = {
|
|
43
|
-
signal: "#2dd4bf",
|
|
44
|
-
muted: "#8a8f8b",
|
|
45
|
-
strong: "#f3f4f1",
|
|
46
|
-
ok: "#4ade80",
|
|
47
|
-
warn: "#facc15",
|
|
48
|
-
err: "#f87171"
|
|
49
|
-
};
|
|
50
|
-
var TUNNEL_COLORS = ["#93c5fd", "#c4b5fd", "#facc15", "#4ade80", "#f9a8d4", "#fdba74", "#67e8f9"];
|
|
51
|
-
function tunnelColor(index) {
|
|
52
|
-
return TUNNEL_COLORS[index % TUNNEL_COLORS.length];
|
|
53
|
-
}
|
|
54
|
-
function fit(text, width) {
|
|
55
|
-
if (width <= 0) return "";
|
|
56
|
-
return text.length > width ? `${text.slice(0, Math.max(0, width - 1))}\u2026` : text.padEnd(width);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// src/session/bodyNote.ts
|
|
60
|
-
var TRUNCATED_NOTE = "Body cut at 64 KB.";
|
|
61
|
-
function bodyNote(r, side) {
|
|
62
|
-
if (side === "req") {
|
|
63
|
-
if (r.reqBodyBytes === 0) return "No body.";
|
|
64
|
-
if (r.reqBody === null && !r.reqTruncated) return "Binary body; not stored.";
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
if (r.bytes === 0) return "No body.";
|
|
68
|
-
if (r.resBody === null) return "Binary body; not stored.";
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// src/ui/format.ts
|
|
73
|
-
function statusColor(status) {
|
|
74
|
-
if (status === null) return "muted";
|
|
75
|
-
if (status === 101) return "signal";
|
|
76
|
-
if (status >= 500) return "err";
|
|
77
|
-
if (status >= 400) return "warn";
|
|
78
|
-
if (status >= 300) return "muted";
|
|
79
|
-
return "ok";
|
|
80
|
-
}
|
|
81
|
-
function formatDuration(ms) {
|
|
82
|
-
return ms < 1e3 ? `${Math.round(ms)}ms` : `${(ms / 1e3).toFixed(1)}s`;
|
|
83
|
-
}
|
|
84
|
-
function formatBytes(n) {
|
|
85
|
-
if (n < 1024) return `${n} B`;
|
|
86
|
-
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
|
|
87
|
-
return `${(n / 1024 / 1024).toFixed(1)} MB`;
|
|
88
|
-
}
|
|
89
|
-
function formatTime(ms) {
|
|
90
|
-
const d = new Date(ms);
|
|
91
|
-
return [d.getHours(), d.getMinutes(), d.getSeconds()].map((n) => String(n).padStart(2, "0")).join(":");
|
|
92
|
-
}
|
|
93
|
-
function localLabel(t) {
|
|
94
|
-
return t.host ? `localhost:${t.port} (Host: ${t.host})` : `localhost:${t.port}`;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export {
|
|
98
|
-
MAX_NAME,
|
|
99
|
-
normalizeName,
|
|
100
|
-
validateName,
|
|
101
|
-
C,
|
|
102
|
-
tunnelColor,
|
|
103
|
-
fit,
|
|
104
|
-
TRUNCATED_NOTE,
|
|
105
|
-
bodyNote,
|
|
106
|
-
statusColor,
|
|
107
|
-
formatDuration,
|
|
108
|
-
formatBytes,
|
|
109
|
-
formatTime,
|
|
110
|
-
localLabel
|
|
111
|
-
};
|