Compare commits

..

38 Commits

Author SHA1 Message Date
Nathan Rajlich
f26858b735 Publish Canary
- @vercel/build-utils@3.0.2-canary.0
 - vercel@24.2.4-canary.0
 - @vercel/client@11.0.3-canary.0
 - @vercel/frameworks@0.9.1-canary.0
 - @vercel/go@1.4.3-canary.0
 - @vercel/next@2.8.66-canary.0
 - @vercel/node-bridge@2.2.2-canary.0
 - @vercel/node@1.15.3-canary.0
 - @vercel/python@2.3.3-canary.0
 - @vercel/redwood@0.8.3-canary.0
 - @vercel/routing-utils@1.13.3-canary.0
 - @vercel/ruby@1.3.6-canary.0
 - @vercel/static-build@0.25.2-canary.0
 - @vercel/static-config@1.0.1-canary.0
2022-05-18 16:54:08 -07:00
Nathan Rajlich
623e43f865 [next] Add @vercel/next Builder (#7793) 2022-05-18 23:51:48 +00:00
Sean Massa
3e696513a2 [examples] add .output to .vercelignore (#7817)
When running `nuxi build` locally, you'll end up with a `.output` directory. This is not used to produce a vercel deployment and can cause confusion with Build Output API v2 (which also expects a `.output` directory). It's better to leave this ignored.
2022-05-18 21:35:15 +00:00
JJ Kasper
285f62c9d0 Remove un-needed use of secret for team as it mangles logs (#7818)
### Related Issues

This removes the secret for turbo team as it filters anywhere vercel is printed in the logs and it doesn't really need to be secret. 

x-ref: https://github.com/vercel/vercel/runs/6496893033?check_suite_focus=true

### 📋 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-05-18 21:02:36 +00:00
JJ Kasper
c43db1788c [tests] Add cross platform chunked testing and leverage turbo more (#7795)
* [tests] Use `turbo` for unit tests

* .

* .

* Revert "."

This reverts commit 3d6204fef3fda3c7b4bf08955a186fe806d7c597.

* Add "src/util/constants.ts" to outputs

* .

* Add `@vercel/node-bridge` outputs

* .

* Mac and Windows

* .

* Node 14

* .

* .

* Add templates to CLI output

* Run only selected test files

* Add cross platform testing

* make test paths relative and have minimum per chunk

* add install arg

* update shell

* bump cache key

* use backslashes on windows

* pass tests as arg

* update script name

* update turbo config

* forward turbo args correctly

* dont use backslashes

* chunk integration tests instead

* update env

* separate static-build tests

* ensure unit test turbo cache is saved

* ensure turbo cache is saved for dev/cli

* fix cache key and update timeout

* Increase static-build unit test timeout

* Leverage turbo remote caching instead of actions/cache

* apply suggestions and test chunking itself

* update other ci jobs

* fix test collecting

Co-authored-by: Nathan Rajlich <n@n8.io>
2022-05-18 13:27:20 -07:00
haitrungle
fc2eb1a30d [examples] Fix typo in SolidStart link (#7589) 2022-05-18 10:42:24 -07:00
Sean Massa
ecf194b7c1 [tests] improve platform-based skips (#7802)
Co-authored-by: Steven <steven@ceriously.com>
2022-05-17 12:12:47 -05:00
Steven
c14e5689f1 [tests] Skip broken go test (#7809)
This test was added several years ago, before the monorepo in https://github.com/vercel/vercel/pull/2812 but it was never working correctly due to the way zero config behaves differently than the test with `builds` in vercel.json

We can fix it in a follow up PR
2022-05-16 14:44:09 -04:00
Steven
54dfe747e2 [build-utils] Deprecate Node.js 12.x with warning (#7779)
Node.js 12 reached EOL April 2022 so its time to notify customers.

This PR will warn starting today and then error once the discontinue date is reached in a couple months.
2022-05-16 16:06:39 +00:00
Steven
2afc8db8e7 Publish Stable
- vercel@24.2.3
 - @vercel/static-build@0.25.1
2022-05-13 18:30:15 -04:00
Steven
cc628dd9fb Publish Canary
- vercel@24.2.3-canary.0
 - @vercel/static-build@0.25.1-canary.0
2022-05-13 18:20:41 -04:00
Steven
dfb6ef949b Revert "[static-build] Support subset of Build Output API v2" (#7803)
Revert "[static-build] Support subset of Build Output API v2 (#7690)"

This reverts commit 05243fb6e9.
2022-05-13 18:19:38 -04:00
Sean Massa
cd4799b5d5 [cli] distinguish error messages (#7794)
* distinguish error messages

* use correct response and assertion

* remove check for APIError because getUser does not always throw those
2022-05-13 12:49:36 -05:00
Steven
5e66d4b2cc Publish Stable
- @vercel/build-utils@3.0.1
 - vercel@24.2.2
 - @vercel/client@11.0.2
 - @vercel/go@1.4.2
 - @vercel/node@1.15.2
 - @vercel/python@2.3.2
 - @vercel/redwood@0.8.2
 - @vercel/ruby@1.3.5
 - @vercel/static-build@0.25.0
2022-05-12 17:19:06 -04:00
Ethan Arrowood
44d7473e7c Publish Canary
- @vercel/redwood@0.8.2-canary.2
 - @vercel/static-build@0.24.2-canary.2
2022-05-12 13:48:21 -06:00
Ethan Arrowood
fddec1286c [redwood][static-build] move path logic up so both commands get pnpm7 (#7792)
move path logic up so both commands get pnpm7
2022-05-12 13:47:09 -06:00
Ethan Arrowood
6e5e700e8d Publish Canary
- vercel@24.2.2-canary.2
 - @vercel/node@1.15.2-canary.1
 - @vercel/redwood@0.8.2-canary.1
 - @vercel/static-build@0.24.2-canary.1
2022-05-12 09:04:20 -06:00
Ryan Carniato
b6e8609b83 [examples] Update SolidStart to Build Output API v3 (#7790) 2022-05-11 23:12:53 -07:00
Ethan Arrowood
78b7bd5ec8 [redwood][static-build] add pnpm7 detection logic to builders (#7787)
add pnpm7 detection logic to builders
2022-05-11 19:37:01 -04:00
Steven
4104a45c2d [tests] Fix node tests (#7786)
Since we released Node.js 16, these tests now resolve `engines` differently
2022-05-11 23:36:18 +00:00
Ethan Arrowood
4c20218e05 Publish Canary
- @vercel/build-utils@3.0.1-canary.1
 - vercel@24.2.2-canary.1
 - @vercel/client@11.0.2-canary.0
 - @vercel/go@1.4.2-canary.0
 - @vercel/node@1.15.2-canary.0
 - @vercel/python@2.3.2-canary.0
 - @vercel/redwood@0.8.2-canary.0
 - @vercel/ruby@1.3.5-canary.0
 - @vercel/static-build@0.24.2-canary.0
2022-05-11 15:47:48 -06:00
Ethan Arrowood
02a0004719 [build-utils] Fix pnpm 7 path setting (#7785)
### Related Issues

Fixes pnpm 7 support. Now uses a yarn installed version and drops an unnecessary check for node version.

### 📋 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-05-11 21:37:44 +00:00
Steven
123bffb776 [examples] Pin Ember to 14.x (#7782) 2022-05-10 14:34:46 -04:00
Steven
074535f27c [build-utils] Downgrade remix and solidstart to Node.js 14 (fsapi) (#7781)
These frameworks (remix and solidstart) use the legacy file system api (v2) so they can't support Node.js 16.x so we change engines to pin to Node.js 14.x

I also added the missing `ENABLE_FILE_SYSTEM_API=1` env var to solidstart to match remix.

https://vercel.com/docs/file-system-api/v2
2022-05-10 13:20:07 -04:00
Sean Massa
05243fb6e9 [static-build] Support subset of Build Output API v2 (#7690) 2022-05-09 14:01:26 -07:00
Sean Massa
097725580c [tests] skip flakey test (#7777)
Skips two flakey tests on Mac OS only. The error often looks like:

<img width="1453" alt="Screen Shot 2022-05-09 at 2 26 52 PM" src="https://user-images.githubusercontent.com/41545/167483088-49498f69-5470-4c1a-98f5-96ca811b838b.png">

Created internal tracking card to dig deeper. Skipping this for now will unclog other work.
2022-05-09 21:00:49 +00:00
Andrew Gadzik
4b09c89e7d [build-utils] Fix version mismatch (#7776)
Fixes 3a1eede63b
2022-05-09 14:51:04 -04:00
agadzik
3a1eede63b Publish Canary
- @vercel/build-utils@3.0.1-canary.0
 - vercel@24.2.2-canary.0
2022-05-09 13:51:37 -04:00
agadzik
9cee0dd5d7 BREAKING CHANGE: updating build-utils to version 3 for DetectorFilesystem changes 2022-05-09 13:47:11 -04:00
John Pham
b801c6e593 [cli] Track source of getting decrypted environment variables (#7754)
* Track source of getting decrypted environment variables

* Add source as a query param

* Revert lock file changes

* Change source from strings to type

* Differential between pull and env pull
2022-05-09 10:24:43 -07:00
Andrew Gadzik
505050b923 [build-utils] Add readdir and chdir functions to DetectorFilesystem (#7751)
In order to support various `fs` operations for monorepo detection, the team needs to add two new abstract functions to `DetectorFilesystem` in order to traverse down into children directories

* readdir
* chdir

```ts
interface Stat {
  name: string
  path: string
  type: "file" | "dir"
}

export abstract class DetectorFilesystem {
  ...
  /**
   * Returns a list of Stat objects from the current working directory.
   * 
   * @example
   * 
   * const url = "https://github.com/vercel/front"
   * const fs = new GitDetectorFilesystem(...) // based on url
   *
   * // calls "https://api.github.com/repos/vercel/front/contents" behind the scenes
   * await fs.readdir() => [
   *    { name: "docs", path: "docs", type: "dir" },
   *    { name: "front", path: "front", type: "dir" },
   *    ...,
   *    { name: "package.json", path: "package.json", type: "file" },
   * ]
   */
   protected abstract _readdir(name: string): Promise<Stat[]>;

 /**
   * Changes the current directory to the specified path and returns a new instance of DetectorFilesystem.
   * 
   * @example
   * 
   * my-repo
   * |-- backend
   * |    |-- api-1
   * |    |-- api-2
   * |    |-- package.json // workspaces: ["api-1", "api-2"]
   * |    |-- yarn.lock
   * |-- frontend
   * |    |-- nextjs-app
   * |    |-- gatsby-app
   * |    |-- package.json
   * |    |-- pnpm-workspaces.yaml // packages: ["nextjs-app", "gatsby-app"]
   * 
   * const fs = new (...) // based on "my-repo" as the root
   * const backendFs = fs.chdir("backend")
   * const frontendFs = fs.chdir("frontend")
   * 
   * const backendWorkspaceManager = detectFramework({ fs: backendFs, frameworkList: workspaceManagers }) // "yarn"
   * const frontendWorkspaceManager = detectFramework({ fs: frontendFs, frameworkList: workspaceManagers }) // "pnpm"
   */
   protected abstract _chdir(name: string): DetectorFilesystem
   ...
}
```

### Related Issues

> Related to https://github.com/vercel/vercel/issues/7750

### 📋 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-05-09 17:12:30 +00:00
Steven
15c7ad241a Publish Stable
- @vercel/build-utils@2.17.0
 - vercel@24.2.1
 - @vercel/client@11.0.1
 - @vercel/frameworks@0.9.0
 - @vercel/go@1.4.1
 - @vercel/node@1.15.1
 - @vercel/python@2.3.1
 - @vercel/redwood@0.8.1
 - @vercel/ruby@1.3.4
 - @vercel/static-build@0.24.1
2022-05-09 10:41:56 -04:00
Steven
ec57654b5b Publish Canary
- @vercel/build-utils@2.16.1-canary.4
 - vercel@24.2.1-canary.4
 - @vercel/client@11.0.1-canary.4
 - @vercel/go@1.4.1-canary.4
 - @vercel/node@1.15.1-canary.4
 - @vercel/python@2.3.1-canary.4
 - @vercel/redwood@0.8.1-canary.4
 - @vercel/ruby@1.3.4-canary.4
 - @vercel/static-build@0.24.1-canary.4
2022-05-09 10:13:58 -04:00
Steven
3b9a9878bc [build-utils] Add Node.js 16.x (#7772)
Add support for Node.js 16

- Related to https://github.com/aws/aws-lambda-base-images/issues/14#issuecomment-1120864028
2022-05-09 10:13:11 -04:00
Nathan Rajlich
70b7db1a15 [node] Move @types/jest to "devDependencies" (#7768)
This should be a dev dep.
2022-05-09 09:23:10 -04:00
Nathan Rajlich
41d6666139 Publish Canary
- @vercel/build-utils@2.16.1-canary.3
 - vercel@24.2.1-canary.3
 - @vercel/client@11.0.1-canary.3
 - @vercel/frameworks@0.8.1-canary.1
 - @vercel/go@1.4.1-canary.3
 - @vercel/node@1.15.1-canary.3
 - @vercel/python@2.3.1-canary.3
 - @vercel/redwood@0.8.1-canary.3
 - @vercel/ruby@1.3.4-canary.3
 - @vercel/static-build@0.24.1-canary.3
2022-05-07 16:08:08 -07:00
Nathan Rajlich
2857219f89 Fix "astro" slug 2022-05-07 11:57:14 -07:00
Tony Sullivan
246c2a0f5d [frameworks] Add Astro (#7747) 2022-05-06 16:51:42 -07:00
1165 changed files with 68265 additions and 1184 deletions

View File

@@ -19,6 +19,10 @@ packages/cli/src/util/dev/templates/*.ts
packages/client/tests/fixtures
packages/client/lib
# next
packages/next/test/integration/middleware
packages/next/test/integration/middleware-eval
# node-bridge
packages/node-bridge/bridge.js
packages/node-bridge/launcher.js

View File

@@ -18,6 +18,10 @@ jobs:
os: [ubuntu-latest]
node: [12]
runs-on: ${{ matrix.os }}
env:
TURBO_REMOTE_ONLY: true
TURBO_TEAM: vercel
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
steps:
- uses: actions/setup-go@v2
with:

View File

@@ -18,6 +18,10 @@ jobs:
os: [ubuntu-latest, macos-latest]
node: [12]
runs-on: ${{ matrix.os }}
env:
TURBO_REMOTE_ONLY: true
TURBO_TEAM: vercel
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
steps:
- uses: actions/setup-go@v2
with:

View File

@@ -1,35 +0,0 @@
name: E2E
on:
push:
branches:
- main
tags:
- '!*'
pull_request:
jobs:
test:
name: E2E
timeout-minutes: 120
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v2
with:
go-version: '1.13.15'
- uses: actions/setup-node@v2
with:
node-version: 12
- uses: actions/checkout@v2
with:
fetch-depth: 100
- run: git --version
- run: git fetch origin main --depth=100
- run: git fetch origin ${{ github.ref }} --depth=100
- run: git diff origin/main...HEAD --name-only
- run: yarn install --network-timeout 1000000
- run: yarn run build
- run: yarn test-integration-once
env:
VERCEL_TEAM_TOKEN: ${{ secrets.VERCEL_TEAM_TOKEN }}
VERCEL_REGISTRATION_URL: ${{ secrets.VERCEL_REGISTRATION_URL }}

View File

@@ -18,6 +18,10 @@ jobs:
os: [ubuntu-latest, macos-latest, windows-latest]
node: [12]
runs-on: ${{ matrix.os }}
env:
TURBO_REMOTE_ONLY: true
TURBO_TEAM: vercel
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
steps:
- uses: actions/setup-go@v2
with:
@@ -40,4 +44,4 @@ jobs:
- run: yarn workspace vercel run coverage
if: matrix.os == 'ubuntu-latest' && matrix.node == 12 # only run coverage once
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

84
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,84 @@
name: Tests
on:
push:
branches:
- main
tags:
- '!*'
pull_request:
env:
NODE_VERSION: '14'
jobs:
setup:
name: Find Changes
runs-on: ubuntu-latest
outputs:
tests: ${{ steps['set-tests'].outputs['tests'] }}
steps:
- uses: actions/checkout@v2
- run: git --version
- run: git fetch origin main
- uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'yarn'
- run: yarn install --network-timeout 1000000
- id: set-tests
run: |
TESTS_ARRAY=$(node utils/chunk-tests.js $SCRIPT_NAME)
echo "Files to test:"
echo "$TESTS_ARRAY"
echo "::set-output name=tests::$TESTS_ARRAY"
test:
timeout-minutes: 120
runs-on: ${{ matrix.runner }}
name: ${{matrix.scriptName}} (${{matrix.packageName}}, ${{matrix.chunkNumber}}, ${{ matrix.runner }})
if: ${{ needs.setup.outputs['tests'] != '[]' }}
env:
TURBO_REMOTE_ONLY: true
TURBO_TEAM: vercel
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
needs:
- setup
strategy:
fail-fast: false
matrix:
include: ${{ fromJson(needs.setup.outputs['tests']) }}
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 2
- uses: actions/setup-go@v2
with:
go-version: '1.13.15'
- uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'yarn'
- run: yarn install --network-timeout 1000000
- name: Build ${{matrix.packageName}} and all its dependencies
run: yarn turbo run build --cache-dir=".turbo" --scope=${{matrix.packageName}} --include-dependencies --no-deps
env:
FORCE_COLOR: '1'
- name: Test ${{matrix.packageName}}
run: node_modules/.bin/turbo run test --cache-dir=".turbo" --scope=${{matrix.packageName}} --no-deps -- ${{ join(matrix.testPaths, ' ') }}
shell: bash
env:
VERCEL_TEAM_TOKEN: ${{ secrets.VERCEL_TEAM_TOKEN }}
VERCEL_REGISTRATION_URL: ${{ secrets.VERCEL_REGISTRATION_URL }}
FORCE_COLOR: '1'
conclusion:
needs:
- test
runs-on: ubuntu-latest
name: E2E
steps:
- name: Done
run: echo "Done."

20
examples/astro/.gitignore vendored Normal file
View File

@@ -0,0 +1,20 @@
# build output
dist/
.output/
# dependencies
node_modules/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# environment variables
.env
.env.production
# macOS-specific files
.DS_Store

2
examples/astro/.npmrc Normal file
View File

@@ -0,0 +1,2 @@
# Expose Astro dependencies for `pnpm` users
shamefully-hoist=true

View File

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

42
examples/astro/README.md Normal file
View File

@@ -0,0 +1,42 @@
# Welcome to [Astro](https://astro.build)
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/starter)
> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun!
## 🚀 Project Structure
Inside of your Astro project, you'll see the following folders and files:
```
/
├── public/
│ └── favicon.ico
├── src/
│ ├── components/
│ │ └── Layout.astro
│ └── pages/
│ └── index.astro
└── package.json
```
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components or layouts.
Any static assets, like images, can be placed in the `public/` directory.
## 🧞 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).

View File

@@ -0,0 +1,4 @@
import { defineConfig } from 'astro/config';
// https://astro.build/config
export default defineConfig({});

View File

@@ -0,0 +1,14 @@
{
"name": "@example/basics",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview"
},
"devDependencies": {
"astro": "^1.0.0-beta.20"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,55 @@
---
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>

View File

@@ -0,0 +1,174 @@
---
import Layout from '../components/Layout.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>
<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>
</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%);
}
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;
}
main {
margin: auto;
padding: 1em;
max-width: 60ch;
}
.text-gradient {
font-weight: 900;
background-image: var(--astro-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-size: 100% 200%;
background-position-y: 100%;
border-radius: 0.4rem;
animation: pulse 4s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% {
background-position-y: 0%;
}
50% {
background-position-y: 80%;
}
}
.instructions {
line-height: 1.8;
margin-bottom: 2rem;
background-image: var(--night-sky-gradient);
padding: 1.5rem;
border-radius: 0.4rem;
color: var(--color-bg);
}
.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

@@ -0,0 +1,15 @@
{
"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"]
}
}

3457
examples/astro/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -48,6 +48,6 @@
"qunit-dom": "^0.8.4"
},
"engines": {
"node": "8.* || >= 10.*"
"node": "14.x"
}
}

View File

@@ -2,3 +2,4 @@ README.md
.nuxt
node_modules
*.log
.output

View File

@@ -21,7 +21,7 @@
"typescript": "^4.1.2"
},
"engines": {
"node": ">=14"
"node": "14.x"
}
},
"node_modules/@babel/code-frame": {

View File

@@ -23,7 +23,7 @@
"typescript": "^4.1.2"
},
"engines": {
"node": ">=14"
"node": "14.x"
},
"sideEffects": false
}
}

View File

@@ -1,6 +1,6 @@
# SolidStart
This directory is a brief example of a [SolidStart](https://github.com/ryansolid/solid-startp) site that can be deployed to Vercel with zero configuration.
This directory is a brief example of a [SolidStart](https://github.com/ryansolid/solid-start) site that can be deployed to Vercel with zero configuration.
## Deploy Your Own

View File

@@ -1,9 +0,0 @@
{
"compilerOptions": {
"jsx": "preserve",
"jsxImportSource": "solid-js",
"paths": {
"~/*": ["./src/*"]
}
}
}

View File

@@ -7,14 +7,14 @@
"type": "module",
"private": true,
"devDependencies": {
"solid-app-router": "^0.1.14",
"solid-js": "^1.2.6",
"solid-meta": "^0.27.2",
"solid-app-router": "^0.3.2",
"solid-js": "^1.3.15",
"solid-meta": "^0.27.3",
"solid-start": "next",
"solid-start-vercel": "next",
"vite": "^2.7.1"
"vite": "^2.9.9"
},
"engines": {
"node": ">=14"
"node": "16.x"
}
}

View File

@@ -0,0 +1,4 @@
import { hydrate } from "solid-js/web";
import { StartClient } from "solid-start/entry-client";
hydrate(() => <StartClient />, document);

View File

@@ -0,0 +1,7 @@
import { StartServer, createHandler, renderAsync } from "solid-start/entry-server";
import { inlineServerModules } from "solid-start/server";
export default createHandler(
inlineServerModules,
renderAsync((context) => <StartServer context={context} />)
);

View File

@@ -1,21 +0,0 @@
// @refresh reload
import { Links, Meta, Outlet, Scripts } from "solid-start/components";
export default function Root({ Start }) {
return (
<Start>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body>
<Outlet />
<Scripts />
</body>
</html>
</Start>
);
}

View File

@@ -0,0 +1,25 @@
// @refresh reload
import { Links, Meta, Routes, Scripts } from "solid-start/root";
import { ErrorBoundary } from "solid-start/error-boundary";
import { Suspense } from "solid-js";
export default function Root() {
return (
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body>
<ErrorBoundary>
<Suspense>
<Routes />
</Suspense>
</ErrorBoundary>
<Scripts />
</body>
</html>
);
}

View File

@@ -0,0 +1,16 @@
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node",
"jsxImportSource": "solid-js",
"jsx": "preserve",
"types": ["vite/client"],
"baseUrl": "./",
"paths": {
"~/*": ["./src/*"]
}
}
}

View File

@@ -0,0 +1,7 @@
{
"build": {
"env": {
"ENABLE_VC_BUILD": "1"
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -44,10 +44,10 @@
"vercel-build": "yarn build && cd api && node -r ts-eager/register ./_lib/script/build.ts",
"pre-commit": "lint-staged",
"test": "jest --rootDir=\"test\" --testPathPattern=\"\\.test.js\"",
"test-unit": "yarn test && node utils/run.js test-unit",
"test-integration-cli": "node utils/run.js test-integration-cli",
"test-integration-once": "node utils/run.js test-integration-once",
"test-integration-dev": "node utils/run.js test-integration-dev",
"test-unit": "yarn test && turbo run test-unit",
"test-integration-cli": "turbo run test-integration-cli",
"test-integration-once": "turbo run test-integration-once",
"test-integration-dev": "turbo run test-integration-dev",
"lint": "eslint . --ext .ts,.js",
"prepare": "husky install"
},

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/build-utils",
"version": "2.16.1-canary.2",
"version": "3.0.2-canary.0",
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.js",
@@ -12,8 +12,9 @@
},
"scripts": {
"build": "node build",
"test-unit": "jest --env node --verbose --runInBand --bail test/unit.*test.*",
"test-integration-once": "jest --env node --verbose --runInBand --bail test/integration.test.ts",
"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"
},
"devDependencies": {
@@ -30,7 +31,7 @@
"@types/node-fetch": "^2.1.6",
"@types/semver": "6.0.0",
"@types/yazl": "2.4.2",
"@vercel/frameworks": "0.8.1-canary.0",
"@vercel/frameworks": "0.9.1-canary.0",
"@vercel/ncc": "0.24.0",
"aggregate-error": "3.0.1",
"async-retry": "1.2.3",

View File

@@ -1,3 +1,8 @@
export interface Stat {
name: string;
path: string;
type: 'file' | 'dir';
}
/**
* `DetectorFilesystem` is an abstract class that represents a virtual filesystem
* to perform read-only operations on in order to detect which framework is being
@@ -27,15 +32,19 @@ export abstract class DetectorFilesystem {
protected abstract _hasPath(name: string): Promise<boolean>;
protected abstract _readFile(name: string): Promise<Buffer>;
protected abstract _isFile(name: string): Promise<boolean>;
protected abstract _readdir(name: string): Promise<Stat[]>;
protected abstract _chdir(name: string): DetectorFilesystem;
private pathCache: Map<string, Promise<boolean>>;
private fileCache: Map<string, Promise<boolean>>;
private readFileCache: Map<string, Promise<Buffer>>;
private readdirCache: Map<string, Promise<Stat[]>>;
constructor() {
this.pathCache = new Map();
this.fileCache = new Map();
this.readFileCache = new Map();
this.readdirCache = new Map();
}
public hasPath = async (path: string): Promise<boolean> => {
@@ -64,4 +73,23 @@ export abstract class DetectorFilesystem {
}
return p;
};
/**
* Returns a list of Stat objects from the current working directory.
*/
public readdir = async (name: string): Promise<Stat[]> => {
let p = this.readdirCache.get(name);
if (!p) {
p = this._readdir(name);
this.readdirCache.set(name, p);
}
return p;
};
/**
* Changes the current directory to the specified path and returns a new instance of DetectorFilesystem.
*/
public chdir = (name: string): DetectorFilesystem => {
return this._chdir(name);
};
}

View File

@@ -4,8 +4,14 @@ import { NowBuildError } from '../errors';
import debug from '../debug';
const allOptions = [
{ major: 16, range: '16.x', runtime: 'nodejs16.x' },
{ major: 14, range: '14.x', runtime: 'nodejs14.x' },
{ major: 12, range: '12.x', runtime: 'nodejs12.x' },
{
major: 12,
range: '12.x',
runtime: 'nodejs12.x',
discontinueDate: new Date('2022-08-09'),
},
{
major: 10,
range: '10.x',

View File

@@ -222,12 +222,6 @@ export async function getNodeVersion(
const latest = getLatestNodeVersion();
return { ...latest, runtime: 'nodejs' };
}
if (process.env.ENABLE_EXPERIMENTAL_NODE16 === '1') {
console.warn(
'Warning: Using experimental Node.js 16.x due to ENABLE_EXPERIMENTAL_NODE16=1'
);
return { major: 16, range: '16.x', runtime: 'nodejs16.x' };
}
const { packageJson } = await scanParentDirs(destPath, true);
let { nodeVersion } = config;
let isAuto = true;
@@ -443,13 +437,9 @@ export function getEnvForPackageManager({
console.log('Detected `package-lock.json` generated by npm 7...');
}
} else if (cliType === 'pnpm') {
if (
typeof lockfileVersion === 'number' &&
lockfileVersion === 5.4 &&
(nodeVersion?.major || 0) > 12
) {
if (typeof lockfileVersion === 'number' && lockfileVersion === 5.4) {
// Ensure that pnpm 7 is at the beginning of the `$PATH`
newEnv.PATH = `/pnpm7/pnpm:${env.PATH}`;
newEnv.PATH = `/pnpm7/node_modules/.bin:${env.PATH}`;
console.log('Detected `pnpm-lock.yaml` generated by pnpm 7...');
}
} else {

View File

@@ -4,8 +4,7 @@
"probes": [
{
"path": "/",
"mustContain": "pnpm version: 7",
"logMustContain": "pnpm run build"
"mustContain": "pnpm version: 7"
}
]
}

View File

@@ -1,21 +1,32 @@
import path from 'path';
import frameworkList from '@vercel/frameworks';
import { detectFramework, DetectorFilesystem } from '../src';
import { Stat } from '../src/detectors/filesystem';
const posixPath = path.posix;
class VirtualFilesystem extends DetectorFilesystem {
private files: Map<string, Buffer>;
private cwd: string;
constructor(files: { [key: string]: string | Buffer }) {
constructor(files: { [key: string]: string | Buffer }, cwd = '') {
super();
this.files = new Map();
this.cwd = cwd;
Object.entries(files).map(([key, value]) => {
const buffer = typeof value === 'string' ? Buffer.from(value) : value;
this.files.set(key, buffer);
});
}
async _hasPath(path: string): Promise<boolean> {
private _normalizePath(rawPath: string): string {
return posixPath.normalize(rawPath);
}
async _hasPath(name: string): Promise<boolean> {
const basePath = this._normalizePath(posixPath.join(this.cwd, name));
for (const file of this.files.keys()) {
if (file.startsWith(path)) {
if (file.startsWith(basePath)) {
return true;
}
}
@@ -24,11 +35,13 @@ class VirtualFilesystem extends DetectorFilesystem {
}
async _isFile(name: string): Promise<boolean> {
return this.files.has(name);
const basePath = this._normalizePath(posixPath.join(this.cwd, name));
return this.files.has(basePath);
}
async _readFile(name: string): Promise<Buffer> {
const file = this.files.get(name);
const basePath = this._normalizePath(posixPath.join(this.cwd, name));
const file = this.files.get(basePath);
if (file === undefined) {
throw new Error('File does not exist');
@@ -40,115 +53,291 @@ class VirtualFilesystem extends DetectorFilesystem {
return file;
}
/**
* An example of how to implement readdir for a virtual filesystem.
*/
async _readdir(name = '/'): Promise<Stat[]> {
return (
[...this.files.keys()]
.map(filepath => {
const basePath = this._normalizePath(
posixPath.join(this.cwd, name === '/' ? '' : name)
);
const fileDirectoryName = posixPath.dirname(filepath);
if (fileDirectoryName === basePath) {
return {
name: posixPath.basename(filepath),
path: filepath.replace(
this.cwd === '' ? this.cwd : `${this.cwd}/`,
''
),
type: 'file',
};
}
if (
(basePath === '.' && fileDirectoryName !== '.') ||
fileDirectoryName.startsWith(basePath)
) {
let subDirectoryName = fileDirectoryName.replace(
basePath === '.' ? '' : `${basePath}/`,
''
);
if (subDirectoryName.includes('/')) {
subDirectoryName = subDirectoryName.split('/')[0];
}
return {
name: subDirectoryName,
path:
name === '/'
? subDirectoryName
: this._normalizePath(posixPath.join(name, subDirectoryName)),
type: 'dir',
};
}
return null;
})
// remove nulls
.filter((stat): stat is Stat => stat !== null)
// remove duplicates
.filter(
(stat, index, self) =>
index ===
self.findIndex(s => s.name === stat.name && s.path === stat.path)
)
);
}
/**
* An example of how to implement chdir for a virtual filesystem.
*/
_chdir(name: string): DetectorFilesystem {
const basePath = this._normalizePath(posixPath.join(this.cwd, name));
const files = Object.fromEntries(
[...this.files.keys()].map(key => [key, this.files.get(key) ?? ''])
);
return new VirtualFilesystem(files, basePath);
}
}
describe('#detectFramework', () => {
it('Do not detect anything', async () => {
const fs = new VirtualFilesystem({
'README.md': '# hi',
'api/cheese.js': 'export default (req, res) => res.end("cheese");',
});
describe('DetectorFilesystem', () => {
it('should return the directory contents relative to the cwd', async () => {
const files = {
'package.json': '{}',
'packages/app1/package.json': '{}',
'packages/app2/package.json': '{}',
};
expect(await detectFramework({ fs, frameworkList })).toBe(null);
const fs = new VirtualFilesystem(files);
expect(await fs.readdir('/')).toEqual([
{ name: 'package.json', path: 'package.json', type: 'file' },
{ name: 'packages', path: 'packages', type: 'dir' },
]);
expect(await fs.readdir('packages')).toEqual([
{ name: 'app1', path: 'packages/app1', type: 'dir' },
{ name: 'app2', path: 'packages/app2', type: 'dir' },
]);
expect(await fs.readdir('./packages')).toEqual([
{ name: 'app1', path: 'packages/app1', type: 'dir' },
{ name: 'app2', path: 'packages/app2', type: 'dir' },
]);
expect(await fs.readdir('packages/app1')).toEqual([
{
name: 'package.json',
path: 'packages/app1/package.json',
type: 'file',
},
]);
});
it('Detect Next.js', async () => {
const fs = new VirtualFilesystem({
'package.json': JSON.stringify({
dependencies: {
next: '9.0.0',
},
}),
it('should be able to change directories', async () => {
const nextPackageJson = JSON.stringify({
dependencies: {
next: '9.0.0',
},
});
const gatsbyPackageJson = JSON.stringify({
dependencies: {
gatsby: '1.0.0',
},
});
expect(await detectFramework({ fs, frameworkList })).toBe('nextjs');
const files = {
'package.json': '{}',
'packages/app1/package.json': nextPackageJson,
'packages/app2/package.json': gatsbyPackageJson,
};
const fs = new VirtualFilesystem(files);
const packagesFs = fs.chdir('packages');
expect(await packagesFs.readdir('/')).toEqual([
{ name: 'app1', path: 'app1', type: 'dir' },
{ name: 'app2', path: 'app2', type: 'dir' },
]);
expect(await packagesFs.hasPath('app1')).toBe(true);
expect(await packagesFs.hasPath('app3')).toBe(false);
expect(await packagesFs.isFile('app1')).toBe(false);
expect(await packagesFs.isFile('app2')).toBe(false);
expect(await packagesFs.isFile('app1/package.json')).toBe(true);
expect(await packagesFs.isFile('app2/package.json')).toBe(true);
expect(
await (await packagesFs.readFile('app1/package.json')).toString()
).toEqual(nextPackageJson);
expect(
await (await packagesFs.readFile('app2/package.json')).toString()
).toEqual(gatsbyPackageJson);
expect(await detectFramework({ fs: packagesFs, frameworkList })).toBe(null);
const nextAppFs = packagesFs.chdir('app1');
expect(await nextAppFs.readdir('/')).toEqual([
{ name: 'package.json', path: 'package.json', type: 'file' },
]);
expect(await (await nextAppFs.readFile('package.json')).toString()).toEqual(
nextPackageJson
);
expect(await detectFramework({ fs: nextAppFs, frameworkList })).toBe(
'nextjs'
);
const gatsbyAppFs = packagesFs.chdir('./app2');
expect(await gatsbyAppFs.readdir('/')).toEqual([
{ name: 'package.json', path: 'package.json', type: 'file' },
]);
expect(
await (await gatsbyAppFs.readFile('package.json')).toString()
).toEqual(gatsbyPackageJson);
expect(await detectFramework({ fs: gatsbyAppFs, frameworkList })).toBe(
'gatsby'
);
});
it('Detect Nuxt.js', async () => {
const fs = new VirtualFilesystem({
'package.json': JSON.stringify({
dependencies: {
nuxt: '1.0.0',
},
}),
describe('#detectFramework', () => {
it('Do not detect anything', async () => {
const fs = new VirtualFilesystem({
'README.md': '# hi',
'api/cheese.js': 'export default (req, res) => res.end("cheese");',
});
expect(await detectFramework({ fs, frameworkList })).toBe(null);
});
expect(await detectFramework({ fs, frameworkList })).toBe('nuxtjs');
});
it('Detect Next.js', async () => {
const fs = new VirtualFilesystem({
'package.json': JSON.stringify({
dependencies: {
next: '9.0.0',
},
}),
});
it('Detect Gatsby', async () => {
const fs = new VirtualFilesystem({
'package.json': JSON.stringify({
dependencies: {
gatsby: '1.0.0',
},
}),
expect(await detectFramework({ fs, frameworkList })).toBe('nextjs');
});
expect(await detectFramework({ fs, frameworkList })).toBe('gatsby');
});
it('Detect Nuxt.js', async () => {
const fs = new VirtualFilesystem({
'package.json': JSON.stringify({
dependencies: {
nuxt: '1.0.0',
},
}),
});
it('Detect Hugo #1', async () => {
const fs = new VirtualFilesystem({
'config.yaml': 'baseURL: http://example.org/',
'content/post.md': '# hello world',
expect(await detectFramework({ fs, frameworkList })).toBe('nuxtjs');
});
expect(await detectFramework({ fs, frameworkList })).toBe('hugo');
});
it('Detect Gatsby', async () => {
const fs = new VirtualFilesystem({
'package.json': JSON.stringify({
dependencies: {
gatsby: '1.0.0',
},
}),
});
it('Detect Hugo #2', async () => {
const fs = new VirtualFilesystem({
'config.json': '{ "baseURL": "http://example.org/" }',
'content/post.md': '# hello world',
expect(await detectFramework({ fs, frameworkList })).toBe('gatsby');
});
expect(await detectFramework({ fs, frameworkList })).toBe('hugo');
});
it('Detect Hugo #1', async () => {
const fs = new VirtualFilesystem({
'config.yaml': 'baseURL: http://example.org/',
'content/post.md': '# hello world',
});
it('Detect Hugo #3', async () => {
const fs = new VirtualFilesystem({
'config.toml': 'baseURL = "http://example.org/"',
'content/post.md': '# hello world',
expect(await detectFramework({ fs, frameworkList })).toBe('hugo');
});
expect(await detectFramework({ fs, frameworkList })).toBe('hugo');
});
it('Detect Hugo #2', async () => {
const fs = new VirtualFilesystem({
'config.json': '{ "baseURL": "http://example.org/" }',
'content/post.md': '# hello world',
});
it('Detect Jekyll', async () => {
const fs = new VirtualFilesystem({
'_config.yml': 'config',
expect(await detectFramework({ fs, frameworkList })).toBe('hugo');
});
expect(await detectFramework({ fs, frameworkList })).toBe('jekyll');
});
it('Detect Hugo #3', async () => {
const fs = new VirtualFilesystem({
'config.toml': 'baseURL = "http://example.org/"',
'content/post.md': '# hello world',
});
it('Detect Middleman', async () => {
const fs = new VirtualFilesystem({
'config.rb': 'config',
expect(await detectFramework({ fs, frameworkList })).toBe('hugo');
});
expect(await detectFramework({ fs, frameworkList })).toBe('middleman');
});
it('Detect Jekyll', async () => {
const fs = new VirtualFilesystem({
'_config.yml': 'config',
});
it('Detect Scully', async () => {
const fs = new VirtualFilesystem({
'package.json': JSON.stringify({
dependencies: {
'@angular/cli': 'latest',
'@scullyio/init': 'latest',
},
}),
expect(await detectFramework({ fs, frameworkList })).toBe('jekyll');
});
expect(await detectFramework({ fs, frameworkList })).toBe('scully');
});
it('Detect Middleman', async () => {
const fs = new VirtualFilesystem({
'config.rb': 'config',
});
it('Detect Zola', async () => {
const fs = new VirtualFilesystem({
'config.toml': 'base_url = "/"',
expect(await detectFramework({ fs, frameworkList })).toBe('middleman');
});
expect(await detectFramework({ fs, frameworkList })).toBe('zola');
it('Detect Scully', async () => {
const fs = new VirtualFilesystem({
'package.json': JSON.stringify({
dependencies: {
'@angular/cli': 'latest',
'@scullyio/init': 'latest',
},
}),
});
expect(await detectFramework({ fs, frameworkList })).toBe('scully');
});
it('Detect Zola', async () => {
const fs = new VirtualFilesystem({
'config.toml': 'base_url = "/"',
});
expect(await detectFramework({ fs, frameworkList })).toBe('zola');
});
});
});

View File

@@ -85,7 +85,7 @@ describe('Test `getEnvForPackageManager()`', () => {
},
},
{
name: 'should set path if pnpm 7+ is detected and Node version is greater than 12',
name: 'should set path if pnpm 7+ is detected',
args: {
cliType: 'pnpm',
nodeVersion: { major: 16, range: '16.x', runtime: 'nodejs16.x' },
@@ -97,21 +97,7 @@ describe('Test `getEnvForPackageManager()`', () => {
},
want: {
FOO: 'bar',
PATH: '/pnpm7/pnpm:foo',
},
},
{
name: 'should not set path if pnpm 7+ is detected and Node version is less than or equal to 12',
args: {
cliType: 'pnpm',
nodeVersion: { major: 12, range: '12.x', runtime: 'nodejs12.x' },
lockfileVersion: 5.4,
env: {
FOO: 'bar',
},
},
want: {
FOO: 'bar',
PATH: '/pnpm7/node_modules/.bin:foo',
},
},
{

View File

@@ -176,9 +176,13 @@ it('should only match supported node versions, otherwise throw an error', async
'major',
14
);
expect(await getSupportedNodeVersion('16.x', false)).toHaveProperty(
'major',
16
);
const autoMessage =
'Please set Node.js Version to 14.x in your Project Settings to use Node.js 14.';
'Please set Node.js Version to 16.x in your Project Settings to use Node.js 16.';
await expectBuilderError(
getSupportedNodeVersion('8.11.x', true),
autoMessage
@@ -196,9 +200,13 @@ it('should only match supported node versions, otherwise throw an error', async
'major',
14
);
expect(await getSupportedNodeVersion('16.x', true)).toHaveProperty(
'major',
16
);
const foundMessage =
'Please set "engines": { "node": "14.x" } in your `package.json` file to use Node.js 14.';
'Please set "engines": { "node": "16.x" } in your `package.json` file to use Node.js 16.';
await expectBuilderError(
getSupportedNodeVersion('8.11.x', false),
foundMessage
@@ -219,8 +227,8 @@ it('should match all semver ranges', async () => {
// See https://docs.npmjs.com/files/package.json#engines
expect(await getSupportedNodeVersion('12.0.0')).toHaveProperty('major', 12);
expect(await getSupportedNodeVersion('12.x')).toHaveProperty('major', 12);
expect(await getSupportedNodeVersion('>=10')).toHaveProperty('major', 14);
expect(await getSupportedNodeVersion('>=10.3.0')).toHaveProperty('major', 14);
expect(await getSupportedNodeVersion('>=10')).toHaveProperty('major', 16);
expect(await getSupportedNodeVersion('>=10.3.0')).toHaveProperty('major', 16);
expect(await getSupportedNodeVersion('11.5.0 - 12.5.0')).toHaveProperty(
'major',
12
@@ -231,6 +239,10 @@ it('should match all semver ranges', async () => {
);
expect(await getSupportedNodeVersion('~12.5.0')).toHaveProperty('major', 12);
expect(await getSupportedNodeVersion('^12.5.0')).toHaveProperty('major', 12);
expect(await getSupportedNodeVersion('12.5.0 - 14.5.0')).toHaveProperty(
'major',
14
);
});
it('should ignore node version in vercel dev getNodeVersion()', async () => {
@@ -246,8 +258,8 @@ it('should ignore node version in vercel dev getNodeVersion()', async () => {
it('should select project setting from config when no package.json is found', async () => {
expect(
await getNodeVersion('/tmp', undefined, { nodeVersion: '14.x' }, {})
).toHaveProperty('range', '14.x');
await getNodeVersion('/tmp', undefined, { nodeVersion: '16.x' }, {})
).toHaveProperty('range', '16.x');
expect(warningMessages).toStrictEqual([]);
});
@@ -277,34 +289,27 @@ it('should not warn when package.json engines matches project setting from confi
expect(warningMessages).toStrictEqual([]);
});
it('should select nodejs16.x with ENABLE_EXPERIMENTAL_NODE16', async () => {
process.env.ENABLE_EXPERIMENTAL_NODE16 = '1';
const result = await getNodeVersion('/tmp', undefined, {}, {});
delete process.env.ENABLE_EXPERIMENTAL_NODE16;
expect(result).toEqual({ major: 16, range: '16.x', runtime: 'nodejs16.x' });
expect(warningMessages).toStrictEqual([
'Warning: Using experimental Node.js 16.x due to ENABLE_EXPERIMENTAL_NODE16=1',
]);
});
it('should get latest node version', async () => {
expect(getLatestNodeVersion()).toHaveProperty('major', 14);
expect(getLatestNodeVersion()).toHaveProperty('major', 16);
});
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('2021-05-01').getTime();
global.Date.now = () => new Date('2022-09-01').getTime();
expect(getSupportedNodeVersion('8.10.x', false)).rejects.toThrow();
expect(getSupportedNodeVersion('8.10.x', true)).rejects.toThrow();
expect(getSupportedNodeVersion('10.x', false)).rejects.toThrow();
expect(getSupportedNodeVersion('10.x', true)).rejects.toThrow();
expect(getSupportedNodeVersion('12.x', false)).rejects.toThrow();
expect(getSupportedNodeVersion('12.x', true)).rejects.toThrow();
const discontinued = getDiscontinuedNodeVersions();
expect(discontinued.length).toBe(2);
expect(discontinued[0]).toHaveProperty('range', '10.x');
expect(discontinued[1]).toHaveProperty('range', '8.10.x');
expect(discontinued.length).toBe(3);
expect(discontinued[0]).toHaveProperty('range', '12.x');
expect(discontinued[1]).toHaveProperty('range', '10.x');
expect(discontinued[2]).toHaveProperty('range', '8.10.x');
global.Date.now = realDateNow;
});
@@ -322,9 +327,19 @@ it('should warn for deprecated versions, soon to be discontinued', async () => {
'major',
10
);
expect(await getSupportedNodeVersion('12.x', false)).toHaveProperty(
'major',
12
);
expect(await getSupportedNodeVersion('12.x', true)).toHaveProperty(
'major',
12
);
expect(warningMessages).toStrictEqual([
'Error: Node.js version 10.x is deprecated. Deployments created on or after 2021-04-20 will fail to build. Please set "engines": { "node": "14.x" } in your `package.json` file to use Node.js 14. This change is the result of a decision made by an upstream infrastructure provider (AWS).',
'Error: Node.js version 10.x is deprecated. Deployments created on or after 2021-04-20 will fail to build. Please set Node.js Version to 14.x in your Project Settings to use Node.js 14. This change is the result of a decision made by an upstream infrastructure provider (AWS).',
'Error: Node.js version 10.x is deprecated. 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. This change is the result of a decision made by an upstream infrastructure provider (AWS).',
'Error: Node.js version 10.x is deprecated. 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. This change is the result of a decision made by an upstream infrastructure provider (AWS).',
'Error: Node.js version 12.x is deprecated. 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. This change is the result of a decision made by an upstream infrastructure provider (AWS).',
'Error: Node.js version 12.x is deprecated. 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. This change is the result of a decision made by an upstream infrastructure provider (AWS).',
]);
global.Date.now = realDateNow;

View File

@@ -2,8 +2,9 @@ import { promises } from 'fs';
import path from 'path';
import { DetectorFilesystem } from '../../src';
import { Stat } from '../../src/detectors/filesystem';
const { stat, readFile } = promises;
const { stat, readFile, readdir } = promises;
export class FixtureFilesystem extends DetectorFilesystem {
private rootPath: string;
@@ -32,4 +33,19 @@ export class FixtureFilesystem extends DetectorFilesystem {
const filePath = path.join(this.rootPath, name);
return (await stat(filePath)).isFile();
}
async _readdir(name: string): Promise<Stat[]> {
const dirPath = path.join(this.rootPath, name);
const files = await readdir(dirPath, { withFileTypes: true });
return files.map(file => ({
name: file.name,
type: file.isFile() ? 'file' : 'dir',
path: path.join(name, file.name),
}));
}
_chdir(name: string): DetectorFilesystem {
return new FixtureFilesystem(path.join(this.rootPath, name));
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "24.2.1-canary.2",
"version": "24.2.4-canary.0",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -12,8 +12,8 @@
},
"scripts": {
"preinstall": "node ./scripts/preinstall.js",
"test": "jest",
"test-unit": "jest --coverage --verbose",
"test": "jest --env node --verbose --runInBand --bail",
"test-unit": "yarn test",
"test-integration-cli": "rimraf test/fixtures/integration && ava test/integration.js --serial --fail-fast --verbose",
"test-integration-dev": "ava test/dev/integration.js --serial --fail-fast --verbose",
"prepublishOnly": "yarn build",
@@ -43,11 +43,11 @@
"node": ">= 12"
},
"dependencies": {
"@vercel/build-utils": "2.16.1-canary.2",
"@vercel/go": "1.4.1-canary.2",
"@vercel/node": "1.15.1-canary.2",
"@vercel/python": "2.3.1-canary.2",
"@vercel/ruby": "1.3.4-canary.2",
"@vercel/build-utils": "3.0.2-canary.0",
"@vercel/go": "1.4.3-canary.0",
"@vercel/node": "1.15.3-canary.0",
"@vercel/python": "2.3.3-canary.0",
"@vercel/ruby": "1.3.6-canary.0",
"update-notifier": "4.1.0"
},
"devDependencies": {
@@ -90,8 +90,8 @@
"@types/update-notifier": "5.1.0",
"@types/which": "1.3.2",
"@types/write-json-file": "2.2.1",
"@vercel/client": "11.0.1-canary.2",
"@vercel/frameworks": "0.8.1-canary.0",
"@vercel/client": "11.0.3-canary.0",
"@vercel/frameworks": "0.9.1-canary.0",
"@vercel/ncc": "0.24.0",
"@zeit/fun": "0.11.2",
"@zeit/source-map-support": "0.6.2",

View File

@@ -13,7 +13,7 @@ async function createConstants() {
export const GA_TRACKING_ID: string | undefined = ${envToString(
'GA_TRACKING_ID'
)};
export const SENTRY_DSN: string | undefined = ${envToString('SENTRY_DSN')};
export const SENTRY_DSN: string | undefined = ${envToString('SENTRY_DSN')};
`;
await writeFile(filename, contents, 'utf8');
}

View File

@@ -91,7 +91,7 @@ export default async function dev(
}
[{ envs: projectEnvs }, { systemEnvValues }] = await Promise.all([
getDecryptedEnvRecords(output, client, project.id),
getDecryptedEnvRecords(output, client, project.id, 'vercel-cli:dev'),
project.autoExposeSystemEnvs
? getSystemEnvValues(output, client, project.id)
: { systemEnvValues: [] },

View File

@@ -79,7 +79,12 @@ export default async function add(
}
}
const { envs } = await getEnvRecords(output, client, project.id);
const { envs } = await getEnvRecords(
output,
client,
project.id,
'vercel-cli:env:add'
);
const existing = new Set(
envs.filter(r => r.key === envName).map(r => r.target)
);

View File

@@ -147,7 +147,8 @@ export default async function main(client: Client) {
argv,
args,
output,
cwd
cwd,
'vercel-cli:env:pull'
);
default:
output.error(getInvalidSubcommand(COMMAND_CONFIG));

View File

@@ -48,10 +48,16 @@ export default async function ls(
const lsStamp = stamp();
const { envs } = await getEnvRecords(output, client, project.id, {
target: envTarget,
gitBranch: envGitBranch,
});
const { envs } = await getEnvRecords(
output,
client,
project.id,
'vercel-cli:env:ls',
{
target: envTarget,
gitBranch: envGitBranch,
}
);
if (envs.length === 0) {
output.log(

View File

@@ -13,6 +13,7 @@ import { Output } from '../../util/output';
import param from '../../util/output/param';
import stamp from '../../util/output/stamp';
import { getCommandName } from '../../util/pkg-name';
import { EnvRecordsSource } from '../../util/env/get-env-records';
const CONTENTS_PREFIX = '# Created by Vercel CLI\n';
@@ -49,7 +50,8 @@ export default async function pull(
opts: Partial<Options>,
args: string[],
output: Output,
cwd: string
cwd: string,
source: Extract<EnvRecordsSource, 'vercel-cli:env:pull' | 'vercel-cli:pull'>
) {
if (args.length > 1) {
output.error(
@@ -90,7 +92,7 @@ export default async function pull(
output.spinner('Downloading');
const [{ envs: projectEnvs }, { systemEnvValues }] = await Promise.all([
getDecryptedEnvRecords(output, client, project.id, environment),
getDecryptedEnvRecords(output, client, project.id, source, environment),
project.autoExposeSystemEnvs
? getSystemEnvValues(output, client, project.id)
: { systemEnvValues: [] },

View File

@@ -67,10 +67,16 @@ export default async function rm(
return 1;
}
const result = await getEnvRecords(output, client, project.id, {
target: envTarget,
gitBranch: envGitBranch,
});
const result = await getEnvRecords(
output,
client,
project.id,
'vercel-cli:env:rm',
{
target: envTarget,
gitBranch: envGitBranch,
}
);
let envs = result.envs.filter(env => env.key === envName);

View File

@@ -131,7 +131,8 @@ async function pullAllEnvFiles(
argv,
[join('.vercel', environmentFile)],
client.output,
cwd
cwd,
'vercel-cli:pull'
);
}

View File

@@ -3,10 +3,20 @@ import Client from '../client';
import { ProjectEnvVariable, ProjectEnvTarget } from '../../types';
import { URLSearchParams } from 'url';
/** The CLI command that was used that needs the environment variables. */
export type EnvRecordsSource =
| 'vercel-cli:env:ls'
| 'vercel-cli:env:add'
| 'vercel-cli:env:rm'
| 'vercel-cli:env:pull'
| 'vercel-cli:dev'
| 'vercel-cli:pull';
export default async function getEnvRecords(
output: Output,
client: Client,
projectId: string,
source: EnvRecordsSource,
{
target,
gitBranch,
@@ -31,6 +41,9 @@ export default async function getEnvRecords(
if (decrypt) {
query.set('decrypt', decrypt.toString());
}
if (source) {
query.set('source', source);
}
const url = `/v8/projects/${projectId}/env?${query}`;

View File

@@ -6,15 +6,16 @@ import {
ProjectEnvVariable,
Secret,
} from '../types';
import getEnvRecords from './env/get-env-records';
import getEnvRecords, { EnvRecordsSource } from './env/get-env-records';
export default async function getDecryptedEnvRecords(
output: Output,
client: Client,
projectId: string,
source: EnvRecordsSource,
target?: ProjectEnvTarget
): Promise<{ envs: ProjectEnvVariable[] }> {
const { envs } = await getEnvRecords(output, client, projectId, {
const { envs } = await getEnvRecords(output, client, projectId, source, {
target: target || ProjectEnvTarget.Development,
decrypt: true,
});

View File

@@ -8,7 +8,7 @@ import { promisify } from 'util';
import getProjectByIdOrName from '../projects/get-project-by-id-or-name';
import Client from '../client';
import { ProjectNotFound } from '../errors-ts';
import { InvalidToken, ProjectNotFound } from '../errors-ts';
import getUser from '../get-user';
import getTeamById from '../teams/get-team-by-id';
import { Output } from '../output';
@@ -150,15 +150,20 @@ export async function getLinkedProject(
getProjectByIdOrName(client, link.projectId, link.orgId),
]);
} catch (err) {
if (err?.status === 403) {
if (err && err.status === 403) {
output.stopSpinner();
throw new NowBuildError({
message: `Could not retrieve Project Settings. To link your Project, remove the ${outputCode(
VERCEL_DIR
)} directory and deploy again.`,
code: 'PROJECT_UNAUTHORIZED',
link: 'https://vercel.link/cannot-load-project-settings',
});
if (err.missingToken) {
throw new InvalidToken();
} else {
throw new NowBuildError({
message: `Could not retrieve Project Settings. To link your Project, remove the ${outputCode(
VERCEL_DIR
)} directory and deploy again.`,
code: 'PROJECT_UNAUTHORIZED',
link: 'https://vercel.link/cannot-load-project-settings',
});
}
}
// Not a special case 403, we should still throw it

View File

@@ -3495,6 +3495,24 @@ test('reject deploying with wrong team .vercel config', async t => {
);
});
test('reject deploying with invalid token', async t => {
const directory = fixture('unauthorized-vercel-config');
const { exitCode, stderr, stdout } = await execa(
binaryPath,
[...defaultArgs, '--confirm'],
{
cwd: directory,
reject: false,
}
);
t.is(exitCode, 1, formatOutput({ stderr, stdout }));
t.regex(
stderr,
/Error! Could not retrieve Project Settings\. To link your Project, remove the `\.vercel` directory and deploy again\./g
);
});
test('[vc link] should show prompts to set up project', async t => {
const dir = fixture('project-link-zeroconf');
const projectName = `project-link-zeroconf-${

View File

@@ -1,7 +1,16 @@
import chance from 'chance';
import { client } from './client';
export function useTeams(teamId?: string) {
export function useTeams(
teamId?: string,
options: {
failMissingToken?: boolean;
failNoAccess?: boolean;
} = {
failMissingToken: false,
failNoAccess: false,
}
) {
const id = teamId || chance().guid();
const teams = [
{
@@ -16,6 +25,25 @@ export function useTeams(teamId?: string) {
for (let team of teams) {
client.scenario.get(`/teams/${team.id}`, (_req, res) => {
if (options.failMissingToken) {
res.statusCode = 403;
res.json({
message: 'The request is missing an authentication token',
code: 'forbidden',
missingToken: true,
});
return;
}
if (options.failNoAccess) {
res.statusCode = 403;
res.send({
code: 'team_unauthorized',
message: 'You are not authorized',
});
return;
}
res.json(team);
});
}

View File

@@ -1,5 +1,7 @@
import parseListen from '../../../src/util/dev/parse-listen';
const IS_WINDOWS = process.platform === 'win32';
describe('parseListen', () => {
it('should parse "0" as port 0', () => {
const result = parseListen('0');
@@ -34,19 +36,27 @@ describe('parseListen', () => {
expect(result[1]).toEqual('127.0.0.1');
});
if (process.platform !== 'win32') {
it('should parse "unix:/home/user/server.sock" as UNIX socket file', () => {
const result = parseListen('unix:/home/user/server.sock');
expect(result).toHaveLength(1);
expect(result[0]).toEqual('/home/user/server.sock');
});
it('should parse "unix:/home/user/server.sock" as UNIX socket file', () => {
if (IS_WINDOWS) {
console.log('Skipping this test on Windows.');
return;
}
it('should parse "pipe:\\\\.\\pipe\\PipeName" as UNIX pipe', () => {
const result = parseListen('pipe:\\\\.\\pipe\\PipeName');
expect(result).toHaveLength(1);
expect(result[0]).toEqual('\\\\.\\pipe\\PipeName');
});
}
const result = parseListen('unix:/home/user/server.sock');
expect(result).toHaveLength(1);
expect(result[0]).toEqual('/home/user/server.sock');
});
it('should parse "pipe:\\\\.\\pipe\\PipeName" as UNIX pipe', () => {
if (IS_WINDOWS) {
console.log('Skipping this test on Windows.');
return;
}
const result = parseListen('pipe:\\\\.\\pipe\\PipeName');
expect(result).toHaveLength(1);
expect(result[0]).toEqual('\\\\.\\pipe\\PipeName');
});
it('should fail to parse "bad://url"', () => {
let err: Error;

View File

@@ -9,6 +9,9 @@ import { createServer } from 'http';
import { client } from '../../mocks/client';
import DevServer from '../../../src/util/dev/server';
const IS_MAC_OS = process.platform === 'darwin';
const IS_WINDOWS = process.platform === 'win32';
async function runNpmInstall(fixturePath: string) {
if (await fs.pathExists(path.join(fixturePath, 'package.json'))) {
return execa('yarn', ['install', '--network-timeout', '1000000'], {
@@ -19,7 +22,17 @@ async function runNpmInstall(fixturePath: string) {
}
const testFixture =
(name: string, fn: (server: DevServer) => Promise<void>) => async () => {
(
name: string,
fn: (server: DevServer) => Promise<void>,
options: { skip: boolean } = { skip: false }
) =>
async () => {
if (options.skip) {
console.log('Skipping this test for this platform.');
return;
}
let server: DevServer | null = null;
const fixturePath = path.join(__dirname, '../../fixtures/unit', name);
await runNpmInstall(fixturePath);
@@ -125,23 +138,29 @@ describe('DevServer', () => {
it(
'should maintain query when builder defines routes',
testFixture('now-dev-next', async server => {
const res = await fetch(`${server.address}/something?url-param=a`);
validateResponseHeaders(res);
testFixture(
'now-dev-next',
async server => {
const res = await fetch(`${server.address}/something?url-param=a`);
validateResponseHeaders(res);
const text = await res.text();
const text = await res.text();
// Hacky way of getting the page payload from the response
// HTML since we don't have a HTML parser handy.
const json = text
.match(/<div>(.*)<\/div>/)![1]
.replace('</div>', '')
.replace(/&quot;/g, '"');
const parsed = JSON.parse(json);
// Hacky way of getting the page payload from the response
// HTML since we don't have a HTML parser handy.
const json = text
.match(/<div>(.*)<\/div>/)![1]
.replace('</div>', '')
.replace(/&quot;/g, '"');
const parsed = JSON.parse(json);
expect(parsed.query['url-param']).toEqual('a');
expect(parsed.query['route-param']).toEqual('b');
})
expect(parsed.query['url-param']).toEqual('a');
expect(parsed.query['route-param']).toEqual('b');
},
{
skip: IS_MAC_OS,
}
)
);
it(
@@ -158,10 +177,9 @@ describe('DevServer', () => {
'should send `etag` header for static files',
testFixture('now-dev-headers', async server => {
const res = await fetch(`${server.address}/foo.txt`);
const expected =
process.platform === 'win32'
? '9dc423ab77c2e0446cd355256efff2ea1be27cbf'
: 'd263af8ab880c0b97eb6c5c125b5d44f9e5addd9';
const expected = IS_WINDOWS
? '9dc423ab77c2e0446cd355256efff2ea1be27cbf'
: 'd263af8ab880c0b97eb6c5c125b5d44f9e5addd9';
expect(res.headers.get('etag')).toEqual(`"${expected}"`);
const body = await res.text();
expect(body.trim()).toEqual('hi');
@@ -170,30 +188,37 @@ describe('DevServer', () => {
it(
'should support default builds and routes',
testFixture('now-dev-default-builds-and-routes', async server => {
let podId: string;
testFixture(
'now-dev-default-builds-and-routes',
async server => {
let podId: string;
let res = await fetch(`${server.address}/`);
validateResponseHeaders(res);
podId = res.headers.get('x-vercel-id')!.match(/:(\w+)-/)![1];
let body = await res.text();
expect(body.includes('hello, this is the frontend')).toBeTruthy();
let res = await fetch(`${server.address}/`);
validateResponseHeaders(res);
podId = res.headers.get('x-vercel-id')!.match(/:(\w+)-/)![1];
let body = await res.text();
expect(body.includes('hello, this is the frontend')).toBeTruthy();
res = await fetch(`${server.address}/api/users`);
validateResponseHeaders(res, podId);
body = await res.text();
expect(body).toEqual('users');
res = await fetch(`${server.address}/api/users`);
validateResponseHeaders(res, podId);
body = await res.text();
expect(body).toEqual('users');
res = await fetch(`${server.address}/api/users/1`);
validateResponseHeaders(res, podId);
body = await res.text();
expect(body).toEqual('users/1');
res = await fetch(`${server.address}/api/users/1`);
validateResponseHeaders(res, podId);
body = await res.text();
expect(body).toEqual('users/1');
res = await fetch(`${server.address}/api/welcome`);
validateResponseHeaders(res, podId);
body = await res.text();
expect(body).toEqual('hello and welcome');
})
res = await fetch(`${server.address}/api/welcome`);
validateResponseHeaders(res, podId);
body = await res.text();
expect(body).toEqual('hello and welcome');
},
{
// this test very often times out in the testFixture startup logic on Mac
skip: IS_MAC_OS,
}
)
);
it(
@@ -206,22 +231,26 @@ describe('DevServer', () => {
})
);
if (process.platform !== 'win32') {
// This test is currently failing on Windows, so skip for now:
// > Creating initial build
// $ serve -l $PORT src
// 'serve' is not recognized as an internal or external command,
// https://github.com/vercel/vercel/pull/6638/checks?check_run_id=3449662836
it(
'should support `@vercel/static-build` routing',
testFixture('now-dev-static-build-routing', async server => {
it(
'should support `@vercel/static-build` routing',
testFixture(
'now-dev-static-build-routing',
async server => {
const res = await fetch(`${server.address}/api/date`);
expect(res.status).toEqual(200);
const body = await res.text();
expect(body.startsWith('The current date:')).toBeTruthy();
})
);
}
},
{
// This test is currently failing on Windows, so skip for now:
// > Creating initial build
// $ serve -l $PORT src
// 'serve' is not recognized as an internal or external command,
// https://github.com/vercel/vercel/pull/6638/checks?check_run_id=3449662836
skip: IS_WINDOWS,
}
)
);
it(
'should support directory listing',

View File

@@ -0,0 +1,71 @@
import { join } from 'path';
import { getLinkedProject } from '../../../src/util/projects/link';
import { client } from '../../mocks/client';
import { defaultProject, useProject } from '../../mocks/project';
import { useTeams } from '../../mocks/team';
import { useUser } from '../../mocks/user';
type UnPromisify<T> = T extends Promise<infer U> ? U : T;
const fixture = (name: string) => join(__dirname, '../../fixtures/unit', name);
describe('getLinkedProject', () => {
it('should fail to return a link when token is missing', async () => {
const cwd = fixture('vercel-pull-next');
useUser();
useTeams('team_dummy', { failMissingToken: true });
useProject({
...defaultProject,
id: 'vercel-pull-next',
name: 'vercel-pull-next',
});
let link: UnPromisify<ReturnType<typeof getLinkedProject>> | undefined;
let error: Error | undefined;
try {
link = await getLinkedProject(client, cwd);
} catch (err) {
error = err;
}
expect(link).toBeUndefined();
if (!error) {
throw new Error(`Expected an error to be thrown.`);
}
expect(error.message).toBe(
'The specified token is not valid. Use `vercel login` to generate a new token.'
);
});
it('should fail to return a link when no access to team', async () => {
const cwd = fixture('vercel-pull-next');
useUser();
useTeams('team_dummy', { failNoAccess: true });
useProject({
...defaultProject,
id: 'vercel-pull-next',
name: 'vercel-pull-next',
});
let link: UnPromisify<ReturnType<typeof getLinkedProject>> | undefined;
let error: Error | undefined;
try {
link = await getLinkedProject(client, cwd);
} catch (err) {
error = err;
}
expect(link).toBeUndefined();
if (!error) {
throw new Error(`Expected an error to be thrown.`);
}
expect(error.message).toBe(
'Could not retrieve Project Settings. To link your Project, remove the `.vercel` directory and deploy again.'
);
});
});

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/client",
"version": "11.0.1-canary.2",
"version": "11.0.3-canary.0",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"homepage": "https://vercel.com",
@@ -15,8 +15,9 @@
},
"scripts": {
"build": "tsc",
"test-integration-once": "jest --verbose --runInBand --bail tests/create-deployment.test.ts tests/create-legacy-deployment.test.ts tests/paths.test.ts",
"test-unit": "jest --verbose --runInBand --bail tests/unit.*test.*"
"test-integration-once": "yarn test tests/create-deployment.test.ts tests/create-legacy-deployment.test.ts tests/paths.test.ts",
"test": "jest --env node --verbose --runInBand --bail",
"test-unit": "yarn test tests/unit.*test.*"
},
"engines": {
"node": ">= 12"
@@ -41,7 +42,7 @@
]
},
"dependencies": {
"@vercel/build-utils": "2.16.1-canary.2",
"@vercel/build-utils": "3.0.2-canary.0",
"@zeit/fetch": "5.2.0",
"async-retry": "1.2.3",
"async-sema": "3.0.0",

View File

@@ -0,0 +1,15 @@
<svg width="1281" height="1280" viewBox="0 0 1281 1280" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M815.931 94.6439C825.65 106.709 830.606 122.99 840.519 155.553L1057.06 866.901C976.999 825.368 889.964 795.413 798.174 779.252L657.182 302.798C654.875 295.002 647.715 289.654 639.585 289.654C631.434 289.654 624.26 295.03 621.972 302.853L482.688 779.011C390.471 795.1 303.038 825.109 222.634 866.793L440.24 155.388L440.24 155.388C450.183 122.882 455.154 106.629 464.874 94.5853C473.455 83.9531 484.616 75.6958 497.293 70.6002C511.652 64.8284 528.649 64.8284 562.642 64.8284H718.067C752.104 64.8284 769.123 64.8284 783.496 70.6123C796.184 75.7184 807.352 83.9923 815.931 94.6439Z" fill="url(#paint0_linear_709_106)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M841.843 900.754C806.146 931.279 734.895 952.097 652.822 952.097C552.089 952.097 467.659 920.737 445.256 878.561C437.247 902.732 435.45 930.396 435.45 948.068C435.45 948.068 430.173 1034.84 490.528 1095.2C490.528 1063.86 515.934 1038.46 547.273 1038.46C600.989 1038.46 600.929 1085.32 600.88 1123.34C600.878 1124.48 600.877 1125.61 600.877 1126.73C600.877 1184.44 636.147 1233.91 686.308 1254.77C678.816 1239.36 674.613 1222.05 674.613 1203.77C674.613 1148.73 706.926 1128.23 744.48 1104.41L744.481 1104.41C774.361 1085.46 807.56 1064.41 830.44 1022.17C842.379 1000.13 849.158 974.893 849.158 948.068C849.158 931.573 846.594 915.676 841.843 900.754Z" fill="#FF5D01"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M841.843 900.754C806.146 931.279 734.895 952.097 652.822 952.097C552.089 952.097 467.659 920.737 445.256 878.561C437.247 902.732 435.45 930.396 435.45 948.068C435.45 948.068 430.173 1034.84 490.528 1095.2C490.528 1063.86 515.934 1038.46 547.273 1038.46C600.989 1038.46 600.929 1085.32 600.88 1123.34C600.878 1124.48 600.877 1125.61 600.877 1126.73C600.877 1184.44 636.147 1233.91 686.308 1254.77C678.816 1239.36 674.613 1222.05 674.613 1203.77C674.613 1148.73 706.926 1128.23 744.48 1104.41L744.481 1104.41C774.361 1085.46 807.56 1064.41 830.44 1022.17C842.379 1000.13 849.158 974.893 849.158 948.068C849.158 931.573 846.594 915.676 841.843 900.754Z" fill="url(#paint1_linear_709_106)"/>
<defs>
<linearGradient id="paint0_linear_709_106" x1="883.889" y1="27.1132" x2="639.848" y2="866.902" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="#F9FAFB"/>
</linearGradient>
<linearGradient id="paint1_linear_709_106" x1="1002.57" y1="652.45" x2="791.219" y2="1094.91" gradientUnits="userSpaceOnUse">
<stop stop-color="#FF1639"/>
<stop offset="1" stop-color="#FF1639" stop-opacity="0"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,15 @@
<svg width="1280" height="1280" viewBox="0 0 1280 1280" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M815.039 94.6439C824.758 106.709 829.714 122.99 839.626 155.553L1056.17 866.901C976.107 825.368 889.072 795.413 797.281 779.252L656.29 302.798C653.983 295.002 646.822 289.654 638.693 289.654C630.542 289.654 623.368 295.03 621.08 302.853L481.795 779.011C389.579 795.1 302.146 825.109 221.741 866.793L439.347 155.388L439.348 155.388C449.291 122.882 454.262 106.629 463.982 94.5853C472.562 83.9531 483.723 75.6958 496.4 70.6002C510.76 64.8284 527.756 64.8284 561.749 64.8284H717.174C751.212 64.8284 768.23 64.8284 782.603 70.6123C795.292 75.7184 806.459 83.9923 815.039 94.6439Z" fill="url(#paint0_linear_709_110)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M840.951 900.754C805.253 931.279 734.002 952.097 651.929 952.097C551.197 952.097 466.767 920.737 444.363 878.561C436.354 902.732 434.558 930.396 434.558 948.068C434.558 948.068 429.281 1034.84 489.636 1095.2C489.636 1063.86 515.042 1038.46 546.381 1038.46C600.097 1038.46 600.036 1085.32 599.987 1123.34C599.986 1124.48 599.984 1125.61 599.984 1126.73C599.984 1184.44 635.255 1233.91 685.416 1254.77C677.924 1239.36 673.721 1222.05 673.721 1203.77C673.721 1148.73 706.034 1128.23 743.588 1104.41L743.588 1104.41C773.469 1085.46 806.668 1064.41 829.548 1022.17C841.486 1000.13 848.265 974.893 848.265 948.068C848.265 931.573 845.702 915.676 840.951 900.754Z" fill="#FF5D01"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M840.951 900.754C805.253 931.279 734.002 952.097 651.929 952.097C551.197 952.097 466.767 920.737 444.363 878.561C436.354 902.732 434.558 930.396 434.558 948.068C434.558 948.068 429.281 1034.84 489.636 1095.2C489.636 1063.86 515.042 1038.46 546.381 1038.46C600.097 1038.46 600.036 1085.32 599.987 1123.34C599.986 1124.48 599.984 1125.61 599.984 1126.73C599.984 1184.44 635.255 1233.91 685.416 1254.77C677.924 1239.36 673.721 1222.05 673.721 1203.77C673.721 1148.73 706.034 1128.23 743.588 1104.41L743.588 1104.41C773.469 1085.46 806.668 1064.41 829.548 1022.17C841.486 1000.13 848.265 974.893 848.265 948.068C848.265 931.573 845.702 915.676 840.951 900.754Z" fill="url(#paint1_linear_709_110)"/>
<defs>
<linearGradient id="paint0_linear_709_110" x1="882.997" y1="27.1132" x2="638.955" y2="866.902" gradientUnits="userSpaceOnUse">
<stop stop-color="#000014"/>
<stop offset="1" stop-color="#150426"/>
</linearGradient>
<linearGradient id="paint1_linear_709_110" x1="1001.68" y1="652.45" x2="790.326" y2="1094.91" gradientUnits="userSpaceOnUse">
<stop stop-color="#FF1639"/>
<stop offset="1" stop-color="#FF1639" stop-opacity="0"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/frameworks",
"version": "0.8.1-canary.0",
"version": "0.9.1-canary.0",
"main": "./dist/frameworks.js",
"types": "./dist/frameworks.d.ts",
"files": [
@@ -9,7 +9,8 @@
"license": "UNLICENSED",
"scripts": {
"build": "tsc",
"test-unit": "jest --env node --verbose --runInBand --bail"
"test": "jest --env node --verbose --runInBand --bail",
"test-unit": "yarn test"
},
"dependencies": {
"@iarna/toml": "2.2.3",
@@ -20,7 +21,7 @@
"@types/js-yaml": "3.12.1",
"@types/node": "12.0.4",
"@types/node-fetch": "2.5.8",
"@vercel/routing-utils": "1.13.2",
"@vercel/routing-utils": "1.13.3-canary.0",
"ajv": "6.12.2",
"typescript": "4.3.4"
}

View File

@@ -60,7 +60,8 @@ export const frameworks = [
slug: 'nextjs',
demo: 'https://nextjs-template.vercel.app',
logo: 'https://raw.githubusercontent.com/vercel/vercel/main/packages/frameworks/logos/next.svg',
darkModeLogo: 'https://raw.githubusercontent.com/vercel/vercel/main/packages/frameworks/logos/next-dark.svg',
darkModeLogo:
'https://raw.githubusercontent.com/vercel/vercel/main/packages/frameworks/logos/next-dark.svg',
screenshot:
'https://assets.vercel.com/image/upload/v1647366075/front/import/nextjs.png',
tagline:
@@ -260,6 +261,73 @@ export const frameworks = [
},
],
},
{
name: 'Astro',
slug: 'astro',
demo: 'https://astro-template.vercel.app',
logo: 'https://raw.githubusercontent.com/vercel/vercel/main/packages/frameworks/logos/astro.svg',
darkModeLogo:
'https://raw.githubusercontent.com/vercel/vercel/main/packages/frameworks/logos/astro-dark.svg',
tagline:
'Astro is a new kind of static site builder for the modern web. Powerful developer experience meets lightweight output.',
description: 'An Astro site, using the basics starter kit.',
website: 'https://astro.build',
envPrefix: 'PUBLIC_',
detectors: {
every: [
{
path: 'package.json',
matchContent:
'"(dev)?(d|D)ependencies":\\s*{[^}]*"astro":\\s*".+?"[^}]*}',
},
],
},
settings: {
installCommand: {
placeholder: '`yarn install` or `npm install`',
},
buildCommand: {
value: 'astro build',
placeholder: '`npm run build` or `astro build`',
},
devCommand: {
value: 'astro dev --port $PORT',
placeholder: 'astro dev',
},
outputDirectory: {
value: 'dist',
},
},
dependency: 'astro',
getFsOutputDir: async () => 'dist',
getOutputDirName: async () => 'dist',
defaultRoutes: [
{
src: '^/dist/(.*)$',
headers: { 'cache-control': 'public, max-age=31536000, immutable' },
continue: true,
},
{
handle: 'filesystem',
},
{
src: '/(.*)',
dest: '/index.html',
},
],
defaultHeaders: [
{
source: '^/dist/(.*)$',
regex: '^/dist/(.*)$',
headers: [
{
key: 'cache-control',
value: 'public, max-age=31536000, immutable',
},
],
},
],
},
{
name: 'Hexo',
slug: 'hexo',

View File

@@ -7,6 +7,9 @@ import fetch from 'node-fetch';
import { URL, URLSearchParams } from 'url';
import frameworkList from '../src/frameworks';
// bump timeout for Windows as network can be slower
jest.setTimeout(15 * 1000);
const SchemaFrameworkDetectionItem = {
type: 'array',
items: [

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/go",
"version": "1.4.1-canary.2",
"version": "1.4.3-canary.0",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/go",
@@ -11,7 +11,8 @@
},
"scripts": {
"build": "node build",
"test-integration-once": "jest --env node --verbose --runInBand --bail",
"test": "yarn jest --env node --verbose --runInBand --bail",
"test-integration-once": "yarn test",
"prepublishOnly": "node build"
},
"files": [
@@ -24,7 +25,7 @@
"@types/fs-extra": "^5.0.5",
"@types/node-fetch": "^2.3.0",
"@types/tar": "^4.0.0",
"@vercel/build-utils": "2.16.1-canary.2",
"@vercel/build-utils": "3.0.2-canary.0",
"@vercel/ncc": "0.24.0",
"async-retry": "1.3.1",
"execa": "^1.0.0",

View File

@@ -21,10 +21,15 @@ beforeAll(async () => {
console.log('builderUrl', builderUrl);
});
const skipFixtures = ['08-include-files'];
const fixturesPath = path.resolve(__dirname, 'fixtures');
// eslint-disable-next-line no-restricted-syntax
for (const fixture of fs.readdirSync(fixturesPath)) {
if (skipFixtures.includes(fixture)) {
console.log(`Skipping test fixture ${fixture}`);
continue;
}
// eslint-disable-next-line no-loop-func
it(`should build ${fixture}`, async () => {
await expect(

2
packages/next/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/dist
test/builder-info.json

36
packages/next/build.js Normal file
View File

@@ -0,0 +1,36 @@
const execa = require('execa');
const { remove, rename } = require('fs-extra');
const buildEdgeFunctionTemplate = require('./scripts/build-edge-function-template');
async function main() {
const isDevBuild = process.argv.includes('--dev');
await remove('dist');
await execa('tsc', [], { stdio: 'inherit' });
await buildEdgeFunctionTemplate();
if (!isDevBuild) {
await execa(
'ncc',
[
'build',
'src/index.ts',
'--minify',
'-e',
'esbuild',
'-e',
'@vercel/build-utils',
'-o',
'dist/main',
],
{ stdio: 'inherit' }
);
await rename('dist/main/index.js', 'dist/index.js');
await remove('dist/main');
}
}
main().catch(err => {
console.error(err);
process.exit(1);
});

View File

@@ -0,0 +1,73 @@
{
"name": "@vercel/next",
"version": "2.8.66-canary.0",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/next-js",
"scripts": {
"build": "node build.js",
"build-dev": "node build.js --dev",
"test": "jest --env node --verbose --bail --runInBand",
"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"
},
"repository": {
"type": "git",
"url": "https://github.com/vercel/vercel.git",
"directory": "packages/next"
},
"files": [
"dist"
],
"jest": {
"preset": "ts-jest/presets/default",
"testEnvironment": "node",
"globals": {
"ts-jest": {
"diagnostics": true,
"isolatedModules": true
}
}
},
"devDependencies": {
"@types/aws-lambda": "8.10.19",
"@types/buffer-crc32": "0.2.0",
"@types/convert-source-map": "1.5.2",
"@types/find-up": "4.0.0",
"@types/fs-extra": "8.0.0",
"@types/glob": "7.1.3",
"@types/next-server": "8.0.0",
"@types/node": "14.17.27",
"@types/resolve-from": "5.0.1",
"@types/semver": "6.0.0",
"@types/text-table": "0.2.1",
"@types/webpack-sources": "3.2.0",
"@vercel/build-utils": "3.0.2-canary.0",
"@vercel/nft": "0.18.1",
"@vercel/routing-utils": "1.13.3-canary.0",
"async-sema": "3.0.1",
"buffer-crc32": "0.2.13",
"cheerio": "1.0.0-rc.10",
"convert-source-map": "1.8.0",
"esbuild": "0.12.22",
"escape-string-regexp": "2.0.0",
"execa": "2.0.4",
"find-up": "4.1.0",
"fs-extra": "7.0.0",
"get-port": "5.0.0",
"nanoid": "3.3.4",
"ndjson": "2.0.0",
"pretty-bytes": "5.6.0",
"resolve-from": "5.0.0",
"semver": "6.1.1",
"set-cookie-parser": "2.4.6",
"source-map": "0.7.3",
"test-listen": "1.1.0",
"text-table": "0.2.0",
"typescript": "4.5.2",
"webpack-sources": "3.2.3"
}
}

View File

@@ -0,0 +1,46 @@
const { build } = require('esbuild');
const assert = require('assert');
const { outputFile } = require('fs-extra');
/**
* @param {Pick<import('esbuild').BuildOptions, 'outfile' | 'format' | 'entryPoints' | 'write'>} options
*/
async function buildTemplate(options) {
return build({
bundle: true,
entryPoints: options.entryPoints,
format: options.format,
legalComments: 'none',
minify: process.env.NODE_ENV !== 'test',
outfile: options.outfile,
// Cloudflare Workers uses the V8 JavaScript engine from Google Chrome.
// The Workers runtime is updated at least once a week, to at least the version
// that is currently used by Chrome's stable release.
// To see the latest stable chrome version: https://www.chromestatus.com/features/schedule
target: 'esnext',
write: options.write,
});
}
async function buildNextjsWrapper() {
const { outputFiles } = await buildTemplate({
entryPoints: ['./src/edge-function-source/get-edge-function'],
outfile: 'dist/___get-nextjs-edge-function.js',
format: 'cjs', // https://esbuild.github.io/api/#format
write: false,
});
assert(outputFiles);
const [src] = outputFiles;
return outputFile(src.path, `module.exports = ${JSON.stringify(src.text)}`);
}
module.exports = buildNextjsWrapper;
if (!module.parent) {
buildNextjsWrapper().catch(err => {
console.error(err);
process.exit(1);
});
}

View File

@@ -0,0 +1,95 @@
import fs from 'fs-extra';
import path from 'path';
import semver from 'semver';
import { ExperimentalTraceVersion } from './utils';
function getCustomData(importName: string, target: string) {
return `
// @ts-nocheck
module.exports = function(...args) {
let original = require('./${importName}');
const finalConfig = {};
const target = { target: '${target}' };
if (typeof original === 'function' && original.constructor.name === 'AsyncFunction') {
// AsyncFunctions will become promises
original = original(...args);
}
if (original instanceof Promise) {
// Special case for promises, as it's currently not supported
// and will just error later on
return original
.then((orignalConfig) => Object.assign(finalConfig, orignalConfig))
.then((config) => Object.assign(config, target));
} else if (typeof original === 'function') {
Object.assign(finalConfig, original(...args));
} else if (typeof original === 'object') {
Object.assign(finalConfig, original);
}
Object.assign(finalConfig, target);
return finalConfig;
}
`.trim();
}
function getDefaultData(target: string) {
return `
// @ts-nocheck
module.exports = { target: '${target}' };
`.trim();
}
export default async function createServerlessConfig(
workPath: string,
entryPath: string,
nextVersion: string | undefined
) {
let target = 'serverless';
if (nextVersion) {
try {
if (semver.gte(nextVersion, ExperimentalTraceVersion)) {
target = 'experimental-serverless-trace';
}
} catch (
_ignored
// eslint-disable-next-line
) {}
}
const primaryConfigPath = path.join(entryPath, 'next.config.js');
const secondaryConfigPath = path.join(workPath, 'next.config.js');
const backupConfigName = `next.config.__vercel_builder_backup__.js`;
const hasPrimaryConfig = fs.existsSync(primaryConfigPath);
const hasSecondaryConfig = fs.existsSync(secondaryConfigPath);
let configPath: string;
let backupConfigPath: string;
if (hasPrimaryConfig) {
// Prefer primary path
configPath = primaryConfigPath;
backupConfigPath = path.join(entryPath, backupConfigName);
} else if (hasSecondaryConfig) {
// Work with secondary path (some monorepo setups)
configPath = secondaryConfigPath;
backupConfigPath = path.join(workPath, backupConfigName);
} else {
// Default to primary path for creation
configPath = primaryConfigPath;
backupConfigPath = path.join(entryPath, backupConfigName);
}
if (fs.existsSync(configPath)) {
await fs.rename(configPath, backupConfigPath);
await fs.writeFile(configPath, getCustomData(backupConfigName, target));
} else {
await fs.writeFile(configPath, getDefaultData(target));
}
return target;
}

View File

@@ -0,0 +1,24 @@
const KIB = 1024;
const MIB = 1024 * KIB;
/**
* The limit after compression. it has to be kibibyte instead of kilobyte
* See https://github.com/cloudflare/wrangler/blob/8907b12add3d70ee21ac597b69cd66f6807571f4/src/wranglerjs/output.rs#L44
*/
const EDGE_FUNCTION_SCRIPT_SIZE_LIMIT = MIB;
/**
* This safety buffer must cover the size of our whole runtime layer compressed
* plus some extra space to allow it to grow in the future. At the time of
* writing this comment the compressed size size is ~7KiB so 20KiB should
* be more than enough.
*/
const EDGE_FUNCTION_SCRIPT_SIZE_BUFFER = 20 * KIB;
/**
* The max size we allow for compressed user code is the compressed script
* limit minus the compressed safety buffer. We must check this limit after
* compressing the user code.
*/
export const EDGE_FUNCTION_USER_SCRIPT_SIZE_LIMIT =
EDGE_FUNCTION_SCRIPT_SIZE_LIMIT - EDGE_FUNCTION_SCRIPT_SIZE_BUFFER;

View File

@@ -0,0 +1,84 @@
import type { NextjsParams } from './get-edge-function';
import { readFile } from 'fs-extra';
import { ConcatSource, Source } from 'webpack-sources';
import { fileToSource, raw, sourcemapped } from '../sourcemapped';
import { join } from 'path';
import { EDGE_FUNCTION_USER_SCRIPT_SIZE_LIMIT } from './constants';
import zlib from 'zlib';
import { promisify } from 'util';
import bytes from 'pretty-bytes';
// @ts-expect-error this is a prebuilt file, based on `../../scripts/build-edge-function-template.js`
import template from '../../dist/___get-nextjs-edge-function.js';
const gzip = promisify<zlib.InputType, Buffer>(zlib.gzip);
/**
* Allows to get the source code for a Next.js Edge Function where the output
* is defined by a set of filePaths that compose all chunks. Those will write
* to a global namespace _ENTRIES. The Next.js parameters will allow to adapt
* the function into the core Edge Function signature.
*
* @param filePaths Array of relative file paths for the function chunks.
* @param params Next.js parameters to adapt it to core edge functions.
* @param outputDir The output directory the files in `filePaths` stored in.
* @returns The source code of the edge function.
*/
export async function getNextjsEdgeFunctionSource(
filePaths: string[],
params: NextjsParams,
outputDir: string,
wasm?: { filePath: string; name: string }[]
): Promise<Source> {
const chunks = new ConcatSource(raw(`let _ENTRIES = {};`));
for (const filePath of filePaths) {
const fullFilePath = join(outputDir, filePath);
const content = await readFile(fullFilePath, 'utf8');
chunks.add(raw(`\n/**/;`));
chunks.add(await fileToSource(content, filePath, fullFilePath));
}
const text = chunks.source();
/**
* We validate at this point because we want to verify against user code.
* It should not count the Worker wrapper nor the Next.js wrapper.
*/
await validateScript(text);
// Wrap to fake module.exports
const getPageMatchCode = `(function () {
const module = { exports: {}, loaded: false };
const fn = (function(module,exports) {${template}\n});
fn(module, module.exports);
return module.exports;
})`;
return sourcemapped`
${raw(getWasmImportStatements(wasm))}
${chunks};
export default ${raw(getPageMatchCode)}.call({}).default(
${raw(JSON.stringify(params))}
)`;
}
function getWasmImportStatements(wasm: { name: string }[] = []) {
return wasm
.filter(({ name }) => name.startsWith('wasm_'))
.map(({ name }) => {
const pathname = `/wasm/${name}.wasm`;
return `const ${name} = require(${JSON.stringify(pathname)});`;
})
.join('\n');
}
async function validateScript(content: string) {
const gzipped = await gzip(content);
if (gzipped.length > EDGE_FUNCTION_USER_SCRIPT_SIZE_LIMIT) {
throw new Error(
`Exceeds maximum edge function script size: ${bytes(
gzipped.length
)} / ${bytes(EDGE_FUNCTION_USER_SCRIPT_SIZE_LIMIT)}`
);
}
}

View File

@@ -0,0 +1,242 @@
/// <reference lib="DOM" />
import { toPlainHeaders } from './to-plain-headers';
export interface NextjsParams {
/**
* The name of the function exposed in _ENTRIES that will be wrapped.
*/
name: string;
/**
* An array with all static pages that the Next.js application contains.
* This is required to estimate if a pathname will match a page.
*/
staticRoutes: { page: string; namedRegex?: string }[];
/**
* An array with dynamic page names and their matching regular expression.
* This is required to estimate if a request will match a dynamic page.
*/
dynamicRoutes?: { page: string; namedRegex?: string }[];
/**
* The Next.js minimal configuration that the Middleware Edge Function
* requires to parse the URL. This must include the locale config and
* the basePath.
*/
nextConfig: NextConfig | null;
}
type EdgeFunction = (
request: Request,
context: { waitUntil(promise: Promise<unknown>): void }
) => Promise<Response>;
/**
* A template to adapt the Next.js Edge Function signature into the core Edge
* Function signature. This will automatically inject parameters that are
* missing in default Edge Functions from the provided configuration
* parameters. Static and Dynamic RegExp are calculated in the module scope
* to avoid recomputing them for each function invocation.
*/
export default function getNextjsEdgeFunction(
params: NextjsParams
): EdgeFunction {
const staticRoutes = params.staticRoutes.map(route => ({
regexp: new RegExp(route.namedRegex!),
page: route.page,
}));
const dynamicRoutes =
params.dynamicRoutes?.map(route => ({
regexp: new RegExp(route.namedRegex!),
page: route.page,
})) || [];
return async function edgeFunction(request, context) {
let pathname = new URL(request.url).pathname;
let pageMatch: PageMatch = {};
// Remove the basePath from the URL
if (params.nextConfig?.basePath) {
if (pathname.startsWith(params.nextConfig.basePath)) {
pathname = pathname.replace(params.nextConfig.basePath, '') || '/';
}
}
// Remove the locale from the URL
if (params.nextConfig?.i18n) {
for (const locale of params.nextConfig.i18n.locales) {
const regexp = new RegExp(`^/${locale}($|/)`, 'i');
if (pathname.match(regexp)) {
pathname = pathname.replace(regexp, '/') || '/';
break;
}
}
}
// Find the page match that will happen if there are no assets matching
for (const route of staticRoutes) {
const result = route.regexp.exec(pathname);
if (result) {
pageMatch.name = route.page;
break;
}
}
if (!pageMatch.name) {
const isApi = isApiRoute(pathname);
for (const route of dynamicRoutes || []) {
/**
* Dynamic API routes should not be checked against dynamic non API
* routes so we skip it in such case. For example, a request to
* /api/test should not match /pages/[slug].test having:
* - pages/api/foo.js
* - pages/[slug]/test.js
*/
if (isApi && !isApiRoute(route.page)) {
continue;
}
const result = route.regexp.exec(pathname);
if (result) {
pageMatch = {
name: route.page,
params: result.groups,
};
break;
}
}
}
// Invoke the function injecting missing parameters
const result = await _ENTRIES[`middleware_${params.name}`].default.call(
{},
{
request: {
url: request.url,
method: request.method,
headers: toPlainHeaders(request.headers),
ip: header(request.headers, IncomingHeaders.Ip),
geo: {
city: header(request.headers, IncomingHeaders.City, true),
country: header(request.headers, IncomingHeaders.Country, true),
latitude: header(request.headers, IncomingHeaders.Latitude),
longitude: header(request.headers, IncomingHeaders.Longitude),
region: header(request.headers, IncomingHeaders.Region, true),
},
nextConfig: params.nextConfig,
page: pageMatch,
body: request.body,
},
}
);
context.waitUntil(result.waitUntil);
return result.response;
};
}
/**
* Allows to get a header value by name but falling back to `undefined` when
* the value does not exist. Optionally, we can make this function decode
* what it reads for certain cases.
*
* @param headers The Headers object.
* @param name The name of the header to extract.
* @param decode Tells if we should decode the value.
* @returns The header value or undefined.
*/
function header(headers: Headers, name: string, decode = false) {
const value = headers.get(name) || undefined;
return decode && value ? decodeURIComponent(value) : value;
}
/**
* Next.js current output will write in the global variable _ENTRIES all of
* the middleware that exist in the application. This global describes its
* signature which we should adapt into the core Edge Function.
*/
declare let _ENTRIES: {
[key: string]: {
default: (params: {
request: {
url: string;
method: string;
headers: {
[header: string]: string | string[] | undefined;
};
ip?: string;
geo?: {
city?: string;
country?: string;
region?: string;
latitude?: string;
longitude?: string;
};
nextConfig?: NextConfig | null;
page?: PageMatch;
body: ReadableStream<Uint8Array> | null;
};
}) => Promise<{ response: Response; waitUntil: Promise<any> }>;
};
};
/**
* A partial Next.js configuration object that contains the required info
* to parse the URL and figure out the pathname.
*/
interface NextConfig {
basePath?: string;
i18n?: {
defaultLocale: string;
domains?: {
defaultLocale: string;
domain: string;
http?: boolean;
locales?: string[];
}[];
localeDetection?: boolean;
locales: string[];
};
}
/**
* Information about the page and parameters that the Middleware Edge
* Function will match in case it doesn't intercept the request.
* TODO We must consider if this should be removed as it is misleading.
*/
interface PageMatch {
name?: string;
params?: { [key: string]: string };
}
function isApiRoute(path: string) {
return path === '/api' || path.startsWith('/api/');
}
enum IncomingHeaders {
/**
* City of the original client IP calculated by Vercel Proxy.
*/
City = 'x-vercel-ip-city',
/**
* Country of the original client IP calculated by Vercel Proxy.
*/
Country = 'x-vercel-ip-country',
/**
* Ip from Vercel Proxy. Do not confuse it with the client Ip.
*/
Ip = 'x-real-ip',
/**
* Latitude of the original client IP calculated by Vercel Proxy.
*/
Latitude = 'x-vercel-ip-latitude',
/**
* Longitude of the original client IP calculated by Vercel Proxy.
*/
Longitude = 'x-vercel-ip-longitude',
/**
* Region of the original client IP calculated by Vercel Proxy.
*/
Region = 'x-vercel-ip-country-region',
}

View File

@@ -0,0 +1,94 @@
interface PlainHeaders {
[header: string]: string | string[] | undefined;
}
/**
* Transforms a standard Headers object into a plean Headers object. This is
* done to support a plain format for headers which is used in the Edge
* Function signature.
*
* @param headers Headers from the original request.
* @returns The same headers formatted as Node Headers.
*/
export function toPlainHeaders(headers?: Headers): PlainHeaders {
const result: PlainHeaders = {};
if (!headers) return result;
headers.forEach((value, key) => {
result[key] = value;
if (key.toLowerCase() === 'set-cookie') {
result[key] = splitCookiesString(value);
}
});
return result;
}
/**
* Set-Cookie header field-values are sometimes comma joined in one string.
* This splits them without choking on commas that are within a single
* set-cookie field-value, such as in the Expires portion. This is uncommon,
* but explicitly allowed (https://tools.ietf.org/html/rfc2616#section-4.2)
*/
export function splitCookiesString(cookiesString: string) {
const cookiesStrings: string[] = [];
let pos = 0;
let start: number;
let ch: string;
let lastComma: number;
let nextStart: number;
let cookiesSeparatorFound: boolean;
function skipWhitespace() {
while (pos < cookiesString.length && /\s/.test(cookiesString.charAt(pos)))
pos += 1;
return pos < cookiesString.length;
}
function notSpecialChar() {
ch = cookiesString.charAt(pos);
return ch !== '=' && ch !== ';' && ch !== ',';
}
while (pos < cookiesString.length) {
start = pos;
cookiesSeparatorFound = false;
while (skipWhitespace()) {
ch = cookiesString.charAt(pos);
if (ch === ',') {
// ',' is a cookie separator if we have later first '=', not ';' or ','
lastComma = pos;
pos += 1;
skipWhitespace();
nextStart = pos;
while (pos < cookiesString.length && notSpecialChar()) {
pos += 1;
}
// currently special character
if (pos < cookiesString.length && cookiesString.charAt(pos) === '=') {
// we found cookies separator
cookiesSeparatorFound = true;
// pos is inside the next cookie, so back up and return it.
pos = nextStart;
cookiesStrings.push(cookiesString.substring(start, lastComma));
start = pos;
} else {
// in param ',' or param separator ';',
// we continue from that comma
pos = lastComma + 1;
}
} else {
pos += 1;
}
}
if (!cookiesSeparatorFound || pos >= cookiesString.length) {
cookiesStrings.push(cookiesString.substring(start, cookiesString.length));
}
}
return cookiesStrings;
}

2642
packages/next/src/index.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,15 @@
import { IncomingMessage, ServerResponse } from 'http';
import next from 'next-server';
import url from 'url';
if (!process.env.NODE_ENV) {
const region = process.env.VERCEL_REGION || process.env.NOW_REGION;
process.env.NODE_ENV = region === 'dev1' ? 'development' : 'production';
}
const app = next({});
module.exports = (req: IncomingMessage, res: ServerResponse) => {
const parsedUrl = url.parse(req.url || '', true);
app.render(req, res, 'PATHNAME_PLACEHOLDER', parsedUrl.query, parsedUrl);
};

View File

@@ -0,0 +1,336 @@
export default [
'0.1.0',
'0.1.1',
'0.2.0',
'0.2.1',
'0.2.2',
'0.2.3',
'0.2.4',
'0.2.5',
'0.2.6',
'0.2.7',
'0.2.8',
'0.2.9',
'0.2.10',
'0.2.11',
'0.2.12',
'0.2.13',
'0.2.14',
'0.3.0',
'0.3.1',
'0.3.2',
'0.3.3',
'0.4.0',
'0.4.1',
'0.9.9',
'0.9.10',
'0.9.11',
'1.0.0',
'1.0.1',
'1.0.2',
'1.1.0',
'1.1.1',
'1.1.2',
'1.2.0',
'1.2.1',
'1.2.2',
'1.2.3',
'2.0.0-beta.0',
'2.0.0-beta.1',
'2.0.0-beta.2',
'2.0.0-beta.3',
'2.0.0-beta.4',
'2.0.0-beta.5',
'2.0.0-beta.6',
'2.0.0-beta.7',
'2.0.0-beta.8',
'2.0.0-beta.9',
'2.0.0-beta.10',
'2.0.0-beta.11',
'2.0.0-beta.12',
'2.0.0-beta.13',
'2.0.0-beta.14',
'2.0.0-beta.15',
'2.0.0-beta.16',
'2.0.0-beta.17',
'2.0.0-beta.18',
'2.0.0-beta.19',
'2.0.0-beta.20',
'2.0.0-beta.21',
'2.0.0-beta.22',
'2.0.0-beta.23',
'2.0.0-beta.24',
'2.0.0-beta.25',
'2.0.0-beta.26',
'2.0.0-beta.27',
'2.0.0-beta.28',
'2.0.0-beta.29',
'2.0.0-beta.30',
'2.0.0-beta.31',
'2.0.0-beta.32',
'2.0.0-beta.33',
'2.0.0-beta.34',
'2.0.0-beta.35',
'2.0.0-beta.36',
'2.0.0-beta.37',
'2.0.0-beta.38',
'2.0.0-beta.39',
'2.0.0-beta.40',
'2.0.0-beta.41',
'2.0.0-beta.42',
'2.0.0',
'2.0.1',
'2.1.0',
'2.1.1',
'2.2.0',
'2.3.0-alpha1',
'2.3.0',
'2.3.1',
'2.4.0',
'2.4.1',
'2.4.2',
'2.4.3',
'2.4.4',
'2.4.5',
'2.4.6',
'2.4.7',
'2.4.8',
'2.4.9',
'3.0.0-beta1',
'3.0.0-beta10',
'3.0.0-beta11',
'3.0.0-beta12',
'3.0.0-beta13',
'3.0.0-beta14',
'3.0.0-beta15',
'3.0.0-beta16',
'3.0.0-beta2',
'3.0.0-beta3',
'3.0.0-beta4',
'3.0.0-beta5',
'3.0.0-beta6',
'3.0.0-beta7',
'3.0.0-beta8',
'3.0.0-beta9',
'3.0.1-beta.1',
'3.0.1-beta.2',
'3.0.1-beta.3',
'3.0.1-beta.4',
'3.0.1-beta.5',
'3.0.1-beta.6',
'3.0.1-beta.7',
'3.0.1-beta.8',
'3.0.1-beta.9',
'3.0.1-beta.10',
'3.0.1-beta.11',
'3.0.1-beta.12',
'3.0.1-beta.13',
'3.0.1-beta.14',
'3.0.1-beta.15',
'3.0.1-beta.16',
'3.0.1-beta.17',
'3.0.1-beta.18',
'3.0.1-beta.19',
'3.0.1-beta.20',
'3.0.1-beta.21',
'3.0.1',
'3.0.2',
'3.0.3',
'3.0.4',
'3.0.5',
'3.0.6',
'3.1.0',
'3.2.0',
'3.2.1',
'3.2.2',
'3.2.3',
'4.0.0-beta.1',
'4.0.0-beta.2',
'4.0.0-beta.3',
'4.0.0-beta.4',
'4.0.0-beta.5',
'4.0.0-beta.6',
'4.0.0',
'4.0.1',
'4.0.2',
'4.0.3',
'4.0.4',
'4.0.5',
'4.1.0',
'4.1.1',
'4.1.2',
'4.1.3',
'4.1.4-canary.1',
'4.1.4-canary.2',
'4.1.4',
'4.2.0-canary.1',
'4.2.0-zones.2',
'4.2.0',
'4.2.1',
'4.2.2',
'4.2.3',
'4.3.0-canary.1',
'4.3.0-universal-alpha.1',
'4.3.0-universal-alpha.2',
'4.3.0-universal-alpha.3',
'4.3.0-universal-alpha.4',
'4.3.0-zones.1',
'4.4.0-canary.2',
'4.4.0-canary.3',
'5.0.0-universal-alpha.1',
'5.0.0-universal-alpha.2',
'5.0.0-universal-alpha.3',
'5.0.0-universal-alpha.4',
'5.0.0-universal-alpha.5',
'5.0.0-universal-alpha.6',
'5.0.0-universal-alpha.7',
'5.0.0-universal-alpha.8',
'5.0.0-universal-alpha.9',
'5.0.0-universal-alpha.10',
'5.0.0-universal-alpha.11',
'5.0.0-universal-alpha.12',
'5.0.0-universal-alpha.13',
'5.0.0-universal-alpha.14',
'5.0.0-universal-alpha.15',
'5.0.0-universal-alpha.16',
'5.0.0-universal-alpha.17',
'5.0.0-universal-alpha.18',
'5.0.0-universal-alpha.19',
'5.0.0-universal-alpha.20',
'5.0.0-universal-alpha.21',
'5.0.0-universal-alpha.22',
'5.0.0-universal-alpha.23',
'5.0.0-zones.1',
'5.0.0',
'5.0.1-canary.1',
'5.0.1-canary.2',
'5.0.1-canary.3',
'5.0.1-canary.4',
'5.0.1-canary.5',
'5.0.1-canary.6',
'5.0.1-canary.7',
'5.0.1-canary.8',
'5.0.1-canary.9',
'5.0.1-canary.10',
'5.0.1-canary.11',
'5.0.1-canary.12',
'5.0.1-canary.13',
'5.0.1-canary.14',
'5.0.1-canary.15',
'5.0.1-canary.16',
'5.0.1-canary.17',
'5.1.0',
'6.0.0-canary.1',
'6.0.0-canary.2',
'6.0.0-canary.3',
'6.0.0-canary.4',
'6.0.0-canary.5',
'6.0.0-canary.6',
'6.0.0-canary.7',
'6.0.0',
'6.0.1-canary.0',
'6.0.1-canary.1',
'6.0.1-canary.2',
'6.0.1',
'6.0.2-canary.0',
'6.0.2',
'6.0.3-canary.0',
'6.0.3-canary.1',
'6.0.3',
'6.0.4-canary.0',
'6.0.4-canary.1',
'6.0.4-canary.2',
'6.0.4-canary.3',
'6.0.4-canary.4',
'6.0.4-canary.5',
'6.0.4-canary.6',
'6.0.4-canary.7',
'6.0.4-canary.8',
'6.0.4-canary.9',
'6.1.0-canary.0',
'6.1.0',
'6.1.1-canary.0',
'6.1.1-canary.1',
'6.1.1-canary.2',
'6.1.1-canary.3',
'6.1.1-canary.4',
'6.1.1-canary.5',
'6.1.1',
'6.1.2',
'7.0.0-canary.0',
'7.0.0-canary.1',
'7.0.0-canary.2',
'7.0.0-canary.3',
'7.0.0-canary.4',
'7.0.0-canary.5',
'7.0.0-canary.6',
'7.0.0-canary.7',
'7.0.0-canary.8',
'7.0.0-canary.9',
'7.0.0-canary.10',
'7.0.0-canary.11',
'7.0.0-canary.12',
'7.0.0-canary.13',
'7.0.0-canary.14',
'7.0.0-canary.15',
'7.0.0-canary.16',
'7.0.0-canary.18',
'7.0.0-canary.19',
'7.0.0-canary.20',
'7.0.0',
'7.0.1-canary.0',
'7.0.1-canary.1',
'7.0.1-canary.2',
'7.0.1-canary.3',
'7.0.1-canary.4',
'7.0.1-canary.5',
'7.0.1-canary.6',
'7.0.1',
'7.0.2-alpha.1',
'7.0.2-alpha.3',
'7.0.2-canary.5',
'7.0.2-canary.6',
'7.0.2-canary.7',
'7.0.2-canary.8',
'7.0.2-canary.9',
'7.0.2-canary.10',
'7.0.2-canary.11',
'7.0.2-canary.12',
'7.0.2-canary.13',
'7.0.2-canary.14',
'7.0.2-canary.15',
'7.0.2-canary.16',
'7.0.2-canary.17',
'7.0.2-canary.18',
'7.0.2-canary.19',
'7.0.2-canary.20',
'7.0.2-canary.21',
'7.0.2-canary.22',
'7.0.2-canary.23',
'7.0.2-canary.24',
'7.0.2-canary.25',
'7.0.2-canary.26',
'7.0.2-canary.27',
'7.0.2-canary.28',
'7.0.2-canary.29',
'7.0.2-canary.31',
'7.0.2-canary.33',
'7.0.2-canary.34',
'7.0.2-canary.35',
'7.0.2-canary.36',
'7.0.2-canary.37',
'7.0.2-canary.38',
'7.0.2-canary.39',
'7.0.2-canary.40',
'7.0.2-canary.41',
'7.0.2-canary.42',
'7.0.2-canary.43',
'7.0.2-canary.44',
'7.0.2-canary.45',
'7.0.2-canary.46',
'7.0.2-canary.47',
'7.0.2-canary.48',
'7.0.2-canary.49',
'7.0.2-canary.50',
'7.0.2',
];

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,43 @@
import { IncomingMessage, ServerResponse } from 'http';
// The Next.js builder can emit the project in a subdirectory depending on how
// many folder levels of `node_modules` are traced. To ensure `process.cwd()`
// returns the proper path, we change the directory to the folder with the
// launcher. This mimics `yarn workspace run` behavior.
process.chdir(__dirname);
const region = process.env.VERCEL_REGION || process.env.NOW_REGION;
if (!process.env.NODE_ENV) {
process.env.NODE_ENV = region === 'dev1' ? 'development' : 'production';
}
if (process.env.NODE_ENV !== 'production' && region !== 'dev1') {
console.warn(
`Warning: NODE_ENV was incorrectly set to "${process.env.NODE_ENV}", this value is being overridden to "production"`
);
process.env.NODE_ENV = 'production';
}
// eslint-disable-next-line
const NextServer = require('__NEXT_SERVER_PATH__').default;
const nextServer = new NextServer({
// @ts-ignore __NEXT_CONFIG__ value is injected
conf: __NEXT_CONFIG__,
dir: '.',
minimalMode: true,
customServer: false,
});
const requestHandler = nextServer.getRequestHandler();
module.exports = async (req: IncomingMessage, res: ServerResponse) => {
try {
// entryDirectory handler
await requestHandler(req, res);
} catch (err) {
console.error(err);
// crash the lambda immediately to clean up any bad module state,
// this was previously handled in ___vc_bridge on an unhandled rejection
// but we can do this quicker by triggering here
process.exit(1);
}
};

View File

@@ -0,0 +1,101 @@
import type { RawSourceMap } from 'source-map';
import convertSourceMap from 'convert-source-map';
import fs from 'fs-extra';
import {
ConcatSource,
OriginalSource,
SourceMapSource,
Source,
} from 'webpack-sources';
/**
* A template literal tag that preserves existing source maps, if any. This
* allows to compose multiple sources and preserve the source maps, so we can
* resolve the correct line numbers in the stack traces later on.
*
* @param strings The string literals.
* @param sources All the sources that may optionally have source maps. Use
* `raw` to pass a string that should be inserted raw (with no source map
* attached).
*/
export function sourcemapped(
strings: TemplateStringsArray,
...sources: Source[]
): Source {
const concat = new ConcatSource();
for (let i = 0; i < Math.max(strings.length, sources.length); i++) {
const string = strings[i];
const source = sources[i];
if (string) concat.add(raw(string));
if (source) concat.add(source);
}
return concat;
}
/**
* A helper to create a Source from a string with no source map.
* This allows to obfuscate the source code from the user and print `[native code]`
* when resolving the stack trace.
*/
export function raw(value: string) {
return new OriginalSource(value, '[native code]');
}
/**
* Takes a file with contents and tries to extract its source maps it will
* first try to use a `${fullFilePath}.map` file if it exists. Then, it will
* try to use the inline source map comment.
*
* @param content The file contents.
* @param sourceName the name of the source.
* @param fullFilePath The full path to the file.
*/
export async function fileToSource(
content: string,
sourceName: string,
fullFilePath?: string
): Promise<Source> {
const sourcemap = await getSourceMap(content, fullFilePath);
const cleanContent = convertSourceMap.removeComments(content);
return sourcemap
? new SourceMapSource(cleanContent, sourceName, sourcemap)
: new OriginalSource(cleanContent, sourceName);
}
/**
* Finds a source map for a given content and file path. First it will try to
* use a `${fullFilePath}.map` file if it exists. Then, it will try to use
* the inline source map comment.
*/
async function getSourceMap(
content: string,
fullFilePath?: string
): Promise<RawSourceMap | null> {
try {
if (fullFilePath && (await fs.pathExists(`${fullFilePath}.map`))) {
const mapJson = await fs.readFile(`${fullFilePath}.map`, 'utf8');
return convertSourceMap.fromJSON(mapJson).toObject();
}
return convertSourceMap.fromComment(content).toObject();
} catch {
return null;
}
}
/**
* Stringifies a source map, removing unnecessary data:
* * `sourcesContent` is not needed to trace back frames.
*/
export function stringifySourceMap(
sourceMap?: RawSourceMap | string | null
): string | undefined {
if (!sourceMap) return;
const obj =
typeof sourceMap === 'object'
? { ...sourceMap }
: convertSourceMap.fromJSON(sourceMap).toObject();
delete obj.sourcesContent;
return JSON.stringify(obj);
}

View File

@@ -0,0 +1,28 @@
// The Next.js builder can emit the project in a subdirectory depending on how
// many folder levels of `node_modules` are traced. To ensure `process.cwd()`
// returns the proper path, we change the directory to the folder with the
// launcher. This mimics `yarn workspace run` behavior.
process.chdir(__dirname);
const region = process.env.VERCEL_REGION || process.env.NOW_REGION;
if (!process.env.NODE_ENV) {
process.env.NODE_ENV = region === 'dev1' ? 'development' : 'production';
}
if (process.env.NODE_ENV !== 'production' && region !== 'dev1') {
console.warn(
`Warning: NODE_ENV was incorrectly set to "${process.env.NODE_ENV}", this value is being overridden to "production"`
);
process.env.NODE_ENV = 'production';
}
// @ts-ignore
// eslint-disable-next-line
let page: any = {};
// __LAUNCHER_PAGE_HANDLER__
// page.render is for React rendering
// page.default is for /api rendering
// page is for module.exports in /api
module.exports = page.render || page.default || page;

View File

@@ -0,0 +1,19 @@
// The Next.js builder can emit the project in a subdirectory depending on how
// many folder levels of `node_modules` are traced. To ensure `process.cwd()`
// returns the proper path, we change the directory to the folder with the
// launcher. This mimics `yarn workspace run` behavior.
process.chdir(__dirname);
if (!process.env.NODE_ENV) {
const region = process.env.VERCEL_REGION || process.env.NOW_REGION;
process.env.NODE_ENV = region === 'dev1' ? 'development' : 'production';
}
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-var-requires
const page = require(__LAUNCHER_PAGE_PATH__);
// page.render is for React rendering
// page.default is for /api rendering
// page is for module.exports in /api
module.exports = page.render || page.default || page;

2365
packages/next/src/utils.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,185 @@
/* eslint-env jest */
const path = require('path');
const cheerio = require('cheerio');
const { check, deployAndTest } = require('../../utils');
const fetch = require('../../../../../test/lib/deployment/fetch-retry');
async function checkForChange(url, initialValue, hardError) {
if (isNaN(initialValue)) {
throw new Error(
`expected number for initialValue, received ${initialValue}`
);
}
return check(
async () => {
const res = await fetch(url);
if (res.status !== 200) {
throw new Error(`Invalid status code ${res.status}`);
}
const $ = cheerio.load(await res.text());
const props = JSON.parse($('#props').text());
if (isNaN(props.random)) {
throw new Error(`Invalid random value ${props.random}`);
}
const newValue = props.random;
return initialValue !== newValue ? 'success' : 'fail';
},
'success',
hardError
);
}
const ctx = {};
describe(`${__dirname.split(path.sep).pop()}`, () => {
it('should deploy and pass probe checks', async () => {
const info = await deployAndTest(__dirname);
Object.assign(ctx, info);
});
it('should revalidate content properly from /docs', async () => {
const dataRes = await fetch(
`${ctx.deploymentUrl}/docs/_next/data/testing-build-id/index.json`
);
expect(dataRes.status).toBe(200);
const data = await dataRes.json();
expect(data.pageProps.index).toBe(true);
const res = await fetch(`${ctx.deploymentUrl}/docs`);
expect(res.status).toBe(200);
let $ = cheerio.load(await res.text());
const props = JSON.parse($('#props').text());
const initialRandom = props.random;
expect(props.index).toBe(true);
expect(JSON.parse($('#query').text())).toEqual({});
expect($('#pathname').text()).toBe('pathname /');
expect($('#asPath').text()).toBe('asPath /');
await checkForChange(`${ctx.deploymentUrl}/docs`, initialRandom);
const res2 = await fetch(`${ctx.deploymentUrl}/docs`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(props2.index).toBe(true);
expect(JSON.parse($('#query').text())).toEqual({});
expect($('#pathname').text()).toBe('pathname /');
expect($('#asPath').text()).toBe('asPath /');
});
it('should load content properly from /docs/hello', async () => {
const dataRes = await fetch(
`${ctx.deploymentUrl}/docs/_next/data/testing-build-id/hello.json`
);
expect(dataRes.status).toBe(200);
const data = await dataRes.json();
expect(data.pageProps.id).toBe(true);
expect(data.pageProps.params).toEqual({ id: 'hello' });
const res = await fetch(`${ctx.deploymentUrl}/docs/hello`);
expect(res.status).toBe(200);
let $ = cheerio.load(await res.text());
const props = JSON.parse($('#props').text());
const initialRandom = props.random;
expect(props.id).toBe(true);
expect(JSON.parse($('#query').text())).toEqual({ id: 'hello' });
expect($('#pathname').text()).toBe('pathname /[id]');
expect($('#asPath').text()).toBe('asPath /hello');
await checkForChange(`${ctx.deploymentUrl}/docs/hello`, initialRandom);
const res2 = await fetch(`${ctx.deploymentUrl}/docs/hello`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(props2.id).toBe(true);
expect(JSON.parse($('#query').text())).toEqual({ id: 'hello' });
expect($('#pathname').text()).toBe('pathname /[id]');
expect($('#asPath').text()).toBe('asPath /hello');
});
it('should revalidate content properly from /docs/blog', async () => {
const dataRes = await fetch(
`${ctx.deploymentUrl}/docs/_next/data/testing-build-id/blog.json`
);
expect(dataRes.status).toBe(200);
const data = await dataRes.json();
expect(data.pageProps.blogIndex).toBe(true);
const res = await fetch(`${ctx.deploymentUrl}/docs/blog`);
expect(res.status).toBe(200);
let $ = cheerio.load(await res.text());
const props = JSON.parse($('#props').text());
const initialRandom = props.random;
expect(props.blogIndex).toBe(true);
expect(JSON.parse($('#query').text())).toEqual({});
expect($('#pathname').text()).toBe('pathname /blog');
expect($('#asPath').text()).toBe('asPath /blog');
await checkForChange(`${ctx.deploymentUrl}/docs/blog`, initialRandom);
const res2 = await fetch(`${ctx.deploymentUrl}/docs/blog`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(props2.blogIndex).toBe(true);
expect(JSON.parse($('#query').text())).toEqual({});
expect($('#pathname').text()).toBe('pathname /blog');
expect($('#asPath').text()).toBe('asPath /blog');
});
it('should revalidate content properly from /docs/blog/another', async () => {
const dataRes = await fetch(
`${ctx.deploymentUrl}/docs/_next/data/testing-build-id/blog/another.json`
);
expect(dataRes.status).toBe(200);
const data = await dataRes.json();
expect(data.pageProps.blogSlug).toBe(true);
expect(data.pageProps.params).toEqual({
slug: 'another',
});
const res = await fetch(`${ctx.deploymentUrl}/docs/blog/another`);
expect(res.status).toBe(200);
let $ = cheerio.load(await res.text());
const props = JSON.parse($('#props').text());
const initialRandom = props.random;
expect(props.blogSlug).toBe(true);
expect(JSON.parse($('#query').text())).toEqual({ slug: 'another' });
expect($('#pathname').text()).toBe('pathname /blog/[slug]');
expect($('#asPath').text()).toBe('asPath /blog/another');
await checkForChange(
`${ctx.deploymentUrl}/docs/blog/another`,
initialRandom
);
const res2 = await fetch(`${ctx.deploymentUrl}/docs/blog/another`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(props2.blogSlug).toBe(true);
expect(JSON.parse($('#query').text())).toEqual({ slug: 'another' });
expect($('#pathname').text()).toBe('pathname /blog/[slug]');
expect($('#asPath').text()).toBe('asPath /blog/another');
});
});

View File

@@ -0,0 +1,7 @@
module.exports = {
basePath: '/docs',
generateBuildId() {
return 'testing-build-id';
},
};

View File

@@ -0,0 +1,7 @@
{
"dependencies": {
"next": "canary",
"react": "latest",
"react-dom": "latest"
}
}

View File

@@ -0,0 +1,24 @@
import { useRouter } from 'next/router';
export default function Home(props) {
const router = useRouter();
return (
<>
<p id="info">id page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="query">{JSON.stringify(router.query)}</p>
<p id="pathname">{`pathname ${router.pathname}`}</p>
<p id="asPath">{`asPath ${router.asPath}`}</p>
</>
);
}
export async function getServerSideProps({ params }) {
return {
props: {
random: Math.random() + Date.now(),
id: true,
params,
},
};
}

View File

@@ -0,0 +1,32 @@
import { useRouter } from 'next/router';
export default function Home(props) {
const router = useRouter();
return (
<>
<p id="info">blog slug page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="query">{JSON.stringify(router.query)}</p>
<p id="pathname">{`pathname ${router.pathname}`}</p>
<p id="asPath">{`asPath ${router.asPath}`}</p>
</>
);
}
export async function getStaticProps({ params }) {
return {
revalidate: 1,
props: {
random: Math.random() + Date.now(),
blogSlug: true,
params,
},
};
}
export async function getStaticPaths() {
return {
paths: ['/blog/first'],
fallback: 'blocking',
};
}

View File

@@ -0,0 +1,24 @@
import { useRouter } from 'next/router';
export default function Home(props) {
const router = useRouter();
return (
<>
<p id="info">blog index page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="query">{JSON.stringify(router.query)}</p>
<p id="pathname">{`pathname ${router.pathname}`}</p>
<p id="asPath">{`asPath ${router.asPath}`}</p>
</>
);
}
export async function getStaticProps() {
return {
revalidate: 1,
props: {
random: Math.random() + Date.now(),
blogIndex: true,
},
};
}

View File

@@ -0,0 +1,24 @@
import { useRouter } from 'next/router';
export default function Home(props) {
const router = useRouter();
return (
<>
<p id="info">index page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="query">{JSON.stringify(router.query)}</p>
<p id="pathname">{`pathname ${router.pathname}`}</p>
<p id="asPath">{`asPath ${router.asPath}`}</p>
</>
);
}
export async function getStaticProps() {
return {
revalidate: 1,
props: {
random: Math.random() + Date.now(),
index: true,
},
};
}

View File

@@ -0,0 +1,9 @@
{
"builds": [
{
"src": "package.json",
"use": "@vercel/next"
}
],
"probes": []
}

View File

@@ -0,0 +1 @@
hello great big wide world!

View File

@@ -0,0 +1,74 @@
const path = require('path');
const cheerio = require('cheerio').default;
const { deployAndTest, check } = require('../../utils');
const fetch = require('../../../../../test/lib/deployment/fetch-retry');
const ctx = {};
describe(`${__dirname.split(path.sep).pop()}`, () => {
it('should deploy and pass probe checks', async () => {
const info = await deployAndTest(__dirname);
Object.assign(ctx, info);
});
it('should revalidate content correctly', async () => {
const res = await fetch(`${ctx.deploymentUrl}/another`);
expect(res.status).toBe(200);
const html = await res.text();
const $ = cheerio.load(html);
const props = JSON.parse($('#props').text());
const previousNow = props.now;
expect(isNaN(props.now)).toBe(false);
expect(props.content[0].trim()).toBe('hello great big wide world!');
expect($('#page').text()).toBe('/another');
await check(async () => {
const res = await fetch(`${ctx.deploymentUrl}/another`);
expect(res.status).toBe(200);
const html = await res.text();
const $ = cheerio.load(html);
const props = JSON.parse($('#props').text());
if (isNaN(props.now)) {
throw new Error('invalid props: ' + html);
}
return props.now !== previousNow &&
props.content[0].trim() === 'hello great big wide world!'
? 'success'
: html;
}, 'success');
});
it('should revalidate content correctly', async () => {
const res = await fetch(`${ctx.deploymentUrl}/post`);
expect(res.status).toBe(200);
const html = await res.text();
const $ = cheerio.load(html);
const props = JSON.parse($('#props').text());
const previousNow = props.now;
expect(props.content[0].trim()).toBe('hello great big wide world!');
expect(isNaN(props.now)).toBe(false);
expect($('#page').text()).toBe('/post');
await check(async () => {
const res = await fetch(`${ctx.deploymentUrl}/post`);
expect(res.status).toBe(200);
const html = await res.text();
const $ = cheerio.load(html);
const props = JSON.parse($('#props').text());
expect($('#page').text()).toBe('/post');
if (isNaN(props.now)) {
throw new Error('invalid props: ' + html);
}
return props.now !== previousNow &&
props.content[0].trim() === 'hello great big wide world!'
? 'success'
: html;
}, 'success');
});
});

View File

@@ -0,0 +1,9 @@
import fs from 'fs';
import path from 'path';
export async function getContent() {
const files = fs.readdirSync(path.join(process.cwd(), 'content'));
return files.map(file =>
fs.readFileSync(path.join(process.cwd(), 'content', file), 'utf8')
);
}

View File

@@ -0,0 +1,6 @@
module.exports = {
reactStrictMode: true,
experimental: {
nftTracing: true,
},
};

View File

@@ -0,0 +1,16 @@
{
"name": "test-trace",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"next": "canary",
"react": "17.0.2",
"react-dom": "17.0.2"
}
}

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