mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-11 12:57:46 +00:00
Compare commits
52 Commits
vercel-plu
...
vercel-plu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5499fa9a04 | ||
|
|
b9fd64faff | ||
|
|
1202ff7b2b | ||
|
|
abd9f019f1 | ||
|
|
edb5eead81 | ||
|
|
6b865ff753 | ||
|
|
4fd0734c48 | ||
|
|
f815421acb | ||
|
|
5da926fee1 | ||
|
|
3559531e4c | ||
|
|
449a3b3648 | ||
|
|
7bd338618c | ||
|
|
9048a6f584 | ||
|
|
0cacb1bdac | ||
|
|
318bf35f82 | ||
|
|
cc7b2691c1 | ||
|
|
3a75c37adc | ||
|
|
acd0e84e73 | ||
|
|
49087d5aac | ||
|
|
7d7f3df980 | ||
|
|
5cf0c316e9 | ||
|
|
f4501433c8 | ||
|
|
19831593ce | ||
|
|
5d85bb1426 | ||
|
|
f194d54b0c | ||
|
|
6542086843 | ||
|
|
2721b3449d | ||
|
|
adb284519a | ||
|
|
b2d91f3121 | ||
|
|
32664cd13b | ||
|
|
db468c489a | ||
|
|
edd9bb506c | ||
|
|
a72549a290 | ||
|
|
4aa6a13912 | ||
|
|
81ea0082f1 | ||
|
|
6dff0875f5 | ||
|
|
30aa392c0a | ||
|
|
c4fc060030 | ||
|
|
3fa08bf64f | ||
|
|
43056bde1f | ||
|
|
a49966b9b4 | ||
|
|
7f55de71bb | ||
|
|
db8e36e04c | ||
|
|
82924bb5c4 | ||
|
|
18b5fac93e | ||
|
|
a6012e600b | ||
|
|
c3abf73f58 | ||
|
|
4873b8b379 | ||
|
|
6248139281 | ||
|
|
507a5de3cd | ||
|
|
be1c78e72f | ||
|
|
c277c649c6 |
2
.github/workflows/cancel.yml
vendored
2
.github/workflows/cancel.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 2
|
timeout-minutes: 2
|
||||||
steps:
|
steps:
|
||||||
- uses: styfle/cancel-workflow-action@0.4.1
|
- uses: styfle/cancel-workflow-action@0.9.1
|
||||||
with:
|
with:
|
||||||
workflow_id: 849295, 849296, 849297, 849298
|
workflow_id: 849295, 849296, 849297, 849298
|
||||||
access_token: ${{ github.token }}
|
access_token: ${{ github.token }}
|
||||||
|
|||||||
2
.github/workflows/test-integration-dev.yml
vendored
2
.github/workflows/test-integration-dev.yml
vendored
@@ -11,7 +11,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
name: Dev
|
name: Dev
|
||||||
timeout-minutes: 60
|
timeout-minutes: 75
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -27,3 +27,4 @@ test/lib/deployment/failed-page.txt
|
|||||||
/public
|
/public
|
||||||
__pycache__
|
__pycache__
|
||||||
.vercel
|
.vercel
|
||||||
|
.output
|
||||||
|
|||||||
@@ -332,7 +332,7 @@ This is an abstract enumeration type that is implemented by one of the following
|
|||||||
- `nodejs10.x`
|
- `nodejs10.x`
|
||||||
- `go1.x`
|
- `go1.x`
|
||||||
- `java11`
|
- `java11`
|
||||||
- `python3.8`
|
- `python3.9`
|
||||||
- `python3.6`
|
- `python3.6`
|
||||||
- `dotnetcore2.1`
|
- `dotnetcore2.1`
|
||||||
- `ruby2.5`
|
- `ruby2.5`
|
||||||
@@ -398,12 +398,12 @@ This utility allows you to _scan_ the filesystem and return a [`Files`](#files)
|
|||||||
The following trivial example downloads everything to the filesystem, only to return it back (therefore just re-creating the passed-in [`Files`](#files)):
|
The following trivial example downloads everything to the filesystem, only to return it back (therefore just re-creating the passed-in [`Files`](#files)):
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const { glob, download } = require('@vercel/build-utils')
|
const { glob, download } = require('@vercel/build-utils');
|
||||||
|
|
||||||
exports.build = ({ files, workPath }) => {
|
exports.build = ({ files, workPath }) => {
|
||||||
await download(files, workPath)
|
await download(files, workPath);
|
||||||
return glob('**', workPath)
|
return glob('**', workPath);
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### `getWritableDirectory()`
|
### `getWritableDirectory()`
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ const frameworks = (_frameworks as Framework[])
|
|||||||
sort: undefined,
|
sort: undefined,
|
||||||
dependency: undefined,
|
dependency: undefined,
|
||||||
defaultRoutes: undefined,
|
defaultRoutes: undefined,
|
||||||
devCommand: undefined,
|
|
||||||
buildCommand: undefined,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (framework.logo) {
|
if (framework.logo) {
|
||||||
|
|||||||
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
8
examples/remix/.gitignore
vendored
Normal file
8
examples/remix/.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
node_modules
|
||||||
|
|
||||||
|
.cache
|
||||||
|
.vercel
|
||||||
|
.output
|
||||||
|
|
||||||
|
public/build
|
||||||
|
api/_build
|
||||||
34
examples/remix/README.md
Normal file
34
examples/remix/README.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Welcome to Remix!
|
||||||
|
|
||||||
|
- [Remix Docs](https://remix.run/docs)
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
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
|
||||||
|
npm i -g vercel
|
||||||
|
vercel
|
||||||
|
```
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
To run your Remix app locally, make sure your project's local dependencies are installed:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
Afterwards, start the Remix development server like so:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open up [http://localhost:3000](http://localhost:3000) and you should be ready to go!
|
||||||
|
|
||||||
|
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.
|
||||||
5
examples/remix/api/index.js
Normal file
5
examples/remix/api/index.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
const { createRequestHandler } = require("@remix-run/vercel");
|
||||||
|
|
||||||
|
module.exports = createRequestHandler({
|
||||||
|
build: require("./_build")
|
||||||
|
});
|
||||||
4
examples/remix/app/entry.client.tsx
Normal file
4
examples/remix/app/entry.client.tsx
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { hydrate } from "react-dom";
|
||||||
|
import { RemixBrowser } from "remix";
|
||||||
|
|
||||||
|
hydrate(<RemixBrowser />, document);
|
||||||
21
examples/remix/app/entry.server.tsx
Normal file
21
examples/remix/app/entry.server.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { renderToString } from "react-dom/server";
|
||||||
|
import { RemixServer } from "remix";
|
||||||
|
import type { EntryContext } from "remix";
|
||||||
|
|
||||||
|
export default function handleRequest(
|
||||||
|
request: Request,
|
||||||
|
responseStatusCode: number,
|
||||||
|
responseHeaders: Headers,
|
||||||
|
remixContext: EntryContext
|
||||||
|
) {
|
||||||
|
let markup = renderToString(
|
||||||
|
<RemixServer context={remixContext} url={request.url} />
|
||||||
|
);
|
||||||
|
|
||||||
|
responseHeaders.set("Content-Type", "text/html");
|
||||||
|
|
||||||
|
return new Response("<!DOCTYPE html>" + markup, {
|
||||||
|
status: responseStatusCode,
|
||||||
|
headers: responseHeaders
|
||||||
|
});
|
||||||
|
}
|
||||||
178
examples/remix/app/root.tsx
Normal file
178
examples/remix/app/root.tsx
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
import {
|
||||||
|
Link,
|
||||||
|
Links,
|
||||||
|
LiveReload,
|
||||||
|
Meta,
|
||||||
|
Outlet,
|
||||||
|
Scripts,
|
||||||
|
ScrollRestoration,
|
||||||
|
useCatch
|
||||||
|
} from "remix";
|
||||||
|
import type { LinksFunction } from "remix";
|
||||||
|
|
||||||
|
import globalStylesUrl from "~/styles/global.css";
|
||||||
|
import darkStylesUrl from "~/styles/dark.css";
|
||||||
|
|
||||||
|
// https://remix.run/api/app#links
|
||||||
|
export let links: LinksFunction = () => {
|
||||||
|
return [
|
||||||
|
{ rel: "stylesheet", href: globalStylesUrl },
|
||||||
|
{
|
||||||
|
rel: "stylesheet",
|
||||||
|
href: darkStylesUrl,
|
||||||
|
media: "(prefers-color-scheme: dark)"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://remix.run/api/conventions#default-export
|
||||||
|
// https://remix.run/api/conventions#route-filenames
|
||||||
|
export default function App() {
|
||||||
|
return (
|
||||||
|
<Document>
|
||||||
|
<Layout>
|
||||||
|
<Outlet />
|
||||||
|
</Layout>
|
||||||
|
</Document>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://remix.run/docs/en/v1/api/conventions#errorboundary
|
||||||
|
export function ErrorBoundary({ error }: { error: Error }) {
|
||||||
|
console.error(error);
|
||||||
|
return (
|
||||||
|
<Document title="Error!">
|
||||||
|
<Layout>
|
||||||
|
<div>
|
||||||
|
<h1>There was an error</h1>
|
||||||
|
<p>{error.message}</p>
|
||||||
|
<hr />
|
||||||
|
<p>
|
||||||
|
Hey, developer, you should replace this with what you want your
|
||||||
|
users to see.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
</Document>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://remix.run/docs/en/v1/api/conventions#catchboundary
|
||||||
|
export function CatchBoundary() {
|
||||||
|
let caught = useCatch();
|
||||||
|
|
||||||
|
let message;
|
||||||
|
switch (caught.status) {
|
||||||
|
case 401:
|
||||||
|
message = (
|
||||||
|
<p>
|
||||||
|
Oops! Looks like you tried to visit a page that you do not have access
|
||||||
|
to.
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 404:
|
||||||
|
message = (
|
||||||
|
<p>Oops! Looks like you tried to visit a page that does not exist.</p>
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(caught.data || caught.statusText);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Document title={`${caught.status} ${caught.statusText}`}>
|
||||||
|
<Layout>
|
||||||
|
<h1>
|
||||||
|
{caught.status}: {caught.statusText}
|
||||||
|
</h1>
|
||||||
|
{message}
|
||||||
|
</Layout>
|
||||||
|
</Document>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Document({
|
||||||
|
children,
|
||||||
|
title
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
title?: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charSet="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
|
{title ? <title>{title}</title> : null}
|
||||||
|
<Meta />
|
||||||
|
<Links />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{children}
|
||||||
|
<ScrollRestoration />
|
||||||
|
<Scripts />
|
||||||
|
{process.env.NODE_ENV === "development" && <LiveReload />}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 659 165"
|
||||||
|
version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||||
|
aria-labelledby="remix-run-logo-title"
|
||||||
|
role="img"
|
||||||
|
width="106"
|
||||||
|
height="30"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<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="M229.43 120.576C225.59 129.536 218.422 133.376 207.158 133.376C194.614 133.376 184.374 126.72 183.35 112.64H263.478V101.12C263.478 70.1437 243.254 44.0317 205.11 44.0317C169.526 44.0317 142.902 69.8877 142.902 105.984C142.902 142.336 169.014 164.352 205.622 164.352C235.83 164.352 256.822 149.76 262.71 123.648L229.43 120.576ZM183.862 92.6717C185.398 81.9197 191.286 73.7277 204.598 73.7277C216.886 73.7277 223.542 82.4317 224.054 92.6717H183.862Z" />
|
||||||
|
<path d="M385.256 66.5597C380.392 53.2477 369.896 44.0317 349.672 44.0317C332.52 44.0317 320.232 51.7117 314.088 64.2557V47.1037H272.616V161.28H314.088V105.216C314.088 88.0638 318.952 76.7997 332.52 76.7997C345.064 76.7997 348.136 84.9917 348.136 100.608V161.28H389.608V105.216C389.608 88.0638 394.216 76.7997 408.04 76.7997C420.584 76.7997 423.4 84.9917 423.4 100.608V161.28H464.872V89.5997C464.872 65.7917 455.656 44.0317 424.168 44.0317C404.968 44.0317 391.4 53.7597 385.256 66.5597Z" />
|
||||||
|
<path d="M478.436 47.104V161.28H519.908V47.104H478.436ZM478.18 36.352H520.164V0H478.18V36.352Z" />
|
||||||
|
<path d="M654.54 47.1035H611.788L592.332 74.2395L573.388 47.1035H527.564L568.78 103.168L523.98 161.28H566.732L589.516 130.304L612.3 161.28H658.124L613.068 101.376L654.54 47.1035Z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
44
examples/remix/app/routes/demos/about.tsx
Normal file
44
examples/remix/app/routes/demos/about.tsx
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { Outlet } from "remix";
|
||||||
|
import type { MetaFunction, LinksFunction } from "remix";
|
||||||
|
|
||||||
|
import stylesUrl from "~/styles/demos/about.css";
|
||||||
|
|
||||||
|
export let meta: MetaFunction = () => {
|
||||||
|
return {
|
||||||
|
title: "About Remix"
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export let links: LinksFunction = () => {
|
||||||
|
return [{ rel: "stylesheet", href: stylesUrl }];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Index() {
|
||||||
|
return (
|
||||||
|
<div className="about">
|
||||||
|
<div className="about__intro">
|
||||||
|
<h2>About Us</h2>
|
||||||
|
<p>
|
||||||
|
Ok, so this page isn't really <em>about us</em>, but we did want to
|
||||||
|
show you a few more things Remix can do.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Did you notice that things look a little different on this page? The
|
||||||
|
CSS that we import in the route file and include in its{" "}
|
||||||
|
<code>links</code> export is only included on this route and its
|
||||||
|
children.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Wait a sec...<em>its children</em>? To understand what we mean by
|
||||||
|
this,{" "}
|
||||||
|
<a href="https://remix.run/tutorial/4-nested-routes-params">
|
||||||
|
read all about nested routes in the docs
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
<hr />
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
17
examples/remix/app/routes/demos/about/index.tsx
Normal file
17
examples/remix/app/routes/demos/about/index.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { Link } from "remix";
|
||||||
|
|
||||||
|
export default function AboutIndex() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
You are looking at the index route for the <code>/about</code> URL
|
||||||
|
segment, but there are nested routes as well!
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>
|
||||||
|
<Link to="whoa">Check out one of them here.</Link>
|
||||||
|
</strong>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
20
examples/remix/app/routes/demos/about/whoa.tsx
Normal file
20
examples/remix/app/routes/demos/about/whoa.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { Link } from "remix";
|
||||||
|
|
||||||
|
export default function AboutIndex() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Whoa, this is a nested route! We render the <code>/about</code> layout
|
||||||
|
route component, and its <code>Outlet</code> renders our route
|
||||||
|
component. 🤯
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>
|
||||||
|
<Link to="..">
|
||||||
|
Go back to the <code>/about</code> index.
|
||||||
|
</Link>
|
||||||
|
</strong>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
101
examples/remix/app/routes/demos/actions.tsx
Normal file
101
examples/remix/app/routes/demos/actions.tsx
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import { useEffect, useRef } from "react";
|
||||||
|
import type { ActionFunction } from "remix";
|
||||||
|
import { Form, json, useActionData, redirect } from "remix";
|
||||||
|
|
||||||
|
export function meta() {
|
||||||
|
return { title: "Actions Demo" };
|
||||||
|
}
|
||||||
|
|
||||||
|
// When your form sends a POST, the action is called on the server.
|
||||||
|
// - https://remix.run/api/conventions#action
|
||||||
|
// - https://remix.run/guides/data-updates
|
||||||
|
export let action: ActionFunction = async ({ request }) => {
|
||||||
|
let formData = await request.formData();
|
||||||
|
let answer = formData.get("answer");
|
||||||
|
|
||||||
|
// Typical action workflows start with validating the form data that just came
|
||||||
|
// over the network. Clientside validation is fine, but you definitely need it
|
||||||
|
// server side. If there's a problem, return the the data and the component
|
||||||
|
// can render it.
|
||||||
|
if (typeof answer !== "string") {
|
||||||
|
return json("Come on, at least try!", { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (answer !== "egg") {
|
||||||
|
return json(`Sorry, ${answer} is not right.`, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, if the data is valid, you'll typically write to a database or send or
|
||||||
|
// email or log the user in, etc. It's recommended to redirect after a
|
||||||
|
// successful action, even if it's to the same place so that non-JavaScript workflows
|
||||||
|
// from the browser doesn't repost the data if the user clicks back.
|
||||||
|
return redirect("/demos/correct");
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ActionsDemo() {
|
||||||
|
// https://remix.run/api/remix#useactiondata
|
||||||
|
let actionMessage = useActionData<string>();
|
||||||
|
let answerRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
// This form works without JavaScript, but when we have JavaScript we can make
|
||||||
|
// the experience better by selecting the input on wrong answers! Go ahead, disable
|
||||||
|
// JavaScript in your browser and see what happens.
|
||||||
|
useEffect(() => {
|
||||||
|
if (actionMessage && answerRef.current) {
|
||||||
|
answerRef.current.select();
|
||||||
|
}
|
||||||
|
}, [actionMessage]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="remix__page">
|
||||||
|
<main>
|
||||||
|
<h2>Actions!</h2>
|
||||||
|
<p>
|
||||||
|
This form submission will send a post request that we handle in our
|
||||||
|
`action` export. Any route can export an action to handle data
|
||||||
|
mutations.
|
||||||
|
</p>
|
||||||
|
<Form method="post" className="remix__form">
|
||||||
|
<h3>Post an Action</h3>
|
||||||
|
<p>
|
||||||
|
<i>What is more useful when it is broken?</i>
|
||||||
|
</p>
|
||||||
|
<label>
|
||||||
|
<div>Answer:</div>
|
||||||
|
<input ref={answerRef} name="answer" type="text" />
|
||||||
|
</label>
|
||||||
|
<div>
|
||||||
|
<button>Answer!</button>
|
||||||
|
</div>
|
||||||
|
{actionMessage ? (
|
||||||
|
<p>
|
||||||
|
<b>{actionMessage}</b>
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
</Form>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<aside>
|
||||||
|
<h3>Additional Resources</h3>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Guide:{" "}
|
||||||
|
<a href="https://remix.run/guides/data-writes">Data Writes</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
API:{" "}
|
||||||
|
<a href="https://remix.run/api/conventions#action">
|
||||||
|
Route Action Export
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
API:{" "}
|
||||||
|
<a href="https://remix.run/api/remix#useactiondata">
|
||||||
|
<code>useActionData</code>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
3
examples/remix/app/routes/demos/correct.tsx
Normal file
3
examples/remix/app/routes/demos/correct.tsx
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export default function NiceWork() {
|
||||||
|
return <h1>You got it right!</h1>;
|
||||||
|
}
|
||||||
43
examples/remix/app/routes/demos/params.tsx
Normal file
43
examples/remix/app/routes/demos/params.tsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { useCatch, Link, json, useLoaderData, Outlet } from "remix";
|
||||||
|
|
||||||
|
export function meta() {
|
||||||
|
return { title: "Boundaries Demo" };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Boundaries() {
|
||||||
|
return (
|
||||||
|
<div className="remix__page">
|
||||||
|
<main>
|
||||||
|
<Outlet />
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<aside>
|
||||||
|
<h2>Click these Links</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<Link to=".">Start over</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link to="one">
|
||||||
|
Param: <i>one</i>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link to="two">
|
||||||
|
Param: <i>two</i>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link to="this-record-does-not-exist">This will be a 404</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link to="shh-its-a-secret">And this will be 401 Unauthorized</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link to="kaboom">This one will throw an error</Link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
110
examples/remix/app/routes/demos/params/$id.tsx
Normal file
110
examples/remix/app/routes/demos/params/$id.tsx
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
import { useCatch, Link, json, useLoaderData } from "remix";
|
||||||
|
import type { LoaderFunction, MetaFunction } from "remix";
|
||||||
|
|
||||||
|
// The `$` in route filenames becomes a pattern that's parsed from the URL and
|
||||||
|
// passed to your loaders so you can look up data.
|
||||||
|
// - https://remix.run/api/conventions#loader-params
|
||||||
|
export let loader: LoaderFunction = async ({ params }) => {
|
||||||
|
// pretend like we're using params.id to look something up in the db
|
||||||
|
|
||||||
|
if (params.id === "this-record-does-not-exist") {
|
||||||
|
// If the record doesn't exist we can't render the route normally, so
|
||||||
|
// instead we throw a 404 reponse to stop running code here and show the
|
||||||
|
// user the catch boundary.
|
||||||
|
throw new Response("Not Found", { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// now pretend like the record exists but the user just isn't authorized to
|
||||||
|
// see it.
|
||||||
|
if (params.id === "shh-its-a-secret") {
|
||||||
|
// Again, we can't render the component if the user isn't authorized. You
|
||||||
|
// can even put data in the response that might help the user rectify the
|
||||||
|
// issue! Like emailing the webmaster for access to the page. (Oh, right,
|
||||||
|
// `json` is just a Response helper that makes it easier to send JSON
|
||||||
|
// responses).
|
||||||
|
throw json({ webmasterEmail: "hello@remix.run" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sometimes your code just blows up and you never anticipated it. Remix will
|
||||||
|
// automatically catch it and send the UI to the error boundary.
|
||||||
|
if (params.id === "kaboom") {
|
||||||
|
lol();
|
||||||
|
}
|
||||||
|
|
||||||
|
// but otherwise the record was found, user has access, so we can do whatever
|
||||||
|
// else we needed to in the loader and return the data. (This is boring, we're
|
||||||
|
// just gonna return the params.id).
|
||||||
|
return { param: params.id };
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ParamDemo() {
|
||||||
|
let data = useLoaderData();
|
||||||
|
return (
|
||||||
|
<h1>
|
||||||
|
The param is <i style={{ color: "red" }}>{data.param}</i>
|
||||||
|
</h1>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://remix.run/api/conventions#catchboundary
|
||||||
|
// https://remix.run/api/remix#usecatch
|
||||||
|
// https://remix.run/api/guides/not-found
|
||||||
|
export function CatchBoundary() {
|
||||||
|
let caught = useCatch();
|
||||||
|
|
||||||
|
let message: React.ReactNode;
|
||||||
|
switch (caught.status) {
|
||||||
|
case 401:
|
||||||
|
message = (
|
||||||
|
<p>
|
||||||
|
Looks like you tried to visit a page that you do not have access to.
|
||||||
|
Maybe ask the webmaster ({caught.data.webmasterEmail}) for access.
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
case 404:
|
||||||
|
message = (
|
||||||
|
<p>Looks like you tried to visit a page that does not exist.</p>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
message = (
|
||||||
|
<p>
|
||||||
|
There was a problem with your request!
|
||||||
|
<br />
|
||||||
|
{caught.status} {caught.statusText}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h2>Oops!</h2>
|
||||||
|
<p>{message}</p>
|
||||||
|
<p>
|
||||||
|
(Isn't it cool that the user gets to stay in context and try a different
|
||||||
|
link in the parts of the UI that didn't blow up?)
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://remix.run/api/conventions#errorboundary
|
||||||
|
// https://remix.run/api/guides/not-found
|
||||||
|
export function ErrorBoundary({ error }: { error: Error }) {
|
||||||
|
console.error(error);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h2>Error!</h2>
|
||||||
|
<p>{error.message}</p>
|
||||||
|
<p>
|
||||||
|
(Isn't it cool that the user gets to stay in context and try a different
|
||||||
|
link in the parts of the UI that didn't blow up?)
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export let meta: MetaFunction = ({ data }) => {
|
||||||
|
return {
|
||||||
|
title: data ? `Param: ${data.param}` : "Oops...",
|
||||||
|
};
|
||||||
|
};
|
||||||
40
examples/remix/app/routes/demos/params/index.tsx
Normal file
40
examples/remix/app/routes/demos/params/index.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { useCatch, Link, json, useLoaderData, Outlet } from "remix";
|
||||||
|
import type { LoaderFunction } from "remix";
|
||||||
|
|
||||||
|
export default function Boundaries() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h2>Params</h2>
|
||||||
|
<p>
|
||||||
|
When you name a route segment with $ like{" "}
|
||||||
|
<code>routes/users/$userId.js</code>, the $ segment will be parsed from
|
||||||
|
the URL and sent to your loaders and actions by the same name.
|
||||||
|
</p>
|
||||||
|
<h2>Errors</h2>
|
||||||
|
<p>
|
||||||
|
When a route throws and error in it's action, loader, or component,
|
||||||
|
Remix automatically catches it, won't even try to render the component,
|
||||||
|
but it will render the route's ErrorBoundary instead. If the route
|
||||||
|
doesn't have one, it will bubble up to the routes above it until it hits
|
||||||
|
the root.
|
||||||
|
</p>
|
||||||
|
<p>So be as granular as you want with your error handling.</p>
|
||||||
|
<h2>Not Found</h2>
|
||||||
|
<p>
|
||||||
|
(and other{" "}
|
||||||
|
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses">
|
||||||
|
client errors
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Loaders and Actions can throw a <code>Response</code> instead of an
|
||||||
|
error and Remix will render the CatchBoundary instead of the component.
|
||||||
|
This is great when loading data from a database isn't found. As soon as
|
||||||
|
you know you can't render the component normally, throw a 404 response
|
||||||
|
and send your app into the catch boundary. Just like error boundaries,
|
||||||
|
catch boundaries bubble, too.
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
100
examples/remix/app/routes/index.tsx
Normal file
100
examples/remix/app/routes/index.tsx
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import type { MetaFunction, LoaderFunction } from "remix";
|
||||||
|
import { useLoaderData, json, Link } from "remix";
|
||||||
|
|
||||||
|
type IndexData = {
|
||||||
|
resources: Array<{ name: string; url: string }>;
|
||||||
|
demos: Array<{ name: string; to: string }>;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Loaders provide data to components and are only ever called on the server, so
|
||||||
|
// you can connect to a database or run any server side code you want right next
|
||||||
|
// to the component that renders it.
|
||||||
|
// https://remix.run/api/conventions#loader
|
||||||
|
export let loader: LoaderFunction = () => {
|
||||||
|
let data: IndexData = {
|
||||||
|
resources: [
|
||||||
|
{
|
||||||
|
name: "Remix Docs",
|
||||||
|
url: "https://remix.run/docs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "React Router Docs",
|
||||||
|
url: "https://reactrouter.com/docs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Remix Discord",
|
||||||
|
url: "https://discord.gg/VBePs6d"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
demos: [
|
||||||
|
{
|
||||||
|
to: "demos/actions",
|
||||||
|
name: "Actions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
to: "demos/about",
|
||||||
|
name: "Nested Routes, CSS loading/unloading"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
to: "demos/params",
|
||||||
|
name: "URL Params and Error Boundaries"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://remix.run/api/remix#json
|
||||||
|
return json(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://remix.run/api/conventions#meta
|
||||||
|
export let meta: MetaFunction = () => {
|
||||||
|
return {
|
||||||
|
title: "Remix Starter",
|
||||||
|
description: "Welcome to remix!"
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://remix.run/guides/routing#index-routes
|
||||||
|
export default function Index() {
|
||||||
|
let data = useLoaderData<IndexData>();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="remix__page">
|
||||||
|
<main>
|
||||||
|
<h2>Welcome to Remix!</h2>
|
||||||
|
<p>We're stoked that you're here. 🥳</p>
|
||||||
|
<p>
|
||||||
|
Feel free to take a look around the code to see how Remix does things,
|
||||||
|
it might be a bit different than what you’re used to. When you're
|
||||||
|
ready to dive deeper, we've got plenty of resources to get you
|
||||||
|
up-and-running quickly.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Check out all the demos in this starter, and then just delete the{" "}
|
||||||
|
<code>app/routes/demos</code> and <code>app/styles/demos</code>{" "}
|
||||||
|
folders when you're ready to turn this into your next project.
|
||||||
|
</p>
|
||||||
|
</main>
|
||||||
|
<aside>
|
||||||
|
<h2>Demos In This App</h2>
|
||||||
|
<ul>
|
||||||
|
{data.demos.map(demo => (
|
||||||
|
<li key={demo.to} className="remix__page__resource">
|
||||||
|
<Link to={demo.to} prefetch="intent">
|
||||||
|
{demo.name}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<h2>Resources</h2>
|
||||||
|
<ul>
|
||||||
|
{data.resources.map(resource => (
|
||||||
|
<li key={resource.url} className="remix__page__resource">
|
||||||
|
<a href={resource.url}>{resource.name}</a>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
7
examples/remix/app/styles/dark.css
Normal file
7
examples/remix/app/styles/dark.css
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
:root {
|
||||||
|
--color-foreground: hsl(0, 0%, 100%);
|
||||||
|
--color-background: hsl(0, 0%, 7%);
|
||||||
|
--color-links: hsl(213, 100%, 73%);
|
||||||
|
--color-links-hover: hsl(213, 100%, 80%);
|
||||||
|
--color-border: hsl(0, 0%, 25%);
|
||||||
|
}
|
||||||
26
examples/remix/app/styles/demos/about.css
Normal file
26
examples/remix/app/styles/demos/about.css
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Whoa whoa whoa, wait a sec...why are we overriding global CSS selectors?
|
||||||
|
* Isn't that kind of scary? How do we know this won't have side effects?
|
||||||
|
*
|
||||||
|
* In Remix, CSS that is included in a route file will *only* show up on that
|
||||||
|
* route (and for nested routes, its children). When the user navigates away
|
||||||
|
* from that route the CSS files linked from those routes will be automatically
|
||||||
|
* unloaded, making your styles much easier to predict and control.
|
||||||
|
*
|
||||||
|
* Read more about styling routes in the docs:
|
||||||
|
* https://remix.run/guides/styling
|
||||||
|
*/
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--color-foreground: hsl(0, 0%, 7%);
|
||||||
|
--color-background: hsl(56, 100%, 50%);
|
||||||
|
--color-links: hsl(345, 56%, 39%);
|
||||||
|
--color-links-hover: hsl(345, 51%, 49%);
|
||||||
|
--color-border: rgb(184, 173, 20);
|
||||||
|
--font-body: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
||||||
|
Liberation Mono, Courier New, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about__intro {
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
216
examples/remix/app/styles/global.css
Normal file
216
examples/remix/app/styles/global.css
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
/*
|
||||||
|
* You can just delete everything here or keep whatever you like, it's just a
|
||||||
|
* quick baseline!
|
||||||
|
*/
|
||||||
|
:root {
|
||||||
|
--color-foreground: hsl(0, 0%, 7%);
|
||||||
|
--color-background: hsl(0, 0%, 100%);
|
||||||
|
--color-links: hsl(213, 100%, 52%);
|
||||||
|
--color-links-hover: hsl(213, 100%, 43%);
|
||||||
|
--color-border: hsl(0, 0%, 82%);
|
||||||
|
--font-body: -apple-system, "Segoe UI", Helvetica Neue, Helvetica, Roboto,
|
||||||
|
Arial, sans-serif, system-ui, "Apple Color Emoji", "Segoe UI Emoji";
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
:-moz-focusring {
|
||||||
|
outline: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
:focus {
|
||||||
|
outline: var(--color-links) solid 2px;
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
background-color: var(--color-background);
|
||||||
|
color: var(--color-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: var(--font-body);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--color-links);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: var(--color-links-hover);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
display: block;
|
||||||
|
height: 1px;
|
||||||
|
border: 0;
|
||||||
|
background-color: var(--color-border);
|
||||||
|
margin-top: 2rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:where([type="text"]),
|
||||||
|
input:where([type="search"]) {
|
||||||
|
display: block;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
width: 100%;
|
||||||
|
font: inherit;
|
||||||
|
line-height: 1;
|
||||||
|
height: calc(1ch + 1.5em);
|
||||||
|
padding-right: 0.5em;
|
||||||
|
padding-left: 0.5em;
|
||||||
|
background-color: hsl(0 0% 100% / 20%);
|
||||||
|
color: var(--color-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sr-only {
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
padding: 0;
|
||||||
|
margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
clip: rect(0, 0, 0, 0);
|
||||||
|
white-space: nowrap;
|
||||||
|
border-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
--gutter: 16px;
|
||||||
|
width: 1024px;
|
||||||
|
max-width: calc(100% - var(--gutter) * 2);
|
||||||
|
margin-right: 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
29
examples/remix/package.json
Normal file
29
examples/remix/package.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"private": true,
|
||||||
|
"name": "remix-app-template",
|
||||||
|
"description": "",
|
||||||
|
"license": "",
|
||||||
|
"scripts": {
|
||||||
|
"build": "remix build",
|
||||||
|
"dev": "remix dev",
|
||||||
|
"postinstall": "remix setup node"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@remix-run/react": "^1.0.6",
|
||||||
|
"react": "^17.0.2",
|
||||||
|
"react-dom": "^17.0.2",
|
||||||
|
"remix": "^1.0.6",
|
||||||
|
"@remix-run/serve": "^1.0.6",
|
||||||
|
"@remix-run/vercel": "^1.0.6"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@remix-run/dev": "^1.0.6",
|
||||||
|
"@types/react": "^17.0.24",
|
||||||
|
"@types/react-dom": "^17.0.9",
|
||||||
|
"typescript": "^4.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"sideEffects": false
|
||||||
|
}
|
||||||
BIN
examples/remix/public/favicon.ico
Normal file
BIN
examples/remix/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
9
examples/remix/remix.config.js
Normal file
9
examples/remix/remix.config.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* @type {import('@remix-run/dev/config').AppConfig}
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
appDirectory: "app",
|
||||||
|
browserBuildDirectory: "public/build",
|
||||||
|
publicPath: "/build/",
|
||||||
|
serverBuildDirectory: "api/_build"
|
||||||
|
};
|
||||||
2
examples/remix/remix.env.d.ts
vendored
Normal file
2
examples/remix/remix.env.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/// <reference types="@remix-run/dev" />
|
||||||
|
/// <reference types="@remix-run/node/globals" />
|
||||||
18
examples/remix/tsconfig.json
Normal file
18
examples/remix/tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["DOM", "DOM.Iterable", "ES2019"],
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"target": "ES2019",
|
||||||
|
"strict": true,
|
||||||
|
"paths": {
|
||||||
|
"~/*": ["./app/*"]
|
||||||
|
},
|
||||||
|
|
||||||
|
// Remix takes care of building everything in `remix build`.
|
||||||
|
"noEmit": true
|
||||||
|
}
|
||||||
|
}
|
||||||
7
examples/remix/vercel.json
Normal file
7
examples/remix/vercel.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"build": {
|
||||||
|
"env": {
|
||||||
|
"ENABLE_FILE_SYSTEM_API": "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vercel/build-utils",
|
"name": "@vercel/build-utils",
|
||||||
"version": "2.12.3-canary.17",
|
"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.11",
|
"@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",
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ import glob from './fs/glob';
|
|||||||
import { normalizePath } from './fs/normalize-path';
|
import { normalizePath } from './fs/normalize-path';
|
||||||
import { FILES_SYMBOL, 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.
|
||||||
@@ -16,42 +15,36 @@ export function convertRuntimeToPlugin(
|
|||||||
buildRuntime: (options: BuildOptions) => Promise<{ output: Lambda }>,
|
buildRuntime: (options: BuildOptions) => Promise<{ output: Lambda }>,
|
||||||
ext: string
|
ext: string
|
||||||
) {
|
) {
|
||||||
|
// This `build()` signature should match `plugin.build()` signature in `vercel build`.
|
||||||
return async function build({ workPath }: { workPath: string }) {
|
return async function build({ workPath }: { 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 functionsManifest: { [key: string]: any } = {};
|
const pages: { [key: string]: any } = {};
|
||||||
const functions = await readVercelConfigFunctions(workPath);
|
|
||||||
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,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
functionsManifest[entrypoint] = {
|
pages[entrypoint] = {
|
||||||
handler: output.handler,
|
handler: output.handler,
|
||||||
runtime: output.runtime,
|
runtime: output.runtime,
|
||||||
memory: config.memory || output.memory,
|
memory: output.memory,
|
||||||
maxDuration: config.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
|
||||||
@@ -98,10 +91,7 @@ export function convertRuntimeToPlugin(
|
|||||||
await fs.writeFile(nft, json);
|
await fs.writeFile(nft, json);
|
||||||
}
|
}
|
||||||
|
|
||||||
await fs.writeFile(
|
await updateFunctionsManifest({ workPath, pages });
|
||||||
join(workPath, '.output', 'functions-manifest.json'),
|
|
||||||
JSON.stringify(functionsManifest)
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,14 +105,10 @@ async function linkOrCopy(existingPath: string, newPath: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readVercelConfigFunctions(
|
async function readJson(filePath: string): Promise<{ [key: string]: any }> {
|
||||||
workPath: string
|
|
||||||
): Promise<BuilderFunctions> {
|
|
||||||
const vercelJsonPath = join(workPath, 'vercel.json');
|
|
||||||
try {
|
try {
|
||||||
const str = await fs.readFile(vercelJsonPath, 'utf8');
|
const str = await fs.readFile(filePath, 'utf8');
|
||||||
const obj = JSON.parse(str);
|
return JSON.parse(str);
|
||||||
return obj.functions || {};
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.code === 'ENOENT') {
|
if (err.code === 'ENOENT') {
|
||||||
return {};
|
return {};
|
||||||
@@ -130,3 +116,111 @@ async function readVercelConfigFunctions(
|
|||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If `.output/functions-manifest.json` exists, append to the pages
|
||||||
|
* property. Otherwise write a new file.
|
||||||
|
*/
|
||||||
|
export async function updateFunctionsManifest({
|
||||||
|
workPath,
|
||||||
|
pages,
|
||||||
|
}: {
|
||||||
|
workPath: string;
|
||||||
|
pages: { [key: string]: any };
|
||||||
|
}) {
|
||||||
|
const functionsManifestPath = join(
|
||||||
|
workPath,
|
||||||
|
'.output',
|
||||||
|
'functions-manifest.json'
|
||||||
|
);
|
||||||
|
const functionsManifest = await readJson(functionsManifestPath);
|
||||||
|
|
||||||
|
if (!functionsManifest.version) functionsManifest.version = 1;
|
||||||
|
if (!functionsManifest.pages) functionsManifest.pages = {};
|
||||||
|
|
||||||
|
for (const [pageKey, pageConfig] of Object.entries(pages)) {
|
||||||
|
functionsManifest.pages[pageKey] = { ...pageConfig };
|
||||||
|
}
|
||||||
|
|
||||||
|
await fs.writeFile(functionsManifestPath, JSON.stringify(functionsManifest));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append routes to the `routes-manifest.json` file.
|
||||||
|
* If the file does not exist, it will be created.
|
||||||
|
*/
|
||||||
|
export async function updateRoutesManifest({
|
||||||
|
workPath,
|
||||||
|
redirects,
|
||||||
|
rewrites,
|
||||||
|
headers,
|
||||||
|
dynamicRoutes,
|
||||||
|
staticRoutes,
|
||||||
|
}: {
|
||||||
|
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?: {
|
||||||
|
page: string;
|
||||||
|
regex: string;
|
||||||
|
namedRegex?: 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 routesManifest = await readJson(routesManifestPath);
|
||||||
|
|
||||||
|
if (!routesManifest.version) routesManifest.version = 3;
|
||||||
|
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 (!routesManifest.dynamicRoutes) routesManifest.dynamicRoutes = [];
|
||||||
|
routesManifest.dynamicRoutes.push(...dynamicRoutes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (staticRoutes) {
|
||||||
|
if (!routesManifest.staticRoutes) routesManifest.staticRoutes = [];
|
||||||
|
routesManifest.staticRoutes.push(...staticRoutes);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,11 @@ export { detectFramework } from './detect-framework';
|
|||||||
export { DetectorFilesystem } from './detectors/filesystem';
|
export { DetectorFilesystem } from './detectors/filesystem';
|
||||||
export { readConfigFile } from './fs/read-config-file';
|
export { readConfigFile } from './fs/read-config-file';
|
||||||
export { normalizePath } from './fs/normalize-path';
|
export { normalizePath } from './fs/normalize-path';
|
||||||
export { convertRuntimeToPlugin } from './convert-runtime-to-plugin';
|
export {
|
||||||
|
convertRuntimeToPlugin,
|
||||||
|
updateFunctionsManifest,
|
||||||
|
updateRoutesManifest,
|
||||||
|
} from './convert-runtime-to-plugin';
|
||||||
|
|
||||||
export * from './schemas';
|
export * from './schemas';
|
||||||
export * from './types';
|
export * from './types';
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ interface CreateLambdaOptions {
|
|||||||
|
|
||||||
interface GetLambdaOptionsFromFunctionOptions {
|
interface GetLambdaOptionsFromFunctionOptions {
|
||||||
sourceFile: string;
|
sourceFile: string;
|
||||||
config?: Config;
|
config?: Pick<Config, 'functions'>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FILES_SYMBOL = Symbol('files');
|
export const FILES_SYMBOL = Symbol('files');
|
||||||
|
|||||||
@@ -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) => {
|
||||||
@@ -71,19 +77,31 @@ 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({
|
||||||
'api/index.py': lambdaOptions,
|
version: 1,
|
||||||
'api/users/get.py': lambdaOptions,
|
pages: {
|
||||||
'api/users/post.py': { ...lambdaOptions, memory: 3008 },
|
'api/index.py': lambdaOptions,
|
||||||
|
'api/users/get.py': lambdaOptions,
|
||||||
|
'api/users/post.py': { ...lambdaOptions, 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: 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',
|
||||||
@@ -113,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',
|
||||||
@@ -146,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.28",
|
"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.17",
|
"@vercel/build-utils": "2.12.3-canary.27",
|
||||||
"@vercel/go": "1.2.4-canary.3",
|
"@vercel/go": "1.2.4-canary.4",
|
||||||
"@vercel/node": "1.12.2-canary.6",
|
"@vercel/node": "1.12.2-canary.7",
|
||||||
"@vercel/python": "2.0.6-canary.5",
|
"@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.6",
|
"vercel-plugin-middleware": "0.0.0-canary.7",
|
||||||
"vercel-plugin-node": "1.12.2-canary.7"
|
"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.11",
|
"@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,9 +14,10 @@ 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 { emoji, prependEmoji } from '../util/emoji';
|
import { emoji, prependEmoji } from '../util/emoji';
|
||||||
import getArgs from '../util/get-args';
|
import getArgs from '../util/get-args';
|
||||||
import handleError from '../util/handle-error';
|
import handleError from '../util/handle-error';
|
||||||
@@ -30,10 +31,7 @@ import { getCommandName, getPkgName } from '../util/pkg-name';
|
|||||||
import { loadCliPlugins } from '../util/plugins';
|
import { loadCliPlugins } from '../util/plugins';
|
||||||
import { findFramework } from '../util/projects/find-framework';
|
import { findFramework } from '../util/projects/find-framework';
|
||||||
import { VERCEL_DIR } from '../util/projects/link';
|
import { VERCEL_DIR } from '../util/projects/link';
|
||||||
import {
|
import { readProjectSettings } from '../util/projects/project-settings';
|
||||||
ProjectLinkAndSettings,
|
|
||||||
readProjectSettings,
|
|
||||||
} from '../util/projects/project-settings';
|
|
||||||
import pull from './pull';
|
import pull from './pull';
|
||||||
|
|
||||||
const sema = new Sema(16, {
|
const sema = new Sema(16, {
|
||||||
@@ -68,16 +66,23 @@ const help = () => {
|
|||||||
|
|
||||||
const OUTPUT_DIR = '.output';
|
const OUTPUT_DIR = '.output';
|
||||||
|
|
||||||
const fields: {
|
|
||||||
name: string;
|
|
||||||
value: keyof ProjectLinkAndSettings['settings'];
|
|
||||||
}[] = [
|
|
||||||
{ name: 'Build Command', value: 'buildCommand' },
|
|
||||||
{ name: 'Output Directory', value: 'outputDirectory' },
|
|
||||||
{ name: 'Root Directory', value: 'rootDirectory' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export default async function main(client: Client) {
|
export default async function main(client: Client) {
|
||||||
|
if (process.env.__VERCEL_BUILD_RUNNING) {
|
||||||
|
client.output.error(
|
||||||
|
`${cmd(
|
||||||
|
`${getPkgName()} build`
|
||||||
|
)} must not recursively invoke itself. Check the Build Command in the Project Settings or the ${cmd(
|
||||||
|
'build'
|
||||||
|
)} script in ${cmd('package.json')}`
|
||||||
|
);
|
||||||
|
client.output.error(
|
||||||
|
`Learn More: https://vercel.link/recursive-invocation-of-commands`
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
process.env.__VERCEL_BUILD_RUNNING = '1';
|
||||||
|
}
|
||||||
|
|
||||||
let argv;
|
let argv;
|
||||||
const buildStamp = stamp();
|
const buildStamp = stamp();
|
||||||
try {
|
try {
|
||||||
@@ -131,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' },
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -152,47 +159,57 @@ export default async function main(client: Client) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const buildState = { ...project.settings };
|
const buildState = { ...project.settings };
|
||||||
|
const formatSetting = (
|
||||||
client.output.log(`Retrieved Project Settings:`);
|
name: string,
|
||||||
client.output.print(
|
override: string | null | undefined,
|
||||||
chalk.dim(` - ${chalk.bold(`Framework Preset:`)} ${framework.name}\n`)
|
defaults: typeof framework.settings.outputDirectory
|
||||||
|
) =>
|
||||||
|
` - ${chalk.bold(`${name}:`)} ${`${
|
||||||
|
override
|
||||||
|
? override + ` (override)`
|
||||||
|
: 'placeholder' in defaults
|
||||||
|
? chalk.italic(`${defaults.placeholder}`)
|
||||||
|
: defaults.value
|
||||||
|
}`}`;
|
||||||
|
console.log(`Retrieved Project Settings:`);
|
||||||
|
console.log(
|
||||||
|
chalk.dim(` - ${chalk.bold(`Framework Preset:`)} ${framework.name}`)
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
chalk.dim(
|
||||||
|
formatSetting(
|
||||||
|
'Build Command',
|
||||||
|
project.settings.buildCommand,
|
||||||
|
framework.settings.buildCommand
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
chalk.dim(
|
||||||
|
formatSetting(
|
||||||
|
'Output Directory',
|
||||||
|
project.settings.outputDirectory,
|
||||||
|
framework.settings.outputDirectory
|
||||||
|
)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
for (let field of fields) {
|
buildState.outputDirectory =
|
||||||
const defaults = (framework.settings as any)[field.value];
|
project.settings.outputDirectory ||
|
||||||
if (defaults) {
|
(isSettingValue(framework.settings.outputDirectory)
|
||||||
client.output.print(
|
? framework.settings.outputDirectory.value
|
||||||
chalk.dim(
|
: null);
|
||||||
` - ${chalk.bold(`${field.name}:`)} ${`${
|
buildState.rootDirectory = project.settings.rootDirectory;
|
||||||
project.settings[field.value]
|
|
||||||
? project.settings[field.value] + ` (override)`
|
|
||||||
: isSettingValue(defaults)
|
|
||||||
? defaults.value
|
|
||||||
: chalk.italic(`${defaults.placeholder}`)
|
|
||||||
}`}\n`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (field.value != 'buildCommand') {
|
|
||||||
(buildState as any)[field.value] = project.settings[field.value]
|
|
||||||
? project.settings[field.value]
|
|
||||||
: defaults
|
|
||||||
? isSettingValue(defaults)
|
|
||||||
? defaults.value
|
|
||||||
: null
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loadedEnvFiles.length > 0) {
|
if (loadedEnvFiles.length > 0) {
|
||||||
client.output.log(
|
console.log(
|
||||||
`Loaded Environment Variables from ${loadedEnvFiles.length} ${pluralize(
|
`Loaded Environment Variables from ${loadedEnvFiles.length} ${pluralize(
|
||||||
'file',
|
'file',
|
||||||
loadedEnvFiles.length
|
loadedEnvFiles.length
|
||||||
)}:`
|
)}:`
|
||||||
);
|
);
|
||||||
for (let envFile of loadedEnvFiles) {
|
for (let envFile of loadedEnvFiles) {
|
||||||
client.output.print(chalk.dim(` - ${envFile.path}\n`));
|
console.log(chalk.dim(` - ${envFile.path}`));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,7 +240,7 @@ export default async function main(client: Client) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (plugins?.pluginCount && plugins?.pluginCount > 0) {
|
if (plugins?.pluginCount && plugins?.pluginCount > 0) {
|
||||||
client.output.log(
|
console.log(
|
||||||
`Loaded ${plugins.pluginCount} CLI ${pluralize(
|
`Loaded ${plugins.pluginCount} CLI ${pluralize(
|
||||||
'Plugin',
|
'Plugin',
|
||||||
plugins.pluginCount
|
plugins.pluginCount
|
||||||
@@ -231,7 +248,7 @@ export default async function main(client: Client) {
|
|||||||
);
|
);
|
||||||
// preBuild Plugins
|
// preBuild Plugins
|
||||||
if (plugins.preBuildPlugins.length > 0) {
|
if (plugins.preBuildPlugins.length > 0) {
|
||||||
client.output.log(
|
console.log(
|
||||||
`Running ${plugins.pluginCount} CLI ${pluralize(
|
`Running ${plugins.pluginCount} CLI ${pluralize(
|
||||||
'Plugin',
|
'Plugin',
|
||||||
plugins.pluginCount
|
plugins.pluginCount
|
||||||
@@ -269,17 +286,31 @@ 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
|
||||||
|
// "node-modules" linker style
|
||||||
|
const env = {
|
||||||
|
YARN_NODE_LINKER: 'node-modules',
|
||||||
|
...spawnOpts.env,
|
||||||
|
};
|
||||||
|
|
||||||
if (typeof buildState.buildCommand === 'string') {
|
if (typeof buildState.buildCommand === 'string') {
|
||||||
client.output.log(`Running Build Command: ${cmd(buildState.buildCommand)}`);
|
console.log(`Running Build Command: ${cmd(buildState.buildCommand)}`);
|
||||||
await execCommand(buildState.buildCommand, {
|
await execCommand(buildState.buildCommand, {
|
||||||
...spawnOpts,
|
...spawnOpts,
|
||||||
// Yarn v2 PnP mode may be activated, so force
|
env,
|
||||||
// "node-modules" linker style
|
cwd,
|
||||||
env: {
|
|
||||||
YARN_NODE_LINKER: 'node-modules',
|
|
||||||
...spawnOpts.env,
|
|
||||||
},
|
|
||||||
cwd: cwd,
|
|
||||||
});
|
});
|
||||||
} else if (fs.existsSync(join(cwd, 'package.json'))) {
|
} else if (fs.existsSync(join(cwd, 'package.json'))) {
|
||||||
await runPackageJsonScript(
|
await runPackageJsonScript(
|
||||||
@@ -288,21 +319,52 @@ export default async function main(client: Client) {
|
|||||||
['vercel-build', 'now-build', 'build'],
|
['vercel-build', 'now-build', 'build'],
|
||||||
spawnOpts
|
spawnOpts
|
||||||
);
|
);
|
||||||
|
} else if (typeof framework.settings.buildCommand.value === 'string') {
|
||||||
|
console.log(
|
||||||
|
`Running Build Command: ${cmd(framework.settings.buildCommand.value)}`
|
||||||
|
);
|
||||||
|
await execCommand(framework.settings.buildCommand.value, {
|
||||||
|
...spawnOpts,
|
||||||
|
env,
|
||||||
|
cwd,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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 (framework.slug === 'nextjs') {
|
// 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/**',
|
||||||
@@ -336,7 +398,7 @@ export default async function main(client: Client) {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
client.output.stopSpinner();
|
client.output.stopSpinner();
|
||||||
client.output.log(
|
console.log(
|
||||||
`Copied ${files.length.toLocaleString()} files from ${param(
|
`Copied ${files.length.toLocaleString()} files from ${param(
|
||||||
distDir
|
distDir
|
||||||
)} to ${param(outputDir)} ${copyStamp()}`
|
)} to ${param(outputDir)} ${copyStamp()}`
|
||||||
@@ -350,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 });
|
||||||
@@ -377,7 +440,7 @@ export default async function main(client: Client) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Special Next.js processing.
|
// Special Next.js processing.
|
||||||
if (framework.slug === 'nextjs') {
|
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(
|
||||||
@@ -428,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) {
|
||||||
@@ -440,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)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -448,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(
|
||||||
@@ -456,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)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -475,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,
|
||||||
});
|
});
|
||||||
@@ -511,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 =>
|
||||||
@@ -528,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'
|
||||||
@@ -543,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,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
@@ -557,12 +638,20 @@ export default async function main(client: Client) {
|
|||||||
|
|
||||||
// Build Plugins
|
// Build Plugins
|
||||||
if (plugins?.buildPlugins && plugins.buildPlugins.length > 0) {
|
if (plugins?.buildPlugins && plugins.buildPlugins.length > 0) {
|
||||||
client.output.log(
|
console.log(
|
||||||
`Running ${plugins.pluginCount} CLI ${pluralize(
|
`Running ${plugins.pluginCount} CLI ${pluralize(
|
||||||
'Plugin',
|
'Plugin',
|
||||||
plugins.pluginCount
|
plugins.pluginCount
|
||||||
)} after Build Command:`
|
)} after Build Command:`
|
||||||
);
|
);
|
||||||
|
let vercelConfig: VercelConfig = {};
|
||||||
|
try {
|
||||||
|
vercelConfig = await fs.readJSON(join(cwd, 'vercel.json'));
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code !== 'ENOENT') {
|
||||||
|
throw new Error(`Failed to read vercel.json: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
for (let item of plugins.buildPlugins) {
|
for (let item of plugins.buildPlugins) {
|
||||||
const { name, plugin, color } = item;
|
const { name, plugin, color } = item;
|
||||||
if (typeof plugin.build === 'function') {
|
if (typeof plugin.build === 'function') {
|
||||||
@@ -575,6 +664,7 @@ export default async function main(client: Client) {
|
|||||||
console.error = (...args: any[]) =>
|
console.error = (...args: any[]) =>
|
||||||
prefixedLog(prefix, args, origErr);
|
prefixedLog(prefix, args, origErr);
|
||||||
await plugin.build({
|
await plugin.build({
|
||||||
|
vercelConfig,
|
||||||
workPath: cwd,
|
workPath: cwd,
|
||||||
});
|
});
|
||||||
client.output.debug(
|
client.output.debug(
|
||||||
@@ -592,13 +682,13 @@ export default async function main(client: Client) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
client.output.print(
|
console.log(
|
||||||
`${prependEmoji(
|
`${prependEmoji(
|
||||||
`Build Completed in ${chalk.bold(OUTPUT_DIR)} ${chalk.gray(
|
`Build Completed in ${chalk.bold(OUTPUT_DIR)} ${chalk.gray(
|
||||||
buildStamp()
|
buildStamp()
|
||||||
)}`,
|
)}`,
|
||||||
emoji('success')
|
emoji('success')
|
||||||
)}\n`
|
)}`
|
||||||
);
|
);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@@ -644,25 +734,37 @@ export async function runPackageJsonScript(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
client.output.log(`Running Build Command: ${cmd(opts.prettyCommand)}\n`);
|
console.log(`Running Build Command: ${cmd(opts.prettyCommand)}\n`);
|
||||||
await spawnAsync(cliType, ['run', scriptName], opts);
|
await spawnAsync(cliType, ['run', scriptName], opts);
|
||||||
client.output.print('\n'); // give it some room
|
console.log(); // give it some room
|
||||||
client.output.debug(`Script complete [${Date.now() - runScriptTime}ms]`);
|
client.output.debug(`Script complete [${Date.now() - runScriptTime}ms]`);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function linkOrCopy(existingPath: string, newPath: string) {
|
async function linkOrCopy(existingPath: string, newPath: string) {
|
||||||
try {
|
try {
|
||||||
await fs.createLink(existingPath, newPath);
|
if (
|
||||||
|
newPath.endsWith('.nft.json') ||
|
||||||
|
newPath.endsWith('middleware-manifest.json') ||
|
||||||
|
newPath.endsWith('required-server-files.json')
|
||||||
|
) {
|
||||||
|
await fs.copy(existingPath, newPath, {
|
||||||
|
overwrite: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await fs.createLink(existingPath, newPath);
|
||||||
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
// eslint-disable-line
|
// eslint-disable-line
|
||||||
// If a hard link to the same file already exists
|
// If a symlink to the same file already exists
|
||||||
// then trying to copy it will make an empty file from it.
|
// then trying to copy it will make an empty file from it.
|
||||||
if (err['code'] === 'EEXIST') return;
|
if (err['code'] === 'EEXIST') return;
|
||||||
// In some VERY rare cases (1 in a thousand), hard-link creation fails on Windows.
|
// In some VERY rare cases (1 in a thousand), symlink creation fails on Windows.
|
||||||
// In that case, we just fall back to copying.
|
// In that case, we just fall back to copying.
|
||||||
// This issue is reproducible with "pnpm add @material-ui/icons@4.9.1"
|
// This issue is reproducible with "pnpm add @material-ui/icons@4.9.1"
|
||||||
await fs.copyFile(existingPath, newPath);
|
await fs.copy(existingPath, newPath, {
|
||||||
|
overwrite: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -709,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);
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { ProjectEnvVariable } from '../../types';
|
|||||||
import Client from '../../util/client';
|
import Client from '../../util/client';
|
||||||
import { getLinkedProject } from '../../util/projects/link';
|
import { getLinkedProject } from '../../util/projects/link';
|
||||||
import { getFrameworks } from '../../util/get-frameworks';
|
import { getFrameworks } from '../../util/get-frameworks';
|
||||||
import { isSettingValue } from '../../util/is-setting-value';
|
|
||||||
import { ProjectSettings } from '../../types';
|
import { ProjectSettings } from '../../types';
|
||||||
import getDecryptedEnvRecords from '../../util/get-decrypted-env-records';
|
import getDecryptedEnvRecords from '../../util/get-decrypted-env-records';
|
||||||
import setupAndLink from '../../util/link/setup-and-link';
|
import setupAndLink from '../../util/link/setup-and-link';
|
||||||
@@ -71,9 +70,9 @@ export default async function dev(
|
|||||||
frameworkSlug = framework.slug;
|
frameworkSlug = framework.slug;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaults = framework.settings.devCommand;
|
const defaults = framework.settings.devCommand.value;
|
||||||
if (isSettingValue(defaults)) {
|
if (defaults) {
|
||||||
devCommand = defaults.value;
|
devCommand = defaults;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,22 @@ const help = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default async function main(client: Client) {
|
export default async function main(client: Client) {
|
||||||
|
if (process.env.__VERCEL_DEV_RUNNING) {
|
||||||
|
client.output.error(
|
||||||
|
`${cmd(
|
||||||
|
`${getPkgName()} dev`
|
||||||
|
)} must not recursively invoke itself. Check the Development Command in the Project Settings or the ${cmd(
|
||||||
|
'dev'
|
||||||
|
)} script in ${cmd('package.json')}`
|
||||||
|
);
|
||||||
|
client.output.error(
|
||||||
|
`Learn More: https://vercel.link/recursive-invocation-of-commands`
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
process.env.__VERCEL_DEV_RUNNING = '1';
|
||||||
|
}
|
||||||
|
|
||||||
let argv;
|
let argv;
|
||||||
let args;
|
let args;
|
||||||
const { output } = client;
|
const { output } = client;
|
||||||
@@ -90,22 +106,21 @@ export default async function main(client: Client) {
|
|||||||
if (pkg) {
|
if (pkg) {
|
||||||
const { scripts } = pkg as PackageJson;
|
const { scripts } = pkg as PackageJson;
|
||||||
|
|
||||||
if (scripts && scripts.dev && /\bnow\b\W+\bdev\b/.test(scripts.dev)) {
|
if (
|
||||||
output.error(
|
scripts &&
|
||||||
`The ${cmd('dev')} script in ${cmd(
|
scripts.dev &&
|
||||||
'package.json'
|
/\b(now|vercel)\b\W+\bdev\b/.test(scripts.dev)
|
||||||
)} must not contain ${cmd('now dev')}`
|
) {
|
||||||
|
client.output.error(
|
||||||
|
`${cmd(
|
||||||
|
`${getPkgName()} dev`
|
||||||
|
)} must not recursively invoke itself. Check the Development Command in the Project Settings or the ${cmd(
|
||||||
|
'dev'
|
||||||
|
)} script in ${cmd('package.json')}`
|
||||||
);
|
);
|
||||||
output.error(`Learn More: http://err.sh/vercel/now-dev-as-dev-script`);
|
client.output.error(
|
||||||
return 1;
|
`Learn More: https://vercel.link/recursive-invocation-of-commands`
|
||||||
}
|
|
||||||
if (scripts && scripts.dev && /\bvercel\b\W+\bdev\b/.test(scripts.dev)) {
|
|
||||||
output.error(
|
|
||||||
`The ${cmd('dev')} script in ${cmd(
|
|
||||||
'package.json'
|
|
||||||
)} must not contain ${cmd('vercel dev')}`
|
|
||||||
);
|
);
|
||||||
output.error(`Learn More: http://err.sh/vercel/now-dev-as-dev-script`);
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,24 +160,26 @@ const main = async () => {
|
|||||||
// * a path to deploy (as in: `vercel path/`)
|
// * a path to deploy (as in: `vercel path/`)
|
||||||
// * a subcommand (as in: `vercel ls`)
|
// * a subcommand (as in: `vercel ls`)
|
||||||
const targetOrSubcommand = argv._[2];
|
const targetOrSubcommand = argv._[2];
|
||||||
|
const isBuildOrDev =
|
||||||
|
targetOrSubcommand === 'build' || targetOrSubcommand === 'dev';
|
||||||
|
|
||||||
output.print(
|
if (isBuildOrDev) {
|
||||||
`${chalk.grey(
|
console.log(
|
||||||
`${getTitleName()} CLI ${pkg.version}${
|
`${chalk.grey(
|
||||||
targetOrSubcommand === 'dev'
|
`${getTitleName()} CLI ${
|
||||||
? ' dev (beta)'
|
pkg.version
|
||||||
: targetOrSubcommand === 'build'
|
} ${targetOrSubcommand} (beta) — https://vercel.com/feedback`
|
||||||
? ' build (beta)'
|
)}`
|
||||||
: ''
|
);
|
||||||
}${
|
} else {
|
||||||
isCanary ||
|
output.print(
|
||||||
targetOrSubcommand === 'dev' ||
|
`${chalk.grey(
|
||||||
targetOrSubcommand === 'build'
|
`${getTitleName()} CLI ${pkg.version}${
|
||||||
? ' — https://vercel.com/feedback'
|
isCanary ? ' — https://vercel.com/feedback' : ''
|
||||||
: ''
|
}`
|
||||||
}`
|
)}\n`
|
||||||
)}\n`
|
);
|
||||||
);
|
}
|
||||||
|
|
||||||
// Handle `--version` directly
|
// Handle `--version` directly
|
||||||
if (!targetOrSubcommand && argv['--version']) {
|
if (!targetOrSubcommand && argv['--version']) {
|
||||||
|
|||||||
@@ -18,12 +18,8 @@ export const isDirectory = (path: string): boolean => {
|
|||||||
const getGlobalPathConfig = (): string => {
|
const getGlobalPathConfig = (): string => {
|
||||||
let customPath: string | undefined;
|
let customPath: string | undefined;
|
||||||
|
|
||||||
try {
|
const argv = getArgs(process.argv.slice(2), {}, { permissive: true });
|
||||||
const argv = getArgs(process.argv.slice(2), {});
|
customPath = argv['--global-config'];
|
||||||
customPath = argv['--global-config'];
|
|
||||||
} catch (_error) {
|
|
||||||
// args are optional so consume error
|
|
||||||
}
|
|
||||||
|
|
||||||
const vercelDirectories = XDGAppPaths('com.vercel.cli').dataDirs();
|
const vercelDirectories = XDGAppPaths('com.vercel.cli').dataDirs();
|
||||||
|
|
||||||
|
|||||||
@@ -7,12 +7,8 @@ import getArgs from '../../util/get-args';
|
|||||||
export default function getLocalPathConfig(prefix: string) {
|
export default function getLocalPathConfig(prefix: string) {
|
||||||
let customPath: string | undefined;
|
let customPath: string | undefined;
|
||||||
|
|
||||||
try {
|
const argv = getArgs(process.argv.slice(2), {}, { permissive: true });
|
||||||
const argv = getArgs(process.argv.slice(2), {});
|
customPath = argv['--local-config'];
|
||||||
customPath = argv['--local-config'];
|
|
||||||
} catch (_error) {
|
|
||||||
// args are optional so consume error
|
|
||||||
}
|
|
||||||
|
|
||||||
// If `--local-config` flag was specified, then that takes priority
|
// If `--local-config` flag was specified, then that takes priority
|
||||||
if (customPath) {
|
if (customPath) {
|
||||||
|
|||||||
@@ -1358,13 +1358,20 @@ export default class DevServer {
|
|||||||
this.cwd,
|
this.cwd,
|
||||||
this.output
|
this.output
|
||||||
);
|
);
|
||||||
for (let plugin of devMiddlewarePlugins) {
|
try {
|
||||||
const result = await plugin.plugin.runDevMiddleware(req, res, this.cwd);
|
for (let plugin of devMiddlewarePlugins) {
|
||||||
if (result.finished) {
|
const result = await plugin.plugin.runDevMiddleware(req, res, this.cwd);
|
||||||
return result;
|
if (result.finished) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return { finished: false };
|
||||||
|
} catch (e) {
|
||||||
|
return {
|
||||||
|
finished: true,
|
||||||
|
error: e,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return { finished: false };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2154,7 +2161,10 @@ export default class DevServer {
|
|||||||
process.stdout.write(data.replace(proxyPort, devPort));
|
process.stdout.write(data.replace(proxyPort, devPort));
|
||||||
});
|
});
|
||||||
|
|
||||||
p.on('exit', () => {
|
p.on('exit', (code: number) => {
|
||||||
|
if (code > 0) {
|
||||||
|
process.exit(code);
|
||||||
|
}
|
||||||
this.devProcessPort = undefined;
|
this.devProcessPort = undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import { Stats } from 'fs';
|
import { Stats } from 'fs';
|
||||||
import { sep, dirname, join, resolve } from 'path';
|
import { sep, dirname, join, resolve } from 'path';
|
||||||
import { readJSON, lstat, readlink, readFile, realpath } from 'fs-extra';
|
import { lstat, readlink, readFile, realpath } from 'fs-extra';
|
||||||
import { isCanary } from './is-canary';
|
import { isCanary } from './is-canary';
|
||||||
import { getPkgName } from './pkg-name';
|
import { getPkgName } from './pkg-name';
|
||||||
|
|
||||||
// `npm` tacks a bunch of extra properties on the `package.json` file,
|
|
||||||
// so check for one of them to determine yarn vs. npm.
|
|
||||||
async function isYarn(): Promise<boolean> {
|
async function isYarn(): Promise<boolean> {
|
||||||
let s: Stats;
|
let s: Stats;
|
||||||
let binPath = process.argv[1];
|
let binPath = process.argv[1];
|
||||||
@@ -20,8 +18,12 @@ async function isYarn(): Promise<boolean> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const pkgPath = join(dirname(binPath), '..', 'package.json');
|
const pkgPath = join(dirname(binPath), '..', 'package.json');
|
||||||
const pkg = await readJSON(pkgPath).catch(() => ({}));
|
/*
|
||||||
return !('_id' in pkg);
|
* Generally, pkgPath looks like:
|
||||||
|
* "/Users/username/.config/yarn/global/node_modules/vercel/package.json"
|
||||||
|
* "/usr/local/share/.config/yarn/global/node_modules/vercel/package.json"
|
||||||
|
*/
|
||||||
|
return pkgPath.includes(join('yarn', 'global'));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getConfigPrefix() {
|
async function getConfigPrefix() {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import ua from 'universal-analytics';
|
import ua from 'universal-analytics';
|
||||||
import { platform, release, userInfo } from 'os';
|
|
||||||
import { getPlatformEnv } from '@vercel/build-utils';
|
import { getPlatformEnv } from '@vercel/build-utils';
|
||||||
|
|
||||||
import userAgent from './ua-browser';
|
import userAgent from './ua-browser';
|
||||||
@@ -16,10 +15,15 @@ export const shouldCollectMetrics =
|
|||||||
|
|
||||||
export const metrics = (): ua.Visitor => {
|
export const metrics = (): ua.Visitor => {
|
||||||
const token =
|
const token =
|
||||||
typeof config.token === 'string' ? config.token : platform() + release();
|
typeof config.token === 'string'
|
||||||
const salt = userInfo().username;
|
? config.token
|
||||||
|
: process.platform + process.arch;
|
||||||
|
const salt =
|
||||||
|
(process.env.USER || '') +
|
||||||
|
(process.env.LANG || '') +
|
||||||
|
(process.env.SHELL || '');
|
||||||
const hash = crypto
|
const hash = crypto
|
||||||
.pbkdf2Sync(token, salt, 1000, 64, 'sha512')
|
.pbkdf2Sync(token, salt, 100, 64, 'sha512')
|
||||||
.toString('hex')
|
.toString('hex')
|
||||||
.substring(0, 24);
|
.substring(0, 24);
|
||||||
|
|
||||||
|
|||||||
@@ -125,10 +125,14 @@ export class Output {
|
|||||||
this.debug(`Spinner invoked (${message}) with a ${delay}ms delay`);
|
this.debug(`Spinner invoked (${message}) with a ${delay}ms delay`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this._spinner) {
|
if (this.isTTY) {
|
||||||
this._spinner.text = message;
|
if (this._spinner) {
|
||||||
|
this._spinner.text = message;
|
||||||
|
} else {
|
||||||
|
this._spinner = wait(message, delay);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this._spinner = wait(message, delay);
|
this.print(`${message}\n`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -244,20 +244,27 @@ export async function linkFolderToProject(
|
|||||||
try {
|
try {
|
||||||
const gitIgnorePath = join(path, '.gitignore');
|
const gitIgnorePath = join(path, '.gitignore');
|
||||||
|
|
||||||
const gitIgnore = await readFile(gitIgnorePath, 'utf8').catch(() => null);
|
let gitIgnore =
|
||||||
const EOL = gitIgnore && gitIgnore.includes('\r\n') ? '\r\n' : os.EOL;
|
(await readFile(gitIgnorePath, 'utf8').catch(() => null)) ?? '';
|
||||||
|
const EOL = gitIgnore.includes('\r\n') ? '\r\n' : os.EOL;
|
||||||
|
let contentModified = false;
|
||||||
|
|
||||||
if (
|
if (!gitIgnore.split(EOL).includes(VERCEL_DIR)) {
|
||||||
!gitIgnore ||
|
gitIgnore += `${
|
||||||
!gitIgnore.split(EOL).includes(VERCEL_DIR) ||
|
gitIgnore.endsWith(EOL) || gitIgnore.length === 0 ? '' : EOL
|
||||||
!gitIgnore.split(EOL).includes(VERCEL_OUTPUT_DIR)
|
}${VERCEL_DIR}${EOL}`;
|
||||||
) {
|
contentModified = true;
|
||||||
await writeFile(
|
}
|
||||||
gitIgnorePath,
|
|
||||||
gitIgnore
|
if (!gitIgnore.split(EOL).includes(VERCEL_OUTPUT_DIR)) {
|
||||||
? `${gitIgnore}${EOL}${VERCEL_DIR}${EOL}${VERCEL_OUTPUT_DIR}${EOL}`
|
gitIgnore += `${
|
||||||
: `${VERCEL_DIR}${EOL}${VERCEL_OUTPUT_DIR}${EOL}`
|
gitIgnore.endsWith(EOL) || gitIgnore.length === 0 ? '' : EOL
|
||||||
);
|
}${VERCEL_OUTPUT_DIR}${EOL}`;
|
||||||
|
contentModified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contentModified) {
|
||||||
|
await writeFile(gitIgnorePath, gitIgnore);
|
||||||
isGitIgnoreUpdated = true;
|
isGitIgnoreUpdated = true;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
3
packages/cli/test/fixtures/unit/edge-middleware-invalid-response/_middleware.js
vendored
Normal file
3
packages/cli/test/fixtures/unit/edge-middleware-invalid-response/_middleware.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export default function () {
|
||||||
|
return 'freecandy';
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@ const getRevertAliasConfigFile = () => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
module.exports = async function prepare(session) {
|
module.exports = async function prepare(session, binaryPath) {
|
||||||
const spec = {
|
const spec = {
|
||||||
'static-single-file': {
|
'static-single-file': {
|
||||||
'first.png': getImageFile(session, { size: 30 }),
|
'first.png': getImageFile(session, { size: 30 }),
|
||||||
@@ -114,6 +114,23 @@ module.exports = async function prepare(session) {
|
|||||||
2
|
2
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
'dev-fail-on-recursion-command': {
|
||||||
|
'package.json': '{}',
|
||||||
|
},
|
||||||
|
'build-fail-on-recursion-command': {
|
||||||
|
'package.json': '{}',
|
||||||
|
},
|
||||||
|
'build-fail-on-recursion-script': {
|
||||||
|
'package.json': JSON.stringify(
|
||||||
|
{
|
||||||
|
scripts: {
|
||||||
|
build: `${binaryPath} build`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
),
|
||||||
|
},
|
||||||
'static-deployment': {
|
'static-deployment': {
|
||||||
'index.txt': 'Hello World',
|
'index.txt': 'Hello World',
|
||||||
},
|
},
|
||||||
@@ -348,6 +365,10 @@ module.exports = async function prepare(session) {
|
|||||||
'project-link-dev': {
|
'project-link-dev': {
|
||||||
'package.json': '{}',
|
'package.json': '{}',
|
||||||
},
|
},
|
||||||
|
'project-link-gitignore': {
|
||||||
|
'package.json': '{}',
|
||||||
|
'.gitignore': '.output',
|
||||||
|
},
|
||||||
'project-link-legacy': {
|
'project-link-legacy': {
|
||||||
'index.html': 'Hello',
|
'index.html': 'Hello',
|
||||||
'vercel.json': '{"builds":[{"src":"*.html","use":"@vercel/static"}]}',
|
'vercel.json': '{"builds":[{"src":"*.html","use":"@vercel/static"}]}',
|
||||||
|
|||||||
396
packages/cli/test/integration.js
vendored
396
packages/cli/test/integration.js
vendored
@@ -252,10 +252,69 @@ const createUser = async () => {
|
|||||||
|
|
||||||
const getConfigAuthPath = () => path.join(globalDir, 'auth.json');
|
const getConfigAuthPath = () => path.join(globalDir, 'auth.json');
|
||||||
|
|
||||||
|
async function setupProject(process, projectName, overrides) {
|
||||||
|
await waitForPrompt(process, chunk => /Set up [^?]+\?/.test(chunk));
|
||||||
|
process.stdin.write('yes\n');
|
||||||
|
|
||||||
|
await waitForPrompt(process, chunk => /Which scope [^?]+\?/.test(chunk));
|
||||||
|
process.stdin.write('\n');
|
||||||
|
|
||||||
|
await waitForPrompt(process, chunk =>
|
||||||
|
chunk.includes('Link to existing project?')
|
||||||
|
);
|
||||||
|
process.stdin.write('no\n');
|
||||||
|
|
||||||
|
await waitForPrompt(process, chunk =>
|
||||||
|
chunk.includes('What’s your project’s name?')
|
||||||
|
);
|
||||||
|
process.stdin.write(`${projectName}\n`);
|
||||||
|
|
||||||
|
await waitForPrompt(process, chunk =>
|
||||||
|
chunk.includes('In which directory is your code located?')
|
||||||
|
);
|
||||||
|
process.stdin.write('\n');
|
||||||
|
|
||||||
|
await waitForPrompt(process, chunk =>
|
||||||
|
chunk.includes('Want to override the settings?')
|
||||||
|
);
|
||||||
|
|
||||||
|
if (overrides) {
|
||||||
|
process.stdin.write('yes\n');
|
||||||
|
|
||||||
|
const { buildCommand, outputDirectory, devCommand } = overrides;
|
||||||
|
|
||||||
|
await waitForPrompt(process, chunk =>
|
||||||
|
chunk.includes(
|
||||||
|
'Which settings would you like to overwrite (select multiple)?'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
process.stdin.write('a\n'); // 'a' means select all
|
||||||
|
|
||||||
|
await waitForPrompt(process, chunk =>
|
||||||
|
chunk.includes(`What's your Build Command?`)
|
||||||
|
);
|
||||||
|
process.stdin.write(`${buildCommand ?? ''}\n`);
|
||||||
|
|
||||||
|
await waitForPrompt(process, chunk =>
|
||||||
|
chunk.includes(`What's your Output Directory?`)
|
||||||
|
);
|
||||||
|
process.stdin.write(`${outputDirectory ?? ''}\n`);
|
||||||
|
|
||||||
|
await waitForPrompt(process, chunk =>
|
||||||
|
chunk.includes(`What's your Development Command?`)
|
||||||
|
);
|
||||||
|
process.stdin.write(`${devCommand ?? ''}\n`);
|
||||||
|
} else {
|
||||||
|
process.stdin.write('no\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
await waitForPrompt(process, chunk => chunk.includes('Linked to'));
|
||||||
|
}
|
||||||
|
|
||||||
test.before(async () => {
|
test.before(async () => {
|
||||||
try {
|
try {
|
||||||
await createUser();
|
await createUser();
|
||||||
await prepareFixtures(contextName);
|
await prepareFixtures(contextName, binaryPath);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('Failed `test.before`');
|
console.log('Failed `test.before`');
|
||||||
console.log(err);
|
console.log(err);
|
||||||
@@ -2227,13 +2286,93 @@ test('whoami', async t => {
|
|||||||
t.is(stdout, contextName, formatOutput({ stdout, stderr }));
|
t.is(stdout, contextName, formatOutput({ stdout, stderr }));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('fail `now dev` dev script without now.json', async t => {
|
test('[vercel dev] fails when dev script calls vercel dev recursively', async t => {
|
||||||
const deploymentPath = fixture('now-dev-fail-dev-script');
|
const deploymentPath = fixture('now-dev-fail-dev-script');
|
||||||
const { exitCode, stderr } = await execute(['dev', deploymentPath]);
|
const { exitCode, stderr } = await execute(['dev', deploymentPath]);
|
||||||
|
|
||||||
t.is(exitCode, 1);
|
t.is(exitCode, 1);
|
||||||
t.true(
|
t.true(
|
||||||
stderr.includes('must not contain `now dev`'),
|
stderr.includes('must not recursively invoke itself'),
|
||||||
|
`Received instead: "${stderr}"`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[vercel dev] fails when development commad calls vercel dev recursively', async t => {
|
||||||
|
const dir = fixture('dev-fail-on-recursion-command');
|
||||||
|
const projectName = `dev-fail-on-recursion-command-${
|
||||||
|
Math.random().toString(36).split('.')[1]
|
||||||
|
}`;
|
||||||
|
|
||||||
|
const dev = execa(binaryPath, ['dev', ...defaultArgs], {
|
||||||
|
cwd: dir,
|
||||||
|
reject: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
await setupProject(dev, projectName, {
|
||||||
|
devCommand: `${binaryPath} dev`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { exitCode, stderr } = await dev;
|
||||||
|
|
||||||
|
t.is(exitCode, 1);
|
||||||
|
t.true(
|
||||||
|
stderr.includes('must not recursively invoke itself'),
|
||||||
|
`Received instead: "${stderr}"`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[vercel build] fails when build commad calls vercel build recursively', async t => {
|
||||||
|
const dir = fixture('build-fail-on-recursion-command');
|
||||||
|
const projectName = `build-fail-on-recursion-command-${
|
||||||
|
Math.random().toString(36).split('.')[1]
|
||||||
|
}`;
|
||||||
|
|
||||||
|
const build = execa(binaryPath, ['build', ...defaultArgs], {
|
||||||
|
cwd: dir,
|
||||||
|
reject: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitForPrompt(build, chunk =>
|
||||||
|
chunk.includes('No Project Settings found locally')
|
||||||
|
);
|
||||||
|
build.stdin.write('yes\n');
|
||||||
|
|
||||||
|
await setupProject(build, projectName, {
|
||||||
|
buildCommand: `${binaryPath} build`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { exitCode, stderr } = await build;
|
||||||
|
|
||||||
|
t.is(exitCode, 1);
|
||||||
|
t.true(
|
||||||
|
stderr.includes('must not recursively invoke itself'),
|
||||||
|
`Received instead: "${stderr}"`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[vercel build] fails when build script calls vercel build recursively', async t => {
|
||||||
|
const dir = fixture('build-fail-on-recursion-script');
|
||||||
|
const projectName = `build-fail-on-recursion-script-${
|
||||||
|
Math.random().toString(36).split('.')[1]
|
||||||
|
}`;
|
||||||
|
|
||||||
|
const build = execa(binaryPath, ['build', ...defaultArgs], {
|
||||||
|
cwd: dir,
|
||||||
|
reject: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitForPrompt(build, chunk =>
|
||||||
|
chunk.includes('No Project Settings found locally')
|
||||||
|
);
|
||||||
|
build.stdin.write('yes\n');
|
||||||
|
|
||||||
|
await setupProject(build, projectName);
|
||||||
|
|
||||||
|
const { exitCode, stderr } = await build;
|
||||||
|
|
||||||
|
t.is(exitCode, 1);
|
||||||
|
t.true(
|
||||||
|
stderr.includes('must not recursively invoke itself'),
|
||||||
`Received instead: "${stderr}"`
|
`Received instead: "${stderr}"`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -2544,7 +2683,7 @@ test('deploy a Lambda with 3 seconds of maxDuration', async t => {
|
|||||||
const url = new URL(output.stdout);
|
const url = new URL(output.stdout);
|
||||||
|
|
||||||
// Should time out
|
// Should time out
|
||||||
url.pathname = '/api/wait-for/4';
|
url.pathname = '/api/wait-for/5';
|
||||||
const response1 = await fetch(url.href);
|
const response1 = await fetch(url.href);
|
||||||
t.is(
|
t.is(
|
||||||
response1.status,
|
response1.status,
|
||||||
@@ -2553,7 +2692,7 @@ test('deploy a Lambda with 3 seconds of maxDuration', async t => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Should not time out
|
// Should not time out
|
||||||
url.pathname = '/api/wait-for/2';
|
url.pathname = '/api/wait-for/1';
|
||||||
const response2 = await fetch(url.href);
|
const response2 = await fetch(url.href);
|
||||||
t.is(
|
t.is(
|
||||||
response2.status,
|
response2.status,
|
||||||
@@ -2683,59 +2822,10 @@ test('should show prompts to set up project during first deploy', async t => {
|
|||||||
|
|
||||||
const now = execa(binaryPath, [dir, ...defaultArgs]);
|
const now = execa(binaryPath, [dir, ...defaultArgs]);
|
||||||
|
|
||||||
await waitForPrompt(now, chunk => /Set up and deploy [^?]+\?/.test(chunk));
|
await setupProject(now, projectName, {
|
||||||
now.stdin.write('yes\n');
|
buildCommand: `mkdir -p o && echo '<h1>custom hello</h1>' > o/index.html`,
|
||||||
|
outputDirectory: 'o',
|
||||||
await waitForPrompt(now, chunk =>
|
});
|
||||||
chunk.includes('Which scope do you want to deploy to?')
|
|
||||||
);
|
|
||||||
now.stdin.write('\n');
|
|
||||||
|
|
||||||
await waitForPrompt(now, chunk =>
|
|
||||||
chunk.includes('Link to existing project?')
|
|
||||||
);
|
|
||||||
now.stdin.write('no\n');
|
|
||||||
|
|
||||||
await waitForPrompt(now, chunk =>
|
|
||||||
chunk.includes('What’s your project’s name?')
|
|
||||||
);
|
|
||||||
now.stdin.write(`${projectName}\n`);
|
|
||||||
|
|
||||||
await waitForPrompt(now, chunk =>
|
|
||||||
chunk.includes('In which directory is your code located?')
|
|
||||||
);
|
|
||||||
now.stdin.write('\n');
|
|
||||||
|
|
||||||
await waitForPrompt(now, chunk =>
|
|
||||||
chunk.includes('Want to override the settings?')
|
|
||||||
);
|
|
||||||
now.stdin.write('yes\n');
|
|
||||||
|
|
||||||
await waitForPrompt(now, chunk =>
|
|
||||||
chunk.includes(
|
|
||||||
'Which settings would you like to overwrite (select multiple)?'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
now.stdin.write('a\n'); // 'a' means select all
|
|
||||||
|
|
||||||
await waitForPrompt(now, chunk =>
|
|
||||||
chunk.includes(`What's your Build Command?`)
|
|
||||||
);
|
|
||||||
now.stdin.write(
|
|
||||||
`mkdir -p o && echo '<h1>custom hello</h1>' > o/index.html\n`
|
|
||||||
);
|
|
||||||
|
|
||||||
await waitForPrompt(now, chunk =>
|
|
||||||
chunk.includes(`What's your Output Directory?`)
|
|
||||||
);
|
|
||||||
now.stdin.write(`o\n`);
|
|
||||||
|
|
||||||
await waitForPrompt(now, chunk =>
|
|
||||||
chunk.includes(`What's your Development Command?`)
|
|
||||||
);
|
|
||||||
now.stdin.write(`\n`);
|
|
||||||
|
|
||||||
await waitForPrompt(now, chunk => chunk.includes('Linked to'));
|
|
||||||
|
|
||||||
const output = await now;
|
const output = await now;
|
||||||
|
|
||||||
@@ -3301,55 +3391,10 @@ test('[vc link] should show prompts to set up project', async t => {
|
|||||||
|
|
||||||
const vc = execa(binaryPath, ['link', ...defaultArgs], { cwd: dir });
|
const vc = execa(binaryPath, ['link', ...defaultArgs], { cwd: dir });
|
||||||
|
|
||||||
await waitForPrompt(vc, chunk => /Set up [^?]+\?/.test(chunk));
|
await setupProject(vc, projectName, {
|
||||||
vc.stdin.write('yes\n');
|
buildCommand: `mkdir -p o && echo '<h1>custom hello</h1>' > o/index.html`,
|
||||||
|
outputDirectory: 'o',
|
||||||
await waitForPrompt(vc, chunk =>
|
});
|
||||||
chunk.includes('Which scope should contain your project?')
|
|
||||||
);
|
|
||||||
vc.stdin.write('\n');
|
|
||||||
|
|
||||||
await waitForPrompt(vc, chunk => chunk.includes('Link to existing project?'));
|
|
||||||
vc.stdin.write('no\n');
|
|
||||||
|
|
||||||
await waitForPrompt(vc, chunk =>
|
|
||||||
chunk.includes('What’s your project’s name?')
|
|
||||||
);
|
|
||||||
vc.stdin.write(`${projectName}\n`);
|
|
||||||
|
|
||||||
await waitForPrompt(vc, chunk =>
|
|
||||||
chunk.includes('In which directory is your code located?')
|
|
||||||
);
|
|
||||||
vc.stdin.write('\n');
|
|
||||||
|
|
||||||
await waitForPrompt(vc, chunk =>
|
|
||||||
chunk.includes('Want to override the settings?')
|
|
||||||
);
|
|
||||||
vc.stdin.write('yes\n');
|
|
||||||
|
|
||||||
await waitForPrompt(vc, chunk =>
|
|
||||||
chunk.includes(
|
|
||||||
'Which settings would you like to overwrite (select multiple)?'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
vc.stdin.write('a\n'); // 'a' means select all
|
|
||||||
|
|
||||||
await waitForPrompt(vc, chunk =>
|
|
||||||
chunk.includes(`What's your Build Command?`)
|
|
||||||
);
|
|
||||||
vc.stdin.write(`mkdir -p o && echo '<h1>custom hello</h1>' > o/index.html\n`);
|
|
||||||
|
|
||||||
await waitForPrompt(vc, chunk =>
|
|
||||||
chunk.includes(`What's your Output Directory?`)
|
|
||||||
);
|
|
||||||
vc.stdin.write(`o\n`);
|
|
||||||
|
|
||||||
await waitForPrompt(vc, chunk =>
|
|
||||||
chunk.includes(`What's your Development Command?`)
|
|
||||||
);
|
|
||||||
vc.stdin.write(`\n`);
|
|
||||||
|
|
||||||
await waitForPrompt(vc, chunk => chunk.includes('Linked to'));
|
|
||||||
|
|
||||||
const output = await vc;
|
const output = await vc;
|
||||||
|
|
||||||
@@ -3408,6 +3453,29 @@ test('[vc link --confirm] should not show prompts and autolink', async t => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('[vc link] should not duplicate paths in .gitignore', async t => {
|
||||||
|
const dir = fixture('project-link-gitignore');
|
||||||
|
|
||||||
|
// remove previously linked project if it exists
|
||||||
|
await remove(path.join(dir, '.vercel'));
|
||||||
|
|
||||||
|
const { exitCode, stderr, stdout } = await execa(
|
||||||
|
binaryPath,
|
||||||
|
['link', '--confirm', ...defaultArgs],
|
||||||
|
{ cwd: dir, reject: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ensure the exit code is right
|
||||||
|
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
|
||||||
|
|
||||||
|
// Ensure the message is correct pattern
|
||||||
|
t.regex(stderr, /Linked to /m);
|
||||||
|
|
||||||
|
// Ensure .gitignore is created
|
||||||
|
const gitignore = await readFile(path.join(dir, '.gitignore'), 'utf8');
|
||||||
|
t.is(gitignore, '.output\n.vercel\n');
|
||||||
|
});
|
||||||
|
|
||||||
test('[vc dev] should show prompts to set up project', async t => {
|
test('[vc dev] should show prompts to set up project', async t => {
|
||||||
const dir = fixture('project-link-dev');
|
const dir = fixture('project-link-dev');
|
||||||
const port = 58352;
|
const port = 58352;
|
||||||
@@ -3422,59 +3490,10 @@ test('[vc dev] should show prompts to set up project', async t => {
|
|||||||
cwd: dir,
|
cwd: dir,
|
||||||
});
|
});
|
||||||
|
|
||||||
await waitForPrompt(dev, chunk => /Set up and develop [^?]+\?/.test(chunk));
|
await setupProject(dev, projectName, {
|
||||||
dev.stdin.write('yes\n');
|
buildCommand: `mkdir -p o && echo '<h1>custom hello</h1>' > o/index.html`,
|
||||||
|
outputDirectory: 'o',
|
||||||
await waitForPrompt(dev, chunk =>
|
});
|
||||||
chunk.includes('Which scope should contain your project?')
|
|
||||||
);
|
|
||||||
dev.stdin.write('\n');
|
|
||||||
|
|
||||||
await waitForPrompt(dev, chunk =>
|
|
||||||
chunk.includes('Link to existing project?')
|
|
||||||
);
|
|
||||||
dev.stdin.write('no\n');
|
|
||||||
|
|
||||||
await waitForPrompt(dev, chunk =>
|
|
||||||
chunk.includes('What’s your project’s name?')
|
|
||||||
);
|
|
||||||
dev.stdin.write(`${projectName}\n`);
|
|
||||||
|
|
||||||
await waitForPrompt(dev, chunk =>
|
|
||||||
chunk.includes('In which directory is your code located?')
|
|
||||||
);
|
|
||||||
dev.stdin.write('\n');
|
|
||||||
|
|
||||||
await waitForPrompt(dev, chunk =>
|
|
||||||
chunk.includes('Want to override the settings?')
|
|
||||||
);
|
|
||||||
dev.stdin.write('yes\n');
|
|
||||||
|
|
||||||
await waitForPrompt(dev, chunk =>
|
|
||||||
chunk.includes(
|
|
||||||
'Which settings would you like to overwrite (select multiple)?'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
dev.stdin.write('a\n'); // 'a' means select all
|
|
||||||
|
|
||||||
await waitForPrompt(dev, chunk =>
|
|
||||||
chunk.includes(`What's your Build Command?`)
|
|
||||||
);
|
|
||||||
dev.stdin.write(
|
|
||||||
`mkdir -p o && echo '<h1>custom hello</h1>' > o/index.html\n`
|
|
||||||
);
|
|
||||||
|
|
||||||
await waitForPrompt(dev, chunk =>
|
|
||||||
chunk.includes(`What's your Output Directory?`)
|
|
||||||
);
|
|
||||||
dev.stdin.write(`o\n`);
|
|
||||||
|
|
||||||
await waitForPrompt(dev, chunk =>
|
|
||||||
chunk.includes(`What's your Development Command?`)
|
|
||||||
);
|
|
||||||
dev.stdin.write(`\n`);
|
|
||||||
|
|
||||||
await waitForPrompt(dev, chunk => chunk.includes('Linked to'));
|
|
||||||
|
|
||||||
// Ensure .gitignore is created
|
// Ensure .gitignore is created
|
||||||
const gitignore = await readFile(path.join(dir, '.gitignore'), 'utf8');
|
const gitignore = await readFile(path.join(dir, '.gitignore'), 'utf8');
|
||||||
@@ -3574,59 +3593,12 @@ test('[vc dev] should send the platform proxy request headers to frontend dev se
|
|||||||
cwd: dir,
|
cwd: dir,
|
||||||
});
|
});
|
||||||
|
|
||||||
await waitForPrompt(dev, chunk => /Set up and develop [^?]+\?/.test(chunk));
|
await setupProject(dev, projectName, {
|
||||||
dev.stdin.write('yes\n');
|
buildCommand: `mkdir -p o && echo '<h1>custom hello</h1>' > o/index.html`,
|
||||||
|
outputDirectory: 'o',
|
||||||
|
devCommand: 'node server.js',
|
||||||
|
});
|
||||||
|
|
||||||
await waitForPrompt(dev, chunk =>
|
|
||||||
chunk.includes('Which scope should contain your project?')
|
|
||||||
);
|
|
||||||
dev.stdin.write('\n');
|
|
||||||
|
|
||||||
await waitForPrompt(dev, chunk =>
|
|
||||||
chunk.includes('Link to existing project?')
|
|
||||||
);
|
|
||||||
dev.stdin.write('no\n');
|
|
||||||
|
|
||||||
await waitForPrompt(dev, chunk =>
|
|
||||||
chunk.includes('What’s your project’s name?')
|
|
||||||
);
|
|
||||||
dev.stdin.write(`${projectName}\n`);
|
|
||||||
|
|
||||||
await waitForPrompt(dev, chunk =>
|
|
||||||
chunk.includes('In which directory is your code located?')
|
|
||||||
);
|
|
||||||
dev.stdin.write('\n');
|
|
||||||
|
|
||||||
await waitForPrompt(dev, chunk =>
|
|
||||||
chunk.includes('Want to override the settings?')
|
|
||||||
);
|
|
||||||
dev.stdin.write('yes\n');
|
|
||||||
|
|
||||||
await waitForPrompt(dev, chunk =>
|
|
||||||
chunk.includes(
|
|
||||||
'Which settings would you like to overwrite (select multiple)?'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
dev.stdin.write('a\n'); // 'a' means select all
|
|
||||||
|
|
||||||
await waitForPrompt(dev, chunk =>
|
|
||||||
chunk.includes(`What's your Build Command?`)
|
|
||||||
);
|
|
||||||
dev.stdin.write(
|
|
||||||
`mkdir -p o && echo '<h1>custom hello</h1>' > o/index.html\n`
|
|
||||||
);
|
|
||||||
|
|
||||||
await waitForPrompt(dev, chunk =>
|
|
||||||
chunk.includes(`What's your Output Directory?`)
|
|
||||||
);
|
|
||||||
dev.stdin.write(`o\n`);
|
|
||||||
|
|
||||||
await waitForPrompt(dev, chunk =>
|
|
||||||
chunk.includes(`What's your Development Command?`)
|
|
||||||
);
|
|
||||||
dev.stdin.write(`node server.js\n`);
|
|
||||||
|
|
||||||
await waitForPrompt(dev, chunk => chunk.includes('Linked to'));
|
|
||||||
await waitForPrompt(dev, chunk => chunk.includes('Ready! Available at'));
|
await waitForPrompt(dev, chunk => chunk.includes('Ready! Available at'));
|
||||||
|
|
||||||
// Ensure that `vc dev` also works
|
// Ensure that `vc dev` also works
|
||||||
|
|||||||
@@ -374,4 +374,15 @@ describe('DevServer', () => {
|
|||||||
);
|
);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
it(
|
||||||
|
'should render an error page when the middleware returns not a Response',
|
||||||
|
testFixture('edge-middleware-invalid-response', async server => {
|
||||||
|
const response = await fetch(`${server.address}/index.html`);
|
||||||
|
const body = await response.text();
|
||||||
|
expect(body).toStrictEqual(
|
||||||
|
'A server error has occurred\n\nEDGE_FUNCTION_INVOCATION_FAILED\n'
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ describe('getUpdateCommand', () => {
|
|||||||
it('should detect update command', async () => {
|
it('should detect update command', async () => {
|
||||||
const updateCommand = await getUpdateCommand();
|
const updateCommand = await getUpdateCommand();
|
||||||
expect(updateCommand).toEqual(
|
expect(updateCommand).toEqual(
|
||||||
`yarn add vercel@${isCanary() ? 'canary' : 'latest'}`
|
`npm i vercel@${isCanary() ? 'canary' : 'latest'}`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vercel/client",
|
"name": "@vercel/client",
|
||||||
"version": "10.2.3-canary.18",
|
"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.17",
|
"@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 |
6
packages/frameworks/logos/remix.svg
Normal file
6
packages/frameworks/logos/remix.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,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vercel/frameworks",
|
"name": "@vercel/frameworks",
|
||||||
"version": "0.5.1-canary.11",
|
"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": [
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
buildCommand: {
|
buildCommand: {
|
||||||
placeholder: '`npm run build` or `blitz build`',
|
placeholder: '`npm run build` or `blitz build`',
|
||||||
|
value: 'blitz build',
|
||||||
},
|
},
|
||||||
devCommand: {
|
devCommand: {
|
||||||
value: 'blitz start',
|
value: 'blitz start',
|
||||||
@@ -51,8 +52,6 @@ export const frameworks = [
|
|||||||
placeholder: 'Next.js default',
|
placeholder: 'Next.js default',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
devCommand: 'blitz start',
|
|
||||||
buildCommand: 'blitz build',
|
|
||||||
getFsOutputDir: async () => '.next',
|
getFsOutputDir: async () => '.next',
|
||||||
getOutputDirName: async () => 'public',
|
getOutputDirName: async () => 'public',
|
||||||
},
|
},
|
||||||
@@ -83,6 +82,7 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
buildCommand: {
|
buildCommand: {
|
||||||
placeholder: '`npm run build` or `next build`',
|
placeholder: '`npm run build` or `next build`',
|
||||||
|
value: 'next build',
|
||||||
},
|
},
|
||||||
devCommand: {
|
devCommand: {
|
||||||
value: 'next dev --port $PORT',
|
value: 'next dev --port $PORT',
|
||||||
@@ -98,8 +98,6 @@ export const frameworks = [
|
|||||||
dependencies: ['next-plugin-sentry', 'next-sentry-source-maps'],
|
dependencies: ['next-plugin-sentry', 'next-sentry-source-maps'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
devCommand: 'next dev --port $PORT',
|
|
||||||
buildCommand: 'next build',
|
|
||||||
getFsOutputDir: async () => '.next',
|
getFsOutputDir: async () => '.next',
|
||||||
getOutputDirName: async () => 'public',
|
getOutputDirName: async () => 'public',
|
||||||
cachePattern: '.next/cache/**',
|
cachePattern: '.next/cache/**',
|
||||||
@@ -114,7 +112,7 @@ export const frameworks = [
|
|||||||
description:
|
description:
|
||||||
'A Gatsby app, using the default starter theme and a Serverless Function API.',
|
'A Gatsby app, using the default starter theme and a Serverless Function API.',
|
||||||
website: 'https://gatsbyjs.org',
|
website: 'https://gatsbyjs.org',
|
||||||
sort: 2,
|
sort: 5,
|
||||||
envPrefix: 'GATSBY_',
|
envPrefix: 'GATSBY_',
|
||||||
detectors: {
|
detectors: {
|
||||||
every: [
|
every: [
|
||||||
@@ -131,6 +129,7 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
buildCommand: {
|
buildCommand: {
|
||||||
placeholder: '`npm run build` or `gatsby build`',
|
placeholder: '`npm run build` or `gatsby build`',
|
||||||
|
value: 'gatsby build',
|
||||||
},
|
},
|
||||||
devCommand: {
|
devCommand: {
|
||||||
value: 'gatsby develop --port $PORT',
|
value: 'gatsby develop --port $PORT',
|
||||||
@@ -141,8 +140,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: 'gatsby',
|
dependency: 'gatsby',
|
||||||
devCommand: 'gatsby develop --port $PORT',
|
|
||||||
buildCommand: 'gatsby build',
|
|
||||||
getOutputDirName: async () => 'public',
|
getOutputDirName: async () => 'public',
|
||||||
getFsOutputDir: async () => 'public',
|
getFsOutputDir: async () => 'public',
|
||||||
defaultRoutes: async (dirPrefix: string) => {
|
defaultRoutes: async (dirPrefix: string) => {
|
||||||
@@ -194,6 +191,74 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
cachePattern: '{.cache,public}/**',
|
cachePattern: '{.cache,public}/**',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Remix',
|
||||||
|
slug: 'remix',
|
||||||
|
demo: 'https://remix.examples.vercel.com',
|
||||||
|
logo: 'https://raw.githubusercontent.com/vercel/vercel/main/packages/frameworks/logos/remix-no-shadow.svg',
|
||||||
|
tagline: 'Build Better Websites',
|
||||||
|
description: 'A new Remix app — the result of running `npx create-remix`.',
|
||||||
|
website: 'https://remix.run',
|
||||||
|
sort: 6,
|
||||||
|
detectors: {
|
||||||
|
every: [
|
||||||
|
{
|
||||||
|
path: 'package.json',
|
||||||
|
matchContent:
|
||||||
|
'"(dev)?(d|D)ependencies":\\s*{[^}]*"remix":\\s*".+?"[^}]*}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
installCommand: {
|
||||||
|
placeholder: '`yarn install` or `npm install`',
|
||||||
|
},
|
||||||
|
buildCommand: {
|
||||||
|
value: 'remix build',
|
||||||
|
placeholder: '`npm run build` or `remix build`',
|
||||||
|
},
|
||||||
|
devCommand: {
|
||||||
|
value: 'remix dev',
|
||||||
|
placeholder: 'remix dev',
|
||||||
|
},
|
||||||
|
outputDirectory: {
|
||||||
|
value: 'public',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dependency: 'remix',
|
||||||
|
getFsOutputDir: async () => 'public',
|
||||||
|
getOutputDirName: async () => 'public',
|
||||||
|
defaultRoutes: [
|
||||||
|
{
|
||||||
|
src: '^/build/(.*)$',
|
||||||
|
headers: { 'cache-control': 'public, max-age=31536000, immutable' },
|
||||||
|
continue: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
handle: 'filesystem',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: '/(.*)',
|
||||||
|
dest: '/api',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
defaultRewrites: [
|
||||||
|
{
|
||||||
|
source: '/(.*)',
|
||||||
|
regex: '/(.*)',
|
||||||
|
destination: '/api',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
defaultHeaders: [
|
||||||
|
{
|
||||||
|
source: '/build/(.*)',
|
||||||
|
regex: '/build/(.*)',
|
||||||
|
headers: [
|
||||||
|
{ key: 'cache-control', value: 'public, max-age=31536000, immutable' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Hexo',
|
name: 'Hexo',
|
||||||
slug: 'hexo',
|
slug: 'hexo',
|
||||||
@@ -203,7 +268,6 @@ export const frameworks = [
|
|||||||
'Hexo is a fast, simple & powerful blog framework powered by Node.js.',
|
'Hexo is a fast, simple & powerful blog framework powered by Node.js.',
|
||||||
description: 'A Hexo site, created with the Hexo CLI.',
|
description: 'A Hexo site, created with the Hexo CLI.',
|
||||||
website: 'https://hexo.io',
|
website: 'https://hexo.io',
|
||||||
sort: 3,
|
|
||||||
detectors: {
|
detectors: {
|
||||||
every: [
|
every: [
|
||||||
{
|
{
|
||||||
@@ -219,6 +283,7 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
buildCommand: {
|
buildCommand: {
|
||||||
placeholder: '`npm run build` or `hexo generate`',
|
placeholder: '`npm run build` or `hexo generate`',
|
||||||
|
value: 'hexo generate',
|
||||||
},
|
},
|
||||||
devCommand: {
|
devCommand: {
|
||||||
value: 'hexo server --port $PORT',
|
value: 'hexo server --port $PORT',
|
||||||
@@ -229,8 +294,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: 'hexo',
|
dependency: 'hexo',
|
||||||
devCommand: 'hexo server --port $PORT',
|
|
||||||
buildCommand: 'hexo generate',
|
|
||||||
getFsOutputDir: async () => 'public',
|
getFsOutputDir: async () => 'public',
|
||||||
getOutputDirName: async () => 'public',
|
getOutputDirName: async () => 'public',
|
||||||
},
|
},
|
||||||
@@ -243,7 +306,6 @@ export const frameworks = [
|
|||||||
'11ty is a simpler static site generator written in JavaScript, created to be an alternative to Jekyll.',
|
'11ty is a simpler static site generator written in JavaScript, created to be an alternative to Jekyll.',
|
||||||
description: 'An Eleventy site, created with npm init.',
|
description: 'An Eleventy site, created with npm init.',
|
||||||
website: 'https://www.11ty.dev',
|
website: 'https://www.11ty.dev',
|
||||||
sort: 4,
|
|
||||||
detectors: {
|
detectors: {
|
||||||
every: [
|
every: [
|
||||||
{
|
{
|
||||||
@@ -259,6 +321,7 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
buildCommand: {
|
buildCommand: {
|
||||||
placeholder: '`npm run build` or `npx @11ty/eleventy`',
|
placeholder: '`npm run build` or `npx @11ty/eleventy`',
|
||||||
|
value: 'npx @11ty/eleventy',
|
||||||
},
|
},
|
||||||
devCommand: {
|
devCommand: {
|
||||||
value: 'npx @11ty/eleventy --serve --watch --port $PORT',
|
value: 'npx @11ty/eleventy --serve --watch --port $PORT',
|
||||||
@@ -269,8 +332,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: '@11ty/eleventy',
|
dependency: '@11ty/eleventy',
|
||||||
devCommand: 'npx @11ty/eleventy --serve --watch --port $PORT',
|
|
||||||
buildCommand: 'npx @11ty/eleventy',
|
|
||||||
getFsOutputDir: async () => '_site',
|
getFsOutputDir: async () => '_site',
|
||||||
getOutputDirName: async () => '_site',
|
getOutputDirName: async () => '_site',
|
||||||
cachePattern: '.cache/**',
|
cachePattern: '.cache/**',
|
||||||
@@ -300,6 +361,7 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
buildCommand: {
|
buildCommand: {
|
||||||
placeholder: '`npm run build` or `docusaurus build`',
|
placeholder: '`npm run build` or `docusaurus build`',
|
||||||
|
value: 'docusaurus build',
|
||||||
},
|
},
|
||||||
devCommand: {
|
devCommand: {
|
||||||
value: 'docusaurus start --port $PORT',
|
value: 'docusaurus start --port $PORT',
|
||||||
@@ -310,8 +372,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: '@docusaurus/core',
|
dependency: '@docusaurus/core',
|
||||||
devCommand: 'docusaurus start --port $PORT',
|
|
||||||
buildCommand: 'docusaurus build',
|
|
||||||
getFsOutputDir: async (dirPrefix: string) => {
|
getFsOutputDir: async (dirPrefix: string) => {
|
||||||
const base = 'build';
|
const base = 'build';
|
||||||
try {
|
try {
|
||||||
@@ -456,6 +516,7 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
buildCommand: {
|
buildCommand: {
|
||||||
placeholder: '`npm run build` or `docusaurus-build`',
|
placeholder: '`npm run build` or `docusaurus-build`',
|
||||||
|
value: 'docusaurus-build',
|
||||||
},
|
},
|
||||||
devCommand: {
|
devCommand: {
|
||||||
value: 'docusaurus-start --port $PORT',
|
value: 'docusaurus-start --port $PORT',
|
||||||
@@ -466,8 +527,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: 'docusaurus',
|
dependency: 'docusaurus',
|
||||||
devCommand: 'docusaurus-start --port $PORT',
|
|
||||||
buildCommand: 'docusaurus-build',
|
|
||||||
getFsOutputDir: async (dirPrefix: string) => {
|
getFsOutputDir: async (dirPrefix: string) => {
|
||||||
const base = 'build';
|
const base = 'build';
|
||||||
try {
|
try {
|
||||||
@@ -523,6 +582,7 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
buildCommand: {
|
buildCommand: {
|
||||||
placeholder: '`npm run build` or `preact build`',
|
placeholder: '`npm run build` or `preact build`',
|
||||||
|
value: 'preact build',
|
||||||
},
|
},
|
||||||
devCommand: {
|
devCommand: {
|
||||||
value: 'preact watch --port $PORT',
|
value: 'preact watch --port $PORT',
|
||||||
@@ -533,8 +593,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: 'preact-cli',
|
dependency: 'preact-cli',
|
||||||
devCommand: 'preact watch --port $PORT',
|
|
||||||
buildCommand: 'preact build',
|
|
||||||
getFsOutputDir: async () => 'build',
|
getFsOutputDir: async () => 'build',
|
||||||
getOutputDirName: async () => 'build',
|
getOutputDirName: async () => 'build',
|
||||||
defaultRoutes: [
|
defaultRoutes: [
|
||||||
@@ -581,6 +639,7 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
buildCommand: {
|
buildCommand: {
|
||||||
placeholder: '`npm run build` or `dojo build`',
|
placeholder: '`npm run build` or `dojo build`',
|
||||||
|
value: 'dojo build',
|
||||||
},
|
},
|
||||||
devCommand: {
|
devCommand: {
|
||||||
value: 'dojo build -m dev -w -s -p $PORT',
|
value: 'dojo build -m dev -w -s -p $PORT',
|
||||||
@@ -591,8 +650,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: '@dojo/cli',
|
dependency: '@dojo/cli',
|
||||||
devCommand: 'dojo build -m dev -w -s -p $PORT',
|
|
||||||
buildCommand: 'dojo build',
|
|
||||||
getFsOutputDir: async () => 'output/dist',
|
getFsOutputDir: async () => 'output/dist',
|
||||||
getOutputDirName: async () => join('output', 'dist'),
|
getOutputDirName: async () => join('output', 'dist'),
|
||||||
defaultRoutes: [
|
defaultRoutes: [
|
||||||
@@ -649,6 +706,7 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
buildCommand: {
|
buildCommand: {
|
||||||
placeholder: '`npm run build` or `ember build`',
|
placeholder: '`npm run build` or `ember build`',
|
||||||
|
value: 'ember build',
|
||||||
},
|
},
|
||||||
devCommand: {
|
devCommand: {
|
||||||
value: 'ember serve --port $PORT',
|
value: 'ember serve --port $PORT',
|
||||||
@@ -659,8 +717,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: 'ember-cli',
|
dependency: 'ember-cli',
|
||||||
devCommand: 'ember serve --port $PORT',
|
|
||||||
buildCommand: 'ember build',
|
|
||||||
getFsOutputDir: async () => 'dist',
|
getFsOutputDir: async () => 'dist',
|
||||||
getOutputDirName: async () => 'dist',
|
getOutputDirName: async () => 'dist',
|
||||||
defaultRoutes: [
|
defaultRoutes: [
|
||||||
@@ -705,6 +761,7 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
buildCommand: {
|
buildCommand: {
|
||||||
placeholder: '`npm run build` or `vue-cli-service build`',
|
placeholder: '`npm run build` or `vue-cli-service build`',
|
||||||
|
value: 'vue-cli-service build',
|
||||||
},
|
},
|
||||||
devCommand: {
|
devCommand: {
|
||||||
value: 'vue-cli-service serve --port $PORT',
|
value: 'vue-cli-service serve --port $PORT',
|
||||||
@@ -715,8 +772,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: '@vue/cli-service',
|
dependency: '@vue/cli-service',
|
||||||
devCommand: 'vue-cli-service serve --port $PORT',
|
|
||||||
buildCommand: 'vue-cli-service build',
|
|
||||||
getFsOutputDir: async () => 'dist',
|
getFsOutputDir: async () => 'dist',
|
||||||
getOutputDirName: async () => 'dist',
|
getOutputDirName: async () => 'dist',
|
||||||
defaultRoutes: [
|
defaultRoutes: [
|
||||||
@@ -783,6 +838,7 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
buildCommand: {
|
buildCommand: {
|
||||||
placeholder: '`npm run build` or `ng build && scully`',
|
placeholder: '`npm run build` or `ng build && scully`',
|
||||||
|
value: 'ng build && scully',
|
||||||
},
|
},
|
||||||
devCommand: {
|
devCommand: {
|
||||||
value: 'ng serve --port $PORT',
|
value: 'ng serve --port $PORT',
|
||||||
@@ -793,8 +849,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: '@scullyio/init',
|
dependency: '@scullyio/init',
|
||||||
devCommand: 'ng serve --port $PORT',
|
|
||||||
buildCommand: 'ng build && scully',
|
|
||||||
getFsOutputDir: async () => 'dist',
|
getFsOutputDir: async () => 'dist',
|
||||||
getOutputDirName: async () => 'dist/static',
|
getOutputDirName: async () => 'dist/static',
|
||||||
},
|
},
|
||||||
@@ -822,6 +876,7 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
buildCommand: {
|
buildCommand: {
|
||||||
placeholder: '`npm run build` or `ng build`',
|
placeholder: '`npm run build` or `ng build`',
|
||||||
|
value: 'ng build',
|
||||||
},
|
},
|
||||||
devCommand: {
|
devCommand: {
|
||||||
value: 'ng serve --port $PORT',
|
value: 'ng serve --port $PORT',
|
||||||
@@ -831,8 +886,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: '@ionic/angular',
|
dependency: '@ionic/angular',
|
||||||
devCommand: 'ng serve --port $PORT',
|
|
||||||
buildCommand: 'ng build',
|
|
||||||
getFsOutputDir: async () => 'www',
|
getFsOutputDir: async () => 'www',
|
||||||
getOutputDirName: async () => 'www',
|
getOutputDirName: async () => 'www',
|
||||||
defaultRoutes: [
|
defaultRoutes: [
|
||||||
@@ -876,6 +929,7 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
buildCommand: {
|
buildCommand: {
|
||||||
placeholder: '`npm run build` or `ng build`',
|
placeholder: '`npm run build` or `ng build`',
|
||||||
|
value: 'ng build',
|
||||||
},
|
},
|
||||||
devCommand: {
|
devCommand: {
|
||||||
value: 'ng serve --port $PORT',
|
value: 'ng serve --port $PORT',
|
||||||
@@ -886,8 +940,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: '@angular/cli',
|
dependency: '@angular/cli',
|
||||||
devCommand: 'ng serve --port $PORT',
|
|
||||||
buildCommand: 'ng build',
|
|
||||||
getFsOutputDir: async () => 'dist',
|
getFsOutputDir: async () => 'dist',
|
||||||
getOutputDirName: async (dirPrefix: string) => {
|
getOutputDirName: async (dirPrefix: string) => {
|
||||||
const base = 'dist';
|
const base = 'dist';
|
||||||
@@ -945,6 +997,7 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
buildCommand: {
|
buildCommand: {
|
||||||
placeholder: '`npm run build` or `polymer build`',
|
placeholder: '`npm run build` or `polymer build`',
|
||||||
|
value: 'polymer build',
|
||||||
},
|
},
|
||||||
devCommand: {
|
devCommand: {
|
||||||
value: 'polymer serve --port $PORT',
|
value: 'polymer serve --port $PORT',
|
||||||
@@ -955,8 +1008,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: 'polymer-cli',
|
dependency: 'polymer-cli',
|
||||||
devCommand: 'polymer serve --port $PORT',
|
|
||||||
buildCommand: 'polymer build',
|
|
||||||
getFsOutputDir: async () => 'build',
|
getFsOutputDir: async () => 'build',
|
||||||
getOutputDirName: async (dirPrefix: string) => {
|
getOutputDirName: async (dirPrefix: string) => {
|
||||||
const base = 'build';
|
const base = 'build';
|
||||||
@@ -996,6 +1047,7 @@ export const frameworks = [
|
|||||||
'Svelte lets you write high performance reactive apps with significantly less boilerplate.',
|
'Svelte lets you write high performance reactive apps with significantly less boilerplate.',
|
||||||
description: 'A basic Svelte app using the default template.',
|
description: 'A basic Svelte app using the default template.',
|
||||||
website: 'https://svelte.dev',
|
website: 'https://svelte.dev',
|
||||||
|
sort: 3,
|
||||||
detectors: {
|
detectors: {
|
||||||
every: [
|
every: [
|
||||||
{
|
{
|
||||||
@@ -1016,6 +1068,7 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
buildCommand: {
|
buildCommand: {
|
||||||
placeholder: '`npm run build` or `rollup -c`',
|
placeholder: '`npm run build` or `rollup -c`',
|
||||||
|
value: 'rollup -c',
|
||||||
},
|
},
|
||||||
devCommand: {
|
devCommand: {
|
||||||
value: 'rollup -c -w',
|
value: 'rollup -c -w',
|
||||||
@@ -1025,8 +1078,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: 'sirv-cli',
|
dependency: 'sirv-cli',
|
||||||
devCommand: 'rollup -c -w',
|
|
||||||
buildCommand: 'rollup -c',
|
|
||||||
getFsOutputDir: async () => 'public',
|
getFsOutputDir: async () => 'public',
|
||||||
getOutputDirName: async () => 'public',
|
getOutputDirName: async () => 'public',
|
||||||
defaultRoutes: [
|
defaultRoutes: [
|
||||||
@@ -1070,6 +1121,7 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
buildCommand: {
|
buildCommand: {
|
||||||
placeholder: '`npm run build` or `svelte-kit build`',
|
placeholder: '`npm run build` or `svelte-kit build`',
|
||||||
|
value: 'svelte-kit build',
|
||||||
},
|
},
|
||||||
devCommand: {
|
devCommand: {
|
||||||
value: 'svelte-kit dev --port $PORT',
|
value: 'svelte-kit dev --port $PORT',
|
||||||
@@ -1079,8 +1131,6 @@ export const frameworks = [
|
|||||||
placeholder: 'public',
|
placeholder: 'public',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
devCommand: 'svelte-kit dev --port $PORT',
|
|
||||||
buildCommand: 'svelte-kit build',
|
|
||||||
getFsOutputDir: async () => '.output',
|
getFsOutputDir: async () => '.output',
|
||||||
getOutputDirName: async () => 'public',
|
getOutputDirName: async () => 'public',
|
||||||
},
|
},
|
||||||
@@ -1108,6 +1158,7 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
buildCommand: {
|
buildCommand: {
|
||||||
placeholder: '`npm run build` or `react-scripts build`',
|
placeholder: '`npm run build` or `react-scripts build`',
|
||||||
|
value: 'react-scripts build',
|
||||||
},
|
},
|
||||||
devCommand: {
|
devCommand: {
|
||||||
value: 'react-scripts start',
|
value: 'react-scripts start',
|
||||||
@@ -1117,8 +1168,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: '@ionic/react',
|
dependency: '@ionic/react',
|
||||||
devCommand: 'react-scripts start',
|
|
||||||
buildCommand: 'react-scripts build',
|
|
||||||
getFsOutputDir: async () => 'build',
|
getFsOutputDir: async () => 'build',
|
||||||
getOutputDirName: async () => 'build',
|
getOutputDirName: async () => 'build',
|
||||||
defaultRoutes: [
|
defaultRoutes: [
|
||||||
@@ -1195,6 +1244,7 @@ export const frameworks = [
|
|||||||
description:
|
description:
|
||||||
'A React app, bootstrapped with create-react-app, and a Serverless Function API.',
|
'A React app, bootstrapped with create-react-app, and a Serverless Function API.',
|
||||||
website: 'https://create-react-app.dev',
|
website: 'https://create-react-app.dev',
|
||||||
|
sort: 4,
|
||||||
envPrefix: 'REACT_APP_',
|
envPrefix: 'REACT_APP_',
|
||||||
detectors: {
|
detectors: {
|
||||||
some: [
|
some: [
|
||||||
@@ -1216,6 +1266,7 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
buildCommand: {
|
buildCommand: {
|
||||||
placeholder: '`npm run build` or `react-scripts build`',
|
placeholder: '`npm run build` or `react-scripts build`',
|
||||||
|
value: 'react-scripts build',
|
||||||
},
|
},
|
||||||
devCommand: {
|
devCommand: {
|
||||||
value: 'react-scripts start',
|
value: 'react-scripts start',
|
||||||
@@ -1225,8 +1276,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: 'react-scripts',
|
dependency: 'react-scripts',
|
||||||
devCommand: 'react-scripts start',
|
|
||||||
buildCommand: 'react-scripts build',
|
|
||||||
getFsOutputDir: async () => 'build',
|
getFsOutputDir: async () => 'build',
|
||||||
getOutputDirName: async () => 'build',
|
getOutputDirName: async () => 'build',
|
||||||
defaultRoutes: [
|
defaultRoutes: [
|
||||||
@@ -1318,6 +1367,7 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
buildCommand: {
|
buildCommand: {
|
||||||
placeholder: '`npm run build` or `gridsome build`',
|
placeholder: '`npm run build` or `gridsome build`',
|
||||||
|
value: 'gridsome build',
|
||||||
},
|
},
|
||||||
devCommand: {
|
devCommand: {
|
||||||
value: 'gridsome develop -p $PORT',
|
value: 'gridsome develop -p $PORT',
|
||||||
@@ -1328,8 +1378,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: 'gridsome',
|
dependency: 'gridsome',
|
||||||
devCommand: 'gridsome develop -p $PORT',
|
|
||||||
buildCommand: 'gridsome build',
|
|
||||||
getFsOutputDir: async () => 'dist',
|
getFsOutputDir: async () => 'dist',
|
||||||
getOutputDirName: async () => 'dist',
|
getOutputDirName: async () => 'dist',
|
||||||
},
|
},
|
||||||
@@ -1357,6 +1405,7 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
buildCommand: {
|
buildCommand: {
|
||||||
placeholder: '`npm run build` or `umi build`',
|
placeholder: '`npm run build` or `umi build`',
|
||||||
|
value: 'umi build',
|
||||||
},
|
},
|
||||||
devCommand: {
|
devCommand: {
|
||||||
value: 'umi dev --port $PORT',
|
value: 'umi dev --port $PORT',
|
||||||
@@ -1367,8 +1416,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: 'umi',
|
dependency: 'umi',
|
||||||
devCommand: 'umi dev --port $PORT',
|
|
||||||
buildCommand: 'umi build',
|
|
||||||
getFsOutputDir: async () => 'dist',
|
getFsOutputDir: async () => 'dist',
|
||||||
getOutputDirName: async () => 'dist',
|
getOutputDirName: async () => 'dist',
|
||||||
defaultRoutes: [
|
defaultRoutes: [
|
||||||
@@ -1412,6 +1459,7 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
buildCommand: {
|
buildCommand: {
|
||||||
placeholder: '`npm run build` or `sapper export`',
|
placeholder: '`npm run build` or `sapper export`',
|
||||||
|
value: 'sapper export',
|
||||||
},
|
},
|
||||||
devCommand: {
|
devCommand: {
|
||||||
value: 'sapper dev --port $PORT',
|
value: 'sapper dev --port $PORT',
|
||||||
@@ -1422,8 +1470,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: 'sapper',
|
dependency: 'sapper',
|
||||||
devCommand: 'sapper dev --port $PORT',
|
|
||||||
buildCommand: 'sapper export',
|
|
||||||
getFsOutputDir: async () => '__sapper__/export',
|
getFsOutputDir: async () => '__sapper__/export',
|
||||||
getOutputDirName: async () => '__sapper__/export',
|
getOutputDirName: async () => '__sapper__/export',
|
||||||
},
|
},
|
||||||
@@ -1451,6 +1497,7 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
buildCommand: {
|
buildCommand: {
|
||||||
placeholder: '`npm run build` or `saber build`',
|
placeholder: '`npm run build` or `saber build`',
|
||||||
|
value: 'saber build',
|
||||||
},
|
},
|
||||||
devCommand: {
|
devCommand: {
|
||||||
value: 'saber --port $PORT',
|
value: 'saber --port $PORT',
|
||||||
@@ -1461,8 +1508,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: 'saber',
|
dependency: 'saber',
|
||||||
devCommand: 'saber --port $PORT',
|
|
||||||
buildCommand: 'saber build',
|
|
||||||
getFsOutputDir: async () => 'public',
|
getFsOutputDir: async () => 'public',
|
||||||
getOutputDirName: async () => 'public',
|
getOutputDirName: async () => 'public',
|
||||||
defaultRoutes: [
|
defaultRoutes: [
|
||||||
@@ -1521,6 +1566,7 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
buildCommand: {
|
buildCommand: {
|
||||||
placeholder: '`npm run build` or `stencil build`',
|
placeholder: '`npm run build` or `stencil build`',
|
||||||
|
value: 'stencil build',
|
||||||
},
|
},
|
||||||
devCommand: {
|
devCommand: {
|
||||||
value: 'stencil build --dev --watch --serve --port $PORT',
|
value: 'stencil build --dev --watch --serve --port $PORT',
|
||||||
@@ -1531,8 +1577,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: '@stencil/core',
|
dependency: '@stencil/core',
|
||||||
devCommand: 'stencil build --dev --watch --serve --port $PORT',
|
|
||||||
buildCommand: 'stencil build',
|
|
||||||
getFsOutputDir: async () => 'www',
|
getFsOutputDir: async () => 'www',
|
||||||
getOutputDirName: async () => 'www',
|
getOutputDirName: async () => 'www',
|
||||||
defaultRoutes: [
|
defaultRoutes: [
|
||||||
@@ -1595,6 +1639,7 @@ export const frameworks = [
|
|||||||
'Nuxt.js is the web comprehensive framework that lets you dream big with Vue.js.',
|
'Nuxt.js is the web comprehensive framework that lets you dream big with Vue.js.',
|
||||||
description: 'A Nuxt.js app, bootstrapped with create-nuxt-app.',
|
description: 'A Nuxt.js app, bootstrapped with create-nuxt-app.',
|
||||||
website: 'https://nuxtjs.org',
|
website: 'https://nuxtjs.org',
|
||||||
|
sort: 2,
|
||||||
envPrefix: 'NUXT_ENV_',
|
envPrefix: 'NUXT_ENV_',
|
||||||
detectors: {
|
detectors: {
|
||||||
every: [
|
every: [
|
||||||
@@ -1611,6 +1656,7 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
buildCommand: {
|
buildCommand: {
|
||||||
placeholder: '`npm run build` or `nuxt generate`',
|
placeholder: '`npm run build` or `nuxt generate`',
|
||||||
|
value: 'nuxt generate',
|
||||||
},
|
},
|
||||||
devCommand: {
|
devCommand: {
|
||||||
value: 'nuxt',
|
value: 'nuxt',
|
||||||
@@ -1620,8 +1666,6 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: 'nuxt',
|
dependency: 'nuxt',
|
||||||
devCommand: 'nuxt',
|
|
||||||
buildCommand: 'nuxt generate',
|
|
||||||
getFsOutputDir: async () => '.output',
|
getFsOutputDir: async () => '.output',
|
||||||
getOutputDirName: async () => 'dist',
|
getOutputDirName: async () => 'dist',
|
||||||
cachePattern: '.nuxt/**',
|
cachePattern: '.nuxt/**',
|
||||||
@@ -1680,8 +1724,6 @@ export const frameworks = [
|
|||||||
placeholder: 'RedwoodJS default',
|
placeholder: 'RedwoodJS default',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
devCommand: 'yarn rw dev --fwd="--port=$PORT --open=false',
|
|
||||||
buildCommand: 'yarn rw deploy vercel',
|
|
||||||
getFsOutputDir: async () => 'public',
|
getFsOutputDir: async () => 'public',
|
||||||
getOutputDirName: async () => 'public',
|
getOutputDirName: async () => 'public',
|
||||||
},
|
},
|
||||||
@@ -1694,7 +1736,6 @@ export const frameworks = [
|
|||||||
'Hugo is the world’s fastest framework for building websites, written in Go.',
|
'Hugo is the world’s fastest framework for building websites, written in Go.',
|
||||||
description: 'A Hugo site, created with the Hugo CLI.',
|
description: 'A Hugo site, created with the Hugo CLI.',
|
||||||
website: 'https://gohugo.io',
|
website: 'https://gohugo.io',
|
||||||
sort: 5,
|
|
||||||
detectors: {
|
detectors: {
|
||||||
some: [
|
some: [
|
||||||
{
|
{
|
||||||
@@ -1717,6 +1758,7 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
buildCommand: {
|
buildCommand: {
|
||||||
placeholder: '`npm run build` or `hugo -D --gc`',
|
placeholder: '`npm run build` or `hugo -D --gc`',
|
||||||
|
value: 'hugo -D --gc',
|
||||||
},
|
},
|
||||||
devCommand: {
|
devCommand: {
|
||||||
value: 'hugo server -D -w -p $PORT',
|
value: 'hugo server -D -w -p $PORT',
|
||||||
@@ -1726,8 +1768,6 @@ export const frameworks = [
|
|||||||
placeholder: '`public` or `publishDir` from the `config` file',
|
placeholder: '`public` or `publishDir` from the `config` file',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
devCommand: 'hugo server -D -w -p $PORT',
|
|
||||||
buildCommand: 'hugo -D --gc',
|
|
||||||
getFsOutputDir: async (dirPrefix: string): Promise<string> => {
|
getFsOutputDir: async (dirPrefix: string): Promise<string> => {
|
||||||
type HugoConfig = { publishDir?: string };
|
type HugoConfig = { publishDir?: string };
|
||||||
const config = await readConfigFile<HugoConfig>(
|
const config = await readConfigFile<HugoConfig>(
|
||||||
@@ -1772,6 +1812,7 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
buildCommand: {
|
buildCommand: {
|
||||||
placeholder: '`npm run build` or `jekyll build`',
|
placeholder: '`npm run build` or `jekyll build`',
|
||||||
|
value: 'jekyll build',
|
||||||
},
|
},
|
||||||
devCommand: {
|
devCommand: {
|
||||||
value: 'bundle exec jekyll serve --watch --port $PORT',
|
value: 'bundle exec jekyll serve --watch --port $PORT',
|
||||||
@@ -1781,8 +1822,6 @@ export const frameworks = [
|
|||||||
placeholder: '`_site` or `destination` from `_config.yml`',
|
placeholder: '`_site` or `destination` from `_config.yml`',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
devCommand: 'bundle exec jekyll serve --watch --port $PORT',
|
|
||||||
buildCommand: 'jekyll build',
|
|
||||||
getFsOutputDir: async (dirPrefix: string): Promise<string> => {
|
getFsOutputDir: async (dirPrefix: string): Promise<string> => {
|
||||||
type JekyllConfig = { destination?: string };
|
type JekyllConfig = { destination?: string };
|
||||||
const config = await readConfigFile<JekyllConfig>(
|
const config = await readConfigFile<JekyllConfig>(
|
||||||
@@ -1821,6 +1860,7 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
buildCommand: {
|
buildCommand: {
|
||||||
placeholder: '`npm run build` or `brunch build --production`',
|
placeholder: '`npm run build` or `brunch build --production`',
|
||||||
|
value: 'brunch build --production',
|
||||||
},
|
},
|
||||||
devCommand: {
|
devCommand: {
|
||||||
value: 'brunch watch --server --port $PORT',
|
value: 'brunch watch --server --port $PORT',
|
||||||
@@ -1830,8 +1870,6 @@ export const frameworks = [
|
|||||||
value: 'public',
|
value: 'public',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
devCommand: 'brunch watch --server --port $PORT',
|
|
||||||
buildCommand: 'brunch build --production',
|
|
||||||
getFsOutputDir: async () => 'public',
|
getFsOutputDir: async () => 'public',
|
||||||
getOutputDirName: async () => 'public',
|
getOutputDirName: async () => 'public',
|
||||||
},
|
},
|
||||||
@@ -1856,18 +1894,17 @@ export const frameworks = [
|
|||||||
value: 'bundle install',
|
value: 'bundle install',
|
||||||
},
|
},
|
||||||
buildCommand: {
|
buildCommand: {
|
||||||
value: '`npm run build` or `bundle exec middleman build`',
|
placeholder: '`npm run build` or `bundle exec middleman build`',
|
||||||
|
value: 'bundle exec middleman build',
|
||||||
},
|
},
|
||||||
devCommand: {
|
devCommand: {
|
||||||
value: 'bundle exec middleman server -p $PORT',
|
|
||||||
placeholder: 'bundle exec middleman server',
|
placeholder: 'bundle exec middleman server',
|
||||||
|
value: 'bundle exec middleman server -p $PORT',
|
||||||
},
|
},
|
||||||
outputDirectory: {
|
outputDirectory: {
|
||||||
value: 'build',
|
value: 'build',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
devCommand: 'bundle exec middleman server -p $PORT',
|
|
||||||
buildCommand: 'bundle exec middleman build',
|
|
||||||
getFsOutputDir: async () => 'build',
|
getFsOutputDir: async () => 'build',
|
||||||
getOutputDirName: async () => 'build',
|
getOutputDirName: async () => 'build',
|
||||||
cachePattern: '{vendor/bin,vendor/cache,vendor/bundle}/**',
|
cachePattern: '{vendor/bin,vendor/cache,vendor/bundle}/**',
|
||||||
@@ -1896,15 +1933,13 @@ export const frameworks = [
|
|||||||
value: 'zola build',
|
value: 'zola build',
|
||||||
},
|
},
|
||||||
devCommand: {
|
devCommand: {
|
||||||
value: 'zola serve --port $PORT',
|
|
||||||
placeholder: 'zola serve',
|
placeholder: 'zola serve',
|
||||||
|
value: 'zola serve --port $PORT',
|
||||||
},
|
},
|
||||||
outputDirectory: {
|
outputDirectory: {
|
||||||
value: 'public',
|
value: 'public',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
devCommand: 'zola serve --port $PORT',
|
|
||||||
buildCommand: 'zola build',
|
|
||||||
getFsOutputDir: async () => 'public',
|
getFsOutputDir: async () => 'public',
|
||||||
getOutputDirName: async () => 'public',
|
getOutputDirName: async () => 'public',
|
||||||
defaultVersion: '0.13.0',
|
defaultVersion: '0.13.0',
|
||||||
@@ -1934,17 +1969,17 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
buildCommand: {
|
buildCommand: {
|
||||||
placeholder: '`npm run build` or `vite build`',
|
placeholder: '`npm run build` or `vite build`',
|
||||||
|
value: 'vite build',
|
||||||
},
|
},
|
||||||
devCommand: {
|
devCommand: {
|
||||||
placeholder: 'vite',
|
placeholder: 'vite',
|
||||||
|
value: 'vite',
|
||||||
},
|
},
|
||||||
outputDirectory: {
|
outputDirectory: {
|
||||||
value: 'dist',
|
value: 'dist',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: 'vite',
|
dependency: 'vite',
|
||||||
devCommand: 'vite',
|
|
||||||
buildCommand: 'vite build',
|
|
||||||
getFsOutputDir: async () => 'dist',
|
getFsOutputDir: async () => 'dist',
|
||||||
getOutputDirName: async () => 'dist',
|
getOutputDirName: async () => 'dist',
|
||||||
},
|
},
|
||||||
@@ -1972,17 +2007,17 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
buildCommand: {
|
buildCommand: {
|
||||||
placeholder: '`npm run build` or `parcel build`',
|
placeholder: '`npm run build` or `parcel build`',
|
||||||
|
value: 'parcel build',
|
||||||
},
|
},
|
||||||
devCommand: {
|
devCommand: {
|
||||||
placeholder: 'parcel',
|
placeholder: 'parcel',
|
||||||
|
value: 'parcel',
|
||||||
},
|
},
|
||||||
outputDirectory: {
|
outputDirectory: {
|
||||||
placeholder: 'dist',
|
value: 'dist',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependency: 'parcel',
|
dependency: 'parcel',
|
||||||
devCommand: 'parcel',
|
|
||||||
buildCommand: 'parcel build',
|
|
||||||
getFsOutputDir: async () => 'dist',
|
getFsOutputDir: async () => 'dist',
|
||||||
getOutputDirName: async () => 'dist',
|
getOutputDirName: async () => 'dist',
|
||||||
defaultRoutes: [
|
defaultRoutes: [
|
||||||
@@ -2016,16 +2051,16 @@ export const frameworks = [
|
|||||||
},
|
},
|
||||||
buildCommand: {
|
buildCommand: {
|
||||||
placeholder: '`npm run vercel-build` or `npm run build`',
|
placeholder: '`npm run vercel-build` or `npm run build`',
|
||||||
|
value: null,
|
||||||
},
|
},
|
||||||
devCommand: {
|
devCommand: {
|
||||||
placeholder: 'None',
|
placeholder: 'None',
|
||||||
|
value: null,
|
||||||
},
|
},
|
||||||
outputDirectory: {
|
outputDirectory: {
|
||||||
placeholder: '`public` if it exists, or `.`',
|
placeholder: '`public` if it exists, or `.`',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
devCommand: null,
|
|
||||||
buildCommand: null,
|
|
||||||
getFsOutputDir: async (dirPrefix: string): Promise<string> => {
|
getFsOutputDir: async (dirPrefix: string): Promise<string> => {
|
||||||
// Public if it exists or `.`
|
// Public if it exists or `.`
|
||||||
let base = 'public';
|
let base = 'public';
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export interface SettingValue {
|
|||||||
* A predefined setting for the detected framework
|
* A predefined setting for the detected framework
|
||||||
* @example "next dev --port $PORT"
|
* @example "next dev --port $PORT"
|
||||||
*/
|
*/
|
||||||
value: string;
|
value: string | null;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,11 +129,11 @@ export interface Framework {
|
|||||||
/**
|
/**
|
||||||
* Default Build Command or a placeholder
|
* Default Build Command or a placeholder
|
||||||
*/
|
*/
|
||||||
buildCommand: Setting;
|
buildCommand: SettingValue;
|
||||||
/**
|
/**
|
||||||
* Default Development Command or a placeholder
|
* Default Development Command or a placeholder
|
||||||
*/
|
*/
|
||||||
devCommand: Setting;
|
devCommand: SettingValue;
|
||||||
/**
|
/**
|
||||||
* Default Output Directory
|
* Default Output Directory
|
||||||
*/
|
*/
|
||||||
@@ -157,6 +157,7 @@ export interface Framework {
|
|||||||
/**
|
/**
|
||||||
* Name of a dependency in `package.json` to detect this framework.
|
* Name of a dependency in `package.json` to detect this framework.
|
||||||
* @example "hexo"
|
* @example "hexo"
|
||||||
|
* @deprecated use `detectors` instead (new frameworks should not use this prop)
|
||||||
*/
|
*/
|
||||||
dependency?: string;
|
dependency?: string;
|
||||||
/**
|
/**
|
||||||
@@ -201,16 +202,6 @@ export interface Framework {
|
|||||||
* @example ".cache/**"
|
* @example ".cache/**"
|
||||||
*/
|
*/
|
||||||
cachePattern?: string;
|
cachePattern?: string;
|
||||||
/**
|
|
||||||
* The default build command for the framework.
|
|
||||||
* @example "next build"
|
|
||||||
*/
|
|
||||||
buildCommand: string | null;
|
|
||||||
/**
|
|
||||||
* The default development command for the framework.
|
|
||||||
* @example "next dev"
|
|
||||||
*/
|
|
||||||
devCommand: string | null;
|
|
||||||
/**
|
/**
|
||||||
* The default version of the framework command that is available within the
|
* The default version of the framework command that is available within the
|
||||||
* build image. Usually an environment variable can be set to override this.
|
* build image. Usually an environment variable can be set to override this.
|
||||||
|
|||||||
14
packages/frameworks/test/frameworks.unit.test.ts
vendored
14
packages/frameworks/test/frameworks.unit.test.ts
vendored
@@ -34,7 +34,7 @@ const SchemaSettings = {
|
|||||||
additionalProperties: false,
|
additionalProperties: false,
|
||||||
properties: {
|
properties: {
|
||||||
value: {
|
value: {
|
||||||
type: 'string',
|
type: ['string', 'null'],
|
||||||
},
|
},
|
||||||
placeholder: {
|
placeholder: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
@@ -58,15 +58,7 @@ const Schema = {
|
|||||||
type: 'array',
|
type: 'array',
|
||||||
items: {
|
items: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
required: [
|
required: ['name', 'slug', 'logo', 'description', 'settings'],
|
||||||
'name',
|
|
||||||
'slug',
|
|
||||||
'logo',
|
|
||||||
'description',
|
|
||||||
'settings',
|
|
||||||
'buildCommand',
|
|
||||||
'devCommand',
|
|
||||||
],
|
|
||||||
properties: {
|
properties: {
|
||||||
name: { type: 'string' },
|
name: { type: 'string' },
|
||||||
slug: { type: ['string', 'null'] },
|
slug: { type: ['string', 'null'] },
|
||||||
@@ -138,8 +130,6 @@ const Schema = {
|
|||||||
|
|
||||||
dependency: { type: 'string' },
|
dependency: { type: 'string' },
|
||||||
cachePattern: { type: 'string' },
|
cachePattern: { type: 'string' },
|
||||||
buildCommand: { type: ['string', 'null'] },
|
|
||||||
devCommand: { type: ['string', 'null'] },
|
|
||||||
defaultVersion: { type: 'string' },
|
defaultVersion: { type: 'string' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ import { join } from 'path';
|
|||||||
import stringArgv from 'string-argv';
|
import stringArgv from 'string-argv';
|
||||||
import { debug } from '@vercel/build-utils';
|
import { debug } from '@vercel/build-utils';
|
||||||
const versionMap = new Map([
|
const versionMap = new Map([
|
||||||
['1.16', '1.16'],
|
['1.17', '1.17.3'],
|
||||||
|
['1.16', '1.16.10'],
|
||||||
['1.15', '1.15.8'],
|
['1.15', '1.15.8'],
|
||||||
['1.14', '1.14.15'],
|
['1.14', '1.14.15'],
|
||||||
['1.13', '1.13.15'],
|
['1.13', '1.13.15'],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vercel/go",
|
"name": "@vercel/go",
|
||||||
"version": "1.2.4-canary.3",
|
"version": "1.2.4-canary.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/index",
|
"main": "./dist/index",
|
||||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/go",
|
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/go",
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
{ "src": "subdirectory/index.go", "use": "@vercel/go" }
|
{ "src": "subdirectory/index.go", "use": "@vercel/go" }
|
||||||
],
|
],
|
||||||
"probes": [
|
"probes": [
|
||||||
{ "path": "/", "mustContain": "cow:go1.16:RANDOMNESS_PLACEHOLDER" },
|
{ "path": "/", "mustContain": "cow:go1.17.3:RANDOMNESS_PLACEHOLDER" },
|
||||||
{
|
{
|
||||||
"path": "/subdirectory",
|
"path": "/subdirectory",
|
||||||
"mustContain": "subcow:go1.16:RANDOMNESS_PLACEHOLDER"
|
"mustContain": "subcow:go1.17.3:RANDOMNESS_PLACEHOLDER"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
module with-nested
|
module with-nested
|
||||||
|
|
||||||
go 1.12
|
go 1.16
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "vercel-plugin-middleware",
|
"name": "vercel-plugin-middleware",
|
||||||
"version": "0.0.0-canary.6",
|
"version": "0.0.0-canary.7",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/index",
|
"main": "./dist/index",
|
||||||
"homepage": "",
|
"homepage": "",
|
||||||
|
|||||||
@@ -3,13 +3,16 @@ _ENTRIES = typeof _ENTRIES === 'undefined' ? {} : _ENTRIES;
|
|||||||
_ENTRIES['middleware_pages/_middleware'] = {
|
_ENTRIES['middleware_pages/_middleware'] = {
|
||||||
default: async function (ev) {
|
default: async function (ev) {
|
||||||
const result = await middleware.default(ev.request, ev);
|
const result = await middleware.default(ev.request, ev);
|
||||||
if (result instanceof Response) {
|
return {
|
||||||
return {
|
promise: Promise.resolve(),
|
||||||
promise: Promise.resolve(),
|
waitUntil: Promise.resolve(),
|
||||||
waitUntil: Promise.resolve(),
|
response:
|
||||||
response: result,
|
result ||
|
||||||
};
|
new Response(null, {
|
||||||
}
|
headers: {
|
||||||
return result;
|
'x-middleware-next': 1,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
37
packages/middleware/test/build.test.ts
vendored
37
packages/middleware/test/build.test.ts
vendored
@@ -1,8 +1,19 @@
|
|||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { promises as fsp } from 'fs';
|
import { promises as fsp } from 'fs';
|
||||||
import { build } from '../src';
|
import { build } from '../src';
|
||||||
|
import { Response } from 'node-fetch';
|
||||||
|
|
||||||
describe('build()', () => {
|
describe('build()', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
//@ts-ignore
|
||||||
|
global.Response = Response;
|
||||||
|
});
|
||||||
|
afterEach(() => {
|
||||||
|
//@ts-ignore
|
||||||
|
delete global.Response;
|
||||||
|
//@ts-ignore
|
||||||
|
delete global._ENTRIES;
|
||||||
|
});
|
||||||
it('should build simple middleware', async () => {
|
it('should build simple middleware', async () => {
|
||||||
const fixture = join(__dirname, 'fixtures/simple');
|
const fixture = join(__dirname, 'fixtures/simple');
|
||||||
await build({
|
await build({
|
||||||
@@ -17,8 +28,30 @@ describe('build()', () => {
|
|||||||
);
|
);
|
||||||
expect(middlewareManifest).toMatchSnapshot();
|
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');
|
||||||
|
const handledResponse = await middleware({
|
||||||
|
request: {
|
||||||
|
url: 'http://google.com',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const unhandledResponse = await middleware({
|
||||||
|
request: {
|
||||||
|
url: 'literallyanythingelse',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(String(handledResponse.response.body)).toEqual('Hi from the edge!');
|
||||||
expect(
|
expect(
|
||||||
await fsp.stat(join(fixture, '.output/server/pages/_middleware.js'))
|
(handledResponse.response as Response).headers.get('x-middleware-next')
|
||||||
).toBeTruthy();
|
).toEqual(null);
|
||||||
|
expect(unhandledResponse.response.body).toEqual(null);
|
||||||
|
expect(
|
||||||
|
(unhandledResponse.response as Response).headers.get('x-middleware-next')
|
||||||
|
).toEqual('1');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1 +1,5 @@
|
|||||||
export default () => new Response('Hi from the edge!');
|
export default req => {
|
||||||
|
if (req.url === 'http://google.com') {
|
||||||
|
return new Response('Hi from the edge!');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -33,14 +33,6 @@ async function main() {
|
|||||||
await fs.remove(symlinkTarget);
|
await fs.remove(symlinkTarget);
|
||||||
await fs.symlink('symlinked-asset', symlinkTarget);
|
await fs.symlink('symlinked-asset', symlinkTarget);
|
||||||
|
|
||||||
// Use types.d.ts as the main types export
|
|
||||||
await Promise.all(
|
|
||||||
(await fs.readdir(outDir))
|
|
||||||
.filter(p => p.endsWith('.d.ts') && p !== 'types.d.ts')
|
|
||||||
.map(p => fs.remove(join(outDir, p)))
|
|
||||||
);
|
|
||||||
await fs.rename(join(outDir, 'types.d.ts'), join(outDir, 'index.d.ts'));
|
|
||||||
|
|
||||||
// Bundle helpers.ts with ncc
|
// Bundle helpers.ts with ncc
|
||||||
await fs.remove(join(outDir, 'helpers.js'));
|
await fs.remove(join(outDir, 'helpers.js'));
|
||||||
const helpersDir = join(outDir, 'helpers');
|
const helpersDir = join(outDir, 'helpers');
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vercel/node",
|
"name": "@vercel/node",
|
||||||
"version": "1.12.2-canary.6",
|
"version": "1.12.2-canary.7",
|
||||||
"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",
|
||||||
|
|||||||
@@ -339,6 +339,9 @@ function getAWSLambdaHandler(entrypoint: string, config: Config) {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensures that everything from `types.ts` is exported in the final `index.d.ts` file.
|
||||||
|
export * from './types';
|
||||||
|
|
||||||
export const version = 3;
|
export const version = 3;
|
||||||
|
|
||||||
export async function build({
|
export async function build({
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"private": false,
|
"private": false,
|
||||||
"name": "vercel-plugin-go",
|
"name": "vercel-plugin-go",
|
||||||
"version": "1.0.0-canary.1",
|
"version": "1.0.0-canary.12",
|
||||||
"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.17",
|
"@vercel/build-utils": "2.12.3-canary.27",
|
||||||
"@vercel/go": "1.2.4-canary.3"
|
"@vercel/go": "1.2.4-canary.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "vercel-plugin-node",
|
"name": "vercel-plugin-node",
|
||||||
"version": "1.12.2-canary.7",
|
"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",
|
||||||
@@ -33,7 +33,9 @@
|
|||||||
"@types/jest": "27.0.2",
|
"@types/jest": "27.0.2",
|
||||||
"@types/node-fetch": "2",
|
"@types/node-fetch": "2",
|
||||||
"@types/test-listen": "1.1.0",
|
"@types/test-listen": "1.1.0",
|
||||||
"@vercel/fun": "1.0.1",
|
"@types/yazl": "2.4.2",
|
||||||
|
"@vercel/build-utils": "2.12.3-canary.27",
|
||||||
|
"@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",
|
||||||
@@ -46,7 +48,8 @@
|
|||||||
"node-fetch": "2",
|
"node-fetch": "2",
|
||||||
"source-map-support": "0.5.12",
|
"source-map-support": "0.5.12",
|
||||||
"test-listen": "1.1.0",
|
"test-listen": "1.1.0",
|
||||||
"ts-morph": "12.0.0"
|
"ts-morph": "12.0.0",
|
||||||
|
"yazl": "2.5.1"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"preset": "ts-jest",
|
"preset": "ts-jest",
|
||||||
|
|||||||
@@ -35,7 +35,11 @@ import {
|
|||||||
debug,
|
debug,
|
||||||
isSymbolicLink,
|
isSymbolicLink,
|
||||||
runNpmInstall,
|
runNpmInstall,
|
||||||
|
updateFunctionsManifest,
|
||||||
|
updateRoutesManifest,
|
||||||
walkParentDirs,
|
walkParentDirs,
|
||||||
|
normalizePath,
|
||||||
|
runPackageJsonScript,
|
||||||
} 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';
|
||||||
@@ -43,6 +47,7 @@ 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';
|
||||||
|
|
||||||
export { shouldServe };
|
export { shouldServe };
|
||||||
export {
|
export {
|
||||||
@@ -64,6 +69,7 @@ const { makeVercelLauncher, makeAwsLauncher } = require_(
|
|||||||
interface DownloadOptions {
|
interface DownloadOptions {
|
||||||
entrypoint: string;
|
entrypoint: string;
|
||||||
workPath: string;
|
workPath: string;
|
||||||
|
installedPaths?: Set<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PortInfo {
|
interface PortInfo {
|
||||||
@@ -109,17 +115,47 @@ const BRIDGE_FILENAME = '__bridge.js';
|
|||||||
const HELPERS_FILENAME = '__helpers.js';
|
const HELPERS_FILENAME = '__helpers.js';
|
||||||
const SOURCEMAP_SUPPORT_FILENAME = '__sourcemap_support.js';
|
const SOURCEMAP_SUPPORT_FILENAME = '__sourcemap_support.js';
|
||||||
|
|
||||||
async function downloadInstallAndBundle({
|
async function maybeInstallAndBuild({
|
||||||
entrypoint,
|
entrypoint,
|
||||||
workPath,
|
workPath,
|
||||||
|
installedPaths,
|
||||||
}: DownloadOptions) {
|
}: DownloadOptions) {
|
||||||
const entrypointFsDirname = join(workPath, dirname(entrypoint));
|
const entrypointFsDirname = join(workPath, dirname(entrypoint));
|
||||||
const nodeVersion = await getNodeVersion(entrypointFsDirname);
|
const nodeVersion = await getNodeVersion(entrypointFsDirname);
|
||||||
const spawnOpts = getSpawnOptions({}, nodeVersion);
|
const spawnOpts = getSpawnOptions({}, nodeVersion);
|
||||||
|
|
||||||
const installTime = Date.now();
|
const lastPath = await walkParentDirs({
|
||||||
await runNpmInstall(entrypointFsDirname, [], spawnOpts, {}, nodeVersion);
|
base: workPath,
|
||||||
debug(`Install complete [${Date.now() - installTime}ms]`);
|
start: entrypointFsDirname,
|
||||||
|
filename: 'package.json',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!lastPath || dirname(lastPath) === workPath) {
|
||||||
|
debug(`Skip install command in \`vercel-plugin-node\` for ${entrypoint}.`);
|
||||||
|
} else if (lastPath) {
|
||||||
|
if (!installedPaths?.has(lastPath)) {
|
||||||
|
installedPaths?.add(lastPath);
|
||||||
|
const installTime = Date.now();
|
||||||
|
await runNpmInstall(dirname(lastPath), [], spawnOpts, {}, nodeVersion);
|
||||||
|
debug(
|
||||||
|
`Install complete [${Date.now() - installTime}ms] for ${relative(
|
||||||
|
workPath,
|
||||||
|
lastPath
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
|
||||||
|
await runPackageJsonScript(
|
||||||
|
dirname(lastPath),
|
||||||
|
// Don't consider "build" script since its intended for frontend code
|
||||||
|
['vercel-build', 'now-build'],
|
||||||
|
spawnOpts
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
debug(
|
||||||
|
`Skip install command in \`vercel-plugin-node\` for ${entrypoint}. Already installed for other entrypoint.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
nodeVersion,
|
nodeVersion,
|
||||||
@@ -346,6 +382,7 @@ function getAWSLambdaHandler(entrypoint: string, config: FunctionConfig) {
|
|||||||
export async function build({ workPath }: { workPath: string }) {
|
export async function build({ workPath }: { 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>();
|
||||||
for (const entrypoint of Object.keys(entrypoints)) {
|
for (const entrypoint of Object.keys(entrypoints)) {
|
||||||
// Dotfiles are not compiled
|
// Dotfiles are not compiled
|
||||||
if (entrypoint.includes('/.')) continue;
|
if (entrypoint.includes('/.')) continue;
|
||||||
@@ -370,7 +407,12 @@ export async function build({ workPath }: { workPath: string }) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
await buildEntrypoint({ workPath, entrypoint, config });
|
await buildEntrypoint({
|
||||||
|
workPath,
|
||||||
|
entrypoint,
|
||||||
|
config,
|
||||||
|
installedPaths,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,46 +420,43 @@ export async function buildEntrypoint({
|
|||||||
workPath,
|
workPath,
|
||||||
entrypoint,
|
entrypoint,
|
||||||
config,
|
config,
|
||||||
|
installedPaths,
|
||||||
}: {
|
}: {
|
||||||
workPath: string;
|
workPath: string;
|
||||||
entrypoint: string;
|
entrypoint: string;
|
||||||
config: FunctionConfig;
|
config: FunctionConfig;
|
||||||
|
installedPaths?: Set<string>;
|
||||||
}) {
|
}) {
|
||||||
|
// Unique hash that will be used as directory name for `.output`.
|
||||||
|
const entrypointHash = crypto
|
||||||
|
.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);
|
||||||
const entrypointWithoutExt = join('/', dir, name);
|
const entrypointWithoutExt = join('/', dir, name);
|
||||||
const entrypointWithoutExtIndex = join(
|
const outputWorkPath = join(outputDirPath, 'inputs', entrypointHash);
|
||||||
dir,
|
const pagesDir = join(outputDirPath, 'server', 'pages');
|
||||||
name,
|
const pageOutput = join(pagesDir, renameTStoJS(entrypoint));
|
||||||
name === 'index' ? '' : 'index'
|
const nftOutput = `${pageOutput}.nft.json`;
|
||||||
);
|
|
||||||
const outputWorkPath = join(
|
|
||||||
outputDirPath,
|
|
||||||
'server/pages',
|
|
||||||
entrypointWithoutExtIndex
|
|
||||||
);
|
|
||||||
await fsp.mkdir(outputWorkPath, { recursive: true });
|
await fsp.mkdir(outputWorkPath, { recursive: true });
|
||||||
|
await fsp.mkdir(parsePath(pageOutput).dir, { recursive: true });
|
||||||
|
|
||||||
console.log(`Compiling "${entrypoint}" to "${outputWorkPath}"`);
|
console.log(`Compiling "${entrypoint}" to "${outputWorkPath}"`);
|
||||||
|
|
||||||
const shouldAddHelpers =
|
const shouldAddHelpers =
|
||||||
config.helpers !== false && process.env.NODEJS_HELPERS !== '0';
|
config.helpers !== false && process.env.NODEJS_HELPERS !== '0';
|
||||||
const awsLambdaHandler = getAWSLambdaHandler(entrypoint, config);
|
const awsLambdaHandler = getAWSLambdaHandler(entrypoint, config);
|
||||||
|
|
||||||
const { nodeVersion } = await downloadInstallAndBundle({
|
const { nodeVersion } = await maybeInstallAndBuild({
|
||||||
entrypoint,
|
entrypoint,
|
||||||
workPath,
|
workPath,
|
||||||
|
installedPaths,
|
||||||
});
|
});
|
||||||
const entrypointPath = join(workPath, entrypoint);
|
const entrypointPath = join(workPath, entrypoint);
|
||||||
|
|
||||||
// TODO NATE: do we want to run the build script?
|
|
||||||
// The frontend build command probably already did this
|
|
||||||
//await runPackageJsonScript(
|
|
||||||
// entrypointFsDirname,
|
|
||||||
// // Don't consider "build" script since its intended for frontend code
|
|
||||||
// ['vercel-build', 'now-build'],
|
|
||||||
// spawnOpts
|
|
||||||
//);
|
|
||||||
|
|
||||||
debug('Tracing input files...');
|
debug('Tracing input files...');
|
||||||
const traceTime = Date.now();
|
const traceTime = Date.now();
|
||||||
const { preparedFiles, shouldAddSourcemapSupport } = await compile(
|
const { preparedFiles, shouldAddSourcemapSupport } = await compile(
|
||||||
@@ -427,11 +466,12 @@ export async function buildEntrypoint({
|
|||||||
);
|
);
|
||||||
debug(`Trace complete [${Date.now() - traceTime}ms]`);
|
debug(`Trace complete [${Date.now() - traceTime}ms]`);
|
||||||
|
|
||||||
const getFileName = (str: string) => `___vc/${str}`;
|
// Has to be in `dirname(entrypoint)` because the `handler` will be prefixed with this path.
|
||||||
|
const getVCFileName = (str: string) => `${dirname(entrypoint)}/___vc/${str}`;
|
||||||
|
|
||||||
const launcher = awsLambdaHandler ? makeAwsLauncher : makeVercelLauncher;
|
const launcher = awsLambdaHandler ? makeAwsLauncher : makeVercelLauncher;
|
||||||
const launcherSource = launcher({
|
const launcherSource = launcher({
|
||||||
entrypointPath: `../${renameTStoJS(relative(workPath, entrypointPath))}`,
|
entrypointPath: `../${renameTStoJS(basename(entrypoint))}`,
|
||||||
bridgePath: `./${BRIDGE_FILENAME}`,
|
bridgePath: `./${BRIDGE_FILENAME}`,
|
||||||
helpersPath: `./${HELPERS_FILENAME}`,
|
helpersPath: `./${HELPERS_FILENAME}`,
|
||||||
sourcemapSupportPath: `./${SOURCEMAP_SUPPORT_FILENAME}`,
|
sourcemapSupportPath: `./${SOURCEMAP_SUPPORT_FILENAME}`,
|
||||||
@@ -441,25 +481,25 @@ export async function buildEntrypoint({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const launcherFiles: Files = {
|
const launcherFiles: Files = {
|
||||||
[getFileName('package.json')]: new FileBlob({
|
[getVCFileName('package.json')]: new FileBlob({
|
||||||
data: JSON.stringify({ type: 'commonjs' }),
|
data: JSON.stringify({ type: 'commonjs' }),
|
||||||
}),
|
}),
|
||||||
[getFileName(LAUNCHER_FILENAME)]: new FileBlob({
|
[getVCFileName(LAUNCHER_FILENAME)]: new FileBlob({
|
||||||
data: launcherSource,
|
data: launcherSource,
|
||||||
}),
|
}),
|
||||||
[getFileName(BRIDGE_FILENAME)]: new FileFsRef({
|
[getVCFileName(BRIDGE_FILENAME)]: new FileFsRef({
|
||||||
fsPath: join(DIST_DIR, 'bridge.js'),
|
fsPath: join(DIST_DIR, 'bridge.js'),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (shouldAddSourcemapSupport) {
|
if (shouldAddSourcemapSupport) {
|
||||||
launcherFiles[getFileName(SOURCEMAP_SUPPORT_FILENAME)] = new FileFsRef({
|
launcherFiles[getVCFileName(SOURCEMAP_SUPPORT_FILENAME)] = new FileFsRef({
|
||||||
fsPath: join(DIST_DIR, 'source-map-support.js'),
|
fsPath: join(DIST_DIR, 'source-map-support.js'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldAddHelpers) {
|
if (shouldAddHelpers) {
|
||||||
launcherFiles[getFileName(HELPERS_FILENAME)] = new FileFsRef({
|
launcherFiles[getVCFileName(HELPERS_FILENAME)] = new FileFsRef({
|
||||||
fsPath: join(DIST_DIR, 'helpers.js'),
|
fsPath: join(DIST_DIR, 'helpers.js'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -470,6 +510,8 @@ export async function buildEntrypoint({
|
|||||||
...launcherFiles,
|
...launcherFiles,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const nftFiles: { input: string; output: string }[] = [];
|
||||||
|
|
||||||
for (const filename of Object.keys(files)) {
|
for (const filename of Object.keys(files)) {
|
||||||
const outPath = join(outputWorkPath, filename);
|
const outPath = join(outputWorkPath, filename);
|
||||||
const file = files[filename];
|
const file = files[filename];
|
||||||
@@ -480,48 +522,44 @@ export async function buildEntrypoint({
|
|||||||
const finishPromise = once(ws, 'finish');
|
const finishPromise = once(ws, 'finish');
|
||||||
file.toStream().pipe(ws);
|
file.toStream().pipe(ws);
|
||||||
await finishPromise;
|
await finishPromise;
|
||||||
|
|
||||||
|
// The `handler` will be `.output/server/pages/api/subdirectory/___vc/__launcher.launcher`
|
||||||
|
// or `.output/server/pages/api/___vc/__launcher.launcher`.
|
||||||
|
// This means everything has to be mounted to the `dirname` of the entrypoint.
|
||||||
|
nftFiles.push({
|
||||||
|
input: relative(dirname(nftOutput), outPath),
|
||||||
|
output: join('.output', 'server', 'pages', filename),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the `functions-mainifest.json` file with this entrypoint
|
|
||||||
// TODO NATE: turn this into a `@vercel/plugin-utils` helper function?
|
|
||||||
const functionsManifestPath = join(outputDirPath, 'functions-manifest.json');
|
|
||||||
let functionsManifest: any = {};
|
|
||||||
try {
|
|
||||||
functionsManifest = JSON.parse(
|
|
||||||
await fsp.readFile(functionsManifestPath, 'utf8')
|
|
||||||
);
|
|
||||||
} catch (_err) {
|
|
||||||
// ignore...
|
|
||||||
}
|
|
||||||
if (!functionsManifest.version) functionsManifest.version = 1;
|
|
||||||
if (!functionsManifest.pages) functionsManifest.pages = {};
|
|
||||||
functionsManifest.pages[entrypointWithoutExtIndex] = {
|
|
||||||
handler: `${getFileName(LAUNCHER_FILENAME).slice(0, -3)}.launcher`,
|
|
||||||
runtime: nodeVersion.runtime,
|
|
||||||
};
|
|
||||||
await fsp.writeFile(
|
await fsp.writeFile(
|
||||||
functionsManifestPath,
|
nftOutput,
|
||||||
JSON.stringify(functionsManifest, null, 2)
|
JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
files: nftFiles,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await fsp.copyFile(
|
||||||
|
join(outputWorkPath, renameTStoJS(entrypoint)),
|
||||||
|
pageOutput
|
||||||
|
);
|
||||||
|
|
||||||
|
const pages = {
|
||||||
|
[normalizePath(relative(pagesDir, pageOutput))]: {
|
||||||
|
handler: `___vc/${LAUNCHER_FILENAME.slice(0, -3)}.launcher`,
|
||||||
|
runtime: nodeVersion.runtime,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
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`).
|
||||||
if (isDynamicRoute(entrypointWithoutExt)) {
|
if (isDynamicRoute(entrypointWithoutExt)) {
|
||||||
const routesManifestPath = join(outputDirPath, 'routes-manifest.json');
|
await updateRoutesManifest({
|
||||||
let routesManifest: any = {};
|
workPath,
|
||||||
try {
|
dynamicRoutes: [pageToRoute(entrypointWithoutExt)],
|
||||||
routesManifest = JSON.parse(
|
});
|
||||||
await fsp.readFile(routesManifestPath, 'utf8')
|
|
||||||
);
|
|
||||||
} catch (_err) {
|
|
||||||
// ignore...
|
|
||||||
}
|
|
||||||
if (!routesManifest.dynamicRoutes) routesManifest.dynamicRoutes = [];
|
|
||||||
routesManifest.dynamicRoutes.push(pageToRoute(entrypointWithoutExt));
|
|
||||||
await fsp.writeFile(
|
|
||||||
routesManifestPath,
|
|
||||||
JSON.stringify(routesManifest, null, 2)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
84
packages/plugin-node/test/build.test.ts
vendored
84
packages/plugin-node/test/build.test.ts
vendored
@@ -1,6 +1,7 @@
|
|||||||
import { join } from 'path';
|
import path from 'path';
|
||||||
import { parse } from 'url';
|
import { parse } from 'url';
|
||||||
import { promises as fsp } from 'fs';
|
import { promises as fsp } from 'fs';
|
||||||
|
import { ZipFile } from 'yazl';
|
||||||
import { createFunction, Lambda } from '@vercel/fun';
|
import { createFunction, Lambda } from '@vercel/fun';
|
||||||
import {
|
import {
|
||||||
Request,
|
Request,
|
||||||
@@ -11,6 +12,7 @@ import {
|
|||||||
Headers,
|
Headers,
|
||||||
} from 'node-fetch';
|
} from 'node-fetch';
|
||||||
import { build } from '../src';
|
import { build } from '../src';
|
||||||
|
import { runNpmInstall, streamToBuffer } from '@vercel/build-utils';
|
||||||
|
|
||||||
interface TestParams {
|
interface TestParams {
|
||||||
fixture: string;
|
fixture: string;
|
||||||
@@ -50,40 +52,66 @@ function withFixture<T>(
|
|||||||
t: (props: TestParams) => Promise<T>
|
t: (props: TestParams) => Promise<T>
|
||||||
): () => Promise<T> {
|
): () => Promise<T> {
|
||||||
return async () => {
|
return async () => {
|
||||||
const fixture = join(__dirname, 'fixtures', name);
|
const fixture = path.join(__dirname, 'fixtures', name);
|
||||||
|
await fsp.rmdir(path.join(fixture, '.output'), { recursive: true });
|
||||||
|
|
||||||
const functions = new Map<string, Lambda>();
|
const functions = new Map<string, Lambda>();
|
||||||
|
|
||||||
async function fetch(r: RequestInfo, init?: RequestInit) {
|
async function fetch(r: RequestInfo, init?: RequestInit) {
|
||||||
const req = new Request(r, init);
|
const req = new Request(r, init);
|
||||||
const url = parse(req.url);
|
const url = parse(req.url);
|
||||||
const pathWithIndex = join(
|
const functionPath = url.pathname!.substring(1);
|
||||||
url.pathname!,
|
|
||||||
url.pathname!.endsWith('/index') ? '' : 'index'
|
|
||||||
).substring(1);
|
|
||||||
|
|
||||||
let status = 404;
|
let status = 404;
|
||||||
let headers: HeadersInit = {};
|
let headers: HeadersInit = {};
|
||||||
let body: string | Buffer = 'Function not found';
|
let body: string | Buffer = 'Function not found';
|
||||||
|
|
||||||
let fn = functions.get(pathWithIndex);
|
let fn = functions.get(functionPath);
|
||||||
if (!fn) {
|
if (!fn) {
|
||||||
const manifest = JSON.parse(
|
const manifest = JSON.parse(
|
||||||
await fsp.readFile(
|
await fsp.readFile(
|
||||||
join(fixture, '.output/functions-manifest.json'),
|
path.join(fixture, '.output/functions-manifest.json'),
|
||||||
'utf8'
|
'utf8'
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
const functionManifest = manifest.pages[pathWithIndex];
|
|
||||||
|
const keyFile = `${functionPath}.js`;
|
||||||
|
const keyIndex = `${functionPath}/index.js`;
|
||||||
|
const fnKey = keyFile in manifest.pages ? keyFile : keyIndex;
|
||||||
|
const functionManifest = manifest.pages[fnKey];
|
||||||
|
|
||||||
if (functionManifest) {
|
if (functionManifest) {
|
||||||
const dir = join(fixture, '.output/server/pages', pathWithIndex);
|
const entry = path.join(fixture, '.output/server/pages', fnKey);
|
||||||
|
const nftFile = JSON.parse(
|
||||||
|
await fsp.readFile(`${entry}.nft.json`, 'utf8')
|
||||||
|
);
|
||||||
|
|
||||||
|
const zip = new ZipFile();
|
||||||
|
zip.addFile(
|
||||||
|
path.join(fixture, '.output/server/pages', fnKey),
|
||||||
|
path.join('.output/server/pages', fnKey)
|
||||||
|
);
|
||||||
|
|
||||||
|
nftFile.files.forEach((f: { input: string; output: string }) => {
|
||||||
|
const input = path.join(path.dirname(entry), f.input);
|
||||||
|
zip.addFile(input, f.output);
|
||||||
|
});
|
||||||
|
zip.end();
|
||||||
|
|
||||||
|
const handler = path.posix.join(
|
||||||
|
'.output/server/pages',
|
||||||
|
path.dirname(fnKey),
|
||||||
|
functionManifest.handler
|
||||||
|
);
|
||||||
|
|
||||||
fn = await createFunction({
|
fn = await createFunction({
|
||||||
Code: {
|
Code: {
|
||||||
Directory: dir,
|
ZipFile: await streamToBuffer(zip.outputStream),
|
||||||
},
|
},
|
||||||
Handler: functionManifest.handler,
|
Handler: handler,
|
||||||
Runtime: functionManifest.runtime,
|
Runtime: functionManifest.runtime,
|
||||||
});
|
});
|
||||||
functions.set(pathWithIndex, fn);
|
functions.set(functionPath, fn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,6 +137,12 @@ function withFixture<T>(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
await fsp.lstat(path.join(fixture, 'package.json')).catch(() => false)
|
||||||
|
) {
|
||||||
|
await runNpmInstall(fixture);
|
||||||
|
}
|
||||||
|
|
||||||
await build({ workPath: fixture });
|
await build({ workPath: fixture });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -316,4 +350,28 @@ describe('build()', () => {
|
|||||||
expect(body3.includes('GraphQL Playground')).toEqual(true);
|
expect(body3.includes('GraphQL Playground')).toEqual(true);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
it(
|
||||||
|
'should build "nested-lock-and-build"',
|
||||||
|
withFixture('nested-lock-and-build', async ({ fetch }) => {
|
||||||
|
const resA = await fetch('/api/users/[id]');
|
||||||
|
|
||||||
|
expect(resA.headers.get('x-date')).toEqual('2021-11-20T20:00:00.000Z');
|
||||||
|
|
||||||
|
const body = await resA.text();
|
||||||
|
expect(body).toEqual(
|
||||||
|
' _______________________________\n' +
|
||||||
|
'< Hello from /api/users/[id].js >\n' +
|
||||||
|
' -------------------------------\n' +
|
||||||
|
' \\ ^__^\n' +
|
||||||
|
' \\ (oo)\\_______\n' +
|
||||||
|
' (__)\\ )\\/\\\n' +
|
||||||
|
' ||----w |\n' +
|
||||||
|
' || ||'
|
||||||
|
);
|
||||||
|
|
||||||
|
const resB = await fetch('/api/profile');
|
||||||
|
expect(await resB.text()).toEqual('true');
|
||||||
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
8
packages/plugin-node/test/fixtures/nested-lock-and-build/api/package.json
vendored
Normal file
8
packages/plugin-node/test/fixtures/nested-lock-and-build/api/package.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"vercel-build": "node ./script.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"cowsay": "1.5.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
9
packages/plugin-node/test/fixtures/nested-lock-and-build/api/profile.js
vendored
Normal file
9
packages/plugin-node/test/fixtures/nested-lock-and-build/api/profile.js
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
const isSvg = require('is-svg');
|
||||||
|
|
||||||
|
module.exports = (req, res) => {
|
||||||
|
res.end(
|
||||||
|
isSvg(
|
||||||
|
'<svg xmlns="http://www.w3.org/2000/svg"><path fill="#00CD9F"/></svg>'
|
||||||
|
).toString()
|
||||||
|
);
|
||||||
|
};
|
||||||
11
packages/plugin-node/test/fixtures/nested-lock-and-build/api/script.js
vendored
Normal file
11
packages/plugin-node/test/fixtures/nested-lock-and-build/api/script.js
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(__dirname, 'users', '.date.js'),
|
||||||
|
`
|
||||||
|
module.exports = {
|
||||||
|
date: '2021-11-20T20:00:00.000Z'
|
||||||
|
};
|
||||||
|
`.trim()
|
||||||
|
);
|
||||||
11
packages/plugin-node/test/fixtures/nested-lock-and-build/api/users/[id].js
vendored
Normal file
11
packages/plugin-node/test/fixtures/nested-lock-and-build/api/users/[id].js
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
const cowsay = require('cowsay');
|
||||||
|
const { date } = require('./.date');
|
||||||
|
|
||||||
|
module.exports = (req, res) => {
|
||||||
|
res.setHeader('x-date', date);
|
||||||
|
res.end(
|
||||||
|
cowsay.say({
|
||||||
|
text: 'Hello from /api/users/[id].js',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
220
packages/plugin-node/test/fixtures/nested-lock-and-build/api/yarn.lock
vendored
Normal file
220
packages/plugin-node/test/fixtures/nested-lock-and-build/api/yarn.lock
vendored
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
ansi-regex@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
|
||||||
|
integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
|
||||||
|
|
||||||
|
ansi-regex@^5.0.1:
|
||||||
|
version "5.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
|
||||||
|
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
|
||||||
|
|
||||||
|
ansi-styles@^4.0.0:
|
||||||
|
version "4.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
|
||||||
|
integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
|
||||||
|
dependencies:
|
||||||
|
color-convert "^2.0.1"
|
||||||
|
|
||||||
|
camelcase@^5.0.0:
|
||||||
|
version "5.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
|
||||||
|
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
|
||||||
|
|
||||||
|
cliui@^6.0.0:
|
||||||
|
version "6.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1"
|
||||||
|
integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==
|
||||||
|
dependencies:
|
||||||
|
string-width "^4.2.0"
|
||||||
|
strip-ansi "^6.0.0"
|
||||||
|
wrap-ansi "^6.2.0"
|
||||||
|
|
||||||
|
color-convert@^2.0.1:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
|
||||||
|
integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
|
||||||
|
dependencies:
|
||||||
|
color-name "~1.1.4"
|
||||||
|
|
||||||
|
color-name@~1.1.4:
|
||||||
|
version "1.1.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
|
||||||
|
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||||
|
|
||||||
|
cowsay@1.5.0:
|
||||||
|
version "1.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/cowsay/-/cowsay-1.5.0.tgz#4a2a453b8b59383c7d7a50e44d765c5de0bf615f"
|
||||||
|
integrity sha512-8Ipzr54Z8zROr/62C8f0PdhQcDusS05gKTS87xxdji8VbWefWly0k8BwGK7+VqamOrkv3eGsCkPtvlHzrhWsCA==
|
||||||
|
dependencies:
|
||||||
|
get-stdin "8.0.0"
|
||||||
|
string-width "~2.1.1"
|
||||||
|
strip-final-newline "2.0.0"
|
||||||
|
yargs "15.4.1"
|
||||||
|
|
||||||
|
decamelize@^1.2.0:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
|
||||||
|
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
|
||||||
|
|
||||||
|
emoji-regex@^8.0.0:
|
||||||
|
version "8.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
|
||||||
|
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
|
||||||
|
|
||||||
|
find-up@^4.1.0:
|
||||||
|
version "4.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
|
||||||
|
integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
|
||||||
|
dependencies:
|
||||||
|
locate-path "^5.0.0"
|
||||||
|
path-exists "^4.0.0"
|
||||||
|
|
||||||
|
get-caller-file@^2.0.1:
|
||||||
|
version "2.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
|
||||||
|
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
|
||||||
|
|
||||||
|
get-stdin@8.0.0:
|
||||||
|
version "8.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53"
|
||||||
|
integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==
|
||||||
|
|
||||||
|
is-fullwidth-code-point@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
|
||||||
|
integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
|
||||||
|
|
||||||
|
is-fullwidth-code-point@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
|
||||||
|
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
|
||||||
|
|
||||||
|
locate-path@^5.0.0:
|
||||||
|
version "5.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
|
||||||
|
integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==
|
||||||
|
dependencies:
|
||||||
|
p-locate "^4.1.0"
|
||||||
|
|
||||||
|
p-limit@^2.2.0:
|
||||||
|
version "2.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
|
||||||
|
integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
|
||||||
|
dependencies:
|
||||||
|
p-try "^2.0.0"
|
||||||
|
|
||||||
|
p-locate@^4.1.0:
|
||||||
|
version "4.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
|
||||||
|
integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==
|
||||||
|
dependencies:
|
||||||
|
p-limit "^2.2.0"
|
||||||
|
|
||||||
|
p-try@^2.0.0:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
|
||||||
|
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
|
||||||
|
|
||||||
|
path-exists@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
|
||||||
|
integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
|
||||||
|
|
||||||
|
require-directory@^2.1.1:
|
||||||
|
version "2.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
|
||||||
|
integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
|
||||||
|
|
||||||
|
require-main-filename@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
|
||||||
|
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
|
||||||
|
|
||||||
|
set-blocking@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
|
||||||
|
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
|
||||||
|
|
||||||
|
string-width@^4.1.0, string-width@^4.2.0:
|
||||||
|
version "4.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
|
dependencies:
|
||||||
|
emoji-regex "^8.0.0"
|
||||||
|
is-fullwidth-code-point "^3.0.0"
|
||||||
|
strip-ansi "^6.0.1"
|
||||||
|
|
||||||
|
string-width@~2.1.1:
|
||||||
|
version "2.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
|
||||||
|
integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
|
||||||
|
dependencies:
|
||||||
|
is-fullwidth-code-point "^2.0.0"
|
||||||
|
strip-ansi "^4.0.0"
|
||||||
|
|
||||||
|
strip-ansi@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
|
||||||
|
integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8=
|
||||||
|
dependencies:
|
||||||
|
ansi-regex "^3.0.0"
|
||||||
|
|
||||||
|
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||||
|
version "6.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
|
dependencies:
|
||||||
|
ansi-regex "^5.0.1"
|
||||||
|
|
||||||
|
strip-final-newline@2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
|
||||||
|
integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
|
||||||
|
|
||||||
|
which-module@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
|
||||||
|
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
|
||||||
|
|
||||||
|
wrap-ansi@^6.2.0:
|
||||||
|
version "6.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
|
||||||
|
integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==
|
||||||
|
dependencies:
|
||||||
|
ansi-styles "^4.0.0"
|
||||||
|
string-width "^4.1.0"
|
||||||
|
strip-ansi "^6.0.0"
|
||||||
|
|
||||||
|
y18n@^4.0.0:
|
||||||
|
version "4.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
|
||||||
|
integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==
|
||||||
|
|
||||||
|
yargs-parser@^18.1.2:
|
||||||
|
version "18.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
|
||||||
|
integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==
|
||||||
|
dependencies:
|
||||||
|
camelcase "^5.0.0"
|
||||||
|
decamelize "^1.2.0"
|
||||||
|
|
||||||
|
yargs@15.4.1:
|
||||||
|
version "15.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
|
||||||
|
integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==
|
||||||
|
dependencies:
|
||||||
|
cliui "^6.0.0"
|
||||||
|
decamelize "^1.2.0"
|
||||||
|
find-up "^4.1.0"
|
||||||
|
get-caller-file "^2.0.1"
|
||||||
|
require-directory "^2.1.1"
|
||||||
|
require-main-filename "^2.0.0"
|
||||||
|
set-blocking "^2.0.0"
|
||||||
|
string-width "^4.2.0"
|
||||||
|
which-module "^2.0.0"
|
||||||
|
y18n "^4.0.0"
|
||||||
|
yargs-parser "^18.1.2"
|
||||||
5
packages/plugin-node/test/fixtures/nested-lock-and-build/package.json
vendored
Normal file
5
packages/plugin-node/test/fixtures/nested-lock-and-build/package.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"is-svg": "4.3.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
22
packages/plugin-node/test/fixtures/nested-lock-and-build/yarn.lock
vendored
Normal file
22
packages/plugin-node/test/fixtures/nested-lock-and-build/yarn.lock
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
fast-xml-parser@^3.19.0:
|
||||||
|
version "3.21.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-3.21.1.tgz#152a1d51d445380f7046b304672dd55d15c9e736"
|
||||||
|
integrity sha512-FTFVjYoBOZTJekiUsawGsSYV9QL0A+zDYCRj7y34IO6Jg+2IMYEtQa+bbictpdpV8dHxXywqU7C0gRDEOFtBFg==
|
||||||
|
dependencies:
|
||||||
|
strnum "^1.0.4"
|
||||||
|
|
||||||
|
is-svg@4.3.1:
|
||||||
|
version "4.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-4.3.1.tgz#8c63ec8c67c8c7f0a8de0a71c8c7d58eccf4406b"
|
||||||
|
integrity sha512-h2CGs+yPUyvkgTJQS9cJzo9lYK06WgRiXUqBBHtglSzVKAuH4/oWsqk7LGfbSa1hGk9QcZ0SyQtVggvBA8LZXA==
|
||||||
|
dependencies:
|
||||||
|
fast-xml-parser "^3.19.0"
|
||||||
|
|
||||||
|
strnum@^1.0.4:
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.4.tgz#e97e36a7d6ba9f93d0d6b496b2ed0678d422832b"
|
||||||
|
integrity sha512-lMzNMfDpaQOLt4B2mEbfzYS0+T7dvCXeojnlGf6f1AygvWDMcWyXYaLbyICfjVu29sErR8fnRagQfBW/N/hGgw==
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"private": false,
|
"private": false,
|
||||||
"name": "vercel-plugin-python",
|
"name": "vercel-plugin-python",
|
||||||
"version": "1.0.0-canary.1",
|
"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.17",
|
"@vercel/build-utils": "2.12.3-canary.27",
|
||||||
"@vercel/python": "2.0.6-canary.5"
|
"@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.1",
|
"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.17",
|
"@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": "*",
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user