Compare commits

..

1 Commits

Author SHA1 Message Date
tknickman
d44ca0d7c5 update 2023-06-08 14:25:12 -04:00
162 changed files with 9567 additions and 4043 deletions

View File

@@ -0,0 +1,5 @@
---
"vercel": patch
---
[cli] vc env pull should add `.env*.local` to `.gitignore`

View File

@@ -0,0 +1,2 @@
---
---

View File

@@ -0,0 +1,5 @@
---
'vercel': patch
---
Add support for `vc dev` command with repo link

View File

@@ -0,0 +1,5 @@
---
"vercel": patch
---
Move readme copy logic to a helper function for `vc link`

View File

@@ -0,0 +1,5 @@
---
'vercel': patch
---
Add support for `vc pull` command with repo link

View File

@@ -0,0 +1,5 @@
---
'vercel': patch
---
Add support for `vc build` command with repo link

View File

@@ -8,25 +8,17 @@
"name": "nextjs",
"version": "0.1.0",
"dependencies": {
"eslint": "8.44.0",
"eslint-config-next": "13.4.9",
"next": "13.4.9",
"eslint": "8.41.0",
"eslint-config-next": "13.4.4",
"next": "13.4.4",
"react": "18.2.0",
"react-dom": "18.2.0"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz",
"integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/@babel/runtime": {
"version": "7.22.6",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz",
"integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==",
"version": "7.21.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz",
"integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==",
"dependencies": {
"regenerator-runtime": "^0.13.11"
},
@@ -57,13 +49,13 @@
}
},
"node_modules/@eslint/eslintrc": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.0.tgz",
"integrity": "sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==",
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz",
"integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==",
"dependencies": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
"espree": "^9.6.0",
"espree": "^9.5.2",
"globals": "^13.19.0",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
@@ -79,17 +71,17 @@
}
},
"node_modules/@eslint/js": {
"version": "8.44.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.44.0.tgz",
"integrity": "sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==",
"version": "8.41.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.41.0.tgz",
"integrity": "sha512-LxcyMGxwmTh2lY9FwHPGWOHmYFCZvbrFCBZL4FzSSsxsRPuhrYUg/49/0KDfW8tnIEaEHtfmn6+NPN+1DqaNmA==",
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz",
"integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==",
"version": "0.11.8",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
"integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==",
"dependencies": {
"@humanwhocodes/object-schema": "^1.2.1",
"debug": "^4.1.1",
@@ -117,22 +109,22 @@
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA=="
},
"node_modules/@next/env": {
"version": "13.4.9",
"resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.9.tgz",
"integrity": "sha512-vuDRK05BOKfmoBYLNi2cujG2jrYbEod/ubSSyqgmEx9n/W3eZaJQdRNhTfumO+qmq/QTzLurW487n/PM/fHOkw=="
"version": "13.4.4",
"resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.4.tgz",
"integrity": "sha512-q/y7VZj/9YpgzDe64Zi6rY1xPizx80JjlU2BTevlajtaE3w1LqweH1gGgxou2N7hdFosXHjGrI4OUvtFXXhGLg=="
},
"node_modules/@next/eslint-plugin-next": {
"version": "13.4.9",
"resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-13.4.9.tgz",
"integrity": "sha512-nDtGpa992tNyAkT/KmSMy7QkHfNZmGCBYhHtafU97DubqxzNdvLsqRtliQ4FU04CysRCtvP2hg8rRC1sAKUTUA==",
"version": "13.4.4",
"resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-13.4.4.tgz",
"integrity": "sha512-5jnh7q6I15efnjR/rR+/TGTc9hn53g3JTbEjAMjmeQiExKqEUgIXqrHI5zlTNlNyzCPkBB860/ctxXheZaF2Vw==",
"dependencies": {
"glob": "7.1.7"
}
},
"node_modules/@next/swc-darwin-arm64": {
"version": "13.4.9",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.9.tgz",
"integrity": "sha512-TVzGHpZoVBk3iDsTOQA/R6MGmFp0+17SWXMEWd6zG30AfuELmSSMe2SdPqxwXU0gbpWkJL1KgfLzy5ReN0crqQ==",
"version": "13.4.4",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.4.tgz",
"integrity": "sha512-xfjgXvp4KalNUKZMHmsFxr1Ug+aGmmO6NWP0uoh4G3WFqP/mJ1xxfww0gMOeMeSq/Jyr5k7DvoZ2Pv+XOITTtw==",
"cpu": [
"arm64"
],
@@ -145,9 +137,9 @@
}
},
"node_modules/@next/swc-darwin-x64": {
"version": "13.4.9",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.9.tgz",
"integrity": "sha512-aSfF1fhv28N2e7vrDZ6zOQ+IIthocfaxuMWGReB5GDriF0caTqtHttAvzOMgJgXQtQx6XhyaJMozLTSEXeNN+A==",
"version": "13.4.4",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.4.tgz",
"integrity": "sha512-ZY9Ti1hkIwJsxGus3nlubIkvYyB0gNOYxKrfsOrLEqD0I2iCX8D7w8v6QQZ2H+dDl6UT29oeEUdDUNGk4UEpfg==",
"cpu": [
"x64"
],
@@ -160,9 +152,9 @@
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
"version": "13.4.9",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.9.tgz",
"integrity": "sha512-JhKoX5ECzYoTVyIy/7KykeO4Z2lVKq7HGQqvAH+Ip9UFn1MOJkOnkPRB7v4nmzqAoY+Je05Aj5wNABR1N18DMg==",
"version": "13.4.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.4.tgz",
"integrity": "sha512-+KZnDeMShYkpkqAvGCEDeqYTRADJXc6SY1jWXz+Uo6qWQO/Jd9CoyhTJwRSxvQA16MoYzvILkGaDqirkRNctyA==",
"cpu": [
"arm64"
],
@@ -175,9 +167,9 @@
}
},
"node_modules/@next/swc-linux-arm64-musl": {
"version": "13.4.9",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.9.tgz",
"integrity": "sha512-OOn6zZBIVkm/4j5gkPdGn4yqQt+gmXaLaSjRSO434WplV8vo2YaBNbSHaTM9wJpZTHVDYyjzuIYVEzy9/5RVZw==",
"version": "13.4.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.4.tgz",
"integrity": "sha512-evC1twrny2XDT4uOftoubZvW3EG0zs0ZxMwEtu/dDGVRO5n5pT48S8qqEIBGBUZYu/Xx4zzpOkIxx1vpWdE+9A==",
"cpu": [
"arm64"
],
@@ -190,9 +182,9 @@
}
},
"node_modules/@next/swc-linux-x64-gnu": {
"version": "13.4.9",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.9.tgz",
"integrity": "sha512-iA+fJXFPpW0SwGmx/pivVU+2t4zQHNOOAr5T378PfxPHY6JtjV6/0s1vlAJUdIHeVpX98CLp9k5VuKgxiRHUpg==",
"version": "13.4.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.4.tgz",
"integrity": "sha512-PX706XcCHr2FfkyhP2lpf+pX/tUvq6/ke7JYnnr0ykNdEMo+sb7cC/o91gnURh4sPYSiZJhsF2gbIqg9rciOHQ==",
"cpu": [
"x64"
],
@@ -205,9 +197,9 @@
}
},
"node_modules/@next/swc-linux-x64-musl": {
"version": "13.4.9",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.9.tgz",
"integrity": "sha512-rlNf2WUtMM+GAQrZ9gMNdSapkVi3koSW3a+dmBVp42lfugWVvnyzca/xJlN48/7AGx8qu62WyO0ya1ikgOxh6A==",
"version": "13.4.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.4.tgz",
"integrity": "sha512-TKUUx3Ftd95JlHV6XagEnqpT204Y+IsEa3awaYIjayn0MOGjgKZMZibqarK3B1FsMSPaieJf2FEAcu9z0yT5aA==",
"cpu": [
"x64"
],
@@ -220,9 +212,9 @@
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
"version": "13.4.9",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.9.tgz",
"integrity": "sha512-5T9ybSugXP77nw03vlgKZxD99AFTHaX8eT1ayKYYnGO9nmYhJjRPxcjU5FyYI+TdkQgEpIcH7p/guPLPR0EbKA==",
"version": "13.4.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.4.tgz",
"integrity": "sha512-FP8AadgSq4+HPtim7WBkCMGbhr5vh9FePXiWx9+YOdjwdQocwoCK5ZVC3OW8oh3TWth6iJ0AXJ/yQ1q1cwSZ3A==",
"cpu": [
"arm64"
],
@@ -235,9 +227,9 @@
}
},
"node_modules/@next/swc-win32-ia32-msvc": {
"version": "13.4.9",
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.9.tgz",
"integrity": "sha512-ojZTCt1lP2ucgpoiFgrFj07uq4CZsq4crVXpLGgQfoFq00jPKRPgesuGPaz8lg1yLfvafkU3Jd1i8snKwYR3LA==",
"version": "13.4.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.4.tgz",
"integrity": "sha512-3WekVmtuA2MCdcAOrgrI+PuFiFURtSyyrN1I3UPtS0ckR2HtLqyqmS334Eulf15g1/bdwMteePdK363X/Y9JMg==",
"cpu": [
"ia32"
],
@@ -250,9 +242,9 @@
}
},
"node_modules/@next/swc-win32-x64-msvc": {
"version": "13.4.9",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.9.tgz",
"integrity": "sha512-QbT03FXRNdpuL+e9pLnu+XajZdm/TtIXVYY4lA9t+9l0fLZbHXDYEKitAqxrOj37o3Vx5ufxiRAniaIebYDCgw==",
"version": "13.4.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.4.tgz",
"integrity": "sha512-AHRITu/CrlQ+qzoqQtEMfaTu7GHaQ6bziQln/pVWpOYC1wU+Mq6VQQFlsDtMCnDztPZtppAXdvvbNS7pcfRzlw==",
"cpu": [
"x64"
],
@@ -297,16 +289,16 @@
}
},
"node_modules/@pkgr/utils": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz",
"integrity": "sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==",
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.1.tgz",
"integrity": "sha512-JOqwkgFEyi+OROIyq7l4Jy28h/WwhDnG/cPkXG2Z1iFbubB6jsHW1NDvmyOzTBxHr3yg68YGirmh1JUgMqa+9w==",
"dependencies": {
"cross-spawn": "^7.0.3",
"fast-glob": "^3.3.0",
"fast-glob": "^3.2.12",
"is-glob": "^4.0.3",
"open": "^9.1.0",
"picocolors": "^1.0.0",
"tslib": "^2.6.0"
"tslib": "^2.5.0"
},
"engines": {
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
@@ -316,9 +308,9 @@
}
},
"node_modules/@rushstack/eslint-patch": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.3.2.tgz",
"integrity": "sha512-V+MvGwaHH03hYhY+k6Ef/xKd6RYlc4q8WBx+2ANmipHJcKuktNcI/NgEsJgdSUF6Lw32njT6OnrRsKYCdgHjYw=="
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.3.0.tgz",
"integrity": "sha512-IthPJsJR85GhOkp3Hvp8zFOPK5ynKn6STyHa/WZpioK7E1aYDiBzpqQPrngc14DszIUkIrdd3k9Iu0XSzlP/1w=="
},
"node_modules/@swc/helpers": {
"version": "0.5.1",
@@ -334,13 +326,13 @@
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="
},
"node_modules/@typescript-eslint/parser": {
"version": "5.61.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.61.0.tgz",
"integrity": "sha512-yGr4Sgyh8uO6fSi9hw3jAFXNBHbCtKKFMdX2IkT3ZqpKmtAq3lHS4ixB/COFuAIJpwl9/AqF7j72ZDWYKmIfvg==",
"version": "5.59.7",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.7.tgz",
"integrity": "sha512-VhpsIEuq/8i5SF+mPg9jSdIwgMBBp0z9XqjiEay+81PYLJuroN+ET1hM5IhkiYMJd9MkTz8iJLt7aaGAgzWUbQ==",
"dependencies": {
"@typescript-eslint/scope-manager": "5.61.0",
"@typescript-eslint/types": "5.61.0",
"@typescript-eslint/typescript-estree": "5.61.0",
"@typescript-eslint/scope-manager": "5.59.7",
"@typescript-eslint/types": "5.59.7",
"@typescript-eslint/typescript-estree": "5.59.7",
"debug": "^4.3.4"
},
"engines": {
@@ -360,12 +352,12 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "5.61.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.61.0.tgz",
"integrity": "sha512-W8VoMjoSg7f7nqAROEmTt6LoBpn81AegP7uKhhW5KzYlehs8VV0ZW0fIDVbcZRcaP3aPSW+JZFua+ysQN+m/Nw==",
"version": "5.59.7",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.7.tgz",
"integrity": "sha512-FL6hkYWK9zBGdxT2wWEd2W8ocXMu3K94i3gvMrjXpx+koFYdYV7KprKfirpgY34vTGzEPPuKoERpP8kD5h7vZQ==",
"dependencies": {
"@typescript-eslint/types": "5.61.0",
"@typescript-eslint/visitor-keys": "5.61.0"
"@typescript-eslint/types": "5.59.7",
"@typescript-eslint/visitor-keys": "5.59.7"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -376,9 +368,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "5.61.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.61.0.tgz",
"integrity": "sha512-ldyueo58KjngXpzloHUog/h9REmHl59G1b3a5Sng1GfBo14BkS3ZbMEb3693gnP1k//97lh7bKsp6/V/0v1veQ==",
"version": "5.59.7",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.7.tgz",
"integrity": "sha512-UnVS2MRRg6p7xOSATscWkKjlf/NDKuqo5TdbWck6rIRZbmKpVNTLALzNvcjIfHBE7736kZOFc/4Z3VcZwuOM/A==",
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
@@ -388,12 +380,12 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "5.61.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.61.0.tgz",
"integrity": "sha512-Fud90PxONnnLZ36oR5ClJBLTLfU4pIWBmnvGwTbEa2cXIqj70AEDEmOmpkFComjBZ/037ueKrOdHuYmSFVD7Rw==",
"version": "5.59.7",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.7.tgz",
"integrity": "sha512-4A1NtZ1I3wMN2UGDkU9HMBL+TIQfbrh4uS0WDMMpf3xMRursDbqEf1ahh6vAAe3mObt8k3ZATnezwG4pdtWuUQ==",
"dependencies": {
"@typescript-eslint/types": "5.61.0",
"@typescript-eslint/visitor-keys": "5.61.0",
"@typescript-eslint/types": "5.59.7",
"@typescript-eslint/visitor-keys": "5.59.7",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@@ -414,11 +406,11 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "5.61.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.61.0.tgz",
"integrity": "sha512-50XQ5VdbWrX06mQXhy93WywSFZZGsv3EOjq+lqp6WC2t+j3mb6A9xYVdrRxafvK88vg9k9u+CT4l6D8PEatjKg==",
"version": "5.59.7",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.7.tgz",
"integrity": "sha512-tyN+X2jvMslUszIiYbF0ZleP+RqQsFVpGrKI6e0Eet1w8WmhsAtmzaqm8oM8WJQ1ysLwhnsK/4hYHJjOgJVfQQ==",
"dependencies": {
"@typescript-eslint/types": "5.61.0",
"@typescript-eslint/types": "5.59.7",
"eslint-visitor-keys": "^3.3.0"
},
"engines": {
@@ -430,9 +422,9 @@
}
},
"node_modules/acorn": {
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
"integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==",
"version": "8.8.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
"integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
"bin": {
"acorn": "bin/acorn"
},
@@ -491,11 +483,11 @@
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"node_modules/aria-query": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz",
"integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==",
"dependencies": {
"dequal": "^2.0.3"
"deep-equal": "^2.0.5"
}
},
"node_modules/array-buffer-byte-length": {
@@ -607,11 +599,11 @@
}
},
"node_modules/axobject-query": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz",
"integrity": "sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==",
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz",
"integrity": "sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==",
"dependencies": {
"dequal": "^2.0.3"
"deep-equal": "^2.0.5"
}
},
"node_modules/balanced-match": {
@@ -704,9 +696,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001512",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001512.tgz",
"integrity": "sha512-2S9nK0G/mE+jasCUsMPlARhRCts1ebcp2Ji8Y8PWi4NDE1iRdLCnEPHkEfeBrGC45L4isBx5ur3IQ6yTE2mRZw==",
"version": "1.0.30001489",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001489.tgz",
"integrity": "sha512-x1mgZEXK8jHIfAxm+xgdpHpk50IN3z3q3zP261/WS+uvePxW8izXuCu6AHz0lkuYTlATDehiZ/tNyYBdSQsOUQ==",
"funding": [
{
"type": "opencollective",
@@ -797,6 +789,34 @@
}
}
},
"node_modules/deep-equal": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.1.tgz",
"integrity": "sha512-lKdkdV6EOGoVn65XaOsPdH4rMxTZOnmFyuIkMjM1i5HHCbfjC97dawgTAy0deYNfuqUqW+Q5VrVaQYtUpSd6yQ==",
"dependencies": {
"array-buffer-byte-length": "^1.0.0",
"call-bind": "^1.0.2",
"es-get-iterator": "^1.1.3",
"get-intrinsic": "^1.2.0",
"is-arguments": "^1.1.1",
"is-array-buffer": "^3.0.2",
"is-date-object": "^1.0.5",
"is-regex": "^1.1.4",
"is-shared-array-buffer": "^1.0.2",
"isarray": "^2.0.5",
"object-is": "^1.1.5",
"object-keys": "^1.1.1",
"object.assign": "^4.1.4",
"regexp.prototype.flags": "^1.5.0",
"side-channel": "^1.0.4",
"which-boxed-primitive": "^1.0.2",
"which-collection": "^1.0.1",
"which-typed-array": "^1.1.9"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -860,14 +880,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
"engines": {
"node": ">=6"
}
},
"node_modules/dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -896,9 +908,9 @@
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
},
"node_modules/enhanced-resolve": {
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz",
"integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==",
"version": "5.14.1",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.14.1.tgz",
"integrity": "sha512-Vklwq2vDKtl0y/vtwjSesgJ5MYS7Etuk5txS8VdKL4AOS1aUlD96zqIfsOSLQsdv3xgMRbtkWM8eG9XDfKUPow==",
"dependencies": {
"graceful-fs": "^4.2.4",
"tapable": "^2.2.0"
@@ -954,6 +966,25 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/es-get-iterator": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz",
"integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==",
"dependencies": {
"call-bind": "^1.0.2",
"get-intrinsic": "^1.1.3",
"has-symbols": "^1.0.3",
"is-arguments": "^1.1.1",
"is-map": "^2.0.2",
"is-set": "^2.0.2",
"is-string": "^1.0.7",
"isarray": "^2.0.5",
"stop-iteration-iterator": "^1.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/es-set-tostringtag": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz",
@@ -1003,15 +1034,15 @@
}
},
"node_modules/eslint": {
"version": "8.44.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.44.0.tgz",
"integrity": "sha512-0wpHoUbDUHgNCyvFB5aXLiQVfK9B0at6gUvzy83k4kAsQ/u769TQDX6iKC+aO4upIHO9WSaA3QoXYQDHbNwf1A==",
"version": "8.41.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.41.0.tgz",
"integrity": "sha512-WQDQpzGBOP5IrXPo4Hc0814r4/v2rrIsB0rhT7jtunIalgg6gYXWhRMOejVO8yH21T/FGaxjmFjBMNqcIlmH1Q==",
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.4.0",
"@eslint/eslintrc": "^2.1.0",
"@eslint/js": "8.44.0",
"@humanwhocodes/config-array": "^0.11.10",
"@eslint/eslintrc": "^2.0.3",
"@eslint/js": "8.41.0",
"@humanwhocodes/config-array": "^0.11.8",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
"ajv": "^6.10.0",
@@ -1022,7 +1053,7 @@
"escape-string-regexp": "^4.0.0",
"eslint-scope": "^7.2.0",
"eslint-visitor-keys": "^3.4.1",
"espree": "^9.6.0",
"espree": "^9.5.2",
"esquery": "^1.4.2",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
@@ -1042,7 +1073,7 @@
"lodash.merge": "^4.6.2",
"minimatch": "^3.1.2",
"natural-compare": "^1.4.0",
"optionator": "^0.9.3",
"optionator": "^0.9.1",
"strip-ansi": "^6.0.1",
"strip-json-comments": "^3.1.0",
"text-table": "^0.2.0"
@@ -1058,11 +1089,11 @@
}
},
"node_modules/eslint-config-next": {
"version": "13.4.9",
"resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-13.4.9.tgz",
"integrity": "sha512-0fLtKRR268NArpqeXXwnLgMXPvF64YESQvptVg+RMLCaijKm3FICN9Y7Jc1p2o+yrWwE4DufJXDM/Vo53D1L7g==",
"version": "13.4.4",
"resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-13.4.4.tgz",
"integrity": "sha512-z/PMbm6L0iC/fwISULxe8IVy4DtNqZk2wQY711o35klenq70O6ns82A8yuMVCFjHC0DIyB2lyugesRtuk9u8dQ==",
"dependencies": {
"@next/eslint-plugin-next": "13.4.9",
"@next/eslint-plugin-next": "13.4.4",
"@rushstack/eslint-patch": "^1.1.3",
"@typescript-eslint/parser": "^5.42.0",
"eslint-import-resolver-node": "^0.3.6",
@@ -1070,7 +1101,7 @@
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-react": "^7.31.7",
"eslint-plugin-react-hooks": "5.0.0-canary-7118f5dd7-20230705"
"eslint-plugin-react-hooks": "^4.5.0"
},
"peerDependencies": {
"eslint": "^7.23.0 || ^8.0.0",
@@ -1126,13 +1157,13 @@
}
},
"node_modules/eslint-import-resolver-typescript/node_modules/globby": {
"version": "13.2.2",
"resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz",
"integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==",
"version": "13.1.4",
"resolved": "https://registry.npmjs.org/globby/-/globby-13.1.4.tgz",
"integrity": "sha512-iui/IiiW+QrJ1X1hKH5qwlMQyv34wJAYwH1vrf8b9kBA4sNiif3gKsMHa+BrdnOpEudWjpotfa7LrTzB1ERS/g==",
"dependencies": {
"dir-glob": "^3.0.1",
"fast-glob": "^3.3.0",
"ignore": "^5.2.4",
"fast-glob": "^3.2.11",
"ignore": "^5.2.0",
"merge2": "^1.4.1",
"slash": "^4.0.0"
},
@@ -1299,9 +1330,9 @@
}
},
"node_modules/eslint-plugin-react-hooks": {
"version": "5.0.0-canary-7118f5dd7-20230705",
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.0.0-canary-7118f5dd7-20230705.tgz",
"integrity": "sha512-AZYbMo/NW9chdL7vk6HQzQhT+PvTAEVqWk9ziruUoW2kAOcN5qNyelv70e0F1VNQAbvutOC9oc+xfWycI9FxDw==",
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz",
"integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==",
"engines": {
"node": ">=10"
},
@@ -1371,11 +1402,11 @@
}
},
"node_modules/espree": {
"version": "9.6.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.6.0.tgz",
"integrity": "sha512-1FH/IiruXZ84tpUlm0aCUEwMl2Ho5ilqVh0VvQXw+byAz/4SAciyHLlfmL5WYqsvD38oymdUwBss0LtK8m4s/A==",
"version": "9.5.2",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz",
"integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==",
"dependencies": {
"acorn": "^8.9.0",
"acorn": "^8.8.0",
"acorn-jsx": "^5.3.2",
"eslint-visitor-keys": "^3.4.1"
},
@@ -1452,9 +1483,9 @@
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"node_modules/fast-glob": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.0.tgz",
"integrity": "sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==",
"version": "3.2.12",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
"integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
@@ -1633,12 +1664,9 @@
}
},
"node_modules/get-tsconfig": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.6.2.tgz",
"integrity": "sha512-E5XrT4CbbXcXWy+1jChlZmrmCwd5KGx502kDCXJJ7y898TtWW9FwoG5HfOLVRKmlmDGkWN2HM9Ho+/Y8F0sJDg==",
"dependencies": {
"resolve-pkg-maps": "^1.0.0"
},
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.5.0.tgz",
"integrity": "sha512-MjhiaIWCJ1sAU4pIQ5i5OfOuHHxVo1oYeNsWTON7jxYkod8pHocXeh+SSbmu5OZZZK73B6cbJ2XADzXehLyovQ==",
"funding": {
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
@@ -1673,11 +1701,6 @@
"node": ">=10.13.0"
}
},
"node_modules/glob-to-regexp": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="
},
"node_modules/globals": {
"version": "13.20.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz",
@@ -1886,6 +1909,21 @@
"node": ">= 0.4"
}
},
"node_modules/is-arguments": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
"integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
"dependencies": {
"call-bind": "^1.0.2",
"has-tostringtag": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-array-buffer": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
@@ -2011,6 +2049,14 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-map": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz",
"integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-negative-zero": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
@@ -2067,6 +2113,14 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-set": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz",
"integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-shared-array-buffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
@@ -2135,6 +2189,14 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-weakmap": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz",
"integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-weakref": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
@@ -2146,6 +2208,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-weakset": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz",
"integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==",
"dependencies": {
"call-bind": "^1.0.2",
"get-intrinsic": "^1.1.1"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-wsl": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
@@ -2171,6 +2245,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/isarray": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -2214,14 +2293,12 @@
}
},
"node_modules/jsx-ast-utils": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.4.tgz",
"integrity": "sha512-fX2TVdCViod6HwKEtSWGHs57oFhVfCMwieb9PuRDgjDPh5XeqJiHFFFJCHxU5cnTc3Bu/GRL+kPiFmw8XWOfKw==",
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz",
"integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==",
"dependencies": {
"array-includes": "^3.1.6",
"array.prototype.flat": "^1.3.1",
"object.assign": "^4.1.4",
"object.values": "^1.1.6"
"array-includes": "^3.1.5",
"object.assign": "^4.1.3"
},
"engines": {
"node": ">=4.0"
@@ -2376,17 +2453,16 @@
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="
},
"node_modules/next": {
"version": "13.4.9",
"resolved": "https://registry.npmjs.org/next/-/next-13.4.9.tgz",
"integrity": "sha512-vtefFm/BWIi/eWOqf1GsmKG3cjKw1k3LjuefKRcL3iiLl3zWzFdPG3as6xtxrGO6gwTzzaO1ktL4oiHt/uvTjA==",
"version": "13.4.4",
"resolved": "https://registry.npmjs.org/next/-/next-13.4.4.tgz",
"integrity": "sha512-C5S0ysM0Ily9McL4Jb48nOQHT1BukOWI59uC3X/xCMlYIh9rJZCv7nzG92J6e1cOBqQbKovlpgvHWFmz4eKKEA==",
"dependencies": {
"@next/env": "13.4.9",
"@next/env": "13.4.4",
"@swc/helpers": "0.5.1",
"busboy": "1.6.0",
"caniuse-lite": "^1.0.30001406",
"postcss": "8.4.14",
"styled-jsx": "5.1.1",
"watchpack": "2.4.0",
"zod": "3.21.4"
},
"bin": {
@@ -2396,15 +2472,15 @@
"node": ">=16.8.0"
},
"optionalDependencies": {
"@next/swc-darwin-arm64": "13.4.9",
"@next/swc-darwin-x64": "13.4.9",
"@next/swc-linux-arm64-gnu": "13.4.9",
"@next/swc-linux-arm64-musl": "13.4.9",
"@next/swc-linux-x64-gnu": "13.4.9",
"@next/swc-linux-x64-musl": "13.4.9",
"@next/swc-win32-arm64-msvc": "13.4.9",
"@next/swc-win32-ia32-msvc": "13.4.9",
"@next/swc-win32-x64-msvc": "13.4.9"
"@next/swc-darwin-arm64": "13.4.4",
"@next/swc-darwin-x64": "13.4.4",
"@next/swc-linux-arm64-gnu": "13.4.4",
"@next/swc-linux-arm64-musl": "13.4.4",
"@next/swc-linux-x64-gnu": "13.4.4",
"@next/swc-linux-x64-musl": "13.4.4",
"@next/swc-win32-arm64-msvc": "13.4.4",
"@next/swc-win32-ia32-msvc": "13.4.4",
"@next/swc-win32-x64-msvc": "13.4.4"
},
"peerDependencies": {
"@opentelemetry/api": "^1.1.0",
@@ -2466,6 +2542,21 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/object-is": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz",
"integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==",
"dependencies": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
@@ -2588,16 +2679,16 @@
}
},
"node_modules/optionator": {
"version": "0.9.3",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
"integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==",
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
"integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
"dependencies": {
"@aashutoshrathi/word-wrap": "^1.2.3",
"deep-is": "^0.1.3",
"fast-levenshtein": "^2.0.6",
"levn": "^0.4.1",
"prelude-ls": "^1.2.1",
"type-check": "^0.4.0"
"type-check": "^0.4.0",
"word-wrap": "^1.2.3"
},
"engines": {
"node": ">= 0.8.0"
@@ -2836,14 +2927,6 @@
"node": ">=4"
}
},
"node_modules/resolve-pkg-maps": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
"funding": {
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
},
"node_modules/reusify": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
@@ -3007,9 +3090,9 @@
}
},
"node_modules/semver": {
"version": "7.5.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz",
"integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==",
"version": "7.5.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz",
"integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==",
"dependencies": {
"lru-cache": "^6.0.0"
},
@@ -3073,6 +3156,17 @@
"node": ">=0.10.0"
}
},
"node_modules/stop-iteration-iterator": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz",
"integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==",
"dependencies": {
"internal-slot": "^1.0.4"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/streamsearch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
@@ -3288,9 +3382,9 @@
}
},
"node_modules/tslib": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz",
"integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA=="
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.2.tgz",
"integrity": "sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA=="
},
"node_modules/tsutils": {
"version": "3.21.0",
@@ -3347,16 +3441,16 @@
}
},
"node_modules/typescript": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz",
"integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==",
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz",
"integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
"node": ">=12.20"
}
},
"node_modules/unbox-primitive": {
@@ -3389,18 +3483,6 @@
"punycode": "^2.1.0"
}
},
"node_modules/watchpack": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
"integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==",
"dependencies": {
"glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.1.2"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -3430,6 +3512,20 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/which-collection": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz",
"integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==",
"dependencies": {
"is-map": "^2.0.1",
"is-set": "^2.0.1",
"is-weakmap": "^2.0.1",
"is-weakset": "^2.0.1"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/which-typed-array": {
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz",
@@ -3449,6 +3545,14 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",

View File

@@ -9,9 +9,9 @@
"lint": "next lint"
},
"dependencies": {
"eslint": "8.44.0",
"eslint-config-next": "13.4.9",
"next": "13.4.9",
"eslint": "8.41.0",
"eslint-config-next": "13.4.4",
"next": "13.4.4",
"react": "18.2.0",
"react-dom": "18.2.0"
}

View File

@@ -9,6 +9,6 @@
},
"devDependencies": {
"@types/jest": "27.4.1",
"@vercel/frameworks": "1.4.3"
"@vercel/frameworks": "1.4.2"
}
}

View File

@@ -1,4 +1,3 @@
/** @type {import('eslint').Linter.Config} */
module.exports = {
extends: ["@remix-run/eslint-config", "@remix-run/eslint-config/node"],
};

View File

@@ -1,12 +1,11 @@
node_modules
/.cache
/build
/public/build
.cache
.env
.vercel
.output
/build/
/public/build
/api/index.js
/api/index.js.map

View File

@@ -1,4 +1,4 @@
import { cssBundleHref } from "@remix-run/css-bundle";
import type { MetaFunction } from "@vercel/remix";
import {
Links,
LiveReload,
@@ -8,18 +8,17 @@ import {
ScrollRestoration,
} from "@remix-run/react";
import { Analytics } from "@vercel/analytics/react";
import type { LinksFunction } from "@vercel/remix";
export const links: LinksFunction = () => [
...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : []),
];
export const meta: MetaFunction = () => ({
charset: "utf-8",
title: "New Remix App",
viewport: "width=device-width,initial-scale=1",
});
export default function App() {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<Meta />
<Links />
</head>

View File

@@ -1,9 +1,5 @@
import type { V2_MetaFunction } from "@vercel/remix";
export const config = { runtime: "edge" };
export const meta: V2_MetaFunction = () => [{ title: "Remix@Edge | New Remix App" }];
export default function Edge() {
return (
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.4" }}>

View File

@@ -1,7 +1,3 @@
import type { V2_MetaFunction } from "@vercel/remix";
export const meta: V2_MetaFunction = () => [{ title: "New Remix App" }];
export default function Index() {
return (
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.4" }}>

View File

@@ -3,28 +3,24 @@
"sideEffects": false,
"scripts": {
"build": "remix build",
"dev": "remix dev",
"start": "remix-serve build",
"typecheck": "tsc"
"dev": "remix dev"
},
"dependencies": {
"@remix-run/css-bundle": "^1.18.0",
"@remix-run/node": "^1.18.0",
"@remix-run/react": "^1.18.0",
"@remix-run/serve": "^1.18.0",
"@remix-run/node": "^1.15.0",
"@remix-run/react": "^1.15.0",
"@remix-run/serve": "^1.15.0",
"@vercel/analytics": "^0.1.11",
"@vercel/remix": "^1.18.0",
"isbot": "^3.6.8",
"@vercel/remix": "^1.15.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@remix-run/dev": "^1.18.0",
"@remix-run/eslint-config": "^1.18.0",
"@remix-run/dev": "^1.15.0",
"@remix-run/eslint-config": "^1.15.0",
"@types/react": "^18.0.25",
"@types/react-dom": "^18.0.11",
"eslint": "^8.28.0",
"typescript": "^5.1.3"
"typescript": "^4.9.3"
},
"engines": {
"node": ">=14"

7492
examples/remix/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,8 @@
/** @type {import('@remix-run/dev').AppConfig} */
/**
* @type {import('@remix-run/dev').AppConfig}
*/
module.exports = {
future: {
v2_dev: true,
v2_errorBoundary: true,
v2_headers: true,
v2_meta: true,
v2_normalizeFormMethod: true,
v2_routeConvention: true,
},
ignoredRouteFiles: ["**/.*"],
serverModuleFormat: "cjs",
ignoredRouteFiles: ['**/.*'],
// appDirectory: "app",
// assetsBuildDirectory: "public/build",
// serverBuildPath: "build/index.js",

View File

@@ -2,7 +2,7 @@
# Saber Example
This directory is a brief example of a [Saber](https://saber.egoist.dev) site that can be deployed to Vercel with zero configuration.
This directory is a brief example of a [Saber](https://saber.land) site that can be deployed to Vercel with zero configuration.
## Deploy Your Own

View File

@@ -5,4 +5,4 @@ layout: page
This is the Saber port of the base Jekyll theme. Check out the [GitHub project](https://github.com/egoist/saber-theme-minima) for detailed usages.
You can find out more info about customizing your theme, as well as basic Saber usage documentation at https://saber.egoist.dev
You can find out more info about customizing your theme, as well as basic Saber usage documentation at https://saber.land

View File

@@ -1,18 +1,5 @@
# @vercel-internals/constants
## 1.0.4
### Patch Changes
- Create new help output and arg parsing for deploy command ([#10090](https://github.com/vercel/vercel/pull/10090))
## 1.0.3
### Patch Changes
- Updated dependencies [[`346892210`](https://github.com/vercel/vercel/commit/3468922108f411482a72acd0331f0f2ee52a6d4c)]:
- @vercel/build-utils@6.8.0
## 1.0.2
### Patch Changes

View File

@@ -1,13 +1,16 @@
{
"private": true,
"name": "@vercel-internals/constants",
"version": "1.0.4",
"version": "1.0.2",
"types": "dist/index.d.ts",
"main": "dist/index.js",
"scripts": {
"build": "tsc -p tsconfig.json"
},
"dependencies": {},
"dependencies": {
"@vercel/build-utils": "6.7.5",
"@vercel/routing-utils": "2.2.1"
},
"devDependencies": {
"@vercel-internals/tsconfig": "1.0.0",
"@vercel/style-guide": "4.0.2",

View File

@@ -3,7 +3,3 @@ export const PROJECT_ENV_TARGET = [
'preview',
'development',
] as const;
export const LOGO = '▲' as const;
export const NAME = 'vercel' as const;
export const TITLE = 'Vercel' as const;

View File

@@ -1,28 +1,5 @@
# @vercel-internals/types
## 1.0.5
### Patch Changes
- Updated dependencies [[`0750517af`](https://github.com/vercel/vercel/commit/0750517af99aea41410d4f1f772ce427699554e7)]:
- @vercel/build-utils@6.8.2
## 1.0.4
### Patch Changes
- Updated dependencies [[`7021279b2`](https://github.com/vercel/vercel/commit/7021279b284f314a4d1bdbb4306b4c22291efa08), [`718bbd365`](https://github.com/vercel/vercel/commit/718bbd365a50271a980bdca231ca801a0eead32b)]:
- @vercel/build-utils@6.8.1
- @vercel-internals/constants@1.0.4
## 1.0.3
### Patch Changes
- Updated dependencies [[`346892210`](https://github.com/vercel/vercel/commit/3468922108f411482a72acd0331f0f2ee52a6d4c)]:
- @vercel/build-utils@6.8.0
- @vercel-internals/constants@1.0.3
## 1.0.2
### Patch Changes

View File

@@ -1,6 +1,5 @@
import type { BuilderFunctions } from '@vercel/build-utils';
import type { Readable, Writable } from 'stream';
import type * as tty from 'tty';
import type { Route } from '@vercel/routing-utils';
import { PROJECT_ENV_TARGET } from '@vercel-internals/constants';
@@ -632,6 +631,6 @@ export interface WritableTTY extends Writable {
export interface Stdio {
stdin: ReadableTTY;
stdout: tty.WriteStream;
stderr: tty.WriteStream;
stdout: WritableTTY;
stderr: WritableTTY;
}

View File

@@ -1,13 +1,13 @@
{
"private": true,
"name": "@vercel-internals/types",
"version": "1.0.5",
"version": "1.0.2",
"types": "index.d.ts",
"main": "index.d.ts",
"dependencies": {
"@types/node": "14.14.31",
"@vercel-internals/constants": "1.0.4",
"@vercel/build-utils": "6.8.2",
"@vercel-internals/constants": "1.0.2",
"@vercel/build-utils": "6.7.5",
"@vercel/routing-utils": "2.2.1"
},
"devDependencies": {

13
lerna.json Normal file
View File

@@ -0,0 +1,13 @@
{
"npmClient": "pnpm",
"useWorkspaces": true,
"packages": ["packages/*"],
"command": {
"publish": {
"npmClient": "npm",
"allowBranch": ["main"],
"registry": "https://registry.npmjs.org/"
}
},
"version": "independent"
}

View File

@@ -32,7 +32,7 @@
"source-map-support": "0.5.12",
"ts-eager": "2.0.2",
"ts-jest": "29.1.0",
"turbo": "1.10.7",
"turbo": "1.10.1",
"typescript": "4.9.5"
},
"scripts": {

View File

@@ -1,23 +1,5 @@
# @vercel/build-utils
## 6.8.2
### Patch Changes
- Push back `nodejs16.x` discontinue date to `2024-02-06` ([#10209](https://github.com/vercel/vercel/pull/10209))
## 6.8.1
### Patch Changes
- Revert "[build-utils] Allow file-ref sema to be controlled through env flag" ([#10167](https://github.com/vercel/vercel/pull/10167))
## 6.8.0
### Minor Changes
- Add `getNodeBinPaths()` and `traverseUpDirectories()` functions ([#10150](https://github.com/vercel/vercel/pull/10150))
## 6.7.5
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/build-utils",
"version": "6.8.2",
"version": "6.7.5",
"license": "Apache-2.0",
"main": "./dist/index.js",
"types": "./dist/index.d.js",

View File

@@ -5,7 +5,13 @@ import path from 'path';
import Sema from 'async-sema';
import { FileBase } from './types';
const semaToPreventEMFILE = new Sema(20);
const DEFAULT_SEMA = 20;
const semaToPreventEMFILE = new Sema(
parseInt(
process.env.VERCEL_INTERNAL_FILE_FS_REF_SEMA || String(DEFAULT_SEMA),
10
) || DEFAULT_SEMA
);
interface FileFsRefOptions {
mode?: number;

View File

@@ -12,7 +12,13 @@ interface FileRefOptions {
mutable?: boolean;
}
const semaToDownloadFromS3 = new Sema(5);
const DEFAULT_SEMA = 5;
const semaToDownloadFromS3 = new Sema(
parseInt(
process.env.VERCEL_INTERNAL_FILE_REF_SEMA || String(DEFAULT_SEMA),
10
) || DEFAULT_SEMA
);
class BailableError extends Error {
public bail: boolean;

View File

@@ -10,7 +10,7 @@ function getOptions() {
major: 16,
range: '16.x',
runtime: 'nodejs16.x',
discontinueDate: new Date('2024-02-06'),
discontinueDate: new Date('2023-08-15'),
},
{
major: 14,

View File

@@ -44,33 +44,23 @@ export interface ScanParentDirsResult {
lockfileVersion?: number;
}
export interface TraverseUpDirectoriesProps {
/**
* The directory to start iterating from, typically the same directory of the entrypoint.
*/
start: string;
export interface WalkParentDirsProps {
/**
* The highest directory, typically the workPath root of the project.
* If this directory is reached and it doesn't contain the file, null is returned.
*/
base?: string;
}
export interface WalkParentDirsProps
extends Required<TraverseUpDirectoriesProps> {
base: string;
/**
* The directory to start searching, typically the same directory of the entrypoint.
* If this directory doesn't contain the file, the parent is checked, etc.
*/
start: string;
/**
* The name of the file to search for, typically `package.json` or `Gemfile`.
*/
filename: string;
}
export interface WalkParentDirsMultiProps
extends Required<TraverseUpDirectoriesProps> {
/**
* The name of the file to search for, typically `package.json` or `Gemfile`.
*/
filenames: string[];
}
export interface SpawnOptionsExtended extends SpawnOptions {
/**
* Pretty formatted command that is being spawned for logging purposes.
@@ -141,24 +131,6 @@ export async function execCommand(command: string, options: SpawnOptions = {}) {
return true;
}
export function* traverseUpDirectories({
start,
base,
}: TraverseUpDirectoriesProps) {
let current: string | undefined = path.normalize(start);
const normalizedRoot = base ? path.normalize(base) : undefined;
while (current) {
yield current;
if (current === normalizedRoot) break;
// Go up one directory
const next = path.join(current, '..');
current = next === current ? undefined : next;
}
}
/**
* @deprecated Use `getNodeBinPaths()` instead.
*/
export async function getNodeBinPath({
cwd,
}: {
@@ -169,15 +141,6 @@ export async function getNodeBinPath({
return path.join(dir, 'node_modules', '.bin');
}
export function getNodeBinPaths({
start,
base,
}: TraverseUpDirectoriesProps): string[] {
return Array.from(traverseUpDirectories({ start, base })).map(dir =>
path.join(dir, 'node_modules/.bin')
);
}
async function chmodPlusX(fsPath: string) {
const s = await fs.stat(fsPath);
const newMode = s.mode | 64 | 8 | 1; // eslint-disable-line no-bitwise
@@ -334,14 +297,22 @@ export async function walkParentDirs({
}: WalkParentDirsProps): Promise<string | null> {
assert(path.isAbsolute(base), 'Expected "base" to be absolute path');
assert(path.isAbsolute(start), 'Expected "start" to be absolute path');
let parent = '';
for (const dir of traverseUpDirectories({ start, base })) {
const fullPath = path.join(dir, filename);
for (let current = start; base.length <= current.length; current = parent) {
const fullPath = path.join(current, filename);
// eslint-disable-next-line no-await-in-loop
if (await fs.pathExists(fullPath)) {
return fullPath;
}
parent = path.dirname(current);
if (parent === current) {
// Reached root directory of the filesystem
break;
}
}
return null;
@@ -351,9 +322,14 @@ async function walkParentDirsMulti({
base,
start,
filenames,
}: WalkParentDirsMultiProps): Promise<(string | undefined)[]> {
for (const dir of traverseUpDirectories({ start, base })) {
const fullPaths = filenames.map(f => path.join(dir, f));
}: {
base: string;
start: string;
filenames: string[];
}): Promise<(string | undefined)[]> {
let parent = '';
for (let current = start; base.length <= current.length; current = parent) {
const fullPaths = filenames.map(f => path.join(current, f));
const existResults = await Promise.all(
fullPaths.map(f => fs.pathExists(f))
);
@@ -362,6 +338,13 @@ async function walkParentDirsMulti({
if (foundOneOrMore) {
return fullPaths.map((f, i) => (existResults[i] ? f : undefined));
}
parent = path.dirname(current);
if (parent === current) {
// Reached root directory of the filesystem
break;
}
}
return [];

View File

@@ -30,9 +30,7 @@ import {
getNodeVersion,
getSpawnOptions,
getNodeBinPath,
getNodeBinPaths,
scanParentDirs,
traverseUpDirectories,
} from './fs/run-user-scripts';
import {
getLatestNodeVersion,
@@ -70,7 +68,6 @@ export {
spawnCommand,
walkParentDirs,
getNodeBinPath,
getNodeBinPaths,
runNpmInstall,
runBundleInstall,
runPipInstall,
@@ -92,7 +89,6 @@ export {
getIgnoreFilter,
cloneEnv,
hardLinkDir,
traverseUpDirectories,
validateNpmrc,
};

View File

@@ -1,17 +0,0 @@
import { join } from 'path';
import { getNodeBinPaths } from '../src/fs/run-user-scripts';
describe('getNodeBinPaths()', () => {
const cwd = process.cwd();
it('should return array of `node_modules/.bin` paths', () => {
const start = join(cwd, 'foo/bar/baz');
const paths = getNodeBinPaths({ start, base: cwd });
expect(paths).toEqual([
join(cwd, 'foo/bar/baz/node_modules/.bin'),
join(cwd, 'foo/bar/node_modules/.bin'),
join(cwd, 'foo/node_modules/.bin'),
join(cwd, 'node_modules/.bin'),
]);
});
});

View File

@@ -238,7 +238,7 @@ it('should get latest node version', async () => {
it('should throw for discontinued versions', async () => {
// Mock a future date so that Node 8 and 10 become discontinued
const realDateNow = Date.now.bind(global.Date);
global.Date.now = () => new Date('2024-02-13').getTime();
global.Date.now = () => new Date('2023-10-01').getTime();
expect(getSupportedNodeVersion('8.10.x', false)).rejects.toThrow();
expect(getSupportedNodeVersion('8.10.x', true)).rejects.toThrow();
@@ -306,8 +306,8 @@ it('should warn for deprecated versions, soon to be discontinued', async () => {
'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-10-03 will fail to build. Please set Node.js Version to 18.x in your Project Settings to use Node.js 18.',
'Error: Node.js version 14.x has reached End-of-Life. Deployments created on or after 2023-08-15 will fail to build. Please set "engines": { "node": "18.x" } in your `package.json` file to use Node.js 18.',
'Error: Node.js version 14.x has reached End-of-Life. Deployments created on or after 2023-08-15 will fail to build. Please set Node.js Version to 18.x in your Project Settings to use Node.js 18.',
'Error: Node.js version 16.x has reached End-of-Life. Deployments created on or after 2024-02-06 will fail to build. Please set "engines": { "node": "18.x" } in your `package.json` file to use Node.js 18.',
'Error: Node.js version 16.x has reached End-of-Life. Deployments created on or after 2024-02-06 will fail to build. Please set Node.js Version to 18.x in your Project Settings to use Node.js 18.',
'Error: Node.js version 16.x has reached End-of-Life. Deployments created on or after 2023-08-15 will fail to build. Please set "engines": { "node": "18.x" } in your `package.json` file to use Node.js 18.',
'Error: Node.js version 16.x has reached End-of-Life. Deployments created on or after 2023-08-15 will fail to build. Please set Node.js Version to 18.x in your Project Settings to use Node.js 18.',
]);
global.Date.now = realDateNow;

View File

@@ -1,50 +0,0 @@
import { traverseUpDirectories } from '../src/fs/run-user-scripts';
const isWindows = process.platform === 'win32';
describe('traverseUpDirectories()', () => {
test.each(
isWindows
? [
{
start: 'C:\\foo\\bar\\baz',
expected: ['C:\\foo\\bar\\baz', 'C:\\foo\\bar', 'C:\\foo', 'C:\\'],
},
{
start: 'C:\\foo\\..\\bar\\.\\baz',
expected: ['C:\\bar\\baz', 'C:\\bar', 'C:\\'],
},
{
start: 'C:\\foo\\bar\\baz\\another',
base: 'C:\\foo\\bar',
expected: [
'C:\\foo\\bar\\baz\\another',
'C:\\foo\\bar\\baz',
'C:\\foo\\bar',
],
},
]
: [
{
start: '/foo/bar/baz',
expected: ['/foo/bar/baz', '/foo/bar', '/foo', '/'],
},
{
start: '/foo/../bar/./baz',
expected: ['/bar/baz', '/bar', '/'],
},
{
start: '/foo/bar/baz/another',
base: '/foo/bar',
expected: ['/foo/bar/baz/another', '/foo/bar/baz', '/foo/bar'],
},
]
)(
'should traverse start="$start", base="$base"',
({ start, base, expected }) => {
expect(Array.from(traverseUpDirectories({ start, base }))).toEqual(
expected
);
}
);
});

View File

@@ -1,103 +1,5 @@
# vercel
## 31.0.3
### Patch Changes
- Fix redeploy target to be undefined when null ([#10201](https://github.com/vercel/vercel/pull/10201))
- Respect forbidden API responses ([#10178](https://github.com/vercel/vercel/pull/10178))
- Update `supports-hyperlinks` to v3 ([#10208](https://github.com/vercel/vercel/pull/10208))
- Updated dependencies [[`0750517af`](https://github.com/vercel/vercel/commit/0750517af99aea41410d4f1f772ce427699554e7)]:
- @vercel/build-utils@6.8.2
- @vercel/static-build@1.3.40
- @vercel/node@2.15.5
- @vercel/remix-builder@1.8.17
## 31.0.2
### Patch Changes
- Allow additional project settings in `createProject()` ([#10172](https://github.com/vercel/vercel/pull/10172))
- Run local Project detection during `vc link --repo`. ([#10094](https://github.com/vercel/vercel/pull/10094))
This allows for creation of new Projects that do not yet exist under the selected scope.
- Redeploy command no longer redeploys preview deployments to production ([#10186](https://github.com/vercel/vercel/pull/10186))
- Added trailing new line at end of help output ([#10170](https://github.com/vercel/vercel/pull/10170))
- Create new help output and arg parsing for deploy command ([#10090](https://github.com/vercel/vercel/pull/10090))
- [cli] Remove `preinstall` script ([#10157](https://github.com/vercel/vercel/pull/10157))
- Updated dependencies [[`7021279b2`](https://github.com/vercel/vercel/commit/7021279b284f314a4d1bdbb4306b4c22291efa08), [`5e5332fbc`](https://github.com/vercel/vercel/commit/5e5332fbc9317a8f3cc4ed0b72ec1a2c76020891), [`027bce00b`](https://github.com/vercel/vercel/commit/027bce00b3821d9b4a8f7ec320cd1c43ab9f4215)]:
- @vercel/build-utils@6.8.1
- @vercel/node@2.15.4
- @vercel/remix-builder@1.8.16
- @vercel/static-build@1.3.39
## 31.0.1
### Patch Changes
- Updated dependencies [[`aa734efc6`](https://github.com/vercel/vercel/commit/aa734efc6c42badd4aa9bf64487904aa64e9bd49)]:
- @vercel/next@3.8.8
## 31.0.0
### Major Changes
- Update `vc dev` redirect response to match production behavior ([#10143](https://github.com/vercel/vercel/pull/10143))
### Patch Changes
- require `--yes` to promote preview deployment ([#10135](https://github.com/vercel/vercel/pull/10135))
- [cli] Optimize write build result for vc build ([#10154](https://github.com/vercel/vercel/pull/10154))
- Only show relevant Project matches in Project selector ([#10114](https://github.com/vercel/vercel/pull/10114))
- [cli] Fix error message when token is invalid ([#10131](https://github.com/vercel/vercel/pull/10131))
- Updated dependencies [[`e4895d979`](https://github.com/vercel/vercel/commit/e4895d979b57e369e0618481c5974243887d72cc), [`346892210`](https://github.com/vercel/vercel/commit/3468922108f411482a72acd0331f0f2ee52a6d4c), [`346892210`](https://github.com/vercel/vercel/commit/3468922108f411482a72acd0331f0f2ee52a6d4c), [`a6de052ed`](https://github.com/vercel/vercel/commit/a6de052ed2f09cc80bf4c2d0f06bedd267a63cdc)]:
- @vercel/next@3.8.7
- @vercel/static-build@1.3.38
- @vercel/build-utils@6.8.0
- @vercel/remix-builder@1.8.15
- @vercel/node@2.15.3
## 30.2.3
### Patch Changes
- [cli] do not force auto-assign value on deployments ([#10110](https://github.com/vercel/vercel/pull/10110))
- Updated dependencies [[`91406abdb`](https://github.com/vercel/vercel/commit/91406abdb0c332152fc6c7c1e4bd3a872b084434), [`2230ea6cc`](https://github.com/vercel/vercel/commit/2230ea6cc1b84c1f03227a4e197b7684635b5955), [`8b3a4146a`](https://github.com/vercel/vercel/commit/8b3a4146af68d2b7288c80a5b919d832dba929b5)]:
- @vercel/node@2.15.2
- @vercel/remix-builder@1.8.14
- @vercel/static-build@1.3.37
## 30.2.2
### Patch Changes
- [cli] vc env pull should add `.env*.local` to `.gitignore` ([#10085](https://github.com/vercel/vercel/pull/10085))
- [cli] Fix team validation bug where you are apart of a team ([#10092](https://github.com/vercel/vercel/pull/10092))
- Add support for `vc dev` command with repo link ([#10082](https://github.com/vercel/vercel/pull/10082))
- Add support for `vc deploy --prebuilt` command with repo link ([#10083](https://github.com/vercel/vercel/pull/10083))
- Move readme copy logic to a helper function for `vc link` ([#10084](https://github.com/vercel/vercel/pull/10084))
- Add support for `vc pull` command with repo link ([#10078](https://github.com/vercel/vercel/pull/10078))
- Add support for `vc build` command with repo link ([#10075](https://github.com/vercel/vercel/pull/10075))
## 30.2.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "31.0.3",
"version": "30.2.1",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -11,6 +11,7 @@
"directory": "packages/cli"
},
"scripts": {
"preinstall": "node ./scripts/preinstall.js",
"test": "jest --env node --verbose --bail",
"test-unit": "pnpm test test/unit/",
"test-e2e": "rimraf test/fixtures/integration && pnpm test test/integration-1.test.ts test/integration-2.test.ts test/integration-3.test.ts",
@@ -31,20 +32,20 @@
"node": ">= 14"
},
"dependencies": {
"@vercel/build-utils": "6.8.2",
"@vercel/build-utils": "6.7.5",
"@vercel/go": "2.5.1",
"@vercel/hydrogen": "0.0.64",
"@vercel/next": "3.8.8",
"@vercel/node": "2.15.5",
"@vercel/next": "3.8.6",
"@vercel/node": "2.15.1",
"@vercel/python": "3.1.60",
"@vercel/redwood": "1.1.15",
"@vercel/remix-builder": "1.8.17",
"@vercel/remix-builder": "1.8.13",
"@vercel/ruby": "1.3.76",
"@vercel/static-build": "1.3.40"
"@vercel/static-build": "1.3.36"
},
"devDependencies": {
"@alex_neo/jest-expect-message": "1.0.5",
"@edge-runtime/node-utils": "2.1.0",
"@edge-runtime/node-utils": "2.0.3",
"@next/env": "11.1.2",
"@sentry/node": "5.5.0",
"@sindresorhus/slugify": "0.11.0",
@@ -84,13 +85,13 @@
"@types/which": "3.0.0",
"@types/write-json-file": "2.2.1",
"@types/yauzl-promise": "2.1.0",
"@vercel-internals/constants": "1.0.4",
"@vercel-internals/constants": "1.0.2",
"@vercel-internals/get-package-json": "1.0.0",
"@vercel-internals/types": "1.0.5",
"@vercel/client": "12.6.5",
"@vercel-internals/types": "1.0.2",
"@vercel/client": "12.6.2",
"@vercel/error-utils": "1.0.10",
"@vercel/frameworks": "1.4.3",
"@vercel/fs-detectors": "4.1.0",
"@vercel/frameworks": "1.4.2",
"@vercel/fs-detectors": "3.9.3",
"@vercel/fun": "1.0.4",
"@vercel/ncc": "0.24.0",
"@vercel/routing-utils": "2.2.1",
@@ -158,7 +159,7 @@
"serve-handler": "6.1.1",
"strip-ansi": "6.0.1",
"stripe": "5.1.0",
"supports-hyperlinks": "3.0.0",
"supports-hyperlinks": "2.2.0",
"tar-fs": "1.16.3",
"test-listen": "1.1.0",
"text-table": "0.2.0",

View File

@@ -0,0 +1,101 @@
#!/usr/bin/env node
const { join } = require('path');
const { statSync } = require('fs');
const pkg = require('../package');
function error(command) {
console.error('> Error:', command);
}
function debug(str) {
if (
process.argv.find(str => str === '--debug') ||
process.env.PREINSTALL_DEBUG
) {
console.log(`[debug] [${new Date().toISOString()}]`, str);
}
}
function isYarn() {
return process.env.npm_config_heading !== 'npm';
}
function isGlobal() {
const cmd = JSON.parse(process.env.npm_config_argv || '{ "original": [] }');
return isYarn()
? cmd.original.includes('global')
: Boolean(process.env.npm_config_global);
}
function isVercel() {
return pkg.name === 'vercel';
}
function validateNodeVersion() {
let semver = '>= 0';
let major = '1';
try {
major = process.versions.node.split('.')[0];
const pkg = require('../package.json');
semver = pkg.engines.node;
} catch (e) {
debug('Failed to read package.json engines');
}
const isValid = eval(`${major} ${semver}`);
return { isValid, expected: semver, actual: process.versions.node };
}
function isInNodeModules(name) {
try {
const nodeModules = join(__dirname, '..', '..');
const stat = statSync(join(nodeModules, name));
return stat.isDirectory();
} catch (err) {
return false;
}
}
async function main() {
if (!isGlobal()) {
debug('Skipping preinstall since Vercel CLI is being installed locally');
return;
}
const ver = validateNodeVersion();
if (!ver.isValid) {
error(
`Detected unsupported Node.js version.\n` +
`Expected "${ver.expected}" but found "${ver.actual}".\n` +
`Please update to the latest Node.js LTS version to install Vercel CLI.`
);
process.exit(1);
}
if (isVercel() && isInNodeModules('now')) {
const uninstall = isYarn()
? 'yarn global remove now'
: 'npm uninstall -g now';
console.error(`NOTE: Run \`${uninstall}\` to uninstall \`now\`\n`);
}
}
process.on('unhandledRejection', err => {
console.error('Unhandled Rejection:');
console.error(err);
process.exit(1);
});
process.on('uncaughtException', err => {
console.error('Uncaught Exception:');
console.error(err);
process.exit(1);
});
main().catch(err => {
console.error(err);
process.exit(1);
});

View File

@@ -134,7 +134,7 @@ const help = () => {
export default async function main(client: Client): Promise<number> {
let { cwd } = client;
const { output } = client;
const { output, spaces } = client;
// Ensure that `vc build` is not being invoked recursively
if (process.env.__VERCEL_BUILD_RUNNING) {
@@ -231,6 +231,8 @@ export default async function main(client: Client): Promise<number> {
project = await readProjectSettings(vercelDir);
}
await spaces.createRun({ project });
// Delete output directory from potential previous build
const defaultOutputDir = join(cwd, projectRootDirectory, OUTPUT_DIR);
const outputDir = argv['--output']
@@ -566,6 +568,12 @@ async function doBuild(
// Store the build result to generate the final `config.json` after
// all builds have completed
buildResults.set(build, buildResult);
await client.spaces.createTask({
key: `${builderPkg.name}:${builder.version}`,
name: `${builderPkg.name}:build`,
workspace: pkg?.name ?? 'unknown',
log: 'Coming Soon',
});
// Start flushing the file outputs to the filesystem asynchronously
ops.push(

View File

@@ -0,0 +1,50 @@
import chalk from 'chalk';
import logo from '../../util/output/logo';
import { getPkgName } from '../../util/pkg-name';
export const help = () => {
return `
${chalk.bold(`${logo} ${getPkgName()} [deploy]`)} [path-to-project] [options]
--prod Create a production deployment
-p, --public Deployment is public (${chalk.dim(
'`/_src`'
)} is exposed)
-e, --env Include an env var during run time (e.g.: ${chalk.dim(
'`-e KEY=value`'
)}). Can appear many times.
-b, --build-env Similar to ${chalk.dim(
'`--env`'
)} but for build time only.
-m, --meta Add metadata for the deployment (e.g.: ${chalk.dim(
'`-m KEY=value`'
)}). Can appear many times.
--no-wait Don't wait for the deployment to finish
-f, --force Force a new deployment even if nothing has changed
--with-cache Retain build cache when using "--force"
--regions Set default regions to enable the deployment on
${chalk.dim('Examples:')}
${chalk.gray('')} Deploy the current directory
${chalk.cyan(`$ ${getPkgName()}`)}
${chalk.gray('')} Deploy a custom path
${chalk.cyan(`$ ${getPkgName()} /usr/src/project`)}
${chalk.gray('')} Deploy with Environment Variables
${chalk.cyan(`$ ${getPkgName()} -e NODE_ENV=production`)}
${chalk.gray('')} Deploy with prebuilt outputs
${chalk.cyan(`$ ${getPkgName()} build`)}
${chalk.cyan(`$ ${getPkgName()} deploy --prebuilt`)}
${chalk.gray('')} Write Deployment URL to a file
${chalk.cyan(`$ ${getPkgName()} > deployment-url.txt`)}
`;
};

View File

@@ -1,181 +0,0 @@
import { Command } from '../help';
export const deployCommand: Command = {
name: 'deploy',
description:
'Deploy your project to Vercel. The `deploy` command is the default command for the Vercel CLI, and can be omitted (`vc deploy my-app` equals `vc my-app`).',
arguments: [
{
name: 'project-path',
required: false,
},
],
options: [
{
name: 'force',
shorthand: 'f',
type: 'boolean',
deprecated: false,
description: 'Force a new deployment even if nothing has changed',
multi: false,
},
{
name: 'with-cache',
shorthand: null,
type: 'boolean',
deprecated: false,
description: 'Retain build cache when using "--force"',
multi: false,
},
{
name: 'public',
shorthand: 'p',
type: 'boolean',
deprecated: false,
description: 'Deployment is public (`/_src`) is exposed)',
multi: false,
},
{
name: 'env',
shorthand: 'e',
type: 'string',
argument: 'key=value',
deprecated: false,
multi: true,
description:
'Specify environment variables during run-time (e.g. `-e KEY1=value1 -e KEY2=value2`)',
},
{
name: 'build-env',
shorthand: 'b',
type: 'string',
argument: 'key=value',
deprecated: false,
multi: true,
description:
'Specify environment variables during build-time (e.g. `-b KEY1=value1 -b KEY2=value2`)',
},
{
name: 'meta',
shorthand: 'm',
type: 'string',
argument: 'key=value',
deprecated: false,
multi: true,
description:
'Specify metadata for the deployment (e.g. `-m KEY1=value1 -m KEY2=value2`)',
},
{
name: 'regions',
shorthand: null,
type: 'string',
deprecated: false,
description: 'Set default regions to enable the deployment on',
multi: false,
},
{
name: 'prebuilt',
shorthand: null,
type: 'boolean',
deprecated: false,
description:
'Use in combination with `vc build`. Deploy an existing build',
multi: false,
},
{
name: 'prod',
shorthand: null,
type: 'boolean',
deprecated: false,
description: 'Create a production deployment',
multi: false,
},
{
name: 'archive',
shorthand: null,
type: 'string',
deprecated: false,
description:
'Compress the deployment code into a file before uploading it',
multi: false,
},
{
name: 'no-wait',
shorthand: null,
type: 'boolean',
deprecated: false,
description: "Don't wait for the deployment to finish",
multi: false,
},
{
name: 'skip-domain',
shorthand: null,
type: 'boolean',
deprecated: false,
description: undefined,
multi: false,
},
{
name: 'yes',
shorthand: 'y',
type: 'boolean',
deprecated: false,
description: 'Use default options to skip all prompts',
multi: false,
},
{
name: 'name',
shorthand: 'n',
type: 'string',
deprecated: true,
description: 'Provide a Vercel Project name',
multi: false,
},
{
name: 'no-clipboard',
shorthand: null,
type: 'boolean',
deprecated: true,
description: 'Do not copy deployment URL to clipboard',
multi: false,
},
{
name: 'target',
shorthand: null,
type: 'string',
deprecated: true,
description: 'Specify the target deployment environment',
multi: false,
},
{
name: 'confirm',
shorthand: 'c',
type: 'boolean',
deprecated: true,
description: 'Use default options to skip all prompts',
multi: false,
},
],
examples: [
{
name: 'Deploy the current directory',
value: 'vercel',
},
{
name: 'Deploy a custom path',
value: 'vercel /usr/src/project',
},
{
name: 'Deploy with run-time Environment Variables',
value: 'vercel -e NODE_ENV=production',
},
{
name: 'Deploy with prebuilt outputs',
value: ['vercel build', 'vercel deploy --prebuilt'],
},
{
name: 'Write Deployment URL to a file',
value: 'vercel > deployment-url.txt',
},
],
};

View File

@@ -59,6 +59,7 @@ import validatePaths, {
} from '../../util/validate-paths';
import { getCommandName } from '../../util/pkg-name';
import { Output } from '../../util/output';
import { help } from './args';
import { getDeploymentChecks } from '../../util/deploy/get-deployment-checks';
import parseTarget from '../../util/deploy/parse-target';
import getPrebuiltJson from '../../util/deploy/get-prebuilt-json';
@@ -68,38 +69,44 @@ import { parseEnv } from '../../util/parse-env';
import { errorToString, isErrnoException, isError } from '@vercel/error-utils';
import { pickOverrides } from '../../util/projects/project-settings';
import { printDeploymentStatus } from '../../util/deploy/print-deployment-status';
import { help } from '../help';
import { deployCommand } from './command';
export default async (client: Client): Promise<number> => {
const { output } = client;
let argv = null;
const argOptions: {
[k: string]:
| BooleanConstructor
| StringConstructor
| string
| [StringConstructor];
} = {};
for (const option of deployCommand.options) {
argOptions[`--${option.name}`] =
option.type === 'boolean' ? Boolean : String;
if (option.shorthand) {
argOptions[`-${option.shorthand}`] = `--${option.name}`;
}
if (
option.name === 'env' ||
option.name === 'build-env' ||
option.name === 'meta'
) {
argOptions[`--${option.name}`] = [String];
}
}
try {
argv = getArgs(client.argv.slice(2), argOptions);
argv = getArgs(client.argv.slice(2), {
'--force': Boolean,
'--with-cache': Boolean,
'--public': Boolean,
'--env': [String],
'--build-env': [String],
'--meta': [String],
// This is not an array in favor of matching
// the config property name.
'--regions': String,
'--prebuilt': Boolean,
'--prod': Boolean,
'--archive': String,
'--no-wait': Boolean,
'--skip-domain': Boolean,
'--yes': Boolean,
'-f': '--force',
'-p': '--public',
'-e': '--env',
'-b': '--build-env',
'-m': '--meta',
'-y': '--yes',
// deprecated
'--name': String,
'-n': '--name',
'--no-clipboard': Boolean,
'--target': String,
'--confirm': Boolean,
'-c': '--confirm',
});
if ('--confirm' in argv) {
output.warn('`--confirm` is deprecated, please use `--yes` instead');
@@ -111,7 +118,7 @@ export default async (client: Client): Promise<number> => {
}
if (argv['--help']) {
output.print(help(deployCommand, { columns: client.stderr.columns }));
output.print(help());
return 2;
}
@@ -196,6 +203,56 @@ export default async (client: Client): Promise<number> => {
return target;
}
// build `--prebuilt`
if (argv['--prebuilt']) {
const prebuiltExists = await fs.pathExists(join(cwd, '.vercel/output'));
if (!prebuiltExists) {
error(
`The ${param(
'--prebuilt'
)} option was used, but no prebuilt output found in ".vercel/output". Run ${getCommandName(
'build'
)} to generate a local build.`
);
return 1;
}
const prebuiltBuild = await getPrebuiltJson(cwd);
// Ensure that there was not a build error
const prebuiltError =
prebuiltBuild?.error ||
prebuiltBuild?.builds?.find(build => 'error' in build)?.error;
if (prebuiltError) {
output.log(
`Prebuilt deployment cannot be created because ${getCommandName(
'build'
)} failed with error:\n`
);
prettyError(prebuiltError);
return 1;
}
// Ensure that the deploy target matches the build target
const assumedTarget = target || 'preview';
if (prebuiltBuild?.target && prebuiltBuild.target !== assumedTarget) {
let specifyTarget = '';
if (prebuiltBuild.target === 'production') {
specifyTarget = ` --prod`;
}
prettyError({
message: `The ${param(
'--prebuilt'
)} option was used with the target environment "${assumedTarget}", but the prebuilt output found in ".vercel/output" was built with target environment "${
prebuiltBuild.target
}". Please run ${getCommandName(`--prebuilt${specifyTarget}`)}.`,
link: 'https://vercel.link/prebuilt-environment-mismatch',
});
return 1;
}
}
const archive = argv['--archive'];
if (typeof archive === 'string' && !isValidArchive(archive)) {
output.error(`Format must be one of: ${VALID_ARCHIVE_FORMATS.join(', ')}`);
@@ -298,66 +355,6 @@ export default async (client: Client): Promise<number> => {
throw new Error(`"org" is not defined`);
}
// build `--prebuilt`
if (argv['--prebuilt']) {
// For repo-style linking, update `cwd` to be the Project
// subdirectory when `rootDirectory` setting is defined
if (
link.status === 'linked' &&
link.repoRoot &&
link.project.rootDirectory
) {
cwd = join(cwd, link.project.rootDirectory);
}
const prebuiltExists = await fs.pathExists(join(cwd, '.vercel/output'));
if (!prebuiltExists) {
error(
`The ${param(
'--prebuilt'
)} option was used, but no prebuilt output found in ".vercel/output". Run ${getCommandName(
'build'
)} to generate a local build.`
);
return 1;
}
const prebuiltBuild = await getPrebuiltJson(cwd);
// Ensure that there was not a build error
const prebuiltError =
prebuiltBuild?.error ||
prebuiltBuild?.builds?.find(build => 'error' in build)?.error;
if (prebuiltError) {
output.log(
`Prebuilt deployment cannot be created because ${getCommandName(
'build'
)} failed with error:\n`
);
prettyError(prebuiltError);
return 1;
}
// Ensure that the deploy target matches the build target
const assumedTarget = target || 'preview';
if (prebuiltBuild?.target && prebuiltBuild.target !== assumedTarget) {
let specifyTarget = '';
if (prebuiltBuild.target === 'production') {
specifyTarget = ` --prod`;
}
prettyError({
message: `The ${param(
'--prebuilt'
)} option was used with the target environment "${assumedTarget}", but the prebuilt output found in ".vercel/output" was built with target environment "${
prebuiltBuild.target
}". Please run ${getCommandName(`--prebuilt${specifyTarget}`)}.`,
link: 'https://vercel.link/prebuilt-environment-mismatch',
});
return 1;
}
}
// Set the `contextName` and `currentTeam` as specified by the
// Project Settings, so that API calls happen with the proper scope
const contextName = org.slug;
@@ -514,8 +511,7 @@ export default async (client: Client): Promise<number> => {
}
try {
// if this flag is not set, use `undefined` to allow the project setting to be used
const autoAssignCustomDomains = argv['--skip-domain'] ? false : undefined;
const autoAssignCustomDomains = !argv['--skip-domain'];
const createArgs: CreateOptions = {
name,

View File

@@ -1,229 +0,0 @@
import chalk from 'chalk';
import stripAnsi from 'strip-ansi';
import { LOGO, NAME } from '@vercel-internals/constants';
const INDENT = ' '.repeat(2);
const NEWLINE = '\n';
export interface CommandOption {
name: string;
shorthand: string | null;
type: 'boolean' | 'string';
argument?: string;
deprecated: boolean;
description?: string;
multi: boolean;
}
export interface CommandArgument {
name: string;
required: boolean;
}
export interface CommandExample {
name: string;
value: string | string[];
}
export interface Command {
name: string;
description: string;
arguments: CommandArgument[];
options: CommandOption[];
examples: CommandExample[];
}
export function calcLineLength(line: string[]) {
return stripAnsi(lineToString(line)).length;
}
// Insert spaces in between non-whitespace items only
export function lineToString(line: string[]) {
let string = '';
for (let i = 0; i < line.length; i++) {
if (i === line.length - 1) {
string += line[i];
} else {
const curr = line[i];
const next = line[i + 1];
string += curr;
if (curr.trim() !== '' && next.trim() !== '') {
string += ' ';
}
}
}
return string;
}
export function outputArrayToString(outputArray: string[]) {
return outputArray.join(NEWLINE);
}
/**
* Example: `▲ vercel deploy [path] [options]`
* @param command
* @returns
*/
export function buildCommandSynopsisLine(command: Command) {
const line: string[] = [LOGO, chalk.bold(NAME), chalk.bold(command.name)];
if (command.arguments.length > 0) {
for (const argument of command.arguments) {
line.push(argument.required ? argument.name : `[${argument.name}]`);
}
}
if (command.options.length > 0) {
line.push('[options]');
}
return lineToString(line);
}
export function buildCommandOptionLines(
command: Command,
options: BuildHelpOutputOptions
) {
// Filter out deprecated and intentionally undocumented options
command.options = command.options.filter(
option => !option.deprecated && option.description !== undefined
);
// Initialize output array with header and empty line
const outputArray: string[] = [chalk.dim(`Options:`), ''];
// Start building option lines
const optionLines: string[][] = [];
// Sort command options alphabetically
command.options.sort((a, b) =>
a.name < b.name ? -1 : a.name > b.name ? 1 : 0
);
// Keep track of longest "start" of an option line to determine description spacing
let maxLineStartLength = 0;
// Iterate over options and create the "start" of each option (e.g. ` -b, --build-env <key=value>`)
for (const option of command.options) {
const startLine: string[] = [INDENT];
if (option.shorthand) {
startLine.push(`-${option.shorthand},`);
}
startLine.push(`--${option.name}`);
if (option.argument) {
startLine.push(`<${option.argument}>`);
}
// the length includes the INDENT
const lineLength = calcLineLength(startLine);
maxLineStartLength = Math.max(lineLength, maxLineStartLength);
optionLines.push(startLine);
}
/*
* Iterate over in-progress option lines to add space-filler and description
* For Example:
* | --archive My description starts here.
* |
* | -b, --build-env <key=value> Start of description here then
* | it wraps here.
* |
* | -e, --env <key=value> My description is short.
*
* Breaking down option lines:
* | -b, --build-env <key=value> Start of description here then
* |[][ ][][ ]
* |↑ ↑ ↑ ↑
* |1 2 3 4
* | it wraps here.
* |[][ ][ ]
* |↑ ↑ ↑
* |5 6 7
* | 1, 5 = indent
* | 2 = start
* | 3, 6 = space-filler
* | 4, 7 = description
*/
for (let i = 0; i < optionLines.length; i++) {
const optionLine = optionLines[i];
const option = command.options[i];
// Add only 2 spaces to the longest line, and then make all shorter lines the same length.
optionLine.push(
' '.repeat(2 + (maxLineStartLength - calcLineLength(optionLine)))
);
// Descriptions may be longer than max line length. Wrap them to the same column as the first description line
const lines: string[][] = [optionLine];
if (option.description) {
for (const descriptionWord of option.description.split(' ')) {
// insert a new line when the next word would match or exceed the maximum line length
if (
calcLineLength(lines[lines.length - 1]) +
stripAnsi(descriptionWord).length >=
options.columns
) {
// initialize the new line with the necessary whitespace. The INDENT is apart of `maxLineStartLength`
lines.push([' '.repeat(maxLineStartLength + 2)]);
}
// insert the word to the current last line
lines[lines.length - 1].push(descriptionWord);
}
}
// for every line, transform into a string and push it to the output
for (const line of lines) {
outputArray.push(lineToString(line));
}
// add an empty line in between in each option block for readability (skip the last block)
if (i !== optionLines.length - 1) outputArray.push('');
}
// return the entire list of options as a single string after delete the last '\n' added to the option list
return outputArrayToString(outputArray);
}
export function buildCommandExampleLines(command: Command) {
const outputArray: string[] = [chalk.dim(`Examples:`), ''];
for (const example of command.examples) {
const nameLine: string[] = [INDENT];
nameLine.push(chalk.gray('-'));
nameLine.push(example.name);
outputArray.push(lineToString(nameLine));
outputArray.push('');
const buildValueLine = (value: string) => {
return lineToString([INDENT, INDENT, chalk.cyan(`$ ${value}`)]);
};
if (Array.isArray(example.value)) {
for (const line of example.value) {
outputArray.push(buildValueLine(line));
}
} else {
outputArray.push(buildValueLine(example.value));
}
outputArray.push('');
}
// delete the last newline added after examples iteration
outputArray.splice(-1);
return outputArrayToString(outputArray);
}
interface BuildHelpOutputOptions {
columns: number;
}
export function buildHelpOutput(
command: Command,
options: BuildHelpOutputOptions
) {
const outputArray: string[] = [
buildCommandSynopsisLine(command),
'',
command.description,
'',
buildCommandOptionLines(command, options),
'',
buildCommandExampleLines(command),
'',
];
return outputArrayToString(outputArray);
}
export interface HelpOptions {
columns?: number;
}
export function help(command: Command, options: HelpOptions) {
return buildHelpOutput(command, {
columns: options.columns ?? 80,
});
}

View File

@@ -97,7 +97,7 @@ export default async function main(client: Client) {
client.output.warn(
`The ${cmd('--repo')} flag is in alpha, please report issues`
);
await ensureRepoLink(client, cwd, { yes, overwrite: true });
await ensureRepoLink(client, cwd, yes);
} else {
const link = await ensureLink('link', client, cwd, {
autoConfirm: yes,

View File

@@ -38,10 +38,6 @@ export default async function rm(client: Client, args: string[]) {
client.output.error('No such project exists');
return 1;
}
if (isAPIError(err) && err.status === 403) {
client.output.error(err.message);
return 1;
}
}
const elapsed = ms(Date.now() - start);
client.output.log(

View File

@@ -73,8 +73,6 @@ export default async (client: Client): Promise<number> => {
return 2;
}
const yes = argv['--yes'] ?? false;
// validate the timeout
let timeout = argv['--timeout'];
if (timeout && ms(timeout) === undefined) {
@@ -105,7 +103,6 @@ export default async (client: Client): Promise<number> => {
client,
deployId: actionOrDeployId,
timeout,
yes,
});
} catch (err) {
if (isErrnoException(err)) {

View File

@@ -4,7 +4,6 @@ import { getCommandName } from '../../util/pkg-name';
import getProjectByDeployment from '../../util/projects/get-project-by-deployment';
import ms from 'ms';
import promoteStatus from './status';
import confirm from '../../util/input/confirm';
/**
* Requests a promotion and waits for it complete.
@@ -17,12 +16,10 @@ export default async function requestPromote({
client,
deployId,
timeout,
yes,
}: {
client: Client;
deployId: string;
timeout?: string;
yes: boolean;
}): Promise<number> {
const { output } = client;
@@ -32,16 +29,6 @@ export default async function requestPromote({
output: client.output,
});
if (deployment.target !== 'production' && !yes) {
const question =
'This deployment does not target production, therefore promotion will not apply production environment variables. Are you sure you want to continue?';
const answer = await confirm(client, question, false);
if (!answer) {
output.error('Canceled');
return 0;
}
}
// request the promotion
await client.fetch(`/v9/projects/${project.id}/promote/${deployment.id}`, {
body: {}, // required

View File

@@ -108,7 +108,7 @@ export default async (client: Client): Promise<number> => {
action: 'redeploy',
},
name: fromDeployment.name,
target: fromDeployment.target ?? undefined,
target: fromDeployment.target || 'production',
},
method: 'POST',
});

View File

@@ -624,6 +624,9 @@ const main = async () => {
.send();
}
} catch (err: unknown) {
// close any inprogress runs
await client.spaces.finishRun({ exitCode: 1 });
if (isErrnoException(err) && err.code === 'ENOTFOUND') {
// Error message will look like the following:
// "request to https://api.vercel.com/v2/user failed, reason: getaddrinfo ENOTFOUND api.vercel.com"
@@ -704,6 +707,9 @@ const main = async () => {
metric.event(eventCategory, `${exitCode}`, pkg.version).send();
}
client.stopLogCapture();
await client.spaces.finishRun({ exitCode });
return exitCode;
};

View File

@@ -131,7 +131,6 @@ async function writeBuildResultV2(
const lambdas = new Map<Lambda, string>();
const overrides: Record<string, PathOverride> = {};
for (const [path, output] of Object.entries(buildResult.output)) {
const normalizedPath = stripDuplicateSlashes(path);
if (isLambda(output)) {
@@ -157,26 +156,11 @@ async function writeBuildResultV2(
const ext = getFileExtension(fallback);
const fallbackName = `${normalizedPath}.prerender-fallback${ext}`;
const fallbackPath = join(outputDir, 'functions', fallbackName);
// if file is already on the disk we can hard link
// instead of creating a new copy
let usedHardLink = false;
if ('fsPath' in fallback) {
try {
await fs.link(fallback.fsPath, fallbackPath);
usedHardLink = true;
} catch (_) {
// if link fails we continue attempting to copy
}
}
if (!usedHardLink) {
const stream = fallback.toStream();
await pipe(
stream,
fs.createWriteStream(fallbackPath, { mode: fallback.mode })
);
}
const stream = fallback.toStream();
await pipe(
stream,
fs.createWriteStream(fallbackPath, { mode: fallback.mode })
);
fallback = new FileFsRef({
...output.fallback,
fsPath: basename(fallbackName),
@@ -304,14 +288,6 @@ async function writeStaticFile(
const dest = join(outputDir, 'static', fsPath);
await fs.mkdirp(dirname(dest));
// if already on disk hard link instead of copying
if ('fsPath' in file) {
try {
return await fs.link(file.fsPath, dest);
} catch (_) {
// if link fails we continue attempting to copy
}
}
await downloadFile(file, dest);
}

View File

@@ -18,6 +18,7 @@ import type {
JSONObject,
Stdio,
ReadableTTY,
WritableTTY,
PaginationOptions,
} from '@vercel-internals/types';
import { sharedPromise } from './promise';
@@ -25,7 +26,8 @@ import { APIError } from './errors-ts';
import { normalizeError } from '@vercel/error-utils';
import type { Agent } from 'http';
import sleep from './sleep';
import type * as tty from 'tty';
import { interceptor } from './interceptor';
import Spaces from './spaces';
const isSAMLError = (v: any): v is SAMLError => {
return v && v.saml;
@@ -48,6 +50,7 @@ export interface ClientOptions extends Stdio {
localConfig?: VercelConfig;
localConfigPath?: string;
agent?: Agent;
spaceId?: string;
}
export const isJSONObject = (v: any): v is JSONObject => {
@@ -59,8 +62,8 @@ export default class Client extends EventEmitter implements Stdio {
apiUrl: string;
authConfig: AuthConfig;
stdin: ReadableTTY;
stdout: tty.WriteStream;
stderr: tty.WriteStream;
stdout: WritableTTY;
stderr: WritableTTY;
output: Output;
config: GlobalConfig;
agent?: Agent;
@@ -68,6 +71,12 @@ export default class Client extends EventEmitter implements Stdio {
localConfigPath?: string;
prompt!: inquirer.PromptModule;
requestIdCounter: number;
spaces: Spaces;
logs: {
restore?: () => void;
stdout: string[];
stderr: string[];
};
constructor(opts: ClientOptions) {
super();
@@ -83,7 +92,14 @@ export default class Client extends EventEmitter implements Stdio {
this.localConfig = opts.localConfig;
this.localConfigPath = opts.localConfigPath;
this.requestIdCounter = 1;
this.spaces = new Spaces({ client: this, spaceId: opts.spaceId });
this.logs = {
stdout: [],
stderr: [],
};
this._createPromptModule();
this._startLogCapture();
}
retry<T>(fn: RetryFunction<T>, { retries = 3, maxTimeout = Infinity } = {}) {
@@ -146,8 +162,7 @@ export default class Client extends EventEmitter implements Stdio {
if (!res.ok) {
const error = await responseError(res);
// we should force reauth only if error has a teamId
if (isSAMLError(error) && error.teamId) {
if (isSAMLError(error)) {
try {
// A SAML error means the token is expired, or is not
// designated for the requested team, so the user needs
@@ -243,4 +258,19 @@ export default class Client extends EventEmitter implements Stdio {
set cwd(v: string) {
process.chdir(v);
}
_startLogCapture() {
this.logs.restore = interceptor({
stdout: log => this.logs.stdout.push(log.toString()),
stderr: log => this.logs.stderr.push(log.toString()),
});
}
stopLogCapture() {
this.logs.restore?.();
this.logs = {
stdout: [],
stderr: [],
};
}
}

View File

@@ -234,10 +234,6 @@ export default async function processDeployment({
return error;
}
if (error.code === 'forbidden') {
return error;
}
throw error;
}

View File

@@ -1131,7 +1131,7 @@ export default class DevServer {
body = redirectTemplate({ location, statusCode });
} else {
res.setHeader('content-type', 'text/plain; charset=utf-8');
body = `Redirecting...\n`;
body = `Redirecting to ${location} (${statusCode})\n`;
}
res.end(body);
}

View File

@@ -2,7 +2,7 @@ import { basename } from 'path';
import { VercelConfig } from '@vercel/client';
export interface GetProjectNameOptions {
argv: { [k: string]: string | undefined };
argv: { '--name'?: string };
nowConfig?: VercelConfig;
paths?: string[];
}

View File

@@ -1,5 +0,0 @@
import type { RepoInfo } from './connect-git-provider';
export function repoInfoToUrl(info: RepoInfo): string {
return `https://${info.provider}.com/${info.org}/${info.repo}`;
}

View File

@@ -50,7 +50,7 @@ export interface CreateOptions {
projectSettings?: any;
skipAutoDetectionConfirmation?: boolean;
noWait?: boolean;
autoAssignCustomDomains?: boolean;
autoAssignCustomDomains: boolean;
}
export interface RemoveOptions {

View File

@@ -0,0 +1,48 @@
import { WriteStream } from 'tty';
type InterceptReceiver = (data: Buffer) => void;
export function interceptor({
stdout,
stderr,
opts = {
passthrough: true,
},
}: {
stdout: InterceptReceiver;
stderr: InterceptReceiver;
opts?: {
passthrough?: boolean;
};
}): () => void {
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
const originalStderrWrite = process.stderr.write.bind(process.stderr);
function injectedWrite(
stream: WriteStream,
callback: (data: Buffer) => void
) {
return (
buffer: Buffer | string,
encoding?: any,
cb?: () => void
): boolean => {
const data =
typeof buffer === 'string' ? Buffer.from(buffer, encoding) : buffer;
callback(data);
if (opts.passthrough) {
return originalStdoutWrite.call(stream, buffer, encoding, cb);
}
return true;
};
}
process.stdout.write = injectedWrite(process.stdout, stdout);
process.stderr.write = injectedWrite(process.stderr, stderr);
return () => {
process.stdout.write = originalStdoutWrite;
process.stderr.write = originalStderrWrite;
};
}

View File

@@ -1,10 +1,8 @@
import chalk from 'chalk';
import inquirer from 'inquirer';
import pluralize from 'pluralize';
import { homedir } from 'os';
import slugify from '@sindresorhus/slugify';
import { basename, join, normalize } from 'path';
import { normalizePath, traverseUpDirectories } from '@vercel/build-utils';
import { join, normalize } from 'path';
import { normalizePath } from '@vercel/build-utils';
import { lstat, readJSON, outputJSON } from 'fs-extra';
import confirm from '../input/confirm';
import toHumanPath from '../humanize-path';
@@ -16,10 +14,6 @@ import selectOrg from '../input/select-org';
import { addToGitIgnore } from './add-to-gitignore';
import type Client from '../client';
import type { Project } from '@vercel-internals/types';
import createProject from '../projects/create-project';
import { detectProjects } from '../projects/detect-projects';
import { repoInfoToUrl } from '../git/repo-info-to-url';
import { connectGitProvider, parseRepoUrl } from '../git/connect-git-provider';
const home = homedir();
@@ -41,22 +35,14 @@ export interface RepoLink {
repoConfig?: RepoProjectsConfig;
}
export interface EnsureRepoLinkOptions {
yes: boolean;
overwrite: boolean;
}
/**
* Given a directory path `cwd`, finds the root of the Git repository
* and returns the parsed `.vercel/repo.json` file if the repository
* has already been linked.
*/
export async function getRepoLink(
client: Client,
cwd: string
): Promise<RepoLink | undefined> {
export async function getRepoLink(cwd: string): Promise<RepoLink | undefined> {
// Determine where the root of the repo is
const rootPath = await findRepoRoot(client, cwd);
const rootPath = await findRepoRoot(cwd);
if (!rootPath) return undefined;
// Read the `repo.json`, if this repo has already been linked
@@ -73,11 +59,11 @@ export async function getRepoLink(
export async function ensureRepoLink(
client: Client,
cwd: string,
{ yes, overwrite }: EnsureRepoLinkOptions
yes = false
): Promise<RepoLink | undefined> {
const { output } = client;
const repoLink = await getRepoLink(client, cwd);
const repoLink = await getRepoLink(cwd);
if (repoLink) {
output.debug(`Found Git repository root directory: ${repoLink.rootPath}`);
} else {
@@ -85,14 +71,7 @@ export async function ensureRepoLink(
}
let { rootPath, repoConfig, repoConfigPath } = repoLink;
if (overwrite || !repoConfig) {
// Detect the projects on the filesystem out of band, so that
// they will be ready by the time the projects are listed
const detectedProjectsPromise = detectProjects(rootPath).catch(err => {
output.debug(`Failed to detect local projects: ${err}`);
return new Map<string, string>();
});
if (!repoConfig) {
// Not yet linked, so prompt user to begin linking
let shouldLink =
yes ||
@@ -129,36 +108,41 @@ export async function ensureRepoLink(
remoteName = remoteNames[0];
} else {
// Prompt user to select which remote to use
const originIndex = remoteNames.indexOf('origin');
const answer = await client.prompt({
type: 'list',
name: 'value',
message: 'Which Git remote should be used?',
choices: remoteNames.sort().map(name => {
choices: remoteNames.map(name => {
return { name: name, value: name };
}),
default: remoteNames.includes('origin') ? 'origin' : undefined,
default: originIndex === -1 ? 0 : originIndex,
});
remoteName = answer.value;
}
const repoUrl = remoteUrls[remoteName];
const parsedRepoUrl = parseRepoUrl(repoUrl);
if (!parsedRepoUrl) {
throw new Error(`Failed to parse Git URL: ${repoUrl}`);
}
const repoUrlLink = output.link(repoUrl, repoInfoToUrl(parsedRepoUrl), {
fallback: () => link(repoUrl),
});
output.spinner(
`Fetching Projects for ${repoUrlLink} under ${chalk.bold(org.slug)}`
`Fetching Projects for ${link(repoUrl)} under ${chalk.bold(org.slug)}`
);
let projects: Project[] = [];
const query = new URLSearchParams({ repoUrl });
const projectsIterator = client.fetchPaginated<{
projects: Project[];
}>(`/v9/projects?${query}`);
const detectedProjects = await detectedProjectsPromise;
let printedFound = false;
for await (const chunk of projectsIterator) {
projects = projects.concat(chunk.projects);
if (!printedFound && projects.length > 0) {
output.log(
`${pluralize('Project', chunk.projects.length)} linked to ${link(
repoUrl
)} under ${chalk.bold(org.slug)}:`
);
printedFound = true;
}
for (const project of chunk.projects) {
output.print(` * ${chalk.cyan(`${org.slug}/${project.name}\n`)}`);
}
if (chunk.pagination.next) {
output.spinner(`Found ${chalk.bold(projects.length)} Projects…`, 0);
}
@@ -166,111 +150,36 @@ export async function ensureRepoLink(
if (projects.length === 0) {
output.log(
`No Projects are linked to ${repoUrlLink} under ${chalk.bold(
`No Projects are linked to ${link(repoUrl)} under ${chalk.bold(
org.slug
)}.`
);
} else {
output.log(
`Found ${pluralize(
'Project',
projects.length,
true
)} linked to ${repoUrlLink} under ${chalk.bold(org.slug)}`
);
// TODO: run detection logic to find potential projects.
// then prompt user to select valid projects.
// then create new Projects
}
// For any projects that already exists on Vercel, remove them from the
// locally detected directories. Any remaining ones will be prompted to
// create new Projects for.
for (const project of projects) {
detectedProjects.delete(project.rootDirectory ?? '');
}
if (detectedProjects.size > 0) {
output.log(
`Detected ${pluralize(
'new Project',
detectedProjects.size,
true
)} that may be created.`
);
}
const addSeparators = projects.length > 0 && detectedProjects.size > 0;
const { selected } = await client.prompt({
type: 'checkbox',
name: 'selected',
message: `Which Projects should be ${
projects.length ? 'linked to' : 'created'
}?`,
choices: [
...(addSeparators
? [new inquirer.Separator('----- Existing Projects -----')]
: []),
...projects.map(project => {
return {
name: `${org.slug}/${project.name}`,
value: project,
checked: true,
};
}),
...(addSeparators
? [new inquirer.Separator('----- New Projects to be created -----')]
: []),
...Array.from(detectedProjects.entries()).map(
([rootDirectory, framework]) => {
const name = slugify(
[basename(rootPath), basename(rootDirectory)]
.filter(Boolean)
.join('-')
);
return {
name: `${org.slug}/${name} (${framework})`,
value: {
newProject: true,
rootDirectory,
name,
framework,
},
};
}
),
],
});
if (selected.length === 0) {
output.print(`No Projects were selected. Repository not linked.\n`);
return;
}
for (let i = 0; i < selected.length; i++) {
const selection = selected[i];
if (!selection.newProject) continue;
const orgAndName = `${org.slug}/${selection.name}`;
output.spinner(`Creating new Project: ${orgAndName}`);
delete selection.newProject;
if (!selection.rootDirectory) delete selection.rootDirectory;
selected[i] = await createProject(client, selection);
await connectGitProvider(
shouldLink =
yes ||
(await confirm(
client,
org,
selected[i].id,
parsedRepoUrl.provider,
`${parsedRepoUrl.org}/${parsedRepoUrl.repo}`
);
output.log(
`Created new Project: ${output.link(
orgAndName,
`https://vercel.com/${orgAndName}`
)}`
);
`Link to ${
projects.length === 1
? 'this Project'
: `these ${chalk.bold(projects.length)} Projects`
}?`,
true
));
if (!shouldLink) {
output.print(`Canceled. Repository not linked.\n`);
return;
}
repoConfig = {
orgId: org.id,
remoteName,
projects: selected.map((project: Project) => {
projects: projects.map(project => {
return {
id: project.id,
name: project.name,
@@ -287,7 +196,7 @@ export async function ensureRepoLink(
output.print(
prependEmoji(
`Linked to ${repoUrlLink} under ${chalk.bold(
`Linked to ${link(repoUrl)} under ${chalk.bold(
org.slug
)} (created ${VERCEL_DIR}${
isGitIgnoreUpdated ? ' and added it to .gitignore' : ''
@@ -309,47 +218,45 @@ export async function ensureRepoLink(
* the nearest `.git/config` file is found. Returns the directory where
* the Git config was found, or `undefined` when no Git repo was found.
*/
export async function findRepoRoot(
client: Client,
start: string
): Promise<string | undefined> {
const { debug } = client.output;
const REPO_JSON_PATH = join(VERCEL_DIR, VERCEL_DIR_REPO);
const GIT_CONFIG_PATH = normalize('.git/config');
for (const current of traverseUpDirectories({ start })) {
export async function findRepoRoot(start: string): Promise<string | undefined> {
for (const current of traverseUpDirectories(start)) {
if (current === home) {
// Sometimes the $HOME directory is set up as a Git repo
// (for dotfiles, etc.). In this case it's safe to say that
// this isn't the repo we're looking for. Bail.
debug('Arrived at home directory');
break;
}
// if `.vercel/repo.json` exists (already linked),
// then consider this the repo root
const repoConfigPath = join(current, REPO_JSON_PATH);
const repoConfigPath = join(current, VERCEL_DIR, VERCEL_DIR_REPO);
let stat = await lstat(repoConfigPath).catch(err => {
if (err.code !== 'ENOENT') throw err;
});
if (stat) {
debug(`Found "${REPO_JSON_PATH}" - detected "${current}" as repo root`);
return current;
}
// if `.git/config` exists (unlinked),
// then consider this the repo root
const gitConfigPath = join(current, GIT_CONFIG_PATH);
const gitConfigPath = join(current, '.git/config');
stat = await lstat(gitConfigPath).catch(err => {
if (err.code !== 'ENOENT') throw err;
});
if (stat) {
debug(`Found "${GIT_CONFIG_PATH}" - detected "${current}" as repo root`);
return current;
}
}
}
debug('Aborting search for repo root');
export function* traverseUpDirectories(start: string) {
let current: string | undefined = normalize(start);
while (current) {
yield current;
// Go up one directory
const next = join(current, '..');
current = next === current ? undefined : next;
}
}
function sortByDirectory(a: RepoProjectConfig, b: RepoProjectConfig): number {
@@ -368,7 +275,7 @@ export function findProjectsFromPath(
path: string
): RepoProjectConfig[] {
const normalizedPath = normalizePath(path);
const matches = projects
return projects
.slice()
.sort(sortByDirectory)
.filter(project => {
@@ -381,9 +288,14 @@ export function findProjectsFromPath(
normalizedPath.startsWith(`${project.directory}/`)
);
});
// If there are multiple matches, we only want the most relevant
// selections (with the deepest directory depth), so pick the first
// one and filter on those matches.
const firstMatch = matches[0];
return matches.filter(match => match.directory === firstMatch.directory);
}
/**
* TODO: remove
*/
export function findProjectFromPath(
projects: RepoProjectConfig[],
path: string
): RepoProjectConfig | undefined {
return findProjectsFromPath(projects, path)[0];
}

View File

@@ -10,6 +10,7 @@ import {
VERCEL_DIR_PROJECT,
} from '../projects/link';
import createProject from '../projects/create-project';
import updateProject from '../projects/update-project';
import Client from '../client';
import handleError from '../handle-error';
import confirm from '../input/confirm';
@@ -244,10 +245,10 @@ export default async function setupAndLink(
settings.rootDirectory = rootDirectory;
}
const project = await createProject(client, {
...settings,
name: newProjectName,
});
const project = await createProject(client, newProjectName);
await updateProject(client, project.id, settings);
Object.assign(project, settings);
await linkFolderToProject(
client,

View File

@@ -3,9 +3,9 @@ import * as ansiEscapes from 'ansi-escapes';
import { supportsHyperlink as detectSupportsHyperlink } from 'supports-hyperlinks';
import renderLink from './link';
import wait, { StopSpinner } from './wait';
import type { WritableTTY } from '@vercel-internals/types';
import { errorToString } from '@vercel/error-utils';
import { removeEmoji } from '../emoji';
import type * as tty from 'tty';
const IS_TEST = process.env.NODE_ENV === 'test';
@@ -24,7 +24,7 @@ interface LinkOptions {
}
export class Output {
stream: tty.WriteStream;
stream: WritableTTY;
debugEnabled: boolean;
supportsHyperlink: boolean;
colorDisabled: boolean;
@@ -32,7 +32,7 @@ export class Output {
private _spinner: StopSpinner | null;
constructor(
stream: tty.WriteStream,
stream: WritableTTY,
{
debug: debugEnabled = false,
supportsHyperlink = detectSupportsHyperlink(stream),

View File

@@ -1,13 +1,13 @@
import Client from '../client';
import type { Project, ProjectSettings } from '@vercel-internals/types';
import type { Project } from '@vercel-internals/types';
export default async function createProject(
client: Client,
settings: ProjectSettings & { name: string }
projectName: string
) {
const project = await client.fetch<Project>('/v1/projects', {
method: 'POST',
body: { ...settings },
body: { name: projectName },
});
return project;
}

View File

@@ -1,38 +0,0 @@
import { join } from 'path';
import frameworks from '@vercel/frameworks';
import {
detectFramework,
getWorkspacePackagePaths,
getWorkspaces,
LocalFileSystemDetector,
} from '@vercel/fs-detectors';
export async function detectProjects(cwd: string) {
const fs = new LocalFileSystemDetector(cwd);
const workspaces = await getWorkspaces({ fs });
const detectedProjects = new Map<string, string>();
const packagePaths = (
await Promise.all(
workspaces.map(workspace =>
getWorkspacePackagePaths({
fs,
workspace,
})
)
)
).flat();
if (packagePaths.length === 0) {
packagePaths.push('/');
}
await Promise.all(
packagePaths.map(async p => {
const framework = await detectFramework({
fs: fs.chdir(join('.', p)),
frameworkList: frameworks,
});
if (!framework) return;
detectedProjects.set(p.slice(1), framework);
})
);
return detectedProjects;
}

View File

@@ -74,7 +74,9 @@ export default async function getProjectByDeployment({
err.code = 'ERR_INVALID_TEAM';
throw err;
}
} else if (team) {
}
if (team) {
const err: NodeJS.ErrnoException = new Error(
`Deployment doesn't belong to current team ${chalk.bold(contextName)}`
);

View File

@@ -83,7 +83,7 @@ async function getProjectLinkFromRepoLink(
client: Client,
path: string
): Promise<ProjectLink | null> {
const repoLink = await getRepoLink(client, path);
const repoLink = await getRepoLink(path);
if (!repoLink?.repoConfig) {
return null;
}
@@ -95,13 +95,11 @@ async function getProjectLinkFromRepoLink(
if (projects.length === 1) {
project = projects[0];
} else {
const selectableProjects =
projects.length > 0 ? projects : repoLink.repoConfig.projects;
const { p } = await client.prompt({
name: 'p',
type: 'list',
message: `Please select a Project:`,
choices: selectableProjects.map(p => ({
choices: repoLink.repoConfig.projects.map(p => ({
value: p,
name: p.name,
})),
@@ -169,7 +167,6 @@ async function getOrgById(client: Client, orgId: string): Promise<Org | null> {
}
async function hasProjectLink(
client: Client,
projectLink: ProjectLink,
path: string
): Promise<boolean> {
@@ -184,7 +181,7 @@ async function hasProjectLink(
}
// linked via `repo.json`?
const repoLink = await getRepoLink(client, path);
const repoLink = await getRepoLink(path);
if (
repoLink?.repoConfig?.orgId === projectLink.orgId &&
repoLink.repoConfig.projects.find(p => p.id === projectLink.projectId)
@@ -246,7 +243,7 @@ export async function getLinkedProject(
if (isAPIError(err) && err.status === 403) {
output.stopSpinner();
if (err.missingToken || err.invalidToken) {
if (err.missingToken) {
throw new InvalidToken();
} else {
throw new NowBuildError({
@@ -304,7 +301,7 @@ export async function linkFolderToProject(
successEmoji: EmojiLabel = 'link'
) {
// if the project is already linked, we skip linking
if (await hasProjectLink(client, projectLink, path)) {
if (await hasProjectLink(projectLink, path)) {
return;
}

View File

@@ -1,16 +1,26 @@
import Client from '../client';
import type { Project, ProjectSettings } from '@vercel-internals/types';
import type { JSONObject, ProjectSettings } from '@vercel-internals/types';
interface ProjectSettingsResponse extends ProjectSettings {
id: string;
name: string;
updatedAt: number;
createdAt: number;
}
export default async function updateProject(
client: Client,
prjNameOrId: string,
settings: ProjectSettings
) {
const res = await client.fetch<Project>(
// `ProjectSettings` is technically compatible with JSONObject
const body = settings as JSONObject;
const res = await client.fetch<ProjectSettingsResponse>(
`/v2/projects/${encodeURIComponent(prjNameOrId)}`,
{
method: 'PATCH',
body: { ...settings },
body,
}
);
return res;

View File

@@ -0,0 +1,188 @@
import fetch, { Headers } from 'node-fetch';
import pkg from '../../../package.json';
import Client from '../client';
import ua from '../ua';
import { ProjectLinkAndSettings } from '../projects/project-settings';
const HOST = 'https://vercel.com/api';
class Run {
id: string;
url: string;
startTime = Date.now();
constructor({ id, url }: { id: string; url: string }) {
this.id = id;
this.url = url;
}
}
/*
Ordering of requests:
1. Create run (POST)
2. Create tasks (POST)
3. Finish run (PATCH)
*/
export default class Spaces {
version: string = 'v0';
type: string = 'VERCEL-CLI';
spaceId: string;
client: Client;
run: Run | undefined;
constructor({ spaceId, client }: { spaceId?: string; client: Client }) {
// TODO: remove default spaceId (vercel-site)
this.spaceId = spaceId || 'space_FwOT2idkmZ5PIDvgK5xY3YpD';
this.client = client;
}
private runsEndpoint({ runId }: { runId?: string } = {}) {
if (runId) {
return `spaces/${this.spaceId}/runs/${runId}`;
}
return `spaces/${this.spaceId}/runs`;
}
private tasksEndpoint({ runId }: { runId: string }) {
return `spaces/${this.spaceId}/runs/${runId}/tasks`;
}
private async gitMetaData() {
return {
gitBranch: 'TODO',
gitSha: 'TODO',
};
}
private async fetch({
method,
body,
endpoint,
}: {
method: 'POST' | 'PATCH';
body: string;
endpoint: string;
}) {
const url = new URL(`${HOST}/${this.version}/${endpoint}`);
if (this.client.config.currentTeam) {
url.searchParams.set('teamId', this.client.config.currentTeam);
}
const headers = new Headers();
headers.set('user-agent', ua);
if (this.client.authConfig.token) {
headers.set('authorization', `Bearer ${this.client.authConfig.token}`);
}
return fetch(url, {
method,
body,
headers,
});
}
async finishRun({ exitCode }: { exitCode: number }) {
if (!this.spaceId) {
return;
}
if (!this.run) {
return;
}
try {
// patch with node-fetch
await this.fetch({
endpoint: this.runsEndpoint({ runId: this.run.id }),
method: 'PATCH',
body: JSON.stringify({
status: 'completed',
endTime: Date.now(),
exitCode,
}),
});
console.log(`✅ Run saved to ${this.run.url}`);
} catch (err) {
console.error(err);
} finally {
// reset the run
this.run = undefined;
}
}
async createRun({ project }: { project: ProjectLinkAndSettings }) {
if (!this.spaceId) {
return;
}
if (this.run) {
console.error(
`Existing run in progress, cannot start a new run before finishing the current run.`
);
return;
}
const data = {
status: 'running',
startTime: Date.now(),
type: this.type,
context: 'LOCAL',
client: {
id: 'vercel-cli',
name: 'Vercel CLI',
version: pkg.version,
},
meta: JSON.stringify({ project }),
repositoryPath: 'TODO',
command: project.settings.buildCommand ?? '',
...this.gitMetaData(),
};
try {
const res = await this.fetch({
endpoint: this.runsEndpoint(),
method: 'POST',
body: JSON.stringify(data),
});
const json = await res.json();
console.log(`Created run: ${json.url}`);
// track the run
this.run = new Run({ id: json.id, url: json.url });
} catch (err) {
console.error(err);
}
}
async createTask(data: {
key: string;
name: string;
workspace: string;
log: string;
}) {
if (!this.spaceId) {
return;
}
if (!this.run) {
console.error(
`No run in progress, must start a new run before creating tasks.`
);
return;
}
try {
const res = await this.fetch({
endpoint: this.tasksEndpoint({ runId: this.run.id }),
method: 'POST',
body: JSON.stringify(data),
});
const json = await res.json();
console.log(`Added task: ${json}`);
} catch (err) {
console.error(err);
}
}
}

View File

@@ -88,10 +88,10 @@ test(
async (testPath: any) => {
const vcRobots = `https://vercel.com/robots.txt`;
await testPath(200, '/rewrite', /User-Agent: \*/m);
await testPath(308, '/redirect', `Redirecting...`, {
await testPath(308, '/redirect', `Redirecting to ${vcRobots} (308)`, {
Location: vcRobots,
});
await testPath(307, '/tempRedirect', `Redirecting...`, {
await testPath(307, '/tempRedirect', `Redirecting to ${vcRobots} (307)`, {
Location: vcRobots,
});
}
@@ -103,10 +103,10 @@ test(
testFixtureStdio('test-routing-case-sensitive', async (testPath: any) => {
await testPath(200, '/Path', 'UPPERCASE');
await testPath(200, '/path', 'lowercase');
await testPath(308, '/GoTo', 'Redirecting...', {
await testPath(308, '/GoTo', 'Redirecting to /upper.html (308)', {
Location: '/upper.html',
});
await testPath(308, '/goto', 'Redirecting...', {
await testPath(308, '/goto', 'Redirecting to /lower.html (308)', {
Location: '/lower.html',
});
})

View File

@@ -233,7 +233,7 @@ test(
expect(res.headers.get('location')).toBe(
`http://localhost:${port}/?foo=bar`
);
expect(body).toBe('Redirecting...\n');
expect(body).toBe('Redirecting to /?foo=bar (301)\n');
}
{

View File

@@ -197,18 +197,21 @@ test(
await testPath(200, '/sub', 'Sub Index Page');
await testPath(200, '/sub/another', 'Sub Another Page');
await testPath(200, '/style.css', 'body { color: green }');
await testPath(308, '/index.html', 'Redirecting...', {
await testPath(308, '/index.html', 'Redirecting to / (308)', {
Location: '/',
});
await testPath(308, '/about.html', 'Redirecting...', {
await testPath(308, '/about.html', 'Redirecting to /about (308)', {
Location: '/about',
});
await testPath(308, '/sub/index.html', 'Redirecting...', {
await testPath(308, '/sub/index.html', 'Redirecting to /sub (308)', {
Location: '/sub',
});
await testPath(308, '/sub/another.html', 'Redirecting...', {
Location: '/sub/another',
});
await testPath(
308,
'/sub/another.html',
'Redirecting to /sub/another (308)',
{ Location: '/sub/another' }
);
})
);
@@ -222,18 +225,21 @@ test(
await testPath(200, '/sub', 'Sub Index Page');
await testPath(200, '/sub/another', 'Sub Another Page');
await testPath(200, '/style.css', 'body { color: green }');
await testPath(308, '/index.html', 'Redirecting...', {
await testPath(308, '/index.html', 'Redirecting to / (308)', {
Location: '/',
});
await testPath(308, '/about.html', 'Redirecting...', {
await testPath(308, '/about.html', 'Redirecting to /about (308)', {
Location: '/about',
});
await testPath(308, '/sub/index.html', 'Redirecting...', {
await testPath(308, '/sub/index.html', 'Redirecting to /sub (308)', {
Location: '/sub',
});
await testPath(308, '/sub/another.html', 'Redirecting...', {
Location: '/sub/another',
});
await testPath(
308,
'/sub/another.html',
'Redirecting to /sub/another (308)',
{ Location: '/sub/another' }
);
}
)
);
@@ -258,16 +264,21 @@ test(
await testPath(200, '/sub/another/', 'Sub Another Page');
await testPath(200, '/style.css', 'body { color: green }');
//TODO: fix this test so that location is `/` instead of `//`
//await testPath(308, '/index.html', 'Redirecting...', { Location: '/' });
await testPath(308, '/about.html', 'Redirecting...', {
//await testPath(308, '/index.html', 'Redirecting to / (308)', { Location: '/' });
await testPath(308, '/about.html', 'Redirecting to /about/ (308)', {
Location: '/about/',
});
await testPath(308, '/sub/index.html', 'Redirecting...', {
await testPath(308, '/sub/index.html', 'Redirecting to /sub/ (308)', {
Location: '/sub/',
});
await testPath(308, '/sub/another.html', 'Redirecting...', {
Location: '/sub/another/',
});
await testPath(
308,
'/sub/another.html',
'Redirecting to /sub/another/ (308)',
{
Location: '/sub/another/',
}
);
})
);
@@ -304,13 +315,13 @@ test(
await testPath(200, '/sub/index.html', 'Sub Index Page');
await testPath(200, '/sub/another.html', 'Sub Another Page');
await testPath(200, '/style.css', 'body { color: green }');
await testPath(308, '/about.html/', 'Redirecting...', {
await testPath(308, '/about.html/', 'Redirecting to /about.html (308)', {
Location: '/about.html',
});
await testPath(308, '/style.css/', 'Redirecting...', {
await testPath(308, '/style.css/', 'Redirecting to /style.css (308)', {
Location: '/style.css',
});
await testPath(308, '/sub', 'Redirecting...', {
await testPath(308, '/sub', 'Redirecting to /sub/ (308)', {
Location: '/sub/',
});
})
@@ -336,15 +347,20 @@ test(
await testPath(200, '/sub/index.html', 'Sub Index Page');
await testPath(200, '/sub/another.html', 'Sub Another Page');
await testPath(200, '/style.css', 'body { color: green }');
await testPath(308, '/about.html/', 'Redirecting...', {
await testPath(308, '/about.html/', 'Redirecting to /about.html (308)', {
Location: '/about.html',
});
await testPath(308, '/sub/', 'Redirecting...', {
await testPath(308, '/sub/', 'Redirecting to /sub (308)', {
Location: '/sub',
});
await testPath(308, '/sub/another.html/', 'Redirecting...', {
Location: '/sub/another.html',
});
await testPath(
308,
'/sub/another.html/',
'Redirecting to /sub/another.html (308)',
{
Location: '/sub/another.html',
}
);
})
);

View File

@@ -44,9 +44,7 @@ function fetchWithRetry(url, opts = {}) {
if (res.status !== opts.status) {
const text = await res.text();
throw new Error(
`Failed to fetch "${url}", received ${res.status}, expected ${
opts.status
}, id: ${res.headers.get('x-vercel-id')}:\n\n${text}\n\n`
`Failed to fetch ${url} with status ${res.status} (expected ${opts.status}):\n\n${text}\n\n`
);
}

View File

@@ -1,4 +0,0 @@
{
"orgId": "team_dummy",
"projectId": "build-output-api-failed-before-builds"
}

View File

@@ -1,4 +0,0 @@
{
"orgId": "team_dummy",
"projectId": "build-output-api-failed-within-build"
}

View File

@@ -1,4 +0,0 @@
{
"orgId": "team_dummy",
"projectId": "build-output-api-preview"
}

View File

@@ -1,4 +0,0 @@
{
"orgId": "team_dummy",
"projectId": "build-output-api-production"
}

View File

@@ -32,9 +32,7 @@ function fetchTokenInformation(token: string, retries = 3) {
if (!res.ok) {
throw new Error(
`Failed to fetch "${url}", status: ${
res.status
}, id: ${res.headers.get('x-vercel-id')}`
`Failed to fetch ${url}, received status ${res.status}`
);
}

View File

@@ -43,9 +43,7 @@ function fetchTokenInformation(token: string, retries = 3) {
if (!res.ok) {
throw new Error(
`Failed to fetch "${url}", status: ${
res.status
}, id: ${res.headers.get('x-vercel-id')}`
`Failed to fetch ${url}, received status ${res.status}`
);
}

View File

@@ -47,9 +47,7 @@ function fetchTokenInformation(token: string, retries = 3) {
if (!res.ok) {
throw new Error(
`Failed to fetch "${url}", status: ${
res.status
}, id: ${res.headers.get('x-vercel-id')}`
`Failed to fetch ${url}, received status ${res.status}`
);
}

View File

@@ -18,7 +18,6 @@ export function useDeployment({
state = 'READY',
createdAt,
project = defaultProject,
target = 'production',
}: {
creator: Pick<User, 'id' | 'email' | 'name' | 'username'>;
state?:
@@ -30,7 +29,6 @@ export function useDeployment({
| 'CANCELED';
createdAt?: number;
project: any; // FIX ME: Use `Project` once PR #9956 is merged
target?: Deployment['target'];
}) {
setupDeploymentEndpoints();
@@ -65,7 +63,7 @@ export function useDeployment({
regions: [],
routes: [],
status: state,
target,
target: 'production',
type: 'LAMBDAS',
url: url.hostname,
version: 2,

View File

@@ -5,11 +5,9 @@ export function useTeams(
teamId?: string,
options: {
failMissingToken?: boolean;
failInvalidToken?: boolean;
failNoAccess?: boolean;
} = {
failMissingToken: false,
failInvalidToken: false,
failNoAccess: false,
}
) {
@@ -36,15 +34,6 @@ export function useTeams(
});
return;
}
if (options.failInvalidToken) {
res.statusCode = 403;
res.json({
message: 'Not authorized',
code: 'forbidden',
invalidToken: true,
});
return;
}
if (options.failNoAccess) {
res.statusCode = 403;

View File

@@ -1,244 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`help command help output snapshots column width 40 1`] = `
"▲ vercel deploy [project-path] [options]
Deploy your project to Vercel. The \`deploy\` command is the default command for the Vercel CLI, and can be omitted (\`vc deploy my-app\` equals \`vc my-app\`).
Options:
--archive Compress
the
deployment
code into
a file
before
uploading
it
-b, --build-env <key=value> Specify
environment
variables
during
build-time
(e.g. \`-b
KEY1=value1
-b
KEY2=value2\`)
-e, --env <key=value> Specify
environment
variables
during
run-time
(e.g. \`-e
KEY1=value1
-e
KEY2=value2\`)
-f, --force Force a
new
deployment
even if
nothing
has
changed
-m, --meta <key=value> Specify
metadata
for the
deployment
(e.g. \`-m
KEY1=value1
-m
KEY2=value2\`)
--no-wait Don't
wait for
the
deployment
to finish
--prebuilt Use in
combination
with \`vc
build\`.
Deploy an
existing
build
--prod Create a
production
deployment
-p, --public
Deployment
is public
(\`/_src\`)
is
exposed)
--regions Set
default
regions
to enable
the
deployment
on
--with-cache Retain
build
cache
when
using
"--force"
-y, --yes Use
default
options
to skip
all
prompts
Examples:
- Deploy the current directory
$ vercel
- Deploy a custom path
$ vercel /usr/src/project
- Deploy with run-time Environment Variables
$ vercel -e NODE_ENV=production
- Deploy with prebuilt outputs
$ vercel build
$ vercel deploy --prebuilt
- Write Deployment URL to a file
$ vercel > deployment-url.txt
"
`;
exports[`help command help output snapshots column width 80 1`] = `
"▲ vercel deploy [project-path] [options]
Deploy your project to Vercel. The \`deploy\` command is the default command for the Vercel CLI, and can be omitted (\`vc deploy my-app\` equals \`vc my-app\`).
Options:
--archive Compress the deployment code into a file before
uploading it
-b, --build-env <key=value> Specify environment variables during build-time
(e.g. \`-b KEY1=value1 -b KEY2=value2\`)
-e, --env <key=value> Specify environment variables during run-time
(e.g. \`-e KEY1=value1 -e KEY2=value2\`)
-f, --force Force a new deployment even if nothing has
changed
-m, --meta <key=value> Specify metadata for the deployment (e.g. \`-m
KEY1=value1 -m KEY2=value2\`)
--no-wait Don't wait for the deployment to finish
--prebuilt Use in combination with \`vc build\`. Deploy an
existing build
--prod Create a production deployment
-p, --public Deployment is public (\`/_src\`) is exposed)
--regions Set default regions to enable the deployment on
--with-cache Retain build cache when using "--force"
-y, --yes Use default options to skip all prompts
Examples:
- Deploy the current directory
$ vercel
- Deploy a custom path
$ vercel /usr/src/project
- Deploy with run-time Environment Variables
$ vercel -e NODE_ENV=production
- Deploy with prebuilt outputs
$ vercel build
$ vercel deploy --prebuilt
- Write Deployment URL to a file
$ vercel > deployment-url.txt
"
`;
exports[`help command help output snapshots column width 120 1`] = `
"▲ vercel deploy [project-path] [options]
Deploy your project to Vercel. The \`deploy\` command is the default command for the Vercel CLI, and can be omitted (\`vc deploy my-app\` equals \`vc my-app\`).
Options:
--archive Compress the deployment code into a file before uploading it
-b, --build-env <key=value> Specify environment variables during build-time (e.g. \`-b KEY1=value1 -b KEY2=value2\`)
-e, --env <key=value> Specify environment variables during run-time (e.g. \`-e KEY1=value1 -e KEY2=value2\`)
-f, --force Force a new deployment even if nothing has changed
-m, --meta <key=value> Specify metadata for the deployment (e.g. \`-m KEY1=value1 -m KEY2=value2\`)
--no-wait Don't wait for the deployment to finish
--prebuilt Use in combination with \`vc build\`. Deploy an existing build
--prod Create a production deployment
-p, --public Deployment is public (\`/_src\`) is exposed)
--regions Set default regions to enable the deployment on
--with-cache Retain build cache when using "--force"
-y, --yes Use default options to skip all prompts
Examples:
- Deploy the current directory
$ vercel
- Deploy a custom path
$ vercel /usr/src/project
- Deploy with run-time Environment Variables
$ vercel -e NODE_ENV=production
- Deploy with prebuilt outputs
$ vercel build
$ vercel deploy --prebuilt
- Write Deployment URL to a file
$ vercel > deployment-url.txt
"
`;

View File

@@ -45,14 +45,6 @@ describe('deploy', () => {
it('should reject deploying when `--prebuilt` is used and `vc build` failed before Builders', async () => {
const cwd = setupUnitFixture('build-output-api-failed-before-builds');
useUser();
useTeams('team_dummy');
useProject({
...defaultProject,
id: 'build-output-api-failed-before-builds',
name: 'build-output-api-failed-before-builds',
});
client.setArgv('deploy', cwd, '--prebuilt');
const exitCodePromise = deploy(client);
await expect(client.stderr).toOutput(
@@ -64,14 +56,6 @@ describe('deploy', () => {
it('should reject deploying when `--prebuilt` is used and `vc build` failed within a Builder', async () => {
const cwd = setupUnitFixture('build-output-api-failed-within-build');
useUser();
useTeams('team_dummy');
useProject({
...defaultProject,
id: 'build-output-api-failed-within-build',
name: 'build-output-api-failed-within-build',
});
client.setArgv('deploy', cwd, '--prebuilt');
const exitCodePromise = deploy(client);
await expect(client.stderr).toOutput(
@@ -81,16 +65,7 @@ describe('deploy', () => {
});
it('should reject deploying a directory that does not contain ".vercel/output" when `--prebuilt` is used', async () => {
useUser();
useTeams('team_dummy');
useProject({
...defaultProject,
name: 'static',
id: 'static',
});
client.cwd = setupUnitFixture('commands/deploy/static');
client.setArgv('deploy', '--prebuilt');
client.setArgv('deploy', __dirname, '--prebuilt');
const exitCodePromise = deploy(client);
await expect(client.stderr).toOutput(
'Error: The "--prebuilt" option was used, but no prebuilt output found in ".vercel/output". Run `vercel build` to generate a local build.\n'
@@ -127,8 +102,8 @@ describe('deploy', () => {
useTeams('team_dummy');
useProject({
...defaultProject,
id: 'build-output-api-production',
name: 'build-output-api-production',
id: 'build-output-api-preview',
name: 'build-output-api-preview',
});
client.setArgv('deploy', cwd, '--prebuilt');

View File

@@ -1,71 +0,0 @@
import {
calcLineLength,
help,
lineToString,
outputArrayToString,
} from '../../../src/commands/help';
import { deployCommand } from '../../../src/commands/deploy/command';
import chalk from 'chalk';
describe('help command', () => {
describe('calcLineLength', () => {
test.each([
{
name: 'without ansi',
line: ['a line without ansi'],
expectedLength: 19,
},
{
name: 'with ansi',
line: [`a line with ${chalk.red('ansi')}`],
expectedLength: 16,
},
])(
'should calculate the correct line length $name',
({ line, expectedLength }) => {
expect(calcLineLength(line)).toBe(expectedLength);
}
);
});
describe('lineToString', () => {
test.each([
{
line: ['a', 'b', 'c'],
expected: 'a b c',
},
{
line: [' ', 'a', ' ', 'b', ' ', 'c', ' '],
expected: ' a b c ',
},
{
line: [' ', ' ', ' '],
expected: ' ',
},
{
line: ['a', ' ', ' ', 'b', 'c'],
expected: 'a b c',
},
])(
'should insert spaces between non-whitespace items only; $line',
({ line, expected }) => {
expect(lineToString(line)).toBe(expected);
}
);
});
describe('outputArrayToString', () => {
test('should join a list of strings using newlines', () => {
expect(outputArrayToString(['line 1', 'line 2', 'line 3'])).toBe(
'line 1\nline 2\nline 3'
);
});
});
describe('help output snapshots', () => {
test.each([40, 80, 120])('column width %i', width => {
expect(help(deployCommand, { columns: width })).toMatchSnapshot();
});
});
});

View File

@@ -101,71 +101,6 @@ describe('promote', () => {
await expect(exitCodePromise).resolves.toEqual(0);
});
it('should fail to promote a preview deployment when user says no', async () => {
const { cwd, previousDeployment } = initPromoteTest({
deploymentTarget: 'preview',
});
client.cwd = cwd;
client.setArgv('promote', previousDeployment.url);
const exitCodePromise = promote(client);
await expect(client.stderr).toOutput(
`Fetching deployment "${previousDeployment.url}" in ${previousDeployment.creator?.username}`
);
await expect(client.stderr).toOutput(
'? This deployment does not target production, therefore promotion will not apply\n production environment variables. Are you sure you want to continue?'
);
// say "no" to the prompt
client.stdin.write('n\n');
await expect(client.stderr).toOutput('Error: Canceled');
await expect(exitCodePromise).resolves.toEqual(0);
});
it('should promote a preview deployment when user says yes', async () => {
const { cwd, previousDeployment } = initPromoteTest({
deploymentTarget: 'preview',
});
client.cwd = cwd;
client.setArgv('promote', previousDeployment.url);
const exitCodePromise = promote(client);
await expect(client.stderr).toOutput(
`Fetching deployment "${previousDeployment.url}" in ${previousDeployment.creator?.username}`
);
await expect(client.stderr).toOutput(
'? This deployment does not target production, therefore promotion will not apply\n production environment variables. Are you sure you want to continue?'
);
// say "yes" to the prompt
client.stdin.write('y\n');
await expect(exitCodePromise).resolves.toEqual(0);
});
it('should promote a preview deployment with --yes', async () => {
const { cwd, previousDeployment } = initPromoteTest({
deploymentTarget: 'preview',
});
client.cwd = cwd;
client.setArgv('promote', previousDeployment.url, '--yes');
const exitCodePromise = promote(client);
await expect(client.stderr).toOutput(
`Fetching deployment "${previousDeployment.url}" in ${previousDeployment.creator?.username}`
);
await expect(client.stderr).toOutput('Promote in progress');
await expect(client.stderr).toOutput(
`Success! ${chalk.bold('vercel-promote')} was promoted to ${
previousDeployment.url
} (${previousDeployment.id})`
);
await expect(exitCodePromise).resolves.toEqual(0);
});
it('should get status while promoting', async () => {
const { cwd, previousDeployment, project } = initPromoteTest({
promotePollCount: 10,
@@ -343,13 +278,11 @@ function initPromoteTest({
promoteJobStatus = 'succeeded',
promotePollCount = 2,
promoteStatusCode,
deploymentTarget,
}: {
promoteAliases?: DeploymentAlias[];
promoteJobStatus?: LastAliasRequest['jobStatus'];
promotePollCount?: number;
promoteStatusCode?: number;
deploymentTarget?: Deployment['target'];
} = {}) {
const cwd = setupUnitFixture('commands/promote/simple-next-site');
const user = useUser();
@@ -361,11 +294,7 @@ function initPromoteTest({
});
const currentDeployment = useDeployment({ creator: user, project });
const previousDeployment = useDeployment({
creator: user,
project,
target: deploymentTarget,
});
const previousDeployment = useDeployment({ creator: user, project });
let pollCounter = 0;
let lastAliasRequest: LastAliasRequest | null = null;

View File

@@ -5,7 +5,6 @@ import { setupUnitFixture } from '../../helpers/setup-unit-fixture';
import { useDeployment } from '../../mocks/deployment';
import { useTeams } from '../../mocks/team';
import { useUser } from '../../mocks/user';
import { Deployment } from '@vercel-internals/types';
describe('redeploy', () => {
it('should error if missing deployment url', async () => {
@@ -78,20 +77,9 @@ describe('redeploy', () => {
await expect(exitCodePromise).resolves.toEqual(0);
});
it('should redeploy to preview', async () => {
const { fromDeployment } = initRedeployTest({ target: null });
client.setArgv('rollback', fromDeployment.id);
const exitCodePromise = redeploy(client);
await expect(client.stderr).toOutput(
`Fetching deployment "${fromDeployment.id}" in ${fromDeployment.creator?.username}`
);
await expect(client.stderr).toOutput('Preview');
await expect(exitCodePromise).resolves.toEqual(0);
});
});
function initRedeployTest({ target }: { target?: Deployment['target'] } = {}) {
function initRedeployTest() {
setupUnitFixture('commands/redeploy/simple-static');
const user = useUser();
useTeams('team_dummy');
@@ -100,18 +88,10 @@ function initRedeployTest({ target }: { target?: Deployment['target'] } = {}) {
id: 'vercel-redeploy',
name: 'vercel-redeploy',
});
const fromDeployment = useDeployment({ creator: user, target });
const toDeployment = useDeployment({ creator: user, target });
const fromDeployment = useDeployment({ creator: user });
const toDeployment = useDeployment({ creator: user });
client.scenario.post(`/v13/deployments`, (req, res) => {
const { target } = req.body;
if (target !== undefined && typeof target !== 'string') {
res.status(400).json({
message: 'Invalid request: `target` should be string',
});
return;
}
res.json(toDeployment);
});

View File

@@ -1,34 +0,0 @@
import { repoInfoToUrl } from '../../../../src/util/git/repo-info-to-url';
import type { RepoInfo } from '../../../../src/util/git/connect-git-provider';
describe('repoInfoToUrl()', () => {
it('should support "github" URL', () => {
const info: RepoInfo = {
provider: 'github',
org: 'vercel',
repo: 'foo',
url: 'git@github.com:vercel/foo.git',
};
expect(repoInfoToUrl(info)).toEqual('https://github.com/vercel/foo');
});
it('should support "gitlab" URL', () => {
const info: RepoInfo = {
provider: 'gitlab',
org: 'vercel',
repo: 'foo',
url: 'git@gitlab.com:vercel/foo.git',
};
expect(repoInfoToUrl(info)).toEqual('https://gitlab.com/vercel/foo');
});
it('should support "bitbucket" URL', () => {
const info: RepoInfo = {
provider: 'bitbucket',
org: 'vercel',
repo: 'foo',
url: 'git@bitbucket.com:vercel/foo.git',
};
expect(repoInfoToUrl(info)).toEqual('https://bitbucket.com/vercel/foo');
});
});

View File

@@ -1,62 +1,91 @@
import { tmpdir } from 'node:os';
import { join, sep } from 'node:path';
import {
findProjectsFromPath,
findProjectFromPath,
findRepoRoot,
RepoProjectConfig,
traverseUpDirectories,
} from '../../../../src/util/link/repo';
import { client } from '../../../mocks/client';
const isWindows = process.platform === 'win32';
// Root of `vercel/vercel` repo
const vercelRepoRoot = join(__dirname, '../../../../../..');
describe('findRepoRoot()', () => {
it('should find Git repo root from root', async () => {
const repoRoot = await findRepoRoot(client, vercelRepoRoot);
const repoRoot = await findRepoRoot(vercelRepoRoot);
expect(repoRoot).toEqual(vercelRepoRoot);
});
it('should find Git repo root sub directory', async () => {
const repoRoot = await findRepoRoot(client, __dirname);
const repoRoot = await findRepoRoot(__dirname);
expect(repoRoot).toEqual(vercelRepoRoot);
});
it('should return `undefined` when no Git root found', async () => {
const repoRoot = await findRepoRoot(client, tmpdir());
const repoRoot = await findRepoRoot(tmpdir());
expect(repoRoot).toEqual(undefined);
});
});
describe('findProjectsFromPath()', () => {
describe('traverseUpDirectories()', () => {
test.each(
isWindows
? [
{
input: 'C:\\foo\\bar\\baz',
expected: ['C:\\foo\\bar\\baz', 'C:\\foo\\bar', 'C:\\foo', 'C:\\'],
},
{
input: 'C:\\foo\\..\\bar\\.\\baz',
expected: ['C:\\bar\\baz', 'C:\\bar', 'C:\\'],
},
]
: [
{
input: '/foo/bar/baz',
expected: ['/foo/bar/baz', '/foo/bar', '/foo', '/'],
},
{
input: '/foo/../bar/./baz',
expected: ['/bar/baz', '/bar', '/'],
},
]
)('should traverse "$input"', ({ input, expected }) => {
expect(Array.from(traverseUpDirectories(input))).toEqual(expected);
});
});
describe('findProjectFromPath()', () => {
const projects: RepoProjectConfig[] = [
{ id: 'root', name: 'r', directory: '.' },
{ id: 'site', name: 'a', directory: 'apps/site' },
{ id: 'site2', name: 'a', directory: 'apps/site2' },
{ id: 'other', name: 'b', directory: 'apps/other' },
{ id: 'duplicate', name: 'd', directory: 'apps/other' },
{ id: 'nested', name: 'n', directory: 'apps/other/nested' },
];
it.each([
{ ids: ['root'], path: '.' },
{ ids: ['root'], path: 'lib' },
{ ids: ['root'], path: 'lib' },
{ ids: ['site'], path: `apps${sep}site` },
{ ids: ['site'], path: `apps${sep}site` },
{ ids: ['site'], path: `apps${sep}site${sep}components` },
{ ids: ['site2'], path: `apps${sep}site2` },
{ ids: ['site2'], path: `apps${sep}site2${sep}inner` },
{ ids: ['other', 'duplicate'], path: `apps${sep}other` },
{ ids: ['other', 'duplicate'], path: `apps${sep}other${sep}lib` },
{ ids: ['nested'], path: `apps${sep}other${sep}nested` },
{ ids: ['nested'], path: `apps${sep}other${sep}nested${sep}foo` },
])('should find Project "$id" for path "$path"', ({ path, ids }) => {
const actual = findProjectsFromPath(projects, path);
expect(actual.map(a => a.id)).toEqual(ids);
{ id: 'root', path: '.' },
{ id: 'root', path: 'lib' },
{ id: 'root', path: 'lib' },
{ id: 'site', path: `apps${sep}site` },
{ id: 'site', path: `apps${sep}site` },
{ id: 'site', path: `apps${sep}site${sep}components` },
{ id: 'site2', path: `apps${sep}site2` },
{ id: 'site2', path: `apps${sep}site2${sep}inner` },
{ id: 'other', path: `apps${sep}other` },
{ id: 'other', path: `apps${sep}other${sep}lib` },
{ id: 'nested', path: `apps${sep}other${sep}nested` },
{ id: 'nested', path: `apps${sep}other${sep}nested${sep}foo` },
])('should find Project "$id" for path "$path"', ({ path, id }) => {
const actual = findProjectFromPath(projects, path);
expect(actual?.id).toEqual(id);
});
it('should return empty array when there are no matching Projects', () => {
const actual = findProjectsFromPath([projects[1]], '.');
expect(actual).toHaveLength(0);
it('should return `undefined` when there are no matching Projects', () => {
const actual = findProjectFromPath([projects[1]], '.');
expect(actual).toBeUndefined();
});
});

View File

@@ -1,30 +0,0 @@
import { join } from 'path';
import { detectProjects } from '../../../../src/util/projects/detect-projects';
const REPO_ROOT = join(__dirname, '../../../../../..');
const EXAMPLES_DIR = join(REPO_ROOT, 'examples');
const FS_DETECTORS_FIXTURES = join(
REPO_ROOT,
'packages/fs-detectors/test/fixtures'
);
describe('detectProjects()', () => {
it('should match "nextjs" example', async () => {
const dir = join(EXAMPLES_DIR, 'nextjs');
const detected = await detectProjects(dir);
expect([...detected.entries()]).toEqual([['', 'nextjs']]);
});
it('should match "30-double-nested-workspaces"', async () => {
const dir = join(FS_DETECTORS_FIXTURES, '30-double-nested-workspaces');
const detected = await detectProjects(dir);
expect(
[...detected.entries()].sort((a, b) => a[0].localeCompare(b[0]))
).toEqual([
['packages/backend/c', 'remix'],
['packages/backend/d', 'nextjs'],
['packages/frontend/a', 'hexo'],
['packages/frontend/b', 'ember'],
]);
});
});

View File

@@ -1,104 +0,0 @@
import { client } from '../../../mocks/client';
import getProjectByDeployment from '../../../../src/util/projects/get-project-by-deployment';
import { useTeams } from '../../../mocks/team';
import { useUser } from '../../../mocks/user';
import { useDeployment } from '../../../mocks/deployment';
import { defaultProject, useProject } from '../../../mocks/project';
describe('getProjectByDeployment', () => {
it('should get project and deployment', async () => {
const user = useUser();
const { project: p } = useProject({
...defaultProject,
id: 'foo',
name: 'foo',
});
const d = useDeployment({
creator: user,
createdAt: Date.now(),
project: p,
});
const { deployment, project } = await getProjectByDeployment({
client,
deployId: d.id,
output: client.output,
});
expect(project.id).toBe(p.id);
expect(deployment.id).toBe(d.id);
});
it('should get project and deployment associated to a team', async () => {
const [team] = useTeams('team_dummy');
const user = useUser();
const { project: p } = useProject({
...defaultProject,
id: 'foo',
name: 'foo',
});
const d = useDeployment({
creator: {
id: team.id,
name: team.name,
email: user.email,
username: team.slug,
},
createdAt: Date.now(),
project: p,
});
client.config.currentTeam = team.id;
d.team = team;
const { deployment, project } = await getProjectByDeployment({
client,
deployId: d.id,
output: client.output,
});
expect(project.id).toBe(p.id);
expect(deployment.id).toBe(d.id);
});
it("should error if deployment team doesn't match current user's team", async () => {
const [team] = useTeams('team_dummy');
const user = useUser();
const { project: p } = useProject({
...defaultProject,
id: 'foo',
name: 'foo',
});
const d = useDeployment({
creator: {
id: team.id,
name: team.name,
email: user.email,
username: team.slug,
},
createdAt: Date.now(),
project: p,
});
client.config.currentTeam = team.id;
await expect(
getProjectByDeployment({
client,
deployId: d.id,
output: client.output,
})
).rejects.toThrowError("Deployment doesn't belong to current team");
client.config.currentTeam = undefined;
d.team = team;
await expect(
getProjectByDeployment({
client,
deployId: d.id,
output: client.output,
})
).rejects.toThrowError('Deployment belongs to a different team');
});
});

View File

@@ -41,35 +41,6 @@ describe('getLinkedProject', () => {
);
});
it('should fail to return a link when token is invalid', async () => {
const cwd = fixture('vercel-pull-next');
useUser();
useTeams('team_dummy', { failInvalidToken: true });
useProject({
...defaultProject,
id: 'vercel-pull-next',
name: 'vercel-pull-next',
});
let link: UnPromisify<ReturnType<typeof getLinkedProject>> | undefined;
let error: Error | undefined;
try {
link = await getLinkedProject(client, cwd);
} catch (err) {
error = err as Error;
}
expect(link).toBeUndefined();
if (!error) {
throw new Error(`Expected an error to be thrown.`);
}
expect(error.message).toBe(
'The specified token is not valid. Use `vercel login` to generate a new token.'
);
});
it('should fail to return a link when no access to team', async () => {
const cwd = fixture('vercel-pull-next');

View File

@@ -0,0 +1,4 @@
declare module 'supports-hyperlinks' {
import { Writable } from 'stream';
export function supportsHyperlink(stream: Writable): boolean;
}

View File

@@ -1,26 +1,5 @@
# @vercel/client
## 12.6.5
### Patch Changes
- Updated dependencies [[`0750517af`](https://github.com/vercel/vercel/commit/0750517af99aea41410d4f1f772ce427699554e7)]:
- @vercel/build-utils@6.8.2
## 12.6.4
### Patch Changes
- Updated dependencies [[`7021279b2`](https://github.com/vercel/vercel/commit/7021279b284f314a4d1bdbb4306b4c22291efa08)]:
- @vercel/build-utils@6.8.1
## 12.6.3
### Patch Changes
- Updated dependencies [[`346892210`](https://github.com/vercel/vercel/commit/3468922108f411482a72acd0331f0f2ee52a6d4c)]:
- @vercel/build-utils@6.8.0
## 12.6.2
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/client",
"version": "12.6.5",
"version": "12.6.2",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"homepage": "https://vercel.com",
@@ -35,7 +35,7 @@
"typescript": "4.9.5"
},
"dependencies": {
"@vercel/build-utils": "6.8.2",
"@vercel/build-utils": "6.7.5",
"@vercel/routing-utils": "2.2.1",
"@zeit/fetch": "5.2.0",
"async-retry": "1.2.3",

View File

@@ -21,7 +21,7 @@
"build:docs": "typedoc && node scripts/fix-links.js && prettier --write docs/**/*.md docs/*.md"
},
"devDependencies": {
"@edge-runtime/jest-environment": "2.2.3",
"@edge-runtime/jest-environment": "2.0.0",
"@types/jest": "27.4.1",
"ts-node": "8.9.1",
"tsup": "6.1.2",

View File

@@ -1,7 +0,0 @@
# @vercel/frameworks
## 1.4.3
### Patch Changes
- [frameworks] Update `saber.land` to `saber.egoist.dev` ([#10148](https://github.com/vercel/vercel/pull/10148))

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/frameworks",
"version": "1.4.3",
"version": "1.4.2",
"main": "./dist/frameworks.js",
"types": "./dist/frameworks.d.ts",
"files": [

View File

@@ -1291,7 +1291,7 @@ export const frameworks = [
tagline:
'Saber is a framework for building static sites in Vue.js that supports data from any source.',
description: 'A Saber site, created with npm init.',
website: 'https://saber.egoist.dev',
website: 'https://saber.land/',
detectors: {
every: [
{

View File

@@ -1,26 +1,5 @@
# @vercel/fs-detectors
## 4.1.0
### Minor Changes
- Add `detectFrameworks()` function ([#10195](https://github.com/vercel/vercel/pull/10195))
## 4.0.1
### Patch Changes
- Resolve symlinks in `LocalFileSystemDetector#readdir()` ([#10126](https://github.com/vercel/vercel/pull/10126))
- Updated dependencies [[`0867f11a6`](https://github.com/vercel/vercel/commit/0867f11a6a1086ef4f4701db2b98da8fcc299586)]:
- @vercel/frameworks@1.4.3
## 4.0.0
### Major Changes
- `LocalFileSystemDetector#readdir()` now returns paths relative to the root dir, instead of absolute paths. This is to align with the usage of the detectors that are using the `DetectorFilesystem` interface. ([#10100](https://github.com/vercel/vercel/pull/10100))
## 3.9.3
### Patch Changes

Some files were not shown because too many files have changed in this diff Show More