mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-12 04:22:14 +00:00
Compare commits
15 Commits
vercel-plu
...
vercel-plu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5499fa9a04 | ||
|
|
b9fd64faff | ||
|
|
1202ff7b2b | ||
|
|
abd9f019f1 | ||
|
|
edb5eead81 | ||
|
|
6b865ff753 | ||
|
|
4fd0734c48 | ||
|
|
f815421acb | ||
|
|
5da926fee1 | ||
|
|
3559531e4c | ||
|
|
449a3b3648 | ||
|
|
7bd338618c | ||
|
|
9048a6f584 | ||
|
|
0cacb1bdac | ||
|
|
318bf35f82 |
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.27",
|
||||||
"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.16",
|
||||||
"@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",
|
||||||
|
|||||||
@@ -2,10 +2,9 @@ import fs from 'fs-extra';
|
|||||||
import { join, dirname, relative } from 'path';
|
import { join, dirname, relative } 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 FileBlob from './file-blob';
|
||||||
import type { BuilderFunctions, BuildOptions, Files } from './types';
|
import type { BuildOptions, Files } from './types';
|
||||||
import minimatch from 'minimatch';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert legacy Runtime to a Plugin.
|
* Convert legacy Runtime to a Plugin.
|
||||||
@@ -17,37 +16,25 @@ export function convertRuntimeToPlugin(
|
|||||||
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,
|
|
||||||
workPath,
|
|
||||||
}: {
|
|
||||||
vercelConfig: { functions?: BuilderFunctions; regions?: string[] };
|
|
||||||
workPath: string;
|
|
||||||
}) {
|
|
||||||
const opts = { cwd: workPath };
|
const opts = { cwd: workPath };
|
||||||
const files = await glob('**', opts);
|
const files = await glob('**', opts);
|
||||||
delete files['vercel.json']; // Builders/Runtimes didn't have vercel.json
|
delete files['vercel.json']; // Builders/Runtimes didn't have vercel.json
|
||||||
const entrypoints = await glob(`api/**/*${ext}`, opts);
|
const entrypoints = await glob(`api/**/*${ext}`, opts);
|
||||||
const pages: { [key: string]: any } = {};
|
const pages: { [key: string]: any } = {};
|
||||||
const { functions = {} } = vercelConfig;
|
|
||||||
const traceDir = join(workPath, '.output', 'runtime-traced-files');
|
const traceDir = join(workPath, '.output', 'runtime-traced-files');
|
||||||
await fs.ensureDir(traceDir);
|
await fs.ensureDir(traceDir);
|
||||||
|
|
||||||
for (const entrypoint of Object.keys(entrypoints)) {
|
for (const entrypoint of Object.keys(entrypoints)) {
|
||||||
const key =
|
|
||||||
Object.keys(functions).find(
|
|
||||||
src => src === entrypoint || minimatch(entrypoint, src)
|
|
||||||
) || '';
|
|
||||||
const config = functions[key] || {};
|
|
||||||
|
|
||||||
const { output } = await buildRuntime({
|
const { output } = await buildRuntime({
|
||||||
files,
|
files,
|
||||||
entrypoint,
|
entrypoint,
|
||||||
workPath,
|
workPath,
|
||||||
config: {
|
config: {
|
||||||
zeroConfig: true,
|
zeroConfig: true,
|
||||||
includeFiles: config.includeFiles,
|
},
|
||||||
excludeFiles: config.excludeFiles,
|
meta: {
|
||||||
|
avoidTopLevelInstall: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -58,7 +45,6 @@ export function convertRuntimeToPlugin(
|
|||||||
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
|
// @ts-ignore This symbol is a private API
|
||||||
@@ -105,7 +91,7 @@ export function convertRuntimeToPlugin(
|
|||||||
await fs.writeFile(nft, json);
|
await fs.writeFile(nft, json);
|
||||||
}
|
}
|
||||||
|
|
||||||
await updateFunctionsManifest({ vercelConfig, workPath, pages });
|
await updateFunctionsManifest({ workPath, pages });
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,15 +119,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 };
|
||||||
}) {
|
}) {
|
||||||
@@ -156,48 +139,88 @@ export async function updateFunctionsManifest({
|
|||||||
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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -18,21 +18,27 @@ 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 workPath = pythonApiWorkpath;
|
||||||
const lambdaOptions = {
|
const lambdaOptions = {
|
||||||
handler: 'index.handler',
|
handler: 'index.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) => {
|
||||||
@@ -44,11 +50,10 @@ describe('convert-runtime-to-plugin', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const lambdaFiles = await fsToJson(workPath);
|
const lambdaFiles = await fsToJson(workPath);
|
||||||
const vercelConfig = JSON.parse(lambdaFiles['vercel.json']);
|
|
||||||
delete lambdaFiles['vercel.json'];
|
delete lambdaFiles['vercel.json'];
|
||||||
const build = await convertRuntimeToPlugin(buildRuntime, '.py');
|
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({
|
||||||
@@ -76,7 +81,7 @@ describe('convert-runtime-to-plugin', () => {
|
|||||||
pages: {
|
pages: {
|
||||||
'api/index.py': lambdaOptions,
|
'api/index.py': lambdaOptions,
|
||||||
'api/users/get.py': lambdaOptions,
|
'api/users/get.py': lambdaOptions,
|
||||||
'api/users/post.py': { ...lambdaOptions, memory: 3008 },
|
'api/users/post.py': { ...lambdaOptions, memory: 512 },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -84,10 +89,19 @@ describe('convert-runtime-to-plugin', () => {
|
|||||||
expect(indexJson).toMatchObject({
|
expect(indexJson).toMatchObject({
|
||||||
version: 1,
|
version: 1,
|
||||||
files: [
|
files: [
|
||||||
|
{
|
||||||
|
input: '../../../../runtime-traced-files/api/db/[id].py',
|
||||||
|
output: 'api/db/[id].py',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
input: '../../../../runtime-traced-files/api/index.py',
|
input: '../../../../runtime-traced-files/api/index.py',
|
||||||
output: 'api/index.py',
|
output: 'api/index.py',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
input:
|
||||||
|
'../../../../runtime-traced-files/api/project/[aid]/[bid]/index.py',
|
||||||
|
output: 'api/project/[aid]/[bid]/index.py',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
input: '../../../../runtime-traced-files/api/users/get.py',
|
input: '../../../../runtime-traced-files/api/users/get.py',
|
||||||
output: 'api/users/get.py',
|
output: 'api/users/get.py',
|
||||||
@@ -117,10 +131,19 @@ describe('convert-runtime-to-plugin', () => {
|
|||||||
expect(getJson).toMatchObject({
|
expect(getJson).toMatchObject({
|
||||||
version: 1,
|
version: 1,
|
||||||
files: [
|
files: [
|
||||||
|
{
|
||||||
|
input: '../../../../../runtime-traced-files/api/db/[id].py',
|
||||||
|
output: 'api/db/[id].py',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
input: '../../../../../runtime-traced-files/api/index.py',
|
input: '../../../../../runtime-traced-files/api/index.py',
|
||||||
output: 'api/index.py',
|
output: 'api/index.py',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
input:
|
||||||
|
'../../../../../runtime-traced-files/api/project/[aid]/[bid]/index.py',
|
||||||
|
output: 'api/project/[aid]/[bid]/index.py',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
input: '../../../../../runtime-traced-files/api/users/get.py',
|
input: '../../../../../runtime-traced-files/api/users/get.py',
|
||||||
output: 'api/users/get.py',
|
output: 'api/users/get.py',
|
||||||
@@ -150,10 +173,19 @@ describe('convert-runtime-to-plugin', () => {
|
|||||||
expect(postJson).toMatchObject({
|
expect(postJson).toMatchObject({
|
||||||
version: 1,
|
version: 1,
|
||||||
files: [
|
files: [
|
||||||
|
{
|
||||||
|
input: '../../../../../runtime-traced-files/api/db/[id].py',
|
||||||
|
output: 'api/db/[id].py',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
input: '../../../../../runtime-traced-files/api/index.py',
|
input: '../../../../../runtime-traced-files/api/index.py',
|
||||||
output: 'api/index.py',
|
output: 'api/index.py',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
input:
|
||||||
|
'../../../../../runtime-traced-files/api/project/[aid]/[bid]/index.py',
|
||||||
|
output: 'api/project/[aid]/[bid]/index.py',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
input: '../../../../../runtime-traced-files/api/users/get.py',
|
input: '../../../../../runtime-traced-files/api/users/get.py',
|
||||||
output: 'api/users/get.py',
|
output: 'api/users/get.py',
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "vercel",
|
"name": "vercel",
|
||||||
"version": "23.1.3-canary.39",
|
"version": "23.1.3-canary.45",
|
||||||
"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.27",
|
||||||
"@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.1",
|
||||||
"@vercel/ruby": "1.2.8-canary.4",
|
"@vercel/ruby": "1.2.8-canary.5",
|
||||||
"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.7",
|
||||||
"vercel-plugin-node": "1.12.2-canary.12"
|
"vercel-plugin-node": "1.12.2-canary.18"
|
||||||
},
|
},
|
||||||
"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.16",
|
||||||
"@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",
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { assert } from 'console';
|
|||||||
import { createHash } from 'crypto';
|
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, resolve } 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,18 @@ 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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 +329,42 @@ 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 differnt project that's not in the settings.
|
||||||
|
const isNextOutput = Boolean(dotNextDir);
|
||||||
|
const outputDir = isNextOutput ? OUTPUT_DIR : join(OUTPUT_DIR, 'static');
|
||||||
|
const distDir =
|
||||||
|
dotNextDir ||
|
||||||
|
userOutputDirectory ||
|
||||||
|
(await framework.getFsOutputDir(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 +412,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 +440,7 @@ export default async function main(client: Client) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Special Next.js processing.
|
// Special Next.js processing.
|
||||||
if (isNextJs) {
|
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 +491,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 +505,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 +517,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 +525,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 +549,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,
|
||||||
});
|
});
|
||||||
@@ -539,6 +586,7 @@ export default async function main(client: Client) {
|
|||||||
baseDir,
|
baseDir,
|
||||||
outputDir: OUTPUT_DIR,
|
outputDir: OUTPUT_DIR,
|
||||||
nftFileName: f.replace(ext, '.js.nft.json'),
|
nftFileName: f.replace(ext, '.js.nft.json'),
|
||||||
|
distDir,
|
||||||
nft: {
|
nft: {
|
||||||
version: 1,
|
version: 1,
|
||||||
files: Array.from(fileList).map(fileListEntry =>
|
files: Array.from(fileList).map(fileListEntry =>
|
||||||
@@ -556,10 +604,12 @@ export default async function main(client: Client) {
|
|||||||
outputDir: OUTPUT_DIR,
|
outputDir: OUTPUT_DIR,
|
||||||
nftFileName: f,
|
nftFileName: f,
|
||||||
nft: json,
|
nft: json,
|
||||||
|
distDir,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client.output.debug(`Resolve ${param('required-server-files.json')}.`);
|
||||||
const requiredServerFilesPath = join(
|
const requiredServerFilesPath = join(
|
||||||
OUTPUT_DIR,
|
OUTPUT_DIR,
|
||||||
'required-server-files.json'
|
'required-server-files.json'
|
||||||
@@ -571,11 +621,14 @@ export default async function main(client: Client) {
|
|||||||
...requiredServerFilesJson,
|
...requiredServerFilesJson,
|
||||||
appDir: '.',
|
appDir: '.',
|
||||||
files: requiredServerFilesJson.files.map((i: string) => {
|
files: requiredServerFilesJson.files.map((i: string) => {
|
||||||
const absolutePath = join(cwd, i.replace('.next', '.output'));
|
const originalPath = join(dirname(distDir), i);
|
||||||
|
const relPath = join(OUTPUT_DIR, relative(distDir, originalPath));
|
||||||
|
|
||||||
|
const absolutePath = join(cwd, relPath);
|
||||||
const output = relative(baseDir, absolutePath);
|
const output = relative(baseDir, absolutePath);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
input: i.replace('.next', '.output'),
|
input: relPath,
|
||||||
output,
|
output,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
@@ -758,24 +811,37 @@ async function resolveNftToOutput({
|
|||||||
baseDir,
|
baseDir,
|
||||||
outputDir,
|
outputDir,
|
||||||
nftFileName,
|
nftFileName,
|
||||||
|
distDir,
|
||||||
nft,
|
nft,
|
||||||
}: {
|
}: {
|
||||||
client: Client;
|
client: Client;
|
||||||
baseDir: string;
|
baseDir: string;
|
||||||
outputDir: string;
|
outputDir: string;
|
||||||
nftFileName: string;
|
nftFileName: string;
|
||||||
|
distDir: string;
|
||||||
nft: NftFile;
|
nft: NftFile;
|
||||||
}) {
|
}) {
|
||||||
client.output.debug(`Processing and resolving ${nftFileName}`);
|
client.output.debug(`Processing and resolving ${nftFileName}`);
|
||||||
await fs.ensureDir(join(outputDir, 'inputs'));
|
await fs.ensureDir(join(outputDir, 'inputs'));
|
||||||
const newFilesList: NftFile['files'] = [];
|
const newFilesList: NftFile['files'] = [];
|
||||||
|
|
||||||
|
// If `distDir` is a subdirectory, then the input has to be resolved to where the `.output` directory will be.
|
||||||
|
const relNftFileName = relative(outputDir, nftFileName);
|
||||||
|
const origNftFilename = join(distDir, relNftFileName);
|
||||||
|
|
||||||
|
if (relNftFileName.startsWith('cache/')) {
|
||||||
|
// No need to process the `cache/` directory.
|
||||||
|
// Paths in it might also not be relative to `cache` itself.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (let fileEntity of nft.files) {
|
for (let fileEntity of nft.files) {
|
||||||
const relativeInput: string =
|
const relativeInput =
|
||||||
typeof fileEntity === 'string' ? fileEntity : fileEntity.input;
|
typeof fileEntity === 'string' ? fileEntity : fileEntity.input;
|
||||||
const fullInput = resolve(join(parse(nftFileName).dir, relativeInput));
|
const fullInput = resolve(join(parse(origNftFilename).dir, relativeInput));
|
||||||
|
|
||||||
// if the resolved path is NOT in the .output directory we move in it there
|
// if the resolved path is NOT in the .output directory we move in it there
|
||||||
if (!fullInput.includes(outputDir)) {
|
if (!fullInput.includes(distDir)) {
|
||||||
const { ext } = parse(fullInput);
|
const { ext } = parse(fullInput);
|
||||||
const raw = await fs.readFile(fullInput);
|
const raw = await fs.readFile(fullInput);
|
||||||
const newFilePath = join(outputDir, 'inputs', hash(raw) + ext);
|
const newFilePath = join(outputDir, 'inputs', hash(raw) + ext);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vercel/client",
|
"name": "@vercel/client",
|
||||||
"version": "10.2.3-canary.22",
|
"version": "10.2.3-canary.28",
|
||||||
"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.27",
|
||||||
"@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",
|
||||||
|
|||||||
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.16",
|
||||||
"main": "./dist/frameworks.js",
|
"main": "./dist/frameworks.js",
|
||||||
"types": "./dist/frameworks.d.ts",
|
"types": "./dist/frameworks.d.ts",
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -195,7 +195,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',
|
||||||
@@ -251,8 +251,8 @@ 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' },
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -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.12",
|
||||||
"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.27",
|
||||||
"@vercel/go": "1.2.4-canary.4"
|
"@vercel/go": "1.2.4-canary.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "vercel-plugin-node",
|
"name": "vercel-plugin-node",
|
||||||
"version": "1.12.2-canary.12",
|
"version": "1.12.2-canary.18",
|
||||||
"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,7 +34,7 @@
|
|||||||
"@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.27",
|
||||||
"@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",
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ 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 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>();
|
||||||
@@ -415,7 +408,6 @@ export async function build({
|
|||||||
}
|
}
|
||||||
|
|
||||||
await buildEntrypoint({
|
await buildEntrypoint({
|
||||||
vercelConfig,
|
|
||||||
workPath,
|
workPath,
|
||||||
entrypoint,
|
entrypoint,
|
||||||
config,
|
config,
|
||||||
@@ -425,13 +417,11 @@ 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;
|
||||||
@@ -561,7 +551,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.13",
|
||||||
"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.27",
|
||||||
"@vercel/python": "2.1.2-canary.0"
|
"@vercel/python": "2.1.2-canary.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
|
|||||||
@@ -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.11",
|
||||||
"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.27",
|
||||||
"@vercel/ruby": "1.2.8-canary.4"
|
"@vercel/ruby": "1.2.8-canary.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vercel/python",
|
"name": "@vercel/python",
|
||||||
"version": "2.1.2-canary.0",
|
"version": "2.1.2-canary.1",
|
||||||
"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",
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { relative, basename } from 'path';
|
||||||
import execa from 'execa';
|
import execa from 'execa';
|
||||||
import { Meta, debug } from '@vercel/build-utils';
|
import { Meta, debug } from '@vercel/build-utils';
|
||||||
|
|
||||||
@@ -135,6 +136,18 @@ export async function installRequirementsFile({
|
|||||||
meta,
|
meta,
|
||||||
args = [],
|
args = [],
|
||||||
}: InstallRequirementsFileArg) {
|
}: InstallRequirementsFileArg) {
|
||||||
|
const fileAtRoot = relative(workPath, filePath) === basename(filePath);
|
||||||
|
|
||||||
|
// If the `requirements.txt` file is located in the Root Directory of the project and
|
||||||
|
// the new File System API is used (`avoidTopLevelInstall`), the Install Command
|
||||||
|
// will have already installed its dependencies, so we don't need to do it again.
|
||||||
|
if (meta.avoidTopLevelInstall && fileAtRoot) {
|
||||||
|
debug(
|
||||||
|
`Skipping requirements file installation, already installed by Install Command`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
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 {
|
||||||
|
|||||||
@@ -61,6 +61,14 @@ function getRubyPath(meta: Meta, gemfileContents: string) {
|
|||||||
// process.env.GEM_HOME), and returns
|
// process.env.GEM_HOME), and returns
|
||||||
// the absolute path to it
|
// the absolute path to it
|
||||||
export async function installBundler(meta: Meta, gemfileContents: string) {
|
export async function installBundler(meta: Meta, gemfileContents: string) {
|
||||||
|
// 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`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const { gemHome, rubyPath, gemPath, vendorPath, runtime } = getRubyPath(
|
const { gemHome, rubyPath, gemPath, vendorPath, runtime } = getRubyPath(
|
||||||
meta,
|
meta,
|
||||||
gemfileContents
|
gemfileContents
|
||||||
|
|||||||
@@ -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.8-canary.5",
|
||||||
"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",
|
||||||
|
|||||||
Reference in New Issue
Block a user