mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-12 21:07:46 +00:00
Compare commits
1493 Commits
@now/pytho
...
@now/pytho
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ed59fcdd3 | ||
|
|
7f211cc7fc | ||
|
|
c511147c7c | ||
|
|
2f7f965188 | ||
|
|
24da26e7cf | ||
|
|
f8f9d656e8 | ||
|
|
b357f3055d | ||
|
|
f9c1f519f4 | ||
|
|
057f9f7045 | ||
|
|
58ec38437d | ||
|
|
2964bf50e0 | ||
|
|
c9572c352a | ||
|
|
b69f176143 | ||
|
|
484dcd5dc1 | ||
|
|
a3412113b7 | ||
|
|
693043bc27 | ||
|
|
82a5bafb42 | ||
|
|
1c8c4c6f72 | ||
|
|
28a2e354d3 | ||
|
|
c89e478fc1 | ||
|
|
7e29a24ea6 | ||
|
|
5fc9b7d36d | ||
|
|
a211c648ed | ||
|
|
c24d8de960 | ||
|
|
34f6e52335 | ||
|
|
77348ea71e | ||
|
|
a1273d1c9e | ||
|
|
4e6e51af9c | ||
|
|
a6366eaff6 | ||
|
|
1403cf47b2 | ||
|
|
6a5a964b55 | ||
|
|
1c40d693c1 | ||
|
|
9cff4ca2c3 | ||
|
|
d1f675bd46 | ||
|
|
cce292f639 | ||
|
|
adbf2d9c31 | ||
|
|
06e94a1f6f | ||
|
|
92e396830a | ||
|
|
f4df1dfe41 | ||
|
|
be3bc4273d | ||
|
|
b12ed4127e | ||
|
|
b6311d5897 | ||
|
|
cb40985b0e | ||
|
|
7ace8b7103 | ||
|
|
aea17f0c6f | ||
|
|
5d56eeb871 | ||
|
|
f3cb60f707 | ||
|
|
1e57be0710 | ||
|
|
ee8e4bfb20 | ||
|
|
7355182586 | ||
|
|
e0683d51fa | ||
|
|
1f1d5ca8bf | ||
|
|
4dfd11ad8e | ||
|
|
d46e106dba | ||
|
|
cd7eabb6b6 | ||
|
|
bfedde6a4d | ||
|
|
ece00674b6 | ||
|
|
aa51f9005a | ||
|
|
836d0dd6ec | ||
|
|
1947813f04 | ||
|
|
9a1eeb6fca | ||
|
|
ca8f347e3a | ||
|
|
51146f5baf | ||
|
|
f2bd36b4f9 | ||
|
|
818018ce42 | ||
|
|
89114179da | ||
|
|
d50c72f2c3 | ||
|
|
ce241e7690 | ||
|
|
b72cbfb708 | ||
|
|
ef7b60225d | ||
|
|
b3718d9378 | ||
|
|
c8c56bcdb1 | ||
|
|
a84b9273b2 | ||
|
|
3d67ad16ff | ||
|
|
dd5d4491e2 | ||
|
|
7dd1199ce0 | ||
|
|
ee7b64ff3a | ||
|
|
6efe7d04bc | ||
|
|
b7f8f7c366 | ||
|
|
9adbb10fca | ||
|
|
6646b97a65 | ||
|
|
939c3a11d7 | ||
|
|
363c3ea39f | ||
|
|
935fca6986 | ||
|
|
7e20c585a1 | ||
|
|
7ecdfbb043 | ||
|
|
2a39f61f34 | ||
|
|
6e0182af02 | ||
|
|
1fd630460c | ||
|
|
4583880377 | ||
|
|
abacaf8d40 | ||
|
|
de8829ccfe | ||
|
|
ba18a3a0cb | ||
|
|
0a0f13994f | ||
|
|
eff29463e7 | ||
|
|
9c62b74122 | ||
|
|
5f19e63409 | ||
|
|
1fd4c3a278 | ||
|
|
281b385188 | ||
|
|
4e4b672af9 | ||
|
|
b70ac1ca1c | ||
|
|
36fc64d0ed | ||
|
|
2dee810e74 | ||
|
|
4fa468299f | ||
|
|
90eac2cf54 | ||
|
|
63ad08074c | ||
|
|
31fcc56dbd | ||
|
|
958b067303 | ||
|
|
6af183c7f1 | ||
|
|
92d9f2d809 | ||
|
|
4d7aaff8d0 | ||
|
|
68b19c8122 | ||
|
|
5b600fea44 | ||
|
|
2c867295e6 | ||
|
|
996c0ffffc | ||
|
|
2344357113 | ||
|
|
e919c591fe | ||
|
|
88516137de | ||
|
|
f515138074 | ||
|
|
6d04016054 | ||
|
|
eb968d813c | ||
|
|
37881e0830 | ||
|
|
e3dd13ba9e | ||
|
|
9054b7f2f5 | ||
|
|
17cd17ac96 | ||
|
|
d7c2d071c1 | ||
|
|
483a117a6a | ||
|
|
3c630343d0 | ||
|
|
fe61b31197 | ||
|
|
af14d0af4b | ||
|
|
e04e6bb188 | ||
|
|
e36f3d355e | ||
|
|
24891e3ce8 | ||
|
|
147c703cce | ||
|
|
3aa92c3f27 | ||
|
|
0d29e37dcb | ||
|
|
089b29c2ca | ||
|
|
af513c249c | ||
|
|
d567b5f2fc | ||
|
|
c336353020 | ||
|
|
523b9de1e0 | ||
|
|
6151f11657 | ||
|
|
c0d3847aab | ||
|
|
93d295ac5c | ||
|
|
8430de13c2 | ||
|
|
c78af57d76 | ||
|
|
172605d77d | ||
|
|
d7dc5a0c45 | ||
|
|
75edd0bab5 | ||
|
|
ac60b3660c | ||
|
|
372a674625 | ||
|
|
fafeadb7ba | ||
|
|
966b1e763f | ||
|
|
6e22c07ecc | ||
|
|
857e4ef874 | ||
|
|
aa03f69bdc | ||
|
|
8fc77139e0 | ||
|
|
bb60e1a5fe | ||
|
|
87802cb002 | ||
|
|
cac9f807cc | ||
|
|
a0b1254820 | ||
|
|
0faff4132b | ||
|
|
1793a1287d | ||
|
|
5b572239c1 | ||
|
|
f6a66d937e | ||
|
|
2cf9a2f489 | ||
|
|
454f4dcc61 | ||
|
|
6e1065fde2 | ||
|
|
80ce06b20c | ||
|
|
99f3ab8b64 | ||
|
|
ca4f6d2491 | ||
|
|
dcd57e148f | ||
|
|
18fa193a17 | ||
|
|
34dce350be | ||
|
|
c7caa7b905 | ||
|
|
2ceb2a78aa | ||
|
|
d97da21afc | ||
|
|
7b52b26ff0 | ||
|
|
3c958a4429 | ||
|
|
d8660b1db3 | ||
|
|
0996640798 | ||
|
|
079f4ba6bf | ||
|
|
bdf78efbfb | ||
|
|
07ba180ede | ||
|
|
94a2b017d4 | ||
|
|
37c06c89d0 | ||
|
|
42ffc2dafb | ||
|
|
cf65fabe27 | ||
|
|
03b5a0c0bf | ||
|
|
f76abe3372 | ||
|
|
cdd43b74ae | ||
|
|
fa633d0e02 | ||
|
|
9b46e60c09 | ||
|
|
58eef7f394 | ||
|
|
e97e0fbb64 | ||
|
|
b82876fd82 | ||
|
|
02ad32ec22 | ||
|
|
433fe35c93 | ||
|
|
90c59d6ae2 | ||
|
|
33672c3d78 | ||
|
|
59ae7a989a | ||
|
|
5767e9e8c2 | ||
|
|
e62b9e8ed9 | ||
|
|
59597ccd17 | ||
|
|
7be49c66ef | ||
|
|
1380e25ef3 | ||
|
|
e825ce746f | ||
|
|
dc9b9c240a | ||
|
|
84d8f73a2d | ||
|
|
b24cfb605b | ||
|
|
57f66fe62f | ||
|
|
d2cd4a3c5a | ||
|
|
4e58951808 | ||
|
|
fbd805aad7 | ||
|
|
2a2705c6e3 | ||
|
|
986c957183 | ||
|
|
c5d063e876 | ||
|
|
110e1445fa | ||
|
|
f9355cbc77 | ||
|
|
0a9bbd902f | ||
|
|
c8c960f785 | ||
|
|
fb0f4beca6 | ||
|
|
21398d7214 | ||
|
|
a5a56836a8 | ||
|
|
e7fd1e3c3f | ||
|
|
64b24bde56 | ||
|
|
613958d6a5 | ||
|
|
21e4ebc7a9 | ||
|
|
16ef6229e4 | ||
|
|
9ecb011732 | ||
|
|
de48e28fa1 | ||
|
|
3aecb0905a | ||
|
|
c1b2da1d57 | ||
|
|
c4bee64abd | ||
|
|
dbf9c5c46b | ||
|
|
630ec06d48 | ||
|
|
d38e464bfe | ||
|
|
49bd2439a7 | ||
|
|
e97314a3c7 | ||
|
|
acb46cef14 | ||
|
|
06b9ff233e | ||
|
|
a189e72fbe | ||
|
|
75e6b15199 | ||
|
|
85170d7231 | ||
|
|
500c36f5d4 | ||
|
|
eeffb55021 | ||
|
|
69dbbeac44 | ||
|
|
1fe8317f1e | ||
|
|
69486c3adb | ||
|
|
6398ef194c | ||
|
|
e6692bb79b | ||
|
|
94fba1d7af | ||
|
|
223d8f4774 | ||
|
|
42e7a7e4e3 | ||
|
|
6716fdd49b | ||
|
|
3b69092fd8 | ||
|
|
aa8eaedbc8 | ||
|
|
f519ed373f | ||
|
|
851dff4b03 | ||
|
|
e4d691eda1 | ||
|
|
d9bb6c8a54 | ||
|
|
c3020e3071 | ||
|
|
e3f31e3e52 | ||
|
|
8c40e1be0f | ||
|
|
b278c7148b | ||
|
|
2791338e04 | ||
|
|
0af87dc2be | ||
|
|
32134dd1f5 | ||
|
|
bdd4441d5c | ||
|
|
99bc1ae7b6 | ||
|
|
c5ebaa11ea | ||
|
|
934fbc8992 | ||
|
|
72cb5515fd | ||
|
|
c7f0770d53 | ||
|
|
7ea49e8ada | ||
|
|
cae6ce96b3 | ||
|
|
3699dfd756 | ||
|
|
6dca96d877 | ||
|
|
88c14b27a2 | ||
|
|
0d2a9539f6 | ||
|
|
bae160bd7c | ||
|
|
92852ecff2 | ||
|
|
ac0c841cb8 | ||
|
|
53e4b71f89 | ||
|
|
017a2692ca | ||
|
|
311f89eecb | ||
|
|
40d2bc4743 | ||
|
|
37160cbc8b | ||
|
|
3807a2b018 | ||
|
|
b6697dd432 | ||
|
|
6c33496e8a | ||
|
|
89f32625ed | ||
|
|
8253e76ec0 | ||
|
|
e0b3e9606a | ||
|
|
dc75a303f7 | ||
|
|
c1eb8ec78c | ||
|
|
12435f25fd | ||
|
|
d4dc5222cf | ||
|
|
bf1e59b2d3 | ||
|
|
3657e4a36e | ||
|
|
09efc1d865 | ||
|
|
32aa94bf3d | ||
|
|
3201227430 | ||
|
|
09a421c23b | ||
|
|
73ce59b492 | ||
|
|
7911d5857d | ||
|
|
05a44a0c70 | ||
|
|
efd8863f8b | ||
|
|
6699028342 | ||
|
|
aa9a234a0b | ||
|
|
91f836818c | ||
|
|
5e37bdc54c | ||
|
|
31c48df795 | ||
|
|
0383b9112b | ||
|
|
f4fe8be4df | ||
|
|
cc643e373d | ||
|
|
651429cb52 | ||
|
|
a374a5ce96 | ||
|
|
bbb4501e9b | ||
|
|
947f9093be | ||
|
|
42f16b6d1e | ||
|
|
9ae747a612 | ||
|
|
9217c5e436 | ||
|
|
d23e7b1054 | ||
|
|
d23eab61cf | ||
|
|
e631de4cfe | ||
|
|
0814bef36f | ||
|
|
ddce65416c | ||
|
|
9e66f9bb08 | ||
|
|
961fbfde55 | ||
|
|
22550c0c48 | ||
|
|
4336f48d22 | ||
|
|
cf299562e3 | ||
|
|
2cd5b35603 | ||
|
|
81abab81f7 | ||
|
|
ad0d7bd560 | ||
|
|
140d10e87b | ||
|
|
6bebc49607 | ||
|
|
22bded50b6 | ||
|
|
b5b02be3c2 | ||
|
|
776f372eb3 | ||
|
|
81279fd40b | ||
|
|
3342485d29 | ||
|
|
028ee848f5 | ||
|
|
7e64c3b8a9 | ||
|
|
704031f7b2 | ||
|
|
5e3c184735 | ||
|
|
88a8022787 | ||
|
|
96844dc4a5 | ||
|
|
a09acd6969 | ||
|
|
4e232f78de | ||
|
|
b146a04772 | ||
|
|
eaaa50e616 | ||
|
|
c893eaeb7a | ||
|
|
5bf7d7fd07 | ||
|
|
ca8fc92b94 | ||
|
|
9956e85f12 | ||
|
|
7fa4739c78 | ||
|
|
0ef2e2a7ec | ||
|
|
8fd1752acf | ||
|
|
14a1446faf | ||
|
|
0c2c8c5ae5 | ||
|
|
a3aa855290 | ||
|
|
511b27ad39 | ||
|
|
e22ce7da0a | ||
|
|
d9a4ce06bc | ||
|
|
77fb14cc60 | ||
|
|
17c397211e | ||
|
|
6ca83644bc | ||
|
|
d1946ea9b6 | ||
|
|
cc9eae3b71 | ||
|
|
7bbc17df4b | ||
|
|
df6b2be482 | ||
|
|
5ff6263fb7 | ||
|
|
04dc8aaf73 | ||
|
|
5435805e58 | ||
|
|
903f819c5d | ||
|
|
5d927b2d25 | ||
|
|
b7a260cc6d | ||
|
|
e8ba8fb97b | ||
|
|
dd1d9d856b | ||
|
|
a853cb84cd | ||
|
|
eef4c65e5f | ||
|
|
3f64594a22 | ||
|
|
a07082ca5c | ||
|
|
81c27771bc | ||
|
|
502f78e835 | ||
|
|
d8935cf121 | ||
|
|
e3c61ac5b7 | ||
|
|
e8e95b8df6 | ||
|
|
f55be4f2de | ||
|
|
ef081cc4b8 | ||
|
|
8e2444d3cd | ||
|
|
f26407e282 | ||
|
|
cded895bf7 | ||
|
|
1211ee4fb3 | ||
|
|
a42bdefe1a | ||
|
|
f025f1007b | ||
|
|
1c11a57371 | ||
|
|
db2d033484 | ||
|
|
40537c9eba | ||
|
|
5cabcb7a27 | ||
|
|
1be9ac59f0 | ||
|
|
64356baed3 | ||
|
|
c944706a0f | ||
|
|
f15deaa51e | ||
|
|
61c3b94460 | ||
|
|
89b018240f | ||
|
|
0124dc9969 | ||
|
|
936441c8a6 | ||
|
|
c80570e096 | ||
|
|
11c67e1c4c | ||
|
|
0d1c0e0f30 | ||
|
|
f50572813e | ||
|
|
648b3a4ae2 | ||
|
|
3f5f71f8ab | ||
|
|
2a44179898 | ||
|
|
efe114fa86 | ||
|
|
4445d5e00f | ||
|
|
96b3c1ee7f | ||
|
|
e1a770ac29 | ||
|
|
db1a2e6482 | ||
|
|
ecdde7c367 | ||
|
|
4d31291ea4 | ||
|
|
b70bd670fd | ||
|
|
d3017649e0 | ||
|
|
e970584219 | ||
|
|
4a6ddf8b1d | ||
|
|
c6205293a9 | ||
|
|
cb47f7bf4b | ||
|
|
a60f107e4b | ||
|
|
eeb1b2442c | ||
|
|
129f234aaa | ||
|
|
331c352e2b | ||
|
|
2ccbaea9dd | ||
|
|
069eca3c62 | ||
|
|
305e364f8b | ||
|
|
4068805ae0 | ||
|
|
9a1e7a4a7a | ||
|
|
bbad3d1b96 | ||
|
|
c62116d9a4 | ||
|
|
c94086ff21 | ||
|
|
e99caa7b97 | ||
|
|
658b9e9007 | ||
|
|
3ef27ae45c | ||
|
|
e669cd1152 | ||
|
|
b22aa7c0cf | ||
|
|
582cbb61fb | ||
|
|
7f4197cf43 | ||
|
|
30889db487 | ||
|
|
2d08d5d23e | ||
|
|
59e7367e03 | ||
|
|
84af278e86 | ||
|
|
67d9ee39e2 | ||
|
|
6c67bb81f7 | ||
|
|
aba19701c0 | ||
|
|
bb79402999 | ||
|
|
420bc4b244 | ||
|
|
323c3d74cb | ||
|
|
4ecec8a8f6 | ||
|
|
98883f9978 | ||
|
|
37cf3be437 | ||
|
|
6e43c322cc | ||
|
|
4b6387bdb5 | ||
|
|
bcd770c0fe | ||
|
|
bb8c7e9f3b | ||
|
|
6e469272c3 | ||
|
|
5e7fa71148 | ||
|
|
12d9a4b4f4 | ||
|
|
084ab2d0e5 | ||
|
|
4ca0d936aa | ||
|
|
5d5a55e7e1 | ||
|
|
e033e5e0ca | ||
|
|
7cb36ef1dd | ||
|
|
9aafb168e9 | ||
|
|
fbb5caf955 | ||
|
|
3f93da550b | ||
|
|
a194e0cc6e | ||
|
|
b007200bcf | ||
|
|
05d88da887 | ||
|
|
0a429bb2f3 | ||
|
|
468ccb7598 | ||
|
|
e47f6f55dc | ||
|
|
17deed91b2 | ||
|
|
cc0bd9f0a2 | ||
|
|
f6f99fef25 | ||
|
|
eb252edc7f | ||
|
|
f3213dbcdc | ||
|
|
c637da7710 | ||
|
|
ab05595582 | ||
|
|
ba8e714329 | ||
|
|
ef86bb0bd9 | ||
|
|
be18b54bfd | ||
|
|
ed168db23c | ||
|
|
236f5f4159 | ||
|
|
e44a91634e | ||
|
|
8b9426eb6c | ||
|
|
c62c57f122 | ||
|
|
c81db0c8d2 | ||
|
|
1c64990e9f | ||
|
|
f1dc7fd14a | ||
|
|
6079d4be25 | ||
|
|
2c4b8335ce | ||
|
|
9bd2f22d5c | ||
|
|
7c421f92e1 | ||
|
|
0626db51bd | ||
|
|
dc29a8b816 | ||
|
|
e00e5e18ea | ||
|
|
d0999e7937 | ||
|
|
158cfad978 | ||
|
|
9145b43a62 | ||
|
|
479d271356 | ||
|
|
8119f4cad2 | ||
|
|
e73b32e9cc | ||
|
|
45bd08e4e5 | ||
|
|
4528243b5c | ||
|
|
bd741cfbeb | ||
|
|
b3b10f0fd7 | ||
|
|
084c014fff | ||
|
|
63614e5303 | ||
|
|
099d2c8195 | ||
|
|
ae813423d8 | ||
|
|
2a5f3f779e | ||
|
|
89e1d83ad4 | ||
|
|
ce23f9e204 | ||
|
|
743eb21682 | ||
|
|
b9f5de2b7a | ||
|
|
229a62d8b6 | ||
|
|
52a6e60f78 | ||
|
|
a7cb747130 | ||
|
|
918d60576f | ||
|
|
62111977fd | ||
|
|
00c05b95e4 | ||
|
|
9ee83b858c | ||
|
|
e4a181321a | ||
|
|
dd6f6aa418 | ||
|
|
b37bfbc1ed | ||
|
|
dd06c4c63d | ||
|
|
a6174cde03 | ||
|
|
c85ab97321 | ||
|
|
957fe9084a | ||
|
|
70f72e5bb8 | ||
|
|
4feb961091 | ||
|
|
6ddc252978 | ||
|
|
5eca7cf5f7 | ||
|
|
76486246c5 | ||
|
|
740a7c8886 | ||
|
|
40b69b4924 | ||
|
|
c5ef4b1dc5 | ||
|
|
ef793bf7e9 | ||
|
|
2be1dd4fcb | ||
|
|
a734eb79d9 | ||
|
|
24dc2a36de | ||
|
|
12881c1b57 | ||
|
|
c66188a6f2 | ||
|
|
61457b532a | ||
|
|
d2ff3749ca | ||
|
|
38bc7cc6d6 | ||
|
|
413fdd7c39 | ||
|
|
f48bb493b0 | ||
|
|
962c5db9fe | ||
|
|
9e961b479c | ||
|
|
718100fafc | ||
|
|
2b38f545d0 | ||
|
|
55b1327119 | ||
|
|
a75ec505ad | ||
|
|
36b1e129b4 | ||
|
|
331b6e0a23 | ||
|
|
fc72ecb223 | ||
|
|
ca51cbcc6b | ||
|
|
6ce8acc4db | ||
|
|
a9edec1619 | ||
|
|
8638d17d87 | ||
|
|
db6ae2998b | ||
|
|
d667a83af5 | ||
|
|
f2a1fb50da | ||
|
|
eb356a5d3d | ||
|
|
efe0ca57f0 | ||
|
|
409b7099be | ||
|
|
e6653992a7 | ||
|
|
c133e81f6e | ||
|
|
28bfcb4c58 | ||
|
|
5cf8f8a70a | ||
|
|
b31ab033c2 | ||
|
|
24a3b99ee3 | ||
|
|
9a6ad1ae43 | ||
|
|
687ef375c8 | ||
|
|
538525e639 | ||
|
|
aff5a0cbed | ||
|
|
b1b1529bd6 | ||
|
|
bb934f0146 | ||
|
|
7d2c5dc39a | ||
|
|
9894869427 | ||
|
|
8e0c1da441 | ||
|
|
6a165e8377 | ||
|
|
eeed5a8273 | ||
|
|
75d98c649a | ||
|
|
d54cc4df51 | ||
|
|
f211f1fa70 | ||
|
|
f60a373479 | ||
|
|
80a82ba276 | ||
|
|
8a3d0fb370 | ||
|
|
20c2930a90 | ||
|
|
bccf602496 | ||
|
|
ed92ef8952 | ||
|
|
1b9c14bd30 | ||
|
|
c287cbeb22 | ||
|
|
d29b1b9cfa | ||
|
|
49293ae44b | ||
|
|
39103f5ae6 | ||
|
|
af2c894fb9 | ||
|
|
42f9745420 | ||
|
|
f016f05c11 | ||
|
|
257f5f293e | ||
|
|
35c3d87055 | ||
|
|
a78813bd74 | ||
|
|
05c2f69523 | ||
|
|
e389a836e1 | ||
|
|
64c526fe1d | ||
|
|
e132d74aa8 | ||
|
|
975c8dc5fc | ||
|
|
3d2929b345 | ||
|
|
c91e529782 | ||
|
|
495ba3bf98 | ||
|
|
c799886f53 | ||
|
|
bcbaad4371 | ||
|
|
91c80ca0a9 | ||
|
|
e7e0ab7d0d | ||
|
|
0b6b74938f | ||
|
|
256ca8796f | ||
|
|
17c659124b | ||
|
|
bcac213114 | ||
|
|
7f4b2bdc87 | ||
|
|
94a958f460 | ||
|
|
7a02249f37 | ||
|
|
94681f28c6 | ||
|
|
9fd1cee6f0 | ||
|
|
2054bb06f1 | ||
|
|
c0b5a01cae | ||
|
|
0bbe5f7806 | ||
|
|
9e25d8e11c | ||
|
|
8623c5201c | ||
|
|
862728f889 | ||
|
|
ad4e20c7e6 | ||
|
|
62d782255a | ||
|
|
3f18d24915 | ||
|
|
23318d9fc9 | ||
|
|
c413cc7349 | ||
|
|
f268a1b390 | ||
|
|
19dd33b037 | ||
|
|
a29890c8ee | ||
|
|
6e3a495914 | ||
|
|
e1005deeb3 | ||
|
|
c61502e24c | ||
|
|
7674a4af4a | ||
|
|
0245a04db9 | ||
|
|
340537bc0b | ||
|
|
c9383b2535 | ||
|
|
eae9f9ed42 | ||
|
|
dfa134e8e4 | ||
|
|
bb7b99bcf9 | ||
|
|
074e96e117 | ||
|
|
0b82c74c58 | ||
|
|
5cc4af257e | ||
|
|
1979e2074f | ||
|
|
e788743f58 | ||
|
|
dae525e56a | ||
|
|
0e32313395 | ||
|
|
2ea1b00fda | ||
|
|
398b371ded | ||
|
|
da89cecaab | ||
|
|
e821d28ccd | ||
|
|
3cd2de7728 | ||
|
|
8e32c66840 | ||
|
|
7716bb5439 | ||
|
|
42a8dc14b0 | ||
|
|
97ff4515ae | ||
|
|
ad6c04b78d | ||
|
|
68c1f7163f | ||
|
|
b94e8a1563 | ||
|
|
60a5659dff | ||
|
|
06b8ccb672 | ||
|
|
ca06186cfe | ||
|
|
b5349fe6a5 | ||
|
|
7ac3c75ee8 | ||
|
|
00437fe3f8 | ||
|
|
930648da13 | ||
|
|
4945218ea6 | ||
|
|
12c3987e94 | ||
|
|
18cedcb7e6 | ||
|
|
925af8066d | ||
|
|
5f46eb9dd7 | ||
|
|
24245bd4bb | ||
|
|
005dd24b18 | ||
|
|
116ee34a10 | ||
|
|
81cf441f66 | ||
|
|
c5a588744a | ||
|
|
48c126a911 | ||
|
|
18c295a88e | ||
|
|
893871e043 | ||
|
|
f2b1c89a53 | ||
|
|
271d7ffc29 | ||
|
|
fd99217303 | ||
|
|
77d07f838f | ||
|
|
1f81d80505 | ||
|
|
7e950496ae | ||
|
|
ec43780e1b | ||
|
|
66fe14d8e2 | ||
|
|
a29abc7d45 | ||
|
|
5d329e0bc3 | ||
|
|
2de9edeed4 | ||
|
|
954d433f6f | ||
|
|
b18196b394 | ||
|
|
926c14e0ef | ||
|
|
c2c986e780 | ||
|
|
26e87093e1 | ||
|
|
ba0323e434 | ||
|
|
55229e1ed7 | ||
|
|
064213ce52 | ||
|
|
f0802c681c | ||
|
|
a6d2ab7d77 | ||
|
|
c5ced92978 | ||
|
|
7efe32f9c8 | ||
|
|
60f163d540 | ||
|
|
f507f28f30 | ||
|
|
54ae7001ec | ||
|
|
d044cfe142 | ||
|
|
3a1c68b439 | ||
|
|
e0489dc171 | ||
|
|
9919701e2d | ||
|
|
9ed9ba472e | ||
|
|
8513ff260b | ||
|
|
900c2a11f7 | ||
|
|
00ffd8d46c | ||
|
|
cfc326b1ce | ||
|
|
dab4c6bf66 | ||
|
|
e21ece1626 | ||
|
|
f80f1f79a6 | ||
|
|
2414767026 | ||
|
|
887b2ccc6b | ||
|
|
164d29ccbe | ||
|
|
4eb46d0436 | ||
|
|
e0c6bb4094 | ||
|
|
3926055a5e | ||
|
|
8c00134ad1 | ||
|
|
6a104004e0 | ||
|
|
52df58b7dc | ||
|
|
983d58c5a6 | ||
|
|
ec46e616db | ||
|
|
3758e57cb8 | ||
|
|
1eaa8f76ea | ||
|
|
8e64d0b5ef | ||
|
|
baceee2ecf | ||
|
|
a23b7dd2bc | ||
|
|
0225163353 | ||
|
|
9dbe89f5aa | ||
|
|
f89effaa66 | ||
|
|
b7e37af8f1 | ||
|
|
1436e636d3 | ||
|
|
d2c33bbb06 | ||
|
|
c5c22620a6 | ||
|
|
fb7b890e40 | ||
|
|
4e8512e660 | ||
|
|
29de58b5a7 | ||
|
|
7a36b9c6a3 | ||
|
|
85ba820b27 | ||
|
|
54ff8ca196 | ||
|
|
9f40a7cca6 | ||
|
|
9be5970378 | ||
|
|
2214516689 | ||
|
|
b7f6d4a859 | ||
|
|
03404e9858 | ||
|
|
236edcc535 | ||
|
|
822b12cc96 | ||
|
|
79d8157798 | ||
|
|
12795f7298 | ||
|
|
86f6df6607 | ||
|
|
b837aca0f8 | ||
|
|
557ea38869 | ||
|
|
6a78563b32 | ||
|
|
ec6f71aed9 | ||
|
|
faccd7fb9e | ||
|
|
796bc0acee | ||
|
|
ae2638f7a9 | ||
|
|
51b7716cc7 | ||
|
|
c368f98ded | ||
|
|
5f1e77cd67 | ||
|
|
a5b4f0bd87 | ||
|
|
aab2acf008 | ||
|
|
d740df9600 | ||
|
|
cb464939a0 | ||
|
|
0f2b35175c | ||
|
|
33fe99ea43 | ||
|
|
22344045b8 | ||
|
|
a81ed47ebc | ||
|
|
9d247a4fb6 | ||
|
|
33f6db1daf | ||
|
|
cf7e0e14f5 | ||
|
|
88a896684f | ||
|
|
55f5aa5b79 | ||
|
|
a4be2eb79d | ||
|
|
81c7705690 | ||
|
|
4dda34fe28 | ||
|
|
5a05f6896f | ||
|
|
e8f7158d52 | ||
|
|
fa10b78b30 | ||
|
|
b9be120b17 | ||
|
|
4020eb1254 | ||
|
|
15178f0332 | ||
|
|
04c11e266a | ||
|
|
7bd40f0aae | ||
|
|
ca17c6da36 | ||
|
|
16b99a6f72 | ||
|
|
88a4f52ccd | ||
|
|
e975ddca8b | ||
|
|
225afafe43 | ||
|
|
1bafb4686f | ||
|
|
2777ec3e2c | ||
|
|
6f817971d0 | ||
|
|
9358b01cac | ||
|
|
a8f8da9906 | ||
|
|
b2d8550777 | ||
|
|
51b6031eab | ||
|
|
370961e872 | ||
|
|
43e2970620 | ||
|
|
385c4e4a2f | ||
|
|
39591b4776 | ||
|
|
2f0218aaa5 | ||
|
|
9ed897c6be | ||
|
|
b53d907b74 | ||
|
|
7aa2a27fc9 | ||
|
|
742be77cfb | ||
|
|
44946520ef | ||
|
|
35d45f7493 | ||
|
|
a6fa395822 | ||
|
|
48ff96d9e7 | ||
|
|
53e0f89311 | ||
|
|
871a89bf50 | ||
|
|
648fb6f949 | ||
|
|
709fd1bf12 | ||
|
|
cf51fdb026 | ||
|
|
1e8a825e94 | ||
|
|
2ab71798de | ||
|
|
683de6048e | ||
|
|
49e619ce11 | ||
|
|
fc5c8dd74f | ||
|
|
fb46cc7761 | ||
|
|
332867a9f5 | ||
|
|
dfea510e68 | ||
|
|
baff58eecc | ||
|
|
0db933b6a3 | ||
|
|
4552ad9b36 | ||
|
|
78a04905f1 | ||
|
|
3caa922e76 | ||
|
|
24572b554b | ||
|
|
35109800d5 | ||
|
|
1c009266cc | ||
|
|
5ec45459e5 | ||
|
|
37964c1e0f | ||
|
|
ea8add7316 | ||
|
|
baed2da509 | ||
|
|
7be66960b7 | ||
|
|
ab6f72e44d | ||
|
|
251083bf38 | ||
|
|
6426b1e430 | ||
|
|
635b20d4ab | ||
|
|
f38669576a | ||
|
|
c7ed5d7850 | ||
|
|
67a918401c | ||
|
|
08f0e02f42 | ||
|
|
0748cb7698 | ||
|
|
76c9a233b9 | ||
|
|
b3f584686b | ||
|
|
18f06fde2a | ||
|
|
c13545d585 | ||
|
|
112497d55e | ||
|
|
91069f6d38 | ||
|
|
2cf846ead3 | ||
|
|
310e1b4247 | ||
|
|
2f12b08616 | ||
|
|
744ef92faa | ||
|
|
aa8d77e571 | ||
|
|
6f8c9fe548 | ||
|
|
b6ded9a7fe | ||
|
|
468fa88139 | ||
|
|
1aa46713c2 | ||
|
|
9029a51707 | ||
|
|
6df363e2da | ||
|
|
36f5b265c8 | ||
|
|
929ea187ce | ||
|
|
822f203f5a | ||
|
|
54d02b6ab2 | ||
|
|
f9919fe854 | ||
|
|
73de0f228a | ||
|
|
9d73f7735e | ||
|
|
a0209f4667 | ||
|
|
60b36cb3f8 | ||
|
|
454ce02565 | ||
|
|
048434a564 | ||
|
|
0334b75cfa | ||
|
|
ce0ed935c8 | ||
|
|
ad89a63091 | ||
|
|
d148edcb67 | ||
|
|
36d2e29954 | ||
|
|
76d3643e0f | ||
|
|
a12e228cc4 | ||
|
|
669ee6a65f | ||
|
|
a7f734fadf | ||
|
|
8405c2d6cd | ||
|
|
10316c871b | ||
|
|
3b94b105fe | ||
|
|
d7abf96414 | ||
|
|
2c4d9d5af0 | ||
|
|
e14ddc11ee | ||
|
|
11eb122ecc | ||
|
|
40be8ef688 | ||
|
|
d7a72a92f2 | ||
|
|
6338809487 | ||
|
|
68408df4d6 | ||
|
|
7266a97b54 | ||
|
|
808038160f | ||
|
|
994b01719e | ||
|
|
6e6330bf07 | ||
|
|
f8b4958986 | ||
|
|
b7530d494c | ||
|
|
e6aed4bd71 | ||
|
|
57d6723adb | ||
|
|
c0ee3f071e | ||
|
|
54cfe2e99e | ||
|
|
25f6cb7522 | ||
|
|
0a69920b3f | ||
|
|
adad86401f | ||
|
|
6b3a5880b1 | ||
|
|
de4e340338 | ||
|
|
c10519acc2 | ||
|
|
dfb4e8921a | ||
|
|
6e51e5db30 | ||
|
|
7a9e12affa | ||
|
|
011d41af57 | ||
|
|
469e8c0505 | ||
|
|
d59bc07b89 | ||
|
|
e7d101c37e | ||
|
|
b0f83bd1a0 | ||
|
|
9016f25504 | ||
|
|
02134807d8 | ||
|
|
fb4da34562 | ||
|
|
0da322560c | ||
|
|
4aece1395e | ||
|
|
cc06b39905 | ||
|
|
de9eebdf76 | ||
|
|
db1af88681 | ||
|
|
7979110f2b | ||
|
|
9a81b5a598 | ||
|
|
23d44347ac | ||
|
|
6f2526a75a | ||
|
|
2bd4341c07 | ||
|
|
2f3581fe1c | ||
|
|
6ddb7246d8 | ||
|
|
ce2773491a | ||
|
|
614b9c412a | ||
|
|
2dfd4e84e1 | ||
|
|
0f74a0695c | ||
|
|
e85c416d01 | ||
|
|
6ac63baaa5 | ||
|
|
96c630b27c | ||
|
|
ff353a264e | ||
|
|
45097563f0 | ||
|
|
12370a8033 | ||
|
|
d1c67b013b | ||
|
|
3dda3e84bf | ||
|
|
a1a85732d4 | ||
|
|
bc4a2c8dce | ||
|
|
f22c288bf4 | ||
|
|
37a63e1c70 | ||
|
|
0c50c5a1a6 | ||
|
|
f2c487162f | ||
|
|
430696f486 | ||
|
|
2426547762 | ||
|
|
e877467a98 | ||
|
|
060c3de74e | ||
|
|
4c297d2ab4 | ||
|
|
53ccc9e02b | ||
|
|
477b30583e | ||
|
|
315bbbe25e | ||
|
|
62cc84fc9a | ||
|
|
e28bf1152c | ||
|
|
9519b3b0e7 | ||
|
|
b7462211e6 | ||
|
|
13d447a81b | ||
|
|
049d6e61df | ||
|
|
482aba0c45 | ||
|
|
f9a931dbd2 | ||
|
|
ec376e272d | ||
|
|
c13d250577 | ||
|
|
c372f1b66b | ||
|
|
bd1aa229e3 | ||
|
|
1819c69de5 | ||
|
|
4fb043c46a | ||
|
|
4995318418 | ||
|
|
6eaf6ecd14 | ||
|
|
822971fc7d | ||
|
|
fb48402f87 | ||
|
|
fd241c1e85 | ||
|
|
a3fc36e573 | ||
|
|
402b4d3457 | ||
|
|
62625df203 | ||
|
|
658c220c85 | ||
|
|
4190a6bfbc | ||
|
|
7aea928a5a | ||
|
|
58d4326511 | ||
|
|
f7b782440a | ||
|
|
17422184e4 | ||
|
|
c765e7efbb | ||
|
|
a3f4c6290d | ||
|
|
c0cd13abcd | ||
|
|
6fab380cd9 | ||
|
|
3732f89362 | ||
|
|
e25388066d | ||
|
|
a7f823609d | ||
|
|
f9362a1e53 | ||
|
|
7a9d5e646f | ||
|
|
1108daa8ed | ||
|
|
d59de91fd6 | ||
|
|
0cfc31f4ad | ||
|
|
e1df47dedc | ||
|
|
b6e4e0d670 | ||
|
|
5d0338a278 | ||
|
|
57aad64c23 | ||
|
|
743db0d265 | ||
|
|
a96a3c6623 | ||
|
|
60c17953c9 | ||
|
|
2068de722c | ||
|
|
33d86505e8 | ||
|
|
2a52d04064 | ||
|
|
7911bc6aea | ||
|
|
733d908c89 | ||
|
|
798ea44c60 | ||
|
|
08d4a3759b | ||
|
|
b29f602ce9 | ||
|
|
09c8144f31 | ||
|
|
b3bd8c5929 | ||
|
|
99790b1560 | ||
|
|
31441e8890 | ||
|
|
f693acbaa9 | ||
|
|
c1548a8cbb | ||
|
|
fc7a75cae2 | ||
|
|
adced907c9 | ||
|
|
49ae9d43ad | ||
|
|
b40ae12e63 | ||
|
|
0ae9fa8342 | ||
|
|
833d0b293f | ||
|
|
ea9a870604 | ||
|
|
c7e5427200 | ||
|
|
96ddda0009 | ||
|
|
635115f94b | ||
|
|
9bb1d799d8 | ||
|
|
eabd15092c | ||
|
|
5e85389792 | ||
|
|
42eb69bb95 | ||
|
|
09c9ff5ecd | ||
|
|
2678656f12 | ||
|
|
eb2a7b56b0 | ||
|
|
f33329d396 | ||
|
|
7d7340bbfc | ||
|
|
0bf2115701 | ||
|
|
0357bfdbaa | ||
|
|
52a76299c5 | ||
|
|
8de3151b8f | ||
|
|
2b3ceba353 | ||
|
|
378274bc3f | ||
|
|
c3145b0801 | ||
|
|
5aca7dafec | ||
|
|
803b1ae91b | ||
|
|
402c4e75a2 | ||
|
|
7a1859d1dd | ||
|
|
927117d5db | ||
|
|
837f433b5c | ||
|
|
b5b18ffefe | ||
|
|
48451e3f81 | ||
|
|
ec9e334f9a | ||
|
|
f646ddfc0f | ||
|
|
2102441cd0 | ||
|
|
95149c3fe4 | ||
|
|
0bfbd40810 | ||
|
|
b3a70e980e | ||
|
|
952c369646 | ||
|
|
3effec0dd5 | ||
|
|
2d563e1694 | ||
|
|
e9ceac13b8 | ||
|
|
06a0607287 | ||
|
|
7e159646fb | ||
|
|
d2781430d3 | ||
|
|
1bb8c80342 | ||
|
|
01e83247ee | ||
|
|
12653f97ad | ||
|
|
6bcb8651a0 | ||
|
|
0071ca68be | ||
|
|
a89bbdfdf9 | ||
|
|
c7bd599e16 | ||
|
|
c66a5b580d | ||
|
|
f145b6a7d2 | ||
|
|
4337f0bab0 | ||
|
|
6b37797de9 | ||
|
|
1843ac4d31 | ||
|
|
6399253318 | ||
|
|
f3638fb871 | ||
|
|
dc88a310cb | ||
|
|
aea9bd4882 | ||
|
|
b69bb4346c | ||
|
|
9af6aa4ba3 | ||
|
|
dff2704662 | ||
|
|
4849352b34 | ||
|
|
87700d9ab9 | ||
|
|
c69c0fd915 | ||
|
|
4c4d5c7baa | ||
|
|
1e9d2cf9a0 | ||
|
|
5579aba636 | ||
|
|
c66ad6a60f | ||
|
|
8e7f087a68 | ||
|
|
72142c32a1 | ||
|
|
5999ec7acd | ||
|
|
af401cdc9b | ||
|
|
f879e3ebdd | ||
|
|
a0b838c947 | ||
|
|
eb2fac75cd | ||
|
|
f0b1e57dcb | ||
|
|
f8f12b0596 | ||
|
|
de25859329 | ||
|
|
b4430de8de | ||
|
|
6d6c4aa4b8 | ||
|
|
baadff146e | ||
|
|
0b85490804 | ||
|
|
8d256228a0 | ||
|
|
5ad1af6d91 | ||
|
|
38b3095b82 | ||
|
|
9289d0cd43 | ||
|
|
d834c0fc0a | ||
|
|
7a995c3801 | ||
|
|
2f41a2bc09 | ||
|
|
e1a80c30be | ||
|
|
19994ccf60 | ||
|
|
1a59b0605f | ||
|
|
1c9aeee901 | ||
|
|
31bfaa0569 | ||
|
|
b24182df5b | ||
|
|
38923e1f91 | ||
|
|
aefee0fad2 | ||
|
|
aa2f53f2d5 | ||
|
|
a0bce9b3c5 | ||
|
|
8e5b7fdc19 | ||
|
|
4ee63506f8 | ||
|
|
1a239f92f4 | ||
|
|
4f15b724ac | ||
|
|
1c68a24a70 | ||
|
|
d863acbc65 | ||
|
|
c736f2ccb4 | ||
|
|
3942b88565 | ||
|
|
a5ac2aca10 | ||
|
|
55ced7a3e5 | ||
|
|
0f2c677035 | ||
|
|
7493f7d485 | ||
|
|
ccbe18b520 | ||
|
|
8bde0bc204 | ||
|
|
28df25e804 | ||
|
|
fa5cb50df9 | ||
|
|
b847e02ff8 | ||
|
|
3e7411440b | ||
|
|
52fa239ae1 | ||
|
|
fdaed3fd8f | ||
|
|
8a3235ce6f | ||
|
|
e054c17e31 | ||
|
|
c7b6b4fc3e | ||
|
|
4d60b96a75 | ||
|
|
0f0b546fa6 | ||
|
|
ed454218b6 | ||
|
|
b2345de9cc | ||
|
|
5dc393f397 | ||
|
|
ca654d938b | ||
|
|
0b0fb6bf1f | ||
|
|
ce85bb0578 | ||
|
|
4c9538d51f | ||
|
|
dc5157a252 | ||
|
|
9cc6d7f4da | ||
|
|
18b301e2a6 | ||
|
|
35f52410d9 | ||
|
|
cf55c05691 | ||
|
|
be9360fbf3 | ||
|
|
3fc07738cb | ||
|
|
299452eb8d | ||
|
|
655bce7795 | ||
|
|
0289b38e4e | ||
|
|
bceb401d19 | ||
|
|
cef74ff787 | ||
|
|
de3b1c896f | ||
|
|
c94bbbc3d3 | ||
|
|
877737d872 | ||
|
|
2c47baeed4 | ||
|
|
0321a03e38 | ||
|
|
ba28773dae | ||
|
|
fcb3f7fb4f | ||
|
|
1db52a9384 | ||
|
|
47986470a3 | ||
|
|
4ddafa700f | ||
|
|
2d90f1b02d | ||
|
|
19d67cb42b | ||
|
|
df2e5c8ab0 | ||
|
|
02d43366c7 | ||
|
|
b51ceb26f8 | ||
|
|
96f559c513 | ||
|
|
1ed613d5be | ||
|
|
2cba7cad2a | ||
|
|
57c684abf3 | ||
|
|
eb3970d779 | ||
|
|
39f09d1b4b | ||
|
|
b876da60aa | ||
|
|
48f85b4450 | ||
|
|
6af4b9ecd5 | ||
|
|
7441730b24 | ||
|
|
f444350a4f | ||
|
|
0892443104 | ||
|
|
c97a0cffe1 | ||
|
|
8ff7340eb2 | ||
|
|
9dcf4d0264 | ||
|
|
4a38798595 | ||
|
|
b80804dae5 | ||
|
|
8e4d885268 | ||
|
|
51f229b0bd | ||
|
|
c6a16a0279 | ||
|
|
16c0a93d46 | ||
|
|
857129c60f | ||
|
|
84e6a52834 | ||
|
|
9a47c6c6d5 | ||
|
|
17362c0cad | ||
|
|
e022bffe40 | ||
|
|
f3381e95ef | ||
|
|
294f1d7983 | ||
|
|
9bfb33d88c | ||
|
|
12b874507f | ||
|
|
cee97582fc | ||
|
|
c66b6843a4 | ||
|
|
9fc03bbc9a | ||
|
|
36c875a7f7 | ||
|
|
48b175fa43 | ||
|
|
4d35ea9bf3 | ||
|
|
62368fda23 | ||
|
|
6b89eca022 | ||
|
|
000c02247d | ||
|
|
6b899188c4 | ||
|
|
24f0efc0e2 | ||
|
|
b6bbb28757 | ||
|
|
8e1cd54c92 | ||
|
|
8b3dff12e8 | ||
|
|
c82b18f772 | ||
|
|
2642e15aff | ||
|
|
bf4dac5c3b | ||
|
|
8729214f8b | ||
|
|
ff6073a932 | ||
|
|
2cfee720a3 | ||
|
|
061e3d212d | ||
|
|
04bb549e36 | ||
|
|
68fcb587a9 | ||
|
|
93165ef0b2 | ||
|
|
a616124cc6 | ||
|
|
2ccb8f9a17 | ||
|
|
4b58fac608 | ||
|
|
a4a3d8a6eb | ||
|
|
dbcb18bffc | ||
|
|
a499580b4d | ||
|
|
4fd952b0da | ||
|
|
234ed2718f | ||
|
|
77c1818572 | ||
|
|
b704aca0fe | ||
|
|
8226f81026 | ||
|
|
1b062aa597 | ||
|
|
3f863cace4 | ||
|
|
737536297c | ||
|
|
c9017b5bf9 | ||
|
|
89dc51fab5 | ||
|
|
78067113d7 | ||
|
|
bef3abbe2c | ||
|
|
1255c24898 | ||
|
|
4e0b923552 | ||
|
|
ef9a6ac6aa | ||
|
|
60c0d725c9 | ||
|
|
59450f470a | ||
|
|
09dfd736db | ||
|
|
3852c9bf3f | ||
|
|
f3d9208d08 | ||
|
|
c62236791a | ||
|
|
bbe6ff820a | ||
|
|
47679469ac | ||
|
|
f7e79a31a0 | ||
|
|
2d8c995756 | ||
|
|
70a0a594b4 | ||
|
|
4273d62460 | ||
|
|
847c71ecf2 | ||
|
|
c493d651db | ||
|
|
06c954f8fe | ||
|
|
9567656b45 | ||
|
|
966737be23 | ||
|
|
6c1188a787 | ||
|
|
39fffe6874 | ||
|
|
a75ae8a1b1 | ||
|
|
c1d49fe8aa | ||
|
|
577831f775 | ||
|
|
098ad6e98c | ||
|
|
248fdbaddf | ||
|
|
417a1abcf7 | ||
|
|
d36ecec9c4 | ||
|
|
7a2895d3a3 | ||
|
|
58f156a491 | ||
|
|
02ee8602cd | ||
|
|
e999f5dd72 | ||
|
|
5e1b099285 | ||
|
|
87b1f5a5fb | ||
|
|
ae8cbdce0a | ||
|
|
95de48c03f | ||
|
|
5ea4fd9a5e | ||
|
|
1f3d9e7e0f | ||
|
|
05e4528b1c | ||
|
|
40789776bc | ||
|
|
75759edd26 | ||
|
|
2a5dc1dba6 | ||
|
|
0cbf50b7ff | ||
|
|
a3f5b805bb | ||
|
|
c337877501 | ||
|
|
a6e9dd850a | ||
|
|
a14d4057de | ||
|
|
210240ce66 | ||
|
|
d21f7fe75c | ||
|
|
54805cd2a0 | ||
|
|
c7da2e732a | ||
|
|
4b4beaa892 | ||
|
|
e13596acae | ||
|
|
8eb065181f | ||
|
|
1d97219fef | ||
|
|
d312f94825 | ||
|
|
478a6f7369 | ||
|
|
e309123296 | ||
|
|
777646cb0e | ||
|
|
39e23f7144 | ||
|
|
b68b5c0ea3 | ||
|
|
46dfeb8ca9 | ||
|
|
6771ad43af | ||
|
|
1881b9c6cb | ||
|
|
dae6b7a980 | ||
|
|
aed6209f6a | ||
|
|
b44a590aa0 | ||
|
|
936b5fc983 | ||
|
|
473d6617f2 | ||
|
|
fcf50b5aeb | ||
|
|
a3835d2e8a | ||
|
|
d89481966e | ||
|
|
3148ce0f31 | ||
|
|
f65363856a | ||
|
|
e205b57352 | ||
|
|
f44a1dbf21 | ||
|
|
b7fe2f606c | ||
|
|
6e81c1795c | ||
|
|
141d8e4467 | ||
|
|
edbfeab75f | ||
|
|
e48ea10ebb | ||
|
|
8565af5526 | ||
|
|
511d3bae8f | ||
|
|
8df233fef1 | ||
|
|
4b97410cc0 | ||
|
|
aadf8097d1 | ||
|
|
40d8b74b1b | ||
|
|
8c4d42891c | ||
|
|
3672374d23 | ||
|
|
9ff9d3f174 | ||
|
|
86540fa7fd | ||
|
|
1538c80a7d | ||
|
|
0225fcfe51 | ||
|
|
57f3861de6 | ||
|
|
04c365a251 | ||
|
|
7dfe4690ce | ||
|
|
92bcf1b7c9 | ||
|
|
8a15d5c65a | ||
|
|
f64374225d | ||
|
|
7537eac6a7 | ||
|
|
e2880d2434 | ||
|
|
8296de16ef | ||
|
|
5cff5e9dfd | ||
|
|
cb07a748c2 | ||
|
|
c7b985bdc6 | ||
|
|
bf32ca0e4a | ||
|
|
a4e52de0e3 | ||
|
|
e43e9b11a0 | ||
|
|
847b9e97c4 | ||
|
|
baad689286 | ||
|
|
5e7afc4385 | ||
|
|
d724b7a631 | ||
|
|
5e17fe5ad6 | ||
|
|
b216adadc0 | ||
|
|
5078c95667 | ||
|
|
7612d77647 | ||
|
|
0d76041c10 | ||
|
|
59be596d24 | ||
|
|
63e51a3c98 | ||
|
|
7f3128b3e5 | ||
|
|
c235813ae7 | ||
|
|
3ee18e7051 | ||
|
|
14fc5d8796 | ||
|
|
9fb0077385 | ||
|
|
2e9c7265b6 | ||
|
|
d9e77b784a | ||
|
|
b5b296ad7f | ||
|
|
acdfde5aa2 | ||
|
|
0eddfbd28c | ||
|
|
037f0610bc | ||
|
|
dd45f8f2ab | ||
|
|
d454c84f61 | ||
|
|
a4c98e07a5 | ||
|
|
85715630bd | ||
|
|
45d8d4a84f | ||
|
|
690882c97a | ||
|
|
e03836e4a1 | ||
|
|
d4ec54135a | ||
|
|
b29785d851 | ||
|
|
148870d706 | ||
|
|
ed1f7e335d | ||
|
|
eaf4695194 | ||
|
|
c312d42302 | ||
|
|
a8a2a6c066 | ||
|
|
8b3512cb07 | ||
|
|
945facdd2c | ||
|
|
8676ed4cff | ||
|
|
e4d4afa840 | ||
|
|
81cf286ea4 | ||
|
|
e834625728 | ||
|
|
a7c22eb08c | ||
|
|
837c358371 | ||
|
|
9743db27e7 | ||
|
|
ce725143e6 | ||
|
|
677805c33a | ||
|
|
3d3f1fe39b | ||
|
|
123c68ad2b | ||
|
|
86861c58af | ||
|
|
d4ddb6b3f9 | ||
|
|
0fc7de40d4 | ||
|
|
3cb4a9c8dd | ||
|
|
d681289457 | ||
|
|
bc1c3c3f5b | ||
|
|
5ae4287c0f | ||
|
|
cde9f56886 | ||
|
|
2b6b006bbd | ||
|
|
54e1bcafc0 | ||
|
|
7e98d0a22b | ||
|
|
c27b4a6aaf | ||
|
|
8f17ffd817 | ||
|
|
0a7d688d32 | ||
|
|
8cb2fe1284 | ||
|
|
e6e375232e | ||
|
|
08c4ab8a0c | ||
|
|
f86647fc26 | ||
|
|
f310f6a86f | ||
|
|
58c6acd265 | ||
|
|
c7d97e3866 | ||
|
|
0890144c61 | ||
|
|
69a7d91b57 | ||
|
|
a9016c88f6 | ||
|
|
e8990742cf | ||
|
|
f51400a3a1 | ||
|
|
e763ee5301 | ||
|
|
fd978699e8 | ||
|
|
bbd9585829 | ||
|
|
54cf1ebb31 | ||
|
|
6fc77c6cfa | ||
|
|
f8372e3bb9 | ||
|
|
6728be7b1a | ||
|
|
65e1a1e731 | ||
|
|
cd9478e853 | ||
|
|
b9364ed4fc | ||
|
|
2715e8e9d8 | ||
|
|
f987c93cf0 | ||
|
|
5c254a7151 | ||
|
|
f253e29f33 | ||
|
|
f37fa13eab | ||
|
|
64765b393a | ||
|
|
54c84b4ce0 | ||
|
|
146bcba794 | ||
|
|
d3dd1b731d | ||
|
|
d608ee7390 | ||
|
|
4e2e0950c7 | ||
|
|
ddc7e97ab6 | ||
|
|
a21759ee42 | ||
|
|
cc4beb94cf | ||
|
|
0bfafa9311 | ||
|
|
4eefc34629 | ||
|
|
4d3f882dc0 | ||
|
|
3a802fbb70 | ||
|
|
6f00b03d24 |
@@ -1,6 +1,5 @@
|
|||||||
version: 2
|
version: 2
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
install:
|
install:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/node:10
|
- image: circleci/node:10
|
||||||
@@ -22,10 +21,11 @@ jobs:
|
|||||||
command: sudo apt-get install golang-go
|
command: sudo apt-get install golang-go
|
||||||
- run:
|
- run:
|
||||||
name: Installing Dependencies
|
name: Installing Dependencies
|
||||||
command: yarn install
|
command: yarn install --check-files --frozen-lockfile
|
||||||
- save_cache:
|
- save_cache:
|
||||||
paths:
|
paths:
|
||||||
- node_modules
|
- node_modules
|
||||||
|
- packages/gatsby-plugin-now/node_modules
|
||||||
- packages/now-build-utils/node_modules
|
- packages/now-build-utils/node_modules
|
||||||
- packages/now-cgi/node_modules
|
- packages/now-cgi/node_modules
|
||||||
- packages/now-cli/node_modules
|
- packages/now-cli/node_modules
|
||||||
@@ -43,6 +43,7 @@ jobs:
|
|||||||
root: .
|
root: .
|
||||||
paths:
|
paths:
|
||||||
- node_modules
|
- node_modules
|
||||||
|
- packages/gatsby-plugin-now/node_modules
|
||||||
- packages/now-build-utils/node_modules
|
- packages/now-build-utils/node_modules
|
||||||
- packages/now-cgi/node_modules
|
- packages/now-cgi/node_modules
|
||||||
- packages/now-cli/node_modules
|
- packages/now-cli/node_modules
|
||||||
@@ -67,6 +68,9 @@ jobs:
|
|||||||
command: sudo apt install -y rsync
|
command: sudo apt install -y rsync
|
||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
at: .
|
at: .
|
||||||
|
- run:
|
||||||
|
name: Linking dependencies
|
||||||
|
command: yarn bootstrap
|
||||||
- run:
|
- run:
|
||||||
name: Building
|
name: Building
|
||||||
command: yarn build
|
command: yarn build
|
||||||
@@ -75,6 +79,7 @@ jobs:
|
|||||||
- persist_to_workspace:
|
- persist_to_workspace:
|
||||||
root: .
|
root: .
|
||||||
paths:
|
paths:
|
||||||
|
- packages/gatsby-plugin-now/test/fixtures
|
||||||
- packages/now-build-utils/dist
|
- packages/now-build-utils/dist
|
||||||
- packages/now-cgi/dist
|
- packages/now-cgi/dist
|
||||||
- packages/now-cli/dist
|
- packages/now-cli/dist
|
||||||
@@ -91,6 +96,7 @@ jobs:
|
|||||||
- packages/now-routing-utils/dist
|
- packages/now-routing-utils/dist
|
||||||
- packages/now-ruby/dist
|
- packages/now-ruby/dist
|
||||||
- packages/now-static-build/dist
|
- packages/now-static-build/dist
|
||||||
|
- packages/now-static-build/test/fixtures/10a-gatsby-redirects/plugins
|
||||||
|
|
||||||
test-lint:
|
test-lint:
|
||||||
docker:
|
docker:
|
||||||
@@ -107,36 +113,14 @@ jobs:
|
|||||||
name: Linting Code
|
name: Linting Code
|
||||||
command: yarn test-lint
|
command: yarn test-lint
|
||||||
|
|
||||||
# test-unit:
|
|
||||||
# docker:
|
|
||||||
# - image: circleci/node:10
|
|
||||||
# working_directory: ~/repo
|
|
||||||
# steps:
|
|
||||||
# - checkout
|
|
||||||
# - attach_workspace:
|
|
||||||
# at: .
|
|
||||||
# - run:
|
|
||||||
# name: Compiling `now dev` HTML error templates
|
|
||||||
# command: node packages/now-cli/scripts/compile-templates.js
|
|
||||||
# - run:
|
|
||||||
# name: Running Unit Tests
|
|
||||||
# command: yarn test-unit --clean false
|
|
||||||
# - persist_to_workspace:
|
|
||||||
# root: .
|
|
||||||
# paths:
|
|
||||||
# - packages/now-cli/.nyc_output
|
|
||||||
|
|
||||||
test-integration-macos-node-8:
|
test-integration-macos-node-8:
|
||||||
macos:
|
macos:
|
||||||
xcode: '9.2.0'
|
xcode: '9.0.1'
|
||||||
working_directory: ~/repo
|
working_directory: ~/repo
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
at: .
|
at: .
|
||||||
- run:
|
|
||||||
name: Update Node.js
|
|
||||||
command: curl -sfLS install-node.now.sh/8.11 | sh -s -- --yes
|
|
||||||
- run:
|
- run:
|
||||||
name: Output version
|
name: Output version
|
||||||
command: node --version
|
command: node --version
|
||||||
@@ -221,15 +205,12 @@ jobs:
|
|||||||
|
|
||||||
test-integration-macos-now-dev-node-8:
|
test-integration-macos-now-dev-node-8:
|
||||||
macos:
|
macos:
|
||||||
xcode: '9.2.0'
|
xcode: '9.0.1'
|
||||||
working_directory: ~/repo
|
working_directory: ~/repo
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
at: .
|
at: .
|
||||||
- run:
|
|
||||||
name: Update Node.js
|
|
||||||
command: curl -sfLS install-node.now.sh/8.11 | sh -s -- --yes
|
|
||||||
- run:
|
- run:
|
||||||
name: Output version
|
name: Output version
|
||||||
command: node --version
|
command: node --version
|
||||||
@@ -345,6 +326,24 @@ jobs:
|
|||||||
name: Running Integration Tests Once
|
name: Running Integration Tests Once
|
||||||
command: yarn test-integration-once --clean false
|
command: yarn test-integration-once --clean false
|
||||||
|
|
||||||
|
test-unit:
|
||||||
|
docker:
|
||||||
|
- image: circleci/node:10
|
||||||
|
working_directory: ~/repo
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- attach_workspace:
|
||||||
|
at: .
|
||||||
|
- run:
|
||||||
|
name: Compiling `now dev` HTML error templates
|
||||||
|
command: node packages/now-cli/scripts/compile-templates.js
|
||||||
|
- run:
|
||||||
|
name: Output version
|
||||||
|
command: node --version
|
||||||
|
- run:
|
||||||
|
name: Running Unit Tests
|
||||||
|
command: yarn test-unit --clean false
|
||||||
|
|
||||||
coverage:
|
coverage:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/node:10
|
- image: circleci/node:10
|
||||||
@@ -384,36 +383,6 @@ jobs:
|
|||||||
name: Finalize Sentry Release
|
name: Finalize Sentry Release
|
||||||
command: sentry-cli releases finalize now-cli@`git describe --tags`
|
command: sentry-cli releases finalize now-cli@`git describe --tags`
|
||||||
|
|
||||||
publish-stable:
|
|
||||||
docker:
|
|
||||||
- image: circleci/node:10
|
|
||||||
working_directory: ~/repo
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
- attach_workspace:
|
|
||||||
at: .
|
|
||||||
- run:
|
|
||||||
name: Saving Authentication Information
|
|
||||||
command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
|
|
||||||
- run:
|
|
||||||
name: Publishing to Stable Channel
|
|
||||||
command: npm publish --tag latest
|
|
||||||
|
|
||||||
publish-canary:
|
|
||||||
docker:
|
|
||||||
- image: circleci/node:10
|
|
||||||
working_directory: ~/repo
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
- attach_workspace:
|
|
||||||
at: .
|
|
||||||
- run:
|
|
||||||
name: Saving Authentication Information
|
|
||||||
command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
|
|
||||||
- run:
|
|
||||||
name: Publishing to Canary Channel
|
|
||||||
command: npm publish --tag canary
|
|
||||||
|
|
||||||
workflows:
|
workflows:
|
||||||
version: 2
|
version: 2
|
||||||
unscheduled:
|
unscheduled:
|
||||||
@@ -434,12 +403,6 @@ workflows:
|
|||||||
filters:
|
filters:
|
||||||
tags:
|
tags:
|
||||||
only: /.*/
|
only: /.*/
|
||||||
# - test-unit:
|
|
||||||
# requires:
|
|
||||||
# - build
|
|
||||||
# filters:
|
|
||||||
# tags:
|
|
||||||
# only: /.*/
|
|
||||||
- test-integration-macos-node-8:
|
- test-integration-macos-node-8:
|
||||||
requires:
|
requires:
|
||||||
- build
|
- build
|
||||||
@@ -515,12 +478,14 @@ workflows:
|
|||||||
- test-integration-once:
|
- test-integration-once:
|
||||||
requires:
|
requires:
|
||||||
- build
|
- build
|
||||||
|
- test-unit:
|
||||||
|
requires:
|
||||||
|
- build
|
||||||
filters:
|
filters:
|
||||||
tags:
|
tags:
|
||||||
only: /.*/
|
only: /.*/
|
||||||
- coverage:
|
- coverage:
|
||||||
requires:
|
requires:
|
||||||
#- test-unit
|
|
||||||
- test-integration-macos-node-8
|
- test-integration-macos-node-8
|
||||||
- test-integration-macos-node-10
|
- test-integration-macos-node-10
|
||||||
- test-integration-macos-node-12
|
- test-integration-macos-node-12
|
||||||
@@ -534,23 +499,8 @@ workflows:
|
|||||||
- test-integration-linux-now-dev-node-10
|
- test-integration-linux-now-dev-node-10
|
||||||
- test-integration-linux-now-dev-node-12
|
- test-integration-linux-now-dev-node-12
|
||||||
- test-integration-once
|
- test-integration-once
|
||||||
|
- test-unit
|
||||||
- test-lint
|
- test-lint
|
||||||
filters:
|
filters:
|
||||||
tags:
|
tags:
|
||||||
only: /.*/
|
only: /.*/
|
||||||
- publish-canary:
|
|
||||||
requires:
|
|
||||||
- coverage
|
|
||||||
filters:
|
|
||||||
tags:
|
|
||||||
only: /^.*canary.*($|\b)/
|
|
||||||
branches:
|
|
||||||
ignore: /.*/
|
|
||||||
- publish-stable:
|
|
||||||
requires:
|
|
||||||
- coverage
|
|
||||||
filters:
|
|
||||||
tags:
|
|
||||||
only: /^(\d+\.)?(\d+\.)?(\*|\d+)$/
|
|
||||||
branches:
|
|
||||||
ignore: /.*/
|
|
||||||
|
|||||||
32
.eslintignore
Normal file
32
.eslintignore
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
|
||||||
|
# gatsby-plugin-now
|
||||||
|
packages/gatsby-plugin-now/test/fixtures
|
||||||
|
|
||||||
|
# now-cli
|
||||||
|
packages/now-cli/@types
|
||||||
|
packages/now-cli/download
|
||||||
|
packages/now-cli/dist
|
||||||
|
packages/now-cli/test/fixtures
|
||||||
|
packages/now-cli/test/dev/fixtures
|
||||||
|
packages/now-cli/bin
|
||||||
|
packages/now-cli/link
|
||||||
|
packages/now-cli/src/util/dev/templates/*.ts
|
||||||
|
|
||||||
|
# now-client
|
||||||
|
packages/now-client/tests/fixtures
|
||||||
|
packages/now-client/lib
|
||||||
|
|
||||||
|
# now-next
|
||||||
|
packages/now-next/test/fixtures
|
||||||
|
|
||||||
|
# now-node
|
||||||
|
packages/now-node/src/bridge.ts
|
||||||
|
packages/now-node/test/fixtures
|
||||||
|
|
||||||
|
# now-node-bridge
|
||||||
|
packages/now-node-bridge/bridge.*
|
||||||
|
|
||||||
|
# now-static-build
|
||||||
|
packages/now-static-build/test/fixtures
|
||||||
64
.eslintrc.json
Normal file
64
.eslintrc.json
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 2018,
|
||||||
|
"sourceType": "module",
|
||||||
|
"modules": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:@typescript-eslint/eslint-recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"prettier",
|
||||||
|
"prettier/@typescript-eslint"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"node": true,
|
||||||
|
"jest": true,
|
||||||
|
"es6": true
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"require-atomic-updates": 0,
|
||||||
|
"@typescript-eslint/ban-ts-ignore": 0,
|
||||||
|
"@typescript-eslint/camelcase": 0,
|
||||||
|
"@typescript-eslint/explicit-function-return-type": 0,
|
||||||
|
"@typescript-eslint/no-empty-function": 0,
|
||||||
|
"@typescript-eslint/no-use-before-define": 0
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["**/*.js"],
|
||||||
|
"rules": {
|
||||||
|
"@typescript-eslint/no-var-requires": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["packages/now-cli/**/*"],
|
||||||
|
"rules": {
|
||||||
|
"lines-between-class-members": 0,
|
||||||
|
"no-async-promise-executor": 0,
|
||||||
|
"no-control-regex": 0,
|
||||||
|
"no-empty": 0,
|
||||||
|
"prefer-const": 0,
|
||||||
|
"prefer-destructuring": 0,
|
||||||
|
"@typescript-eslint/ban-types": 0,
|
||||||
|
"@typescript-eslint/consistent-type-assertions": 0,
|
||||||
|
"@typescript-eslint/member-delimiter-style": 0,
|
||||||
|
"@typescript-eslint/no-empty-function": 0,
|
||||||
|
"@typescript-eslint/no-explicit-any": 0,
|
||||||
|
"@typescript-eslint/no-inferrable-types": 0,
|
||||||
|
"@typescript-eslint/no-var-requires": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["packages/now-client/**/*"],
|
||||||
|
"rules": {
|
||||||
|
"prefer-const": 0,
|
||||||
|
"require-atomic-updates": 0,
|
||||||
|
"@typescript-eslint/ban-ts-ignore": 0,
|
||||||
|
"@typescript-eslint/no-explicit-any": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Ignore test fixtures in GitHub Languages
|
||||||
|
# See https://github.com/github/linguist#vendored-code
|
||||||
|
packages/*/test/* linguist-vendored
|
||||||
4
.github/workflows/publish.yml
vendored
4
.github/workflows/publish.yml
vendored
@@ -9,7 +9,7 @@ on:
|
|||||||
- '!*'
|
- '!*'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish:
|
Publish:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
- name: Install
|
- name: Install
|
||||||
run: yarn install --pure-lockfile
|
run: yarn install --check-files --frozen-lockfile
|
||||||
- name: Build
|
- name: Build
|
||||||
run: yarn build
|
run: yarn build
|
||||||
- name: Publish
|
- name: Publish
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -12,7 +12,9 @@ packages/now-cli/.builders
|
|||||||
packages/now-cli/assets
|
packages/now-cli/assets
|
||||||
packages/now-cli/src/util/dev/templates/*.ts
|
packages/now-cli/src/util/dev/templates/*.ts
|
||||||
packages/now-cli/test/**/yarn.lock
|
packages/now-cli/test/**/yarn.lock
|
||||||
|
!packages/now-cli/test/dev/**/yarn.lock
|
||||||
packages/now-cli/test/**/node_modules
|
packages/now-cli/test/**/node_modules
|
||||||
packages/now-cli/test/dev/fixtures/08-hugo/hugo
|
packages/now-cli/test/dev/fixtures/08-hugo/hugo
|
||||||
packages/now-cli/test/dev/fixtures/**/dist
|
packages/now-cli/test/dev/fixtures/**/dist
|
||||||
packages/now-cli/test/dev/fixtures/**/public
|
packages/now-cli/test/dev/fixtures/**/public
|
||||||
|
packages/now-cli/test/fixtures/integration
|
||||||
|
|||||||
@@ -12,5 +12,6 @@ optimistic_updates = true
|
|||||||
|
|
||||||
[merge.message]
|
[merge.message]
|
||||||
title = "pull_request_title"
|
title = "pull_request_title"
|
||||||
|
body = "pull_request_body"
|
||||||
include_pr_number = true
|
include_pr_number = true
|
||||||
body_type = "markdown"
|
body_type = "markdown"
|
||||||
|
|||||||
4
.prettierrc
Normal file
4
.prettierrc
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "es5"
|
||||||
|
}
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
# Builders Developer Reference
|
# Runtime Developer Reference
|
||||||
|
|
||||||
The following page is a reference for how to create a Builder using the available Builder's API.
|
The following page is a reference for how to create a Runtime using the available Runtime API.
|
||||||
|
|
||||||
A Builder is an npm module that exposes a `build` function and optionally an `analyze` function and `prepareCache` function.
|
A Runtime is an npm module that exposes a `build` function and optionally an `analyze` function and `prepareCache` function.
|
||||||
Official Builders are published to [npmjs.com](https://npmjs.com) as a package and referenced in the `use` property of the `now.json` configuration file.
|
Official Runtimes are published to [npmjs.com](https://npmjs.com) as a package and referenced in the `use` property of the `now.json` configuration file.
|
||||||
However, the `use` property will work with any [npm install argument](https://docs.npmjs.com/cli/install) such as a git repo url which is useful for testing your Builder.
|
However, the `use` property will work with any [npm install argument](https://docs.npmjs.com/cli/install) such as a git repo url which is useful for testing your Runtime.
|
||||||
|
|
||||||
See the [Builders Documentation](https://zeit.co/docs/v2/advanced/builders) to view example usage.
|
See the [Runtimes Documentation](https://zeit.co/docs/v2/advanced/runtimes) to view example usage.
|
||||||
|
|
||||||
## Builder Exports
|
## Runtime Exports
|
||||||
|
|
||||||
### `version`
|
### `version`
|
||||||
|
|
||||||
A **required** exported constant that decides which version of the Builder API to use.
|
A **required** exported constant that decides which version of the Runtime API to use.
|
||||||
|
|
||||||
The latest and suggested version is `2`.
|
The latest and suggested version is `3`.
|
||||||
|
|
||||||
### `analyze`
|
### `analyze`
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ export analyze(options: AnalyzeOptions) {
|
|||||||
|
|
||||||
### `build`
|
### `build`
|
||||||
|
|
||||||
A **required** exported function that returns a [Files](#files) data structure that contains the Build outputs, which can be a [Static File](#file) or a [Serverless Function](#serverless-function).
|
A **required** exported function that returns a [Serverless Function](#serverless-function).
|
||||||
|
|
||||||
What's a Serverless Function? Read about [Serverless Function concepts](https://zeit.co/docs/v2/deployments/concepts/lambdas) to learn more.
|
What's a Serverless Function? Read about [Serverless Function concepts](https://zeit.co/docs/v2/deployments/concepts/lambdas) to learn more.
|
||||||
|
|
||||||
@@ -58,9 +58,9 @@ build({
|
|||||||
filesRemoved?: Array<String>
|
filesRemoved?: Array<String>
|
||||||
}
|
}
|
||||||
}) : {
|
}) : {
|
||||||
watch: Array<String>,
|
watch?: Array<String>,
|
||||||
output: Files output,
|
output: Lambda,
|
||||||
routes: Object
|
routes?: Object
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ export prepareCache(options: PrepareCacheOptions) {
|
|||||||
|
|
||||||
### `shouldServe`
|
### `shouldServe`
|
||||||
|
|
||||||
An **optional** exported function that is only used by `now dev` in [Now CLI](https:///download) and indicates whether a [Builder](https://zeit.co/docs/v2/advanced/builders) wants to be responsible for building a certain request path.
|
An **optional** exported function that is only used by `now dev` in [Now CLI](https:///download) and indicates whether a [Runtime](https://zeit.co/docs/v2/advanced/runtimes) wants to be responsible for building a certain request path.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
shouldServe({
|
shouldServe({
|
||||||
@@ -133,7 +133,7 @@ export shouldServe(options: ShouldServeOptions) {
|
|||||||
|
|
||||||
If this method is not defined, Now CLI will default to [this function](https://github.com/zeit/now/blob/52994bfe26c5f4f179bdb49783ee57ce19334631/packages/now-build-utils/src/should-serve.ts).
|
If this method is not defined, Now CLI will default to [this function](https://github.com/zeit/now/blob/52994bfe26c5f4f179bdb49783ee57ce19334631/packages/now-build-utils/src/should-serve.ts).
|
||||||
|
|
||||||
### Builder Options
|
### Runtime Options
|
||||||
|
|
||||||
The exported functions [`analyze`](#analyze), [`build`](#build), and [`prepareCache`](#preparecache) receive one argument with the following properties.
|
The exported functions [`analyze`](#analyze), [`build`](#build), and [`prepareCache`](#preparecache) receive one argument with the following properties.
|
||||||
|
|
||||||
@@ -145,78 +145,15 @@ The exported functions [`analyze`](#analyze), [`build`](#build), and [`prepareCa
|
|||||||
- `cachePath`: A writable temporary directory where you can build a cache for the next run. This is only passed to `prepareCache`.
|
- `cachePath`: A writable temporary directory where you can build a cache for the next run. This is only passed to `prepareCache`.
|
||||||
- `config`: An arbitrary object passed from by the user in the [Build definition](#defining-the-build-step) in `now.json`.
|
- `config`: An arbitrary object passed from by the user in the [Build definition](#defining-the-build-step) in `now.json`.
|
||||||
|
|
||||||
## Example: html-minifier
|
## Examples
|
||||||
|
|
||||||
Let's walk through what it takes to create a simple builder that takes in a HTML source file and yields a minified HTML static file as its build output.
|
Check out our [Node.js Runtime](https://github.com/zeit/now/tree/master/packages/now-node), [Go Runtime](https://github.com/zeit/now/tree/master/packages/now-go), [Python Runtime](https://github.com/zeit/now/tree/master/packages/now-python) or [Ruby Runtime](https://github.com/zeit/now/tree/master/packages/now-ruby) for examples of how to build one.
|
||||||
|
|
||||||
While this is a very simple builder, the approach demonstrated here can be used to return anything: one or more static files and/or one or more lambdas.
|
|
||||||
|
|
||||||
## Setting up the module
|
|
||||||
|
|
||||||
### Defining the analyze step
|
|
||||||
|
|
||||||
The `analyze` hook is optional. Its goal is to give the developer a tool to avoid wasting time _re-computing a build_ that has already occurred.
|
|
||||||
|
|
||||||
The return value of `analyze` is a _fingerprint_: a simple string that uniquely identifies the build process.
|
|
||||||
|
|
||||||
If `analyze` is not specified, its behavior is to use as the fingerprint the combined checksums of **all the files in the same directory level as the entrypoint**. This is a default that errs on making sure that we re-execute builds when files _other than the entrypoint_ (like dependencies, manifest files, etc) have changed.
|
|
||||||
|
|
||||||
For our `html-minify` example, we know that HTML files don't have dependencies. Therefore, our analyze step can just return the `digest` of the entrypoint.
|
|
||||||
|
|
||||||
Our `index.js` file looks as follows:
|
|
||||||
|
|
||||||
```js
|
|
||||||
exports.analyze = function({ files, entrypoint }) {
|
|
||||||
return files[entrypoint].digest
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This means that we will only re-minify and re-create the build output _only if the file contents (and therefore its digest) change._
|
|
||||||
|
|
||||||
### Defining the build step
|
|
||||||
|
|
||||||
Your module will need some utilities to manipulate the data structures we pass you, create new ones and alter the filesystem.
|
|
||||||
|
|
||||||
To that end, we expose our API as part of a `@now/build-utils` package. This package is always loaded on your behalf, so make sure it's only included as `peerDependencies` in your `package.json`.
|
|
||||||
|
|
||||||
Builders can include dependencies of their liking:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const htmlMinifier = require('html-minifier')
|
|
||||||
|
|
||||||
exports.version = 2
|
|
||||||
|
|
||||||
exports.analyze = ({ files, entrypoint }) => files[entrypoint].digest
|
|
||||||
|
|
||||||
exports.build = async ({ files, entrypoint, config }) => {
|
|
||||||
const stream = files[entrypoint].toStream()
|
|
||||||
const options = Object.assign({}, config || {})
|
|
||||||
const { data } = await FileBlob.fromStream({ stream })
|
|
||||||
const content = data.toString()
|
|
||||||
const minified = htmlMinifier(content, options)
|
|
||||||
const result = new FileBlob({ data: minified })
|
|
||||||
|
|
||||||
return {
|
|
||||||
output: {
|
|
||||||
[entrypoint]: result
|
|
||||||
},
|
|
||||||
watch: [],
|
|
||||||
routes: {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Defining a `prepareCache` step
|
|
||||||
|
|
||||||
If our builder had performed work that could be re-used in the next build invocation, we could define a `prepareCache` step.
|
|
||||||
|
|
||||||
In this case, there are not intermediate artifacts that we can cache, and our `analyze` step already takes care of caching the full output based on the fingerprint of the input.
|
|
||||||
|
|
||||||
## Technical Details
|
## Technical Details
|
||||||
|
|
||||||
### Execution Context
|
### Execution Context
|
||||||
|
|
||||||
A [Serverless Function](https://zeit.co/docs/v2/advanced/concepts/lambdas) is created where the builder logic is executed. The lambda is run using the Node.js 8 runtime. A brand new sandbox is created for each deployment, for security reasons. The sandbox is cleaned up between executions to ensure no lingering temporary files are shared from build to build.
|
A [Serverless Function](https://zeit.co/docs/v2/advanced/concepts/lambdas) is created where the Runtime logic is executed. The lambda is run using the Node.js 8 runtime. A brand new sandbox is created for each deployment, for security reasons. The sandbox is cleaned up between executions to ensure no lingering temporary files are shared from build to build.
|
||||||
|
|
||||||
All the APIs you export ([`analyze`](#analyze), [`build`](#build) and [`prepareCache`](#preparecache)) are not guaranteed to be run in the same process, but the filesystem we expose (e.g.: `workPath` and the results of calling [`getWriteableDirectory`](#getWriteableDirectory) ) is retained.
|
All the APIs you export ([`analyze`](#analyze), [`build`](#build) and [`prepareCache`](#preparecache)) are not guaranteed to be run in the same process, but the filesystem we expose (e.g.: `workPath` and the results of calling [`getWriteableDirectory`](#getWriteableDirectory) ) is retained.
|
||||||
|
|
||||||
@@ -228,23 +165,23 @@ When a new build is created, we pre-populate the `workPath` supplied to `analyze
|
|||||||
|
|
||||||
The `analyze` step can modify that directory, and it will not be re-created when it's supplied to `build` and `prepareCache`.
|
The `analyze` step can modify that directory, and it will not be re-created when it's supplied to `build` and `prepareCache`.
|
||||||
|
|
||||||
To learn how the cache key is computed and invalidated, refer to the [overview](https://zeit.co/docs/v2/advanced/builders#technical-details).
|
To learn how the cache key is computed and invalidated, refer to the [overview](https://zeit.co/docs/v2/advanced/runtimes#technical-details).
|
||||||
|
|
||||||
### Accessing Environment and Secrets
|
### Accessing Environment and Secrets
|
||||||
|
|
||||||
The env and secrets specified by the user as `build.env` are passed to the builder process. This means you can access user env via `process.env` in Node.js.
|
The env and secrets specified by the user as `build.env` are passed to the Runtime process. This means you can access user env via `process.env` in Node.js.
|
||||||
|
|
||||||
### Utilities as peerDependencies
|
### Utilities as peerDependencies
|
||||||
|
|
||||||
When you publish your builder to npm, make sure to not specify `@now/build-utils` (as seen below in the API definitions) as a dependency, but rather as part of `peerDependencies`.
|
When you publish your Runtime to npm, make sure to not specify `@now/build-utils` (as seen below in the API definitions) as a dependency, but rather as part of `peerDependencies`.
|
||||||
|
|
||||||
## Types
|
## Types
|
||||||
|
|
||||||
### `Files`
|
### `Files`
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { File } from '@now/build-utils'
|
import { File } from '@now/build-utils';
|
||||||
type Files = { [filePath: string]: File }
|
type Files = { [filePath: string]: File };
|
||||||
```
|
```
|
||||||
|
|
||||||
This is an abstract type that is implemented as a plain [JavaScript Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object). It's helpful to think of it as a virtual filesystem representation.
|
This is an abstract type that is implemented as a plain [JavaScript Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object). It's helpful to think of it as a virtual filesystem representation.
|
||||||
@@ -265,7 +202,7 @@ An example of a valid output `Files` object is:
|
|||||||
This is an abstract type that can be imported if you are using TypeScript.
|
This is an abstract type that can be imported if you are using TypeScript.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { File } from '@now/build-utils'
|
import { File } from '@now/build-utils';
|
||||||
```
|
```
|
||||||
|
|
||||||
Valid `File` types include:
|
Valid `File` types include:
|
||||||
@@ -277,7 +214,7 @@ Valid `File` types include:
|
|||||||
### `FileRef`
|
### `FileRef`
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { FileRef } from '@now/build-utils'
|
import { FileRef } from '@now/build-utils';
|
||||||
```
|
```
|
||||||
|
|
||||||
This is a [JavaScript class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) that represents an abstract file instance stored in our platform, based on the file identifier string (its checksum). When a `Files` object is passed as an input to `analyze` or `build`, all its values will be instances of `FileRef`.
|
This is a [JavaScript class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) that represents an abstract file instance stored in our platform, based on the file identifier string (its checksum). When a `Files` object is passed as an input to `analyze` or `build`, all its values will be instances of `FileRef`.
|
||||||
@@ -294,7 +231,7 @@ This is a [JavaScript class](https://developer.mozilla.org/en-US/docs/Web/JavaSc
|
|||||||
### `FileFsRef`
|
### `FileFsRef`
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { FileFsRef } from '@now/build-utils'
|
import { FileFsRef } from '@now/build-utils';
|
||||||
```
|
```
|
||||||
|
|
||||||
This is a [JavaScript class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) that represents an abstract instance of a file present in the filesystem that the build process is executing in.
|
This is a [JavaScript class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) that represents an abstract instance of a file present in the filesystem that the build process is executing in.
|
||||||
@@ -312,7 +249,7 @@ This is a [JavaScript class](https://developer.mozilla.org/en-US/docs/Web/JavaSc
|
|||||||
### `FileBlob`
|
### `FileBlob`
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { FileBlob } from '@now/build-utils'
|
import { FileBlob } from '@now/build-utils';
|
||||||
```
|
```
|
||||||
|
|
||||||
This is a [JavaScript class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) that represents an abstract instance of a file present in memory.
|
This is a [JavaScript class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) that represents an abstract instance of a file present in memory.
|
||||||
@@ -330,10 +267,10 @@ This is a [JavaScript class](https://developer.mozilla.org/en-US/docs/Web/JavaSc
|
|||||||
### `Lambda`
|
### `Lambda`
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { Lambda } from '@now/build-utils'
|
import { Lambda } from '@now/build-utils';
|
||||||
```
|
```
|
||||||
|
|
||||||
This is a [JavaScript class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes), called a Serverless Function, that can be created by supplying `files`, `handler`, `runtime`, and `environment` as an object to the [`createLambda`](#createlambda) helper. The instances of this class should not be created directly. Instead use a call to [`createLambda`](#createlambda).
|
This is a [JavaScript class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes), called a Serverless Function, that can be created by supplying `files`, `handler`, `runtime`, and `environment` as an object to the [`createLambda`](#createlambda) helper. The instances of this class should not be created directly. Instead, invoke the [`createLambda`](#createlambda) helper function.
|
||||||
|
|
||||||
**Properties:**
|
**Properties:**
|
||||||
|
|
||||||
@@ -358,27 +295,27 @@ This is an abstract enumeration type that is implemented by one of the following
|
|||||||
|
|
||||||
## JavaScript API
|
## JavaScript API
|
||||||
|
|
||||||
The following is exposed by `@now/build-utils` to simplify the process of writing Builders, manipulating the file system, using the above types, etc.
|
The following is exposed by `@now/build-utils` to simplify the process of writing Runtimes, manipulating the file system, using the above types, etc.
|
||||||
|
|
||||||
### `createLambda`
|
### `createLambda`
|
||||||
|
|
||||||
Signature: `createLambda(Object spec) : Lambda`
|
Signature: `createLambda(Object spec) : Lambda`
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { createLambda } from '@now/build-utils'
|
import { createLambda } from '@now/build-utils';
|
||||||
```
|
```
|
||||||
|
|
||||||
Constructor for the [`Lambda`](#lambda) type.
|
Constructor for the [`Lambda`](#lambda) type.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const { createLambda, FileBlob } = require('@now/build-utils')
|
const { createLambda, FileBlob } = require('@now/build-utils');
|
||||||
await createLambda({
|
await createLambda({
|
||||||
runtime: 'nodejs8.10',
|
runtime: 'nodejs8.10',
|
||||||
handler: 'index.main',
|
handler: 'index.main',
|
||||||
files: {
|
files: {
|
||||||
'index.js': new FileBlob({ data: 'exports.main = () => {}' })
|
'index.js': new FileBlob({ data: 'exports.main = () => {}' }),
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### `download`
|
### `download`
|
||||||
@@ -386,7 +323,7 @@ await createLambda({
|
|||||||
Signature: `download() : Files`
|
Signature: `download() : Files`
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { download } from '@now/build-utils'
|
import { download } from '@now/build-utils';
|
||||||
```
|
```
|
||||||
|
|
||||||
This utility allows you to download the contents of a [`Files`](#files) data structure, therefore creating the filesystem represented in it.
|
This utility allows you to download the contents of a [`Files`](#files) data structure, therefore creating the filesystem represented in it.
|
||||||
@@ -396,7 +333,7 @@ Since `Files` is an abstract way of representing files, you can think of `downlo
|
|||||||
If the **optional** `meta` property is passed (the argument for [build](#build)), only the files that have changed are downloaded. This is decided using `filesRemoved` and `filesChanged` inside that object.
|
If the **optional** `meta` property is passed (the argument for [build](#build)), only the files that have changed are downloaded. This is decided using `filesRemoved` and `filesChanged` inside that object.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
await download(files, workPath, meta)
|
await download(files, workPath, meta);
|
||||||
```
|
```
|
||||||
|
|
||||||
### `glob`
|
### `glob`
|
||||||
@@ -404,7 +341,7 @@ await download(files, workPath, meta)
|
|||||||
Signature: `glob() : Files`
|
Signature: `glob() : Files`
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { glob } from '@now/build-utils'
|
import { glob } from '@now/build-utils';
|
||||||
```
|
```
|
||||||
|
|
||||||
This utility allows you to _scan_ the filesystem and return a [`Files`](#files) representation of the matched glob search string. It can be thought of as the reverse of [`download`](#download).
|
This utility allows you to _scan_ the filesystem and return a [`Files`](#files) representation of the matched glob search string. It can be thought of as the reverse of [`download`](#download).
|
||||||
@@ -425,7 +362,7 @@ exports.build = ({ files, workPath }) => {
|
|||||||
Signature: `getWriteableDirectory() : String`
|
Signature: `getWriteableDirectory() : String`
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { getWriteableDirectory } from '@now/build-utils'
|
import { getWriteableDirectory } from '@now/build-utils';
|
||||||
```
|
```
|
||||||
|
|
||||||
In some occasions, you might want to write to a temporary directory.
|
In some occasions, you might want to write to a temporary directory.
|
||||||
@@ -435,7 +372,7 @@ In some occasions, you might want to write to a temporary directory.
|
|||||||
Signature: `rename(Files) : Files`
|
Signature: `rename(Files) : Files`
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { rename } from '@now/build-utils'
|
import { rename } from '@now/build-utils';
|
||||||
```
|
```
|
||||||
|
|
||||||
Renames the keys of the [`Files`](#files) object, which represent the paths. For example, to remove the `*.go` suffix you can use:
|
Renames the keys of the [`Files`](#files) object, which represent the paths. For example, to remove the `*.go` suffix you can use:
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
# Publishing to npm
|
|
||||||
|
|
||||||
Always publish to the Canary Channel as soon as a PR is merged into the `canary` branch.
|
|
||||||
|
|
||||||
```
|
|
||||||
yarn publish-canary
|
|
||||||
```
|
|
||||||
|
|
||||||
Publish the Stable Channel weekly.
|
|
||||||
|
|
||||||
- Cherry pick each commit from `canary` to `master` branch
|
|
||||||
- Verify that you are _in-sync_ with canary (with the exception of the `version` line in `package.json`)
|
|
||||||
- Deploy the modified Builders
|
|
||||||
|
|
||||||
```
|
|
||||||
# View differences excluding "Publish" commits
|
|
||||||
git checkout canary && git pull
|
|
||||||
git log --pretty=format:"$ad- %s [%an]" | grep -v Publish > ~/Desktop/canary.txt
|
|
||||||
git checkout master && git pull
|
|
||||||
git log --pretty=format:"$ad- %s [%an]" | grep -v Publish > ~/Desktop/master.txt
|
|
||||||
diff ~/Desktop/canary.txt ~/Desktop/master.txt
|
|
||||||
|
|
||||||
# Cherry pick all PRs from canary into master ...
|
|
||||||
git cherry-pick <PR501_COMMIT_SHA>
|
|
||||||
git cherry-pick <PR502_COMMIT_SHA>
|
|
||||||
git cherry-pick <PR503_COMMIT_SHA>
|
|
||||||
git cherry-pick <PR504_COMMIT_SHA>
|
|
||||||
|
|
||||||
# Verify the only difference is "version" in package.json
|
|
||||||
git diff origin/canary
|
|
||||||
|
|
||||||
# Ship it
|
|
||||||
yarn publish-stable
|
|
||||||
```
|
|
||||||
|
|
||||||
After running this publish step, GitHub Actions will take care of publishing the modified Builder packages to npm.
|
|
||||||
|
|
||||||
If for some reason GitHub Actions fails to publish the npm package, you may do so
|
|
||||||
manually by running `npm publish` from the package directory. Make sure to
|
|
||||||
use `npm publish --tag canary` if you are publishing a canary release!
|
|
||||||
@@ -1,10 +1,8 @@
|
|||||||

|

|
||||||
|
|
||||||
[](https://circleci.com/gh/zeit/workflows/now)
|
[](https://circleci.com/gh/zeit/workflows/now/tree/master)
|
||||||
[](https://spectrum.chat/zeit)
|
[](https://spectrum.chat/zeit)
|
||||||
|
|
||||||
**Note**: The [canary](https://github.com/zeit/now/tree/canary) branch is under heavy development – the stable release branch is [master](https://github.com/zeit/now/tree/master).
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
To install the latest version of Now CLI, visit [zeit.co/download](https://zeit.co/download) or run this command:
|
To install the latest version of Now CLI, visit [zeit.co/download](https://zeit.co/download) or run this command:
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
# Versioning
|
|
||||||
|
|
||||||
Builders are released to two different channels.
|
|
||||||
|
|
||||||
## Channels
|
|
||||||
|
|
||||||
| Channel | Git Branch | npm dist-tag | use example |
|
|
||||||
| ------- | ------------------------------------------------------------- | ------------ | ------------------ |
|
|
||||||
| Canary | [canary](https://github.com/zeit/now/commits/canary) | `@canary` | `@now/node@canary` |
|
|
||||||
| Stable | [master](https://github.com/zeit/now/commits/master) | `@latest` | `@now/node@latest` |
|
|
||||||
|
|
||||||
All PRs are submitted to the `canary` branch. Once a PR is merged into the `canary` branch, it should be published to npm immediately using the Canary Channel.
|
|
||||||
|
|
||||||
## Version Selection
|
|
||||||
|
|
||||||
Since Builders are published to [npmjs.com](https://npmjs.com), this makes versioning works the same for Builders as it does for any npm package. The `use` statement in [now.json](https://zeit.co/docs/v2/advanced/configuration#builds) has a similar syntax to `npm install`.
|
|
||||||
|
|
||||||
The following are valid examples [@now/node](https://www.npmjs.com/package/@now/node?activeTab=versions):
|
|
||||||
|
|
||||||
- `@now/node`
|
|
||||||
- `@now/node@0.7.3`
|
|
||||||
- `@now/node@canary`
|
|
||||||
- `@now/node@0.7.2-canary.2`
|
|
||||||
|
|
||||||
We always recommend using the latest version by leaving off the dist-tag suffix, `@now/node` for example.
|
|
||||||
18
changelog.js
Normal file
18
changelog.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
const { execSync } = require('child_process');
|
||||||
|
|
||||||
|
const commit = execSync('git log --pretty=format:"%s %H"')
|
||||||
|
.toString()
|
||||||
|
.trim()
|
||||||
|
.split('\n')
|
||||||
|
.find(line => line.startsWith('Publish Stable '))
|
||||||
|
.split(' ')
|
||||||
|
.pop();
|
||||||
|
|
||||||
|
if (!commit) {
|
||||||
|
throw new Error('Unable to find last publish commit');
|
||||||
|
}
|
||||||
|
|
||||||
|
const log = execSync(`git log --pretty=format:"- %s [%an]" ${commit}...HEAD`).toString().trim();
|
||||||
|
|
||||||
|
console.log(`Changes since the last publish commit ${commit}:`);
|
||||||
|
console.log(`\n${log}\n`);
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#### Why This Error Occurred
|
#### Why This Error Occurred
|
||||||
|
|
||||||
The domain you supplied cannot be verified using either the intended set of nameservers of the given verification TXT record.
|
The domain you supplied cannot be verified using either the intended set of nameservers or the given verification TXT record.
|
||||||
|
|
||||||
#### Possible Ways to Fix It
|
#### Possible Ways to Fix It
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ the provided `$PORT` that the builder expects the server to bind to.
|
|||||||
For example, if you are using Gatsby, your `now-dev` script must use the `-p`
|
For example, if you are using Gatsby, your `now-dev` script must use the `-p`
|
||||||
(port) option to bind to the `$PORT` specified from the builder:
|
(port) option to bind to the `$PORT` specified from the builder:
|
||||||
|
|
||||||
|
> *In Windows environments, reference the `PORT` environment variable with `%PORT%`*
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
...
|
...
|
||||||
|
|||||||
44
package.json
44
package.json
@@ -3,30 +3,62 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"workspaces": [
|
"workspaces": {
|
||||||
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
|
"nohoist": [
|
||||||
|
"**/@types/**"
|
||||||
|
]
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lerna": "3.16.4"
|
"lerna": "3.16.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@typescript-eslint/eslint-plugin": "2.0.0",
|
||||||
|
"@typescript-eslint/parser": "2.0.0",
|
||||||
"@zeit/ncc": "0.20.4",
|
"@zeit/ncc": "0.20.4",
|
||||||
"async-retry": "1.2.3",
|
"async-retry": "1.2.3",
|
||||||
"buffer-replace": "1.0.0",
|
"buffer-replace": "1.0.0",
|
||||||
"node-fetch": "2.6.0"
|
"eslint": "6.2.2",
|
||||||
|
"eslint-config-prettier": "6.1.0",
|
||||||
|
"husky": "3.0.4",
|
||||||
|
"lint-staged": "9.2.5",
|
||||||
|
"node-fetch": "2.6.0",
|
||||||
|
"prettier": "1.18.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lerna": "lerna",
|
"lerna": "lerna",
|
||||||
"bootstrap": "lerna bootstrap",
|
"bootstrap": "lerna bootstrap",
|
||||||
"publish-stable": "git checkout master && git pull && lerna version --exact",
|
"publish-stable": "git pull && lerna version --message 'Publish Stable' --exact",
|
||||||
"publish-canary": "git checkout canary && git pull && lerna version prerelease --preid canary --exact",
|
"publish-canary": "git pull && lerna version prerelease --preid canary --message 'Publish Canary' --exact",
|
||||||
"publish-from-github": "./.circleci/publish.sh",
|
"publish-from-github": "./.circleci/publish.sh",
|
||||||
|
"changelog": "node changelog.js",
|
||||||
"build": "node run.js build all",
|
"build": "node run.js build all",
|
||||||
"test-lint": "node run.js test-lint",
|
"test-lint": "node run.js test-lint",
|
||||||
"test-unit": "node run.js test-unit",
|
"test-unit": "node run.js test-unit",
|
||||||
"test-integration": "node run.js test-integration",
|
"test-integration": "node run.js test-integration",
|
||||||
"test-integration-once": "node run.js test-integration-once",
|
"test-integration-once": "node run.js test-integration-once",
|
||||||
"test-integration-now-dev": "node run.js test-integration-now-dev"
|
"test-integration-now-dev": "node run.js test-integration-now-dev",
|
||||||
|
"lint": "eslint . --ext .ts,.js"
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"*.{js,ts}": [
|
||||||
|
"prettier --write",
|
||||||
|
"eslint",
|
||||||
|
"git add"
|
||||||
|
],
|
||||||
|
"*.{json,md}": [
|
||||||
|
"prettier --write",
|
||||||
|
"git add"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"husky": {
|
||||||
|
"hooks": {
|
||||||
|
"pre-commit": "lint-staged"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"signal-exit": "TooTallNate/signal-exit#update/sighub-to-sigint-on-windows"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
6
packages/gatsby-plugin-now/build.sh
Executable file
6
packages/gatsby-plugin-now/build.sh
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# build fixtures for tests
|
||||||
|
yarn --cwd test/fixtures install
|
||||||
|
yarn --cwd test/fixtures run build
|
||||||
51
packages/gatsby-plugin-now/gatsby-node.js
Normal file
51
packages/gatsby-plugin-now/gatsby-node.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const writeFile = require('util').promisify(require('fs').writeFile);
|
||||||
|
|
||||||
|
const REDIRECT_FILE_NAME = '__now_routes_g4t5bY.json';
|
||||||
|
|
||||||
|
exports.onPostBuild = async ({ store }) => {
|
||||||
|
const { redirects, program } = store.getState();
|
||||||
|
|
||||||
|
const routes = [{ handle: 'filesystem' }];
|
||||||
|
|
||||||
|
for (const redirect of redirects) {
|
||||||
|
const route = {
|
||||||
|
src: redirect.fromPath,
|
||||||
|
status: redirect.statusCode || (redirect.isPermanent ? 301 : 302),
|
||||||
|
headers: { Location: redirect.toPath },
|
||||||
|
};
|
||||||
|
|
||||||
|
if (redirect.force) {
|
||||||
|
routes.unshift(route);
|
||||||
|
} else {
|
||||||
|
routes.push(route);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we implement gatsby's recommendations
|
||||||
|
// https://www.gatsbyjs.org/docs/caching/
|
||||||
|
const finalRoutes = [
|
||||||
|
{
|
||||||
|
src: '^/static/(.*)$',
|
||||||
|
headers: { 'cache-control': 'public,max-age=31536000,immutable' },
|
||||||
|
continue: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: '^/.*\\.(js|css)$',
|
||||||
|
headers: { 'cache-control': 'public,max-age=31536000,immutable' },
|
||||||
|
continue: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: '^/(sw\\.js|app-data\\.json|.*\\.html|page-data/.*)$',
|
||||||
|
headers: { 'cache-control': 'public,max-age=0,must-revalidate' },
|
||||||
|
continue: true,
|
||||||
|
},
|
||||||
|
...routes,
|
||||||
|
{ src: '.*', status: 404, dest: '/404.html' },
|
||||||
|
];
|
||||||
|
|
||||||
|
await writeFile(
|
||||||
|
path.join(program.directory, 'public', REDIRECT_FILE_NAME),
|
||||||
|
JSON.stringify(finalRoutes)
|
||||||
|
);
|
||||||
|
};
|
||||||
1
packages/gatsby-plugin-now/index.js
Normal file
1
packages/gatsby-plugin-now/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
// noop
|
||||||
36
packages/gatsby-plugin-now/package.json
Normal file
36
packages/gatsby-plugin-now/package.json
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "gatsby-plugin-now",
|
||||||
|
"version": "1.2.3",
|
||||||
|
"main": "index.js",
|
||||||
|
"license": "MIT",
|
||||||
|
"homepage": "https://zeit.co/guides/deploying-gatsby-with-now",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/zeit/now.git",
|
||||||
|
"directory": "packages/gatsby-plugin-now"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"gatsby",
|
||||||
|
"gatsby-plugin"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "./build.sh",
|
||||||
|
"test-integration-once": "jest --verbose"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"index.js",
|
||||||
|
"gatsby-node.js"
|
||||||
|
],
|
||||||
|
"peerDependencies": {
|
||||||
|
"gatsby": ">=2.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"jest": "24.9.0"
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"testPathIgnorePatterns": [
|
||||||
|
"/node_modules/",
|
||||||
|
"<rootDir>/test/fixtures/"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
7
packages/gatsby-plugin-now/readme.md
Normal file
7
packages/gatsby-plugin-now/readme.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# gatsby-plugin-now
|
||||||
|
|
||||||
|
⚠️ The use of this plugin is deprecated. ZEIT Now supports Gatsby Redirects out-of-the-box and does not require the use a plugin.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
This plugin generates [Now Routes](https://zeit.co/docs/v2/advanced/routes) for [redirects](https://www.gatsbyjs.org/docs/actions/#createRedirect) you configured for to your Gatsby project.
|
||||||
112
packages/gatsby-plugin-now/test/__snapshots__/index.test.js.snap
Normal file
112
packages/gatsby-plugin-now/test/__snapshots__/index.test.js.snap
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`test generated now routes 1`] = `
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"continue": true,
|
||||||
|
"headers": Object {
|
||||||
|
"cache-control": "public,max-age=31536000,immutable",
|
||||||
|
},
|
||||||
|
"src": "^/static/(.*)$",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"continue": true,
|
||||||
|
"headers": Object {
|
||||||
|
"cache-control": "public,max-age=31536000,immutable",
|
||||||
|
},
|
||||||
|
"src": "^/.*\\\\.(js|css)$",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"continue": true,
|
||||||
|
"headers": Object {
|
||||||
|
"cache-control": "public,max-age=0,must-revalidate",
|
||||||
|
},
|
||||||
|
"src": "^/(sw\\\\.js|app-data\\\\.json|.*\\\\.html|page-data/.*)$",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"headers": Object {
|
||||||
|
"Location": "/",
|
||||||
|
},
|
||||||
|
"src": "/my-special-redirect",
|
||||||
|
"status": 302,
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"handle": "filesystem",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"headers": Object {
|
||||||
|
"Location": "/page-2",
|
||||||
|
},
|
||||||
|
"src": "/page2",
|
||||||
|
"status": 301,
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"headers": Object {
|
||||||
|
"Location": "/page-2/",
|
||||||
|
},
|
||||||
|
"src": "/page2/",
|
||||||
|
"status": 301,
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"headers": Object {
|
||||||
|
"Location": "/",
|
||||||
|
},
|
||||||
|
"src": "/orange",
|
||||||
|
"status": 302,
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"headers": Object {
|
||||||
|
"Location": "/",
|
||||||
|
},
|
||||||
|
"src": "/grape",
|
||||||
|
"status": 302,
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"headers": Object {
|
||||||
|
"Location": "/page-2/",
|
||||||
|
},
|
||||||
|
"src": "/blue",
|
||||||
|
"status": 302,
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"headers": Object {
|
||||||
|
"Location": "/page-2/",
|
||||||
|
},
|
||||||
|
"src": "/randirect",
|
||||||
|
"status": 302,
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"headers": Object {
|
||||||
|
"Location": "/",
|
||||||
|
},
|
||||||
|
"src": "/juice",
|
||||||
|
"status": 302,
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"headers": Object {
|
||||||
|
"Location": "/",
|
||||||
|
},
|
||||||
|
"src": "/soda",
|
||||||
|
"status": 302,
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"headers": Object {
|
||||||
|
"Location": "/page-2/",
|
||||||
|
},
|
||||||
|
"src": "/donut",
|
||||||
|
"status": 302,
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"headers": Object {
|
||||||
|
"Location": "/page-2/",
|
||||||
|
},
|
||||||
|
"src": "/randorect",
|
||||||
|
"status": 302,
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"dest": "/404.html",
|
||||||
|
"src": ".*",
|
||||||
|
"status": 404,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
4
packages/gatsby-plugin-now/test/fixtures/.gitignore
vendored
Normal file
4
packages/gatsby-plugin-now/test/fixtures/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
public
|
||||||
|
node_modules
|
||||||
|
.cache
|
||||||
|
yarn.lock
|
||||||
3
packages/gatsby-plugin-now/test/fixtures/gatsby-config.js
vendored
Normal file
3
packages/gatsby-plugin-now/test/fixtures/gatsby-config.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: [{ resolve: require.resolve('../../') }]
|
||||||
|
};
|
||||||
105
packages/gatsby-plugin-now/test/fixtures/gatsby-node.js
vendored
Normal file
105
packages/gatsby-plugin-now/test/fixtures/gatsby-node.js
vendored
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Implement the Gatsby API “createPages”. This is called once the
|
||||||
|
// data layer is bootstrapped to let plugins create pages from data.
|
||||||
|
exports.createPages = ({ actions }) => {
|
||||||
|
// need createRedirect action in actions collection
|
||||||
|
// to make the redirection magic happen.
|
||||||
|
// https://www.gatsbyjs.org/docs/bound-action-creators/
|
||||||
|
const { createRedirect } = actions;
|
||||||
|
|
||||||
|
// Let’s set up some string consts to use thoroughout the following.
|
||||||
|
// MDN > JavaScript reference > Statements and declarations
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const
|
||||||
|
// Maybe we usually redirect to page 2, with trailing slash.
|
||||||
|
const page2Path = `/page-2/`;
|
||||||
|
// But sometimes to homepage.
|
||||||
|
const homePath = `/`;
|
||||||
|
|
||||||
|
// One-off redirect, note trailing slash missing on fromPath and
|
||||||
|
// toPath here.
|
||||||
|
createRedirect({
|
||||||
|
fromPath: `/page2`,
|
||||||
|
isPermanent: true,
|
||||||
|
redirectInBrowser: true,
|
||||||
|
toPath: `/page-2`
|
||||||
|
});
|
||||||
|
|
||||||
|
// Another one-off redirect, note trailing slash on toPath here.
|
||||||
|
// This time we want trailing slash on toPath so we use
|
||||||
|
// page2Path. Need to handle trailing-slashed and non-trailing-
|
||||||
|
// slashed fromPaths separately, Gatsby serves page components
|
||||||
|
// at either version by default, but we need to explicitly redirect
|
||||||
|
// both versions independently, more on page components:
|
||||||
|
// Docs > Building with Components
|
||||||
|
// https://www.gatsbyjs.org/docs/building-with-components/
|
||||||
|
// and handling trailing slashes:
|
||||||
|
// Docs > Creating and modifying pages > Removing trailing slashes
|
||||||
|
// https://www.gatsbyjs.org/docs/creating-and-modifying-pages/#removing-trailing-slashes
|
||||||
|
createRedirect({
|
||||||
|
fromPath: `/page2/`,
|
||||||
|
isPermanent: true,
|
||||||
|
redirectInBrowser: true,
|
||||||
|
toPath: page2Path
|
||||||
|
});
|
||||||
|
|
||||||
|
// One approach to handle several redirects at once is to create an
|
||||||
|
// array of from/to path pairs.
|
||||||
|
let redirectBatch1 = [
|
||||||
|
{ f: `/orange`, t: `/` },
|
||||||
|
// We could use homePath and page2Path directly here.
|
||||||
|
{ f: `/grape`, t: homePath },
|
||||||
|
{ f: `/blue`, t: page2Path },
|
||||||
|
// or leave to empty and swap for page2Path later on.
|
||||||
|
{ f: `/randirect`, t: `` }
|
||||||
|
];
|
||||||
|
|
||||||
|
// Then we can loop through the array of object literals to create
|
||||||
|
// each redirect. A for loop would do the trick
|
||||||
|
for (var { f: f, t: t } of redirectBatch1) {
|
||||||
|
// Here we swap any empty toPath values for trusty page 2 via
|
||||||
|
// page2Path.
|
||||||
|
if (t === ``) {
|
||||||
|
t = page2Path;
|
||||||
|
}
|
||||||
|
createRedirect({
|
||||||
|
fromPath: f,
|
||||||
|
redirectInBrowser: true,
|
||||||
|
toPath: t
|
||||||
|
});
|
||||||
|
// Uncomment next line to see loop in action during build
|
||||||
|
// console.log('\nRedirecting:\n' + f + '\nTo:\n' + t + '\n');
|
||||||
|
// or check .cache/redirects.json post-compile.
|
||||||
|
}
|
||||||
|
|
||||||
|
// A more modern approach might use forEach rather than for...of
|
||||||
|
// Compare
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Loops_and_iteration#for...of_statement
|
||||||
|
// and
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
|
||||||
|
let redirectBatch2 = [
|
||||||
|
{ f: `/juice`, t: `/` },
|
||||||
|
{ f: `/soda`, t: `/` },
|
||||||
|
{ f: `/donut`, t: page2Path },
|
||||||
|
{ f: `/randorect`, t: `` }
|
||||||
|
];
|
||||||
|
|
||||||
|
redirectBatch2.forEach(({ f, t }) => {
|
||||||
|
if (t === ``) {
|
||||||
|
t = page2Path;
|
||||||
|
}
|
||||||
|
createRedirect({
|
||||||
|
fromPath: f,
|
||||||
|
redirectInBrowser: true,
|
||||||
|
toPath: t
|
||||||
|
});
|
||||||
|
// Uncomment next line to see forEach in action during build
|
||||||
|
// console.log('\nRedirecting:\n' + f + '\nTo:\n' + t + '\n');
|
||||||
|
});
|
||||||
|
|
||||||
|
createRedirect({
|
||||||
|
fromPath: '/my-special-redirect',
|
||||||
|
toPath: homePath,
|
||||||
|
force: true
|
||||||
|
});
|
||||||
|
};
|
||||||
11
packages/gatsby-plugin-now/test/fixtures/package.json
vendored
Normal file
11
packages/gatsby-plugin-now/test/fixtures/package.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "fixtures",
|
||||||
|
"dependencies": {
|
||||||
|
"gatsby": "2.14.0",
|
||||||
|
"react": "16.9.0",
|
||||||
|
"react-dom": "16.9.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "gatsby build"
|
||||||
|
}
|
||||||
|
}
|
||||||
9
packages/gatsby-plugin-now/test/fixtures/src/pages/index.js
vendored
Normal file
9
packages/gatsby-plugin-now/test/fixtures/src/pages/index.js
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const IndexPage = () => (
|
||||||
|
<div>
|
||||||
|
<h1>Hi people</h1>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default IndexPage;
|
||||||
5
packages/gatsby-plugin-now/test/index.test.js
vendored
Normal file
5
packages/gatsby-plugin-now/test/index.test.js
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
test('test generated now routes', async () => {
|
||||||
|
const nowRoutes = require('./fixtures/public/__now_routes_g4t5bY.json');
|
||||||
|
|
||||||
|
expect(nowRoutes).toMatchSnapshot();
|
||||||
|
});
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/build-utils",
|
"name": "@now/build-utils",
|
||||||
"version": "0.9.14",
|
"version": "1.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./dist/index.d.js",
|
"types": "./dist/index.d.js",
|
||||||
"homepage": "https://zeit.co/docs/v2/deployments/builders/developer-guide",
|
"homepage": "https://github.com/zeit/now/blob/master/DEVELOPING_A_RUNTIME.md",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/zeit/now.git",
|
"url": "https://github.com/zeit/now.git",
|
||||||
@@ -12,7 +12,8 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "./build.sh",
|
"build": "./build.sh",
|
||||||
"test-integration-once": "jest --env node --verbose --runInBand",
|
"test-unit": "jest --env node --verbose --runInBand test/unit.test.js",
|
||||||
|
"test-integration-once": "jest --env node --verbose --runInBand test/integration.test.js",
|
||||||
"prepublishOnly": "./build.sh"
|
"prepublishOnly": "./build.sh"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
export default function debug(message: string, ...additional: any[]) {
|
export default function debug(message: string, ...additional: any[]) {
|
||||||
if (process.env.NOW_BUILDER_DEBUG) {
|
if (process.env.NOW_BUILDER_DEBUG) {
|
||||||
console.log(message, ...additional);
|
console.log(message, ...additional);
|
||||||
} else if (process.env.NOW_BUILDER_ANNOTATE) {
|
|
||||||
console.log(`[now-builder-debug] ${message}`, ...additional);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { PackageJson, Builder, Config } from './types';
|
|
||||||
import minimatch from 'minimatch';
|
import minimatch from 'minimatch';
|
||||||
|
import { valid as validSemver } from 'semver';
|
||||||
|
import { PackageJson, Builder, Config, BuilderFunctions } from './types';
|
||||||
|
|
||||||
interface ErrorResponse {
|
interface ErrorResponse {
|
||||||
code: string;
|
code: string;
|
||||||
@@ -8,34 +9,41 @@ interface ErrorResponse {
|
|||||||
|
|
||||||
interface Options {
|
interface Options {
|
||||||
tag?: 'canary' | 'latest' | string;
|
tag?: 'canary' | 'latest' | string;
|
||||||
|
functions?: BuilderFunctions;
|
||||||
}
|
}
|
||||||
|
|
||||||
const src: string = 'package.json';
|
const src = 'package.json';
|
||||||
const config: Config = { zeroConfig: true };
|
const config: Config = { zeroConfig: true };
|
||||||
|
|
||||||
const MISSING_BUILD_SCRIPT_ERROR: ErrorResponse = {
|
const MISSING_BUILD_SCRIPT_ERROR: ErrorResponse = {
|
||||||
code: 'missing_build_script',
|
code: 'missing_build_script',
|
||||||
message:
|
message:
|
||||||
'Your `package.json` file is missing a `build` property inside the `script` property.' +
|
'Your `package.json` file is missing a `build` property inside the `script` property.' +
|
||||||
'\nMore details: https://zeit.co/docs/v2/advanced/platform/frequently-asked-questions#missing-build-script',
|
'\nMore details: https://zeit.co/docs/v2/platform/frequently-asked-questions#missing-build-script',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Static builders are special cased in `@now/static-build`
|
// Static builders are special cased in `@now/static-build`
|
||||||
function getBuilders(): Map<string, Builder> {
|
function getBuilders({ tag }: Options = {}): Map<string, Builder> {
|
||||||
|
const withTag = tag ? `@${tag}` : '';
|
||||||
|
const config = { zeroConfig: true };
|
||||||
|
|
||||||
return new Map<string, Builder>([
|
return new Map<string, Builder>([
|
||||||
['next', { src, use: '@now/next', config }],
|
['next', { src, use: `@now/next${withTag}`, config }],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must be a function to ensure that the returned
|
// Must be a function to ensure that the returned
|
||||||
// object won't be a reference
|
// object won't be a reference
|
||||||
function getApiBuilders(): Builder[] {
|
function getApiBuilders({ tag }: Pick<Options, 'tag'> = {}): Builder[] {
|
||||||
|
const withTag = tag ? `@${tag}` : '';
|
||||||
|
const config = { zeroConfig: true };
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{ src: 'api/**/*.js', use: '@now/node', config },
|
{ src: 'api/**/*.js', use: `@now/node${withTag}`, config },
|
||||||
{ src: 'api/**/*.ts', use: '@now/node', config },
|
{ src: 'api/**/*.ts', use: `@now/node${withTag}`, config },
|
||||||
{ src: 'api/**/*.go', use: '@now/go', config },
|
{ src: 'api/**/*.go', use: `@now/go${withTag}`, config },
|
||||||
{ src: 'api/**/*.py', use: '@now/python', config },
|
{ src: 'api/**/*.py', use: `@now/python${withTag}`, config },
|
||||||
{ src: 'api/**/*.rb', use: '@now/ruby', config },
|
{ src: 'api/**/*.rb', use: `@now/ruby${withTag}`, config },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,12 +56,66 @@ function hasBuildScript(pkg: PackageJson | undefined) {
|
|||||||
return Boolean(scripts && scripts['build']);
|
return Boolean(scripts && scripts['build']);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function detectBuilder(pkg: PackageJson): Promise<Builder> {
|
function getApiFunctionBuilder(
|
||||||
for (const [dependency, builder] of getBuilders()) {
|
file: string,
|
||||||
|
prevBuilder: Builder | undefined,
|
||||||
|
{ functions = {} }: Pick<Options, 'functions'>
|
||||||
|
) {
|
||||||
|
const key = Object.keys(functions).find(
|
||||||
|
k => file === k || minimatch(file, k)
|
||||||
|
);
|
||||||
|
const fn = key ? functions[key] : undefined;
|
||||||
|
|
||||||
|
if (!fn || (!fn.runtime && !prevBuilder)) {
|
||||||
|
return prevBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
const src = (prevBuilder && prevBuilder.src) || file;
|
||||||
|
const use = fn.runtime || (prevBuilder && prevBuilder.use);
|
||||||
|
|
||||||
|
const config: Config = { zeroConfig: true };
|
||||||
|
|
||||||
|
if (key) {
|
||||||
|
Object.assign(config, {
|
||||||
|
functions: {
|
||||||
|
[key]: fn,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { includeFiles, excludeFiles } = fn;
|
||||||
|
|
||||||
|
if (includeFiles) Object.assign(config, { includeFiles });
|
||||||
|
if (excludeFiles) Object.assign(config, { excludeFiles });
|
||||||
|
|
||||||
|
return use ? { use, src, config } : prevBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function detectFrontBuilder(
|
||||||
|
pkg: PackageJson,
|
||||||
|
builders: Builder[],
|
||||||
|
options: Options
|
||||||
|
): Promise<Builder> {
|
||||||
|
for (const [dependency, builder] of getBuilders(options)) {
|
||||||
const deps = Object.assign({}, pkg.dependencies, pkg.devDependencies);
|
const deps = Object.assign({}, pkg.dependencies, pkg.devDependencies);
|
||||||
|
|
||||||
// Return the builder when a dependency matches
|
// Return the builder when a dependency matches
|
||||||
if (deps[dependency]) {
|
if (deps[dependency]) {
|
||||||
|
if (options.functions) {
|
||||||
|
Object.entries(options.functions).forEach(([key, func]) => {
|
||||||
|
// When the builder is not used yet we'll use it for the frontend
|
||||||
|
if (
|
||||||
|
builders.every(
|
||||||
|
b => !(b.config && b.config.functions && b.config.functions[key])
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
if (!builder.config) builder.config = {};
|
||||||
|
if (!builder.config.functions) builder.config.functions = {};
|
||||||
|
builder.config.functions[key] = { ...func };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,7 +125,22 @@ async function detectBuilder(pkg: PackageJson): Promise<Builder> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Files that match a specific pattern will get ignored
|
// Files that match a specific pattern will get ignored
|
||||||
export function ignoreApiFilter(file: string) {
|
export function getIgnoreApiFilter(optionsOrBuilders: Options | Builder[]) {
|
||||||
|
const possiblePatterns: string[] = getApiBuilders().map(b => b.src);
|
||||||
|
|
||||||
|
if (Array.isArray(optionsOrBuilders)) {
|
||||||
|
optionsOrBuilders.forEach(({ src }) => possiblePatterns.push(src));
|
||||||
|
} else if (optionsOrBuilders.functions) {
|
||||||
|
Object.keys(optionsOrBuilders.functions).forEach(p =>
|
||||||
|
possiblePatterns.push(p)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (file: string) => {
|
||||||
|
if (!file.startsWith('api/')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (file.includes('/.')) {
|
if (file.includes('/.')) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -76,13 +153,12 @@ export function ignoreApiFilter(file: string) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the file does not match any builder we also
|
if (possiblePatterns.every(p => !(file === p || minimatch(file, p)))) {
|
||||||
// don't want to create a route e.g. `package.json`
|
|
||||||
if (getApiBuilders().every(({ src }) => !minimatch(file, src))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to sort the file paths by alphabet to make
|
// We need to sort the file paths by alphabet to make
|
||||||
@@ -91,20 +167,173 @@ export function sortFiles(fileA: string, fileB: string) {
|
|||||||
return fileA.localeCompare(fileB);
|
return fileA.localeCompare(fileB);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function detectApiBuilders(files: string[]): Promise<Builder[]> {
|
async function detectApiBuilders(
|
||||||
|
files: string[],
|
||||||
|
options: Options
|
||||||
|
): Promise<Builder[]> {
|
||||||
const builds = files
|
const builds = files
|
||||||
.sort(sortFiles)
|
.sort(sortFiles)
|
||||||
.filter(ignoreApiFilter)
|
.filter(getIgnoreApiFilter(options))
|
||||||
.map(file => {
|
.map(file => {
|
||||||
const result = getApiBuilders().find(
|
const apiBuilders = getApiBuilders(options);
|
||||||
({ src }): boolean => minimatch(file, src)
|
const apiBuilder = apiBuilders.find(b => minimatch(file, b.src));
|
||||||
);
|
const fnBuilder = getApiFunctionBuilder(file, apiBuilder, options);
|
||||||
|
return fnBuilder ? { ...fnBuilder, src: file } : null;
|
||||||
return result ? { ...result, src: file } : null;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const finishedBuilds = builds.filter(Boolean);
|
return builds.filter(Boolean) as Builder[];
|
||||||
return finishedBuilds as Builder[];
|
}
|
||||||
|
|
||||||
|
// When a package has files that conflict with `/api` routes
|
||||||
|
// e.g. Next.js pages/api we'll check it here and return an error.
|
||||||
|
async function checkConflictingFiles(
|
||||||
|
files: string[],
|
||||||
|
builders: Builder[]
|
||||||
|
): Promise<ErrorResponse | null> {
|
||||||
|
// For Next.js
|
||||||
|
if (builders.some(b => b.use.startsWith('@now/next'))) {
|
||||||
|
const hasApiPages = files.some(file => file.startsWith('pages/api/'));
|
||||||
|
const hasApiBuilders = builders.some(b => b.src.startsWith('api/'));
|
||||||
|
|
||||||
|
if (hasApiPages && hasApiBuilders) {
|
||||||
|
return {
|
||||||
|
code: 'conflicting_files',
|
||||||
|
message:
|
||||||
|
'It is not possible to use `api` and `pages/api` at the same time, please only use one option',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When e.g. Next.js receives a `functions` property it has to make sure,
|
||||||
|
// that it can handle those files, otherwise there are unused functions.
|
||||||
|
async function checkUnusedFunctionsOnFrontendBuilder(
|
||||||
|
files: string[],
|
||||||
|
builder: Builder
|
||||||
|
): Promise<ErrorResponse | null> {
|
||||||
|
const { config: { functions = undefined } = {} } = builder;
|
||||||
|
|
||||||
|
if (!functions) return null;
|
||||||
|
|
||||||
|
if (builder.use.startsWith('@now/next')) {
|
||||||
|
const matchingFiles = files.filter(file =>
|
||||||
|
Object.keys(functions).some(key => file === key || minimatch(file, key))
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const matchedFile of matchingFiles) {
|
||||||
|
if (
|
||||||
|
!matchedFile.startsWith('src/pages/') &&
|
||||||
|
!matchedFile.startsWith('pages/')
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
code: 'unused_function',
|
||||||
|
message: `The function for ${matchedFile} can't be handled by any builder`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateFunctions(files: string[], { functions = {} }: Options) {
|
||||||
|
const apiBuilders = getApiBuilders();
|
||||||
|
|
||||||
|
for (const [path, func] of Object.entries(functions)) {
|
||||||
|
if (path.length > 256) {
|
||||||
|
return {
|
||||||
|
code: 'invalid_function_glob',
|
||||||
|
message: 'Function globs must be less than 256 characters long.',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!func || typeof func !== 'object') {
|
||||||
|
return {
|
||||||
|
code: 'invalid_function',
|
||||||
|
message: 'Function must be an object.',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(func).length === 0) {
|
||||||
|
return {
|
||||||
|
code: 'invalid_function',
|
||||||
|
message: 'Function must contain at least one property.',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
func.maxDuration !== undefined &&
|
||||||
|
(func.maxDuration < 1 ||
|
||||||
|
func.maxDuration > 900 ||
|
||||||
|
!Number.isInteger(func.maxDuration))
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
code: 'invalid_function_duration',
|
||||||
|
message: 'Functions must have a duration between 1 and 900.',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
func.memory !== undefined &&
|
||||||
|
(func.memory < 128 || func.memory > 3008 || func.memory % 64 !== 0)
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
code: 'invalid_function_memory',
|
||||||
|
message:
|
||||||
|
'Functions must have a memory value between 128 and 3008 in steps of 64.',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (files.some(f => f === path || minimatch(f, path)) === false) {
|
||||||
|
return {
|
||||||
|
code: 'invalid_function_source',
|
||||||
|
message: `No source file matched the function for ${path}.`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (func.runtime !== undefined) {
|
||||||
|
const tag = `${func.runtime}`.split('@').pop();
|
||||||
|
|
||||||
|
if (!tag || !validSemver(tag)) {
|
||||||
|
return {
|
||||||
|
code: 'invalid_function_runtime',
|
||||||
|
message:
|
||||||
|
'Function Runtimes must have a valid version, for example `now-php@1.0.0`.',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
apiBuilders.some(b => func.runtime && func.runtime.startsWith(b.use))
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
code: 'invalid_function_runtime',
|
||||||
|
message: `The function Runtime ${func.runtime} is not a Community Runtime and must not be specified.`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (func.includeFiles !== undefined) {
|
||||||
|
if (typeof func.includeFiles !== 'string') {
|
||||||
|
return {
|
||||||
|
code: 'invalid_function_property',
|
||||||
|
message: `The property \`includeFiles\` must be a string.`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (func.excludeFiles !== undefined) {
|
||||||
|
if (typeof func.excludeFiles !== 'string') {
|
||||||
|
return {
|
||||||
|
code: 'invalid_function_property',
|
||||||
|
message: `The property \`excludeFiles\` must be a string.`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// When zero config is used we can call this function
|
// When zero config is used we can call this function
|
||||||
@@ -112,24 +341,56 @@ async function detectApiBuilders(files: string[]): Promise<Builder[]> {
|
|||||||
export async function detectBuilders(
|
export async function detectBuilders(
|
||||||
files: string[],
|
files: string[],
|
||||||
pkg?: PackageJson | undefined | null,
|
pkg?: PackageJson | undefined | null,
|
||||||
options?: Options
|
options: Options = {}
|
||||||
): Promise<{
|
): Promise<{
|
||||||
builders: Builder[] | null;
|
builders: Builder[] | null;
|
||||||
errors: ErrorResponse[] | null;
|
errors: ErrorResponse[] | null;
|
||||||
|
warnings: ErrorResponse[];
|
||||||
}> {
|
}> {
|
||||||
const errors: ErrorResponse[] = [];
|
const errors: ErrorResponse[] = [];
|
||||||
|
const warnings: ErrorResponse[] = [];
|
||||||
|
|
||||||
|
const functionError = validateFunctions(files, options);
|
||||||
|
|
||||||
|
if (functionError) {
|
||||||
|
return {
|
||||||
|
builders: null,
|
||||||
|
errors: [functionError],
|
||||||
|
warnings,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Detect all builders for the `api` directory before anything else
|
// Detect all builders for the `api` directory before anything else
|
||||||
let builders = await detectApiBuilders(files);
|
const builders = await detectApiBuilders(files, options);
|
||||||
|
|
||||||
if (pkg && hasBuildScript(pkg)) {
|
if (pkg && hasBuildScript(pkg)) {
|
||||||
builders.push(await detectBuilder(pkg));
|
const frontendBuilder = await detectFrontBuilder(pkg, builders, options);
|
||||||
|
builders.push(frontendBuilder);
|
||||||
|
|
||||||
|
const conflictError = await checkConflictingFiles(files, builders);
|
||||||
|
|
||||||
|
if (conflictError) {
|
||||||
|
warnings.push(conflictError);
|
||||||
|
}
|
||||||
|
|
||||||
|
const unusedFunctionError = await checkUnusedFunctionsOnFrontendBuilder(
|
||||||
|
files,
|
||||||
|
frontendBuilder
|
||||||
|
);
|
||||||
|
|
||||||
|
if (unusedFunctionError) {
|
||||||
|
return {
|
||||||
|
builders: null,
|
||||||
|
errors: [unusedFunctionError],
|
||||||
|
warnings,
|
||||||
|
};
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (pkg && builders.length === 0) {
|
if (pkg && builders.length === 0) {
|
||||||
// We only show this error when there are no api builders
|
// We only show this error when there are no api builders
|
||||||
// since the dependencies of the pkg could be used for those
|
// since the dependencies of the pkg could be used for those
|
||||||
errors.push(MISSING_BUILD_SCRIPT_ERROR);
|
errors.push(MISSING_BUILD_SCRIPT_ERROR);
|
||||||
return { errors, builders: null };
|
return { errors, warnings, builders: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
// We allow a `public` directory
|
// We allow a `public` directory
|
||||||
@@ -140,37 +401,16 @@ export async function detectBuilders(
|
|||||||
src: 'public/**/*',
|
src: 'public/**/*',
|
||||||
config,
|
config,
|
||||||
});
|
});
|
||||||
} else if (builders.length > 0) {
|
} else if (
|
||||||
// We can't use pattern matching, since `!(api)` and `!(api)/**/*`
|
builders.length > 0 &&
|
||||||
// won't give the correct results
|
files.some(f => !f.startsWith('api/') && f !== 'package.json')
|
||||||
builders.push(
|
) {
|
||||||
...files
|
// Everything besides the api directory
|
||||||
.filter(name => !name.startsWith('api/'))
|
// and package.json can be served as static files
|
||||||
.filter(name => !(name === 'package.json'))
|
builders.push({
|
||||||
.map(name => ({
|
|
||||||
use: '@now/static',
|
use: '@now/static',
|
||||||
src: name,
|
src: '!{api/**,package.json}',
|
||||||
config,
|
config,
|
||||||
}))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Change the tag for the builders
|
|
||||||
if (builders && builders.length) {
|
|
||||||
const tag = options && options.tag;
|
|
||||||
|
|
||||||
if (tag) {
|
|
||||||
builders = builders.map((originBuilder: Builder) => {
|
|
||||||
// Copy builder to make sure it is not a reference
|
|
||||||
const builder = { ...originBuilder };
|
|
||||||
|
|
||||||
// @now/static has no canary builder
|
|
||||||
if (builder.use !== '@now/static') {
|
|
||||||
builder.use = `${builder.use}@${tag}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,5 +418,6 @@ export async function detectBuilders(
|
|||||||
return {
|
return {
|
||||||
builders: builders.length ? builders : null,
|
builders: builders.length ? builders : null,
|
||||||
errors: errors.length ? errors : null,
|
errors: errors.length ? errors : null,
|
||||||
|
warnings,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Route, Builder } from './types';
|
|
||||||
import { parse as parsePath } from 'path';
|
import { parse as parsePath } from 'path';
|
||||||
import { ignoreApiFilter, sortFiles } from './detect-builders';
|
import { Route, Builder } from './types';
|
||||||
|
import { getIgnoreApiFilter, sortFiles } from './detect-builders';
|
||||||
|
|
||||||
function escapeName(name: string) {
|
function escapeName(name: string) {
|
||||||
const special = '[]^$.|?*+()'.split('');
|
const special = '[]^$.|?*+()'.split('');
|
||||||
@@ -43,11 +43,10 @@ function getSegmentName(segment: string): string | null {
|
|||||||
function createRouteFromPath(filePath: string): Route {
|
function createRouteFromPath(filePath: string): Route {
|
||||||
const parts = filePath.split('/');
|
const parts = filePath.split('/');
|
||||||
|
|
||||||
let counter: number = 1;
|
let counter = 1;
|
||||||
const query: string[] = [];
|
const query: string[] = [];
|
||||||
|
|
||||||
const srcParts = parts.map(
|
const srcParts = parts.map((segment, index): string => {
|
||||||
(segment, index): string => {
|
|
||||||
const name = getSegmentName(segment);
|
const name = getSegmentName(segment);
|
||||||
const isLast = index === parts.length - 1;
|
const isLast = index === parts.length - 1;
|
||||||
|
|
||||||
@@ -61,7 +60,7 @@ function createRouteFromPath(filePath: string): Route {
|
|||||||
const prefix = isIndex ? '\\/' : '';
|
const prefix = isIndex ? '\\/' : '';
|
||||||
|
|
||||||
const names = [
|
const names = [
|
||||||
prefix,
|
isIndex ? prefix : `${fileName}\\/`,
|
||||||
prefix + escapeName(fileName),
|
prefix + escapeName(fileName),
|
||||||
prefix + escapeName(fileName) + escapeName(ext),
|
prefix + escapeName(fileName) + escapeName(ext),
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
@@ -72,8 +71,7 @@ function createRouteFromPath(filePath: string): Route {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return segment;
|
return segment;
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
const { name: fileName } = parsePath(filePath);
|
const { name: fileName } = parsePath(filePath);
|
||||||
const isIndex = fileName === 'index';
|
const isIndex = fileName === 'index';
|
||||||
@@ -120,7 +118,7 @@ function partiallyMatches(pathA: string, pathB: string): boolean {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Counts how often a path occurres when all placeholders
|
// Counts how often a path occurs when all placeholders
|
||||||
// got resolved, so we can check if they have conflicts
|
// got resolved, so we can check if they have conflicts
|
||||||
function pathOccurrences(filePath: string, files: string[]): string[] {
|
function pathOccurrences(filePath: string, files: string[]): string[] {
|
||||||
const getAbsolutePath = (unresolvedPath: string): string => {
|
const getAbsolutePath = (unresolvedPath: string): string => {
|
||||||
@@ -199,7 +197,10 @@ interface RoutesResult {
|
|||||||
error: { [key: string]: string } | null;
|
error: { [key: string]: string } | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function detectApiRoutes(files: string[]): Promise<RoutesResult> {
|
async function detectApiRoutes(
|
||||||
|
files: string[],
|
||||||
|
builders: Builder[]
|
||||||
|
): Promise<RoutesResult> {
|
||||||
if (!files || files.length === 0) {
|
if (!files || files.length === 0) {
|
||||||
return { defaultRoutes: null, error: null };
|
return { defaultRoutes: null, error: null };
|
||||||
}
|
}
|
||||||
@@ -207,7 +208,7 @@ async function detectApiRoutes(files: string[]): Promise<RoutesResult> {
|
|||||||
// The deepest routes need to be
|
// The deepest routes need to be
|
||||||
// the first ones to get handled
|
// the first ones to get handled
|
||||||
const sortedFiles = files
|
const sortedFiles = files
|
||||||
.filter(ignoreApiFilter)
|
.filter(getIgnoreApiFilter(builders))
|
||||||
.sort(sortFiles)
|
.sort(sortFiles)
|
||||||
.sort(sortFilesBySegmentCount);
|
.sort(sortFilesBySegmentCount);
|
||||||
|
|
||||||
@@ -228,7 +229,7 @@ async function detectApiRoutes(files: string[]): Promise<RoutesResult> {
|
|||||||
error: {
|
error: {
|
||||||
code: 'conflicting_path_segment',
|
code: 'conflicting_path_segment',
|
||||||
message:
|
message:
|
||||||
`The segment "${conflictingSegment}" occurres more than ` +
|
`The segment "${conflictingSegment}" occurs more than ` +
|
||||||
`one time in your path "${file}". Please make sure that ` +
|
`one time in your path "${file}". Please make sure that ` +
|
||||||
`every segment in a path is unique`,
|
`every segment in a path is unique`,
|
||||||
},
|
},
|
||||||
@@ -284,7 +285,7 @@ export async function detectRoutes(
|
|||||||
files: string[],
|
files: string[],
|
||||||
builders: Builder[]
|
builders: Builder[]
|
||||||
): Promise<RoutesResult> {
|
): Promise<RoutesResult> {
|
||||||
const routesResult = await detectApiRoutes(files);
|
const routesResult = await detectApiRoutes(files, builders);
|
||||||
|
|
||||||
if (routesResult.defaultRoutes && hasPublicBuilder(builders)) {
|
if (routesResult.defaultRoutes && hasPublicBuilder(builders)) {
|
||||||
routesResult.defaultRoutes.push({
|
routesResult.defaultRoutes.push({
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ import { File } from './types';
|
|||||||
|
|
||||||
interface FileBlobOptions {
|
interface FileBlobOptions {
|
||||||
mode?: number;
|
mode?: number;
|
||||||
|
contentType?: string;
|
||||||
data: string | Buffer;
|
data: string | Buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FromStreamOptions {
|
interface FromStreamOptions {
|
||||||
mode?: number;
|
mode?: number;
|
||||||
|
contentType?: string;
|
||||||
stream: NodeJS.ReadableStream;
|
stream: NodeJS.ReadableStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -16,16 +18,22 @@ export default class FileBlob implements File {
|
|||||||
public type: 'FileBlob';
|
public type: 'FileBlob';
|
||||||
public mode: number;
|
public mode: number;
|
||||||
public data: string | Buffer;
|
public data: string | Buffer;
|
||||||
|
public contentType: string | undefined;
|
||||||
|
|
||||||
constructor({ mode = 0o100644, data }: FileBlobOptions) {
|
constructor({ mode = 0o100644, contentType, data }: FileBlobOptions) {
|
||||||
assert(typeof mode === 'number');
|
assert(typeof mode === 'number');
|
||||||
assert(typeof data === 'string' || Buffer.isBuffer(data));
|
assert(typeof data === 'string' || Buffer.isBuffer(data));
|
||||||
this.type = 'FileBlob';
|
this.type = 'FileBlob';
|
||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
|
this.contentType = contentType;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async fromStream({ mode = 0o100644, stream }: FromStreamOptions) {
|
static async fromStream({
|
||||||
|
mode = 0o100644,
|
||||||
|
contentType,
|
||||||
|
stream,
|
||||||
|
}: FromStreamOptions) {
|
||||||
assert(typeof mode === 'number');
|
assert(typeof mode === 'number');
|
||||||
assert(typeof stream.pipe === 'function'); // is-stream
|
assert(typeof stream.pipe === 'function'); // is-stream
|
||||||
const chunks: Buffer[] = [];
|
const chunks: Buffer[] = [];
|
||||||
@@ -37,7 +45,7 @@ export default class FileBlob implements File {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const data = Buffer.concat(chunks);
|
const data = Buffer.concat(chunks);
|
||||||
return new FileBlob({ mode, data });
|
return new FileBlob({ mode, contentType, data });
|
||||||
}
|
}
|
||||||
|
|
||||||
toStream(): NodeJS.ReadableStream {
|
toStream(): NodeJS.ReadableStream {
|
||||||
|
|||||||
@@ -9,11 +9,13 @@ const semaToPreventEMFILE = new Sema(20);
|
|||||||
|
|
||||||
interface FileFsRefOptions {
|
interface FileFsRefOptions {
|
||||||
mode?: number;
|
mode?: number;
|
||||||
|
contentType?: string;
|
||||||
fsPath: string;
|
fsPath: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FromStreamOptions {
|
interface FromStreamOptions {
|
||||||
mode: number;
|
mode: number;
|
||||||
|
contentType?: string;
|
||||||
stream: NodeJS.ReadableStream;
|
stream: NodeJS.ReadableStream;
|
||||||
fsPath: string;
|
fsPath: string;
|
||||||
}
|
}
|
||||||
@@ -22,17 +24,20 @@ class FileFsRef implements File {
|
|||||||
public type: 'FileFsRef';
|
public type: 'FileFsRef';
|
||||||
public mode: number;
|
public mode: number;
|
||||||
public fsPath: string;
|
public fsPath: string;
|
||||||
|
public contentType: string | undefined;
|
||||||
|
|
||||||
constructor({ mode = 0o100644, fsPath }: FileFsRefOptions) {
|
constructor({ mode = 0o100644, contentType, fsPath }: FileFsRefOptions) {
|
||||||
assert(typeof mode === 'number');
|
assert(typeof mode === 'number');
|
||||||
assert(typeof fsPath === 'string');
|
assert(typeof fsPath === 'string');
|
||||||
this.type = 'FileFsRef';
|
this.type = 'FileFsRef';
|
||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
|
this.contentType = contentType;
|
||||||
this.fsPath = fsPath;
|
this.fsPath = fsPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async fromFsPath({
|
static async fromFsPath({
|
||||||
mode,
|
mode,
|
||||||
|
contentType,
|
||||||
fsPath,
|
fsPath,
|
||||||
}: FileFsRefOptions): Promise<FileFsRef> {
|
}: FileFsRefOptions): Promise<FileFsRef> {
|
||||||
let m = mode;
|
let m = mode;
|
||||||
@@ -40,11 +45,12 @@ class FileFsRef implements File {
|
|||||||
const stat = await fs.lstat(fsPath);
|
const stat = await fs.lstat(fsPath);
|
||||||
m = stat.mode;
|
m = stat.mode;
|
||||||
}
|
}
|
||||||
return new FileFsRef({ mode: m, fsPath });
|
return new FileFsRef({ mode: m, contentType, fsPath });
|
||||||
}
|
}
|
||||||
|
|
||||||
static async fromStream({
|
static async fromStream({
|
||||||
mode = 0o100644,
|
mode = 0o100644,
|
||||||
|
contentType,
|
||||||
stream,
|
stream,
|
||||||
fsPath,
|
fsPath,
|
||||||
}: FromStreamOptions): Promise<FileFsRef> {
|
}: FromStreamOptions): Promise<FileFsRef> {
|
||||||
@@ -63,7 +69,7 @@ class FileFsRef implements File {
|
|||||||
dest.on('error', reject);
|
dest.on('error', reject);
|
||||||
});
|
});
|
||||||
|
|
||||||
return new FileFsRef({ mode, fsPath });
|
return new FileFsRef({ mode, contentType, fsPath });
|
||||||
}
|
}
|
||||||
|
|
||||||
async toStreamAsync(): Promise<NodeJS.ReadableStream> {
|
async toStreamAsync(): Promise<NodeJS.ReadableStream> {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { File } from './types';
|
|||||||
interface FileRefOptions {
|
interface FileRefOptions {
|
||||||
mode?: number;
|
mode?: number;
|
||||||
digest: string;
|
digest: string;
|
||||||
|
contentType?: string;
|
||||||
mutable?: boolean;
|
mutable?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,14 +27,21 @@ export default class FileRef implements File {
|
|||||||
public type: 'FileRef';
|
public type: 'FileRef';
|
||||||
public mode: number;
|
public mode: number;
|
||||||
public digest: string;
|
public digest: string;
|
||||||
|
public contentType: string | undefined;
|
||||||
private mutable: boolean;
|
private mutable: boolean;
|
||||||
|
|
||||||
constructor({ mode = 0o100644, digest, mutable = false }: FileRefOptions) {
|
constructor({
|
||||||
|
mode = 0o100644,
|
||||||
|
digest,
|
||||||
|
contentType,
|
||||||
|
mutable = false,
|
||||||
|
}: FileRefOptions) {
|
||||||
assert(typeof mode === 'number');
|
assert(typeof mode === 'number');
|
||||||
assert(typeof digest === 'string');
|
assert(typeof digest === 'string');
|
||||||
this.type = 'FileRef';
|
this.type = 'FileRef';
|
||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
this.digest = digest;
|
this.digest = digest;
|
||||||
|
this.contentType = contentType;
|
||||||
this.mutable = mutable;
|
this.mutable = mutable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { intersects } from 'semver';
|
import { intersects } from 'semver';
|
||||||
import { NodeVersion } from '../types';
|
import { NodeVersion } from '../types';
|
||||||
|
import debug from '../debug';
|
||||||
|
|
||||||
const supportedOptions: NodeVersion[] = [
|
const supportedOptions: NodeVersion[] = [
|
||||||
{ major: 10, range: '10.x', runtime: 'nodejs10.x' },
|
{ major: 10, range: '10.x', runtime: 'nodejs10.x' },
|
||||||
@@ -20,7 +21,7 @@ export async function getSupportedNodeVersion(
|
|||||||
|
|
||||||
if (!engineRange) {
|
if (!engineRange) {
|
||||||
if (!silent) {
|
if (!silent) {
|
||||||
console.log(
|
debug(
|
||||||
'missing `engines` in `package.json`, using default range: ' +
|
'missing `engines` in `package.json`, using default range: ' +
|
||||||
selection.range
|
selection.range
|
||||||
);
|
);
|
||||||
@@ -34,7 +35,7 @@ export async function getSupportedNodeVersion(
|
|||||||
});
|
});
|
||||||
if (found) {
|
if (found) {
|
||||||
if (!silent) {
|
if (!silent) {
|
||||||
console.log(
|
debug(
|
||||||
'Found `engines` in `package.json`, selecting range: ' +
|
'Found `engines` in `package.json`, selecting range: ' +
|
||||||
selection.range
|
selection.range
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,21 +1,22 @@
|
|||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import debug from '../debug';
|
||||||
import spawn from 'cross-spawn';
|
import spawn from 'cross-spawn';
|
||||||
import { SpawnOptions } from 'child_process';
|
import { SpawnOptions } from 'child_process';
|
||||||
import { deprecate } from 'util';
|
import { deprecate } from 'util';
|
||||||
|
import { cpus } from 'os';
|
||||||
import { Meta, PackageJson, NodeVersion, Config } from '../types';
|
import { Meta, PackageJson, NodeVersion, Config } from '../types';
|
||||||
import { getSupportedNodeVersion } from './node-version';
|
import { getSupportedNodeVersion } from './node-version';
|
||||||
|
|
||||||
function spawnAsync(
|
export function spawnAsync(
|
||||||
command: string,
|
command: string,
|
||||||
args: string[],
|
args: string[],
|
||||||
cwd: string,
|
|
||||||
opts: SpawnOptions = {}
|
opts: SpawnOptions = {}
|
||||||
) {
|
) {
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
const stderrLogs: Buffer[] = [];
|
const stderrLogs: Buffer[] = [];
|
||||||
opts = { stdio: 'inherit', cwd, ...opts };
|
opts = { stdio: 'inherit', ...opts };
|
||||||
const child = spawn(command, args, opts);
|
const child = spawn(command, args, opts);
|
||||||
|
|
||||||
if (opts.stdio === 'pipe' && child.stderr) {
|
if (opts.stdio === 'pipe' && child.stderr) {
|
||||||
@@ -54,7 +55,10 @@ export async function runShellScript(
|
|||||||
assert(path.isAbsolute(fsPath));
|
assert(path.isAbsolute(fsPath));
|
||||||
const destPath = path.dirname(fsPath);
|
const destPath = path.dirname(fsPath);
|
||||||
await chmodPlusX(fsPath);
|
await chmodPlusX(fsPath);
|
||||||
await spawnAsync(`./${path.basename(fsPath)}`, args, destPath, spawnOpts);
|
await spawnAsync(`./${path.basename(fsPath)}`, args, {
|
||||||
|
cwd: destPath,
|
||||||
|
...spawnOpts,
|
||||||
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,38 +132,100 @@ async function scanParentDirs(destPath: string, readPackageJson = false) {
|
|||||||
export async function runNpmInstall(
|
export async function runNpmInstall(
|
||||||
destPath: string,
|
destPath: string,
|
||||||
args: string[] = [],
|
args: string[] = [],
|
||||||
spawnOpts?: SpawnOptions
|
spawnOpts?: SpawnOptions,
|
||||||
|
meta?: Meta
|
||||||
) {
|
) {
|
||||||
|
if (meta && meta.isDev) {
|
||||||
|
debug('Skipping dependency installation because dev mode is enabled');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
assert(path.isAbsolute(destPath));
|
assert(path.isAbsolute(destPath));
|
||||||
|
|
||||||
let commandArgs = args;
|
let commandArgs = args;
|
||||||
console.log(`installing to ${destPath}`);
|
debug(`Installing to ${destPath}`);
|
||||||
const { hasPackageLockJson } = await scanParentDirs(destPath);
|
const { hasPackageLockJson } = await scanParentDirs(destPath);
|
||||||
|
|
||||||
const opts = spawnOpts || { env: process.env };
|
const opts = { cwd: destPath, ...spawnOpts } || {
|
||||||
|
cwd: destPath,
|
||||||
|
env: process.env,
|
||||||
|
};
|
||||||
|
|
||||||
if (hasPackageLockJson) {
|
if (hasPackageLockJson) {
|
||||||
commandArgs = args.filter(a => a !== '--prefer-offline');
|
commandArgs = args.filter(a => a !== '--prefer-offline');
|
||||||
await spawnAsync(
|
await spawnAsync(
|
||||||
'npm',
|
'npm',
|
||||||
commandArgs.concat(['install', '--unsafe-perm']),
|
commandArgs.concat(['install', '--unsafe-perm']),
|
||||||
destPath,
|
|
||||||
opts
|
opts
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
await spawnAsync(
|
await spawnAsync(
|
||||||
'yarn',
|
'yarn',
|
||||||
commandArgs.concat(['--ignore-engines', '--cwd', destPath]),
|
commandArgs.concat(['--ignore-engines', '--cwd', destPath]),
|
||||||
destPath,
|
|
||||||
opts
|
opts
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function runBundleInstall(
|
||||||
|
destPath: string,
|
||||||
|
args: string[] = [],
|
||||||
|
spawnOpts?: SpawnOptions,
|
||||||
|
meta?: Meta
|
||||||
|
) {
|
||||||
|
if (meta && meta.isDev) {
|
||||||
|
debug('Skipping dependency installation because dev mode is enabled');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(path.isAbsolute(destPath));
|
||||||
|
const opts = { cwd: destPath, ...spawnOpts } || {
|
||||||
|
cwd: destPath,
|
||||||
|
env: process.env,
|
||||||
|
};
|
||||||
|
|
||||||
|
await spawnAsync(
|
||||||
|
'bundle',
|
||||||
|
args.concat([
|
||||||
|
'install',
|
||||||
|
'--no-prune',
|
||||||
|
'--retry',
|
||||||
|
'3',
|
||||||
|
'--jobs',
|
||||||
|
String(cpus().length || 1),
|
||||||
|
]),
|
||||||
|
opts
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function runPipInstall(
|
||||||
|
destPath: string,
|
||||||
|
args: string[] = [],
|
||||||
|
spawnOpts?: SpawnOptions,
|
||||||
|
meta?: Meta
|
||||||
|
) {
|
||||||
|
if (meta && meta.isDev) {
|
||||||
|
debug('Skipping dependency installation because dev mode is enabled');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(path.isAbsolute(destPath));
|
||||||
|
const opts = { cwd: destPath, ...spawnOpts } || {
|
||||||
|
cwd: destPath,
|
||||||
|
env: process.env,
|
||||||
|
};
|
||||||
|
|
||||||
|
await spawnAsync(
|
||||||
|
'pip3',
|
||||||
|
['install', '--disable-pip-version-check', ...args],
|
||||||
|
opts
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export async function runPackageJsonScript(
|
export async function runPackageJsonScript(
|
||||||
destPath: string,
|
destPath: string,
|
||||||
scriptName: string,
|
scriptName: string,
|
||||||
opts?: SpawnOptions
|
spawnOpts?: SpawnOptions
|
||||||
) {
|
) {
|
||||||
assert(path.isAbsolute(destPath));
|
assert(path.isAbsolute(destPath));
|
||||||
const { packageJson, hasPackageLockJson } = await scanParentDirs(
|
const { packageJson, hasPackageLockJson } = await scanParentDirs(
|
||||||
@@ -174,17 +240,14 @@ export async function runPackageJsonScript(
|
|||||||
);
|
);
|
||||||
if (!hasScript) return false;
|
if (!hasScript) return false;
|
||||||
|
|
||||||
|
const opts = { cwd: destPath, ...spawnOpts };
|
||||||
|
|
||||||
if (hasPackageLockJson) {
|
if (hasPackageLockJson) {
|
||||||
console.log(`running "npm run ${scriptName}"`);
|
console.log(`Running "npm run ${scriptName}"`);
|
||||||
await spawnAsync('npm', ['run', scriptName], destPath, opts);
|
await spawnAsync('npm', ['run', scriptName], opts);
|
||||||
} else {
|
} else {
|
||||||
console.log(`running "yarn run ${scriptName}"`);
|
console.log(`Running "yarn run ${scriptName}"`);
|
||||||
await spawnAsync(
|
await spawnAsync('yarn', ['--cwd', destPath, 'run', scriptName], opts);
|
||||||
'yarn',
|
|
||||||
['--cwd', destPath, 'run', scriptName],
|
|
||||||
destPath,
|
|
||||||
opts
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
import FileBlob from './file-blob';
|
import FileBlob from './file-blob';
|
||||||
import FileFsRef from './file-fs-ref';
|
import FileFsRef from './file-fs-ref';
|
||||||
import FileRef from './file-ref';
|
import FileRef from './file-ref';
|
||||||
import { Lambda, createLambda } from './lambda';
|
import { Lambda, createLambda, getLambdaOptionsFromFunction } from './lambda';
|
||||||
|
import { Prerender } from './prerender';
|
||||||
import download, { DownloadedFiles } from './fs/download';
|
import download, { DownloadedFiles } from './fs/download';
|
||||||
import getWriteableDirectory from './fs/get-writable-directory';
|
import getWriteableDirectory from './fs/get-writable-directory';
|
||||||
import glob from './fs/glob';
|
import glob from './fs/glob';
|
||||||
import rename from './fs/rename';
|
import rename from './fs/rename';
|
||||||
import {
|
import {
|
||||||
|
spawnAsync,
|
||||||
installDependencies,
|
installDependencies,
|
||||||
runPackageJsonScript,
|
runPackageJsonScript,
|
||||||
runNpmInstall,
|
runNpmInstall,
|
||||||
|
runBundleInstall,
|
||||||
|
runPipInstall,
|
||||||
runShellScript,
|
runShellScript,
|
||||||
getNodeVersion,
|
getNodeVersion,
|
||||||
getSpawnOptions,
|
getSpawnOptions,
|
||||||
@@ -26,14 +30,18 @@ export {
|
|||||||
FileRef,
|
FileRef,
|
||||||
Lambda,
|
Lambda,
|
||||||
createLambda,
|
createLambda,
|
||||||
|
Prerender,
|
||||||
download,
|
download,
|
||||||
DownloadedFiles,
|
DownloadedFiles,
|
||||||
getWriteableDirectory,
|
getWriteableDirectory,
|
||||||
glob,
|
glob,
|
||||||
rename,
|
rename,
|
||||||
|
spawnAsync,
|
||||||
installDependencies,
|
installDependencies,
|
||||||
runPackageJsonScript,
|
runPackageJsonScript,
|
||||||
runNpmInstall,
|
runNpmInstall,
|
||||||
|
runBundleInstall,
|
||||||
|
runPipInstall,
|
||||||
runShellScript,
|
runShellScript,
|
||||||
getNodeVersion,
|
getNodeVersion,
|
||||||
getSpawnOptions,
|
getSpawnOptions,
|
||||||
@@ -42,6 +50,7 @@ export {
|
|||||||
detectBuilders,
|
detectBuilders,
|
||||||
detectRoutes,
|
detectRoutes,
|
||||||
debug,
|
debug,
|
||||||
|
getLambdaOptionsFromFunction,
|
||||||
};
|
};
|
||||||
|
|
||||||
export * from './types';
|
export * from './types';
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
import Sema from 'async-sema';
|
import Sema from 'async-sema';
|
||||||
import { ZipFile } from 'yazl';
|
import { ZipFile } from 'yazl';
|
||||||
|
import minimatch from 'minimatch';
|
||||||
import { readlink } from 'fs-extra';
|
import { readlink } from 'fs-extra';
|
||||||
import { Files } from './types';
|
import { Files, Config } from './types';
|
||||||
import FileFsRef from './file-fs-ref';
|
import FileFsRef from './file-fs-ref';
|
||||||
import { isSymbolicLink } from './fs/download';
|
import { isSymbolicLink } from './fs/download';
|
||||||
import streamToBuffer from './fs/stream-to-buffer';
|
import streamToBuffer from './fs/stream-to-buffer';
|
||||||
@@ -15,6 +16,8 @@ interface LambdaOptions {
|
|||||||
zipBuffer: Buffer;
|
zipBuffer: Buffer;
|
||||||
handler: string;
|
handler: string;
|
||||||
runtime: string;
|
runtime: string;
|
||||||
|
memory?: number;
|
||||||
|
maxDuration?: number;
|
||||||
environment: Environment;
|
environment: Environment;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,21 +25,39 @@ interface CreateLambdaOptions {
|
|||||||
files: Files;
|
files: Files;
|
||||||
handler: string;
|
handler: string;
|
||||||
runtime: string;
|
runtime: string;
|
||||||
|
memory?: number;
|
||||||
|
maxDuration?: number;
|
||||||
environment?: Environment;
|
environment?: Environment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface GetLambdaOptionsFromFunctionOptions {
|
||||||
|
sourceFile: string;
|
||||||
|
config?: Config;
|
||||||
|
}
|
||||||
|
|
||||||
export class Lambda {
|
export class Lambda {
|
||||||
public type: 'Lambda';
|
public type: 'Lambda';
|
||||||
public zipBuffer: Buffer;
|
public zipBuffer: Buffer;
|
||||||
public handler: string;
|
public handler: string;
|
||||||
public runtime: string;
|
public runtime: string;
|
||||||
|
public memory?: number;
|
||||||
|
public maxDuration?: number;
|
||||||
public environment: Environment;
|
public environment: Environment;
|
||||||
|
|
||||||
constructor({ zipBuffer, handler, runtime, environment }: LambdaOptions) {
|
constructor({
|
||||||
|
zipBuffer,
|
||||||
|
handler,
|
||||||
|
runtime,
|
||||||
|
maxDuration,
|
||||||
|
memory,
|
||||||
|
environment,
|
||||||
|
}: LambdaOptions) {
|
||||||
this.type = 'Lambda';
|
this.type = 'Lambda';
|
||||||
this.zipBuffer = zipBuffer;
|
this.zipBuffer = zipBuffer;
|
||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
this.runtime = runtime;
|
this.runtime = runtime;
|
||||||
|
this.memory = memory;
|
||||||
|
this.maxDuration = maxDuration;
|
||||||
this.environment = environment;
|
this.environment = environment;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -48,6 +69,8 @@ export async function createLambda({
|
|||||||
files,
|
files,
|
||||||
handler,
|
handler,
|
||||||
runtime,
|
runtime,
|
||||||
|
memory,
|
||||||
|
maxDuration,
|
||||||
environment = {},
|
environment = {},
|
||||||
}: CreateLambdaOptions): Promise<Lambda> {
|
}: CreateLambdaOptions): Promise<Lambda> {
|
||||||
assert(typeof files === 'object', '"files" must be an object');
|
assert(typeof files === 'object', '"files" must be an object');
|
||||||
@@ -55,6 +78,14 @@ export async function createLambda({
|
|||||||
assert(typeof runtime === 'string', '"runtime" is not a string');
|
assert(typeof runtime === 'string', '"runtime" is not a string');
|
||||||
assert(typeof environment === 'object', '"environment" is not an object');
|
assert(typeof environment === 'object', '"environment" is not an object');
|
||||||
|
|
||||||
|
if (memory !== undefined) {
|
||||||
|
assert(typeof memory === 'number', '"memory" is not a number');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxDuration !== undefined) {
|
||||||
|
assert(typeof maxDuration === 'number', '"maxDuration" is not a number');
|
||||||
|
}
|
||||||
|
|
||||||
await sema.acquire();
|
await sema.acquire();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -63,6 +94,8 @@ export async function createLambda({
|
|||||||
zipBuffer,
|
zipBuffer,
|
||||||
handler,
|
handler,
|
||||||
runtime,
|
runtime,
|
||||||
|
memory,
|
||||||
|
maxDuration,
|
||||||
environment,
|
environment,
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
@@ -105,3 +138,23 @@ export async function createZip(files: Files): Promise<Buffer> {
|
|||||||
|
|
||||||
return zipBuffer;
|
return zipBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getLambdaOptionsFromFunction({
|
||||||
|
sourceFile,
|
||||||
|
config,
|
||||||
|
}: GetLambdaOptionsFromFunctionOptions): Promise<
|
||||||
|
Pick<LambdaOptions, 'memory' | 'maxDuration'>
|
||||||
|
> {
|
||||||
|
if (config && config.functions) {
|
||||||
|
for (const [pattern, fn] of Object.entries(config.functions)) {
|
||||||
|
if (sourceFile === pattern || minimatch(sourceFile, pattern)) {
|
||||||
|
return {
|
||||||
|
memory: fn.memory,
|
||||||
|
maxDuration: fn.maxDuration,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|||||||
42
packages/now-build-utils/src/prerender.ts
Normal file
42
packages/now-build-utils/src/prerender.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import FileBlob from './file-blob';
|
||||||
|
import FileFsRef from './file-fs-ref';
|
||||||
|
import FileRef from './file-ref';
|
||||||
|
import { Lambda } from './lambda';
|
||||||
|
|
||||||
|
interface PrerenderOptions {
|
||||||
|
expiration: number;
|
||||||
|
lambda: Lambda;
|
||||||
|
fallback: FileBlob | FileFsRef | FileRef | null;
|
||||||
|
group?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Prerender {
|
||||||
|
public type: 'Prerender';
|
||||||
|
public expiration: number;
|
||||||
|
public lambda: Lambda;
|
||||||
|
public fallback: FileBlob | FileFsRef | FileRef | null;
|
||||||
|
public group?: number;
|
||||||
|
|
||||||
|
constructor({ expiration, lambda, fallback, group }: PrerenderOptions) {
|
||||||
|
this.type = 'Prerender';
|
||||||
|
this.expiration = expiration;
|
||||||
|
this.lambda = lambda;
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof group !== 'undefined' &&
|
||||||
|
(group <= 0 || !Number.isInteger(group))
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
'The `group` argument for `Prerender` needs to be a natural number.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.group = group;
|
||||||
|
|
||||||
|
if (typeof fallback === 'undefined') {
|
||||||
|
throw new Error(
|
||||||
|
'The `fallback` argument for `Prerender` needs to be a `FileBlob`, `FileFsRef`, `FileRef`, or null.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.fallback = fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
import FileRef from './file-ref';
|
import FileRef from './file-ref';
|
||||||
import FileFsRef from './file-fs-ref';
|
import FileFsRef from './file-fs-ref';
|
||||||
|
|
||||||
|
export interface Env {
|
||||||
|
[name: string]: string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export interface File {
|
export interface File {
|
||||||
type: string;
|
type: string;
|
||||||
mode: number;
|
mode: number;
|
||||||
|
contentType?: string;
|
||||||
toStream: () => NodeJS.ReadableStream;
|
toStream: () => NodeJS.ReadableStream;
|
||||||
/**
|
/**
|
||||||
* The absolute path to the file in the filesystem
|
* The absolute path to the file in the filesystem
|
||||||
@@ -34,6 +39,7 @@ export interface Config {
|
|||||||
| boolean
|
| boolean
|
||||||
| number
|
| number
|
||||||
| { [key: string]: string }
|
| { [key: string]: string }
|
||||||
|
| BuilderFunctions
|
||||||
| undefined;
|
| undefined;
|
||||||
maxLambdaSize?: string;
|
maxLambdaSize?: string;
|
||||||
includeFiles?: string | string[];
|
includeFiles?: string | string[];
|
||||||
@@ -44,6 +50,7 @@ export interface Config {
|
|||||||
debug?: boolean;
|
debug?: boolean;
|
||||||
zeroConfig?: boolean;
|
zeroConfig?: boolean;
|
||||||
import?: { [key: string]: string };
|
import?: { [key: string]: string };
|
||||||
|
functions?: BuilderFunctions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Meta {
|
export interface Meta {
|
||||||
@@ -52,6 +59,8 @@ export interface Meta {
|
|||||||
requestPath?: string;
|
requestPath?: string;
|
||||||
filesChanged?: string[];
|
filesChanged?: string[];
|
||||||
filesRemoved?: string[];
|
filesRemoved?: string[];
|
||||||
|
env?: Env;
|
||||||
|
buildEnv?: Env;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AnalyzeOptions {
|
export interface AnalyzeOptions {
|
||||||
@@ -184,23 +193,110 @@ export interface ShouldServeOptions {
|
|||||||
config: Config;
|
config: Config;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PackageJson {
|
/**
|
||||||
|
* Credit to Iain Reid, MIT license.
|
||||||
|
* Source: https://gist.github.com/iainreid820/5c1cc527fe6b5b7dba41fec7fe54bf6e
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||||
|
namespace PackageJson {
|
||||||
|
/**
|
||||||
|
* An author or contributor
|
||||||
|
*/
|
||||||
|
export interface Author {
|
||||||
name: string;
|
name: string;
|
||||||
version: string;
|
email?: string;
|
||||||
engines?: {
|
homepage?: string;
|
||||||
[key: string]: string;
|
}
|
||||||
node: string;
|
|
||||||
npm: string;
|
/**
|
||||||
};
|
* A map of exposed bin commands
|
||||||
scripts?: {
|
*/
|
||||||
[key: string]: string;
|
export interface BinMap {
|
||||||
};
|
[commandName: string]: string;
|
||||||
dependencies?: {
|
}
|
||||||
[key: string]: string;
|
|
||||||
};
|
/**
|
||||||
devDependencies?: {
|
* A bugs link
|
||||||
[key: string]: string;
|
*/
|
||||||
};
|
export interface Bugs {
|
||||||
|
email: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Config {
|
||||||
|
name?: string;
|
||||||
|
config?: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map of dependencies
|
||||||
|
*/
|
||||||
|
export interface DependencyMap {
|
||||||
|
[dependencyName: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CommonJS package structure
|
||||||
|
*/
|
||||||
|
export interface Directories {
|
||||||
|
lib?: string;
|
||||||
|
bin?: string;
|
||||||
|
man?: string;
|
||||||
|
doc?: string;
|
||||||
|
example?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Engines {
|
||||||
|
node?: string;
|
||||||
|
npm?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PublishConfig {
|
||||||
|
registry?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A project repository
|
||||||
|
*/
|
||||||
|
export interface Repository {
|
||||||
|
type: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScriptsMap {
|
||||||
|
[scriptName: string]: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PackageJson {
|
||||||
|
readonly name?: string;
|
||||||
|
readonly version?: string;
|
||||||
|
readonly description?: string;
|
||||||
|
readonly keywords?: string[];
|
||||||
|
readonly homepage?: string;
|
||||||
|
readonly bugs?: string | PackageJson.Bugs;
|
||||||
|
readonly license?: string;
|
||||||
|
readonly author?: string | PackageJson.Author;
|
||||||
|
readonly contributors?: string[] | PackageJson.Author[];
|
||||||
|
readonly files?: string[];
|
||||||
|
readonly main?: string;
|
||||||
|
readonly bin?: string | PackageJson.BinMap;
|
||||||
|
readonly man?: string | string[];
|
||||||
|
readonly directories?: PackageJson.Directories;
|
||||||
|
readonly repository?: string | PackageJson.Repository;
|
||||||
|
readonly scripts?: PackageJson.ScriptsMap;
|
||||||
|
readonly config?: PackageJson.Config;
|
||||||
|
readonly dependencies?: PackageJson.DependencyMap;
|
||||||
|
readonly devDependencies?: PackageJson.DependencyMap;
|
||||||
|
readonly peerDependencies?: PackageJson.DependencyMap;
|
||||||
|
readonly optionalDependencies?: PackageJson.DependencyMap;
|
||||||
|
readonly bundledDependencies?: string[];
|
||||||
|
readonly engines?: PackageJson.Engines;
|
||||||
|
readonly os?: string[];
|
||||||
|
readonly cpu?: string[];
|
||||||
|
readonly preferGlobal?: boolean;
|
||||||
|
readonly private?: boolean;
|
||||||
|
readonly publishConfig?: PackageJson.PublishConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NodeVersion {
|
export interface NodeVersion {
|
||||||
@@ -214,3 +310,13 @@ export interface Builder {
|
|||||||
src: string;
|
src: string;
|
||||||
config?: Config;
|
config?: Config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BuilderFunctions {
|
||||||
|
[key: string]: {
|
||||||
|
memory?: number;
|
||||||
|
maxDuration?: number;
|
||||||
|
runtime?: string;
|
||||||
|
includeFiles?: string;
|
||||||
|
excludeFiles?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
209
packages/now-build-utils/test/integration.test.js
vendored
Normal file
209
packages/now-build-utils/test/integration.test.js
vendored
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
const {
|
||||||
|
packAndDeploy,
|
||||||
|
testDeployment,
|
||||||
|
} = require('../../../test/lib/deployment/test-deployment');
|
||||||
|
const { glob, detectBuilders, detectRoutes } = require('../');
|
||||||
|
|
||||||
|
jest.setTimeout(4 * 60 * 1000);
|
||||||
|
|
||||||
|
const builderUrl = '@canary';
|
||||||
|
let buildUtilsUrl;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const buildUtilsPath = path.resolve(__dirname, '..');
|
||||||
|
buildUtilsUrl = await packAndDeploy(buildUtilsPath);
|
||||||
|
console.log('buildUtilsUrl', buildUtilsUrl);
|
||||||
|
});
|
||||||
|
|
||||||
|
const fixturesPath = path.resolve(__dirname, 'fixtures');
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
for (const fixture of fs.readdirSync(fixturesPath)) {
|
||||||
|
if (fixture.includes('zero-config')) {
|
||||||
|
// Those have separate tests
|
||||||
|
continue; // eslint-disable-line no-continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-loop-func
|
||||||
|
it(`should build ${fixture}`, async () => {
|
||||||
|
await expect(
|
||||||
|
testDeployment(
|
||||||
|
{ builderUrl, buildUtilsUrl },
|
||||||
|
path.join(fixturesPath, fixture)
|
||||||
|
)
|
||||||
|
).resolves.toBeDefined();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// few foreign tests
|
||||||
|
|
||||||
|
const buildersToTestWith = ['now-next', 'now-node', 'now-static-build'];
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
for (const builder of buildersToTestWith) {
|
||||||
|
const fixturesPath2 = path.resolve(
|
||||||
|
__dirname,
|
||||||
|
`../../${builder}/test/fixtures`
|
||||||
|
);
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
for (const fixture of fs.readdirSync(fixturesPath2)) {
|
||||||
|
// don't run all foreign fixtures, just some
|
||||||
|
if (['01-cowsay', '01-cache-headers', '03-env-vars'].includes(fixture)) {
|
||||||
|
// eslint-disable-next-line no-loop-func
|
||||||
|
it(`should build ${builder}/${fixture}`, async () => {
|
||||||
|
await expect(
|
||||||
|
testDeployment(
|
||||||
|
{ builderUrl, buildUtilsUrl },
|
||||||
|
path.join(fixturesPath2, fixture)
|
||||||
|
)
|
||||||
|
).resolves.toBeDefined();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it('Test `detectBuilders` and `detectRoutes`', async () => {
|
||||||
|
const fixture = path.join(__dirname, 'fixtures', '01-zero-config-api');
|
||||||
|
const pkg = await fs.readJSON(path.join(fixture, 'package.json'));
|
||||||
|
const fileList = await glob('**', fixture);
|
||||||
|
const files = Object.keys(fileList);
|
||||||
|
|
||||||
|
const probes = [
|
||||||
|
{
|
||||||
|
path: '/api/my-endpoint',
|
||||||
|
mustContain: 'my-endpoint',
|
||||||
|
status: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/api/other-endpoint',
|
||||||
|
mustContain: 'other-endpoint',
|
||||||
|
status: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/api/team/zeit',
|
||||||
|
mustContain: 'team/zeit',
|
||||||
|
status: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/api/user/myself',
|
||||||
|
mustContain: 'user/myself',
|
||||||
|
status: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/api/not-okay/',
|
||||||
|
status: 404,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/api',
|
||||||
|
status: 404,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/api/',
|
||||||
|
status: 404,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
mustContain: 'hello from index.txt',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const { builders } = await detectBuilders(files, pkg);
|
||||||
|
const { defaultRoutes } = await detectRoutes(files, builders);
|
||||||
|
|
||||||
|
const nowConfig = { builds: builders, routes: defaultRoutes, probes };
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(fixture, 'now.json'),
|
||||||
|
JSON.stringify(nowConfig, null, 2)
|
||||||
|
);
|
||||||
|
|
||||||
|
const deployment = await testDeployment(
|
||||||
|
{ builderUrl, buildUtilsUrl },
|
||||||
|
fixture
|
||||||
|
);
|
||||||
|
expect(deployment).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Test `detectBuilders` and `detectRoutes` with `index` files', async () => {
|
||||||
|
const fixture = path.join(__dirname, 'fixtures', '02-zero-config-api');
|
||||||
|
const pkg = await fs.readJSON(path.join(fixture, 'package.json'));
|
||||||
|
const fileList = await glob('**', fixture);
|
||||||
|
const files = Object.keys(fileList);
|
||||||
|
|
||||||
|
const probes = [
|
||||||
|
{
|
||||||
|
path: '/api/not-okay',
|
||||||
|
status: 404,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/api',
|
||||||
|
mustContain: 'hello from api/index.js',
|
||||||
|
status: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/api/',
|
||||||
|
mustContain: 'hello from api/index.js',
|
||||||
|
status: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/api/index',
|
||||||
|
mustContain: 'hello from api/index.js',
|
||||||
|
status: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/api/index.js',
|
||||||
|
mustContain: 'hello from api/index.js',
|
||||||
|
status: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/api/date.js',
|
||||||
|
mustContain: 'hello from api/date.js',
|
||||||
|
status: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Someone might expect this to be `date.js`,
|
||||||
|
// but I doubt that there is any case were both
|
||||||
|
// `date/index.js` and `date.js` exists,
|
||||||
|
// so it is not special cased
|
||||||
|
path: '/api/date',
|
||||||
|
mustContain: 'hello from api/date/index.js',
|
||||||
|
status: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/api/date/',
|
||||||
|
mustContain: 'hello from api/date/index.js',
|
||||||
|
status: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/api/date/index',
|
||||||
|
mustContain: 'hello from api/date/index.js',
|
||||||
|
status: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/api/date/index.js',
|
||||||
|
mustContain: 'hello from api/date/index.js',
|
||||||
|
status: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
mustContain: 'hello from index.txt',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const { builders } = await detectBuilders(files, pkg);
|
||||||
|
const { defaultRoutes } = await detectRoutes(files, builders);
|
||||||
|
|
||||||
|
const nowConfig = { builds: builders, routes: defaultRoutes, probes };
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(fixture, 'now.json'),
|
||||||
|
JSON.stringify(nowConfig, null, 2)
|
||||||
|
);
|
||||||
|
|
||||||
|
const deployment = await testDeployment(
|
||||||
|
{ builderUrl, buildUtilsUrl },
|
||||||
|
fixture
|
||||||
|
);
|
||||||
|
expect(deployment).toBeDefined();
|
||||||
|
});
|
||||||
@@ -1,34 +1,13 @@
|
|||||||
/* global beforeAll, expect, it, jest */
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
||||||
const execa = require('execa');
|
const execa = require('execa');
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const { createZip } = require('../dist/lambda');
|
const { createZip } = require('../dist/lambda');
|
||||||
const {
|
const { glob, download, detectBuilders, detectRoutes } = require('../');
|
||||||
glob, download, detectBuilders, detectRoutes,
|
|
||||||
} = require('../');
|
|
||||||
const {
|
const {
|
||||||
getSupportedNodeVersion,
|
getSupportedNodeVersion,
|
||||||
defaultSelection,
|
defaultSelection,
|
||||||
} = require('../dist/fs/node-version');
|
} = require('../dist/fs/node-version');
|
||||||
const {
|
|
||||||
packAndDeploy,
|
|
||||||
testDeployment,
|
|
||||||
} = require('../../../test/lib/deployment/test-deployment');
|
|
||||||
|
|
||||||
jest.setTimeout(4 * 60 * 1000);
|
|
||||||
|
|
||||||
const builderUrl = '@canary';
|
|
||||||
let buildUtilsUrl;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
const buildUtilsPath = path.resolve(__dirname, '..');
|
|
||||||
buildUtilsUrl = await packAndDeploy(buildUtilsPath);
|
|
||||||
console.log('buildUtilsUrl', buildUtilsUrl);
|
|
||||||
});
|
|
||||||
|
|
||||||
// unit tests
|
|
||||||
|
|
||||||
it('should re-create symlinks properly', async () => {
|
it('should re-create symlinks properly', async () => {
|
||||||
const files = await glob('**', path.join(__dirname, 'symlinks'));
|
const files = await glob('**', path.join(__dirname, 'symlinks'));
|
||||||
@@ -86,33 +65,33 @@ it('should match all semver ranges', () => {
|
|||||||
// See https://docs.npmjs.com/files/package.json#engines
|
// See https://docs.npmjs.com/files/package.json#engines
|
||||||
expect(getSupportedNodeVersion('10.0.0')).resolves.toHaveProperty(
|
expect(getSupportedNodeVersion('10.0.0')).resolves.toHaveProperty(
|
||||||
'major',
|
'major',
|
||||||
10,
|
10
|
||||||
);
|
);
|
||||||
expect(getSupportedNodeVersion('10.x')).resolves.toHaveProperty('major', 10);
|
expect(getSupportedNodeVersion('10.x')).resolves.toHaveProperty('major', 10);
|
||||||
expect(getSupportedNodeVersion('>=10')).resolves.toHaveProperty('major', 10);
|
expect(getSupportedNodeVersion('>=10')).resolves.toHaveProperty('major', 10);
|
||||||
expect(getSupportedNodeVersion('>=10.3.0')).resolves.toHaveProperty(
|
expect(getSupportedNodeVersion('>=10.3.0')).resolves.toHaveProperty(
|
||||||
'major',
|
'major',
|
||||||
10,
|
10
|
||||||
);
|
);
|
||||||
expect(getSupportedNodeVersion('8.5.0 - 10.5.0')).resolves.toHaveProperty(
|
expect(getSupportedNodeVersion('8.5.0 - 10.5.0')).resolves.toHaveProperty(
|
||||||
'major',
|
'major',
|
||||||
10,
|
10
|
||||||
);
|
);
|
||||||
expect(getSupportedNodeVersion('>=9.0.0')).resolves.toHaveProperty(
|
expect(getSupportedNodeVersion('>=9.0.0')).resolves.toHaveProperty(
|
||||||
'major',
|
'major',
|
||||||
10,
|
10
|
||||||
);
|
);
|
||||||
expect(getSupportedNodeVersion('>=9.5.0 <=10.5.0')).resolves.toHaveProperty(
|
expect(getSupportedNodeVersion('>=9.5.0 <=10.5.0')).resolves.toHaveProperty(
|
||||||
'major',
|
'major',
|
||||||
10,
|
10
|
||||||
);
|
);
|
||||||
expect(getSupportedNodeVersion('~10.5.0')).resolves.toHaveProperty(
|
expect(getSupportedNodeVersion('~10.5.0')).resolves.toHaveProperty(
|
||||||
'major',
|
'major',
|
||||||
10,
|
10
|
||||||
);
|
);
|
||||||
expect(getSupportedNodeVersion('^10.5.0')).resolves.toHaveProperty(
|
expect(getSupportedNodeVersion('^10.5.0')).resolves.toHaveProperty(
|
||||||
'major',
|
'major',
|
||||||
10,
|
10
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -146,68 +125,16 @@ it('should support require by path for legacy builders', () => {
|
|||||||
expect(Lambda2).toBe(index.Lambda);
|
expect(Lambda2).toBe(index.Lambda);
|
||||||
});
|
});
|
||||||
|
|
||||||
// own fixtures
|
describe('Test `detectBuilders`', () => {
|
||||||
|
it('package.json + no build', async () => {
|
||||||
const fixturesPath = path.resolve(__dirname, 'fixtures');
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
|
||||||
for (const fixture of fs.readdirSync(fixturesPath)) {
|
|
||||||
if (fixture.includes('zero-config')) {
|
|
||||||
// Those have separate tests
|
|
||||||
continue; // eslint-disable-line no-continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-loop-func
|
|
||||||
it(`should build ${fixture}`, async () => {
|
|
||||||
await expect(
|
|
||||||
testDeployment(
|
|
||||||
{ builderUrl, buildUtilsUrl },
|
|
||||||
path.join(fixturesPath, fixture),
|
|
||||||
),
|
|
||||||
).resolves.toBeDefined();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// few foreign tests
|
|
||||||
|
|
||||||
const buildersToTestWith = ['now-next', 'now-node', 'now-static-build'];
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
|
||||||
for (const builder of buildersToTestWith) {
|
|
||||||
const fixturesPath2 = path.resolve(
|
|
||||||
__dirname,
|
|
||||||
`../../${builder}/test/fixtures`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
|
||||||
for (const fixture of fs.readdirSync(fixturesPath2)) {
|
|
||||||
// don't run all foreign fixtures, just some
|
|
||||||
if (['01-cowsay', '01-cache-headers', '03-env-vars'].includes(fixture)) {
|
|
||||||
// eslint-disable-next-line no-loop-func
|
|
||||||
it(`should build ${builder}/${fixture}`, async () => {
|
|
||||||
await expect(
|
|
||||||
testDeployment(
|
|
||||||
{ builderUrl, buildUtilsUrl },
|
|
||||||
path.join(fixturesPath2, fixture),
|
|
||||||
),
|
|
||||||
).resolves.toBeDefined();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
it('Test `detectBuilders`', async () => {
|
|
||||||
{
|
|
||||||
// package.json + no build
|
|
||||||
const pkg = { dependencies: { next: '9.0.0' } };
|
const pkg = { dependencies: { next: '9.0.0' } };
|
||||||
const files = ['package.json', 'pages/index.js', 'public/index.html'];
|
const files = ['package.json', 'pages/index.js', 'public/index.html'];
|
||||||
const { builders, errors } = await detectBuilders(files, pkg);
|
const { builders, errors } = await detectBuilders(files, pkg);
|
||||||
expect(builders).toBe(null);
|
expect(builders).toBe(null);
|
||||||
expect(errors.length).toBe(1);
|
expect(errors.length).toBe(1);
|
||||||
}
|
});
|
||||||
|
|
||||||
{
|
it('package.json + no build + next', async () => {
|
||||||
// package.json + no build + next
|
|
||||||
const pkg = {
|
const pkg = {
|
||||||
scripts: { build: 'next build' },
|
scripts: { build: 'next build' },
|
||||||
dependencies: { next: '9.0.0' },
|
dependencies: { next: '9.0.0' },
|
||||||
@@ -216,10 +143,9 @@ it('Test `detectBuilders`', async () => {
|
|||||||
const { builders, errors } = await detectBuilders(files, pkg);
|
const { builders, errors } = await detectBuilders(files, pkg);
|
||||||
expect(builders[0].use).toBe('@now/next');
|
expect(builders[0].use).toBe('@now/next');
|
||||||
expect(errors).toBe(null);
|
expect(errors).toBe(null);
|
||||||
}
|
});
|
||||||
|
|
||||||
{
|
it('package.json + no build + next', async () => {
|
||||||
// package.json + no build + next
|
|
||||||
const pkg = {
|
const pkg = {
|
||||||
scripts: { build: 'next build' },
|
scripts: { build: 'next build' },
|
||||||
devDependencies: { next: '9.0.0' },
|
devDependencies: { next: '9.0.0' },
|
||||||
@@ -228,61 +154,53 @@ it('Test `detectBuilders`', async () => {
|
|||||||
const { builders, errors } = await detectBuilders(files, pkg);
|
const { builders, errors } = await detectBuilders(files, pkg);
|
||||||
expect(builders[0].use).toBe('@now/next');
|
expect(builders[0].use).toBe('@now/next');
|
||||||
expect(errors).toBe(null);
|
expect(errors).toBe(null);
|
||||||
}
|
});
|
||||||
|
|
||||||
{
|
it('package.json + no build', async () => {
|
||||||
// package.json + no build
|
|
||||||
const pkg = {};
|
const pkg = {};
|
||||||
const files = ['package.json'];
|
const files = ['package.json'];
|
||||||
const { builders, errors } = await detectBuilders(files, pkg);
|
const { builders, errors } = await detectBuilders(files, pkg);
|
||||||
expect(builders).toBe(null);
|
expect(builders).toBe(null);
|
||||||
expect(errors.length).toBe(1);
|
expect(errors.length).toBe(1);
|
||||||
}
|
});
|
||||||
|
|
||||||
{
|
it('static file', async () => {
|
||||||
// static file
|
|
||||||
const files = ['index.html'];
|
const files = ['index.html'];
|
||||||
const { builders, errors } = await detectBuilders(files);
|
const { builders, errors } = await detectBuilders(files);
|
||||||
expect(builders).toBe(null);
|
expect(builders).toBe(null);
|
||||||
expect(errors).toBe(null);
|
expect(errors).toBe(null);
|
||||||
}
|
});
|
||||||
|
|
||||||
{
|
it('no package.json + public', async () => {
|
||||||
// no package.json + public
|
|
||||||
const files = ['api/users.js', 'public/index.html'];
|
const files = ['api/users.js', 'public/index.html'];
|
||||||
const { builders, errors } = await detectBuilders(files);
|
const { builders, errors } = await detectBuilders(files);
|
||||||
expect(builders[1].use).toBe('@now/static');
|
expect(builders[1].use).toBe('@now/static');
|
||||||
expect(errors).toBe(null);
|
expect(errors).toBe(null);
|
||||||
}
|
});
|
||||||
|
|
||||||
{
|
it('no package.json + no build + raw static + api', async () => {
|
||||||
// no package.json + no build + raw static + api
|
|
||||||
const files = ['api/users.js', 'index.html'];
|
const files = ['api/users.js', 'index.html'];
|
||||||
const { builders, errors } = await detectBuilders(files);
|
const { builders, errors } = await detectBuilders(files);
|
||||||
expect(builders[0].use).toBe('@now/node');
|
expect(builders[0].use).toBe('@now/node');
|
||||||
expect(builders[0].src).toBe('api/users.js');
|
expect(builders[0].src).toBe('api/users.js');
|
||||||
expect(builders[1].use).toBe('@now/static');
|
expect(builders[1].use).toBe('@now/static');
|
||||||
expect(builders[1].src).toBe('index.html');
|
expect(builders[1].src).toBe('!{api/**,package.json}');
|
||||||
expect(builders.length).toBe(2);
|
expect(builders.length).toBe(2);
|
||||||
expect(errors).toBe(null);
|
expect(errors).toBe(null);
|
||||||
}
|
});
|
||||||
|
|
||||||
{
|
it('package.json + no build + root + api', async () => {
|
||||||
// package.json + no build + root + api
|
|
||||||
const files = ['index.html', 'api/[endpoint].js', 'static/image.png'];
|
const files = ['index.html', 'api/[endpoint].js', 'static/image.png'];
|
||||||
const { builders, errors } = await detectBuilders(files);
|
const { builders, errors } = await detectBuilders(files);
|
||||||
expect(builders[0].use).toBe('@now/node');
|
expect(builders[0].use).toBe('@now/node');
|
||||||
expect(builders[0].src).toBe('api/[endpoint].js');
|
expect(builders[0].src).toBe('api/[endpoint].js');
|
||||||
expect(builders[1].use).toBe('@now/static');
|
expect(builders[1].use).toBe('@now/static');
|
||||||
expect(builders[1].src).toBe('index.html');
|
expect(builders[1].src).toBe('!{api/**,package.json}');
|
||||||
expect(builders[2].use).toBe('@now/static');
|
expect(builders.length).toBe(2);
|
||||||
expect(builders[2].src).toBe('static/image.png');
|
|
||||||
expect(builders.length).toBe(3);
|
|
||||||
expect(errors).toBe(null);
|
expect(errors).toBe(null);
|
||||||
}
|
});
|
||||||
|
|
||||||
{
|
it('api + ignore files', async () => {
|
||||||
// api + ignore files
|
|
||||||
const files = [
|
const files = [
|
||||||
'api/_utils/handler.js',
|
'api/_utils/handler.js',
|
||||||
'api/[endpoint]/.helper.js',
|
'api/[endpoint]/.helper.js',
|
||||||
@@ -293,10 +211,9 @@ it('Test `detectBuilders`', async () => {
|
|||||||
expect(builders[0].use).toBe('@now/node');
|
expect(builders[0].use).toBe('@now/node');
|
||||||
expect(builders[0].src).toBe('api/[endpoint]/[id].js');
|
expect(builders[0].src).toBe('api/[endpoint]/[id].js');
|
||||||
expect(builders.length).toBe(1);
|
expect(builders.length).toBe(1);
|
||||||
}
|
});
|
||||||
|
|
||||||
{
|
it('api + next + public', async () => {
|
||||||
// api + next + public
|
|
||||||
const pkg = {
|
const pkg = {
|
||||||
scripts: { build: 'next build' },
|
scripts: { build: 'next build' },
|
||||||
devDependencies: { next: '9.0.0' },
|
devDependencies: { next: '9.0.0' },
|
||||||
@@ -309,10 +226,9 @@ it('Test `detectBuilders`', async () => {
|
|||||||
expect(builders[1].use).toBe('@now/next');
|
expect(builders[1].use).toBe('@now/next');
|
||||||
expect(builders[1].src).toBe('package.json');
|
expect(builders[1].src).toBe('package.json');
|
||||||
expect(builders.length).toBe(2);
|
expect(builders.length).toBe(2);
|
||||||
}
|
});
|
||||||
|
|
||||||
{
|
it('api + next + raw static', async () => {
|
||||||
// api + next + raw static
|
|
||||||
const pkg = {
|
const pkg = {
|
||||||
scripts: { build: 'next build' },
|
scripts: { build: 'next build' },
|
||||||
devDependencies: { next: '9.0.0' },
|
devDependencies: { next: '9.0.0' },
|
||||||
@@ -325,24 +241,20 @@ it('Test `detectBuilders`', async () => {
|
|||||||
expect(builders[1].use).toBe('@now/next');
|
expect(builders[1].use).toBe('@now/next');
|
||||||
expect(builders[1].src).toBe('package.json');
|
expect(builders[1].src).toBe('package.json');
|
||||||
expect(builders.length).toBe(2);
|
expect(builders.length).toBe(2);
|
||||||
}
|
});
|
||||||
|
|
||||||
{
|
it('api + raw static', async () => {
|
||||||
// api + raw static
|
|
||||||
const files = ['api/endpoint.js', 'index.html', 'favicon.ico'];
|
const files = ['api/endpoint.js', 'index.html', 'favicon.ico'];
|
||||||
|
|
||||||
const { builders } = await detectBuilders(files);
|
const { builders } = await detectBuilders(files);
|
||||||
expect(builders[0].use).toBe('@now/node');
|
expect(builders[0].use).toBe('@now/node');
|
||||||
expect(builders[0].src).toBe('api/endpoint.js');
|
expect(builders[0].src).toBe('api/endpoint.js');
|
||||||
expect(builders[1].use).toBe('@now/static');
|
expect(builders[1].use).toBe('@now/static');
|
||||||
expect(builders[1].src).toBe('favicon.ico');
|
expect(builders[1].src).toBe('!{api/**,package.json}');
|
||||||
expect(builders[2].use).toBe('@now/static');
|
expect(builders.length).toBe(2);
|
||||||
expect(builders[2].src).toBe('index.html');
|
});
|
||||||
expect(builders.length).toBe(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
it('api + public', async () => {
|
||||||
// api + public
|
|
||||||
const files = [
|
const files = [
|
||||||
'api/endpoint.js',
|
'api/endpoint.js',
|
||||||
'public/index.html',
|
'public/index.html',
|
||||||
@@ -356,19 +268,17 @@ it('Test `detectBuilders`', async () => {
|
|||||||
expect(builders[1].use).toBe('@now/static');
|
expect(builders[1].use).toBe('@now/static');
|
||||||
expect(builders[1].src).toBe('public/**/*');
|
expect(builders[1].src).toBe('public/**/*');
|
||||||
expect(builders.length).toBe(2);
|
expect(builders.length).toBe(2);
|
||||||
}
|
});
|
||||||
|
|
||||||
{
|
it('just public', async () => {
|
||||||
// just public
|
|
||||||
const files = ['public/index.html', 'public/favicon.ico', 'README.md'];
|
const files = ['public/index.html', 'public/favicon.ico', 'README.md'];
|
||||||
|
|
||||||
const { builders } = await detectBuilders(files);
|
const { builders } = await detectBuilders(files);
|
||||||
expect(builders[0].src).toBe('public/**/*');
|
expect(builders[0].src).toBe('public/**/*');
|
||||||
expect(builders.length).toBe(1);
|
expect(builders.length).toBe(1);
|
||||||
}
|
});
|
||||||
|
|
||||||
{
|
it('next + public', async () => {
|
||||||
// next + public
|
|
||||||
const pkg = {
|
const pkg = {
|
||||||
scripts: { build: 'next build' },
|
scripts: { build: 'next build' },
|
||||||
devDependencies: { next: '9.0.0' },
|
devDependencies: { next: '9.0.0' },
|
||||||
@@ -379,10 +289,9 @@ it('Test `detectBuilders`', async () => {
|
|||||||
expect(builders[0].use).toBe('@now/next');
|
expect(builders[0].use).toBe('@now/next');
|
||||||
expect(builders[0].src).toBe('package.json');
|
expect(builders[0].src).toBe('package.json');
|
||||||
expect(builders.length).toBe(1);
|
expect(builders.length).toBe(1);
|
||||||
}
|
});
|
||||||
|
|
||||||
{
|
it('nuxt', async () => {
|
||||||
// nuxt
|
|
||||||
const pkg = {
|
const pkg = {
|
||||||
scripts: { build: 'nuxt build' },
|
scripts: { build: 'nuxt build' },
|
||||||
dependencies: { nuxt: '2.8.1' },
|
dependencies: { nuxt: '2.8.1' },
|
||||||
@@ -393,10 +302,9 @@ it('Test `detectBuilders`', async () => {
|
|||||||
expect(builders[0].use).toBe('@now/static-build');
|
expect(builders[0].use).toBe('@now/static-build');
|
||||||
expect(builders[0].src).toBe('package.json');
|
expect(builders[0].src).toBe('package.json');
|
||||||
expect(builders.length).toBe(1);
|
expect(builders.length).toBe(1);
|
||||||
}
|
});
|
||||||
|
|
||||||
{
|
it('package.json with no build + api', async () => {
|
||||||
// package.json with no build + api
|
|
||||||
const pkg = { dependencies: { next: '9.0.0' } };
|
const pkg = { dependencies: { next: '9.0.0' } };
|
||||||
const files = ['package.json', 'api/[endpoint].js'];
|
const files = ['package.json', 'api/[endpoint].js'];
|
||||||
|
|
||||||
@@ -404,37 +312,33 @@ it('Test `detectBuilders`', async () => {
|
|||||||
expect(builders[0].use).toBe('@now/node');
|
expect(builders[0].use).toBe('@now/node');
|
||||||
expect(builders[0].src).toBe('api/[endpoint].js');
|
expect(builders[0].src).toBe('api/[endpoint].js');
|
||||||
expect(builders.length).toBe(1);
|
expect(builders.length).toBe(1);
|
||||||
}
|
});
|
||||||
|
|
||||||
{
|
it('package.json with no build + public directory', async () => {
|
||||||
// package.json with no build + public directory
|
|
||||||
const pkg = { dependencies: { next: '9.0.0' } };
|
const pkg = { dependencies: { next: '9.0.0' } };
|
||||||
const files = ['package.json', 'public/index.html'];
|
const files = ['package.json', 'public/index.html'];
|
||||||
|
|
||||||
const { builders, errors } = await detectBuilders(files, pkg);
|
const { builders, errors } = await detectBuilders(files, pkg);
|
||||||
expect(builders).toBe(null);
|
expect(builders).toBe(null);
|
||||||
expect(errors.length).toBe(1);
|
expect(errors.length).toBe(1);
|
||||||
}
|
});
|
||||||
|
|
||||||
{
|
it('no package.json + api', async () => {
|
||||||
// no package.json + api
|
|
||||||
const files = ['api/[endpoint].js', 'api/[endpoint]/[id].js'];
|
const files = ['api/[endpoint].js', 'api/[endpoint]/[id].js'];
|
||||||
|
|
||||||
const { builders } = await detectBuilders(files);
|
const { builders } = await detectBuilders(files);
|
||||||
expect(builders.length).toBe(2);
|
expect(builders.length).toBe(2);
|
||||||
}
|
});
|
||||||
|
|
||||||
{
|
it('no package.json + no api', async () => {
|
||||||
// no package.json + no api
|
|
||||||
const files = ['index.html'];
|
const files = ['index.html'];
|
||||||
|
|
||||||
const { builders, errors } = await detectBuilders(files);
|
const { builders, errors } = await detectBuilders(files);
|
||||||
expect(builders).toBe(null);
|
expect(builders).toBe(null);
|
||||||
expect(errors).toBe(null);
|
expect(errors).toBe(null);
|
||||||
}
|
});
|
||||||
|
|
||||||
{
|
it('package.json + api + canary', async () => {
|
||||||
// package.json + api + canary
|
|
||||||
const pkg = {
|
const pkg = {
|
||||||
scripts: { build: 'next build' },
|
scripts: { build: 'next build' },
|
||||||
dependencies: { next: '9.0.0' },
|
dependencies: { next: '9.0.0' },
|
||||||
@@ -450,10 +354,9 @@ it('Test `detectBuilders`', async () => {
|
|||||||
expect(builders[1].use).toBe('@now/node@canary');
|
expect(builders[1].use).toBe('@now/node@canary');
|
||||||
expect(builders[2].use).toBe('@now/next@canary');
|
expect(builders[2].use).toBe('@now/next@canary');
|
||||||
expect(builders.length).toBe(3);
|
expect(builders.length).toBe(3);
|
||||||
}
|
});
|
||||||
|
|
||||||
{
|
it('package.json + api + latest', async () => {
|
||||||
// package.json + api + latest
|
|
||||||
const pkg = {
|
const pkg = {
|
||||||
scripts: { build: 'next build' },
|
scripts: { build: 'next build' },
|
||||||
dependencies: { next: '9.0.0' },
|
dependencies: { next: '9.0.0' },
|
||||||
@@ -469,10 +372,9 @@ it('Test `detectBuilders`', async () => {
|
|||||||
expect(builders[1].use).toBe('@now/node@latest');
|
expect(builders[1].use).toBe('@now/node@latest');
|
||||||
expect(builders[2].use).toBe('@now/next@latest');
|
expect(builders[2].use).toBe('@now/next@latest');
|
||||||
expect(builders.length).toBe(3);
|
expect(builders.length).toBe(3);
|
||||||
}
|
});
|
||||||
|
|
||||||
{
|
it('package.json + api + random tag', async () => {
|
||||||
// package.json + api + random tag
|
|
||||||
const pkg = {
|
const pkg = {
|
||||||
scripts: { build: 'next build' },
|
scripts: { build: 'next build' },
|
||||||
dependencies: { next: '9.0.0' },
|
dependencies: { next: '9.0.0' },
|
||||||
@@ -488,7 +390,329 @@ it('Test `detectBuilders`', async () => {
|
|||||||
expect(builders[1].use).toBe('@now/node@haha');
|
expect(builders[1].use).toBe('@now/node@haha');
|
||||||
expect(builders[2].use).toBe('@now/next@haha');
|
expect(builders[2].use).toBe('@now/next@haha');
|
||||||
expect(builders.length).toBe(3);
|
expect(builders.length).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('next.js pages/api + api', async () => {
|
||||||
|
const pkg = {
|
||||||
|
scripts: { build: 'next build' },
|
||||||
|
dependencies: { next: '9.0.0' },
|
||||||
|
};
|
||||||
|
const files = ['api/user.js', 'pages/api/user.js'];
|
||||||
|
|
||||||
|
const { warnings, errors, builders } = await detectBuilders(files, pkg);
|
||||||
|
|
||||||
|
expect(errors).toBe(null);
|
||||||
|
expect(warnings[0]).toBeDefined();
|
||||||
|
expect(warnings[0].code).toBe('conflicting_files');
|
||||||
|
expect(builders).toBeDefined();
|
||||||
|
expect(builders.length).toBe(2);
|
||||||
|
expect(builders[0].use).toBe('@now/node');
|
||||||
|
expect(builders[1].use).toBe('@now/next');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('many static files + one api file', async () => {
|
||||||
|
const files = Array.from({ length: 5000 }).map((_, i) => `file${i}.html`);
|
||||||
|
files.push('api/index.ts');
|
||||||
|
const { builders } = await detectBuilders(files);
|
||||||
|
|
||||||
|
expect(builders.length).toBe(2);
|
||||||
|
expect(builders[0].use).toBe('@now/node');
|
||||||
|
expect(builders[0].src).toBe('api/index.ts');
|
||||||
|
expect(builders[1].use).toBe('@now/static');
|
||||||
|
expect(builders[1].src).toBe('!{api/**,package.json}');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('functions with nextjs', async () => {
|
||||||
|
const pkg = {
|
||||||
|
scripts: { build: 'next build' },
|
||||||
|
dependencies: { next: '9.0.0' },
|
||||||
|
};
|
||||||
|
const functions = {
|
||||||
|
'pages/api/teams/**': {
|
||||||
|
memory: 128,
|
||||||
|
maxDuration: 10,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const files = [
|
||||||
|
'package.json',
|
||||||
|
'pages/index.js',
|
||||||
|
'pages/api/teams/members.ts',
|
||||||
|
];
|
||||||
|
const { builders, errors } = await detectBuilders(files, pkg, {
|
||||||
|
functions,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(errors).toBe(null);
|
||||||
|
expect(builders.length).toBe(1);
|
||||||
|
expect(builders[0]).toEqual({
|
||||||
|
src: 'package.json',
|
||||||
|
use: '@now/next',
|
||||||
|
config: {
|
||||||
|
zeroConfig: true,
|
||||||
|
functions: {
|
||||||
|
'pages/api/teams/**': {
|
||||||
|
memory: 128,
|
||||||
|
maxDuration: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('extend with functions', async () => {
|
||||||
|
const pkg = {
|
||||||
|
scripts: { build: 'next build' },
|
||||||
|
dependencies: { next: '9.0.0' },
|
||||||
|
};
|
||||||
|
const functions = {
|
||||||
|
'api/users/*.ts': {
|
||||||
|
runtime: 'my-custom-runtime-package@1.0.0',
|
||||||
|
},
|
||||||
|
'api/teams/members.ts': {
|
||||||
|
memory: 128,
|
||||||
|
maxDuration: 10,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const files = [
|
||||||
|
'package.json',
|
||||||
|
'pages/index.js',
|
||||||
|
'api/users/[id].ts',
|
||||||
|
'api/teams/members.ts',
|
||||||
|
];
|
||||||
|
const { builders } = await detectBuilders(files, pkg, { functions });
|
||||||
|
|
||||||
|
expect(builders.length).toBe(3);
|
||||||
|
expect(builders[0]).toEqual({
|
||||||
|
src: 'api/teams/members.ts',
|
||||||
|
use: '@now/node',
|
||||||
|
config: {
|
||||||
|
zeroConfig: true,
|
||||||
|
functions: {
|
||||||
|
'api/teams/members.ts': {
|
||||||
|
memory: 128,
|
||||||
|
maxDuration: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(builders[1]).toEqual({
|
||||||
|
src: 'api/users/[id].ts',
|
||||||
|
use: 'my-custom-runtime-package@1.0.0',
|
||||||
|
config: {
|
||||||
|
zeroConfig: true,
|
||||||
|
functions: {
|
||||||
|
'api/users/*.ts': {
|
||||||
|
runtime: 'my-custom-runtime-package@1.0.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(builders[2]).toEqual({
|
||||||
|
src: 'package.json',
|
||||||
|
use: '@now/next',
|
||||||
|
config: {
|
||||||
|
zeroConfig: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invalid function key', async () => {
|
||||||
|
const functions = { ['a'.repeat(1000)]: { memory: 128 } };
|
||||||
|
const files = ['pages/index.ts'];
|
||||||
|
const { builders, errors } = await detectBuilders(files, null, {
|
||||||
|
functions,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(builders).toBe(null);
|
||||||
|
expect(errors.length).toBe(1);
|
||||||
|
expect(errors[0].code).toBe('invalid_function_glob');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invalid function maxDuration', async () => {
|
||||||
|
const functions = { 'pages/index.ts': { maxDuration: -1 } };
|
||||||
|
const files = ['pages/index.ts'];
|
||||||
|
const { builders, errors } = await detectBuilders(files, null, {
|
||||||
|
functions,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(builders).toBe(null);
|
||||||
|
expect(errors.length).toBe(1);
|
||||||
|
expect(errors[0].code).toBe('invalid_function_duration');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invalid function memory', async () => {
|
||||||
|
const functions = { 'pages/index.ts': { memory: 200 } };
|
||||||
|
const files = ['pages/index.ts'];
|
||||||
|
const { builders, errors } = await detectBuilders(files, null, {
|
||||||
|
functions,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(builders).toBe(null);
|
||||||
|
expect(errors.length).toBe(1);
|
||||||
|
expect(errors[0].code).toBe('invalid_function_memory');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('missing runtime version', async () => {
|
||||||
|
const functions = { 'pages/index.ts': { runtime: 'haha' } };
|
||||||
|
const files = ['pages/index.ts'];
|
||||||
|
const { builders, errors } = await detectBuilders(files, null, {
|
||||||
|
functions,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(builders).toBe(null);
|
||||||
|
expect(errors.length).toBe(1);
|
||||||
|
expect(errors[0].code).toBe('invalid_function_runtime');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('use a custom runtime', async () => {
|
||||||
|
const functions = { 'api/user.php': { runtime: 'now-php@0.0.5' } };
|
||||||
|
const files = ['api/user.php'];
|
||||||
|
const { builders, errors } = await detectBuilders(files, null, {
|
||||||
|
functions,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(errors).toBe(null);
|
||||||
|
expect(builders.length).toBe(1);
|
||||||
|
expect(builders[0].use).toBe('now-php@0.0.5');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('use a custom runtime but without a source', async () => {
|
||||||
|
const functions = { 'api/user.php': { runtime: 'now-php@0.0.5' } };
|
||||||
|
const files = ['api/team.js'];
|
||||||
|
const { errors } = await detectBuilders(files, null, {
|
||||||
|
functions,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(errors.length).toBe(1);
|
||||||
|
expect(errors[0].code).toBe('invalid_function_source');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('do not allow empty functions', async () => {
|
||||||
|
const functions = { 'api/user.php': {} };
|
||||||
|
const files = ['api/user.php'];
|
||||||
|
const { errors } = await detectBuilders(files, null, {
|
||||||
|
functions,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(errors.length).toBe(1);
|
||||||
|
expect(errors[0].code).toBe('invalid_function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('do not allow null functions', async () => {
|
||||||
|
const functions = { 'api/user.php': null };
|
||||||
|
const files = ['api/user.php'];
|
||||||
|
const { errors } = await detectBuilders(files, null, {
|
||||||
|
functions,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(errors.length).toBe(1);
|
||||||
|
expect(errors[0].code).toBe('invalid_function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Do not allow functions that are not used by @now/next', async () => {
|
||||||
|
const pkg = {
|
||||||
|
scripts: { build: 'next build' },
|
||||||
|
dependencies: { next: '9.0.0' },
|
||||||
|
};
|
||||||
|
const functions = { 'test.js': { memory: 1024 } };
|
||||||
|
const files = ['pages/index.js', 'test.js'];
|
||||||
|
|
||||||
|
const { errors } = await detectBuilders(files, pkg, { functions });
|
||||||
|
|
||||||
|
expect(errors).toBeDefined();
|
||||||
|
expect(errors[0].code).toBe('unused_function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Do not allow function non Community Runtimes', async () => {
|
||||||
|
const functions = {
|
||||||
|
'api/test.js': { memory: 128, runtime: '@now/node@1.0.0' },
|
||||||
|
};
|
||||||
|
const files = ['api/test.js'];
|
||||||
|
|
||||||
|
const { errors } = await detectBuilders(files, null, { functions });
|
||||||
|
|
||||||
|
expect(errors).toBeDefined();
|
||||||
|
expect(errors[0].code).toBe('invalid_function_runtime');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Must include includeFiles config property', async () => {
|
||||||
|
const functions = {
|
||||||
|
'api/test.js': { includeFiles: 'text/include.txt' }
|
||||||
}
|
}
|
||||||
|
const files = ['api/test.js'];
|
||||||
|
|
||||||
|
const { builders, errors } = await detectBuilders(files, null, { functions });
|
||||||
|
|
||||||
|
expect(errors).toBe(null);
|
||||||
|
expect(builders).not.toBe(null);
|
||||||
|
expect(builders[0].use).toBe('@now/node');
|
||||||
|
expect(builders[0].config).toMatchObject({
|
||||||
|
functions,
|
||||||
|
zeroConfig: true,
|
||||||
|
includeFiles: 'text/include.txt'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Must include excludeFiles config property', async () => {
|
||||||
|
const functions = {
|
||||||
|
'api/test.js': { excludeFiles: 'text/exclude.txt' }
|
||||||
|
}
|
||||||
|
const files = ['api/test.js'];
|
||||||
|
|
||||||
|
const { builders, errors } = await detectBuilders(files, null, { functions });
|
||||||
|
|
||||||
|
expect(errors).toBe(null);
|
||||||
|
expect(builders).not.toBe(null);
|
||||||
|
expect(builders[0].use).toBe('@now/node');
|
||||||
|
expect(builders[0].config).toMatchObject({
|
||||||
|
functions,
|
||||||
|
zeroConfig: true,
|
||||||
|
excludeFiles: 'text/exclude.txt'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Must include excludeFiles and includeFiles config property', async () => {
|
||||||
|
const functions = {
|
||||||
|
'api/test.js': { excludeFiles: 'text/exclude.txt', includeFiles: 'text/include.txt' }
|
||||||
|
}
|
||||||
|
const files = ['api/test.js'];
|
||||||
|
|
||||||
|
const { builders, errors } = await detectBuilders(files, null, { functions });
|
||||||
|
|
||||||
|
expect(errors).toBe(null);
|
||||||
|
expect(builders).not.toBe(null);
|
||||||
|
expect(builders[0].use).toBe('@now/node');
|
||||||
|
expect(builders[0].config).toMatchObject({
|
||||||
|
functions,
|
||||||
|
zeroConfig: true,
|
||||||
|
excludeFiles: 'text/exclude.txt',
|
||||||
|
includeFiles: 'text/include.txt'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Must fail for includeFiles config property', async () => {
|
||||||
|
const functions = {
|
||||||
|
'api/test.js': { includeFiles: { test: 1 } }
|
||||||
|
}
|
||||||
|
const files = ['api/test.js'];
|
||||||
|
|
||||||
|
const { errors } = await detectBuilders(files, null, { functions });
|
||||||
|
|
||||||
|
expect(errors).not.toBe(null);
|
||||||
|
expect(errors[0].code).toBe('invalid_function_property');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Must fail for excludeFiles config property', async () => {
|
||||||
|
const functions = {
|
||||||
|
'api/test.js': { excludeFiles: { test: 1 } }
|
||||||
|
}
|
||||||
|
const files = ['api/test.js'];
|
||||||
|
|
||||||
|
const { errors } = await detectBuilders(files, null, { functions });
|
||||||
|
|
||||||
|
expect(errors).not.toBe(null);
|
||||||
|
expect(errors[0].code).toBe('invalid_function_property');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Test `detectRoutes`', async () => {
|
it('Test `detectRoutes`', async () => {
|
||||||
@@ -592,10 +816,10 @@ it('Test `detectRoutes`', async () => {
|
|||||||
|
|
||||||
expect(defaultRoutes.length).toBe(3);
|
expect(defaultRoutes.length).toBe(3);
|
||||||
expect(defaultRoutes[0].src).toBe(
|
expect(defaultRoutes[0].src).toBe(
|
||||||
'^/api/date(\\/|\\/index|\\/index\\.js)?$',
|
'^/api/date(\\/|\\/index|\\/index\\.js)?$'
|
||||||
);
|
);
|
||||||
expect(defaultRoutes[0].dest).toBe('/api/date/index.js');
|
expect(defaultRoutes[0].dest).toBe('/api/date/index.js');
|
||||||
expect(defaultRoutes[1].src).toBe('^/api/(date|date\\.js)$');
|
expect(defaultRoutes[1].src).toBe('^/api/(date\\/|date|date\\.js)$');
|
||||||
expect(defaultRoutes[1].dest).toBe('/api/date.js');
|
expect(defaultRoutes[1].dest).toBe('/api/date.js');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -607,10 +831,10 @@ it('Test `detectRoutes`', async () => {
|
|||||||
|
|
||||||
expect(defaultRoutes.length).toBe(3);
|
expect(defaultRoutes.length).toBe(3);
|
||||||
expect(defaultRoutes[0].src).toBe(
|
expect(defaultRoutes[0].src).toBe(
|
||||||
'^/api/([^\\/]+)(\\/|\\/index|\\/index\\.js)?$',
|
'^/api/([^\\/]+)(\\/|\\/index|\\/index\\.js)?$'
|
||||||
);
|
);
|
||||||
expect(defaultRoutes[0].dest).toBe('/api/[date]/index.js?date=$1');
|
expect(defaultRoutes[0].dest).toBe('/api/[date]/index.js?date=$1');
|
||||||
expect(defaultRoutes[1].src).toBe('^/api/(date|date\\.js)$');
|
expect(defaultRoutes[1].src).toBe('^/api/(date\\/|date|date\\.js)$');
|
||||||
expect(defaultRoutes[1].dest).toBe('/api/date.js');
|
expect(defaultRoutes[1].dest).toBe('/api/date.js');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -633,147 +857,16 @@ it('Test `detectRoutes`', async () => {
|
|||||||
expect(builders[3].use).toBe('@now/node');
|
expect(builders[3].use).toBe('@now/node');
|
||||||
expect(defaultRoutes.length).toBe(5);
|
expect(defaultRoutes.length).toBe(5);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
it('Test `detectBuilders` and `detectRoutes`', async () => {
|
{
|
||||||
const fixture = path.join(__dirname, 'fixtures', '01-zero-config-api');
|
// use a custom runtime
|
||||||
const pkg = await fs.readJSON(path.join(fixture, 'package.json'));
|
const functions = { 'api/user.php': { runtime: 'now-php@0.0.5' } };
|
||||||
const fileList = await glob('**', fixture);
|
const files = ['api/user.php'];
|
||||||
const files = Object.keys(fileList);
|
|
||||||
|
|
||||||
const probes = [
|
const { builders } = await detectBuilders(files, null, { functions });
|
||||||
{
|
|
||||||
path: '/api/my-endpoint',
|
|
||||||
mustContain: 'my-endpoint',
|
|
||||||
status: 200,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/api/other-endpoint',
|
|
||||||
mustContain: 'other-endpoint',
|
|
||||||
status: 200,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/api/team/zeit',
|
|
||||||
mustContain: 'team/zeit',
|
|
||||||
status: 200,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/api/user/myself',
|
|
||||||
mustContain: 'user/myself',
|
|
||||||
status: 200,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/api/not-okay/',
|
|
||||||
status: 404,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/api',
|
|
||||||
status: 404,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/api/',
|
|
||||||
status: 404,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
mustContain: 'hello from index.txt',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const { builders } = await detectBuilders(files, pkg);
|
|
||||||
const { defaultRoutes } = await detectRoutes(files, builders);
|
const { defaultRoutes } = await detectRoutes(files, builders);
|
||||||
|
|
||||||
const nowConfig = { builds: builders, routes: defaultRoutes, probes };
|
expect(defaultRoutes.length).toBe(2);
|
||||||
await fs.writeFile(
|
expect(defaultRoutes[0].dest).toBe('/api/user.php');
|
||||||
path.join(fixture, 'now.json'),
|
}
|
||||||
JSON.stringify(nowConfig, null, 2),
|
|
||||||
);
|
|
||||||
|
|
||||||
const deployment = await testDeployment(
|
|
||||||
{ builderUrl, buildUtilsUrl },
|
|
||||||
fixture,
|
|
||||||
);
|
|
||||||
expect(deployment).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Test `detectBuilders` and `detectRoutes` with `index` files', async () => {
|
|
||||||
const fixture = path.join(__dirname, 'fixtures', '02-zero-config-api');
|
|
||||||
const pkg = await fs.readJSON(path.join(fixture, 'package.json'));
|
|
||||||
const fileList = await glob('**', fixture);
|
|
||||||
const files = Object.keys(fileList);
|
|
||||||
|
|
||||||
const probes = [
|
|
||||||
{
|
|
||||||
path: '/api/not-okay',
|
|
||||||
status: 404,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/api',
|
|
||||||
mustContain: 'hello from api/index.js',
|
|
||||||
status: 200,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/api/',
|
|
||||||
mustContain: 'hello from api/index.js',
|
|
||||||
status: 200,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/api/index',
|
|
||||||
mustContain: 'hello from api/index.js',
|
|
||||||
status: 200,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/api/index.js',
|
|
||||||
mustContain: 'hello from api/index.js',
|
|
||||||
status: 200,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/api/date.js',
|
|
||||||
mustContain: 'hello from api/date.js',
|
|
||||||
status: 200,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Someone might expect this to be `date.js`,
|
|
||||||
// but I doubt that there is any case were both
|
|
||||||
// `date/index.js` and `date.js` exists,
|
|
||||||
// so it is not special cased
|
|
||||||
path: '/api/date',
|
|
||||||
mustContain: 'hello from api/date/index.js',
|
|
||||||
status: 200,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/api/date/',
|
|
||||||
mustContain: 'hello from api/date/index.js',
|
|
||||||
status: 200,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/api/date/index',
|
|
||||||
mustContain: 'hello from api/date/index.js',
|
|
||||||
status: 200,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/api/date/index.js',
|
|
||||||
mustContain: 'hello from api/date/index.js',
|
|
||||||
status: 200,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
mustContain: 'hello from index.txt',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const { builders } = await detectBuilders(files, pkg);
|
|
||||||
const { defaultRoutes } = await detectRoutes(files, builders);
|
|
||||||
|
|
||||||
const nowConfig = { builds: builders, routes: defaultRoutes, probes };
|
|
||||||
await fs.writeFile(
|
|
||||||
path.join(fixture, 'now.json'),
|
|
||||||
JSON.stringify(nowConfig, null, 2),
|
|
||||||
);
|
|
||||||
|
|
||||||
const deployment = await testDeployment(
|
|
||||||
{ builderUrl, buildUtilsUrl },
|
|
||||||
fixture,
|
|
||||||
);
|
|
||||||
expect(deployment).toBeDefined();
|
|
||||||
});
|
});
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
indent_style = tab
|
|
||||||
indent_size = 4
|
|
||||||
tab_width = 4
|
|
||||||
end_of_line = lf
|
|
||||||
charset = utf-8
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
insert_final_newline = true
|
|
||||||
|
|
||||||
[{*.json,*.json.example,*.gyp,*.yml}]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 2
|
|
||||||
|
|
||||||
[*.py]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 4
|
|
||||||
|
|
||||||
[*.md]
|
|
||||||
trim_trailing_whitespace = false
|
|
||||||
|
|
||||||
# Ideal settings - some plugins might support these.
|
|
||||||
[*.js]
|
|
||||||
quote_type = single
|
|
||||||
|
|
||||||
[{*.c,*.cc,*.h,*.hh,*.cpp,*.hpp,*.m,*.mm,*.mpp,*.js,*.java,*.go,*.rs,*.php,*.ng,*.jsx,*.ts,*.d,*.cs,*.swift}]
|
|
||||||
curly_bracket_next_line = false
|
|
||||||
spaces_around_operators = true
|
|
||||||
spaces_around_brackets = outside
|
|
||||||
# close enough to 1TB
|
|
||||||
indent_brace_style = K&R
|
|
||||||
1
packages/now-cgi/.gitignore
vendored
1
packages/now-cgi/.gitignore
vendored
@@ -1,2 +1 @@
|
|||||||
node_modules
|
|
||||||
handler
|
handler
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { mkdirp, copyFile } = require('fs-extra');
|
const { mkdirp, copyFile } = require('fs-extra');
|
||||||
|
|
||||||
const glob = require('@now/build-utils/fs/glob'); // eslint-disable-line import/no-extraneous-dependencies
|
const {
|
||||||
const download = require('@now/build-utils/fs/download'); // eslint-disable-line import/no-extraneous-dependencies
|
glob,
|
||||||
const { createLambda } = require('@now/build-utils/lambda'); // eslint-disable-line import/no-extraneous-dependencies
|
download,
|
||||||
const getWritableDirectory = require('@now/build-utils/fs/get-writable-directory'); // eslint-disable-line import/no-extraneous-dependencies
|
shouldServe,
|
||||||
const { shouldServe } = require('@now/build-utils'); // eslint-disable-line import/no-extraneous-dependencies
|
createLambda,
|
||||||
|
getWritableDirectory,
|
||||||
|
} = require('@now/build-utils');
|
||||||
|
|
||||||
exports.analyze = ({ files, entrypoint }) => files[entrypoint].digest;
|
exports.analyze = ({ files, entrypoint }) => files[entrypoint].digest;
|
||||||
|
|
||||||
exports.build = async ({
|
exports.version = 3;
|
||||||
workPath, files, entrypoint, meta,
|
|
||||||
}) => {
|
exports.build = async ({ workPath, files, entrypoint, meta, config }) => {
|
||||||
console.log('downloading files...');
|
console.log('downloading files...');
|
||||||
const outDir = await getWritableDirectory();
|
const outDir = await getWritableDirectory();
|
||||||
|
|
||||||
@@ -26,7 +28,7 @@ exports.build = async ({
|
|||||||
// For now only the entrypoint file is copied into the lambda
|
// For now only the entrypoint file is copied into the lambda
|
||||||
await copyFile(
|
await copyFile(
|
||||||
path.join(workPath, entrypoint),
|
path.join(workPath, entrypoint),
|
||||||
path.join(outDir, entrypoint),
|
path.join(outDir, entrypoint)
|
||||||
);
|
);
|
||||||
|
|
||||||
const lambda = await createLambda({
|
const lambda = await createLambda({
|
||||||
@@ -38,9 +40,7 @@ exports.build = async ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return { output: lambda };
|
||||||
[entrypoint]: lambda,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.shouldServe = shouldServe;
|
exports.shouldServe = shouldServe;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@now/cgi",
|
"name": "@now/cgi",
|
||||||
"version": "0.1.5",
|
"version": "1.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
@types
|
|
||||||
download
|
|
||||||
dist
|
|
||||||
test/fixtures
|
|
||||||
test/dev/fixtures
|
|
||||||
bin
|
|
||||||
link
|
|
||||||
src/util/dev/templates/*.ts
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
'extends': [
|
|
||||||
'airbnb',
|
|
||||||
'prettier'
|
|
||||||
],
|
|
||||||
'parser': '@typescript-eslint/parser',
|
|
||||||
'parserOptions': {
|
|
||||||
'ecmaVersion': 2018,
|
|
||||||
'sourceType': 'module',
|
|
||||||
'modules': true
|
|
||||||
},
|
|
||||||
'plugins': [
|
|
||||||
'@typescript-eslint'
|
|
||||||
],
|
|
||||||
'settings': {
|
|
||||||
'import/resolver': {
|
|
||||||
'typescript': {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'rules': {
|
|
||||||
'quotes': [
|
|
||||||
2,
|
|
||||||
'single',
|
|
||||||
{
|
|
||||||
'allowTemplateLiterals': true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'class-methods-use-this': 0,
|
|
||||||
'consistent-return': 0,
|
|
||||||
'func-names': 0,
|
|
||||||
'global-require': 0,
|
|
||||||
'guard-for-in': 0,
|
|
||||||
'import/no-duplicates': 0,
|
|
||||||
'import/no-dynamic-require': 0,
|
|
||||||
'import/no-extraneous-dependencies': 0,
|
|
||||||
'import/prefer-default-export': 0,
|
|
||||||
'lines-between-class-members': 0,
|
|
||||||
'no-await-in-loop': 0,
|
|
||||||
'no-bitwise': 0,
|
|
||||||
'no-console': 0,
|
|
||||||
'no-continue': 0,
|
|
||||||
'no-control-regex': 0,
|
|
||||||
'no-empty': 0,
|
|
||||||
'no-loop-func': 0,
|
|
||||||
'no-nested-ternary': 0,
|
|
||||||
'no-param-reassign': 0,
|
|
||||||
'no-plusplus': 0,
|
|
||||||
'no-restricted-globals': 0,
|
|
||||||
'no-restricted-syntax': 0,
|
|
||||||
'no-shadow': 0,
|
|
||||||
'no-underscore-dangle': 0,
|
|
||||||
'no-use-before-define': 0,
|
|
||||||
'prefer-const': 0,
|
|
||||||
'prefer-destructuring': 0,
|
|
||||||
'camelcase': 0,
|
|
||||||
'no-unused-vars': 0, // in favor of '@typescript-eslint/no-unused-vars'
|
|
||||||
// 'indent': 0 // in favor of '@typescript-eslint/indent'
|
|
||||||
'@typescript-eslint/no-unused-vars': 'warn',
|
|
||||||
// '@typescript-eslint/indent': ['error', 2] // this might conflict with a lot ongoing changes
|
|
||||||
'@typescript-eslint/no-array-constructor': 'error',
|
|
||||||
'@typescript-eslint/adjacent-overload-signatures': 'error',
|
|
||||||
'@typescript-eslint/class-name-casing': 'error',
|
|
||||||
'@typescript-eslint/interface-name-prefix': 'error',
|
|
||||||
'@typescript-eslint/no-empty-interface': 'error',
|
|
||||||
'@typescript-eslint/no-inferrable-types': 'error',
|
|
||||||
'@typescript-eslint/no-misused-new': 'error',
|
|
||||||
'@typescript-eslint/no-namespace': 'error',
|
|
||||||
'@typescript-eslint/no-non-null-assertion': 'error',
|
|
||||||
'@typescript-eslint/no-parameter-properties': 'error',
|
|
||||||
'@typescript-eslint/no-triple-slash-reference': 'error',
|
|
||||||
'@typescript-eslint/prefer-namespace-keyword': 'error',
|
|
||||||
'@typescript-eslint/type-annotation-spacing': 'error',
|
|
||||||
// '@typescript-eslint/array-type': 'error',
|
|
||||||
// '@typescript-eslint/ban-types': 'error',
|
|
||||||
// '@typescript-eslint/explicit-function-return-type': 'warn',
|
|
||||||
// '@typescript-eslint/explicit-member-accessibility': 'error',
|
|
||||||
// '@typescript-eslint/member-delimiter-style': 'error',
|
|
||||||
// '@typescript-eslint/no-angle-bracket-type-assertion': 'error',
|
|
||||||
// '@typescript-eslint/no-explicit-any': 'warn',
|
|
||||||
// '@typescript-eslint/no-object-literal-type-assertion': 'error',
|
|
||||||
// '@typescript-eslint/no-use-before-define': 'error',
|
|
||||||
// '@typescript-eslint/no-var-requires': 'error',
|
|
||||||
// '@typescript-eslint/prefer-interface': 'error'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
declare module 'cache-or-tmp-directory' {
|
|
||||||
export default function (appName: string) : string | null
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
declare module 'pcre-to-regexp' {
|
|
||||||
export default function (pattern: string, keys?: string[]): RegExp
|
|
||||||
}
|
|
||||||
@@ -2,19 +2,19 @@
|
|||||||
|
|
||||||
[](https://spectrum.chat/zeit)
|
[](https://spectrum.chat/zeit)
|
||||||
|
|
||||||
## Usage
|
## Usages
|
||||||
|
|
||||||
To install the latest version of Now CLI, visit [zeit.co/download](https://zeit.co/download) or run this command:
|
To install the latest version of Now CLI, visit [zeit.co/download](https://zeit.co/download) or run this command:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
npm i -g now
|
npm i -g now
|
||||||
```
|
```
|
||||||
|
|
||||||
To quickly start a new project, run the following commands:
|
To quickly start a new project, run the following commands:
|
||||||
|
|
||||||
```
|
```
|
||||||
now init # Pick an example project to clone
|
now init # Pick an example project
|
||||||
cd <PROJECT> # Change directory to the newly created project
|
cd <PROJECT> # Change directory to the new project
|
||||||
now dev # Run locally during development
|
now dev # Run locally during development
|
||||||
now # Deploy to the cloud
|
now # Deploy to the cloud
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "now",
|
"name": "now",
|
||||||
"version": "16.1.3",
|
"version": "16.5.0",
|
||||||
"preferGlobal": true,
|
"preferGlobal": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "The command-line interface for Now",
|
"description": "The command-line interface for Now",
|
||||||
@@ -11,17 +11,16 @@
|
|||||||
"directory": "packages/now-cli"
|
"directory": "packages/now-cli"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "yarn test-lint",
|
|
||||||
"preinstall": "node ./scripts/preinstall.js",
|
"preinstall": "node ./scripts/preinstall.js",
|
||||||
"test-unit": "nyc ava test/*unit.js --serial --fail-fast --verbose",
|
"test-unit": "nyc ava test/*unit.js --serial --fail-fast --verbose",
|
||||||
"test-integration": "ava test/integration.js --serial --fail-fast",
|
"test-integration": "ava test/integration.js --serial --fail-fast",
|
||||||
|
"test-integration-v1": "ava test/integration-v1.js --serial --fail-fast",
|
||||||
"test-integration-now-dev": "ava test/dev/integration.js --serial --fail-fast --verbose",
|
"test-integration-now-dev": "ava test/dev/integration.js --serial --fail-fast --verbose",
|
||||||
"test-lint": "eslint . --ext .js,.ts",
|
|
||||||
"prepublishOnly": "yarn build",
|
"prepublishOnly": "yarn build",
|
||||||
"coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov",
|
"coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov",
|
||||||
"build": "ts-node ./scripts/build.ts",
|
"build": "ts-node ./scripts/build.ts",
|
||||||
"build-dev": "ts-node ./scripts/build.ts --dev",
|
"build-dev": "ts-node ./scripts/build.ts --dev",
|
||||||
"format-modified": "prettier --parser typescript --write --single-quote `git diff --name-only HEAD * | grep -e \".*\\.ts$\" -e \".*\\.js$\" | xargs echo`"
|
"test-lint": "eslint . --ext .ts,.js --ignore-path ../../.eslintignore"
|
||||||
},
|
},
|
||||||
"nyc": {
|
"nyc": {
|
||||||
"include": [
|
"include": [
|
||||||
@@ -42,12 +41,6 @@
|
|||||||
"instrument": true,
|
"instrument": true,
|
||||||
"all": true
|
"all": true
|
||||||
},
|
},
|
||||||
"git": {
|
|
||||||
"pre-commit": [
|
|
||||||
"test-lint",
|
|
||||||
"format-modified"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"now": "./dist/index.js"
|
"now": "./dist/index.js"
|
||||||
},
|
},
|
||||||
@@ -66,16 +59,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 8.11"
|
"node": ">= 8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@now/build-utils": "0.9.14",
|
|
||||||
"@now/go": "latest",
|
|
||||||
"@now/next": "latest",
|
|
||||||
"@now/node": "latest",
|
|
||||||
"@now/php": "latest",
|
|
||||||
"@now/routing-utils": "1.2.3",
|
|
||||||
"@now/static-build": "latest",
|
|
||||||
"@sentry/node": "5.5.0",
|
"@sentry/node": "5.5.0",
|
||||||
"@types/ansi-escapes": "3.0.0",
|
"@types/ansi-escapes": "3.0.0",
|
||||||
"@types/ansi-regex": "4.0.0",
|
"@types/ansi-regex": "4.0.0",
|
||||||
@@ -84,7 +70,6 @@
|
|||||||
"@types/debug": "0.0.31",
|
"@types/debug": "0.0.31",
|
||||||
"@types/dotenv": "6.1.1",
|
"@types/dotenv": "6.1.1",
|
||||||
"@types/escape-html": "0.0.20",
|
"@types/escape-html": "0.0.20",
|
||||||
"@types/execa": "0.9.0",
|
|
||||||
"@types/fs-extra": "5.0.5",
|
"@types/fs-extra": "5.0.5",
|
||||||
"@types/glob": "7.1.1",
|
"@types/glob": "7.1.1",
|
||||||
"@types/http-proxy": "1.16.2",
|
"@types/http-proxy": "1.16.2",
|
||||||
@@ -106,11 +91,8 @@
|
|||||||
"@types/universal-analytics": "0.4.2",
|
"@types/universal-analytics": "0.4.2",
|
||||||
"@types/which": "1.3.1",
|
"@types/which": "1.3.1",
|
||||||
"@types/write-json-file": "2.2.1",
|
"@types/write-json-file": "2.2.1",
|
||||||
"@typescript-eslint/eslint-plugin": "1.6.0",
|
|
||||||
"@typescript-eslint/parser": "1.1.0",
|
|
||||||
"@zeit/dockerignore": "0.0.5",
|
"@zeit/dockerignore": "0.0.5",
|
||||||
"@zeit/fun": "0.9.1",
|
"@zeit/fun": "0.10.3",
|
||||||
"@zeit/git-hooks": "0.1.4",
|
|
||||||
"@zeit/ncc": "0.18.5",
|
"@zeit/ncc": "0.18.5",
|
||||||
"@zeit/source-map-support": "0.6.2",
|
"@zeit/source-map-support": "0.6.2",
|
||||||
"ajv": "6.10.2",
|
"ajv": "6.10.2",
|
||||||
@@ -123,7 +105,6 @@
|
|||||||
"async-sema": "2.1.4",
|
"async-sema": "2.1.4",
|
||||||
"ava": "2.2.0",
|
"ava": "2.2.0",
|
||||||
"bytes": "3.0.0",
|
"bytes": "3.0.0",
|
||||||
"cache-or-tmp-directory": "1.0.0",
|
|
||||||
"chalk": "2.4.2",
|
"chalk": "2.4.2",
|
||||||
"chokidar": "2.1.6",
|
"chokidar": "2.1.6",
|
||||||
"clipboardy": "2.1.0",
|
"clipboardy": "2.1.0",
|
||||||
@@ -142,19 +123,12 @@
|
|||||||
"email-validator": "1.1.1",
|
"email-validator": "1.1.1",
|
||||||
"epipebomb": "1.0.0",
|
"epipebomb": "1.0.0",
|
||||||
"escape-html": "1.0.3",
|
"escape-html": "1.0.3",
|
||||||
"eslint": "5.16.0",
|
|
||||||
"eslint-config-airbnb": "17.1.0",
|
|
||||||
"eslint-config-prettier": "4.1.0",
|
|
||||||
"eslint-import-resolver-typescript": "1.1.1",
|
|
||||||
"eslint-plugin-import": "2.16.0",
|
|
||||||
"eslint-plugin-jsx-a11y": "6.2.1",
|
|
||||||
"eslint-plugin-react": "7.12.4",
|
|
||||||
"esm": "3.1.4",
|
"esm": "3.1.4",
|
||||||
"execa": "1.0.0",
|
"execa": "3.2.0",
|
||||||
"fetch-h2": "2.0.3",
|
|
||||||
"fs-extra": "7.0.1",
|
"fs-extra": "7.0.1",
|
||||||
"glob": "7.1.2",
|
"glob": "7.1.2",
|
||||||
"http-proxy": "1.17.0",
|
"http-proxy": "1.17.0",
|
||||||
|
"ignore": "4.0.6",
|
||||||
"ini": "1.3.4",
|
"ini": "1.3.4",
|
||||||
"inquirer": "3.3.0",
|
"inquirer": "3.3.0",
|
||||||
"is-url": "1.2.2",
|
"is-url": "1.2.2",
|
||||||
@@ -165,15 +139,15 @@
|
|||||||
"mime-types": "2.1.24",
|
"mime-types": "2.1.24",
|
||||||
"minimatch": "3.0.4",
|
"minimatch": "3.0.4",
|
||||||
"mri": "1.1.0",
|
"mri": "1.1.0",
|
||||||
"ms": "2.1.1",
|
"ms": "2.1.2",
|
||||||
"node-fetch": "1.7.3",
|
"node-fetch": "1.7.3",
|
||||||
|
"now-client": "./packages/now-client",
|
||||||
"npm-package-arg": "6.1.0",
|
"npm-package-arg": "6.1.0",
|
||||||
"nyc": "13.2.0",
|
"nyc": "13.2.0",
|
||||||
"ora": "3.4.0",
|
"ora": "3.4.0",
|
||||||
"pcre-to-regexp": "0.0.5",
|
"pcre-to-regexp": "1.0.0",
|
||||||
"pluralize": "7.0.0",
|
"pluralize": "7.0.0",
|
||||||
"pre-commit": "1.2.2",
|
"pre-commit": "1.2.2",
|
||||||
"prettier": "1.16.2",
|
|
||||||
"printf": "0.2.5",
|
"printf": "0.2.5",
|
||||||
"progress": "2.0.3",
|
"progress": "2.0.3",
|
||||||
"promisepipe": "3.0.0",
|
"promisepipe": "3.0.0",
|
||||||
@@ -194,8 +168,9 @@
|
|||||||
"through2": "2.0.3",
|
"through2": "2.0.3",
|
||||||
"title": "3.4.1",
|
"title": "3.4.1",
|
||||||
"tmp-promise": "1.0.3",
|
"tmp-promise": "1.0.3",
|
||||||
|
"tree-kill": "1.2.1",
|
||||||
"ts-node": "8.3.0",
|
"ts-node": "8.3.0",
|
||||||
"typescript": "3.2.4",
|
"typescript": "3.6.4",
|
||||||
"universal-analytics": "0.4.20",
|
"universal-analytics": "0.4.20",
|
||||||
"update-check": "1.5.3",
|
"update-check": "1.5.3",
|
||||||
"utility-types": "2.1.0",
|
"utility-types": "2.1.0",
|
||||||
|
|||||||
@@ -8,16 +8,13 @@ import { createWriteStream, mkdirp, remove, writeJSON } from 'fs-extra';
|
|||||||
|
|
||||||
import { getDistTag } from '../src/util/get-dist-tag';
|
import { getDistTag } from '../src/util/get-dist-tag';
|
||||||
import pkg from '../package.json';
|
import pkg from '../package.json';
|
||||||
|
import { getBundledBuilders } from '../src/util/dev/get-bundled-builders';
|
||||||
|
|
||||||
const dirRoot = join(__dirname, '..');
|
const dirRoot = join(__dirname, '..');
|
||||||
|
|
||||||
const bundledBuilders = Object.keys(pkg.devDependencies).filter(d =>
|
|
||||||
d.startsWith('@now/')
|
|
||||||
);
|
|
||||||
|
|
||||||
async function createBuildersTarball() {
|
async function createBuildersTarball() {
|
||||||
const distTag = getDistTag(pkg.version);
|
const distTag = getDistTag(pkg.version);
|
||||||
const builders = Array.from(bundledBuilders).map(b => `${b}@${distTag}`);
|
const builders = Array.from(getBundledBuilders()).map(b => `${b}@${distTag}`);
|
||||||
console.log(`Creating builders tarball with: ${builders.join(', ')}`);
|
console.log(`Creating builders tarball with: ${builders.join(', ')}`);
|
||||||
|
|
||||||
const buildersDir = join(dirRoot, '.builders');
|
const buildersDir = join(dirRoot, '.builders');
|
||||||
@@ -39,7 +36,7 @@ async function createBuildersTarball() {
|
|||||||
const yarn = join(dirRoot, '../../node_modules/yarn/bin/yarn.js');
|
const yarn = join(dirRoot, '../../node_modules/yarn/bin/yarn.js');
|
||||||
await execa(process.execPath, [yarn, 'add', '--no-lockfile', ...builders], {
|
await execa(process.execPath, [yarn, 'add', '--no-lockfile', ...builders], {
|
||||||
cwd: buildersDir,
|
cwd: buildersDir,
|
||||||
stdio: 'inherit'
|
stdio: 'inherit',
|
||||||
});
|
});
|
||||||
|
|
||||||
const packer = tar.pack(buildersDir);
|
const packer = tar.pack(buildersDir);
|
||||||
@@ -66,7 +63,7 @@ async function main() {
|
|||||||
// Compile the `doT.js` template files for `now dev`
|
// Compile the `doT.js` template files for `now dev`
|
||||||
console.log();
|
console.log();
|
||||||
await execa(process.execPath, [join(__dirname, 'compile-templates.js')], {
|
await execa(process.execPath, [join(__dirname, 'compile-templates.js')], {
|
||||||
stdio: 'inherit'
|
stdio: 'inherit',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Do the initial `ncc` build
|
// Do the initial `ncc` build
|
||||||
@@ -92,20 +89,22 @@ async function main() {
|
|||||||
// get compiled into the final ncc bundle file, however, we want them to be
|
// get compiled into the final ncc bundle file, however, we want them to be
|
||||||
// present in the npm package because the contents of those files are involved
|
// present in the npm package because the contents of those files are involved
|
||||||
// with `fun`'s cache invalidation mechanism and they need to be shasum'd.
|
// with `fun`'s cache invalidation mechanism and they need to be shasum'd.
|
||||||
const runtimes = join(dirRoot, '../../node_modules/@zeit/fun/dist/src/runtimes');
|
const runtimes = join(
|
||||||
|
dirRoot,
|
||||||
|
'../../node_modules/@zeit/fun/dist/src/runtimes'
|
||||||
|
);
|
||||||
const dest = join(dirRoot, 'dist/runtimes');
|
const dest = join(dirRoot, 'dist/runtimes');
|
||||||
await cpy('**/*', dest, { parents: true, cwd: runtimes });
|
await cpy('**/*', dest, { parents: true, cwd: runtimes });
|
||||||
|
|
||||||
console.log('Finished building `now-cli`');
|
console.log('Finished building `now-cli`');
|
||||||
}
|
}
|
||||||
|
|
||||||
process.on('unhandledRejection', (err: any) => {
|
process.on('unhandledRejection', (reason: any, promise: Promise<any>) => {
|
||||||
console.error('Unhandled Rejection:');
|
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
||||||
console.error(err);
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on('uncaughtException', (err: any) => {
|
process.on('uncaughtException', err => {
|
||||||
console.error('Uncaught Exception:');
|
console.error('Uncaught Exception:');
|
||||||
console.error(err);
|
console.error(err);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
|||||||
@@ -11,7 +11,10 @@ import strlen from '../../util/strlen.ts';
|
|||||||
import wait from '../../util/output/wait';
|
import wait from '../../util/output/wait';
|
||||||
|
|
||||||
export default async function ls(ctx, opts, args, output) {
|
export default async function ls(ctx, opts, args, output) {
|
||||||
const { authConfig: { token }, config } = ctx;
|
const {
|
||||||
|
authConfig: { token },
|
||||||
|
config
|
||||||
|
} = ctx;
|
||||||
const { currentTeam } = config;
|
const { currentTeam } = config;
|
||||||
const { apiUrl } = ctx;
|
const { apiUrl } = ctx;
|
||||||
const { '--debug': debugEnabled } = opts;
|
const { '--debug': debugEnabled } = opts;
|
||||||
@@ -48,7 +51,6 @@ export default async function ls(ctx, opts, args, output) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!opts['--json']) {
|
|
||||||
cancelWait = wait(
|
cancelWait = wait(
|
||||||
args[0]
|
args[0]
|
||||||
? `Fetching alias details for "${args[0]}" under ${chalk.bold(
|
? `Fetching alias details for "${args[0]}" under ${chalk.bold(
|
||||||
@@ -56,7 +58,6 @@ export default async function ls(ctx, opts, args, output) {
|
|||||||
)}`
|
)}`
|
||||||
: `Fetching aliases under ${chalk.bold(contextName)}`
|
: `Fetching aliases under ${chalk.bold(contextName)}`
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
const aliases = await getAliases(now);
|
const aliases = await getAliases(now);
|
||||||
if (cancelWait) cancelWait();
|
if (cancelWait) cancelWait();
|
||||||
@@ -72,7 +73,7 @@ export default async function ls(ctx, opts, args, output) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (opts['--json']) {
|
if (opts['--json']) {
|
||||||
output.print(JSON.stringify({ rules: alias.rules }, null, 2));
|
console.log(JSON.stringify({ rules: alias.rules }, null, 2));
|
||||||
} else {
|
} else {
|
||||||
const rules = alias.rules || [];
|
const rules = alias.rules || [];
|
||||||
output.log(
|
output.log(
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export default async function set(
|
|||||||
const {
|
const {
|
||||||
authConfig: { token },
|
authConfig: { token },
|
||||||
config,
|
config,
|
||||||
localConfig
|
localConfig,
|
||||||
} = ctx;
|
} = ctx;
|
||||||
|
|
||||||
const { currentTeam } = config;
|
const { currentTeam } = config;
|
||||||
@@ -48,14 +48,14 @@ export default async function set(
|
|||||||
const {
|
const {
|
||||||
'--debug': debugEnabled,
|
'--debug': debugEnabled,
|
||||||
'--no-verify': noVerify,
|
'--no-verify': noVerify,
|
||||||
'--rules': rulesPath
|
'--rules': rulesPath,
|
||||||
} = opts;
|
} = opts;
|
||||||
|
|
||||||
const client = new Client({
|
const client = new Client({
|
||||||
apiUrl,
|
apiUrl,
|
||||||
token,
|
token,
|
||||||
currentTeam,
|
currentTeam,
|
||||||
debug: debugEnabled
|
debug: debugEnabled,
|
||||||
});
|
});
|
||||||
let contextName = null;
|
let contextName = null;
|
||||||
let user = null;
|
let user = null;
|
||||||
@@ -79,12 +79,14 @@ export default async function set(
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isValidName(args[0])) {
|
if (args.length >= 1 && !isValidName(args[0])) {
|
||||||
output.error(`The provided argument "${args[0]}" is not a valid deployment`);
|
output.error(
|
||||||
|
`The provided argument "${args[0]}" is not a valid deployment`
|
||||||
|
);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isValidName(args[1])) {
|
if (args.length >= 2 && !isValidName(args[1])) {
|
||||||
output.error(`The provided argument "${args[1]}" is not a valid domain`);
|
output.error(`The provided argument "${args[1]}" is not a valid domain`);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -212,6 +214,7 @@ export default async function set(
|
|||||||
for (const target of targets) {
|
for (const target of targets) {
|
||||||
output.log(`Assigning alias ${target} to deployment ${deployment.url}`);
|
output.log(`Assigning alias ${target} to deployment ${deployment.url}`);
|
||||||
|
|
||||||
|
const isWildcard = isWildcardAlias(target);
|
||||||
const record = await assignAlias(
|
const record = await assignAlias(
|
||||||
output,
|
output,
|
||||||
client,
|
client,
|
||||||
@@ -222,13 +225,14 @@ export default async function set(
|
|||||||
);
|
);
|
||||||
const handleResult = handleSetupDomainError(
|
const handleResult = handleSetupDomainError(
|
||||||
output,
|
output,
|
||||||
handleCreateAliasError(output, record)
|
handleCreateAliasError(output, record),
|
||||||
|
isWildcard
|
||||||
);
|
);
|
||||||
if (handleResult === 1) {
|
if (handleResult === 1) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const prefix = isWildcardAlias(handleResult.alias) ? '' : 'https://';
|
const prefix = isWildcard ? '' : 'https://';
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`${chalk.cyan('> Success!')} ${chalk.bold(
|
`${chalk.cyan('> Success!')} ${chalk.bold(
|
||||||
@@ -246,10 +250,15 @@ type SetupDomainError = Exclude<SetupDomainResolve, Domain>;
|
|||||||
|
|
||||||
function handleSetupDomainError<T>(
|
function handleSetupDomainError<T>(
|
||||||
output: Output,
|
output: Output,
|
||||||
error: SetupDomainError | T
|
error: SetupDomainError | T,
|
||||||
|
isWildcard: boolean = false
|
||||||
): T | 1 {
|
): T | 1 {
|
||||||
if (error instanceof ERRORS.DomainVerificationFailed) {
|
if (
|
||||||
const { nsVerification, txtVerification, domain } = error.meta;
|
error instanceof ERRORS.DomainVerificationFailed ||
|
||||||
|
error instanceof ERRORS.DomainNsNotVerifiedForWildcard
|
||||||
|
) {
|
||||||
|
const { nsVerification, domain } = error.meta;
|
||||||
|
|
||||||
output.error(
|
output.error(
|
||||||
`We could not alias since the domain ${domain} could not be verified due to the following reasons:\n`
|
`We could not alias since the domain ${domain} could not be verified due to the following reasons:\n`
|
||||||
);
|
);
|
||||||
@@ -265,6 +274,8 @@ function handleSetupDomainError<T>(
|
|||||||
{ extraSpace: ' ' }
|
{ extraSpace: ' ' }
|
||||||
)}\n\n`
|
)}\n\n`
|
||||||
);
|
);
|
||||||
|
if (error instanceof ERRORS.DomainVerificationFailed && !isWildcard) {
|
||||||
|
const { txtVerification } = error.meta;
|
||||||
output.print(
|
output.print(
|
||||||
` ${chalk.gray(
|
` ${chalk.gray(
|
||||||
'b)'
|
'b)'
|
||||||
@@ -284,6 +295,13 @@ function handleSetupDomainError<T>(
|
|||||||
output.print(
|
output.print(
|
||||||
` We will also periodically run a verification check for you and you will receive an email once your domain is verified.\n`
|
` We will also periodically run a verification check for you and you will receive an email once your domain is verified.\n`
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
output.print(
|
||||||
|
` Once your domain uses the nameservers from above, run again ${cmd(
|
||||||
|
'now domains verify <domain>'
|
||||||
|
)}.\n`
|
||||||
|
);
|
||||||
|
}
|
||||||
output.print(' Read more: https://err.sh/now/domain-verification\n');
|
output.print(' Read more: https://err.sh/now/domain-verification\n');
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -349,9 +367,7 @@ function handleSetupDomainError<T>(
|
|||||||
|
|
||||||
if (error instanceof ERRORS.DomainPurchasePending) {
|
if (error instanceof ERRORS.DomainPurchasePending) {
|
||||||
output.error(
|
output.error(
|
||||||
`The domain ${
|
`The domain ${error.meta.domain} is processing and will be available once the order is completed.`
|
||||||
error.meta.domain
|
|
||||||
} is processing and will be available once the order is completed.`
|
|
||||||
);
|
);
|
||||||
output.print(
|
output.print(
|
||||||
` An email will be sent upon completion so you can alias to your new domain.\n`
|
` An email will be sent upon completion so you can alias to your new domain.\n`
|
||||||
@@ -467,9 +483,7 @@ function handleCreateAliasError<T>(
|
|||||||
}
|
}
|
||||||
if (error instanceof ERRORS.ForbiddenScaleMinInstances) {
|
if (error instanceof ERRORS.ForbiddenScaleMinInstances) {
|
||||||
output.error(
|
output.error(
|
||||||
`You can't scale to more than ${
|
`You can't scale to more than ${error.meta.max} min instances with your current plan.`
|
||||||
error.meta.max
|
|
||||||
} min instances with your current plan.`
|
|
||||||
);
|
);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -490,9 +504,7 @@ function handleCreateAliasError<T>(
|
|||||||
|
|
||||||
if (error instanceof ERRORS.CertMissing) {
|
if (error instanceof ERRORS.CertMissing) {
|
||||||
output.error(
|
output.error(
|
||||||
`There is no certificate for the domain ${
|
`There is no certificate for the domain ${error.meta.domain} and it could not be created.`
|
||||||
error.meta.domain
|
|
||||||
} and it could not be created.`
|
|
||||||
);
|
);
|
||||||
output.log(
|
output.log(
|
||||||
`Please generate a new certificate manually with ${cmd(
|
`Please generate a new certificate manually with ${cmd(
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
import Now from '../../util';
|
import Now from '../../util';
|
||||||
import Client from '../../util/client.ts';
|
import Client from '../../util/client';
|
||||||
import getScope from '../../util/get-scope.ts';
|
import getScope from '../../util/get-scope';
|
||||||
import stamp from '../../util/output/stamp.ts';
|
import stamp from '../../util/output/stamp';
|
||||||
import wait from '../../util/output/wait';
|
import wait from '../../util/output/wait';
|
||||||
import createCertFromFile from '../../util/certs/create-cert-from-file';
|
import createCertFromFile from '../../util/certs/create-cert-from-file';
|
||||||
import createCertForCns from '../../util/certs/create-cert-for-cns';
|
import createCertForCns from '../../util/certs/create-cert-for-cns';
|
||||||
|
import { NowContext } from '../../types';
|
||||||
|
import { Output } from '../../util/output';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DomainPermissionDenied,
|
DomainPermissionDenied,
|
||||||
@@ -14,7 +17,20 @@ import {
|
|||||||
} from '../../util/errors-ts';
|
} from '../../util/errors-ts';
|
||||||
import handleCertError from '../../util/certs/handle-cert-error';
|
import handleCertError from '../../util/certs/handle-cert-error';
|
||||||
|
|
||||||
async function add(ctx, opts, args, output) {
|
interface Options {
|
||||||
|
'--overwrite'?: boolean;
|
||||||
|
'--debug'?: boolean;
|
||||||
|
'--crt'?: string;
|
||||||
|
'--key'?: string;
|
||||||
|
'--ca'?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function add(
|
||||||
|
ctx: NowContext,
|
||||||
|
opts: Options,
|
||||||
|
args: string[],
|
||||||
|
output: Output
|
||||||
|
): Promise<number> {
|
||||||
const {
|
const {
|
||||||
authConfig: { token },
|
authConfig: { token },
|
||||||
config
|
config
|
||||||
@@ -77,10 +93,12 @@ async function add(ctx, opts, args, output) {
|
|||||||
|
|
||||||
// Create a custom certificate from the given file paths
|
// Create a custom certificate from the given file paths
|
||||||
cert = await createCertFromFile(now, keyPath, crtPath, caPath, contextName);
|
cert = await createCertFromFile(now, keyPath, crtPath, caPath, contextName);
|
||||||
|
|
||||||
if (cert instanceof InvalidCert) {
|
if (cert instanceof InvalidCert) {
|
||||||
output.error(`The provided certificate is not valid and can't be added.`);
|
output.error(`The provided certificate is not valid and can't be added.`);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cert instanceof DomainPermissionDenied) {
|
if (cert instanceof DomainPermissionDenied) {
|
||||||
output.error(
|
output.error(
|
||||||
`You don't have permissions over domain ${chalk.underline(
|
`You don't have permissions over domain ${chalk.underline(
|
||||||
@@ -97,6 +115,7 @@ async function add(ctx, opts, args, output) {
|
|||||||
'now certs issue <cn> <cns>'
|
'now certs issue <cn> <cns>'
|
||||||
)} instead`
|
)} instead`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (args.length < 1) {
|
if (args.length < 1) {
|
||||||
output.error(
|
output.error(
|
||||||
`Invalid number of arguments to create a custom certificate entry. Usage:`
|
`Invalid number of arguments to create a custom certificate entry. Usage:`
|
||||||
@@ -107,16 +126,19 @@ async function add(ctx, opts, args, output) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create the certificate from the given array of CNs
|
// Create the certificate from the given array of CNs
|
||||||
const cns = args.reduce((res, item) => [...res, ...item.split(',')], []);
|
const cns = args.reduce<string[]>((res, item) => res.concat(item.split(',')), []);
|
||||||
const cancelWait = wait(
|
const cancelWait = wait(
|
||||||
`Generating a certificate for ${chalk.bold(cns.join(', '))}`
|
`Generating a certificate for ${chalk.bold(cns.join(', '))}`
|
||||||
);
|
);
|
||||||
|
|
||||||
cert = await createCertForCns(now, cns, contextName);
|
cert = await createCertForCns(now, cns, contextName);
|
||||||
cancelWait();
|
cancelWait();
|
||||||
|
}
|
||||||
|
|
||||||
const result = handleCertError(output, cert);
|
const result = handleCertError(output, cert);
|
||||||
|
|
||||||
if (result === 1) {
|
if (result === 1) {
|
||||||
return result
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cert instanceof DomainPermissionDenied) {
|
if (cert instanceof DomainPermissionDenied) {
|
||||||
@@ -127,14 +149,20 @@ async function add(ctx, opts, args, output) {
|
|||||||
);
|
);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
if (cert instanceof Error) {
|
||||||
|
// All cert errors are handled above,
|
||||||
|
// so this is only for typescript
|
||||||
|
throw cert;
|
||||||
|
} else {
|
||||||
// Print success message
|
// Print success message
|
||||||
output.success(
|
output.success(
|
||||||
`Certificate entry for ${chalk.bold(
|
`Certificate entry for ${chalk.bold(
|
||||||
cert.cns.join(', ')
|
cert.cns.join(', ')
|
||||||
)} created ${addStamp()}`
|
)} created ${addStamp()}`
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//
|
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
import { handleError } from '../../util/error';
|
import { handleError } from '../../util/error';
|
||||||
|
|
||||||
import createOutput from '../../util/output';
|
import createOutput from '../../util/output';
|
||||||
@@ -12,6 +12,7 @@ import add from './add';
|
|||||||
import issue from './issue';
|
import issue from './issue';
|
||||||
import ls from './ls';
|
import ls from './ls';
|
||||||
import rm from './rm';
|
import rm from './rm';
|
||||||
|
import { NowContext } from '../../types';
|
||||||
|
|
||||||
const help = () => {
|
const help = () => {
|
||||||
console.log(`
|
console.log(`
|
||||||
@@ -71,7 +72,7 @@ const COMMAND_CONFIG = {
|
|||||||
rm: ['rm', 'remove']
|
rm: ['rm', 'remove']
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function main(ctx) {
|
export default async function main(ctx: NowContext) {
|
||||||
let argv;
|
let argv;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -3,16 +3,29 @@ import ms from 'ms';
|
|||||||
import plural from 'pluralize';
|
import plural from 'pluralize';
|
||||||
import psl from 'psl';
|
import psl from 'psl';
|
||||||
import table from 'text-table';
|
import table from 'text-table';
|
||||||
|
// @ts-ignore
|
||||||
import Now from '../../util';
|
import Now from '../../util';
|
||||||
import cmd from '../../util/output/cmd';
|
import cmd from '../../util/output/cmd';
|
||||||
import Client from '../../util/client.ts';
|
import Client from '../../util/client';
|
||||||
import getScope from '../../util/get-scope.ts';
|
import getScope from '../../util/get-scope';
|
||||||
import stamp from '../../util/output/stamp.ts';
|
import stamp from '../../util/output/stamp';
|
||||||
import getCerts from '../../util/certs/get-certs';
|
import getCerts from '../../util/certs/get-certs';
|
||||||
import { CertNotFound } from '../../util/errors-ts';
|
import { CertNotFound } from '../../util/errors-ts';
|
||||||
import strlen from '../../util/strlen.ts';
|
import strlen from '../../util/strlen';
|
||||||
|
import { Output } from '../../util/output';
|
||||||
|
import { NowContext, Cert } from '../../types';
|
||||||
|
|
||||||
async function ls(ctx, opts, args, output) {
|
interface Options {
|
||||||
|
'--debug'?: boolean;
|
||||||
|
'--after'?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ls(
|
||||||
|
ctx: NowContext,
|
||||||
|
opts: Options,
|
||||||
|
args: string[],
|
||||||
|
output: Output
|
||||||
|
): Promise<number> {
|
||||||
const { authConfig: { token }, config } = ctx;
|
const { authConfig: { token }, config } = ctx;
|
||||||
const { currentTeam } = config;
|
const { currentTeam } = config;
|
||||||
const { apiUrl } = ctx;
|
const { apiUrl } = ctx;
|
||||||
@@ -32,7 +45,6 @@ async function ls(ctx, opts, args, output) {
|
|||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
// $FlowFixMe
|
|
||||||
const now = new Now({ apiUrl, token, debug, currentTeam });
|
const now = new Now({ apiUrl, token, debug, currentTeam });
|
||||||
const lsStamp = stamp();
|
const lsStamp = stamp();
|
||||||
|
|
||||||
@@ -55,7 +67,6 @@ async function ls(ctx, opts, args, output) {
|
|||||||
throw certificates;
|
throw certificates;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { uid: lastCert } = certificates[certificates.length - 1];
|
|
||||||
const certs = sortByCn(certificates);
|
const certs = sortByCn(certificates);
|
||||||
|
|
||||||
output.log(
|
output.log(
|
||||||
@@ -65,7 +76,8 @@ async function ls(ctx, opts, args, output) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (certs.length >= 100) {
|
if (certs.length >= 100) {
|
||||||
output.note(`There may be more certificates that can be retrieved with ${cmd(`now ${process.argv.slice(2).join(' ')} --after=${lastCert}`)}.`);
|
const { uid: lastCert } = certificates[certificates.length - 1];
|
||||||
|
output.note(`There may be more certificates that can be retrieved with ${cmd(`now ${process.argv.slice(2).join(' ')} --after=${lastCert}`)}.\n`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (certs.length > 0) {
|
if (certs.length > 0) {
|
||||||
@@ -75,7 +87,7 @@ async function ls(ctx, opts, args, output) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatCertsTable(certsList) {
|
function formatCertsTable(certsList: Cert[]) {
|
||||||
return `${table(
|
return `${table(
|
||||||
[formatCertsTableHead(), ...formatCertsTableBody(certsList)],
|
[formatCertsTableHead(), ...formatCertsTableBody(certsList)],
|
||||||
{
|
{
|
||||||
@@ -86,7 +98,7 @@ function formatCertsTable(certsList) {
|
|||||||
).replace(/^(.*)/gm, ' $1')}\n`;
|
).replace(/^(.*)/gm, ' $1')}\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatCertsTableHead() {
|
function formatCertsTableHead(): string[] {
|
||||||
return [
|
return [
|
||||||
chalk.dim('id'),
|
chalk.dim('id'),
|
||||||
chalk.dim('cns'),
|
chalk.dim('cns'),
|
||||||
@@ -96,15 +108,12 @@ function formatCertsTableHead() {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatCertsTableBody(certsList) {
|
function formatCertsTableBody(certsList: Cert[]) {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
return certsList.reduce(
|
return certsList.reduce<string[][]>((result, cert) => result.concat(formatCert(now, cert)), []);
|
||||||
(result, cert) => [...result, ...formatCert(now, cert)],
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatCert(time, cert) {
|
function formatCert(time: Date, cert: Cert) {
|
||||||
return cert.cns.map(
|
return cert.cns.map(
|
||||||
(cn, idx) =>
|
(cn, idx) =>
|
||||||
idx === 0
|
idx === 0
|
||||||
@@ -113,26 +122,26 @@ function formatCert(time, cert) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatCertNonFirstCn(cn, multiple) {
|
function formatCertNonFirstCn(cn: string, multiple: boolean): string[] {
|
||||||
return ['', formatCertCn(cn, multiple), '', '', ''];
|
return ['', formatCertCn(cn, multiple), '', '', ''];
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatCertCn(cn, multiple) {
|
function formatCertCn(cn: string, multiple: boolean) {
|
||||||
return multiple ? `${chalk.gray('-')} ${chalk.bold(cn)}` : chalk.bold(cn);
|
return multiple ? `${chalk.gray('-')} ${chalk.bold(cn)}` : chalk.bold(cn);
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatCertFirstCn(time, cert, cn, multiple) {
|
function formatCertFirstCn(time: Date, cert: Cert, cn: string, multiple: boolean): string[] {
|
||||||
return [
|
return [
|
||||||
cert.uid,
|
cert.uid,
|
||||||
formatCertCn(cn, multiple),
|
formatCertCn(cn, multiple),
|
||||||
formatExpirationDate(new Date(cert.expiration)),
|
formatExpirationDate(new Date(cert.expiration)),
|
||||||
cert.autoRenew ? 'yes' : 'no',
|
cert.autoRenew ? 'yes' : 'no',
|
||||||
chalk.gray(ms(time - new Date(cert.created)))
|
chalk.gray(ms(time.getTime() - new Date(cert.created).getTime()))
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatExpirationDate(date) {
|
function formatExpirationDate(date: Date) {
|
||||||
const diff = date - Date.now();
|
const diff = date.getTime() - Date.now();
|
||||||
return diff < 0
|
return diff < 0
|
||||||
? chalk.gray(`${ms(-diff)} ago`)
|
? chalk.gray(`${ms(-diff)} ago`)
|
||||||
: chalk.gray(`in ${ms(diff)}`);
|
: chalk.gray(`in ${ms(diff)}`);
|
||||||
@@ -143,8 +152,8 @@ function formatExpirationDate(date) {
|
|||||||
* to 'wildcard' since that will allow psl get the root domain
|
* to 'wildcard' since that will allow psl get the root domain
|
||||||
* properly to make the comparison.
|
* properly to make the comparison.
|
||||||
*/
|
*/
|
||||||
function sortByCn(certsList) {
|
function sortByCn(certsList: Cert[]) {
|
||||||
return certsList.concat().sort((a, b) => {
|
return certsList.concat().sort((a: Cert, b: Cert) => {
|
||||||
const domainA = psl.get(a.cns[0].replace('*', 'wildcard'));
|
const domainA = psl.get(a.cns[0].replace('*', 'wildcard'));
|
||||||
const domainB = psl.get(b.cns[0].replace('*', 'wildcard'));
|
const domainB = psl.get(b.cns[0].replace('*', 'wildcard'));
|
||||||
if (!domainA || !domainB) return 0;
|
if (!domainA || !domainB) return 0;
|
||||||
@@ -10,7 +10,9 @@ export const latestHelp = () => `
|
|||||||
|
|
||||||
${chalk.dim('Basic')}
|
${chalk.dim('Basic')}
|
||||||
|
|
||||||
deploy [path] Performs a deployment ${chalk.bold('(default)')}
|
deploy [path] Performs a deployment ${chalk.bold(
|
||||||
|
'(default)'
|
||||||
|
)}
|
||||||
dev Start a local development server
|
dev Start a local development server
|
||||||
init [example] Initialize an example project
|
init [example] Initialize an example project
|
||||||
ls | list [app] Lists deployments
|
ls | list [app] Lists deployments
|
||||||
@@ -18,7 +20,6 @@ export const latestHelp = () => `
|
|||||||
login [email] Logs into your account or creates a new one
|
login [email] Logs into your account or creates a new one
|
||||||
logout Logs out of your account
|
logout Logs out of your account
|
||||||
switch [scope] Switches between teams and your personal account
|
switch [scope] Switches between teams and your personal account
|
||||||
update Updates Now CLI to the latest version
|
|
||||||
help [cmd] Displays complete help for [cmd]
|
help [cmd] Displays complete help for [cmd]
|
||||||
|
|
||||||
${chalk.dim('Advanced')}
|
${chalk.dim('Advanced')}
|
||||||
@@ -29,7 +30,6 @@ export const latestHelp = () => `
|
|||||||
certs [cmd] Manages your SSL certificates
|
certs [cmd] Manages your SSL certificates
|
||||||
secrets [name] Manages your secret environment variables
|
secrets [name] Manages your secret environment variables
|
||||||
logs [url] Displays the logs for a deployment
|
logs [url] Displays the logs for a deployment
|
||||||
scale [args] Scales the instance count of a deployment
|
|
||||||
teams Manages your teams
|
teams Manages your teams
|
||||||
whoami Shows the username of the currently logged in user
|
whoami Shows the username of the currently logged in user
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ export const latestArgs = {
|
|||||||
'-e': '--env',
|
'-e': '--env',
|
||||||
'-b': '--build-env',
|
'-b': '--build-env',
|
||||||
'-C': '--no-clipboard',
|
'-C': '--no-clipboard',
|
||||||
'-m': '--meta'
|
'-m': '--meta',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const legacyArgsMri = {
|
export const legacyArgsMri = {
|
||||||
@@ -126,7 +126,8 @@ export const legacyArgsMri = {
|
|||||||
'meta',
|
'meta',
|
||||||
'session-affinity',
|
'session-affinity',
|
||||||
'regions',
|
'regions',
|
||||||
'dotenv'
|
'dotenv',
|
||||||
|
'target',
|
||||||
],
|
],
|
||||||
boolean: [
|
boolean: [
|
||||||
'help',
|
'help',
|
||||||
@@ -143,11 +144,12 @@ export const legacyArgsMri = {
|
|||||||
'public',
|
'public',
|
||||||
'no-scale',
|
'no-scale',
|
||||||
'no-verify',
|
'no-verify',
|
||||||
'dotenv'
|
'dotenv',
|
||||||
|
'prod',
|
||||||
],
|
],
|
||||||
default: {
|
default: {
|
||||||
C: false,
|
C: false,
|
||||||
clipboard: true
|
clipboard: true,
|
||||||
},
|
},
|
||||||
alias: {
|
alias: {
|
||||||
env: 'e',
|
env: 'e',
|
||||||
@@ -164,8 +166,8 @@ export const legacyArgsMri = {
|
|||||||
'session-affinity': 'S',
|
'session-affinity': 'S',
|
||||||
name: 'n',
|
name: 'n',
|
||||||
project: 'P',
|
project: 'P',
|
||||||
alias: 'a'
|
alias: 'a',
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// The following arg parsing is simply to make it compatible
|
// The following arg parsing is simply to make it compatible
|
||||||
|
|||||||
@@ -3,18 +3,14 @@ import bytes from 'bytes';
|
|||||||
import { write as copy } from 'clipboardy';
|
import { write as copy } from 'clipboardy';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import title from 'title';
|
import title from 'title';
|
||||||
import Progress from 'progress';
|
|
||||||
import Client from '../../util/client';
|
import Client from '../../util/client';
|
||||||
import wait from '../../util/output/wait';
|
|
||||||
import { handleError } from '../../util/error';
|
import { handleError } from '../../util/error';
|
||||||
import getArgs from '../../util/get-args';
|
import getArgs from '../../util/get-args';
|
||||||
import toHumanPath from '../../util/humanize-path';
|
import toHumanPath from '../../util/humanize-path';
|
||||||
import Now from '../../util';
|
import Now from '../../util';
|
||||||
import stamp from '../../util/output/stamp.ts';
|
import stamp from '../../util/output/stamp.ts';
|
||||||
import { isReady, isDone, isFailed } from '../../util/build-state';
|
|
||||||
import createDeploy from '../../util/deploy/create-deploy';
|
import createDeploy from '../../util/deploy/create-deploy';
|
||||||
import getDeploymentByIdOrHost from '../../util/deploy/get-deployment-by-id-or-host';
|
import getDeploymentByIdOrHost from '../../util/deploy/get-deployment-by-id-or-host';
|
||||||
import sleep from '../../util/sleep';
|
|
||||||
import parseMeta from '../../util/parse-meta';
|
import parseMeta from '../../util/parse-meta';
|
||||||
import code from '../../util/output/code';
|
import code from '../../util/output/code';
|
||||||
import param from '../../util/output/param';
|
import param from '../../util/output/param';
|
||||||
@@ -36,12 +32,15 @@ import {
|
|||||||
AliasDomainConfigured,
|
AliasDomainConfigured,
|
||||||
MissingBuildScript,
|
MissingBuildScript,
|
||||||
ConflictingFilePath,
|
ConflictingFilePath,
|
||||||
ConflictingPathSegment
|
ConflictingPathSegment,
|
||||||
|
BuildError,
|
||||||
|
NotDomainOwner,
|
||||||
} from '../../util/errors-ts';
|
} from '../../util/errors-ts';
|
||||||
import { SchemaValidationFailed } from '../../util/errors';
|
import { SchemaValidationFailed } from '../../util/errors';
|
||||||
import purchaseDomainIfAvailable from '../../util/domains/purchase-domain-if-available';
|
import purchaseDomainIfAvailable from '../../util/domains/purchase-domain-if-available';
|
||||||
import handleCertError from '../../util/certs/handle-cert-error';
|
import handleCertError from '../../util/certs/handle-cert-error';
|
||||||
import isWildcardAlias from '../../util/alias/is-wildcard-alias';
|
import isWildcardAlias from '../../util/alias/is-wildcard-alias';
|
||||||
|
import shouldDeployDir from '../../util/deploy/should-deploy-dir';
|
||||||
|
|
||||||
const addProcessEnv = async (log, env) => {
|
const addProcessEnv = async (log, env) => {
|
||||||
let val;
|
let val;
|
||||||
@@ -72,11 +71,12 @@ const addProcessEnv = async (log, env) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const deploymentErrorMsg = `Your deployment failed. Please retry later. More: https://err.sh/now/deployment-error`;
|
const deploymentErrorMsg = `Your deployment failed. Please retry later. More: https://err.sh/now/deployment-error`;
|
||||||
const prepareAlias = input => isWildcardAlias(input) ? input : `https://${input}`;
|
const prepareAlias = input =>
|
||||||
|
isWildcardAlias(input) ? input : `https://${input}`;
|
||||||
|
|
||||||
const printDeploymentStatus = async (
|
const printDeploymentStatus = async (
|
||||||
output,
|
output,
|
||||||
{ url, readyState, alias: aliasList, aliasError },
|
{ readyState, alias: aliasList, aliasError },
|
||||||
deployStamp,
|
deployStamp,
|
||||||
clipboardEnabled,
|
clipboardEnabled,
|
||||||
localConfig,
|
localConfig,
|
||||||
@@ -94,10 +94,18 @@ const printDeploymentStatus = async (
|
|||||||
const preparedAlias = prepareAlias(firstAlias);
|
const preparedAlias = prepareAlias(firstAlias);
|
||||||
try {
|
try {
|
||||||
await copy(`https://${firstAlias}`);
|
await copy(`https://${firstAlias}`);
|
||||||
output.ready(`Deployed to ${chalk.bold(chalk.cyan(preparedAlias))} ${chalk.gray('[in clipboard]')} ${deployStamp()}`);
|
output.ready(
|
||||||
|
`Deployed to ${chalk.bold(
|
||||||
|
chalk.cyan(preparedAlias)
|
||||||
|
)} ${chalk.gray('[in clipboard]')} ${deployStamp()}`
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
output.debug(`Error copying to clipboard: ${err}`);
|
output.debug(`Error copying to clipboard: ${err}`);
|
||||||
output.ready(`Deployed to ${chalk.bold(chalk.cyan(preparedAlias))} ${deployStamp()}`);
|
output.ready(
|
||||||
|
`Deployed to ${chalk.bold(
|
||||||
|
chalk.cyan(preparedAlias)
|
||||||
|
)} ${deployStamp()}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -109,13 +117,17 @@ const printDeploymentStatus = async (
|
|||||||
|
|
||||||
for (const alias of aliasList) {
|
for (const alias of aliasList) {
|
||||||
const index = aliasList.indexOf(alias);
|
const index = aliasList.indexOf(alias);
|
||||||
const isLast = index === (aliasList.length - 1);
|
const isLast = index === aliasList.length - 1;
|
||||||
const shouldCopy = matching ? alias === matching : isLast;
|
const shouldCopy = matching ? alias === matching : isLast;
|
||||||
|
|
||||||
if (shouldCopy && clipboardEnabled) {
|
if (shouldCopy && clipboardEnabled) {
|
||||||
try {
|
try {
|
||||||
await copy(`https://${alias}`);
|
await copy(`https://${alias}`);
|
||||||
output.print(`- ${chalk.bold(chalk.cyan(prepareAlias(alias)))} ${chalk.gray('[in clipboard]')}\n`);
|
output.print(
|
||||||
|
`- ${chalk.bold(chalk.cyan(prepareAlias(alias)))} ${chalk.gray(
|
||||||
|
'[in clipboard]'
|
||||||
|
)}\n`
|
||||||
|
);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -138,20 +150,6 @@ const printDeploymentStatus = async (
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const failedBuilds = builds.filter(isFailed);
|
|
||||||
const amount = failedBuilds.length;
|
|
||||||
|
|
||||||
if (amount > 0) {
|
|
||||||
output.error('Build failed');
|
|
||||||
output.error(
|
|
||||||
`Check your logs at https://${url}/_logs or run ${code(
|
|
||||||
`now logs ${url}`
|
|
||||||
)}`
|
|
||||||
);
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
output.error(deploymentErrorMsg);
|
output.error(deploymentErrorMsg);
|
||||||
return 1;
|
return 1;
|
||||||
};
|
};
|
||||||
@@ -206,7 +204,15 @@ export default async function main(
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { apiUrl, authConfig: { token }, config: { currentTeam } } = ctx;
|
if (!(await shouldDeployDir(argv._[0], output))) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
apiUrl,
|
||||||
|
authConfig: { token },
|
||||||
|
config: { currentTeam },
|
||||||
|
} = ctx;
|
||||||
const { log, debug, error, warn } = output;
|
const { log, debug, error, warn } = output;
|
||||||
const paths = Object.keys(stats);
|
const paths = Object.keys(stats);
|
||||||
const debugEnabled = argv['--debug'];
|
const debugEnabled = argv['--debug'];
|
||||||
@@ -236,7 +242,6 @@ export default async function main(
|
|||||||
parseMeta(argv['--meta'])
|
parseMeta(argv['--meta'])
|
||||||
);
|
);
|
||||||
|
|
||||||
let syncCount;
|
|
||||||
let deployStamp = stamp();
|
let deployStamp = stamp();
|
||||||
let deployment = null;
|
let deployment = null;
|
||||||
|
|
||||||
@@ -289,11 +294,15 @@ export default async function main(
|
|||||||
parseEnv(argv['--env'])
|
parseEnv(argv['--env'])
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Enable debug mode for builders
|
||||||
|
const buildDebugEnv = debugEnabled ? { NOW_BUILDER_DEBUG: '1' } : {};
|
||||||
|
|
||||||
// Merge build env out of `build.env` from now.json, and `--build-env` args
|
// Merge build env out of `build.env` from now.json, and `--build-env` args
|
||||||
const deploymentBuildEnv = Object.assign(
|
const deploymentBuildEnv = Object.assign(
|
||||||
{},
|
{},
|
||||||
parseEnv(localConfig.build && localConfig.build.env),
|
parseEnv(localConfig.build && localConfig.build.env),
|
||||||
parseEnv(argv['--build-env'])
|
parseEnv(argv['--build-env']),
|
||||||
|
buildDebugEnv
|
||||||
);
|
);
|
||||||
|
|
||||||
// If there's any undefined values, then inherit them from this process
|
// If there's any undefined values, then inherit them from this process
|
||||||
@@ -313,7 +322,12 @@ export default async function main(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
const project = getProjectName({argv, nowConfig: localConfig, isFile, paths});
|
const project = getProjectName({
|
||||||
|
argv,
|
||||||
|
nowConfig: localConfig,
|
||||||
|
isFile,
|
||||||
|
paths,
|
||||||
|
});
|
||||||
log(`Using project ${chalk.bold(project)}`);
|
log(`Using project ${chalk.bold(project)}`);
|
||||||
|
|
||||||
const createArgs = {
|
const createArgs = {
|
||||||
@@ -327,19 +341,26 @@ export default async function main(
|
|||||||
type: null,
|
type: null,
|
||||||
nowConfig: localConfig,
|
nowConfig: localConfig,
|
||||||
regions,
|
regions,
|
||||||
meta
|
meta,
|
||||||
|
deployStamp,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (argv['--target']) {
|
if (argv['--target']) {
|
||||||
const deprecatedTarget = argv['--target'];
|
const deprecatedTarget = argv['--target'];
|
||||||
|
|
||||||
if (!['staging', 'production'].includes(deprecatedTarget)) {
|
if (!['staging', 'production'].includes(deprecatedTarget)) {
|
||||||
error(`The specified ${param('--target')} ${code(deprecatedTarget)} is not valid`);
|
error(
|
||||||
|
`The specified ${param('--target')} ${code(
|
||||||
|
deprecatedTarget
|
||||||
|
)} is not valid`
|
||||||
|
);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deprecatedTarget === 'production') {
|
if (deprecatedTarget === 'production') {
|
||||||
warn('We recommend using the much shorter `--prod` option instead of `--target production` (deprecated)');
|
warn(
|
||||||
|
'We recommend using the much shorter `--prod` option instead of `--target production` (deprecated)'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
output.debug(`Setting target to ${deprecatedTarget}`);
|
output.debug(`Setting target to ${deprecatedTarget}`);
|
||||||
@@ -351,7 +372,7 @@ export default async function main(
|
|||||||
|
|
||||||
deployStamp = stamp();
|
deployStamp = stamp();
|
||||||
|
|
||||||
const firstDeployCall = await createDeploy(
|
deployment = await createDeploy(
|
||||||
output,
|
output,
|
||||||
now,
|
now,
|
||||||
contextName,
|
contextName,
|
||||||
@@ -360,13 +381,49 @@ export default async function main(
|
|||||||
ctx
|
ctx
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (deployment instanceof NotDomainOwner) {
|
||||||
|
output.error(deployment);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const deploymentResponse = handleCertError(
|
||||||
|
output,
|
||||||
|
await getDeploymentByIdOrHost(now, contextName, deployment.id, 'v10')
|
||||||
|
);
|
||||||
|
|
||||||
|
if (deploymentResponse === 1) {
|
||||||
|
return deploymentResponse;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
firstDeployCall instanceof DomainNotFound &&
|
deploymentResponse instanceof DeploymentNotFound ||
|
||||||
firstDeployCall.meta && firstDeployCall.meta.domain
|
deploymentResponse instanceof DeploymentPermissionDenied ||
|
||||||
|
deploymentResponse instanceof InvalidDeploymentId
|
||||||
) {
|
) {
|
||||||
output.debug(`The domain ${
|
output.error(deploymentResponse.message);
|
||||||
firstDeployCall.meta.domain
|
return 1;
|
||||||
} was not found, trying to purchase it`);
|
}
|
||||||
|
|
||||||
|
if (handleCertError(output, deployment) === 1) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deployment === null) {
|
||||||
|
error('Uploading failed. Please try again.');
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
debug(`Error: ${err}\n${err.stack}`);
|
||||||
|
|
||||||
|
if (err instanceof NotDomainOwner) {
|
||||||
|
output.error(err.message);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err instanceof DomainNotFound && err.meta && err.meta.domain) {
|
||||||
|
output.debug(
|
||||||
|
`The domain ${err.meta.domain} was not found, trying to purchase it`
|
||||||
|
);
|
||||||
|
|
||||||
const purchase = await purchaseDomainIfAvailable(
|
const purchase = await purchaseDomainIfAvailable(
|
||||||
output,
|
output,
|
||||||
@@ -374,16 +431,14 @@ export default async function main(
|
|||||||
apiUrl: ctx.apiUrl,
|
apiUrl: ctx.apiUrl,
|
||||||
token: ctx.authConfig.token,
|
token: ctx.authConfig.token,
|
||||||
currentTeam: ctx.config.currentTeam,
|
currentTeam: ctx.config.currentTeam,
|
||||||
debug: debugEnabled
|
debug: debugEnabled,
|
||||||
}),
|
}),
|
||||||
firstDeployCall.meta.domain,
|
err.meta.domain,
|
||||||
contextName
|
contextName
|
||||||
);
|
);
|
||||||
|
|
||||||
if (purchase === true) {
|
if (purchase === true) {
|
||||||
output.success(`Successfully purchased the domain ${
|
output.success(`Successfully purchased the domain ${err.meta.domain}!`);
|
||||||
firstDeployCall.meta.domain
|
|
||||||
}!`);
|
|
||||||
|
|
||||||
// We exit if the purchase is completed since
|
// We exit if the purchase is completed since
|
||||||
// the domain verification can take some time
|
// the domain verification can take some time
|
||||||
@@ -391,7 +446,7 @@ export default async function main(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (purchase === false || purchase instanceof UserAborted) {
|
if (purchase === false || purchase instanceof UserAborted) {
|
||||||
handleCreateDeployError(output, firstDeployCall);
|
handleCreateDeployError(output, deployment);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -399,121 +454,37 @@ export default async function main(
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handleCertError(output, firstDeployCall) === 1) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
firstDeployCall instanceof DomainNotFound ||
|
err instanceof DomainNotFound ||
|
||||||
firstDeployCall instanceof DomainNotVerified ||
|
err instanceof DomainNotVerified ||
|
||||||
firstDeployCall instanceof DomainPermissionDenied ||
|
err instanceof NotDomainOwner ||
|
||||||
firstDeployCall instanceof DomainVerificationFailed ||
|
err instanceof DomainPermissionDenied ||
|
||||||
firstDeployCall instanceof SchemaValidationFailed ||
|
err instanceof DomainVerificationFailed ||
|
||||||
firstDeployCall instanceof InvalidDomain ||
|
err instanceof SchemaValidationFailed ||
|
||||||
firstDeployCall instanceof DeploymentNotFound ||
|
err instanceof InvalidDomain ||
|
||||||
firstDeployCall instanceof BuildsRateLimited ||
|
err instanceof DeploymentNotFound ||
|
||||||
firstDeployCall instanceof DeploymentsRateLimited ||
|
err instanceof BuildsRateLimited ||
|
||||||
firstDeployCall instanceof AliasDomainConfigured ||
|
err instanceof DeploymentsRateLimited ||
|
||||||
firstDeployCall instanceof MissingBuildScript ||
|
err instanceof AliasDomainConfigured ||
|
||||||
firstDeployCall instanceof ConflictingFilePath ||
|
err instanceof MissingBuildScript ||
|
||||||
firstDeployCall instanceof ConflictingPathSegment
|
err instanceof ConflictingFilePath ||
|
||||||
|
err instanceof ConflictingPathSegment
|
||||||
) {
|
) {
|
||||||
handleCreateDeployError(output, firstDeployCall);
|
handleCreateDeployError(output, err);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
deployment = firstDeployCall;
|
if (err instanceof BuildError) {
|
||||||
|
output.error('Build failed');
|
||||||
if (now.syncFileCount > 0) {
|
output.error(
|
||||||
const uploadStamp = stamp();
|
`Check your logs at ${now.url}/_logs or run ${code(
|
||||||
|
`now logs ${now.url}`
|
||||||
await new Promise((resolve, reject) => {
|
)}`
|
||||||
if (now.syncFileCount !== now.fileCount) {
|
|
||||||
debug(`Total files ${now.fileCount}, ${now.syncFileCount} changed`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const size = bytes(now.syncAmount);
|
|
||||||
syncCount = `${now.syncFileCount} file${now.syncFileCount > 1
|
|
||||||
? 's'
|
|
||||||
: ''}`;
|
|
||||||
const bar = new Progress(
|
|
||||||
`${chalk.gray(
|
|
||||||
'>'
|
|
||||||
)} Upload [:bar] :percent :etas (${size}) [${syncCount}]`,
|
|
||||||
{
|
|
||||||
width: 20,
|
|
||||||
complete: '=',
|
|
||||||
incomplete: '',
|
|
||||||
total: now.syncAmount,
|
|
||||||
clear: true
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
now.upload({ scale: {} });
|
|
||||||
|
|
||||||
now.on('upload', ({ names, data }) => {
|
|
||||||
debug(`Uploaded: ${names.join(' ')} (${bytes(data.length)})`);
|
|
||||||
});
|
|
||||||
|
|
||||||
now.on('uploadProgress', progress => {
|
|
||||||
bar.tick(progress);
|
|
||||||
});
|
|
||||||
|
|
||||||
now.on('complete', resolve);
|
|
||||||
|
|
||||||
now.on('error', err => {
|
|
||||||
error('Upload failed');
|
|
||||||
reject(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!quiet && syncCount) {
|
|
||||||
log(`Synced ${syncCount} (${bytes(now.syncAmount)}) ${uploadStamp()}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < 4; i += 1) {
|
|
||||||
deployStamp = stamp();
|
|
||||||
const secondDeployCall = await createDeploy(
|
|
||||||
output,
|
|
||||||
now,
|
|
||||||
contextName,
|
|
||||||
paths,
|
|
||||||
createArgs
|
|
||||||
);
|
|
||||||
|
|
||||||
if (handleCertError(output, secondDeployCall) === 1) {
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
secondDeployCall instanceof DomainPermissionDenied ||
|
|
||||||
secondDeployCall instanceof DomainVerificationFailed ||
|
|
||||||
secondDeployCall instanceof SchemaValidationFailed ||
|
|
||||||
secondDeployCall instanceof DeploymentNotFound ||
|
|
||||||
secondDeployCall instanceof DeploymentsRateLimited ||
|
|
||||||
secondDeployCall instanceof AliasDomainConfigured ||
|
|
||||||
secondDeployCall instanceof MissingBuildScript ||
|
|
||||||
secondDeployCall instanceof ConflictingFilePath ||
|
|
||||||
secondDeployCall instanceof ConflictingPathSegment
|
|
||||||
) {
|
|
||||||
handleCreateDeployError(output, secondDeployCall);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (now.syncFileCount === 0) {
|
|
||||||
deployment = secondDeployCall;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (deployment === null) {
|
|
||||||
error('Uploading failed. Please try again.');
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
debug(`Error: ${err}\n${err.stack}`);
|
|
||||||
|
|
||||||
if (err.keyword === 'additionalProperties' && err.dataPath === '.scale') {
|
if (err.keyword === 'additionalProperties' && err.dataPath === '.scale') {
|
||||||
const { additionalProperty = '' } = err.params || {};
|
const { additionalProperty = '' } = err.params || {};
|
||||||
const message = `Invalid DC name for the scale option: ${additionalProperty}`;
|
const message = `Invalid DC name for the scale option: ${additionalProperty}`;
|
||||||
@@ -531,115 +502,15 @@ export default async function main(
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { url } = now;
|
return printDeploymentStatus(
|
||||||
|
|
||||||
if (isTTY) {
|
|
||||||
log(`${url} ${chalk.gray(`[v2]`)} ${deployStamp()}`);
|
|
||||||
} else {
|
|
||||||
process.stdout.write(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If an error occurred, we want to let it fall down to rendering
|
|
||||||
// builds so the user can see in which build the error occurred.
|
|
||||||
if (isReady(deployment)) {
|
|
||||||
return printDeploymentStatus(output, deployment, deployStamp, !argv['--no-clipboard'], localConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
const sleepingTime = ms('1.5s');
|
|
||||||
const allBuildsTime = stamp();
|
|
||||||
const times = {};
|
|
||||||
const buildsUrl = `/v1/now/deployments/${deployment.id}/builds`;
|
|
||||||
|
|
||||||
let builds = [];
|
|
||||||
let buildsCompleted = false;
|
|
||||||
let buildSpinner = null;
|
|
||||||
|
|
||||||
let deploymentSpinner = null;
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-constant-condition
|
|
||||||
while (true) {
|
|
||||||
if (!buildsCompleted) {
|
|
||||||
const { builds: freshBuilds } = await now.fetch(buildsUrl);
|
|
||||||
|
|
||||||
// If there are no builds, we need to exit.
|
|
||||||
if (freshBuilds.length === 0 || freshBuilds.every(isDone)) {
|
|
||||||
builds = freshBuilds;
|
|
||||||
buildsCompleted = true;
|
|
||||||
} else {
|
|
||||||
for (const build of freshBuilds) {
|
|
||||||
const id = build.id;
|
|
||||||
const done = isDone(build);
|
|
||||||
|
|
||||||
if (times[id]) {
|
|
||||||
if (done && typeof times[id] === 'function') {
|
|
||||||
times[id] = times[id]();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
times[id] = done ? allBuildsTime() : stamp();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (JSON.stringify(builds) !== JSON.stringify(freshBuilds)) {
|
|
||||||
builds = freshBuilds;
|
|
||||||
|
|
||||||
if (buildSpinner === null) {
|
|
||||||
buildSpinner = wait('Building...');
|
|
||||||
}
|
|
||||||
|
|
||||||
buildsCompleted = builds.every(isDone);
|
|
||||||
|
|
||||||
if (builds.some(isFailed)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const deploymentResponse = handleCertError(
|
|
||||||
output,
|
output,
|
||||||
await getDeploymentByIdOrHost(now, contextName, deployment.id, 'v9')
|
deployment,
|
||||||
)
|
deployStamp,
|
||||||
|
!argv['--no-clipboard'],
|
||||||
if (deploymentResponse === 1) {
|
localConfig
|
||||||
return deploymentResponse;
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
deploymentResponse instanceof DeploymentNotFound ||
|
|
||||||
deploymentResponse instanceof DeploymentPermissionDenied ||
|
|
||||||
deploymentResponse instanceof InvalidDeploymentId
|
|
||||||
) {
|
|
||||||
output.error(deploymentResponse.message);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isReady(deploymentResponse) || isFailed(deploymentResponse)) {
|
|
||||||
deployment = deploymentResponse;
|
|
||||||
|
|
||||||
if (typeof deploymentSpinner === 'function') {
|
|
||||||
// This stops it
|
|
||||||
deploymentSpinner();
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
} else if (!deploymentSpinner) {
|
|
||||||
if (typeof buildSpinner === 'function') {
|
|
||||||
buildSpinner();
|
|
||||||
}
|
|
||||||
|
|
||||||
deploymentSpinner = wait('Finalizing...');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await sleep(sleepingTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof buildSpinner === 'function') {
|
|
||||||
buildSpinner();
|
|
||||||
}
|
|
||||||
|
|
||||||
return printDeploymentStatus(output, deployment, deployStamp, !argv['--no-clipboard'], localConfig, builds);
|
|
||||||
};
|
|
||||||
|
|
||||||
function handleCreateDeployError(output, error) {
|
function handleCreateDeployError(output, error) {
|
||||||
if (error instanceof InvalidDomain) {
|
if (error instanceof InvalidDomain) {
|
||||||
output.error(`The domain ${error.meta.domain} is not valid`);
|
output.error(`The domain ${error.meta.domain} is not valid`);
|
||||||
@@ -708,18 +579,20 @@ function handleCreateDeployError(output, error) {
|
|||||||
}
|
}
|
||||||
if (error instanceof TooManyRequests) {
|
if (error instanceof TooManyRequests) {
|
||||||
output.error(
|
output.error(
|
||||||
`Too many requests detected for ${error.meta
|
`Too many requests detected for ${error.meta.api} API. Try again in ${ms(
|
||||||
.api} API. Try again in ${ms(error.meta.retryAfter * 1000, {
|
error.meta.retryAfter * 1000,
|
||||||
long: true
|
{
|
||||||
})}.`
|
long: true,
|
||||||
|
}
|
||||||
|
)}.`
|
||||||
);
|
);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (error instanceof DomainNotVerified) {
|
if (error instanceof DomainNotVerified) {
|
||||||
output.error(
|
output.error(
|
||||||
`The domain used as an alias ${
|
`The domain used as an alias ${chalk.underline(
|
||||||
chalk.underline(error.meta.domain)
|
error.meta.domain
|
||||||
} is not verified yet. Please verify it.`
|
)} is not verified yet. Please verify it.`
|
||||||
);
|
);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -730,6 +603,7 @@ function handleCreateDeployError(output, error) {
|
|||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
error instanceof DeploymentNotFound ||
|
error instanceof DeploymentNotFound ||
|
||||||
|
error instanceof NotDomainOwner ||
|
||||||
error instanceof DeploymentsRateLimited ||
|
error instanceof DeploymentsRateLimited ||
|
||||||
error instanceof AliasDomainConfigured ||
|
error instanceof AliasDomainConfigured ||
|
||||||
error instanceof MissingBuildScript ||
|
error instanceof MissingBuildScript ||
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { resolve, basename, join } from 'path';
|
|||||||
import { eraseLines } from 'ansi-escapes';
|
import { eraseLines } from 'ansi-escapes';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { write as copy } from 'clipboardy';
|
import { write as copy } from 'clipboardy';
|
||||||
import bytes from 'bytes';
|
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
@@ -13,7 +12,6 @@ import ms from 'ms';
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import title from 'title';
|
import title from 'title';
|
||||||
import plural from 'pluralize';
|
import plural from 'pluralize';
|
||||||
import Progress from 'progress';
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { handleError } from '../../util/error';
|
import { handleError } from '../../util/error';
|
||||||
import chars from '../../util/output/chars';
|
import chars from '../../util/output/chars';
|
||||||
@@ -34,19 +32,16 @@ import promptOptions from '../../util/prompt-options';
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import readMetaData from '../../util/read-metadata';
|
import readMetaData from '../../util/read-metadata';
|
||||||
import toHumanPath from '../../util/humanize-path';
|
import toHumanPath from '../../util/humanize-path';
|
||||||
import combineAsyncGenerators from '../../util/combine-async-generators';
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import createDeploy from '../../util/deploy/create-deploy';
|
import createDeploy from '../../util/deploy/create-deploy';
|
||||||
import eventListenerToGenerator from '../../util/event-listener-to-generator';
|
import eventListenerToGenerator from '../../util/event-listener-to-generator';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import formatLogCmd from '../../util/output/format-log-cmd';
|
|
||||||
// @ts-ignore
|
|
||||||
import formatLogOutput from '../../util/output/format-log-output';
|
import formatLogOutput from '../../util/output/format-log-output';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import getEventsStream from '../../util/deploy/get-events-stream';
|
import getEventsStream from '../../util/deploy/get-events-stream';
|
||||||
|
import shouldDeployDir from '../../util/deploy/should-deploy-dir';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import getInstanceIndex from '../../util/deploy/get-instance-index';
|
import getInstanceIndex from '../../util/deploy/get-instance-index';
|
||||||
import getStateChangeFromPolling from '../../util/deploy/get-state-change-from-polling';
|
|
||||||
import joinWords from '../../util/output/join-words';
|
import joinWords from '../../util/output/join-words';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import normalizeRegionsList from '../../util/scale/normalize-regions-list';
|
import normalizeRegionsList from '../../util/scale/normalize-regions-list';
|
||||||
@@ -67,11 +62,12 @@ import {
|
|||||||
DomainVerificationFailed,
|
DomainVerificationFailed,
|
||||||
TooManyRequests,
|
TooManyRequests,
|
||||||
VerifyScaleTimeout,
|
VerifyScaleTimeout,
|
||||||
DeploymentsRateLimited
|
DeploymentsRateLimited,
|
||||||
|
NotDomainOwner,
|
||||||
} from '../../util/errors-ts';
|
} from '../../util/errors-ts';
|
||||||
import {
|
import {
|
||||||
InvalidAllForScale,
|
InvalidAllForScale,
|
||||||
InvalidRegionOrDCForScale
|
InvalidRegionOrDCForScale,
|
||||||
} from '../../util/errors';
|
} from '../../util/errors';
|
||||||
import { SchemaValidationFailed } from '../../util/errors';
|
import { SchemaValidationFailed } from '../../util/errors';
|
||||||
import handleCertError from '../../util/certs/handle-cert-error';
|
import handleCertError from '../../util/certs/handle-cert-error';
|
||||||
@@ -198,11 +194,10 @@ const promptForEnvFields = async (list: string[]) => {
|
|||||||
for (const field of list) {
|
for (const field of list) {
|
||||||
questions.push({
|
questions.push({
|
||||||
name: field,
|
name: field,
|
||||||
message: field
|
message: field,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-unassigned-import
|
|
||||||
require('../../util/input/patch-inquirer');
|
require('../../util/input/patch-inquirer');
|
||||||
|
|
||||||
log('Please enter values for the following environment variables:');
|
log('Please enter values for the following environment variables:');
|
||||||
@@ -221,7 +216,7 @@ const promptForEnvFields = async (list: string[]) => {
|
|||||||
|
|
||||||
async function canUseZeroConfig(cwd: string): Promise<boolean> {
|
async function canUseZeroConfig(cwd: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const pkg = (await readPackage(join(cwd, 'package.json')));
|
const pkg = await readPackage(join(cwd, 'package.json'));
|
||||||
|
|
||||||
if (!pkg || pkg instanceof Error) {
|
if (!pkg || pkg instanceof Error) {
|
||||||
return false;
|
return false;
|
||||||
@@ -276,6 +271,10 @@ export default async function main(
|
|||||||
paths = [process.cwd()];
|
paths = [process.cwd()];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(await shouldDeployDir(argv._[0], output))) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Options
|
// Options
|
||||||
forceNew = argv.force;
|
forceNew = argv.force;
|
||||||
deploymentName = argv.name;
|
deploymentName = argv.name;
|
||||||
@@ -297,15 +296,27 @@ export default async function main(
|
|||||||
quiet = !isTTY;
|
quiet = !isTTY;
|
||||||
({ log, error, note, debug, warn } = output);
|
({ log, error, note, debug, warn } = output);
|
||||||
|
|
||||||
const infoUrl = await canUseZeroConfig(paths[0])
|
const infoUrl = 'https://zeit.co/guides/migrate-to-zeit-now';
|
||||||
? 'https://zeit.co/guides/migrate-to-zeit-now'
|
|
||||||
: 'https://zeit.co/docs/v2/advanced/platform/changes-in-now-2-0'
|
|
||||||
|
|
||||||
warn(`You are using an old version of the Now Platform. More: ${link(infoUrl)}`);
|
warn(
|
||||||
|
`You are using an old version of the Now Platform. More: ${link(infoUrl)}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (argv.prod || argv.target) {
|
||||||
|
error(
|
||||||
|
`The option ${cmd(
|
||||||
|
argv.prod ? '--prod' : '--target'
|
||||||
|
)} is not supported for Now 1.0 deployments. To manually alias a deployment, use ${cmd(
|
||||||
|
'now alias'
|
||||||
|
)} instead.`
|
||||||
|
);
|
||||||
|
await exit(1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
authConfig: { token },
|
authConfig: { token },
|
||||||
config
|
config,
|
||||||
} = ctx;
|
} = ctx;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -315,7 +326,7 @@ export default async function main(
|
|||||||
token,
|
token,
|
||||||
config,
|
config,
|
||||||
firstRun: true,
|
firstRun: true,
|
||||||
deploymentType: undefined
|
deploymentType: undefined,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
await stopDeployment(err);
|
await stopDeployment(err);
|
||||||
@@ -328,7 +339,7 @@ async function sync({
|
|||||||
token,
|
token,
|
||||||
config: { currentTeam },
|
config: { currentTeam },
|
||||||
firstRun,
|
firstRun,
|
||||||
deploymentType
|
deploymentType,
|
||||||
}: SyncOptions): Promise<void> {
|
}: SyncOptions): Promise<void> {
|
||||||
return new Promise(async (_resolve, reject) => {
|
return new Promise(async (_resolve, reject) => {
|
||||||
let deployStamp = stamp();
|
let deployStamp = stamp();
|
||||||
@@ -477,7 +488,7 @@ async function sync({
|
|||||||
|
|
||||||
// XXX: legacy
|
// XXX: legacy
|
||||||
deploymentType,
|
deploymentType,
|
||||||
sessionAffinity
|
sessionAffinity,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -487,7 +498,7 @@ async function sync({
|
|||||||
meta,
|
meta,
|
||||||
deploymentName,
|
deploymentName,
|
||||||
deploymentType,
|
deploymentType,
|
||||||
sessionAffinity
|
sessionAffinity,
|
||||||
} = await readMeta(
|
} = await readMeta(
|
||||||
paths[0],
|
paths[0],
|
||||||
deploymentName,
|
deploymentName,
|
||||||
@@ -500,7 +511,7 @@ async function sync({
|
|||||||
'dockerfile_missing',
|
'dockerfile_missing',
|
||||||
'no_dockerfile_commands',
|
'no_dockerfile_commands',
|
||||||
'unsupported_deployment_type',
|
'unsupported_deployment_type',
|
||||||
'multiple_manifests'
|
'multiple_manifests',
|
||||||
];
|
];
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -538,7 +549,7 @@ async function sync({
|
|||||||
// Read scale and fail if we have both regions and scale
|
// Read scale and fail if we have both regions and scale
|
||||||
if (regions.length > 0 && Object.keys(scaleFromConfig).length > 0) {
|
if (regions.length > 0 && Object.keys(scaleFromConfig).length > 0) {
|
||||||
error(
|
error(
|
||||||
'Can\'t set both `regions` and `scale` options simultaneously',
|
"Can't set both `regions` and `scale` options simultaneously",
|
||||||
'regions-and-scale-at-once'
|
'regions-and-scale-at-once'
|
||||||
);
|
);
|
||||||
await exit(1);
|
await exit(1);
|
||||||
@@ -549,9 +560,7 @@ async function sync({
|
|||||||
dcIds = normalizeRegionsList(regions);
|
dcIds = normalizeRegionsList(regions);
|
||||||
if (dcIds instanceof InvalidRegionOrDCForScale) {
|
if (dcIds instanceof InvalidRegionOrDCForScale) {
|
||||||
error(
|
error(
|
||||||
`The value "${
|
`The value "${dcIds.meta.regionOrDC}" is not a valid region or DC identifier`
|
||||||
dcIds.meta.regionOrDC
|
|
||||||
}" is not a valid region or DC identifier`
|
|
||||||
);
|
);
|
||||||
await exit(1);
|
await exit(1);
|
||||||
return 1;
|
return 1;
|
||||||
@@ -566,7 +575,7 @@ async function sync({
|
|||||||
scale = dcIds.reduce(
|
scale = dcIds.reduce(
|
||||||
(result: DcScale, dcId: string) => ({
|
(result: DcScale, dcId: string) => ({
|
||||||
...result,
|
...result,
|
||||||
[dcId]: { min: 0, max: 1 }
|
[dcId]: { min: 0, max: 1 },
|
||||||
}),
|
}),
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
@@ -662,8 +671,9 @@ async function sync({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const hasSecrets = Object.keys(deploymentEnv).some(key =>
|
const hasSecrets = Object.keys(deploymentEnv).some(key =>
|
||||||
deploymentEnv[key].startsWith('@')
|
(deploymentEnv[key] || '').startsWith('@')
|
||||||
);
|
);
|
||||||
|
|
||||||
const secretsPromise = hasSecrets ? now.listSecrets() : null;
|
const secretsPromise = hasSecrets ? now.listSecrets() : null;
|
||||||
|
|
||||||
const findSecret = async (uidOrName: string) => {
|
const findSecret = async (uidOrName: string) => {
|
||||||
@@ -755,15 +765,13 @@ async function sync({
|
|||||||
parseMeta(argv.meta)
|
parseMeta(argv.meta)
|
||||||
);
|
);
|
||||||
|
|
||||||
let syncCount;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
meta.name = getProjectName({
|
meta.name = getProjectName({
|
||||||
argv,
|
argv,
|
||||||
nowConfig,
|
nowConfig,
|
||||||
isFile,
|
isFile,
|
||||||
paths,
|
paths,
|
||||||
pre: meta.name
|
pre: meta.name,
|
||||||
});
|
});
|
||||||
log(`Using project ${chalk.bold(meta.name)}`);
|
log(`Using project ${chalk.bold(meta.name)}`);
|
||||||
const createArgs = Object.assign(
|
const createArgs = Object.assign(
|
||||||
@@ -777,13 +785,15 @@ async function sync({
|
|||||||
scale,
|
scale,
|
||||||
wantsPublic,
|
wantsPublic,
|
||||||
sessionAffinity,
|
sessionAffinity,
|
||||||
isFile
|
isFile,
|
||||||
|
nowConfig,
|
||||||
|
deployStamp,
|
||||||
},
|
},
|
||||||
meta
|
meta
|
||||||
);
|
);
|
||||||
|
|
||||||
deployStamp = stamp();
|
deployStamp = stamp();
|
||||||
const firstDeployCall = await createDeploy(
|
deployment = await createDeploy(
|
||||||
output,
|
output,
|
||||||
now,
|
now,
|
||||||
contextName,
|
contextName,
|
||||||
@@ -791,118 +801,24 @@ async function sync({
|
|||||||
createArgs
|
createArgs
|
||||||
);
|
);
|
||||||
|
|
||||||
const handledResult = handleCertError(output, firstDeployCall);
|
const handledResult = handleCertError(output, deployment);
|
||||||
if (handledResult === 1) {
|
if (handledResult === 1) {
|
||||||
return handledResult;
|
return handledResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
firstDeployCall instanceof DomainNotFound ||
|
deployment instanceof DomainNotFound ||
|
||||||
firstDeployCall instanceof DomainPermissionDenied ||
|
deployment instanceof NotDomainOwner ||
|
||||||
firstDeployCall instanceof DomainVerificationFailed ||
|
deployment instanceof DomainPermissionDenied ||
|
||||||
firstDeployCall instanceof SchemaValidationFailed ||
|
deployment instanceof DomainVerificationFailed ||
|
||||||
firstDeployCall instanceof DeploymentNotFound ||
|
deployment instanceof SchemaValidationFailed ||
|
||||||
firstDeployCall instanceof DeploymentsRateLimited
|
deployment instanceof DeploymentNotFound ||
|
||||||
|
deployment instanceof DeploymentsRateLimited
|
||||||
) {
|
) {
|
||||||
handleCreateDeployError(output, firstDeployCall);
|
handleCreateDeployError(output, deployment);
|
||||||
await exit(1);
|
await exit(1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
deployment = firstDeployCall;
|
|
||||||
|
|
||||||
if (now.syncFileCount > 0) {
|
|
||||||
const uploadStamp = stamp();
|
|
||||||
await new Promise(resolve => {
|
|
||||||
if (now.syncFileCount !== now.fileCount) {
|
|
||||||
debug(`Total files ${now.fileCount}, ${now.syncFileCount} changed`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const size = bytes(now.syncAmount);
|
|
||||||
syncCount = `${now.syncFileCount} file${
|
|
||||||
now.syncFileCount > 1 ? 's' : ''
|
|
||||||
}`;
|
|
||||||
const bar = new Progress(
|
|
||||||
`${chalk.gray(
|
|
||||||
'>'
|
|
||||||
)} Upload [:bar] :percent :etas (${size}) [${syncCount}]`,
|
|
||||||
{
|
|
||||||
width: 20,
|
|
||||||
complete: '=',
|
|
||||||
incomplete: '',
|
|
||||||
total: now.syncAmount,
|
|
||||||
clear: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
now.upload({ scale });
|
|
||||||
|
|
||||||
now.on(
|
|
||||||
'upload',
|
|
||||||
({ names, data }: { names: string[]; data: Buffer }) => {
|
|
||||||
debug(`Uploaded: ${names.join(' ')} (${bytes(data.length)})`);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
now.on('uploadProgress', (progress: number) => {
|
|
||||||
bar.tick(progress);
|
|
||||||
});
|
|
||||||
|
|
||||||
now.on('complete', resolve);
|
|
||||||
|
|
||||||
now.on('error', (err: Error) => {
|
|
||||||
error('Upload failed');
|
|
||||||
reject(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!quiet && syncCount) {
|
|
||||||
log(
|
|
||||||
`Synced ${syncCount} (${bytes(now.syncAmount)}) ${uploadStamp()}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < 4; i += 1) {
|
|
||||||
deployStamp = stamp();
|
|
||||||
const secondDeployCall = await createDeploy(
|
|
||||||
output,
|
|
||||||
now,
|
|
||||||
contextName,
|
|
||||||
paths,
|
|
||||||
createArgs
|
|
||||||
);
|
|
||||||
|
|
||||||
const handledResult = handleCertError(output, secondDeployCall);
|
|
||||||
if (handledResult === 1) {
|
|
||||||
return handledResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
secondDeployCall instanceof DomainNotFound ||
|
|
||||||
secondDeployCall instanceof DomainPermissionDenied ||
|
|
||||||
secondDeployCall instanceof DomainVerificationFailed ||
|
|
||||||
secondDeployCall instanceof SchemaValidationFailed ||
|
|
||||||
secondDeployCall instanceof TooManyRequests ||
|
|
||||||
secondDeployCall instanceof DeploymentNotFound ||
|
|
||||||
secondDeployCall instanceof DeploymentsRateLimited
|
|
||||||
) {
|
|
||||||
handleCreateDeployError(output, secondDeployCall);
|
|
||||||
await exit(1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (now.syncFileCount === 0) {
|
|
||||||
deployment = secondDeployCall;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (deployment === null) {
|
|
||||||
error('Uploading failed. Please try again.');
|
|
||||||
await exit(1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.code === 'plan_requires_public') {
|
if (err.code === 'plan_requires_public') {
|
||||||
if (!wantsPublic) {
|
if (!wantsPublic) {
|
||||||
@@ -915,7 +831,7 @@ async function sync({
|
|||||||
|
|
||||||
if (isTTY) {
|
if (isTTY) {
|
||||||
proceed = await promptBool('Are you sure you want to proceed?', {
|
proceed = await promptBool('Are you sure you want to proceed?', {
|
||||||
trailing: eraseLines(1)
|
trailing: eraseLines(1),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -955,10 +871,10 @@ async function sync({
|
|||||||
output,
|
output,
|
||||||
token,
|
token,
|
||||||
config: {
|
config: {
|
||||||
currentTeam
|
currentTeam,
|
||||||
},
|
},
|
||||||
firstRun: false,
|
firstRun: false,
|
||||||
deploymentType
|
deploymentType,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1003,8 +919,6 @@ async function sync({
|
|||||||
} else {
|
} else {
|
||||||
log(`${chalk.bold(chalk.cyan(url))}${dcs} ${deployStamp()}`);
|
log(`${chalk.bold(chalk.cyan(url))}${dcs} ${deployStamp()}`);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
process.stdout.write(url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deploymentType === 'static') {
|
if (deploymentType === 'static') {
|
||||||
@@ -1023,49 +937,8 @@ async function sync({
|
|||||||
// Show build logs
|
// Show build logs
|
||||||
// (We have to add this check for flow but it will never happen)
|
// (We have to add this check for flow but it will never happen)
|
||||||
if (deployment !== null) {
|
if (deployment !== null) {
|
||||||
// If the created deployment is ready it was a deduping and we should exit
|
|
||||||
if (deployment.readyState !== 'READY') {
|
|
||||||
require('assert')(deployment); // mute linter
|
|
||||||
const instanceIndex = getInstanceIndex();
|
const instanceIndex = getInstanceIndex();
|
||||||
const eventsStream = await maybeGetEventsStream(now, deployment);
|
const eventsStream = await maybeGetEventsStream(now, deployment);
|
||||||
const eventsGenerator = getEventsGenerator(
|
|
||||||
now,
|
|
||||||
contextName,
|
|
||||||
deployment,
|
|
||||||
eventsStream
|
|
||||||
);
|
|
||||||
|
|
||||||
for await (const _event of eventsGenerator) {
|
|
||||||
const event = _event as any;
|
|
||||||
// Stop when the deployment is ready
|
|
||||||
if (
|
|
||||||
event.type === 'state-change' &&
|
|
||||||
event.payload.value === 'READY'
|
|
||||||
) {
|
|
||||||
output.log(`Build completed`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop then there is an error state
|
|
||||||
if (
|
|
||||||
event.type === 'state-change' &&
|
|
||||||
event.payload.value === 'ERROR'
|
|
||||||
) {
|
|
||||||
output.error(`Build failed`);
|
|
||||||
await exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For any relevant event we receive, print the result
|
|
||||||
if (event.type === 'build-start') {
|
|
||||||
output.log('Building…');
|
|
||||||
} else if (event.type === 'command') {
|
|
||||||
output.log(formatLogCmd(event.payload.text));
|
|
||||||
} else if (event.type === 'stdout' || event.type === 'stderr') {
|
|
||||||
formatLogOutput(event.payload.text).forEach((msg: string) =>
|
|
||||||
output.log(msg)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!noVerify) {
|
if (!noVerify) {
|
||||||
output.log(
|
output.log(
|
||||||
@@ -1085,12 +958,10 @@ async function sync({
|
|||||||
const dcOrEvent = _dcOrEvent as any;
|
const dcOrEvent = _dcOrEvent as any;
|
||||||
if (dcOrEvent instanceof VerifyScaleTimeout) {
|
if (dcOrEvent instanceof VerifyScaleTimeout) {
|
||||||
output.error(
|
output.error(
|
||||||
`Instance verification timed out (${ms(
|
`Instance verification timed out (${ms(dcOrEvent.meta.timeout)})`
|
||||||
dcOrEvent.meta.timeout
|
|
||||||
)})`
|
|
||||||
);
|
);
|
||||||
output.log(
|
output.log(
|
||||||
'Read more: https://err.sh/now/verification-timeout'
|
'Read more: https://err.sh/now-cli/verification-timeout'
|
||||||
);
|
);
|
||||||
await exit(1);
|
await exit(1);
|
||||||
} else if (Array.isArray(dcOrEvent)) {
|
} else if (Array.isArray(dcOrEvent)) {
|
||||||
@@ -1115,7 +986,6 @@ async function sync({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
output.success(`Deployment ready`);
|
output.success(`Deployment ready`);
|
||||||
await exit(0);
|
await exit(0);
|
||||||
@@ -1134,7 +1004,7 @@ async function readMeta(
|
|||||||
deploymentType,
|
deploymentType,
|
||||||
deploymentName: _deploymentName,
|
deploymentName: _deploymentName,
|
||||||
quiet: true,
|
quiet: true,
|
||||||
sessionAffinity: _sessionAffinity
|
sessionAffinity: _sessionAffinity,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!deploymentType) {
|
if (!deploymentType) {
|
||||||
@@ -1151,7 +1021,7 @@ async function readMeta(
|
|||||||
meta,
|
meta,
|
||||||
deploymentName: _deploymentName,
|
deploymentName: _deploymentName,
|
||||||
deploymentType,
|
deploymentType,
|
||||||
sessionAffinity: _sessionAffinity
|
sessionAffinity: _sessionAffinity,
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (isTTY && err.code === 'multiple_manifests') {
|
if (isTTY && err.code === 'multiple_manifests') {
|
||||||
@@ -1165,7 +1035,7 @@ async function readMeta(
|
|||||||
try {
|
try {
|
||||||
deploymentType = await promptOptions([
|
deploymentType = await promptOptions([
|
||||||
['npm', `${chalk.bold('package.json')}\t${chalk.gray(' --npm')} `],
|
['npm', `${chalk.bold('package.json')}\t${chalk.gray(' --npm')} `],
|
||||||
['docker', `${chalk.bold('Dockerfile')}\t${chalk.gray('--docker')} `]
|
['docker', `${chalk.bold('Dockerfile')}\t${chalk.gray('--docker')} `],
|
||||||
]);
|
]);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
throw err;
|
throw err;
|
||||||
@@ -1191,35 +1061,13 @@ async function maybeGetEventsStream(now: Now, deployment: any) {
|
|||||||
try {
|
try {
|
||||||
return await getEventsStream(now, deployment.deploymentId, {
|
return await getEventsStream(now, deployment.deploymentId, {
|
||||||
direction: 'forward',
|
direction: 'forward',
|
||||||
follow: true
|
follow: true,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEventsGenerator(
|
|
||||||
now: Now,
|
|
||||||
contextName: string,
|
|
||||||
deployment: any,
|
|
||||||
eventsStream: any
|
|
||||||
) {
|
|
||||||
const stateChangeFromPollingGenerator = getStateChangeFromPolling(
|
|
||||||
now,
|
|
||||||
contextName,
|
|
||||||
deployment.deploymentId,
|
|
||||||
deployment.readyState
|
|
||||||
);
|
|
||||||
if (eventsStream !== null) {
|
|
||||||
return combineAsyncGenerators(
|
|
||||||
eventListenerToGenerator('data', eventsStream),
|
|
||||||
stateChangeFromPollingGenerator
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return stateChangeFromPollingGenerator;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getVerifyDCsGenerator(
|
function getVerifyDCsGenerator(
|
||||||
output: Output,
|
output: Output,
|
||||||
now: Now,
|
now: Now,
|
||||||
@@ -1229,7 +1077,7 @@ function getVerifyDCsGenerator(
|
|||||||
const verifyDeployment = verifyDeploymentScale(
|
const verifyDeployment = verifyDeploymentScale(
|
||||||
output,
|
output,
|
||||||
now,
|
now,
|
||||||
deployment.deploymentId,
|
deployment.deploymentId || deployment.uid,
|
||||||
deployment.scale
|
deployment.scale
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1296,9 +1144,9 @@ function handleCreateDeployError(output: Output, error: Error) {
|
|||||||
output.error(
|
output.error(
|
||||||
`Failed to validate ${highlight(
|
`Failed to validate ${highlight(
|
||||||
'now.json'
|
'now.json'
|
||||||
)}: ${message}\nDocumentation: ${
|
)}: ${message}\nDocumentation: ${link(
|
||||||
link('https://zeit.co/docs/v2/advanced/configuration')
|
'https://zeit.co/docs/v2/advanced/configuration'
|
||||||
}`
|
)}`
|
||||||
);
|
);
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
@@ -1308,7 +1156,7 @@ function handleCreateDeployError(output: Output, error: Error) {
|
|||||||
`Too many requests detected for ${error.meta.api} API. Try again in ${ms(
|
`Too many requests detected for ${error.meta.api} API. Try again in ${ms(
|
||||||
error.meta.retryAfter * 1000,
|
error.meta.retryAfter * 1000,
|
||||||
{
|
{
|
||||||
long: true
|
long: true,
|
||||||
}
|
}
|
||||||
)}.`
|
)}.`
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
|
import { PackageJson } from '@now/build-utils';
|
||||||
|
|
||||||
import getArgs from '../../util/get-args';
|
import getArgs from '../../util/get-args';
|
||||||
import getSubcommand from '../../util/get-subcommand';
|
import getSubcommand from '../../util/get-subcommand';
|
||||||
@@ -11,11 +12,10 @@ import logo from '../../util/output/logo';
|
|||||||
import cmd from '../../util/output/cmd';
|
import cmd from '../../util/output/cmd';
|
||||||
import dev from './dev';
|
import dev from './dev';
|
||||||
import readPackage from '../../util/read-package';
|
import readPackage from '../../util/read-package';
|
||||||
import { Package } from '../../util/dev/types';
|
|
||||||
import readConfig from '../../util/config/read-config';
|
import readConfig from '../../util/config/read-config';
|
||||||
|
|
||||||
const COMMAND_CONFIG = {
|
const COMMAND_CONFIG = {
|
||||||
dev: ['dev']
|
dev: ['dev'],
|
||||||
};
|
};
|
||||||
|
|
||||||
const help = () => {
|
const help = () => {
|
||||||
@@ -54,18 +54,12 @@ export default async function main(ctx: NowContext) {
|
|||||||
|
|
||||||
// Deprecated
|
// Deprecated
|
||||||
'--port': Number,
|
'--port': Number,
|
||||||
'-p': '--port'
|
'-p': '--port',
|
||||||
});
|
});
|
||||||
const debug = argv['--debug'];
|
const debug = argv['--debug'];
|
||||||
args = getSubcommand(argv._.slice(1), COMMAND_CONFIG).args;
|
args = getSubcommand(argv._.slice(1), COMMAND_CONFIG).args;
|
||||||
output = createOutput({ debug });
|
output = createOutput({ debug });
|
||||||
|
|
||||||
// Builders won't show debug logs by default
|
|
||||||
// the `NOW_BUILDER_DEBUG` env variable will enable them
|
|
||||||
if (debug) {
|
|
||||||
process.env.NOW_BUILDER_DEBUG = '1';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('--port' in argv) {
|
if ('--port' in argv) {
|
||||||
output.warn('`--port` is deprecated, please use `--listen` instead');
|
output.warn('`--port` is deprecated, please use `--listen` instead');
|
||||||
argv['--listen'] = String(argv['--port']);
|
argv['--listen'] = String(argv['--port']);
|
||||||
@@ -90,7 +84,7 @@ export default async function main(ctx: NowContext) {
|
|||||||
const pkg = await readPackage(path.join(dir, 'package.json'));
|
const pkg = await readPackage(path.join(dir, 'package.json'));
|
||||||
|
|
||||||
if (pkg) {
|
if (pkg) {
|
||||||
const { scripts } = pkg as Package;
|
const { scripts } = pkg as PackageJson;
|
||||||
|
|
||||||
if (scripts && scripts.dev && /\bnow\b\W+\bdev\b/.test(scripts.dev)) {
|
if (scripts && scripts.dev && /\bnow\b\W+\bdev\b/.test(scripts.dev)) {
|
||||||
output.error(
|
output.error(
|
||||||
@@ -98,9 +92,7 @@ export default async function main(ctx: NowContext) {
|
|||||||
'package.json'
|
'package.json'
|
||||||
)} must not contain ${cmd('now dev')}`
|
)} must not contain ${cmd('now dev')}`
|
||||||
);
|
);
|
||||||
output.error(
|
output.error(`More details: http://err.sh/now/now-dev-as-dev-script`);
|
||||||
`More details: http://err.sh/now/now-dev-as-dev-script`
|
|
||||||
);
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export default async function verify(
|
|||||||
) {
|
) {
|
||||||
const {
|
const {
|
||||||
authConfig: { token },
|
authConfig: { token },
|
||||||
config
|
config,
|
||||||
} = ctx;
|
} = ctx;
|
||||||
const { currentTeam } = config;
|
const { currentTeam } = config;
|
||||||
const { apiUrl } = ctx;
|
const { apiUrl } = ctx;
|
||||||
@@ -122,7 +122,15 @@ export default async function verify(
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.txtVerifiedAt) {
|
if (result.nsVerifiedAt) {
|
||||||
|
console.log(
|
||||||
|
`${chalk.cyan('> Success!')} Domain ${chalk.bold(
|
||||||
|
domain.name
|
||||||
|
)} was verified using nameservers. ${verifyStamp()}`
|
||||||
|
);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`${chalk.cyan('> Success!')} Domain ${chalk.bold(
|
`${chalk.cyan('> Success!')} Domain ${chalk.bold(
|
||||||
domain.name
|
domain.name
|
||||||
@@ -135,11 +143,3 @@ export default async function verify(
|
|||||||
);
|
);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(
|
|
||||||
`${chalk.cyan('> Success!')} Domain ${chalk.bold(
|
|
||||||
domain.name
|
|
||||||
)} was verified using nameservers. ${verifyStamp()}`
|
|
||||||
);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -34,6 +34,5 @@ export default new Map([
|
|||||||
['team', 'teams'],
|
['team', 'teams'],
|
||||||
['teams', 'teams'],
|
['teams', 'teams'],
|
||||||
['update', 'update'],
|
['update', 'update'],
|
||||||
[ 'upgrade', 'upgrade'],
|
['whoami', 'whoami'],
|
||||||
[ 'whoami', 'whoami']
|
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export default async function main(ctx) {
|
|||||||
'--all': Boolean,
|
'--all': Boolean,
|
||||||
'--meta': [String],
|
'--meta': [String],
|
||||||
'-a': '--all',
|
'-a': '--all',
|
||||||
'-m': '--meta'
|
'-m': '--meta',
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
handleError(err);
|
handleError(err);
|
||||||
@@ -84,7 +84,7 @@ export default async function main(ctx) {
|
|||||||
const debugEnabled = argv['--debug'];
|
const debugEnabled = argv['--debug'];
|
||||||
|
|
||||||
const { print, log, error, note, debug } = createOutput({
|
const { print, log, error, note, debug } = createOutput({
|
||||||
debug: debugEnabled
|
debug: debugEnabled,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (argv._.length > 2) {
|
if (argv._.length > 2) {
|
||||||
@@ -103,13 +103,16 @@ export default async function main(ctx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const meta = parseMeta(argv['--meta']);
|
const meta = parseMeta(argv['--meta']);
|
||||||
const { authConfig: { token }, config } = ctx;
|
const {
|
||||||
|
authConfig: { token },
|
||||||
|
config,
|
||||||
|
} = ctx;
|
||||||
const { currentTeam, includeScheme } = config;
|
const { currentTeam, includeScheme } = config;
|
||||||
const client = new Client({
|
const client = new Client({
|
||||||
apiUrl,
|
apiUrl,
|
||||||
token,
|
token,
|
||||||
currentTeam,
|
currentTeam,
|
||||||
debug: debugEnabled
|
debug: debugEnabled,
|
||||||
});
|
});
|
||||||
let contextName = null;
|
let contextName = null;
|
||||||
|
|
||||||
@@ -165,7 +168,7 @@ export default async function main(ctx) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
debug('Fetching deployments');
|
debug('Fetching deployments');
|
||||||
deployments = await now.list(app, { version: 4, meta });
|
deployments = await now.list(app, { version: 5, meta });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
stopSpinner();
|
stopSpinner();
|
||||||
throw err;
|
throw err;
|
||||||
@@ -202,7 +205,16 @@ export default async function main(ctx) {
|
|||||||
const item = aliases.find(e => e.uid === app || e.alias === app);
|
const item = aliases.find(e => e.uid === app || e.alias === app);
|
||||||
|
|
||||||
if (item) {
|
if (item) {
|
||||||
debug('Found alias that matches app name');
|
debug(`Found alias that matches app name: ${item.alias}`);
|
||||||
|
|
||||||
|
if (Array.isArray(item.rules)) {
|
||||||
|
now.close();
|
||||||
|
stopSpinner();
|
||||||
|
log(`Found matching path alias: ${chalk.cyan(item.alias)}`);
|
||||||
|
log(`Please run ${cmd(`now alias ls ${item.alias}`)} instead`);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
const match = await now.findDeployment(item.deploymentId);
|
const match = await now.findDeployment(item.deploymentId);
|
||||||
const instances = await getDeploymentInstances(
|
const instances = await getDeploymentInstances(
|
||||||
now,
|
now,
|
||||||
@@ -250,7 +262,9 @@ export default async function main(ctx) {
|
|||||||
|
|
||||||
// information to help the user find other deployments or instances
|
// information to help the user find other deployments or instances
|
||||||
if (app == null) {
|
if (app == null) {
|
||||||
log(`To list more deployments for a project run ${cmd('now ls [project]')}`);
|
log(
|
||||||
|
`To list more deployments for a project run ${cmd('now ls [project]')}`
|
||||||
|
);
|
||||||
} else if (!argv['--all']) {
|
} else if (!argv['--all']) {
|
||||||
log(`To list deployment instances run ${cmd('now ls --all [project]')}`);
|
log(`To list deployment instances run ${cmd('now ls --all [project]')}`);
|
||||||
}
|
}
|
||||||
@@ -260,19 +274,18 @@ export default async function main(ctx) {
|
|||||||
console.log(
|
console.log(
|
||||||
`${table(
|
`${table(
|
||||||
[
|
[
|
||||||
['project', 'latest deployment', 'inst #', 'type', 'state', 'age'].map(s => chalk.dim(s)),
|
['project', 'latest deployment', 'state', 'age', 'username'].map(s =>
|
||||||
|
chalk.dim(s)
|
||||||
|
),
|
||||||
...deployments
|
...deployments
|
||||||
.sort(sortRecent())
|
.sort(sortRecent())
|
||||||
.map(dep => [
|
.map(dep => [
|
||||||
[
|
[
|
||||||
getProjectName(dep),
|
getProjectName(dep),
|
||||||
chalk.bold((includeScheme ? 'https://' : '') + dep.url),
|
chalk.bold((includeScheme ? 'https://' : '') + dep.url),
|
||||||
dep.instanceCount == null || dep.type === 'LAMBDAS'
|
|
||||||
? chalk.gray('-')
|
|
||||||
: dep.instanceCount,
|
|
||||||
dep.type === 'LAMBDAS' ? chalk.gray('-') : dep.type,
|
|
||||||
stateString(dep.state),
|
stateString(dep.state),
|
||||||
chalk.gray(ms(Date.now() - new Date(dep.created)))
|
chalk.gray(ms(Date.now() - new Date(dep.created))),
|
||||||
|
dep.creator.username,
|
||||||
],
|
],
|
||||||
...(argv['--all']
|
...(argv['--all']
|
||||||
? dep.instances.map(i => [
|
? dep.instances.map(i => [
|
||||||
@@ -280,9 +293,9 @@ export default async function main(ctx) {
|
|||||||
` ${chalk.gray('-')} ${i.url} `,
|
` ${chalk.gray('-')} ${i.url} `,
|
||||||
'',
|
'',
|
||||||
'',
|
'',
|
||||||
''
|
'',
|
||||||
])
|
])
|
||||||
: [])
|
: []),
|
||||||
])
|
])
|
||||||
// flatten since the previous step returns a nested
|
// flatten since the previous step returns a nested
|
||||||
// array of the deployment and (optionally) its instances
|
// array of the deployment and (optionally) its instances
|
||||||
@@ -293,12 +306,12 @@ export default async function main(ctx) {
|
|||||||
// we only want to render one deployment per app
|
// we only want to render one deployment per app
|
||||||
filterUniqueApps()
|
filterUniqueApps()
|
||||||
: () => true
|
: () => true
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
align: ['l', 'l', 'r', 'l', 'b'],
|
align: ['l', 'l', 'r', 'l', 'b'],
|
||||||
hsep: ' '.repeat(4),
|
hsep: ' '.repeat(4),
|
||||||
stringLength: strlen
|
stringLength: strlen,
|
||||||
}
|
}
|
||||||
).replace(/^/gm, ' ')}\n\n`
|
).replace(/^/gm, ' ')}\n\n`
|
||||||
);
|
);
|
||||||
@@ -310,7 +323,7 @@ function getProjectName(d) {
|
|||||||
return 'files';
|
return 'files';
|
||||||
}
|
}
|
||||||
|
|
||||||
return d.name
|
return d.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
// renders the state string
|
// renders the state string
|
||||||
|
|||||||
@@ -84,8 +84,8 @@ export default async function main(ctx) {
|
|||||||
debug: 'd',
|
debug: 'd',
|
||||||
query: 'q',
|
query: 'q',
|
||||||
follow: 'f',
|
follow: 'f',
|
||||||
output: 'o'
|
output: 'o',
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
argv._ = argv._.slice(1);
|
argv._ = argv._.slice(1);
|
||||||
@@ -136,14 +136,17 @@ export default async function main(ctx) {
|
|||||||
types = argv.all ? [] : ['command', 'stdout', 'stderr', 'exit'];
|
types = argv.all ? [] : ['command', 'stdout', 'stderr', 'exit'];
|
||||||
outputMode = argv.output in logPrinters ? argv.output : 'short';
|
outputMode = argv.output in logPrinters ? argv.output : 'short';
|
||||||
|
|
||||||
const { authConfig: { token }, config } = ctx;
|
const {
|
||||||
|
authConfig: { token },
|
||||||
|
config,
|
||||||
|
} = ctx;
|
||||||
const { currentTeam } = config;
|
const { currentTeam } = config;
|
||||||
const now = new Now({ apiUrl, token, debug, currentTeam });
|
const now = new Now({ apiUrl, token, debug, currentTeam });
|
||||||
const client = new Client({
|
const client = new Client({
|
||||||
apiUrl,
|
apiUrl,
|
||||||
token,
|
token,
|
||||||
currentTeam,
|
currentTeam,
|
||||||
debug: debugEnabled
|
debug: debugEnabled,
|
||||||
});
|
});
|
||||||
let contextName = null;
|
let contextName = null;
|
||||||
|
|
||||||
@@ -206,7 +209,7 @@ export default async function main(ctx) {
|
|||||||
types,
|
types,
|
||||||
instanceId,
|
instanceId,
|
||||||
since,
|
since,
|
||||||
until
|
until,
|
||||||
}; // no follow
|
}; // no follow
|
||||||
const storage = [];
|
const storage = [];
|
||||||
const storeEvent = event => storage.push(event);
|
const storeEvent = event => storage.push(event);
|
||||||
@@ -216,7 +219,7 @@ export default async function main(ctx) {
|
|||||||
onEvent: storeEvent,
|
onEvent: storeEvent,
|
||||||
quiet: false,
|
quiet: false,
|
||||||
debug,
|
debug,
|
||||||
findOpts: findOpts1
|
findOpts: findOpts1,
|
||||||
});
|
});
|
||||||
|
|
||||||
const printedEventIds = new Set();
|
const printedEventIds = new Set();
|
||||||
@@ -238,14 +241,14 @@ export default async function main(ctx) {
|
|||||||
types,
|
types,
|
||||||
instanceId,
|
instanceId,
|
||||||
since: since2,
|
since: since2,
|
||||||
follow: true
|
follow: true,
|
||||||
};
|
};
|
||||||
await printEvents(now, deployment.uid || deployment.id, currentTeam, {
|
await printEvents(now, deployment.uid || deployment.id, currentTeam, {
|
||||||
mode: 'logs',
|
mode: 'logs',
|
||||||
onEvent: printEvent,
|
onEvent: printEvent,
|
||||||
quiet: false,
|
quiet: false,
|
||||||
debug,
|
debug,
|
||||||
findOpts: findOpts2
|
findOpts: findOpts2,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,25 +283,53 @@ function printLogShort(log) {
|
|||||||
` ${obj.status} ${obj.bodyBytesSent}`;
|
` ${obj.status} ${obj.bodyBytesSent}`;
|
||||||
} else if (log.type === 'event') {
|
} else if (log.type === 'event') {
|
||||||
data = `EVENT ${log.event} ${JSON.stringify(log.payload)}`;
|
data = `EVENT ${log.event} ${JSON.stringify(log.payload)}`;
|
||||||
|
} else if (obj) {
|
||||||
|
data = JSON.stringify(obj, null, 2);
|
||||||
} else {
|
} else {
|
||||||
data = obj
|
data = (log.text || '')
|
||||||
? JSON.stringify(obj, null, 2)
|
|
||||||
: (log.text || '')
|
|
||||||
.replace(/\n$/, '')
|
.replace(/\n$/, '')
|
||||||
.replace(/^\n/, '')
|
.replace(/^\n/, '')
|
||||||
// eslint-disable-next-line no-control-regex
|
// eslint-disable-next-line no-control-regex
|
||||||
.replace(/\x1b\[1000D/g, '')
|
.replace(/\x1b\[1000D/g, '')
|
||||||
.replace(/\x1b\[0K/g, '')
|
.replace(/\x1b\[0K/g, '')
|
||||||
.replace(/\x1b\[1A/g, '');
|
.replace(/\x1b\[1A/g, '');
|
||||||
|
if (/warning/i.test(data)) {
|
||||||
|
data = chalk.yellow(data);
|
||||||
|
} else if (log.type === 'stderr') {
|
||||||
|
data = chalk.red(data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const date = new Date(log.created).toISOString();
|
const date = new Date(log.created).toISOString();
|
||||||
|
|
||||||
data.split('\n').forEach((line, i) => {
|
data.split('\n').forEach((line, i) => {
|
||||||
|
if (
|
||||||
|
line.includes('START RequestId:') ||
|
||||||
|
line.includes('END RequestId:') ||
|
||||||
|
line.includes('XRAY TraceId:')
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.includes('REPORT RequestId:')) {
|
||||||
|
line = line.substring(line.indexOf('Duration:'), line.length);
|
||||||
|
|
||||||
|
if (line.includes('Init Duration:')) {
|
||||||
|
line = line.substring(0, line.indexOf('Init Duration:'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
console.log(`${chalk.dim(date)} ${line}`);
|
console.log(
|
||||||
|
`${chalk.dim(date)} ${line.replace('[now-builder-debug] ', '')}`
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
console.log(`${' '.repeat(date.length)} ${line}`);
|
console.log(
|
||||||
|
`${' '.repeat(date.length)} ${line.replace(
|
||||||
|
'[now-builder-debug] ',
|
||||||
|
''
|
||||||
|
)}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -327,7 +358,7 @@ function printLogRaw(log) {
|
|||||||
|
|
||||||
const logPrinters = {
|
const logPrinters = {
|
||||||
short: printLogShort,
|
short: printLogShort,
|
||||||
raw: printLogRaw
|
raw: printLogRaw,
|
||||||
};
|
};
|
||||||
|
|
||||||
function toTimestamp(datestr) {
|
function toTimestamp(datestr) {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import Client from '../util/client.ts';
|
|||||||
import logo from '../util/output/logo';
|
import logo from '../util/output/logo';
|
||||||
import getScope from '../util/get-scope';
|
import getScope from '../util/get-scope';
|
||||||
|
|
||||||
const e = encodeURIComponent
|
const e = encodeURIComponent;
|
||||||
|
|
||||||
const help = () => {
|
const help = () => {
|
||||||
console.log(`
|
console.log(`
|
||||||
@@ -48,8 +48,8 @@ const main = async ctx => {
|
|||||||
argv = mri(ctx.argv.slice(2), {
|
argv = mri(ctx.argv.slice(2), {
|
||||||
boolean: ['help'],
|
boolean: ['help'],
|
||||||
alias: {
|
alias: {
|
||||||
help: 'h'
|
help: 'h',
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
argv._ = argv._.slice(1);
|
argv._ = argv._.slice(1);
|
||||||
@@ -63,7 +63,10 @@ const main = async ctx => {
|
|||||||
await exit(0);
|
await exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { authConfig: { token }, config: { currentTeam }} = ctx;
|
const {
|
||||||
|
authConfig: { token },
|
||||||
|
config: { currentTeam },
|
||||||
|
} = ctx;
|
||||||
const client = new Client({ apiUrl, token, currentTeam, debug });
|
const client = new Client({ apiUrl, token, currentTeam, debug });
|
||||||
|
|
||||||
const { contextName } = await getScope(client);
|
const { contextName } = await getScope(client);
|
||||||
@@ -93,17 +96,21 @@ async function run({ client, contextName }) {
|
|||||||
if (args.length !== 0) {
|
if (args.length !== 0) {
|
||||||
console.error(
|
console.error(
|
||||||
error(
|
error(
|
||||||
`Invalid number of arguments. Usage: ${chalk.cyan('`now projects ls`')}`
|
`Invalid number of arguments. Usage: ${chalk.cyan(
|
||||||
|
'`now projects ls`'
|
||||||
|
)}`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
return exit(1);
|
return exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const list = await client.fetch('/projects/list', {method: 'GET'});
|
const list = await client.fetch('/v2/projects/', { method: 'GET' });
|
||||||
const elapsed = ms(new Date() - start);
|
const elapsed = ms(new Date() - start);
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`> ${plural('project', list.length, true)} found under ${chalk.bold(contextName)} ${chalk.gray(`[${elapsed}]`)}`
|
`> ${plural('project', list.length, true)} found under ${chalk.bold(
|
||||||
|
contextName
|
||||||
|
)} ${chalk.gray(`[${elapsed}]`)}`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (list.length > 0) {
|
if (list.length > 0) {
|
||||||
@@ -115,13 +122,13 @@ async function run({ client, contextName }) {
|
|||||||
list.map(secret => [
|
list.map(secret => [
|
||||||
'',
|
'',
|
||||||
chalk.bold(secret.name),
|
chalk.bold(secret.name),
|
||||||
chalk.gray(`${ms(cur - new Date(secret.updatedAt)) } ago`)
|
chalk.gray(`${ms(cur - new Date(secret.updatedAt))} ago`),
|
||||||
])
|
])
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
align: ['l', 'l', 'l'],
|
align: ['l', 'l', 'l'],
|
||||||
hsep: ' '.repeat(2),
|
hsep: ' '.repeat(2),
|
||||||
stringLength: strlen
|
stringLength: strlen,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -148,11 +155,11 @@ async function run({ client, contextName }) {
|
|||||||
|
|
||||||
// Check the existence of the project
|
// Check the existence of the project
|
||||||
try {
|
try {
|
||||||
await client.fetch(`/projects/info/${e(name)}`)
|
await client.fetch(`/projects/info/${e(name)}`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.status === 404) {
|
if (err.status === 404) {
|
||||||
console.error(error('No such project exists'))
|
console.error(error('No such project exists'));
|
||||||
return exit(1)
|
return exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,7 +169,9 @@ async function run({ client, contextName }) {
|
|||||||
return exit(0);
|
return exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
await client.fetch('/projects/remove', {method: 'DELETE', body: {name}});
|
await client.fetch(`/v2/projects/${name}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
});
|
||||||
const elapsed = ms(new Date() - start);
|
const elapsed = ms(new Date() - start);
|
||||||
console.log(
|
console.log(
|
||||||
`${chalk.cyan('> Success!')} Project ${chalk.bold(
|
`${chalk.cyan('> Success!')} Project ${chalk.bold(
|
||||||
@@ -193,7 +202,10 @@ async function run({ client, contextName }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const [name] = args;
|
const [name] = args;
|
||||||
await client.fetch('/projects/ensure-project', {method: 'POST', body: {name}});
|
await client.fetch('/projects/ensure-project', {
|
||||||
|
method: 'POST',
|
||||||
|
body: { name },
|
||||||
|
});
|
||||||
const elapsed = ms(new Date() - start);
|
const elapsed = ms(new Date() - start);
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
@@ -204,9 +216,7 @@ async function run({ client, contextName }) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.error(
|
console.error(error('Please specify a valid subcommand: ls | add | rm'));
|
||||||
error('Please specify a valid subcommand: ls | add | rm')
|
|
||||||
);
|
|
||||||
help();
|
help();
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,11 +70,12 @@ let subcommand;
|
|||||||
|
|
||||||
const main = async ctx => {
|
const main = async ctx => {
|
||||||
argv = mri(ctx.argv.slice(2), {
|
argv = mri(ctx.argv.slice(2), {
|
||||||
boolean: ['help', 'debug'],
|
boolean: ['help', 'debug', 'yes'],
|
||||||
alias: {
|
alias: {
|
||||||
help: 'h',
|
help: 'h',
|
||||||
debug: 'd'
|
debug: 'd',
|
||||||
}
|
yes: 'y',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
argv._ = argv._.slice(1);
|
argv._ = argv._.slice(1);
|
||||||
@@ -88,7 +89,10 @@ const main = async ctx => {
|
|||||||
await exit(0);
|
await exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { authConfig: { token }, config: { currentTeam } } = ctx;
|
const {
|
||||||
|
authConfig: { token },
|
||||||
|
config: { currentTeam },
|
||||||
|
} = ctx;
|
||||||
const output = createOutput({ debug });
|
const output = createOutput({ debug });
|
||||||
const client = new Client({ apiUrl, token, currentTeam, debug });
|
const client = new Client({ apiUrl, token, currentTeam, debug });
|
||||||
let contextName = null;
|
let contextName = null;
|
||||||
@@ -105,7 +109,7 @@ const main = async ctx => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await run({ token, contextName, currentTeam });
|
await run({ output, token, contextName, currentTeam });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
handleError(err);
|
handleError(err);
|
||||||
exit(1);
|
exit(1);
|
||||||
@@ -121,7 +125,7 @@ export default async ctx => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
async function run({ token, contextName, currentTeam }) {
|
async function run({ output, token, contextName, currentTeam }) {
|
||||||
const secrets = new NowSecrets({ apiUrl, token, debug, currentTeam });
|
const secrets = new NowSecrets({ apiUrl, token, debug, currentTeam });
|
||||||
const args = argv._.slice(1);
|
const args = argv._.slice(1);
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
@@ -153,13 +157,13 @@ async function run({ token, contextName, currentTeam }) {
|
|||||||
list.map(secret => [
|
list.map(secret => [
|
||||||
'',
|
'',
|
||||||
chalk.bold(secret.name),
|
chalk.bold(secret.name),
|
||||||
chalk.gray(`${ms(cur - new Date(secret.created))} ago`)
|
chalk.gray(`${ms(cur - new Date(secret.created))} ago`),
|
||||||
])
|
])
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
align: ['l', 'l', 'l'],
|
align: ['l', 'l', 'l'],
|
||||||
hsep: ' '.repeat(2),
|
hsep: ' '.repeat(2),
|
||||||
stringLength: strlen
|
stringLength: strlen,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -185,7 +189,7 @@ async function run({ token, contextName, currentTeam }) {
|
|||||||
const theSecret = list.find(secret => secret.name === args[0]);
|
const theSecret = list.find(secret => secret.name === args[0]);
|
||||||
|
|
||||||
if (theSecret) {
|
if (theSecret) {
|
||||||
const yes = await readConfirmation(theSecret);
|
const yes = argv.yes || (await readConfirmation(theSecret));
|
||||||
if (!yes) {
|
if (!yes) {
|
||||||
console.error(error('User abort'));
|
console.error(error('User abort'));
|
||||||
return exit(0);
|
return exit(0);
|
||||||
@@ -250,6 +254,10 @@ async function run({ token, contextName, currentTeam }) {
|
|||||||
await secrets.add(name, value);
|
await secrets.add(name, value);
|
||||||
const elapsed = ms(new Date() - start);
|
const elapsed = ms(new Date() - start);
|
||||||
|
|
||||||
|
if (name !== name.toLowerCase()) {
|
||||||
|
output.warn(`Your secret name was converted to lower-case`);
|
||||||
|
}
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`${chalk.cyan('> Success!')} Secret ${chalk.bold(
|
`${chalk.cyan('> Success!')} Secret ${chalk.bold(
|
||||||
name.toLowerCase()
|
name.toLowerCase()
|
||||||
@@ -275,7 +283,7 @@ function readConfirmation(secret) {
|
|||||||
const time = chalk.gray(`${ms(new Date() - new Date(secret.created))} ago`);
|
const time = chalk.gray(`${ms(new Date() - new Date(secret.created))} ago`);
|
||||||
const tbl = table([[chalk.bold(secret.name), time]], {
|
const tbl = table([[chalk.bold(secret.name), time]], {
|
||||||
align: ['r', 'l'],
|
align: ['r', 'l'],
|
||||||
hsep: ' '.repeat(6)
|
hsep: ' '.repeat(6),
|
||||||
});
|
});
|
||||||
|
|
||||||
process.stdout.write(
|
process.stdout.write(
|
||||||
|
|||||||
@@ -1,323 +0,0 @@
|
|||||||
import chalk from 'chalk';
|
|
||||||
import createOutput from '../util/output';
|
|
||||||
import cmd from '../util/output/cmd.ts';
|
|
||||||
import logo from '../util/output/logo';
|
|
||||||
import { handleError } from '../util/error';
|
|
||||||
import Client from '../util/client.ts';
|
|
||||||
import getScope from '../util/get-scope.ts';
|
|
||||||
import getArgs from '../util/get-args';
|
|
||||||
import promptBool from '../util/prompt-bool';
|
|
||||||
import Now from '../util';
|
|
||||||
import wait from '../util/output/wait';
|
|
||||||
import plans from '../util/plans';
|
|
||||||
|
|
||||||
const help = type => {
|
|
||||||
console.log(`
|
|
||||||
${chalk.bold(`${logo} now ${type}`)} [options]
|
|
||||||
|
|
||||||
${chalk.dim('Options:')}
|
|
||||||
|
|
||||||
-h, --help Output usage information
|
|
||||||
-A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline(
|
|
||||||
'FILE'
|
|
||||||
)} Path to the local ${'`now.json`'} file
|
|
||||||
-Q ${chalk.bold.underline('DIR')}, --global-config=${chalk.bold.underline(
|
|
||||||
'DIR'
|
|
||||||
)} Path to the global ${'`.now`'} directory
|
|
||||||
-d, --debug Debug mode [off]
|
|
||||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
|
||||||
'TOKEN'
|
|
||||||
)} Login token
|
|
||||||
-S, --scope Set a custom scope
|
|
||||||
-y, --yes Skip the confirmation prompt
|
|
||||||
|
|
||||||
${chalk.dim('Examples:')}
|
|
||||||
|
|
||||||
${chalk.gray('–')} ${type === 'upgrade'
|
|
||||||
? 'Upgrade to the Unlimited plan'
|
|
||||||
: 'Downgrade to the Free plan'}
|
|
||||||
|
|
||||||
${chalk.cyan(`$ now ${type}`)}
|
|
||||||
${type === 'upgrade'
|
|
||||||
? `
|
|
||||||
${chalk.yellow('NOTE:')} ${chalk.gray(
|
|
||||||
'Make sure you have a payment method, or add one:'
|
|
||||||
)}
|
|
||||||
|
|
||||||
${chalk.cyan(`$ now billing add`)}
|
|
||||||
`
|
|
||||||
: ''}
|
|
||||||
${chalk.gray('–')} ${type === 'upgrade'
|
|
||||||
? 'Upgrade to the Unlimited plan without confirming'
|
|
||||||
: 'Downgrade to the Free plan without confirming'}
|
|
||||||
|
|
||||||
${chalk.cyan(`$ now ${type} --yes`)}
|
|
||||||
`);
|
|
||||||
};
|
|
||||||
|
|
||||||
const upgradeToUnlimited = async ({ error }, now, reactivation = false) => {
|
|
||||||
const cancelWait = wait(reactivation ? 'Re-activating' : 'Upgrading');
|
|
||||||
|
|
||||||
try {
|
|
||||||
await now.fetch(`/plan`, {
|
|
||||||
method: 'PUT',
|
|
||||||
body: {
|
|
||||||
plan: 'unlimited',
|
|
||||||
reactivation
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
cancelWait();
|
|
||||||
|
|
||||||
if (err.code === 'no_team_owner') {
|
|
||||||
error(
|
|
||||||
`You are not an owner of this team. Please ask an owner to upgrade your membership.`
|
|
||||||
);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err.code === 'customer_not_found') {
|
|
||||||
error(
|
|
||||||
`No payment method available. Please add one using ${cmd(
|
|
||||||
'now billing add'
|
|
||||||
)} before upgrading.`
|
|
||||||
);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
error(`Not able to upgrade. Please try again later.`);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
cancelWait();
|
|
||||||
};
|
|
||||||
|
|
||||||
const downgradeToFree = async ({ error }, now) => {
|
|
||||||
const cancelWait = wait('Downgrading');
|
|
||||||
|
|
||||||
try {
|
|
||||||
await now.fetch(`/plan`, {
|
|
||||||
method: 'PUT',
|
|
||||||
body: {
|
|
||||||
plan: 'free'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
cancelWait();
|
|
||||||
|
|
||||||
if (err.code === 'no_team_owner') {
|
|
||||||
error(
|
|
||||||
`You are not an owner of this team. Please ask an owner to upgrade your membership.`
|
|
||||||
);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
error(`Not able to downgrade. Please try again later.`);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
cancelWait();
|
|
||||||
};
|
|
||||||
|
|
||||||
export default async function main(ctx) {
|
|
||||||
let argv;
|
|
||||||
|
|
||||||
try {
|
|
||||||
argv = getArgs(ctx.argv.slice(2), {
|
|
||||||
'--yes': Boolean,
|
|
||||||
'-y': '--yes'
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
handleError(err);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const type = argv._[0];
|
|
||||||
const skipConfirmation = argv['--yes'];
|
|
||||||
|
|
||||||
if (argv['--help']) {
|
|
||||||
help(type);
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
const apiUrl = ctx.apiUrl;
|
|
||||||
const debugEnabled = argv['--debug'];
|
|
||||||
const output = createOutput({ debug: debugEnabled });
|
|
||||||
const { log, success, warn } = output;
|
|
||||||
if (type === 'upgrade') {
|
|
||||||
log(`Are you trying to upgrade Now CLI? Run ${cmd('now update')}!`);
|
|
||||||
}
|
|
||||||
warn(`${cmd(`now ${type}`)} is deprecated and will soon be removed.`);
|
|
||||||
log(`Change your plan here: ${chalk.cyan('https://zeit.co/account/plan')}\n`);
|
|
||||||
|
|
||||||
const { authConfig: { token }, config } = ctx;
|
|
||||||
const { currentTeam } = config;
|
|
||||||
const client = new Client({
|
|
||||||
apiUrl,
|
|
||||||
token,
|
|
||||||
currentTeam,
|
|
||||||
debug: debugEnabled
|
|
||||||
});
|
|
||||||
let user = null;
|
|
||||||
let team = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
({ user, team } = await getScope(client));
|
|
||||||
} catch (err) {
|
|
||||||
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
|
|
||||||
output.error(err.message);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
const prefix = currentTeam
|
|
||||||
? `Your team ${chalk.bold(team.name)} is`
|
|
||||||
: 'You are';
|
|
||||||
const now = new Now({ apiUrl, token, debug: debugEnabled, currentTeam });
|
|
||||||
const billing = currentTeam ? team.billing : user.billing;
|
|
||||||
const plan = (billing && billing.plan) || 'free';
|
|
||||||
|
|
||||||
if (billing && billing.cancelation) {
|
|
||||||
const date = new Date(billing.cancelation).toLocaleString();
|
|
||||||
|
|
||||||
log(
|
|
||||||
`Your subscription is set to ${chalk.bold('downgrade')} on ${chalk.bold(
|
|
||||||
date
|
|
||||||
)}.`
|
|
||||||
);
|
|
||||||
const confirmed =
|
|
||||||
skipConfirmation ||
|
|
||||||
(await promptBool(
|
|
||||||
output,
|
|
||||||
`Would you like to prevent this from happening?`
|
|
||||||
));
|
|
||||||
|
|
||||||
if (!confirmed) {
|
|
||||||
log(`No action taken`);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
await upgradeToUnlimited(output, now, true);
|
|
||||||
success(`${prefix} back on the ${chalk.bold(plans[plan])} plan. Enjoy!`);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (plan === 'unlimited') {
|
|
||||||
if (type === 'upgrade') {
|
|
||||||
log(
|
|
||||||
`${prefix} already on the ${chalk.bold(
|
|
||||||
'Unlimited'
|
|
||||||
)} plan. This is the highest plan.`
|
|
||||||
);
|
|
||||||
log(
|
|
||||||
`If you want to upgrade a different scope, switch to it by using ${cmd(
|
|
||||||
'now switch'
|
|
||||||
)} first.`
|
|
||||||
);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (type === 'downgrade') {
|
|
||||||
log(`${prefix} on the ${chalk.bold('Unlimited')} plan.`);
|
|
||||||
const confirmed =
|
|
||||||
skipConfirmation ||
|
|
||||||
(await promptBool(
|
|
||||||
output,
|
|
||||||
`Would you like to downgrade to the ${chalk.bold('Free')} plan?`
|
|
||||||
));
|
|
||||||
|
|
||||||
if (!confirmed) {
|
|
||||||
log(`Aborted`);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
await downgradeToFree(output, now);
|
|
||||||
success(
|
|
||||||
`${prefix} now on the ${chalk.bold(
|
|
||||||
'Free'
|
|
||||||
)} plan. We are sad to see you go!`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (plan === 'free' || plan === 'oss') {
|
|
||||||
if (type === 'downgrade') {
|
|
||||||
log(
|
|
||||||
`${prefix} already on the ${chalk.bold(
|
|
||||||
'Free'
|
|
||||||
)} plan. This is the lowest plan.`
|
|
||||||
);
|
|
||||||
log(
|
|
||||||
`If you want to downgrade a different scope, switch to it by using ${cmd(
|
|
||||||
'now switch'
|
|
||||||
)} first.`
|
|
||||||
);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (type === 'upgrade') {
|
|
||||||
log(`${prefix} on the ${chalk.bold('Free')} plan.`);
|
|
||||||
const confirmed =
|
|
||||||
skipConfirmation ||
|
|
||||||
(await promptBool(
|
|
||||||
output,
|
|
||||||
`Would you like to upgrade to the ${chalk.bold(
|
|
||||||
'Unlimited'
|
|
||||||
)} plan (starting at ${chalk.bold('$0.99/month')})?`
|
|
||||||
));
|
|
||||||
|
|
||||||
if (!confirmed) {
|
|
||||||
log(`Aborted`);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
await upgradeToUnlimited(output, now);
|
|
||||||
success(`${prefix} now on the ${chalk.bold('Unlimited')} plan. Enjoy!`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log(`${prefix} on the old ${chalk.bold(plans[plan])} plan (Now 1.0).`);
|
|
||||||
|
|
||||||
if (type === 'upgrade') {
|
|
||||||
const confirmed =
|
|
||||||
skipConfirmation ||
|
|
||||||
(await promptBool(
|
|
||||||
output,
|
|
||||||
`Would you like to upgrade to the new ${chalk.bold(
|
|
||||||
'Unlimited'
|
|
||||||
)} plan (starting at ${chalk.bold('$0.99/month')})?`
|
|
||||||
));
|
|
||||||
|
|
||||||
if (!confirmed) {
|
|
||||||
log(`Aborted`);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
await upgradeToUnlimited(output, now);
|
|
||||||
success(`${prefix} now on the new ${chalk.bold('Unlimited')} plan. Enjoy!`);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const confirmed =
|
|
||||||
skipConfirmation ||
|
|
||||||
(await promptBool(
|
|
||||||
output,
|
|
||||||
`Would you like to downgrade to the new ${chalk.bold('Free')} plan?`
|
|
||||||
));
|
|
||||||
|
|
||||||
if (!confirmed) {
|
|
||||||
log(`Aborted`);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
await downgradeToFree(output, now);
|
|
||||||
success(
|
|
||||||
`${prefix} now on the new ${chalk.bold(
|
|
||||||
'Free'
|
|
||||||
)} plan. We are sad to see you go!`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -17,7 +17,7 @@ import info from './util/output/info';
|
|||||||
import getNowDir from './util/config/global-path';
|
import getNowDir from './util/config/global-path';
|
||||||
import {
|
import {
|
||||||
getDefaultConfig,
|
getDefaultConfig,
|
||||||
getDefaultAuthConfig
|
getDefaultAuthConfig,
|
||||||
} from './util/config/get-default';
|
} from './util/config/get-default';
|
||||||
import hp from './util/humanize-path';
|
import hp from './util/humanize-path';
|
||||||
import commands from './commands/index.ts';
|
import commands from './commands/index.ts';
|
||||||
@@ -29,15 +29,15 @@ import getUser from './util/get-user.ts';
|
|||||||
import Client from './util/client.ts';
|
import Client from './util/client.ts';
|
||||||
import NowTeams from './util/teams';
|
import NowTeams from './util/teams';
|
||||||
import cmd from './util/output/cmd';
|
import cmd from './util/output/cmd';
|
||||||
import highlight from './util/output/highlight';
|
|
||||||
import { handleError } from './util/error';
|
import { handleError } from './util/error';
|
||||||
|
import highlight from './util/output/highlight';
|
||||||
import reportError from './util/report-error';
|
import reportError from './util/report-error';
|
||||||
import getConfig from './util/get-config';
|
import getConfig from './util/get-config';
|
||||||
import * as ERRORS from './util/errors-ts';
|
import * as ERRORS from './util/errors-ts';
|
||||||
import { NowError } from './util/now-error';
|
import { NowError } from './util/now-error';
|
||||||
import { SENTRY_DSN } from './util/constants.ts';
|
import { SENTRY_DSN } from './util/constants.ts';
|
||||||
import { metrics, shouldCollectMetrics } from './util/metrics.ts';
|
|
||||||
import getUpdateCommand from './util/get-update-command';
|
import getUpdateCommand from './util/get-update-command';
|
||||||
|
import { metrics, shouldCollectMetrics } from './util/metrics.ts';
|
||||||
|
|
||||||
const NOW_DIR = getNowDir();
|
const NOW_DIR = getNowDir();
|
||||||
const NOW_CONFIG_PATH = configFiles.getConfigFilePath();
|
const NOW_CONFIG_PATH = configFiles.getConfigFilePath();
|
||||||
@@ -53,7 +53,7 @@ sourceMap.install();
|
|||||||
Sentry.init({
|
Sentry.init({
|
||||||
dsn: SENTRY_DSN,
|
dsn: SENTRY_DSN,
|
||||||
release: `now-cli@${pkg.version}`,
|
release: `now-cli@${pkg.version}`,
|
||||||
environment: pkg.version.includes('canary') ? 'canary' : 'stable'
|
environment: pkg.version.includes('canary') ? 'canary' : 'stable',
|
||||||
});
|
});
|
||||||
|
|
||||||
let debug = () => {};
|
let debug = () => {};
|
||||||
@@ -71,7 +71,7 @@ const main = async argv_ => {
|
|||||||
'--version': Boolean,
|
'--version': Boolean,
|
||||||
'-v': '--version',
|
'-v': '--version',
|
||||||
'--debug': Boolean,
|
'--debug': Boolean,
|
||||||
'-d': '--debug'
|
'-d': '--debug',
|
||||||
},
|
},
|
||||||
{ permissive: true }
|
{ permissive: true }
|
||||||
);
|
);
|
||||||
@@ -102,7 +102,10 @@ const main = async argv_ => {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (localConfig instanceof NowError && !(localConfig instanceof ERRORS.CantFindConfig)) {
|
if (
|
||||||
|
localConfig instanceof NowError &&
|
||||||
|
!(localConfig instanceof ERRORS.CantFindConfig)
|
||||||
|
) {
|
||||||
output.error(`Failed to load local config file: ${localConfig.message}`);
|
output.error(`Failed to load local config file: ${localConfig.message}`);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -118,7 +121,7 @@ const main = async argv_ => {
|
|||||||
if (targetOrSubcommand !== 'update') {
|
if (targetOrSubcommand !== 'update') {
|
||||||
update = await checkForUpdate(pkg, {
|
update = await checkForUpdate(pkg, {
|
||||||
interval: ms('1d'),
|
interval: ms('1d'),
|
||||||
distTag: pkg.version.includes('canary') ? 'canary' : 'latest'
|
distTag: pkg.version.includes('canary') ? 'canary' : 'latest',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -135,7 +138,15 @@ const main = async argv_ => {
|
|||||||
console.log(
|
console.log(
|
||||||
info(
|
info(
|
||||||
`${chalk.bgRed('UPDATE AVAILABLE')} ` +
|
`${chalk.bgRed('UPDATE AVAILABLE')} ` +
|
||||||
`Run ${cmd(await getUpdateCommand())} to install Now CLI ${update.latest}`
|
`Run ${cmd(await getUpdateCommand())} to install Now CLI ${
|
||||||
|
update.latest
|
||||||
|
}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
info(
|
||||||
|
`Changelog: https://github.com/zeit/now/releases/tag/now@${update.latest}`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -307,9 +318,9 @@ const main = async argv_ => {
|
|||||||
console.error(
|
console.error(
|
||||||
error(
|
error(
|
||||||
`${'An unexpected error occurred while trying to write the ' +
|
`${'An unexpected error occurred while trying to write the ' +
|
||||||
`default now config to "${hp(
|
`default now config to "${hp(NOW_AUTH_CONFIG_PATH)}" `}${
|
||||||
NOW_AUTH_CONFIG_PATH
|
err.message
|
||||||
)}" `}${err.message}`
|
}`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
return 1;
|
return 1;
|
||||||
@@ -329,7 +340,7 @@ const main = async argv_ => {
|
|||||||
config,
|
config,
|
||||||
authConfig,
|
authConfig,
|
||||||
localConfig,
|
localConfig,
|
||||||
argv: argv_
|
argv: argv_,
|
||||||
};
|
};
|
||||||
|
|
||||||
let subcommand;
|
let subcommand;
|
||||||
@@ -339,7 +350,8 @@ const main = async argv_ => {
|
|||||||
const targetPath = join(process.cwd(), targetOrSubcommand);
|
const targetPath = join(process.cwd(), targetOrSubcommand);
|
||||||
const targetPathExists = existsSync(targetPath);
|
const targetPathExists = existsSync(targetPath);
|
||||||
const subcommandExists =
|
const subcommandExists =
|
||||||
GLOBAL_COMMANDS.has(targetOrSubcommand) || commands.has(targetOrSubcommand);
|
GLOBAL_COMMANDS.has(targetOrSubcommand) ||
|
||||||
|
commands.has(targetOrSubcommand);
|
||||||
|
|
||||||
if (targetPathExists && subcommandExists) {
|
if (targetPathExists && subcommandExists) {
|
||||||
console.error(
|
console.error(
|
||||||
@@ -412,7 +424,7 @@ const main = async argv_ => {
|
|||||||
message:
|
message:
|
||||||
'No existing credentials found. Please run ' +
|
'No existing credentials found. Please run ' +
|
||||||
`${param('now login')} or pass ${param('--token')}`,
|
`${param('now login')} or pass ${param('--token')}`,
|
||||||
slug: 'no-credentials-found'
|
slug: 'no-credentials-found',
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -426,7 +438,7 @@ const main = async argv_ => {
|
|||||||
message: `This command doesn't work with ${param(
|
message: `This command doesn't work with ${param(
|
||||||
'--token'
|
'--token'
|
||||||
)}. Please use ${param('--scope')}.`,
|
)}. Please use ${param('--scope')}.`,
|
||||||
slug: 'no-token-allowed'
|
slug: 'no-token-allowed',
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -440,7 +452,7 @@ const main = async argv_ => {
|
|||||||
console.error(
|
console.error(
|
||||||
error({
|
error({
|
||||||
message: `You defined ${param('--token')}, but it's missing a value`,
|
message: `You defined ${param('--token')}, but it's missing a value`,
|
||||||
slug: 'missing-token-value'
|
slug: 'missing-token-value',
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -459,11 +471,22 @@ const main = async argv_ => {
|
|||||||
const targetCommand = commands.get(subcommand);
|
const targetCommand = commands.get(subcommand);
|
||||||
|
|
||||||
if (argv['--team']) {
|
if (argv['--team']) {
|
||||||
output.warn(`The ${param('--team')} flag is deprecated. Please use ${param('--scope')} instead.`);
|
output.warn(
|
||||||
|
`The ${param('--team')} flag is deprecated. Please use ${param(
|
||||||
|
'--scope'
|
||||||
|
)} instead.`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof scope === 'string' && targetCommand !== 'login' && targetCommand !== 'dev' && !(targetCommand === 'teams' && argv._[3] !== 'invite')) {
|
if (
|
||||||
const { authConfig: { token } } = ctx;
|
typeof scope === 'string' &&
|
||||||
|
targetCommand !== 'login' &&
|
||||||
|
targetCommand !== 'dev' &&
|
||||||
|
!(targetCommand === 'teams' && argv._[3] !== 'invite')
|
||||||
|
) {
|
||||||
|
const {
|
||||||
|
authConfig: { token },
|
||||||
|
} = ctx;
|
||||||
const client = new Client({ apiUrl, token });
|
const client = new Client({ apiUrl, token });
|
||||||
|
|
||||||
let user = null;
|
let user = null;
|
||||||
@@ -475,7 +498,7 @@ const main = async argv_ => {
|
|||||||
console.error(
|
console.error(
|
||||||
error({
|
error({
|
||||||
message: `You do not have access to the specified account`,
|
message: `You do not have access to the specified account`,
|
||||||
slug: 'scope-not-accessible'
|
slug: 'scope-not-accessible',
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -499,7 +522,7 @@ const main = async argv_ => {
|
|||||||
console.error(
|
console.error(
|
||||||
error({
|
error({
|
||||||
message: `You do not have access to the specified team`,
|
message: `You do not have access to the specified team`,
|
||||||
slug: 'scope-not-accessible'
|
slug: 'scope-not-accessible',
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -517,7 +540,7 @@ const main = async argv_ => {
|
|||||||
console.error(
|
console.error(
|
||||||
error({
|
error({
|
||||||
message: 'The specified scope does not exist',
|
message: 'The specified scope does not exist',
|
||||||
slug: 'scope-not-existent'
|
slug: 'scope-not-existent',
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -577,7 +600,8 @@ const main = async argv_ => {
|
|||||||
if (shouldCollectMetrics) {
|
if (shouldCollectMetrics) {
|
||||||
metric
|
metric
|
||||||
.event(eventCategory, '1', pkg.version)
|
.event(eventCategory, '1', pkg.version)
|
||||||
.exception(err.message).send();
|
.exception(err.message)
|
||||||
|
.send();
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
@@ -586,7 +610,8 @@ const main = async argv_ => {
|
|||||||
if (shouldCollectMetrics) {
|
if (shouldCollectMetrics) {
|
||||||
metric
|
metric
|
||||||
.event(eventCategory, '1', pkg.version)
|
.event(eventCategory, '1', pkg.version)
|
||||||
.exception(err.message).send();
|
.exception(err.message)
|
||||||
|
.send();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise it is an unexpected error and we should show the trace
|
// Otherwise it is an unexpected error and we should show the trace
|
||||||
@@ -647,9 +672,7 @@ process.on('uncaughtException', handleUnexpected);
|
|||||||
// subcommands waiting for further data won't work (like `logs` and `logout`)!
|
// subcommands waiting for further data won't work (like `logs` and `logout`)!
|
||||||
main(process.argv)
|
main(process.argv)
|
||||||
.then(exitCode => {
|
.then(exitCode => {
|
||||||
|
process.exitCode = exitCode;
|
||||||
process.emit('nowExit');
|
process.emit('nowExit');
|
||||||
process.on('beforeExit', () => {
|
|
||||||
process.exit(exitCode);
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
.catch(handleUnexpected);
|
.catch(handleUnexpected);
|
||||||
|
|||||||
@@ -195,28 +195,31 @@ export type DNSRecord = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type SRVRecordData = {
|
type SRVRecordData = {
|
||||||
name: string,
|
name: string;
|
||||||
type: 'SRV',
|
type: 'SRV';
|
||||||
srv: {
|
srv: {
|
||||||
port: number,
|
port: number;
|
||||||
priority: number,
|
priority: number;
|
||||||
target: string,
|
target: string;
|
||||||
weight: number,
|
weight: number;
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
type MXRecordData = {
|
|
||||||
name: string,
|
|
||||||
type: 'MX',
|
|
||||||
value: string,
|
|
||||||
mxPriority: number,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DNSRecordData = {
|
type MXRecordData = {
|
||||||
name: string,
|
name: string;
|
||||||
type: string,
|
type: 'MX';
|
||||||
value: string,
|
value: string;
|
||||||
} | SRVRecordData | MXRecordData;
|
mxPriority: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DNSRecordData =
|
||||||
|
| {
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
| SRVRecordData
|
||||||
|
| MXRecordData;
|
||||||
|
|
||||||
export interface Project {
|
export interface Project {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -1,142 +0,0 @@
|
|||||||
import { JsonBody, StreamBody, context } from 'fetch-h2';
|
|
||||||
|
|
||||||
// Packages
|
|
||||||
import { parse } from 'url';
|
|
||||||
import Sema from 'async-sema';
|
|
||||||
import createOutput, { Output } from './output/create-output';
|
|
||||||
|
|
||||||
const MAX_REQUESTS_PER_CONNECTION = 1000;
|
|
||||||
|
|
||||||
type CurrentContext = ReturnType<typeof context> & {
|
|
||||||
fetchesMade: number;
|
|
||||||
ongoingFetches: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface AgentFetchOptions {
|
|
||||||
method?: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
|
|
||||||
body?: NodeJS.ReadableStream | string;
|
|
||||||
headers: { [key: string]: string };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a `fetch` version with a similar API to the browser's configured with a
|
|
||||||
* HTTP2 agent. It encodes `body` automatically as JSON.
|
|
||||||
*
|
|
||||||
* @param {String} host
|
|
||||||
* @return {Function} fetch
|
|
||||||
*/
|
|
||||||
export default class NowAgent {
|
|
||||||
_contexts: ReturnType<typeof context>[];
|
|
||||||
_currContext: CurrentContext;
|
|
||||||
_output: Output;
|
|
||||||
_protocol?: string;
|
|
||||||
_sema: Sema;
|
|
||||||
_url: string;
|
|
||||||
|
|
||||||
constructor(url: string, { debug = false } = {}) {
|
|
||||||
// We use multiple contexts because each context represent one connection
|
|
||||||
// With nginx, we're limited to 1000 requests before a connection is closed
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_v2_module.html#http2_max_requests
|
|
||||||
// To get arround this, we keep track of requests made on a connection. when we're about to hit 1000
|
|
||||||
// we start up a new connection, and re-route all future traffic through the new connection
|
|
||||||
// and when the final request from the old connection resolves, we auto-close the old connection
|
|
||||||
this._contexts = [context()];
|
|
||||||
this._currContext = {
|
|
||||||
...this._contexts[0],
|
|
||||||
fetchesMade: 0,
|
|
||||||
ongoingFetches: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
const parsed = parse(url);
|
|
||||||
this._url = url;
|
|
||||||
this._protocol = parsed.protocol;
|
|
||||||
this._sema = new Sema(20);
|
|
||||||
this._output = createOutput({ debug });
|
|
||||||
}
|
|
||||||
|
|
||||||
setConcurrency({
|
|
||||||
maxStreams,
|
|
||||||
capacity
|
|
||||||
}: {
|
|
||||||
maxStreams: number;
|
|
||||||
capacity: number;
|
|
||||||
}) {
|
|
||||||
this._sema = new Sema(maxStreams || 20, { capacity });
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetch(path: string, opts: AgentFetchOptions) {
|
|
||||||
const { debug } = this._output;
|
|
||||||
await this._sema.acquire();
|
|
||||||
let currentContext: CurrentContext;
|
|
||||||
this._currContext.fetchesMade++;
|
|
||||||
if (this._currContext.fetchesMade >= MAX_REQUESTS_PER_CONNECTION) {
|
|
||||||
const ctx = { ...context(), fetchesMade: 1, ongoingFetches: 0 };
|
|
||||||
this._contexts.push(ctx);
|
|
||||||
this._currContext = ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're changing contexts, we don't want to record the ongoingFetch on the old context
|
|
||||||
// That'll cause an off-by-one error when trying to close the old socket later
|
|
||||||
this._currContext.ongoingFetches++;
|
|
||||||
currentContext = this._currContext;
|
|
||||||
|
|
||||||
debug(
|
|
||||||
`Total requests made on socket #${this._contexts.length}: ${this
|
|
||||||
._currContext.fetchesMade}`
|
|
||||||
);
|
|
||||||
debug(
|
|
||||||
`Concurrent requests on socket #${this._contexts.length}: ${this
|
|
||||||
._currContext.ongoingFetches}`
|
|
||||||
);
|
|
||||||
|
|
||||||
let body: JsonBody | StreamBody | string | undefined;
|
|
||||||
if (opts.body && typeof opts.body === 'object') {
|
|
||||||
if (typeof (<NodeJS.ReadableStream>opts.body).pipe === 'function') {
|
|
||||||
body = new StreamBody(<NodeJS.ReadableStream>opts.body);
|
|
||||||
} else {
|
|
||||||
opts.headers['Content-Type'] = 'application/json';
|
|
||||||
body = new JsonBody(opts.body);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
body = opts.body;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { host, protocol } = parse(path);
|
|
||||||
const url = host ? `${protocol}//${host}` : this._url;
|
|
||||||
const handleCompleted = async <T>(res: T) => {
|
|
||||||
currentContext.ongoingFetches--;
|
|
||||||
if (
|
|
||||||
(currentContext !== this._currContext || host) &&
|
|
||||||
currentContext.ongoingFetches <= 0
|
|
||||||
) {
|
|
||||||
// We've completely moved on to a new socket
|
|
||||||
// close the old one
|
|
||||||
|
|
||||||
// TODO: Fix race condition:
|
|
||||||
// If the response is a stream, and the server is still streaming data
|
|
||||||
// we should check if the stream has closed before disconnecting
|
|
||||||
// hasCompleted CAN technically be called before the res body stream is closed
|
|
||||||
debug('Closing old socket');
|
|
||||||
currentContext.disconnect(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._sema.release();
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
return currentContext
|
|
||||||
.fetch((host ? '' : this._url) + path, { ...opts, body })
|
|
||||||
.then(res => handleCompleted(res))
|
|
||||||
.catch((err: Error) => {
|
|
||||||
handleCompleted(null);
|
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
close() {
|
|
||||||
const { debug } = this._output;
|
|
||||||
debug('Closing agent');
|
|
||||||
|
|
||||||
this._currContext.disconnect(this._url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -34,6 +34,11 @@ export default async function getDeploymentForAlias(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const appName = await getAppName(output, localConfig, localConfigPath);
|
const appName = await getAppName(output, localConfig, localConfigPath);
|
||||||
|
|
||||||
|
if (!appName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const deployment = await getAppLastDeployment(
|
const deployment = await getAppLastDeployment(
|
||||||
output,
|
output,
|
||||||
client,
|
client,
|
||||||
|
|||||||
@@ -7,7 +7,11 @@ export default async function getInferredTargets(
|
|||||||
output: Output,
|
output: Output,
|
||||||
config: Config
|
config: Config
|
||||||
) {
|
) {
|
||||||
output.warn(`The ${cmd('now alias')} command (no arguments) was deprecated in favour of ${cmd('now --prod')}.`);
|
output.warn(
|
||||||
|
`The ${cmd(
|
||||||
|
'now alias'
|
||||||
|
)} command (no arguments) was deprecated in favor of ${cmd('now --prod')}.`
|
||||||
|
);
|
||||||
|
|
||||||
// This field is deprecated, warn about it
|
// This field is deprecated, warn about it
|
||||||
if (config.aliases) {
|
if (config.aliases) {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import qs from 'querystring';
|
import qs from 'querystring';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { parse as parseUrl } from 'url';
|
import { parse as parseUrl } from 'url';
|
||||||
|
import fetch, { RequestInit } from 'node-fetch';
|
||||||
import retry, { RetryFunction, Options as RetryOptions } from 'async-retry';
|
import retry, { RetryFunction, Options as RetryOptions } from 'async-retry';
|
||||||
import createOutput, { Output } from './output/create-output';
|
import createOutput, { Output } from './output/create-output';
|
||||||
import Agent, { AgentFetchOptions } from './agent';
|
|
||||||
import responseError from './response-error';
|
import responseError from './response-error';
|
||||||
import ua from './ua';
|
import ua from './ua';
|
||||||
|
|
||||||
@@ -17,7 +17,6 @@ export type FetchOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default class Client extends EventEmitter {
|
export default class Client extends EventEmitter {
|
||||||
_agent: Agent;
|
|
||||||
_apiUrl: string;
|
_apiUrl: string;
|
||||||
_debug: boolean;
|
_debug: boolean;
|
||||||
_forceNew: boolean;
|
_forceNew: boolean;
|
||||||
@@ -30,7 +29,7 @@ export default class Client extends EventEmitter {
|
|||||||
token,
|
token,
|
||||||
currentTeam,
|
currentTeam,
|
||||||
forceNew = false,
|
forceNew = false,
|
||||||
debug = false
|
debug = false,
|
||||||
}: {
|
}: {
|
||||||
apiUrl: string;
|
apiUrl: string;
|
||||||
token: string;
|
token: string;
|
||||||
@@ -44,30 +43,23 @@ export default class Client extends EventEmitter {
|
|||||||
this._forceNew = forceNew;
|
this._forceNew = forceNew;
|
||||||
this._output = createOutput({ debug });
|
this._output = createOutput({ debug });
|
||||||
this._apiUrl = apiUrl;
|
this._apiUrl = apiUrl;
|
||||||
this._agent = new Agent(apiUrl, { debug });
|
|
||||||
this._onRetry = this._onRetry.bind(this);
|
this._onRetry = this._onRetry.bind(this);
|
||||||
this.currentTeam = currentTeam;
|
this.currentTeam = currentTeam;
|
||||||
|
|
||||||
const closeAgent = () => {
|
|
||||||
this._agent.close();
|
|
||||||
process.removeListener('nowExit', closeAgent);
|
|
||||||
};
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
process.on('nowExit', closeAgent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
retry<T>(fn: RetryFunction<T>, { retries = 3, maxTimeout = Infinity } = {}) {
|
retry<T>(fn: RetryFunction<T>, { retries = 3, maxTimeout = Infinity } = {}) {
|
||||||
return retry(fn, {
|
return retry(fn, {
|
||||||
retries,
|
retries,
|
||||||
maxTimeout,
|
maxTimeout,
|
||||||
onRetry: this._onRetry
|
onRetry: this._onRetry,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_fetch(_url: string, opts: FetchOptions = {}) {
|
_fetch(_url: string, opts: FetchOptions = {}) {
|
||||||
const parsedUrl = parseUrl(_url, true);
|
const parsedUrl = parseUrl(_url, true);
|
||||||
const apiUrl = parsedUrl.host ? `${parsedUrl.protocol}//${parsedUrl.host}` : '';
|
const apiUrl = parsedUrl.host
|
||||||
|
? `${parsedUrl.protocol}//${parsedUrl.host}`
|
||||||
|
: '';
|
||||||
|
|
||||||
if (opts.useCurrentTeam !== false && this.currentTeam) {
|
if (opts.useCurrentTeam !== false && this.currentTeam) {
|
||||||
const query = parsedUrl.query;
|
const query = parsedUrl.query;
|
||||||
@@ -80,20 +72,19 @@ export default class Client extends EventEmitter {
|
|||||||
Object.assign(opts, {
|
Object.assign(opts, {
|
||||||
body: JSON.stringify(opts.body),
|
body: JSON.stringify(opts.body),
|
||||||
headers: Object.assign({}, opts.headers, {
|
headers: Object.assign({}, opts.headers, {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json',
|
||||||
})
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
opts.headers = opts.headers || {};
|
opts.headers = opts.headers || {};
|
||||||
opts.headers.authorization = `Bearer ${this._token}`;
|
opts.headers.Authorization = `Bearer ${this._token}`;
|
||||||
opts.headers['user-agent'] = ua;
|
opts.headers['user-agent'] = ua;
|
||||||
|
|
||||||
|
const url = `${apiUrl ? '' : this._apiUrl}${_url}`;
|
||||||
return this._output.time(
|
return this._output.time(
|
||||||
`${opts.method || 'GET'} ${apiUrl ? '' : this._apiUrl}${_url} ${JSON.stringify(
|
`${opts.method || 'GET'} ${url} ${JSON.stringify(opts.body) || ''}`,
|
||||||
opts.body
|
fetch(url, opts as RequestInit)
|
||||||
) || ''}`,
|
|
||||||
this._agent.fetch(_url, opts as AgentFetchOptions)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +117,5 @@ export default class Client extends EventEmitter {
|
|||||||
this._output.debug(`Retrying: ${error}\n${error.stack}`);
|
this._output.debug(`Retrying: ${error}\n${error.stack}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
close() {
|
close() {}
|
||||||
this._agent.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,40 +15,45 @@ export default async function createDeploy(
|
|||||||
return await now.create(paths, createArgs);
|
return await now.create(paths, createArgs);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.code === 'rate_limited') {
|
if (error.code === 'rate_limited') {
|
||||||
return new ERRORS_TS.DeploymentsRateLimited(error.message);
|
throw new ERRORS_TS.DeploymentsRateLimited(error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Means that the domain used as a suffix no longer exists
|
// Means that the domain used as a suffix no longer exists
|
||||||
if (error.code === 'domain_missing') {
|
if (error.code === 'domain_missing') {
|
||||||
return new ERRORS_TS.DomainNotFound(error.value);
|
throw new ERRORS_TS.DomainNotFound(error.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error.code === 'domain_not_found' && error.domain) {
|
if (error.code === 'domain_not_found' && error.domain) {
|
||||||
return new ERRORS_TS.DomainNotFound(error.domain);
|
throw new ERRORS_TS.DomainNotFound(error.domain);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This error occures when a domain used in the `alias`
|
// This error occures when a domain used in the `alias`
|
||||||
// is not yet verified
|
// is not yet verified
|
||||||
if (error.code === 'domain_not_verified' && error.domain) {
|
if (error.code === 'domain_not_verified' && error.domain) {
|
||||||
return new ERRORS_TS.DomainNotVerified(error.domain);
|
throw new ERRORS_TS.DomainNotVerified(error.domain);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the domain used as a suffix is not verified, we fail
|
// If the domain used as a suffix is not verified, we fail
|
||||||
if (error.code === 'domain_not_verified' && error.value) {
|
if (error.code === 'domain_not_verified' && error.value) {
|
||||||
return new ERRORS_TS.DomainVerificationFailed(error.value);
|
throw new ERRORS_TS.DomainVerificationFailed(error.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the domain isn't owned by the user
|
||||||
|
if (error.code === 'not_domain_owner') {
|
||||||
|
throw new ERRORS_TS.NotDomainOwner(error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error.code === 'builds_rate_limited') {
|
if (error.code === 'builds_rate_limited') {
|
||||||
return new ERRORS_TS.BuildsRateLimited(error.message);
|
throw new ERRORS_TS.BuildsRateLimited(error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the user doesn't have permissions over the domain used as a suffix we fail
|
// If the user doesn't have permissions over the domain used as a suffix we fail
|
||||||
if (error.code === 'forbidden') {
|
if (error.code === 'forbidden') {
|
||||||
return new ERRORS_TS.DomainPermissionDenied(error.value, contextName);
|
throw new ERRORS_TS.DomainPermissionDenied(error.value, contextName);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error.code === 'bad_request' && error.keyword) {
|
if (error.code === 'bad_request' && error.keyword) {
|
||||||
return new ERRORS.SchemaValidationFailed(
|
throw new ERRORS.SchemaValidationFailed(
|
||||||
error.message,
|
error.message,
|
||||||
error.keyword,
|
error.keyword,
|
||||||
error.dataPath,
|
error.dataPath,
|
||||||
@@ -57,19 +62,19 @@ export default async function createDeploy(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (error.code === 'domain_configured') {
|
if (error.code === 'domain_configured') {
|
||||||
return new ERRORS_TS.AliasDomainConfigured(error);
|
throw new ERRORS_TS.AliasDomainConfigured(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error.code === 'missing_build_script') {
|
if (error.code === 'missing_build_script') {
|
||||||
return new ERRORS_TS.MissingBuildScript(error);
|
throw new ERRORS_TS.MissingBuildScript(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error.code === 'conflicting_file_path') {
|
if (error.code === 'conflicting_file_path') {
|
||||||
return new ERRORS_TS.ConflictingFilePath(error);
|
throw new ERRORS_TS.ConflictingFilePath(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error.code === 'conflicting_path_segment') {
|
if (error.code === 'conflicting_path_segment') {
|
||||||
return new ERRORS_TS.ConflictingPathSegment(error);
|
throw new ERRORS_TS.ConflictingPathSegment(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the cert is missing we try to generate a new one and the retry
|
// If the cert is missing we try to generate a new one and the retry
|
||||||
@@ -87,10 +92,10 @@ export default async function createDeploy(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (error.code === 'not_found') {
|
if (error.code === 'not_found') {
|
||||||
return new ERRORS_TS.DeploymentNotFound({ context: contextName });
|
throw new ERRORS_TS.DeploymentNotFound({ context: contextName });
|
||||||
}
|
}
|
||||||
|
|
||||||
const certError = mapCertError(error)
|
const certError = mapCertError(error);
|
||||||
if (certError) {
|
if (certError) {
|
||||||
return certError;
|
return certError;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import { Deployment } from '../../types';
|
|||||||
import {
|
import {
|
||||||
DeploymentNotFound,
|
DeploymentNotFound,
|
||||||
DeploymentPermissionDenied,
|
DeploymentPermissionDenied,
|
||||||
InvalidDeploymentId
|
InvalidDeploymentId,
|
||||||
} from '../errors-ts';
|
} from '../errors-ts';
|
||||||
import mapCertError from '../certs/map-cert-error';
|
import mapCertError from '../certs/map-cert-error';
|
||||||
|
|
||||||
type APIVersion = 'v5' | 'v9';
|
type APIVersion = 'v5' | 'v10';
|
||||||
|
|
||||||
export default async function getDeploymentByIdOrHost(
|
export default async function getDeploymentByIdOrHost(
|
||||||
client: Client,
|
client: Client,
|
||||||
@@ -58,10 +58,8 @@ async function getDeploymentById(
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Response = {
|
type Response = {
|
||||||
deployment: {
|
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
};
|
|
||||||
|
|
||||||
async function getDeploymentByHost(
|
async function getDeploymentByHost(
|
||||||
client: Client,
|
client: Client,
|
||||||
@@ -69,7 +67,9 @@ async function getDeploymentByHost(
|
|||||||
apiVersion: APIVersion
|
apiVersion: APIVersion
|
||||||
) {
|
) {
|
||||||
const response = await client.fetch<Response>(
|
const response = await client.fetch<Response>(
|
||||||
`/v4/now/hosts/${encodeURIComponent(host)}?resolve=1&noState=1`
|
`/v10/now/deployments/get?url=${encodeURIComponent(
|
||||||
|
host
|
||||||
|
)}&resolve=1&noState=1`
|
||||||
);
|
);
|
||||||
return getDeploymentById(client, response.deployment.id, apiVersion);
|
return getDeploymentById(client, response.id, apiVersion);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
//
|
|
||||||
import sleep from '../sleep';
|
|
||||||
|
|
||||||
import createPollingFn from '../create-polling-fn';
|
|
||||||
|
|
||||||
import getDeploymentByIdOrThrow from './get-deployment-by-id-or-throw';
|
|
||||||
|
|
||||||
const POLLING_INTERVAL = 5000;
|
|
||||||
|
|
||||||
async function* getStatusChangeFromPolling(
|
|
||||||
now: any,
|
|
||||||
contextName: string,
|
|
||||||
idOrHost: string,
|
|
||||||
initialState: string
|
|
||||||
) {
|
|
||||||
const pollDeployment = createPollingFn(
|
|
||||||
getDeploymentByIdOrThrow,
|
|
||||||
POLLING_INTERVAL
|
|
||||||
);
|
|
||||||
let prevState = initialState;
|
|
||||||
for await (const deployment of pollDeployment(now, contextName, idOrHost)) {
|
|
||||||
if (prevState !== deployment.state) {
|
|
||||||
await sleep(5000);
|
|
||||||
yield {
|
|
||||||
type: 'state-change',
|
|
||||||
created: Date.now(),
|
|
||||||
payload: { value: deployment.state }
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
prevState = deployment.state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default getStatusChangeFromPolling;
|
|
||||||
246
packages/now-cli/src/util/deploy/process-deployment.ts
Normal file
246
packages/now-cli/src/util/deploy/process-deployment.ts
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
import bytes from 'bytes';
|
||||||
|
import Progress from 'progress';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import pluralize from 'pluralize';
|
||||||
|
import {
|
||||||
|
createDeployment,
|
||||||
|
createLegacyDeployment,
|
||||||
|
DeploymentOptions,
|
||||||
|
} from '../../../../now-client';
|
||||||
|
import wait from '../output/wait';
|
||||||
|
import { Output } from '../output';
|
||||||
|
// @ts-ignore
|
||||||
|
import Now from '../../util';
|
||||||
|
import { NowConfig } from '../dev/types';
|
||||||
|
|
||||||
|
export default async function processDeployment({
|
||||||
|
now,
|
||||||
|
output,
|
||||||
|
hashes,
|
||||||
|
paths,
|
||||||
|
requestBody,
|
||||||
|
uploadStamp,
|
||||||
|
deployStamp,
|
||||||
|
legacy,
|
||||||
|
env,
|
||||||
|
quiet,
|
||||||
|
nowConfig,
|
||||||
|
}: {
|
||||||
|
now: Now;
|
||||||
|
output: Output;
|
||||||
|
hashes: { [key: string]: any };
|
||||||
|
paths: string[];
|
||||||
|
requestBody: DeploymentOptions;
|
||||||
|
uploadStamp: () => number;
|
||||||
|
deployStamp: () => number;
|
||||||
|
legacy: boolean;
|
||||||
|
env: any;
|
||||||
|
quiet: boolean;
|
||||||
|
nowConfig?: NowConfig;
|
||||||
|
}) {
|
||||||
|
const { warn, log, debug, note } = output;
|
||||||
|
let bar: Progress | null = null;
|
||||||
|
|
||||||
|
const path0 = paths[0];
|
||||||
|
const opts: DeploymentOptions = {
|
||||||
|
...requestBody,
|
||||||
|
debug: now._debug,
|
||||||
|
apiUrl: now._apiUrl,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!legacy) {
|
||||||
|
let queuedSpinner = null;
|
||||||
|
let buildSpinner = null;
|
||||||
|
let deploySpinner = null;
|
||||||
|
|
||||||
|
for await (const event of createDeployment(path0, opts, nowConfig)) {
|
||||||
|
if (event.type === 'hashes-calculated') {
|
||||||
|
hashes = event.payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === 'warning') {
|
||||||
|
warn(event.payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === 'notice') {
|
||||||
|
note(event.payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === 'file_count') {
|
||||||
|
debug(
|
||||||
|
`Total files ${event.payload.total.size}, ${event.payload.missing.length} changed`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!quiet) {
|
||||||
|
log(
|
||||||
|
`Synced ${pluralize(
|
||||||
|
'file',
|
||||||
|
event.payload.missing.length,
|
||||||
|
true
|
||||||
|
)} ${uploadStamp()}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const missingSize = event.payload.missing
|
||||||
|
.map((sha: string) => event.payload.total.get(sha).data.length)
|
||||||
|
.reduce((a: number, b: number) => a + b, 0);
|
||||||
|
|
||||||
|
bar = new Progress(`${chalk.gray('>')} Upload [:bar] :percent :etas`, {
|
||||||
|
width: 20,
|
||||||
|
complete: '=',
|
||||||
|
incomplete: '',
|
||||||
|
total: missingSize,
|
||||||
|
clear: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === 'file-uploaded') {
|
||||||
|
debug(
|
||||||
|
`Uploaded: ${event.payload.file.names.join(' ')} (${bytes(
|
||||||
|
event.payload.file.data.length
|
||||||
|
)})`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (bar) {
|
||||||
|
bar.tick(event.payload.file.data.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === 'created') {
|
||||||
|
now._host = event.payload.url;
|
||||||
|
|
||||||
|
if (!quiet) {
|
||||||
|
const version = legacy ? `${chalk.grey('[v1]')} ` : '';
|
||||||
|
log(`https://${event.payload.url} ${version}${deployStamp()}`);
|
||||||
|
} else {
|
||||||
|
process.stdout.write(`https://${event.payload.url}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queuedSpinner === null) {
|
||||||
|
queuedSpinner = wait('Queued...');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
event.type === 'build-state-changed' &&
|
||||||
|
event.payload.readyState === 'BUILDING'
|
||||||
|
) {
|
||||||
|
if (queuedSpinner) {
|
||||||
|
queuedSpinner();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buildSpinner === null) {
|
||||||
|
buildSpinner = wait('Building...');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === 'all-builds-completed') {
|
||||||
|
if (queuedSpinner) {
|
||||||
|
queuedSpinner();
|
||||||
|
}
|
||||||
|
if (buildSpinner) {
|
||||||
|
buildSpinner();
|
||||||
|
}
|
||||||
|
|
||||||
|
deploySpinner = wait('Finalizing...');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle error events
|
||||||
|
if (event.type === 'error') {
|
||||||
|
if (queuedSpinner) {
|
||||||
|
queuedSpinner();
|
||||||
|
}
|
||||||
|
if (buildSpinner) {
|
||||||
|
buildSpinner();
|
||||||
|
}
|
||||||
|
if (deploySpinner) {
|
||||||
|
deploySpinner();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw await now.handleDeploymentError(event.payload, { hashes, env });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle ready event
|
||||||
|
if (event.type === 'alias-assigned') {
|
||||||
|
if (queuedSpinner) {
|
||||||
|
queuedSpinner();
|
||||||
|
}
|
||||||
|
if (buildSpinner) {
|
||||||
|
buildSpinner();
|
||||||
|
}
|
||||||
|
if (deploySpinner) {
|
||||||
|
deploySpinner();
|
||||||
|
}
|
||||||
|
|
||||||
|
return event.payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for await (const event of createLegacyDeployment(path0, opts, nowConfig)) {
|
||||||
|
if (event.type === 'hashes-calculated') {
|
||||||
|
hashes = event.payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === 'file_count') {
|
||||||
|
debug(
|
||||||
|
`Total files ${event.payload.total.size}, ${event.payload.missing.length} changed`
|
||||||
|
);
|
||||||
|
if (!quiet) {
|
||||||
|
log(
|
||||||
|
`Synced ${pluralize(
|
||||||
|
'file',
|
||||||
|
event.payload.missing.length,
|
||||||
|
true
|
||||||
|
)} ${uploadStamp()}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const missingSize = event.payload.missing
|
||||||
|
.map((sha: string) => event.payload.total.get(sha).data.length)
|
||||||
|
.reduce((a: number, b: number) => a + b, 0);
|
||||||
|
|
||||||
|
bar = new Progress(`${chalk.gray('>')} Upload [:bar] :percent :etas`, {
|
||||||
|
width: 20,
|
||||||
|
complete: '=',
|
||||||
|
incomplete: '',
|
||||||
|
total: missingSize,
|
||||||
|
clear: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === 'file-uploaded') {
|
||||||
|
debug(
|
||||||
|
`Uploaded: ${event.payload.file.names.join(' ')} (${bytes(
|
||||||
|
event.payload.file.data.length
|
||||||
|
)})`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (bar) {
|
||||||
|
bar.tick(event.payload.file.data.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === 'created') {
|
||||||
|
now._host = event.payload.url;
|
||||||
|
|
||||||
|
if (!quiet) {
|
||||||
|
const version = legacy ? `${chalk.grey('[v1]')} ` : '';
|
||||||
|
log(`${event.payload.url} ${version}${deployStamp()}`);
|
||||||
|
} else {
|
||||||
|
process.stdout.write(`https://${event.payload.url}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle error events
|
||||||
|
if (event.type === 'error') {
|
||||||
|
throw await now.handleDeploymentError(event.payload, { hashes, env });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle ready event
|
||||||
|
if (event.type === 'ready') {
|
||||||
|
log(`Build completed`);
|
||||||
|
return event.payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
packages/now-cli/src/util/deploy/should-deploy-dir.ts
Normal file
18
packages/now-cli/src/util/deploy/should-deploy-dir.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { homedir } from 'os';
|
||||||
|
import promptBool from '../input/prompt-bool';
|
||||||
|
import { Output } from '../output';
|
||||||
|
|
||||||
|
export default async function shouldDeployDir(argv0: string, output: Output) {
|
||||||
|
let yes = true;
|
||||||
|
if (argv0 === homedir()) {
|
||||||
|
if (
|
||||||
|
!(await promptBool(
|
||||||
|
'You are deploying your home directory. Do you want to continue?'
|
||||||
|
))
|
||||||
|
) {
|
||||||
|
output.log('Aborted');
|
||||||
|
yes = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return yes;
|
||||||
|
}
|
||||||
@@ -2,31 +2,35 @@ import chalk from 'chalk';
|
|||||||
import execa from 'execa';
|
import execa from 'execa';
|
||||||
import semver from 'semver';
|
import semver from 'semver';
|
||||||
import pipe from 'promisepipe';
|
import pipe from 'promisepipe';
|
||||||
|
import retry from 'async-retry';
|
||||||
import npa from 'npm-package-arg';
|
import npa from 'npm-package-arg';
|
||||||
import { extract } from 'tar-fs';
|
import { extract } from 'tar-fs';
|
||||||
import { createHash } from 'crypto';
|
import { createHash } from 'crypto';
|
||||||
import { createGunzip } from 'zlib';
|
import { createGunzip } from 'zlib';
|
||||||
import { join, resolve } from 'path';
|
import { join, resolve } from 'path';
|
||||||
import { funCacheDir } from '@zeit/fun';
|
import { funCacheDir } from '@zeit/fun';
|
||||||
import cacheDirectory from 'cache-or-tmp-directory';
|
import { PackageJson } from '@now/build-utils';
|
||||||
|
import XDGAppPaths from 'xdg-app-paths';
|
||||||
import {
|
import {
|
||||||
createReadStream,
|
createReadStream,
|
||||||
mkdirp,
|
mkdirp,
|
||||||
readFile,
|
readFile,
|
||||||
readJSON,
|
readJSON,
|
||||||
writeFile,
|
writeFile,
|
||||||
remove
|
remove,
|
||||||
} from 'fs-extra';
|
} from 'fs-extra';
|
||||||
import pkg from '../../../package.json';
|
import pkg from '../../../package.json';
|
||||||
|
|
||||||
import { NoBuilderCacheError, BuilderCacheCleanError } from '../errors-ts';
|
import { NoBuilderCacheError } from '../errors-ts';
|
||||||
import wait from '../output/wait';
|
import wait from '../output/wait';
|
||||||
import { Output } from '../output';
|
import { Output } from '../output';
|
||||||
import { getDistTag } from '../get-dist-tag';
|
import { getDistTag } from '../get-dist-tag';
|
||||||
import { devDependencies } from '../../../package.json';
|
|
||||||
|
|
||||||
import * as staticBuilder from './static-builder';
|
import * as staticBuilder from './static-builder';
|
||||||
import { BuilderWithPackage, Package } from './types';
|
import { BuilderWithPackage } from './types';
|
||||||
|
import { getBundledBuilders } from './get-bundled-builders';
|
||||||
|
|
||||||
|
declare const __non_webpack_require__: typeof require;
|
||||||
|
|
||||||
const registryTypes = new Set(['version', 'tag', 'range']);
|
const registryTypes = new Set(['version', 'tag', 'range']);
|
||||||
|
|
||||||
@@ -34,14 +38,10 @@ const localBuilders: { [key: string]: BuilderWithPackage } = {
|
|||||||
'@now/static': {
|
'@now/static': {
|
||||||
runInProcess: true,
|
runInProcess: true,
|
||||||
builder: Object.freeze(staticBuilder),
|
builder: Object.freeze(staticBuilder),
|
||||||
package: Object.freeze({ name: '@now/static', version: '' })
|
package: Object.freeze({ name: '@now/static', version: '' }),
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const bundledBuilders = Object.keys(devDependencies).filter(d =>
|
|
||||||
d.startsWith('@now/')
|
|
||||||
);
|
|
||||||
|
|
||||||
const distTag = getDistTag(pkg.version);
|
const distTag = getDistTag(pkg.version);
|
||||||
|
|
||||||
export const cacheDirPromise = prepareCacheDir();
|
export const cacheDirPromise = prepareCacheDir();
|
||||||
@@ -80,7 +80,7 @@ export async function prepareCacheDir() {
|
|||||||
const { NOW_BUILDER_CACHE_DIR } = process.env;
|
const { NOW_BUILDER_CACHE_DIR } = process.env;
|
||||||
const designated = NOW_BUILDER_CACHE_DIR
|
const designated = NOW_BUILDER_CACHE_DIR
|
||||||
? resolve(NOW_BUILDER_CACHE_DIR)
|
? resolve(NOW_BUILDER_CACHE_DIR)
|
||||||
: cacheDirectory('co.zeit.now');
|
: XDGAppPaths('co.zeit.now').cache();
|
||||||
|
|
||||||
if (!designated) {
|
if (!designated) {
|
||||||
throw new NoBuilderCacheError();
|
throw new NoBuilderCacheError();
|
||||||
@@ -117,7 +117,7 @@ export async function prepareBuilderDir() {
|
|||||||
export async function prepareBuilderModulePath() {
|
export async function prepareBuilderModulePath() {
|
||||||
const [builderDir, builderContents] = await Promise.all([
|
const [builderDir, builderContents] = await Promise.all([
|
||||||
builderDirPromise,
|
builderDirPromise,
|
||||||
readFile(join(__dirname, 'builder-worker.js'))
|
readFile(join(__dirname, 'builder-worker.js')),
|
||||||
]);
|
]);
|
||||||
let needsWrite = false;
|
let needsWrite = false;
|
||||||
const builderSha = getSha(builderContents);
|
const builderSha = getSha(builderContents);
|
||||||
@@ -140,24 +140,6 @@ export async function prepareBuilderModulePath() {
|
|||||||
return cachedBuilderPath;
|
return cachedBuilderPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is responsible for cleaning the cache
|
|
||||||
export async function cleanCacheDir(output: Output): Promise<void> {
|
|
||||||
const cacheDir = await cacheDirPromise;
|
|
||||||
try {
|
|
||||||
output.log(chalk`{magenta Deleting} ${cacheDir}`);
|
|
||||||
await remove(cacheDir);
|
|
||||||
} catch (err) {
|
|
||||||
throw new BuilderCacheCleanError(cacheDir, err.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await remove(funCacheDir);
|
|
||||||
output.log(chalk`{magenta Deleting} ${funCacheDir}`);
|
|
||||||
} catch (err) {
|
|
||||||
throw new BuilderCacheCleanError(funCacheDir, err.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getNpmVersion(use = ''): string {
|
function getNpmVersion(use = ''): string {
|
||||||
const parsed = npa(use);
|
const parsed = npa(use);
|
||||||
if (registryTypes.has(parsed.type)) {
|
if (registryTypes.has(parsed.type)) {
|
||||||
@@ -179,7 +161,7 @@ export function getBuildUtils(packages: string[]): string {
|
|||||||
export function filterPackage(
|
export function filterPackage(
|
||||||
builderSpec: string,
|
builderSpec: string,
|
||||||
distTag: string,
|
distTag: string,
|
||||||
buildersPkg: Package
|
buildersPkg: PackageJson
|
||||||
) {
|
) {
|
||||||
if (builderSpec in localBuilders) return false;
|
if (builderSpec in localBuilders) return false;
|
||||||
const parsed = npa(builderSpec);
|
const parsed = npa(builderSpec);
|
||||||
@@ -187,7 +169,7 @@ export function filterPackage(
|
|||||||
parsed.name &&
|
parsed.name &&
|
||||||
parsed.type === 'tag' &&
|
parsed.type === 'tag' &&
|
||||||
parsed.fetchSpec === distTag &&
|
parsed.fetchSpec === distTag &&
|
||||||
bundledBuilders.includes(parsed.name) &&
|
getBundledBuilders().includes(parsed.name) &&
|
||||||
buildersPkg.dependencies
|
buildersPkg.dependencies
|
||||||
) {
|
) {
|
||||||
const parsedInstalled = npa(
|
const parsedInstalled = npa(
|
||||||
@@ -233,13 +215,13 @@ export async function installBuilders(
|
|||||||
}
|
}
|
||||||
const yarnPath = join(yarnDir, 'yarn');
|
const yarnPath = join(yarnDir, 'yarn');
|
||||||
const buildersPkgPath = join(builderDir, 'package.json');
|
const buildersPkgPath = join(builderDir, 'package.json');
|
||||||
const buildersPkg = await readJSON(buildersPkgPath);
|
const buildersPkgBefore = await readJSON(buildersPkgPath);
|
||||||
|
|
||||||
packages.push(getBuildUtils(packages));
|
packages.push(getBuildUtils(packages));
|
||||||
|
|
||||||
// Filter out any packages that come packaged with `now-cli`
|
// Filter out any packages that come packaged with `now-cli`
|
||||||
const packagesToInstall = packages.filter(p =>
|
const packagesToInstall = packages.filter(p =>
|
||||||
filterPackage(p, distTag, buildersPkg)
|
filterPackage(p, distTag, buildersPkgBefore)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (packagesToInstall.length === 0) {
|
if (packagesToInstall.length === 0) {
|
||||||
@@ -250,8 +232,11 @@ export async function installBuilders(
|
|||||||
const stopSpinner = wait(
|
const stopSpinner = wait(
|
||||||
`Installing builders: ${packagesToInstall.sort().join(', ')}`
|
`Installing builders: ${packagesToInstall.sort().join(', ')}`
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await execa(
|
await retry(
|
||||||
|
() =>
|
||||||
|
execa(
|
||||||
process.execPath,
|
process.execPath,
|
||||||
[
|
[
|
||||||
yarnPath,
|
yarnPath,
|
||||||
@@ -259,15 +244,28 @@ export async function installBuilders(
|
|||||||
'--exact',
|
'--exact',
|
||||||
'--no-lockfile',
|
'--no-lockfile',
|
||||||
'--non-interactive',
|
'--non-interactive',
|
||||||
...packagesToInstall
|
...packagesToInstall,
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
cwd: builderDir
|
cwd: builderDir,
|
||||||
}
|
}
|
||||||
|
),
|
||||||
|
{ retries: 2 }
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
stopSpinner();
|
stopSpinner();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updatedPackages: string[] = [];
|
||||||
|
const buildersPkgAfter = await readJSON(buildersPkgPath);
|
||||||
|
for (const [name, version] of Object.entries(buildersPkgAfter.dependencies)) {
|
||||||
|
if (version !== buildersPkgBefore.dependencies[name]) {
|
||||||
|
output.debug(`Builder "${name}" updated to version \`${version}\``);
|
||||||
|
updatedPackages.push(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
purgeRequireCache(updatedPackages, builderDir, output);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateBuilders(
|
export async function updateBuilders(
|
||||||
@@ -286,7 +284,9 @@ export async function updateBuilders(
|
|||||||
|
|
||||||
packages.push(getBuildUtils(packages));
|
packages.push(getBuildUtils(packages));
|
||||||
|
|
||||||
await execa(
|
await retry(
|
||||||
|
() =>
|
||||||
|
execa(
|
||||||
process.execPath,
|
process.execPath,
|
||||||
[
|
[
|
||||||
yarnPath,
|
yarnPath,
|
||||||
@@ -294,11 +294,13 @@ export async function updateBuilders(
|
|||||||
'--exact',
|
'--exact',
|
||||||
'--no-lockfile',
|
'--no-lockfile',
|
||||||
'--non-interactive',
|
'--non-interactive',
|
||||||
...packages.filter(p => p !== '@now/static')
|
...packages.filter(p => p !== '@now/static'),
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
cwd: builderDir
|
cwd: builderDir,
|
||||||
}
|
}
|
||||||
|
),
|
||||||
|
{ retries: 2 }
|
||||||
);
|
);
|
||||||
|
|
||||||
const updatedPackages: string[] = [];
|
const updatedPackages: string[] = [];
|
||||||
@@ -310,6 +312,8 @@ export async function updateBuilders(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
purgeRequireCache(updatedPackages, builderDir, output);
|
||||||
|
|
||||||
return updatedPackages;
|
return updatedPackages;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,7 +340,7 @@ export async function getBuilder(
|
|||||||
const pkg = require(join(dest, 'package.json'));
|
const pkg = require(join(dest, 'package.json'));
|
||||||
builderWithPkg = {
|
builderWithPkg = {
|
||||||
builder: Object.freeze(mod),
|
builder: Object.freeze(mod),
|
||||||
package: Object.freeze(pkg)
|
package: Object.freeze(pkg),
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.code === 'MODULE_NOT_FOUND') {
|
if (err.code === 'MODULE_NOT_FOUND') {
|
||||||
@@ -357,7 +361,7 @@ export async function getBuilder(
|
|||||||
|
|
||||||
function getPackageName(
|
function getPackageName(
|
||||||
parsed: npa.Result,
|
parsed: npa.Result,
|
||||||
buildersPkg: Package
|
buildersPkg: PackageJson
|
||||||
): string | null {
|
): string | null {
|
||||||
if (registryTypes.has(parsed.type)) {
|
if (registryTypes.has(parsed.type)) {
|
||||||
return parsed.name;
|
return parsed.name;
|
||||||
@@ -378,10 +382,32 @@ function getSha(buffer: Buffer): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function hasBundledBuilders(dependencies: { [name: string]: string }): boolean {
|
function hasBundledBuilders(dependencies: { [name: string]: string }): boolean {
|
||||||
for (const name of bundledBuilders) {
|
for (const name of getBundledBuilders()) {
|
||||||
if (!(name in dependencies)) {
|
if (!(name in dependencies)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function purgeRequireCache(
|
||||||
|
packages: string[],
|
||||||
|
builderDir: string,
|
||||||
|
output: Output
|
||||||
|
) {
|
||||||
|
const _require =
|
||||||
|
typeof __non_webpack_require__ === 'function'
|
||||||
|
? __non_webpack_require__
|
||||||
|
: require;
|
||||||
|
|
||||||
|
// The `require()` cache for the builder's assets must be purged
|
||||||
|
const packagesPaths = packages.map(b => join(builderDir, 'node_modules', b));
|
||||||
|
for (const id of Object.keys(_require.cache)) {
|
||||||
|
for (const path of packagesPaths) {
|
||||||
|
if (id.startsWith(path)) {
|
||||||
|
output.debug(`Purging require cache for "${id}"`);
|
||||||
|
delete _require.cache[id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,16 +2,17 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
import bytes from 'bytes';
|
import bytes from 'bytes';
|
||||||
|
import { promisify } from 'util';
|
||||||
import { delimiter, dirname, join } from 'path';
|
import { delimiter, dirname, join } from 'path';
|
||||||
import { fork, ChildProcess } from 'child_process';
|
import { fork, ChildProcess } from 'child_process';
|
||||||
import { createFunction } from '@zeit/fun';
|
import { createFunction } from '@zeit/fun';
|
||||||
import { File, Lambda, FileBlob, FileFsRef } from '@now/build-utils';
|
import { Builder, File, Lambda, FileBlob, FileFsRef } from '@now/build-utils';
|
||||||
import stripAnsi from 'strip-ansi';
|
import stripAnsi from 'strip-ansi';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import which from 'which';
|
import which from 'which';
|
||||||
import plural from 'pluralize';
|
import plural from 'pluralize';
|
||||||
import ora, { Ora } from 'ora';
|
|
||||||
import minimatch from 'minimatch';
|
import minimatch from 'minimatch';
|
||||||
|
import _treeKill from 'tree-kill';
|
||||||
|
|
||||||
import { Output } from '../output';
|
import { Output } from '../output';
|
||||||
import highlight from '../output/highlight';
|
import highlight from '../output/highlight';
|
||||||
@@ -23,13 +24,14 @@ import { builderModulePathPromise, getBuilder } from './builder-cache';
|
|||||||
import {
|
import {
|
||||||
EnvConfig,
|
EnvConfig,
|
||||||
NowConfig,
|
NowConfig,
|
||||||
BuildConfig,
|
|
||||||
BuildMatch,
|
BuildMatch,
|
||||||
BuildResult,
|
BuildResult,
|
||||||
BuilderInputs,
|
BuilderInputs,
|
||||||
BuilderOutput,
|
BuilderOutput,
|
||||||
BuilderOutputs
|
BuildResultV3,
|
||||||
|
BuilderOutputs,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
import { normalizeRoutes } from '@now/routing-utils';
|
||||||
|
|
||||||
interface BuildMessage {
|
interface BuildMessage {
|
||||||
type: string;
|
type: string;
|
||||||
@@ -41,7 +43,7 @@ interface BuildMessageResult extends BuildMessage {
|
|||||||
error?: object;
|
error?: object;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isLogging = new WeakSet<ChildProcess>();
|
const treeKill = promisify(_treeKill);
|
||||||
|
|
||||||
let nodeBinPromise: Promise<string>;
|
let nodeBinPromise: Promise<string>;
|
||||||
|
|
||||||
@@ -49,43 +51,48 @@ async function getNodeBin(): Promise<string> {
|
|||||||
return which.sync('node', { nothrow: true }) || process.execPath;
|
return which.sync('node', { nothrow: true }) || process.execPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
function pipeChildLogging(child: ChildProcess): void {
|
|
||||||
if (!isLogging.has(child)) {
|
|
||||||
child.stdout!.pipe(process.stdout);
|
|
||||||
child.stderr!.pipe(process.stderr);
|
|
||||||
isLogging.add(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createBuildProcess(
|
async function createBuildProcess(
|
||||||
match: BuildMatch,
|
match: BuildMatch,
|
||||||
buildEnv: EnvConfig,
|
buildEnv: EnvConfig,
|
||||||
workPath: string,
|
workPath: string,
|
||||||
output: Output,
|
output: Output,
|
||||||
yarnPath?: string
|
yarnPath?: string,
|
||||||
|
debugEnabled: boolean = false
|
||||||
): Promise<ChildProcess> {
|
): Promise<ChildProcess> {
|
||||||
if (!nodeBinPromise) {
|
if (!nodeBinPromise) {
|
||||||
nodeBinPromise = getNodeBin();
|
nodeBinPromise = getNodeBin();
|
||||||
}
|
}
|
||||||
const [execPath, modulePath] = await Promise.all([
|
const [execPath, modulePath] = await Promise.all([
|
||||||
nodeBinPromise,
|
nodeBinPromise,
|
||||||
builderModulePathPromise
|
builderModulePathPromise,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Ensure that `node` is in the builder's `PATH`
|
||||||
let PATH = `${dirname(execPath)}${delimiter}${process.env.PATH}`;
|
let PATH = `${dirname(execPath)}${delimiter}${process.env.PATH}`;
|
||||||
|
|
||||||
|
// Ensure that `yarn` is in the builder's `PATH`
|
||||||
if (yarnPath) {
|
if (yarnPath) {
|
||||||
PATH = `${yarnPath}${delimiter}${PATH}`;
|
PATH = `${yarnPath}${delimiter}${PATH}`;
|
||||||
}
|
}
|
||||||
const buildProcess = fork(modulePath, [], {
|
|
||||||
cwd: workPath,
|
const env: EnvConfig = {
|
||||||
env: {
|
|
||||||
...process.env,
|
...process.env,
|
||||||
PATH,
|
PATH,
|
||||||
...buildEnv,
|
...buildEnv,
|
||||||
NOW_REGION: 'dev1'
|
NOW_REGION: 'dev1',
|
||||||
},
|
};
|
||||||
|
|
||||||
|
// Builders won't show debug logs by default.
|
||||||
|
// The `NOW_BUILDER_DEBUG` env variable enables them.
|
||||||
|
if (debugEnabled) {
|
||||||
|
env.NOW_BUILDER_DEBUG = '1';
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildProcess = fork(modulePath, [], {
|
||||||
|
cwd: workPath,
|
||||||
|
env,
|
||||||
execPath,
|
execPath,
|
||||||
execArgv: [],
|
execArgv: [],
|
||||||
stdio: ['ignore', 'pipe', 'pipe', 'ipc']
|
|
||||||
});
|
});
|
||||||
match.buildProcess = buildProcess;
|
match.buildProcess = buildProcess;
|
||||||
|
|
||||||
@@ -96,9 +103,6 @@ async function createBuildProcess(
|
|||||||
match.buildProcess = undefined;
|
match.buildProcess = undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
buildProcess.stdout!.setEncoding('utf8');
|
|
||||||
buildProcess.stderr!.setEncoding('utf8');
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// The first message that the builder process sends is the `ready` event
|
// The first message that the builder process sends is the `ready` event
|
||||||
buildProcess.once('message', ({ type }) => {
|
buildProcess.once('message', ({ type }) => {
|
||||||
@@ -122,7 +126,7 @@ export async function executeBuild(
|
|||||||
filesRemoved?: string[]
|
filesRemoved?: string[]
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const {
|
const {
|
||||||
builderWithPkg: { runInProcess, builder, package: pkg }
|
builderWithPkg: { runInProcess, builder, package: pkg },
|
||||||
} = match;
|
} = match;
|
||||||
const { src: entrypoint } = match;
|
const { src: entrypoint } = match;
|
||||||
const { env, debug, buildEnv, yarnPath, cwd: workPath } = devServer;
|
const { env, debug, buildEnv, yarnPath, cwd: workPath } = devServer;
|
||||||
@@ -150,7 +154,8 @@ export async function executeBuild(
|
|||||||
buildEnv,
|
buildEnv,
|
||||||
workPath,
|
workPath,
|
||||||
devServer.output,
|
devServer.output,
|
||||||
yarnPath
|
yarnPath,
|
||||||
|
debug
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,45 +170,16 @@ export async function executeBuild(
|
|||||||
filesChanged,
|
filesChanged,
|
||||||
filesRemoved,
|
filesRemoved,
|
||||||
env,
|
env,
|
||||||
buildEnv
|
buildEnv,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let buildResultOrOutputs: BuilderOutputs | BuildResult;
|
let buildResultOrOutputs: BuilderOutputs | BuildResult | BuildResultV3;
|
||||||
if (buildProcess) {
|
if (buildProcess) {
|
||||||
let spinLogger;
|
|
||||||
let spinner: Ora | undefined;
|
|
||||||
const fullLogs: string[] = [];
|
|
||||||
|
|
||||||
if (isInitialBuild && !debug && process.stdout.isTTY) {
|
|
||||||
const logTitle = `${chalk.bold(
|
|
||||||
`Preparing ${chalk.underline(entrypoint)} for build`
|
|
||||||
)}:`;
|
|
||||||
spinner = ora(logTitle).start();
|
|
||||||
|
|
||||||
spinLogger = (data: Buffer) => {
|
|
||||||
const rawLog = stripAnsi(data.toString());
|
|
||||||
fullLogs.push(rawLog);
|
|
||||||
|
|
||||||
const lines = rawLog.replace(/\s+$/, '').split('\n');
|
|
||||||
const spinText = `${logTitle} ${lines[lines.length - 1]}`;
|
|
||||||
const maxCols = process.stdout.columns || 80;
|
|
||||||
const overflow = stripAnsi(spinText).length + 2 - maxCols;
|
|
||||||
spinner!.text =
|
|
||||||
overflow > 0 ? `${spinText.slice(0, -overflow - 3)}...` : spinText;
|
|
||||||
};
|
|
||||||
|
|
||||||
buildProcess!.stdout!.on('data', spinLogger);
|
|
||||||
buildProcess!.stderr!.on('data', spinLogger);
|
|
||||||
} else {
|
|
||||||
pipeChildLogging(buildProcess!);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
buildProcess.send({
|
buildProcess.send({
|
||||||
type: 'build',
|
type: 'build',
|
||||||
builderName: pkg.name,
|
builderName: pkg.name,
|
||||||
buildParams
|
buildParams,
|
||||||
});
|
});
|
||||||
|
|
||||||
buildResultOrOutputs = await new Promise((resolve, reject) => {
|
buildResultOrOutputs = await new Promise((resolve, reject) => {
|
||||||
@@ -233,42 +209,93 @@ export async function executeBuild(
|
|||||||
buildProcess!.on('exit', onExit);
|
buildProcess!.on('exit', onExit);
|
||||||
buildProcess!.on('message', onMessage);
|
buildProcess!.on('message', onMessage);
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
if (spinner) {
|
|
||||||
spinner.stop();
|
|
||||||
spinner = undefined;
|
|
||||||
console.log(fullLogs.join(''));
|
|
||||||
}
|
|
||||||
throw err;
|
|
||||||
} finally {
|
|
||||||
if (spinLogger) {
|
|
||||||
buildProcess.stdout!.removeListener('data', spinLogger);
|
|
||||||
buildProcess.stderr!.removeListener('data', spinLogger);
|
|
||||||
}
|
|
||||||
if (spinner) {
|
|
||||||
spinner.stop();
|
|
||||||
}
|
|
||||||
pipeChildLogging(buildProcess!);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
buildResultOrOutputs = await builder.build(buildParams);
|
buildResultOrOutputs = await builder.build(buildParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort out build result to builder v2 shape
|
// Sort out build result to builder v2 shape
|
||||||
if (builder.version === undefined) {
|
if (!builder.version || builder.version === 1) {
|
||||||
// `BuilderOutputs` map was returned (Now Builder v1 behavior)
|
// `BuilderOutputs` map was returned (Now Builder v1 behavior)
|
||||||
result = {
|
result = {
|
||||||
output: buildResultOrOutputs as BuilderOutputs,
|
output: buildResultOrOutputs as BuilderOutputs,
|
||||||
routes: [],
|
routes: [],
|
||||||
watch: []
|
watch: [],
|
||||||
|
distPath:
|
||||||
|
typeof buildResultOrOutputs.distPath === 'string'
|
||||||
|
? buildResultOrOutputs.distPath
|
||||||
|
: undefined,
|
||||||
};
|
};
|
||||||
|
} else if (builder.version === 2) {
|
||||||
|
result = buildResultOrOutputs as BuildResult;
|
||||||
|
} else if (builder.version === 3) {
|
||||||
|
const { output, ...rest } = buildResultOrOutputs as BuildResultV3;
|
||||||
|
|
||||||
|
if (!output || (output as BuilderOutput).type !== 'Lambda') {
|
||||||
|
throw new Error('The result of "builder.build()" must be a `Lambda`');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output.maxDuration) {
|
||||||
|
throw new Error(
|
||||||
|
'The result of "builder.build()" must not contain `memory`'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output.memory) {
|
||||||
|
throw new Error(
|
||||||
|
'The result of "builder.build()" must not contain `maxDuration`'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [src, func] of Object.entries(config.functions || {})) {
|
||||||
|
if (src === entrypoint || minimatch(entrypoint, src)) {
|
||||||
|
if (func.maxDuration) {
|
||||||
|
output.maxDuration = func.maxDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (func.memory) {
|
||||||
|
output.memory = func.memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = {
|
||||||
|
...rest,
|
||||||
|
output: {
|
||||||
|
[entrypoint]: output,
|
||||||
|
},
|
||||||
|
} as BuildResult;
|
||||||
} else {
|
} else {
|
||||||
result = buildResultOrOutputs as BuildResult;
|
result = buildResultOrOutputs as BuildResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Normalize Builder Routes
|
||||||
|
const normalized = normalizeRoutes(result.routes);
|
||||||
|
if (normalized.error) {
|
||||||
|
throw new Error(normalized.error.message);
|
||||||
|
} else {
|
||||||
|
result.routes = normalized.routes || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { output } = result;
|
||||||
|
|
||||||
|
// Mimic fmeta-util and convert cleanUrls
|
||||||
|
if (nowConfig.cleanUrls) {
|
||||||
|
Object.entries(output)
|
||||||
|
.filter(([name, value]) => name.endsWith('.html'))
|
||||||
|
.forEach(([name, value]) => {
|
||||||
|
const cleanName = name.slice(0, -5);
|
||||||
|
delete output[name];
|
||||||
|
output[cleanName] = value;
|
||||||
|
if (value.type === 'FileBlob' || value.type === 'FileFsRef') {
|
||||||
|
value.contentType = value.contentType || 'text/html; charset=utf-8';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Convert the JSON-ified output map back into their corresponding `File`
|
// Convert the JSON-ified output map back into their corresponding `File`
|
||||||
// subclass type instances.
|
// subclass type instances.
|
||||||
const output = result.output as BuilderOutputs;
|
|
||||||
for (const name of Object.keys(output)) {
|
for (const name of Object.keys(output)) {
|
||||||
const obj = output[name] as File;
|
const obj = output[name] as File;
|
||||||
let lambda: Lambda;
|
let lambda: Lambda;
|
||||||
@@ -340,15 +367,15 @@ export async function executeBuild(
|
|||||||
Code: { ZipFile: asset.zipBuffer },
|
Code: { ZipFile: asset.zipBuffer },
|
||||||
Handler: asset.handler,
|
Handler: asset.handler,
|
||||||
Runtime: asset.runtime,
|
Runtime: asset.runtime,
|
||||||
MemorySize: 3008,
|
MemorySize: asset.memory || 3008,
|
||||||
Environment: {
|
Environment: {
|
||||||
Variables: {
|
Variables: {
|
||||||
...nowConfig.env,
|
...nowConfig.env,
|
||||||
...asset.environment,
|
...asset.environment,
|
||||||
...env,
|
...env,
|
||||||
NOW_REGION: 'dev1'
|
NOW_REGION: 'dev1',
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,7 +409,7 @@ export async function getBuildMatches(
|
|||||||
return matches;
|
return matches;
|
||||||
}
|
}
|
||||||
|
|
||||||
const noMatches: BuildConfig[] = [];
|
const noMatches: Builder[] = [];
|
||||||
const builds = nowConfig.builds || [{ src: '**', use: '@now/static' }];
|
const builds = nowConfig.builds || [{ src: '**', use: '@now/static' }];
|
||||||
|
|
||||||
for (const buildConfig of builds) {
|
for (const buildConfig of builds) {
|
||||||
@@ -420,7 +447,7 @@ export async function getBuildMatches(
|
|||||||
builderWithPkg,
|
builderWithPkg,
|
||||||
buildOutput: {},
|
buildOutput: {},
|
||||||
buildResults: new Map(),
|
buildResults: new Map(),
|
||||||
buildTimestamp: 0
|
buildTimestamp: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -442,3 +469,35 @@ export async function getBuildMatches(
|
|||||||
|
|
||||||
return matches;
|
return matches;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function shutdownBuilder(
|
||||||
|
match: BuildMatch,
|
||||||
|
{ debug }: Output
|
||||||
|
): Promise<void> {
|
||||||
|
const ops: Promise<void>[] = [];
|
||||||
|
|
||||||
|
if (match.buildProcess) {
|
||||||
|
const { pid } = match.buildProcess;
|
||||||
|
debug(`Killing builder sub-process with PID ${pid}`);
|
||||||
|
const killPromise = treeKill(pid)
|
||||||
|
.then(() => {
|
||||||
|
debug(`Killed builder with PID ${pid}`);
|
||||||
|
})
|
||||||
|
.catch((err: Error) => {
|
||||||
|
debug(`Failed to kill builder with PID ${pid}: ${err}`);
|
||||||
|
});
|
||||||
|
ops.push(killPromise);
|
||||||
|
delete match.buildProcess;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match.buildOutput) {
|
||||||
|
for (const asset of Object.values(match.buildOutput)) {
|
||||||
|
if (asset.type === 'Lambda' && asset.fn) {
|
||||||
|
debug(`Shutting down Lambda function`);
|
||||||
|
ops.push(asset.fn.destroy());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(ops);
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ export const httpStatusDescriptionMap = new Map([
|
|||||||
[502, 'BAD_GATEWAY'],
|
[502, 'BAD_GATEWAY'],
|
||||||
[503, 'SERVICE_UNAVAILABLE'],
|
[503, 'SERVICE_UNAVAILABLE'],
|
||||||
[504, 'GATEWAY_TIMEOUT'],
|
[504, 'GATEWAY_TIMEOUT'],
|
||||||
[508, 'INFINITE_LOOP']
|
[508, 'INFINITE_LOOP'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const errorMessageMap = new Map([
|
export const errorMessageMap = new Map([
|
||||||
[400, 'Bad request'],
|
[400, 'Bad request'],
|
||||||
[402, 'Payment required'],
|
[402, 'Payment required'],
|
||||||
[403, 'You don\'t have the required permissions'],
|
[403, "You don't have the required permissions"],
|
||||||
[404, 'The page could not be found'],
|
[404, 'The page could not be found'],
|
||||||
[405, 'Method not allowed'],
|
[405, 'Method not allowed'],
|
||||||
[410, 'The deployment has been removed'],
|
[410, 'The deployment has been removed'],
|
||||||
@@ -28,7 +28,7 @@ export const errorMessageMap = new Map([
|
|||||||
[501, 'Not implemented'],
|
[501, 'Not implemented'],
|
||||||
[503, 'The deployment is currently unavailable'],
|
[503, 'The deployment is currently unavailable'],
|
||||||
[504, 'An error occurred with your deployment'],
|
[504, 'An error occurred with your deployment'],
|
||||||
[508, 'Infinite loop detected']
|
[508, 'Infinite loop detected'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
interface ErrorMessage {
|
interface ErrorMessage {
|
||||||
@@ -40,20 +40,20 @@ interface ErrorMessage {
|
|||||||
const appError = {
|
const appError = {
|
||||||
title: 'An error occurred with this application.',
|
title: 'An error occurred with this application.',
|
||||||
subtitle: 'This is an error with the application itself, not the platform.',
|
subtitle: 'This is an error with the application itself, not the platform.',
|
||||||
app_error: true
|
app_error: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const infrastructureError = {
|
const infrastructureError = {
|
||||||
title: 'An internal error occurred with ZEIT Now.',
|
title: 'An internal error occurred with ZEIT Now.',
|
||||||
subtitle: 'This is an error with the platform itself, not the application.',
|
subtitle: 'This is an error with the platform itself, not the application.',
|
||||||
app_error: false
|
app_error: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const pageNotFoundError = {
|
const pageNotFoundError = {
|
||||||
title: 'The page could not be found.',
|
title: 'The page could not be found.',
|
||||||
subtitle: 'The page could not be found in the application.',
|
subtitle: 'The page could not be found in the application.',
|
||||||
app_error: true
|
app_error: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function generateErrorMessage(
|
export function generateErrorMessage(
|
||||||
@@ -68,7 +68,7 @@ export function generateErrorMessage(
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
title: errorMessageMap.get(statusCode) || 'Error occurred',
|
title: errorMessageMap.get(statusCode) || 'Error occurred',
|
||||||
app_error: false
|
app_error: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
11
packages/now-cli/src/util/dev/get-bundled-builders.ts
Normal file
11
packages/now-cli/src/util/dev/get-bundled-builders.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
export function getBundledBuilders() {
|
||||||
|
return [
|
||||||
|
'@now/go',
|
||||||
|
'@now/next',
|
||||||
|
'@now/node',
|
||||||
|
'@now/ruby',
|
||||||
|
'@now/python',
|
||||||
|
'@now/static-build',
|
||||||
|
'@now/build-utils',
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -4,7 +4,13 @@ import PCRE from 'pcre-to-regexp';
|
|||||||
import isURL from './is-url';
|
import isURL from './is-url';
|
||||||
import DevServer from './server';
|
import DevServer from './server';
|
||||||
|
|
||||||
import { HttpHeadersConfig, RouteConfig, RouteResult } from './types';
|
import {
|
||||||
|
HttpHeadersConfig,
|
||||||
|
RouteConfig,
|
||||||
|
RouteResult,
|
||||||
|
NowConfig,
|
||||||
|
} from './types';
|
||||||
|
import { isHandler } from '@now/routing-utils';
|
||||||
|
|
||||||
export function resolveRouteParameters(
|
export function resolveRouteParameters(
|
||||||
str: string,
|
str: string,
|
||||||
@@ -40,9 +46,8 @@ export default async function(
|
|||||||
let idx = -1;
|
let idx = -1;
|
||||||
for (const routeConfig of routes) {
|
for (const routeConfig of routes) {
|
||||||
idx++;
|
idx++;
|
||||||
let { src, headers, methods, handle } = routeConfig;
|
if (isHandler(routeConfig)) {
|
||||||
if (handle) {
|
if (routeConfig.handle === 'filesystem' && devServer) {
|
||||||
if (handle === 'filesystem' && devServer) {
|
|
||||||
if (await devServer.hasFilesystem(reqPathname)) {
|
if (await devServer.hasFilesystem(reqPathname)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -50,18 +55,12 @@ export default async function(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let { src, headers, methods } = routeConfig;
|
||||||
|
|
||||||
if (Array.isArray(methods) && reqMethod && !methods.includes(reqMethod)) {
|
if (Array.isArray(methods) && reqMethod && !methods.includes(reqMethod)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!src.startsWith('^')) {
|
|
||||||
src = `^${src}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!src.endsWith('$')) {
|
|
||||||
src = `${src}$`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const keys: string[] = [];
|
const keys: string[] = [];
|
||||||
const matcher = PCRE(`%${src}%i`, keys);
|
const matcher = PCRE(`%${src}%i`, keys);
|
||||||
const match =
|
const match =
|
||||||
@@ -98,7 +97,7 @@ export default async function(
|
|||||||
headers: combinedHeaders,
|
headers: combinedHeaders,
|
||||||
uri_args: query,
|
uri_args: query,
|
||||||
matched_route: routeConfig,
|
matched_route: routeConfig,
|
||||||
matched_route_idx: idx
|
matched_route_idx: idx,
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
@@ -114,7 +113,7 @@ export default async function(
|
|||||||
headers: combinedHeaders,
|
headers: combinedHeaders,
|
||||||
uri_args: query,
|
uri_args: query,
|
||||||
matched_route: routeConfig,
|
matched_route: routeConfig,
|
||||||
matched_route_idx: idx
|
matched_route_idx: idx,
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -127,7 +126,7 @@ export default async function(
|
|||||||
found: false,
|
found: false,
|
||||||
dest: reqPathname,
|
dest: reqPathname,
|
||||||
uri_args: query,
|
uri_args: query,
|
||||||
headers: combinedHeaders
|
headers: combinedHeaders,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,13 +13,15 @@ import serveHandler from 'serve-handler';
|
|||||||
import { watch, FSWatcher } from 'chokidar';
|
import { watch, FSWatcher } from 'chokidar';
|
||||||
import { parse as parseDotenv } from 'dotenv';
|
import { parse as parseDotenv } from 'dotenv';
|
||||||
import { basename, dirname, extname, join } from 'path';
|
import { basename, dirname, extname, join } from 'path';
|
||||||
|
import { getTransformedRoutes } from '@now/routing-utils';
|
||||||
import directoryTemplate from 'serve-handler/src/directory';
|
import directoryTemplate from 'serve-handler/src/directory';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
Builder,
|
||||||
FileFsRef,
|
FileFsRef,
|
||||||
PackageJson,
|
PackageJson,
|
||||||
detectBuilders,
|
detectBuilders,
|
||||||
detectRoutes
|
detectRoutes,
|
||||||
} from '@now/build-utils';
|
} from '@now/build-utils';
|
||||||
|
|
||||||
import { once } from '../once';
|
import { once } from '../once';
|
||||||
@@ -33,20 +35,29 @@ import { version as cliVersion } from '../../../package.json';
|
|||||||
import {
|
import {
|
||||||
createIgnore,
|
createIgnore,
|
||||||
staticFiles as getFiles,
|
staticFiles as getFiles,
|
||||||
getAllProjectFiles
|
getAllProjectFiles,
|
||||||
} from '../get-files';
|
} from '../get-files';
|
||||||
import { validateNowConfigBuilds, validateNowConfigRoutes } from './validate';
|
import {
|
||||||
|
validateNowConfigBuilds,
|
||||||
|
validateNowConfigRoutes,
|
||||||
|
validateNowConfigCleanUrls,
|
||||||
|
validateNowConfigHeaders,
|
||||||
|
validateNowConfigRedirects,
|
||||||
|
validateNowConfigRewrites,
|
||||||
|
validateNowConfigTrailingSlash,
|
||||||
|
validateNowConfigFunctions,
|
||||||
|
} from './validate';
|
||||||
|
|
||||||
import isURL from './is-url';
|
import isURL from './is-url';
|
||||||
import devRouter from './router';
|
import devRouter from './router';
|
||||||
import getMimeType from './mime-type';
|
import getMimeType from './mime-type';
|
||||||
import { getYarnPath } from './yarn-installer';
|
import { getYarnPath } from './yarn-installer';
|
||||||
import { executeBuild, getBuildMatches } from './builder';
|
import { executeBuild, getBuildMatches, shutdownBuilder } from './builder';
|
||||||
import { generateErrorMessage, generateHttpStatusDescription } from './errors';
|
import { generateErrorMessage, generateHttpStatusDescription } from './errors';
|
||||||
import {
|
import {
|
||||||
builderDirPromise,
|
builderDirPromise,
|
||||||
installBuilders,
|
installBuilders,
|
||||||
updateBuilders
|
updateBuilders,
|
||||||
} from './builder-cache';
|
} from './builder-cache';
|
||||||
|
|
||||||
// HTML templates
|
// HTML templates
|
||||||
@@ -60,7 +71,6 @@ import {
|
|||||||
EnvConfig,
|
EnvConfig,
|
||||||
NowConfig,
|
NowConfig,
|
||||||
DevServerOptions,
|
DevServerOptions,
|
||||||
BuildConfig,
|
|
||||||
BuildMatch,
|
BuildMatch,
|
||||||
BuildResult,
|
BuildResult,
|
||||||
BuilderInputs,
|
BuilderInputs,
|
||||||
@@ -70,7 +80,7 @@ import {
|
|||||||
InvokeResult,
|
InvokeResult,
|
||||||
ListenSpec,
|
ListenSpec,
|
||||||
RouteConfig,
|
RouteConfig,
|
||||||
RouteResult
|
RouteResult,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
interface FSEvent {
|
interface FSEvent {
|
||||||
@@ -78,16 +88,7 @@ interface FSEvent {
|
|||||||
path: string;
|
path: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NodeRequire {
|
function sortBuilders(buildA: Builder, buildB: Builder) {
|
||||||
(id: string): any;
|
|
||||||
cache: {
|
|
||||||
[name: string]: any;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
declare const __non_webpack_require__: NodeRequire;
|
|
||||||
|
|
||||||
function sortBuilders(buildA: BuildConfig, buildB: BuildConfig) {
|
|
||||||
if (buildA && buildA.use && buildA.use.startsWith('@now/static-build')) {
|
if (buildA && buildA.use && buildA.use.startsWith('@now/static-build')) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -182,6 +183,20 @@ export default class DevServer {
|
|||||||
const filesChanged: Set<string> = new Set();
|
const filesChanged: Set<string> = new Set();
|
||||||
const filesRemoved: Set<string> = new Set();
|
const filesRemoved: Set<string> = new Set();
|
||||||
|
|
||||||
|
const distPaths: string[] = [];
|
||||||
|
|
||||||
|
for (const buildMatch of this.buildMatches.values()) {
|
||||||
|
for (const buildResult of buildMatch.buildResults.values()) {
|
||||||
|
if (buildResult.distPath) {
|
||||||
|
distPaths.push(buildResult.distPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
events = events.filter(event =>
|
||||||
|
distPaths.every(distPath => !event.path.startsWith(distPath))
|
||||||
|
);
|
||||||
|
|
||||||
// First, update the `files` mapping of source files
|
// First, update the `files` mapping of source files
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
if (event.type === 'add') {
|
if (event.type === 'add') {
|
||||||
@@ -255,9 +270,7 @@ export default class DevServer {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.output.debug(
|
this.output.debug(
|
||||||
`Not rebuilding because \`shouldServe()\` returned \`false\` for "${
|
`Not rebuilding because \`shouldServe()\` returned \`false\` for "${match.use}" request path "${requestPath}"`
|
||||||
match.use
|
|
||||||
}" request path "${requestPath}"`
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -335,13 +348,18 @@ export default class DevServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete build matches that no longer exists
|
// Delete build matches that no longer exists
|
||||||
|
const ops: Promise<void>[] = [];
|
||||||
for (const src of this.buildMatches.keys()) {
|
for (const src of this.buildMatches.keys()) {
|
||||||
if (!sources.includes(src)) {
|
if (!sources.includes(src)) {
|
||||||
this.output.debug(`Removing build match for "${src}"`);
|
this.output.debug(`Removing build match for "${src}"`);
|
||||||
// TODO: shutdown lambda functions
|
const match = this.buildMatches.get(src);
|
||||||
|
if (match) {
|
||||||
|
ops.push(shutdownBuilder(match, this.output));
|
||||||
|
}
|
||||||
this.buildMatches.delete(src);
|
this.buildMatches.delete(src);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
await Promise.all(ops);
|
||||||
|
|
||||||
// Add the new matches to the `buildMatches` map
|
// Add the new matches to the `buildMatches` map
|
||||||
const blockingBuilds: Promise<void>[] = [];
|
const blockingBuilds: Promise<void>[] = [];
|
||||||
@@ -376,7 +394,7 @@ export default class DevServer {
|
|||||||
// Sort build matches to make sure `@now/static-build` is always last
|
// Sort build matches to make sure `@now/static-build` is always last
|
||||||
this.buildMatches = new Map(
|
this.buildMatches = new Map(
|
||||||
[...this.buildMatches.entries()].sort((matchA, matchB) => {
|
[...this.buildMatches.entries()].sort((matchA, matchB) => {
|
||||||
return sortBuilders(matchA[1] as BuildConfig, matchB[1] as BuildConfig);
|
return sortBuilders(matchA[1] as Builder, matchB[1] as Builder);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -390,33 +408,15 @@ export default class DevServer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const _require =
|
|
||||||
typeof __non_webpack_require__ === 'function'
|
|
||||||
? __non_webpack_require__
|
|
||||||
: require;
|
|
||||||
|
|
||||||
// The `require()` cache for the builder's assets must be purged
|
|
||||||
const builderDir = await builderDirPromise;
|
|
||||||
const updatedBuilderPaths = updatedBuilders.map(b =>
|
|
||||||
join(builderDir, 'node_modules', b)
|
|
||||||
);
|
|
||||||
for (const id of Object.keys(_require.cache)) {
|
|
||||||
for (const path of updatedBuilderPaths) {
|
|
||||||
if (id.startsWith(path)) {
|
|
||||||
this.output.debug(`Purging require cache for "${id}"`);
|
|
||||||
delete _require.cache[id];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete any build matches that have the old builder required already
|
// Delete any build matches that have the old builder required already
|
||||||
for (const buildMatch of this.buildMatches.values()) {
|
for (const buildMatch of this.buildMatches.values()) {
|
||||||
const {
|
const {
|
||||||
src,
|
src,
|
||||||
builderWithPkg: { package: pkg }
|
builderWithPkg: { package: pkg },
|
||||||
} = buildMatch;
|
} = buildMatch;
|
||||||
if (pkg.name === '@now/static') continue;
|
if (pkg.name === '@now/static') continue;
|
||||||
if (updatedBuilders.includes(pkg.name)) {
|
if (pkg.name && updatedBuilders.includes(pkg.name)) {
|
||||||
|
shutdownBuilder(buildMatch, this.output);
|
||||||
this.buildMatches.delete(src);
|
this.buildMatches.delete(src);
|
||||||
this.output.debug(`Invalidated build match for "${src}"`);
|
this.output.debug(`Invalidated build match for "${src}"`);
|
||||||
}
|
}
|
||||||
@@ -441,7 +441,7 @@ export default class DevServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
this.validateEnvConfig(fileName, base || {}, env);
|
return this.validateEnvConfig(fileName, base || {}, env);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof MissingDotenvVarsError) {
|
if (err instanceof MissingDotenvVarsError) {
|
||||||
this.output.error(err.message);
|
this.output.error(err.message);
|
||||||
@@ -450,7 +450,7 @@ export default class DevServer {
|
|||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { ...base, ...env };
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getNowConfig(
|
async getNowConfig(
|
||||||
@@ -473,7 +473,6 @@ export default class DevServer {
|
|||||||
isInitialLoad: boolean = false
|
isInitialLoad: boolean = false
|
||||||
): Promise<NowConfig> {
|
): Promise<NowConfig> {
|
||||||
if (canUseCache && this.cachedNowConfig) {
|
if (canUseCache && this.cachedNowConfig) {
|
||||||
this.output.debug('Using cached `now.json` config');
|
|
||||||
return this.cachedNowConfig;
|
return this.cachedNowConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -506,8 +505,6 @@ export default class DevServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// no builds -> zero config
|
|
||||||
if (!config.builds || config.builds.length === 0) {
|
|
||||||
const allFiles = await getAllProjectFiles(this.cwd, this.output);
|
const allFiles = await getAllProjectFiles(this.cwd, this.output);
|
||||||
const files = allFiles.filter(this.filter);
|
const files = allFiles.filter(this.filter);
|
||||||
|
|
||||||
@@ -516,8 +513,22 @@ export default class DevServer {
|
|||||||
`filtered out ${allFiles.length - files.length} files`
|
`filtered out ${allFiles.length - files.length} files`
|
||||||
);
|
);
|
||||||
|
|
||||||
const { builders, errors } = await detectBuilders(files, pkg, {
|
await this.validateNowConfig(config);
|
||||||
tag: getDistTag(cliVersion) === 'canary' ? 'canary' : 'latest'
|
const { error: routeError, routes: maybeRoutes } = getTransformedRoutes({
|
||||||
|
nowConfig: config,
|
||||||
|
filePaths: files,
|
||||||
|
});
|
||||||
|
if (routeError) {
|
||||||
|
this.output.error(routeError.message);
|
||||||
|
await this.exit();
|
||||||
|
}
|
||||||
|
config.routes = maybeRoutes || [];
|
||||||
|
|
||||||
|
// no builds -> zero config
|
||||||
|
if (!config.builds || config.builds.length === 0) {
|
||||||
|
const { builders, warnings, errors } = await detectBuilders(files, pkg, {
|
||||||
|
tag: getDistTag(cliVersion) === 'canary' ? 'canary' : 'latest',
|
||||||
|
functions: config.functions,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (errors) {
|
if (errors) {
|
||||||
@@ -525,6 +536,10 @@ export default class DevServer {
|
|||||||
await this.exit();
|
await this.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (warnings && warnings.length > 0) {
|
||||||
|
warnings.forEach(warning => this.output.warn(warning.message));
|
||||||
|
}
|
||||||
|
|
||||||
if (builders) {
|
if (builders) {
|
||||||
const { defaultRoutes, error: routesError } = await detectRoutes(
|
const { defaultRoutes, error: routesError } = await detectRoutes(
|
||||||
files,
|
files,
|
||||||
@@ -583,32 +598,41 @@ export default class DevServer {
|
|||||||
return pkg;
|
return pkg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async tryValidateOrExit(
|
||||||
|
config: NowConfig,
|
||||||
|
validate: (c: NowConfig) => string | null
|
||||||
|
): Promise<void> {
|
||||||
|
const message = validate(config);
|
||||||
|
|
||||||
|
if (message) {
|
||||||
|
this.output.error(message);
|
||||||
|
await this.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async validateNowConfig(config: NowConfig): Promise<void> {
|
async validateNowConfig(config: NowConfig): Promise<void> {
|
||||||
if (config.version === 1) {
|
if (config.version === 1) {
|
||||||
this.output.error('Only `version: 2` is supported by `now dev`');
|
this.output.error('Only `version: 2` is supported by `now dev`');
|
||||||
await this.exit(1);
|
await this.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildsError = validateNowConfigBuilds(config);
|
await this.tryValidateOrExit(config, validateNowConfigBuilds);
|
||||||
|
await this.tryValidateOrExit(config, validateNowConfigRoutes);
|
||||||
if (buildsError) {
|
await this.tryValidateOrExit(config, validateNowConfigCleanUrls);
|
||||||
this.output.error(buildsError);
|
await this.tryValidateOrExit(config, validateNowConfigHeaders);
|
||||||
await this.exit(1);
|
await this.tryValidateOrExit(config, validateNowConfigRedirects);
|
||||||
}
|
await this.tryValidateOrExit(config, validateNowConfigRewrites);
|
||||||
|
await this.tryValidateOrExit(config, validateNowConfigTrailingSlash);
|
||||||
const routesError = validateNowConfigRoutes(config);
|
await this.tryValidateOrExit(config, validateNowConfigFunctions);
|
||||||
|
|
||||||
if (routesError) {
|
|
||||||
this.output.error(routesError);
|
|
||||||
await this.exit(1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
validateEnvConfig(
|
validateEnvConfig(
|
||||||
type: string,
|
type: string,
|
||||||
env: EnvConfig = {},
|
env: EnvConfig = {},
|
||||||
localEnv: EnvConfig = {}
|
localEnv: EnvConfig = {}
|
||||||
): void {
|
): EnvConfig {
|
||||||
|
// Validate if there are any missing env vars defined in `now.json`,
|
||||||
|
// but not in the `.env` / `.build.env` file
|
||||||
const missing: string[] = Object.entries(env)
|
const missing: string[] = Object.entries(env)
|
||||||
.filter(
|
.filter(
|
||||||
([name, value]) =>
|
([name, value]) =>
|
||||||
@@ -617,9 +641,36 @@ export default class DevServer {
|
|||||||
!hasOwnProperty(localEnv, name)
|
!hasOwnProperty(localEnv, name)
|
||||||
)
|
)
|
||||||
.map(([name]) => name);
|
.map(([name]) => name);
|
||||||
if (missing.length >= 1) {
|
|
||||||
|
if (missing.length > 0) {
|
||||||
throw new MissingDotenvVarsError(type, missing);
|
throw new MissingDotenvVarsError(type, missing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const merged: EnvConfig = { ...env, ...localEnv };
|
||||||
|
|
||||||
|
// Validate that the env var name matches what AWS Lambda allows:
|
||||||
|
// - https://docs.aws.amazon.com/lambda/latest/dg/env_variables.html
|
||||||
|
let hasInvalidName = false;
|
||||||
|
for (const key of Object.keys(merged)) {
|
||||||
|
if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(key)) {
|
||||||
|
this.output.warn(
|
||||||
|
`Ignoring ${type
|
||||||
|
.split('.')
|
||||||
|
.slice(1)
|
||||||
|
.reverse()
|
||||||
|
.join(' ')} var ${JSON.stringify(key)} because name is invalid`
|
||||||
|
);
|
||||||
|
hasInvalidName = true;
|
||||||
|
delete merged[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasInvalidName) {
|
||||||
|
this.output.log(
|
||||||
|
'Env var names must start with letters, and can only contain alphanumeric characters and underscores'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return merged;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -652,7 +703,7 @@ export default class DevServer {
|
|||||||
const nowConfigBuild = nowConfig.build || {};
|
const nowConfigBuild = nowConfig.build || {};
|
||||||
const [env, buildEnv] = await Promise.all([
|
const [env, buildEnv] = await Promise.all([
|
||||||
this.getLocalEnv('.env', nowConfig.env),
|
this.getLocalEnv('.env', nowConfig.env),
|
||||||
this.getLocalEnv('.env.build', nowConfigBuild.env)
|
this.getLocalEnv('.env.build', nowConfigBuild.env),
|
||||||
]);
|
]);
|
||||||
Object.assign(process.env, buildEnv);
|
Object.assign(process.env, buildEnv);
|
||||||
this.env = env;
|
this.env = env;
|
||||||
@@ -670,8 +721,8 @@ export default class DevServer {
|
|||||||
|
|
||||||
const builders: Set<string> = new Set(
|
const builders: Set<string> = new Set(
|
||||||
(nowConfig.builds || [])
|
(nowConfig.builds || [])
|
||||||
.filter((b: BuildConfig) => b.use)
|
.filter((b: Builder) => b.use)
|
||||||
.map((b: BuildConfig) => b.use as string)
|
.map((b: Builder) => b.use as string)
|
||||||
);
|
);
|
||||||
|
|
||||||
await installBuilders(builders, this.yarnPath, this.output);
|
await installBuilders(builders, this.yarnPath, this.output);
|
||||||
@@ -684,10 +735,12 @@ export default class DevServer {
|
|||||||
this.yarnPath,
|
this.yarnPath,
|
||||||
this.output
|
this.output
|
||||||
)
|
)
|
||||||
.then(updatedBuilders =>
|
.then(updatedBuilders => {
|
||||||
this.invalidateBuildMatches(nowConfig, updatedBuilders)
|
this.updateBuildersPromise = null;
|
||||||
)
|
this.invalidateBuildMatches(nowConfig, updatedBuilders);
|
||||||
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
|
this.updateBuildersPromise = null;
|
||||||
this.output.error(`Failed to update builders: ${err.message}`);
|
this.output.error(`Failed to update builders: ${err.message}`);
|
||||||
this.output.debug(err.stack);
|
this.output.debug(err.stack);
|
||||||
});
|
});
|
||||||
@@ -716,7 +769,7 @@ export default class DevServer {
|
|||||||
ignoreInitial: true,
|
ignoreInitial: true,
|
||||||
useFsEvents: false,
|
useFsEvents: false,
|
||||||
usePolling: false,
|
usePolling: false,
|
||||||
persistent: true
|
persistent: true,
|
||||||
});
|
});
|
||||||
this.watcher.on('add', (path: string) => {
|
this.watcher.on('add', (path: string) => {
|
||||||
this.enqueueFsEvent('add', path);
|
this.enqueueFsEvent('add', path);
|
||||||
@@ -786,22 +839,18 @@ export default class DevServer {
|
|||||||
const ops: Promise<void>[] = [];
|
const ops: Promise<void>[] = [];
|
||||||
|
|
||||||
for (const match of this.buildMatches.values()) {
|
for (const match of this.buildMatches.values()) {
|
||||||
if (!match.buildOutput) continue;
|
ops.push(shutdownBuilder(match, this.output));
|
||||||
|
|
||||||
for (const asset of Object.values(match.buildOutput)) {
|
|
||||||
if (asset.type === 'Lambda' && asset.fn) {
|
|
||||||
ops.push(asset.fn.destroy());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ops.push(close(this.server));
|
ops.push(close(this.server));
|
||||||
|
|
||||||
if (this.watcher) {
|
if (this.watcher) {
|
||||||
|
this.output.debug(`Closing file watcher`);
|
||||||
this.watcher.close();
|
this.watcher.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.updateBuildersPromise) {
|
if (this.updateBuildersPromise) {
|
||||||
|
this.output.debug(`Waiting for builders update to complete`);
|
||||||
ops.push(this.updateBuildersPromise);
|
ops.push(this.updateBuildersPromise);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -856,8 +905,8 @@ export default class DevServer {
|
|||||||
const json = JSON.stringify({
|
const json = JSON.stringify({
|
||||||
error: {
|
error: {
|
||||||
code: statusCode,
|
code: statusCode,
|
||||||
message: errorMessage.title
|
message: errorMessage.title,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
body = `${json}\n`;
|
body = `${json}\n`;
|
||||||
} else if (accept.includes('html')) {
|
} else if (accept.includes('html')) {
|
||||||
@@ -870,7 +919,7 @@ export default class DevServer {
|
|||||||
http_status_code: statusCode,
|
http_status_code: statusCode,
|
||||||
http_status_description,
|
http_status_description,
|
||||||
error_code,
|
error_code,
|
||||||
now_id: nowRequestId
|
now_id: nowRequestId,
|
||||||
});
|
});
|
||||||
} else if (statusCode === 502) {
|
} else if (statusCode === 502) {
|
||||||
view = errorTemplate502({
|
view = errorTemplate502({
|
||||||
@@ -878,19 +927,19 @@ export default class DevServer {
|
|||||||
http_status_code: statusCode,
|
http_status_code: statusCode,
|
||||||
http_status_description,
|
http_status_description,
|
||||||
error_code,
|
error_code,
|
||||||
now_id: nowRequestId
|
now_id: nowRequestId,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
view = errorTemplate({
|
view = errorTemplate({
|
||||||
http_status_code: statusCode,
|
http_status_code: statusCode,
|
||||||
http_status_description,
|
http_status_description,
|
||||||
now_id: nowRequestId
|
now_id: nowRequestId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
body = errorTemplateBase({
|
body = errorTemplateBase({
|
||||||
http_status_code: statusCode,
|
http_status_code: statusCode,
|
||||||
http_status_description,
|
http_status_description,
|
||||||
view
|
view,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
res.setHeader('content-type', 'text/plain; charset=utf-8');
|
res.setHeader('content-type', 'text/plain; charset=utf-8');
|
||||||
@@ -917,7 +966,7 @@ export default class DevServer {
|
|||||||
res.setHeader('content-type', 'application/json');
|
res.setHeader('content-type', 'application/json');
|
||||||
const json = JSON.stringify({
|
const json = JSON.stringify({
|
||||||
redirect: location,
|
redirect: location,
|
||||||
status: String(statusCode)
|
status: String(statusCode),
|
||||||
});
|
});
|
||||||
body = `${json}\n`;
|
body = `${json}\n`;
|
||||||
} else if (accept.includes('html')) {
|
} else if (accept.includes('html')) {
|
||||||
@@ -949,7 +998,7 @@ export default class DevServer {
|
|||||||
server: 'now',
|
server: 'now',
|
||||||
'x-now-trace': 'dev1',
|
'x-now-trace': 'dev1',
|
||||||
'x-now-id': nowRequestId,
|
'x-now-id': nowRequestId,
|
||||||
'x-now-cache': 'MISS'
|
'x-now-cache': 'MISS',
|
||||||
};
|
};
|
||||||
for (const [name, value] of Object.entries(allHeaders)) {
|
for (const [name, value] of Object.entries(allHeaders)) {
|
||||||
res.setHeader(name, value);
|
res.setHeader(name, value);
|
||||||
@@ -976,7 +1025,7 @@ export default class DevServer {
|
|||||||
'x-now-deployment-url': host,
|
'x-now-deployment-url': host,
|
||||||
'x-now-id': nowRequestId,
|
'x-now-id': nowRequestId,
|
||||||
'x-now-log-id': nowRequestId.split('-')[2],
|
'x-now-log-id': nowRequestId.split('-')[2],
|
||||||
'x-zeit-co-forwarded-for': ip
|
'x-zeit-co-forwarded-for': ip,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1057,7 +1106,7 @@ export default class DevServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const method = req.method || 'GET';
|
const method = req.method || 'GET';
|
||||||
this.output.log(`${chalk.bold(method)} ${req.url}`);
|
this.output.debug(`${chalk.bold(method)} ${req.url}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const nowConfig = await this.getNowConfig();
|
const nowConfig = await this.getNowConfig();
|
||||||
@@ -1139,7 +1188,7 @@ export default class DevServer {
|
|||||||
|
|
||||||
if (status) {
|
if (status) {
|
||||||
res.statusCode = status;
|
res.statusCode = status;
|
||||||
if ([301, 302, 303].includes(status)) {
|
if (300 <= status && status <= 399) {
|
||||||
await this.sendRedirect(
|
await this.sendRedirect(
|
||||||
req,
|
req,
|
||||||
res,
|
res,
|
||||||
@@ -1183,9 +1232,7 @@ export default class DevServer {
|
|||||||
Object.assign(origUrl.query, uri_args);
|
Object.assign(origUrl.query, uri_args);
|
||||||
const newUrl = url.format(origUrl);
|
const newUrl = url.format(origUrl);
|
||||||
this.output.debug(
|
this.output.debug(
|
||||||
`Checking build result's ${
|
`Checking build result's ${buildResult.routes.length} \`routes\` to match ${newUrl}`
|
||||||
buildResult.routes.length
|
|
||||||
} \`routes\` to match ${newUrl}`
|
|
||||||
);
|
);
|
||||||
const matchedRoute = await devRouter(
|
const matchedRoute = await devRouter(
|
||||||
newUrl,
|
newUrl,
|
||||||
@@ -1210,12 +1257,12 @@ export default class DevServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let foundAsset = findAsset(match, requestPath);
|
let foundAsset = findAsset(match, requestPath, nowConfig);
|
||||||
if ((!foundAsset || this.shouldRebuild(req)) && callLevel === 0) {
|
if ((!foundAsset || this.shouldRebuild(req)) && callLevel === 0) {
|
||||||
await this.triggerBuild(match, buildRequestPath, req);
|
await this.triggerBuild(match, buildRequestPath, req);
|
||||||
|
|
||||||
// Since the `asset` was re-built, resolve it again to get the new asset
|
// Since the `asset` was re-built, resolve it again to get the new asset
|
||||||
foundAsset = findAsset(match, requestPath);
|
foundAsset = findAsset(match, requestPath, nowConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!foundAsset) {
|
if (!foundAsset) {
|
||||||
@@ -1224,7 +1271,10 @@ export default class DevServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { asset, assetKey } = foundAsset;
|
const { asset, assetKey } = foundAsset;
|
||||||
this.output.debug(`Serving asset: [${asset.type}] ${assetKey}`);
|
this.output.debug(
|
||||||
|
`Serving asset: [${asset.type}] ${assetKey} ${(asset as any)
|
||||||
|
.contentType || ''}`
|
||||||
|
);
|
||||||
|
|
||||||
/* eslint-disable no-case-declarations */
|
/* eslint-disable no-case-declarations */
|
||||||
switch (asset.type) {
|
switch (asset.type) {
|
||||||
@@ -1238,17 +1288,17 @@ export default class DevServer {
|
|||||||
headers: [
|
headers: [
|
||||||
{
|
{
|
||||||
key: 'Content-Type',
|
key: 'Content-Type',
|
||||||
value: getMimeType(assetKey)
|
value: asset.contentType || getMimeType(assetKey),
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
case 'FileBlob':
|
case 'FileBlob':
|
||||||
const headers: http.OutgoingHttpHeaders = {
|
const headers: http.OutgoingHttpHeaders = {
|
||||||
'Content-Length': asset.data.length,
|
'Content-Length': asset.data.length,
|
||||||
'Content-Type': getMimeType(assetKey)
|
'Content-Type': asset.contentType || getMimeType(assetKey),
|
||||||
};
|
};
|
||||||
this.setResponseHeaders(res, nowRequestId, headers);
|
this.setResponseHeaders(res, nowRequestId, headers);
|
||||||
res.end(asset.data);
|
res.end(asset.data);
|
||||||
@@ -1273,7 +1323,7 @@ export default class DevServer {
|
|||||||
Object.assign(parsed.query, uri_args);
|
Object.assign(parsed.query, uri_args);
|
||||||
const path = url.format({
|
const path = url.format({
|
||||||
pathname: parsed.pathname,
|
pathname: parsed.pathname,
|
||||||
query: parsed.query
|
query: parsed.query,
|
||||||
});
|
});
|
||||||
|
|
||||||
const body = await rawBody(req);
|
const body = await rawBody(req);
|
||||||
@@ -1283,7 +1333,7 @@ export default class DevServer {
|
|||||||
path,
|
path,
|
||||||
headers: this.getNowProxyHeaders(req, nowRequestId),
|
headers: this.getNowProxyHeaders(req, nowRequestId),
|
||||||
encoding: 'base64',
|
encoding: 'base64',
|
||||||
body: body.toString('base64')
|
body: body.toString('base64'),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.output.debug(`Invoking lambda: "${assetKey}" with ${path}`);
|
this.output.debug(`Invoking lambda: "${assetKey}" with ${path}`);
|
||||||
@@ -1292,7 +1342,7 @@ export default class DevServer {
|
|||||||
try {
|
try {
|
||||||
result = await asset.fn<InvokeResult>({
|
result = await asset.fn<InvokeResult>({
|
||||||
Action: 'Invoke',
|
Action: 'Invoke',
|
||||||
body: JSON.stringify(payload)
|
body: JSON.stringify(payload),
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@@ -1379,7 +1429,7 @@ export default class DevServer {
|
|||||||
relative: href,
|
relative: href,
|
||||||
ext,
|
ext,
|
||||||
title: href,
|
title: href,
|
||||||
base
|
base,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1391,13 +1441,13 @@ export default class DevServer {
|
|||||||
const paths = [
|
const paths = [
|
||||||
{
|
{
|
||||||
name: directory,
|
name: directory,
|
||||||
url: requestPath
|
url: requestPath,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
const directoryHtml = directoryTemplate({
|
const directoryHtml = directoryTemplate({
|
||||||
files,
|
files,
|
||||||
paths,
|
paths,
|
||||||
directory
|
directory,
|
||||||
});
|
});
|
||||||
this.setResponseHeaders(res, nowRequestId);
|
this.setResponseHeaders(res, nowRequestId);
|
||||||
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
||||||
@@ -1409,25 +1459,6 @@ export default class DevServer {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Serve project directory as a static deployment.
|
|
||||||
*/
|
|
||||||
serveProjectAsStatic = async (
|
|
||||||
req: http.IncomingMessage,
|
|
||||||
res: http.ServerResponse,
|
|
||||||
nowRequestId: string
|
|
||||||
) => {
|
|
||||||
const filePath = req.url ? req.url.replace(/^\//, '') : '';
|
|
||||||
|
|
||||||
if (filePath && typeof this.files[filePath] === 'undefined') {
|
|
||||||
await this.send404(req, res, nowRequestId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setResponseHeaders(res, nowRequestId);
|
|
||||||
return serveStaticFile(req, res, this.cwd, { cleanUrls: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
async hasFilesystem(dest: string): Promise<boolean> {
|
async hasFilesystem(dest: string): Promise<boolean> {
|
||||||
const requestPath = dest.replace(/^\//, '');
|
const requestPath = dest.replace(/^\//, '');
|
||||||
if (
|
if (
|
||||||
@@ -1459,7 +1490,7 @@ function proxyPass(
|
|||||||
ws: true,
|
ws: true,
|
||||||
xfwd: true,
|
xfwd: true,
|
||||||
ignorePath: true,
|
ignorePath: true,
|
||||||
target: dest
|
target: dest,
|
||||||
});
|
});
|
||||||
|
|
||||||
proxy.on('error', (error: NodeJS.ErrnoException) => {
|
proxy.on('error', (error: NodeJS.ErrnoException) => {
|
||||||
@@ -1490,7 +1521,7 @@ function serveStaticFile(
|
|||||||
public: cwd,
|
public: cwd,
|
||||||
cleanUrls: false,
|
cleanUrls: false,
|
||||||
etag: true,
|
etag: true,
|
||||||
...opts
|
...opts,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1544,22 +1575,49 @@ async function shouldServe(
|
|||||||
isFilesystem?: boolean
|
isFilesystem?: boolean
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const {
|
const {
|
||||||
src: entrypoint,
|
src,
|
||||||
config,
|
config,
|
||||||
builderWithPkg: { builder }
|
builderWithPkg: { builder },
|
||||||
} = match;
|
} = match;
|
||||||
if (typeof builder.shouldServe === 'function') {
|
const nowConfig = await devServer.getNowConfig();
|
||||||
|
const cleanSrc = src.endsWith('.html') ? src.slice(0, -5) : src;
|
||||||
|
const trimmedPath = requestPath.endsWith('/')
|
||||||
|
? requestPath.slice(0, -1)
|
||||||
|
: requestPath;
|
||||||
|
|
||||||
|
if (
|
||||||
|
nowConfig.cleanUrls &&
|
||||||
|
nowConfig.trailingSlash &&
|
||||||
|
cleanSrc === trimmedPath
|
||||||
|
) {
|
||||||
|
// Mimic fmeta-util and convert cleanUrls and trailingSlash
|
||||||
|
return true;
|
||||||
|
} else if (
|
||||||
|
nowConfig.cleanUrls &&
|
||||||
|
!nowConfig.trailingSlash &&
|
||||||
|
cleanSrc === requestPath
|
||||||
|
) {
|
||||||
|
// Mimic fmeta-util and convert cleanUrls
|
||||||
|
return true;
|
||||||
|
} else if (
|
||||||
|
!nowConfig.cleanUrls &&
|
||||||
|
nowConfig.trailingSlash &&
|
||||||
|
src === trimmedPath
|
||||||
|
) {
|
||||||
|
// Mimic fmeta-util and convert trailingSlash
|
||||||
|
return true;
|
||||||
|
} else if (typeof builder.shouldServe === 'function') {
|
||||||
const shouldServe = await builder.shouldServe({
|
const shouldServe = await builder.shouldServe({
|
||||||
entrypoint,
|
entrypoint: src,
|
||||||
files,
|
files,
|
||||||
config,
|
config,
|
||||||
requestPath,
|
requestPath,
|
||||||
workPath: devServer.cwd
|
workPath: devServer.cwd,
|
||||||
});
|
});
|
||||||
if (shouldServe) {
|
if (shouldServe) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (findAsset(match, requestPath)) {
|
} else if (findAsset(match, requestPath, nowConfig)) {
|
||||||
// If there's no `shouldServe()` function, then look up if there's
|
// If there's no `shouldServe()` function, then look up if there's
|
||||||
// a matching build asset on the `match` that has already been built.
|
// a matching build asset on the `match` that has already been built.
|
||||||
return true;
|
return true;
|
||||||
@@ -1597,7 +1655,8 @@ async function findMatchingRoute(
|
|||||||
|
|
||||||
function findAsset(
|
function findAsset(
|
||||||
match: BuildMatch,
|
match: BuildMatch,
|
||||||
requestPath: string
|
requestPath: string,
|
||||||
|
nowConfig: NowConfig
|
||||||
): { asset: BuilderOutput; assetKey: string } | void {
|
): { asset: BuilderOutput; assetKey: string } | void {
|
||||||
if (!match.buildOutput) {
|
if (!match.buildOutput) {
|
||||||
return;
|
return;
|
||||||
@@ -1605,6 +1664,10 @@ function findAsset(
|
|||||||
let assetKey: string = requestPath.replace(/\/$/, '');
|
let assetKey: string = requestPath.replace(/\/$/, '');
|
||||||
let asset = match.buildOutput[requestPath];
|
let asset = match.buildOutput[requestPath];
|
||||||
|
|
||||||
|
if (nowConfig.trailingSlash && requestPath.endsWith('/')) {
|
||||||
|
asset = match.buildOutput[requestPath.slice(0, -1)];
|
||||||
|
}
|
||||||
|
|
||||||
// In the case of an index path, fall back to iterating over the
|
// In the case of an index path, fall back to iterating over the
|
||||||
// builder outputs and doing an "is index" check until a match is found.
|
// builder outputs and doing an "is index" check until a match is found.
|
||||||
if (!asset) {
|
if (!asset) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export const version = 2;
|
|||||||
|
|
||||||
export function build({ files, entrypoint }: BuilderParams): BuildResult {
|
export function build({ files, entrypoint }: BuilderParams): BuildResult {
|
||||||
const output = {
|
const output = {
|
||||||
[entrypoint]: files[entrypoint]
|
[entrypoint]: files[entrypoint],
|
||||||
};
|
};
|
||||||
const watch = [entrypoint];
|
const watch = [entrypoint];
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ export function build({ files, entrypoint }: BuilderParams): BuildResult {
|
|||||||
export function shouldServe({
|
export function shouldServe({
|
||||||
entrypoint,
|
entrypoint,
|
||||||
files,
|
files,
|
||||||
requestPath
|
requestPath,
|
||||||
}: ShouldServeParams) {
|
}: ShouldServeParams) {
|
||||||
if (isIndex(entrypoint)) {
|
if (isIndex(entrypoint)) {
|
||||||
const indexPath = join(requestPath, basename(entrypoint));
|
const indexPath = join(requestPath, basename(entrypoint));
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
import http from 'http';
|
import http from 'http';
|
||||||
import { ChildProcess } from 'child_process';
|
import { ChildProcess } from 'child_process';
|
||||||
import { Lambda as FunLambda } from '@zeit/fun';
|
import { Lambda as FunLambda } from '@zeit/fun';
|
||||||
import { FileBlob, FileFsRef, Lambda } from '@now/build-utils';
|
import {
|
||||||
|
Builder as BuildConfig,
|
||||||
|
FileBlob,
|
||||||
|
FileFsRef,
|
||||||
|
Lambda,
|
||||||
|
PackageJson,
|
||||||
|
BuilderFunctions,
|
||||||
|
} from '@now/build-utils';
|
||||||
|
import { NowRedirect, NowRewrite, NowHeader, Route } from '@now/routing-utils';
|
||||||
import { Output } from '../output';
|
import { Output } from '../output';
|
||||||
|
|
||||||
export interface DevServerOptions {
|
export interface DevServerOptions {
|
||||||
@@ -13,12 +21,6 @@ export interface EnvConfig {
|
|||||||
[name: string]: string | undefined;
|
[name: string]: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BuildConfig {
|
|
||||||
src: string;
|
|
||||||
use?: string;
|
|
||||||
config?: object;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BuildMatch extends BuildConfig {
|
export interface BuildMatch extends BuildConfig {
|
||||||
builderWithPkg: BuilderWithPackage;
|
builderWithPkg: BuilderWithPackage;
|
||||||
buildOutput: BuilderOutputs;
|
buildOutput: BuilderOutputs;
|
||||||
@@ -27,15 +29,7 @@ export interface BuildMatch extends BuildConfig {
|
|||||||
buildProcess?: ChildProcess;
|
buildProcess?: ChildProcess;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RouteConfig {
|
export type RouteConfig = Route;
|
||||||
src: string;
|
|
||||||
dest: string;
|
|
||||||
methods?: string[];
|
|
||||||
headers?: HttpHeadersConfig;
|
|
||||||
status?: number;
|
|
||||||
handle?: string;
|
|
||||||
continue?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NowConfig {
|
export interface NowConfig {
|
||||||
name?: string;
|
name?: string;
|
||||||
@@ -47,6 +41,12 @@ export interface NowConfig {
|
|||||||
builds?: BuildConfig[];
|
builds?: BuildConfig[];
|
||||||
routes?: RouteConfig[];
|
routes?: RouteConfig[];
|
||||||
files?: string[];
|
files?: string[];
|
||||||
|
cleanUrls?: boolean;
|
||||||
|
rewrites?: NowRewrite[];
|
||||||
|
redirects?: NowRedirect[];
|
||||||
|
headers?: NowHeader[];
|
||||||
|
trailingSlash?: boolean;
|
||||||
|
functions?: BuilderFunctions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HttpHandler {
|
export interface HttpHandler {
|
||||||
@@ -100,7 +100,7 @@ export interface BuilderConfigAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Builder {
|
export interface Builder {
|
||||||
version?: 2;
|
version?: 1 | 2 | 3;
|
||||||
config?: BuilderConfigAttr;
|
config?: BuilderConfigAttr;
|
||||||
build(
|
build(
|
||||||
params: BuilderParams
|
params: BuilderParams
|
||||||
@@ -119,6 +119,14 @@ export interface BuildResult {
|
|||||||
output: BuilderOutputs;
|
output: BuilderOutputs;
|
||||||
routes: RouteConfig[];
|
routes: RouteConfig[];
|
||||||
watch: string[];
|
watch: string[];
|
||||||
|
distPath?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BuildResultV3 {
|
||||||
|
output: Lambda;
|
||||||
|
routes: RouteConfig[];
|
||||||
|
watch: string[];
|
||||||
|
distPath?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ShouldServeParams {
|
export interface ShouldServeParams {
|
||||||
@@ -129,18 +137,10 @@ export interface ShouldServeParams {
|
|||||||
workPath: string;
|
workPath: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Package {
|
|
||||||
name: string;
|
|
||||||
version: string;
|
|
||||||
scripts?: { [key: string]: string };
|
|
||||||
dependencies?: { [name: string]: string };
|
|
||||||
devDependencies?: { [name: string]: string };
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BuilderWithPackage {
|
export interface BuilderWithPackage {
|
||||||
runInProcess?: boolean;
|
runInProcess?: boolean;
|
||||||
builder: Readonly<Builder>;
|
builder: Readonly<Builder>;
|
||||||
package: Readonly<Package>;
|
package: Readonly<PackageJson>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HttpHeadersConfig {
|
export interface HttpHeadersConfig {
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import Ajv from 'ajv';
|
import Ajv from 'ajv';
|
||||||
import { schema as routesSchema } from '@now/routing-utils';
|
import {
|
||||||
|
routesSchema,
|
||||||
|
cleanUrlsSchema,
|
||||||
|
headersSchema,
|
||||||
|
redirectsSchema,
|
||||||
|
rewritesSchema,
|
||||||
|
trailingSlashSchema,
|
||||||
|
} from '@now/routing-utils';
|
||||||
import { NowConfig } from './types';
|
import { NowConfig } from './types';
|
||||||
|
|
||||||
const ajv = new Ajv();
|
const ajv = new Ajv();
|
||||||
@@ -16,52 +23,106 @@ const buildsSchema = {
|
|||||||
src: {
|
src: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
minLength: 1,
|
minLength: 1,
|
||||||
maxLength: 4096
|
maxLength: 4096,
|
||||||
},
|
},
|
||||||
use: {
|
use: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
minLength: 3,
|
minLength: 3,
|
||||||
maxLength: 256
|
maxLength: 256,
|
||||||
|
},
|
||||||
|
config: { type: 'object' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const functionsSchema = {
|
||||||
|
type: 'object',
|
||||||
|
minProperties: 1,
|
||||||
|
maxProperties: 50,
|
||||||
|
additionalProperties: false,
|
||||||
|
patternProperties: {
|
||||||
|
'^.{1,256}$': {
|
||||||
|
type: 'object',
|
||||||
|
additionalProperties: false,
|
||||||
|
properties: {
|
||||||
|
runtime: {
|
||||||
|
type: 'string',
|
||||||
|
maxLength: 256,
|
||||||
|
},
|
||||||
|
memory: {
|
||||||
|
enum: Object.keys(Array.from({ length: 50 }))
|
||||||
|
.slice(2, 48)
|
||||||
|
.map(x => Number(x) * 64),
|
||||||
|
},
|
||||||
|
maxDuration: {
|
||||||
|
type: 'number',
|
||||||
|
minimum: 1,
|
||||||
|
maximum: 900,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
config: { type: 'object' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const validateBuilds = ajv.compile(buildsSchema);
|
const validateBuilds = ajv.compile(buildsSchema);
|
||||||
const validateRoutes = ajv.compile(routesSchema);
|
const validateRoutes = ajv.compile(routesSchema);
|
||||||
|
const validateCleanUrls = ajv.compile(cleanUrlsSchema);
|
||||||
|
const validateHeaders = ajv.compile(headersSchema);
|
||||||
|
const validateRedirects = ajv.compile(redirectsSchema);
|
||||||
|
const validateRewrites = ajv.compile(rewritesSchema);
|
||||||
|
const validateTrailingSlash = ajv.compile(trailingSlashSchema);
|
||||||
|
const validateFunctions = ajv.compile(functionsSchema);
|
||||||
|
|
||||||
export function validateNowConfigBuilds({ builds }: NowConfig) {
|
export function validateNowConfigBuilds(config: NowConfig) {
|
||||||
if (!builds) {
|
return validateKey(config, 'builds', validateBuilds);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateNowConfigRoutes(config: NowConfig) {
|
||||||
|
return validateKey(config, 'routes', validateRoutes);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateNowConfigCleanUrls(config: NowConfig) {
|
||||||
|
return validateKey(config, 'cleanUrls', validateCleanUrls);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateNowConfigHeaders(config: NowConfig) {
|
||||||
|
return validateKey(config, 'headers', validateHeaders);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateNowConfigRedirects(config: NowConfig) {
|
||||||
|
return validateKey(config, 'redirects', validateRedirects);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateNowConfigRewrites(config: NowConfig) {
|
||||||
|
return validateKey(config, 'rewrites', validateRewrites);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateNowConfigTrailingSlash(config: NowConfig) {
|
||||||
|
return validateKey(config, 'trailingSlash', validateTrailingSlash);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateNowConfigFunctions(config: NowConfig) {
|
||||||
|
return validateKey(config, 'functions', validateFunctions);
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateKey(
|
||||||
|
config: NowConfig,
|
||||||
|
key: keyof NowConfig,
|
||||||
|
validate: Ajv.ValidateFunction
|
||||||
|
) {
|
||||||
|
const value = config[key];
|
||||||
|
if (!value) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!validateBuilds(builds)) {
|
if (!validate(value)) {
|
||||||
if (!validateBuilds.errors) {
|
if (!validate.errors) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const error = validateBuilds.errors[0];
|
const error = validate.errors[0];
|
||||||
|
|
||||||
return `Invalid \`builds\` property: ${error.dataPath} ${error.message}`;
|
return `Invalid \`${key}\` property: ${error.dataPath} ${error.message}`;
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function validateNowConfigRoutes({ routes }: NowConfig) {
|
|
||||||
if (!routes) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateRoutes(routes)) {
|
|
||||||
if (!validateRoutes.errors) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const error = validateRoutes.errors[0];
|
|
||||||
|
|
||||||
return `Invalid \`routes\` property: ${error.dataPath} ${error.message}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
writeFile,
|
writeFile,
|
||||||
statSync,
|
statSync,
|
||||||
chmodSync,
|
chmodSync,
|
||||||
createReadStream
|
createReadStream,
|
||||||
} from 'fs-extra';
|
} from 'fs-extra';
|
||||||
import pipe from 'promisepipe';
|
import pipe from 'promisepipe';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
@@ -63,7 +63,7 @@ async function installYarn(output: Output): Promise<string> {
|
|||||||
output.debug(`Downloading ${YARN_URL}`);
|
output.debug(`Downloading ${YARN_URL}`);
|
||||||
const response = await fetch(YARN_URL, {
|
const response = await fetch(YARN_URL, {
|
||||||
compress: false,
|
compress: false,
|
||||||
redirect: 'follow'
|
redirect: 'follow',
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
@@ -90,7 +90,7 @@ async function installYarn(output: Output): Promise<string> {
|
|||||||
'@echo off',
|
'@echo off',
|
||||||
'@SETLOCAL',
|
'@SETLOCAL',
|
||||||
'@SET PATHEXT=%PATHEXT:;.JS;=;%',
|
'@SET PATHEXT=%PATHEXT:;.JS;=;%',
|
||||||
'node "%~dp0\\yarn" %*'
|
'node "%~dp0\\yarn" %*',
|
||||||
].join('\r\n')
|
].join('\r\n')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,24 @@
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
import { Response } from 'fetch-h2'
|
import { Response } from 'node-fetch';
|
||||||
import { DomainNotFound, InvalidDomain } from '../errors-ts';
|
import { DomainNotFound, InvalidDomain } from '../errors-ts';
|
||||||
import Client from '../client';
|
import Client from '../client';
|
||||||
import wait from '../output/wait';
|
import wait from '../output/wait';
|
||||||
|
|
||||||
type JSONResponse = {
|
type JSONResponse = {
|
||||||
recordIds: string[]
|
recordIds: string[];
|
||||||
}
|
};
|
||||||
|
|
||||||
export default async function importZonefile(
|
export default async function importZonefile(
|
||||||
client: Client,
|
client: Client,
|
||||||
contextName: string,
|
contextName: string,
|
||||||
domain: string,
|
domain: string,
|
||||||
zonefilePath: string,
|
zonefilePath: string
|
||||||
) {
|
) {
|
||||||
const cancelWait = wait(`Importing Zone file for domain ${domain} under ${chalk.bold(contextName)}`);
|
const cancelWait = wait(
|
||||||
|
`Importing Zone file for domain ${domain} under ${chalk.bold(contextName)}`
|
||||||
|
);
|
||||||
const zonefile = readFileSync(resolve(zonefilePath), 'utf8');
|
const zonefile = readFileSync(resolve(zonefilePath), 'utf8');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -27,7 +29,7 @@ export default async function importZonefile(
|
|||||||
json: false,
|
json: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { recordIds } = await res.json() as JSONResponse;
|
const { recordIds } = (await res.json()) as JSONResponse;
|
||||||
cancelWait();
|
cancelWait();
|
||||||
return recordIds;
|
return recordIds;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import maybeGetDomainByName from './maybe-get-domain-by-name';
|
|||||||
import purchaseDomainIfAvailable from './purchase-domain-if-available';
|
import purchaseDomainIfAvailable from './purchase-domain-if-available';
|
||||||
import verifyDomain from './verify-domain';
|
import verifyDomain from './verify-domain';
|
||||||
import extractDomain from '../alias/extract-domain';
|
import extractDomain from '../alias/extract-domain';
|
||||||
|
import isWildcardAlias from '../alias/is-wildcard-alias';
|
||||||
|
|
||||||
export default async function setupDomain(
|
export default async function setupDomain(
|
||||||
output: Output,
|
output: Output,
|
||||||
@@ -34,7 +35,7 @@ export default async function setupDomain(
|
|||||||
|
|
||||||
if (info) {
|
if (info) {
|
||||||
output.debug(`Domain ${domain} found for the given context`);
|
output.debug(`Domain ${domain} found for the given context`);
|
||||||
if (!info.verified) {
|
if (!info.verified || (!info.nsVerifiedAt && isWildcardAlias(alias))) {
|
||||||
output.debug(
|
output.debug(
|
||||||
`Domain ${domain} is not verified, trying to perform a verification`
|
`Domain ${domain} is not verified, trying to perform a verification`
|
||||||
);
|
);
|
||||||
@@ -47,6 +48,15 @@ export default async function setupDomain(
|
|||||||
output.debug(`Domain ${domain} verification failed`);
|
output.debug(`Domain ${domain} verification failed`);
|
||||||
return verificationResult;
|
return verificationResult;
|
||||||
}
|
}
|
||||||
|
if (!verificationResult.nsVerifiedAt && isWildcardAlias(alias)) {
|
||||||
|
return new ERRORS.DomainNsNotVerifiedForWildcard({
|
||||||
|
domain,
|
||||||
|
nsVerification: {
|
||||||
|
intendedNameservers: verificationResult.intendedNameservers,
|
||||||
|
nameservers: verificationResult.nameservers
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
output.debug(`Domain ${domain} successfuly verified`);
|
output.debug(`Domain ${domain} successfuly verified`);
|
||||||
return maybeGetDomainByName(client, contextName, domain) as Promise<
|
return maybeGetDomainByName(client, contextName, domain) as Promise<
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import bytes from 'bytes';
|
import bytes from 'bytes';
|
||||||
import { Response } from 'fetch-h2';
|
import { Response } from 'node-fetch';
|
||||||
import { NowError } from './now-error';
|
import { NowError } from './now-error';
|
||||||
import param from './output/param';
|
import param from './output/param';
|
||||||
import cmd from './output/cmd';
|
import cmd from './output/cmd';
|
||||||
@@ -53,7 +53,7 @@ export class TeamDeleted extends NowError<'TEAM_DELETED', {}> {
|
|||||||
message: `Your team was deleted. You can switch to a different one using ${param(
|
message: `Your team was deleted. You can switch to a different one using ${param(
|
||||||
'now switch'
|
'now switch'
|
||||||
)}.`,
|
)}.`,
|
||||||
meta: {}
|
meta: {},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,7 +67,7 @@ export class InvalidToken extends NowError<'NOT_AUTHORIZED', {}> {
|
|||||||
super({
|
super({
|
||||||
code: `NOT_AUTHORIZED`,
|
code: `NOT_AUTHORIZED`,
|
||||||
message: `The specified token is not valid`,
|
message: `The specified token is not valid`,
|
||||||
meta: {}
|
meta: {},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,7 +81,7 @@ export class MissingUser extends NowError<'MISSING_USER', {}> {
|
|||||||
super({
|
super({
|
||||||
code: 'MISSING_USER',
|
code: 'MISSING_USER',
|
||||||
message: `Not able to load user, missing from response`,
|
message: `Not able to load user, missing from response`,
|
||||||
meta: {}
|
meta: {},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,7 +98,7 @@ export class DomainAlreadyExists extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'DOMAIN_ALREADY_EXISTS',
|
code: 'DOMAIN_ALREADY_EXISTS',
|
||||||
meta: { domain },
|
meta: { domain },
|
||||||
message: `The domain ${domain} already exists under a different context.`
|
message: `The domain ${domain} already exists under a different context.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,7 +115,7 @@ export class DomainPermissionDenied extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'DOMAIN_PERMISSION_DENIED',
|
code: 'DOMAIN_PERMISSION_DENIED',
|
||||||
meta: { domain, context },
|
meta: { domain, context },
|
||||||
message: `You don't have access to the domain ${domain} under ${context}.`
|
message: `You don't have access to the domain ${domain} under ${context}.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,7 +128,7 @@ export class DomainExternal extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'DOMAIN_EXTERNAL',
|
code: 'DOMAIN_EXTERNAL',
|
||||||
meta: { domain },
|
meta: { domain },
|
||||||
message: `The domain ${domain} must point to zeit.world.`
|
message: `The domain ${domain} must point to zeit.world.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -143,7 +143,7 @@ export class SourceNotFound extends NowError<'SOURCE_NOT_FOUND', {}> {
|
|||||||
meta: {},
|
meta: {},
|
||||||
message: `Not able to purchase. Please add a payment method using ${cmd(
|
message: `Not able to purchase. Please add a payment method using ${cmd(
|
||||||
'now billing add'
|
'now billing add'
|
||||||
)}.`
|
)}.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,7 +156,7 @@ export class InvalidTransferAuthCode extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'INVALID_TRANSFER_AUTH_CODE',
|
code: 'INVALID_TRANSFER_AUTH_CODE',
|
||||||
meta: { domain, authCode },
|
meta: { domain, authCode },
|
||||||
message: `The provided auth code does not match with the one expected by the current registar`
|
message: `The provided auth code does not match with the one expected by the current registar`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,7 +169,7 @@ export class DomainRegistrationFailed extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'DOMAIN_REGISTRATION_FAILED',
|
code: 'DOMAIN_REGISTRATION_FAILED',
|
||||||
meta: { domain },
|
meta: { domain },
|
||||||
message
|
message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -185,7 +185,7 @@ export class DomainNotFound extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'DOMAIN_NOT_FOUND',
|
code: 'DOMAIN_NOT_FOUND',
|
||||||
meta: { domain },
|
meta: { domain },
|
||||||
message: `The domain ${domain} can't be found.`
|
message: `The domain ${domain} can't be found.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -198,7 +198,7 @@ export class DomainNotVerified extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'DOMAIN_NOT_VERIFIED',
|
code: 'DOMAIN_NOT_VERIFIED',
|
||||||
meta: { domain },
|
meta: { domain },
|
||||||
message: `The domain ${domain} is not verified.`
|
message: `The domain ${domain} is not verified.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,7 +221,7 @@ export class DomainVerificationFailed extends NowError<
|
|||||||
domain,
|
domain,
|
||||||
nsVerification,
|
nsVerification,
|
||||||
txtVerification,
|
txtVerification,
|
||||||
purchased = false
|
purchased = false,
|
||||||
}: {
|
}: {
|
||||||
domain: string;
|
domain: string;
|
||||||
nsVerification: NSVerificationError;
|
nsVerification: NSVerificationError;
|
||||||
@@ -231,7 +231,7 @@ export class DomainVerificationFailed extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'DOMAIN_VERIFICATION_FAILED',
|
code: 'DOMAIN_VERIFICATION_FAILED',
|
||||||
meta: { domain, nsVerification, txtVerification, purchased },
|
meta: { domain, nsVerification, txtVerification, purchased },
|
||||||
message: `We can't verify the domain ${domain}. Both Name Servers and DNS TXT verifications failed.`
|
message: `We can't verify the domain ${domain}. Both Name Servers and DNS TXT verifications failed.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -252,6 +252,31 @@ export type TXTVerificationError = {
|
|||||||
values: string[];
|
values: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This error is returned when the domain is not verified by nameservers for wildcard alias.
|
||||||
|
*/
|
||||||
|
export class DomainNsNotVerifiedForWildcard extends NowError<
|
||||||
|
'DOMAIN_NS_NOT_VERIFIED_FOR_WILDCARD',
|
||||||
|
{
|
||||||
|
domain: string;
|
||||||
|
nsVerification: NSVerificationError;
|
||||||
|
}
|
||||||
|
> {
|
||||||
|
constructor({
|
||||||
|
domain,
|
||||||
|
nsVerification,
|
||||||
|
}: {
|
||||||
|
domain: string;
|
||||||
|
nsVerification: NSVerificationError;
|
||||||
|
}) {
|
||||||
|
super({
|
||||||
|
code: 'DOMAIN_NS_NOT_VERIFIED_FOR_WILDCARD',
|
||||||
|
meta: { domain, nsVerification },
|
||||||
|
message: `The domain ${domain} is not verified by nameservers for wildcard alias.`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used when a domain is validated because we tried to add it to an account
|
* Used when a domain is validated because we tried to add it to an account
|
||||||
* via API or for any other reason.
|
* via API or for any other reason.
|
||||||
@@ -264,7 +289,17 @@ export class InvalidDomain extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'INVALID_DOMAIN',
|
code: 'INVALID_DOMAIN',
|
||||||
meta: { domain },
|
meta: { domain },
|
||||||
message: message || `The domain ${domain} is not valid.`
|
message: message || `The domain ${domain} is not valid.`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NotDomainOwner extends NowError<'NOT_DOMAIN_OWNER', {}> {
|
||||||
|
constructor(message: string) {
|
||||||
|
super({
|
||||||
|
code: 'NOT_DOMAIN_OWNER',
|
||||||
|
meta: {},
|
||||||
|
message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,7 +312,7 @@ export class InvalidDeploymentId extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'INVALID_DEPLOYMENT_ID',
|
code: 'INVALID_DEPLOYMENT_ID',
|
||||||
meta: { id },
|
meta: { id },
|
||||||
message: `The deployment id "${id}" is not valid.`
|
message: `The deployment id "${id}" is not valid.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -294,7 +329,7 @@ export class UnsupportedTLD extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'UNSUPPORTED_TLD',
|
code: 'UNSUPPORTED_TLD',
|
||||||
meta: { domain },
|
meta: { domain },
|
||||||
message: `The TLD for domain name ${domain} is not supported.`
|
message: `The TLD for domain name ${domain} is not supported.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -311,7 +346,7 @@ export class DomainNotAvailable extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'DOMAIN_NOT_AVAILABLE',
|
code: 'DOMAIN_NOT_AVAILABLE',
|
||||||
meta: { domain },
|
meta: { domain },
|
||||||
message: `The domain ${domain} is not available to be purchased.`
|
message: `The domain ${domain} is not available to be purchased.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -328,7 +363,7 @@ export class DomainServiceNotAvailable extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'DOMAIN_SERVICE_NOT_AVAILABLE',
|
code: 'DOMAIN_SERVICE_NOT_AVAILABLE',
|
||||||
meta: { domain },
|
meta: { domain },
|
||||||
message: `The domain purchase is unavailable, try again later.`
|
message: `The domain purchase is unavailable, try again later.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -345,7 +380,7 @@ export class DomainNotTransferable extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'DOMAIN_NOT_TRANSFERABLE',
|
code: 'DOMAIN_NOT_TRANSFERABLE',
|
||||||
meta: { domain },
|
meta: { domain },
|
||||||
message: `The domain ${domain} is not available to be transferred.`
|
message: `The domain ${domain} is not available to be transferred.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -361,7 +396,7 @@ export class UnexpectedDomainPurchaseError extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'UNEXPECTED_DOMAIN_PURCHASE_ERROR',
|
code: 'UNEXPECTED_DOMAIN_PURCHASE_ERROR',
|
||||||
meta: { domain },
|
meta: { domain },
|
||||||
message: `An unexpected error happened while purchasing.`
|
message: `An unexpected error happened while purchasing.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -374,7 +409,7 @@ export class DomainPaymentError extends NowError<'DOMAIN_PAYMENT_ERROR', {}> {
|
|||||||
super({
|
super({
|
||||||
code: 'DOMAIN_PAYMENT_ERROR',
|
code: 'DOMAIN_PAYMENT_ERROR',
|
||||||
meta: {},
|
meta: {},
|
||||||
message: `Your card was declined.`
|
message: `Your card was declined.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -391,7 +426,7 @@ export class DomainPurchasePending extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'DOMAIN_PURCHASE_PENDING',
|
code: 'DOMAIN_PURCHASE_PENDING',
|
||||||
meta: { domain },
|
meta: { domain },
|
||||||
message: `The domain purchase for ${domain} is pending.`
|
message: `The domain purchase for ${domain} is pending.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -405,7 +440,7 @@ export class UserAborted extends NowError<'USER_ABORTED', {}> {
|
|||||||
super({
|
super({
|
||||||
code: 'USER_ABORTED',
|
code: 'USER_ABORTED',
|
||||||
meta: {},
|
meta: {},
|
||||||
message: `The user aborted the operation.`
|
message: `The user aborted the operation.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -415,7 +450,7 @@ export class CertNotFound extends NowError<'CERT_NOT_FOUND', { id: string }> {
|
|||||||
super({
|
super({
|
||||||
code: 'CERT_NOT_FOUND',
|
code: 'CERT_NOT_FOUND',
|
||||||
meta: { id },
|
meta: { id },
|
||||||
message: `The cert ${id} can't be found.`
|
message: `The cert ${id} can't be found.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -428,7 +463,7 @@ export class CertsPermissionDenied extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'CERTS_PERMISSION_DENIED',
|
code: 'CERTS_PERMISSION_DENIED',
|
||||||
meta: { domain },
|
meta: { domain },
|
||||||
message: `You don't have access to ${domain}'s certs under ${context}.`
|
message: `You don't have access to ${domain}'s certs under ${context}.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -441,7 +476,7 @@ export class CertOrderNotFound extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'CERT_ORDER_NOT_FOUND',
|
code: 'CERT_ORDER_NOT_FOUND',
|
||||||
meta: { cns },
|
meta: { cns },
|
||||||
message: `No cert order could be found for cns ${cns.join(' ,')}`
|
message: `No cert order could be found for cns ${cns.join(' ,')}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -459,7 +494,7 @@ export class TooManyRequests extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'TOO_MANY_REQUESTS',
|
code: 'TOO_MANY_REQUESTS',
|
||||||
meta: { api, retryAfter },
|
meta: { api, retryAfter },
|
||||||
message: `Rate limited. Too many requests to the same endpoint.`
|
message: `Rate limited. Too many requests to the same endpoint.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -493,7 +528,7 @@ export class CertError extends NowError<
|
|||||||
cns,
|
cns,
|
||||||
code,
|
code,
|
||||||
message,
|
message,
|
||||||
helpUrl
|
helpUrl,
|
||||||
}: {
|
}: {
|
||||||
cns: string[];
|
cns: string[];
|
||||||
code: CertErrorCode;
|
code: CertErrorCode;
|
||||||
@@ -503,7 +538,7 @@ export class CertError extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: `CERT_ERROR`,
|
code: `CERT_ERROR`,
|
||||||
meta: { cns, code, helpUrl },
|
meta: { cns, code, helpUrl },
|
||||||
message
|
message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -522,7 +557,7 @@ export class CertConfigurationError extends NowError<
|
|||||||
message,
|
message,
|
||||||
external,
|
external,
|
||||||
type,
|
type,
|
||||||
helpUrl
|
helpUrl,
|
||||||
}: {
|
}: {
|
||||||
cns: string[];
|
cns: string[];
|
||||||
message: string;
|
message: string;
|
||||||
@@ -533,7 +568,7 @@ export class CertConfigurationError extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: `CERT_CONFIGURATION_ERROR`,
|
code: `CERT_CONFIGURATION_ERROR`,
|
||||||
meta: { cns, helpUrl, external, type },
|
meta: { cns, helpUrl, external, type },
|
||||||
message
|
message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -550,7 +585,7 @@ export class DeploymentNotFound extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'DEPLOYMENT_NOT_FOUND',
|
code: 'DEPLOYMENT_NOT_FOUND',
|
||||||
meta: { id, context },
|
meta: { id, context },
|
||||||
message: `Can't find the deployment ${id} under the context ${context}`
|
message: `Can't find the deployment ${id} under the context ${context}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -561,13 +596,13 @@ export class DeploymentNotFound extends NowError<
|
|||||||
*/
|
*/
|
||||||
export class DeploymentNotReady extends NowError<
|
export class DeploymentNotReady extends NowError<
|
||||||
'DEPLOYMENT_NOT_READY',
|
'DEPLOYMENT_NOT_READY',
|
||||||
{ url: string; }
|
{ url: string }
|
||||||
> {
|
> {
|
||||||
constructor({ url = '' }: { url: string }) {
|
constructor({ url = '' }: { url: string }) {
|
||||||
super({
|
super({
|
||||||
code: 'DEPLOYMENT_NOT_READY',
|
code: 'DEPLOYMENT_NOT_READY',
|
||||||
meta: { url },
|
meta: { url },
|
||||||
message: `The deployment https://${url} is not ready.`
|
message: `The deployment https://${url} is not ready.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -580,7 +615,7 @@ export class DeploymentFailedAliasImpossible extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'DEPLOYMENT_FAILED_ALIAS_IMPOSSIBLE',
|
code: 'DEPLOYMENT_FAILED_ALIAS_IMPOSSIBLE',
|
||||||
meta: {},
|
meta: {},
|
||||||
message: `The deployment build has failed and cannot be aliased`
|
message: `The deployment build has failed and cannot be aliased`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -597,7 +632,7 @@ export class DeploymentPermissionDenied extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'DEPLOYMENT_PERMISSION_DENIED',
|
code: 'DEPLOYMENT_PERMISSION_DENIED',
|
||||||
meta: { id, context },
|
meta: { id, context },
|
||||||
message: `You don't have access to the deployment ${id} under ${context}.`
|
message: `You don't have access to the deployment ${id} under ${context}.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -610,7 +645,7 @@ export class DeploymentTypeUnsupported extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'DEPLOYMENT_TYPE_UNSUPPORTED',
|
code: 'DEPLOYMENT_TYPE_UNSUPPORTED',
|
||||||
meta: {},
|
meta: {},
|
||||||
message: `This region only accepts Serverless Docker Deployments`
|
message: `This region only accepts Serverless Docker Deployments`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -624,7 +659,7 @@ export class InvalidAlias extends NowError<'INVALID_ALIAS', { alias: string }> {
|
|||||||
super({
|
super({
|
||||||
code: 'INVALID_ALIAS',
|
code: 'INVALID_ALIAS',
|
||||||
meta: { alias },
|
meta: { alias },
|
||||||
message: `The given alias ${alias} is not valid`
|
message: `The given alias ${alias} is not valid`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -638,7 +673,7 @@ export class AliasInUse extends NowError<'ALIAS_IN_USE', { alias: string }> {
|
|||||||
super({
|
super({
|
||||||
code: 'ALIAS_IN_USE',
|
code: 'ALIAS_IN_USE',
|
||||||
meta: { alias },
|
meta: { alias },
|
||||||
message: `The alias is already in use`
|
message: `The alias is already in use`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -653,7 +688,7 @@ export class CertMissing extends NowError<'ALIAS_IN_USE', { domain: string }> {
|
|||||||
super({
|
super({
|
||||||
code: 'ALIAS_IN_USE',
|
code: 'ALIAS_IN_USE',
|
||||||
meta: { domain },
|
meta: { domain },
|
||||||
message: `The alias is already in use`
|
message: `The alias is already in use`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -666,7 +701,7 @@ export class ForbiddenScaleMinInstances extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'FORBIDDEN_SCALE_MIN_INSTANCES',
|
code: 'FORBIDDEN_SCALE_MIN_INSTANCES',
|
||||||
meta: { url, max },
|
meta: { url, max },
|
||||||
message: `You can't scale to more than ${max} min instances with your current plan.`
|
message: `You can't scale to more than ${max} min instances with your current plan.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -679,7 +714,7 @@ export class ForbiddenScaleMaxInstances extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'FORBIDDEN_SCALE_MAX_INSTANCES',
|
code: 'FORBIDDEN_SCALE_MAX_INSTANCES',
|
||||||
meta: { url, max },
|
meta: { url, max },
|
||||||
message: `You can't scale to more than ${max} max instances with your current plan.`
|
message: `You can't scale to more than ${max} max instances with your current plan.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -692,7 +727,7 @@ export class InvalidScaleMinMaxRelation extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'INVALID_SCALE_MIN_MAX_RELATION',
|
code: 'INVALID_SCALE_MIN_MAX_RELATION',
|
||||||
meta: { url },
|
meta: { url },
|
||||||
message: `Min number of instances can't be higher than max.`
|
message: `Min number of instances can't be higher than max.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -705,7 +740,7 @@ export class NotSupportedMinScaleSlots extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'NOT_SUPPORTED_MIN_SCALE_SLOTS',
|
code: 'NOT_SUPPORTED_MIN_SCALE_SLOTS',
|
||||||
meta: { url },
|
meta: { url },
|
||||||
message: `Cloud v2 does not yet support setting a non-zero min scale setting.`
|
message: `Cloud v2 does not yet support setting a non-zero min scale setting.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -718,7 +753,7 @@ export class VerifyScaleTimeout extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'VERIFY_SCALE_TIMEOUT',
|
code: 'VERIFY_SCALE_TIMEOUT',
|
||||||
meta: { timeout },
|
meta: { timeout },
|
||||||
message: `Instance verification timed out (${timeout}ms)`
|
message: `Instance verification timed out (${timeout}ms)`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -731,7 +766,7 @@ export class CantParseJSONFile extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'CANT_PARSE_JSON_FILE',
|
code: 'CANT_PARSE_JSON_FILE',
|
||||||
meta: { file },
|
meta: { file },
|
||||||
message: `Can't parse json file`
|
message: `Can't parse json file`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -744,7 +779,20 @@ export class CantFindConfig extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'CANT_FIND_CONFIG',
|
code: 'CANT_FIND_CONFIG',
|
||||||
meta: { paths },
|
meta: { paths },
|
||||||
message: `Can't find a configuration file in the given locations.`
|
message: `Can't find a configuration file in the given locations.`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class WorkingDirectoryDoesNotExist extends NowError<
|
||||||
|
'CWD_DOES_NOT_EXIST',
|
||||||
|
{}
|
||||||
|
> {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
code: 'CWD_DOES_NOT_EXIST',
|
||||||
|
meta: {},
|
||||||
|
message: 'The current working directory does not exist.',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -754,7 +802,7 @@ export class FileNotFound extends NowError<'FILE_NOT_FOUND', { file: string }> {
|
|||||||
super({
|
super({
|
||||||
code: 'FILE_NOT_FOUND',
|
code: 'FILE_NOT_FOUND',
|
||||||
meta: { file },
|
meta: { file },
|
||||||
message: `Can't find a file in provided location '${file}'.`
|
message: `Can't find a file in provided location '${file}'.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -767,7 +815,7 @@ export class RulesFileValidationError extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'PATH_ALIAS_VALIDATION_ERROR',
|
code: 'PATH_ALIAS_VALIDATION_ERROR',
|
||||||
meta: { location, message },
|
meta: { location, message },
|
||||||
message: `The provided rules format in file for path alias are invalid`
|
message: `The provided rules format in file for path alias are invalid`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -777,7 +825,7 @@ export class NoAliasInConfig extends NowError<'NO_ALIAS_IN_CONFIG', {}> {
|
|||||||
super({
|
super({
|
||||||
code: 'NO_ALIAS_IN_CONFIG',
|
code: 'NO_ALIAS_IN_CONFIG',
|
||||||
meta: {},
|
meta: {},
|
||||||
message: `There is no alias set up in config file.`
|
message: `There is no alias set up in config file.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -790,7 +838,7 @@ export class InvalidAliasInConfig extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'INVALID_ALIAS_IN_CONFIG',
|
code: 'INVALID_ALIAS_IN_CONFIG',
|
||||||
meta: { value },
|
meta: { value },
|
||||||
message: `Invalid alias option in configuration.`
|
message: `Invalid alias option in configuration.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -803,7 +851,7 @@ export class RuleValidationFailed extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'RULE_VALIDATION_FAILED',
|
code: 'RULE_VALIDATION_FAILED',
|
||||||
meta: { message },
|
meta: { message },
|
||||||
message: `The server validation for rules failed`
|
message: `The server validation for rules failed`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -816,7 +864,7 @@ export class InvalidMinForScale extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'INVALID_MIN_FOR_SCALE',
|
code: 'INVALID_MIN_FOR_SCALE',
|
||||||
meta: { value },
|
meta: { value },
|
||||||
message: `Invalid <min> parameter "${value}". A number or "auto" were expected`
|
message: `Invalid <min> parameter "${value}". A number or "auto" were expected`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -829,7 +877,7 @@ export class InvalidArgsForMinMaxScale extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'INVALID_ARGS_FOR_MIN_MAX_SCALE',
|
code: 'INVALID_ARGS_FOR_MIN_MAX_SCALE',
|
||||||
meta: { min },
|
meta: { min },
|
||||||
message: `Invalid number of arguments: expected <min> ("${min}") and [max]`
|
message: `Invalid number of arguments: expected <min> ("${min}") and [max]`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -842,7 +890,7 @@ export class InvalidMaxForScale extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'INVALID_MAX_FOR_SCALE',
|
code: 'INVALID_MAX_FOR_SCALE',
|
||||||
meta: { value },
|
meta: { value },
|
||||||
message: `Invalid <max> parameter "${value}". A number or "auto" were expected`
|
message: `Invalid <max> parameter "${value}". A number or "auto" were expected`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -852,7 +900,7 @@ export class InvalidCert extends NowError<'INVALID_CERT', {}> {
|
|||||||
super({
|
super({
|
||||||
code: 'INVALID_CERT',
|
code: 'INVALID_CERT',
|
||||||
meta: {},
|
meta: {},
|
||||||
message: `The provided custom certificate is invalid and couldn't be added`
|
message: `The provided custom certificate is invalid and couldn't be added`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -865,7 +913,7 @@ export class DNSPermissionDenied extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'DNS_PERMISSION_DENIED',
|
code: 'DNS_PERMISSION_DENIED',
|
||||||
meta: { domain },
|
meta: { domain },
|
||||||
message: `You don't have access to the DNS records of ${domain}.`
|
message: `You don't have access to the DNS records of ${domain}.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -875,7 +923,7 @@ export class DNSInvalidPort extends NowError<'DNS_INVALID_PORT', {}> {
|
|||||||
super({
|
super({
|
||||||
code: 'DNS_INVALID_PORT',
|
code: 'DNS_INVALID_PORT',
|
||||||
meta: {},
|
meta: {},
|
||||||
message: `Invalid <port> parameter. A number was expected`
|
message: `Invalid <port> parameter. A number was expected`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -888,7 +936,7 @@ export class DNSInvalidType extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'DNS_INVALID_TYPE',
|
code: 'DNS_INVALID_TYPE',
|
||||||
meta: { type },
|
meta: { type },
|
||||||
message: `Invalid <type> parameter "${type}". Expected one of A, AAAA, ALIAS, CAA, CNAME, MX, SRV, TXT`
|
message: `Invalid <type> parameter "${type}". Expected one of A, AAAA, ALIAS, CAA, CNAME, MX, SRV, TXT`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -901,7 +949,7 @@ export class DNSConflictingRecord extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'DNS_CONFLICTING_RECORD',
|
code: 'DNS_CONFLICTING_RECORD',
|
||||||
meta: { record },
|
meta: { record },
|
||||||
message: ` A conflicting record exists "${record}".`
|
message: ` A conflicting record exists "${record}".`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -924,7 +972,7 @@ export class DomainRemovalConflict extends NowError<
|
|||||||
pendingAsyncPurchase,
|
pendingAsyncPurchase,
|
||||||
resolvable,
|
resolvable,
|
||||||
suffix,
|
suffix,
|
||||||
transferring
|
transferring,
|
||||||
}: {
|
}: {
|
||||||
aliases: string[];
|
aliases: string[];
|
||||||
certs: string[];
|
certs: string[];
|
||||||
@@ -942,9 +990,9 @@ export class DomainRemovalConflict extends NowError<
|
|||||||
pendingAsyncPurchase,
|
pendingAsyncPurchase,
|
||||||
suffix,
|
suffix,
|
||||||
transferring,
|
transferring,
|
||||||
resolvable
|
resolvable,
|
||||||
},
|
},
|
||||||
message
|
message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -957,7 +1005,7 @@ export class DomainMoveConflict extends NowError<
|
|||||||
message,
|
message,
|
||||||
pendingAsyncPurchase,
|
pendingAsyncPurchase,
|
||||||
resolvable,
|
resolvable,
|
||||||
suffix
|
suffix,
|
||||||
}: {
|
}: {
|
||||||
message: string;
|
message: string;
|
||||||
pendingAsyncPurchase: boolean;
|
pendingAsyncPurchase: boolean;
|
||||||
@@ -969,9 +1017,9 @@ export class DomainMoveConflict extends NowError<
|
|||||||
meta: {
|
meta: {
|
||||||
pendingAsyncPurchase,
|
pendingAsyncPurchase,
|
||||||
resolvable,
|
resolvable,
|
||||||
suffix
|
suffix,
|
||||||
},
|
},
|
||||||
message
|
message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -981,17 +1029,23 @@ export class InvalidEmail extends NowError<'INVALID_EMAIL', { email: string }> {
|
|||||||
super({
|
super({
|
||||||
code: 'INVALID_EMAIL',
|
code: 'INVALID_EMAIL',
|
||||||
message,
|
message,
|
||||||
meta: { email }
|
meta: { email },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AccountNotFound extends NowError<'ACCOUNT_NOT_FOUND', { email: string }> {
|
export class AccountNotFound extends NowError<
|
||||||
constructor(email: string, message: string = `Please sign up: https://zeit.co/signup`) {
|
'ACCOUNT_NOT_FOUND',
|
||||||
|
{ email: string }
|
||||||
|
> {
|
||||||
|
constructor(
|
||||||
|
email: string,
|
||||||
|
message: string = `Please sign up: https://zeit.co/signup`
|
||||||
|
) {
|
||||||
super({
|
super({
|
||||||
code: 'ACCOUNT_NOT_FOUND',
|
code: 'ACCOUNT_NOT_FOUND',
|
||||||
message,
|
message,
|
||||||
meta: { email }
|
meta: { email },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1004,7 +1058,7 @@ export class InvalidMoveDestination extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'INVALID_MOVE_DESTINATION',
|
code: 'INVALID_MOVE_DESTINATION',
|
||||||
message: `Invalid move destination "${destination}"`,
|
message: `Invalid move destination "${destination}"`,
|
||||||
meta: { destination }
|
meta: { destination },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1017,7 +1071,7 @@ export class InvalidMoveToken extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'INVALID_MOVE_TOKEN',
|
code: 'INVALID_MOVE_TOKEN',
|
||||||
message: `Invalid move token "${token}"`,
|
message: `Invalid move token "${token}"`,
|
||||||
meta: { token }
|
meta: { token },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1027,20 +1081,7 @@ export class NoBuilderCacheError extends NowError<'NO_BUILDER_CACHE', {}> {
|
|||||||
super({
|
super({
|
||||||
code: 'NO_BUILDER_CACHE',
|
code: 'NO_BUILDER_CACHE',
|
||||||
message: 'Could not find cache directory for now-builders.',
|
message: 'Could not find cache directory for now-builders.',
|
||||||
meta: {}
|
meta: {},
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class BuilderCacheCleanError extends NowError<
|
|
||||||
'BUILDER_CACHE_CLEAN_FAILED',
|
|
||||||
{ path: string }
|
|
||||||
> {
|
|
||||||
constructor(path: string, message: string) {
|
|
||||||
super({
|
|
||||||
code: 'BUILDER_CACHE_CLEAN_FAILED',
|
|
||||||
message: `Error cleaning builder cache: ${message}`,
|
|
||||||
meta: { path }
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1057,7 +1098,7 @@ export class LambdaSizeExceededError extends NowError<
|
|||||||
).toLowerCase()}) exceeds the maximum size limit (${bytes(
|
).toLowerCase()}) exceeds the maximum size limit (${bytes(
|
||||||
maxLambdaSize
|
maxLambdaSize
|
||||||
).toLowerCase()}). Learn more: https://zeit.co/docs/v2/deployments/concepts/lambdas/#maximum-bundle-size`,
|
).toLowerCase()}). Learn more: https://zeit.co/docs/v2/deployments/concepts/lambdas/#maximum-bundle-size`,
|
||||||
meta: { size, maxLambdaSize }
|
meta: { size, maxLambdaSize },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1076,7 +1117,7 @@ export class MissingDotenvVarsError extends NowError<
|
|||||||
} else {
|
} else {
|
||||||
message = [
|
message = [
|
||||||
`The following env vars are not defined in ${code(type)} file:`,
|
`The following env vars are not defined in ${code(type)} file:`,
|
||||||
...missing.map(name => ` - ${JSON.stringify(name)}`)
|
...missing.map(name => ` - ${JSON.stringify(name)}`),
|
||||||
].join('\n');
|
].join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1085,17 +1126,20 @@ export class MissingDotenvVarsError extends NowError<
|
|||||||
super({
|
super({
|
||||||
code: 'MISSING_DOTENV_VARS',
|
code: 'MISSING_DOTENV_VARS',
|
||||||
message,
|
message,
|
||||||
meta: { type, missing }
|
meta: { type, missing },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DeploymentsRateLimited extends NowError<'DEPLOYMENTS_RATE_LIMITED', {}> {
|
export class DeploymentsRateLimited extends NowError<
|
||||||
|
'DEPLOYMENTS_RATE_LIMITED',
|
||||||
|
{}
|
||||||
|
> {
|
||||||
constructor(message: string) {
|
constructor(message: string) {
|
||||||
super({
|
super({
|
||||||
code: 'DEPLOYMENTS_RATE_LIMITED',
|
code: 'DEPLOYMENTS_RATE_LIMITED',
|
||||||
meta: {},
|
meta: {},
|
||||||
message
|
message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1105,7 +1149,7 @@ export class BuildsRateLimited extends NowError<'BUILDS_RATE_LIMITED', {}> {
|
|||||||
super({
|
super({
|
||||||
code: 'BUILDS_RATE_LIMITED',
|
code: 'BUILDS_RATE_LIMITED',
|
||||||
meta: {},
|
meta: {},
|
||||||
message
|
message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1115,47 +1159,66 @@ export class ProjectNotFound extends NowError<'PROJECT_NOT_FOUND', {}> {
|
|||||||
super({
|
super({
|
||||||
code: 'PROJECT_NOT_FOUND',
|
code: 'PROJECT_NOT_FOUND',
|
||||||
meta: {},
|
meta: {},
|
||||||
message: `There is no project for "${nameOrId}"`
|
message: `There is no project for "${nameOrId}"`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AliasDomainConfigured extends NowError<'DOMAIN_CONFIGURED', {}> {
|
export class AliasDomainConfigured extends NowError<'DOMAIN_CONFIGURED', {}> {
|
||||||
constructor({ message }: { message: string; }) {
|
constructor({ message }: { message: string }) {
|
||||||
super({
|
super({
|
||||||
code: 'DOMAIN_CONFIGURED',
|
code: 'DOMAIN_CONFIGURED',
|
||||||
meta: {},
|
meta: {},
|
||||||
message
|
message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MissingBuildScript extends NowError<'MISSING_BUILD_SCRIPT', {}> {
|
export class MissingBuildScript extends NowError<'MISSING_BUILD_SCRIPT', {}> {
|
||||||
constructor({ message }: { message: string; }) {
|
constructor({ message }: { message: string }) {
|
||||||
super({
|
super({
|
||||||
code: 'MISSING_BUILD_SCRIPT',
|
code: 'MISSING_BUILD_SCRIPT',
|
||||||
meta: {},
|
meta: {},
|
||||||
message
|
message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ConflictingFilePath extends NowError<'CONFLICTING_FILE_PATH', {}> {
|
export class ConflictingFilePath extends NowError<'CONFLICTING_FILE_PATH', {}> {
|
||||||
constructor({ message }: { message: string; }) {
|
constructor({ message }: { message: string }) {
|
||||||
super({
|
super({
|
||||||
code: 'CONFLICTING_FILE_PATH',
|
code: 'CONFLICTING_FILE_PATH',
|
||||||
meta: {},
|
meta: {},
|
||||||
message
|
message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ConflictingPathSegment extends NowError<'CONFLICTING_PATH_SEGMENT', {}> {
|
export class ConflictingPathSegment extends NowError<
|
||||||
constructor({ message }: { message: string; }) {
|
'CONFLICTING_PATH_SEGMENT',
|
||||||
|
{}
|
||||||
|
> {
|
||||||
|
constructor({ message }: { message: string }) {
|
||||||
super({
|
super({
|
||||||
code: 'CONFLICTING_PATH_SEGMENT',
|
code: 'CONFLICTING_PATH_SEGMENT',
|
||||||
meta: {},
|
meta: {},
|
||||||
message
|
message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BuildError extends NowError<'BUILD_ERROR', {}> {
|
||||||
|
constructor({
|
||||||
|
message,
|
||||||
|
meta,
|
||||||
|
}: {
|
||||||
|
message: string;
|
||||||
|
meta: { entrypoint: string };
|
||||||
|
}) {
|
||||||
|
super({
|
||||||
|
code: 'BUILD_ERROR',
|
||||||
|
meta,
|
||||||
|
message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { CantParseJSONFile, CantFindConfig } from './errors-ts';
|
import {
|
||||||
|
CantParseJSONFile,
|
||||||
|
CantFindConfig,
|
||||||
|
WorkingDirectoryDoesNotExist,
|
||||||
|
} from './errors-ts';
|
||||||
import humanizePath from './humanize-path';
|
import humanizePath from './humanize-path';
|
||||||
import readJSONFile from './read-json-file';
|
import readJSONFile from './read-json-file';
|
||||||
import readPackage from './read-package';
|
import readPackage from './read-package';
|
||||||
@@ -8,14 +12,25 @@ import { Output } from './output';
|
|||||||
|
|
||||||
let config: Config;
|
let config: Config;
|
||||||
|
|
||||||
export default async function getConfig(output: Output, configFile?: string) {
|
export default async function getConfig(
|
||||||
const localPath = process.cwd();
|
output: Output,
|
||||||
|
configFile?: string
|
||||||
|
): Promise<Config | Error> {
|
||||||
// If config was already read, just return it
|
// If config was already read, just return it
|
||||||
if (config) {
|
if (config) {
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let localPath: string;
|
||||||
|
try {
|
||||||
|
localPath = process.cwd();
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code === 'ENOENT') {
|
||||||
|
return new WorkingDirectoryDoesNotExist();
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
// First try with the config supplied by the user via --local-config
|
// First try with the config supplied by the user via --local-config
|
||||||
if (configFile) {
|
if (configFile) {
|
||||||
const localFilePath = path.resolve(localPath, configFile);
|
const localFilePath = path.resolve(localPath, configFile);
|
||||||
@@ -27,8 +42,7 @@ export default async function getConfig(output: Output, configFile?: string) {
|
|||||||
return localConfig;
|
return localConfig;
|
||||||
}
|
}
|
||||||
if (localConfig !== null) {
|
if (localConfig !== null) {
|
||||||
const castedConfig = localConfig;
|
config = localConfig;
|
||||||
config = castedConfig;
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,10 @@ export default function handleError(
|
|||||||
|
|
||||||
if ((<APIError>error).status === 403) {
|
if ((<APIError>error).status === 403) {
|
||||||
console.error(
|
console.error(
|
||||||
errorOutput('Authentication error. Run `now login` to log-in again.')
|
errorOutput(
|
||||||
|
error.message ||
|
||||||
|
'Authentication error. Run `now login` to log-in again.'
|
||||||
|
)
|
||||||
);
|
);
|
||||||
} else if ((<APIError>error).status === 429) {
|
} else if ((<APIError>error).status === 429) {
|
||||||
// Rate limited: display the message from the server-side,
|
// Rate limited: display the message from the server-side,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { homedir } from 'os';
|
import { homedir } from 'os';
|
||||||
import { resolve as resolvePath, join, basename } from 'path';
|
import { resolve as resolvePath } from 'path';
|
||||||
import EventEmitter from 'events';
|
import EventEmitter from 'events';
|
||||||
import qs from 'querystring';
|
import qs from 'querystring';
|
||||||
import { parse as parseUrl } from 'url';
|
import { parse as parseUrl } from 'url';
|
||||||
@@ -7,24 +7,22 @@ import bytes from 'bytes';
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import retry from 'async-retry';
|
import retry from 'async-retry';
|
||||||
import { parse as parseIni } from 'ini';
|
import { parse as parseIni } from 'ini';
|
||||||
import { createReadStream } from 'fs';
|
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
|
import fetch from 'node-fetch';
|
||||||
import { URLSearchParams } from 'url';
|
import { URLSearchParams } from 'url';
|
||||||
import {
|
import {
|
||||||
staticFiles as getFiles,
|
staticFiles as getFiles,
|
||||||
npm as getNpmFiles,
|
npm as getNpmFiles,
|
||||||
docker as getDockerFiles
|
docker as getDockerFiles,
|
||||||
} from './get-files';
|
} from './get-files';
|
||||||
import Agent from './agent.ts';
|
|
||||||
import ua from './ua.ts';
|
import ua from './ua.ts';
|
||||||
import hash from './hash';
|
import processDeployment from './deploy/process-deployment.ts';
|
||||||
import highlight from './output/highlight';
|
import highlight from './output/highlight';
|
||||||
import createOutput from './output';
|
import createOutput from './output';
|
||||||
import { responseError } from './error';
|
import { responseError } from './error';
|
||||||
|
import stamp from './output/stamp';
|
||||||
// How many concurrent HTTP/2 stream uploads
|
import { BuildError } from './errors-ts';
|
||||||
const MAX_CONCURRENT = 50;
|
|
||||||
|
|
||||||
// Check if running windows
|
// Check if running windows
|
||||||
const IS_WIN = process.platform.startsWith('win');
|
const IS_WIN = process.platform.startsWith('win');
|
||||||
@@ -39,14 +37,8 @@ export default class Now extends EventEmitter {
|
|||||||
this._forceNew = forceNew;
|
this._forceNew = forceNew;
|
||||||
this._output = createOutput({ debug });
|
this._output = createOutput({ debug });
|
||||||
this._apiUrl = apiUrl;
|
this._apiUrl = apiUrl;
|
||||||
this._agent = new Agent(apiUrl, { debug });
|
|
||||||
this._onRetry = this._onRetry.bind(this);
|
this._onRetry = this._onRetry.bind(this);
|
||||||
this.currentTeam = currentTeam;
|
this.currentTeam = currentTeam;
|
||||||
const closeAgent = () => {
|
|
||||||
this._agent.close();
|
|
||||||
process.removeListener('nowExit', closeAgent);
|
|
||||||
};
|
|
||||||
process.on('nowExit', closeAgent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(
|
async create(
|
||||||
@@ -61,7 +53,6 @@ export default class Now extends EventEmitter {
|
|||||||
nowConfig = {},
|
nowConfig = {},
|
||||||
hasNowJson = false,
|
hasNowJson = false,
|
||||||
sessionAffinity = 'random',
|
sessionAffinity = 'random',
|
||||||
isFile = false,
|
|
||||||
atlas = false,
|
atlas = false,
|
||||||
|
|
||||||
// Latest
|
// Latest
|
||||||
@@ -73,22 +64,40 @@ export default class Now extends EventEmitter {
|
|||||||
quiet = false,
|
quiet = false,
|
||||||
env,
|
env,
|
||||||
build,
|
build,
|
||||||
followSymlinks = true,
|
|
||||||
forceNew = false,
|
forceNew = false,
|
||||||
target = null
|
target = null,
|
||||||
|
deployStamp,
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
const { log, warn, time } = this._output;
|
const opts = { output: this._output, hasNowJson };
|
||||||
|
const { log, warn, debug } = this._output;
|
||||||
const isBuilds = type === null;
|
const isBuilds = type === null;
|
||||||
|
|
||||||
let files = [];
|
let files = [];
|
||||||
|
let hashes = {};
|
||||||
const relatives = {};
|
const relatives = {};
|
||||||
let engines;
|
let engines;
|
||||||
|
let deployment;
|
||||||
|
let requestBody = {};
|
||||||
|
|
||||||
await time('Getting files', async () => {
|
if (isBuilds) {
|
||||||
const opts = { output: this._output, hasNowJson };
|
requestBody = {
|
||||||
|
token: this._token,
|
||||||
|
teamId: this.currentTeam,
|
||||||
|
env,
|
||||||
|
build,
|
||||||
|
public: wantsPublic || nowConfig.public,
|
||||||
|
name,
|
||||||
|
project,
|
||||||
|
meta,
|
||||||
|
regions,
|
||||||
|
force: forceNew,
|
||||||
|
};
|
||||||
|
|
||||||
if (type === 'npm') {
|
if (target) {
|
||||||
|
requestBody.target = target;
|
||||||
|
}
|
||||||
|
} else if (type === 'npm') {
|
||||||
files = await getNpmFiles(paths[0], pkg, nowConfig, opts);
|
files = await getNpmFiles(paths[0], pkg, nowConfig, opts);
|
||||||
|
|
||||||
// A `start` or `now-start` npm script, or a `server.js` file
|
// A `start` or `now-start` npm script, or a `server.js` file
|
||||||
@@ -108,9 +117,7 @@ export default class Now extends EventEmitter {
|
|||||||
engines = nowConfig.engines || pkg.engines;
|
engines = nowConfig.engines || pkg.engines;
|
||||||
forwardNpm = forwardNpm || nowConfig.forwardNpm;
|
forwardNpm = forwardNpm || nowConfig.forwardNpm;
|
||||||
} else if (type === 'static') {
|
} else if (type === 'static') {
|
||||||
if (isFile) {
|
if (paths.length === 1) {
|
||||||
files = [resolvePath(paths[0])];
|
|
||||||
} else if (paths.length === 1) {
|
|
||||||
files = await getFiles(paths[0], nowConfig, opts);
|
files = await getFiles(paths[0], nowConfig, opts);
|
||||||
} else {
|
} else {
|
||||||
if (!files) {
|
if (!files) {
|
||||||
@@ -128,30 +135,23 @@ export default class Now extends EventEmitter {
|
|||||||
}
|
}
|
||||||
} else if (type === 'docker') {
|
} else if (type === 'docker') {
|
||||||
files = await getDockerFiles(paths[0], nowConfig, opts);
|
files = await getDockerFiles(paths[0], nowConfig, opts);
|
||||||
} else if (isBuilds) {
|
|
||||||
opts.isBuilds = isBuilds;
|
|
||||||
|
|
||||||
if (isFile) {
|
|
||||||
files = [resolvePath(paths[0])];
|
|
||||||
} else if (paths.length === 1) {
|
|
||||||
files = await getFiles(paths[0], {}, opts);
|
|
||||||
} else {
|
|
||||||
if (!files) {
|
|
||||||
files = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const path of paths) {
|
const uploadStamp = stamp();
|
||||||
const list = await getFiles(path, {}, opts);
|
|
||||||
files = files.concat(list);
|
|
||||||
|
|
||||||
for (const file of list) {
|
if (isBuilds) {
|
||||||
relatives[file] = path;
|
deployment = await processDeployment({
|
||||||
}
|
now: this,
|
||||||
}
|
output: this._output,
|
||||||
}
|
hashes,
|
||||||
}
|
paths,
|
||||||
|
requestBody,
|
||||||
|
uploadStamp,
|
||||||
|
deployStamp,
|
||||||
|
quiet,
|
||||||
|
nowConfig,
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
// Read `registry.npmjs.org` authToken from .npmrc
|
// Read `registry.npmjs.org` authToken from .npmrc
|
||||||
let authToken;
|
let authToken;
|
||||||
|
|
||||||
@@ -160,82 +160,9 @@ export default class Now extends EventEmitter {
|
|||||||
(await readAuthToken(paths[0])) || (await readAuthToken(homedir()));
|
(await readAuthToken(paths[0])) || (await readAuthToken(homedir()));
|
||||||
}
|
}
|
||||||
|
|
||||||
const hashes = await time('Computing hashes', () => {
|
requestBody = {
|
||||||
const pkgDetails = Object.assign({ name }, pkg);
|
token: this._token,
|
||||||
return hash(files, pkgDetails);
|
teamId: this.currentTeam,
|
||||||
});
|
|
||||||
|
|
||||||
this._files = hashes;
|
|
||||||
|
|
||||||
const deployment = await this.retry(async bail => {
|
|
||||||
// Flatten the array to contain files to sync where each nested input
|
|
||||||
// array has a group of files with the same sha but different path
|
|
||||||
const files = await time(
|
|
||||||
'Get files ready for deployment',
|
|
||||||
Promise.all(
|
|
||||||
Array.prototype.concat.apply(
|
|
||||||
[],
|
|
||||||
await Promise.all(
|
|
||||||
Array.from(this._files).map(async ([sha, { data, names }]) => {
|
|
||||||
const statFn = followSymlinks ? fs.stat : fs.lstat;
|
|
||||||
|
|
||||||
return names.map(async name => {
|
|
||||||
const getMode = async () => {
|
|
||||||
const st = await statFn(name);
|
|
||||||
return st.mode;
|
|
||||||
};
|
|
||||||
|
|
||||||
const mode = await getMode();
|
|
||||||
const multipleStatic = Object.keys(relatives).length !== 0;
|
|
||||||
|
|
||||||
let file;
|
|
||||||
|
|
||||||
if (isFile) {
|
|
||||||
file = basename(paths[0]);
|
|
||||||
} else if (multipleStatic) {
|
|
||||||
file = toRelative(name, join(relatives[name], '..'));
|
|
||||||
} else {
|
|
||||||
file = toRelative(name, paths[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
sha,
|
|
||||||
size: data.length,
|
|
||||||
file,
|
|
||||||
mode
|
|
||||||
};
|
|
||||||
});
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// This is a useful warning because it prevents people
|
|
||||||
// from getting confused about a deployment that renders 404.
|
|
||||||
if (
|
|
||||||
files.length === 0 ||
|
|
||||||
files.every(item => item.file.startsWith('.'))
|
|
||||||
) {
|
|
||||||
warn(
|
|
||||||
'There are no files (or only files starting with a dot) inside your deployment.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const queryProps = {};
|
|
||||||
const requestBody = isBuilds
|
|
||||||
? {
|
|
||||||
version: 2,
|
|
||||||
env,
|
|
||||||
build,
|
|
||||||
public: wantsPublic || nowConfig.public,
|
|
||||||
name,
|
|
||||||
project,
|
|
||||||
files,
|
|
||||||
meta,
|
|
||||||
regions
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
env,
|
env,
|
||||||
build,
|
build,
|
||||||
meta,
|
meta,
|
||||||
@@ -246,188 +173,44 @@ export default class Now extends EventEmitter {
|
|||||||
description,
|
description,
|
||||||
deploymentType: type,
|
deploymentType: type,
|
||||||
registryAuthToken: authToken,
|
registryAuthToken: authToken,
|
||||||
files,
|
|
||||||
engines,
|
engines,
|
||||||
scale,
|
scale,
|
||||||
sessionAffinity,
|
sessionAffinity,
|
||||||
limits: nowConfig.limits,
|
limits: nowConfig.limits,
|
||||||
atlas
|
atlas,
|
||||||
|
config: nowConfig,
|
||||||
|
functions: nowConfig.functions,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (Object.keys(nowConfig).length > 0) {
|
deployment = await processDeployment({
|
||||||
if (isBuilds) {
|
legacy: true,
|
||||||
// These properties are only used inside Now CLI and
|
now: this,
|
||||||
// are not supported on the API.
|
output: this._output,
|
||||||
const exclude = ['github', 'scope'];
|
hashes,
|
||||||
|
paths,
|
||||||
// Request properties that are made of a combination of
|
requestBody,
|
||||||
// command flags and config properties were already set
|
uploadStamp,
|
||||||
// earlier. Here, we are setting request properties that
|
deployStamp,
|
||||||
// are purely made of their equally-named config property.
|
quiet,
|
||||||
for (const key of Object.keys(nowConfig)) {
|
env,
|
||||||
const value = nowConfig[key];
|
nowConfig,
|
||||||
|
|
||||||
if (!requestBody[key] && !exclude.includes(key)) {
|
|
||||||
requestBody[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
requestBody.config = nowConfig;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isBuilds) {
|
|
||||||
if (forceNew) {
|
|
||||||
queryProps.forceNew = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (target) {
|
|
||||||
requestBody.target = target;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isFile) {
|
|
||||||
requestBody.routes = [
|
|
||||||
{
|
|
||||||
src: '/',
|
|
||||||
dest: `/${files[0].file}`
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = qs.stringify(queryProps);
|
|
||||||
const version = isBuilds ? 'v9' : 'v4';
|
|
||||||
|
|
||||||
const res = await this._fetch(
|
|
||||||
`/${version}/now/deployments${query ? `?${query}` : ''}`,
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
body: requestBody
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// No retry on 4xx
|
|
||||||
let body;
|
|
||||||
|
|
||||||
try {
|
|
||||||
body = await res.json();
|
|
||||||
} catch (err) {
|
|
||||||
throw new Error(
|
|
||||||
`Unexpected response error: ${err.message} (${
|
|
||||||
res.status
|
|
||||||
} status code)`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res.status === 429) {
|
|
||||||
if (body.error && body.error.code === 'builds_rate_limited') {
|
|
||||||
const err = new Error(body.error.message);
|
|
||||||
err.status = res.status;
|
|
||||||
err.retryAfter = 'never';
|
|
||||||
err.code = body.error.code;
|
|
||||||
|
|
||||||
return bail(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let msg = 'You have been creating deployments at a very fast pace. ';
|
|
||||||
|
|
||||||
if (body.error && body.error.limit && body.error.limit.reset) {
|
|
||||||
const { reset } = body.error.limit;
|
|
||||||
const difference = reset * 1000 - Date.now();
|
|
||||||
|
|
||||||
msg += `Please retry in ${ms(difference, { long: true })}.`;
|
|
||||||
} else {
|
|
||||||
msg += 'Please slow down.';
|
|
||||||
}
|
|
||||||
|
|
||||||
const err = new Error(msg);
|
|
||||||
|
|
||||||
err.status = res.status;
|
|
||||||
err.retryAfter = 'never';
|
|
||||||
|
|
||||||
return bail(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the deployment domain is missing a cert, bail with the error
|
|
||||||
if (
|
|
||||||
res.status === 400 &&
|
|
||||||
body.error &&
|
|
||||||
body.error.code === 'cert_missing'
|
|
||||||
) {
|
|
||||||
bail(await responseError(res, null, body));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
res.status === 400 &&
|
|
||||||
body.error &&
|
|
||||||
body.error.code === 'missing_files'
|
|
||||||
) {
|
|
||||||
return body;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res.status === 404 && body.error && body.error.code === 'not_found') {
|
|
||||||
return body;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res.status >= 400 && res.status < 500) {
|
|
||||||
const err = new Error();
|
|
||||||
|
|
||||||
if (body.error) {
|
|
||||||
const { code, unreferencedBuildSpecs } = body.error;
|
|
||||||
|
|
||||||
if (code === 'env_value_invalid_type') {
|
|
||||||
const { key } = body.error;
|
|
||||||
err.message =
|
|
||||||
`The env key ${key} has an invalid type: ${typeof env[key]}. ` +
|
|
||||||
'Please supply a String or a Number (https://err.sh/now/env-value-invalid-type)';
|
|
||||||
} else if (code === 'unreferenced_build_specifications') {
|
|
||||||
const count = unreferencedBuildSpecs.length;
|
|
||||||
const prefix = count === 1 ? 'build' : 'builds';
|
|
||||||
|
|
||||||
err.message =
|
|
||||||
`You defined ${count} ${prefix} that did not match any source files (please ensure they are NOT defined in ${highlight(
|
|
||||||
'.nowignore'
|
|
||||||
)}):` +
|
|
||||||
`\n- ${unreferencedBuildSpecs
|
|
||||||
.map(item => JSON.stringify(item))
|
|
||||||
.join('\n- ')}`;
|
|
||||||
} else {
|
|
||||||
Object.assign(err, body.error);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err.message = 'Not able to create deployment';
|
|
||||||
}
|
|
||||||
|
|
||||||
return bail(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res.status !== 200) {
|
|
||||||
throw new Error(body.error.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [name, value] of res.headers.entries()) {
|
|
||||||
if (name.startsWith('x-now-warning-')) {
|
|
||||||
this._output.warn(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return body;
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// We report about files whose sizes are too big
|
// We report about files whose sizes are too big
|
||||||
let missingVersion = false;
|
let missingVersion = false;
|
||||||
|
|
||||||
if (deployment.warnings) {
|
if (deployment && deployment.warnings) {
|
||||||
let sizeExceeded = 0;
|
let sizeExceeded = 0;
|
||||||
|
|
||||||
deployment.warnings.forEach(warning => {
|
deployment.warnings.forEach(warning => {
|
||||||
if (warning.reason === 'size_limit_exceeded') {
|
if (warning.reason === 'size_limit_exceeded') {
|
||||||
const { sha, limit } = warning;
|
const { sha, limit } = warning;
|
||||||
const n = hashes.get(sha).names.pop();
|
const n = hashes[sha].names.pop();
|
||||||
|
|
||||||
warn(`Skipping file ${n} (size exceeded ${bytes(limit)}`);
|
warn(`Skipping file ${n} (size exceeded ${bytes(limit)}`);
|
||||||
|
|
||||||
hashes.get(sha).names.unshift(n); // Move name (hack, if duplicate matches we report them in order)
|
hashes[sha].names.unshift(n); // Move name (hack, if duplicate matches we report them in order)
|
||||||
sizeExceeded++;
|
sizeExceeded++;
|
||||||
} else if (warning.reason === 'node_version_not_found') {
|
} else if (warning.reason === 'node_version_not_found') {
|
||||||
warn(`Requested node version ${warning.wanted} is not available`);
|
warn(`Requested node version ${warning.wanted} is not available`);
|
||||||
@@ -445,19 +228,10 @@ export default class Now extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deployment.error && deployment.error.code === 'missing_files') {
|
|
||||||
this._missing = deployment.error.missing || [];
|
|
||||||
this._fileCount = files.length;
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isBuilds && !quiet && type === 'npm' && deployment.nodeVersion) {
|
if (!isBuilds && !quiet && type === 'npm' && deployment.nodeVersion) {
|
||||||
if (engines && engines.node && !missingVersion) {
|
if (engines && engines.node && !missingVersion) {
|
||||||
log(
|
log(
|
||||||
chalk`Using Node.js {bold ${
|
chalk`Using Node.js {bold ${deployment.nodeVersion}} (requested: {dim \`${engines.node}\`})`
|
||||||
deployment.nodeVersion
|
|
||||||
}} (requested: {dim \`${engines.node}\`})`
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
log(chalk`Using Node.js {bold ${deployment.nodeVersion}} (default)`);
|
log(chalk`Using Node.js {bold ${deployment.nodeVersion}} (default)`);
|
||||||
@@ -472,81 +246,90 @@ export default class Now extends EventEmitter {
|
|||||||
return deployment;
|
return deployment;
|
||||||
}
|
}
|
||||||
|
|
||||||
upload({ atlas = false, scale = {} } = {}) {
|
async handleDeploymentError(error, { hashes, env }) {
|
||||||
const { debug, time } = this._output;
|
if (error.status === 429) {
|
||||||
debug(`Will upload ${this._missing.length} files`);
|
if (error.code === 'builds_rate_limited') {
|
||||||
|
const err = new Error(error.message);
|
||||||
|
err.status = error.status;
|
||||||
|
err.retryAfter = 'never';
|
||||||
|
err.code = error.code;
|
||||||
|
|
||||||
this._agent.setConcurrency({
|
return err;
|
||||||
maxStreams: MAX_CONCURRENT,
|
|
||||||
capacity: this._missing.length
|
|
||||||
});
|
|
||||||
|
|
||||||
time(
|
|
||||||
'Uploading files',
|
|
||||||
Promise.all(
|
|
||||||
this._missing.map(sha =>
|
|
||||||
retry(
|
|
||||||
async bail => {
|
|
||||||
const file = this._files.get(sha);
|
|
||||||
const fPath = file.names[0];
|
|
||||||
const stream = createReadStream(fPath);
|
|
||||||
const { data } = file;
|
|
||||||
|
|
||||||
const fstreamPush = stream.push;
|
|
||||||
|
|
||||||
let uploadedSoFar = 0;
|
|
||||||
stream.push = chunk => {
|
|
||||||
// If we're about to push the last chunk, then don't do it here
|
|
||||||
// But instead, we'll "hang" the progress bar and do it on 200
|
|
||||||
if (chunk && uploadedSoFar + chunk.length < data.length) {
|
|
||||||
this.emit('uploadProgress', chunk.length);
|
|
||||||
uploadedSoFar += chunk.length;
|
|
||||||
}
|
}
|
||||||
return fstreamPush.call(stream, chunk);
|
|
||||||
};
|
|
||||||
|
|
||||||
const url = atlas ? '/v1/now/images' : '/v2/now/files';
|
let msg = 'You have been creating deployments at a very fast pace. ';
|
||||||
const additionalHeaders = atlas
|
|
||||||
? {
|
|
||||||
'x-now-dcs': Object.keys(scale).join(',')
|
|
||||||
}
|
|
||||||
: {};
|
|
||||||
const res = await this._fetch(url, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/octet-stream',
|
|
||||||
'x-now-digest': sha,
|
|
||||||
'x-now-size': data.length,
|
|
||||||
...additionalHeaders
|
|
||||||
},
|
|
||||||
body: stream
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res.status === 200) {
|
if (error.limit && error.limit.reset) {
|
||||||
// What we want
|
const { reset } = error.limit;
|
||||||
this.emit('uploadProgress', file.data.length - uploadedSoFar);
|
const difference = reset * 1000 - Date.now();
|
||||||
this.emit('upload', file);
|
|
||||||
} else if (res.status > 200 && res.status < 500) {
|
msg += `Please retry in ${ms(difference, { long: true })}.`;
|
||||||
// If something is wrong with our request, we don't retry
|
|
||||||
return bail(await responseError(res, `Failed to upload file with status: ${res.status}`));
|
|
||||||
} else {
|
} else {
|
||||||
// If something is wrong with the server, we retry
|
msg += 'Please slow down.';
|
||||||
throw await responseError(res, 'Failed to upload file');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const err = new Error(msg);
|
||||||
|
|
||||||
|
err.status = error.status;
|
||||||
|
err.retryAfter = 'never';
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the deployment domain is missing a cert, bail with the error
|
||||||
|
if (error.status === 400 && error.code === 'cert_missing') {
|
||||||
|
return responseError(error, null, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error.status === 400 && error.code === 'missing_files') {
|
||||||
|
this._missing = error.missing || [];
|
||||||
|
this._fileCount = hashes.length;
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error.status === 404 && error.code === 'not_found') {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error.status >= 400 && error.status < 500) {
|
||||||
|
const err = new Error();
|
||||||
|
|
||||||
|
const { code, unreferencedBuildSpecs } = error;
|
||||||
|
|
||||||
|
if (code === 'env_value_invalid_type') {
|
||||||
|
const { key } = error;
|
||||||
|
err.message =
|
||||||
|
`The env key ${key} has an invalid type: ${typeof env[key]}. ` +
|
||||||
|
'Please supply a String or a Number (https://err.sh/now-cli/env-value-invalid-type)';
|
||||||
|
} else if (code === 'unreferenced_build_specifications') {
|
||||||
|
const count = unreferencedBuildSpecs.length;
|
||||||
|
const prefix = count === 1 ? 'build' : 'builds';
|
||||||
|
|
||||||
|
err.message =
|
||||||
|
`You defined ${count} ${prefix} that did not match any source files (please ensure they are NOT defined in ${highlight(
|
||||||
|
'.nowignore'
|
||||||
|
)}):` +
|
||||||
|
`\n- ${unreferencedBuildSpecs
|
||||||
|
.map(item => JSON.stringify(item))
|
||||||
|
.join('\n- ')}`;
|
||||||
|
} else {
|
||||||
|
Object.assign(err, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle build errors
|
||||||
|
if (error.id && error.id.startsWith('bld_')) {
|
||||||
|
return new BuildError({
|
||||||
|
meta: {
|
||||||
|
entrypoint: error.entrypoint,
|
||||||
},
|
},
|
||||||
{
|
});
|
||||||
retries: 3,
|
|
||||||
randomize: true,
|
|
||||||
onRetry: this._onRetry
|
|
||||||
}
|
}
|
||||||
)
|
|
||||||
)
|
return new Error(error.message);
|
||||||
)
|
|
||||||
)
|
|
||||||
.then(() => {
|
|
||||||
this.emit('complete');
|
|
||||||
})
|
|
||||||
.catch(err => this.emit('error', err));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async listSecrets() {
|
async listSecrets() {
|
||||||
@@ -589,7 +372,7 @@ export default class Now extends EventEmitter {
|
|||||||
{
|
{
|
||||||
retries: 3,
|
retries: 3,
|
||||||
minTimeout: 2500,
|
minTimeout: 2500,
|
||||||
onRetry: this._onRetry
|
onRetry: this._onRetry,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -597,7 +380,7 @@ export default class Now extends EventEmitter {
|
|||||||
if (!app && !Object.keys(meta).length) {
|
if (!app && !Object.keys(meta).length) {
|
||||||
// Get the 35 latest projects and their latest deployment
|
// Get the 35 latest projects and their latest deployment
|
||||||
const query = new URLSearchParams({ limit: 35 });
|
const query = new URLSearchParams({ limit: 35 });
|
||||||
const projects = await fetchRetry(`/projects/list?${query}`);
|
const projects = await fetchRetry(`/v2/projects/?${query}`);
|
||||||
|
|
||||||
const deployments = await Promise.all(
|
const deployments = await Promise.all(
|
||||||
projects.map(async ({ id: projectId }) => {
|
projects.map(async ({ id: projectId }) => {
|
||||||
@@ -647,7 +430,7 @@ export default class Now extends EventEmitter {
|
|||||||
{
|
{
|
||||||
retries: 3,
|
retries: 3,
|
||||||
minTimeout: 2500,
|
minTimeout: 2500,
|
||||||
onRetry: this._onRetry
|
onRetry: this._onRetry,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -667,11 +450,11 @@ export default class Now extends EventEmitter {
|
|||||||
host = host.slice(0, -1);
|
host = host.slice(0, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = `/v3/now/hosts/${encodeURIComponent(
|
const url = `/v10/now/deployments/get?url=${encodeURIComponent(
|
||||||
host
|
host
|
||||||
)}?resolve=1&noState=1`;
|
)}&resolve=1&noState=1`;
|
||||||
|
|
||||||
const { deployment } = await this.retry(
|
const deployment = await this.retry(
|
||||||
async bail => {
|
async bail => {
|
||||||
const res = await this._fetch(url);
|
const res = await this._fetch(url);
|
||||||
|
|
||||||
@@ -697,7 +480,7 @@ export default class Now extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const url = `/${
|
const url = `/${
|
||||||
isBuilds ? 'v9' : 'v5'
|
isBuilds ? 'v11' : 'v5'
|
||||||
}/now/deployments/${encodeURIComponent(id)}`;
|
}/now/deployments/${encodeURIComponent(id)}`;
|
||||||
|
|
||||||
return this.retry(
|
return this.retry(
|
||||||
@@ -727,7 +510,7 @@ export default class Now extends EventEmitter {
|
|||||||
|
|
||||||
await this.retry(async bail => {
|
await this.retry(async bail => {
|
||||||
const res = await this._fetch(url, {
|
const res = await this._fetch(url, {
|
||||||
method: 'DELETE'
|
method: 'DELETE',
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
@@ -748,7 +531,7 @@ export default class Now extends EventEmitter {
|
|||||||
return retry(fn, {
|
return retry(fn, {
|
||||||
retries,
|
retries,
|
||||||
maxTimeout,
|
maxTimeout,
|
||||||
onRetry: this._onRetry
|
onRetry: this._onRetry,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -756,9 +539,7 @@ export default class Now extends EventEmitter {
|
|||||||
this._output.debug(`Retrying: ${err}\n${err.stack}`);
|
this._output.debug(`Retrying: ${err}\n${err.stack}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
close() {
|
close() {}
|
||||||
this._agent.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
get id() {
|
get id() {
|
||||||
return this._id;
|
return this._id;
|
||||||
@@ -802,14 +583,21 @@ export default class Now extends EventEmitter {
|
|||||||
|
|
||||||
opts.headers = opts.headers || {};
|
opts.headers = opts.headers || {};
|
||||||
opts.headers.accept = 'application/json';
|
opts.headers.accept = 'application/json';
|
||||||
opts.headers.authorization = `Bearer ${this._token}`;
|
opts.headers.Authorization = `Bearer ${this._token}`;
|
||||||
opts.headers['user-agent'] = ua;
|
opts.headers['user-agent'] = ua;
|
||||||
|
|
||||||
|
if (
|
||||||
|
opts.body &&
|
||||||
|
typeof opts.body === 'object' &&
|
||||||
|
opts.body.constructor === Object
|
||||||
|
) {
|
||||||
|
opts.body = JSON.stringify(opts.body);
|
||||||
|
opts.headers['Content-Type'] = 'application/json';
|
||||||
|
}
|
||||||
|
|
||||||
return this._output.time(
|
return this._output.time(
|
||||||
`${opts.method || 'GET'} ${this._apiUrl}${_url} ${JSON.stringify(
|
`${opts.method || 'GET'} ${this._apiUrl}${_url} ${opts.body || ''}`,
|
||||||
opts.body
|
fetch(`${this._apiUrl}${_url}`, opts)
|
||||||
) || ''}`,
|
|
||||||
this._agent.fetch(_url, opts)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -827,8 +615,8 @@ export default class Now extends EventEmitter {
|
|||||||
opts = Object.assign({}, opts, {
|
opts = Object.assign({}, opts, {
|
||||||
body: JSON.stringify(opts.body),
|
body: JSON.stringify(opts.body),
|
||||||
headers: Object.assign({}, opts.headers, {
|
headers: Object.assign({}, opts.headers, {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json',
|
||||||
})
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const res = await this._fetch(url, opts);
|
const res = await this._fetch(url, opts);
|
||||||
@@ -875,6 +663,7 @@ function hasNpmStart(pkg) {
|
|||||||
|
|
||||||
function hasFile(base, files, name) {
|
function hasFile(base, files, name) {
|
||||||
const relative = files.map(file => toRelative(file, base));
|
const relative = files.map(file => toRelative(file, base));
|
||||||
|
console.log(731, relative);
|
||||||
return relative.indexOf(name) !== -1;
|
return relative.indexOf(name) !== -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import inquirer from 'inquirer';
|
|||||||
import stripAnsi from 'strip-ansi';
|
import stripAnsi from 'strip-ansi';
|
||||||
import eraseLines from '../output/erase-lines';
|
import eraseLines from '../output/erase-lines';
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-unassigned-import
|
|
||||||
import './patch-inquirer';
|
import './patch-inquirer';
|
||||||
|
|
||||||
function getLength(string) {
|
function getLength(string) {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable import/no-unresolved */
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import pkg from '../../package.json';
|
import pkg from '../../package.json';
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user