Compare commits

..

38 Commits

Author SHA1 Message Date
Sean Massa
31f79c7de1 Publish Stable
- vercel@27.4.0
 - @vercel/go@2.1.0
 - @vercel/next@3.1.17
2022-08-09 16:11:57 -05:00
Craig Andrews
4c230c8436 [go] add lambda wrapper support to the go runtime (#8350)
Co-authored-by: Sean Massa <EndangeredMassa@gmail.com>
2022-08-09 16:08:20 -05:00
Lee Robinson
7941f5a104 [examples] Update Astro template for 1.0. (#8357)
To prepare for [Astro 1.0](https://github.com/withastro/astro/releases/tag/astro%401.0.0-rc.1), this PR updates the template (it has [been deployed](https://astro-template.vercel.app/)).
2022-08-09 18:09:38 +00:00
JJ Kasper
5b931afbf3 [next] Ensure $$ in ISR page is handled correctly (#8359)
* Ensure $$ in ISR page is handled correctly

* update test

* Remove duplicate tests

* add comment

* tweak
2022-08-09 11:13:15 -05:00
Chris Barber
15080364b8 [cli] Standardize on --yes instead of --confirm (#8330)
Allow for `--yes` flag consistently throughout CLI commands.

Also adds a deprecation warning for usage of `--confirm`,
since `--yes` is now preferred.
2022-08-08 18:05:22 -07:00
Steven
47e3381c6d Publish Stable
- @vercel/build-utils@5.3.0
 - vercel@27.3.8
 - @vercel/client@12.1.11
 - @vercel/go@2.0.16
 - @vercel/hydrogen@0.0.13
 - @vercel/next@3.1.16
 - @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
2022-08-08 19:11:12 -04:00
Sean Massa
33aefdc029 [go] fix function name conflict (#8299)
Co-authored-by: Steven <steven@ceriously.com>
2022-08-08 17:07:02 -05:00
Steven
30fe76a0cf [build-utils] Push back nodejs12.x sunset date (#8355)
This PR pushes back the `nodejs12.x` discontinue date to give more projects a chance to upgrade
2022-08-08 20:46:04 +00:00
Thomas Knickman
97ef88dc28 chore(docs): use relative links for README (#8354)
This allows README links to work locally (within your IDE) without kicking to a browser - and maintains the current functionality elsewhere.
2022-08-08 20:04:09 +00:00
Nathan Rajlich
f679098d7a [cli] Fix "retrieve" typo (#8352) 2022-08-08 19:22:00 +00:00
Steven
2b57e12ad3 [build-utils] Refactor framework prefixed env vars into shared function (#8166)
This consolidates the logic to get the framework-specific prefixed System Environment Variables into a single shared function so each builder can reuse the same function.

- Related to #7009 
- Related to #8306

In the future, this feature could be added to any other missing builders as well as `vc dev` but we'll save that for a new PR.
2022-08-05 00:51:52 +00:00
Nathan Rajlich
c4e94ad03f [cli] Always set BROWSER=none env var for frontend dev server in vc dev (#8326)
Previously `vc dev` would only set this env var for "create-react-app" Framework preset, but other frameworks do the "auto-open browser window" behavior as well, and respect this env var (Docusaurus, specifically).

So always set the `BROWSER=none` env var regardless of which framework preset is selected. Also simplifies the code since this was the only place `frameworkSlug` property was being used.
2022-08-04 21:49:14 +00:00
Steven
32afd67d29 Publish Stable
- @vercel/build-utils@5.2.0
 - vercel@27.3.7
 - @vercel/client@12.1.10
 - @vercel/edge@0.0.3
 - @vercel/frameworks@1.1.3
 - @vercel/fs-detectors@2.0.5
 - @vercel/go@2.0.15
 - @vercel/hydrogen@0.0.12
 - @vercel/next@3.1.15
 - @vercel/node@2.5.6
 - @vercel/python@3.1.7
 - @vercel/redwood@1.0.16
 - @vercel/remix@1.0.17
 - @vercel/routing-utils@2.0.2
 - @vercel/ruby@1.3.23
 - @vercel/static-build@1.0.16
 - @vercel/static-config@2.0.3
2022-08-04 15:11:10 -04:00
Nathan Rajlich
7523e39f18 [tests] Remove leftover debugging .pipe() call in vc build tests (#8317) 2022-08-04 17:51:48 +00:00
Craig Andrews
99f2f2f1ba [build-utils] Add flag to indicate that a custom runtime supports lambda wrappers (#8324)
### 📋 Checklist

<!--
  Please keep your PR as a Draft until the checklist is complete
-->

#### Tests

- [ ] The code changed/added as part of this PR has been covered with tests
- [ ] All tests pass locally with `yarn test-unit`

#### Code Review

- [ ] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
2022-08-04 17:23:55 +00:00
Steven
63830d38ce [cli] Fix vc secret rm (#8320)
Fixes a regression from the the refactor in https://github.com/vercel/vercel/pull/8039 that was causing the following error:

```
Error! client.prompt is not a function
```
2022-08-04 13:01:01 -04:00
JJ Kasper
f3428dd212 [next] Remove old middleware test (#8323)
Remove old middleware test
2022-08-04 11:57:43 -05:00
Steven
5eb8b16cbd Publish Stable
- @vercel/build-utils@5.1.1
 - vercel@27.3.6
 - @vercel/client@12.1.9
 - @vercel/edge@0.0.2
 - @vercel/frameworks@1.1.2
 - @vercel/fs-detectors@2.0.4
 - @vercel/go@2.0.14
 - @vercel/hydrogen@0.0.11
 - @vercel/next@3.1.14
 - @vercel/node@2.5.5
 - @vercel/python@3.1.6
 - @vercel/redwood@1.0.15
 - @vercel/remix@1.0.16
 - @vercel/routing-utils@2.0.1
 - @vercel/ruby@1.3.22
 - @vercel/static-build@1.0.15
 - @vercel/static-config@2.0.2
2022-08-04 11:39:58 -04:00
JJ Kasper
226bf02be2 [next] Remove middleware regexp modifying (#8321)
x-ref: [slack thread](https://vercel.slack.com/archives/C03SF65BYSG/p1659626639087909)
2022-08-04 11:39:21 -04:00
Steven
8505872f55 [tests] Update package.json scripts (#8318)
This PR consolidates all the `test` scripts to be the same and removes the `prepublishOnly` script since we always run `build` before publishing to npm.
2022-08-04 11:02:56 -04:00
Steven
7db6436797 Publish Stable
- @vercel/build-utils@5.1.0
 - vercel@27.3.5
 - @vercel/client@12.1.8
 - @vercel/go@2.0.13
 - @vercel/hydrogen@0.0.10
 - @vercel/next@3.1.13
 - @vercel/node@2.5.4
 - @vercel/python@3.1.5
 - @vercel/redwood@1.0.14
 - @vercel/remix@1.0.15
 - @vercel/ruby@1.3.21
 - @vercel/static-build@1.0.14
2022-08-04 08:37:04 -04:00
Chris Barber
e2d76e9c92 [cli] recreate symlinked files instead of copying (#8270)
Instead of copying symlinked files during a build, recreate the symlink.

### 📋 Checklist

#### Tests

- [x] The code changed/added as part of this PR has been covered with tests
- [x] All tests pass locally with `yarn test-unit`

#### Code Review

- [ ] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
2022-08-04 04:07:33 +00:00
Nathan Rajlich
337cb21d67 [cli] Fix flaky error in builds.json tests (#8316)
Follow-up to #8305.

The `expect().toOutput()` call was frequently timing out before the
command writes the error to the terminal, causing the test to fail.
So wait for the command to return the exit code before running assertion
on the printed output.
2022-08-03 20:38:45 -07:00
JJ Kasper
6bfff3e9eb [next] Remove un-necessary duplicate i18n route (#8313)
* Remove un-necessary duplicate i18n route

* update
2022-08-03 21:02:19 -05:00
Sean Massa
ac5b259c11 [go] refactor away use of downloaded result (#8291)
Review Notes: Turn off diff whitespace.

### Refactor

This PR refactors away the use of the result of `download` for a couple of reasons:
- Keeping `files` and `downloadedFiles` in sync with the file system (like when we rename a file that starts with a bracket) is easy to forget to do, causing bugs
- Nate says that `files` is something we've wanted to move away from using anyway
- It simplifies the code in a few places
- It was getting in the way of other fixes that need to be made

We do still call `download`, but it should be a no-op most of the time.

As a consequence of these changes, this PR also addresses:
- the builder no longer leaves build artifacts around, in many cases
- the builder can compile files that start with brackets again; routes don't seem to allow this to file to respond to a dynamic segments yet, though

### Next Steps

Upcoming PRs will resolve builder issues:

- bracket endpoints responding to dynamic segments
- exported function name conflict handling
- compilation targets should only apply to the source code build, not the analyze go utility

### Operating In-place

We also now have a cleanup step that clears out created files, created directories, and undoes file renames. This fixes an issue where multiple builds on the same directory would fail. It also cleans the user's project code when they are using `vc build`.

Ideally, we'd probably copy all of the code to a separate location, then freely do filesystem operations there. It's not clear to me if this is preferred for large projects because it would have to happen once per endpoint: 100 Go files would cause 10,000 (100 * 100) file copies (or symlinks). 

It has to copy once per endpoint because we potentially need all of the files around in case any of them are imported. If we had nft-style tracing for Go, we could copy only what we needed.

This gets more complex in the next step where the exported function names will be renamed during compilation to fix the name conflict issue.
2022-08-04 01:11:35 +00:00
Steven
bfc553db11 [tests] Add support for probes.json (#8279)
Previously, our test fixtures used to use a probes prop in `vercel.json` that was removed right before it was deployed.

This PR allows a separate `probes.json` file with the same content to separate the test fixture input from the test probes.

This allows us to test real "zero config" deployments without a `vercel.json` file.
2022-08-03 20:15:50 -04:00
Nathan Rajlich
2b101d4692 [cli] Remove legacy config file migration logic (#8199)
Removes the legacy config file migration logic from back in the days when Zeit CLI supported multiple "providers". This was from a _very_ long time ago and we should expect that anyone who would have migrated at this point, has.
2022-08-03 23:48:17 +00:00
Matthew Stanciu
3316f38cb4 [cli] Strip scheme from vc inspect argument (#8307)
Right now, `vc inspect` fails to find a deployment if you include `http://` before it. But it works with no scheme and with `https://`.

Since it appears no scheme is what the API looks for anyway, and to avoid confusion, this PR strips any included scheme from the `deploymentIdOrHost` argument.

### 📋 Checklist

<!--
  Please keep your PR as a Draft until the checklist is complete
-->

#### Tests

- [x] The code changed/added as part of this PR has been covered with tests
- [x] All tests pass locally with `yarn test-unit`

#### Code Review

- [x] This PR has a concise title and thorough description useful to a reviewer
- [x] Issue from task tracker has a link to this PR
2022-08-03 22:48:44 +00:00
Nathan Rajlich
7837387127 [cli] Print error from Builder in vc build (#8305)
Ensures that errors that are serialized into `builds.json` are also printed to the terminal when running the `vc build` command.

Co-authored-by: Steven <steven@ceriously.com>
2022-08-03 17:32:50 -04:00
Thomas Knickman
f478200dd3 [static-build] set TURBO_CI_VENDOR_ENV_KEY environment variable (#8306)
Set `TURBO_CI_VENDOR_ENV_KEY` to support https://github.com/vercel/turborepo/pull/1622
2022-08-03 17:31:18 -04:00
Matthew Stanciu
c29de8206a [cli] Minor vc env pull diff formatting changes (#8303)
#8170 added a new message at the end of `vc env pull` which shows a delta of what was added, modified, and removed. Some people shared feedback that the yellow chalk color and `~` prefix to indicate modified variables was confusing. This PR instead keeps the prefix as `+` with a green color, but adds a `(Modified)` suffix at the end of every modified variable.

<img width="638" alt="Screen Shot 2022-08-03 at 10 18 28 AM" src="https://user-images.githubusercontent.com/14811170/182670327-5a3df6db-d84d-40a1-956b-9cf159501759.png">

### 📋 Checklist

<!--
  Please keep your PR as a Draft until the checklist is complete
-->

#### Tests

- [x] The code changed/added as part of this PR has been covered with tests
- [x] All tests pass locally with `yarn test-unit`

#### Code Review

- [ ] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
2022-08-03 20:04:03 +00:00
JJ Kasper
a2df3b5463 [next] Update data route handling for i18n and static routes (#8304)
* Ensure dynamic data route handles missing default locale path

* Ensure static data routes are still handled
2022-08-03 13:40:00 -05:00
Steven
73446e544a [python] Fix error message for discontinued Python 3.6 (#8300)
This fixes the error message when a discontinued version of python (for example, Python 3.6) is detected.

https://vercel.com/changelog/python-3-6-is-being-deprecated
2022-08-03 13:09:03 -04:00
JJ Kasper
21ff4a58c3 [next] Ensure we resolve _next/data dynamic routes correctly with i18n (#8297)
* Ensure we resolve _next/data dynamic routes correctly with i18n

* remove test version
2022-08-03 09:03:15 -05:00
JJ Kasper
2b9eb02b8c [next] Fix _next/data resolving priority for dynamic routes (#8278)
* Fix _next/data resolving priority for dynamic routes

* Apply suggestions from code review

* Ensure we match middleware for _next/data without header

* fix nested middleware case

* Update data routes generating

* Add version lock for non-nested middleware

* use path.posix
2022-08-02 17:41:06 -05:00
JJ Kasper
4ef4722460 [next] Fix priority for notFound preview routes (#7902)
Fix priority for notFound preview routes
2022-08-02 17:09:09 -05:00
Sean Massa
be5308b137 [dev] log middleware errors in vc dev (#8267)
Middleware server setup wasn't logging errors the same way that dev server setup was. This meant that middleware instantiation errors (like invalid config) would cause requests to 500, but no errors to be logged to the console.

This PR updates the invalid config error, makes sure errors in this area are logged out, and adds a test for this behavior.

**It may be appropriate to fail the deploy (and crash `vc dev`) in this case instead, though. What do others think?**

---

During `vc dev` with middleware that has an invalid `config.matcher` value...

Before: You see a 500 response in the browser and no output in the terminal.

After: You see a 500 response in the browser and this output in the terminal:

```
Error! Middleware's `config.matcher` values must start with "/". Received: not-a-valid-matcher
```

---

Related Issue: https://github.com/vercel/edge-functions/issues/220
2022-08-02 20:01:42 +00:00
Steven
08a83a94f8 [docs] Link to Build Output API docs (#8292)
* [docs] Link to Build Output API docs

Co-authored-by: Sean Massa <EndangeredMassa@gmail.com>
2022-08-02 12:35:36 -04:00
336 changed files with 3546 additions and 6423 deletions

View File

@@ -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:

View File

@@ -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)

View File

@@ -18,3 +18,4 @@ pnpm-debug.log*
# macOS-specific files
.DS_Store
.vercel

View File

@@ -1 +0,0 @@
README.md

View File

@@ -1,10 +1,16 @@
# Welcome to [Astro](https://astro.build)
# Astro
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](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.
[![Deploy with Vercel](https://vercel.com/button)](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 |

View File

@@ -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"
}
}

View 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>&rarr;</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>

View File

@@ -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
View File

@@ -0,0 +1 @@
/// <reference types="astro/client" />

View 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>

View File

@@ -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>&rarr;</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>&rarr;</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>&rarr;</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>&rarr;</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>

View File

@@ -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

View File

@@ -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",

View File

@@ -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);

View File

@@ -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,

View 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;
}

View File

@@ -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,

View File

@@ -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> {

View 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);
});
}
});

View File

@@ -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;

View File

@@ -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",

View File

@@ -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:')}

View File

@@ -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 },

View File

@@ -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:')}

View File

@@ -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']) {

View File

@@ -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,

View File

@@ -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']);

View File

@@ -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);

View File

@@ -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:')}

View File

@@ -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;
}

View File

@@ -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(

View File

@@ -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()];

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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;

View File

@@ -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:')}

View File

@@ -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);
}

View File

@@ -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) &&

View File

@@ -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;

View File

@@ -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);
}
/**

View File

@@ -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',
};

View File

@@ -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}`,

View File

@@ -24,7 +24,6 @@ export { VercelConfig };
export interface DevServerOptions {
output: Output;
devCommand?: string;
frameworkSlug?: string;
projectSettings?: ProjectSettings;
systemEnvValues?: string[];
projectEnvs?: ProjectEnvVariable[];

View File

@@ -9,6 +9,7 @@ type LinkResult = {
org: Org;
project: Project;
};
export async function ensureLink(
commandName: string,
client: Client,

View File

@@ -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'
);
}

View 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)
}

View File

@@ -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")
}

View File

@@ -0,0 +1,7 @@
export const config = {
matcher: 'not-a-valid-matcher',
};
export default function middleware(request, _event) {
return new Response(null);
}

View File

@@ -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);

View File

@@ -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');
})
);

View File

@@ -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}".`);
}

View File

@@ -0,0 +1,7 @@
{
"orgId": ".",
"projectId": ".",
"settings": {
"framework": null
}
}

View File

@@ -0,0 +1 @@
<h1>Vercel</h1>

View File

@@ -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 }
);

View File

@@ -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);

View File

@@ -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);
});
});

View File

@@ -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(

View File

@@ -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 });

View File

@@ -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",

View File

@@ -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",

View File

@@ -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"
}

View File

@@ -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",

View File

@@ -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 {

View File

@@ -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))
}

View File

@@ -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))
}

View File

@@ -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",

View File

@@ -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)
}

View File

@@ -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" }
]

View 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")
}

View File

@@ -0,0 +1,3 @@
module go-mod
go 1.15

View 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")
}

View 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")
}

View 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")
}

View 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")
}

View 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" }
]
}

View File

@@ -0,0 +1 @@
module env

View 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"))
}

View 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
View 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');
});
});

View File

@@ -11,7 +11,7 @@
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitThis": false,
"types": ["node"],
"types": ["node", "jest"],
"strict": true,
"target": "ES2020"
}

View File

@@ -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"
}
}

View File

@@ -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",

View File

@@ -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);

View File

@@ -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

View File

@@ -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`
);
}

View 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;

View 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;

View 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;

View File

@@ -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": {

View 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);
});
});

View 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;
}
}

View 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',
},
],
};
},
};

View File

@@ -0,0 +1,11 @@
{
"scripts": {
"dev": "next dev",
"build": "next build"
},
"dependencies": {
"next": "canary",
"react": "latest",
"react-dom": "latest"
}
}

View File

@@ -0,0 +1,3 @@
export default function Page() {
return <p>/[teamId]/[slug]</p>;
}

View 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',
};
}

View 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',
},
});

View File

@@ -0,0 +1,3 @@
export default function Index() {
return <p className="title">Dynamic route</p>;
}

View 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