mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-11 12:57:46 +00:00
Compare commits
76 Commits
vercel-plu
...
@vercel/py
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3ef240f6e | ||
|
|
5b26ebc7b8 | ||
|
|
3427ad6ce0 | ||
|
|
4ab5e4326b | ||
|
|
d24a3ce3ab | ||
|
|
29a44db8d9 | ||
|
|
695f3a9212 | ||
|
|
3ff777b8ed | ||
|
|
d94b9806ab | ||
|
|
35c8fc2729 | ||
|
|
0a468fd6d7 | ||
|
|
d31ebbabe4 | ||
|
|
09c9b71adb | ||
|
|
5975db4d66 | ||
|
|
2c86ac654c | ||
|
|
ca5f066eb9 | ||
|
|
410ef86102 | ||
|
|
6792edf32a | ||
|
|
67de167a7e | ||
|
|
0c5c05d90b | ||
|
|
fe43c9c4b2 | ||
|
|
d6a5aa4f6d | ||
|
|
1c3701628d | ||
|
|
45689f22ab | ||
|
|
2c3ddffaac | ||
|
|
c3ea0195c2 | ||
|
|
5f5e50cff0 | ||
|
|
160f4d46d9 | ||
|
|
8d619bd7cc | ||
|
|
b94337d842 | ||
|
|
34f4222ca2 | ||
|
|
5de045edd7 | ||
|
|
5efd3b98de | ||
|
|
82c83312c7 | ||
|
|
5ccb983007 | ||
|
|
7a921399be | ||
|
|
3900f2f982 | ||
|
|
09939f1e07 | ||
|
|
fc3a3ca81f | ||
|
|
ba7bf2e4a6 | ||
|
|
00641037fc | ||
|
|
6f4a1b527b | ||
|
|
1b95576dd2 | ||
|
|
9227471aca | ||
|
|
bf060296eb | ||
|
|
9b3aa41f2e | ||
|
|
ae36585cdb | ||
|
|
e4c636ddd2 | ||
|
|
ae3b25be4b | ||
|
|
a64ed13a40 | ||
|
|
6c1c0e6676 | ||
|
|
82fdd5d121 | ||
|
|
8b40f4435e | ||
|
|
38c87602bb | ||
|
|
7aef3013e7 | ||
|
|
c18676ab4d | ||
|
|
df450c815d | ||
|
|
792ab38760 | ||
|
|
0bba3e76c1 | ||
|
|
3d961ffbb9 | ||
|
|
a3039f57bb | ||
|
|
5499fa9a04 | ||
|
|
b9fd64faff | ||
|
|
1202ff7b2b | ||
|
|
abd9f019f1 | ||
|
|
edb5eead81 | ||
|
|
6b865ff753 | ||
|
|
4fd0734c48 | ||
|
|
f815421acb | ||
|
|
5da926fee1 | ||
|
|
3559531e4c | ||
|
|
449a3b3648 | ||
|
|
7bd338618c | ||
|
|
9048a6f584 | ||
|
|
0cacb1bdac | ||
|
|
318bf35f82 |
40
.github/CODEOWNERS
vendored
40
.github/CODEOWNERS
vendored
@@ -4,24 +4,26 @@
|
|||||||
* @TooTallNate
|
* @TooTallNate
|
||||||
/.github/workflows @AndyBitz @styfle
|
/.github/workflows @AndyBitz @styfle
|
||||||
/packages/frameworks @AndyBitz
|
/packages/frameworks @AndyBitz
|
||||||
/packages/cli/src/commands/dev @TooTallNate @styfle @AndyBitz
|
/packages/cli/src/commands/build @TooTallNate @styfle @AndyBitz @gdborton @jaredpalmer
|
||||||
/packages/cli/src/util/dev @TooTallNate @styfle @AndyBitz
|
/packages/cli/src/commands/dev @TooTallNate @styfle @AndyBitz
|
||||||
/packages/cli/src/commands/domains @javivelasco @mglagola @anatrajkovska
|
/packages/cli/src/util/dev @TooTallNate @styfle @AndyBitz
|
||||||
/packages/cli/src/commands/certs @javivelasco @mglagola @anatrajkovska
|
/packages/cli/src/commands/domains @javivelasco @mglagola @anatrajkovska
|
||||||
/packages/cli/src/commands/env @styfle @lucleray
|
/packages/cli/src/commands/certs @javivelasco @mglagola @anatrajkovska
|
||||||
/packages/client @rdev @styfle @TooTallNate
|
/packages/cli/src/commands/env @styfle @lucleray
|
||||||
/packages/build-utils @styfle @AndyBitz @TooTallNate
|
/packages/client @styfle @TooTallNate
|
||||||
/packages/node @styfle @TooTallNate @lucleray
|
/packages/build-utils @styfle @AndyBitz @TooTallNate
|
||||||
/packages/node-bridge @styfle @TooTallNate @lucleray
|
/packages/middleware @gdborton @javivelasco
|
||||||
/packages/next @Timer @ijjk
|
/packages/node @styfle @TooTallNate @lucleray
|
||||||
/packages/go @styfle @TooTallNate
|
/packages/node-bridge @styfle @TooTallNate @lucleray
|
||||||
/packages/python @styfle @TooTallNate
|
/packages/next @Timer @ijjk
|
||||||
/packages/ruby @styfle @coetry @TooTallNate
|
/packages/go @styfle @TooTallNate
|
||||||
/packages/static-build @styfle @AndyBitz
|
/packages/python @styfle @TooTallNate
|
||||||
/packages/routing-utils @styfle @dav-is @ijjk
|
/packages/ruby @styfle @TooTallNate
|
||||||
/examples @mcsdevv @timothyis
|
/packages/static-build @styfle @AndyBitz
|
||||||
|
/packages/routing-utils @styfle @dav-is @ijjk
|
||||||
|
/examples @mcsdevv
|
||||||
/examples/create-react-app @Timer
|
/examples/create-react-app @Timer
|
||||||
/examples/nextjs @timneutkens @Timer
|
/examples/nextjs @timneutkens @Timer
|
||||||
/examples/hugo @mcsdevv @timothyis @styfle
|
/examples/hugo @mcsdevv @styfle
|
||||||
/examples/jekyll @mcsdevv @timothyis @styfle
|
/examples/jekyll @mcsdevv @styfle
|
||||||
/examples/zola @mcsdevv @timothyis @styfle
|
/examples/zola @mcsdevv @styfle
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"description": "API for the vercel/vercel repo",
|
"description": "API for the vercel/vercel repo",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"vercel-build": "yarn --cwd .. && node ../utils/run.js build all"
|
"vercel-build": "node ../utils/run.js build all"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry/node": "5.11.1",
|
"@sentry/node": "5.11.1",
|
||||||
|
|||||||
15787
examples/nextjs/package-lock.json
generated
Normal file
15787
examples/nextjs/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
10
examples/remix/.gitignore
vendored
10
examples/remix/.gitignore
vendored
@@ -1,8 +1,8 @@
|
|||||||
node_modules
|
node_modules
|
||||||
|
|
||||||
/.cache
|
.cache
|
||||||
/.vercel
|
.vercel
|
||||||
/.output
|
.output
|
||||||
|
|
||||||
/public/build
|
public/build
|
||||||
/api/build
|
api/_build
|
||||||
|
|||||||
@@ -2,56 +2,33 @@
|
|||||||
|
|
||||||
- [Remix Docs](https://remix.run/docs)
|
- [Remix Docs](https://remix.run/docs)
|
||||||
|
|
||||||
## Vercel Setup
|
## Deployment
|
||||||
|
|
||||||
First you'll need the [Vercel CLI](https://vercel.com/docs/cli):
|
After having run the `create-remix` command and selected "Vercel" as a deployment target, you only need to [import your Git repository](https://vercel.com/new) into Vercel, and it will be deployed.
|
||||||
|
|
||||||
|
If you'd like to avoid using a Git repository, you can also deploy the directory by running [Vercel CLI](https://vercel.com/cli):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm i -g vercel
|
npm i -g vercel
|
||||||
|
vercel
|
||||||
```
|
```
|
||||||
|
|
||||||
Before you can run the app in development, you need to link this project to a new Vercel project on your account.
|
It is generally recommended to use a Git repository, because future commits will then automatically be deployed by Vercel, through its [Git Integration](https://vercel.com/docs/concepts/git).
|
||||||
|
|
||||||
**It is important that you use a new project. If you try to link this project to an existing project (like a Next.js site) you will have problems.**
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ vercel link
|
|
||||||
```
|
|
||||||
|
|
||||||
Follow the prompts, and when it's done you should be able to get started.
|
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
You will be running two processes during development when using Vercel as your server.
|
To run your Remix app locally, make sure your project's local dependencies are installed:
|
||||||
|
|
||||||
- Your Vercel server in one
|
|
||||||
- The Remix development server in another
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# in one tab
|
npm install
|
||||||
$ vercel dev
|
```
|
||||||
|
|
||||||
# in another
|
Afterwards, start the Remix development server like so:
|
||||||
$ npm run dev
|
|
||||||
|
```sh
|
||||||
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Open up [http://localhost:3000](http://localhost:3000) and you should be ready to go!
|
Open up [http://localhost:3000](http://localhost:3000) and you should be ready to go!
|
||||||
|
|
||||||
If you'd rather run everything in a single tab, you can look at [concurrently](https://npm.im/concurrently) or similar tools to run both processes in one tab.
|
If you're used to using the `vercel dev` command provided by [Vercel CLI](https://vercel.com/cli) instead, you can also use that, but it's not needed.
|
||||||
|
|
||||||
## Deploying
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ npm run build
|
|
||||||
# preview deployment
|
|
||||||
$ vercel
|
|
||||||
|
|
||||||
# production deployment
|
|
||||||
$ vercel --prod
|
|
||||||
```
|
|
||||||
|
|
||||||
### GitHub Automatic Deployments
|
|
||||||
|
|
||||||
For some reason the GitHub integration doesn't deploy the public folder. We're working with Vercel to figure this out.
|
|
||||||
|
|
||||||
For now, [you can set up a GitHub action with this config](https://gist.github.com/mcansh/91f8effda798b41bb373351fad217070) from our friend [@mcansh](https://github.com/mcansh).
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const { createRequestHandler } = require("@remix-run/vercel");
|
const { createRequestHandler } = require("@remix-run/vercel");
|
||||||
|
|
||||||
module.exports = createRequestHandler({
|
module.exports = createRequestHandler({
|
||||||
build: require("./build")
|
build: require("./_build")
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as React from "react";
|
|
||||||
import {
|
import {
|
||||||
Link,
|
Link,
|
||||||
Links,
|
Links,
|
||||||
@@ -7,23 +6,14 @@ import {
|
|||||||
Outlet,
|
Outlet,
|
||||||
Scripts,
|
Scripts,
|
||||||
ScrollRestoration,
|
ScrollRestoration,
|
||||||
useCatch,
|
useCatch
|
||||||
useLocation
|
|
||||||
} from "remix";
|
} from "remix";
|
||||||
import type { LinksFunction } from "remix";
|
import type { LinksFunction } from "remix";
|
||||||
|
|
||||||
import deleteMeRemixStyles from "~/styles/demos/remix.css";
|
|
||||||
import globalStylesUrl from "~/styles/global.css";
|
import globalStylesUrl from "~/styles/global.css";
|
||||||
import darkStylesUrl from "~/styles/dark.css";
|
import darkStylesUrl from "~/styles/dark.css";
|
||||||
|
|
||||||
/**
|
// https://remix.run/api/app#links
|
||||||
* The `links` export is a function that returns an array of objects that map to
|
|
||||||
* the attributes for an HTML `<link>` element. These will load `<link>` tags on
|
|
||||||
* every route in the app, but individual routes can include their own links
|
|
||||||
* that are automatically unloaded when a user navigates away from the route.
|
|
||||||
*
|
|
||||||
* https://remix.run/api/app#links
|
|
||||||
*/
|
|
||||||
export let links: LinksFunction = () => {
|
export let links: LinksFunction = () => {
|
||||||
return [
|
return [
|
||||||
{ rel: "stylesheet", href: globalStylesUrl },
|
{ rel: "stylesheet", href: globalStylesUrl },
|
||||||
@@ -31,16 +21,12 @@ export let links: LinksFunction = () => {
|
|||||||
rel: "stylesheet",
|
rel: "stylesheet",
|
||||||
href: darkStylesUrl,
|
href: darkStylesUrl,
|
||||||
media: "(prefers-color-scheme: dark)"
|
media: "(prefers-color-scheme: dark)"
|
||||||
},
|
}
|
||||||
{ rel: "stylesheet", href: deleteMeRemixStyles }
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
// https://remix.run/api/conventions#default-export
|
||||||
* The root module's default export is a component that renders the current
|
// https://remix.run/api/conventions#route-filenames
|
||||||
* route via the `<Outlet />` component. Think of this as the global layout
|
|
||||||
* component for your app.
|
|
||||||
*/
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
<Document>
|
<Document>
|
||||||
@@ -51,68 +37,27 @@ export default function App() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Document({
|
// https://remix.run/docs/en/v1/api/conventions#errorboundary
|
||||||
children,
|
export function ErrorBoundary({ error }: { error: Error }) {
|
||||||
title
|
console.error(error);
|
||||||
}: {
|
|
||||||
children: React.ReactNode;
|
|
||||||
title?: string;
|
|
||||||
}) {
|
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<Document title="Error!">
|
||||||
<head>
|
<Layout>
|
||||||
<meta charSet="utf-8" />
|
<div>
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
<h1>There was an error</h1>
|
||||||
{title ? <title>{title}</title> : null}
|
<p>{error.message}</p>
|
||||||
<Meta />
|
<hr />
|
||||||
<Links />
|
<p>
|
||||||
</head>
|
Hey, developer, you should replace this with what you want your
|
||||||
<body>
|
users to see.
|
||||||
{children}
|
</p>
|
||||||
<RouteChangeAnnouncement />
|
</div>
|
||||||
<ScrollRestoration />
|
</Layout>
|
||||||
<Scripts />
|
</Document>
|
||||||
{process.env.NODE_ENV === "development" && <LiveReload />}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Layout({ children }: React.PropsWithChildren<{}>) {
|
|
||||||
return (
|
|
||||||
<div className="remix-app">
|
|
||||||
<header className="remix-app__header">
|
|
||||||
<div className="container remix-app__header-content">
|
|
||||||
<Link to="/" title="Remix" className="remix-app__header-home-link">
|
|
||||||
<RemixLogo />
|
|
||||||
</Link>
|
|
||||||
<nav aria-label="Main navigation" className="remix-app__header-nav">
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<Link to="/">Home</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://remix.run/docs">Remix Docs</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://github.com/remix-run/remix">GitHub</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<div className="remix-app__main">
|
|
||||||
<div className="container remix-app__main-content">{children}</div>
|
|
||||||
</div>
|
|
||||||
<footer className="remix-app__footer">
|
|
||||||
<div className="container remix-app__footer-content">
|
|
||||||
<p>© You!</p>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://remix.run/docs/en/v1/api/conventions#catchboundary
|
||||||
export function CatchBoundary() {
|
export function CatchBoundary() {
|
||||||
let caught = useCatch();
|
let caught = useCatch();
|
||||||
|
|
||||||
@@ -148,26 +93,68 @@ export function CatchBoundary() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ErrorBoundary({ error }: { error: Error }) {
|
function Document({
|
||||||
console.error(error);
|
children,
|
||||||
|
title
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
title?: string;
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<Document title="Error!">
|
<html lang="en">
|
||||||
<Layout>
|
<head>
|
||||||
<div>
|
<meta charSet="utf-8" />
|
||||||
<h1>There was an error</h1>
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
<p>{error.message}</p>
|
{title ? <title>{title}</title> : null}
|
||||||
<hr />
|
<Meta />
|
||||||
<p>
|
<Links />
|
||||||
Hey, developer, you should replace this with what you want your
|
</head>
|
||||||
users to see.
|
<body>
|
||||||
</p>
|
{children}
|
||||||
</div>
|
<ScrollRestoration />
|
||||||
</Layout>
|
<Scripts />
|
||||||
</Document>
|
{process.env.NODE_ENV === "development" && <LiveReload />}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function RemixLogo(props: React.ComponentPropsWithoutRef<"svg">) {
|
function Layout({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<div className="remix-app">
|
||||||
|
<header className="remix-app__header">
|
||||||
|
<div className="container remix-app__header-content">
|
||||||
|
<Link to="/" title="Remix" className="remix-app__header-home-link">
|
||||||
|
<RemixLogo />
|
||||||
|
</Link>
|
||||||
|
<nav aria-label="Main navigation" className="remix-app__header-nav">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<Link to="/">Home</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://remix.run/docs">Remix Docs</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://github.com/remix-run/remix">GitHub</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div className="remix-app__main">
|
||||||
|
<div className="container remix-app__main-content">{children}</div>
|
||||||
|
</div>
|
||||||
|
<footer className="remix-app__footer">
|
||||||
|
<div className="container remix-app__footer-content">
|
||||||
|
<p>© You!</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function RemixLogo() {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
viewBox="0 0 659 165"
|
viewBox="0 0 659 165"
|
||||||
@@ -179,7 +166,6 @@ function RemixLogo(props: React.ComponentPropsWithoutRef<"svg">) {
|
|||||||
width="106"
|
width="106"
|
||||||
height="30"
|
height="30"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
{...props}
|
|
||||||
>
|
>
|
||||||
<title id="remix-run-logo-title">Remix Logo</title>
|
<title id="remix-run-logo-title">Remix Logo</title>
|
||||||
<path d="M0 161V136H45.5416C53.1486 136 54.8003 141.638 54.8003 145V161H0Z M133.85 124.16C135.3 142.762 135.3 151.482 135.3 161H92.2283C92.2283 158.927 92.2653 157.03 92.3028 155.107C92.4195 149.128 92.5411 142.894 91.5717 130.304C90.2905 111.872 82.3473 107.776 67.7419 107.776H54.8021H0V74.24H69.7918C88.2407 74.24 97.4651 68.632 97.4651 53.784C97.4651 40.728 88.2407 32.816 69.7918 32.816H0V0H77.4788C119.245 0 140 19.712 140 51.2C140 74.752 125.395 90.112 105.665 92.672C122.32 96 132.057 105.472 133.85 124.16Z" />
|
<path d="M0 161V136H45.5416C53.1486 136 54.8003 141.638 54.8003 145V161H0Z M133.85 124.16C135.3 142.762 135.3 151.482 135.3 161H92.2283C92.2283 158.927 92.2653 157.03 92.3028 155.107C92.4195 149.128 92.5411 142.894 91.5717 130.304C90.2905 111.872 82.3473 107.776 67.7419 107.776H54.8021H0V74.24H69.7918C88.2407 74.24 97.4651 68.632 97.4651 53.784C97.4651 40.728 88.2407 32.816 69.7918 32.816H0V0H77.4788C119.245 0 140 19.712 140 51.2C140 74.752 125.395 90.112 105.665 92.672C122.32 96 132.057 105.472 133.85 124.16Z" />
|
||||||
@@ -190,58 +176,3 @@ function RemixLogo(props: React.ComponentPropsWithoutRef<"svg">) {
|
|||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides an alert for screen reader users when the route changes.
|
|
||||||
*/
|
|
||||||
const RouteChangeAnnouncement = React.memo(() => {
|
|
||||||
let [hydrated, setHydrated] = React.useState(false);
|
|
||||||
let [innerHtml, setInnerHtml] = React.useState("");
|
|
||||||
let location = useLocation();
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
setHydrated(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
let firstRenderRef = React.useRef(true);
|
|
||||||
React.useEffect(() => {
|
|
||||||
// Skip the first render because we don't want an announcement on the
|
|
||||||
// initial page load.
|
|
||||||
if (firstRenderRef.current) {
|
|
||||||
firstRenderRef.current = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let pageTitle = location.pathname === "/" ? "Home page" : document.title;
|
|
||||||
setInnerHtml(`Navigated to ${pageTitle}`);
|
|
||||||
}, [location.pathname]);
|
|
||||||
|
|
||||||
// Render nothing on the server. The live region provides no value unless
|
|
||||||
// scripts are loaded and the browser takes over normal routing.
|
|
||||||
if (!hydrated) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
aria-live="assertive"
|
|
||||||
aria-atomic
|
|
||||||
id="route-change-region"
|
|
||||||
style={{
|
|
||||||
border: "0",
|
|
||||||
clipPath: "inset(100%)",
|
|
||||||
clip: "rect(0 0 0 0)",
|
|
||||||
height: "1px",
|
|
||||||
margin: "-1px",
|
|
||||||
overflow: "hidden",
|
|
||||||
padding: "0",
|
|
||||||
position: "absolute",
|
|
||||||
width: "1px",
|
|
||||||
whiteSpace: "nowrap",
|
|
||||||
wordWrap: "normal"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{innerHtml}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,120 +0,0 @@
|
|||||||
/*
|
|
||||||
* You probably want to just delete this file; it's just for the demo pages.
|
|
||||||
*/
|
|
||||||
.remix-app {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
min-height: 100vh;
|
|
||||||
min-height: calc(100vh - env(safe-area-inset-bottom));
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix-app > * {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix-app__header {
|
|
||||||
padding-top: 1rem;
|
|
||||||
padding-bottom: 1rem;
|
|
||||||
border-bottom: 1px solid var(--color-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix-app__header-content {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix-app__header-home-link {
|
|
||||||
width: 106px;
|
|
||||||
height: 30px;
|
|
||||||
color: var(--color-foreground);
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix-app__header-nav ul {
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix-app__header-nav li {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix-app__main {
|
|
||||||
flex: 1 1 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix-app__footer {
|
|
||||||
padding-top: 1rem;
|
|
||||||
padding-bottom: 1rem;
|
|
||||||
border-top: 1px solid var(--color-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix-app__footer-content {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix__page {
|
|
||||||
--gap: 1rem;
|
|
||||||
--space: 2rem;
|
|
||||||
display: grid;
|
|
||||||
grid-auto-rows: min-content;
|
|
||||||
gap: var(--gap);
|
|
||||||
padding-top: var(--space);
|
|
||||||
padding-bottom: var(--space);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media print, screen and (min-width: 640px) {
|
|
||||||
.remix__page {
|
|
||||||
--gap: 2rem;
|
|
||||||
grid-auto-rows: unset;
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 1024px) {
|
|
||||||
.remix__page {
|
|
||||||
--gap: 4rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix__page > main > :first-child {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix__page > main > :last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix__page > aside {
|
|
||||||
margin: 0;
|
|
||||||
padding: 1.5ch 2ch;
|
|
||||||
border: solid 1px var(--color-border);
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix__page > aside > :first-child {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix__page > aside > :last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix__form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
padding: 1rem;
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remix__form > * {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
@@ -96,3 +96,121 @@ input:where([type="search"]) {
|
|||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.remix-app {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 100vh;
|
||||||
|
min-height: calc(100vh - env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
|
|
||||||
|
.remix-app > * {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remix-app__header {
|
||||||
|
padding-top: 1rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.remix-app__header-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remix-app__header-home-link {
|
||||||
|
width: 106px;
|
||||||
|
height: 30px;
|
||||||
|
color: var(--color-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.remix-app__header-nav ul {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remix-app__header-nav li {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remix-app__main {
|
||||||
|
flex: 1 1 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remix-app__footer {
|
||||||
|
padding-top: 1rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.remix-app__footer-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remix__page {
|
||||||
|
--gap: 1rem;
|
||||||
|
--space: 2rem;
|
||||||
|
display: grid;
|
||||||
|
grid-auto-rows: min-content;
|
||||||
|
gap: var(--gap);
|
||||||
|
padding-top: var(--space);
|
||||||
|
padding-bottom: var(--space);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print, screen and (min-width: 640px) {
|
||||||
|
.remix__page {
|
||||||
|
--gap: 2rem;
|
||||||
|
grid-auto-rows: unset;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 1024px) {
|
||||||
|
.remix__page {
|
||||||
|
--gap: 4rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.remix__page > main > :first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remix__page > main > :last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remix__page > aside {
|
||||||
|
margin: 0;
|
||||||
|
padding: 1.5ch 2ch;
|
||||||
|
border: solid 1px var(--color-border);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remix__page > aside > :first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remix__page > aside > :last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remix__form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remix__form > * {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|||||||
8345
examples/remix/package-lock.json
generated
Normal file
8345
examples/remix/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -9,14 +9,15 @@
|
|||||||
"postinstall": "remix setup node"
|
"postinstall": "remix setup node"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@remix-run/react": "^1.0.4",
|
"@remix-run/react": "^1.0.6",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"remix": "^1.0.4",
|
"remix": "^1.0.6",
|
||||||
"@remix-run/vercel": "^1.0.4"
|
"@remix-run/serve": "^1.0.6",
|
||||||
|
"@remix-run/vercel": "^1.0.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@remix-run/dev": "^1.0.4",
|
"@remix-run/dev": "^1.0.6",
|
||||||
"@types/react": "^17.0.24",
|
"@types/react": "^17.0.24",
|
||||||
"@types/react-dom": "^17.0.9",
|
"@types/react-dom": "^17.0.9",
|
||||||
"typescript": "^4.1.2"
|
"typescript": "^4.1.2"
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ module.exports = {
|
|||||||
appDirectory: "app",
|
appDirectory: "app",
|
||||||
browserBuildDirectory: "public/build",
|
browserBuildDirectory: "public/build",
|
||||||
publicPath: "/build/",
|
publicPath: "/build/",
|
||||||
serverBuildDirectory: "api/build"
|
serverBuildDirectory: "api/_build"
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"build": {
|
"build": {
|
||||||
"env": {
|
"env": {
|
||||||
"ENABLE_FILE_SYSTEM_API": "1"
|
"ENABLE_FILE_SYSTEM_API": "1"
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vercel/build-utils",
|
"name": "@vercel/build-utils",
|
||||||
"version": "2.12.3-canary.21",
|
"version": "2.12.3-canary.42",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./dist/index.d.js",
|
"types": "./dist/index.d.js",
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
"@types/node-fetch": "^2.1.6",
|
"@types/node-fetch": "^2.1.6",
|
||||||
"@types/semver": "6.0.0",
|
"@types/semver": "6.0.0",
|
||||||
"@types/yazl": "^2.4.1",
|
"@types/yazl": "^2.4.1",
|
||||||
"@vercel/frameworks": "0.5.1-canary.13",
|
"@vercel/frameworks": "0.5.1-canary.17",
|
||||||
"@vercel/ncc": "0.24.0",
|
"@vercel/ncc": "0.24.0",
|
||||||
"aggregate-error": "3.0.1",
|
"aggregate-error": "3.0.1",
|
||||||
"async-retry": "1.2.3",
|
"async-retry": "1.2.3",
|
||||||
|
|||||||
@@ -1,124 +1,296 @@
|
|||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import { join, dirname, relative } from 'path';
|
import { join, parse, relative, dirname, basename, extname } from 'path';
|
||||||
import glob from './fs/glob';
|
import glob from './fs/glob';
|
||||||
import { normalizePath } from './fs/normalize-path';
|
import { normalizePath } from './fs/normalize-path';
|
||||||
import { FILES_SYMBOL, getLambdaOptionsFromFunction, Lambda } from './lambda';
|
import { FILES_SYMBOL, Lambda } from './lambda';
|
||||||
import type FileBlob from './file-blob';
|
import type { BuildOptions, Files } from './types';
|
||||||
import type { BuilderFunctions, BuildOptions, Files } from './types';
|
import { debug, getIgnoreFilter } from '.';
|
||||||
import minimatch from 'minimatch';
|
|
||||||
|
// `.output` was already created by the Build Command, so we have
|
||||||
|
// to ensure its contents don't get bundled into the Lambda. Similarily,
|
||||||
|
// we don't want to bundle anything from `.vercel` either. Lastly,
|
||||||
|
// Builders/Runtimes didn't have `vercel.json` or `now.json`.
|
||||||
|
const ignoredPaths = ['.output', '.vercel', 'vercel.json', 'now.json'];
|
||||||
|
|
||||||
|
const shouldIgnorePath = (
|
||||||
|
file: string,
|
||||||
|
ignoreFilter: any,
|
||||||
|
ignoreFile: boolean
|
||||||
|
) => {
|
||||||
|
const isNative = ignoredPaths.some(item => {
|
||||||
|
return file.startsWith(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!ignoreFile) {
|
||||||
|
return isNative;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isNative || ignoreFilter(file);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSourceFiles = async (workPath: string, ignoreFilter: any) => {
|
||||||
|
const list = await glob('**', {
|
||||||
|
cwd: workPath,
|
||||||
|
});
|
||||||
|
|
||||||
|
// We're not passing this as an `ignore` filter to the `glob` function above,
|
||||||
|
// so that we can re-use exactly the same `getIgnoreFilter` method that the
|
||||||
|
// Build Step uses (literally the same code). Note that this exclusion only applies
|
||||||
|
// when deploying. Locally, another exclusion is needed, which is handled
|
||||||
|
// further below in the `convertRuntimeToPlugin` function.
|
||||||
|
for (const file in list) {
|
||||||
|
if (shouldIgnorePath(file, ignoreFilter, true)) {
|
||||||
|
delete list[file];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert legacy Runtime to a Plugin.
|
* Convert legacy Runtime to a Plugin.
|
||||||
* @param buildRuntime - a legacy build() function from a Runtime
|
* @param buildRuntime - a legacy build() function from a Runtime
|
||||||
|
* @param packageName - the name of the package, for example `vercel-plugin-python`
|
||||||
* @param ext - the file extension, for example `.py`
|
* @param ext - the file extension, for example `.py`
|
||||||
*/
|
*/
|
||||||
export function convertRuntimeToPlugin(
|
export function convertRuntimeToPlugin(
|
||||||
buildRuntime: (options: BuildOptions) => Promise<{ output: Lambda }>,
|
buildRuntime: (options: BuildOptions) => Promise<{ output: Lambda }>,
|
||||||
|
packageName: string,
|
||||||
ext: string
|
ext: string
|
||||||
) {
|
) {
|
||||||
// This `build()` signature should match `plugin.build()` signature in `vercel build`.
|
// This `build()` signature should match `plugin.build()` signature in `vercel build`.
|
||||||
return async function build({
|
return async function build({ workPath }: { workPath: string }) {
|
||||||
vercelConfig,
|
// We also don't want to provide any files to Runtimes that were ignored
|
||||||
workPath,
|
// through `.vercelignore` or `.nowignore`, because the Build Step does the same.
|
||||||
}: {
|
const ignoreFilter = await getIgnoreFilter(workPath);
|
||||||
vercelConfig: { functions?: BuilderFunctions; regions?: string[] };
|
|
||||||
workPath: string;
|
// Retrieve the files that are currently available on the File System,
|
||||||
}) {
|
// before the Legacy Runtime has even started to build.
|
||||||
const opts = { cwd: workPath };
|
const sourceFilesPreBuild = await getSourceFiles(workPath, ignoreFilter);
|
||||||
const files = await glob('**', opts);
|
|
||||||
delete files['vercel.json']; // Builders/Runtimes didn't have vercel.json
|
// Instead of doing another `glob` to get all the matching source files,
|
||||||
const entrypoints = await glob(`api/**/*${ext}`, opts);
|
// we'll filter the list of existing files down to only the ones
|
||||||
|
// that are matching the entrypoint pattern, so we're first creating
|
||||||
|
// a clean new list to begin.
|
||||||
|
const entrypoints = Object.assign({}, sourceFilesPreBuild);
|
||||||
|
|
||||||
|
const entrypointMatch = new RegExp(`^api/.*${ext}$`);
|
||||||
|
|
||||||
|
// Up next, we'll strip out the files from the list of entrypoints
|
||||||
|
// that aren't actually considered entrypoints.
|
||||||
|
for (const file in entrypoints) {
|
||||||
|
if (!entrypointMatch.test(file)) {
|
||||||
|
delete entrypoints[file];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const pages: { [key: string]: any } = {};
|
const pages: { [key: string]: any } = {};
|
||||||
const { functions = {} } = vercelConfig;
|
const pluginName = packageName.replace('vercel-plugin-', '');
|
||||||
const traceDir = join(workPath, '.output', 'runtime-traced-files');
|
const outputPath = join(workPath, '.output');
|
||||||
|
|
||||||
|
const traceDir = join(
|
||||||
|
outputPath,
|
||||||
|
`inputs`,
|
||||||
|
// Legacy Runtimes can only provide API Routes, so that's
|
||||||
|
// why we can use this prefix for all of them. Here, we have to
|
||||||
|
// make sure to not use a cryptic hash name, because people
|
||||||
|
// need to be able to easily inspect the output.
|
||||||
|
`api-routes-${pluginName}`
|
||||||
|
);
|
||||||
|
|
||||||
await fs.ensureDir(traceDir);
|
await fs.ensureDir(traceDir);
|
||||||
|
|
||||||
for (const entrypoint of Object.keys(entrypoints)) {
|
const entryRoot = join(outputPath, 'server', 'pages');
|
||||||
const key =
|
|
||||||
Object.keys(functions).find(
|
|
||||||
src => src === entrypoint || minimatch(entrypoint, src)
|
|
||||||
) || '';
|
|
||||||
const config = functions[key] || {};
|
|
||||||
|
|
||||||
|
for (const entrypoint of Object.keys(entrypoints)) {
|
||||||
const { output } = await buildRuntime({
|
const { output } = await buildRuntime({
|
||||||
files,
|
files: sourceFilesPreBuild,
|
||||||
entrypoint,
|
entrypoint,
|
||||||
workPath,
|
workPath,
|
||||||
config: {
|
config: {
|
||||||
zeroConfig: true,
|
zeroConfig: true,
|
||||||
includeFiles: config.includeFiles,
|
},
|
||||||
excludeFiles: config.excludeFiles,
|
meta: {
|
||||||
|
avoidTopLevelInstall: true,
|
||||||
|
skipDownload: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
pages[entrypoint] = {
|
// @ts-ignore This symbol is a private API
|
||||||
handler: output.handler,
|
const lambdaFiles: Files = output[FILES_SYMBOL];
|
||||||
|
|
||||||
|
// When deploying, the `files` that are passed to the Legacy Runtimes already
|
||||||
|
// have certain files that are ignored stripped, but locally, that list of
|
||||||
|
// files isn't used by the Legacy Runtimes, so we need to apply the filters
|
||||||
|
// to the outputs that they are returning instead.
|
||||||
|
for (const file in lambdaFiles) {
|
||||||
|
if (shouldIgnorePath(file, ignoreFilter, false)) {
|
||||||
|
delete lambdaFiles[file];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let handlerFileBase = output.handler;
|
||||||
|
let handlerFile = lambdaFiles[handlerFileBase];
|
||||||
|
let handlerHasImport = false;
|
||||||
|
|
||||||
|
const { handler } = output;
|
||||||
|
const handlerMethod = handler.split('.').pop();
|
||||||
|
const handlerFileName = handler.replace(`.${handlerMethod}`, '');
|
||||||
|
|
||||||
|
// For compiled languages, the launcher file for the Lambda generated
|
||||||
|
// by the Legacy Runtime matches the `handler` defined for it, but for
|
||||||
|
// interpreted languages, the `handler` consists of the launcher file name
|
||||||
|
// without an extension, plus the name of the method inside of that file
|
||||||
|
// that should be invoked, so we have to construct the file path explicitly.
|
||||||
|
if (!handlerFile) {
|
||||||
|
handlerFileBase = handlerFileName + ext;
|
||||||
|
handlerFile = lambdaFiles[handlerFileBase];
|
||||||
|
handlerHasImport = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!handlerFile || !handlerFile.fsPath) {
|
||||||
|
throw new Error(
|
||||||
|
`Could not find a handler file. Please ensure that \`files\` for the returned \`Lambda\` contains an \`FileFsRef\` named "${handlerFileBase}" with a valid \`fsPath\`.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlerExtName = extname(handlerFile.fsPath);
|
||||||
|
|
||||||
|
const entryBase = basename(entrypoint).replace(ext, handlerExtName);
|
||||||
|
const entryPath = join(dirname(entrypoint), entryBase);
|
||||||
|
const entry = join(entryRoot, entryPath);
|
||||||
|
|
||||||
|
// Create the parent directory of the API Route that will be created
|
||||||
|
// for the current entrypoint inside of `.output/server/pages/api`.
|
||||||
|
await fs.ensureDir(dirname(entry));
|
||||||
|
|
||||||
|
// For compiled languages, the launcher file will be binary and therefore
|
||||||
|
// won't try to import a user-provided request handler (instead, it will
|
||||||
|
// contain it). But for interpreted languages, the launcher might try to
|
||||||
|
// load a user-provided request handler from the source file instead of bundling
|
||||||
|
// it, so we have to adjust the import statement inside the launcher to point
|
||||||
|
// to the respective source file. Previously, Legacy Runtimes simply expected
|
||||||
|
// the user-provided request-handler to be copied right next to the launcher,
|
||||||
|
// but with the new File System API, files won't be moved around unnecessarily.
|
||||||
|
if (handlerHasImport) {
|
||||||
|
const { fsPath } = handlerFile;
|
||||||
|
const encoding = 'utf-8';
|
||||||
|
|
||||||
|
// This is the true directory of the user-provided request handler in the
|
||||||
|
// source files, so that's what we will use as an import path in the launcher.
|
||||||
|
const locationPrefix = relative(entry, outputPath);
|
||||||
|
|
||||||
|
let handlerContent = await fs.readFile(fsPath, encoding);
|
||||||
|
|
||||||
|
const importPaths = [
|
||||||
|
// This is the full entrypoint path, like `./api/test.py`. In our tests
|
||||||
|
// Python didn't support importing from a parent directory without using different
|
||||||
|
// code in the launcher that registers it as a location for modules and then changing
|
||||||
|
// the importing syntax, but continuing to import it like before seems to work. If
|
||||||
|
// other languages need this, we should consider excluding Python explicitly.
|
||||||
|
// `./${entrypoint}`,
|
||||||
|
|
||||||
|
// This is the entrypoint path without extension, like `api/test`
|
||||||
|
entrypoint.slice(0, -ext.length),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Generate a list of regular expressions that we can use for
|
||||||
|
// finding matches, but only allow matches if the import path is
|
||||||
|
// wrapped inside single (') or double quotes (").
|
||||||
|
const patterns = importPaths.map(path => {
|
||||||
|
// eslint-disable-next-line no-useless-escape
|
||||||
|
return new RegExp(`('|")(${path.replace(/\./g, '\\.')})('|")`, 'g');
|
||||||
|
});
|
||||||
|
|
||||||
|
let replacedMatch = null;
|
||||||
|
|
||||||
|
for (const pattern of patterns) {
|
||||||
|
const newContent = handlerContent.replace(
|
||||||
|
pattern,
|
||||||
|
(_, p1, p2, p3) => {
|
||||||
|
return `${p1}${join(locationPrefix, p2)}${p3}`;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (newContent !== handlerContent) {
|
||||||
|
debug(
|
||||||
|
`Replaced "${pattern}" inside "${entry}" to ensure correct import of user-provided request handler`
|
||||||
|
);
|
||||||
|
|
||||||
|
handlerContent = newContent;
|
||||||
|
replacedMatch = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!replacedMatch) {
|
||||||
|
new Error(
|
||||||
|
`No replacable matches for "${importPaths[0]}" or "${importPaths[1]}" found in "${fsPath}"`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await fs.writeFile(entry, handlerContent, encoding);
|
||||||
|
} else {
|
||||||
|
await fs.copy(handlerFile.fsPath, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy Runtimes based on interpreted languages will create a new launcher file
|
||||||
|
// for every entrypoint, but they will create each one inside `workPath`, which means that
|
||||||
|
// the launcher for one entrypoint will overwrite the launcher provided for the previous
|
||||||
|
// entrypoint. That's why, above, we copy the file contents into the new destination (and
|
||||||
|
// optionally transform them along the way), instead of linking. We then also want to remove
|
||||||
|
// the copy origin right here, so that the `workPath` doesn't contain a useless launcher file
|
||||||
|
// once the build has finished running.
|
||||||
|
await fs.remove(handlerFile.fsPath);
|
||||||
|
debug(`Removed temporary file "${handlerFile.fsPath}"`);
|
||||||
|
|
||||||
|
const nft = `${entry}.nft.json`;
|
||||||
|
|
||||||
|
const json = JSON.stringify({
|
||||||
|
version: 2,
|
||||||
|
files: Object.keys(lambdaFiles)
|
||||||
|
.map(file => {
|
||||||
|
const { fsPath } = lambdaFiles[file];
|
||||||
|
|
||||||
|
if (!fsPath) {
|
||||||
|
throw new Error(
|
||||||
|
`File "${file}" is missing valid \`fsPath\` property`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The handler was already moved into position above.
|
||||||
|
if (file === handlerFileBase) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalizePath(relative(dirname(nft), fsPath));
|
||||||
|
})
|
||||||
|
.filter(Boolean),
|
||||||
|
});
|
||||||
|
|
||||||
|
await fs.writeFile(nft, json);
|
||||||
|
|
||||||
|
// Add an entry that will later on be added to the `functions-manifest.json`
|
||||||
|
// file that is placed inside of the `.output` directory.
|
||||||
|
pages[normalizePath(entryPath)] = {
|
||||||
|
// Because the underlying file used as a handler was placed
|
||||||
|
// inside `.output/server/pages/api`, it no longer has the name it originally
|
||||||
|
// had and is now named after the API Route that it's responsible for,
|
||||||
|
// so we have to adjust the name of the Lambda handler accordingly.
|
||||||
|
handler: handler.replace(handlerFileName, parse(entry).name),
|
||||||
runtime: output.runtime,
|
runtime: output.runtime,
|
||||||
memory: output.memory,
|
memory: output.memory,
|
||||||
maxDuration: output.maxDuration,
|
maxDuration: output.maxDuration,
|
||||||
environment: output.environment,
|
environment: output.environment,
|
||||||
allowQuery: output.allowQuery,
|
allowQuery: output.allowQuery,
|
||||||
regions: output.regions,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// @ts-ignore This symbol is a private API
|
|
||||||
const lambdaFiles: Files = output[FILES_SYMBOL];
|
|
||||||
|
|
||||||
const entry = join(workPath, '.output', 'server', 'pages', entrypoint);
|
|
||||||
await fs.ensureDir(dirname(entry));
|
|
||||||
await linkOrCopy(files[entrypoint].fsPath, entry);
|
|
||||||
|
|
||||||
const tracedFiles: {
|
|
||||||
absolutePath: string;
|
|
||||||
relativePath: string;
|
|
||||||
}[] = [];
|
|
||||||
|
|
||||||
Object.entries(lambdaFiles).forEach(async ([relPath, file]) => {
|
|
||||||
const newPath = join(traceDir, relPath);
|
|
||||||
tracedFiles.push({ absolutePath: newPath, relativePath: relPath });
|
|
||||||
if (file.fsPath) {
|
|
||||||
await linkOrCopy(file.fsPath, newPath);
|
|
||||||
} else if (file.type === 'FileBlob') {
|
|
||||||
const { data, mode } = file as FileBlob;
|
|
||||||
await fs.writeFile(newPath, data, { mode });
|
|
||||||
} else {
|
|
||||||
throw new Error(`Unknown file type: ${file.type}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const nft = join(
|
|
||||||
workPath,
|
|
||||||
'.output',
|
|
||||||
'server',
|
|
||||||
'pages',
|
|
||||||
`${entrypoint}.nft.json`
|
|
||||||
);
|
|
||||||
const json = JSON.stringify({
|
|
||||||
version: 1,
|
|
||||||
files: tracedFiles.map(f => ({
|
|
||||||
input: normalizePath(relative(nft, f.absolutePath)),
|
|
||||||
output: normalizePath(f.relativePath),
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
|
|
||||||
await fs.ensureDir(dirname(nft));
|
|
||||||
await fs.writeFile(nft, json);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await updateFunctionsManifest({ vercelConfig, workPath, pages });
|
// Add any Serverless Functions that were exposed by the Legacy Runtime
|
||||||
|
// to the `functions-manifest.json` file provided in `.output`.
|
||||||
|
await updateFunctionsManifest({ workPath, pages });
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function linkOrCopy(existingPath: string, newPath: string) {
|
|
||||||
try {
|
|
||||||
await fs.createLink(existingPath, newPath);
|
|
||||||
} catch (err: any) {
|
|
||||||
if (err.code !== 'EEXIST') {
|
|
||||||
await fs.copyFile(existingPath, newPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function readJson(filePath: string): Promise<{ [key: string]: any }> {
|
async function readJson(filePath: string): Promise<{ [key: string]: any }> {
|
||||||
try {
|
try {
|
||||||
const str = await fs.readFile(filePath, 'utf8');
|
const str = await fs.readFile(filePath, 'utf8');
|
||||||
@@ -133,15 +305,12 @@ async function readJson(filePath: string): Promise<{ [key: string]: any }> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* If `.output/functions-manifest.json` exists, append to the pages
|
* If `.output/functions-manifest.json` exists, append to the pages
|
||||||
* property. Otherwise write a new file. This will also read `vercel.json`
|
* property. Otherwise write a new file.
|
||||||
* and apply relevant `functions` property config.
|
|
||||||
*/
|
*/
|
||||||
export async function updateFunctionsManifest({
|
export async function updateFunctionsManifest({
|
||||||
vercelConfig,
|
|
||||||
workPath,
|
workPath,
|
||||||
pages,
|
pages,
|
||||||
}: {
|
}: {
|
||||||
vercelConfig: { functions?: BuilderFunctions; regions?: string[] };
|
|
||||||
workPath: string;
|
workPath: string;
|
||||||
pages: { [key: string]: any };
|
pages: { [key: string]: any };
|
||||||
}) {
|
}) {
|
||||||
@@ -152,52 +321,92 @@ export async function updateFunctionsManifest({
|
|||||||
);
|
);
|
||||||
const functionsManifest = await readJson(functionsManifestPath);
|
const functionsManifest = await readJson(functionsManifestPath);
|
||||||
|
|
||||||
if (!functionsManifest.version) functionsManifest.version = 1;
|
if (!functionsManifest.version) functionsManifest.version = 2;
|
||||||
if (!functionsManifest.pages) functionsManifest.pages = {};
|
if (!functionsManifest.pages) functionsManifest.pages = {};
|
||||||
|
|
||||||
for (const [pageKey, pageConfig] of Object.entries(pages)) {
|
for (const [pageKey, pageConfig] of Object.entries(pages)) {
|
||||||
const fnConfig = await getLambdaOptionsFromFunction({
|
functionsManifest.pages[pageKey] = { ...pageConfig };
|
||||||
sourceFile: pageKey,
|
|
||||||
config: vercelConfig,
|
|
||||||
});
|
|
||||||
functionsManifest.pages[pageKey] = {
|
|
||||||
...pageConfig,
|
|
||||||
memory: fnConfig.memory || pageConfig.memory,
|
|
||||||
maxDuration: fnConfig.maxDuration || pageConfig.maxDuration,
|
|
||||||
regions: vercelConfig.regions || pageConfig.regions,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await fs.writeFile(functionsManifestPath, JSON.stringify(functionsManifest));
|
await fs.writeFile(functionsManifestPath, JSON.stringify(functionsManifest));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will append routes to the `routes-manifest.json` file.
|
* Append routes to the `routes-manifest.json` file.
|
||||||
* If the file does not exist, it'll be created.
|
* If the file does not exist, it will be created.
|
||||||
*/
|
*/
|
||||||
export async function updateRoutesManifest({
|
export async function updateRoutesManifest({
|
||||||
workPath,
|
workPath,
|
||||||
|
redirects,
|
||||||
|
rewrites,
|
||||||
|
headers,
|
||||||
dynamicRoutes,
|
dynamicRoutes,
|
||||||
|
staticRoutes,
|
||||||
}: {
|
}: {
|
||||||
workPath: string;
|
workPath: string;
|
||||||
|
redirects?: {
|
||||||
|
source: string;
|
||||||
|
destination: string;
|
||||||
|
statusCode: number;
|
||||||
|
regex: string;
|
||||||
|
}[];
|
||||||
|
rewrites?: {
|
||||||
|
source: string;
|
||||||
|
destination: string;
|
||||||
|
regex: string;
|
||||||
|
}[];
|
||||||
|
headers?: {
|
||||||
|
source: string;
|
||||||
|
headers: {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}[];
|
||||||
|
regex: string;
|
||||||
|
}[];
|
||||||
dynamicRoutes?: {
|
dynamicRoutes?: {
|
||||||
page: string;
|
page: string;
|
||||||
regex: string;
|
regex: string;
|
||||||
namedRegex?: string;
|
namedRegex?: string;
|
||||||
routeKeys?: { [named: string]: string };
|
routeKeys?: { [named: string]: string };
|
||||||
}[];
|
}[];
|
||||||
|
staticRoutes?: {
|
||||||
|
page: string;
|
||||||
|
regex: string;
|
||||||
|
namedRegex?: string;
|
||||||
|
routeKeys?: { [named: string]: string };
|
||||||
|
}[];
|
||||||
}) {
|
}) {
|
||||||
const routesManifestPath = join(workPath, '.output', 'routes-manifest.json');
|
const routesManifestPath = join(workPath, '.output', 'routes-manifest.json');
|
||||||
|
|
||||||
const routesManifest = await readJson(routesManifestPath);
|
const routesManifest = await readJson(routesManifestPath);
|
||||||
|
|
||||||
if (!routesManifest.version) routesManifest.version = 1;
|
if (!routesManifest.version) routesManifest.version = 3;
|
||||||
if (routesManifest.pages404 === undefined) routesManifest.pages404 = true;
|
if (routesManifest.pages404 === undefined) routesManifest.pages404 = true;
|
||||||
|
|
||||||
|
if (redirects) {
|
||||||
|
if (!routesManifest.redirects) routesManifest.redirects = [];
|
||||||
|
routesManifest.redirects.push(...redirects);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rewrites) {
|
||||||
|
if (!routesManifest.rewrites) routesManifest.rewrites = [];
|
||||||
|
routesManifest.rewrites.push(...rewrites);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers) {
|
||||||
|
if (!routesManifest.headers) routesManifest.headers = [];
|
||||||
|
routesManifest.headers.push(...headers);
|
||||||
|
}
|
||||||
|
|
||||||
if (dynamicRoutes) {
|
if (dynamicRoutes) {
|
||||||
if (!routesManifest.dynamicRoutes) routesManifest.dynamicRoutes = [];
|
if (!routesManifest.dynamicRoutes) routesManifest.dynamicRoutes = [];
|
||||||
routesManifest.dynamicRoutes.push(...dynamicRoutes);
|
routesManifest.dynamicRoutes.push(...dynamicRoutes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (staticRoutes) {
|
||||||
|
if (!routesManifest.staticRoutes) routesManifest.staticRoutes = [];
|
||||||
|
routesManifest.staticRoutes.push(...staticRoutes);
|
||||||
|
}
|
||||||
|
|
||||||
await fs.writeFile(routesManifestPath, JSON.stringify(routesManifest));
|
await fs.writeFile(routesManifestPath, JSON.stringify(routesManifest));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ export async function detectBuilders(
|
|||||||
redirectRoutes: Route[] | null;
|
redirectRoutes: Route[] | null;
|
||||||
rewriteRoutes: Route[] | null;
|
rewriteRoutes: Route[] | null;
|
||||||
errorRoutes: Route[] | null;
|
errorRoutes: Route[] | null;
|
||||||
|
limitedRoutes: LimitedRoutes | null;
|
||||||
}> {
|
}> {
|
||||||
const errors: ErrorResponse[] = [];
|
const errors: ErrorResponse[] = [];
|
||||||
const warnings: ErrorResponse[] = [];
|
const warnings: ErrorResponse[] = [];
|
||||||
@@ -114,6 +115,7 @@ export async function detectBuilders(
|
|||||||
redirectRoutes: null,
|
redirectRoutes: null,
|
||||||
rewriteRoutes: null,
|
rewriteRoutes: null,
|
||||||
errorRoutes: null,
|
errorRoutes: null,
|
||||||
|
limitedRoutes: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,6 +181,7 @@ export async function detectBuilders(
|
|||||||
redirectRoutes: null,
|
redirectRoutes: null,
|
||||||
rewriteRoutes: null,
|
rewriteRoutes: null,
|
||||||
errorRoutes: null,
|
errorRoutes: null,
|
||||||
|
limitedRoutes: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,6 +260,7 @@ export async function detectBuilders(
|
|||||||
defaultRoutes: null,
|
defaultRoutes: null,
|
||||||
rewriteRoutes: null,
|
rewriteRoutes: null,
|
||||||
errorRoutes: null,
|
errorRoutes: null,
|
||||||
|
limitedRoutes: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,6 +303,7 @@ export async function detectBuilders(
|
|||||||
defaultRoutes: null,
|
defaultRoutes: null,
|
||||||
rewriteRoutes: null,
|
rewriteRoutes: null,
|
||||||
errorRoutes: null,
|
errorRoutes: null,
|
||||||
|
limitedRoutes: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,6 +331,7 @@ export async function detectBuilders(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const routesResult = getRouteResult(
|
const routesResult = getRouteResult(
|
||||||
|
pkg,
|
||||||
apiRoutes,
|
apiRoutes,
|
||||||
dynamicRoutes,
|
dynamicRoutes,
|
||||||
usedOutputDirectory,
|
usedOutputDirectory,
|
||||||
@@ -342,6 +348,7 @@ export async function detectBuilders(
|
|||||||
defaultRoutes: routesResult.defaultRoutes,
|
defaultRoutes: routesResult.defaultRoutes,
|
||||||
rewriteRoutes: routesResult.rewriteRoutes,
|
rewriteRoutes: routesResult.rewriteRoutes,
|
||||||
errorRoutes: routesResult.errorRoutes,
|
errorRoutes: routesResult.errorRoutes,
|
||||||
|
limitedRoutes: routesResult.limitedRoutes,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -932,7 +939,14 @@ function createRouteFromPath(
|
|||||||
return { route, isDynamic };
|
return { route, isDynamic };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface LimitedRoutes {
|
||||||
|
defaultRoutes: Route[];
|
||||||
|
redirectRoutes: Route[];
|
||||||
|
rewriteRoutes: Route[];
|
||||||
|
}
|
||||||
|
|
||||||
function getRouteResult(
|
function getRouteResult(
|
||||||
|
pkg: PackageJson | undefined | null,
|
||||||
apiRoutes: Source[],
|
apiRoutes: Source[],
|
||||||
dynamicRoutes: Source[],
|
dynamicRoutes: Source[],
|
||||||
outputDirectory: string,
|
outputDirectory: string,
|
||||||
@@ -944,11 +958,18 @@ function getRouteResult(
|
|||||||
redirectRoutes: Route[];
|
redirectRoutes: Route[];
|
||||||
rewriteRoutes: Route[];
|
rewriteRoutes: Route[];
|
||||||
errorRoutes: Route[];
|
errorRoutes: Route[];
|
||||||
|
limitedRoutes: LimitedRoutes;
|
||||||
} {
|
} {
|
||||||
|
const deps = Object.assign({}, pkg?.dependencies, pkg?.devDependencies);
|
||||||
const defaultRoutes: Route[] = [];
|
const defaultRoutes: Route[] = [];
|
||||||
const redirectRoutes: Route[] = [];
|
const redirectRoutes: Route[] = [];
|
||||||
const rewriteRoutes: Route[] = [];
|
const rewriteRoutes: Route[] = [];
|
||||||
const errorRoutes: Route[] = [];
|
const errorRoutes: Route[] = [];
|
||||||
|
const limitedRoutes: LimitedRoutes = {
|
||||||
|
defaultRoutes: [],
|
||||||
|
redirectRoutes: [],
|
||||||
|
rewriteRoutes: [],
|
||||||
|
};
|
||||||
const framework = frontendBuilder?.config?.framework || '';
|
const framework = frontendBuilder?.config?.framework || '';
|
||||||
const isNextjs =
|
const isNextjs =
|
||||||
framework === 'nextjs' || isOfficialRuntime('next', frontendBuilder?.use);
|
framework === 'nextjs' || isOfficialRuntime('next', frontendBuilder?.use);
|
||||||
@@ -956,14 +977,43 @@ function getRouteResult(
|
|||||||
|
|
||||||
if (apiRoutes && apiRoutes.length > 0) {
|
if (apiRoutes && apiRoutes.length > 0) {
|
||||||
if (options.featHandleMiss) {
|
if (options.featHandleMiss) {
|
||||||
|
// Exclude extension names if the corresponding plugin is not found in package.json
|
||||||
|
// detectBuilders({ignoreRoutesForBuilders: ['@vercel/python']})
|
||||||
|
// return a copy of routes.
|
||||||
|
// We should exclud errorRoutes and
|
||||||
const extSet = detectApiExtensions(apiBuilders);
|
const extSet = detectApiExtensions(apiBuilders);
|
||||||
|
const withTag = options.tag ? `@${options.tag}` : '';
|
||||||
|
const extSetLimited = detectApiExtensions(
|
||||||
|
apiBuilders.filter(b => {
|
||||||
|
if (
|
||||||
|
b.use === `@vercel/python${withTag}` &&
|
||||||
|
!('vercel-plugin-python' in deps)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
b.use === `@vercel/go${withTag}` &&
|
||||||
|
!('vercel-plugin-go' in deps)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
b.use === `@vercel/ruby${withTag}` &&
|
||||||
|
!('vercel-plugin-ruby' in deps)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
if (extSet.size > 0) {
|
if (extSet.size > 0) {
|
||||||
const exts = Array.from(extSet)
|
const extGroup = `(?:\\.(?:${Array.from(extSet)
|
||||||
.map(ext => ext.slice(1))
|
.map(ext => ext.slice(1))
|
||||||
.join('|');
|
.join('|')}))`;
|
||||||
|
const extGroupLimited = `(?:\\.(?:${Array.from(extSetLimited)
|
||||||
const extGroup = `(?:\\.(?:${exts}))`;
|
.map(ext => ext.slice(1))
|
||||||
|
.join('|')}))`;
|
||||||
|
|
||||||
if (options.cleanUrls) {
|
if (options.cleanUrls) {
|
||||||
redirectRoutes.push({
|
redirectRoutes.push({
|
||||||
@@ -979,6 +1029,20 @@ function getRouteResult(
|
|||||||
},
|
},
|
||||||
status: 308,
|
status: 308,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
limitedRoutes.redirectRoutes.push({
|
||||||
|
src: `^/(api(?:.+)?)/index${extGroupLimited}?/?$`,
|
||||||
|
headers: { Location: options.trailingSlash ? '/$1/' : '/$1' },
|
||||||
|
status: 308,
|
||||||
|
});
|
||||||
|
|
||||||
|
limitedRoutes.redirectRoutes.push({
|
||||||
|
src: `^/api/(.+)${extGroupLimited}/?$`,
|
||||||
|
headers: {
|
||||||
|
Location: options.trailingSlash ? '/api/$1/' : '/api/$1',
|
||||||
|
},
|
||||||
|
status: 308,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
defaultRoutes.push({ handle: 'miss' });
|
defaultRoutes.push({ handle: 'miss' });
|
||||||
defaultRoutes.push({
|
defaultRoutes.push({
|
||||||
@@ -986,10 +1050,18 @@ function getRouteResult(
|
|||||||
dest: '/api/$1',
|
dest: '/api/$1',
|
||||||
check: true,
|
check: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
limitedRoutes.defaultRoutes.push({ handle: 'miss' });
|
||||||
|
limitedRoutes.defaultRoutes.push({
|
||||||
|
src: `^/api/(.+)${extGroupLimited}$`,
|
||||||
|
dest: '/api/$1',
|
||||||
|
check: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rewriteRoutes.push(...dynamicRoutes);
|
rewriteRoutes.push(...dynamicRoutes);
|
||||||
|
limitedRoutes.rewriteRoutes.push(...dynamicRoutes);
|
||||||
|
|
||||||
if (typeof ignoreRuntimes === 'undefined') {
|
if (typeof ignoreRuntimes === 'undefined') {
|
||||||
// This route is only necessary to hide the directory listing
|
// This route is only necessary to hide the directory listing
|
||||||
@@ -1040,6 +1112,7 @@ function getRouteResult(
|
|||||||
redirectRoutes,
|
redirectRoutes,
|
||||||
rewriteRoutes,
|
rewriteRoutes,
|
||||||
errorRoutes,
|
errorRoutes,
|
||||||
|
limitedRoutes,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
84
packages/build-utils/src/get-ignore-filter.ts
Normal file
84
packages/build-utils/src/get-ignore-filter.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import path from 'path';
|
||||||
|
import fs from 'fs-extra';
|
||||||
|
import ignore from 'ignore';
|
||||||
|
|
||||||
|
interface CodedError extends Error {
|
||||||
|
code: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCodedError(error: unknown): error is CodedError {
|
||||||
|
return (
|
||||||
|
error !== null &&
|
||||||
|
error !== undefined &&
|
||||||
|
(error as CodedError).code !== undefined
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearRelative(s: string) {
|
||||||
|
return s.replace(/(\n|^)\.\//g, '$1');
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function (
|
||||||
|
downloadPath: string,
|
||||||
|
rootDirectory?: string | undefined
|
||||||
|
) {
|
||||||
|
const readFile = async (p: string) => {
|
||||||
|
try {
|
||||||
|
return await fs.readFile(p, 'utf8');
|
||||||
|
} catch (error: any) {
|
||||||
|
if (
|
||||||
|
error.code === 'ENOENT' ||
|
||||||
|
(error instanceof Error && error.message.includes('ENOENT'))
|
||||||
|
) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const vercelIgnorePath = path.join(
|
||||||
|
downloadPath,
|
||||||
|
rootDirectory || '',
|
||||||
|
'.vercelignore'
|
||||||
|
);
|
||||||
|
const nowIgnorePath = path.join(
|
||||||
|
downloadPath,
|
||||||
|
rootDirectory || '',
|
||||||
|
'.nowignore'
|
||||||
|
);
|
||||||
|
const ignoreContents = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
ignoreContents.push(
|
||||||
|
...(
|
||||||
|
await Promise.all([readFile(vercelIgnorePath), readFile(nowIgnorePath)])
|
||||||
|
).filter(Boolean)
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
if (isCodedError(error) && error.code === 'ENOTDIR') {
|
||||||
|
console.log(`Warning: Cannot read ignore file from ${vercelIgnorePath}`);
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ignoreContents.length === 2) {
|
||||||
|
throw new Error(
|
||||||
|
'Cannot use both a `.vercelignore` and `.nowignore` file. Please delete the `.nowignore` file.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ignoreContents.length === 0) {
|
||||||
|
return () => false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ignoreFilter: any = ignore().add(clearRelative(ignoreContents[0]!));
|
||||||
|
|
||||||
|
return function (p: string) {
|
||||||
|
// we should not ignore now.json and vercel.json if it asked to.
|
||||||
|
// we depend on these files for building the app with sourceless
|
||||||
|
if (p === 'now.json' || p === 'vercel.json') return false;
|
||||||
|
return ignoreFilter.test(p).ignored;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { createHash } from 'crypto';
|
||||||
import FileBlob from './file-blob';
|
import FileBlob from './file-blob';
|
||||||
import FileFsRef from './file-fs-ref';
|
import FileFsRef from './file-fs-ref';
|
||||||
import FileRef from './file-ref';
|
import FileRef from './file-ref';
|
||||||
@@ -33,6 +34,7 @@ import { NowBuildError } from './errors';
|
|||||||
import streamToBuffer from './fs/stream-to-buffer';
|
import streamToBuffer from './fs/stream-to-buffer';
|
||||||
import shouldServe from './should-serve';
|
import shouldServe from './should-serve';
|
||||||
import debug from './debug';
|
import debug from './debug';
|
||||||
|
import getIgnoreFilter from './get-ignore-filter';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
FileBlob,
|
FileBlob,
|
||||||
@@ -70,6 +72,7 @@ export {
|
|||||||
isSymbolicLink,
|
isSymbolicLink,
|
||||||
getLambdaOptionsFromFunction,
|
getLambdaOptionsFromFunction,
|
||||||
scanParentDirs,
|
scanParentDirs,
|
||||||
|
getIgnoreFilter,
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -132,3 +135,11 @@ export const getPlatformEnv = (name: string): string | undefined => {
|
|||||||
}
|
}
|
||||||
return n;
|
return n;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function for generating file or directories names in `.output/inputs`
|
||||||
|
* for dependencies of files provided to the File System API.
|
||||||
|
*/
|
||||||
|
export const getInputHash = (source: Buffer | string): string => {
|
||||||
|
return createHash('sha1').update(source).digest('hex');
|
||||||
|
};
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ export interface Meta {
|
|||||||
filesRemoved?: string[];
|
filesRemoved?: string[];
|
||||||
env?: Env;
|
env?: Env;
|
||||||
buildEnv?: Env;
|
buildEnv?: Env;
|
||||||
|
avoidTopLevelInstall?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AnalyzeOptions {
|
export interface AnalyzeOptions {
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
# users.rb
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"functions": {
|
"functions": {
|
||||||
"api/users/post.py": {
|
"api/users.rb": {
|
||||||
"memory": 3008
|
"memory": 3008
|
||||||
},
|
},
|
||||||
"api/not-matching-anything.py": {
|
"api/doesnt-exist.rb": {
|
||||||
"memory": 768
|
"memory": 768
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# [id].py
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# project/[aid]/[bid]/index.py
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"functions": {
|
||||||
|
"api/users/post.py": {
|
||||||
|
"memory": 3008
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2385,13 +2385,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
|||||||
{
|
{
|
||||||
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
|
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
|
||||||
|
|
||||||
const { defaultRoutes, rewriteRoutes, errorRoutes } = await detectBuilders(
|
const { defaultRoutes, rewriteRoutes, errorRoutes, limitedRoutes } =
|
||||||
files,
|
await detectBuilders(files, null, {
|
||||||
null,
|
|
||||||
{
|
|
||||||
featHandleMiss,
|
featHandleMiss,
|
||||||
}
|
});
|
||||||
);
|
|
||||||
expect(defaultRoutes).toStrictEqual([
|
expect(defaultRoutes).toStrictEqual([
|
||||||
{ handle: 'miss' },
|
{ handle: 'miss' },
|
||||||
{
|
{
|
||||||
@@ -2414,6 +2411,22 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Limited routes should have js but not go since the go plugin is not installed
|
||||||
|
expect(limitedRoutes).toStrictEqual({
|
||||||
|
redirectRoutes: [],
|
||||||
|
rewriteRoutes: [],
|
||||||
|
defaultRoutes: [
|
||||||
|
{
|
||||||
|
handle: 'miss',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: '^/api/(.+)(?:\\.(?:js))$',
|
||||||
|
dest: '/api/$1',
|
||||||
|
check: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
const pattern = new RegExp(errorRoutes![0].src!);
|
const pattern = new RegExp(errorRoutes![0].src!);
|
||||||
|
|
||||||
[
|
[
|
||||||
@@ -2816,8 +2829,13 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
|||||||
{
|
{
|
||||||
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
|
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
|
||||||
|
|
||||||
const { defaultRoutes, redirectRoutes, rewriteRoutes, errorRoutes } =
|
const {
|
||||||
await detectBuilders(files, null, options);
|
defaultRoutes,
|
||||||
|
redirectRoutes,
|
||||||
|
rewriteRoutes,
|
||||||
|
errorRoutes,
|
||||||
|
limitedRoutes,
|
||||||
|
} = await detectBuilders(files, null, options);
|
||||||
testHeaders(redirectRoutes);
|
testHeaders(redirectRoutes);
|
||||||
expect(defaultRoutes).toStrictEqual([]);
|
expect(defaultRoutes).toStrictEqual([]);
|
||||||
expect(rewriteRoutes).toStrictEqual([
|
expect(rewriteRoutes).toStrictEqual([
|
||||||
@@ -2834,6 +2852,28 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Limited routes should have js but not go since the go plugin is not installed
|
||||||
|
expect(limitedRoutes).toStrictEqual({
|
||||||
|
redirectRoutes: [
|
||||||
|
{
|
||||||
|
src: '^/(api(?:.+)?)/index(?:\\.(?:js))?/?$',
|
||||||
|
headers: {
|
||||||
|
Location: '/$1',
|
||||||
|
},
|
||||||
|
status: 308,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: '^/api/(.+)(?:\\.(?:js))/?$',
|
||||||
|
headers: {
|
||||||
|
Location: '/api/$1',
|
||||||
|
},
|
||||||
|
status: 308,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rewriteRoutes: [],
|
||||||
|
defaultRoutes: [],
|
||||||
|
});
|
||||||
|
|
||||||
// expected redirect should match inputs
|
// expected redirect should match inputs
|
||||||
const getLocation = createReplaceLocation(redirectRoutes);
|
const getLocation = createReplaceLocation(redirectRoutes);
|
||||||
|
|
||||||
@@ -3077,7 +3117,7 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
|||||||
{
|
{
|
||||||
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
|
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
|
||||||
|
|
||||||
const { defaultRoutes, redirectRoutes, rewriteRoutes } =
|
const { defaultRoutes, redirectRoutes, rewriteRoutes, limitedRoutes } =
|
||||||
await detectBuilders(files, null, options);
|
await detectBuilders(files, null, options);
|
||||||
testHeaders(redirectRoutes);
|
testHeaders(redirectRoutes);
|
||||||
expect(defaultRoutes).toStrictEqual([]);
|
expect(defaultRoutes).toStrictEqual([]);
|
||||||
@@ -3088,6 +3128,28 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Limited routes should have js but not go since the go plugin is not installed
|
||||||
|
expect(limitedRoutes).toStrictEqual({
|
||||||
|
redirectRoutes: [
|
||||||
|
{
|
||||||
|
src: '^/(api(?:.+)?)/index(?:\\.(?:js))?/?$',
|
||||||
|
headers: {
|
||||||
|
Location: '/$1/',
|
||||||
|
},
|
||||||
|
status: 308,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: '^/api/(.+)(?:\\.(?:js))/?$',
|
||||||
|
headers: {
|
||||||
|
Location: '/api/$1/',
|
||||||
|
},
|
||||||
|
status: 308,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rewriteRoutes: [],
|
||||||
|
defaultRoutes: [],
|
||||||
|
});
|
||||||
|
|
||||||
// expected redirect should match inputs
|
// expected redirect should match inputs
|
||||||
const getLocation = createReplaceLocation(redirectRoutes);
|
const getLocation = createReplaceLocation(redirectRoutes);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import { BuildOptions, createLambda } from '../src';
|
import { BuildOptions, createLambda, FileFsRef } from '../src';
|
||||||
import { convertRuntimeToPlugin } from '../src/convert-runtime-to-plugin';
|
import { convertRuntimeToPlugin } from '../src/convert-runtime-to-plugin';
|
||||||
|
|
||||||
async function fsToJson(dir: string, output: Record<string, any> = {}) {
|
async function fsToJson(dir: string, output: Record<string, any> = {}) {
|
||||||
@@ -18,24 +18,43 @@ async function fsToJson(dir: string, output: Record<string, any> = {}) {
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
const workPath = join(__dirname, 'walk', 'python-api');
|
const invalidFuncWorkpath = join(
|
||||||
|
__dirname,
|
||||||
|
'convert-runtime',
|
||||||
|
'invalid-functions'
|
||||||
|
);
|
||||||
|
const pythonApiWorkpath = join(__dirname, 'convert-runtime', 'python-api');
|
||||||
|
|
||||||
describe('convert-runtime-to-plugin', () => {
|
describe('convert-runtime-to-plugin', () => {
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await fs.remove(join(workPath, '.output'));
|
await fs.remove(join(invalidFuncWorkpath, '.output'));
|
||||||
|
await fs.remove(join(pythonApiWorkpath, '.output'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create correct fileystem for python', async () => {
|
it('should create correct fileystem for python', async () => {
|
||||||
|
const ext = '.py';
|
||||||
|
const workPath = pythonApiWorkpath;
|
||||||
|
const handlerName = 'vc__handler__python';
|
||||||
|
const handlerFileName = handlerName + ext;
|
||||||
|
|
||||||
const lambdaOptions = {
|
const lambdaOptions = {
|
||||||
handler: 'index.handler',
|
handler: `${handlerName}.vc_handler`,
|
||||||
runtime: 'python3.9',
|
runtime: 'python3.9',
|
||||||
memory: 512,
|
memory: 512,
|
||||||
maxDuration: 5,
|
maxDuration: 5,
|
||||||
environment: {},
|
environment: {},
|
||||||
regions: ['sfo1'],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildRuntime = async (opts: BuildOptions) => {
|
const buildRuntime = async (opts: BuildOptions) => {
|
||||||
|
const handlerPath = join(workPath, handlerFileName);
|
||||||
|
|
||||||
|
// This is the usual time at which a Legacy Runtime writes its Lambda launcher.
|
||||||
|
await fs.writeFile(handlerPath, '# handler');
|
||||||
|
|
||||||
|
opts.files[handlerFileName] = new FileFsRef({
|
||||||
|
fsPath: handlerPath,
|
||||||
|
});
|
||||||
|
|
||||||
const lambda = await createLambda({
|
const lambda = await createLambda({
|
||||||
files: opts.files,
|
files: opts.files,
|
||||||
...lambdaOptions,
|
...lambdaOptions,
|
||||||
@@ -43,26 +62,24 @@ describe('convert-runtime-to-plugin', () => {
|
|||||||
return { output: lambda };
|
return { output: lambda };
|
||||||
};
|
};
|
||||||
|
|
||||||
const lambdaFiles = await fsToJson(workPath);
|
const packageName = 'vercel-plugin-python';
|
||||||
const vercelConfig = JSON.parse(lambdaFiles['vercel.json']);
|
const build = await convertRuntimeToPlugin(buildRuntime, packageName, ext);
|
||||||
delete lambdaFiles['vercel.json'];
|
|
||||||
const build = await convertRuntimeToPlugin(buildRuntime, '.py');
|
|
||||||
|
|
||||||
await build({ vercelConfig, workPath });
|
await build({ workPath });
|
||||||
|
|
||||||
const output = await fsToJson(join(workPath, '.output'));
|
const output = await fsToJson(join(workPath, '.output'));
|
||||||
|
|
||||||
expect(output).toMatchObject({
|
expect(output).toMatchObject({
|
||||||
'functions-manifest.json': expect.stringContaining('{'),
|
'functions-manifest.json': expect.stringContaining('{'),
|
||||||
'runtime-traced-files': lambdaFiles,
|
|
||||||
server: {
|
server: {
|
||||||
pages: {
|
pages: {
|
||||||
api: {
|
api: {
|
||||||
'index.py': expect.stringContaining('index'),
|
'index.py': expect.stringContaining('handler'),
|
||||||
'index.py.nft.json': expect.stringContaining('{'),
|
'index.py.nft.json': expect.stringContaining('{'),
|
||||||
users: {
|
users: {
|
||||||
'get.py': expect.stringContaining('get'),
|
'get.py': expect.stringContaining('handler'),
|
||||||
'get.py.nft.json': expect.stringContaining('{'),
|
'get.py.nft.json': expect.stringContaining('{'),
|
||||||
'post.py': expect.stringContaining('post'),
|
'post.py': expect.stringContaining('handler'),
|
||||||
'post.py.nft.json': expect.stringContaining('{'),
|
'post.py.nft.json': expect.stringContaining('{'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -72,42 +89,30 @@ describe('convert-runtime-to-plugin', () => {
|
|||||||
|
|
||||||
const funcManifest = JSON.parse(output['functions-manifest.json']);
|
const funcManifest = JSON.parse(output['functions-manifest.json']);
|
||||||
expect(funcManifest).toMatchObject({
|
expect(funcManifest).toMatchObject({
|
||||||
version: 1,
|
version: 2,
|
||||||
pages: {
|
pages: {
|
||||||
'api/index.py': lambdaOptions,
|
'api/index.py': { ...lambdaOptions, handler: 'index.vc_handler' },
|
||||||
'api/users/get.py': lambdaOptions,
|
'api/users/get.py': { ...lambdaOptions, handler: 'get.vc_handler' },
|
||||||
'api/users/post.py': { ...lambdaOptions, memory: 3008 },
|
'api/users/post.py': {
|
||||||
|
...lambdaOptions,
|
||||||
|
handler: 'post.vc_handler',
|
||||||
|
memory: 512,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const indexJson = JSON.parse(output.server.pages.api['index.py.nft.json']);
|
const indexJson = JSON.parse(output.server.pages.api['index.py.nft.json']);
|
||||||
expect(indexJson).toMatchObject({
|
expect(indexJson).toMatchObject({
|
||||||
version: 1,
|
version: 2,
|
||||||
files: [
|
files: [
|
||||||
{
|
'../../../../api/db/[id].py',
|
||||||
input: '../../../../runtime-traced-files/api/index.py',
|
'../../../../api/index.py',
|
||||||
output: 'api/index.py',
|
'../../../../api/project/[aid]/[bid]/index.py',
|
||||||
},
|
'../../../../api/users/get.py',
|
||||||
{
|
'../../../../api/users/post.py',
|
||||||
input: '../../../../runtime-traced-files/api/users/get.py',
|
'../../../../file.txt',
|
||||||
output: 'api/users/get.py',
|
'../../../../util/date.py',
|
||||||
},
|
'../../../../util/math.py',
|
||||||
{
|
|
||||||
input: '../../../../runtime-traced-files/api/users/post.py',
|
|
||||||
output: 'api/users/post.py',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: '../../../../runtime-traced-files/file.txt',
|
|
||||||
output: 'file.txt',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: '../../../../runtime-traced-files/util/date.py',
|
|
||||||
output: 'util/date.py',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: '../../../../runtime-traced-files/util/math.py',
|
|
||||||
output: 'util/math.py',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -115,32 +120,16 @@ describe('convert-runtime-to-plugin', () => {
|
|||||||
output.server.pages.api.users['get.py.nft.json']
|
output.server.pages.api.users['get.py.nft.json']
|
||||||
);
|
);
|
||||||
expect(getJson).toMatchObject({
|
expect(getJson).toMatchObject({
|
||||||
version: 1,
|
version: 2,
|
||||||
files: [
|
files: [
|
||||||
{
|
'../../../../../api/db/[id].py',
|
||||||
input: '../../../../../runtime-traced-files/api/index.py',
|
'../../../../../api/index.py',
|
||||||
output: 'api/index.py',
|
'../../../../../api/project/[aid]/[bid]/index.py',
|
||||||
},
|
'../../../../../api/users/get.py',
|
||||||
{
|
'../../../../../api/users/post.py',
|
||||||
input: '../../../../../runtime-traced-files/api/users/get.py',
|
'../../../../../file.txt',
|
||||||
output: 'api/users/get.py',
|
'../../../../../util/date.py',
|
||||||
},
|
'../../../../../util/math.py',
|
||||||
{
|
|
||||||
input: '../../../../../runtime-traced-files/api/users/post.py',
|
|
||||||
output: 'api/users/post.py',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: '../../../../../runtime-traced-files/file.txt',
|
|
||||||
output: 'file.txt',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: '../../../../../runtime-traced-files/util/date.py',
|
|
||||||
output: 'util/date.py',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: '../../../../../runtime-traced-files/util/math.py',
|
|
||||||
output: 'util/math.py',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -148,32 +137,16 @@ describe('convert-runtime-to-plugin', () => {
|
|||||||
output.server.pages.api.users['post.py.nft.json']
|
output.server.pages.api.users['post.py.nft.json']
|
||||||
);
|
);
|
||||||
expect(postJson).toMatchObject({
|
expect(postJson).toMatchObject({
|
||||||
version: 1,
|
version: 2,
|
||||||
files: [
|
files: [
|
||||||
{
|
'../../../../../api/db/[id].py',
|
||||||
input: '../../../../../runtime-traced-files/api/index.py',
|
'../../../../../api/index.py',
|
||||||
output: 'api/index.py',
|
'../../../../../api/project/[aid]/[bid]/index.py',
|
||||||
},
|
'../../../../../api/users/get.py',
|
||||||
{
|
'../../../../../api/users/post.py',
|
||||||
input: '../../../../../runtime-traced-files/api/users/get.py',
|
'../../../../../file.txt',
|
||||||
output: 'api/users/get.py',
|
'../../../../../util/date.py',
|
||||||
},
|
'../../../../../util/math.py',
|
||||||
{
|
|
||||||
input: '../../../../../runtime-traced-files/api/users/post.py',
|
|
||||||
output: 'api/users/post.py',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: '../../../../../runtime-traced-files/file.txt',
|
|
||||||
output: 'file.txt',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: '../../../../../runtime-traced-files/util/date.py',
|
|
||||||
output: 'util/date.py',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: '../../../../../runtime-traced-files/util/math.py',
|
|
||||||
output: 'util/math.py',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ Finally, [connect your Git repository to Vercel](https://vercel.com/docs/git) an
|
|||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
For details on how to use Vercel CLI, check out our [documentation](https://vercel.com/docs).
|
For details on how to use Vercel CLI, check out our [documentation](https://vercel.com/docs/cli).
|
||||||
|
|
||||||
## Local Development
|
## Local Development
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "vercel",
|
"name": "vercel",
|
||||||
"version": "23.1.3-canary.39",
|
"version": "23.1.3-canary.67",
|
||||||
"preferGlobal": true,
|
"preferGlobal": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "The command-line interface for Vercel",
|
"description": "The command-line interface for Vercel",
|
||||||
@@ -43,14 +43,14 @@
|
|||||||
"node": ">= 12"
|
"node": ">= 12"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vercel/build-utils": "2.12.3-canary.21",
|
"@vercel/build-utils": "2.12.3-canary.42",
|
||||||
"@vercel/go": "1.2.4-canary.4",
|
"@vercel/go": "1.2.4-canary.4",
|
||||||
"@vercel/node": "1.12.2-canary.7",
|
"@vercel/node": "1.12.2-canary.7",
|
||||||
"@vercel/python": "2.1.2-canary.0",
|
"@vercel/python": "2.1.2-canary.2",
|
||||||
"@vercel/ruby": "1.2.8-canary.4",
|
"@vercel/ruby": "1.2.10-canary.0",
|
||||||
"update-notifier": "4.1.0",
|
"update-notifier": "4.1.0",
|
||||||
"vercel-plugin-middleware": "0.0.0-canary.7",
|
"vercel-plugin-middleware": "0.0.0-canary.19",
|
||||||
"vercel-plugin-node": "1.12.2-canary.12"
|
"vercel-plugin-node": "1.12.2-canary.34"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@next/env": "11.1.2",
|
"@next/env": "11.1.2",
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
"@types/update-notifier": "5.1.0",
|
"@types/update-notifier": "5.1.0",
|
||||||
"@types/which": "1.3.2",
|
"@types/which": "1.3.2",
|
||||||
"@types/write-json-file": "2.2.1",
|
"@types/write-json-file": "2.2.1",
|
||||||
"@vercel/frameworks": "0.5.1-canary.13",
|
"@vercel/frameworks": "0.5.1-canary.17",
|
||||||
"@vercel/ncc": "0.24.0",
|
"@vercel/ncc": "0.24.0",
|
||||||
"@vercel/nft": "0.17.0",
|
"@vercel/nft": "0.17.0",
|
||||||
"@zeit/fun": "0.11.2",
|
"@zeit/fun": "0.11.2",
|
||||||
|
|||||||
@@ -5,16 +5,16 @@ import {
|
|||||||
GlobOptions,
|
GlobOptions,
|
||||||
scanParentDirs,
|
scanParentDirs,
|
||||||
spawnAsync,
|
spawnAsync,
|
||||||
|
glob as buildUtilsGlob,
|
||||||
} from '@vercel/build-utils';
|
} from '@vercel/build-utils';
|
||||||
import { nodeFileTrace } from '@vercel/nft';
|
import { nodeFileTrace } from '@vercel/nft';
|
||||||
import Sema from 'async-sema';
|
import Sema from 'async-sema';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import { SpawnOptions } from 'child_process';
|
import { SpawnOptions } from 'child_process';
|
||||||
import { assert } from 'console';
|
import { assert } from 'console';
|
||||||
import { createHash } from 'crypto';
|
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import ogGlob from 'glob';
|
import ogGlob from 'glob';
|
||||||
import { isAbsolute, join, parse, relative, resolve } from 'path';
|
import { dirname, isAbsolute, join, parse, relative } from 'path';
|
||||||
import pluralize from 'pluralize';
|
import pluralize from 'pluralize';
|
||||||
import Client from '../util/client';
|
import Client from '../util/client';
|
||||||
import { VercelConfig } from '../util/dev/types';
|
import { VercelConfig } from '../util/dev/types';
|
||||||
@@ -136,9 +136,11 @@ export default async function main(client: Client) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Set process.env with loaded environment variables
|
// Set process.env with loaded environment variables
|
||||||
await processEnv(loadedEnvFiles);
|
processEnv(loadedEnvFiles);
|
||||||
|
|
||||||
const spawnOpts = {
|
const spawnOpts: {
|
||||||
|
env: Record<string, string | undefined>;
|
||||||
|
} = {
|
||||||
env: { ...combinedEnv, VERCEL: '1' },
|
env: { ...combinedEnv, VERCEL: '1' },
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -284,6 +286,21 @@ export default async function main(client: Client) {
|
|||||||
// Clean the output directory
|
// Clean the output directory
|
||||||
fs.removeSync(join(cwd, OUTPUT_DIR));
|
fs.removeSync(join(cwd, OUTPUT_DIR));
|
||||||
|
|
||||||
|
if (framework && process.env.VERCEL_URL && 'envPrefix' in framework) {
|
||||||
|
for (const key of Object.keys(process.env)) {
|
||||||
|
if (key.startsWith('VERCEL_')) {
|
||||||
|
const newKey = `${framework.envPrefix}${key}`;
|
||||||
|
// Set `process.env` and `spawnOpts.env` to make sure the variables are
|
||||||
|
// available to the `build` step and the CLI Plugins.
|
||||||
|
process.env[newKey] = process.env[newKey] || process.env[key];
|
||||||
|
spawnOpts.env[newKey] = process.env[newKey];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required for Next.js to produce the correct `.nft.json` files.
|
||||||
|
spawnOpts.env.NEXT_PRIVATE_OUTPUT_TRACE_ROOT = baseDir;
|
||||||
|
|
||||||
// Yarn v2 PnP mode may be activated, so force
|
// Yarn v2 PnP mode may be activated, so force
|
||||||
// "node-modules" linker style
|
// "node-modules" linker style
|
||||||
const env = {
|
const env = {
|
||||||
@@ -315,22 +332,48 @@ export default async function main(client: Client) {
|
|||||||
cwd,
|
cwd,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// don't trust framework detection here because they might be switching to next on a branch
|
|
||||||
const isNextJs = fs.existsSync(join(cwd, '.next'));
|
|
||||||
|
|
||||||
if (!fs.existsSync(join(cwd, OUTPUT_DIR))) {
|
if (!fs.existsSync(join(cwd, OUTPUT_DIR))) {
|
||||||
let outputDir = join(OUTPUT_DIR, 'static');
|
let dotNextDir: string | null = null;
|
||||||
let distDir = await framework.getFsOutputDir(cwd);
|
|
||||||
if (isNextJs) {
|
// If a custom `outputDirectory` was set, we'll need to verify
|
||||||
outputDir = OUTPUT_DIR;
|
// if it's `.next` output, or just static output.
|
||||||
|
const userOutputDirectory = project.settings.outputDirectory;
|
||||||
|
|
||||||
|
if (typeof userOutputDirectory === 'string') {
|
||||||
|
if (fs.existsSync(join(cwd, userOutputDirectory, 'BUILD_ID'))) {
|
||||||
|
dotNextDir = join(cwd, userOutputDirectory);
|
||||||
|
client.output.debug(
|
||||||
|
`Consider ${param(userOutputDirectory)} as ${param('.next')} output.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (fs.existsSync(join(cwd, '.next'))) {
|
||||||
|
dotNextDir = join(cwd, '.next');
|
||||||
|
client.output.debug(`Found ${param('.next')} directory.`);
|
||||||
}
|
}
|
||||||
const copyStamp = stamp();
|
|
||||||
|
// We cannot rely on the `framework` alone, as it might be a static export,
|
||||||
|
// and the current build might use a different project that's not in the settings.
|
||||||
|
const isNextOutput = Boolean(dotNextDir);
|
||||||
|
const nextExport = await getNextExportStatus(dotNextDir);
|
||||||
|
const outputDir =
|
||||||
|
isNextOutput && !nextExport ? OUTPUT_DIR : join(OUTPUT_DIR, 'static');
|
||||||
|
const getDistDir = framework.getFsOutputDir || framework.getOutputDirName;
|
||||||
|
const distDir =
|
||||||
|
(nextExport?.exportDetail.outDirectory
|
||||||
|
? relative(cwd, nextExport.exportDetail.outDirectory)
|
||||||
|
: false) ||
|
||||||
|
dotNextDir ||
|
||||||
|
userOutputDirectory ||
|
||||||
|
(await getDistDir(cwd));
|
||||||
|
|
||||||
await fs.ensureDir(join(cwd, outputDir));
|
await fs.ensureDir(join(cwd, outputDir));
|
||||||
const relativeDistDir = relative(cwd, distDir);
|
|
||||||
|
const copyStamp = stamp();
|
||||||
client.output.spinner(
|
client.output.spinner(
|
||||||
`Copying files from ${param(distDir)} to ${param(outputDir)}`
|
`Copying files from ${param(distDir)} to ${param(outputDir)}`
|
||||||
);
|
);
|
||||||
const files = await glob(join(relativeDistDir, '**'), {
|
const files = await glob(join(relative(cwd, distDir), '**'), {
|
||||||
ignore: [
|
ignore: [
|
||||||
'node_modules/**',
|
'node_modules/**',
|
||||||
'.vercel/**',
|
'.vercel/**',
|
||||||
@@ -378,6 +421,7 @@ export default async function main(client: Client) {
|
|||||||
`Generating build manifest: ${param(buildManifestPath)}`
|
`Generating build manifest: ${param(buildManifestPath)}`
|
||||||
);
|
);
|
||||||
const buildManifest = {
|
const buildManifest = {
|
||||||
|
version: 1,
|
||||||
cache: framework.cachePattern ? [framework.cachePattern] : [],
|
cache: framework.cachePattern ? [framework.cachePattern] : [],
|
||||||
};
|
};
|
||||||
await fs.writeJSON(buildManifestPath, buildManifest, { spaces: 2 });
|
await fs.writeJSON(buildManifestPath, buildManifest, { spaces: 2 });
|
||||||
@@ -405,7 +449,53 @@ export default async function main(client: Client) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Special Next.js processing.
|
// Special Next.js processing.
|
||||||
if (isNextJs) {
|
if (nextExport) {
|
||||||
|
client.output.debug('Found `next export` output.');
|
||||||
|
|
||||||
|
const htmlFiles = await buildUtilsGlob(
|
||||||
|
'**/*.html',
|
||||||
|
join(cwd, OUTPUT_DIR, 'static')
|
||||||
|
);
|
||||||
|
|
||||||
|
if (nextExport.exportDetail.success !== true) {
|
||||||
|
client.output.error(
|
||||||
|
`Export of Next.js app failed. Please check your build logs.`
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
await fs.mkdirp(join(cwd, OUTPUT_DIR, 'server', 'pages'));
|
||||||
|
await fs.mkdirp(join(cwd, OUTPUT_DIR, 'static'));
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
Object.keys(htmlFiles).map(async fileName => {
|
||||||
|
await sema.acquire();
|
||||||
|
|
||||||
|
const input = join(cwd, OUTPUT_DIR, 'static', fileName);
|
||||||
|
const target = join(cwd, OUTPUT_DIR, 'server', 'pages', fileName);
|
||||||
|
|
||||||
|
await fs.mkdirp(dirname(target));
|
||||||
|
|
||||||
|
await fs.promises.rename(input, target).finally(() => {
|
||||||
|
sema.release();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const file of [
|
||||||
|
'BUILD_ID',
|
||||||
|
'images-manifest.json',
|
||||||
|
'routes-manifest.json',
|
||||||
|
'build-manifest.json',
|
||||||
|
]) {
|
||||||
|
const input = join(nextExport.dotNextDir, file);
|
||||||
|
|
||||||
|
if (fs.existsSync(input)) {
|
||||||
|
// Do not use `smartCopy`, since we want to overwrite if they already exist.
|
||||||
|
await fs.copyFile(input, join(OUTPUT_DIR, file));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (isNextOutput) {
|
||||||
// The contents of `.output/static` should be placed inside of `.output/static/_next/static`
|
// The contents of `.output/static` should be placed inside of `.output/static/_next/static`
|
||||||
const tempStatic = '___static';
|
const tempStatic = '___static';
|
||||||
await fs.rename(
|
await fs.rename(
|
||||||
@@ -456,10 +546,12 @@ export default async function main(client: Client) {
|
|||||||
// `public`, then`static`). We can't read both at the same time because that would mean we'd
|
// `public`, then`static`). We can't read both at the same time because that would mean we'd
|
||||||
// read public for old Next.js versions that don't support it, which might be breaking (and
|
// read public for old Next.js versions that don't support it, which might be breaking (and
|
||||||
// we don't want to make vercel build specific framework versions).
|
// we don't want to make vercel build specific framework versions).
|
||||||
|
const nextSrcDirectory = dirname(distDir);
|
||||||
|
|
||||||
const publicFiles = await glob('public/**', {
|
const publicFiles = await glob('public/**', {
|
||||||
nodir: true,
|
nodir: true,
|
||||||
dot: true,
|
dot: true,
|
||||||
cwd,
|
cwd: nextSrcDirectory,
|
||||||
absolute: true,
|
absolute: true,
|
||||||
});
|
});
|
||||||
if (publicFiles.length > 0) {
|
if (publicFiles.length > 0) {
|
||||||
@@ -468,7 +560,11 @@ export default async function main(client: Client) {
|
|||||||
smartCopy(
|
smartCopy(
|
||||||
client,
|
client,
|
||||||
f,
|
f,
|
||||||
f.replace('public', join(OUTPUT_DIR, 'static'))
|
join(
|
||||||
|
OUTPUT_DIR,
|
||||||
|
'static',
|
||||||
|
relative(join(dirname(distDir), 'public'), f)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -476,7 +572,7 @@ export default async function main(client: Client) {
|
|||||||
const staticFiles = await glob('static/**', {
|
const staticFiles = await glob('static/**', {
|
||||||
nodir: true,
|
nodir: true,
|
||||||
dot: true,
|
dot: true,
|
||||||
cwd,
|
cwd: nextSrcDirectory,
|
||||||
absolute: true,
|
absolute: true,
|
||||||
});
|
});
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
@@ -484,7 +580,12 @@ export default async function main(client: Client) {
|
|||||||
smartCopy(
|
smartCopy(
|
||||||
client,
|
client,
|
||||||
f,
|
f,
|
||||||
f.replace('static', join(OUTPUT_DIR, 'static', 'static'))
|
join(
|
||||||
|
OUTPUT_DIR,
|
||||||
|
'static',
|
||||||
|
'static',
|
||||||
|
relative(join(dirname(distDir), 'static'), f)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -503,6 +604,7 @@ export default async function main(client: Client) {
|
|||||||
const nftFiles = await glob(join(OUTPUT_DIR, '**', '*.nft.json'), {
|
const nftFiles = await glob(join(OUTPUT_DIR, '**', '*.nft.json'), {
|
||||||
nodir: true,
|
nodir: true,
|
||||||
dot: true,
|
dot: true,
|
||||||
|
ignore: ['cache/**'],
|
||||||
cwd,
|
cwd,
|
||||||
absolute: true,
|
absolute: true,
|
||||||
});
|
});
|
||||||
@@ -534,28 +636,15 @@ export default async function main(client: Client) {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
fileList.delete(relative(cwd, f));
|
fileList.delete(relative(cwd, f));
|
||||||
await resolveNftToOutput({
|
|
||||||
client,
|
const nftFileName = f.replace(ext, '.js.nft.json');
|
||||||
baseDir,
|
client.output.debug(`Creating ${nftFileName}`);
|
||||||
outputDir: OUTPUT_DIR,
|
|
||||||
nftFileName: f.replace(ext, '.js.nft.json'),
|
await fs.writeJSON(nftFileName, {
|
||||||
nft: {
|
version: 2,
|
||||||
version: 1,
|
files: Array.from(fileList).map(fileListEntry =>
|
||||||
files: Array.from(fileList).map(fileListEntry =>
|
relative(dir, fileListEntry)
|
||||||
relative(dir, fileListEntry)
|
),
|
||||||
),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (let f of nftFiles) {
|
|
||||||
const json = await fs.readJson(f);
|
|
||||||
await resolveNftToOutput({
|
|
||||||
client,
|
|
||||||
baseDir,
|
|
||||||
outputDir: OUTPUT_DIR,
|
|
||||||
nftFileName: f,
|
|
||||||
nft: json,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -564,22 +653,25 @@ export default async function main(client: Client) {
|
|||||||
OUTPUT_DIR,
|
OUTPUT_DIR,
|
||||||
'required-server-files.json'
|
'required-server-files.json'
|
||||||
);
|
);
|
||||||
const requiredServerFilesJson = await fs.readJSON(
|
|
||||||
requiredServerFilesPath
|
|
||||||
);
|
|
||||||
await fs.writeJSON(requiredServerFilesPath, {
|
|
||||||
...requiredServerFilesJson,
|
|
||||||
appDir: '.',
|
|
||||||
files: requiredServerFilesJson.files.map((i: string) => {
|
|
||||||
const absolutePath = join(cwd, i.replace('.next', '.output'));
|
|
||||||
const output = relative(baseDir, absolutePath);
|
|
||||||
|
|
||||||
return {
|
if (fs.existsSync(requiredServerFilesPath)) {
|
||||||
input: i.replace('.next', '.output'),
|
client.output.debug(`Resolve ${param('required-server-files.json')}.`);
|
||||||
output,
|
|
||||||
};
|
const requiredServerFilesJson = await fs.readJSON(
|
||||||
}),
|
requiredServerFilesPath
|
||||||
});
|
);
|
||||||
|
|
||||||
|
await fs.writeJSON(requiredServerFilesPath, {
|
||||||
|
...requiredServerFilesJson,
|
||||||
|
appDir: '.',
|
||||||
|
files: requiredServerFilesJson.files.map((i: string) => {
|
||||||
|
const originalPath = join(requiredServerFilesJson.appDir, i);
|
||||||
|
const relPath = join(OUTPUT_DIR, relative(distDir, originalPath));
|
||||||
|
|
||||||
|
return relPath;
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -734,70 +826,51 @@ async function glob(pattern: string, options: GlobOptions): Promise<string[]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Computes a hash for the given buf.
|
* Files will only exist when `next export` was used.
|
||||||
*
|
|
||||||
* @param {Buffer} file data
|
|
||||||
* @return {String} hex digest
|
|
||||||
*/
|
*/
|
||||||
function hash(buf: Buffer): string {
|
async function getNextExportStatus(dotNextDir: string | null) {
|
||||||
return createHash('sha1').update(buf).digest('hex');
|
if (!dotNextDir) {
|
||||||
}
|
return null;
|
||||||
|
|
||||||
interface NftFile {
|
|
||||||
version: number;
|
|
||||||
files: (string | { input: string; output: string })[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// resolveNftToOutput takes nft file and moves all of its trace files
|
|
||||||
// into the specified directory + `inputs`, (renaming them to their hash + ext) and
|
|
||||||
// subsequently updating the original nft file accordingly. This is done
|
|
||||||
// to make the `.output` directory be self-contained, so that it works
|
|
||||||
// properly with `vc --prebuilt`.
|
|
||||||
async function resolveNftToOutput({
|
|
||||||
client,
|
|
||||||
baseDir,
|
|
||||||
outputDir,
|
|
||||||
nftFileName,
|
|
||||||
nft,
|
|
||||||
}: {
|
|
||||||
client: Client;
|
|
||||||
baseDir: string;
|
|
||||||
outputDir: string;
|
|
||||||
nftFileName: string;
|
|
||||||
nft: NftFile;
|
|
||||||
}) {
|
|
||||||
client.output.debug(`Processing and resolving ${nftFileName}`);
|
|
||||||
await fs.ensureDir(join(outputDir, 'inputs'));
|
|
||||||
const newFilesList: NftFile['files'] = [];
|
|
||||||
for (let fileEntity of nft.files) {
|
|
||||||
const relativeInput: string =
|
|
||||||
typeof fileEntity === 'string' ? fileEntity : fileEntity.input;
|
|
||||||
const fullInput = resolve(join(parse(nftFileName).dir, relativeInput));
|
|
||||||
|
|
||||||
// if the resolved path is NOT in the .output directory we move in it there
|
|
||||||
if (!fullInput.includes(outputDir)) {
|
|
||||||
const { ext } = parse(fullInput);
|
|
||||||
const raw = await fs.readFile(fullInput);
|
|
||||||
const newFilePath = join(outputDir, 'inputs', hash(raw) + ext);
|
|
||||||
smartCopy(client, fullInput, newFilePath);
|
|
||||||
|
|
||||||
// We have to use `baseDir` instead of `cwd`, because we want to
|
|
||||||
// mount everything from there (especially `node_modules`).
|
|
||||||
// This is important for NPM Workspaces where `node_modules` is not
|
|
||||||
// in the directory of the workspace.
|
|
||||||
const output = relative(baseDir, fullInput).replace('.output', '.next');
|
|
||||||
|
|
||||||
newFilesList.push({
|
|
||||||
input: relative(parse(nftFileName).dir, newFilePath),
|
|
||||||
output,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
newFilesList.push(relativeInput);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Update the .nft.json with new input and output mapping
|
|
||||||
await fs.writeJSON(nftFileName, {
|
const exportDetail: {
|
||||||
...nft,
|
success: boolean;
|
||||||
files: newFilesList,
|
outDirectory: string;
|
||||||
});
|
} | null = await fs
|
||||||
|
.readJson(join(dotNextDir, 'export-detail.json'))
|
||||||
|
.catch(error => {
|
||||||
|
if (error.code === 'ENOENT') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!exportDetail) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportMarker: {
|
||||||
|
version: 1;
|
||||||
|
exportTrailingSlash: boolean;
|
||||||
|
hasExportPathMap: boolean;
|
||||||
|
} | null = await fs
|
||||||
|
.readJSON(join(dotNextDir, 'export-marker.json'))
|
||||||
|
.catch(error => {
|
||||||
|
if (error.code === 'ENOENT') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
dotNextDir,
|
||||||
|
exportDetail,
|
||||||
|
exportMarker: {
|
||||||
|
trailingSlash: exportMarker?.hasExportPathMap
|
||||||
|
? exportMarker.exportTrailingSlash
|
||||||
|
: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -447,6 +447,7 @@ export default async (client: Client) => {
|
|||||||
forceNew: argv['--force'],
|
forceNew: argv['--force'],
|
||||||
withCache: argv['--with-cache'],
|
withCache: argv['--with-cache'],
|
||||||
prebuilt: argv['--prebuilt'],
|
prebuilt: argv['--prebuilt'],
|
||||||
|
rootDirectory,
|
||||||
quiet,
|
quiet,
|
||||||
wantsPublic: argv['--public'] || localConfig.public,
|
wantsPublic: argv['--public'] || localConfig.public,
|
||||||
isFile,
|
isFile,
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ export default async function processDeployment({
|
|||||||
isSettingUpProject: boolean;
|
isSettingUpProject: boolean;
|
||||||
skipAutoDetectionConfirmation?: boolean;
|
skipAutoDetectionConfirmation?: boolean;
|
||||||
cwd?: string;
|
cwd?: string;
|
||||||
|
rootDirectory?: string;
|
||||||
}) {
|
}) {
|
||||||
let {
|
let {
|
||||||
now,
|
now,
|
||||||
@@ -64,6 +65,7 @@ export default async function processDeployment({
|
|||||||
nowConfig,
|
nowConfig,
|
||||||
quiet,
|
quiet,
|
||||||
prebuilt,
|
prebuilt,
|
||||||
|
rootDirectory,
|
||||||
} = args;
|
} = args;
|
||||||
|
|
||||||
const { debug } = output;
|
const { debug } = output;
|
||||||
@@ -86,6 +88,7 @@ export default async function processDeployment({
|
|||||||
force,
|
force,
|
||||||
withCache,
|
withCache,
|
||||||
prebuilt,
|
prebuilt,
|
||||||
|
rootDirectory,
|
||||||
skipAutoDetectionConfirmation,
|
skipAutoDetectionConfirmation,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -968,7 +968,7 @@ export default class DevServer {
|
|||||||
socket.destroy();
|
socket.destroy();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const target = `http://localhost:${this.devProcessPort}`;
|
const target = `http://127.0.0.1:${this.devProcessPort}`;
|
||||||
this.output.debug(`Detected "upgrade" event, proxying to ${target}`);
|
this.output.debug(`Detected "upgrade" event, proxying to ${target}`);
|
||||||
this.proxy.ws(req, socket, head, { target });
|
this.proxy.ws(req, socket, head, { target });
|
||||||
});
|
});
|
||||||
@@ -1663,7 +1663,7 @@ export default class DevServer {
|
|||||||
if (!match) {
|
if (!match) {
|
||||||
// If the dev command is started, then proxy to it
|
// If the dev command is started, then proxy to it
|
||||||
if (this.devProcessPort) {
|
if (this.devProcessPort) {
|
||||||
const upstream = `http://localhost:${this.devProcessPort}`;
|
const upstream = `http://127.0.0.1:${this.devProcessPort}`;
|
||||||
debug(`Proxying to frontend dev server: ${upstream}`);
|
debug(`Proxying to frontend dev server: ${upstream}`);
|
||||||
|
|
||||||
// Add the Vercel platform proxy request headers
|
// Add the Vercel platform proxy request headers
|
||||||
@@ -1810,7 +1810,7 @@ export default class DevServer {
|
|||||||
return proxyPass(
|
return proxyPass(
|
||||||
req,
|
req,
|
||||||
res,
|
res,
|
||||||
`http://localhost:${port}`,
|
`http://127.0.0.1:${port}`,
|
||||||
this,
|
this,
|
||||||
requestId,
|
requestId,
|
||||||
false
|
false
|
||||||
@@ -1847,7 +1847,7 @@ export default class DevServer {
|
|||||||
return proxyPass(
|
return proxyPass(
|
||||||
req,
|
req,
|
||||||
res,
|
res,
|
||||||
`http://localhost:${this.devProcessPort}`,
|
`http://127.0.0.1:${this.devProcessPort}`,
|
||||||
this,
|
this,
|
||||||
requestId,
|
requestId,
|
||||||
false
|
false
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export interface CreateOptions {
|
|||||||
project?: string;
|
project?: string;
|
||||||
wantsPublic: boolean;
|
wantsPublic: boolean;
|
||||||
prebuilt?: boolean;
|
prebuilt?: boolean;
|
||||||
|
rootDirectory?: string;
|
||||||
meta: Dictionary<string>;
|
meta: Dictionary<string>;
|
||||||
regions?: string[];
|
regions?: string[];
|
||||||
quiet?: boolean;
|
quiet?: boolean;
|
||||||
@@ -113,6 +114,7 @@ export default class Now extends EventEmitter {
|
|||||||
name,
|
name,
|
||||||
project,
|
project,
|
||||||
prebuilt = false,
|
prebuilt = false,
|
||||||
|
rootDirectory,
|
||||||
wantsPublic,
|
wantsPublic,
|
||||||
meta,
|
meta,
|
||||||
regions,
|
regions,
|
||||||
@@ -168,6 +170,7 @@ export default class Now extends EventEmitter {
|
|||||||
skipAutoDetectionConfirmation,
|
skipAutoDetectionConfirmation,
|
||||||
cwd,
|
cwd,
|
||||||
prebuilt,
|
prebuilt,
|
||||||
|
rootDirectory,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (deployment && deployment.warnings) {
|
if (deployment && deployment.warnings) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export type ProjectLinkAndSettings = ProjectLink & {
|
|||||||
buildCommand: Project['buildCommand'];
|
buildCommand: Project['buildCommand'];
|
||||||
devCommand: Project['devCommand'];
|
devCommand: Project['devCommand'];
|
||||||
outputDirectory: Project['outputDirectory'];
|
outputDirectory: Project['outputDirectory'];
|
||||||
|
directoryListing: Project['directoryListing'];
|
||||||
rootDirectory: Project['rootDirectory'];
|
rootDirectory: Project['rootDirectory'];
|
||||||
framework: Project['framework'];
|
framework: Project['framework'];
|
||||||
};
|
};
|
||||||
@@ -29,6 +30,7 @@ export async function writeProjectSettings(
|
|||||||
settings: {
|
settings: {
|
||||||
buildCommand: project.buildCommand,
|
buildCommand: project.buildCommand,
|
||||||
devCommand: project.devCommand,
|
devCommand: project.devCommand,
|
||||||
|
outputDirectory: project.outputDirectory,
|
||||||
directoryListing: project.directoryListing,
|
directoryListing: project.directoryListing,
|
||||||
rootDirectory: project.rootDirectory,
|
rootDirectory: project.rootDirectory,
|
||||||
framework: project.framework,
|
framework: project.framework,
|
||||||
|
|||||||
6
packages/cli/test/fixtures/unit/edge-middleware-strict/_middleware.ts
vendored
Normal file
6
packages/cli/test/fixtures/unit/edge-middleware-strict/_middleware.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export default function (req) {
|
||||||
|
const isStrict = (function () {
|
||||||
|
return !this;
|
||||||
|
})();
|
||||||
|
return new Response('is strict mode? ' + (isStrict ? 'yes' : 'no'));
|
||||||
|
}
|
||||||
@@ -385,4 +385,13 @@ describe('DevServer', () => {
|
|||||||
);
|
);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
it(
|
||||||
|
'should run middleware in strict mode',
|
||||||
|
testFixture('edge-middleware-strict', async server => {
|
||||||
|
const response = await fetch(`${server.address}/index.html`);
|
||||||
|
const body = await response.text();
|
||||||
|
expect(body).toStrictEqual('is strict mode? yes');
|
||||||
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
2
packages/client/.gitignore
vendored
2
packages/client/.gitignore
vendored
@@ -6,3 +6,5 @@ node_modules
|
|||||||
!tests/fixtures/nowignore/node_modules
|
!tests/fixtures/nowignore/node_modules
|
||||||
!tests/fixtures/vercelignore-allow-nodemodules/node_modules
|
!tests/fixtures/vercelignore-allow-nodemodules/node_modules
|
||||||
!tests/fixtures/vercelignore-allow-nodemodules/sub/node_modules
|
!tests/fixtures/vercelignore-allow-nodemodules/sub/node_modules
|
||||||
|
!tests/fixtures/file-system-api/.output
|
||||||
|
!tests/fixtures/file-system-api-root-directory/**/.output
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vercel/client",
|
"name": "@vercel/client",
|
||||||
"version": "10.2.3-canary.22",
|
"version": "10.2.3-canary.45",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"typings": "dist/index.d.ts",
|
"typings": "dist/index.d.ts",
|
||||||
"homepage": "https://vercel.com",
|
"homepage": "https://vercel.com",
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vercel/build-utils": "2.12.3-canary.21",
|
"@vercel/build-utils": "2.12.3-canary.42",
|
||||||
"@zeit/fetch": "5.2.0",
|
"@zeit/fetch": "5.2.0",
|
||||||
"async-retry": "1.2.3",
|
"async-retry": "1.2.3",
|
||||||
"async-sema": "3.0.0",
|
"async-sema": "3.0.0",
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { lstatSync } from 'fs-extra';
|
import { lstatSync } from 'fs-extra';
|
||||||
|
|
||||||
import { relative, isAbsolute } from 'path';
|
import { relative, isAbsolute } from 'path';
|
||||||
import hashes, { mapToObject } from './utils/hashes';
|
import { hashes, mapToObject, resolveNftJsonFiles } from './utils/hashes';
|
||||||
import { upload } from './upload';
|
import { upload } from './upload';
|
||||||
import { buildFileTree, createDebug, parseVercelConfig } from './utils';
|
import { buildFileTree, createDebug, parseVercelConfig } from './utils';
|
||||||
import { DeploymentError } from './errors';
|
import { DeploymentError } from './errors';
|
||||||
import {
|
import {
|
||||||
NowConfig,
|
VercelConfig,
|
||||||
VercelClientOptions,
|
VercelClientOptions,
|
||||||
DeploymentOptions,
|
DeploymentOptions,
|
||||||
DeploymentEventType,
|
DeploymentEventType,
|
||||||
@@ -16,7 +16,7 @@ export default function buildCreateDeployment() {
|
|||||||
return async function* createDeployment(
|
return async function* createDeployment(
|
||||||
clientOptions: VercelClientOptions,
|
clientOptions: VercelClientOptions,
|
||||||
deploymentOptions: DeploymentOptions = {},
|
deploymentOptions: DeploymentOptions = {},
|
||||||
nowConfig: NowConfig = {}
|
nowConfig: VercelConfig = {}
|
||||||
): AsyncIterableIterator<{ type: DeploymentEventType; payload: any }> {
|
): AsyncIterableIterator<{ type: DeploymentEventType; payload: any }> {
|
||||||
const { path } = clientOptions;
|
const { path } = clientOptions;
|
||||||
|
|
||||||
@@ -74,12 +74,7 @@ export default function buildCreateDeployment() {
|
|||||||
debug(`Provided 'path' is a single file`);
|
debug(`Provided 'path' is a single file`);
|
||||||
}
|
}
|
||||||
|
|
||||||
let { fileList } = await buildFileTree(
|
let { fileList } = await buildFileTree(path, clientOptions, debug);
|
||||||
path,
|
|
||||||
clientOptions.isDirectory,
|
|
||||||
debug,
|
|
||||||
clientOptions.prebuilt
|
|
||||||
);
|
|
||||||
|
|
||||||
let configPath: string | undefined;
|
let configPath: string | undefined;
|
||||||
if (!nowConfig) {
|
if (!nowConfig) {
|
||||||
@@ -114,7 +109,11 @@ export default function buildCreateDeployment() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const files = await hashes(fileList);
|
const hashedFileMap = await hashes(fileList);
|
||||||
|
const nftFileList = clientOptions.prebuilt
|
||||||
|
? await resolveNftJsonFiles(hashedFileMap)
|
||||||
|
: [];
|
||||||
|
const files = await hashes(nftFileList, hashedFileMap);
|
||||||
|
|
||||||
debug(`Yielding a 'hashes-calculated' event with ${files.size} hashes`);
|
debug(`Yielding a 'hashes-calculated' event with ${files.size} hashes`);
|
||||||
yield { type: 'hashes-calculated', payload: mapToObject(files) };
|
yield { type: 'hashes-calculated', payload: mapToObject(files) };
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export interface VercelClientOptions {
|
|||||||
apiUrl?: string;
|
apiUrl?: string;
|
||||||
force?: boolean;
|
force?: boolean;
|
||||||
prebuilt?: boolean;
|
prebuilt?: boolean;
|
||||||
|
rootDirectory?: string;
|
||||||
withCache?: boolean;
|
withCache?: boolean;
|
||||||
userAgent?: string;
|
userAgent?: string;
|
||||||
defaultName?: string;
|
defaultName?: string;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { createHash } from 'crypto';
|
import { createHash } from 'crypto';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import { Sema } from 'async-sema';
|
import { Sema } from 'async-sema';
|
||||||
|
import { join, dirname } from 'path';
|
||||||
|
|
||||||
export interface DeploymentFile {
|
export interface DeploymentFile {
|
||||||
names: string[];
|
names: string[];
|
||||||
@@ -15,9 +16,7 @@ export interface DeploymentFile {
|
|||||||
* @return {String} hex digest
|
* @return {String} hex digest
|
||||||
*/
|
*/
|
||||||
function hash(buf: Buffer): string {
|
function hash(buf: Buffer): string {
|
||||||
return createHash('sha1')
|
return createHash('sha1').update(buf).digest('hex');
|
||||||
.update(buf)
|
|
||||||
.digest('hex');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,34 +38,68 @@ export const mapToObject = (
|
|||||||
/**
|
/**
|
||||||
* Computes hashes for the contents of each file given.
|
* Computes hashes for the contents of each file given.
|
||||||
*
|
*
|
||||||
* @param {Array} of {String} full paths
|
* @param files - absolute file paths
|
||||||
* @return {Map}
|
* @param map - optional map of files to append
|
||||||
|
* @return Map of hash digest to file object
|
||||||
*/
|
*/
|
||||||
async function hashes(files: string[]): Promise<Map<string, DeploymentFile>> {
|
export async function hashes(
|
||||||
const map = new Map<string, DeploymentFile>();
|
files: string[],
|
||||||
|
map = new Map<string, DeploymentFile>()
|
||||||
|
): Promise<Map<string, DeploymentFile>> {
|
||||||
const semaphore = new Sema(100);
|
const semaphore = new Sema(100);
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
files.map(
|
files.map(async (name: string): Promise<void> => {
|
||||||
async (name: string): Promise<void> => {
|
await semaphore.acquire();
|
||||||
await semaphore.acquire();
|
const data = await fs.readFile(name);
|
||||||
const data = await fs.readFile(name);
|
const { mode } = await fs.stat(name);
|
||||||
const { mode } = await fs.stat(name);
|
|
||||||
|
|
||||||
const h = hash(data);
|
const h = hash(data);
|
||||||
const entry = map.get(h);
|
const entry = map.get(h);
|
||||||
|
|
||||||
if (entry) {
|
if (entry) {
|
||||||
|
if (entry.names[0] !== name) {
|
||||||
entry.names.push(name);
|
entry.names.push(name);
|
||||||
} else {
|
|
||||||
map.set(h, { names: [name], data, mode });
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
semaphore.release();
|
map.set(h, { names: [name], data, mode });
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
semaphore.release();
|
||||||
|
})
|
||||||
);
|
);
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default hashes;
|
export async function resolveNftJsonFiles(
|
||||||
|
hashedFiles: Map<string, DeploymentFile>
|
||||||
|
): Promise<string[]> {
|
||||||
|
const semaphore = new Sema(100);
|
||||||
|
const existingFiles = Array.from(hashedFiles.values());
|
||||||
|
const resolvedFiles = new Set<string>();
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
existingFiles.map(async file => {
|
||||||
|
await semaphore.acquire();
|
||||||
|
const fsPath = file.names[0];
|
||||||
|
if (fsPath.endsWith('.nft.json')) {
|
||||||
|
const json = file.data.toString('utf8');
|
||||||
|
const { version, files } = JSON.parse(json) as {
|
||||||
|
version: number;
|
||||||
|
files: string[] | { input: string; output: string }[];
|
||||||
|
};
|
||||||
|
if (version === 1 || version === 2) {
|
||||||
|
for (let f of files) {
|
||||||
|
const relPath = typeof f === 'string' ? f : f.input;
|
||||||
|
resolvedFiles.add(join(dirname(fsPath), relPath));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error(`Invalid nft.json version: ${version}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
semaphore.release();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return Array.from(resolvedFiles);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { DeploymentFile } from './hashes';
|
import { DeploymentFile } from './hashes';
|
||||||
import { FetchOptions } from '@zeit/fetch';
|
import { FetchOptions } from '@zeit/fetch';
|
||||||
import { nodeFetch, zeitFetch } from './fetch';
|
import { nodeFetch, zeitFetch } from './fetch';
|
||||||
import { join, sep, relative } from 'path';
|
import { join, sep, relative, posix } from 'path';
|
||||||
import { URL } from 'url';
|
import { URL } from 'url';
|
||||||
import ignore from 'ignore';
|
import ignore from 'ignore';
|
||||||
type Ignore = ReturnType<typeof ignore>;
|
type Ignore = ReturnType<typeof ignore>;
|
||||||
@@ -81,13 +81,16 @@ const maybeRead = async function <T>(path: string, default_: T) {
|
|||||||
|
|
||||||
export async function buildFileTree(
|
export async function buildFileTree(
|
||||||
path: string | string[],
|
path: string | string[],
|
||||||
isDirectory: boolean,
|
{
|
||||||
debug: Debug,
|
isDirectory,
|
||||||
prebuilt?: boolean
|
prebuilt,
|
||||||
|
rootDirectory,
|
||||||
|
}: Pick<VercelClientOptions, 'isDirectory' | 'prebuilt' | 'rootDirectory'>,
|
||||||
|
debug: Debug
|
||||||
): Promise<{ fileList: string[]; ignoreList: string[] }> {
|
): Promise<{ fileList: string[]; ignoreList: string[] }> {
|
||||||
const ignoreList: string[] = [];
|
const ignoreList: string[] = [];
|
||||||
let fileList: string[];
|
let fileList: string[];
|
||||||
let { ig, ignores } = await getVercelIgnore(path, prebuilt);
|
let { ig, ignores } = await getVercelIgnore(path, prebuilt, rootDirectory);
|
||||||
|
|
||||||
debug(`Found ${ignores.length} rules in .vercelignore`);
|
debug(`Found ${ignores.length} rules in .vercelignore`);
|
||||||
debug('Building file tree...');
|
debug('Building file tree...');
|
||||||
@@ -119,37 +122,50 @@ export async function buildFileTree(
|
|||||||
|
|
||||||
export async function getVercelIgnore(
|
export async function getVercelIgnore(
|
||||||
cwd: string | string[],
|
cwd: string | string[],
|
||||||
prebuilt?: boolean
|
prebuilt?: boolean,
|
||||||
|
rootDirectory?: string
|
||||||
): Promise<{ ig: Ignore; ignores: string[] }> {
|
): Promise<{ ig: Ignore; ignores: string[] }> {
|
||||||
const ignores: string[] = prebuilt
|
let ignores: string[] = [];
|
||||||
? ['*', '!.output', '!.output/**']
|
|
||||||
: [
|
const outputDir = posix.join(rootDirectory || '', '.output');
|
||||||
'.hg',
|
|
||||||
'.git',
|
if (prebuilt) {
|
||||||
'.gitmodules',
|
ignores.push('*');
|
||||||
'.svn',
|
const parts = outputDir.split('/');
|
||||||
'.cache',
|
parts.forEach((_, i) => {
|
||||||
'.next',
|
const level = parts.slice(0, i + 1).join('/');
|
||||||
'.now',
|
ignores.push(`!${level}`);
|
||||||
'.vercel',
|
});
|
||||||
'.npmignore',
|
ignores.push(`!${outputDir}/**`);
|
||||||
'.dockerignore',
|
} else {
|
||||||
'.gitignore',
|
ignores = [
|
||||||
'.*.swp',
|
'.hg',
|
||||||
'.DS_Store',
|
'.git',
|
||||||
'.wafpicke-*',
|
'.gitmodules',
|
||||||
'.lock-wscript',
|
'.svn',
|
||||||
'.env.local',
|
'.cache',
|
||||||
'.env.*.local',
|
'.next',
|
||||||
'.venv',
|
'.now',
|
||||||
'npm-debug.log',
|
'.vercel',
|
||||||
'config.gypi',
|
'.npmignore',
|
||||||
'node_modules',
|
'.dockerignore',
|
||||||
'__pycache__',
|
'.gitignore',
|
||||||
'venv',
|
'.*.swp',
|
||||||
'CVS',
|
'.DS_Store',
|
||||||
'.output',
|
'.wafpicke-*',
|
||||||
];
|
'.lock-wscript',
|
||||||
|
'.env.local',
|
||||||
|
'.env.*.local',
|
||||||
|
'.venv',
|
||||||
|
'npm-debug.log',
|
||||||
|
'config.gypi',
|
||||||
|
'node_modules',
|
||||||
|
'__pycache__',
|
||||||
|
'venv',
|
||||||
|
'CVS',
|
||||||
|
`.output`,
|
||||||
|
];
|
||||||
|
}
|
||||||
const cwds = Array.isArray(cwd) ? cwd : [cwd];
|
const cwds = Array.isArray(cwd) ? cwd : [cwd];
|
||||||
|
|
||||||
const files = await Promise.all(
|
const files = await Promise.all(
|
||||||
@@ -250,39 +266,31 @@ export const prepareFiles = (
|
|||||||
files: Map<string, DeploymentFile>,
|
files: Map<string, DeploymentFile>,
|
||||||
clientOptions: VercelClientOptions
|
clientOptions: VercelClientOptions
|
||||||
): PreparedFile[] => {
|
): PreparedFile[] => {
|
||||||
const preparedFiles = [...files.keys()].reduce(
|
const preparedFiles: PreparedFile[] = [];
|
||||||
(acc: PreparedFile[], sha: string): PreparedFile[] => {
|
for (const [sha, file] of files) {
|
||||||
const next = [...acc];
|
for (const name of file.names) {
|
||||||
|
let fileName: string;
|
||||||
|
|
||||||
const file = files.get(sha) as DeploymentFile;
|
if (clientOptions.isDirectory) {
|
||||||
|
// Directory
|
||||||
for (const name of file.names) {
|
fileName =
|
||||||
let fileName: string;
|
typeof clientOptions.path === 'string'
|
||||||
|
? relative(clientOptions.path, name)
|
||||||
if (clientOptions.isDirectory) {
|
: name;
|
||||||
// Directory
|
} else {
|
||||||
fileName =
|
// Array of files or single file
|
||||||
typeof clientOptions.path === 'string'
|
const segments = name.split(sep);
|
||||||
? relative(clientOptions.path, name)
|
fileName = segments[segments.length - 1];
|
||||||
: name;
|
|
||||||
} else {
|
|
||||||
// Array of files or single file
|
|
||||||
const segments = name.split(sep);
|
|
||||||
fileName = segments[segments.length - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
next.push({
|
|
||||||
file: isWin ? fileName.replace(/\\/g, '/') : fileName,
|
|
||||||
size: file.data.byteLength || file.data.length,
|
|
||||||
mode: file.mode,
|
|
||||||
sha,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return next;
|
preparedFiles.push({
|
||||||
},
|
file: isWin ? fileName.replace(/\\/g, '/') : fileName,
|
||||||
[]
|
size: file.data.byteLength || file.data.length,
|
||||||
);
|
mode: file.mode,
|
||||||
|
sha,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return preparedFiles;
|
return preparedFiles;
|
||||||
};
|
};
|
||||||
|
|||||||
1
packages/client/tests/fixtures/file-system-api-root-directory/foo.txt
vendored
Normal file
1
packages/client/tests/fixtures/file-system-api-root-directory/foo.txt
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
foo
|
||||||
1
packages/client/tests/fixtures/file-system-api-root-directory/root/.output/baz.txt
vendored
Normal file
1
packages/client/tests/fixtures/file-system-api-root-directory/root/.output/baz.txt
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
baz
|
||||||
1
packages/client/tests/fixtures/file-system-api-root-directory/root/.output/sub/qux.txt
vendored
Normal file
1
packages/client/tests/fixtures/file-system-api-root-directory/root/.output/sub/qux.txt
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
qux
|
||||||
1
packages/client/tests/fixtures/file-system-api-root-directory/root/bar.txt
vendored
Normal file
1
packages/client/tests/fixtures/file-system-api-root-directory/root/bar.txt
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
bar
|
||||||
1
packages/client/tests/fixtures/file-system-api-root-directory/someother/.output/baz.txt
vendored
Normal file
1
packages/client/tests/fixtures/file-system-api-root-directory/someother/.output/baz.txt
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
baz
|
||||||
1
packages/client/tests/fixtures/file-system-api-root-directory/someother/.output/sub/qux.txt
vendored
Normal file
1
packages/client/tests/fixtures/file-system-api-root-directory/someother/.output/sub/qux.txt
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
qux
|
||||||
1
packages/client/tests/fixtures/file-system-api-root-directory/someother/bar.txt
vendored
Normal file
1
packages/client/tests/fixtures/file-system-api-root-directory/someother/bar.txt
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
bar
|
||||||
1
packages/client/tests/fixtures/file-system-api/.output/baz.txt
vendored
Normal file
1
packages/client/tests/fixtures/file-system-api/.output/baz.txt
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
baz
|
||||||
1
packages/client/tests/fixtures/file-system-api/.output/sub/qux.txt
vendored
Normal file
1
packages/client/tests/fixtures/file-system-api/.output/sub/qux.txt
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
qux
|
||||||
1
packages/client/tests/fixtures/file-system-api/foo.txt
vendored
Normal file
1
packages/client/tests/fixtures/file-system-api/foo.txt
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
foo
|
||||||
1
packages/client/tests/fixtures/file-system-api/sub/bar.txt
vendored
Normal file
1
packages/client/tests/fixtures/file-system-api/sub/bar.txt
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
bar
|
||||||
4
packages/client/tests/tsconfig.json
Normal file
4
packages/client/tests/tsconfig.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"include": ["*.test.ts"]
|
||||||
|
}
|
||||||
@@ -17,7 +17,11 @@ const toAbsolutePaths = (cwd: string, files: string[]) =>
|
|||||||
describe('buildFileTree()', () => {
|
describe('buildFileTree()', () => {
|
||||||
it('should exclude files using `.nowignore` blocklist', async () => {
|
it('should exclude files using `.nowignore` blocklist', async () => {
|
||||||
const cwd = fixture('nowignore');
|
const cwd = fixture('nowignore');
|
||||||
const { fileList, ignoreList } = await buildFileTree(cwd, true, noop);
|
const { fileList, ignoreList } = await buildFileTree(
|
||||||
|
cwd,
|
||||||
|
{ isDirectory: true },
|
||||||
|
noop
|
||||||
|
);
|
||||||
|
|
||||||
const expectedFileList = toAbsolutePaths(cwd, ['.nowignore', 'index.txt']);
|
const expectedFileList = toAbsolutePaths(cwd, ['.nowignore', 'index.txt']);
|
||||||
expect(normalizeWindowsPaths(expectedFileList).sort()).toEqual(
|
expect(normalizeWindowsPaths(expectedFileList).sort()).toEqual(
|
||||||
@@ -36,7 +40,11 @@ describe('buildFileTree()', () => {
|
|||||||
|
|
||||||
it('should include the node_modules using `.vercelignore` allowlist', async () => {
|
it('should include the node_modules using `.vercelignore` allowlist', async () => {
|
||||||
const cwd = fixture('vercelignore-allow-nodemodules');
|
const cwd = fixture('vercelignore-allow-nodemodules');
|
||||||
const { fileList, ignoreList } = await buildFileTree(cwd, true, noop);
|
const { fileList, ignoreList } = await buildFileTree(
|
||||||
|
cwd,
|
||||||
|
{ isDirectory: true },
|
||||||
|
noop
|
||||||
|
);
|
||||||
|
|
||||||
const expected = toAbsolutePaths(cwd, [
|
const expected = toAbsolutePaths(cwd, [
|
||||||
'node_modules/one.txt',
|
'node_modules/one.txt',
|
||||||
@@ -54,4 +62,90 @@ describe('buildFileTree()', () => {
|
|||||||
normalizeWindowsPaths(ignoreList).sort()
|
normalizeWindowsPaths(ignoreList).sort()
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should find root files but ignore .output files when prebuilt=false', async () => {
|
||||||
|
const cwd = fixture('file-system-api');
|
||||||
|
const { fileList, ignoreList } = await buildFileTree(
|
||||||
|
cwd,
|
||||||
|
{ isDirectory: true, prebuilt: false },
|
||||||
|
noop
|
||||||
|
);
|
||||||
|
|
||||||
|
const expectedFileList = toAbsolutePaths(cwd, ['foo.txt', 'sub/bar.txt']);
|
||||||
|
expect(normalizeWindowsPaths(expectedFileList).sort()).toEqual(
|
||||||
|
normalizeWindowsPaths(fileList).sort()
|
||||||
|
);
|
||||||
|
|
||||||
|
const expectedIgnoreList = ['.output'];
|
||||||
|
expect(normalizeWindowsPaths(expectedIgnoreList).sort()).toEqual(
|
||||||
|
normalizeWindowsPaths(ignoreList).sort()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find .output files but ignore other files when prebuilt=true', async () => {
|
||||||
|
const cwd = fixture('file-system-api');
|
||||||
|
const { fileList, ignoreList } = await buildFileTree(
|
||||||
|
cwd,
|
||||||
|
{ isDirectory: true, prebuilt: true },
|
||||||
|
noop
|
||||||
|
);
|
||||||
|
|
||||||
|
const expectedFileList = toAbsolutePaths(cwd, [
|
||||||
|
'.output/baz.txt',
|
||||||
|
'.output/sub/qux.txt',
|
||||||
|
]);
|
||||||
|
expect(normalizeWindowsPaths(expectedFileList).sort()).toEqual(
|
||||||
|
normalizeWindowsPaths(fileList).sort()
|
||||||
|
);
|
||||||
|
|
||||||
|
const expectedIgnoreList = ['foo.txt', 'sub'];
|
||||||
|
expect(normalizeWindowsPaths(expectedIgnoreList).sort()).toEqual(
|
||||||
|
normalizeWindowsPaths(ignoreList).sort()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find root files but ignore all .output files when prebuilt=false and rootDirectory=root', async () => {
|
||||||
|
const cwd = fixture('file-system-api-root-directory');
|
||||||
|
const { fileList, ignoreList } = await buildFileTree(
|
||||||
|
cwd,
|
||||||
|
{ isDirectory: true, prebuilt: false, rootDirectory: 'root' },
|
||||||
|
noop
|
||||||
|
);
|
||||||
|
|
||||||
|
const expectedFileList = toAbsolutePaths(cwd, [
|
||||||
|
'foo.txt',
|
||||||
|
'root/bar.txt',
|
||||||
|
'someother/bar.txt',
|
||||||
|
]);
|
||||||
|
expect(normalizeWindowsPaths(expectedFileList).sort()).toEqual(
|
||||||
|
normalizeWindowsPaths(fileList).sort()
|
||||||
|
);
|
||||||
|
|
||||||
|
const expectedIgnoreList = ['root/.output', 'someother/.output'];
|
||||||
|
expect(normalizeWindowsPaths(expectedIgnoreList).sort()).toEqual(
|
||||||
|
normalizeWindowsPaths(ignoreList).sort()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find root/.output files but ignore other files when prebuilt=true and rootDirectory=root', async () => {
|
||||||
|
const cwd = fixture('file-system-api-root-directory');
|
||||||
|
const { fileList, ignoreList } = await buildFileTree(
|
||||||
|
cwd,
|
||||||
|
{ isDirectory: true, prebuilt: true, rootDirectory: 'root' },
|
||||||
|
noop
|
||||||
|
);
|
||||||
|
|
||||||
|
const expectedFileList = toAbsolutePaths(cwd, [
|
||||||
|
'root/.output/baz.txt',
|
||||||
|
'root/.output/sub/qux.txt',
|
||||||
|
]);
|
||||||
|
expect(normalizeWindowsPaths(expectedFileList).sort()).toEqual(
|
||||||
|
normalizeWindowsPaths(fileList).sort()
|
||||||
|
);
|
||||||
|
|
||||||
|
const expectedIgnoreList = ['foo.txt', 'root/bar.txt', 'someother'];
|
||||||
|
expect(normalizeWindowsPaths(expectedIgnoreList).sort()).toEqual(
|
||||||
|
normalizeWindowsPaths(ignoreList).sort()
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
6
packages/frameworks/logos/remix-no-shadow.svg
Normal file
6
packages/frameworks/logos/remix-no-shadow.svg
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<svg viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M700 0H100C44.772 0 0 44.772 0 100v600c0 55.228 44.772 100 100 100h600c55.228 0 100-44.772 100-100V100C800 44.772 755.228 0 700 0Z" fill="#212121"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M587.947 527.768c4.254 54.65 4.254 80.268 4.254 108.232H465.756c0-6.091.109-11.663.219-17.313.342-17.564.699-35.88-2.147-72.868-3.761-54.152-27.08-66.185-69.957-66.185H195v-98.525h204.889c54.16 0 81.241-16.476 81.241-60.098 0-38.357-27.081-61.601-81.241-61.601H195V163h227.456C545.069 163 606 220.912 606 313.42c0 69.193-42.877 114.319-100.799 121.84 48.895 9.777 77.48 37.605 82.746 92.508Z" fill="#fff"/>
|
||||||
|
<path d="M195 636v-73.447h133.697c22.332 0 27.181 16.563 27.181 26.441V636H195Z" fill="#fff"/>
|
||||||
|
<path d="M194.5 636v.5h161.878v-47.506c0-5.006-1.226-11.734-5.315-17.224-4.108-5.515-11.059-9.717-22.366-9.717H194.5V636Z" stroke="#fff" stroke-opacity=".8"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 958 B |
@@ -1,25 +1,6 @@
|
|||||||
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<rect width="800" height="800" fill="#212121"/>
|
<path d="M700 0H100C44.772 0 0 44.772 0 100v600c0 55.228 44.772 100 100 100h600c55.228 0 100-44.772 100-100V100C800 44.772 755.228 0 700 0Z" fill="#212121"/>
|
||||||
<g filter="url(#filter0_dd_126_53)">
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M587.947 527.768c4.254 54.65 4.254 80.268 4.254 108.232H465.756c0-6.091.109-11.663.219-17.313.342-17.564.699-35.88-2.147-72.868-3.761-54.152-27.08-66.185-69.957-66.185H195v-98.525h204.889c54.16 0 81.241-16.476 81.241-60.098 0-38.357-27.081-61.601-81.241-61.601H195V163h227.456C545.069 163 606 220.912 606 313.42c0 69.193-42.877 114.319-100.799 121.84 48.895 9.777 77.48 37.605 82.746 92.508Z" fill="#fff"/>
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M587.947 527.768C592.201 582.418 592.201 608.036 592.201 636H465.756C465.756 629.909 465.865 624.337 465.975 618.687C466.317 601.123 466.674 582.807 463.828 545.819C460.067 491.667 436.748 479.634 393.871 479.634H355.883H195V381.109H399.889C454.049 381.109 481.13 364.633 481.13 321.011C481.13 282.654 454.049 259.41 399.889 259.41H195V163H422.456C545.069 163 606 220.912 606 313.42C606 382.613 563.123 427.739 505.201 435.26C554.096 445.037 582.681 472.865 587.947 527.768Z" fill="#E8F2FF"/>
|
<path d="M195 636v-73.447h133.697c22.332 0 27.181 16.563 27.181 26.441V636H195Z" fill="#fff"/>
|
||||||
<path d="M195 636V562.553H328.697C351.029 562.553 355.878 579.116 355.878 588.994V636H195Z" fill="#E8F2FF"/>
|
<path d="M194.5 636v.5h161.878v-47.506c0-5.006-1.226-11.734-5.315-17.224-4.108-5.515-11.059-9.717-22.366-9.717H194.5V636Z" stroke="#fff" stroke-opacity=".8"/>
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<filter id="filter0_dd_126_53" x="131" y="99" width="539" height="601" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
|
||||||
<feOffset/>
|
|
||||||
<feGaussianBlur stdDeviation="28"/>
|
|
||||||
<feComposite in2="hardAlpha" operator="out"/>
|
|
||||||
<feColorMatrix type="matrix" values="0 0 0 0 0.223529 0 0 0 0 0.572549 0 0 0 0 1 0 0 0 1 0"/>
|
|
||||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_126_53"/>
|
|
||||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
|
||||||
<feOffset/>
|
|
||||||
<feGaussianBlur stdDeviation="32"/>
|
|
||||||
<feComposite in2="hardAlpha" operator="out"/>
|
|
||||||
<feColorMatrix type="matrix" values="0 0 0 0 0.223529 0 0 0 0 0.572549 0 0 0 0 1 0 0 0 0.9 0"/>
|
|
||||||
<feBlend mode="normal" in2="effect1_dropShadow_126_53" result="effect2_dropShadow_126_53"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow_126_53" result="shape"/>
|
|
||||||
</filter>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 958 B |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vercel/frameworks",
|
"name": "@vercel/frameworks",
|
||||||
"version": "0.5.1-canary.13",
|
"version": "0.5.1-canary.17",
|
||||||
"main": "./dist/frameworks.js",
|
"main": "./dist/frameworks.js",
|
||||||
"types": "./dist/frameworks.d.ts",
|
"types": "./dist/frameworks.d.ts",
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -141,7 +141,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
dependency: 'gatsby',
|
dependency: 'gatsby',
|
||||||
getOutputDirName: async () => 'public',
|
getOutputDirName: async () => 'public',
|
||||||
getFsOutputDir: async () => 'public',
|
|
||||||
defaultRoutes: async (dirPrefix: string) => {
|
defaultRoutes: async (dirPrefix: string) => {
|
||||||
// This file could be generated by gatsby-plugin-now or gatsby-plugin-zeit-now
|
// This file could be generated by gatsby-plugin-now or gatsby-plugin-zeit-now
|
||||||
try {
|
try {
|
||||||
@@ -195,7 +194,7 @@ export const frameworks = [
|
|||||||
name: 'Remix',
|
name: 'Remix',
|
||||||
slug: 'remix',
|
slug: 'remix',
|
||||||
demo: 'https://remix.examples.vercel.com',
|
demo: 'https://remix.examples.vercel.com',
|
||||||
logo: 'https://raw.githubusercontent.com/vercel/vercel/main/packages/frameworks/logos/remix.svg',
|
logo: 'https://raw.githubusercontent.com/vercel/vercel/main/packages/frameworks/logos/remix-no-shadow.svg',
|
||||||
tagline: 'Build Better Websites',
|
tagline: 'Build Better Websites',
|
||||||
description: 'A new Remix app — the result of running `npx create-remix`.',
|
description: 'A new Remix app — the result of running `npx create-remix`.',
|
||||||
website: 'https://remix.run',
|
website: 'https://remix.run',
|
||||||
@@ -226,7 +225,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: 'remix',
|
dependency: 'remix',
|
||||||
getFsOutputDir: async () => 'public',
|
|
||||||
getOutputDirName: async () => 'public',
|
getOutputDirName: async () => 'public',
|
||||||
defaultRoutes: [
|
defaultRoutes: [
|
||||||
{
|
{
|
||||||
@@ -251,13 +249,16 @@ export const frameworks = [
|
|||||||
],
|
],
|
||||||
defaultHeaders: [
|
defaultHeaders: [
|
||||||
{
|
{
|
||||||
source: '^/build/(.*)$',
|
source: '/build/(.*)',
|
||||||
regex: '^/build/(.*)$',
|
regex: '/build/(.*)',
|
||||||
headers: [
|
headers: [
|
||||||
{ key: 'cache-control', value: 'public, max-age=31536000, immutable' },
|
{
|
||||||
|
key: 'cache-control',
|
||||||
|
value: 'public, max-age=31536000, immutable',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Hexo',
|
name: 'Hexo',
|
||||||
@@ -294,7 +295,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: 'hexo',
|
dependency: 'hexo',
|
||||||
getFsOutputDir: async () => 'public',
|
|
||||||
getOutputDirName: async () => 'public',
|
getOutputDirName: async () => 'public',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -332,7 +332,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: '@11ty/eleventy',
|
dependency: '@11ty/eleventy',
|
||||||
getFsOutputDir: async () => '_site',
|
|
||||||
getOutputDirName: async () => '_site',
|
getOutputDirName: async () => '_site',
|
||||||
cachePattern: '.cache/**',
|
cachePattern: '.cache/**',
|
||||||
},
|
},
|
||||||
@@ -372,22 +371,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: '@docusaurus/core',
|
dependency: '@docusaurus/core',
|
||||||
getFsOutputDir: async (dirPrefix: string) => {
|
|
||||||
const base = 'build';
|
|
||||||
try {
|
|
||||||
const location = join(dirPrefix, base);
|
|
||||||
const content = await readdir(location, { withFileTypes: true });
|
|
||||||
|
|
||||||
// If there is only one file in it that is a dir we'll use it as dist dir
|
|
||||||
if (content.length === 1 && content[0].isDirectory()) {
|
|
||||||
return join(base, content[0].name);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error detecting output directory: `, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return base;
|
|
||||||
},
|
|
||||||
getOutputDirName: async (dirPrefix: string) => {
|
getOutputDirName: async (dirPrefix: string) => {
|
||||||
const base = 'build';
|
const base = 'build';
|
||||||
try {
|
try {
|
||||||
@@ -527,21 +510,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: 'docusaurus',
|
dependency: 'docusaurus',
|
||||||
getFsOutputDir: async (dirPrefix: string) => {
|
|
||||||
const base = 'build';
|
|
||||||
try {
|
|
||||||
const location = join(dirPrefix, base);
|
|
||||||
const content = await readdir(location, { withFileTypes: true });
|
|
||||||
|
|
||||||
// If there is only one file in it that is a dir we'll use it as dist dir
|
|
||||||
if (content.length === 1 && content[0].isDirectory()) {
|
|
||||||
return join(base, content[0].name);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error detecting output directory: `, error);
|
|
||||||
}
|
|
||||||
return base;
|
|
||||||
},
|
|
||||||
getOutputDirName: async (dirPrefix: string) => {
|
getOutputDirName: async (dirPrefix: string) => {
|
||||||
const base = 'build';
|
const base = 'build';
|
||||||
try {
|
try {
|
||||||
@@ -593,7 +561,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: 'preact-cli',
|
dependency: 'preact-cli',
|
||||||
getFsOutputDir: async () => 'build',
|
|
||||||
getOutputDirName: async () => 'build',
|
getOutputDirName: async () => 'build',
|
||||||
defaultRoutes: [
|
defaultRoutes: [
|
||||||
{
|
{
|
||||||
@@ -650,7 +617,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: '@dojo/cli',
|
dependency: '@dojo/cli',
|
||||||
getFsOutputDir: async () => 'output/dist',
|
|
||||||
getOutputDirName: async () => join('output', 'dist'),
|
getOutputDirName: async () => join('output', 'dist'),
|
||||||
defaultRoutes: [
|
defaultRoutes: [
|
||||||
{
|
{
|
||||||
@@ -717,7 +683,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: 'ember-cli',
|
dependency: 'ember-cli',
|
||||||
getFsOutputDir: async () => 'dist',
|
|
||||||
getOutputDirName: async () => 'dist',
|
getOutputDirName: async () => 'dist',
|
||||||
defaultRoutes: [
|
defaultRoutes: [
|
||||||
{
|
{
|
||||||
@@ -772,7 +737,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: '@vue/cli-service',
|
dependency: '@vue/cli-service',
|
||||||
getFsOutputDir: async () => 'dist',
|
|
||||||
getOutputDirName: async () => 'dist',
|
getOutputDirName: async () => 'dist',
|
||||||
defaultRoutes: [
|
defaultRoutes: [
|
||||||
{
|
{
|
||||||
@@ -849,7 +813,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: '@scullyio/init',
|
dependency: '@scullyio/init',
|
||||||
getFsOutputDir: async () => 'dist',
|
|
||||||
getOutputDirName: async () => 'dist/static',
|
getOutputDirName: async () => 'dist/static',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -886,7 +849,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: '@ionic/angular',
|
dependency: '@ionic/angular',
|
||||||
getFsOutputDir: async () => 'www',
|
|
||||||
getOutputDirName: async () => 'www',
|
getOutputDirName: async () => 'www',
|
||||||
defaultRoutes: [
|
defaultRoutes: [
|
||||||
{
|
{
|
||||||
@@ -940,7 +902,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: '@angular/cli',
|
dependency: '@angular/cli',
|
||||||
getFsOutputDir: async () => 'dist',
|
|
||||||
getOutputDirName: async (dirPrefix: string) => {
|
getOutputDirName: async (dirPrefix: string) => {
|
||||||
const base = 'dist';
|
const base = 'dist';
|
||||||
try {
|
try {
|
||||||
@@ -1008,7 +969,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: 'polymer-cli',
|
dependency: 'polymer-cli',
|
||||||
getFsOutputDir: async () => 'build',
|
|
||||||
getOutputDirName: async (dirPrefix: string) => {
|
getOutputDirName: async (dirPrefix: string) => {
|
||||||
const base = 'build';
|
const base = 'build';
|
||||||
try {
|
try {
|
||||||
@@ -1078,7 +1038,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: 'sirv-cli',
|
dependency: 'sirv-cli',
|
||||||
getFsOutputDir: async () => 'public',
|
|
||||||
getOutputDirName: async () => 'public',
|
getOutputDirName: async () => 'public',
|
||||||
defaultRoutes: [
|
defaultRoutes: [
|
||||||
{
|
{
|
||||||
@@ -1128,10 +1087,9 @@ export const frameworks = [
|
|||||||
placeholder: 'svelte-kit dev',
|
placeholder: 'svelte-kit dev',
|
||||||
},
|
},
|
||||||
outputDirectory: {
|
outputDirectory: {
|
||||||
placeholder: 'public',
|
value: 'public',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
getFsOutputDir: async () => '.output',
|
|
||||||
getOutputDirName: async () => 'public',
|
getOutputDirName: async () => 'public',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1168,7 +1126,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: '@ionic/react',
|
dependency: '@ionic/react',
|
||||||
getFsOutputDir: async () => 'build',
|
|
||||||
getOutputDirName: async () => 'build',
|
getOutputDirName: async () => 'build',
|
||||||
defaultRoutes: [
|
defaultRoutes: [
|
||||||
{
|
{
|
||||||
@@ -1276,7 +1233,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: 'react-scripts',
|
dependency: 'react-scripts',
|
||||||
getFsOutputDir: async () => 'build',
|
|
||||||
getOutputDirName: async () => 'build',
|
getOutputDirName: async () => 'build',
|
||||||
defaultRoutes: [
|
defaultRoutes: [
|
||||||
{
|
{
|
||||||
@@ -1378,7 +1334,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: 'gridsome',
|
dependency: 'gridsome',
|
||||||
getFsOutputDir: async () => 'dist',
|
|
||||||
getOutputDirName: async () => 'dist',
|
getOutputDirName: async () => 'dist',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1416,7 +1371,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: 'umi',
|
dependency: 'umi',
|
||||||
getFsOutputDir: async () => 'dist',
|
|
||||||
getOutputDirName: async () => 'dist',
|
getOutputDirName: async () => 'dist',
|
||||||
defaultRoutes: [
|
defaultRoutes: [
|
||||||
{
|
{
|
||||||
@@ -1470,7 +1424,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: 'sapper',
|
dependency: 'sapper',
|
||||||
getFsOutputDir: async () => '__sapper__/export',
|
|
||||||
getOutputDirName: async () => '__sapper__/export',
|
getOutputDirName: async () => '__sapper__/export',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1508,7 +1461,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: 'saber',
|
dependency: 'saber',
|
||||||
getFsOutputDir: async () => 'public',
|
|
||||||
getOutputDirName: async () => 'public',
|
getOutputDirName: async () => 'public',
|
||||||
defaultRoutes: [
|
defaultRoutes: [
|
||||||
{
|
{
|
||||||
@@ -1577,7 +1529,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: '@stencil/core',
|
dependency: '@stencil/core',
|
||||||
getFsOutputDir: async () => 'www',
|
|
||||||
getOutputDirName: async () => 'www',
|
getOutputDirName: async () => 'www',
|
||||||
defaultRoutes: [
|
defaultRoutes: [
|
||||||
{
|
{
|
||||||
@@ -1666,7 +1617,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: 'nuxt',
|
dependency: 'nuxt',
|
||||||
getFsOutputDir: async () => '.output',
|
|
||||||
getOutputDirName: async () => 'dist',
|
getOutputDirName: async () => 'dist',
|
||||||
cachePattern: '.nuxt/**',
|
cachePattern: '.nuxt/**',
|
||||||
defaultRoutes: [
|
defaultRoutes: [
|
||||||
@@ -1724,7 +1674,6 @@ export const frameworks = [
|
|||||||
placeholder: 'RedwoodJS default',
|
placeholder: 'RedwoodJS default',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
getFsOutputDir: async () => 'public',
|
|
||||||
getOutputDirName: async () => 'public',
|
getOutputDirName: async () => 'public',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1768,16 +1717,6 @@ export const frameworks = [
|
|||||||
placeholder: '`public` or `publishDir` from the `config` file',
|
placeholder: '`public` or `publishDir` from the `config` file',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
getFsOutputDir: async (dirPrefix: string): Promise<string> => {
|
|
||||||
type HugoConfig = { publishDir?: string };
|
|
||||||
const config = await readConfigFile<HugoConfig>(
|
|
||||||
['config.json', 'config.yaml', 'config.toml'].map(fileName => {
|
|
||||||
return join(dirPrefix, fileName);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return (config && config.publishDir) || 'public';
|
|
||||||
},
|
|
||||||
getOutputDirName: async (dirPrefix: string): Promise<string> => {
|
getOutputDirName: async (dirPrefix: string): Promise<string> => {
|
||||||
type HugoConfig = { publishDir?: string };
|
type HugoConfig = { publishDir?: string };
|
||||||
const config = await readConfigFile<HugoConfig>(
|
const config = await readConfigFile<HugoConfig>(
|
||||||
@@ -1822,13 +1761,6 @@ export const frameworks = [
|
|||||||
placeholder: '`_site` or `destination` from `_config.yml`',
|
placeholder: '`_site` or `destination` from `_config.yml`',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
getFsOutputDir: async (dirPrefix: string): Promise<string> => {
|
|
||||||
type JekyllConfig = { destination?: string };
|
|
||||||
const config = await readConfigFile<JekyllConfig>(
|
|
||||||
join(dirPrefix, '_config.yml')
|
|
||||||
);
|
|
||||||
return (config && config.destination) || '_site';
|
|
||||||
},
|
|
||||||
getOutputDirName: async (dirPrefix: string): Promise<string> => {
|
getOutputDirName: async (dirPrefix: string): Promise<string> => {
|
||||||
type JekyllConfig = { destination?: string };
|
type JekyllConfig = { destination?: string };
|
||||||
const config = await readConfigFile<JekyllConfig>(
|
const config = await readConfigFile<JekyllConfig>(
|
||||||
@@ -1870,7 +1802,6 @@ export const frameworks = [
|
|||||||
value: 'public',
|
value: 'public',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
getFsOutputDir: async () => 'public',
|
|
||||||
getOutputDirName: async () => 'public',
|
getOutputDirName: async () => 'public',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1905,7 +1836,6 @@ export const frameworks = [
|
|||||||
value: 'build',
|
value: 'build',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
getFsOutputDir: async () => 'build',
|
|
||||||
getOutputDirName: async () => 'build',
|
getOutputDirName: async () => 'build',
|
||||||
cachePattern: '{vendor/bin,vendor/cache,vendor/bundle}/**',
|
cachePattern: '{vendor/bin,vendor/cache,vendor/bundle}/**',
|
||||||
},
|
},
|
||||||
@@ -1940,7 +1870,6 @@ export const frameworks = [
|
|||||||
value: 'public',
|
value: 'public',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
getFsOutputDir: async () => 'public',
|
|
||||||
getOutputDirName: async () => 'public',
|
getOutputDirName: async () => 'public',
|
||||||
defaultVersion: '0.13.0',
|
defaultVersion: '0.13.0',
|
||||||
},
|
},
|
||||||
@@ -1980,7 +1909,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: 'vite',
|
dependency: 'vite',
|
||||||
getFsOutputDir: async () => 'dist',
|
|
||||||
getOutputDirName: async () => 'dist',
|
getOutputDirName: async () => 'dist',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -2018,7 +1946,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: 'parcel',
|
dependency: 'parcel',
|
||||||
getFsOutputDir: async () => 'dist',
|
|
||||||
getOutputDirName: async () => 'dist',
|
getOutputDirName: async () => 'dist',
|
||||||
defaultRoutes: [
|
defaultRoutes: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -162,9 +162,9 @@ export interface Framework {
|
|||||||
dependency?: string;
|
dependency?: string;
|
||||||
/**
|
/**
|
||||||
* Function that returns the name of the directory that the framework outputs
|
* Function that returns the name of the directory that the framework outputs
|
||||||
* its build results to. In some cases this is read from a configuration file.
|
* its File System API build results to, usually called `.output`.
|
||||||
*/
|
*/
|
||||||
getFsOutputDir: (dirPrefix: string) => Promise<string>;
|
getFsOutputDir?: (dirPrefix: string) => Promise<string>;
|
||||||
/**
|
/**
|
||||||
* Function that returns the name of the directory that the framework outputs
|
* Function that returns the name of the directory that the framework outputs
|
||||||
* its STATIC build results to. In some cases this is read from a configuration file.
|
* its STATIC build results to. In some cases this is read from a configuration file.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "vercel-plugin-middleware",
|
"name": "vercel-plugin-middleware",
|
||||||
"version": "0.0.0-canary.7",
|
"version": "0.0.0-canary.19",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/index",
|
"main": "./dist/index",
|
||||||
"homepage": "",
|
"homepage": "",
|
||||||
@@ -30,6 +30,7 @@
|
|||||||
"@types/node-fetch": "^2",
|
"@types/node-fetch": "^2",
|
||||||
"@types/ua-parser-js": "0.7.36",
|
"@types/ua-parser-js": "0.7.36",
|
||||||
"@types/uuid": "8.3.1",
|
"@types/uuid": "8.3.1",
|
||||||
|
"@vercel/build-utils": "2.12.3-canary.42",
|
||||||
"@vercel/ncc": "0.24.0",
|
"@vercel/ncc": "0.24.0",
|
||||||
"cookie": "0.4.1",
|
"cookie": "0.4.1",
|
||||||
"formdata-node": "4.3.1",
|
"formdata-node": "4.3.1",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import * as middleware from './_middleware';
|
import * as middleware from './_temp_middleware';
|
||||||
_ENTRIES = typeof _ENTRIES === 'undefined' ? {} : _ENTRIES;
|
_ENTRIES = typeof _ENTRIES === 'undefined' ? {} : _ENTRIES;
|
||||||
_ENTRIES['middleware_pages/_middleware'] = {
|
_ENTRIES['middleware_pages/_middleware'] = {
|
||||||
default: async function (ev) {
|
default: async function (ev) {
|
||||||
|
|||||||
52
packages/middleware/src/esbuild-plugins.ts
Normal file
52
packages/middleware/src/esbuild-plugins.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import path from 'path';
|
||||||
|
import * as esbuild from 'esbuild';
|
||||||
|
|
||||||
|
const processInjectFile = `
|
||||||
|
// envOverride is passed by esbuild plugin
|
||||||
|
const env = envOverride
|
||||||
|
function cwd() {
|
||||||
|
return '/'
|
||||||
|
}
|
||||||
|
function chdir(dir) {
|
||||||
|
throw new Error('process.chdir is not supported')
|
||||||
|
}
|
||||||
|
export const process = {
|
||||||
|
argv: [],
|
||||||
|
env,
|
||||||
|
chdir,
|
||||||
|
cwd,
|
||||||
|
};
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function nodeProcessPolyfillPlugin({ env = {} } = {}): esbuild.Plugin {
|
||||||
|
return {
|
||||||
|
name: 'node-process-polyfill',
|
||||||
|
setup({ initialOptions, onResolve, onLoad }) {
|
||||||
|
onResolve({ filter: /_virtual-process-polyfill_\.js/ }, ({ path }) => {
|
||||||
|
return {
|
||||||
|
path,
|
||||||
|
sideEffects: false,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
onLoad({ filter: /_virtual-process-polyfill_\.js/ }, () => {
|
||||||
|
const contents = `const envOverride = ${JSON.stringify(
|
||||||
|
env
|
||||||
|
)};\n${processInjectFile}`;
|
||||||
|
return {
|
||||||
|
loader: 'js',
|
||||||
|
contents,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const polyfills = [
|
||||||
|
path.resolve(__dirname, '_virtual-process-polyfill_.js'),
|
||||||
|
];
|
||||||
|
if (initialOptions.inject) {
|
||||||
|
initialOptions.inject.push(...polyfills);
|
||||||
|
} else {
|
||||||
|
initialOptions.inject = [...polyfills];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import { promises as fsp } from 'fs';
|
|||||||
import { IncomingMessage, ServerResponse } from 'http';
|
import { IncomingMessage, ServerResponse } from 'http';
|
||||||
import libGlob from 'glob';
|
import libGlob from 'glob';
|
||||||
import Proxy from 'http-proxy';
|
import Proxy from 'http-proxy';
|
||||||
|
import { updateFunctionsManifest } from '@vercel/build-utils';
|
||||||
|
|
||||||
import { run } from './websandbox';
|
import { run } from './websandbox';
|
||||||
import type { FetchEventResult } from './websandbox/types';
|
import type { FetchEventResult } from './websandbox/types';
|
||||||
@@ -16,13 +17,15 @@ import {
|
|||||||
UrlWithParsedQuery,
|
UrlWithParsedQuery,
|
||||||
} from 'url';
|
} from 'url';
|
||||||
import { toNodeHeaders } from './websandbox/utils';
|
import { toNodeHeaders } from './websandbox/utils';
|
||||||
|
import { nodeProcessPolyfillPlugin } from './esbuild-plugins';
|
||||||
|
|
||||||
const glob = util.promisify(libGlob);
|
const glob = util.promisify(libGlob);
|
||||||
const SUPPORTED_EXTENSIONS = ['.js', '.ts'];
|
const SUPPORTED_EXTENSIONS = ['.js', '.ts'];
|
||||||
|
|
||||||
// File name of the `entries.js` file that gets copied into the
|
// File name of the `entries.js` file that gets copied into the
|
||||||
// project directory. Use a name that is unlikely to conflict.
|
// project directory. Use a name that is unlikely to conflict.
|
||||||
const ENTRIES_NAME = '___vc_entries.js';
|
const TMP_ENTRIES_NAME = '.output/inputs/middleware/___vc_entries.js';
|
||||||
|
const TMP_MIDDLEWARE_BUNDLE = '.output/inputs/middleware/_temp_middleware.js';
|
||||||
|
|
||||||
async function getMiddlewareFile(workingDirectory: string) {
|
async function getMiddlewareFile(workingDirectory: string) {
|
||||||
// Only the root-level `_middleware.*` files are considered.
|
// Only the root-level `_middleware.*` files are considered.
|
||||||
@@ -52,17 +55,37 @@ async function getMiddlewareFile(workingDirectory: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function build({ workPath }: { workPath: string }) {
|
export async function build({ workPath }: { workPath: string }) {
|
||||||
const entriesPath = join(workPath, ENTRIES_NAME);
|
const entriesPath = join(workPath, TMP_ENTRIES_NAME);
|
||||||
|
const transientFilePath = join(workPath, TMP_MIDDLEWARE_BUNDLE);
|
||||||
const middlewareFile = await getMiddlewareFile(workPath);
|
const middlewareFile = await getMiddlewareFile(workPath);
|
||||||
if (!middlewareFile) return;
|
if (!middlewareFile) return;
|
||||||
|
|
||||||
console.log('Compiling middleware file: %j', middlewareFile);
|
console.log('Compiling middleware file: %j', middlewareFile);
|
||||||
|
|
||||||
// Create `_ENTRIES` wrapper
|
/**
|
||||||
await fsp.copyFile(join(__dirname, 'entries.js'), entriesPath);
|
* Two builds happen here, because esbuild doesn't offer a way to add a banner
|
||||||
|
* to individual input files, and the entries wrapper relies on running in
|
||||||
// Build
|
* non-strict mode to access the ENTRIES global.
|
||||||
|
*
|
||||||
|
* To work around this, we bundle the middleware directly and add
|
||||||
|
* 'use strict'; to make the entire bundle run in strict mode. We then bundle
|
||||||
|
* a second time, adding the global ENTRIES wrapper and preserving the
|
||||||
|
* 'use strict' for the entire scope of the original bundle.
|
||||||
|
*/
|
||||||
try {
|
try {
|
||||||
|
await esbuild.build({
|
||||||
|
entryPoints: [middlewareFile],
|
||||||
|
bundle: true,
|
||||||
|
absWorkingDir: workPath,
|
||||||
|
outfile: transientFilePath,
|
||||||
|
banner: {
|
||||||
|
js: '"use strict";',
|
||||||
|
},
|
||||||
|
plugins: [nodeProcessPolyfillPlugin({ env: process.env })],
|
||||||
|
format: 'cjs',
|
||||||
|
});
|
||||||
|
// Create `_ENTRIES` wrapper
|
||||||
|
await fsp.copyFile(join(__dirname, 'entries.js'), entriesPath);
|
||||||
await esbuild.build({
|
await esbuild.build({
|
||||||
entryPoints: [entriesPath],
|
entryPoints: [entriesPath],
|
||||||
bundle: true,
|
bundle: true,
|
||||||
@@ -70,29 +93,24 @@ export async function build({ workPath }: { workPath: string }) {
|
|||||||
outfile: join(workPath, '.output/server/pages/_middleware.js'),
|
outfile: join(workPath, '.output/server/pages/_middleware.js'),
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
|
await fsp.unlink(transientFilePath);
|
||||||
await fsp.unlink(entriesPath);
|
await fsp.unlink(entriesPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write middleware manifest
|
const fileName = basename(middlewareFile);
|
||||||
const middlewareManifest = {
|
const pages: { [key: string]: any } = {};
|
||||||
version: 1,
|
|
||||||
sortedMiddleware: ['/'],
|
pages[fileName] = {
|
||||||
middleware: {
|
runtime: 'web',
|
||||||
'/': {
|
env: [],
|
||||||
env: [],
|
files: ['server/pages/_middleware.js'],
|
||||||
files: ['server/pages/_middleware.js'],
|
name: 'pages/_middleware',
|
||||||
name: 'pages/_middleware',
|
page: '/',
|
||||||
page: '/',
|
regexp: '^/.*$',
|
||||||
regexp: '^/.*$',
|
sortingIndex: 1,
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
const middlewareManifestData = JSON.stringify(middlewareManifest, null, 2);
|
|
||||||
const middlewareManifestPath = join(
|
await updateFunctionsManifest({ workPath, pages });
|
||||||
workPath,
|
|
||||||
'.output/server/middleware-manifest.json'
|
|
||||||
);
|
|
||||||
await fsp.writeFile(middlewareManifestPath, middlewareManifestData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const stringifyQuery = (req: IncomingMessage, query: ParsedUrlQuery) => {
|
const stringifyQuery = (req: IncomingMessage, query: ParsedUrlQuery) => {
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ export async function run(params: {
|
|||||||
const content = readFileSync(params.path, 'utf-8');
|
const content = readFileSync(params.path, 'utf-8');
|
||||||
const esBuildResult = esbuild.transformSync(content, {
|
const esBuildResult = esbuild.transformSync(content, {
|
||||||
format: 'cjs',
|
format: 'cjs',
|
||||||
|
banner: '"use strict";',
|
||||||
});
|
});
|
||||||
const x = vm.runInNewContext(m.wrap(esBuildResult.code), cache.sandbox, {
|
const x = vm.runInNewContext(m.wrap(esBuildResult.code), cache.sandbox, {
|
||||||
filename: params.path,
|
filename: params.path,
|
||||||
@@ -163,6 +164,7 @@ function sandboxRequire(referrer: string, specifier: string) {
|
|||||||
|
|
||||||
const transformOptions: esbuild.TransformOptions = {
|
const transformOptions: esbuild.TransformOptions = {
|
||||||
format: 'cjs',
|
format: 'cjs',
|
||||||
|
banner: '"use strict";',
|
||||||
};
|
};
|
||||||
if (extname(resolved) === '.json') {
|
if (extname(resolved) === '.json') {
|
||||||
transformOptions.loader = 'json';
|
transformOptions.loader = 'json';
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
exports[`build() should build simple middleware 1`] = `
|
exports[`build() should build simple middleware 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"middleware": Object {
|
"pages": Object {
|
||||||
"/": Object {
|
"_middleware.js": Object {
|
||||||
"env": Array [],
|
"env": Array [],
|
||||||
"files": Array [
|
"files": Array [
|
||||||
"server/pages/_middleware.js",
|
"server/pages/_middleware.js",
|
||||||
@@ -11,11 +11,10 @@ Object {
|
|||||||
"name": "pages/_middleware",
|
"name": "pages/_middleware",
|
||||||
"page": "/",
|
"page": "/",
|
||||||
"regexp": "^/.*$",
|
"regexp": "^/.*$",
|
||||||
|
"runtime": "web",
|
||||||
|
"sortingIndex": 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"sortedMiddleware": Array [
|
"version": 2,
|
||||||
"/",
|
|
||||||
],
|
|
||||||
"version": 1,
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
78
packages/middleware/test/build.test.ts
vendored
78
packages/middleware/test/build.test.ts
vendored
@@ -3,6 +3,30 @@ import { promises as fsp } from 'fs';
|
|||||||
import { build } from '../src';
|
import { build } from '../src';
|
||||||
import { Response } from 'node-fetch';
|
import { Response } from 'node-fetch';
|
||||||
|
|
||||||
|
const setupFixture = async (fixture: string) => {
|
||||||
|
const fixturePath = join(__dirname, `fixtures/${fixture}`);
|
||||||
|
await build({
|
||||||
|
workPath: fixturePath,
|
||||||
|
});
|
||||||
|
|
||||||
|
const functionsManifest = JSON.parse(
|
||||||
|
await fsp.readFile(
|
||||||
|
join(fixturePath, '.output/functions-manifest.json'),
|
||||||
|
'utf8'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const outputFile = join(fixturePath, '.output/server/pages/_middleware.js');
|
||||||
|
expect(await fsp.stat(outputFile)).toBeTruthy();
|
||||||
|
require(outputFile);
|
||||||
|
//@ts-ignore
|
||||||
|
const middleware = global._ENTRIES['middleware_pages/_middleware'].default;
|
||||||
|
return {
|
||||||
|
middleware,
|
||||||
|
functionsManifest,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
describe('build()', () => {
|
describe('build()', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
@@ -15,25 +39,9 @@ describe('build()', () => {
|
|||||||
delete global._ENTRIES;
|
delete global._ENTRIES;
|
||||||
});
|
});
|
||||||
it('should build simple middleware', async () => {
|
it('should build simple middleware', async () => {
|
||||||
const fixture = join(__dirname, 'fixtures/simple');
|
const { functionsManifest, middleware } = await setupFixture('simple');
|
||||||
await build({
|
|
||||||
workPath: fixture,
|
|
||||||
});
|
|
||||||
|
|
||||||
const middlewareManifest = JSON.parse(
|
expect(functionsManifest).toMatchSnapshot();
|
||||||
await fsp.readFile(
|
|
||||||
join(fixture, '.output/server/middleware-manifest.json'),
|
|
||||||
'utf8'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
expect(middlewareManifest).toMatchSnapshot();
|
|
||||||
|
|
||||||
const outputFile = join(fixture, '.output/server/pages/_middleware.js');
|
|
||||||
expect(await fsp.stat(outputFile)).toBeTruthy();
|
|
||||||
|
|
||||||
require(outputFile);
|
|
||||||
//@ts-ignore
|
|
||||||
const middleware = global._ENTRIES['middleware_pages/_middleware'].default;
|
|
||||||
expect(typeof middleware).toStrictEqual('function');
|
expect(typeof middleware).toStrictEqual('function');
|
||||||
const handledResponse = await middleware({
|
const handledResponse = await middleware({
|
||||||
request: {
|
request: {
|
||||||
@@ -54,4 +62,38 @@ describe('build()', () => {
|
|||||||
(unhandledResponse.response as Response).headers.get('x-middleware-next')
|
(unhandledResponse.response as Response).headers.get('x-middleware-next')
|
||||||
).toEqual('1');
|
).toEqual('1');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should build simple middleware with env vars', async () => {
|
||||||
|
const expectedEnvVar = 'expected-env-var';
|
||||||
|
const fixture = join(__dirname, 'fixtures/env');
|
||||||
|
process.env.ENV_VAR_SHOULD_BE_DEFINED = expectedEnvVar;
|
||||||
|
await build({
|
||||||
|
workPath: fixture,
|
||||||
|
});
|
||||||
|
// env var should be inlined in the output
|
||||||
|
delete process.env.ENV_VAR_SHOULD_BE_DEFINED;
|
||||||
|
|
||||||
|
const outputFile = join(fixture, '.output/server/pages/_middleware.js');
|
||||||
|
expect(await fsp.stat(outputFile)).toBeTruthy();
|
||||||
|
|
||||||
|
require(outputFile);
|
||||||
|
//@ts-ignore
|
||||||
|
const middleware = global._ENTRIES['middleware_pages/_middleware'].default;
|
||||||
|
expect(typeof middleware).toStrictEqual('function');
|
||||||
|
const handledResponse = await middleware({
|
||||||
|
request: {},
|
||||||
|
});
|
||||||
|
expect(String(handledResponse.response.body)).toEqual(expectedEnvVar);
|
||||||
|
expect(
|
||||||
|
(handledResponse.response as Response).headers.get('x-middleware-next')
|
||||||
|
).toEqual(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a middleware that runs in strict mode', async () => {
|
||||||
|
const { middleware } = await setupFixture('use-strict');
|
||||||
|
const response = await middleware({
|
||||||
|
request: {},
|
||||||
|
});
|
||||||
|
expect(String(response.response.body)).toEqual('is strict mode? yes');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
3
packages/middleware/test/fixtures/env/_middleware.js
vendored
Normal file
3
packages/middleware/test/fixtures/env/_middleware.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export default req => {
|
||||||
|
return new Response(process.env.ENV_VAR_SHOULD_BE_DEFINED);
|
||||||
|
};
|
||||||
6
packages/middleware/test/fixtures/use-strict/_middleware.js
vendored
Normal file
6
packages/middleware/test/fixtures/use-strict/_middleware.js
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export default function (req) {
|
||||||
|
const isStrict = (function () {
|
||||||
|
return !this;
|
||||||
|
})();
|
||||||
|
return new Response('is strict mode? ' + (isStrict ? 'yes' : 'no'));
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"private": false,
|
"private": false,
|
||||||
"name": "vercel-plugin-go",
|
"name": "vercel-plugin-go",
|
||||||
"version": "1.0.0-canary.6",
|
"version": "1.0.0-canary.30",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"files": [
|
"files": [
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
"prepublishOnly": "tsc"
|
"prepublishOnly": "tsc"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vercel/build-utils": "2.12.3-canary.21",
|
"@vercel/build-utils": "2.12.3-canary.42",
|
||||||
"@vercel/go": "1.2.4-canary.4"
|
"@vercel/go": "1.2.4-canary.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { convertRuntimeToPlugin } from '@vercel/build-utils';
|
import { convertRuntimeToPlugin } from '@vercel/build-utils';
|
||||||
import * as go from '@vercel/go';
|
import * as go from '@vercel/go';
|
||||||
|
|
||||||
export const build = convertRuntimeToPlugin(go.build, '.go');
|
export const build = convertRuntimeToPlugin(go.build, 'vercel-plugin-go', '.go');
|
||||||
|
|
||||||
export const startDevServer = go.startDevServer;
|
export const startDevServer = go.startDevServer;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "vercel-plugin-node",
|
"name": "vercel-plugin-node",
|
||||||
"version": "1.12.2-canary.12",
|
"version": "1.12.2-canary.34",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/index",
|
"main": "./dist/index",
|
||||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
|
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
|
||||||
@@ -34,12 +34,12 @@
|
|||||||
"@types/node-fetch": "2",
|
"@types/node-fetch": "2",
|
||||||
"@types/test-listen": "1.1.0",
|
"@types/test-listen": "1.1.0",
|
||||||
"@types/yazl": "2.4.2",
|
"@types/yazl": "2.4.2",
|
||||||
"@vercel/build-utils": "2.12.3-canary.21",
|
"@vercel/build-utils": "2.12.3-canary.42",
|
||||||
"@vercel/fun": "1.0.3",
|
"@vercel/fun": "1.0.3",
|
||||||
"@vercel/ncc": "0.24.0",
|
"@vercel/ncc": "0.24.0",
|
||||||
"@vercel/nft": "0.14.0",
|
"@vercel/nft": "0.14.0",
|
||||||
"@vercel/node-bridge": "2.1.1-canary.2",
|
"@vercel/node-bridge": "2.1.1-canary.2",
|
||||||
"@vercel/static-config": "0.0.1-canary.0",
|
"@vercel/static-config": "0.0.1-canary.1",
|
||||||
"abort-controller": "3.0.0",
|
"abort-controller": "3.0.0",
|
||||||
"content-type": "1.0.4",
|
"content-type": "1.0.4",
|
||||||
"cookie": "0.4.0",
|
"cookie": "0.4.0",
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import {
|
|||||||
walkParentDirs,
|
walkParentDirs,
|
||||||
normalizePath,
|
normalizePath,
|
||||||
runPackageJsonScript,
|
runPackageJsonScript,
|
||||||
|
getInputHash,
|
||||||
} from '@vercel/build-utils';
|
} from '@vercel/build-utils';
|
||||||
import { FromSchema } from 'json-schema-to-ts';
|
import { FromSchema } from 'json-schema-to-ts';
|
||||||
import { getConfig, BaseFunctionConfigSchema } from '@vercel/static-config';
|
import { getConfig, BaseFunctionConfigSchema } from '@vercel/static-config';
|
||||||
@@ -47,8 +48,6 @@ import { AbortController } from 'abort-controller';
|
|||||||
import { Register, register } from './typescript';
|
import { Register, register } from './typescript';
|
||||||
import { pageToRoute } from './router/page-to-route';
|
import { pageToRoute } from './router/page-to-route';
|
||||||
import { isDynamicRoute } from './router/is-dynamic';
|
import { isDynamicRoute } from './router/is-dynamic';
|
||||||
import crypto from 'crypto';
|
|
||||||
import type { VercelConfig } from '@vercel/client';
|
|
||||||
|
|
||||||
export { shouldServe };
|
export { shouldServe };
|
||||||
export {
|
export {
|
||||||
@@ -380,13 +379,7 @@ function getAWSLambdaHandler(entrypoint: string, config: FunctionConfig) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO NATE: turn this into a `@vercel/plugin-utils` helper function?
|
// TODO NATE: turn this into a `@vercel/plugin-utils` helper function?
|
||||||
export async function build({
|
export async function build({ workPath }: { workPath: string }) {
|
||||||
vercelConfig,
|
|
||||||
workPath,
|
|
||||||
}: {
|
|
||||||
vercelConfig: VercelConfig;
|
|
||||||
workPath: string;
|
|
||||||
}) {
|
|
||||||
const project = new Project();
|
const project = new Project();
|
||||||
const entrypoints = await glob('api/**/*.[jt]s', workPath);
|
const entrypoints = await glob('api/**/*.[jt]s', workPath);
|
||||||
const installedPaths = new Set<string>();
|
const installedPaths = new Set<string>();
|
||||||
@@ -408,14 +401,13 @@ export async function build({
|
|||||||
getConfig(project, absEntrypoint, FunctionConfigSchema) || {};
|
getConfig(project, absEntrypoint, FunctionConfigSchema) || {};
|
||||||
|
|
||||||
// No config exported means "node", but if there is a config
|
// No config exported means "node", but if there is a config
|
||||||
// and "runtime" is defined, but it is not "node" then don't
|
// and "use" is defined, but it is not "node" then don't
|
||||||
// compile this file.
|
// compile this file.
|
||||||
if (config.runtime && config.runtime !== 'node') {
|
if (config.use && config.use !== 'node') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
await buildEntrypoint({
|
await buildEntrypoint({
|
||||||
vercelConfig,
|
|
||||||
workPath,
|
workPath,
|
||||||
entrypoint,
|
entrypoint,
|
||||||
config,
|
config,
|
||||||
@@ -425,23 +417,18 @@ export async function build({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function buildEntrypoint({
|
export async function buildEntrypoint({
|
||||||
vercelConfig,
|
|
||||||
workPath,
|
workPath,
|
||||||
entrypoint,
|
entrypoint,
|
||||||
config,
|
config,
|
||||||
installedPaths,
|
installedPaths,
|
||||||
}: {
|
}: {
|
||||||
vercelConfig: VercelConfig;
|
|
||||||
workPath: string;
|
workPath: string;
|
||||||
entrypoint: string;
|
entrypoint: string;
|
||||||
config: FunctionConfig;
|
config: FunctionConfig;
|
||||||
installedPaths?: Set<string>;
|
installedPaths?: Set<string>;
|
||||||
}) {
|
}) {
|
||||||
// Unique hash that will be used as directory name for `.output`.
|
// Unique hash that will be used as directory name for `.output`.
|
||||||
const entrypointHash = crypto
|
const entrypointHash = 'api-routes-node-' + getInputHash(entrypoint);
|
||||||
.createHash('sha256')
|
|
||||||
.update(entrypoint)
|
|
||||||
.digest('hex');
|
|
||||||
const outputDirPath = join(workPath, '.output');
|
const outputDirPath = join(workPath, '.output');
|
||||||
|
|
||||||
const { dir, name } = parsePath(entrypoint);
|
const { dir, name } = parsePath(entrypoint);
|
||||||
@@ -561,7 +548,7 @@ export async function buildEntrypoint({
|
|||||||
runtime: nodeVersion.runtime,
|
runtime: nodeVersion.runtime,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
await updateFunctionsManifest({ vercelConfig, workPath, pages });
|
await updateFunctionsManifest({ workPath, pages });
|
||||||
|
|
||||||
// Update the `routes-mainifest.json` file with the wildcard route
|
// Update the `routes-mainifest.json` file with the wildcard route
|
||||||
// when the entrypoint is dynamic (i.e. `/api/[id].ts`).
|
// when the entrypoint is dynamic (i.e. `/api/[id].ts`).
|
||||||
|
|||||||
11
packages/plugin-node/test/build.test.ts
vendored
11
packages/plugin-node/test/build.test.ts
vendored
@@ -143,16 +143,7 @@ function withFixture<T>(
|
|||||||
await runNpmInstall(fixture);
|
await runNpmInstall(fixture);
|
||||||
}
|
}
|
||||||
|
|
||||||
let vercelConfig = {};
|
await build({ workPath: fixture });
|
||||||
try {
|
|
||||||
vercelConfig = JSON.parse(
|
|
||||||
await fsp.readFile(path.join(fixture, 'vercel.json'), 'utf8')
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
// Consume error
|
|
||||||
}
|
|
||||||
|
|
||||||
await build({ vercelConfig, workPath: fixture });
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await t({ fixture, fetch });
|
return await t({ fixture, fetch });
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"private": false,
|
"private": false,
|
||||||
"name": "vercel-plugin-python",
|
"name": "vercel-plugin-python",
|
||||||
"version": "1.0.0-canary.7",
|
"version": "1.0.0-canary.31",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"files": [
|
"files": [
|
||||||
@@ -17,8 +17,8 @@
|
|||||||
"prepublishOnly": "tsc"
|
"prepublishOnly": "tsc"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vercel/build-utils": "2.12.3-canary.21",
|
"@vercel/build-utils": "2.12.3-canary.42",
|
||||||
"@vercel/python": "2.1.2-canary.0"
|
"@vercel/python": "2.1.2-canary.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { convertRuntimeToPlugin } from '@vercel/build-utils';
|
import { convertRuntimeToPlugin } from '@vercel/build-utils';
|
||||||
import * as python from '@vercel/python';
|
import * as python from '@vercel/python';
|
||||||
|
|
||||||
export const build = convertRuntimeToPlugin(python.build, '.py');
|
export const build = convertRuntimeToPlugin(python.build, 'vercel-plugin-python', '.py');
|
||||||
|
|
||||||
//export const startDevServer = python.startDevServer;
|
//export const startDevServer = python.startDevServer;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"private": false,
|
"private": false,
|
||||||
"name": "vercel-plugin-ruby",
|
"name": "vercel-plugin-ruby",
|
||||||
"version": "1.0.0-canary.5",
|
"version": "1.0.0-canary.30",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"files": [
|
"files": [
|
||||||
@@ -17,8 +17,8 @@
|
|||||||
"prepublishOnly": "tsc"
|
"prepublishOnly": "tsc"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vercel/build-utils": "2.12.3-canary.21",
|
"@vercel/build-utils": "2.12.3-canary.42",
|
||||||
"@vercel/ruby": "1.2.8-canary.4"
|
"@vercel/ruby": "1.2.10-canary.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { convertRuntimeToPlugin } from '@vercel/build-utils';
|
import { convertRuntimeToPlugin } from '@vercel/build-utils';
|
||||||
import * as ruby from '@vercel/ruby';
|
import * as ruby from '@vercel/ruby';
|
||||||
|
|
||||||
export const build = convertRuntimeToPlugin(ruby.build, '.rb');
|
export const build = convertRuntimeToPlugin(ruby.build, 'vercel-plugin-ruby', '.rb');
|
||||||
|
|
||||||
//export const startDevServer = ruby.startDevServer;
|
//export const startDevServer = ruby.startDevServer;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vercel/python",
|
"name": "@vercel/python",
|
||||||
"version": "2.1.2-canary.0",
|
"version": "2.1.2-canary.2",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/python",
|
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/python",
|
||||||
|
|||||||
@@ -135,6 +135,11 @@ export async function installRequirementsFile({
|
|||||||
meta,
|
meta,
|
||||||
args = [],
|
args = [],
|
||||||
}: InstallRequirementsFileArg) {
|
}: InstallRequirementsFileArg) {
|
||||||
|
// The Vercel platform already handles `requirements.txt` for frontend projects,
|
||||||
|
// but the installation logic there is different, because it seems to install all
|
||||||
|
// of the dependencies globally, whereas, for this Runtime, we want it to happen only
|
||||||
|
// locally, so we'll run a separate installation.
|
||||||
|
|
||||||
if (
|
if (
|
||||||
meta.isDev &&
|
meta.isDev &&
|
||||||
(await areRequirementsInstalled(pythonPath, filePath, workPath))
|
(await areRequirementsInstalled(pythonPath, filePath, workPath))
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { join, dirname } from 'path';
|
import { join, dirname, relative } from 'path';
|
||||||
import execa from 'execa';
|
import execa from 'execa';
|
||||||
import {
|
import {
|
||||||
ensureDir,
|
ensureDir,
|
||||||
@@ -85,10 +85,12 @@ export async function build({
|
|||||||
}: BuildOptions) {
|
}: BuildOptions) {
|
||||||
await download(files, workPath, meta);
|
await download(files, workPath, meta);
|
||||||
const entrypointFsDirname = join(workPath, dirname(entrypoint));
|
const entrypointFsDirname = join(workPath, dirname(entrypoint));
|
||||||
|
const gemfileName = 'Gemfile';
|
||||||
|
|
||||||
const gemfilePath = await walkParentDirs({
|
const gemfilePath = await walkParentDirs({
|
||||||
base: workPath,
|
base: workPath,
|
||||||
start: entrypointFsDirname,
|
start: entrypointFsDirname,
|
||||||
filename: 'Gemfile',
|
filename: gemfileName,
|
||||||
});
|
});
|
||||||
const gemfileContents = gemfilePath
|
const gemfileContents = gemfilePath
|
||||||
? await readFile(gemfilePath, 'utf8')
|
? await readFile(gemfilePath, 'utf8')
|
||||||
@@ -130,15 +132,24 @@ export async function build({
|
|||||||
'did not find a vendor directory but found a Gemfile, bundling gems...'
|
'did not find a vendor directory but found a Gemfile, bundling gems...'
|
||||||
);
|
);
|
||||||
|
|
||||||
// try installing. this won't work if native extesions are required.
|
const fileAtRoot = relative(workPath, gemfilePath) === gemfileName;
|
||||||
// if that's the case, gems should be vendored locally before deploying.
|
|
||||||
try {
|
// If the `Gemfile` is located in the Root Directory of the project and
|
||||||
await bundleInstall(bundlerPath, bundleDir, gemfilePath);
|
// the new File System API is used (`avoidTopLevelInstall`), the Install Command
|
||||||
} catch (err) {
|
// will have already installed its dependencies, so we don't need to do it again.
|
||||||
debug(
|
if (meta.avoidTopLevelInstall && fileAtRoot) {
|
||||||
'unable to build gems from Gemfile. vendor the gems locally with "bundle install --deployment" and retry.'
|
debug('Skipping `bundle install` — already handled by Install Command');
|
||||||
);
|
} else {
|
||||||
throw err;
|
// try installing. this won't work if native extesions are required.
|
||||||
|
// if that's the case, gems should be vendored locally before deploying.
|
||||||
|
try {
|
||||||
|
await bundleInstall(bundlerPath, bundleDir, gemfilePath);
|
||||||
|
} catch (err) {
|
||||||
|
debug(
|
||||||
|
'unable to build gems from Gemfile. vendor the gems locally with "bundle install --deployment" and retry.'
|
||||||
|
);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -9,13 +9,24 @@ interface RubyVersion extends NodeVersion {
|
|||||||
|
|
||||||
const allOptions: RubyVersion[] = [
|
const allOptions: RubyVersion[] = [
|
||||||
{ major: 2, minor: 7, range: '2.7.x', runtime: 'ruby2.7' },
|
{ major: 2, minor: 7, range: '2.7.x', runtime: 'ruby2.7' },
|
||||||
{ major: 2, minor: 5, range: '2.5.x', runtime: 'ruby2.5' },
|
{
|
||||||
|
major: 2,
|
||||||
|
minor: 5,
|
||||||
|
range: '2.5.x',
|
||||||
|
runtime: 'ruby2.5',
|
||||||
|
discontinueDate: new Date('2021-11-30'),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function getLatestRubyVersion(): RubyVersion {
|
function getLatestRubyVersion(): RubyVersion {
|
||||||
return allOptions[0];
|
return allOptions[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isDiscontinued({ discontinueDate }: RubyVersion): boolean {
|
||||||
|
const today = Date.now();
|
||||||
|
return discontinueDate !== undefined && discontinueDate.getTime() <= today;
|
||||||
|
}
|
||||||
|
|
||||||
function getRubyPath(meta: Meta, gemfileContents: string) {
|
function getRubyPath(meta: Meta, gemfileContents: string) {
|
||||||
let selection = getLatestRubyVersion();
|
let selection = getLatestRubyVersion();
|
||||||
if (meta.isDev) {
|
if (meta.isDev) {
|
||||||
@@ -37,8 +48,20 @@ function getRubyPath(meta: Meta, gemfileContents: string) {
|
|||||||
if (!found) {
|
if (!found) {
|
||||||
throw new NowBuildError({
|
throw new NowBuildError({
|
||||||
code: 'RUBY_INVALID_VERSION',
|
code: 'RUBY_INVALID_VERSION',
|
||||||
message: 'Found `Gemfile` with invalid Ruby version: `' + line + '`.',
|
message: `Found \`Gemfile\` with invalid Ruby version: \`${line}.\``,
|
||||||
link: 'https://vercel.com/docs/runtimes#official-runtimes/ruby/ruby-version',
|
link: 'http://vercel.link/ruby-version',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (isDiscontinued(selection)) {
|
||||||
|
const latest = getLatestRubyVersion();
|
||||||
|
const intro = `Found \`Gemfile\` with discontinued Ruby version: \`${line}.\``;
|
||||||
|
const hint = `Please set \`ruby "~> ${latest.range}"\` in your \`Gemfile\` to use Ruby ${latest.range}.`;
|
||||||
|
const upstream =
|
||||||
|
'This change is the result of a decision made by an upstream infrastructure provider (AWS).';
|
||||||
|
throw new NowBuildError({
|
||||||
|
code: 'RUBY_DISCONTINUED_VERSION',
|
||||||
|
link: 'http://vercel.link/ruby-version',
|
||||||
|
message: `${intro} ${hint} ${upstream}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,6 +89,23 @@ export async function installBundler(meta: Meta, gemfileContents: string) {
|
|||||||
gemfileContents
|
gemfileContents
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// If the new File System API is used (`avoidTopLevelInstall`), the Install Command
|
||||||
|
// will have already installed the dependencies, so we don't need to do it again.
|
||||||
|
if (meta.avoidTopLevelInstall) {
|
||||||
|
debug(
|
||||||
|
`Skipping bundler installation, already installed by Install Command`
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
gemHome,
|
||||||
|
rubyPath,
|
||||||
|
gemPath,
|
||||||
|
vendorPath,
|
||||||
|
runtime,
|
||||||
|
bundlerPath: join(gemHome, 'bin', 'bundler'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
debug('installing bundler...');
|
debug('installing bundler...');
|
||||||
await execa(gemPath, ['install', 'bundler', '--no-document'], {
|
await execa(gemPath, ['install', 'bundler', '--no-document'], {
|
||||||
stdio: 'pipe',
|
stdio: 'pipe',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@vercel/ruby",
|
"name": "@vercel/ruby",
|
||||||
"author": "Nathan Cahill <nathan@nathancahill.com>",
|
"author": "Nathan Cahill <nathan@nathancahill.com>",
|
||||||
"version": "1.2.8-canary.4",
|
"version": "1.2.10-canary.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/index",
|
"main": "./dist/index",
|
||||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/ruby",
|
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/ruby",
|
||||||
|
|||||||
2
packages/ruby/test/fixtures/02-cowsay-vendored/.bundle/config
vendored
Normal file
2
packages/ruby/test/fixtures/02-cowsay-vendored/.bundle/config
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
---
|
||||||
|
BUNDLE_PATH: "vendor/bundle"
|
||||||
@@ -2,6 +2,6 @@
|
|||||||
|
|
||||||
source "https://rubygems.org"
|
source "https://rubygems.org"
|
||||||
|
|
||||||
ruby "~> 2.5.0"
|
ruby "~> 2.7.0"
|
||||||
|
|
||||||
gem "cowsay", "~> 0.3.0"
|
gem "cowsay", "~> 0.3.0"
|
||||||
|
|||||||
16
packages/ruby/test/fixtures/02-cowsay-vendored/Gemfile.lock
vendored
Normal file
16
packages/ruby/test/fixtures/02-cowsay-vendored/Gemfile.lock
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
GEM
|
||||||
|
remote: https://rubygems.org/
|
||||||
|
specs:
|
||||||
|
cowsay (0.3.0)
|
||||||
|
|
||||||
|
PLATFORMS
|
||||||
|
x86_64-darwin-21
|
||||||
|
|
||||||
|
DEPENDENCIES
|
||||||
|
cowsay (~> 0.3.0)
|
||||||
|
|
||||||
|
RUBY VERSION
|
||||||
|
ruby 2.7.5p203
|
||||||
|
|
||||||
|
BUNDLED WITH
|
||||||
|
2.2.22
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": 2,
|
"version": 2,
|
||||||
"builds": [{ "src": "index.rb", "use": "@vercel/ruby" }],
|
"builds": [{ "src": "index.rb", "use": "@vercel/ruby" }],
|
||||||
"build": { "env": { "RUBY_VERSION": "2.5.x" } },
|
|
||||||
"probes": [{ "path": "/", "mustContain": "gem:RANDOMNESS_PLACEHOLDER" }]
|
"probes": [{ "path": "/", "mustContain": "gem:RANDOMNESS_PLACEHOLDER" }]
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user