mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-11 21:07:47 +00:00
Compare commits
45 Commits
framework-
...
@vercel/py
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e659eecf48 | ||
|
|
b428f7ff83 | ||
|
|
5eb133283d | ||
|
|
646c29600e | ||
|
|
469eb4315d | ||
|
|
6dc54d0d64 | ||
|
|
adc84d5148 | ||
|
|
88642b1ce8 | ||
|
|
4b8d207533 | ||
|
|
36fe5cc4d1 | ||
|
|
370b0dbed2 | ||
|
|
cc7a82fb0a | ||
|
|
6eea26c39e | ||
|
|
b8bfae7840 | ||
|
|
dc6a0a1cbb | ||
|
|
a6807c9d21 | ||
|
|
c628090d08 | ||
|
|
4e0b291ed1 | ||
|
|
ee0bc9b0c8 | ||
|
|
e516c1f49f | ||
|
|
01f53f36fc | ||
|
|
f2d396caae | ||
|
|
001f2f60b8 | ||
|
|
78ca930287 | ||
|
|
b03e18df12 | ||
|
|
3a6b8b072c | ||
|
|
d480cd6bbd | ||
|
|
181b624bf4 | ||
|
|
200495e4ce | ||
|
|
9178f14c40 | ||
|
|
9a790f5352 | ||
|
|
33d8be7f8f | ||
|
|
82aa86b786 | ||
|
|
6288d5ef43 | ||
|
|
00a97b7ae2 | ||
|
|
a19fb3aa0f | ||
|
|
93fae76f9c | ||
|
|
e5131d5557 | ||
|
|
930063638c | ||
|
|
7b509ae672 | ||
|
|
711da37771 | ||
|
|
5e50b96994 | ||
|
|
35d2f5950f | ||
|
|
37f969416c | ||
|
|
75630e0982 |
332
LICENSE
332
LICENSE
@@ -1,190 +1,202 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
https://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
1. Definitions.
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
1. Definitions.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
Copyright 2017 Vercel, Inc.
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
Copyright 2017 Vercel, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"dependencies": {
|
||||
"@sentry/node": "5.11.1",
|
||||
"got": "10.2.1",
|
||||
"node-fetch": "2.6.0",
|
||||
"node-fetch": "2.6.1",
|
||||
"parse-github-url": "1.0.2",
|
||||
"tar-fs": "2.0.0",
|
||||
"unzip-stream": "0.3.0"
|
||||
|
||||
@@ -362,10 +362,10 @@ ms@^2.1.1:
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||
|
||||
node-fetch@2.6.0:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
|
||||
integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
|
||||
node-fetch@2.6.1:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
|
||||
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
|
||||
|
||||
normalize-url@^4.1.0:
|
||||
version "4.5.0"
|
||||
|
||||
@@ -3073,9 +3073,9 @@ hmac-drbg@^1.0.1:
|
||||
minimalistic-crypto-utils "^1.0.1"
|
||||
|
||||
hosted-git-info@^2.1.4, hosted-git-info@^2.6.0, hosted-git-info@^2.7.1:
|
||||
version "2.8.8"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
|
||||
integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
|
||||
version "2.8.9"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
|
||||
integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
|
||||
|
||||
hpack.js@^2.1.6:
|
||||
version "2.1.6"
|
||||
|
||||
@@ -4681,9 +4681,9 @@ hmac-drbg@^1.0.1:
|
||||
minimalistic-crypto-utils "^1.0.1"
|
||||
|
||||
hosted-git-info@^2.1.4:
|
||||
version "2.8.8"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
|
||||
integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
|
||||
version "2.8.9"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
|
||||
integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
|
||||
|
||||
hsl-regex@^1.0.0:
|
||||
version "1.0.0"
|
||||
@@ -8195,9 +8195,9 @@ sprintf-js@~1.0.2:
|
||||
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
|
||||
|
||||
ssri@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8"
|
||||
integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5"
|
||||
integrity sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==
|
||||
dependencies:
|
||||
figgy-pudding "^3.5.1"
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "vercel-monorepo",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"license": "Apache-2.0",
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
"packages/*"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/build-utils",
|
||||
"version": "2.10.3-canary.3",
|
||||
"version": "2.11.1",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.js",
|
||||
@@ -29,7 +29,7 @@
|
||||
"@types/node-fetch": "^2.1.6",
|
||||
"@types/semver": "6.0.0",
|
||||
"@types/yazl": "^2.4.1",
|
||||
"@vercel/frameworks": "0.3.3-canary.3",
|
||||
"@vercel/frameworks": "0.4.1",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"aggregate-error": "3.0.1",
|
||||
"async-retry": "1.2.3",
|
||||
|
||||
1
packages/cgi/.gitignore
vendored
1
packages/cgi/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
handler
|
||||
@@ -1,25 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
const execa = require('execa');
|
||||
const { join } = require('path');
|
||||
const { homedir } = require('os');
|
||||
|
||||
async function main() {
|
||||
process.env.GOOS = 'linux';
|
||||
process.env.GOARCH = 'amd64';
|
||||
process.env.GOPATH = join(homedir(), 'go');
|
||||
|
||||
await execa('go', ['get', 'github.com/aws/aws-lambda-go/events'], {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
await execa('go', ['get', 'github.com/aws/aws-lambda-go/lambda'], {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
await execa('go', ['build', '-o', 'handler', 'main.go'], {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,45 +0,0 @@
|
||||
const path = require('path');
|
||||
const { mkdirp, copyFile } = require('fs-extra');
|
||||
|
||||
const {
|
||||
glob,
|
||||
download,
|
||||
shouldServe,
|
||||
createLambda,
|
||||
getWritableDirectory,
|
||||
} = require('@vercel/build-utils');
|
||||
|
||||
exports.analyze = ({ files, entrypoint }) => files[entrypoint].digest;
|
||||
|
||||
exports.version = 3;
|
||||
|
||||
exports.build = async ({ workPath, files, entrypoint, meta }) => {
|
||||
const outDir = await getWritableDirectory();
|
||||
|
||||
await download(files, workPath, meta);
|
||||
|
||||
const handlerPath = path.join(__dirname, 'handler');
|
||||
await copyFile(handlerPath, path.join(outDir, 'handler'));
|
||||
|
||||
const entrypointOutDir = path.join(outDir, path.dirname(entrypoint));
|
||||
await mkdirp(entrypointOutDir);
|
||||
|
||||
// For now only the entrypoint file is copied into the lambda
|
||||
await copyFile(
|
||||
path.join(workPath, entrypoint),
|
||||
path.join(outDir, entrypoint)
|
||||
);
|
||||
|
||||
const lambda = await createLambda({
|
||||
files: await glob('**', outDir),
|
||||
handler: 'handler',
|
||||
runtime: 'go1.x',
|
||||
environment: {
|
||||
SCRIPT_FILENAME: entrypoint,
|
||||
},
|
||||
});
|
||||
|
||||
return { output: lambda };
|
||||
};
|
||||
|
||||
exports.shouldServe = shouldServe;
|
||||
@@ -1,36 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
now "../../utils/go/bridge"
|
||||
"net/http"
|
||||
"net/http/cgi"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type CgiHandler struct {
|
||||
http.Handler
|
||||
Dir string
|
||||
Script string
|
||||
}
|
||||
|
||||
func (h *CgiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
handler := cgi.Handler{
|
||||
Path: h.Script,
|
||||
Root: "/" + h.Script,
|
||||
Dir: h.Dir,
|
||||
Env: []string{
|
||||
"HTTPS=on",
|
||||
"SERVER_PORT=443",
|
||||
"SERVER_SOFTWARE=@vercel/cgi",
|
||||
},
|
||||
}
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func main() {
|
||||
workdir, _ := filepath.Abs(".")
|
||||
script := os.Getenv("SCRIPT_FILENAME")
|
||||
handler := &CgiHandler{nil, workdir, script}
|
||||
now.Start(handler)
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"name": "@vercel/cgi",
|
||||
"version": "1.0.7",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/vercel/vercel.git",
|
||||
"directory": "packages/cgi"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "node build",
|
||||
"prepublishOnly": "node build"
|
||||
},
|
||||
"files": [
|
||||
"index.js",
|
||||
"handler"
|
||||
],
|
||||
"dependencies": {
|
||||
"fs-extra": "7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"rmfr": "2.0.0"
|
||||
}
|
||||
}
|
||||
1
packages/cli/@types/inquirer/index.d.ts
vendored
1
packages/cli/@types/inquirer/index.d.ts
vendored
@@ -1 +0,0 @@
|
||||
declare module 'inquirer';
|
||||
5
packages/cli/@types/jsonlines/index.d.ts
vendored
Normal file
5
packages/cli/@types/jsonlines/index.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
declare module 'jsonlines' {
|
||||
import { Transform } from 'stream';
|
||||
|
||||
function parse(): Transform;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vercel",
|
||||
"version": "22.0.2-canary.3",
|
||||
"version": "23.0.1",
|
||||
"preferGlobal": true,
|
||||
"license": "Apache-2.0",
|
||||
"description": "The command-line interface for Vercel",
|
||||
@@ -17,8 +17,8 @@
|
||||
"test-integration-dev": "ava test/dev/integration.js --serial --fail-fast --verbose",
|
||||
"prepublishOnly": "yarn build",
|
||||
"coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov",
|
||||
"build": "ts-node ./scripts/build.ts",
|
||||
"build-dev": "ts-node ./scripts/build.ts --dev"
|
||||
"build": "node -r ts-eager/register ./scripts/build.ts",
|
||||
"build-dev": "node -r ts-eager/register ./scripts/build.ts --dev"
|
||||
},
|
||||
"nyc": {
|
||||
"include": [
|
||||
@@ -61,10 +61,10 @@
|
||||
"node": ">= 12"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "2.10.3-canary.3",
|
||||
"@vercel/build-utils": "2.11.1",
|
||||
"@vercel/go": "1.2.2",
|
||||
"@vercel/node": "1.10.0",
|
||||
"@vercel/python": "2.0.1",
|
||||
"@vercel/node": "1.11.1",
|
||||
"@vercel/python": "2.0.4",
|
||||
"@vercel/ruby": "1.2.6",
|
||||
"update-notifier": "4.1.0"
|
||||
},
|
||||
@@ -82,13 +82,14 @@
|
||||
"@types/fs-extra": "5.0.5",
|
||||
"@types/glob": "7.1.1",
|
||||
"@types/http-proxy": "1.16.2",
|
||||
"@types/inquirer": "7.3.1",
|
||||
"@types/load-json-file": "2.0.7",
|
||||
"@types/mime-types": "2.1.0",
|
||||
"@types/minimatch": "3.0.3",
|
||||
"@types/mri": "1.1.0",
|
||||
"@types/ms": "0.7.30",
|
||||
"@types/node": "11.11.0",
|
||||
"@types/node-fetch": "2.1.4",
|
||||
"@types/node-fetch": "2.5.10",
|
||||
"@types/npm-package-arg": "6.1.0",
|
||||
"@types/pluralize": "0.0.29",
|
||||
"@types/progress": "2.0.3",
|
||||
@@ -99,7 +100,7 @@
|
||||
"@types/universal-analytics": "0.4.2",
|
||||
"@types/which": "1.3.2",
|
||||
"@types/write-json-file": "2.2.1",
|
||||
"@vercel/frameworks": "0.3.3-canary.3",
|
||||
"@vercel/frameworks": "0.4.1",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"@zeit/fun": "0.11.2",
|
||||
"@zeit/source-map-support": "0.6.2",
|
||||
@@ -116,7 +117,7 @@
|
||||
"chalk": "4.1.0",
|
||||
"chokidar": "3.3.1",
|
||||
"clipboardy": "2.1.0",
|
||||
"codecov": "3.7.1",
|
||||
"codecov": "3.8.2",
|
||||
"cpy": "7.2.0",
|
||||
"credit-card": "3.0.1",
|
||||
"date-fns": "1.29.0",
|
||||
@@ -147,7 +148,7 @@
|
||||
"node-fetch": "2.6.1",
|
||||
"npm-package-arg": "6.1.0",
|
||||
"nyc": "13.2.0",
|
||||
"open": "8.0.2",
|
||||
"open": "8.2.0",
|
||||
"ora": "3.4.0",
|
||||
"pcre-to-regexp": "1.0.0",
|
||||
"pluralize": "7.0.0",
|
||||
@@ -168,6 +169,7 @@
|
||||
"title": "3.4.1",
|
||||
"tmp-promise": "1.0.3",
|
||||
"tree-kill": "1.2.2",
|
||||
"ts-eager": "2.0.2",
|
||||
"ts-node": "8.3.0",
|
||||
"typescript": "3.9.3",
|
||||
"universal-analytics": "0.4.20",
|
||||
|
||||
@@ -2,10 +2,11 @@ import chalk from 'chalk';
|
||||
|
||||
import { handleError } from '../../util/error';
|
||||
|
||||
import Client from '../../util/client';
|
||||
import getArgs from '../../util/get-args';
|
||||
import getSubcommand from '../../util/get-subcommand';
|
||||
import logo from '../../util/output/logo';
|
||||
import { getPkgName } from '../../util/pkg-name.ts';
|
||||
import { getPkgName } from '../../util/pkg-name';
|
||||
|
||||
import ls from './ls';
|
||||
import rm from './rm';
|
||||
@@ -36,6 +37,7 @@ const help = () => {
|
||||
)} Login token
|
||||
-S, --scope Set a custom scope
|
||||
-N, --next Show next page of results
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
${chalk.gray('–')} Add a new alias to ${chalk.underline('my-api.vercel.app')}
|
||||
@@ -64,13 +66,13 @@ const help = () => {
|
||||
};
|
||||
|
||||
const COMMAND_CONFIG = {
|
||||
default: 'set',
|
||||
default: ['set'],
|
||||
ls: ['ls', 'list'],
|
||||
rm: ['rm', 'remove'],
|
||||
set: ['set'],
|
||||
};
|
||||
|
||||
export default async function main(client) {
|
||||
export default async function main(client: Client) {
|
||||
let argv;
|
||||
|
||||
try {
|
||||
@@ -1,21 +1,26 @@
|
||||
import chalk from 'chalk';
|
||||
import ms from 'ms';
|
||||
import table from 'text-table';
|
||||
import Now from '../../util';
|
||||
import Client from '../../util/client';
|
||||
import getAliases from '../../util/alias/get-aliases';
|
||||
import getScope from '../../util/get-scope.ts';
|
||||
import stamp from '../../util/output/stamp.ts';
|
||||
import strlen from '../../util/strlen.ts';
|
||||
import getScope from '../../util/get-scope';
|
||||
import stamp from '../../util/output/stamp';
|
||||
import strlen from '../../util/strlen';
|
||||
import getCommandFlags from '../../util/get-command-flags';
|
||||
import { getCommandName } from '../../util/pkg-name.ts';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
|
||||
export default async function ls(client, opts, args) {
|
||||
const {
|
||||
apiUrl,
|
||||
authConfig: { token },
|
||||
output,
|
||||
config: { currentTeam },
|
||||
} = client;
|
||||
import { Alias } from '../../types';
|
||||
|
||||
interface Options {
|
||||
'--next'?: number;
|
||||
}
|
||||
|
||||
export default async function ls(
|
||||
client: Client,
|
||||
opts: Options,
|
||||
args: string[]
|
||||
) {
|
||||
const { output } = client;
|
||||
const { '--next': nextTimestamp } = opts;
|
||||
|
||||
let contextName = null;
|
||||
@@ -36,13 +41,6 @@ export default async function ls(client, opts, args) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const now = new Now({
|
||||
apiUrl,
|
||||
token,
|
||||
debug: client.output.isDebugEnabled(),
|
||||
currentTeam,
|
||||
output,
|
||||
});
|
||||
const lsStamp = stamp();
|
||||
|
||||
if (args.length > 0) {
|
||||
@@ -56,8 +54,9 @@ export default async function ls(client, opts, args) {
|
||||
|
||||
output.spinner(`Fetching aliases under ${chalk.bold(contextName)}`);
|
||||
|
||||
// Get the list of alias
|
||||
const { aliases, pagination } = await getAliases(
|
||||
now,
|
||||
client,
|
||||
undefined,
|
||||
nextTimestamp
|
||||
);
|
||||
@@ -73,21 +72,20 @@ export default async function ls(client, opts, args) {
|
||||
);
|
||||
}
|
||||
|
||||
now.close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
function printAliasTable(aliases) {
|
||||
function printAliasTable(aliases: Alias[]) {
|
||||
return `${table(
|
||||
[
|
||||
['source', 'url', 'age'].map(h => chalk.gray(h)),
|
||||
['source', 'url', 'age'].map(header => chalk.gray(header)),
|
||||
...aliases.map(a => [
|
||||
// for legacy reasons, we might have situations
|
||||
// where the deployment was deleted and the alias
|
||||
// not collected appropriately, and we need to handle it
|
||||
a.deployment && a.deployment.url ? a.deployment.url : chalk.gray('–'),
|
||||
a.alias,
|
||||
ms(Date.now() - new Date(a.createdAt)),
|
||||
ms(Date.now() - a.createdAt),
|
||||
]),
|
||||
],
|
||||
{
|
||||
@@ -1,23 +1,29 @@
|
||||
import chalk from 'chalk';
|
||||
import ms from 'ms';
|
||||
import table from 'text-table';
|
||||
import Now from '../../util';
|
||||
import getScope from '../../util/get-scope.ts';
|
||||
import Client from '../../util/client';
|
||||
import getScope from '../../util/get-scope';
|
||||
import removeAliasById from '../../util/alias/remove-alias-by-id';
|
||||
import stamp from '../../util/output/stamp.ts';
|
||||
import strlen from '../../util/strlen.ts';
|
||||
import stamp from '../../util/output/stamp';
|
||||
import strlen from '../../util/strlen';
|
||||
import confirm from '../../util/input/confirm';
|
||||
import { isValidName } from '../../util/is-valid-name';
|
||||
import findAliasByAliasOrId from '../../util/alias/find-alias-by-alias-or-id';
|
||||
import { getCommandName } from '../../util/pkg-name.ts';
|
||||
|
||||
export default async function rm(client, opts, args) {
|
||||
const {
|
||||
apiUrl,
|
||||
authConfig: { token },
|
||||
output,
|
||||
config: { currentTeam },
|
||||
} = client;
|
||||
import { Alias } from '../../types';
|
||||
import { Output } from '../../util/output';
|
||||
import { isValidName } from '../../util/is-valid-name';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
|
||||
type Options = {
|
||||
'--yes': boolean;
|
||||
};
|
||||
|
||||
export default async function rm(
|
||||
client: Client,
|
||||
opts: Partial<Options>,
|
||||
args: string[]
|
||||
) {
|
||||
const { output } = client;
|
||||
|
||||
let contextName = null;
|
||||
|
||||
@@ -32,13 +38,6 @@ export default async function rm(client, opts, args) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
const now = new Now({
|
||||
apiUrl,
|
||||
token,
|
||||
debug: client.output.isDebugEnabled(),
|
||||
currentTeam,
|
||||
output,
|
||||
});
|
||||
const [aliasOrId] = args;
|
||||
|
||||
if (args.length !== 1) {
|
||||
@@ -61,7 +60,8 @@ export default async function rm(client, opts, args) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const alias = await findAliasByAliasOrId(output, now, aliasOrId);
|
||||
const alias = await findAliasByAliasOrId(output, client, aliasOrId);
|
||||
|
||||
if (!alias) {
|
||||
output.error(
|
||||
`Alias not found by "${aliasOrId}" under ${chalk.bold(contextName)}`
|
||||
@@ -76,7 +76,7 @@ export default async function rm(client, opts, args) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
await removeAliasById(now, alias.uid);
|
||||
await removeAliasById(client, alias.uid);
|
||||
console.log(
|
||||
`${chalk.cyan('> Success!')} Alias ${chalk.bold(
|
||||
alias.alias
|
||||
@@ -85,7 +85,7 @@ export default async function rm(client, opts, args) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
async function confirmAliasRemove(output, alias) {
|
||||
async function confirmAliasRemove(output: Output, alias: Alias) {
|
||||
const srcUrl = alias.deployment
|
||||
? chalk.underline(alias.deployment.url)
|
||||
: null;
|
||||
@@ -94,7 +94,7 @@ async function confirmAliasRemove(output, alias) {
|
||||
[
|
||||
...(srcUrl ? [srcUrl] : []),
|
||||
chalk.underline(alias.alias),
|
||||
chalk.gray(`${ms(new Date() - new Date(alias.created))} ago`),
|
||||
chalk.gray(`${ms(Date.now() - alias.createdAt)} ago`),
|
||||
],
|
||||
],
|
||||
{
|
||||
@@ -19,7 +19,7 @@ import link from '../../util/output/link';
|
||||
import { User } from '../../types';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import toHost from '../../util/to-host';
|
||||
import { NowConfig } from '../../util/dev/types';
|
||||
import { VercelConfig } from '../../util/dev/types';
|
||||
|
||||
type Options = {
|
||||
'--debug': boolean;
|
||||
@@ -28,7 +28,7 @@ type Options = {
|
||||
|
||||
export default async function set(
|
||||
client: Client,
|
||||
opts: Options,
|
||||
opts: Partial<Options>,
|
||||
args: string[]
|
||||
) {
|
||||
const { output, localConfig } = client;
|
||||
@@ -421,7 +421,7 @@ function handleCreateAliasError<T>(
|
||||
return error;
|
||||
}
|
||||
|
||||
function getTargetsForAlias(args: string[], { alias }: NowConfig) {
|
||||
function getTargetsForAlias(args: string[], { alias }: VercelConfig) {
|
||||
if (args.length) {
|
||||
return [args[args.length - 1]]
|
||||
.map(target => (target.indexOf('.') !== -1 ? toHost(target) : target))
|
||||
|
||||
@@ -111,7 +111,6 @@ async function chooseFromDropdown(message: string, exampleList: string[]) {
|
||||
|
||||
return listInput({
|
||||
message,
|
||||
separator: false,
|
||||
choices,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,12 +3,14 @@ import getArgs from '../util/get-args';
|
||||
import buildsList from '../util/output/builds';
|
||||
import routesList from '../util/output/routes';
|
||||
import indent from '../util/output/indent';
|
||||
import Now from '../util';
|
||||
import logo from '../util/output/logo';
|
||||
import elapsed from '../util/output/elapsed.ts';
|
||||
import elapsed from '../util/output/elapsed';
|
||||
import { handleError } from '../util/error';
|
||||
import getScope from '../util/get-scope.ts';
|
||||
import { getPkgName, getCommandName } from '../util/pkg-name.ts';
|
||||
import getScope from '../util/get-scope';
|
||||
import { getPkgName, getCommandName } from '../util/pkg-name';
|
||||
import Client from '../util/client';
|
||||
import { getDeployment } from '../util/get-deployment';
|
||||
import { Deployment } from '@vercel/client';
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
@@ -33,15 +35,15 @@ const help = () => {
|
||||
|
||||
${chalk.gray('–')} Get information about a deployment by its unique URL
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} inspect my-deployment-ji2fjij2.now.sh`)}
|
||||
${chalk.cyan(`$ ${getPkgName()} inspect my-deployment-ji2fjij2.vercel.app`)}
|
||||
|
||||
${chalk.gray('-')} Get information about the deployment an alias points to
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} inspect my-deployment.now.sh`)}
|
||||
${chalk.cyan(`$ ${getPkgName()} inspect my-deployment.vercel.app`)}
|
||||
`);
|
||||
};
|
||||
|
||||
export default async function main(client) {
|
||||
export default async function main(client: Client) {
|
||||
let deployment;
|
||||
let argv;
|
||||
|
||||
@@ -57,14 +59,7 @@ export default async function main(client) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
const {
|
||||
apiUrl,
|
||||
output,
|
||||
authConfig: { token },
|
||||
config,
|
||||
} = client;
|
||||
const debugEnabled = argv['--debug'];
|
||||
const { print, log, error } = output;
|
||||
const { print, log, error } = client.output;
|
||||
|
||||
// extract the first parameter
|
||||
const [, deploymentIdOrHost] = argv._;
|
||||
@@ -75,9 +70,7 @@ export default async function main(client) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const { currentTeam } = config;
|
||||
|
||||
let contextName = null;
|
||||
let contextName: string | null = null;
|
||||
|
||||
try {
|
||||
({ contextName } = await getScope(client));
|
||||
@@ -90,22 +83,14 @@ export default async function main(client) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
const now = new Now({
|
||||
apiUrl,
|
||||
token,
|
||||
debug: debugEnabled,
|
||||
currentTeam,
|
||||
output,
|
||||
});
|
||||
|
||||
// resolve the deployment, since we might have been given an alias
|
||||
const depFetchStart = Date.now();
|
||||
output.spinner(
|
||||
client.output.spinner(
|
||||
`Fetching deployment "${deploymentIdOrHost}" in ${chalk.bold(contextName)}`
|
||||
);
|
||||
|
||||
try {
|
||||
deployment = await now.findDeployment(deploymentIdOrHost);
|
||||
deployment = await getDeployment(client, deploymentIdOrHost);
|
||||
} catch (err) {
|
||||
if (err.status === 404) {
|
||||
error(
|
||||
@@ -127,11 +112,11 @@ export default async function main(client) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
const { id, name, url, created, routes, readyState } = deployment;
|
||||
const { id, name, url, createdAt, routes, readyState } = deployment;
|
||||
|
||||
const { builds } =
|
||||
deployment.version === 2
|
||||
? await now.fetch(`/v1/now/deployments/${id}/builds`)
|
||||
? await client.fetch(`/v1/now/deployments/${id}/builds`)
|
||||
: { builds: [] };
|
||||
|
||||
log(
|
||||
@@ -146,10 +131,10 @@ export default async function main(client) {
|
||||
print(` ${chalk.cyan('name')}\t${name}\n`);
|
||||
print(` ${chalk.cyan('readyState')}\t${stateString(readyState)}\n`);
|
||||
print(` ${chalk.cyan('url')}\t\t${url}\n`);
|
||||
if (created) {
|
||||
if (createdAt) {
|
||||
print(
|
||||
` ${chalk.cyan('createdAt')}\t${new Date(created)} ${elapsed(
|
||||
Date.now() - created,
|
||||
` ${chalk.cyan('createdAt')}\t${new Date(createdAt)} ${elapsed(
|
||||
Date.now() - createdAt,
|
||||
true
|
||||
)}\n`
|
||||
);
|
||||
@@ -157,7 +142,7 @@ export default async function main(client) {
|
||||
print('\n\n');
|
||||
|
||||
if (builds.length > 0) {
|
||||
const times = {};
|
||||
const times: { [id: string]: string | null } = {};
|
||||
|
||||
for (const build of builds) {
|
||||
const { id, createdAt, readyStateAt } = build;
|
||||
@@ -179,7 +164,7 @@ export default async function main(client) {
|
||||
}
|
||||
|
||||
// renders the state string
|
||||
function stateString(s) {
|
||||
function stateString(s: Deployment['readyState']) {
|
||||
switch (s) {
|
||||
case 'INITIALIZING':
|
||||
return chalk.yellow(s);
|
||||
@@ -12,7 +12,6 @@ import { getCommandName, getPkgName } from '../util/pkg-name';
|
||||
import getGlobalPathConfig from '../util/config/global-path';
|
||||
import { writeToAuthConfigFile, writeToConfigFile } from '../util/config/files';
|
||||
import Client from '../util/client';
|
||||
import { LoginParams } from '../util/login/types';
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
@@ -46,7 +45,7 @@ const help = () => {
|
||||
|
||||
export default async function login(client: Client): Promise<number> {
|
||||
let argv;
|
||||
const { apiUrl, output } = client;
|
||||
const { output } = client;
|
||||
|
||||
try {
|
||||
argv = getArgs(client.argv.slice(2));
|
||||
@@ -68,18 +67,17 @@ export default async function login(client: Client): Promise<number> {
|
||||
const input = argv._[1];
|
||||
|
||||
let result: number | string = 1;
|
||||
const params: LoginParams = { output, apiUrl };
|
||||
|
||||
if (input) {
|
||||
// Email or Team slug was provided via command line
|
||||
if (validateEmail(input)) {
|
||||
result = await doEmailLogin(input, params);
|
||||
result = await doEmailLogin(client, input);
|
||||
} else {
|
||||
result = await doSsoLogin(input, params);
|
||||
result = await doSsoLogin(client, input);
|
||||
}
|
||||
} else {
|
||||
// Interactive mode
|
||||
result = await prompt(params);
|
||||
result = await prompt(client);
|
||||
}
|
||||
|
||||
// The login function failed, so it returned an exit code
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import chalk from 'chalk';
|
||||
import fetch from 'node-fetch';
|
||||
import logo from '../util/output/logo';
|
||||
// @ts-ignore
|
||||
import { handleError } from '../util/error';
|
||||
import { writeToConfigFile, writeToAuthConfigFile } from '../util/config/files';
|
||||
import getArgs from '../util/get-args';
|
||||
@@ -48,10 +46,9 @@ export default async function main(client: Client): Promise<number> {
|
||||
return 2;
|
||||
}
|
||||
|
||||
const { authConfig, config, apiUrl, output } = client;
|
||||
const { token } = authConfig;
|
||||
const { authConfig, config, output } = client;
|
||||
|
||||
if (!token) {
|
||||
if (!authConfig.token) {
|
||||
output.note(
|
||||
`Not currently logged in, so ${getCommandName('logout')} did nothing`
|
||||
);
|
||||
@@ -59,6 +56,21 @@ export default async function main(client: Client): Promise<number> {
|
||||
}
|
||||
|
||||
output.spinner('Logging out…', 200);
|
||||
let exitCode = 0;
|
||||
|
||||
try {
|
||||
await client.fetch(`/v3/user/tokens/current`, {
|
||||
method: 'DELETE',
|
||||
useCurrentTeam: false,
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.status === 403) {
|
||||
output.debug('Token is invalid so it cannot be revoked');
|
||||
} else if (err.status !== 200) {
|
||||
output.debug(err?.message ?? '');
|
||||
exitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
delete config.currentTeam;
|
||||
|
||||
@@ -75,26 +87,15 @@ export default async function main(client: Client): Promise<number> {
|
||||
writeToAuthConfigFile(authConfig);
|
||||
output.debug('Configuration has been deleted');
|
||||
} catch (err) {
|
||||
output.error(`Couldn't remove config while logging out`);
|
||||
return 1;
|
||||
output.debug(err?.message ?? '');
|
||||
exitCode = 1;
|
||||
}
|
||||
|
||||
const res = await fetch(`${apiUrl}/v3/user/tokens/current`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (res.status === 403) {
|
||||
output.debug('Token is invalid so it cannot be revoked');
|
||||
} else if (res.status !== 200) {
|
||||
const err = await res.json();
|
||||
output.error('Failed to revoke token');
|
||||
output.debug(err ? err.message : '');
|
||||
return 1;
|
||||
if (exitCode === 0) {
|
||||
output.log('Logged out!');
|
||||
} else {
|
||||
output.error(`Failed during logout`);
|
||||
}
|
||||
|
||||
output.log('Logged out!');
|
||||
return 0;
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import chalk from 'chalk';
|
||||
import Now from '../util';
|
||||
import logo from '../util/output/logo';
|
||||
import elapsed from '../util/output/elapsed.ts';
|
||||
import elapsed from '../util/output/elapsed';
|
||||
import { maybeURL, normalizeURL } from '../util/url';
|
||||
import printEvents from '../util/events';
|
||||
import getScope from '../util/get-scope.ts';
|
||||
import { getPkgName } from '../util/pkg-name.ts';
|
||||
import getArgs from '../util/get-args.ts';
|
||||
import handleError from '../util/handle-error.ts';
|
||||
import printEvents, { DeploymentEvent } from '../util/events';
|
||||
import getScope from '../util/get-scope';
|
||||
import { getPkgName } from '../util/pkg-name';
|
||||
import getArgs from '../util/get-args';
|
||||
import Client from '../util/client';
|
||||
import { getDeployment } from '../util/get-deployment';
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
@@ -53,35 +53,25 @@ const help = () => {
|
||||
`);
|
||||
};
|
||||
|
||||
export default async function main(client) {
|
||||
let argv;
|
||||
let deploymentIdOrURL;
|
||||
|
||||
let debug;
|
||||
export default async function main(client: Client) {
|
||||
let head;
|
||||
let limit;
|
||||
let follow;
|
||||
let outputMode;
|
||||
|
||||
let since;
|
||||
let until;
|
||||
let deploymentIdOrURL;
|
||||
|
||||
try {
|
||||
argv = getArgs(client.argv.slice(2), {
|
||||
'--since': String,
|
||||
'--until': String,
|
||||
'--output': String,
|
||||
'--limit': Number,
|
||||
'--head': Boolean,
|
||||
'--follow': Boolean,
|
||||
'-f': '--follow',
|
||||
'-o': '--output',
|
||||
'-n': '--limit',
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
return 1;
|
||||
}
|
||||
const argv = getArgs(client.argv.slice(2), {
|
||||
'--since': String,
|
||||
'--until': String,
|
||||
'--output': String,
|
||||
'--limit': Number,
|
||||
'--head': Boolean,
|
||||
'--follow': Boolean,
|
||||
'-f': '--follow',
|
||||
'-o': '--output',
|
||||
'-n': '--limit',
|
||||
});
|
||||
|
||||
argv._ = argv._.slice(1);
|
||||
deploymentIdOrURL = argv._[0];
|
||||
@@ -91,12 +81,7 @@ export default async function main(client) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
const {
|
||||
authConfig: { token },
|
||||
apiUrl,
|
||||
output,
|
||||
config,
|
||||
} = client;
|
||||
const { output } = client;
|
||||
|
||||
try {
|
||||
since = argv['--since'] ? toTimestamp(argv['--since']) : 0;
|
||||
@@ -124,40 +109,24 @@ export default async function main(client) {
|
||||
deploymentIdOrURL = normalizedURL;
|
||||
}
|
||||
|
||||
debug = argv['--debug'];
|
||||
|
||||
head = argv['--head'];
|
||||
limit = argv['--limit'] || 100;
|
||||
follow = argv['--follow'];
|
||||
if (follow) until = 0;
|
||||
outputMode = argv['--output'] in logPrinters ? argv['--output'] : 'short';
|
||||
const logPrinter = getLogPrinter(argv['--output'], 'short');
|
||||
|
||||
const { currentTeam } = config;
|
||||
const now = new Now({ apiUrl, token, debug, currentTeam, output });
|
||||
let contextName = null;
|
||||
const { contextName } = await getScope(client);
|
||||
|
||||
try {
|
||||
({ contextName } = await getScope(client));
|
||||
} catch (err) {
|
||||
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
|
||||
output.error(err.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
let deployment;
|
||||
const id = deploymentIdOrURL;
|
||||
|
||||
const depFetchStart = Date.now();
|
||||
output.spinner(`Fetching deployment "${id}" in ${chalk.bold(contextName)}`);
|
||||
|
||||
let deployment;
|
||||
try {
|
||||
deployment = await now.findDeployment(id);
|
||||
deployment = await getDeployment(client, id);
|
||||
} catch (err) {
|
||||
output.stopSpinner();
|
||||
now.close();
|
||||
|
||||
if (err.status === 404) {
|
||||
output.error(
|
||||
@@ -183,31 +152,28 @@ export default async function main(client) {
|
||||
)} ${elapsed(Date.now() - depFetchStart)}`
|
||||
);
|
||||
|
||||
let direction = head ? 'forward' : 'backward';
|
||||
if (since && !until) direction = 'forward';
|
||||
const findOpts1 = {
|
||||
direction,
|
||||
limit,
|
||||
since,
|
||||
until,
|
||||
}; // no follow
|
||||
const storage = [];
|
||||
const storeEvent = event => storage.push(event);
|
||||
const storage: DeploymentEvent[] = [];
|
||||
|
||||
await printEvents(now, deployment.uid || deployment.id, currentTeam, {
|
||||
let direction = head ? ('forward' as const) : ('backward' as const);
|
||||
if (since && !until) direction = 'forward';
|
||||
|
||||
await printEvents(client, deployment.id, {
|
||||
mode: 'logs',
|
||||
onEvent: storeEvent,
|
||||
onEvent: event => storage.push(event),
|
||||
quiet: false,
|
||||
debug,
|
||||
findOpts: findOpts1,
|
||||
output,
|
||||
findOpts: {
|
||||
direction,
|
||||
limit,
|
||||
since,
|
||||
until,
|
||||
},
|
||||
});
|
||||
|
||||
const printedEventIds = new Set();
|
||||
const printEvent = event => {
|
||||
const printedEventIds = new Set<string>();
|
||||
const printEvent = (event: DeploymentEvent) => {
|
||||
if (printedEventIds.has(event.id)) return 0;
|
||||
printedEventIds.add(event.id);
|
||||
return logPrinters[outputMode](event);
|
||||
return logPrinter(event);
|
||||
};
|
||||
storage.sort(compareEvents).forEach(printEvent);
|
||||
|
||||
@@ -216,26 +182,22 @@ export default async function main(client) {
|
||||
// NOTE: the API ignores `since` on follow mode.
|
||||
// (but not sure if it's always true on legacy deployments)
|
||||
const since2 = lastEvent ? lastEvent.date : Date.now();
|
||||
const findOpts2 = {
|
||||
direction: 'forward',
|
||||
since: since2,
|
||||
follow: true,
|
||||
};
|
||||
await printEvents(now, deployment.uid || deployment.id, currentTeam, {
|
||||
await printEvents(client, deployment.id, {
|
||||
mode: 'logs',
|
||||
onEvent: printEvent,
|
||||
quiet: false,
|
||||
debug,
|
||||
findOpts: findOpts2,
|
||||
output,
|
||||
findOpts: {
|
||||
direction: 'forward',
|
||||
since: since2,
|
||||
follow: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
now.close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
function compareEvents(d1, d2) {
|
||||
function compareEvents(d1: DeploymentEvent, d2: DeploymentEvent) {
|
||||
const c1 = d1.date || d1.created;
|
||||
const c2 = d2.date || d2.created;
|
||||
if (c1 !== c2) return c1 - c2;
|
||||
@@ -246,10 +208,10 @@ function compareEvents(d1, d2) {
|
||||
return d1.created - d2.created; // if date are equal and no serial
|
||||
}
|
||||
|
||||
function printLogShort(log) {
|
||||
function printLogShort(log: any) {
|
||||
if (!log.created) return; // keepalive
|
||||
|
||||
let data;
|
||||
let data: string;
|
||||
const obj = log.object;
|
||||
if (log.type === 'request') {
|
||||
data =
|
||||
@@ -315,7 +277,7 @@ function printLogShort(log) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
function printLogRaw(log) {
|
||||
function printLogRaw(log: any) {
|
||||
if (!log.created) return; // keepalive
|
||||
|
||||
if (log.object) {
|
||||
@@ -340,7 +302,27 @@ const logPrinters = {
|
||||
raw: printLogRaw,
|
||||
};
|
||||
|
||||
function toTimestamp(datestr) {
|
||||
type OutputMode = keyof typeof logPrinters;
|
||||
|
||||
const isLogPrinter = (v: any): v is OutputMode => {
|
||||
return v && v in logPrinters;
|
||||
};
|
||||
|
||||
const getLogPrinter = (mode: string | undefined, def: OutputMode) => {
|
||||
if (mode) {
|
||||
if (isLogPrinter(mode)) {
|
||||
return logPrinters[mode];
|
||||
}
|
||||
throw new TypeError(
|
||||
`Invalid output mode "${mode}". Must be one of: ${Object.keys(
|
||||
logPrinters
|
||||
).join(', ')}`
|
||||
);
|
||||
}
|
||||
return logPrinters[def];
|
||||
};
|
||||
|
||||
function toTimestamp(datestr: string) {
|
||||
const t = Date.parse(datestr);
|
||||
if (isNaN(t)) {
|
||||
throw new TypeError('Invalid date string');
|
||||
@@ -187,25 +187,22 @@ async function run({ client, contextName }) {
|
||||
|
||||
const name = args[0];
|
||||
|
||||
// Check the existence of the project
|
||||
try {
|
||||
await client.fetch(`/projects/info/${e(name)}`);
|
||||
} catch (err) {
|
||||
if (err.status === 404) {
|
||||
console.error(error('No such project exists'));
|
||||
return exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
const yes = await readConfirmation(name);
|
||||
if (!yes) {
|
||||
console.error(error('User abort'));
|
||||
return exit(0);
|
||||
}
|
||||
|
||||
await client.fetch(`/v2/projects/${name}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
try {
|
||||
await client.fetch(`/v2/projects/${e(name)}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.status === 404) {
|
||||
console.error(error('No such project exists'));
|
||||
return exit(1);
|
||||
}
|
||||
}
|
||||
const elapsed = ms(new Date() - start);
|
||||
console.log(
|
||||
`${chalk.cyan('> Success!')} Project ${chalk.bold(
|
||||
|
||||
@@ -115,8 +115,6 @@ export default async function main(client) {
|
||||
try {
|
||||
({ contextName } = await getScope(client));
|
||||
} catch (err) {
|
||||
client.close();
|
||||
|
||||
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
|
||||
output.error(err.message);
|
||||
return 1;
|
||||
@@ -210,7 +208,6 @@ export default async function main(client) {
|
||||
.map(id => chalk.bold(`"${id}"`))
|
||||
.join(', ')}. Run ${getCommandName('ls')} to list.`
|
||||
);
|
||||
client.close();
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -233,7 +230,6 @@ export default async function main(client) {
|
||||
|
||||
if (confirmation !== 'y' && confirmation !== 'yes') {
|
||||
output.log('Aborted');
|
||||
client.close();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -265,7 +261,6 @@ export default async function main(client) {
|
||||
console.log(`${chalk.gray('-')} ${chalk.bold(project.name)}`);
|
||||
});
|
||||
|
||||
client.close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import chalk from 'chalk';
|
||||
import error from '../util/output/error';
|
||||
import NowTeams from '../util/teams';
|
||||
import logo from '../util/output/logo';
|
||||
import list from './teams/list';
|
||||
import add from './teams/add';
|
||||
import change from './teams/switch';
|
||||
import invite from './teams/invite';
|
||||
import { getPkgName } from '../util/pkg-name.ts';
|
||||
import getArgs from '../util/get-args.ts';
|
||||
import handleError from '../util/handle-error.ts';
|
||||
import error from '../../util/output/error';
|
||||
import NowTeams from '../../util/teams';
|
||||
import logo from '../../util/output/logo';
|
||||
import list from './list';
|
||||
import add from './add';
|
||||
import change from './switch';
|
||||
import invite from './invite';
|
||||
import { getPkgName } from '../../util/pkg-name';
|
||||
import getArgs from '../../util/get-args';
|
||||
import handleError from '../../util/handle-error';
|
||||
import Client from '../../util/client';
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
@@ -65,7 +66,7 @@ let debug;
|
||||
let apiUrl;
|
||||
let subcommand;
|
||||
|
||||
const main = async client => {
|
||||
export default async (client: Client) => {
|
||||
try {
|
||||
argv = getArgs(client.argv.slice(2), {
|
||||
'--since': String,
|
||||
@@ -113,7 +114,7 @@ const main = async client => {
|
||||
}
|
||||
case 'switch':
|
||||
case 'change': {
|
||||
exitCode = await change(client, argv);
|
||||
exitCode = await change(client, argv._[0]);
|
||||
break;
|
||||
}
|
||||
case 'add':
|
||||
@@ -139,13 +140,3 @@ const main = async client => {
|
||||
teams.close();
|
||||
return exitCode || 0;
|
||||
};
|
||||
|
||||
export default async client => {
|
||||
try {
|
||||
return await main(client);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
handleError(err);
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
@@ -1,190 +0,0 @@
|
||||
// Packages
|
||||
import chalk from 'chalk';
|
||||
|
||||
// Utilities
|
||||
import listInput from '../../util/input/list';
|
||||
import success from '../../util/output/success';
|
||||
import info from '../../util/output/info';
|
||||
import error from '../../util/output/error';
|
||||
import param from '../../util/output/param.ts';
|
||||
import { writeToConfigFile } from '../../util/config/files';
|
||||
import getUser from '../../util/get-user.ts';
|
||||
import NowTeams from '../../util/teams';
|
||||
|
||||
const updateCurrentTeam = (config, newTeam) => {
|
||||
if (newTeam) {
|
||||
config.currentTeam = newTeam.id;
|
||||
} else {
|
||||
delete config.currentTeam;
|
||||
}
|
||||
|
||||
writeToConfigFile(config);
|
||||
};
|
||||
|
||||
export default async function change(client, argv) {
|
||||
const {
|
||||
apiUrl,
|
||||
authConfig: { token },
|
||||
debug,
|
||||
config,
|
||||
output,
|
||||
} = client;
|
||||
output.spinner('Fetching teams');
|
||||
|
||||
// We're loading the teams here without `currentTeam`, so that
|
||||
// people can use `vercel switch` in the case that their
|
||||
// current team was deleted.
|
||||
const teams = new NowTeams({ apiUrl, token, debug, output });
|
||||
const list = (await teams.ls()).teams;
|
||||
|
||||
let { currentTeam } = config;
|
||||
const accountIsCurrent = !currentTeam;
|
||||
|
||||
output.spinner('Fetching user information');
|
||||
let user;
|
||||
try {
|
||||
user = await getUser(client);
|
||||
} catch (err) {
|
||||
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
|
||||
output.error(err.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (accountIsCurrent) {
|
||||
currentTeam = {
|
||||
slug: user.username || user.email,
|
||||
};
|
||||
} else {
|
||||
currentTeam = list.find(team => team.id === currentTeam);
|
||||
|
||||
if (!currentTeam) {
|
||||
output.error(`You are not a part of the current team anymore`);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (argv._.length !== 0) {
|
||||
const desiredSlug = argv._[0];
|
||||
const newTeam = list.find(team => team.slug === desiredSlug);
|
||||
|
||||
if (newTeam) {
|
||||
updateCurrentTeam(config, newTeam);
|
||||
console.log(
|
||||
success(
|
||||
`The team ${chalk.bold(newTeam.name)} (${
|
||||
newTeam.slug
|
||||
}) is now active!`
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (desiredSlug === user.username) {
|
||||
output.spinner('Saving');
|
||||
updateCurrentTeam(config);
|
||||
|
||||
output.stopSpinner();
|
||||
console.log(
|
||||
success(`Your account (${chalk.bold(desiredSlug)}) is now active!`)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
console.error(
|
||||
error(`Could not find membership for team ${param(desiredSlug)}`)
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const choices = list.map(({ id, slug, name }) => {
|
||||
name = `${slug} (${name})`;
|
||||
|
||||
if (id === currentTeam.id) {
|
||||
name += ` ${chalk.bold('(current)')}`;
|
||||
}
|
||||
|
||||
return {
|
||||
name,
|
||||
value: slug,
|
||||
short: slug,
|
||||
};
|
||||
});
|
||||
|
||||
const suffix = accountIsCurrent ? ` ${chalk.bold('(current)')}` : '';
|
||||
|
||||
const userEntryName = user.username
|
||||
? `${user.username} (${user.email})${suffix}`
|
||||
: user.email;
|
||||
|
||||
choices.unshift({
|
||||
name: userEntryName,
|
||||
value: user.email,
|
||||
short: user.username,
|
||||
});
|
||||
|
||||
// Let's bring the current team to the beginning of the list
|
||||
if (!accountIsCurrent) {
|
||||
const index = choices.findIndex(
|
||||
choice => choice.value === currentTeam.slug
|
||||
);
|
||||
const choice = choices.splice(index, 1)[0];
|
||||
choices.unshift(choice);
|
||||
}
|
||||
|
||||
output.stopSpinner();
|
||||
|
||||
let message;
|
||||
|
||||
if (currentTeam) {
|
||||
message = `Switch to:`;
|
||||
}
|
||||
|
||||
const choice = await listInput({
|
||||
message,
|
||||
choices,
|
||||
separator: false,
|
||||
eraseFinalAnswer: true,
|
||||
});
|
||||
|
||||
// Abort
|
||||
if (!choice) {
|
||||
console.log(info('No changes made'));
|
||||
return 0;
|
||||
}
|
||||
|
||||
const newTeam = list.find(item => item.slug === choice);
|
||||
|
||||
// Switch to account
|
||||
if (!newTeam) {
|
||||
if (currentTeam.slug === user.username || currentTeam.slug === user.email) {
|
||||
console.log(info('No changes made'));
|
||||
return 0;
|
||||
}
|
||||
|
||||
output.spinner('Saving');
|
||||
updateCurrentTeam(config);
|
||||
|
||||
output.stopSpinner();
|
||||
console.log(success(`Your account (${chalk.bold(choice)}) is now active!`));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (newTeam.slug === currentTeam.slug) {
|
||||
console.log(info('No changes made'));
|
||||
return 0;
|
||||
}
|
||||
|
||||
output.spinner('Saving');
|
||||
updateCurrentTeam(config, newTeam);
|
||||
|
||||
output.stopSpinner();
|
||||
console.log(
|
||||
success(
|
||||
`The team ${chalk.bold(newTeam.name)} (${newTeam.slug}) is now active!`
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
151
packages/cli/src/commands/teams/switch.ts
Normal file
151
packages/cli/src/commands/teams/switch.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
// Packages
|
||||
import chalk from 'chalk';
|
||||
|
||||
// Utilities
|
||||
import Client from '../../util/client';
|
||||
import { emoji } from '../../util/emoji';
|
||||
import getUser from '../../util/get-user';
|
||||
import getTeams from '../../util/get-teams';
|
||||
import listInput from '../../util/input/list';
|
||||
import { Team, GlobalConfig } from '../../types';
|
||||
import { writeToConfigFile } from '../../util/config/files';
|
||||
|
||||
const updateCurrentTeam = (config: GlobalConfig, team?: Team) => {
|
||||
if (team) {
|
||||
config.currentTeam = team.id;
|
||||
} else {
|
||||
delete config.currentTeam;
|
||||
}
|
||||
|
||||
writeToConfigFile(config);
|
||||
};
|
||||
|
||||
export default async function main(client: Client, desiredSlug?: string) {
|
||||
const { config, output } = client;
|
||||
const personalScopeSelected = !config.currentTeam;
|
||||
|
||||
output.spinner('Fetching teams information');
|
||||
const [user, teams] = await Promise.all([getUser(client), getTeams(client)]);
|
||||
|
||||
const currentTeam = personalScopeSelected
|
||||
? undefined
|
||||
: teams.find(team => team.id === config.currentTeam);
|
||||
|
||||
if (!personalScopeSelected && !currentTeam) {
|
||||
output.error(`You are not a member of the current team anymore.`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!desiredSlug) {
|
||||
const teamChoices = teams
|
||||
.slice(0)
|
||||
.sort((a, b) => {
|
||||
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
|
||||
})
|
||||
.map(team => {
|
||||
let title = `${team.name} (${team.slug})`;
|
||||
const selected = team.id === currentTeam?.id;
|
||||
|
||||
if (selected) {
|
||||
title += ` ${chalk.bold('(current)')}`;
|
||||
}
|
||||
|
||||
if (team.limited) {
|
||||
title += ` ${emoji('locked')}`;
|
||||
}
|
||||
|
||||
return {
|
||||
name: title,
|
||||
value: team.slug,
|
||||
short: team.slug,
|
||||
selected,
|
||||
};
|
||||
});
|
||||
|
||||
// Add the User scope entry at the top
|
||||
let suffix = personalScopeSelected ? ` ${chalk.bold('(current)')}` : '';
|
||||
|
||||
// SAML tokens can not interact with the user scope
|
||||
if (user.limited) {
|
||||
suffix += ` ${emoji('locked')}`;
|
||||
}
|
||||
|
||||
const choices = [
|
||||
{ separator: 'Personal Account' },
|
||||
{
|
||||
name: `${user.name || user.email} (${user.username})${suffix}`,
|
||||
value: user.username,
|
||||
short: user.username,
|
||||
selected: personalScopeSelected,
|
||||
},
|
||||
{ separator: 'Teams' },
|
||||
...teamChoices,
|
||||
];
|
||||
|
||||
output.stopSpinner();
|
||||
desiredSlug = await listInput({
|
||||
message: 'Switch to:',
|
||||
choices,
|
||||
eraseFinalAnswer: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Abort
|
||||
if (!desiredSlug) {
|
||||
output.log('No changes made.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (desiredSlug === user.username || desiredSlug === user.email) {
|
||||
// Switch to user's personal account
|
||||
if (personalScopeSelected) {
|
||||
output.log('No changes made');
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (user.limited) {
|
||||
await client.reauthenticate({
|
||||
scope: user.username,
|
||||
teamId: null,
|
||||
});
|
||||
}
|
||||
|
||||
updateCurrentTeam(config);
|
||||
|
||||
output.success(
|
||||
`Your account (${chalk.bold(user.username)}) is now active!`
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Switch to selected team
|
||||
const newTeam = teams.find(team => team.slug === desiredSlug);
|
||||
|
||||
if (!newTeam) {
|
||||
output.error(
|
||||
`You do not have permission to access scope ${chalk.bold(desiredSlug)}.`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (newTeam.slug === currentTeam?.slug) {
|
||||
output.log('No changes made');
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (newTeam.limited) {
|
||||
const samlEnabled = newTeam.saml?.connection?.state === 'active';
|
||||
await client.reauthenticate({
|
||||
teamId: samlEnabled ? newTeam.id : null,
|
||||
scope: newTeam.slug,
|
||||
enforced: samlEnabled && newTeam.saml?.enforced === true,
|
||||
});
|
||||
}
|
||||
|
||||
updateCurrentTeam(config, newTeam);
|
||||
|
||||
output.success(
|
||||
`The team ${chalk.bold(newTeam.name)} (${newTeam.slug}) is now active!`
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
@@ -587,10 +587,10 @@ const main = async () => {
|
||||
const eventCategory = 'Exit Code';
|
||||
|
||||
try {
|
||||
const start = new Date();
|
||||
const start = Date.now();
|
||||
const full = require(`./commands/${targetCommand}`).default;
|
||||
exitCode = await full(client);
|
||||
const end = new Date() - start;
|
||||
const end = Date.now() - start;
|
||||
|
||||
if (shouldCollectMetrics) {
|
||||
const category = 'Command Invocation';
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
export type Primitive =
|
||||
| bigint
|
||||
| boolean
|
||||
| null
|
||||
| number
|
||||
| string
|
||||
| symbol
|
||||
| undefined;
|
||||
|
||||
export type JSONArray = JSONValue[];
|
||||
|
||||
export type JSONValue = Primitive | JSONObject | JSONArray;
|
||||
|
||||
export interface JSONObject {
|
||||
[key: string]: JSONValue;
|
||||
}
|
||||
|
||||
export interface AuthConfig {
|
||||
token: string;
|
||||
token?: string;
|
||||
skipWrite?: boolean;
|
||||
}
|
||||
|
||||
@@ -45,18 +62,26 @@ export type User = {
|
||||
updatedAt: number;
|
||||
};
|
||||
name?: string;
|
||||
limited?: boolean;
|
||||
};
|
||||
|
||||
export type Team = {
|
||||
export interface Team {
|
||||
id: string;
|
||||
avatar?: string;
|
||||
avatar?: string | null;
|
||||
billing: Billing;
|
||||
created: string;
|
||||
creatorId: string;
|
||||
membership: { uid: string; role: 'MEMBER' | 'OWNER'; created: number };
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
limited?: boolean;
|
||||
saml?: {
|
||||
enforced: boolean;
|
||||
connection?: {
|
||||
state: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export type Domain = {
|
||||
id: string;
|
||||
@@ -120,7 +145,7 @@ export type Deployment = {
|
||||
export type Alias = {
|
||||
uid: string;
|
||||
alias: string;
|
||||
created: string;
|
||||
createdAt: number;
|
||||
deployment: {
|
||||
id: string;
|
||||
url: string;
|
||||
@@ -268,3 +293,13 @@ export type ProjectLinkResult =
|
||||
| { status: 'linked'; org: Org; project: Project }
|
||||
| { status: 'not_linked'; org: null; project: null }
|
||||
| { status: 'error'; exitCode: number };
|
||||
|
||||
export interface Token {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
origin?: string;
|
||||
activeAt: number;
|
||||
createdAt: number;
|
||||
teamId?: string;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Output } from '../output';
|
||||
import { Alias } from '../../types';
|
||||
|
||||
import Client from '../client';
|
||||
|
||||
export default async function findAliasByAliasOrId(
|
||||
@@ -11,7 +12,6 @@ export default async function findAliasByAliasOrId(
|
||||
`/now/aliases/${encodeURIComponent(getSafeAlias(aliasOrId))}`
|
||||
);
|
||||
}
|
||||
|
||||
function getSafeAlias(alias: string) {
|
||||
return alias
|
||||
.replace(/^https:\/\//i, '')
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Alias } from '../../types';
|
||||
import { Alias, PaginationOptions } from '../../types';
|
||||
import Client from '../client';
|
||||
|
||||
type Response = {
|
||||
aliases: Alias[];
|
||||
pagination: PaginationOptions;
|
||||
};
|
||||
|
||||
export default async function getAliases(
|
||||
|
||||
@@ -3,7 +3,7 @@ import chalk from 'chalk';
|
||||
import Client from '../client';
|
||||
import { Output } from '../output';
|
||||
import { User } from '../../types';
|
||||
import { NowConfig } from '../dev/types';
|
||||
import { VercelConfig } from '../dev/types';
|
||||
import getDeploymentsByAppName from '../deploy/get-deployments-by-appname';
|
||||
import getDeploymentByIdOrHost from '../deploy/get-deployment-by-id-or-host';
|
||||
|
||||
@@ -35,7 +35,7 @@ export async function getDeploymentForAlias(
|
||||
localConfigPath: string | undefined,
|
||||
user: User,
|
||||
contextName: string,
|
||||
localConfig: NowConfig
|
||||
localConfig: VercelConfig
|
||||
) {
|
||||
output.spinner(`Fetching deployment to alias in ${chalk.bold(contextName)}`);
|
||||
|
||||
|
||||
@@ -2,6 +2,6 @@ import Client from '../client';
|
||||
|
||||
export default async function removeAliasById(client: Client, id: string) {
|
||||
return client.fetch(`/now/aliases/${id}`, {
|
||||
method: 'DELETE'
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,22 +1,28 @@
|
||||
import { URLSearchParams } from 'url';
|
||||
import { EventEmitter } from 'events';
|
||||
import { URLSearchParams } from 'url';
|
||||
import { parse as parseUrl } from 'url';
|
||||
import fetch, { RequestInit } from 'node-fetch';
|
||||
import { VercelConfig } from '@vercel/client';
|
||||
import retry, { RetryFunction, Options as RetryOptions } from 'async-retry';
|
||||
import fetch, { BodyInit, Headers, RequestInit, Response } from 'node-fetch';
|
||||
import ua from './ua';
|
||||
import { Output } from './output/create-output';
|
||||
import responseError from './response-error';
|
||||
import ua from './ua';
|
||||
import printIndications from './print-indications';
|
||||
import { AuthConfig, GlobalConfig } from '../types';
|
||||
import { NowConfig } from './dev/types';
|
||||
import doSsoLogin from './login/sso';
|
||||
import reauthenticate from './login/reauthenticate';
|
||||
import { SAMLError } from './login/types';
|
||||
import { writeToAuthConfigFile } from './config/files';
|
||||
import { AuthConfig, GlobalConfig, JSONObject } from '../types';
|
||||
import { sharedPromise } from './promise';
|
||||
import { APIError } from './errors-ts';
|
||||
import { bold } from 'chalk';
|
||||
|
||||
export interface FetchOptions {
|
||||
body?: NodeJS.ReadableStream | object | string;
|
||||
headers?: { [key: string]: string };
|
||||
const isSAMLError = (v: any): v is SAMLError => {
|
||||
return v && v.saml;
|
||||
};
|
||||
|
||||
export interface FetchOptions extends Omit<RequestInit, 'body'> {
|
||||
body?: BodyInit | JSONObject;
|
||||
json?: boolean;
|
||||
method?: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
|
||||
retry?: RetryOptions;
|
||||
useCurrentTeam?: boolean;
|
||||
accountId?: string;
|
||||
@@ -28,16 +34,20 @@ export interface ClientOptions {
|
||||
authConfig: AuthConfig;
|
||||
output: Output;
|
||||
config: GlobalConfig;
|
||||
localConfig: NowConfig;
|
||||
localConfig: VercelConfig;
|
||||
}
|
||||
|
||||
const isJSONObject = (v: any): v is JSONObject => {
|
||||
return v && typeof v == 'object' && v.constructor === Object;
|
||||
};
|
||||
|
||||
export default class Client extends EventEmitter {
|
||||
argv: string[];
|
||||
apiUrl: string;
|
||||
authConfig: AuthConfig;
|
||||
output: Output;
|
||||
config: GlobalConfig;
|
||||
localConfig: NowConfig;
|
||||
localConfig: VercelConfig;
|
||||
|
||||
constructor(opts: ClientOptions) {
|
||||
super();
|
||||
@@ -47,7 +57,6 @@ export default class Client extends EventEmitter {
|
||||
this.output = opts.output;
|
||||
this.config = opts.config;
|
||||
this.localConfig = opts.localConfig;
|
||||
this._onRetry = this._onRetry.bind(this);
|
||||
}
|
||||
|
||||
retry<T>(fn: RetryFunction<T>, { retries = 3, maxTimeout = Infinity } = {}) {
|
||||
@@ -78,51 +87,43 @@ export default class Client extends EventEmitter {
|
||||
}
|
||||
|
||||
_url = `${apiUrl}${parsedUrl.pathname}?${query}`;
|
||||
|
||||
delete opts.useCurrentTeam;
|
||||
delete opts.accountId;
|
||||
}
|
||||
|
||||
if (opts.json !== false && opts.body && typeof opts.body === 'object') {
|
||||
Object.assign(opts, {
|
||||
body: JSON.stringify(opts.body),
|
||||
headers: Object.assign({}, opts.headers, {
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
});
|
||||
}
|
||||
const headers = new Headers(opts.headers);
|
||||
headers.set('authorization', `Bearer ${this.authConfig.token}`);
|
||||
headers.set('user-agent', ua);
|
||||
|
||||
opts.headers = opts.headers || {};
|
||||
opts.headers.Authorization = `Bearer ${this.authConfig.token}`;
|
||||
opts.headers['user-agent'] = ua;
|
||||
let body;
|
||||
if (isJSONObject(opts.body)) {
|
||||
body = JSON.stringify(opts.body);
|
||||
headers.set('content-type', 'application/json; charset=utf8');
|
||||
} else {
|
||||
body = opts.body;
|
||||
}
|
||||
|
||||
const url = `${apiUrl ? '' : this.apiUrl}${_url}`;
|
||||
return this.output.time(
|
||||
`${opts.method || 'GET'} ${url} ${JSON.stringify(opts.body) || ''}`,
|
||||
fetch(url, opts as RequestInit)
|
||||
fetch(url, { ...opts, headers, body })
|
||||
);
|
||||
}
|
||||
|
||||
async fetch<T>(url: string, opts: FetchOptions = {}): Promise<T> {
|
||||
fetch(url: string, opts: { json: false }): Promise<Response>;
|
||||
fetch<T>(url: string, opts?: FetchOptions): Promise<T>;
|
||||
fetch(url: string, opts: FetchOptions = {}) {
|
||||
return this.retry(async bail => {
|
||||
const res = await this._fetch(url, opts);
|
||||
|
||||
printIndications(res);
|
||||
|
||||
if (!res.ok) {
|
||||
const error = await responseError(res);
|
||||
|
||||
if (error.saml && error.teamId) {
|
||||
// If a SAML error is encountered then we re-trigger the SAML
|
||||
// authentication flow for the team specified in the error.
|
||||
const result = await doSsoLogin(error.teamId, this);
|
||||
|
||||
if (typeof result === 'number') {
|
||||
this.output.prettyError(error);
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
this.authConfig.token = result;
|
||||
writeToAuthConfigFile(this.authConfig);
|
||||
if (isSAMLError(error)) {
|
||||
// A SAML error means the token is expired, or is not
|
||||
// designated for the requested team, so the user needs
|
||||
// to re-authenticate
|
||||
await this.reauthenticate(error);
|
||||
} else if (res.status >= 400 && res.status < 500) {
|
||||
// Any other 4xx should bail without retrying
|
||||
return bail(error);
|
||||
@@ -136,21 +137,37 @@ export default class Client extends EventEmitter {
|
||||
return res;
|
||||
}
|
||||
|
||||
if (!res.headers.get('content-type')) {
|
||||
const contentType = res.headers.get('content-type');
|
||||
if (!contentType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
printIndications(res);
|
||||
|
||||
return res.headers.get('content-type').includes('application/json')
|
||||
? res.json()
|
||||
: res;
|
||||
return contentType.includes('application/json') ? res.json() : res;
|
||||
}, opts.retry);
|
||||
}
|
||||
|
||||
_onRetry(error: Error) {
|
||||
this.output.debug(`Retrying: ${error}\n${error.stack}`);
|
||||
}
|
||||
reauthenticate = sharedPromise(async function (
|
||||
this: Client,
|
||||
error: SAMLError
|
||||
) {
|
||||
const result = await reauthenticate(this, error);
|
||||
|
||||
close() {}
|
||||
if (typeof result === 'number') {
|
||||
if (error instanceof APIError) {
|
||||
this.output.prettyError(error);
|
||||
} else {
|
||||
this.output.error(
|
||||
`Failed to re-authenticate for ${bold(error.scope)} scope`
|
||||
);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
this.authConfig.token = result;
|
||||
writeToAuthConfigFile(this.authConfig);
|
||||
});
|
||||
|
||||
_onRetry = (error: Error) => {
|
||||
this.output.debug(`Retrying: ${error}\n${error.stack}`);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import getLocalPathConfig from './local-path';
|
||||
import { NowError } from '../now-error';
|
||||
import error from '../output/error';
|
||||
import highlight from '../output/highlight';
|
||||
import { NowConfig } from '../dev/types';
|
||||
import { VercelConfig } from '../dev/types';
|
||||
import { AuthConfig, GlobalConfig } from '../../types';
|
||||
|
||||
const VERCEL_DIR = getGlobalPathConfig();
|
||||
@@ -100,8 +100,8 @@ export function getAuthConfigFilePath() {
|
||||
|
||||
export function readLocalConfig(
|
||||
prefix: string = process.cwd()
|
||||
): NowConfig | null {
|
||||
let config: NowConfig | null = null;
|
||||
): VercelConfig | null {
|
||||
let config: VercelConfig | null = null;
|
||||
let target = '';
|
||||
|
||||
try {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { join } from 'path';
|
||||
import { CantParseJSONFile } from '../errors-ts';
|
||||
import readJSONFile from '../read-json-file';
|
||||
import { NowConfig } from '../dev/types';
|
||||
import { VercelConfig } from '../dev/types';
|
||||
import getLocalConfigPath from './local-path';
|
||||
|
||||
export default async function readConfig(dir: string) {
|
||||
@@ -13,7 +13,7 @@ export default async function readConfig(dir: string) {
|
||||
}
|
||||
|
||||
if (result) {
|
||||
return result as NowConfig;
|
||||
return result as VercelConfig;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -39,7 +39,9 @@ export default async function getDeploymentsByProjectId(
|
||||
query.set('from', options.from.toString());
|
||||
}
|
||||
|
||||
const { deployments } = await client.fetch<Response>(`/v4/now/deployments?${query}`);
|
||||
const { deployments } = await client.fetch<Response>(
|
||||
`/v4/now/deployments?${query}`
|
||||
);
|
||||
total += deployments.length;
|
||||
|
||||
if (options.max && total >= options.max) {
|
||||
@@ -49,15 +51,15 @@ export default async function getDeploymentsByProjectId(
|
||||
if (options.continue && deployments.length === limit) {
|
||||
const nextFrom = deployments[deployments.length - 1].created;
|
||||
const nextOptions = Object.assign({}, options, { from: nextFrom });
|
||||
deployments.push(...(await getDeploymentsByProjectId(client, projectId, nextOptions, total)));
|
||||
deployments.push(
|
||||
...(await getDeploymentsByProjectId(
|
||||
client,
|
||||
projectId,
|
||||
nextOptions,
|
||||
total
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
return deployments;
|
||||
}
|
||||
|
||||
export async function getAllDeploymentsByProjectId(
|
||||
client: Client,
|
||||
projectId: string
|
||||
) {
|
||||
return getDeploymentsByProjectId(client, projectId, { from: null, limit: 100, continue: true });
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import { Output } from '../output';
|
||||
// @ts-ignore
|
||||
import Now from '../../util';
|
||||
import { NowConfig } from '../dev/types';
|
||||
import { VercelConfig } from '../dev/types';
|
||||
import { Org } from '../../types';
|
||||
import ua from '../ua';
|
||||
import { linkFolderToProject } from '../projects/link';
|
||||
@@ -43,7 +43,7 @@ export default async function processDeployment({
|
||||
uploadStamp: () => string;
|
||||
deployStamp: () => string;
|
||||
quiet: boolean;
|
||||
nowConfig?: NowConfig;
|
||||
nowConfig?: VercelConfig;
|
||||
force?: boolean;
|
||||
withCache?: boolean;
|
||||
org: Org;
|
||||
|
||||
@@ -27,7 +27,7 @@ import { LambdaSizeExceededError } from '../errors-ts';
|
||||
import DevServer from './server';
|
||||
import { getBuilder } from './builder-cache';
|
||||
import {
|
||||
NowConfig,
|
||||
VercelConfig,
|
||||
BuildMatch,
|
||||
BuildResult,
|
||||
BuilderInputs,
|
||||
@@ -96,7 +96,7 @@ async function createBuildProcess(
|
||||
}
|
||||
|
||||
export async function executeBuild(
|
||||
nowConfig: NowConfig,
|
||||
nowConfig: VercelConfig,
|
||||
devServer: DevServer,
|
||||
files: BuilderInputs,
|
||||
match: BuildMatch,
|
||||
@@ -383,7 +383,7 @@ export async function executeBuild(
|
||||
}
|
||||
|
||||
export async function getBuildMatches(
|
||||
nowConfig: NowConfig,
|
||||
nowConfig: VercelConfig,
|
||||
cwd: string,
|
||||
output: Output,
|
||||
devServer: DevServer,
|
||||
|
||||
@@ -4,7 +4,7 @@ import PCRE from 'pcre-to-regexp';
|
||||
import isURL from './is-url';
|
||||
import DevServer from './server';
|
||||
|
||||
import { NowConfig, HttpHeadersConfig, RouteResult } from './types';
|
||||
import { VercelConfig, HttpHeadersConfig, RouteResult } from './types';
|
||||
import { isHandler, Route, HandleValue } from '@vercel/routing-utils';
|
||||
|
||||
export function resolveRouteParameters(
|
||||
@@ -50,7 +50,7 @@ export async function devRouter(
|
||||
reqMethod?: string,
|
||||
routes?: Route[],
|
||||
devServer?: DevServer,
|
||||
nowConfig?: NowConfig,
|
||||
nowConfig?: VercelConfig,
|
||||
previousHeaders?: HttpHeadersConfig,
|
||||
missRoutes?: Route[],
|
||||
phase?: HandleValue | null
|
||||
|
||||
@@ -49,7 +49,7 @@ import sleep from '../sleep';
|
||||
import { Output } from '../output';
|
||||
import { relative } from '../path-helpers';
|
||||
import { getDistTag } from '../get-dist-tag';
|
||||
import getNowConfigPath from '../config/local-path';
|
||||
import getVercelConfigPath from '../config/local-path';
|
||||
import { MissingDotenvVarsError } from '../errors-ts';
|
||||
import cliPkg from '../pkg';
|
||||
import { getVercelDirectory } from '../projects/link';
|
||||
@@ -73,7 +73,7 @@ import errorTemplate502 from './templates/error_502';
|
||||
import redirectTemplate from './templates/redirect';
|
||||
|
||||
import {
|
||||
NowConfig,
|
||||
VercelConfig,
|
||||
DevServerOptions,
|
||||
BuildMatch,
|
||||
BuildResult,
|
||||
@@ -145,7 +145,8 @@ export default class DevServer {
|
||||
private devServerPids: Set<number>;
|
||||
private projectSettings?: ProjectSettings;
|
||||
|
||||
private getNowConfigPromise: Promise<NowConfig> | null;
|
||||
private vercelConfigWarning: boolean;
|
||||
private getVercelConfigPromise: Promise<VercelConfig> | null;
|
||||
private blockingBuildsPromise: Promise<void> | null;
|
||||
private updateBuildersPromise: Promise<void> | null;
|
||||
private updateBuildersTimeout: NodeJS.Timeout | undefined;
|
||||
@@ -181,7 +182,8 @@ export default class DevServer {
|
||||
this.inProgressBuilds = new Map();
|
||||
this.devCacheDir = join(getVercelDirectory(cwd), 'cache');
|
||||
|
||||
this.getNowConfigPromise = null;
|
||||
this.vercelConfigWarning = false;
|
||||
this.getVercelConfigPromise = null;
|
||||
this.blockingBuildsPromise = null;
|
||||
this.updateBuildersPromise = null;
|
||||
this.startPromise = null;
|
||||
@@ -244,7 +246,7 @@ export default class DevServer {
|
||||
}
|
||||
}
|
||||
|
||||
const nowConfig = await this.getNowConfig();
|
||||
const nowConfig = await this.getVercelConfig();
|
||||
|
||||
// Update the build matches in case an entrypoint was created or deleted
|
||||
await this.updateBuildMatches(nowConfig);
|
||||
@@ -375,7 +377,7 @@ export default class DevServer {
|
||||
}
|
||||
|
||||
async updateBuildMatches(
|
||||
nowConfig: NowConfig,
|
||||
nowConfig: VercelConfig,
|
||||
isInitial = false
|
||||
): Promise<void> {
|
||||
const fileList = this.resolveBuildFiles(this.files);
|
||||
@@ -460,7 +462,7 @@ export default class DevServer {
|
||||
}
|
||||
|
||||
async invalidateBuildMatches(
|
||||
nowConfig: NowConfig,
|
||||
nowConfig: VercelConfig,
|
||||
updatedBuilders: string[]
|
||||
): Promise<void> {
|
||||
if (updatedBuilders.length === 0) {
|
||||
@@ -516,25 +518,25 @@ export default class DevServer {
|
||||
return {};
|
||||
}
|
||||
|
||||
clearNowConfigPromise = () => {
|
||||
this.getNowConfigPromise = null;
|
||||
clearVercelConfigPromise = () => {
|
||||
this.getVercelConfigPromise = null;
|
||||
};
|
||||
|
||||
getNowConfig(): Promise<NowConfig> {
|
||||
if (this.getNowConfigPromise) {
|
||||
return this.getNowConfigPromise;
|
||||
getVercelConfig(): Promise<VercelConfig> {
|
||||
if (this.getVercelConfigPromise) {
|
||||
return this.getVercelConfigPromise;
|
||||
}
|
||||
this.getNowConfigPromise = this._getNowConfig();
|
||||
this.getVercelConfigPromise = this._getVercelConfig();
|
||||
|
||||
// Clean up the promise once it has resolved
|
||||
const clear = this.clearNowConfigPromise;
|
||||
this.getNowConfigPromise.finally(clear);
|
||||
const clear = this.clearVercelConfigPromise;
|
||||
this.getVercelConfigPromise.finally(clear);
|
||||
|
||||
return this.getNowConfigPromise;
|
||||
return this.getVercelConfigPromise;
|
||||
}
|
||||
|
||||
async _getNowConfig(): Promise<NowConfig> {
|
||||
const configPath = getNowConfigPath(this.cwd);
|
||||
async _getVercelConfig(): Promise<VercelConfig> {
|
||||
const configPath = getVercelConfigPath(this.cwd);
|
||||
|
||||
const [
|
||||
pkg = null,
|
||||
@@ -543,10 +545,10 @@ export default class DevServer {
|
||||
config = { version: 2, [fileNameSymbol]: 'vercel.json' },
|
||||
] = await Promise.all([
|
||||
this.readJsonFile<PackageJson>('package.json'),
|
||||
this.readJsonFile<NowConfig>(configPath),
|
||||
this.readJsonFile<VercelConfig>(configPath),
|
||||
]);
|
||||
|
||||
await this.validateNowConfig(config);
|
||||
await this.validateVercelConfig(config);
|
||||
const { error: routeError, routes: maybeRoutes } = getTransformedRoutes({
|
||||
nowConfig: config,
|
||||
});
|
||||
@@ -634,7 +636,21 @@ export default class DevServer {
|
||||
config.builds.sort(sortBuilders);
|
||||
}
|
||||
|
||||
await this.validateNowConfig(config);
|
||||
await this.validateVercelConfig(config);
|
||||
|
||||
// TODO: temporarily strip and warn since `has` is not implemented yet
|
||||
config.routes = (config.routes || []).filter(route => {
|
||||
if ('has' in route) {
|
||||
if (!this.vercelConfigWarning) {
|
||||
this.vercelConfigWarning = true;
|
||||
this.output.warn(
|
||||
`The "has" property in ${config[fileNameSymbol]} will be ignored during development. Deployments will work as expected.`
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
this.caseSensitive = hasNewRoutingProperties(config);
|
||||
this.apiDir = detectApiDirectory(config.builds || []);
|
||||
@@ -710,8 +726,8 @@ export default class DevServer {
|
||||
}
|
||||
|
||||
async tryValidateOrExit(
|
||||
config: NowConfig,
|
||||
validate: (c: NowConfig) => string | null
|
||||
config: VercelConfig,
|
||||
validate: (c: VercelConfig) => string | null
|
||||
): Promise<void> {
|
||||
const message = validate(config);
|
||||
|
||||
@@ -721,7 +737,7 @@ export default class DevServer {
|
||||
}
|
||||
}
|
||||
|
||||
async validateNowConfig(config: NowConfig): Promise<void> {
|
||||
async validateVercelConfig(config: VercelConfig): Promise<void> {
|
||||
if (config.version === 1) {
|
||||
this.output.error('Cannot run `version: 1` projects.');
|
||||
await this.exit(1);
|
||||
@@ -855,7 +871,7 @@ export default class DevServer {
|
||||
.replace('[::]', 'localhost')
|
||||
.replace('127.0.0.1', 'localhost');
|
||||
|
||||
const nowConfig = await this.getNowConfig();
|
||||
const nowConfig = await this.getVercelConfig();
|
||||
const devCommandPromise = this.runDevCommand();
|
||||
|
||||
const files = await getFiles(this.cwd, { output: this.output });
|
||||
@@ -982,7 +998,7 @@ export default class DevServer {
|
||||
|
||||
if (devProcess) {
|
||||
ops.push(
|
||||
new Promise((resolve, reject) => {
|
||||
new Promise<void>((resolve, reject) => {
|
||||
devProcess.once('exit', () => resolve());
|
||||
try {
|
||||
process.kill(devProcess.pid);
|
||||
@@ -1201,7 +1217,7 @@ export default class DevServer {
|
||||
match: BuildMatch,
|
||||
requestPath: string | null,
|
||||
req: http.IncomingMessage | null,
|
||||
nowConfig: NowConfig,
|
||||
nowConfig: VercelConfig,
|
||||
previousBuildResult?: BuildResult,
|
||||
filesChanged?: string[],
|
||||
filesRemoved?: string[]
|
||||
@@ -1290,7 +1306,7 @@ export default class DevServer {
|
||||
this.output.debug(`${chalk.bold(method)} ${req.url}`);
|
||||
|
||||
try {
|
||||
const nowConfig = await this.getNowConfig();
|
||||
const nowConfig = await this.getVercelConfig();
|
||||
await this.serveProjectAsNowV2(req, res, nowRequestId, nowConfig);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@@ -1339,7 +1355,7 @@ export default class DevServer {
|
||||
req: http.IncomingMessage,
|
||||
res: http.ServerResponse,
|
||||
nowRequestId: string,
|
||||
nowConfig: NowConfig,
|
||||
nowConfig: VercelConfig,
|
||||
routes: Route[] | undefined = nowConfig.routes,
|
||||
callLevel: number = 0
|
||||
) => {
|
||||
@@ -1992,7 +2008,7 @@ export default class DevServer {
|
||||
return true;
|
||||
}
|
||||
|
||||
async hasFilesystem(dest: string, nowConfig: NowConfig): Promise<boolean> {
|
||||
async hasFilesystem(dest: string, nowConfig: VercelConfig): Promise<boolean> {
|
||||
if (
|
||||
await findBuildMatch(
|
||||
this.buildMatches,
|
||||
@@ -2181,7 +2197,7 @@ async function findBuildMatch(
|
||||
files: BuilderInputs,
|
||||
requestPath: string,
|
||||
devServer: DevServer,
|
||||
nowConfig: NowConfig,
|
||||
nowConfig: VercelConfig,
|
||||
isFilesystem = false
|
||||
): Promise<BuildMatch | null> {
|
||||
requestPath = requestPath.replace(/^\//, '');
|
||||
@@ -2219,7 +2235,7 @@ async function shouldServe(
|
||||
files: BuilderInputs,
|
||||
requestPath: string,
|
||||
devServer: DevServer,
|
||||
nowConfig: NowConfig,
|
||||
nowConfig: VercelConfig,
|
||||
isFilesystem = false
|
||||
): Promise<boolean> {
|
||||
const {
|
||||
@@ -2284,7 +2300,7 @@ async function findMatchingRoute(
|
||||
match: BuildMatch,
|
||||
requestPath: string,
|
||||
devServer: DevServer,
|
||||
nowConfig: NowConfig
|
||||
nowConfig: VercelConfig
|
||||
): Promise<RouteResult | void> {
|
||||
const reqUrl = `/${requestPath}`;
|
||||
for (const buildResult of match.buildResults.values()) {
|
||||
@@ -2305,7 +2321,7 @@ async function findMatchingRoute(
|
||||
function findAsset(
|
||||
match: BuildMatch,
|
||||
requestPath: string,
|
||||
nowConfig: NowConfig
|
||||
nowConfig: VercelConfig
|
||||
): { asset: BuilderOutput; assetKey: string } | void {
|
||||
if (!match.buildOutput) {
|
||||
return;
|
||||
@@ -2397,7 +2413,7 @@ function filterFrontendBuilds(build: Builder) {
|
||||
return !frontendRuntimeSet.has(name || '');
|
||||
}
|
||||
|
||||
function hasNewRoutingProperties(nowConfig: NowConfig) {
|
||||
function hasNewRoutingProperties(nowConfig: VercelConfig) {
|
||||
return (
|
||||
typeof nowConfig.cleanUrls !== undefined ||
|
||||
typeof nowConfig.headers !== undefined ||
|
||||
|
||||
@@ -14,12 +14,12 @@ import {
|
||||
Lambda,
|
||||
PackageJson,
|
||||
} from '@vercel/build-utils';
|
||||
import { NowConfig } from '@vercel/client';
|
||||
import { VercelConfig } from '@vercel/client';
|
||||
import { HandleValue, Route } from '@vercel/routing-utils';
|
||||
import { Output } from '../output';
|
||||
import { ProjectEnvVariable, ProjectSettings } from '../../types';
|
||||
|
||||
export { NowConfig };
|
||||
export { VercelConfig };
|
||||
|
||||
export interface DevServerOptions {
|
||||
output: Output;
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
rewritesSchema,
|
||||
trailingSlashSchema,
|
||||
} from '@vercel/routing-utils';
|
||||
import { NowConfig } from './types';
|
||||
import { VercelConfig } from './types';
|
||||
import {
|
||||
functionsSchema,
|
||||
buildsSchema,
|
||||
@@ -36,7 +36,7 @@ const vercelConfigSchema = {
|
||||
const ajv = new Ajv();
|
||||
const validate = ajv.compile(vercelConfigSchema);
|
||||
|
||||
export function validateConfig(config: NowConfig): NowBuildError | null {
|
||||
export function validateConfig(config: VercelConfig): NowBuildError | null {
|
||||
if (!validate(config)) {
|
||||
if (validate.errors && validate.errors[0]) {
|
||||
const error = validate.errors[0];
|
||||
|
||||
@@ -1,28 +1,17 @@
|
||||
export type EmojiLabel =
|
||||
| 'notice'
|
||||
| 'tip'
|
||||
| 'warning'
|
||||
| 'link'
|
||||
| 'inspect'
|
||||
| 'success';
|
||||
export const emojiLabels = {
|
||||
notice: '📝',
|
||||
tip: '💡',
|
||||
warning: '❗️',
|
||||
link: '🔗',
|
||||
inspect: '🔍',
|
||||
success: '✅',
|
||||
locked: '🔒',
|
||||
} as const;
|
||||
|
||||
export function emoji(label: EmojiLabel): string | undefined {
|
||||
switch (label) {
|
||||
case 'notice':
|
||||
return '📝';
|
||||
case 'tip':
|
||||
return '💡';
|
||||
case 'warning':
|
||||
return '❗️';
|
||||
case 'link':
|
||||
return '🔗';
|
||||
case 'inspect':
|
||||
return '🔍';
|
||||
case 'success':
|
||||
return '✅';
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
export type EmojiLabel = keyof typeof emojiLabels;
|
||||
|
||||
export function emoji(label: EmojiLabel) {
|
||||
return emojiLabels[label];
|
||||
}
|
||||
|
||||
export function prependEmoji(message: string, emoji?: string): string {
|
||||
|
||||
@@ -2,52 +2,41 @@
|
||||
import { URLSearchParams } from 'url';
|
||||
|
||||
// Packages
|
||||
import retry from 'async-retry';
|
||||
import jsonlines from 'jsonlines';
|
||||
import { eraseLines } from 'ansi-escapes';
|
||||
|
||||
import jsonlines from 'jsonlines';
|
||||
import retry from 'async-retry';
|
||||
import Client from './client';
|
||||
import { getDeployment } from './get-deployment';
|
||||
|
||||
export interface FindOpts {
|
||||
direction: 'forward' | 'backward';
|
||||
limit?: number;
|
||||
since?: number;
|
||||
until?: number;
|
||||
follow?: boolean;
|
||||
}
|
||||
|
||||
export interface PrintEventsOptions {
|
||||
mode: string;
|
||||
onEvent: (event: DeploymentEvent) => void;
|
||||
quiet?: boolean;
|
||||
findOpts: FindOpts;
|
||||
}
|
||||
|
||||
export interface DeploymentEvent {
|
||||
id: string;
|
||||
created: number;
|
||||
date?: number;
|
||||
serial?: string;
|
||||
}
|
||||
|
||||
async function printEvents(
|
||||
now,
|
||||
deploymentIdOrURL,
|
||||
currentTeam = null,
|
||||
{
|
||||
mode,
|
||||
onOpen = () => {},
|
||||
onEvent,
|
||||
quiet,
|
||||
debugEnabled,
|
||||
findOpts,
|
||||
output,
|
||||
} = {}
|
||||
client: Client,
|
||||
deploymentIdOrURL: string,
|
||||
{ mode, onEvent, quiet, findOpts }: PrintEventsOptions
|
||||
) {
|
||||
const { log, debug } = output;
|
||||
|
||||
let onOpenCalled = false;
|
||||
function callOnOpenOnce() {
|
||||
if (onOpenCalled) return;
|
||||
onOpenCalled = true;
|
||||
onOpen();
|
||||
}
|
||||
|
||||
const query = new URLSearchParams({
|
||||
direction: findOpts.direction,
|
||||
limit: findOpts.limit,
|
||||
since: findOpts.since,
|
||||
until: findOpts.until,
|
||||
follow: findOpts.follow ? '1' : '',
|
||||
format: 'lines',
|
||||
});
|
||||
|
||||
let eventsUrl = `/v1/now/deployments/${deploymentIdOrURL}/events?${query}`;
|
||||
let pollUrl = `/v3/now/deployments/${deploymentIdOrURL}`;
|
||||
|
||||
if (currentTeam) {
|
||||
eventsUrl += `&teamId=${currentTeam.id}`;
|
||||
pollUrl += `?teamId=${currentTeam.id}`;
|
||||
}
|
||||
|
||||
debug(`Events ${eventsUrl}`);
|
||||
const { log, debug } = client.output;
|
||||
|
||||
// we keep track of how much we log in case we
|
||||
// drop the connection and have to start over
|
||||
@@ -59,29 +48,34 @@ async function printEvents(
|
||||
debug('Retrying events');
|
||||
}
|
||||
|
||||
const eventsRes = await now._fetch(eventsUrl);
|
||||
const query = new URLSearchParams({
|
||||
direction: findOpts.direction,
|
||||
follow: findOpts.follow ? '1' : '',
|
||||
format: 'lines',
|
||||
});
|
||||
if (findOpts.limit) query.set('limit', String(findOpts.limit));
|
||||
if (findOpts.since) query.set('since', String(findOpts.since));
|
||||
if (findOpts.until) query.set('until', String(findOpts.until));
|
||||
|
||||
const eventsUrl = `/v1/now/deployments/${deploymentIdOrURL}/events?${query}`;
|
||||
const eventsRes = await client.fetch(eventsUrl, { json: false });
|
||||
|
||||
if (eventsRes.ok) {
|
||||
const readable = eventsRes.readable
|
||||
? await eventsRes.readable()
|
||||
: eventsRes.body;
|
||||
const readable = eventsRes.body;
|
||||
|
||||
// handle the event stream and make the promise get rejected
|
||||
// if errors occur so we can retry
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const stream = readable.pipe(jsonlines.parse());
|
||||
|
||||
let poller;
|
||||
let poller: ReturnType<typeof setTimeout>;
|
||||
|
||||
if (mode === 'deploy') {
|
||||
poller = (function startPoller() {
|
||||
return setTimeout(async () => {
|
||||
try {
|
||||
const pollRes = await now._fetch(pollUrl);
|
||||
if (!pollRes.ok)
|
||||
throw new Error(`Response ${pollRes.status}`);
|
||||
const json = await pollRes.json();
|
||||
if (json.state === 'READY') {
|
||||
const json = await getDeployment(client, deploymentIdOrURL);
|
||||
if (json.readyState === 'READY') {
|
||||
stream.end();
|
||||
finish();
|
||||
return;
|
||||
@@ -96,10 +90,9 @@ async function printEvents(
|
||||
}
|
||||
|
||||
let finishCalled = false;
|
||||
function finish(error) {
|
||||
function finish(error?: Error) {
|
||||
if (finishCalled) return;
|
||||
finishCalled = true;
|
||||
callOnOpenOnce();
|
||||
clearTimeout(poller);
|
||||
if (error) {
|
||||
reject(error);
|
||||
@@ -110,26 +103,24 @@ async function printEvents(
|
||||
|
||||
let latestLogDate = 0;
|
||||
|
||||
const onData = data => {
|
||||
const { event } = data;
|
||||
if (event === 'state' && data.payload.value === 'READY') {
|
||||
const onData = (data: any) => {
|
||||
const { event, payload, date } = data;
|
||||
if (event === 'state' && payload.value === 'READY') {
|
||||
if (mode === 'deploy') {
|
||||
stream.end();
|
||||
finish();
|
||||
}
|
||||
} else {
|
||||
latestLogDate = Math.max(latestLogDate, data.date);
|
||||
const linesPrinted = onEvent(data, callOnOpenOnce);
|
||||
o += linesPrinted || 0;
|
||||
latestLogDate = Math.max(latestLogDate, date);
|
||||
onEvent(data);
|
||||
}
|
||||
};
|
||||
|
||||
let onErrorCalled = false;
|
||||
const onError = err => {
|
||||
const onError = (err: Error) => {
|
||||
if (finishCalled || onErrorCalled) return;
|
||||
onErrorCalled = true;
|
||||
o++;
|
||||
callOnOpenOnce();
|
||||
|
||||
const errorMessage = `Deployment event stream error: ${err.message}`;
|
||||
if (!findOpts.follow) {
|
||||
@@ -140,7 +131,6 @@ async function printEvents(
|
||||
debug(errorMessage);
|
||||
clearTimeout(poller);
|
||||
stream.destroy(err);
|
||||
readable.destroy(err);
|
||||
|
||||
const retryFindOpts = {
|
||||
...findOpts,
|
||||
@@ -149,12 +139,10 @@ async function printEvents(
|
||||
|
||||
setTimeout(() => {
|
||||
// retry without maximum amount nor clear past logs etc
|
||||
printEvents(now, deploymentIdOrURL, currentTeam, {
|
||||
printEvents(client, deploymentIdOrURL, {
|
||||
mode,
|
||||
onOpen,
|
||||
onEvent,
|
||||
quiet,
|
||||
debugEnabled,
|
||||
findOpts: retryFindOpts,
|
||||
}).then(resolve, reject);
|
||||
}, 2000);
|
||||
@@ -166,7 +154,6 @@ async function printEvents(
|
||||
readable.on('error', onError);
|
||||
});
|
||||
}
|
||||
callOnOpenOnce();
|
||||
const err = new Error(`Deployment events status ${eventsRes.status}`);
|
||||
|
||||
if (eventsRes.status < 500) {
|
||||
@@ -9,15 +9,15 @@ import {
|
||||
import humanizePath from './humanize-path';
|
||||
import readJSONFile from './read-json-file';
|
||||
import readPackage from './read-package';
|
||||
import { NowConfig } from './dev/types';
|
||||
import { VercelConfig } from './dev/types';
|
||||
import { Output } from './output';
|
||||
|
||||
let config: NowConfig;
|
||||
let config: VercelConfig;
|
||||
|
||||
export default async function getConfig(
|
||||
output: Output,
|
||||
configFile?: string
|
||||
): Promise<NowConfig | Error> {
|
||||
): Promise<VercelConfig | Error> {
|
||||
// If config was already read, just return it
|
||||
if (config) {
|
||||
return config;
|
||||
@@ -44,7 +44,7 @@ export default async function getConfig(
|
||||
return localConfig;
|
||||
}
|
||||
if (localConfig !== null) {
|
||||
config = localConfig as NowConfig;
|
||||
config = localConfig as VercelConfig;
|
||||
config[fileNameSymbol] = configFile;
|
||||
return config;
|
||||
}
|
||||
@@ -68,13 +68,13 @@ export default async function getConfig(
|
||||
}
|
||||
if (vercelConfig !== null) {
|
||||
output.debug(`Found config in file "${vercelFilePath}"`);
|
||||
config = vercelConfig as NowConfig;
|
||||
config = vercelConfig as VercelConfig;
|
||||
config[fileNameSymbol] = 'vercel.json';
|
||||
return config;
|
||||
}
|
||||
if (nowConfig !== null) {
|
||||
output.debug(`Found config in file "${nowFilePath}"`);
|
||||
config = nowConfig as NowConfig;
|
||||
config = nowConfig as VercelConfig;
|
||||
config[fileNameSymbol] = 'now.json';
|
||||
return config;
|
||||
}
|
||||
@@ -87,7 +87,7 @@ export default async function getConfig(
|
||||
}
|
||||
if (pkgConfig) {
|
||||
output.debug(`Found config in package ${pkgFilePath}`);
|
||||
config = pkgConfig as NowConfig;
|
||||
config = pkgConfig as VercelConfig;
|
||||
config[fileNameSymbol] = 'package.json';
|
||||
return config;
|
||||
}
|
||||
|
||||
27
packages/cli/src/util/get-deployment.ts
Normal file
27
packages/cli/src/util/get-deployment.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { stringify } from 'querystring';
|
||||
import { Deployment } from '@vercel/client';
|
||||
import Client from './client';
|
||||
|
||||
export async function getDeployment(
|
||||
client: Client,
|
||||
hostOrId: string
|
||||
): Promise<Deployment> {
|
||||
let url = `/v13/deployments`;
|
||||
|
||||
if (hostOrId.includes('.')) {
|
||||
let host = hostOrId.replace(/^https:\/\//i, '');
|
||||
|
||||
if (host.slice(-1) === '/') {
|
||||
host = host.slice(0, -1);
|
||||
}
|
||||
|
||||
url += `/get?${stringify({
|
||||
url: host,
|
||||
})}`;
|
||||
} else {
|
||||
url += `/${encodeURIComponent(hostOrId)}`;
|
||||
}
|
||||
|
||||
const deployment = await client.fetch<Deployment>(url);
|
||||
return deployment;
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
import Client from './client';
|
||||
import { APIError, InvalidToken } from './errors-ts';
|
||||
import { Team } from '../types';
|
||||
// @ts-ignore
|
||||
import NowTeams from './teams.js';
|
||||
import { APIError, InvalidToken } from './errors-ts';
|
||||
|
||||
let teams: Team[] | undefined;
|
||||
|
||||
@@ -10,15 +8,11 @@ export default async function getTeams(client: Client): Promise<Team[]> {
|
||||
if (teams) return teams;
|
||||
|
||||
try {
|
||||
// we're using NowTeams because `client.fetch` hangs on windows
|
||||
const teamClient = new NowTeams({
|
||||
apiUrl: client.apiUrl,
|
||||
token: client.authConfig.token,
|
||||
debug: client.output.isDebugEnabled(),
|
||||
const body = await client.fetch<{ teams: Team[] }>('/v1/teams', {
|
||||
useCurrentTeam: false,
|
||||
});
|
||||
|
||||
teams = (await teamClient.ls()).teams;
|
||||
return teams || [];
|
||||
teams = body.teams || [];
|
||||
return teams;
|
||||
} catch (error) {
|
||||
if (error instanceof APIError && error.status === 403) {
|
||||
throw new InvalidToken();
|
||||
|
||||
@@ -6,15 +6,12 @@ export default async function confirm(
|
||||
): Promise<boolean> {
|
||||
require('./patch-inquirer');
|
||||
|
||||
const name = `${Date.now()}`;
|
||||
|
||||
const answers = await inquirer.prompt({
|
||||
type: 'confirm',
|
||||
name,
|
||||
name: 'value',
|
||||
message,
|
||||
default: preferred,
|
||||
});
|
||||
|
||||
const answer = answers[name] as boolean;
|
||||
return answer;
|
||||
return answers.value;
|
||||
}
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
import inquirer from 'inquirer';
|
||||
import stripAnsi from 'strip-ansi';
|
||||
import eraseLines from '../output/erase-lines';
|
||||
|
||||
function getLength(string) {
|
||||
let biggestLength = 0;
|
||||
string.split('\n').map(str => {
|
||||
str = stripAnsi(str);
|
||||
if (str.length > biggestLength) {
|
||||
biggestLength = str.length;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
return biggestLength;
|
||||
}
|
||||
|
||||
export default async function({
|
||||
message = 'the question',
|
||||
// eslint-disable-line no-unused-vars
|
||||
choices = [
|
||||
{
|
||||
name: 'something\ndescription\ndetails\netc',
|
||||
value: 'something unique',
|
||||
short: 'generally the first line of `name`',
|
||||
},
|
||||
],
|
||||
pageSize = 15, // Show 15 lines without scrolling (~4 credit cards)
|
||||
separator = true, // Puts a blank separator between each choice
|
||||
abort = 'end', // Wether the `abort` option will be at the `start` or the `end`,
|
||||
eraseFinalAnswer = false, // If true, the line with the final answee that inquirer prints will be erased before returning
|
||||
}) {
|
||||
require('./patch-inquirer-legacy');
|
||||
|
||||
let biggestLength = 0;
|
||||
|
||||
choices = choices.map(choice => {
|
||||
if (choice.name) {
|
||||
const length = getLength(choice.name);
|
||||
if (length > biggestLength) {
|
||||
biggestLength = length;
|
||||
}
|
||||
return choice;
|
||||
}
|
||||
throw new Error('Invalid choice');
|
||||
});
|
||||
|
||||
if (separator === true) {
|
||||
choices = choices.reduce(
|
||||
(prev, curr) => prev.concat(new inquirer.Separator(' '), curr),
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
const abortSeparator = new inquirer.Separator('─'.repeat(biggestLength));
|
||||
const _abort = {
|
||||
name: 'Abort',
|
||||
value: undefined,
|
||||
};
|
||||
|
||||
if (abort === 'start') {
|
||||
const blankSep = choices.shift();
|
||||
choices.unshift(abortSeparator);
|
||||
choices.unshift(_abort);
|
||||
choices.unshift(blankSep);
|
||||
} else {
|
||||
choices.push(abortSeparator);
|
||||
choices.push(_abort);
|
||||
}
|
||||
|
||||
const nonce = Date.now();
|
||||
const answer = await inquirer.prompt({
|
||||
name: nonce,
|
||||
type: 'list',
|
||||
message,
|
||||
choices,
|
||||
pageSize,
|
||||
});
|
||||
if (eraseFinalAnswer === true) {
|
||||
process.stdout.write(eraseLines(2));
|
||||
}
|
||||
return answer[nonce];
|
||||
}
|
||||
123
packages/cli/src/util/input/list.ts
Normal file
123
packages/cli/src/util/input/list.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import inquirer from 'inquirer';
|
||||
import stripAnsi from 'strip-ansi';
|
||||
import eraseLines from '../output/erase-lines';
|
||||
|
||||
interface ListEntry {
|
||||
name: string;
|
||||
value: string;
|
||||
short: string;
|
||||
selected?: boolean;
|
||||
}
|
||||
|
||||
interface ListSeparator {
|
||||
separator: string;
|
||||
}
|
||||
|
||||
type ListChoice = ListEntry | ListSeparator | typeof inquirer.Separator;
|
||||
|
||||
interface ListOptions {
|
||||
message: string;
|
||||
choices: ListChoice[];
|
||||
pageSize?: number;
|
||||
separator?: boolean;
|
||||
abort?: 'start' | 'end';
|
||||
eraseFinalAnswer?: boolean;
|
||||
}
|
||||
|
||||
function getLength(input: string): number {
|
||||
let biggestLength = 0;
|
||||
for (const line of input.split('\n')) {
|
||||
const str = stripAnsi(line);
|
||||
if (str.length > biggestLength) {
|
||||
biggestLength = str.length;
|
||||
}
|
||||
}
|
||||
return biggestLength;
|
||||
}
|
||||
|
||||
export default async function list({
|
||||
message = 'the question',
|
||||
// eslint-disable-line no-unused-vars
|
||||
choices: _choices = [
|
||||
{
|
||||
name: 'something\ndescription\ndetails\netc',
|
||||
value: 'something unique',
|
||||
short: 'generally the first line of `name`',
|
||||
},
|
||||
],
|
||||
pageSize = 15, // Show 15 lines without scrolling (~4 credit cards)
|
||||
separator = false, // Puts a blank separator between each choice
|
||||
abort = 'end', // Whether the `abort` option will be at the `start` or the `end`,
|
||||
eraseFinalAnswer = false, // If true, the line with the final answer that inquirer prints will be erased before returning
|
||||
}: ListOptions): Promise<string> {
|
||||
require('./patch-inquirer-legacy');
|
||||
|
||||
let biggestLength = 0;
|
||||
let selected: string | undefined;
|
||||
|
||||
// First calculate the biggest length
|
||||
for (const choice of _choices) {
|
||||
if ('name' in choice) {
|
||||
const length = getLength(choice.name);
|
||||
if (length > biggestLength) {
|
||||
biggestLength = length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const choices = _choices.map(choice => {
|
||||
if (choice instanceof inquirer.Separator) {
|
||||
return choice;
|
||||
}
|
||||
|
||||
if ('separator' in choice) {
|
||||
const prefix = `── ${choice.separator} `;
|
||||
const suffix = '─'.repeat(biggestLength - getLength(prefix));
|
||||
return new inquirer.Separator(`${prefix}${suffix}`);
|
||||
}
|
||||
|
||||
if ('short' in choice) {
|
||||
if (choice.selected) {
|
||||
if (selected) throw new Error('Only one choice may be selected');
|
||||
selected = choice.short;
|
||||
}
|
||||
return choice;
|
||||
}
|
||||
|
||||
throw new Error('Invalid choice');
|
||||
});
|
||||
|
||||
if (separator) {
|
||||
for (let i = 0; i < choices.length; i += 2) {
|
||||
choices.splice(i, 0, new inquirer.Separator(' '));
|
||||
}
|
||||
}
|
||||
|
||||
const abortSeparator = new inquirer.Separator('─'.repeat(biggestLength));
|
||||
const _abort = {
|
||||
name: 'Abort',
|
||||
value: '',
|
||||
short: '',
|
||||
};
|
||||
|
||||
if (abort === 'start') {
|
||||
choices.unshift(_abort, abortSeparator);
|
||||
} else {
|
||||
choices.push(abortSeparator, _abort);
|
||||
}
|
||||
|
||||
const answer = await inquirer.prompt({
|
||||
name: 'value',
|
||||
type: 'list',
|
||||
default: selected,
|
||||
message,
|
||||
choices,
|
||||
pageSize,
|
||||
});
|
||||
|
||||
if (eraseFinalAnswer === true) {
|
||||
process.stdout.write(eraseLines(2));
|
||||
}
|
||||
|
||||
return answer.value;
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { join, basename } from 'path';
|
||||
import chalk from 'chalk';
|
||||
import { remove } from 'fs-extra';
|
||||
import { ProjectLinkResult, ProjectSettings } from '../../types';
|
||||
import { NowConfig } from '../dev/types';
|
||||
import { VercelConfig } from '../dev/types';
|
||||
import {
|
||||
getLinkedProject,
|
||||
linkFolderToProject,
|
||||
@@ -134,7 +134,7 @@ export default async function setupAndLink(
|
||||
return { status: 'error', exitCode: 1 };
|
||||
}
|
||||
|
||||
let localConfig: NowConfig = {};
|
||||
let localConfig: VercelConfig = {};
|
||||
if (client.localConfig && !(client.localConfig instanceof Error)) {
|
||||
localConfig = client.localConfig;
|
||||
}
|
||||
|
||||
@@ -9,5 +9,5 @@ export default function doBitbucketLogin(params: LoginParams) {
|
||||
// cookie that the OAuth callback URL depends on
|
||||
'https://vercel.com'
|
||||
);
|
||||
return doOauthLogin(url, 'Bitbucket', params);
|
||||
return doOauthLogin(params, url, 'Bitbucket');
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import executeLogin from './login';
|
||||
import { LoginParams } from './types';
|
||||
|
||||
export default async function doEmailLogin(
|
||||
email: string,
|
||||
params: LoginParams
|
||||
params: LoginParams,
|
||||
email: string
|
||||
): Promise<number | string> {
|
||||
let securityCode;
|
||||
let verificationToken;
|
||||
@@ -42,7 +42,7 @@ export default async function doEmailLogin(
|
||||
while (!token) {
|
||||
try {
|
||||
await sleep(ms('1s'));
|
||||
token = await verify(email, verificationToken, params);
|
||||
token = await verify(email, verificationToken, 'Email', params);
|
||||
} catch (err) {
|
||||
if (err.message !== 'Confirmation incomplete') {
|
||||
output.error(err.message);
|
||||
|
||||
@@ -9,5 +9,5 @@ export default function doGithubLogin(params: LoginParams) {
|
||||
// cookie that the OAuth callback URL depends on
|
||||
'https://vercel.com'
|
||||
);
|
||||
return doOauthLogin(url, 'GitHub', params);
|
||||
return doOauthLogin(params, url, 'GitHub');
|
||||
}
|
||||
|
||||
@@ -6,5 +6,5 @@ export default function doGitlabLogin(params: LoginParams) {
|
||||
// Can't use `apiUrl` here because this URL sets a
|
||||
// cookie that the OAuth callback URL depends on
|
||||
const url = new URL('/api/registration/gitlab/connect', 'https://vercel.com');
|
||||
return doOauthLogin(url, 'GitLab', params);
|
||||
return doOauthLogin(params, url, 'GitLab');
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import fetch from 'node-fetch';
|
||||
import { hostname } from 'os';
|
||||
import { InvalidEmail, AccountNotFound } from '../errors-ts';
|
||||
import ua from '../ua';
|
||||
import { getTitleName } from '../pkg-name';
|
||||
import { LoginData } from './types';
|
||||
|
||||
export default async function login(
|
||||
@@ -10,20 +8,13 @@ export default async function login(
|
||||
email: string,
|
||||
mode: 'login' | 'signup' = 'login'
|
||||
): Promise<LoginData> {
|
||||
const hyphens = new RegExp('-', 'g');
|
||||
const host = hostname().replace(hyphens, ' ').replace('.local', '');
|
||||
const tokenName = `${getTitleName()} CLI on ${host}`;
|
||||
|
||||
const response = await fetch(`${apiUrl}/now/registration?mode=${mode}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': ua,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
tokenName,
|
||||
email,
|
||||
}),
|
||||
body: JSON.stringify({ email }),
|
||||
});
|
||||
|
||||
const body = await response.json();
|
||||
|
||||
@@ -6,23 +6,25 @@ import { LoginParams } from './types';
|
||||
import prompt from './prompt';
|
||||
import verify from './verify';
|
||||
import highlight from '../output/highlight';
|
||||
import link from '../output/link';
|
||||
import eraseLines from '../output/erase-lines';
|
||||
|
||||
export default async function doOauthLogin(
|
||||
params: LoginParams,
|
||||
url: URL,
|
||||
provider: string,
|
||||
params: LoginParams
|
||||
provider: string
|
||||
): Promise<number | string> {
|
||||
const { output } = params;
|
||||
|
||||
output.spinner(
|
||||
`Please complete the ${provider} authentication in your web browser`
|
||||
);
|
||||
|
||||
const server = http.createServer();
|
||||
const address = await listen(server, 0, '127.0.0.1');
|
||||
const { port } = new URL(address);
|
||||
url.searchParams.append('mode', 'login');
|
||||
url.searchParams.append('next', `http://localhost:${port}`);
|
||||
url.searchParams.set('mode', 'login');
|
||||
url.searchParams.set('next', `http://localhost:${port}`);
|
||||
|
||||
output.log(`Please visit the following URL in your web browser:`);
|
||||
output.log(link(url.href));
|
||||
output.spinner(`Waiting for ${provider} authentication to be completed`);
|
||||
|
||||
try {
|
||||
const [query] = await Promise.all([
|
||||
@@ -70,6 +72,9 @@ export default async function doOauthLogin(
|
||||
open(url.href),
|
||||
]);
|
||||
|
||||
output.stopSpinner();
|
||||
output.print(eraseLines(3));
|
||||
|
||||
const loginError = query.get('loginError');
|
||||
if (loginError) {
|
||||
const err = JSON.parse(loginError);
|
||||
@@ -98,7 +103,7 @@ export default async function doOauthLogin(
|
||||
}
|
||||
|
||||
output.spinner('Verifying authentication token');
|
||||
const token = await verify(email, verificationToken, params);
|
||||
const token = await verify(email, verificationToken, provider, params);
|
||||
output.success(
|
||||
`${provider} authentication complete for ${highlight(email)}`
|
||||
);
|
||||
|
||||
@@ -2,14 +2,17 @@ import inquirer from 'inquirer';
|
||||
import error from '../output/error';
|
||||
import listInput from '../input/list';
|
||||
import { getCommandName } from '../pkg-name';
|
||||
import { LoginParams } from './types';
|
||||
import { LoginParams, SAMLError } from './types';
|
||||
import doSsoLogin from './sso';
|
||||
import doEmailLogin from './email';
|
||||
import doGithubLogin from './github';
|
||||
import doGitlabLogin from './gitlab';
|
||||
import doBitbucketLogin from './bitbucket';
|
||||
|
||||
export default async function prompt(params: LoginParams) {
|
||||
export default async function prompt(
|
||||
params: LoginParams,
|
||||
error?: Pick<SAMLError, 'teamId'>
|
||||
) {
|
||||
let result: number | string = 1;
|
||||
|
||||
const choices = [
|
||||
@@ -17,17 +20,17 @@ export default async function prompt(params: LoginParams) {
|
||||
{ name: 'Continue with GitLab', value: 'gitlab', short: 'gitlab' },
|
||||
{ name: 'Continue with Bitbucket', value: 'bitbucket', short: 'bitbucket' },
|
||||
{ name: 'Continue with Email', value: 'email', short: 'email' },
|
||||
{ name: 'Continue with SAML Single Sign-On', value: 'saml', short: 'saml' },
|
||||
{ name: 'Continue with SAML Single Sign-On', value: 'sso', short: 'sso' },
|
||||
];
|
||||
|
||||
if (params.ssoUserId) {
|
||||
// Remove SAML login option if we're connecting SAML Profile
|
||||
if (params.ssoUserId || (error && !error.teamId)) {
|
||||
// Remove SAML login option if we're connecting SAML Profile,
|
||||
// or if this is a SAML error for a user / team without SAML
|
||||
choices.pop();
|
||||
}
|
||||
|
||||
const choice = await listInput({
|
||||
message: 'Log in to Vercel',
|
||||
separator: false,
|
||||
choices,
|
||||
});
|
||||
|
||||
@@ -39,10 +42,10 @@ export default async function prompt(params: LoginParams) {
|
||||
result = await doBitbucketLogin(params);
|
||||
} else if (choice === 'email') {
|
||||
const email = await readInput('Enter your email address');
|
||||
result = await doEmailLogin(email, params);
|
||||
} else if (choice === 'saml') {
|
||||
const slug = await readInput('Enter your Team slug');
|
||||
result = await doSsoLogin(slug, params);
|
||||
result = await doEmailLogin(params, email);
|
||||
} else if (choice === 'sso') {
|
||||
const slug = error?.teamId || (await readInput('Enter your Team slug'));
|
||||
result = await doSsoLogin(params, slug);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
28
packages/cli/src/util/login/reauthenticate.ts
Normal file
28
packages/cli/src/util/login/reauthenticate.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { bold } from 'chalk';
|
||||
import doSsoLogin from './sso';
|
||||
import showLoginPrompt from './prompt';
|
||||
import { LoginParams, SAMLError } from './types';
|
||||
import confirm from '../input/confirm';
|
||||
|
||||
export default async function reauthenticate(
|
||||
params: LoginParams,
|
||||
error: Pick<SAMLError, 'enforced' | 'scope' | 'teamId'>
|
||||
): Promise<string | number> {
|
||||
let result: string | number = 1;
|
||||
if (error.teamId && error.enforced) {
|
||||
// If team has SAML enforced then trigger the SSO login directly
|
||||
params.output.log(
|
||||
`You must re-authenticate with SAML to use ${bold(error.scope)} scope.`
|
||||
);
|
||||
if (await confirm(`Log in with SAML?`, true)) {
|
||||
result = await doSsoLogin(params, error.teamId);
|
||||
}
|
||||
} else {
|
||||
// Personal account, or team that does not have SAML enforced
|
||||
params.output.log(
|
||||
`You must re-authenticate to use ${bold(error.scope)} scope.`
|
||||
);
|
||||
result = await showLoginPrompt(params, error);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -1,17 +1,9 @@
|
||||
import { URL } from 'url';
|
||||
import { hostname } from 'os';
|
||||
import { getTitleName } from '../pkg-name';
|
||||
import { LoginParams } from './types';
|
||||
import doOauthLogin from './oauth';
|
||||
|
||||
export default function doSsoLogin(teamIdOrSlug: string, params: LoginParams) {
|
||||
const hyphens = new RegExp('-', 'g');
|
||||
const host = hostname().replace(hyphens, ' ').replace('.local', '');
|
||||
const tokenName = `${getTitleName()} CLI on ${host}`;
|
||||
|
||||
export default function doSsoLogin(params: LoginParams, teamIdOrSlug: string) {
|
||||
const url = new URL('/auth/sso', params.apiUrl);
|
||||
url.searchParams.append('teamId', teamIdOrSlug);
|
||||
url.searchParams.append('tokenName', tokenName);
|
||||
|
||||
return doOauthLogin(url, 'SAML Single Sign-On', params);
|
||||
url.searchParams.set('teamId', teamIdOrSlug);
|
||||
return doOauthLogin(params, url, 'SAML Single Sign-On');
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { AuthConfig } from '../../types';
|
||||
import { Output } from '../output';
|
||||
|
||||
export interface LoginParams {
|
||||
authConfig: AuthConfig;
|
||||
apiUrl: string;
|
||||
output: Output;
|
||||
ssoUserId?: string;
|
||||
@@ -10,3 +12,10 @@ export interface LoginData {
|
||||
token: string;
|
||||
securityCode: string;
|
||||
}
|
||||
|
||||
export interface SAMLError {
|
||||
saml?: true;
|
||||
teamId: string | null;
|
||||
scope: string;
|
||||
enforced?: boolean;
|
||||
}
|
||||
|
||||
@@ -1,24 +1,41 @@
|
||||
import { URL } from 'url';
|
||||
import fetch from 'node-fetch';
|
||||
import fetch, { Headers } from 'node-fetch';
|
||||
import ua from '../ua';
|
||||
import { LoginParams } from './types';
|
||||
import { hostname } from 'os';
|
||||
import { getTitleName } from '../pkg-name';
|
||||
|
||||
export default async function verify(
|
||||
email: string,
|
||||
verificationToken: string,
|
||||
{ apiUrl, ssoUserId }: LoginParams
|
||||
provider: string,
|
||||
{ authConfig, apiUrl, ssoUserId }: LoginParams
|
||||
): Promise<string> {
|
||||
const url = new URL('/registration/verify', apiUrl);
|
||||
url.searchParams.append('email', email);
|
||||
url.searchParams.append('token', verificationToken);
|
||||
if (ssoUserId) {
|
||||
url.searchParams.append('ssoUserId', ssoUserId);
|
||||
url.searchParams.set('email', email);
|
||||
url.searchParams.set('token', verificationToken);
|
||||
|
||||
const headers = new Headers({ 'User-Agent': ua });
|
||||
|
||||
if (authConfig.token) {
|
||||
// If there is already an auth token then it will be
|
||||
// upgraded, rather than a new token being created
|
||||
headers.set('Authorization', `Bearer ${authConfig.token}`);
|
||||
} else {
|
||||
// Set the "name" of the Token that will be created
|
||||
const hyphens = new RegExp('-', 'g');
|
||||
const host = hostname().replace(hyphens, ' ').replace('.local', '');
|
||||
const tokenName = `${getTitleName()} CLI on ${host} via ${provider}`;
|
||||
url.searchParams.set('tokenName', tokenName);
|
||||
}
|
||||
|
||||
const res = await fetch(url.href, {
|
||||
headers: { 'User-Agent': ua },
|
||||
});
|
||||
// If `ssoUserId` is defined then this verification
|
||||
// will complete the SAML two-step login connection
|
||||
if (ssoUserId) {
|
||||
url.searchParams.set('ssoUserId', ssoUserId);
|
||||
}
|
||||
|
||||
const res = await fetch(url.href, { headers });
|
||||
const body = await res.json();
|
||||
|
||||
if (!res.ok) {
|
||||
|
||||
24
packages/cli/src/util/promise.ts
Normal file
24
packages/cli/src/util/promise.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Wraps a function such that only one in-flight invocation is active at a time.
|
||||
*
|
||||
* That is, if the returned function is invoked more that one time before the
|
||||
* promise returned from the initial invocation resolves, then the same promise
|
||||
* is returned for subsequent invocations.
|
||||
*
|
||||
* Once the promise has resolved, the next invocation of the returned function
|
||||
* will re-invoke the original function again.
|
||||
*/
|
||||
export function sharedPromise<P extends any[], V, T>(
|
||||
fn: (this: T, ...args: P) => Promise<V>
|
||||
) {
|
||||
let promise: Promise<V> | null = null;
|
||||
return function (this: T, ...args: P) {
|
||||
if (!promise) {
|
||||
promise = fn.apply(this, args);
|
||||
promise.finally(() => {
|
||||
promise = null;
|
||||
});
|
||||
}
|
||||
return promise;
|
||||
};
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import path from 'path';
|
||||
import { CantParseJSONFile } from './errors-ts';
|
||||
import readJSONFile from './read-json-file';
|
||||
import { NowConfig } from './dev/types';
|
||||
import { VercelConfig } from './dev/types';
|
||||
import { PackageJson } from '@vercel/build-utils';
|
||||
|
||||
interface CustomPackage extends PackageJson {
|
||||
now?: NowConfig;
|
||||
now?: VercelConfig;
|
||||
}
|
||||
|
||||
export default async function readPackage(file?: string) {
|
||||
|
||||
@@ -7,9 +7,9 @@ import { parse } from 'url';
|
||||
* google.com => google.com
|
||||
*/
|
||||
|
||||
function toHost(url: string) {
|
||||
function toHost(url: string): string {
|
||||
if (/^https?:\/\//.test(url)) {
|
||||
return parse(url).host;
|
||||
return parse(url).host!;
|
||||
}
|
||||
|
||||
// Remove any path if present
|
||||
|
||||
@@ -9,9 +9,6 @@
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e"
|
||||
},
|
||||
"engines": {
|
||||
"node": "10.x"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "8.1.0",
|
||||
|
||||
@@ -997,10 +997,10 @@ bluebird@^3.3.0, bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5:
|
||||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
|
||||
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
|
||||
|
||||
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.4.0:
|
||||
version "4.11.8"
|
||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
|
||||
integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==
|
||||
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9:
|
||||
version "4.12.0"
|
||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88"
|
||||
integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==
|
||||
|
||||
bn.js@^5.1.1:
|
||||
version "5.1.1"
|
||||
@@ -1066,7 +1066,7 @@ braces@~3.0.2:
|
||||
dependencies:
|
||||
fill-range "^7.0.1"
|
||||
|
||||
brorand@^1.0.1:
|
||||
brorand@^1.0.1, brorand@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
|
||||
integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=
|
||||
@@ -1529,7 +1529,7 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
|
||||
dependencies:
|
||||
delayed-stream "~1.0.0"
|
||||
|
||||
commander@^2.11.0, commander@^2.12.1, commander@^2.20.0, commander@~2.20.3:
|
||||
commander@^2.11.0, commander@^2.12.1, commander@^2.20.0:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||
@@ -2088,17 +2088,17 @@ electron-to-chromium@^1.3.164, electron-to-chromium@^1.3.413:
|
||||
integrity sha512-FtEGg/wRKT765QehmpRqMDaUbRam03Y3brE+6nUfoxCr9XgyEl+zAgN4+EraOJeyTGTGh27u7Mdx5Hl9qAhJPQ==
|
||||
|
||||
elliptic@^6.0.0, elliptic@^6.5.2:
|
||||
version "6.5.2"
|
||||
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.2.tgz#05c5678d7173c049d8ca433552224a495d0e3762"
|
||||
integrity sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==
|
||||
version "6.5.4"
|
||||
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
|
||||
integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==
|
||||
dependencies:
|
||||
bn.js "^4.4.0"
|
||||
brorand "^1.0.1"
|
||||
bn.js "^4.11.9"
|
||||
brorand "^1.1.0"
|
||||
hash.js "^1.0.0"
|
||||
hmac-drbg "^1.0.0"
|
||||
inherits "^2.0.1"
|
||||
minimalistic-assert "^1.0.0"
|
||||
minimalistic-crypto-utils "^1.0.0"
|
||||
hmac-drbg "^1.0.1"
|
||||
inherits "^2.0.4"
|
||||
minimalistic-assert "^1.0.1"
|
||||
minimalistic-crypto-utils "^1.0.1"
|
||||
|
||||
emoji-regex@^7.0.1:
|
||||
version "7.0.3"
|
||||
@@ -2842,9 +2842,9 @@ handle-thing@^2.0.0:
|
||||
integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==
|
||||
|
||||
handlebars@^4.0.3:
|
||||
version "4.7.6"
|
||||
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.6.tgz#d4c05c1baf90e9945f77aa68a7a219aa4a7df74e"
|
||||
integrity sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==
|
||||
version "4.7.7"
|
||||
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1"
|
||||
integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==
|
||||
dependencies:
|
||||
minimist "^1.2.5"
|
||||
neo-async "^2.6.0"
|
||||
@@ -2955,7 +2955,7 @@ hash.js@^1.0.0, hash.js@^1.0.3:
|
||||
inherits "^2.0.3"
|
||||
minimalistic-assert "^1.0.1"
|
||||
|
||||
hmac-drbg@^1.0.0:
|
||||
hmac-drbg@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
|
||||
integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=
|
||||
@@ -2965,9 +2965,9 @@ hmac-drbg@^1.0.0:
|
||||
minimalistic-crypto-utils "^1.0.1"
|
||||
|
||||
hosted-git-info@^2.1.4, hosted-git-info@^2.6.0, hosted-git-info@^2.7.1:
|
||||
version "2.8.8"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
|
||||
integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
|
||||
version "2.8.9"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
|
||||
integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
|
||||
|
||||
hpack.js@^2.1.6:
|
||||
version "2.1.6"
|
||||
@@ -3948,9 +3948,9 @@ lodash.tail@^4.1.1:
|
||||
integrity sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=
|
||||
|
||||
lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.4, lodash@^4.17.5:
|
||||
version "4.17.15"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
|
||||
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
|
||||
log4js@^4.0.0:
|
||||
version "4.5.1"
|
||||
@@ -4181,7 +4181,7 @@ minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
|
||||
integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
|
||||
|
||||
minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
|
||||
minimalistic-crypto-utils@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
|
||||
integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=
|
||||
@@ -4330,9 +4330,9 @@ negotiator@0.6.2:
|
||||
integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
|
||||
|
||||
neo-async@^2.5.0, neo-async@^2.6.0:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
|
||||
integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==
|
||||
version "2.6.2"
|
||||
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
|
||||
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
|
||||
|
||||
nice-try@^1.0.4:
|
||||
version "1.0.5"
|
||||
@@ -5192,9 +5192,9 @@ querystring@0.2.0:
|
||||
integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=
|
||||
|
||||
querystringify@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e"
|
||||
integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
|
||||
integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
|
||||
|
||||
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
|
||||
version "2.1.0"
|
||||
@@ -6109,9 +6109,9 @@ sshpk@^1.7.0:
|
||||
tweetnacl "~0.14.0"
|
||||
|
||||
ssri@^6.0.0, ssri@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8"
|
||||
integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5"
|
||||
integrity sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==
|
||||
dependencies:
|
||||
figgy-pudding "^3.5.1"
|
||||
|
||||
@@ -6601,11 +6601,9 @@ typescript@3.4.5:
|
||||
integrity sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw==
|
||||
|
||||
uglify-js@^3.1.4:
|
||||
version "3.9.3"
|
||||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.9.3.tgz#4a285d1658b8a2ebaef9e51366b3a0f7acd79ec2"
|
||||
integrity sha512-r5ImcL6QyzQGVimQoov3aL2ZScywrOgBXGndbWrdehKoSvGe/RmiE5Jpw/v+GvxODt6l2tpBXwA7n+qZVlHBMA==
|
||||
dependencies:
|
||||
commander "~2.20.3"
|
||||
version "3.13.7"
|
||||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.13.7.tgz#25468a3b39b1c875df03f0937b2b7036a93f3fee"
|
||||
integrity sha512-1Psi2MmnZJbnEsgJJIlfnd7tFlJfitusmR7zDI8lXlFI0ACD4/Rm/xdrU8bh6zF0i74aiVoBtkRiFulkrmh3AA==
|
||||
|
||||
ultron@~1.1.0:
|
||||
version "1.1.1"
|
||||
@@ -6704,9 +6702,9 @@ urix@^0.1.0:
|
||||
integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
|
||||
|
||||
url-parse@^1.4.3:
|
||||
version "1.4.7"
|
||||
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278"
|
||||
integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.1.tgz#d5fa9890af8a5e1f274a2c98376510f6425f6e3b"
|
||||
integrity sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==
|
||||
dependencies:
|
||||
querystringify "^2.1.1"
|
||||
requires-port "^1.0.0"
|
||||
|
||||
@@ -4571,9 +4571,9 @@ hoopy@^0.1.4:
|
||||
integrity sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==
|
||||
|
||||
hosted-git-info@^2.1.4:
|
||||
version "2.8.8"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
|
||||
integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
|
||||
version "2.8.9"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
|
||||
integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
|
||||
|
||||
hpack.js@^2.1.6:
|
||||
version "2.1.6"
|
||||
@@ -6056,9 +6056,9 @@ lodash.sortby@^4.7.0:
|
||||
integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
|
||||
|
||||
lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.3, lodash@^4.17.4:
|
||||
version "4.17.15"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
|
||||
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
|
||||
loglevel@^1.6.3:
|
||||
version "1.6.8"
|
||||
@@ -7324,9 +7324,9 @@ querystring@0.2.0:
|
||||
integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=
|
||||
|
||||
querystringify@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e"
|
||||
integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
|
||||
integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
|
||||
|
||||
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
|
||||
version "2.1.0"
|
||||
@@ -8207,9 +8207,9 @@ sshpk@^1.7.0:
|
||||
tweetnacl "~0.14.0"
|
||||
|
||||
ssri@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8"
|
||||
integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5"
|
||||
integrity sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==
|
||||
dependencies:
|
||||
figgy-pudding "^3.5.1"
|
||||
|
||||
@@ -8903,9 +8903,9 @@ url-loader@1.1.2:
|
||||
schema-utils "^1.0.0"
|
||||
|
||||
url-parse@^1.4.3:
|
||||
version "1.4.7"
|
||||
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278"
|
||||
integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.1.tgz#d5fa9890af8a5e1f274a2c98376510f6425f6e3b"
|
||||
integrity sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==
|
||||
dependencies:
|
||||
querystringify "^2.1.1"
|
||||
requires-port "^1.0.0"
|
||||
|
||||
@@ -4580,9 +4580,9 @@ hmac-drbg@^1.0.1:
|
||||
minimalistic-crypto-utils "^1.0.1"
|
||||
|
||||
hosted-git-info@^2.1.4:
|
||||
version "2.8.8"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
|
||||
integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
|
||||
version "2.8.9"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
|
||||
integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
|
||||
|
||||
hpack.js@^2.1.6:
|
||||
version "2.1.6"
|
||||
@@ -7896,9 +7896,9 @@ querystring@0.2.0:
|
||||
integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=
|
||||
|
||||
querystringify@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e"
|
||||
integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
|
||||
integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
|
||||
|
||||
raf@^3.4.1:
|
||||
version "3.4.1"
|
||||
@@ -8898,9 +8898,9 @@ sshpk@^1.7.0:
|
||||
tweetnacl "~0.14.0"
|
||||
|
||||
ssri@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8"
|
||||
integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5"
|
||||
integrity sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==
|
||||
dependencies:
|
||||
figgy-pudding "^3.5.1"
|
||||
|
||||
@@ -9529,9 +9529,9 @@ url-loader@1.1.2:
|
||||
schema-utils "^1.0.0"
|
||||
|
||||
url-parse@^1.4.3:
|
||||
version "1.4.7"
|
||||
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278"
|
||||
integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.1.tgz#d5fa9890af8a5e1f274a2c98376510f6425f6e3b"
|
||||
integrity sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==
|
||||
dependencies:
|
||||
querystringify "^2.1.1"
|
||||
requires-port "^1.0.0"
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 78 KiB |
@@ -1710,7 +1710,9 @@ test(
|
||||
expectHeader('image/svg+xml'),
|
||||
fetchOpts('image/webp')
|
||||
);
|
||||
// bmp should bypass: serve as-is
|
||||
/* Disabled bmp because `next dev` bypasses
|
||||
* and production will convert. Eventually
|
||||
* we can enable once `next dev` supports it.
|
||||
await testPath(
|
||||
200,
|
||||
toUrl('/test.bmp', 64, 50),
|
||||
@@ -1718,6 +1720,7 @@ test(
|
||||
expectHeader('image/bmp'),
|
||||
fetchOpts('image/webp')
|
||||
);
|
||||
*/
|
||||
// animated gif should bypass: serve as-is
|
||||
await testPath(
|
||||
200,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/client",
|
||||
"version": "10.0.1-canary.3",
|
||||
"version": "10.1.1",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"homepage": "https://vercel.com",
|
||||
@@ -40,7 +40,7 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "2.10.3-canary.3",
|
||||
"@vercel/build-utils": "2.11.1",
|
||||
"@zeit/fetch": "5.2.0",
|
||||
"async-retry": "1.2.3",
|
||||
"async-sema": "3.0.0",
|
||||
|
||||
@@ -55,7 +55,7 @@ export interface Deployment {
|
||||
| 'DEPLOYING'
|
||||
| 'READY'
|
||||
| 'ERROR';
|
||||
createdAt: string;
|
||||
createdAt: number;
|
||||
createdIn: string;
|
||||
env: Dictionary<string>;
|
||||
build: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/frameworks",
|
||||
"version": "0.3.3-canary.3",
|
||||
"version": "0.4.1",
|
||||
"main": "./dist/frameworks.js",
|
||||
"types": "./dist/frameworks.d.ts",
|
||||
"files": [
|
||||
@@ -20,7 +20,7 @@
|
||||
"@types/js-yaml": "3.12.1",
|
||||
"@types/node": "12.0.4",
|
||||
"@types/node-fetch": "2.5.8",
|
||||
"@vercel/routing-utils": "1.11.1-canary.1",
|
||||
"@vercel/routing-utils": "1.11.2",
|
||||
"ajv": "6.12.2",
|
||||
"jest": "24.9.0",
|
||||
"ts-jest": "24.1.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/node",
|
||||
"version": "1.10.0",
|
||||
"version": "1.11.1",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
|
||||
@@ -33,7 +33,7 @@
|
||||
"@types/etag": "1.8.0",
|
||||
"@types/test-listen": "1.1.0",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"@vercel/nft": "0.10.0",
|
||||
"@vercel/nft": "0.13.1",
|
||||
"content-type": "1.0.4",
|
||||
"cookie": "0.4.0",
|
||||
"etag": "1.8.1",
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
{
|
||||
"engines": {
|
||||
"node": "10.x"
|
||||
}
|
||||
"private": true
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
{
|
||||
"engines": {
|
||||
"node": "10.x"
|
||||
}
|
||||
"private": true
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ async function getResult(url) {
|
||||
}
|
||||
|
||||
module.exports = async (req, res) => {
|
||||
const result = await getResult('https://vercel.com/about');
|
||||
const result = await getResult('https://vercel.com/docs');
|
||||
if (req && result && result.lhr && result.lhr.categories) {
|
||||
res.end('lighthouse:RANDOMNESS_PLACEHOLDER');
|
||||
} else {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"engines": {
|
||||
"node": "10.x"
|
||||
"node": "14.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"chrome-aws-lambda": "1.20.4",
|
||||
"lighthouse": "5.6.0",
|
||||
"puppeteer-core": "1.20.0"
|
||||
"chrome-aws-lambda": "7.0.0",
|
||||
"lighthouse": "7.4.0",
|
||||
"puppeteer-core": "7.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
"config": {
|
||||
"functions": {
|
||||
"**/*.js": {
|
||||
"memory": 3008
|
||||
"memory": 3008,
|
||||
"maxDuration": 30
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,5 @@
|
||||
"dependencies": {
|
||||
"@builders-transpiled-test/lib": "*"
|
||||
},
|
||||
"engines": {
|
||||
"node": "10.x"
|
||||
},
|
||||
"version": "1.0.0"
|
||||
}
|
||||
|
||||
@@ -3,8 +3,5 @@
|
||||
"dependencies": {
|
||||
"@builders-typescript-test/lib": "*"
|
||||
},
|
||||
"engines": {
|
||||
"node": "10.x"
|
||||
},
|
||||
"version": "1.0.0"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/python",
|
||||
"version": "2.0.1",
|
||||
"version": "2.0.4",
|
||||
"main": "./dist/index.js",
|
||||
"license": "MIT",
|
||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/python",
|
||||
|
||||
@@ -86,6 +86,7 @@ export const build = async ({
|
||||
|
||||
await installRequirement({
|
||||
dependency: 'werkzeug',
|
||||
version: '1.0.1',
|
||||
workPath,
|
||||
meta,
|
||||
});
|
||||
@@ -109,6 +110,7 @@ export const build = async ({
|
||||
const tempDir = await getWriteableDirectory();
|
||||
await installRequirement({
|
||||
dependency: 'pipfile-requirements',
|
||||
version: '0.3.0',
|
||||
workPath: tempDir,
|
||||
meta,
|
||||
args: ['--no-warn-script-location'],
|
||||
|
||||
@@ -61,39 +61,28 @@ async function pipInstall(workPath: string, args: string[]) {
|
||||
// distutils.errors.DistutilsOptionError: can't combine user with
|
||||
// prefix, exec_prefix/home, or install_(plat)base
|
||||
process.env.PIP_USER = '0';
|
||||
debug(
|
||||
`Running "pip install --disable-pip-version-check --target ${target} --upgrade ${args.join(
|
||||
' '
|
||||
)}"...`
|
||||
);
|
||||
const cmdArgs = [
|
||||
'install',
|
||||
'--disable-pip-version-check',
|
||||
'--target',
|
||||
target,
|
||||
...args,
|
||||
];
|
||||
debug(`Running "pip3 ${cmdArgs.join(' ')}"...`);
|
||||
try {
|
||||
await execa(
|
||||
pipPath,
|
||||
[
|
||||
'install',
|
||||
'--disable-pip-version-check',
|
||||
'--target',
|
||||
target,
|
||||
'--upgrade',
|
||||
...args,
|
||||
],
|
||||
{
|
||||
cwd: workPath,
|
||||
stdio: 'pipe',
|
||||
}
|
||||
);
|
||||
await execa(pipPath, cmdArgs, {
|
||||
cwd: workPath,
|
||||
stdio: 'pipe',
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(
|
||||
`Failed to run "pip install --disable-pip-version-check --target ${target} --upgrade ${args.join(
|
||||
' '
|
||||
)}"...`
|
||||
);
|
||||
console.log(`Failed to run "pip3 ${cmdArgs.join(' ')}"`);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
interface InstallRequirementArg {
|
||||
dependency: string;
|
||||
version: string;
|
||||
workPath: string;
|
||||
meta: Meta;
|
||||
args?: string[];
|
||||
@@ -101,6 +90,7 @@ interface InstallRequirementArg {
|
||||
|
||||
export async function installRequirement({
|
||||
dependency,
|
||||
version,
|
||||
workPath,
|
||||
meta,
|
||||
args = [],
|
||||
@@ -111,7 +101,8 @@ export async function installRequirement({
|
||||
);
|
||||
return;
|
||||
}
|
||||
await pipInstall(workPath, [dependency, ...args]);
|
||||
const exact = `${dependency}==${version}`;
|
||||
await pipInstall(workPath, [exact, ...args]);
|
||||
}
|
||||
|
||||
interface InstallRequirementsFileArg {
|
||||
|
||||
@@ -82,14 +82,27 @@ elif 'app' in __vc_variables:
|
||||
not inspect.iscoroutinefunction(__vc_module.app.__call__)
|
||||
):
|
||||
print('using Web Server Gateway Interface (WSGI)')
|
||||
from io import BytesIO
|
||||
from urllib.parse import urlparse
|
||||
from werkzeug._compat import BytesIO
|
||||
from werkzeug._compat import string_types
|
||||
from werkzeug._compat import to_bytes
|
||||
from werkzeug._compat import wsgi_encoding_dance
|
||||
from werkzeug.datastructures import Headers
|
||||
from werkzeug.wrappers import Response
|
||||
|
||||
string_types = (str,)
|
||||
|
||||
def to_bytes(x, charset=sys.getdefaultencoding(), errors="strict"):
|
||||
if x is None:
|
||||
return None
|
||||
if isinstance(x, (bytes, bytearray, memoryview)):
|
||||
return bytes(x)
|
||||
if isinstance(x, str):
|
||||
return x.encode(charset, errors)
|
||||
raise TypeError("Expected bytes")
|
||||
|
||||
def wsgi_encoding_dance(s, charset="utf-8", errors="replace"):
|
||||
if isinstance(s, str):
|
||||
s = s.encode(charset)
|
||||
return s.decode("latin1", errors)
|
||||
|
||||
def vc_handler(event, context):
|
||||
payload = json.loads(event['body'])
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/routing-utils",
|
||||
"version": "1.11.1-canary.1",
|
||||
"version": "1.11.2",
|
||||
"description": "Vercel routing utilities",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
|
||||
@@ -37,9 +37,9 @@ function getCheckAndContinue(
|
||||
route
|
||||
)}`
|
||||
);
|
||||
} else if (route.check) {
|
||||
} else if (route.check && !route.override) {
|
||||
checks.push(route);
|
||||
} else if (route.continue) {
|
||||
} else if (route.continue && !route.override) {
|
||||
continues.push(route);
|
||||
} else {
|
||||
others.push(route);
|
||||
|
||||
@@ -104,6 +104,9 @@ export const routesSchema = {
|
||||
continue: {
|
||||
type: 'boolean',
|
||||
},
|
||||
override: {
|
||||
type: 'boolean',
|
||||
},
|
||||
check: {
|
||||
type: 'boolean',
|
||||
},
|
||||
|
||||
@@ -187,6 +187,10 @@ function collectHasSegments(has?: HasField) {
|
||||
const hasSegments = new Set<string>();
|
||||
|
||||
for (const hasItem of has || []) {
|
||||
if ('key' in hasItem && hasItem.type === 'header') {
|
||||
hasItem.key = hasItem.key.toLowerCase();
|
||||
}
|
||||
|
||||
if (!hasItem.value && 'key' in hasItem) {
|
||||
hasSegments.add(hasItem.key);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ export type Source = {
|
||||
headers?: { [name: string]: string };
|
||||
methods?: string[];
|
||||
continue?: boolean;
|
||||
override?: boolean;
|
||||
check?: boolean;
|
||||
important?: boolean;
|
||||
status?: number;
|
||||
|
||||
94
packages/routing-utils/test/merge.spec.js
vendored
94
packages/routing-utils/test/merge.spec.js
vendored
@@ -419,3 +419,97 @@ test('mergeRoutes ensure `handle: error` comes last', () => {
|
||||
];
|
||||
deepStrictEqual(actual, expected);
|
||||
});
|
||||
|
||||
test('mergeRoutes ensure beforeFiles comes after redirects (continue)', () => {
|
||||
const userRoutes = [];
|
||||
const builds = [
|
||||
{
|
||||
use: '@vercel/next',
|
||||
entrypoint: 'package.json',
|
||||
routes: [
|
||||
{
|
||||
src: '^/home$',
|
||||
status: 301,
|
||||
headers: {
|
||||
Location: '/',
|
||||
},
|
||||
},
|
||||
{
|
||||
src: '^/hello$',
|
||||
dest: '/somewhere',
|
||||
continue: true,
|
||||
override: true,
|
||||
},
|
||||
{
|
||||
handle: 'filesystem',
|
||||
},
|
||||
{
|
||||
src: '^/404$',
|
||||
dest: '/404',
|
||||
status: 404,
|
||||
check: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
const actual = mergeRoutes({ userRoutes, builds });
|
||||
const expected = [
|
||||
{ src: '^/home$', status: 301, headers: { Location: '/' } },
|
||||
{
|
||||
src: '^/hello$',
|
||||
dest: '/somewhere',
|
||||
continue: true,
|
||||
override: true,
|
||||
},
|
||||
{ handle: 'filesystem' },
|
||||
{ src: '^/404$', dest: '/404', status: 404, check: true },
|
||||
];
|
||||
deepStrictEqual(actual, expected);
|
||||
});
|
||||
|
||||
test('mergeRoutes ensure beforeFiles comes after redirects (check)', () => {
|
||||
const userRoutes = [];
|
||||
const builds = [
|
||||
{
|
||||
use: '@vercel/next',
|
||||
entrypoint: 'package.json',
|
||||
routes: [
|
||||
{
|
||||
src: '^/home$',
|
||||
status: 301,
|
||||
headers: {
|
||||
Location: '/',
|
||||
},
|
||||
},
|
||||
{
|
||||
src: '^/hello$',
|
||||
dest: '/somewhere',
|
||||
check: true,
|
||||
override: true,
|
||||
},
|
||||
{
|
||||
handle: 'filesystem',
|
||||
},
|
||||
{
|
||||
src: '^/404$',
|
||||
dest: '/404',
|
||||
status: 404,
|
||||
check: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
const actual = mergeRoutes({ userRoutes, builds });
|
||||
const expected = [
|
||||
{ src: '^/home$', status: 301, headers: { Location: '/' } },
|
||||
{
|
||||
src: '^/hello$',
|
||||
dest: '/somewhere',
|
||||
check: true,
|
||||
override: true,
|
||||
},
|
||||
{ handle: 'filesystem' },
|
||||
{ src: '^/404$', dest: '/404', status: 404, check: true },
|
||||
];
|
||||
deepStrictEqual(actual, expected);
|
||||
});
|
||||
|
||||
@@ -247,7 +247,7 @@ test('convertRedirects', () => {
|
||||
},
|
||||
{
|
||||
type: 'header',
|
||||
key: 'x-pathname',
|
||||
key: 'X-Pathname',
|
||||
value: '(?<another>hello|world)',
|
||||
},
|
||||
],
|
||||
@@ -549,7 +549,7 @@ test('convertRewrites', () => {
|
||||
},
|
||||
{
|
||||
type: 'header',
|
||||
key: 'x-pathname',
|
||||
key: 'X-Pathname',
|
||||
value: '(?<another>hello|world)',
|
||||
},
|
||||
],
|
||||
@@ -893,7 +893,7 @@ test('convertHeaders', () => {
|
||||
},
|
||||
{
|
||||
type: 'header',
|
||||
key: 'x-pathname',
|
||||
key: 'X-Pathname',
|
||||
value: '(?<another>hello|world)',
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"github.com/aws/aws-lambda-go/events"
|
||||
"github.com/aws/aws-lambda-go/lambda"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Request struct {
|
||||
Host string `json:"host"`
|
||||
Path string `json:"path"`
|
||||
Method string `json:"method"`
|
||||
Headers map[string]string `json:"headers"`
|
||||
Encoding string `json:"encoding,omitempty"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
StatusCode int `json:"statusCode"`
|
||||
Headers map[string][]string `json:"headers"`
|
||||
Encoding string `json:"encoding,omitemtpy"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
|
||||
type ResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
statusCode int
|
||||
headers http.Header
|
||||
body *bytes.Buffer
|
||||
}
|
||||
|
||||
func (w *ResponseWriter) Header() http.Header {
|
||||
return w.headers
|
||||
}
|
||||
|
||||
func (w *ResponseWriter) Write(p []byte) (n int, err error) {
|
||||
n, err = w.body.Write(p)
|
||||
return
|
||||
}
|
||||
|
||||
func (w *ResponseWriter) WriteHeader(statusCode int) {
|
||||
w.statusCode = statusCode
|
||||
}
|
||||
|
||||
var userHandler http.Handler
|
||||
|
||||
func Serve(handler http.Handler, req *Request) (res Response, err error) {
|
||||
var body []byte
|
||||
if req.Encoding == "base64" {
|
||||
body, err = base64.StdEncoding.DecodeString(req.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
body = []byte(req.Body)
|
||||
}
|
||||
|
||||
r, err := http.NewRequest(req.Method, req.Path, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for k, v := range req.Headers {
|
||||
r.Header.Add(k, v)
|
||||
switch strings.ToLower(k) {
|
||||
case "host":
|
||||
// we need to set `Host` in the request
|
||||
// because Go likes to ignore the `Host` header
|
||||
// see https://github.com/golang/go/issues/7682
|
||||
r.Host = v
|
||||
case "content-length":
|
||||
contentLength, _ := strconv.ParseInt(v, 10, 64)
|
||||
r.ContentLength = contentLength
|
||||
case "x-forwarded-for":
|
||||
case "x-real-ip":
|
||||
r.RemoteAddr = v
|
||||
}
|
||||
}
|
||||
|
||||
var bodyBuf bytes.Buffer
|
||||
w := &ResponseWriter{
|
||||
nil,
|
||||
http.StatusOK,
|
||||
make(http.Header),
|
||||
&bodyBuf,
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
defer r.Body.Close()
|
||||
|
||||
headers := make(map[string][]string)
|
||||
for k, v := range w.headers {
|
||||
for _, s := range v {
|
||||
headers[k] = append(headers[k], s)
|
||||
}
|
||||
}
|
||||
|
||||
res = Response{
|
||||
StatusCode: w.statusCode,
|
||||
Headers: headers,
|
||||
Encoding: "base64",
|
||||
Body: base64.StdEncoding.EncodeToString(bodyBuf.Bytes()),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Maps the `APIGatewayProxyRequest` to a `Request` instance and invokes `Serve()`
|
||||
func handler(event events.APIGatewayProxyRequest) (res Response, err error) {
|
||||
var req Request
|
||||
err = json.Unmarshal([]byte(event.Body), &req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
res, err = Serve(userHandler, &req)
|
||||
return
|
||||
}
|
||||
|
||||
// Starts the Lambda
|
||||
func Start(h http.Handler) {
|
||||
userHandler = h
|
||||
lambda.Start(handler)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user