mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-25 03:39:14 +00:00
Compare commits
38 Commits
@vercel/cl
...
@vercel/ne
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31f79c7de1 | ||
|
|
4c230c8436 | ||
|
|
7941f5a104 | ||
|
|
5b931afbf3 | ||
|
|
15080364b8 | ||
|
|
47e3381c6d | ||
|
|
33aefdc029 | ||
|
|
30fe76a0cf | ||
|
|
97ef88dc28 | ||
|
|
f679098d7a | ||
|
|
2b57e12ad3 | ||
|
|
c4e94ad03f | ||
|
|
32afd67d29 | ||
|
|
7523e39f18 | ||
|
|
99f2f2f1ba | ||
|
|
63830d38ce | ||
|
|
f3428dd212 | ||
|
|
5eb8b16cbd | ||
|
|
226bf02be2 | ||
|
|
8505872f55 | ||
|
|
7db6436797 | ||
|
|
e2d76e9c92 | ||
|
|
337cb21d67 | ||
|
|
6bfff3e9eb | ||
|
|
ac5b259c11 | ||
|
|
bfc553db11 | ||
|
|
2b101d4692 | ||
|
|
3316f38cb4 | ||
|
|
7837387127 | ||
|
|
f478200dd3 | ||
|
|
c29de8206a | ||
|
|
a2df3b5463 | ||
|
|
73446e544a | ||
|
|
21ff4a58c3 | ||
|
|
2b9eb02b8c | ||
|
|
4ef4722460 | ||
|
|
be5308b137 | ||
|
|
08a83a94f8 |
@@ -1,7 +1,9 @@
|
||||
# Runtime Developer Reference
|
||||
|
||||
The following page is a reference for how to create a Runtime by implementing
|
||||
the Runtime API interface.
|
||||
the Runtime API interface. It's a way to add support for a new programming language to Vercel.
|
||||
|
||||
> Note: If you're the author of a web framework, please use the [Build Output API](https://vercel.com/docs/build-output-api/v3) instead to make your framework compatible with Vercel.
|
||||
|
||||
A Runtime is an npm module that implements the following interface:
|
||||
|
||||
|
||||
@@ -35,6 +35,6 @@ For details on how to use Vercel, check out our [documentation](https://vercel.c
|
||||
|
||||
## Contributing
|
||||
|
||||
- [Code of Conduct](https://github.com/vercel/vercel/blob/main/.github/CODE_OF_CONDUCT.md)
|
||||
- [Contributing Guidelines](https://github.com/vercel/vercel/blob/main/.github/CONTRIBUTING.md)
|
||||
- [MIT License](https://github.com/vercel/vercel/blob/main/LICENSE)
|
||||
- [Code of Conduct](./.github/CODE_OF_CONDUCT.md)
|
||||
- [Contributing Guidelines](./.github/CONTRIBUTING.md)
|
||||
- [MIT License](./LICENSE)
|
||||
|
||||
1
examples/astro/.gitignore
vendored
1
examples/astro/.gitignore
vendored
@@ -18,3 +18,4 @@ pnpm-debug.log*
|
||||
|
||||
# macOS-specific files
|
||||
.DS_Store
|
||||
.vercel
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
README.md
|
||||
@@ -1,10 +1,16 @@
|
||||
# Welcome to [Astro](https://astro.build)
|
||||
# Astro
|
||||
|
||||
[](https://stackblitz.com/github/withastro/astro/tree/latest/examples/starter)
|
||||
This directory is a brief example of an [Astro](https://astro.build/) site that can be deployed to Vercel with zero configuration.
|
||||
|
||||
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
||||
## Deploy Your Own
|
||||
|
||||
## 🚀 Project Structure
|
||||
Deploy your own Astro project with Vercel.
|
||||
|
||||
[](https://vercel.com/new/clone?repository-url=https://github.com/vercel/vercel/tree/main/examples/astro&template=astro)
|
||||
|
||||
_Live Example: https://astro-template.vercel.app_
|
||||
|
||||
## Project Structure
|
||||
|
||||
Inside of your Astro project, you'll see the following folders and files:
|
||||
|
||||
@@ -26,17 +32,15 @@ There's nothing special about `src/components/`, but that's where we like to put
|
||||
|
||||
Any static assets, like images, can be placed in the `public/` directory.
|
||||
|
||||
## 🧞 Commands
|
||||
## Commands
|
||||
|
||||
All commands are run from the root of the project, from a terminal:
|
||||
|
||||
| Command | Action |
|
||||
| :---------------- | :------------------------------------------- |
|
||||
| `npm install` | Installs dependencies |
|
||||
| `npm run dev` | Starts local dev server at `localhost:3000` |
|
||||
| `npm run build` | Build your production site to `./dist/` |
|
||||
| `npm run preview` | Preview your build locally, before deploying |
|
||||
|
||||
## 👀 Want to learn more?
|
||||
|
||||
Feel free to check [our documentation](https://github.com/withastro/astro) or jump into our [Discord server](https://astro.build/chat).
|
||||
| Command | Action |
|
||||
| :--------------------- | :------------------------------------------------- |
|
||||
| `npm install` | Installs dependencies |
|
||||
| `npm run dev` | Starts local dev server at `localhost:3000` |
|
||||
| `npm run build` | Build your production site to `./dist/` |
|
||||
| `npm run preview` | Preview your build locally, before deploying |
|
||||
| `npm run astro ...` | Run CLI commands like `astro add`, `astro preview` |
|
||||
| `npm run astro --help` | Get help using the Astro CLI |
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
{
|
||||
"name": "@example/basics",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"start": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview"
|
||||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
},
|
||||
"devDependencies": {
|
||||
"astro": "^1.0.0-beta.20"
|
||||
"astro": "^1.0.0-rc.8"
|
||||
}
|
||||
}
|
||||
|
||||
76
examples/astro/src/components/Card.astro
Normal file
76
examples/astro/src/components/Card.astro
Normal file
@@ -0,0 +1,76 @@
|
||||
---
|
||||
export interface Props {
|
||||
title: string;
|
||||
body: string;
|
||||
href: string;
|
||||
}
|
||||
|
||||
const { href, title, body } = Astro.props as Props;
|
||||
---
|
||||
|
||||
<li class="link-card">
|
||||
<a href={href}>
|
||||
<h2>
|
||||
{title}
|
||||
<span>→</span>
|
||||
</h2>
|
||||
<p>
|
||||
{body}
|
||||
</p>
|
||||
</a>
|
||||
</li>
|
||||
<style>
|
||||
:root {
|
||||
--link-gradient: linear-gradient(45deg, #4f39fa, #da62c4 30%, var(--color-border) 60%);
|
||||
}
|
||||
|
||||
.link-card {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
padding: 0.15rem;
|
||||
background-image: var(--link-gradient);
|
||||
background-size: 400%;
|
||||
border-radius: 0.5rem;
|
||||
background-position: 100%;
|
||||
transition: background-position 0.6s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.link-card > a {
|
||||
width: 100%;
|
||||
text-decoration: none;
|
||||
line-height: 1.4;
|
||||
padding: 1em 1.3em;
|
||||
border-radius: 0.35rem;
|
||||
color: var(--text-color);
|
||||
background-color: white;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
transition: color 0.6s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0.75rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
h2 span {
|
||||
display: inline-block;
|
||||
transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.link-card:is(:hover, :focus-within) {
|
||||
background-position: 0;
|
||||
}
|
||||
|
||||
.link-card:is(:hover, :focus-within) h2 {
|
||||
color: #4f39fa;
|
||||
}
|
||||
|
||||
.link-card:is(:hover, :focus-within) h2 span {
|
||||
will-change: transform;
|
||||
transform: translateX(2px);
|
||||
}
|
||||
</style>
|
||||
@@ -1,55 +0,0 @@
|
||||
---
|
||||
export interface Props {
|
||||
title: string;
|
||||
}
|
||||
|
||||
const { title } = Astro.props as Props;
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<title>{title}</title>
|
||||
</head>
|
||||
<body>
|
||||
<slot />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--font-size-base: clamp(1rem, 0.34vw + 0.91rem, 1.19rem);
|
||||
--font-size-lg: clamp(1.2rem, 0.7vw + 1.2rem, 1.5rem);
|
||||
--font-size-xl: clamp(2.44rem, 2.38vw + 1.85rem, 3.75rem);
|
||||
|
||||
--color-text: hsl(12, 5%, 4%);
|
||||
--color-bg: hsl(10, 21%, 95%);
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: system-ui, sans-serif;
|
||||
font-size: var(--font-size-base);
|
||||
color: var(--color-text);
|
||||
background-color: var(--color-bg);
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:global(h1) {
|
||||
font-size: var(--font-size-xl);
|
||||
}
|
||||
|
||||
:global(h2) {
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
|
||||
:global(code) {
|
||||
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
||||
Bitstream Vera Sans Mono, Courier New, monospace;
|
||||
}
|
||||
</style>
|
||||
1
examples/astro/src/env.d.ts
vendored
Normal file
1
examples/astro/src/env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="astro/client" />
|
||||
56
examples/astro/src/layouts/Layout.astro
Normal file
56
examples/astro/src/layouts/Layout.astro
Normal file
@@ -0,0 +1,56 @@
|
||||
---
|
||||
export interface Props {
|
||||
title: string;
|
||||
}
|
||||
|
||||
const { title } = Astro.props as Props;
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<title>{title}</title>
|
||||
</head>
|
||||
<body>
|
||||
<slot />
|
||||
</body>
|
||||
</html>
|
||||
<style>
|
||||
:root {
|
||||
--font-size-base: clamp(1rem, 0.34vw + 0.91rem, 1.19rem);
|
||||
--font-size-lg: clamp(1.2rem, 0.7vw + 1.2rem, 1.5rem);
|
||||
--font-size-xl: clamp(2.44rem, 2.38vw + 1.85rem, 3.75rem);
|
||||
|
||||
--color-text: hsl(12, 5%, 4%);
|
||||
--color-bg: hsl(10, 21%, 95%);
|
||||
--color-border: hsl(17, 24%, 90%);
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: system-ui, sans-serif;
|
||||
font-size: var(--font-size-base);
|
||||
color: var(--color-text);
|
||||
background-color: var(--color-bg);
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:global(h1) {
|
||||
font-size: var(--font-size-xl);
|
||||
}
|
||||
|
||||
:global(h2) {
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
|
||||
:global(code) {
|
||||
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
||||
Bitstream Vera Sans Mono, Courier New, monospace;
|
||||
}
|
||||
</style>
|
||||
@@ -1,81 +1,52 @@
|
||||
---
|
||||
import Layout from '../components/Layout.astro';
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import Card from '../components/Card.astro';
|
||||
---
|
||||
|
||||
<Layout title="Welcome to Astro.">
|
||||
<main>
|
||||
<h1>Welcome to <span class="text-gradient">Astro</span></h1>
|
||||
<p class="instructions"><strong>Your first mission:</strong> tweak this message to try our hot module reloading. Check the <code>src/pages</code> directory!</p>
|
||||
<p class="instructions">
|
||||
Check out the <code>src/pages</code> directory to get started.<br />
|
||||
<strong>Code Challenge:</strong> Tweak the "Welcome to Astro" message above.
|
||||
</p>
|
||||
<ul role="list" class="link-card-grid">
|
||||
<li class="link-card">
|
||||
<a href="https://astro.build/integrations/">
|
||||
<h2>Integrations <span>→</span></h2>
|
||||
<p>Add component frameworks, Tailwind, Partytown, and more!</p>
|
||||
</a>
|
||||
</li>
|
||||
<li class="link-card">
|
||||
<a href="https://astro.build/themes/">
|
||||
<h2>Themes <span>→</span></h2>
|
||||
<p>Explore a galaxy of community-built starters.</p>
|
||||
</a>
|
||||
</li>
|
||||
<li class="link-card">
|
||||
<a href="https://docs.astro.build/">
|
||||
<h2>Docs <span>→</span></h2>
|
||||
<p>Learn our complete feature set and explore the API.</p>
|
||||
</a>
|
||||
</li>
|
||||
<li class="link-card">
|
||||
<a href="https://astro.build/chat/">
|
||||
<h2>Chat <span>→</span></h2>
|
||||
<p>
|
||||
Ask, contribute, and have fun on our community Discord
|
||||
<svg
|
||||
class="heart"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
width="16"
|
||||
height="16"
|
||||
fill="currentColor"
|
||||
>
|
||||
<title>heart</title>
|
||||
<path d="M256 448l-30.164-27.211C118.718 322.442 48 258.61 48 179.095 48 114.221 97.918 64 162.4 64c36.399 0 70.717 16.742 93.6 43.947C278.882 80.742 313.199 64 349.6 64 414.082 64 464 114.221 464 179.095c0 79.516-70.719 143.348-177.836 241.694L256 448z" />
|
||||
</svg>
|
||||
</p>
|
||||
</a>
|
||||
</li>
|
||||
<Card
|
||||
href="https://docs.astro.build/"
|
||||
title="Documentation"
|
||||
body="Learn how Astro works and explore the official API docs."
|
||||
/>
|
||||
<Card
|
||||
href="https://astro.build/integrations/"
|
||||
title="Integrations"
|
||||
body="Supercharge your project with new frameworks and libraries."
|
||||
/>
|
||||
<Card
|
||||
href="https://astro.build/themes/"
|
||||
title="Themes"
|
||||
body="Explore a galaxy of community-built starter themes."
|
||||
/>
|
||||
<Card
|
||||
href="https://astro.build/chat/"
|
||||
title="Chat"
|
||||
body="Come say hi to our amazing Discord community. ❤️"
|
||||
/>
|
||||
</ul>
|
||||
</main>
|
||||
</Layout>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--color-border: hsl(17, 24%, 90%);
|
||||
--astro-gradient: linear-gradient(0deg,#4F39FA, #DA62C4);
|
||||
--link-gradient: linear-gradient(45deg, #4F39FA, #DA62C4 30%, var(--color-border) 60%);
|
||||
--night-sky-gradient: linear-gradient(0deg, #392362 -33%, #431f69 10%, #30216b 50%, #1f1638 100%);
|
||||
--astro-gradient: linear-gradient(0deg, #4f39fa, #da62c4);
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
transition: color 0.6s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
h2 span {
|
||||
display: inline-block;
|
||||
transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 0.875em;
|
||||
border: 0.1em solid var(--color-border);
|
||||
border-radius: 4px;
|
||||
padding: 0.15em 0.25em;
|
||||
h1 {
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
main {
|
||||
margin: auto;
|
||||
padding: 1em;
|
||||
padding: 1em;
|
||||
max-width: 60ch;
|
||||
}
|
||||
|
||||
@@ -83,7 +54,7 @@ import Layout from '../components/Layout.astro';
|
||||
font-weight: 900;
|
||||
background-image: var(--astro-gradient);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-size: 100% 200%;
|
||||
background-position-y: 100%;
|
||||
border-radius: 0.4rem;
|
||||
@@ -91,7 +62,8 @@ import Layout from '../components/Layout.astro';
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
0%,
|
||||
100% {
|
||||
background-position-y: 0%;
|
||||
}
|
||||
50% {
|
||||
@@ -100,75 +72,25 @@ import Layout from '../components/Layout.astro';
|
||||
}
|
||||
|
||||
.instructions {
|
||||
line-height: 1.8;
|
||||
margin-bottom: 2rem;
|
||||
background-image: var(--night-sky-gradient);
|
||||
padding: 1.5rem;
|
||||
line-height: 1.6;
|
||||
margin: 1rem 0;
|
||||
background: #4f39fa;
|
||||
padding: 1rem;
|
||||
border-radius: 0.4rem;
|
||||
color: var(--color-bg);
|
||||
}
|
||||
|
||||
.instructions code {
|
||||
font-size: 0.875em;
|
||||
border: 0.1em solid var(--color-border);
|
||||
border-radius: 4px;
|
||||
padding: 0.15em 0.25em;
|
||||
}
|
||||
|
||||
.link-card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(24ch, 1fr));
|
||||
gap: 1rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.link-card {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
padding: 0.15rem;
|
||||
background-image: var(--link-gradient);
|
||||
background-size: 400%;
|
||||
border-radius: 0.5rem;
|
||||
background-position: 100%;
|
||||
transition: background-position 0.6s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.link-card > a {
|
||||
width: 100%;
|
||||
text-decoration: none;
|
||||
line-height: 1.4;
|
||||
padding: 1em 1.3em;
|
||||
border-radius: 0.35rem;
|
||||
color: var(--text-color);
|
||||
background-color: white;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.link-card:is(:hover, :focus-within) {
|
||||
background-position: 0;
|
||||
}
|
||||
|
||||
.link-card:is(:hover, :focus-within) h2 {
|
||||
color: #4F39FA;
|
||||
}
|
||||
|
||||
.link-card:is(:hover, :focus-within) h2 span {
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
.heart {
|
||||
display: inline-block;
|
||||
color: #DA62C4;
|
||||
animation: heartbeat 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes heartbeat {
|
||||
0%,
|
||||
50%,
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
5% {
|
||||
transform: scale(1.125);
|
||||
}
|
||||
10% {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
15% {
|
||||
transform: scale(1.25);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Enable top-level await, and other modern ESM features.
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
// Enable node-style module resolution, for things like npm package imports.
|
||||
"moduleResolution": "node",
|
||||
// Enable JSON imports.
|
||||
"resolveJsonModule": true,
|
||||
// Enable stricter transpilation for better output.
|
||||
"isolatedModules": true,
|
||||
// Add type definitions for our Vite runtime.
|
||||
"types": ["vite/client"]
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/build-utils",
|
||||
"version": "5.0.8",
|
||||
"version": "5.3.0",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.js",
|
||||
@@ -14,8 +14,7 @@
|
||||
"build": "node build",
|
||||
"test": "jest --env node --verbose --runInBand --bail",
|
||||
"test-unit": "yarn test test/unit.*test.*",
|
||||
"test-integration-once": "yarn test test/integration.test.ts",
|
||||
"prepublishOnly": "node build"
|
||||
"test-integration-once": "yarn test test/integration.test.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iarna/toml": "2.2.3",
|
||||
|
||||
@@ -27,9 +27,7 @@ async function prepareSymlinkTarget(
|
||||
}
|
||||
|
||||
if (file.type === 'FileRef' || file.type === 'FileBlob') {
|
||||
const targetPathBufferPromise = await streamToBuffer(
|
||||
await file.toStreamAsync()
|
||||
);
|
||||
const targetPathBufferPromise = streamToBuffer(await file.toStreamAsync());
|
||||
const [targetPathBuffer] = await Promise.all([
|
||||
targetPathBufferPromise,
|
||||
mkdirPromise,
|
||||
@@ -42,9 +40,15 @@ async function prepareSymlinkTarget(
|
||||
);
|
||||
}
|
||||
|
||||
async function downloadFile(file: File, fsPath: string): Promise<FileFsRef> {
|
||||
export async function downloadFile(
|
||||
file: File,
|
||||
fsPath: string
|
||||
): Promise<FileFsRef> {
|
||||
const { mode } = file;
|
||||
|
||||
// If the source is a symlink, try to create it instead of copying the file.
|
||||
// Note: creating symlinks on Windows requires admin priviliges or symlinks
|
||||
// enabled in the group policy. We may want to improve the error message.
|
||||
if (isSymbolicLink(mode)) {
|
||||
const target = await prepareSymlinkTarget(file, fsPath);
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ const allOptions = [
|
||||
major: 12,
|
||||
range: '12.x',
|
||||
runtime: 'nodejs12.x',
|
||||
discontinueDate: new Date('2022-08-09'),
|
||||
discontinueDate: new Date('2022-10-01'),
|
||||
},
|
||||
{
|
||||
major: 10,
|
||||
|
||||
32
packages/build-utils/src/get-prefixed-env-vars.ts
Normal file
32
packages/build-utils/src/get-prefixed-env-vars.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
type Envs = { [key: string]: string | undefined };
|
||||
|
||||
/**
|
||||
* Get the framework-specific prefixed System Environment Variables.
|
||||
* See https://vercel.com/docs/concepts/projects/environment-variables#system-environment-variables
|
||||
* @param envPrefix - Prefix, typically from `@vercel/frameworks`
|
||||
* @param envs - Environment Variables, typically from `process.env`
|
||||
*/
|
||||
export function getPrefixedEnvVars({
|
||||
envPrefix,
|
||||
envs,
|
||||
}: {
|
||||
envPrefix: string | undefined;
|
||||
envs: Envs;
|
||||
}): Envs {
|
||||
const vercelSystemEnvPrefix = 'VERCEL_';
|
||||
const newEnvs: Envs = {};
|
||||
if (envPrefix && envs.VERCEL_URL) {
|
||||
Object.keys(envs)
|
||||
.filter(key => key.startsWith(vercelSystemEnvPrefix))
|
||||
.forEach(key => {
|
||||
const newKey = `${envPrefix}${key}`;
|
||||
if (!(newKey in envs)) {
|
||||
newEnvs[newKey] = envs[key];
|
||||
}
|
||||
});
|
||||
// Tell turbo to exclude all Vercel System Env Vars
|
||||
// See https://github.com/vercel/turborepo/pull/1622
|
||||
newEnvs.TURBO_CI_VENDOR_ENV_KEY = `${envPrefix}${vercelSystemEnvPrefix}`;
|
||||
}
|
||||
return newEnvs;
|
||||
}
|
||||
@@ -4,7 +4,11 @@ import FileRef from './file-ref';
|
||||
import { Lambda, createLambda, getLambdaOptionsFromFunction } from './lambda';
|
||||
import { NodejsLambda } from './nodejs-lambda';
|
||||
import { Prerender } from './prerender';
|
||||
import download, { DownloadedFiles, isSymbolicLink } from './fs/download';
|
||||
import download, {
|
||||
downloadFile,
|
||||
DownloadedFiles,
|
||||
isSymbolicLink,
|
||||
} from './fs/download';
|
||||
import getWriteableDirectory from './fs/get-writable-directory';
|
||||
import glob, { GlobOptions } from './fs/glob';
|
||||
import rename from './fs/rename';
|
||||
@@ -36,6 +40,7 @@ import streamToBuffer from './fs/stream-to-buffer';
|
||||
import debug from './debug';
|
||||
import getIgnoreFilter from './get-ignore-filter';
|
||||
import { getPlatformEnv } from './get-platform-env';
|
||||
import { getPrefixedEnvVars } from './get-prefixed-env-vars';
|
||||
|
||||
export {
|
||||
FileBlob,
|
||||
@@ -46,6 +51,7 @@ export {
|
||||
createLambda,
|
||||
Prerender,
|
||||
download,
|
||||
downloadFile,
|
||||
DownloadedFiles,
|
||||
getWriteableDirectory,
|
||||
glob,
|
||||
@@ -71,6 +77,7 @@ export {
|
||||
getDiscontinuedNodeVersions,
|
||||
getSpawnOptions,
|
||||
getPlatformEnv,
|
||||
getPrefixedEnvVars,
|
||||
streamToBuffer,
|
||||
debug,
|
||||
isSymbolicLink,
|
||||
|
||||
@@ -22,6 +22,7 @@ export interface LambdaOptionsBase {
|
||||
allowQuery?: string[];
|
||||
regions?: string[];
|
||||
supportsMultiPayloads?: boolean;
|
||||
supportsWrapper?: boolean;
|
||||
}
|
||||
|
||||
export interface LambdaOptionsWithFiles extends LambdaOptionsBase {
|
||||
@@ -58,6 +59,7 @@ export class Lambda {
|
||||
*/
|
||||
zipBuffer?: Buffer;
|
||||
supportsMultiPayloads?: boolean;
|
||||
supportsWrapper?: boolean;
|
||||
|
||||
constructor(opts: LambdaOptions) {
|
||||
const {
|
||||
@@ -69,6 +71,7 @@ export class Lambda {
|
||||
allowQuery,
|
||||
regions,
|
||||
supportsMultiPayloads,
|
||||
supportsWrapper,
|
||||
} = opts;
|
||||
if ('files' in opts) {
|
||||
assert(typeof opts.files === 'object', '"files" must be an object');
|
||||
@@ -103,6 +106,13 @@ export class Lambda {
|
||||
);
|
||||
}
|
||||
|
||||
if (supportsWrapper !== undefined) {
|
||||
assert(
|
||||
typeof supportsWrapper === 'boolean',
|
||||
'"supportsWrapper" is not a boolean'
|
||||
);
|
||||
}
|
||||
|
||||
if (regions !== undefined) {
|
||||
assert(Array.isArray(regions), '"regions" is not an Array');
|
||||
assert(
|
||||
@@ -121,6 +131,7 @@ export class Lambda {
|
||||
this.regions = regions;
|
||||
this.zipBuffer = 'zipBuffer' in opts ? opts.zipBuffer : undefined;
|
||||
this.supportsMultiPayloads = supportsMultiPayloads;
|
||||
this.supportsWrapper = supportsWrapper;
|
||||
}
|
||||
|
||||
async createZip(): Promise<Buffer> {
|
||||
|
||||
87
packages/build-utils/test/unit.get-prefixed-env-vars.test.ts
vendored
Normal file
87
packages/build-utils/test/unit.get-prefixed-env-vars.test.ts
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
import { getPrefixedEnvVars } from '../src';
|
||||
|
||||
describe('Test `getPrefixedEnvVars()`', () => {
|
||||
const cases: Array<{
|
||||
name: string;
|
||||
args: Parameters<typeof getPrefixedEnvVars>[0];
|
||||
want: ReturnType<typeof getPrefixedEnvVars>;
|
||||
}> = [
|
||||
{
|
||||
name: 'should work with NEXT_PUBLIC_',
|
||||
args: {
|
||||
envPrefix: 'NEXT_PUBLIC_',
|
||||
envs: {
|
||||
VERCEL: '1',
|
||||
VERCEL_URL: 'example.vercel.sh',
|
||||
USER_ENV_VAR_NOT_VERCEL: 'example.com',
|
||||
FOO: 'bar',
|
||||
},
|
||||
},
|
||||
want: {
|
||||
NEXT_PUBLIC_VERCEL_URL: 'example.vercel.sh',
|
||||
TURBO_CI_VENDOR_ENV_KEY: 'NEXT_PUBLIC_VERCEL_',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'should work with GATSBY_',
|
||||
args: {
|
||||
envPrefix: 'GATSBY_',
|
||||
envs: {
|
||||
USER_ENV_VAR_NOT_VERCEL: 'example.com',
|
||||
FOO: 'bar',
|
||||
VERCEL_URL: 'example.vercel.sh',
|
||||
VERCEL_ENV: 'production',
|
||||
VERCEL_REGION: 'iad1',
|
||||
VERCEL_GIT_COMMIT_AUTHOR_LOGIN: 'rauchg',
|
||||
},
|
||||
},
|
||||
want: {
|
||||
GATSBY_VERCEL_URL: 'example.vercel.sh',
|
||||
GATSBY_VERCEL_ENV: 'production',
|
||||
GATSBY_VERCEL_REGION: 'iad1',
|
||||
GATSBY_VERCEL_GIT_COMMIT_AUTHOR_LOGIN: 'rauchg',
|
||||
TURBO_CI_VENDOR_ENV_KEY: 'GATSBY_VERCEL_',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'should not return anything if no system env vars detected',
|
||||
args: {
|
||||
envPrefix: 'GATSBY_',
|
||||
envs: {
|
||||
USER_ENV_VAR_NOT_VERCEL: 'example.com',
|
||||
FOO: 'bar',
|
||||
BLARG_VERCEL_THING: 'fake',
|
||||
},
|
||||
},
|
||||
want: {},
|
||||
},
|
||||
{
|
||||
name: 'should not return anything if envPrefix is empty string',
|
||||
args: {
|
||||
envPrefix: '',
|
||||
envs: {
|
||||
VERCEL: '1',
|
||||
VERCEL_URL: 'example.vercel.sh',
|
||||
},
|
||||
},
|
||||
want: {},
|
||||
},
|
||||
{
|
||||
name: 'should not return anything if envPrefix is undefined',
|
||||
args: {
|
||||
envPrefix: undefined,
|
||||
envs: {
|
||||
VERCEL: '1',
|
||||
VERCEL_URL: 'example.vercel.sh',
|
||||
},
|
||||
},
|
||||
want: {},
|
||||
},
|
||||
];
|
||||
|
||||
for (const { name, args, want } of cases) {
|
||||
it(name, () => {
|
||||
expect(getPrefixedEnvVars(args)).toEqual(want);
|
||||
});
|
||||
}
|
||||
});
|
||||
6
packages/build-utils/test/unit.test.ts
vendored
6
packages/build-utils/test/unit.test.ts
vendored
@@ -394,7 +394,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('2022-09-01').getTime();
|
||||
global.Date.now = () => new Date('2022-10-15').getTime();
|
||||
|
||||
expect(getSupportedNodeVersion('8.10.x', false)).rejects.toThrow();
|
||||
expect(getSupportedNodeVersion('8.10.x', true)).rejects.toThrow();
|
||||
@@ -436,8 +436,8 @@ it('should warn for deprecated versions, soon to be discontinued', async () => {
|
||||
expect(warningMessages).toStrictEqual([
|
||||
'Error: Node.js version 10.x has reached End-of-Life. Deployments created on or after 2021-04-20 will fail to build. Please set "engines": { "node": "16.x" } in your `package.json` file to use Node.js 16.',
|
||||
'Error: Node.js version 10.x has reached End-of-Life. Deployments created on or after 2021-04-20 will fail to build. Please set Node.js Version to 16.x in your Project Settings to use Node.js 16.',
|
||||
'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-08-09 will fail to build. Please set "engines": { "node": "16.x" } in your `package.json` file to use Node.js 16.',
|
||||
'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-08-09 will fail to build. Please set Node.js Version to 16.x in your Project Settings to use Node.js 16.',
|
||||
'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-10-01 will fail to build. Please set "engines": { "node": "16.x" } in your `package.json` file to use Node.js 16.',
|
||||
'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-10-01 will fail to build. Please set Node.js Version to 16.x in your Project Settings to use Node.js 16.',
|
||||
]);
|
||||
|
||||
global.Date.now = realDateNow;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vercel",
|
||||
"version": "27.3.4",
|
||||
"version": "27.4.0",
|
||||
"preferGlobal": true,
|
||||
"license": "Apache-2.0",
|
||||
"description": "The command-line interface for Vercel",
|
||||
@@ -16,7 +16,6 @@
|
||||
"test-unit": "yarn test test/unit/",
|
||||
"test-integration-cli": "rimraf test/fixtures/integration && ava test/integration.js --serial --fail-fast --verbose",
|
||||
"test-integration-dev": "yarn test test/dev/",
|
||||
"prepublishOnly": "yarn build",
|
||||
"coverage": "codecov",
|
||||
"build": "ts-node ./scripts/build.ts",
|
||||
"dev": "ts-node ./src/index.ts"
|
||||
@@ -42,16 +41,16 @@
|
||||
"node": ">= 14"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "5.0.8",
|
||||
"@vercel/go": "2.0.12",
|
||||
"@vercel/hydrogen": "0.0.9",
|
||||
"@vercel/next": "3.1.12",
|
||||
"@vercel/node": "2.5.3",
|
||||
"@vercel/python": "3.1.4",
|
||||
"@vercel/redwood": "1.0.13",
|
||||
"@vercel/remix": "1.0.14",
|
||||
"@vercel/ruby": "1.3.20",
|
||||
"@vercel/static-build": "1.0.13",
|
||||
"@vercel/build-utils": "5.3.0",
|
||||
"@vercel/go": "2.1.0",
|
||||
"@vercel/hydrogen": "0.0.13",
|
||||
"@vercel/next": "3.1.17",
|
||||
"@vercel/node": "2.5.7",
|
||||
"@vercel/python": "3.1.8",
|
||||
"@vercel/redwood": "1.0.17",
|
||||
"@vercel/remix": "1.0.18",
|
||||
"@vercel/ruby": "1.3.24",
|
||||
"@vercel/static-build": "1.0.17",
|
||||
"update-notifier": "5.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -97,9 +96,9 @@
|
||||
"@types/which": "1.3.2",
|
||||
"@types/write-json-file": "2.2.1",
|
||||
"@types/yauzl-promise": "2.1.0",
|
||||
"@vercel/client": "12.1.7",
|
||||
"@vercel/frameworks": "1.1.1",
|
||||
"@vercel/fs-detectors": "2.0.3",
|
||||
"@vercel/client": "12.1.11",
|
||||
"@vercel/frameworks": "1.1.3",
|
||||
"@vercel/fs-detectors": "2.0.5",
|
||||
"@vercel/fun": "1.0.4",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"@zeit/source-map-support": "0.6.2",
|
||||
|
||||
@@ -37,6 +37,7 @@ const help = () => {
|
||||
)} Login token
|
||||
-S, --scope Set a custom scope
|
||||
-N, --next Show next page of results
|
||||
-y, --yes Skip the confirmation prompt when removing an alias
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ const help = () => {
|
||||
--output [path] Directory where built assets should be written to
|
||||
--prod Build a production deployment
|
||||
-d, --debug Debug mode [off]
|
||||
-y, --yes Skip the confirmation prompt
|
||||
-y, --yes Pull environment variables and project settings if not found locally
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
@@ -157,7 +157,7 @@ export default async function main(client: Client): Promise<number> {
|
||||
client.output.print(
|
||||
`No Project Settings found locally. Run ${cli.getCommandName(
|
||||
'pull --yes'
|
||||
)} to retreive them.`
|
||||
)} to retrieve them.`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -469,6 +469,8 @@ async function doBuild(
|
||||
)
|
||||
);
|
||||
} catch (err: any) {
|
||||
output.prettyError(err);
|
||||
|
||||
const writeConfigJsonPromise = fs.writeJSON(
|
||||
join(outputDir, 'config.json'),
|
||||
{ version: 3 },
|
||||
|
||||
@@ -73,7 +73,7 @@ export const help = () => `
|
||||
-S, --scope Set a custom scope
|
||||
--regions Set default regions to enable the deployment on
|
||||
--prod Create a production deployment
|
||||
-c, --confirm Confirm default options and skip questions
|
||||
-y, --yes Skip questions when setting up new project using default scope and settings
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
|
||||
@@ -87,20 +87,27 @@ export default async (client: Client) => {
|
||||
'--regions': String,
|
||||
'--prebuilt': Boolean,
|
||||
'--prod': Boolean,
|
||||
'--confirm': Boolean,
|
||||
'--yes': Boolean,
|
||||
'-f': '--force',
|
||||
'-p': '--public',
|
||||
'-e': '--env',
|
||||
'-b': '--build-env',
|
||||
'-m': '--meta',
|
||||
'-c': '--confirm',
|
||||
'-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');
|
||||
argv['--yes'] = argv['--confirm'];
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
return 1;
|
||||
@@ -173,7 +180,7 @@ export default async (client: Client) => {
|
||||
}
|
||||
|
||||
const { path } = pathValidation;
|
||||
const autoConfirm = argv['--confirm'];
|
||||
const autoConfirm = argv['--yes'];
|
||||
|
||||
// deprecate --name
|
||||
if (argv['--name']) {
|
||||
|
||||
@@ -17,7 +17,7 @@ import { OUTPUT_DIR } from '../../util/build/write-build-result';
|
||||
|
||||
type Options = {
|
||||
'--listen': string;
|
||||
'--confirm': boolean;
|
||||
'--yes': boolean;
|
||||
};
|
||||
|
||||
export default async function dev(
|
||||
@@ -38,7 +38,7 @@ export default async function dev(
|
||||
|
||||
if (link.status === 'not_linked' && !process.env.__VERCEL_SKIP_DEV_CMD) {
|
||||
link = await setupAndLink(client, cwd, {
|
||||
autoConfirm: opts['--confirm'],
|
||||
autoConfirm: opts['--yes'],
|
||||
successEmoji: 'link',
|
||||
setupMsg: 'Set up and develop',
|
||||
});
|
||||
@@ -54,14 +54,13 @@ export default async function dev(
|
||||
client.output.error(
|
||||
`Command ${getCommandName(
|
||||
'dev'
|
||||
)} requires confirmation. Use option ${param('--confirm')} to confirm.`
|
||||
)} requires confirmation. Use option ${param('--yes')} to confirm.`
|
||||
);
|
||||
}
|
||||
return link.exitCode;
|
||||
}
|
||||
|
||||
let devCommand: string | undefined;
|
||||
let frameworkSlug: string | undefined;
|
||||
let projectSettings: ProjectSettings | undefined;
|
||||
let projectEnvs: ProjectEnvVariable[] = [];
|
||||
let systemEnvValues: string[] = [];
|
||||
@@ -77,10 +76,6 @@ export default async function dev(
|
||||
const framework = frameworks.find(f => f.slug === project.framework);
|
||||
|
||||
if (framework) {
|
||||
if (framework.slug) {
|
||||
frameworkSlug = framework.slug;
|
||||
}
|
||||
|
||||
const defaults = framework.settings.devCommand.value;
|
||||
if (defaults) {
|
||||
devCommand = defaults;
|
||||
@@ -120,7 +115,6 @@ export default async function dev(
|
||||
const devServer = new DevServer(cwd, {
|
||||
output,
|
||||
devCommand,
|
||||
frameworkSlug,
|
||||
projectSettings,
|
||||
projectEnvs,
|
||||
systemEnvValues,
|
||||
|
||||
@@ -33,7 +33,7 @@ const help = () => {
|
||||
-d, --debug Debug mode [off]
|
||||
-l, --listen [uri] Specify a URI endpoint on which to listen [0.0.0.0:3000]
|
||||
-t, --token [token] Specify an Authorization Token
|
||||
--confirm Skip questions and use defaults when setting up a new project
|
||||
-y, --yes Skip questions when setting up new project using default scope and settings
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
@@ -74,14 +74,22 @@ export default async function main(client: Client) {
|
||||
argv = getArgs(client.argv.slice(2), {
|
||||
'--listen': String,
|
||||
'-l': '--listen',
|
||||
'--confirm': Boolean,
|
||||
'--yes': Boolean,
|
||||
'-y': '--yes',
|
||||
|
||||
// Deprecated
|
||||
'--port': Number,
|
||||
'-p': '--port',
|
||||
'--confirm': Boolean,
|
||||
'-c': '--confirm',
|
||||
});
|
||||
args = getSubcommand(argv._.slice(1), COMMAND_CONFIG).args;
|
||||
|
||||
if ('--confirm' in argv) {
|
||||
output.warn('`--confirm` is deprecated, please use `--yes` instead');
|
||||
argv['--yes'] = argv['--confirm'];
|
||||
}
|
||||
|
||||
if ('--port' in argv) {
|
||||
output.warn('`--port` is deprecated, please use `--listen` instead');
|
||||
argv['--listen'] = String(argv['--port']);
|
||||
|
||||
@@ -45,6 +45,7 @@ const help = () => {
|
||||
)} Login token
|
||||
-S, --scope Set a custom scope
|
||||
-N, --next Show next page of results
|
||||
-y, --yes Skip the confirmation prompt when removing a domain
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
@@ -92,6 +93,7 @@ export default async function main(client: Client) {
|
||||
'--force': Boolean,
|
||||
'--next': Number,
|
||||
'-N': '--next',
|
||||
'-y': '--yes',
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
|
||||
1
packages/cli/src/commands/env/index.ts
vendored
1
packages/cli/src/commands/env/index.ts
vendored
@@ -42,6 +42,7 @@ const help = () => {
|
||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
||||
'TOKEN'
|
||||
)} Login token
|
||||
-y, --yes Skip the confirmation prompt when overwriting env file on pull or removing an env variable
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
|
||||
13
packages/cli/src/commands/env/pull.ts
vendored
13
packages/cli/src/commands/env/pull.ts
vendored
@@ -130,6 +130,12 @@ export default async function pull(
|
||||
|
||||
await outputFile(fullPath, contents, 'utf8');
|
||||
|
||||
if (deltaString) {
|
||||
output.print('\n' + deltaString);
|
||||
} else if (oldEnv && exists) {
|
||||
output.log('No changes found.');
|
||||
}
|
||||
|
||||
output.print(
|
||||
`${prependEmoji(
|
||||
`${exists ? 'Updated' : 'Created'} ${chalk.bold(
|
||||
@@ -139,13 +145,6 @@ export default async function pull(
|
||||
)}\n`
|
||||
);
|
||||
|
||||
output.print('\n');
|
||||
if (deltaString) {
|
||||
output.print(deltaString);
|
||||
} else if (oldEnv && exists) {
|
||||
output.log('No changes found.');
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ export default async function connect(
|
||||
org: Org | undefined
|
||||
) {
|
||||
const { output } = client;
|
||||
const confirm = Boolean(argv['--confirm']);
|
||||
const confirm = Boolean(argv['--yes']);
|
||||
|
||||
if (args.length !== 0) {
|
||||
output.error(
|
||||
|
||||
@@ -16,15 +16,16 @@ const help = () => {
|
||||
|
||||
${chalk.dim('Commands:')}
|
||||
|
||||
connect Connect your Git config "origin" remote as a Git provider to your project
|
||||
disconnect Disconnect the Git provider repository from your project
|
||||
connect Connect your Git config "origin" remote as a Git provider to your project
|
||||
disconnect Disconnect the Git provider repository from your project
|
||||
|
||||
${chalk.dim('Options:')}
|
||||
|
||||
-h, --help Output usage information
|
||||
-h, --help Output usage information
|
||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
||||
'TOKEN'
|
||||
)} Login token
|
||||
)} Login token
|
||||
-y, --yes Skip questions when setting up new project using default scope and settings
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
@@ -49,7 +50,12 @@ export default async function main(client: Client) {
|
||||
|
||||
try {
|
||||
argv = getArgs(client.argv.slice(2), {
|
||||
'--confirm': Boolean,
|
||||
'--yes': Boolean,
|
||||
'-y': '--yes',
|
||||
|
||||
// deprecated
|
||||
'-c': '--yes',
|
||||
'--confirm': '--yes',
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
@@ -64,7 +70,7 @@ export default async function main(client: Client) {
|
||||
argv._ = argv._.slice(1);
|
||||
subcommand = argv._[0];
|
||||
const args = argv._.slice(1);
|
||||
const confirm = Boolean(argv['--confirm']);
|
||||
const confirm = Boolean(argv['--yes']);
|
||||
const { output } = client;
|
||||
|
||||
let paths = [process.cwd()];
|
||||
|
||||
@@ -15,6 +15,7 @@ import { Build } from '../types';
|
||||
import title from 'title';
|
||||
import { isErrnoException } from '../util/is-error';
|
||||
import { isAPIError } from '../util/errors-ts';
|
||||
import { URL } from 'url';
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
@@ -66,7 +67,7 @@ export default async function main(client: Client) {
|
||||
const { print, log, error } = client.output;
|
||||
|
||||
// extract the first parameter
|
||||
const [, deploymentIdOrHost] = argv._;
|
||||
let [, deploymentIdOrHost] = argv._;
|
||||
|
||||
if (argv._.length !== 2) {
|
||||
error(`${getCommandName('inspect <url>')} expects exactly one argument`);
|
||||
@@ -90,12 +91,16 @@ export default async function main(client: Client) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
// resolve the deployment, since we might have been given an alias
|
||||
const depFetchStart = Date.now();
|
||||
|
||||
try {
|
||||
deploymentIdOrHost = new URL(deploymentIdOrHost).hostname;
|
||||
} catch {}
|
||||
client.output.spinner(
|
||||
`Fetching deployment "${deploymentIdOrHost}" in ${chalk.bold(contextName)}`
|
||||
);
|
||||
|
||||
// resolve the deployment, since we might have been given an alias
|
||||
try {
|
||||
deployment = await getDeployment(client, deploymentIdOrHost);
|
||||
} catch (err: unknown) {
|
||||
|
||||
@@ -27,7 +27,7 @@ const help = () => {
|
||||
-p ${chalk.bold.underline('NAME')}, --project=${chalk.bold.underline(
|
||||
'NAME'
|
||||
)} Project name
|
||||
--confirm Confirm default options and skip questions
|
||||
-y, --yes Skip questions when setting up new project using default scope and settings
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
@@ -39,7 +39,7 @@ const help = () => {
|
||||
'–'
|
||||
)} Link current directory with default options and skip questions
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} link --confirm`)}
|
||||
${chalk.cyan(`$ ${getPkgName()} link --yes`)}
|
||||
|
||||
${chalk.gray('–')} Link a specific directory to a Vercel Project
|
||||
|
||||
@@ -49,9 +49,14 @@ const help = () => {
|
||||
|
||||
export default async function main(client: Client) {
|
||||
const argv = getArgs(client.argv.slice(2), {
|
||||
'--confirm': Boolean,
|
||||
'--yes': Boolean,
|
||||
'-y': '--yes',
|
||||
'--project': String,
|
||||
'-p': '--project',
|
||||
|
||||
// deprecated
|
||||
'--confirm': Boolean,
|
||||
'-c': '--confirm',
|
||||
});
|
||||
|
||||
if (argv['--help']) {
|
||||
@@ -59,10 +64,15 @@ export default async function main(client: Client) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
if ('--confirm' in argv) {
|
||||
client.output.warn('`--confirm` is deprecated, please use `--yes` instead');
|
||||
argv['--yes'] = argv['--confirm'];
|
||||
}
|
||||
|
||||
const cwd = argv._[1] || process.cwd();
|
||||
const link = await setupAndLink(client, cwd, {
|
||||
forceDelete: true,
|
||||
autoConfirm: argv['--confirm'],
|
||||
autoConfirm: argv['--yes'],
|
||||
projectName: argv['--project'],
|
||||
successEmoji: 'success',
|
||||
setupMsg: 'Set up',
|
||||
@@ -73,7 +83,7 @@ export default async function main(client: Client) {
|
||||
client.output.error(
|
||||
`Command ${getCommandName(
|
||||
'link'
|
||||
)} requires confirmation. Use option ${param('--confirm')} to confirm.`
|
||||
)} requires confirmation. Use option ${param('--yes')} to confirm.`
|
||||
);
|
||||
}
|
||||
return link.exitCode;
|
||||
|
||||
@@ -35,7 +35,7 @@ const help = () => {
|
||||
'DIR'
|
||||
)} Path to the global ${'`.vercel`'} directory
|
||||
-d, --debug Debug mode [off]
|
||||
--confirm Skip the confirmation prompt
|
||||
-y, --yes Skip questions when setting up new project using default scope and settings
|
||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
||||
'TOKEN'
|
||||
)} Login token
|
||||
@@ -78,7 +78,12 @@ export default async function main(client: Client) {
|
||||
'-m': '--meta',
|
||||
'--next': Number,
|
||||
'-N': '--next',
|
||||
'--yes': Boolean,
|
||||
'-y': '--yes',
|
||||
|
||||
// deprecated
|
||||
'--confirm': Boolean,
|
||||
'-c': '--confirm',
|
||||
});
|
||||
} catch (err) {
|
||||
handleError(err);
|
||||
@@ -87,6 +92,11 @@ export default async function main(client: Client) {
|
||||
|
||||
const { output, config } = client;
|
||||
|
||||
if ('--confirm' in argv) {
|
||||
output.warn('`--confirm` is deprecated, please use `--yes` instead');
|
||||
argv['--yes'] = argv['--confirm'];
|
||||
}
|
||||
|
||||
const { print, log, error, note, debug, spinner } = output;
|
||||
|
||||
if (argv._.length > 2) {
|
||||
@@ -99,7 +109,7 @@ export default async function main(client: Client) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
const yes = argv['--confirm'] || false;
|
||||
const yes = !!argv['--yes'];
|
||||
|
||||
const meta = parseMeta(argv['--meta']);
|
||||
const { includeScheme } = config;
|
||||
|
||||
@@ -38,7 +38,7 @@ const help = () => {
|
||||
)} Path to the global ${'`.vercel`'} directory
|
||||
-d, --debug Debug mode [off]
|
||||
--environment [environment] Deployment environment [development]
|
||||
-y, --yes Skip the confirmation prompt
|
||||
-y, --yes Skip questions when setting up new project using default scope and settings
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
|
||||
@@ -226,7 +226,8 @@ async function run({ output, contextName, currentTeam, client }) {
|
||||
|
||||
if (theSecret) {
|
||||
const yes =
|
||||
argv.yes || (await readConfirmation(output, theSecret, contextName));
|
||||
argv.yes ||
|
||||
(await readConfirmation(client, output, theSecret, contextName));
|
||||
if (!yes) {
|
||||
output.print(`Aborted. Secret not deleted.\n`);
|
||||
return 0;
|
||||
@@ -353,7 +354,7 @@ async function run({ output, contextName, currentTeam, client }) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
async function readConfirmation(output, secret, contextName) {
|
||||
async function readConfirmation(client, output, secret, contextName) {
|
||||
const time = chalk.gray(`${ms(new Date() - new Date(secret.created))} ago`);
|
||||
const tbl = table([[chalk.bold(secret.name), time]], {
|
||||
align: ['r', 'l'],
|
||||
@@ -367,5 +368,5 @@ async function readConfirmation(output, secret, contextName) {
|
||||
);
|
||||
output.print(` ${tbl}\n`);
|
||||
|
||||
return confirm(`${chalk.bold.red('Are you sure?')}`, false);
|
||||
return confirm(client, `${chalk.bold.red('Are you sure?')}`, false);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ try {
|
||||
// Test to see if cwd has been deleted before
|
||||
// importing 3rd party packages that might need cwd.
|
||||
process.cwd();
|
||||
} catch (err) {
|
||||
} catch (err: unknown) {
|
||||
if (isError(err) && err.message.includes('uv_cwd')) {
|
||||
console.error('Error! The current working directory does not exist.');
|
||||
process.exit(1);
|
||||
@@ -40,8 +40,8 @@ import getConfig from './util/get-config';
|
||||
import * as configFiles from './util/config/files';
|
||||
import getGlobalPathConfig from './util/config/global-path';
|
||||
import {
|
||||
getDefaultConfig,
|
||||
getDefaultAuthConfig,
|
||||
defaultAuthConfig,
|
||||
defaultGlobalConfig,
|
||||
} from './util/config/get-default';
|
||||
import * as ERRORS from './util/errors-ts';
|
||||
import { APIError } from './util/errors-ts';
|
||||
@@ -50,7 +50,7 @@ import getUpdateCommand from './util/get-update-command';
|
||||
import { metrics, shouldCollectMetrics } from './util/metrics';
|
||||
import { getCommandName, getTitleName } from './util/pkg-name';
|
||||
import doLoginPrompt from './util/login/prompt';
|
||||
import { GlobalConfig } from './types';
|
||||
import { AuthConfig, GlobalConfig } from './types';
|
||||
import { VercelConfig } from '@vercel/client';
|
||||
|
||||
const isCanary = pkg.version.includes('canary');
|
||||
@@ -208,160 +208,59 @@ const main = async () => {
|
||||
VERCEL_DIR
|
||||
)}" ${errorToString(err)}`
|
||||
);
|
||||
}
|
||||
|
||||
let migrated = false;
|
||||
let configExists;
|
||||
|
||||
try {
|
||||
configExists = existsSync(VERCEL_CONFIG_PATH);
|
||||
} catch (err: unknown) {
|
||||
console.error(
|
||||
error(
|
||||
`${
|
||||
'An unexpected error occurred while trying to find the ' +
|
||||
`config file "${hp(VERCEL_CONFIG_PATH)}" `
|
||||
}${errorToString(err)}`
|
||||
)
|
||||
);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
let config: GlobalConfig | null = null;
|
||||
|
||||
if (configExists) {
|
||||
try {
|
||||
config = configFiles.readConfigFile();
|
||||
} catch (err) {
|
||||
console.error(
|
||||
error(
|
||||
`${
|
||||
'An unexpected error occurred while trying to read the ' +
|
||||
`config in "${hp(VERCEL_CONFIG_PATH)}" `
|
||||
}${errorToString(err)}`
|
||||
)
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// This is from when Vercel CLI supported
|
||||
// multiple providers. In that case, we really
|
||||
// need to migrate.
|
||||
if (
|
||||
// @ts-ignore
|
||||
config.sh ||
|
||||
// @ts-ignore
|
||||
config.user ||
|
||||
// @ts-ignore
|
||||
typeof config.user === 'object' ||
|
||||
typeof config.currentTeam === 'object'
|
||||
) {
|
||||
configExists = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!configExists) {
|
||||
const results = await getDefaultConfig(config);
|
||||
|
||||
config = results.config;
|
||||
migrated = results.migrated;
|
||||
|
||||
try {
|
||||
configFiles.writeToConfigFile(config);
|
||||
} catch (err: unknown) {
|
||||
console.error(
|
||||
error(
|
||||
`${
|
||||
'An unexpected error occurred while trying to write the ' +
|
||||
`default config to "${hp(VERCEL_CONFIG_PATH)}" `
|
||||
}${errorToString(err)}`
|
||||
)
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
let authConfigExists;
|
||||
|
||||
try {
|
||||
authConfigExists = existsSync(VERCEL_AUTH_CONFIG_PATH);
|
||||
} catch (err: unknown) {
|
||||
console.error(
|
||||
error(
|
||||
`${
|
||||
'An unexpected error occurred while trying to find the ' +
|
||||
`auth file "${hp(VERCEL_AUTH_CONFIG_PATH)}" `
|
||||
}${errorToString(err)}`
|
||||
)
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
let authConfig = null;
|
||||
|
||||
const subcommandsWithoutToken = [
|
||||
'login',
|
||||
'logout',
|
||||
'help',
|
||||
'init',
|
||||
'update',
|
||||
'build',
|
||||
];
|
||||
|
||||
if (authConfigExists) {
|
||||
try {
|
||||
authConfig = configFiles.readAuthConfigFile();
|
||||
} catch (err: unknown) {
|
||||
console.error(
|
||||
error(
|
||||
`${
|
||||
'An unexpected error occurred while trying to read the ' +
|
||||
`auth config in "${hp(VERCEL_AUTH_CONFIG_PATH)}" `
|
||||
}${errorToString(err)}`
|
||||
)
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// This is from when Vercel CLI supported
|
||||
// multiple providers. In that case, we really
|
||||
// need to migrate.
|
||||
// @ts-ignore
|
||||
if (authConfig.credentials) {
|
||||
authConfigExists = false;
|
||||
}
|
||||
} else {
|
||||
const results = await getDefaultAuthConfig(authConfig);
|
||||
|
||||
authConfig = results.config;
|
||||
migrated = results.migrated;
|
||||
|
||||
try {
|
||||
configFiles.writeToAuthConfigFile(authConfig);
|
||||
} catch (err: unknown) {
|
||||
console.error(
|
||||
error(
|
||||
`${
|
||||
'An unexpected error occurred while trying to write the ' +
|
||||
`default config to "${hp(VERCEL_AUTH_CONFIG_PATH)}" `
|
||||
}${errorToString(err)}`
|
||||
)
|
||||
let config: GlobalConfig;
|
||||
try {
|
||||
config = configFiles.readConfigFile();
|
||||
} catch (err: unknown) {
|
||||
if (isErrnoException(err) && err.code === 'ENOENT') {
|
||||
config = defaultGlobalConfig;
|
||||
try {
|
||||
configFiles.writeToConfigFile(config);
|
||||
} catch (err: unknown) {
|
||||
output.error(
|
||||
`An unexpected error occurred while trying to save the config to "${hp(
|
||||
VERCEL_CONFIG_PATH
|
||||
)}" ${errorToString(err)}`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
output.error(
|
||||
`An unexpected error occurred while trying to read the config in "${hp(
|
||||
VERCEL_CONFIG_PATH
|
||||
)}" ${errorToString(err)}`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Let the user know we migrated the config
|
||||
if (migrated) {
|
||||
const directory = param(hp(VERCEL_DIR));
|
||||
debug(
|
||||
`The credentials and configuration within the ${directory} directory were upgraded`
|
||||
);
|
||||
let authConfig: AuthConfig;
|
||||
try {
|
||||
authConfig = configFiles.readAuthConfigFile();
|
||||
} catch (err: unknown) {
|
||||
if (isErrnoException(err) && err.code === 'ENOENT') {
|
||||
authConfig = defaultAuthConfig;
|
||||
try {
|
||||
configFiles.writeToAuthConfigFile(authConfig);
|
||||
} catch (err: unknown) {
|
||||
output.error(
|
||||
`An unexpected error occurred while trying to write the auth config to "${hp(
|
||||
VERCEL_AUTH_CONFIG_PATH
|
||||
)}" ${errorToString(err)}`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
output.error(
|
||||
`An unexpected error occurred while trying to read the auth config in "${hp(
|
||||
VERCEL_AUTH_CONFIG_PATH
|
||||
)}" ${errorToString(err)}`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof argv['--api'] === 'string') {
|
||||
@@ -371,18 +270,12 @@ const main = async () => {
|
||||
}
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line no-new
|
||||
new URL(apiUrl);
|
||||
} catch (err) {
|
||||
} catch (err: unknown) {
|
||||
output.error(`Please provide a valid URL instead of ${highlight(apiUrl)}.`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!config) {
|
||||
output.error(`Vercel global config was not loaded.`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Shared API `Client` instance for all sub-commands to utilize
|
||||
client = new Client({
|
||||
apiUrl,
|
||||
@@ -430,6 +323,15 @@ const main = async () => {
|
||||
client.argv.push('-h');
|
||||
}
|
||||
|
||||
const subcommandsWithoutToken = [
|
||||
'login',
|
||||
'logout',
|
||||
'help',
|
||||
'init',
|
||||
'update',
|
||||
'build',
|
||||
];
|
||||
|
||||
// Prompt for login if there is no current token
|
||||
if (
|
||||
(!authConfig || !authConfig.token) &&
|
||||
|
||||
@@ -20,13 +20,15 @@ export interface JSONObject {
|
||||
}
|
||||
|
||||
export interface AuthConfig {
|
||||
_?: string;
|
||||
'// Note'?: string;
|
||||
'// Docs'?: string;
|
||||
token?: string;
|
||||
skipWrite?: boolean;
|
||||
}
|
||||
|
||||
export interface GlobalConfig {
|
||||
_?: string;
|
||||
'// Note'?: string;
|
||||
'// Docs'?: string;
|
||||
currentTeam?: string;
|
||||
includeScheme?: string;
|
||||
collectMetrics?: boolean;
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
PackageJson,
|
||||
Prerender,
|
||||
download,
|
||||
downloadFile,
|
||||
EdgeFunction,
|
||||
BuildResultBuildOutput,
|
||||
getLambdaOptionsFromFunction,
|
||||
@@ -266,9 +267,7 @@ async function writeStaticFile(
|
||||
const dest = join(outputDir, 'static', fsPath);
|
||||
await fs.mkdirp(dirname(dest));
|
||||
|
||||
// TODO: handle (or skip) symlinks?
|
||||
const stream = file.toStream();
|
||||
await pipe(stream, fs.createWriteStream(dest, { mode: file.mode }));
|
||||
await downloadFile(file, dest);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,75 +1,15 @@
|
||||
import { AuthConfig, GlobalConfig } from '../../types';
|
||||
|
||||
export const getDefaultConfig = async (existingCopy?: GlobalConfig | null) => {
|
||||
let migrated = false;
|
||||
|
||||
const config: GlobalConfig = {
|
||||
_: 'This is your Vercel config file. For more information see the global configuration documentation: https://vercel.com/docs/configuration#global',
|
||||
collectMetrics: true,
|
||||
};
|
||||
|
||||
if (existingCopy) {
|
||||
const keep = [
|
||||
'_',
|
||||
'currentTeam',
|
||||
'desktop',
|
||||
'updateChannel',
|
||||
'collectMetrics',
|
||||
'api',
|
||||
// This is deleted later in the code
|
||||
];
|
||||
|
||||
try {
|
||||
const existing = Object.assign({}, existingCopy);
|
||||
// @ts-ignore
|
||||
const sh = Object.assign({}, existing.sh || {});
|
||||
|
||||
Object.assign(config, existing, sh);
|
||||
|
||||
for (const key of Object.keys(config)) {
|
||||
if (!keep.includes(key)) {
|
||||
// @ts-ignore
|
||||
delete config[key];
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof config.currentTeam === 'object') {
|
||||
// @ts-ignore
|
||||
config.currentTeam = config.currentTeam.id;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
if (typeof config.user === 'object') {
|
||||
// @ts-ignore
|
||||
config.user = config.user.uid || config.user.id;
|
||||
}
|
||||
|
||||
migrated = true;
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
return { config, migrated };
|
||||
export const defaultGlobalConfig: GlobalConfig = {
|
||||
'// Note':
|
||||
'This is your Vercel config file. For more information see the global configuration documentation.',
|
||||
'// Docs':
|
||||
'https://vercel.com/docs/project-configuration#global-configuration/config-json',
|
||||
collectMetrics: true,
|
||||
};
|
||||
|
||||
export const getDefaultAuthConfig = async (existing?: AuthConfig | null) => {
|
||||
let migrated = false;
|
||||
|
||||
const config: AuthConfig = {
|
||||
_: 'This is your Vercel credentials file. DO NOT SHARE! More: https://vercel.com/docs/configuration#global',
|
||||
};
|
||||
|
||||
if (existing) {
|
||||
try {
|
||||
// @ts-ignore
|
||||
const sh = existing.credentials.find(item => item.provider === 'sh');
|
||||
|
||||
if (sh) {
|
||||
config.token = sh.token;
|
||||
}
|
||||
|
||||
migrated = true;
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
return { config, migrated };
|
||||
export const defaultAuthConfig: AuthConfig = {
|
||||
'// Note': 'This is your Vercel credentials file. DO NOT SHARE!',
|
||||
'// Docs':
|
||||
'https://vercel.com/docs/project-configuration#global-configuration/auth-json',
|
||||
};
|
||||
|
||||
@@ -131,7 +131,6 @@ export default class DevServer {
|
||||
public output: Output;
|
||||
public proxy: httpProxy;
|
||||
public envConfigs: EnvConfigs;
|
||||
public frameworkSlug?: string;
|
||||
public files: BuilderInputs;
|
||||
public address: string;
|
||||
public devCacheDir: string;
|
||||
@@ -175,7 +174,6 @@ export default class DevServer {
|
||||
this.address = '';
|
||||
this.devCommand = options.devCommand;
|
||||
this.projectSettings = options.projectSettings;
|
||||
this.frameworkSlug = options.frameworkSlug;
|
||||
this.caseSensitive = false;
|
||||
this.apiDir = null;
|
||||
this.apiExtensions = new Set();
|
||||
@@ -1556,6 +1554,8 @@ export default class DevServer {
|
||||
(err as any).link = 'https://vercel.link/command-not-found';
|
||||
}
|
||||
|
||||
this.output.prettyError(err);
|
||||
|
||||
await this.sendError(
|
||||
req,
|
||||
res,
|
||||
@@ -2208,7 +2208,10 @@ export default class DevServer {
|
||||
// Because of child process 'pipe' below, isTTY will be false.
|
||||
// Most frameworks use `chalk`/`supports-color` so we enable it anyway.
|
||||
FORCE_COLOR: process.stdout.isTTY ? '1' : '0',
|
||||
...(this.frameworkSlug === 'create-react-app' ? { BROWSER: 'none' } : {}),
|
||||
// Prevent framework dev servers from automatically opening a web
|
||||
// browser window, since it will not be the port that `vc dev`
|
||||
// is listening on and thus will be missing Vercel features.
|
||||
BROWSER: 'none',
|
||||
...process.env,
|
||||
...this.envConfigs.allEnv,
|
||||
PORT: `${port}`,
|
||||
|
||||
@@ -24,7 +24,6 @@ export { VercelConfig };
|
||||
export interface DevServerOptions {
|
||||
output: Output;
|
||||
devCommand?: string;
|
||||
frameworkSlug?: string;
|
||||
projectSettings?: ProjectSettings;
|
||||
systemEnvValues?: string[];
|
||||
projectEnvs?: ProjectEnvVariable[];
|
||||
|
||||
@@ -9,6 +9,7 @@ type LinkResult = {
|
||||
org: Org;
|
||||
project: Project;
|
||||
};
|
||||
|
||||
export async function ensureLink(
|
||||
commandName: string,
|
||||
client: Client,
|
||||
|
||||
14
packages/cli/src/util/env/diff-env-files.ts
vendored
14
packages/cli/src/util/env/diff-env-files.ts
vendored
@@ -63,19 +63,25 @@ export function buildDeltaString(
|
||||
const { added, changed, removed } = findChanges(oldEnv, newEnv);
|
||||
|
||||
let deltaString = '';
|
||||
deltaString += chalk.green(addDeltaSection('+', changed, true));
|
||||
deltaString += chalk.green(addDeltaSection('+', added));
|
||||
deltaString += chalk.yellow(addDeltaSection('~', changed));
|
||||
deltaString += chalk.red(addDeltaSection('-', removed));
|
||||
|
||||
return deltaString ? chalk.gray('Changes:\n') + deltaString : deltaString;
|
||||
return deltaString
|
||||
? chalk.gray('Changes:\n') + deltaString + '\n'
|
||||
: deltaString;
|
||||
}
|
||||
|
||||
function addDeltaSection(prefix: string, arr: string[]): string {
|
||||
function addDeltaSection(
|
||||
prefix: string,
|
||||
arr: string[],
|
||||
changed: boolean = false
|
||||
): string {
|
||||
if (arr.length === 0) return '';
|
||||
return (
|
||||
arr
|
||||
.sort()
|
||||
.map(item => `${prefix} ${item}`)
|
||||
.map(item => `${prefix} ${item}${changed ? ' (Updated)' : ''}`)
|
||||
.join('\n') + '\n'
|
||||
);
|
||||
}
|
||||
|
||||
10
packages/cli/test/dev/fixtures/go/api/[segement].go
Normal file
10
packages/cli/test/dev/fixtures/go/api/[segement].go
Normal file
@@ -0,0 +1,10 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "Req Path: %s", r.URL.Path)
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
package handler
|
||||
package another
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Another(w http.ResponseWriter, r *http.Request) {
|
||||
func HandlerAnother(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "This is another page")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export const config = {
|
||||
matcher: 'not-a-valid-matcher',
|
||||
};
|
||||
|
||||
export default function middleware(request, _event) {
|
||||
return new Response(null);
|
||||
}
|
||||
@@ -227,7 +227,7 @@ test('[vercel dev] should handle syntax errors thrown in edge functions', async
|
||||
expect(await res.text()).toMatch(
|
||||
/<strong>500<\/strong>: INTERNAL_SERVER_ERROR/g
|
||||
);
|
||||
expect(stderr).toMatch(/Failed to instantiate edge runtime./g);
|
||||
expect(stderr).toMatch(/Failed to compile user code for edge runtime./g);
|
||||
expect(stderr).toMatch(/Unexpected end of file/g);
|
||||
expect(stderr).toMatch(
|
||||
/Failed to complete request to \/api\/edge-error-syntax: Error: socket hang up/g
|
||||
@@ -307,6 +307,35 @@ test('[vercel dev] should handle missing handler errors thrown in edge functions
|
||||
}
|
||||
});
|
||||
|
||||
test('[vercel dev] should handle invalid middleware config', async () => {
|
||||
const dir = fixture('middleware-matchers-invalid');
|
||||
const { dev, port, readyResolver } = await testFixture(dir);
|
||||
|
||||
try {
|
||||
await readyResolver;
|
||||
|
||||
let res = await fetch(`http://localhost:${port}/api/whatever`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept:
|
||||
'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||
},
|
||||
});
|
||||
validateResponseHeaders(res);
|
||||
|
||||
const { stderr } = await dev.kill('SIGTERM');
|
||||
|
||||
expect(await res.text()).toMatch(
|
||||
/<strong>500<\/strong>: INTERNAL_SERVER_ERROR/g
|
||||
);
|
||||
expect(stderr).toMatch(
|
||||
/Middleware's `config.matcher` .+ Received: not-a-valid-matcher/g
|
||||
);
|
||||
} finally {
|
||||
await dev.kill('SIGTERM');
|
||||
}
|
||||
});
|
||||
|
||||
test('[vercel dev] should support request body', async () => {
|
||||
const dir = fixture('node-request-body');
|
||||
const { dev, port, readyResolver } = await testFixture(dir);
|
||||
|
||||
@@ -388,9 +388,8 @@ test(
|
||||
await testPath(200, `/api/index.go`, 'This is the index page');
|
||||
await testPath(200, `/api/another`, 'This is another page');
|
||||
await testPath(200, '/api/another.go', 'This is another page');
|
||||
// DISABLED: These assertions rely on different bracket names working.
|
||||
// await testPath(200, `/api/foo`, 'Req Path: /api/foo');
|
||||
// await testPath(200, `/api/bar`, 'Req Path: /api/bar');
|
||||
await testPath(200, `/api/foo`, 'Req Path: /api/foo');
|
||||
await testPath(200, `/api/bar`, 'Req Path: /api/bar');
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -305,7 +305,7 @@ function testFixtureStdio(
|
||||
? ['--scope', process.env.VERCEL_TEAM_ID]
|
||||
: []),
|
||||
'link',
|
||||
'--confirm',
|
||||
'--yes',
|
||||
],
|
||||
{ cwd, stdio: 'pipe', reject: false }
|
||||
);
|
||||
@@ -348,7 +348,10 @@ function testFixtureStdio(
|
||||
: []),
|
||||
'deploy',
|
||||
...(process.env.VERCEL_CLI_VERSION
|
||||
? ['--build-env', `VERCEL_CLI_VERSION=${process.env.VERCEL_CLI_VERSION}`]
|
||||
? [
|
||||
'--build-env',
|
||||
`VERCEL_CLI_VERSION=${process.env.VERCEL_CLI_VERSION}`,
|
||||
]
|
||||
: []),
|
||||
'--public',
|
||||
'--debug',
|
||||
@@ -430,7 +433,7 @@ function testFixtureStdio(
|
||||
);
|
||||
}
|
||||
|
||||
if (stderr.includes('Command failed') || stderr.includes('Error!')) {
|
||||
if (stderr.includes('Command failed')) {
|
||||
dev.kill('SIGTERM');
|
||||
throw new Error(`Failed for "${directory}" with stderr "${stderr}".`);
|
||||
}
|
||||
|
||||
7
packages/cli/test/fixtures/unit/commands/build/static-symlink/.vercel/project.json
vendored
Normal file
7
packages/cli/test/fixtures/unit/commands/build/static-symlink/.vercel/project.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"orgId": ".",
|
||||
"projectId": ".",
|
||||
"settings": {
|
||||
"framework": null
|
||||
}
|
||||
}
|
||||
1
packages/cli/test/fixtures/unit/commands/build/static-symlink/index.html
vendored
Normal file
1
packages/cli/test/fixtures/unit/commands/build/static-symlink/index.html
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<h1>Vercel</h1>
|
||||
125
packages/cli/test/integration.js
vendored
125
packages/cli/test/integration.js
vendored
@@ -126,7 +126,7 @@ ${stdout}
|
||||
async function vcLink(t, projectPath) {
|
||||
const { exitCode, stderr, stdout } = await execa(
|
||||
binaryPath,
|
||||
['link', '--confirm', ...defaultArgs],
|
||||
['link', '--yes', ...defaultArgs],
|
||||
{
|
||||
reject: false,
|
||||
cwd: projectPath,
|
||||
@@ -658,7 +658,7 @@ test('[vc link] with vercel.json configuration overrides should create a valid d
|
||||
|
||||
const { exitCode, stderr, stdout } = await execa(
|
||||
binaryPath,
|
||||
['link', '--confirm', ...defaultArgs],
|
||||
['link', '--yes', ...defaultArgs],
|
||||
{
|
||||
reject: false,
|
||||
cwd: directory,
|
||||
@@ -683,7 +683,7 @@ test('deploy using only now.json with `redirects` defined', async t => {
|
||||
|
||||
const { exitCode, stderr, stdout } = await execa(
|
||||
binaryPath,
|
||||
[target, ...defaultArgs, '--confirm'],
|
||||
[target, ...defaultArgs, '--yes'],
|
||||
{
|
||||
reject: false,
|
||||
}
|
||||
@@ -703,14 +703,7 @@ test('deploy using --local-config flag v2', async t => {
|
||||
|
||||
const { exitCode, stderr, stdout } = await execa(
|
||||
binaryPath,
|
||||
[
|
||||
'deploy',
|
||||
target,
|
||||
'--local-config',
|
||||
configPath,
|
||||
...defaultArgs,
|
||||
'--confirm',
|
||||
],
|
||||
['deploy', target, '--local-config', configPath, ...defaultArgs, '--yes'],
|
||||
{
|
||||
reject: false,
|
||||
}
|
||||
@@ -747,7 +740,7 @@ test('deploy fails using --local-config flag with non-existent path', async t =>
|
||||
'--local-config',
|
||||
'does-not-exist.json',
|
||||
...defaultArgs,
|
||||
'--confirm',
|
||||
'--yes',
|
||||
],
|
||||
{
|
||||
reject: false,
|
||||
@@ -772,7 +765,7 @@ test('deploy using --local-config flag above target', async t => {
|
||||
'--local-config',
|
||||
'./now-root.json',
|
||||
...defaultArgs,
|
||||
'--confirm',
|
||||
'--yes',
|
||||
],
|
||||
{
|
||||
cwd: root,
|
||||
@@ -801,7 +794,7 @@ test('Deploy `api-env` fixture and test `vercel env` command', async t => {
|
||||
async function vcLink() {
|
||||
const { exitCode, stderr, stdout } = await execa(
|
||||
binaryPath,
|
||||
['link', '--confirm', ...defaultArgs],
|
||||
['link', '--yes', ...defaultArgs],
|
||||
{
|
||||
reject: false,
|
||||
cwd: target,
|
||||
@@ -1345,7 +1338,7 @@ test('deploy with metadata containing "=" in the value', async t => {
|
||||
|
||||
const { exitCode, stderr, stdout } = await execa(
|
||||
binaryPath,
|
||||
[target, ...defaultArgs, '--confirm', '--meta', 'someKey=='],
|
||||
[target, ...defaultArgs, '--yes', '--meta', 'someKey=='],
|
||||
{ reject: false }
|
||||
);
|
||||
|
||||
@@ -1421,7 +1414,7 @@ test('should add secret with hyphen prefix', async t => {
|
||||
formatOutput({ stderr: secretCall.stderr, stdout: secretCall.stdout })
|
||||
);
|
||||
|
||||
let targetCall = await execa(binaryPath, [...defaultArgs, '--confirm'], {
|
||||
let targetCall = await execa(binaryPath, [...defaultArgs, '--yes'], {
|
||||
cwd: target,
|
||||
reject: false,
|
||||
});
|
||||
@@ -1475,7 +1468,7 @@ test('ignore files specified in .nowignore', async t => {
|
||||
'--name',
|
||||
session,
|
||||
...defaultArgs,
|
||||
'--confirm',
|
||||
'--yes',
|
||||
];
|
||||
const targetCall = await execa(binaryPath, args, {
|
||||
cwd: directory,
|
||||
@@ -1503,7 +1496,7 @@ test('ignore files specified in .nowignore via allowlist', async t => {
|
||||
'--name',
|
||||
session,
|
||||
...defaultArgs,
|
||||
'--confirm',
|
||||
'--yes',
|
||||
];
|
||||
const targetCall = await execa(binaryPath, args, {
|
||||
cwd: directory,
|
||||
@@ -1575,7 +1568,7 @@ test('domains inspect', async t => {
|
||||
`-V`,
|
||||
`2`,
|
||||
`--name=${projectName}`,
|
||||
'--confirm',
|
||||
'--yes',
|
||||
'--public',
|
||||
]);
|
||||
t.is(output.exitCode, 0, formatOutput(output));
|
||||
@@ -1811,7 +1804,7 @@ test('ensure we render a warning for deployments with no files', async t => {
|
||||
'--name',
|
||||
session,
|
||||
...defaultArgs,
|
||||
'--confirm',
|
||||
'--yes',
|
||||
'--force',
|
||||
],
|
||||
{
|
||||
@@ -1939,7 +1932,7 @@ test('ensure the `scope` property works with email', async t => {
|
||||
session,
|
||||
...defaultArgs,
|
||||
'--force',
|
||||
'--confirm',
|
||||
'--yes',
|
||||
],
|
||||
{
|
||||
reject: false,
|
||||
@@ -1979,7 +1972,7 @@ test('ensure the `scope` property works with username', async t => {
|
||||
session,
|
||||
...defaultArgs,
|
||||
'--force',
|
||||
'--confirm',
|
||||
'--yes',
|
||||
],
|
||||
{
|
||||
reject: false,
|
||||
@@ -2012,7 +2005,7 @@ test('try to create a builds deployments with wrong now.json', async t => {
|
||||
|
||||
const { stderr, stdout, exitCode } = await execa(
|
||||
binaryPath,
|
||||
[directory, '--public', ...defaultArgs, '--confirm'],
|
||||
[directory, '--public', ...defaultArgs, '--yes'],
|
||||
{
|
||||
reject: false,
|
||||
}
|
||||
@@ -2037,7 +2030,7 @@ test('try to create a builds deployments with wrong vercel.json', async t => {
|
||||
|
||||
const { stderr, stdout, exitCode } = await execa(
|
||||
binaryPath,
|
||||
[directory, '--public', ...defaultArgs, '--confirm'],
|
||||
[directory, '--public', ...defaultArgs, '--yes'],
|
||||
{
|
||||
reject: false,
|
||||
}
|
||||
@@ -2061,7 +2054,7 @@ test('try to create a builds deployments with wrong `build.env` property', async
|
||||
|
||||
const { stderr, stdout, exitCode } = await execa(
|
||||
binaryPath,
|
||||
['--public', ...defaultArgs, '--confirm'],
|
||||
['--public', ...defaultArgs, '--yes'],
|
||||
{
|
||||
cwd: directory,
|
||||
reject: false,
|
||||
@@ -2093,7 +2086,7 @@ test('create a builds deployments with no actual builds', async t => {
|
||||
session,
|
||||
...defaultArgs,
|
||||
'--force',
|
||||
'--confirm',
|
||||
'--yes',
|
||||
],
|
||||
{
|
||||
reject: false,
|
||||
@@ -2120,7 +2113,7 @@ test('create a staging deployment', async t => {
|
||||
directory,
|
||||
'--target=staging',
|
||||
...args,
|
||||
'--confirm',
|
||||
'--yes',
|
||||
]);
|
||||
|
||||
console.log(targetCall.stderr);
|
||||
@@ -2150,7 +2143,7 @@ test('create a production deployment', async t => {
|
||||
directory,
|
||||
'--target=production',
|
||||
...args,
|
||||
'--confirm',
|
||||
'--yes',
|
||||
]);
|
||||
|
||||
console.log(targetCall.stderr);
|
||||
@@ -2211,7 +2204,7 @@ test('use build-env', async t => {
|
||||
|
||||
const { stdout, stderr, exitCode } = await execa(
|
||||
binaryPath,
|
||||
[directory, '--public', ...defaultArgs, '--confirm'],
|
||||
[directory, '--public', ...defaultArgs, '--yes'],
|
||||
{
|
||||
reject: false,
|
||||
}
|
||||
@@ -2244,7 +2237,7 @@ test('use `--debug` CLI flag', async t => {
|
||||
session,
|
||||
'--debug',
|
||||
...defaultArgs,
|
||||
'--confirm',
|
||||
'--yes',
|
||||
],
|
||||
{
|
||||
reject: false,
|
||||
@@ -2276,7 +2269,7 @@ test('try to deploy non-existing path', async t => {
|
||||
|
||||
const { stderr, stdout, exitCode } = await execa(
|
||||
binaryPath,
|
||||
[session, ...defaultArgs, '--confirm'],
|
||||
[session, ...defaultArgs, '--yes'],
|
||||
{
|
||||
reject: false,
|
||||
}
|
||||
@@ -2296,7 +2289,7 @@ test('try to deploy with non-existing team', async t => {
|
||||
|
||||
const { stderr, stdout, exitCode } = await execa(
|
||||
binaryPath,
|
||||
[target, '--scope', session, ...defaultArgs, '--confirm'],
|
||||
[target, '--scope', session, ...defaultArgs, '--yes'],
|
||||
{
|
||||
reject: false,
|
||||
}
|
||||
@@ -2434,7 +2427,7 @@ test('try to revert a deployment and assign the automatic aliases', async t => {
|
||||
stdout: deploymentUrl,
|
||||
stderr,
|
||||
exitCode,
|
||||
} = await execute([firstDeployment, '--confirm']);
|
||||
} = await execute([firstDeployment, '--yes']);
|
||||
|
||||
t.is(exitCode, 0, formatOutput({ stderr, stdout: deploymentUrl }));
|
||||
|
||||
@@ -2455,7 +2448,7 @@ test('try to revert a deployment and assign the automatic aliases', async t => {
|
||||
stdout: deploymentUrl,
|
||||
stderr,
|
||||
exitCode,
|
||||
} = await execute([secondDeployment, '--confirm']);
|
||||
} = await execute([secondDeployment, '--yes']);
|
||||
|
||||
t.is(exitCode, 0, formatOutput({ stderr, stdout: deploymentUrl }));
|
||||
|
||||
@@ -2478,7 +2471,7 @@ test('try to revert a deployment and assign the automatic aliases', async t => {
|
||||
stdout: deploymentUrl,
|
||||
stderr,
|
||||
exitCode,
|
||||
} = await execute([firstDeployment, '--confirm']);
|
||||
} = await execute([firstDeployment, '--yes']);
|
||||
|
||||
t.is(exitCode, 0, formatOutput({ stderr, stdout: deploymentUrl }));
|
||||
|
||||
@@ -2560,7 +2553,7 @@ test('`vercel rm` removes a deployment', async t => {
|
||||
'-V',
|
||||
2,
|
||||
'--force',
|
||||
'--confirm',
|
||||
'--yes',
|
||||
],
|
||||
{
|
||||
reject: false,
|
||||
@@ -2616,7 +2609,7 @@ test('`vercel rm` 404 exits quickly', async t => {
|
||||
|
||||
test('render build errors', async t => {
|
||||
const deploymentPath = fixture('failing-build');
|
||||
const output = await execute([deploymentPath, '--confirm']);
|
||||
const output = await execute([deploymentPath, '--yes']);
|
||||
|
||||
console.log(output.stderr);
|
||||
console.log(output.stdout);
|
||||
@@ -2690,12 +2683,7 @@ test('vercel hasOwnProperty not a valid subcommand', async t => {
|
||||
|
||||
test('create zero-config deployment', async t => {
|
||||
const fixturePath = fixture('zero-config-next-js');
|
||||
const output = await execute([
|
||||
fixturePath,
|
||||
'--force',
|
||||
'--public',
|
||||
'--confirm',
|
||||
]);
|
||||
const output = await execute([fixturePath, '--force', '--public', '--yes']);
|
||||
|
||||
console.log('isCanary', isCanary);
|
||||
console.log(output.stderr);
|
||||
@@ -2726,12 +2714,7 @@ test('create zero-config deployment', async t => {
|
||||
|
||||
test('next unsupported functions config shows warning link', async t => {
|
||||
const fixturePath = fixture('zero-config-next-js-functions-warning');
|
||||
const output = await execute([
|
||||
fixturePath,
|
||||
'--force',
|
||||
'--public',
|
||||
'--confirm',
|
||||
]);
|
||||
const output = await execute([fixturePath, '--force', '--public', '--yes']);
|
||||
|
||||
console.log('isCanary', isCanary);
|
||||
console.log(output.stderr);
|
||||
@@ -2819,7 +2802,7 @@ test('vercel secret rm', async t => {
|
||||
|
||||
test('deploy a Lambda with 128MB of memory', async t => {
|
||||
const directory = fixture('lambda-with-128-memory');
|
||||
const output = await execute([directory, '--confirm']);
|
||||
const output = await execute([directory, '--yes']);
|
||||
|
||||
t.is(output.exitCode, 0, formatOutput(output));
|
||||
|
||||
@@ -2836,7 +2819,7 @@ test('deploy a Lambda with 128MB of memory', async t => {
|
||||
|
||||
test('fail to deploy a Lambda with an incorrect value for of memory', async t => {
|
||||
const directory = fixture('lambda-with-200-memory');
|
||||
const output = await execute([directory, '--confirm']);
|
||||
const output = await execute([directory, '--yes']);
|
||||
|
||||
t.is(output.exitCode, 1, formatOutput(output));
|
||||
t.regex(output.stderr, /steps of 64/gm, formatOutput(output));
|
||||
@@ -2845,7 +2828,7 @@ test('fail to deploy a Lambda with an incorrect value for of memory', async t =>
|
||||
|
||||
test('deploy a Lambda with 3 seconds of maxDuration', async t => {
|
||||
const directory = fixture('lambda-with-3-second-timeout');
|
||||
const output = await execute([directory, '--confirm']);
|
||||
const output = await execute([directory, '--yes']);
|
||||
|
||||
t.is(output.exitCode, 0, formatOutput(output));
|
||||
|
||||
@@ -2872,7 +2855,7 @@ test('deploy a Lambda with 3 seconds of maxDuration', async t => {
|
||||
|
||||
test('fail to deploy a Lambda with an incorrect value for maxDuration', async t => {
|
||||
const directory = fixture('lambda-with-1000-second-timeout');
|
||||
const output = await execute([directory, '--confirm']);
|
||||
const output = await execute([directory, '--yes']);
|
||||
|
||||
t.is(output.exitCode, 1, formatOutput(output));
|
||||
t.regex(
|
||||
@@ -2895,7 +2878,7 @@ test('invalid `--token`', async t => {
|
||||
|
||||
test('deploy a Lambda with a specific runtime', async t => {
|
||||
const directory = fixture('lambda-with-php-runtime');
|
||||
const output = await execute([directory, '--public', '--confirm']);
|
||||
const output = await execute([directory, '--public', '--yes']);
|
||||
|
||||
t.is(output.exitCode, 0, formatOutput(output));
|
||||
|
||||
@@ -2907,7 +2890,7 @@ test('deploy a Lambda with a specific runtime', async t => {
|
||||
|
||||
test('fail to deploy a Lambda with a specific runtime but without a locked version', async t => {
|
||||
const directory = fixture('lambda-with-invalid-runtime');
|
||||
const output = await execute([directory, '--confirm']);
|
||||
const output = await execute([directory, '--yes']);
|
||||
|
||||
t.is(output.exitCode, 1, formatOutput(output));
|
||||
t.regex(
|
||||
@@ -2957,7 +2940,7 @@ test('assign a domain to a project', async t => {
|
||||
const domain = `project-domain.${contextName}.vercel.app`;
|
||||
const directory = fixture('static-deployment');
|
||||
|
||||
const deploymentOutput = await execute([directory, '--public', '--confirm']);
|
||||
const deploymentOutput = await execute([directory, '--public', '--yes']);
|
||||
t.is(deploymentOutput.exitCode, 0, formatOutput(deploymentOutput));
|
||||
|
||||
const host = deploymentOutput.stdout.trim().replace('https://', '');
|
||||
@@ -2977,7 +2960,7 @@ test('assign a domain to a project', async t => {
|
||||
|
||||
test('ensure `github` and `scope` are not sent to the API', async t => {
|
||||
const directory = fixture('github-and-scope-config');
|
||||
const output = await execute([directory, '--confirm']);
|
||||
const output = await execute([directory, '--yes']);
|
||||
|
||||
t.is(output.exitCode, 0, formatOutput(output));
|
||||
});
|
||||
@@ -3292,7 +3275,7 @@ test('deploy with `VERCEL_ORG_ID` and `VERCEL_PROJECT_ID`', async t => {
|
||||
const directory = fixture('static-deployment');
|
||||
|
||||
// generate `.vercel`
|
||||
await execute([directory, '--confirm']);
|
||||
await execute([directory, '--yes']);
|
||||
|
||||
const link = require(path.join(directory, '.vercel/project.json'));
|
||||
await remove(path.join(directory, '.vercel'));
|
||||
@@ -3343,7 +3326,7 @@ test('deploy shows notice when project in `.vercel` does not exists', async t =>
|
||||
test('use `rootDirectory` from project when deploying', async t => {
|
||||
const directory = fixture('project-root-directory');
|
||||
|
||||
const firstResult = await execute([directory, '--confirm', '--public']);
|
||||
const firstResult = await execute([directory, '--yes', '--public']);
|
||||
t.is(firstResult.exitCode, 0, formatOutput(firstResult));
|
||||
|
||||
const { host: firstHost } = new URL(firstResult.stdout);
|
||||
@@ -3438,7 +3421,7 @@ test('deploys with only now.json and README.md', async t => {
|
||||
|
||||
const { exitCode, stderr, stdout } = await execa(
|
||||
binaryPath,
|
||||
[...defaultArgs, '--confirm'],
|
||||
[...defaultArgs, '--yes'],
|
||||
{
|
||||
cwd: directory,
|
||||
reject: false,
|
||||
@@ -3457,7 +3440,7 @@ test('deploys with only vercel.json and README.md', async t => {
|
||||
|
||||
const { exitCode, stderr, stdout } = await execa(
|
||||
binaryPath,
|
||||
[...defaultArgs, '--confirm'],
|
||||
[...defaultArgs, '--yes'],
|
||||
{
|
||||
cwd: directory,
|
||||
reject: false,
|
||||
@@ -3476,7 +3459,7 @@ test('reject conflicting `vercel.json` and `now.json` files', async t => {
|
||||
|
||||
const { exitCode, stderr, stdout } = await execa(
|
||||
binaryPath,
|
||||
[...defaultArgs, '--confirm'],
|
||||
[...defaultArgs, '--yes'],
|
||||
{
|
||||
cwd: directory,
|
||||
reject: false,
|
||||
@@ -3515,7 +3498,7 @@ test('deploy gatsby twice and print cached directories', async t => {
|
||||
const pkg = JSON.parse(packageJsonOriginal);
|
||||
|
||||
async function tryDeploy(cwd) {
|
||||
await execa(binaryPath, [...defaultArgs, '--public', '--confirm'], {
|
||||
await execa(binaryPath, [...defaultArgs, '--public', '--yes'], {
|
||||
cwd,
|
||||
stdio: 'inherit',
|
||||
reject: true,
|
||||
@@ -3553,7 +3536,7 @@ test('deploy pnpm twice using pnp and symlink=false', async t => {
|
||||
session,
|
||||
...defaultArgs,
|
||||
'--public',
|
||||
'--confirm',
|
||||
'--yes',
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -3581,7 +3564,7 @@ test('reject deploying with wrong team .vercel config', async t => {
|
||||
|
||||
const { exitCode, stderr, stdout } = await execa(
|
||||
binaryPath,
|
||||
[...defaultArgs, '--confirm'],
|
||||
[...defaultArgs, '--yes'],
|
||||
{
|
||||
cwd: directory,
|
||||
reject: false,
|
||||
@@ -3601,7 +3584,7 @@ test('reject deploying with invalid token', async t => {
|
||||
const directory = fixture('unauthorized-vercel-config');
|
||||
const { exitCode, stderr, stdout } = await execa(
|
||||
binaryPath,
|
||||
[...defaultArgs, '--confirm'],
|
||||
[...defaultArgs, '--yes'],
|
||||
{
|
||||
cwd: directory,
|
||||
reject: false,
|
||||
@@ -3658,7 +3641,7 @@ test('[vc link] should show prompts to set up project', async t => {
|
||||
);
|
||||
});
|
||||
|
||||
test('[vc link --confirm] should not show prompts and autolink', async t => {
|
||||
test('[vc link --yes] should not show prompts and autolink', async t => {
|
||||
const dir = fixture('project-link-confirm');
|
||||
|
||||
// remove previously linked project if it exists
|
||||
@@ -3666,7 +3649,7 @@ test('[vc link --confirm] should not show prompts and autolink', async t => {
|
||||
|
||||
const { exitCode, stderr, stdout } = await execa(
|
||||
binaryPath,
|
||||
['link', '--confirm', ...defaultArgs],
|
||||
['link', '--yes', ...defaultArgs],
|
||||
{ cwd: dir, reject: false }
|
||||
);
|
||||
|
||||
@@ -3701,7 +3684,7 @@ test('[vc link] should not duplicate paths in .gitignore', async t => {
|
||||
|
||||
const { exitCode, stderr, stdout } = await execa(
|
||||
binaryPath,
|
||||
['link', '--confirm', ...defaultArgs],
|
||||
['link', '--yes', ...defaultArgs],
|
||||
{
|
||||
cwd: dir,
|
||||
reject: false,
|
||||
@@ -3875,7 +3858,7 @@ test('[vc link] should support the `--project` flag', async t => {
|
||||
|
||||
const [user, output] = await Promise.all([
|
||||
fetchTokenInformation(token),
|
||||
execute(['link', '--confirm', '--project', projectName, directory]),
|
||||
execute(['link', '--yes', '--project', projectName, directory]),
|
||||
]);
|
||||
|
||||
t.is(output.exitCode, 0, formatOutput(output));
|
||||
@@ -3996,7 +3979,7 @@ test('vercel.json configuration overrides in an existing project do not prompt u
|
||||
const deployment = await execa(
|
||||
binaryPath,
|
||||
[directory, ...defaultArgs, '--public'].concat(
|
||||
autoConfirm ? ['--confirm'] : []
|
||||
autoConfirm ? ['--yes'] : []
|
||||
),
|
||||
{ reject: false }
|
||||
);
|
||||
|
||||
@@ -143,6 +143,54 @@ describe('build', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle symlinked static files', async () => {
|
||||
const cwd = fixture('static-symlink');
|
||||
const output = join(cwd, '.vercel/output');
|
||||
|
||||
// try to create the symlink, if it fails (e.g. Windows), skip the test
|
||||
try {
|
||||
await fs.unlink(join(cwd, 'foo.html'));
|
||||
await fs.symlink(join(cwd, 'index.html'), join(cwd, 'foo.html'));
|
||||
} catch (e) {
|
||||
console.log('Symlinks not available, skipping test');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
process.chdir(cwd);
|
||||
const exitCode = await build(client);
|
||||
expect(exitCode).toEqual(0);
|
||||
|
||||
// `builds.json` says that "@vercel/static" was run
|
||||
const builds = await fs.readJSON(join(output, 'builds.json'));
|
||||
expect(builds).toMatchObject({
|
||||
target: 'preview',
|
||||
builds: [
|
||||
{
|
||||
require: '@vercel/static',
|
||||
apiVersion: 2,
|
||||
src: '**',
|
||||
use: '@vercel/static',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// "static" directory contains static files
|
||||
const files = await fs.readdir(join(output, 'static'));
|
||||
expect(files.sort()).toEqual(['foo.html', 'index.html']);
|
||||
expect(
|
||||
(await fs.lstat(join(output, 'static', 'foo.html'))).isSymbolicLink()
|
||||
).toEqual(true);
|
||||
expect(
|
||||
(await fs.lstat(join(output, 'static', 'index.html'))).isSymbolicLink()
|
||||
).toEqual(false);
|
||||
} finally {
|
||||
await fs.unlink(join(cwd, 'foo.html'));
|
||||
process.chdir(originalCwd);
|
||||
delete process.env.__VERCEL_BUILD_RUNNING;
|
||||
}
|
||||
});
|
||||
|
||||
it('should normalize "src" path in `vercel.json`', async () => {
|
||||
const cwd = fixture('normalize-src');
|
||||
const output = join(cwd, '.vercel/output');
|
||||
@@ -661,6 +709,11 @@ describe('build', () => {
|
||||
const exitCode = await build(client);
|
||||
expect(exitCode).toEqual(1);
|
||||
|
||||
// Error gets printed to the terminal
|
||||
await expect(client.stderr).toOutput(
|
||||
'Error! Function must contain at least one property.'
|
||||
);
|
||||
|
||||
// `builds.json` contains top-level "error" property
|
||||
const builds = await fs.readJSON(join(output, 'builds.json'));
|
||||
expect(builds.builds).toBeUndefined();
|
||||
@@ -687,6 +740,9 @@ describe('build', () => {
|
||||
const exitCode = await build(client);
|
||||
expect(exitCode).toEqual(1);
|
||||
|
||||
// Error gets printed to the terminal
|
||||
await expect(client.stderr).toOutput("Duplicate identifier 'res'.");
|
||||
|
||||
// `builds.json` contains "error" build
|
||||
const builds = await fs.readJSON(join(output, 'builds.json'));
|
||||
expect(builds.builds).toHaveLength(4);
|
||||
@@ -846,7 +902,6 @@ describe('build', () => {
|
||||
output = join(cwd, '.vercel/output');
|
||||
|
||||
process.chdir(cwd);
|
||||
client.stderr.pipe(process.stderr);
|
||||
const exitCode = await build(client);
|
||||
expect(exitCode).toEqual(0);
|
||||
|
||||
|
||||
@@ -178,10 +178,10 @@ describe('env', () => {
|
||||
await expect(client.stderr).toOutput(
|
||||
'Downloading `development` Environment Variables for Project env-pull-delta'
|
||||
);
|
||||
await expect(client.stderr).toOutput('Updated .env file');
|
||||
await expect(client.stderr).toOutput(
|
||||
'+ NEW_VAR\n~ SPECIAL_FLAG\n- TEST\n'
|
||||
'+ SPECIAL_FLAG (Updated)\n+ NEW_VAR\n- TEST\n'
|
||||
);
|
||||
await expect(client.stderr).toOutput('Updated .env file');
|
||||
|
||||
await expect(pullPromise).resolves.toEqual(0);
|
||||
} finally {
|
||||
@@ -218,8 +218,8 @@ describe('env', () => {
|
||||
|
||||
client.setArgv('env', 'pull', '--yes', '--cwd', cwd);
|
||||
const pullPromise = env(client);
|
||||
await expect(client.stderr).toOutput('Updated .env file');
|
||||
await expect(client.stderr).toOutput('> No changes found.');
|
||||
await expect(client.stderr).toOutput('Updated .env file');
|
||||
await expect(pullPromise).resolves.toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -76,7 +76,7 @@ describe('git', () => {
|
||||
id: 'no-git-config',
|
||||
name: 'no-git-config',
|
||||
});
|
||||
client.setArgv('git', 'connect', '--confirm');
|
||||
client.setArgv('git', 'connect', '--yes');
|
||||
const exitCode = await git(client);
|
||||
expect(exitCode).toEqual(1);
|
||||
await expect(client.stderr).toOutput(
|
||||
@@ -98,7 +98,7 @@ describe('git', () => {
|
||||
id: 'no-remote-url',
|
||||
name: 'no-remote-url',
|
||||
});
|
||||
client.setArgv('git', 'connect', '--confirm');
|
||||
client.setArgv('git', 'connect', '--yes');
|
||||
const exitCode = await git(client);
|
||||
expect(exitCode).toEqual(1);
|
||||
await expect(client.stderr).toOutput(
|
||||
@@ -121,7 +121,7 @@ describe('git', () => {
|
||||
id: 'bad-remote-url',
|
||||
name: 'bad-remote-url',
|
||||
});
|
||||
client.setArgv('git', 'connect', '--confirm');
|
||||
client.setArgv('git', 'connect', '--yes');
|
||||
const exitCode = await git(client);
|
||||
expect(exitCode).toEqual(1);
|
||||
|
||||
@@ -148,7 +148,7 @@ describe('git', () => {
|
||||
id: 'new-connection',
|
||||
name: 'new-connection',
|
||||
});
|
||||
client.setArgv('git', 'connect', '--confirm');
|
||||
client.setArgv('git', 'connect', '--yes');
|
||||
const gitPromise = git(client);
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
@@ -201,7 +201,7 @@ describe('git', () => {
|
||||
updatedAt: 1656109539791,
|
||||
};
|
||||
|
||||
client.setArgv('git', 'connect', '--confirm');
|
||||
client.setArgv('git', 'connect', '--yes');
|
||||
const gitPromise = git(client);
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
@@ -253,7 +253,7 @@ describe('git', () => {
|
||||
createdAt: 1656109539791,
|
||||
updatedAt: 1656109539791,
|
||||
};
|
||||
client.setArgv('git', 'connect', '--confirm');
|
||||
client.setArgv('git', 'connect', '--yes');
|
||||
const gitPromise = git(client);
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
@@ -283,7 +283,7 @@ describe('git', () => {
|
||||
name: 'invalid-repo',
|
||||
});
|
||||
|
||||
client.setArgv('git', 'connect', '--confirm');
|
||||
client.setArgv('git', 'connect', '--yes');
|
||||
const gitPromise = git(client);
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
|
||||
@@ -15,6 +15,17 @@ describe('inspect', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should strip the scheme of a url', async () => {
|
||||
const user = useUser();
|
||||
const deployment = useDeployment({ creator: user });
|
||||
client.setArgv('inspect', `http://${deployment.url}`);
|
||||
const exitCode = await inspect(client);
|
||||
expect(exitCode).toEqual(0);
|
||||
await expect(client.stderr).toOutput(
|
||||
`> Fetched deployment ${deployment.url} in ${user.username}`
|
||||
);
|
||||
});
|
||||
|
||||
it('should print error when deployment not found', async () => {
|
||||
const user = useUser();
|
||||
useDeployment({ creator: user });
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/client",
|
||||
"version": "12.1.7",
|
||||
"version": "12.1.11",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"homepage": "https://vercel.com",
|
||||
@@ -42,8 +42,8 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "5.0.8",
|
||||
"@vercel/routing-utils": "2.0.0",
|
||||
"@vercel/build-utils": "5.3.0",
|
||||
"@vercel/routing-utils": "2.0.2",
|
||||
"@zeit/fetch": "5.2.0",
|
||||
"async-retry": "1.2.3",
|
||||
"async-sema": "3.0.0",
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "@vercel/edge",
|
||||
"version": "0.0.1",
|
||||
"version": "0.0.3",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsup src/index.ts --dts --format esm,cjs",
|
||||
"test-unit": "jest",
|
||||
"prepublishOnly": "yarn build"
|
||||
"test": "jest --env node --verbose --runInBand --bail",
|
||||
"test-unit": "yarn test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@edge-runtime/jest-environment": "1.1.0-beta.7",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/frameworks",
|
||||
"version": "1.1.1",
|
||||
"version": "1.1.3",
|
||||
"main": "./dist/frameworks.js",
|
||||
"types": "./dist/frameworks.d.ts",
|
||||
"files": [
|
||||
@@ -21,7 +21,7 @@
|
||||
"@types/js-yaml": "3.12.1",
|
||||
"@types/node": "12.0.4",
|
||||
"@types/node-fetch": "2.5.8",
|
||||
"@vercel/routing-utils": "2.0.0",
|
||||
"@vercel/routing-utils": "2.0.2",
|
||||
"ajv": "6.12.2",
|
||||
"typescript": "4.3.4"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/fs-detectors",
|
||||
"version": "2.0.3",
|
||||
"version": "2.0.5",
|
||||
"description": "Vercel filesystem detectors",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -14,14 +14,13 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"prepublishOnly": "tsc",
|
||||
"build": "tsc",
|
||||
"test": "yarn jest --env node --verbose --runInBand --bail test/unit.*test.*",
|
||||
"test": "jest --env node --verbose --runInBand --bail test/unit.*test.*",
|
||||
"test-unit": "yarn test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/frameworks": "1.1.1",
|
||||
"@vercel/routing-utils": "2.0.0",
|
||||
"@vercel/frameworks": "1.1.3",
|
||||
"@vercel/routing-utils": "2.0.2",
|
||||
"glob": "8.0.3",
|
||||
"js-yaml": "4.1.0",
|
||||
"minimatch": "3.0.4",
|
||||
|
||||
@@ -12,6 +12,8 @@ import {
|
||||
mkdirp,
|
||||
move,
|
||||
remove,
|
||||
rmdir,
|
||||
readdir,
|
||||
} from 'fs-extra';
|
||||
import {
|
||||
BuildOptions,
|
||||
@@ -71,20 +73,29 @@ async function initPrivateGit(credentials: string) {
|
||||
* which works great for this feature. We also need to add a suffix during `vercel dev`
|
||||
* since the entrypoint is already stripped of its suffix before build() is called.
|
||||
*/
|
||||
async function getRenamedEntrypoint(entrypoint: string, files: Files) {
|
||||
function getRenamedEntrypoint(entrypoint: string): string | undefined {
|
||||
const filename = basename(entrypoint);
|
||||
if (filename.startsWith('[')) {
|
||||
const newEntrypoint = entrypoint.replace('/[', '/now-bracket[');
|
||||
const file = files[entrypoint];
|
||||
delete files[entrypoint];
|
||||
files[newEntrypoint] = file;
|
||||
|
||||
debug(`Renamed entrypoint from ${entrypoint} to ${newEntrypoint}`);
|
||||
entrypoint = newEntrypoint;
|
||||
return newEntrypoint;
|
||||
}
|
||||
|
||||
return entrypoint;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
type UndoFileAction = {
|
||||
from: string;
|
||||
to: string | undefined;
|
||||
};
|
||||
|
||||
type UndoFunctionRename = {
|
||||
fsPath: string;
|
||||
from: string;
|
||||
to: string;
|
||||
};
|
||||
|
||||
export const version = 3;
|
||||
|
||||
export async function build({
|
||||
@@ -94,332 +105,517 @@ export async function build({
|
||||
workPath,
|
||||
meta = {},
|
||||
}: BuildOptions) {
|
||||
if (process.env.GIT_CREDENTIALS) {
|
||||
debug('Initialize Git credentials...');
|
||||
await initPrivateGit(process.env.GIT_CREDENTIALS);
|
||||
}
|
||||
|
||||
if (process.env.GO111MODULE) {
|
||||
console.log(`\nManually assigning 'GO111MODULE' is not recommended.
|
||||
|
||||
By default:
|
||||
- 'GO111MODULE=on' If entrypoint package name is not 'main'
|
||||
- 'GO111MODULE=off' If entrypoint package name is 'main'
|
||||
|
||||
We highly recommend you leverage Go Modules in your project.
|
||||
Learn more: https://github.com/golang/go/wiki/Modules
|
||||
`);
|
||||
}
|
||||
entrypoint = await getRenamedEntrypoint(entrypoint, files);
|
||||
const entrypointArr = entrypoint.split(sep);
|
||||
|
||||
// eslint-disable-next-line prefer-const
|
||||
let [goPath, outDir] = await Promise.all([
|
||||
getWriteableDirectory(),
|
||||
getWriteableDirectory(),
|
||||
]);
|
||||
|
||||
const goPath = await getWriteableDirectory();
|
||||
const srcPath = join(goPath, 'src', 'lambda');
|
||||
const downloadPath = meta.skipDownload ? workPath : srcPath;
|
||||
const downloadedFiles = await download(files, downloadPath, meta);
|
||||
await download(files, downloadPath, meta);
|
||||
|
||||
// keep track of file system actions we need to undo
|
||||
// the keys "from" and "to" refer to what needs to be done
|
||||
// in order to undo the action, not what the original action was
|
||||
const undoFileActions: UndoFileAction[] = [];
|
||||
const undoDirectoryCreation: string[] = [];
|
||||
const undoFunctionRenames: UndoFunctionRename[] = [];
|
||||
|
||||
debug(`Parsing AST for "${entrypoint}"`);
|
||||
let analyzed: string;
|
||||
try {
|
||||
let goModAbsPathDir = '';
|
||||
const fileName = 'go.mod';
|
||||
if (fileName in downloadedFiles) {
|
||||
goModAbsPathDir = dirname(downloadedFiles[fileName].fsPath);
|
||||
debug(`Found ${fileName} file in "${goModAbsPathDir}"`);
|
||||
} else if ('api/go.mod' in downloadedFiles) {
|
||||
goModAbsPathDir = dirname(downloadedFiles['api/go.mod'].fsPath);
|
||||
debug(`Found ${fileName} file in "${goModAbsPathDir}"`);
|
||||
if (process.env.GIT_CREDENTIALS) {
|
||||
debug('Initialize Git credentials...');
|
||||
await initPrivateGit(process.env.GIT_CREDENTIALS);
|
||||
}
|
||||
analyzed = await getAnalyzedEntrypoint(
|
||||
workPath,
|
||||
downloadedFiles[entrypoint].fsPath,
|
||||
goModAbsPathDir
|
||||
);
|
||||
} catch (err) {
|
||||
console.log(`Failed to parse AST for "${entrypoint}"`);
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (!analyzed) {
|
||||
const err = new Error(
|
||||
`Could not find an exported function in "${entrypoint}"
|
||||
Learn more: https://vercel.com/docs/runtimes#official-runtimes/go
|
||||
`
|
||||
);
|
||||
console.log(err.message);
|
||||
throw err;
|
||||
}
|
||||
if (process.env.GO111MODULE) {
|
||||
console.log(`\nManually assigning 'GO111MODULE' is not recommended.
|
||||
|
||||
const parsedAnalyzed = JSON.parse(analyzed) as Analyzed;
|
||||
By default:
|
||||
- 'GO111MODULE=on' If entrypoint package name is not 'main'
|
||||
- 'GO111MODULE=off' If entrypoint package name is 'main'
|
||||
|
||||
// find `go.mod` in downloadedFiles
|
||||
const entrypointDirname = dirname(downloadedFiles[entrypoint].fsPath);
|
||||
let isGoModExist = false;
|
||||
let goModPath = '';
|
||||
let isGoModInRootDir = false;
|
||||
for (const file of Object.keys(downloadedFiles)) {
|
||||
const { fsPath } = downloadedFiles[file];
|
||||
const fileDirname = dirname(fsPath);
|
||||
if (file === 'go.mod') {
|
||||
isGoModExist = true;
|
||||
isGoModInRootDir = true;
|
||||
goModPath = fileDirname;
|
||||
} else if (file.endsWith('go.mod')) {
|
||||
if (entrypointDirname === fileDirname) {
|
||||
isGoModExist = true;
|
||||
goModPath = fileDirname;
|
||||
debug(`Found file dirname equals entrypoint dirname: ${fileDirname}`);
|
||||
break;
|
||||
We highly recommend you leverage Go Modules in your project.
|
||||
Learn more: https://github.com/golang/go/wiki/Modules
|
||||
`);
|
||||
}
|
||||
|
||||
const originalEntrypointAbsolute = join(workPath, entrypoint);
|
||||
const renamedEntrypoint = getRenamedEntrypoint(entrypoint);
|
||||
if (renamedEntrypoint) {
|
||||
await move(join(workPath, entrypoint), join(workPath, renamedEntrypoint));
|
||||
undoFileActions.push({
|
||||
to: join(workPath, entrypoint),
|
||||
from: join(workPath, renamedEntrypoint),
|
||||
});
|
||||
entrypoint = renamedEntrypoint;
|
||||
}
|
||||
|
||||
const entrypointAbsolute = join(workPath, entrypoint);
|
||||
const entrypointArr = entrypoint.split(sep);
|
||||
|
||||
debug(`Parsing AST for "${entrypoint}"`);
|
||||
let analyzed: string;
|
||||
try {
|
||||
const goModAbsPath = await findGoModPath(workPath);
|
||||
if (goModAbsPath) {
|
||||
debug(`Found ${goModAbsPath}"`);
|
||||
}
|
||||
|
||||
if (!isGoModInRootDir && config.zeroConfig && file === 'api/go.mod') {
|
||||
// We didn't find `/go.mod` but we found `/api/go.mod` so move it to the root
|
||||
analyzed = await getAnalyzedEntrypoint(
|
||||
workPath,
|
||||
entrypointAbsolute,
|
||||
dirname(goModAbsPath)
|
||||
);
|
||||
} catch (err) {
|
||||
console.log(`Failed to parse AST for "${entrypoint}"`);
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (!analyzed) {
|
||||
const err = new Error(
|
||||
`Could not find an exported function in "${entrypoint}"
|
||||
Learn more: https://vercel.com/docs/runtimes#official-runtimes/go
|
||||
`
|
||||
);
|
||||
console.log(err.message);
|
||||
throw err;
|
||||
}
|
||||
|
||||
const parsedAnalyzed = JSON.parse(analyzed) as Analyzed;
|
||||
|
||||
// find `go.mod` in modFiles
|
||||
const entrypointDirname = dirname(entrypointAbsolute);
|
||||
let isGoModExist = false;
|
||||
let goModPath = '';
|
||||
let isGoModInRootDir = false;
|
||||
|
||||
const modFileRefs = await glob('**/*.mod', workPath);
|
||||
const modFiles = Object.keys(modFileRefs);
|
||||
|
||||
for (const file of modFiles) {
|
||||
const fileDirname = dirname(file);
|
||||
if (file === 'go.mod') {
|
||||
isGoModExist = true;
|
||||
isGoModInRootDir = true;
|
||||
goModPath = join(fileDirname, '..');
|
||||
const pathParts = fsPath.split(sep);
|
||||
pathParts.pop(); // Remove go.mod
|
||||
pathParts.pop(); // Remove api
|
||||
pathParts.push('go.mod');
|
||||
const newFsPath = pathParts.join(sep);
|
||||
debug(`Moving api/go.mod to root: ${fsPath} to ${newFsPath}`);
|
||||
await move(fsPath, newFsPath);
|
||||
const oldSumPath = join(dirname(fsPath), 'go.sum');
|
||||
const newSumPath = join(dirname(newFsPath), 'go.sum');
|
||||
if (await pathExists(oldSumPath)) {
|
||||
debug(`Moving api/go.sum to root: ${oldSumPath} to ${newSumPath}`);
|
||||
await move(oldSumPath, newSumPath);
|
||||
goModPath = join(workPath, fileDirname);
|
||||
} else if (file.endsWith('go.mod')) {
|
||||
if (entrypointDirname === fileDirname) {
|
||||
isGoModExist = true;
|
||||
goModPath = join(workPath, fileDirname);
|
||||
|
||||
debug(`Found file dirname equals entrypoint dirname: ${fileDirname}`);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!isGoModInRootDir && config.zeroConfig && file === 'api/go.mod') {
|
||||
// We didn't find `/go.mod` but we found `/api/go.mod` so move it to the root
|
||||
isGoModExist = true;
|
||||
isGoModInRootDir = true;
|
||||
goModPath = join(fileDirname, '..');
|
||||
const pathParts = file.split(sep);
|
||||
pathParts.pop(); // Remove go.mod
|
||||
pathParts.pop(); // Remove api
|
||||
pathParts.push('go.mod');
|
||||
|
||||
const newRoot = pathParts.join(sep);
|
||||
const newFsPath = join(workPath, newRoot);
|
||||
|
||||
debug(`Moving api/go.mod to root: ${file} to ${newFsPath}`);
|
||||
await move(file, newFsPath);
|
||||
undoFileActions.push({
|
||||
to: file,
|
||||
from: newFsPath,
|
||||
});
|
||||
|
||||
const oldSumPath = join(dirname(file), 'go.sum');
|
||||
const newSumPath = join(dirname(newFsPath), 'go.sum');
|
||||
if (await pathExists(oldSumPath)) {
|
||||
debug(`Moving api/go.sum to root: ${oldSumPath} to ${newSumPath}`);
|
||||
await move(oldSumPath, newSumPath);
|
||||
undoFileActions.push({
|
||||
to: oldSumPath,
|
||||
from: newSumPath,
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const input = entrypointDirname;
|
||||
const includedFiles: Files = {};
|
||||
const input = entrypointDirname;
|
||||
const includedFiles: Files = {};
|
||||
|
||||
if (config && config.includeFiles) {
|
||||
const patterns = Array.isArray(config.includeFiles)
|
||||
? config.includeFiles
|
||||
: [config.includeFiles];
|
||||
for (const pattern of patterns) {
|
||||
const fsFiles = await glob(pattern, input);
|
||||
for (const [assetName, asset] of Object.entries(fsFiles)) {
|
||||
includedFiles[assetName] = asset;
|
||||
if (config && config.includeFiles) {
|
||||
const patterns = Array.isArray(config.includeFiles)
|
||||
? config.includeFiles
|
||||
: [config.includeFiles];
|
||||
for (const pattern of patterns) {
|
||||
const fsFiles = await glob(pattern, input);
|
||||
for (const [assetName, asset] of Object.entries(fsFiles)) {
|
||||
includedFiles[assetName] = asset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handlerFunctionName = parsedAnalyzed.functionName;
|
||||
debug(`Found exported function "${handlerFunctionName}" in "${entrypoint}"`);
|
||||
|
||||
if (!isGoModExist && 'vendor' in downloadedFiles) {
|
||||
throw new Error('`go.mod` is required to use a `vendor` directory.');
|
||||
}
|
||||
|
||||
// check if package name other than main
|
||||
// using `go.mod` way building the handler
|
||||
const packageName = parsedAnalyzed.packageName;
|
||||
|
||||
if (isGoModExist && packageName === 'main') {
|
||||
throw new Error('Please change `package main` to `package handler`');
|
||||
}
|
||||
|
||||
if (packageName !== 'main') {
|
||||
const go = await createGo(
|
||||
workPath,
|
||||
goPath,
|
||||
process.platform,
|
||||
process.arch,
|
||||
{
|
||||
cwd: entrypointDirname,
|
||||
},
|
||||
true
|
||||
const originalFunctionName = parsedAnalyzed.functionName;
|
||||
const handlerFunctionName = getNewHandlerFunctionName(
|
||||
originalFunctionName,
|
||||
entrypoint
|
||||
);
|
||||
if (!isGoModExist) {
|
||||
try {
|
||||
const defaultGoModContent = `module ${packageName}`;
|
||||
await renameHandlerFunction(
|
||||
entrypointAbsolute,
|
||||
originalFunctionName,
|
||||
handlerFunctionName
|
||||
);
|
||||
undoFunctionRenames.push({
|
||||
fsPath: originalEntrypointAbsolute,
|
||||
from: handlerFunctionName,
|
||||
to: originalFunctionName,
|
||||
});
|
||||
|
||||
await writeFile(join(entrypointDirname, 'go.mod'), defaultGoModContent);
|
||||
if (!isGoModExist) {
|
||||
if (await pathExists(join(workPath, 'vendor'))) {
|
||||
throw new Error('`go.mod` is required to use a `vendor` directory.');
|
||||
}
|
||||
}
|
||||
|
||||
// check if package name other than main
|
||||
// using `go.mod` way building the handler
|
||||
const packageName = parsedAnalyzed.packageName;
|
||||
|
||||
if (isGoModExist && packageName === 'main') {
|
||||
throw new Error('Please change `package main` to `package handler`');
|
||||
}
|
||||
|
||||
const outDir = await getWriteableDirectory();
|
||||
|
||||
if (packageName !== 'main') {
|
||||
const go = await createGo(
|
||||
workPath,
|
||||
goPath,
|
||||
process.platform,
|
||||
process.arch,
|
||||
{
|
||||
cwd: entrypointDirname,
|
||||
},
|
||||
true
|
||||
);
|
||||
if (!isGoModExist) {
|
||||
try {
|
||||
const defaultGoModContent = `module ${packageName}`;
|
||||
|
||||
await writeFile(
|
||||
join(entrypointDirname, 'go.mod'),
|
||||
defaultGoModContent
|
||||
);
|
||||
|
||||
undoFileActions.push({
|
||||
to: undefined, // delete
|
||||
from: join(entrypointDirname, 'go.mod'),
|
||||
});
|
||||
|
||||
// remove the `go.sum` file that will be generated as well
|
||||
undoFileActions.push({
|
||||
to: undefined, // delete
|
||||
from: join(entrypointDirname, 'go.sum'),
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(`Failed to create default go.mod for ${packageName}`);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
const mainModGoFileName = 'main.go';
|
||||
const modMainGoContents = await readFile(
|
||||
join(__dirname, mainModGoFileName),
|
||||
'utf8'
|
||||
);
|
||||
|
||||
let goPackageName = `${packageName}/${packageName}`;
|
||||
const goFuncName = `${packageName}.${handlerFunctionName}`;
|
||||
|
||||
if (isGoModExist) {
|
||||
const goModContents = await readFile(join(goModPath, 'go.mod'), 'utf8');
|
||||
const usrModName = goModContents.split('\n')[0].split(' ')[1];
|
||||
if (entrypointArr.length > 1 && isGoModInRootDir) {
|
||||
const cleanPackagePath = [...entrypointArr];
|
||||
cleanPackagePath.pop();
|
||||
goPackageName = `${usrModName}/${cleanPackagePath.join('/')}`;
|
||||
} else {
|
||||
goPackageName = `${usrModName}/${packageName}`;
|
||||
}
|
||||
}
|
||||
|
||||
const mainModGoContents = modMainGoContents
|
||||
.replace('__VC_HANDLER_PACKAGE_NAME', goPackageName)
|
||||
.replace('__VC_HANDLER_FUNC_NAME', goFuncName);
|
||||
|
||||
if (isGoModExist && isGoModInRootDir) {
|
||||
debug('[mod-root] Write main file to ' + downloadPath);
|
||||
await writeFile(
|
||||
join(downloadPath, mainModGoFileName),
|
||||
mainModGoContents
|
||||
);
|
||||
undoFileActions.push({
|
||||
to: undefined, // delete
|
||||
from: join(downloadPath, mainModGoFileName),
|
||||
});
|
||||
} else if (isGoModExist && !isGoModInRootDir) {
|
||||
debug('[mod-other] Write main file to ' + goModPath);
|
||||
await writeFile(join(goModPath, mainModGoFileName), mainModGoContents);
|
||||
undoFileActions.push({
|
||||
to: undefined, // delete
|
||||
from: join(goModPath, mainModGoFileName),
|
||||
});
|
||||
} else {
|
||||
debug('[entrypoint] Write main file to ' + entrypointDirname);
|
||||
await writeFile(
|
||||
join(entrypointDirname, mainModGoFileName),
|
||||
mainModGoContents
|
||||
);
|
||||
undoFileActions.push({
|
||||
to: undefined, // delete
|
||||
from: join(entrypointDirname, mainModGoFileName),
|
||||
});
|
||||
}
|
||||
|
||||
// move user go file to folder
|
||||
try {
|
||||
// default path
|
||||
let finalDestination = join(entrypointDirname, packageName, entrypoint);
|
||||
|
||||
// if `entrypoint` include folder, only use filename
|
||||
if (entrypointArr.length > 1) {
|
||||
finalDestination = join(
|
||||
entrypointDirname,
|
||||
packageName,
|
||||
entrypointArr[entrypointArr.length - 1]
|
||||
);
|
||||
}
|
||||
|
||||
if (dirname(entrypointAbsolute) === goModPath || !isGoModExist) {
|
||||
debug(
|
||||
`moving entrypoint "${entrypointAbsolute}" to "${finalDestination}"`
|
||||
);
|
||||
|
||||
await move(entrypointAbsolute, finalDestination);
|
||||
undoFileActions.push({
|
||||
to: entrypointAbsolute,
|
||||
from: finalDestination,
|
||||
});
|
||||
undoDirectoryCreation.push(dirname(finalDestination));
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(`Failed to create default go.mod for ${packageName}`);
|
||||
console.log('Failed to move entry to package folder');
|
||||
throw err;
|
||||
}
|
||||
|
||||
let baseGoModPath = '';
|
||||
if (isGoModExist && isGoModInRootDir) {
|
||||
baseGoModPath = downloadPath;
|
||||
} else if (isGoModExist && !isGoModInRootDir) {
|
||||
baseGoModPath = goModPath;
|
||||
} else {
|
||||
baseGoModPath = entrypointDirname;
|
||||
}
|
||||
|
||||
debug('Tidy `go.mod` file...');
|
||||
try {
|
||||
// ensure go.mod up-to-date
|
||||
await go.mod();
|
||||
} catch (err) {
|
||||
console.log('failed to `go mod tidy`');
|
||||
throw err;
|
||||
}
|
||||
|
||||
debug('Running `go build`...');
|
||||
const destPath = join(outDir, handlerFileName);
|
||||
|
||||
try {
|
||||
const src = [join(baseGoModPath, mainModGoFileName)];
|
||||
|
||||
await go.build(src, destPath);
|
||||
} catch (err) {
|
||||
console.log('failed to `go build`');
|
||||
throw err;
|
||||
}
|
||||
} else {
|
||||
// legacy mode
|
||||
// we need `main.go` in the same dir as the entrypoint,
|
||||
// otherwise `go build` will refuse to build
|
||||
const go = await createGo(
|
||||
workPath,
|
||||
goPath,
|
||||
process.platform,
|
||||
process.arch,
|
||||
{
|
||||
cwd: entrypointDirname,
|
||||
},
|
||||
false
|
||||
);
|
||||
const originalMainGoContents = await readFile(
|
||||
join(__dirname, 'main.go'),
|
||||
'utf8'
|
||||
);
|
||||
const mainGoContents = originalMainGoContents
|
||||
.replace('"__VC_HANDLER_PACKAGE_NAME"', '')
|
||||
.replace('__VC_HANDLER_FUNC_NAME', handlerFunctionName);
|
||||
|
||||
// in order to allow the user to have `main.go`,
|
||||
// we need our `main.go` to be called something else
|
||||
const mainGoFileName = 'main__vc__go__.go';
|
||||
|
||||
// Go doesn't like to build files in different directories,
|
||||
// so now we place `main.go` together with the user code
|
||||
await writeFile(join(entrypointDirname, mainGoFileName), mainGoContents);
|
||||
undoFileActions.push({
|
||||
to: undefined, // delete
|
||||
from: join(entrypointDirname, mainGoFileName),
|
||||
});
|
||||
|
||||
// `go get` will look at `*.go` (note we set `cwd`), parse the `import`s
|
||||
// and download any packages that aren't part of the stdlib
|
||||
debug('Running `go get`...');
|
||||
try {
|
||||
await go.get();
|
||||
} catch (err) {
|
||||
console.log('Failed to `go get`');
|
||||
throw err;
|
||||
}
|
||||
|
||||
debug('Running `go build`...');
|
||||
const destPath = join(outDir, handlerFileName);
|
||||
try {
|
||||
const src = [
|
||||
join(entrypointDirname, mainGoFileName),
|
||||
entrypointAbsolute,
|
||||
].map(file => normalize(file));
|
||||
await go.build(src, destPath);
|
||||
} catch (err) {
|
||||
console.log('failed to `go build`');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
const mainModGoFileName = 'main__mod__.go';
|
||||
const modMainGoContents = await readFile(
|
||||
join(__dirname, mainModGoFileName),
|
||||
'utf8'
|
||||
);
|
||||
const lambda = await createLambda({
|
||||
files: { ...(await glob('**', outDir)), ...includedFiles },
|
||||
handler: handlerFileName,
|
||||
runtime: 'go1.x',
|
||||
supportsWrapper: true,
|
||||
environment: {},
|
||||
});
|
||||
|
||||
let goPackageName = `${packageName}/${packageName}`;
|
||||
const goFuncName = `${packageName}.${handlerFunctionName}`;
|
||||
|
||||
if (isGoModExist) {
|
||||
const goModContents = await readFile(join(goModPath, 'go.mod'), 'utf8');
|
||||
const usrModName = goModContents.split('\n')[0].split(' ')[1];
|
||||
if (entrypointArr.length > 1 && isGoModInRootDir) {
|
||||
const cleanPackagePath = [...entrypointArr];
|
||||
cleanPackagePath.pop();
|
||||
goPackageName = `${usrModName}/${cleanPackagePath.join('/')}`;
|
||||
} else {
|
||||
goPackageName = `${usrModName}/${packageName}`;
|
||||
}
|
||||
const watch = parsedAnalyzed.watch;
|
||||
let watchSub: string[] = [];
|
||||
// if `entrypoint` located in subdirectory
|
||||
// we will need to concat it with return watch array
|
||||
if (entrypointArr.length > 1) {
|
||||
entrypointArr.pop();
|
||||
watchSub = parsedAnalyzed.watch.map(file => join(...entrypointArr, file));
|
||||
}
|
||||
|
||||
const mainModGoContents = modMainGoContents
|
||||
.replace('__VC_HANDLER_PACKAGE_NAME', goPackageName)
|
||||
.replace('__VC_HANDLER_FUNC_NAME', goFuncName);
|
||||
return {
|
||||
output: lambda,
|
||||
watch: watch.concat(watchSub),
|
||||
};
|
||||
} catch (error) {
|
||||
debug('Go Builder Error: ' + error);
|
||||
|
||||
if (isGoModExist && isGoModInRootDir) {
|
||||
debug('[mod-root] Write main file to ' + downloadPath);
|
||||
await writeFile(join(downloadPath, mainModGoFileName), mainModGoContents);
|
||||
} else if (isGoModExist && !isGoModInRootDir) {
|
||||
debug('[mod-other] Write main file to ' + goModPath);
|
||||
await writeFile(join(goModPath, mainModGoFileName), mainModGoContents);
|
||||
} else {
|
||||
debug('[entrypoint] Write main file to ' + entrypointDirname);
|
||||
await writeFile(
|
||||
join(entrypointDirname, mainModGoFileName),
|
||||
mainModGoContents
|
||||
throw error;
|
||||
} finally {
|
||||
try {
|
||||
await cleanupFileSystem(
|
||||
undoFileActions,
|
||||
undoDirectoryCreation,
|
||||
undoFunctionRenames
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(`Build cleanup failed: ${error.message}`);
|
||||
debug('Cleanup Error: ' + error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// move user go file to folder
|
||||
try {
|
||||
// default path
|
||||
let finalDestination = join(entrypointDirname, packageName, entrypoint);
|
||||
async function renameHandlerFunction(fsPath: string, from: string, to: string) {
|
||||
let fileContents = await readFile(fsPath, 'utf8');
|
||||
|
||||
// if `entrypoint` include folder, only use filename
|
||||
if (entrypointArr.length > 1) {
|
||||
finalDestination = join(
|
||||
entrypointDirname,
|
||||
packageName,
|
||||
entrypointArr[entrypointArr.length - 1]
|
||||
);
|
||||
}
|
||||
const fromRegex = new RegExp(`\\b${from}\\b`, 'g');
|
||||
fileContents = fileContents.replace(fromRegex, to);
|
||||
|
||||
if (
|
||||
dirname(downloadedFiles[entrypoint].fsPath) === goModPath ||
|
||||
!isGoModExist
|
||||
) {
|
||||
await move(downloadedFiles[entrypoint].fsPath, finalDestination);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Failed to move entry to package folder');
|
||||
throw err;
|
||||
}
|
||||
await writeFile(fsPath, fileContents);
|
||||
}
|
||||
|
||||
let baseGoModPath = '';
|
||||
if (isGoModExist && isGoModInRootDir) {
|
||||
baseGoModPath = downloadPath;
|
||||
} else if (isGoModExist && !isGoModInRootDir) {
|
||||
baseGoModPath = goModPath;
|
||||
export function getNewHandlerFunctionName(
|
||||
originalFunctionName: string,
|
||||
entrypoint: string
|
||||
) {
|
||||
if (!originalFunctionName) {
|
||||
throw new Error(
|
||||
'Handler function renaming failed because original function name was empty.'
|
||||
);
|
||||
}
|
||||
|
||||
if (!entrypoint) {
|
||||
throw new Error(
|
||||
'Handler function renaming failed because entrypoint was empty.'
|
||||
);
|
||||
}
|
||||
|
||||
debug(`Found exported function "${originalFunctionName}" in "${entrypoint}"`);
|
||||
|
||||
const pathSlug = entrypoint.replace(/(\s|\\|\/|\]|\[|-|\.)/g, '_');
|
||||
|
||||
const newHandlerName = `${originalFunctionName}_${pathSlug}`;
|
||||
debug(
|
||||
`Renaming handler function temporarily from "${originalFunctionName}" to "${newHandlerName}"`
|
||||
);
|
||||
|
||||
return newHandlerName;
|
||||
}
|
||||
|
||||
async function cleanupFileSystem(
|
||||
undoFileActions: UndoFileAction[],
|
||||
undoDirectoryCreation: string[],
|
||||
undoFunctionRenames: UndoFunctionRename[]
|
||||
) {
|
||||
// we have to undo the actions in reverse order in cases
|
||||
// where one file was moved multiple times, which happens
|
||||
// using files that start with brackets
|
||||
for (const action of undoFileActions.reverse()) {
|
||||
if (action.to) {
|
||||
await move(action.from, action.to);
|
||||
} else {
|
||||
baseGoModPath = entrypointDirname;
|
||||
}
|
||||
|
||||
debug('Tidy `go.mod` file...');
|
||||
try {
|
||||
// ensure go.mod up-to-date
|
||||
await go.mod();
|
||||
} catch (err) {
|
||||
console.log('failed to `go mod tidy`');
|
||||
throw err;
|
||||
}
|
||||
|
||||
debug('Running `go build`...');
|
||||
const destPath = join(outDir, handlerFileName);
|
||||
|
||||
try {
|
||||
const src = [join(baseGoModPath, mainModGoFileName)];
|
||||
|
||||
await go.build(src, destPath);
|
||||
} catch (err) {
|
||||
console.log('failed to `go build`');
|
||||
throw err;
|
||||
}
|
||||
} else {
|
||||
// legacy mode
|
||||
// we need `main.go` in the same dir as the entrypoint,
|
||||
// otherwise `go build` will refuse to build
|
||||
const go = await createGo(
|
||||
workPath,
|
||||
goPath,
|
||||
process.platform,
|
||||
process.arch,
|
||||
{
|
||||
cwd: entrypointDirname,
|
||||
},
|
||||
false
|
||||
);
|
||||
const origianlMainGoContents = await readFile(
|
||||
join(__dirname, 'main.go'),
|
||||
'utf8'
|
||||
);
|
||||
const mainGoContents = origianlMainGoContents.replace(
|
||||
'__VC_HANDLER_FUNC_NAME',
|
||||
handlerFunctionName
|
||||
);
|
||||
|
||||
// in order to allow the user to have `main.go`,
|
||||
// we need our `main.go` to be called something else
|
||||
const mainGoFileName = 'main__vc__go__.go';
|
||||
|
||||
// Go doesn't like to build files in different directories,
|
||||
// so now we place `main.go` together with the user code
|
||||
await writeFile(join(entrypointDirname, mainGoFileName), mainGoContents);
|
||||
|
||||
// `go get` will look at `*.go` (note we set `cwd`), parse the `import`s
|
||||
// and download any packages that aren't part of the stdlib
|
||||
debug('Running `go get`...');
|
||||
try {
|
||||
await go.get();
|
||||
} catch (err) {
|
||||
console.log('Failed to `go get`');
|
||||
throw err;
|
||||
}
|
||||
|
||||
debug('Running `go build`...');
|
||||
const destPath = join(outDir, handlerFileName);
|
||||
try {
|
||||
const src = [
|
||||
join(entrypointDirname, mainGoFileName),
|
||||
downloadedFiles[entrypoint].fsPath,
|
||||
].map(file => normalize(file));
|
||||
await go.build(src, destPath);
|
||||
} catch (err) {
|
||||
console.log('failed to `go build`');
|
||||
throw err;
|
||||
await remove(action.from);
|
||||
}
|
||||
}
|
||||
|
||||
const lambda = await createLambda({
|
||||
files: { ...(await glob('**', outDir)), ...includedFiles },
|
||||
handler: handlerFileName,
|
||||
runtime: 'go1.x',
|
||||
environment: {},
|
||||
// after files are moved back, we can undo function renames
|
||||
// these reference the original file location
|
||||
for (const rename of undoFunctionRenames) {
|
||||
await renameHandlerFunction(rename.fsPath, rename.from, rename.to);
|
||||
}
|
||||
|
||||
const undoDirectoryPromises = undoDirectoryCreation.map(async directory => {
|
||||
const contents = await readdir(directory);
|
||||
// only delete an empty directory
|
||||
// if it has contents, either something went wrong during cleanup or this
|
||||
// directory contains project source code that should not be deleted
|
||||
if (!contents.length) {
|
||||
return rmdir(directory);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
await Promise.all(undoDirectoryPromises);
|
||||
}
|
||||
|
||||
const watch = parsedAnalyzed.watch;
|
||||
let watchSub: string[] = [];
|
||||
// if `entrypoint` located in subdirectory
|
||||
// we will need to concat it with return watch array
|
||||
if (entrypointArr.length > 1) {
|
||||
entrypointArr.pop();
|
||||
watchSub = parsedAnalyzed.watch.map(file => join(...entrypointArr, file));
|
||||
async function findGoModPath(workPath: string): Promise<string> {
|
||||
let checkPath = join(workPath, 'go.mod');
|
||||
if (await pathExists(checkPath)) {
|
||||
return checkPath;
|
||||
}
|
||||
|
||||
return {
|
||||
output: lambda,
|
||||
watch: watch.concat(watchSub),
|
||||
};
|
||||
checkPath = join(workPath, 'api/go.mod');
|
||||
if (await pathExists(checkPath)) {
|
||||
return checkPath;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function isPortInfo(v: any): v is PortInfo {
|
||||
|
||||
@@ -1,10 +1,31 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
vc "github.com/vercel/go-bridge/go/bridge"
|
||||
"net/http"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"__VC_HANDLER_PACKAGE_NAME"
|
||||
vc "github.com/vercel/go-bridge/go/bridge"
|
||||
)
|
||||
|
||||
func checkForLambdaWrapper() {
|
||||
wrapper := os.Getenv("AWS_LAMBDA_EXEC_WRAPPER")
|
||||
if wrapper == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// Removing the env var doesn't work
|
||||
// Set it to empty string to override the previous value
|
||||
os.Setenv("AWS_LAMBDA_EXEC_WRAPPER", "")
|
||||
argv := append([]string{wrapper}, os.Args...)
|
||||
err := syscall.Exec(wrapper, argv, os.Environ())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
checkForLambdaWrapper()
|
||||
vc.Start(http.HandlerFunc(__VC_HANDLER_FUNC_NAME))
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"__VC_HANDLER_PACKAGE_NAME"
|
||||
"net/http"
|
||||
|
||||
vc "github.com/vercel/go-bridge/go/bridge"
|
||||
)
|
||||
|
||||
func main() {
|
||||
vc.Start(http.HandlerFunc(__VC_HANDLER_FUNC_NAME))
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/go",
|
||||
"version": "2.0.12",
|
||||
"version": "2.1.0",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/go",
|
||||
@@ -11,21 +11,31 @@
|
||||
},
|
||||
"scripts": {
|
||||
"build": "node build",
|
||||
"test": "yarn jest --env node --verbose --runInBand --bail",
|
||||
"test-integration-once": "yarn test",
|
||||
"prepublishOnly": "node build"
|
||||
"test": "jest --env node --verbose --runInBand --bail",
|
||||
"test-integration-once": "yarn test"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"jest": {
|
||||
"preset": "ts-jest",
|
||||
"testEnvironment": "node",
|
||||
"globals": {
|
||||
"ts-jest": {
|
||||
"diagnostics": true,
|
||||
"isolatedModules": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tootallnate/once": "1.1.2",
|
||||
"@types/async-retry": "1.4.2",
|
||||
"@types/execa": "^0.9.0",
|
||||
"@types/fs-extra": "^5.0.5",
|
||||
"@types/jest": "28.1.6",
|
||||
"@types/node-fetch": "^2.3.0",
|
||||
"@types/tar": "^4.0.0",
|
||||
"@vercel/build-utils": "5.0.8",
|
||||
"@vercel/build-utils": "5.3.0",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"async-retry": "1.3.1",
|
||||
"execa": "^1.0.0",
|
||||
|
||||
@@ -6,5 +6,5 @@ import (
|
||||
)
|
||||
|
||||
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "hello:RANDOMNESS_PLACEHOLDER")
|
||||
fmt.Fprintf(w, "Req Path: %s", r.URL.Path)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
{
|
||||
"version": 2,
|
||||
"builds": [{ "src": "api/**/*.go", "use": "@vercel/go" }],
|
||||
"probes": [
|
||||
{
|
||||
"path": "/api/[hello].go",
|
||||
"mustContain": "hello:RANDOMNESS_PLACEHOLDER"
|
||||
"mustContain": "Req Path: /api/[hello].go"
|
||||
},
|
||||
{
|
||||
"path": "/api/whatever",
|
||||
"mustContain": "Req Path: /api/whatever"
|
||||
},
|
||||
{ "path": "/api/sub/[hi].go", "mustContain": "hi:RANDOMNESS_PLACEHOLDER" }
|
||||
]
|
||||
|
||||
12
packages/go/test/fixtures/21-func-conflicts/dupe-handler.go
vendored
Normal file
12
packages/go/test/fixtures/21-func-conflicts/dupe-handler.go
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// "Handler" conflicts with the other files' exported function,
|
||||
// but should still work
|
||||
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "from dupe-handler.go")
|
||||
}
|
||||
3
packages/go/test/fixtures/21-func-conflicts/go.mod
vendored
Normal file
3
packages/go/test/fixtures/21-func-conflicts/go.mod
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
module go-mod
|
||||
|
||||
go 1.15
|
||||
11
packages/go/test/fixtures/21-func-conflicts/index.go
vendored
Normal file
11
packages/go/test/fixtures/21-func-conflicts/index.go
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Handler func
|
||||
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "from index.go")
|
||||
}
|
||||
12
packages/go/test/fixtures/21-func-conflicts/other-package.go
vendored
Normal file
12
packages/go/test/fixtures/21-func-conflicts/other-package.go
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
package other
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// "Handler" conflicts with the other files' exported function,
|
||||
// but not in the same package
|
||||
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "from other-package.go")
|
||||
}
|
||||
11
packages/go/test/fixtures/21-func-conflicts/sub/one.go
vendored
Normal file
11
packages/go/test/fixtures/21-func-conflicts/sub/one.go
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// "Handler" conflicts with the other files' exported function
|
||||
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "from /sub/one.go")
|
||||
}
|
||||
11
packages/go/test/fixtures/21-func-conflicts/sub/two.go
vendored
Normal file
11
packages/go/test/fixtures/21-func-conflicts/sub/two.go
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// "Handler" conflicts with the other files' exported function
|
||||
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "from /sub/two.go")
|
||||
}
|
||||
11
packages/go/test/fixtures/21-func-conflicts/vercel.json
vendored
Normal file
11
packages/go/test/fixtures/21-func-conflicts/vercel.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": 2,
|
||||
"builds": [{ "src": "**/*.go", "use": "@vercel/go" }],
|
||||
"probes": [
|
||||
{ "path": "/", "mustContain": "from index.go" },
|
||||
{ "path": "/dupe-handler.go", "mustContain": "from dupe-handler.go" },
|
||||
{ "path": "/other-package.go", "mustContain": "from other-package.go" },
|
||||
{ "path": "/sub/one.go", "mustContain": "from /sub/one.go" },
|
||||
{ "path": "/sub/two.go", "mustContain": "from /sub/two.go" }
|
||||
]
|
||||
}
|
||||
1
packages/go/test/fixtures/22-large-environment/env/go.mod
vendored
Normal file
1
packages/go/test/fixtures/22-large-environment/env/go.mod
vendored
Normal file
@@ -0,0 +1 @@
|
||||
module env
|
||||
19
packages/go/test/fixtures/22-large-environment/env/index.go
vendored
Normal file
19
packages/go/test/fixtures/22-large-environment/env/index.go
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Handler function
|
||||
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||
rdm := os.Getenv("RANDOMNESS_ENV")
|
||||
if rdm == "" {
|
||||
fmt.Println("No env received")
|
||||
}
|
||||
|
||||
fmt.Fprintln(w, rdm)
|
||||
fmt.Fprintln(w, os.Getenv("LOREM"))
|
||||
fmt.Fprintln(w, os.Getenv("IPSUM"))
|
||||
}
|
||||
28
packages/go/test/fixtures/22-large-environment/now.json
vendored
Normal file
28
packages/go/test/fixtures/22-large-environment/now.json
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"version": 2,
|
||||
"builds": [
|
||||
{
|
||||
"src": "env/index.go",
|
||||
"use": "@vercel/go"
|
||||
}
|
||||
],
|
||||
"env": {
|
||||
"RANDOMNESS_ENV": "RANDOMNESS_PLACEHOLDER",
|
||||
"LOREM": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean turpis nisl, porta vel dictum id, placerat eu massa. Curabitur id diam at urna elementum condimentum a eget augue. Sed vehicula, mauris quis tincidunt iaculis, lacus quam dictum nulla, eu pellentesque justo lectus a erat. Integer volutpat magna tortor, non mollis tortor rhoncus quis. Donec id urna ligula. Praesent et ligula id ligula blandit rhoncus. Proin consequat, justo id maximus lacinia, tortor dui facilisis nunc, at aliquet odio orci nec tellus. Vestibulum sagittis nec sem id mollis. Donec eleifend risus eget lectus mattis convallis. Nam ac urna commodo, venenatis massa ut, varius magna. Aliquam erat volutpat. Ut ac lacinia erat. Mauris finibus vehicula elementum. Proin mauris neque, fringilla a erat fermentum, convallis elementum urna. Pellentesque bibendum nisl eget nisi sodales, a faucibus felis scelerisque. Fusce blandit imperdiet nunc, ac hendrerit ante placerat sed. Cras metus dolor, cursus non orci sed, iaculis tempor nunc. Quisque vitae enim pharetra, viverra massa non, mollis magna. Vivamus sit amet ultricies ligula, in vulputate sapien. Praesent ullamcorper justo in elit vulputate, et varius augue egestas. Donec quis rutrum mauris. Suspendisse placerat volutpat gravida. Nunc laoreet velit a accumsan faucibus. Nunc eu lorem sem. Sed id nunc a metus gravida accumsan. Morbi aliquet purus id ipsum dictum, nec finibus quam ullamcorper. Quisque sapien nulla, laoreet a accumsan non, luctus quis ante. Sed sit amet pellentesque magna. Aenean pulvinar porta est sed posuere. Aenean id nisl dictum, varius diam vel, facilisis ex. Praesent quis justo id mi eleifend eleifend. Aliquam imperdiet purus non ligula lobortis laoreet. Sed mollis aliquet dui et luctus. Donec ut lacus vel tellus porta feugiat. Nam lacinia euismod libero. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi facilisis quam nec nisl pretium, id blandit sapien pretium. Donec id sapien varius, ornare mi sed, pretium magna. Phasellus tortor ligula, porttitor sit amet magna in, semper condimentum elit.",
|
||||
"IPSUM": "Phasellus ac orci eleifend, dignissim turpis et, aliquet libero. Praesent aliquet justo augue, vel vulputate ex dictum ut. Donec eu interdum ex, sit amet hendrerit felis. Maecenas eget iaculis orci, eget porta eros. Pellentesque vitae neque in velit dapibus luctus. Pellentesque ornare et tellus eu congue. Aliquam eu sem vel neque varius faucibus. Ut eget tortor ornare, fermentum enim nec, pellentesque massa. Phasellus rhoncus aliquet nunc nec semper. Nullam sed iaculis tellus. Mauris a sollicitudin velit, id egestas odio. Suspendisse commodo commodo turpis, et sollicitudin sem commodo a. Vivamus condimentum, arcu ac tempus blandit, lectus ligula pulvinar est, in congue mi nunc et lacus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi sodales ipsum quis scelerisque vehicula. Quisque gravida nibh vitae mattis sollicitudin. Donec fringilla dapibus urna non gravida. Phasellus et eros id magna tristique consequat. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In lacus neque, auctor sed arcu at, varius volutpat est. Maecenas eget ante sed ipsum sagittis laoreet ut nec nisi. Quisque scelerisque, risus ut efficitur sollicitudin, neque est faucibus lacus, vitae eleifend nulla sem a magna. Integer viverra, diam eget venenatis pretium, augue ex pulvinar justo, ac ultrices neque nisl laoreet risus. Pellentesque commodo ultrices laoreet. Nulla nec ipsum non augue hendrerit vulputate sed eget diam. Maecenas semper rutrum ligula. Sed egestas, orci sed volutpat varius, eros mi lacinia magna, tincidunt aliquet nibh lacus eget dui. Integer vestibulum velit in interdum ultrices. Mauris porta vitae quam non placerat. In nisi risus, hendrerit rhoncus hendrerit at, lacinia vel mauris. Curabitur tempus mattis eros nec consequat. Sed posuere elit lobortis libero porta, sed pharetra tortor ornare. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse pulvinar ante vitae metus ullamcorper euismod. Nulla facilisi. Donec quam nulla, eleifend vel consequat sed, maximus et nisi. Donec molestie euismod semper. Fusce eget arcu feugiat, efficitur lectus sed, feugiat justo. Mauris ultricies pretium ante non faucibus. Aenean egestas ante nunc, id pellentesque metus blandit eu. Nullam faucibus fringilla lectus, quis dapibus turpis elementum eu. Nunc eget dolor in velit molestie interdum id eu justo. Aliquam ornare arcu quis tincidunt posuere. Mauris sed porttitor ligula. Vestibulum tincidunt non lacus id lacinia. Donec ex augue, convallis vel justo vel, faucibus ultricies tortor."
|
||||
},
|
||||
"probes": [
|
||||
{
|
||||
"path": "/env",
|
||||
"mustContain": "RANDOMNESS_PLACEHOLDER"
|
||||
},
|
||||
{
|
||||
"path": "/env",
|
||||
"mustContain": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean turpis nisl, porta vel dictum id, placerat eu massa. Curabitur id diam at urna elementum condimentum a eget augue. Sed vehicula, mauris quis tincidunt iaculis, lacus quam dictum nulla, eu pellentesque justo lectus a erat. Integer volutpat magna tortor, non mollis tortor rhoncus quis. Donec id urna ligula. Praesent et ligula id ligula blandit rhoncus. Proin consequat, justo id maximus lacinia, tortor dui facilisis nunc, at aliquet odio orci nec tellus. Vestibulum sagittis nec sem id mollis. Donec eleifend risus eget lectus mattis convallis. Nam ac urna commodo, venenatis massa ut, varius magna. Aliquam erat volutpat. Ut ac lacinia erat. Mauris finibus vehicula elementum. Proin mauris neque, fringilla a erat fermentum, convallis elementum urna. Pellentesque bibendum nisl eget nisi sodales, a faucibus felis scelerisque. Fusce blandit imperdiet nunc, ac hendrerit ante placerat sed. Cras metus dolor, cursus non orci sed, iaculis tempor nunc. Quisque vitae enim pharetra, viverra massa non, mollis magna. Vivamus sit amet ultricies ligula, in vulputate sapien. Praesent ullamcorper justo in elit vulputate, et varius augue egestas. Donec quis rutrum mauris. Suspendisse placerat volutpat gravida. Nunc laoreet velit a accumsan faucibus. Nunc eu lorem sem. Sed id nunc a metus gravida accumsan. Morbi aliquet purus id ipsum dictum, nec finibus quam ullamcorper. Quisque sapien nulla, laoreet a accumsan non, luctus quis ante. Sed sit amet pellentesque magna. Aenean pulvinar porta est sed posuere. Aenean id nisl dictum, varius diam vel, facilisis ex. Praesent quis justo id mi eleifend eleifend. Aliquam imperdiet purus non ligula lobortis laoreet. Sed mollis aliquet dui et luctus. Donec ut lacus vel tellus porta feugiat. Nam lacinia euismod libero. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi facilisis quam nec nisl pretium, id blandit sapien pretium. Donec id sapien varius, ornare mi sed, pretium magna. Phasellus tortor ligula, porttitor sit amet magna in, semper condimentum elit."
|
||||
},
|
||||
{
|
||||
"path": "/env",
|
||||
"mustContain": "Phasellus ac orci eleifend, dignissim turpis et, aliquet libero. Praesent aliquet justo augue, vel vulputate ex dictum ut. Donec eu interdum ex, sit amet hendrerit felis. Maecenas eget iaculis orci, eget porta eros. Pellentesque vitae neque in velit dapibus luctus. Pellentesque ornare et tellus eu congue. Aliquam eu sem vel neque varius faucibus. Ut eget tortor ornare, fermentum enim nec, pellentesque massa. Phasellus rhoncus aliquet nunc nec semper. Nullam sed iaculis tellus. Mauris a sollicitudin velit, id egestas odio. Suspendisse commodo commodo turpis, et sollicitudin sem commodo a. Vivamus condimentum, arcu ac tempus blandit, lectus ligula pulvinar est, in congue mi nunc et lacus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi sodales ipsum quis scelerisque vehicula. Quisque gravida nibh vitae mattis sollicitudin. Donec fringilla dapibus urna non gravida. Phasellus et eros id magna tristique consequat. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In lacus neque, auctor sed arcu at, varius volutpat est. Maecenas eget ante sed ipsum sagittis laoreet ut nec nisi. Quisque scelerisque, risus ut efficitur sollicitudin, neque est faucibus lacus, vitae eleifend nulla sem a magna. Integer viverra, diam eget venenatis pretium, augue ex pulvinar justo, ac ultrices neque nisl laoreet risus. Pellentesque commodo ultrices laoreet. Nulla nec ipsum non augue hendrerit vulputate sed eget diam. Maecenas semper rutrum ligula. Sed egestas, orci sed volutpat varius, eros mi lacinia magna, tincidunt aliquet nibh lacus eget dui. Integer vestibulum velit in interdum ultrices. Mauris porta vitae quam non placerat. In nisi risus, hendrerit rhoncus hendrerit at, lacinia vel mauris. Curabitur tempus mattis eros nec consequat. Sed posuere elit lobortis libero porta, sed pharetra tortor ornare. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse pulvinar ante vitae metus ullamcorper euismod. Nulla facilisi. Donec quam nulla, eleifend vel consequat sed, maximus et nisi. Donec molestie euismod semper. Fusce eget arcu feugiat, efficitur lectus sed, feugiat justo. Mauris ultricies pretium ante non faucibus. Aenean egestas ante nunc, id pellentesque metus blandit eu. Nullam faucibus fringilla lectus, quis dapibus turpis elementum eu. Nunc eget dolor in velit molestie interdum id eu justo. Aliquam ornare arcu quis tincidunt posuere. Mauris sed porttitor ligula. Vestibulum tincidunt non lacus id lacinia. Donec ex augue, convallis vel justo vel, faucibus ultricies tortor."
|
||||
}
|
||||
]
|
||||
}
|
||||
92
packages/go/test/index.test.ts
vendored
Normal file
92
packages/go/test/index.test.ts
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
import { getNewHandlerFunctionName } from '../index';
|
||||
|
||||
describe('getNewHandlerFunctionName', function () {
|
||||
it('does nothing with empty original function name', async () => {
|
||||
let error;
|
||||
try {
|
||||
getNewHandlerFunctionName('', 'some/kind-of-file.js');
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toEqual(
|
||||
'Handler function renaming failed because original function name was empty.'
|
||||
);
|
||||
});
|
||||
|
||||
it('does nothing with empty original function name', async () => {
|
||||
let error;
|
||||
try {
|
||||
getNewHandlerFunctionName('Handler', '');
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toEqual(
|
||||
'Handler function renaming failed because entrypoint was empty.'
|
||||
);
|
||||
});
|
||||
|
||||
it('generates slug with back slashes in file path', async () => {
|
||||
const newFunctionName = getNewHandlerFunctionName(
|
||||
'Handler',
|
||||
'some\\file.js'
|
||||
);
|
||||
expect(newFunctionName).toEqual('Handler_some_file_js');
|
||||
});
|
||||
|
||||
it('generates slug with forward slashes in file path', async () => {
|
||||
const newFunctionName = getNewHandlerFunctionName(
|
||||
'Handler',
|
||||
'some/file.js'
|
||||
);
|
||||
expect(newFunctionName).toEqual('Handler_some_file_js');
|
||||
});
|
||||
|
||||
it('generates slug with dashes in file path', async () => {
|
||||
const newFunctionName = getNewHandlerFunctionName(
|
||||
'Handler',
|
||||
'kind-of-file.js'
|
||||
);
|
||||
expect(newFunctionName).toEqual('Handler_kind_of_file_js');
|
||||
});
|
||||
|
||||
it('generates slug with dashes in file path', async () => {
|
||||
const newFunctionName = getNewHandlerFunctionName(
|
||||
'Handler',
|
||||
'kind-of-file.js'
|
||||
);
|
||||
expect(newFunctionName).toEqual('Handler_kind_of_file_js');
|
||||
});
|
||||
|
||||
it('generates slug with brackets in file path', async () => {
|
||||
const newFunctionName = getNewHandlerFunctionName(
|
||||
'Handler',
|
||||
'[segment].js'
|
||||
);
|
||||
// this expects two underscores on each side intentionally
|
||||
// left (1): there's an added separator between original function name and slug;
|
||||
// left (2): the opening bracket is replaced
|
||||
// right (1): the closing bracket is replaced
|
||||
// right (2): the period is replaced
|
||||
expect(newFunctionName).toEqual('Handler__segment__js');
|
||||
});
|
||||
|
||||
it('generates slug with space in file path', async () => {
|
||||
const newFunctionName = getNewHandlerFunctionName(
|
||||
'Handler',
|
||||
'kind of file.js'
|
||||
);
|
||||
expect(newFunctionName).toEqual('Handler_kind_of_file_js');
|
||||
});
|
||||
|
||||
it('generates slug with periods in file path', async () => {
|
||||
const newFunctionName = getNewHandlerFunctionName(
|
||||
'Handler',
|
||||
'kind.of.file.js'
|
||||
);
|
||||
expect(newFunctionName).toEqual('Handler_kind_of_file_js');
|
||||
});
|
||||
});
|
||||
@@ -11,7 +11,7 @@
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitThis": false,
|
||||
"types": ["node"],
|
||||
"types": ["node", "jest"],
|
||||
"strict": true,
|
||||
"target": "ES2020"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/hydrogen",
|
||||
"version": "0.0.9",
|
||||
"version": "0.0.13",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"homepage": "https://vercel.com/docs",
|
||||
@@ -12,8 +12,7 @@
|
||||
"scripts": {
|
||||
"build": "node build.js",
|
||||
"test-integration-once": "yarn test test/test.js",
|
||||
"test": "jest --env node --verbose --bail --runInBand",
|
||||
"prepublishOnly": "node build.js"
|
||||
"test": "jest --env node --verbose --bail --runInBand"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
@@ -22,7 +21,7 @@
|
||||
"devDependencies": {
|
||||
"@types/jest": "27.5.1",
|
||||
"@types/node": "*",
|
||||
"@vercel/build-utils": "5.0.8",
|
||||
"@vercel/build-utils": "5.3.0",
|
||||
"typescript": "4.6.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/next",
|
||||
"version": "3.1.12",
|
||||
"version": "3.1.17",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/next-js",
|
||||
@@ -11,8 +11,7 @@
|
||||
"test-unit": "yarn test test/build.test.ts test/unit/",
|
||||
"test-next-local": "jest --env node --verbose --bail --forceExit --testTimeout=360000 test/integration/*.test.js test/integration/*.test.ts",
|
||||
"test-next-local:middleware": "jest --env node --verbose --bail --useStderr --testTimeout=360000 test/integration/middleware.test.ts",
|
||||
"test-integration-once": "rm test/builder-info.json; jest --env node --verbose --runInBand --bail test/fixtures/**/*.test.js",
|
||||
"prepublishOnly": "yarn build"
|
||||
"test-integration-once": "rm test/builder-info.json; jest --env node --verbose --runInBand --bail test/fixtures/**/*.test.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -45,9 +44,9 @@
|
||||
"@types/semver": "6.0.0",
|
||||
"@types/text-table": "0.2.1",
|
||||
"@types/webpack-sources": "3.2.0",
|
||||
"@vercel/build-utils": "5.0.8",
|
||||
"@vercel/build-utils": "5.3.0",
|
||||
"@vercel/nft": "0.21.0",
|
||||
"@vercel/routing-utils": "2.0.0",
|
||||
"@vercel/routing-utils": "2.0.2",
|
||||
"async-sema": "3.0.1",
|
||||
"buffer-crc32": "0.2.13",
|
||||
"cheerio": "1.0.0-rc.10",
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
download,
|
||||
getLambdaOptionsFromFunction,
|
||||
getNodeVersion,
|
||||
getPrefixedEnvVars,
|
||||
getSpawnOptions,
|
||||
getScriptName,
|
||||
glob,
|
||||
@@ -224,14 +225,14 @@ export const build: BuildV2 = async ({
|
||||
)
|
||||
);
|
||||
|
||||
Object.keys(process.env)
|
||||
.filter(key => key.startsWith('VERCEL_'))
|
||||
.forEach(key => {
|
||||
const newKey = `NEXT_PUBLIC_${key}`;
|
||||
if (!(newKey in process.env)) {
|
||||
process.env[newKey] = process.env[key];
|
||||
}
|
||||
});
|
||||
const prefixedEnvs = getPrefixedEnvVars({
|
||||
envPrefix: 'NEXT_PUBLIC_',
|
||||
envs: process.env,
|
||||
});
|
||||
|
||||
for (const [key, value] of Object.entries(prefixedEnvs)) {
|
||||
process.env[key] = value;
|
||||
}
|
||||
|
||||
await download(files, workPath, meta);
|
||||
|
||||
|
||||
@@ -151,6 +151,7 @@ export async function serverBuild({
|
||||
nextVersion,
|
||||
CORRECTED_MANIFESTS_VERSION
|
||||
);
|
||||
|
||||
let hasStatic500 = !!staticPages[path.join(entryDirectory, '500')];
|
||||
|
||||
if (lambdaPageKeys.length === 0) {
|
||||
@@ -1209,10 +1210,6 @@ export async function serverBuild({
|
||||
]
|
||||
: []),
|
||||
|
||||
// ensure prerender's for notFound: true static routes
|
||||
// have 404 status code when not in preview mode
|
||||
...notFoundPreviewRoutes,
|
||||
|
||||
...headers,
|
||||
|
||||
...redirects,
|
||||
@@ -1223,6 +1220,10 @@ export async function serverBuild({
|
||||
|
||||
...beforeFilesRewrites,
|
||||
|
||||
// ensure prerender's for notFound: true static routes
|
||||
// have 404 status code when not in preview mode
|
||||
...notFoundPreviewRoutes,
|
||||
|
||||
// Make sure to 404 for the /404 path itself
|
||||
...(i18n
|
||||
? [
|
||||
@@ -1328,7 +1329,8 @@ export async function serverBuild({
|
||||
dest: '$0',
|
||||
},
|
||||
|
||||
// remove locale prefixes to check public files
|
||||
// remove locale prefixes to check public files and
|
||||
// to allow checking non-prefixed lambda outputs
|
||||
...(i18n
|
||||
? [
|
||||
{
|
||||
@@ -1341,20 +1343,6 @@ export async function serverBuild({
|
||||
]
|
||||
: []),
|
||||
|
||||
// for non-shared lambdas remove locale prefix if present
|
||||
// to allow checking for lambda
|
||||
...(!i18n
|
||||
? []
|
||||
: [
|
||||
{
|
||||
src: `${path.join('/', entryDirectory, '/')}(?:${i18n?.locales
|
||||
.map(locale => escapeStringRegexp(locale))
|
||||
.join('|')})/(.*)`,
|
||||
dest: '/$1',
|
||||
check: true,
|
||||
},
|
||||
]),
|
||||
|
||||
// routes that are called after each rewrite or after routes
|
||||
// if there no rewrites
|
||||
{ handle: 'rewrite' },
|
||||
@@ -1362,8 +1350,54 @@ export async function serverBuild({
|
||||
// re-build /_next/data URL after resolving
|
||||
...denormalizeNextDataRoute(),
|
||||
|
||||
...(isNextDataServerResolving
|
||||
? dataRoutes.filter(route => {
|
||||
// filter to only static data routes as dynamic routes will be handled
|
||||
// below
|
||||
const { pathname } = new URL(route.dest || '/', 'http://n');
|
||||
return !isDynamicRoute(pathname.replace(/\.json$/, ''));
|
||||
})
|
||||
: []),
|
||||
|
||||
// /_next/data routes for getServerProps/getStaticProps pages
|
||||
...dataRoutes,
|
||||
...(isNextDataServerResolving
|
||||
? // when resolving data routes for middleware we need to include
|
||||
// all dynamic routes including non-SSG/SSP so that the priority
|
||||
// is correct
|
||||
dynamicRoutes
|
||||
.map(route => {
|
||||
route = Object.assign({}, route);
|
||||
route.src = path.posix.join(
|
||||
'^/',
|
||||
entryDirectory,
|
||||
'_next/data/',
|
||||
escapedBuildId,
|
||||
route.src.replace(/(^\^|\$$)/g, '') + '.json$'
|
||||
);
|
||||
|
||||
const { pathname } = new URL(route.dest || '/', 'http://n');
|
||||
let isPrerender = !!prerenders[path.join('./', pathname)];
|
||||
|
||||
if (routesManifest.i18n) {
|
||||
for (const locale of routesManifest.i18n?.locales || []) {
|
||||
const prerenderPathname = pathname.replace(
|
||||
/^\/\$nextLocale/,
|
||||
`/${locale}`
|
||||
);
|
||||
if (prerenders[path.join('./', prerenderPathname)]) {
|
||||
isPrerender = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isPrerender) {
|
||||
route.dest = `/_next/data/${buildId}${pathname}.json`;
|
||||
}
|
||||
return route;
|
||||
})
|
||||
.filter(Boolean)
|
||||
: dataRoutes),
|
||||
|
||||
...(!isNextDataServerResolving
|
||||
? [
|
||||
@@ -1395,6 +1429,7 @@ export async function serverBuild({
|
||||
'x-nextjs-matched-path': '/$1',
|
||||
},
|
||||
continue: true,
|
||||
override: true,
|
||||
},
|
||||
// add a catch-all data route so we don't 404 when getting
|
||||
// middleware effects
|
||||
|
||||
@@ -1832,7 +1832,10 @@ export const onPrerenderRoute =
|
||||
if (nonDynamicSsg || isFallback || isOmitted) {
|
||||
outputPathData = outputPathData.replace(
|
||||
new RegExp(`${escapeStringRegexp(origRouteFileNoExt)}.json$`),
|
||||
`${routeFileNoExt}.json`
|
||||
// ensure we escape "$" correctly while replacing as "$" is a special
|
||||
// character, we need to do double escaping as first is for the initial
|
||||
// replace on the routeFile and then the second on the outputPath
|
||||
`${routeFileNoExt.replace(/\$/g, '$$$$')}.json`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
19
packages/next/test/fixtures/00-i18n-support/pages/$$.js
vendored
Normal file
19
packages/next/test/fixtures/00-i18n-support/pages/$$.js
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
function Page({ date }) {
|
||||
return (
|
||||
<>
|
||||
<h1>$$</h1>
|
||||
<p>Date: {date}</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getStaticProps() {
|
||||
return {
|
||||
props: {
|
||||
date: new Date().toISOString(),
|
||||
page: '$$',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default Page;
|
||||
20
packages/next/test/fixtures/00-i18n-support/pages/$$b.js
vendored
Normal file
20
packages/next/test/fixtures/00-i18n-support/pages/$$b.js
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
function Page({ date }) {
|
||||
return (
|
||||
<>
|
||||
<h1>$$b</h1>
|
||||
<p>Date: {date}</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getStaticProps() {
|
||||
return {
|
||||
props: {
|
||||
date: new Date().toISOString(),
|
||||
page: '$$b',
|
||||
},
|
||||
revalidate: 5,
|
||||
};
|
||||
}
|
||||
|
||||
export default Page;
|
||||
20
packages/next/test/fixtures/00-i18n-support/pages/b$$.js
vendored
Normal file
20
packages/next/test/fixtures/00-i18n-support/pages/b$$.js
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
function Page({ date }) {
|
||||
return (
|
||||
<>
|
||||
<h1>b$$</h1>
|
||||
<p>Date: {date}</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getStaticProps() {
|
||||
return {
|
||||
props: {
|
||||
date: new Date().toISOString(),
|
||||
page: 'b$$',
|
||||
},
|
||||
revalidate: 5,
|
||||
};
|
||||
}
|
||||
|
||||
export default Page;
|
||||
@@ -7,6 +7,36 @@
|
||||
}
|
||||
],
|
||||
"probes": [
|
||||
{
|
||||
"path": "/$$",
|
||||
"status": 200,
|
||||
"mustContain": ">$$<"
|
||||
},
|
||||
{
|
||||
"path": "/_next/data/testing-build-id/en/$$.json",
|
||||
"status": 200,
|
||||
"mustContain": "\"$$\""
|
||||
},
|
||||
{
|
||||
"path": "/$$b",
|
||||
"status": 200,
|
||||
"mustContain": ">$$b<"
|
||||
},
|
||||
{
|
||||
"path": "/_next/data/testing-build-id/en/$$b.json",
|
||||
"status": 200,
|
||||
"mustContain": "\"$$b\""
|
||||
},
|
||||
{
|
||||
"path": "/b$$",
|
||||
"status": 200,
|
||||
"mustContain": ">b$$<"
|
||||
},
|
||||
{
|
||||
"path": "/_next/data/testing-build-id/en/b$$.json",
|
||||
"status": 200,
|
||||
"mustContain": "\"b$$\""
|
||||
},
|
||||
{
|
||||
"path": "/",
|
||||
"headers": {
|
||||
|
||||
8
packages/next/test/fixtures/00-middleware-i18n/index.test.js
vendored
Normal file
8
packages/next/test/fixtures/00-middleware-i18n/index.test.js
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
const path = require('path');
|
||||
const { deployAndTest } = require('../../utils');
|
||||
|
||||
describe(`${__dirname.split(path.sep).pop()}`, () => {
|
||||
it('should deploy and pass probe checks', async () => {
|
||||
await deployAndTest(__dirname);
|
||||
});
|
||||
});
|
||||
248
packages/next/test/fixtures/00-middleware-i18n/middleware.js
vendored
Normal file
248
packages/next/test/fixtures/00-middleware-i18n/middleware.js
vendored
Normal file
@@ -0,0 +1,248 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
const ALLOWED = ['allowed'];
|
||||
|
||||
export const config = {
|
||||
matcher: [
|
||||
'/dynamic/:path*',
|
||||
'/_sites/:path*',
|
||||
'/:teamId/:slug',
|
||||
'/:path*',
|
||||
'/',
|
||||
],
|
||||
};
|
||||
|
||||
export function middleware(request) {
|
||||
const url = request.nextUrl;
|
||||
const pathname = url.pathname;
|
||||
|
||||
if (process.env.FOO) {
|
||||
console.log(`Includes env variable ${process.env.FOO}`);
|
||||
}
|
||||
|
||||
if (url.pathname === '/redirect-me') {
|
||||
url.pathname = '/from-middleware';
|
||||
return NextResponse.redirect(url, 307);
|
||||
}
|
||||
|
||||
if (url.pathname === '/next') {
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
if (url.pathname === '/version') {
|
||||
return NextResponse.json({
|
||||
enumerable: Object.keys(self).includes('VercelRuntime'),
|
||||
version: self.VercelRuntime.version,
|
||||
});
|
||||
}
|
||||
|
||||
if (url.pathname === '/globals') {
|
||||
const globalThisKeys = Object.keys(globalThis);
|
||||
const globalKeys = globalThisKeys.reduce((acc, globalName) => {
|
||||
const key = globalName.toString();
|
||||
if (global[key]) acc.push(key);
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
const res = NextResponse.next();
|
||||
res.headers.set(
|
||||
'data',
|
||||
JSON.stringify({ globals: globalKeys, globalThis: globalThisKeys })
|
||||
);
|
||||
return res;
|
||||
}
|
||||
|
||||
if (url.pathname === '/log') {
|
||||
console.log('hi there');
|
||||
return;
|
||||
}
|
||||
|
||||
if (url.pathname === '/somewhere') {
|
||||
url.pathname = '/from-middleware';
|
||||
return NextResponse.redirect(url);
|
||||
}
|
||||
|
||||
if (url.pathname === '/logs') {
|
||||
console.clear();
|
||||
for (let i = 0; i < 3; i++) console.count();
|
||||
console.count('test');
|
||||
console.count('test');
|
||||
console.dir({ hello: 'world' });
|
||||
console.log('hello');
|
||||
console.log('world');
|
||||
return;
|
||||
}
|
||||
|
||||
if (url.pathname === '/greetings') {
|
||||
const data = { message: 'hello world!' };
|
||||
const res = NextResponse.next();
|
||||
res.headers.set('x-example', 'edge');
|
||||
res.headers.set('data', JSON.stringify(data));
|
||||
return res;
|
||||
}
|
||||
|
||||
if (url.pathname === '/rewrite-me-to-about') {
|
||||
url.pathname = '/about';
|
||||
url.searchParams.set('middleware', 'foo');
|
||||
return NextResponse.rewrite(url);
|
||||
}
|
||||
|
||||
if (url.pathname === '/rewrite-to-site') {
|
||||
const customUrl = new URL(url);
|
||||
customUrl.pathname = '/_sites/subdomain-1/';
|
||||
console.log('rewriting to', customUrl.pathname, customUrl.href);
|
||||
return NextResponse.rewrite(customUrl);
|
||||
}
|
||||
|
||||
if (url.pathname === '/redirect-me-to-about') {
|
||||
url.pathname = '/about';
|
||||
url.searchParams.set('middleware', 'foo');
|
||||
return Response.redirect(url);
|
||||
}
|
||||
|
||||
if (url.pathname === '/rewrite-absolute') {
|
||||
return NextResponse.rewrite('https://example.vercel.sh/foo?foo=bar');
|
||||
}
|
||||
|
||||
if (url.pathname === '/rewrite-relative') {
|
||||
url.pathname = '/foo';
|
||||
url.searchParams.set('foo', 'bar');
|
||||
return NextResponse.rewrite(url);
|
||||
}
|
||||
|
||||
if (url.pathname === '/redirect-absolute') {
|
||||
return Response.redirect('https://vercel.com');
|
||||
}
|
||||
|
||||
if (url.pathname === '/redirect-301') {
|
||||
url.pathname = '/greetings';
|
||||
return NextResponse.redirect(url, 301);
|
||||
}
|
||||
|
||||
if (url.pathname === '/reflect') {
|
||||
const res = NextResponse.next();
|
||||
res.headers.set(
|
||||
'data',
|
||||
JSON.stringify({
|
||||
geo: request.geo,
|
||||
headers: Object.fromEntries(request.headers),
|
||||
ip: request.ip,
|
||||
method: request.method,
|
||||
nextUrl: {
|
||||
hash: request.nextUrl.hash,
|
||||
hostname: request.nextUrl.hostname,
|
||||
pathname: request.nextUrl.pathname,
|
||||
port: request.nextUrl.port,
|
||||
protocol: request.nextUrl.protocol,
|
||||
search: request.nextUrl.search,
|
||||
},
|
||||
url: request.url,
|
||||
})
|
||||
);
|
||||
return res;
|
||||
}
|
||||
|
||||
if (url.pathname === '/stream-response') {
|
||||
const { readable, writable } = new TransformStream();
|
||||
const waitUntil = (async () => {
|
||||
const enc = new TextEncoder();
|
||||
const writer = writable.getWriter();
|
||||
writer.write(enc.encode('this is a streamed '));
|
||||
writer.write(enc.encode('response '));
|
||||
return writer.close();
|
||||
})();
|
||||
|
||||
return {
|
||||
waitUntil,
|
||||
response: NextResponse.next(),
|
||||
};
|
||||
}
|
||||
|
||||
if (url.pathname === '/throw-error') {
|
||||
const error = new Error('oh no!');
|
||||
console.log('This is not worker.js');
|
||||
console.error(error);
|
||||
return new Promise((_, reject) => reject(error));
|
||||
}
|
||||
|
||||
if (url.pathname === '/throw-error-internal') {
|
||||
function myFunctionName() {
|
||||
throw new Error('Oh no!');
|
||||
}
|
||||
|
||||
function anotherFunction() {
|
||||
return myFunctionName();
|
||||
}
|
||||
|
||||
try {
|
||||
anotherFunction();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
return new Promise((_, reject) => reject(new Error('oh no!')));
|
||||
}
|
||||
|
||||
if (url.pathname === '/unhandledrejection') {
|
||||
Promise.reject(new TypeError('captured unhandledrejection error.'));
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
if (pathname.startsWith('/query-params')) {
|
||||
if (pathname.endsWith('/clear')) {
|
||||
const strategy =
|
||||
url.searchParams.get('strategy') === 'rewrite' ? 'rewrite' : 'redirect';
|
||||
|
||||
for (const key of [...url.searchParams.keys()]) {
|
||||
if (!ALLOWED.includes(key)) {
|
||||
url.searchParams.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
const newPath = url.pathname.replace(/\/clear$/, '');
|
||||
url.pathname = newPath;
|
||||
|
||||
if (strategy === 'redirect') {
|
||||
return NextResponse.redirect(url);
|
||||
} else {
|
||||
return NextResponse.rewrite(url);
|
||||
}
|
||||
}
|
||||
|
||||
const obj = Object.fromEntries([...url.searchParams.entries()]);
|
||||
|
||||
const res = NextResponse.next();
|
||||
res.headers.set('data', JSON.stringify(obj));
|
||||
return res;
|
||||
}
|
||||
|
||||
if (pathname.startsWith('/home')) {
|
||||
if (!request.cookies.get('bucket')) {
|
||||
const bucket = Math.random() >= 0.5 ? 'a' : 'b';
|
||||
url.pathname = `/home/${bucket}`;
|
||||
const response = NextResponse.rewrite(url);
|
||||
response.cookies.set('bucket', bucket);
|
||||
return response;
|
||||
}
|
||||
|
||||
url.pathname = `/home/${request.cookies.get('bucket')}`;
|
||||
return NextResponse.rewrite(url);
|
||||
}
|
||||
|
||||
if (pathname.startsWith('/fetch-subrequest')) {
|
||||
const destinationUrl =
|
||||
url.searchParams.get('url') || 'https://example.vercel.sh';
|
||||
return fetch(destinationUrl, { headers: request.headers });
|
||||
}
|
||||
|
||||
if (url.pathname === '/dynamic/greet') {
|
||||
const res = NextResponse.next();
|
||||
res.headers.set(
|
||||
'data',
|
||||
JSON.stringify({
|
||||
message: url.searchParams.get('greeting') || 'Hi friend',
|
||||
})
|
||||
);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
42
packages/next/test/fixtures/00-middleware-i18n/next.config.js
vendored
Normal file
42
packages/next/test/fixtures/00-middleware-i18n/next.config.js
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
module.exports = {
|
||||
generateBuildId() {
|
||||
return 'testing-build-id';
|
||||
},
|
||||
i18n: {
|
||||
locales: ['en', 'fr'],
|
||||
defaultLocale: 'en',
|
||||
},
|
||||
redirects() {
|
||||
return [
|
||||
{
|
||||
source: '/redirect-me',
|
||||
destination: '/from-next-config',
|
||||
permanent: false,
|
||||
},
|
||||
];
|
||||
},
|
||||
rewrites() {
|
||||
return {
|
||||
beforeFiles: [
|
||||
{
|
||||
source: '/rewrite-before-files',
|
||||
destination: '/somewhere',
|
||||
},
|
||||
],
|
||||
afterFiles: [
|
||||
{
|
||||
source: '/after-file-rewrite',
|
||||
destination: '/about',
|
||||
},
|
||||
{
|
||||
source: '/after-file-rewrite-auto-static',
|
||||
destination: '/home/a',
|
||||
},
|
||||
{
|
||||
source: '/after-file-rewrite-auto-static-dynamic',
|
||||
destination: '/dynamic/first',
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
11
packages/next/test/fixtures/00-middleware-i18n/package.json
vendored
Normal file
11
packages/next/test/fixtures/00-middleware-i18n/package.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "canary",
|
||||
"react": "latest",
|
||||
"react-dom": "latest"
|
||||
}
|
||||
}
|
||||
3
packages/next/test/fixtures/00-middleware-i18n/pages/[teamId]/[slug].js
vendored
Normal file
3
packages/next/test/fixtures/00-middleware-i18n/pages/[teamId]/[slug].js
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function Page() {
|
||||
return <p>/[teamId]/[slug]</p>;
|
||||
}
|
||||
31
packages/next/test/fixtures/00-middleware-i18n/pages/_sites/[site].js
vendored
Normal file
31
packages/next/test/fixtures/00-middleware-i18n/pages/_sites/[site].js
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
export default function Page(props) {
|
||||
return (
|
||||
<>
|
||||
<p>/_sites/[site]</p>
|
||||
<p>{JSON.stringify(props)}</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function getStaticProps({ params }) {
|
||||
return {
|
||||
props: {
|
||||
params,
|
||||
now: Date.now(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function getStaticPaths() {
|
||||
return {
|
||||
paths: [
|
||||
{
|
||||
params: { site: 'subdomain-1' },
|
||||
},
|
||||
{
|
||||
params: { site: 'subdomain-2' },
|
||||
},
|
||||
],
|
||||
fallback: 'blocking',
|
||||
};
|
||||
}
|
||||
17
packages/next/test/fixtures/00-middleware-i18n/pages/about.js
vendored
Normal file
17
packages/next/test/fixtures/00-middleware-i18n/pages/about.js
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
export default function Main({ message, middleware }) {
|
||||
return (
|
||||
<div>
|
||||
<h1 className="title">About Page</h1>
|
||||
<p className={message}>{message}</p>
|
||||
<p className="middleware">{middleware}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const getServerSideProps = ({ query }) => ({
|
||||
props: {
|
||||
middleware: query.middleware || '',
|
||||
message: query.message || '',
|
||||
page: 'about',
|
||||
},
|
||||
});
|
||||
3
packages/next/test/fixtures/00-middleware-i18n/pages/dynamic/[id]/index.js
vendored
Normal file
3
packages/next/test/fixtures/00-middleware-i18n/pages/dynamic/[id]/index.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function Index() {
|
||||
return <p className="title">Dynamic route</p>;
|
||||
}
|
||||
3
packages/next/test/fixtures/00-middleware-i18n/pages/dynamic/static.js
vendored
Normal file
3
packages/next/test/fixtures/00-middleware-i18n/pages/dynamic/static.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function Index() {
|
||||
return <p className="title">static route</p>;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user