Compare commits

...

27 Commits

Author SHA1 Message Date
Steven
2906d83eae Publish Stable
- @vercel/build-utils@5.4.1
 - vercel@28.2.1
 - @vercel/client@12.2.3
 - @vercel/edge@0.0.4
 - @vercel/fs-detectors@2.1.0
 - @vercel/go@2.2.4
 - @vercel/hydrogen@0.0.17
 - @vercel/next@3.1.23
 - @vercel/node@2.5.12
 - @vercel/python@3.1.13
 - @vercel/redwood@1.0.21
 - @vercel/remix@1.0.22
 - @vercel/ruby@1.3.30
 - @vercel/static-build@1.0.21
2022-09-01 13:16:42 -04:00
Steven
675c3e2915 [cli] Change error message from Error! to Error: (#8498)
We have code that tries to detect and highlight errors in the build logs, however it doesn't look for `Error!`, only `Error:`.

We could update that highlight code or we could update Vercel CLI to make it consistent.

This PR is the latter.
2022-09-01 13:12:41 -04:00
Steven
13a8a7dbf6 [next] Revert nft to 0.21.0 (#8499)
In Next.js 12, we introduce `outputFileTracing` as the default behavior. This moved `@vercel/nft` from `@vercel/next` into `next` itself so users can upgrade or downgrade Next.js if there is a bug in `@vercel/nft`.

Unfortunately, some users are setting `outputFileTracing: false` which deopts tracing back from `next` to `@vercel/next`. So we should pin `@vercel/nft` here and never upgrade in case of any bugs.
2022-09-01 09:13:35 -07:00
Andrew Gadzik
b480e632a3 Add writeFile to DetectorFilesystem (#8493)
Adds a `writeFile` function to `DetectorFilesystem` that will be used to update the various file cache maps.

When detecting npm7+ monorepos, we identified a performance improvement where the service can inspect the `package-lock.json` file for workspaces, and reuse the package information for each workspace in framework-detection.

The pseudo code in `vercel/api` will look something like this

For a given lockfile
```json
{
  ...,
  "packages": {
    "": {
      "name": "npm-workspaces",
      "version": "1.0.0",
      "license": "ISC",
      "workspaces": {
        "packages": [
          "apps/*"
        ]
      }
    },
    "apps/admin": {
      "version": "0.1.0",
      "dependencies": {
        "next": "12.2.5",
        "react": "18.2.0",
        "react-dom": "18.2.0"
      },
      "devDependencies": {
        "eslint": "8.23.0",
        "eslint-config-next": "12.2.5"
      }
    },
    ...,
}
```

```ts
// for each projectPath we detect in package-lock.json
// switch the cwd of the fs to the project directory
const projectFs = fs.chdir(projectPath);
// gets the package info from the lockfile
const projectPackageInfo = lockFile.packages[projectPath];
// insert this content into fs cache
projectFs.writeFile('package.json', projectPackageInfo)
// call detectFramework, which should now have a cached "package.json" file 
const projectFramework = await detectFramework(projectFs);
```

### Related Issues

Related to [HIT-57](https://linear.app/vercel/issue/HIT-57/monorepo-detection-api-prevent-rate-limits)

### 📋 Checklist

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

#### Tests

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

#### Code Review

- [ ] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
2022-08-31 18:51:03 +00:00
Chris Barber
bc6c364888 [node] Added 'cause' to error message (#8467)
When running `vc dev` for a project using `experimental-edge` runtime and a fetch-related error occurs, the message is a little vague:

```
Unhandled rejection: fetch failed
```

In this case, the fetch promise (defined in @edge-runtime/primitives) is being rejected with a `TypeError: fetch failed` that has a `cause` property containing the actual `response.error` message. The `response.error` is the actual error and is never reported to the user.

This PR simply appends the `cause` message, if exists, to the fetch failure message.

```
Unhandled rejection: fetch failed: Error: getaddrinfo ENOTFOUND undefined
```

### 📋 Checklist

#### Tests

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

#### Code Review

- [ ] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
2022-08-30 02:32:38 +00:00
Steven
6cad10c899 [build-utils] Replace npm bin with custom implementation (#8483)
This PR removes `npm bin` in favor of a custom implementation since the command will be removed in npm 9.

- Related to https://github.com/npm/statusboard/issues/537
2022-08-30 01:23:34 +00:00
Gal Schlezinger
0d78ec4666 [edge] Add markdown documentation using TypeDoc (#8446) 2022-08-29 12:36:44 +03:00
Nathan Rajlich
b05d653cf1 [cli] Use unique "name" in package.json test fixtures (#8475)
This fixes a few warnings when running Jest that look something like:

```
jest-haste-map: Haste module naming collision: nextjs
  The following files share their name; please adjust your hasteImpl:
    * <rootDir>/test/dev/fixtures/10-nextjs-node/package.json
    * <rootDir>/test/dev/fixtures/25-nextjs-src-dir/package.json
```
2022-08-26 21:43:25 +00:00
Nathan Rajlich
5a38b94de2 [cli] Replace "progress" module with custom implementation (#8397)
The `progress` module is overkill for what we are using it for and it's inconsistent with our styling for the rest of the deployment process.

So drop the npm module in favor of an in-house progress bar implementation that can be used inside of the Ora `output.spinner()` that we already use for the other steps of the deployment process.

**Before:**

<img width="307" alt="Screen Shot 2022-08-25 at 5 07 28 PM" src="https://user-images.githubusercontent.com/71256/186790052-18431ac0-b05b-434e-b885-9eb76d880b9d.png">

**After:**

<img width="347" alt="Screen Shot 2022-08-25 at 5 09 35 PM" src="https://user-images.githubusercontent.com/71256/186790168-8dd3e9a2-863a-45cc-bd21-d98308d34e22.png">
2022-08-26 21:03:38 +00:00
Nathan Rajlich
6668d6cf21 [client] Add "tar-fs" dependency (#8473)
Fixes #8471.
2022-08-26 18:02:01 +00:00
Andrew Gadzik
b63f444abd [fs-detectors] Add a default workspace manager when there's no lock file (#8469)
This fixes an issue with this example,

https://vercel.com/api/v1/integrations/detect-eligible-projects?url=https%3A%2F%2Fgithub.com%2Fvercel%2Fturborepo%2Ftree%2Fmain%2Fexamples%2Fkitchen-sink

Where the repo has no lockfile, and hence our workspace detection fails.  This fix is to add a fallback of "npm" in this scenario

### Related Issues

Fixes https://github.com/vercel/turbo-internal/issues/232

### 📋 Checklist

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

#### Tests

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

#### Code Review

- [ ] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
2022-08-26 16:24:13 +00:00
Steven
9d71d4332f [tests] Update codeowners for fs-detectors (#8470)
Update codeowners for `@vercel/fs-detectors` to add @agadzik and @chloetedder
2022-08-26 15:43:14 +00:00
Steven
151db21a2f [tests] Add required labels (#8464)
This action will fail if the required labels are not applied to a PR.

We allow multiple `area` labels and only a single `semver` label.

- Example failure: https://github.com/vercel/vercel/runs/8024949804?check_suite_focus=true#step:2:19
- Example success: https://github.com/vercel/vercel/runs/8026212031?check_suite_focus=true#step:2:19
2022-08-25 22:20:37 +00:00
Steven
aba9c95ea2 [tests] Change PR automerge label (#8465)
We decided to change this to an explicit label and rely on a separate action to check for correct labels (see #8464).

This action likely won't work for PRs from forks, but that might be okay because our other actions don't work for forks either.
2022-08-25 22:05:55 +00:00
Sean Massa
e7e0a55b72 Publish Stable
- @vercel/build-utils@5.4.0
 - vercel@28.2.0
 - @vercel/client@12.2.2
 - @vercel/go@2.2.3
 - @vercel/hydrogen@0.0.16
 - @vercel/next@3.1.22
 - @vercel/node@2.5.11
 - @vercel/python@3.1.12
 - @vercel/redwood@1.0.20
 - @vercel/remix@1.0.21
 - @vercel/ruby@1.3.29
 - @vercel/static-build@1.0.20
2022-08-25 15:50:50 -05:00
Sean Massa
c2163e3e4f [tests] Update CODEOWNERS (#8463)
Changes:

- added @cb1kenobi @Ethan-Arrowood to sections owned by the Vercel CLI team
- removed outdated code owners: if this was you and you believe this was in error, let us know!
- removed no-op lines
2022-08-25 20:06:49 +00:00
Nathan Rajlich
873f549637 [cli] Normalize Builder output paths in vc dev (#8461)
Fixes https://github.com/vercel/og-image/issues/180.
2022-08-25 19:09:12 +00:00
Naoyuki Kanezawa
ead1e411ee [next] support middleware-manifest v2 (#8319)
### Related Issues

As the title, support a new version of middleware-manifest of next.js that is going to be added in https://github.com/vercel/next.js/pull/39257

### 📋 Checklist

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

#### Tests

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

#### Code Review

- [ ] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
2022-08-25 18:57:12 +00:00
Sean Massa
7c1c089a70 [tests] rename go test function (#8458)
This function name can be anything, but the current one is confusing. This is what the function gets renamed to during compilation. Let's change it back to avoid confusion.
2022-08-25 15:11:05 +00:00
Ethan Arrowood
a7dbd9649b [cli] add override support for vc build and vc dev (#8451)
### Related Issues

Adds configuration override support to `vc build` and `vc dev` commands.

### 📋 Checklist

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

#### Tests

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

#### Code Review

- [ ] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
2022-08-25 03:00:52 +00:00
Chris Barber
2e439045c9 [cli] Improved 'vc git' help description for --yes flag (#8460)
When adding the `--yes` flag to the `vc git` command's help screen, the description was copy/pasted and not accurate, so this PR improves the description to be more accurate.

### 📋 Checklist

#### Tests

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

#### Code Review

- [ ] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
2022-08-25 01:25:30 +00:00
Steven
520e0d01f4 [frameworks] Fix link to framework logo (#8459)
This link should be using our custom domain
2022-08-24 16:07:17 -04:00
Steven
3856623785 [build-utils] Prefix debug message (#8378)
This PR adds `VERCEL_DEBUG_PREFIX` environment variable which lets you set a prefix for the debug logs.
2022-08-24 18:50:20 +00:00
Steven
1c14e945f9 [tests] Reduce test logs (#8455)
This PR reduces the noisy test logs that currently look like this:

<img width="1013" alt="image" src="https://user-images.githubusercontent.com/229881/186479890-e954ca9c-782e-49b8-89e7-6409185bb490.png">
2022-08-24 14:00:10 -04:00
Steven
32357fc06f [node][next][redwood][remix] Bump @vercel/nft@0.22.0 (#8454)
This PR bumps `@vercel/nft` to version [0.22.0](https://github.com/vercel/nft/releases/tag/0.22.0)
2022-08-24 12:13:34 -04:00
Lee Robinson
f7c57dc539 [examples] Update SvelteKit example for breaking changes. (#8439)
There were recently a few breaking changes introduced into SvelteKit. This PR clones that latest changes from a demo SvelteKit application, while retaining support for Vercel Analytics.

Deployed latest to https://sveltekit-template.vercel.app/ and confirmed analytics are sending.
2022-08-24 15:15:50 +00:00
github-actions[bot]
34f7c35c13 [examples] Upgrade Next.js to version 12.2.5 (#8453)
This auto-generated PR updates Next.js to version 12.2.5

Co-authored-by: Vercel Team Bot <team@zeit.co>
2022-08-24 10:41:54 -04:00
125 changed files with 5993 additions and 7466 deletions

28
.github/CODEOWNERS vendored
View File

@@ -1,23 +1,17 @@
# Documentation # Documentation
# https://help.github.com/en/articles/about-code-owners # https://help.github.com/en/articles/about-code-owners
* @TooTallNate @EndangeredMassa @styfle * @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood
/.github/workflows @TooTallNate @EndangeredMassa @styfle @ijjk /.github/workflows @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @ijjk
/packages/frameworks @TooTallNate @EndangeredMassa @styfle @AndyBitz /packages/cli/src/commands/domains @mglagola @anatrajkovska
/packages/cli/src/commands/domains @javivelasco @mglagola @anatrajkovska /packages/cli/src/commands/certs @mglagola @anatrajkovska
/packages/cli/src/commands/certs @javivelasco @mglagola @anatrajkovska /packages/cli/src/commands/env @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood
/packages/cli/src/commands/env @styfle @lucleray /packages/fs-detectors @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @agadzik @chloetedder
/packages/client @TooTallNate @EndangeredMassa @styfle /packages/middleware @gdborton @vercel/edge-function
/packages/build-utils @TooTallNate @EndangeredMassa @styfle @AndyBitz /packages/node-bridge @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @ijjk
/packages/middleware @gdborton @javivelasco /packages/next @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @ijjk
/packages/node @TooTallNate @EndangeredMassa @styfle /packages/routing-utils @TooTallNate @EndangeredMassa @styfle @cb1kenobi @Ethan-Arrowood @ijjk
/packages/node-bridge @TooTallNate @EndangeredMassa @styfle @ijjk /packages/edge @vercel/edge-function
/packages/next @TooTallNate @ijjk
/packages/go @TooTallNate @EndangeredMassa @styfle
/packages/python @TooTallNate @EndangeredMassa @styfle
/packages/ruby @TooTallNate @EndangeredMassa @styfle
/packages/static-build @TooTallNate @EndangeredMassa @styfle @AndyBitz
/packages/routing-utils @TooTallNate @EndangeredMassa @styfle @ijjk
/examples @leerob /examples @leerob
/examples/create-react-app @Timer /examples/create-react-app @Timer
/examples/nextjs @timneutkens @ijjk @styfle /examples/nextjs @timneutkens @ijjk @styfle

22
.github/workflows/required-pr-label.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
name: Required PR Label
on:
pull_request:
types: [opened, labeled, unlabeled, synchronize]
jobs:
label:
runs-on: ubuntu-latest
steps:
- name: Check PR Labels
uses: actions/github-script@v6
with:
script: |
const labels = context.payload.pull_request.labels.map(l => l.name);
if (labels.filter(l => l.startsWith('area:')).length === 0) {
console.error('\u001b[31mMissing label: Please add at least one "area" label.');
process.exit(1);
}
if (labels.filter(l => l.startsWith('semver:')).length !== 1) {
console.error('\u001b[31mMissing label: Please add exactly one "semver" label.');
process.exit(1);
}
console.log('\u001b[32mSuccess: This pull request has correct labels, thanks!');

View File

@@ -1,7 +1,7 @@
version = 1 version = 1
[merge] [merge]
automerge_label = ["semver-major","semver-minor","semver-patch"] automerge_label = ["pr: automerge"]
blacklist_title_regex = "^WIP.*" blacklist_title_regex = "^WIP.*"
blacklist_labels = ["work in progress"] blacklist_labels = ["work in progress"]
method = "squash" method = "squash"

View File

@@ -17,7 +17,7 @@ const frameworks = (_frameworks as Framework[])
}; };
if (framework.logo) { if (framework.logo) {
framework.logo = `https://res.cloudinary.com/zeit-inc/image/fetch/${framework.logo}`; framework.logo = `https://assets.vercel.com/zeit-inc/image/fetch/${framework.logo}`;
} }
return framework; return framework;

View File

@@ -26,10 +26,11 @@ yarn-error.log*
.pnpm-debug.log* .pnpm-debug.log*
# local env files # local env files
.env.local .env*.local
.env.development.local
.env.test.local
.env.production.local
# vercel # vercel
.vercel .vercel
# typescript
*.tsbuildinfo
next-env.d.ts

View File

@@ -1,6 +1,7 @@
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
reactStrictMode: true, reactStrictMode: true,
swcMinify: true,
} }
module.exports = nextConfig module.exports = nextConfig

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,6 @@
{ {
"name": "nextjs",
"version": "0.1.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
@@ -7,12 +9,12 @@
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"next": "12.1.4", "next": "12.2.5",
"react": "18.0.0", "react": "18.2.0",
"react-dom": "18.0.0" "react-dom": "18.2.0"
}, },
"devDependencies": { "devDependencies": {
"eslint": "8.12.0", "eslint": "8.22.0",
"eslint-config-next": "12.1.4" "eslint-config-next": "12.2.5"
} }
} }

View File

@@ -114,3 +114,16 @@
flex-direction: column; flex-direction: column;
} }
} }
@media (prefers-color-scheme: dark) {
.card,
.footer {
border-color: #222;
}
.code {
background: #111;
}
.logo img {
filter: invert(1);
}
}

View File

@@ -14,3 +14,13 @@ a {
* { * {
box-sizing: border-box; box-sizing: border-box;
} }
@media (prefers-color-scheme: dark) {
html {
color-scheme: dark;
}
body {
color: white;
background: black;
}
}

1703
examples/nextjs/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -8,21 +8,9 @@ Everything you need to build a Svelte project, powered by [`create-svelte`](http
_Live Example: https://sveltekit-template.vercel.app_ _Live Example: https://sveltekit-template.vercel.app_
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npm init svelte
# create a new project in my-app
npm init svelte my-app
```
## Developing ## Developing
Once you've created a project and installed dependencies with `pnpm install`, start a development server: Once you've installed dependencies with `pnpm install`, start a development server:
```bash ```bash
pnpm run dev pnpm run dev

View File

@@ -10,4 +10,8 @@
"sourceMap": true, "sourceMap": true,
"strict": true "strict": true
} }
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias and https://kit.svelte.dev/docs/configuration#files
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
} }

View File

@@ -1,25 +1,25 @@
{ {
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "svelte-kit dev", "dev": "vite dev",
"build": "svelte-kit build", "build": "vite build",
"package": "svelte-kit package", "package": "svelte-kit package",
"preview": "svelte-kit preview", "preview": "vite preview",
"prepare": "svelte-kit sync",
"check": "svelte-check --tsconfig ./jsconfig.json", "check": "svelte-check --tsconfig ./jsconfig.json",
"check:watch": "svelte-check --tsconfig ./jsconfig.json --watch", "check:watch": "svelte-check --tsconfig ./jsconfig.json --watch",
"lint": "prettier --check --plugin-search-dir=. .", "lint": "prettier --check .",
"format": "prettier --write --plugin-search-dir=. ." "format": "prettier --write ."
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/adapter-auto": "1.0.0-next.50", "@sveltejs/adapter-auto": "next",
"@sveltejs/kit": "1.0.0-next.347", "@sveltejs/kit": "next",
"@types/cookie": "^0.4.1", "@types/cookie": "^0.5.1",
"prettier": "^2.5.1", "prettier": "^2.6.2",
"prettier-plugin-svelte": "^2.5.0", "prettier-plugin-svelte": "^2.7.0",
"svelte": "^3.46.0", "svelte": "^3.46.0",
"svelte-check": "^2.2.6", "svelte-check": "^2.7.1",
"typescript": "~4.6.2" "typescript": "^4.7.4",
"vite": "^3.0.8"
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {

1458
examples/sveltekit/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,6 @@
/// <reference types="@sveltejs/kit" />
// See https://kit.svelte.dev/docs/types#app // See https://kit.svelte.dev/docs/types#app
// for information about these interfaces // for information about these interfaces
// and what to do when importing types
declare namespace App { declare namespace App {
interface Locals { interface Locals {
userid: string; userid: string;
@@ -9,7 +8,7 @@ declare namespace App {
// interface Platform {} // interface Platform {}
// interface Session {} // interface PrivateEnv {}
// interface Stuff {} // interface PublicEnv {}
} }

View File

@@ -3,7 +3,7 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" /> <link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width" />
%sveltekit.head% %sveltekit.head%
</head> </head>
<body> <body>

View File

@@ -1,107 +1,107 @@
<script> <script>
import { spring } from 'svelte/motion'; import { spring } from 'svelte/motion';
let count = 0; let count = 0;
const displayed_count = spring(); const displayed_count = spring();
$: displayed_count.set(count); $: displayed_count.set(count);
$: offset = modulo($displayed_count, 1); $: offset = modulo($displayed_count, 1);
/** /**
* @param {number} n * @param {number} n
* @param {number} m * @param {number} m
*/ */
function modulo(n, m) { function modulo(n, m) {
// handle negative numbers // handle negative numbers
return ((n % m) + m) % m; return ((n % m) + m) % m;
} }
</script> </script>
<div class="counter"> <div class="counter">
<button on:click={() => (count -= 1)} aria-label="Decrease the counter by one"> <button on:click={() => (count -= 1)} aria-label="Decrease the counter by one">
<svg aria-hidden="true" viewBox="0 0 1 1"> <svg aria-hidden="true" viewBox="0 0 1 1">
<path d="M0,0.5 L1,0.5" /> <path d="M0,0.5 L1,0.5" />
</svg> </svg>
</button> </button>
<div class="counter-viewport"> <div class="counter-viewport">
<div class="counter-digits" style="transform: translate(0, {100 * offset}%)"> <div class="counter-digits" style="transform: translate(0, {100 * offset}%)">
<strong class="hidden" aria-hidden="true">{Math.floor($displayed_count + 1)}</strong> <strong class="hidden" aria-hidden="true">{Math.floor($displayed_count + 1)}</strong>
<strong>{Math.floor($displayed_count)}</strong> <strong>{Math.floor($displayed_count)}</strong>
</div> </div>
</div> </div>
<button on:click={() => (count += 1)} aria-label="Increase the counter by one"> <button on:click={() => (count += 1)} aria-label="Increase the counter by one">
<svg aria-hidden="true" viewBox="0 0 1 1"> <svg aria-hidden="true" viewBox="0 0 1 1">
<path d="M0,0.5 L1,0.5 M0.5,0 L0.5,1" /> <path d="M0,0.5 L1,0.5 M0.5,0 L0.5,1" />
</svg> </svg>
</button> </button>
</div> </div>
<style> <style>
.counter { .counter {
display: flex; display: flex;
border-top: 1px solid rgba(0, 0, 0, 0.1); border-top: 1px solid rgba(0, 0, 0, 0.1);
border-bottom: 1px solid rgba(0, 0, 0, 0.1); border-bottom: 1px solid rgba(0, 0, 0, 0.1);
margin: 1rem 0; margin: 1rem 0;
} }
.counter button { .counter button {
width: 2em; width: 2em;
padding: 0; padding: 0;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border: 0; border: 0;
background-color: transparent; background-color: transparent;
touch-action: manipulation; touch-action: manipulation;
color: var(--text-color); color: var(--text-color);
font-size: 2rem; font-size: 2rem;
} }
.counter button:hover { .counter button:hover {
background-color: var(--secondary-color); background-color: var(--secondary-color);
} }
svg { svg {
width: 25%; width: 25%;
height: 25%; height: 25%;
} }
path { path {
vector-effect: non-scaling-stroke; vector-effect: non-scaling-stroke;
stroke-width: 2px; stroke-width: 2px;
stroke: var(--text-color); stroke: var(--text-color);
} }
.counter-viewport { .counter-viewport {
width: 8em; width: 8em;
height: 4em; height: 4em;
overflow: hidden; overflow: hidden;
text-align: center; text-align: center;
position: relative; position: relative;
} }
.counter-viewport strong { .counter-viewport strong {
position: absolute; position: absolute;
display: flex; display: flex;
width: 100%; width: 100%;
height: 100%; height: 100%;
font-weight: 400; font-weight: 400;
color: var(--accent-color); color: var(--accent-color);
font-size: 4rem; font-size: 4rem;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.counter-digits { .counter-digits {
position: absolute; position: absolute;
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.hidden { .hidden {
top: -100%; top: -100%;
user-select: none; user-select: none;
} }
</style> </style>

View File

@@ -31,11 +31,11 @@ import { invalidate } from '$app/navigation';
export function enhance(form, { pending, error, result } = {}) { export function enhance(form, { pending, error, result } = {}) {
let current_token; let current_token;
/** @param {SubmitEvent} e */ /** @param {SubmitEvent} event */
async function handle_submit(e) { async function handle_submit(event) {
const token = (current_token = {}); const token = (current_token = {});
e.preventDefault(); event.preventDefault();
const data = new FormData(form); const data = new FormData(form);
@@ -63,11 +63,11 @@ export function enhance(form, { pending, error, result } = {}) {
} else { } else {
console.error(await response.text()); console.error(await response.text());
} }
} catch (e) { } catch (err) {
if (error && e instanceof Error) { if (error && err instanceof Error) {
error({ data, form, error: e, response: null }); error({ data, form, error: err, response: null });
} else { } else {
throw e; throw err;
} }
} }
} }

View File

@@ -1,124 +1,124 @@
<script> <script>
import { page } from '$app/stores'; import { page } from '$app/stores';
import logo from './svelte-logo.svg'; import logo from './svelte-logo.svg';
</script> </script>
<header> <header>
<div class="corner"> <div class="corner">
<a href="https://kit.svelte.dev"> <a href="https://kit.svelte.dev">
<img src={logo} alt="SvelteKit" /> <img src={logo} alt="SvelteKit" />
</a> </a>
</div> </div>
<nav> <nav>
<svg viewBox="0 0 2 3" aria-hidden="true"> <svg viewBox="0 0 2 3" aria-hidden="true">
<path d="M0,0 L1,2 C1.5,3 1.5,3 2,3 L2,0 Z" /> <path d="M0,0 L1,2 C1.5,3 1.5,3 2,3 L2,0 Z" />
</svg> </svg>
<ul> <ul>
<li class:active={$page.url.pathname === '/'}><a sveltekit:prefetch href="/">Home</a></li> <li class:active={$page.url.pathname === '/'}><a sveltekit:prefetch href="/">Home</a></li>
<li class:active={$page.url.pathname === '/about'}> <li class:active={$page.url.pathname === '/about'}>
<a sveltekit:prefetch href="/about">About</a> <a sveltekit:prefetch href="/about">About</a>
</li> </li>
<li class:active={$page.url.pathname === '/todos'}> <li class:active={$page.url.pathname === '/todos'}>
<a sveltekit:prefetch href="/todos">Todos</a> <a sveltekit:prefetch href="/todos">Todos</a>
</li> </li>
</ul> </ul>
<svg viewBox="0 0 2 3" aria-hidden="true"> <svg viewBox="0 0 2 3" aria-hidden="true">
<path d="M0,0 L0,3 C0.5,3 0.5,3 1,2 L2,0 Z" /> <path d="M0,0 L0,3 C0.5,3 0.5,3 1,2 L2,0 Z" />
</svg> </svg>
</nav> </nav>
<div class="corner"> <div class="corner">
<!-- TODO put something else here? github link? --> <!-- TODO put something else here? github link? -->
</div> </div>
</header> </header>
<style> <style>
header { header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} }
.corner { .corner {
width: 3em; width: 3em;
height: 3em; height: 3em;
} }
.corner a { .corner a {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.corner img { .corner img {
width: 2em; width: 2em;
height: 2em; height: 2em;
object-fit: contain; object-fit: contain;
} }
nav { nav {
display: flex; display: flex;
justify-content: center; justify-content: center;
--background: rgba(255, 255, 255, 0.7); --background: rgba(255, 255, 255, 0.7);
} }
svg { svg {
width: 2em; width: 2em;
height: 3em; height: 3em;
display: block; display: block;
} }
path { path {
fill: var(--background); fill: var(--background);
} }
ul { ul {
position: relative; position: relative;
padding: 0; padding: 0;
margin: 0; margin: 0;
height: 3em; height: 3em;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
list-style: none; list-style: none;
background: var(--background); background: var(--background);
background-size: contain; background-size: contain;
} }
li { li {
position: relative; position: relative;
height: 100%; height: 100%;
} }
li.active::before { li.active::before {
--size: 6px; --size: 6px;
content: ''; content: '';
width: 0; width: 0;
height: 0; height: 0;
position: absolute; position: absolute;
top: 0; top: 0;
left: calc(50% - var(--size)); left: calc(50% - var(--size));
border: var(--size) solid transparent; border: var(--size) solid transparent;
border-top: var(--size) solid var(--accent-color); border-top: var(--size) solid var(--accent-color);
} }
nav a { nav a {
display: flex; display: flex;
height: 100%; height: 100%;
align-items: center; align-items: center;
padding: 0 1em; padding: 0 1em;
color: var(--heading-color); color: var(--heading-color);
font-weight: 700; font-weight: 700;
font-size: 0.8rem; font-size: 0.8rem;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.1em; letter-spacing: 0.1em;
text-decoration: none; text-decoration: none;
transition: color 0.2s linear; transition: color 0.2s linear;
} }
a:hover { a:hover {
color: var(--accent-color); color: var(--accent-color);
} }
</style> </style>

View File

@@ -0,0 +1,58 @@
<script>
import Header from '$lib/header/Header.svelte';
import { webVitals } from '$lib/vitals';
import { browser } from '$app/env';
import { page } from '$app/stores';
import '../app.css';
let analyticsId = import.meta.env.VERCEL_ANALYTICS_ID;
$: if (browser && analyticsId) {
webVitals({
path: $page.url.pathname,
params: $page.params,
analyticsId
})
}
</script>
<Header />
<main>
<slot />
</main>
<footer>
<p>visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to learn SvelteKit</p>
</footer>
<style>
main {
flex: 1;
display: flex;
flex-direction: column;
padding: 1rem;
width: 100%;
max-width: 1024px;
margin: 0 auto;
box-sizing: border-box;
}
footer {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 40px;
}
footer a {
font-weight: bold;
}
@media (min-width: 480px) {
footer {
padding: 40px 0;
}
}
</style>

View File

@@ -0,0 +1 @@
export const prerender = true;

View File

@@ -0,0 +1,57 @@
<script>
import Counter from '$lib/Counter.svelte';
</script>
<svelte:head>
<title>Home</title>
<meta name="description" content="Svelte demo app" />
</svelte:head>
<section>
<h1>
<span class="welcome">
<picture>
<source srcset="svelte-welcome.webp" type="image/webp" />
<img src="svelte-welcome.png" alt="Welcome" />
</picture>
</span>
to your new<br />SvelteKit app
</h1>
<h2>
try editing <strong>src/routes/index.svelte</strong>
</h2>
<Counter />
</section>
<style>
section {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
flex: 1;
}
h1 {
width: 100%;
}
.welcome {
display: block;
position: relative;
width: 100%;
height: 0;
padding: 0 0 calc(100% * 495 / 2048) 0;
}
.welcome img {
position: absolute;
width: 100%;
height: 100%;
top: 0;
display: block;
}
</style>

View File

@@ -1,58 +0,0 @@
<script>
import Header from '$lib/header/Header.svelte';
import { webVitals } from '$lib/vitals';
import { browser } from '$app/env';
import { page } from '$app/stores';
import '../app.css';
let analyticsId = import.meta.env.VERCEL_ANALYTICS_ID;
$: if (browser && analyticsId) {
webVitals({
path: $page.url.pathname,
params: $page.params,
analyticsId
})
}
</script>
<Header />
<main>
<slot />
</main>
<footer>
<p>visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to learn SvelteKit</p>
</footer>
<style>
main {
flex: 1;
display: flex;
flex-direction: column;
padding: 1rem;
width: 100%;
max-width: 1024px;
margin: 0 auto;
box-sizing: border-box;
}
footer {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 40px;
}
footer a {
font-weight: bold;
}
@media (min-width: 480px) {
footer {
padding: 40px 0;
}
}
</style>

View File

@@ -1,50 +0,0 @@
<script context="module">
import { browser, dev } from '$app/env';
// we don't need any JS on this page, though we'll load
// it in dev so that we get hot module replacement...
export const hydrate = dev;
// ...but if the client-side router is already loaded
// (i.e. we came here from elsewhere in the app), use it
export const router = browser;
// since there's no dynamic data here, we can prerender
// it so that it gets served as a static asset in prod
export const prerender = true;
</script>
<svelte:head>
<title>About</title>
<meta name="description" content="About this app" />
</svelte:head>
<div class="content">
<h1>About this app</h1>
<p>
This is a <a href="https://kit.svelte.dev">SvelteKit</a> app. You can make your own by typing the
following into your command line and following the prompts:
</p>
<pre>npm init svelte</pre>
<p>
The page you're looking at is purely static HTML, with no client-side interactivity needed.
Because of that, we don't need to load any JavaScript. Try viewing the page's source, or opening
the devtools network panel and reloading.
</p>
<p>
The <a href="/todos">TODOs</a> page illustrates SvelteKit's data loading and form handling. Try using
it with JavaScript disabled!
</p>
</div>
<style>
.content {
width: 100%;
max-width: var(--column-width);
margin: var(--column-margin-top) auto 0 auto;
}
</style>

View File

@@ -0,0 +1,13 @@
import { browser, dev } from '$app/env';
// we don't need any JS on this page, though we'll load
// it in dev so that we get hot module replacement...
export const hydrate = dev;
// ...but if the client-side router is already loaded
// (i.e. we came here from elsewhere in the app), use it
export const router = browser;
// since there's no dynamic data here, we can prerender
// it so that it gets served as a static asset in prod
export const prerender = true;

View File

@@ -0,0 +1,34 @@
<svelte:head>
<title>About</title>
<meta name="description" content="About this app" />
</svelte:head>
<div class="content">
<h1>About this app</h1>
<p>
This is a <a href="https://kit.svelte.dev">SvelteKit</a> app. You can make your own by typing the
following into your command line and following the prompts:
</p>
<pre>npm create svelte@latest</pre>
<p>
The page you're looking at is purely static HTML, with no client-side interactivity needed.
Because of that, we don't need to load any JavaScript. Try viewing the page's source, or opening
the devtools network panel and reloading.
</p>
<p>
The <a href="/todos">TODOs</a> page illustrates SvelteKit's data loading and form handling. Try using
it with JavaScript disabled!
</p>
</div>
<style>
.content {
width: 100%;
max-width: var(--column-width);
margin: var(--column-margin-top) auto 0 auto;
}
</style>

View File

@@ -1,60 +0,0 @@
<script context="module">
export const prerender = true;
</script>
<script>
import Counter from '$lib/Counter.svelte';
</script>
<svelte:head>
<title>Home</title>
<meta name="description" content="Svelte demo app" />
</svelte:head>
<section>
<h1>
<div class="welcome">
<picture>
<source srcset="svelte-welcome.webp" type="image/webp" />
<img src="svelte-welcome.png" alt="Welcome" />
</picture>
</div>
to your new<br />SvelteKit app
</h1>
<h2>
try editing <strong>src/routes/index.svelte</strong>
</h2>
<Counter />
</section>
<style>
section {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
flex: 1;
}
h1 {
width: 100%;
}
.welcome {
position: relative;
width: 100%;
height: 0;
padding: 0 0 calc(100% * 495 / 2048) 0;
}
.welcome img {
position: absolute;
width: 100%;
height: 100%;
top: 0;
display: block;
}
</style>

View File

@@ -0,0 +1,62 @@
import { error } from '@sveltejs/kit';
import { api } from './api';
/**
* @typedef {{
* uid: string;
* created_at: Date;
* text: string;
* done: boolean;
* pending_delete: boolean;
* }} Todo
*/
/** @type {import('./$types').PageServerLoad} */
export const load = async ({ locals }) => {
// locals.userid comes from src/hooks.js
const response = await api('GET', `todos/${locals.userid}`);
if (response.status === 404) {
// user hasn't created a todo list.
// start with an empty array
return {
/** @type {Todo[]} */
todos: []
};
}
if (response.status === 200) {
return {
/** @type {Todo[]} */
todos: await response.json()
};
}
throw error(response.status);
};
/** @type {import('./$types').Action} */
export const POST = async ({ request, locals }) => {
const form = await request.formData();
await api('POST', `todos/${locals.userid}`, {
text: form.get('text')
});
};
/** @type {import('./$types').Action} */
export const PATCH = async ({ request, locals }) => {
const form = await request.formData();
await api('PATCH', `todos/${locals.userid}/${form.get('uid')}`, {
text: form.has('text') ? form.get('text') : undefined,
done: form.has('done') ? !!form.get('done') : undefined
});
};
/** @type {import('./$types').Action} */
export const DELETE = async ({ request, locals }) => {
const form = await request.formData();
await api('DELETE', `todos/${locals.userid}/${form.get('uid')}`);
};

View File

@@ -0,0 +1,180 @@
<script>
import { enhance } from '$lib/form';
import { scale } from 'svelte/transition';
import { flip } from 'svelte/animate';
/** @type {import('./$types').PageData} */
export let data;
</script>
<svelte:head>
<title>Todos</title>
<meta name="description" content="A todo list app" />
</svelte:head>
<div class="todos">
<h1>Todos</h1>
<form
class="new"
action="/todos"
method="post"
use:enhance={{
result: async ({ form }) => {
form.reset();
}
}}
>
<input name="text" aria-label="Add todo" placeholder="+ tap to add a todo" />
</form>
{#each data.todos as todo (todo.uid)}
<div
class="todo"
class:done={todo.done}
transition:scale|local={{ start: 0.7 }}
animate:flip={{ duration: 200 }}
>
<form
action="/todos?_method=PATCH"
method="post"
use:enhance={{
pending: ({ data }) => {
todo.done = !!data.get('done');
}
}}
>
<input type="hidden" name="uid" value={todo.uid} />
<input type="hidden" name="done" value={todo.done ? '' : 'true'} />
<button class="toggle" aria-label="Mark todo as {todo.done ? 'not done' : 'done'}" />
</form>
<form class="text" action="/todos?_method=PATCH" method="post" use:enhance>
<input type="hidden" name="uid" value={todo.uid} />
<input aria-label="Edit todo" type="text" name="text" value={todo.text} />
<button class="save" aria-label="Save todo" />
</form>
<form
action="/todos?_method=DELETE"
method="post"
use:enhance={{
pending: () => (todo.pending_delete = true)
}}
>
<input type="hidden" name="uid" value={todo.uid} />
<button class="delete" aria-label="Delete todo" disabled={todo.pending_delete} />
</form>
</div>
{/each}
</div>
<style>
.todos {
width: 100%;
max-width: var(--column-width);
margin: var(--column-margin-top) auto 0 auto;
line-height: 1;
}
.new {
margin: 0 0 0.5rem 0;
}
input {
border: 1px solid transparent;
}
input:focus-visible {
box-shadow: inset 1px 1px 6px rgba(0, 0, 0, 0.1);
border: 1px solid #ff3e00 !important;
outline: none;
}
.new input {
font-size: 28px;
width: 100%;
padding: 0.5em 1em 0.3em 1em;
box-sizing: border-box;
background: rgba(255, 255, 255, 0.05);
border-radius: 8px;
text-align: center;
}
.todo {
display: grid;
grid-template-columns: 2rem 1fr 2rem;
grid-gap: 0.5rem;
align-items: center;
margin: 0 0 0.5rem 0;
padding: 0.5rem;
background-color: white;
border-radius: 8px;
filter: drop-shadow(2px 4px 6px rgba(0, 0, 0, 0.1));
transform: translate(-1px, -1px);
transition: filter 0.2s, transform 0.2s;
}
.done {
transform: none;
opacity: 0.4;
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.1));
}
form.text {
position: relative;
display: flex;
align-items: center;
flex: 1;
}
.todo input {
flex: 1;
padding: 0.5em 2em 0.5em 0.8em;
border-radius: 3px;
}
.todo button {
width: 2em;
height: 2em;
border: none;
background-color: transparent;
background-position: 50% 50%;
background-repeat: no-repeat;
}
button.toggle {
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 50%;
box-sizing: border-box;
background-size: 1em auto;
}
.done .toggle {
background-image: url("data:image/svg+xml,%3Csvg width='22' height='16' viewBox='0 0 22 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 1.5L7.4375 14.5L1.5 8.5909' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
.delete {
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.5 5V22H19.5V5H4.5Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M10 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M14 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M2 5H22' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M8 5L9.6445 2H14.3885L16 5H8Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3C/svg%3E%0A");
opacity: 0.2;
}
.delete:hover,
.delete:focus {
transition: opacity 0.2s;
opacity: 1;
}
.save {
position: absolute;
right: 0;
opacity: 0;
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 2H3.5C2.67158 2 2 2.67157 2 3.5V20.5C2 21.3284 2.67158 22 3.5 22H20.5C21.3284 22 22 21.3284 22 20.5V3.5C22 2.67157 21.3284 2 20.5 2Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M17 2V11H7.5V2H17Z' fill='white' stroke='white' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M13.5 5.5V7.5' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3Cpath d='M5.99844 2H18.4992' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E%0A");
}
.todo input:focus + .save,
.save:focus {
transition: opacity 0.2s;
opacity: 1;
}
</style>

View File

@@ -1,9 +1,7 @@
/* /*
This module is used by the /todos endpoint to This module is used by the /todos endpoint to
make calls to api.svelte.dev, which stores todos make calls to api.svelte.dev, which stores todos
for each user. The leading underscore indicates that this is for each user.
a private module, _not_ an endpoint visiting /todos/_api
will net you a 404 response.
(The data on the todo app will expire periodically; no (The data on the todo app will expire periodically; no
guarantees are made. Don't use it to organise your life.) guarantees are made. Don't use it to organise your life.)

View File

@@ -1,70 +0,0 @@
import { api } from './_api';
/** @type {import('./__types').RequestHandler} */
export const get = async ({ locals }) => {
// locals.userid comes from src/hooks.js
const response = await api('get', `todos/${locals.userid}`);
if (response.status === 404) {
// user hasn't created a todo list.
// start with an empty array
return {
body: {
todos: []
}
};
}
if (response.status === 200) {
return {
body: {
todos: await response.json()
}
};
}
return {
status: response.status
};
};
/** @type {import('./index').RequestHandler} */
export const post = async ({ request, locals }) => {
const form = await request.formData();
await api('post', `todos/${locals.userid}`, {
text: form.get('text')
});
return {};
};
// If the user has JavaScript disabled, the URL will change to
// include the method override unless we redirect back to /todos
const redirect = {
status: 303,
headers: {
location: '/todos'
}
};
/** @type {import('./index').RequestHandler} */
export const patch = async ({ request, locals }) => {
const form = await request.formData();
await api('patch', `todos/${locals.userid}/${form.get('uid')}`, {
text: form.has('text') ? form.get('text') : undefined,
done: form.has('done') ? !!form.get('done') : undefined
});
return redirect;
};
/** @type {import('./index').RequestHandler} */
export const del = async ({ request, locals }) => {
const form = await request.formData();
await api('delete', `todos/${locals.userid}/${form.get('uid')}`);
return redirect;
};

View File

@@ -1,190 +0,0 @@
<script>
import { enhance } from '$lib/form';
import { scale } from 'svelte/transition';
import { flip } from 'svelte/animate';
/**
* @typedef {{
* uid: string;
* created_at: Date;
* text: string;
* done: boolean;
* pending_delete: boolean;
* }} Todo
*/
/** @type {Todo[]} */
export let todos;
</script>
<svelte:head>
<title>Todos</title>
<meta name="description" content="A todo list app" />
</svelte:head>
<div class="todos">
<h1>Todos</h1>
<form
class="new"
action="/todos"
method="post"
use:enhance={{
result: async ({ form }) => {
form.reset();
}
}}
>
<input name="text" aria-label="Add todo" placeholder="+ tap to add a todo" />
</form>
{#each todos as todo (todo.uid)}
<div
class="todo"
class:done={todo.done}
transition:scale|local={{ start: 0.7 }}
animate:flip={{ duration: 200 }}
>
<form
action="/todos?_method=PATCH"
method="post"
use:enhance={{
pending: ({ data }) => {
todo.done = !!data.get('done');
}
}}
>
<input type="hidden" name="uid" value={todo.uid} />
<input type="hidden" name="done" value={todo.done ? '' : 'true'} />
<button class="toggle" aria-label="Mark todo as {todo.done ? 'not done' : 'done'}" />
</form>
<form class="text" action="/todos?_method=PATCH" method="post" use:enhance>
<input type="hidden" name="uid" value={todo.uid} />
<input aria-label="Edit todo" type="text" name="text" value={todo.text} />
<button class="save" aria-label="Save todo" />
</form>
<form
action="/todos?_method=DELETE"
method="post"
use:enhance={{
pending: () => (todo.pending_delete = true)
}}
>
<input type="hidden" name="uid" value={todo.uid} />
<button class="delete" aria-label="Delete todo" disabled={todo.pending_delete} />
</form>
</div>
{/each}
</div>
<style>
.todos {
width: 100%;
max-width: var(--column-width);
margin: var(--column-margin-top) auto 0 auto;
line-height: 1;
}
.new {
margin: 0 0 0.5rem 0;
}
input {
border: 1px solid transparent;
}
input:focus-visible {
box-shadow: inset 1px 1px 6px rgba(0, 0, 0, 0.1);
border: 1px solid #ff3e00 !important;
outline: none;
}
.new input {
font-size: 28px;
width: 100%;
padding: 0.5em 1em 0.3em 1em;
box-sizing: border-box;
background: rgba(255, 255, 255, 0.05);
border-radius: 8px;
text-align: center;
}
.todo {
display: grid;
grid-template-columns: 2rem 1fr 2rem;
grid-gap: 0.5rem;
align-items: center;
margin: 0 0 0.5rem 0;
padding: 0.5rem;
background-color: white;
border-radius: 8px;
filter: drop-shadow(2px 4px 6px rgba(0, 0, 0, 0.1));
transform: translate(-1px, -1px);
transition: filter 0.2s, transform 0.2s;
}
.done {
transform: none;
opacity: 0.4;
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.1));
}
form.text {
position: relative;
display: flex;
align-items: center;
flex: 1;
}
.todo input {
flex: 1;
padding: 0.5em 2em 0.5em 0.8em;
border-radius: 3px;
}
.todo button {
width: 2em;
height: 2em;
border: none;
background-color: transparent;
background-position: 50% 50%;
background-repeat: no-repeat;
}
button.toggle {
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 50%;
box-sizing: border-box;
background-size: 1em auto;
}
.done .toggle {
background-image: url("data:image/svg+xml,%3Csvg width='22' height='16' viewBox='0 0 22 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 1.5L7.4375 14.5L1.5 8.5909' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
.delete {
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.5 5V22H19.5V5H4.5Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M10 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M14 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M2 5H22' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M8 5L9.6445 2H14.3885L16 5H8Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3C/svg%3E%0A");
opacity: 0.2;
}
.delete:hover,
.delete:focus {
transition: opacity 0.2s;
opacity: 1;
}
.save {
position: absolute;
right: 0;
opacity: 0;
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 2H3.5C2.67158 2 2 2.67157 2 3.5V20.5C2 21.3284 2.67158 22 3.5 22H20.5C21.3284 22 22 21.3284 22 20.5V3.5C22 2.67157 21.3284 2 20.5 2Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M17 2V11H7.5V2H17Z' fill='white' stroke='white' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M13.5 5.5V7.5' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3Cpath d='M5.99844 2H18.4992' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E%0A");
}
.todo input:focus + .save,
.save:focus {
transition: opacity 0.2s;
opacity: 1;
}
</style>

View File

@@ -8,11 +8,6 @@ const config = {
// Override http methods in the Todo forms // Override http methods in the Todo forms
methodOverride: { methodOverride: {
allowed: ['PATCH', 'DELETE'] allowed: ['PATCH', 'DELETE']
},
vite: {
define: {
'import.meta.env.VERCEL_ANALYTICS_ID': JSON.stringify(process.env.VERCEL_ANALYTICS_ID)
}
} }
} }
}; };

View File

@@ -0,0 +1,11 @@
import { sveltekit } from '@sveltejs/kit/vite';
/** @type {import('vite').UserConfig} */
const config = {
plugins: [sveltekit()],
define: {
'import.meta.env.VERCEL_ANALYTICS_ID': JSON.stringify(process.env.VERCEL_ANALYTICS_ID)
}
};
export default config;

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,10 @@
"packages/*" "packages/*"
], ],
"nohoist": [ "nohoist": [
"**/@types/**" "**/@types/**",
"**/typedoc",
"**/typedoc-plugin-markdown",
"**/typedoc-plugin-mdn-links"
] ]
}, },
"dependencies": { "dependencies": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/build-utils", "name": "@vercel/build-utils",
"version": "5.3.2", "version": "5.4.1",
"license": "MIT", "license": "MIT",
"main": "./dist/index.js", "main": "./dist/index.js",
"types": "./dist/index.d.js", "types": "./dist/index.d.js",

View File

@@ -3,5 +3,7 @@ import { getPlatformEnv } from './get-platform-env';
export default function debug(message: string, ...additional: any[]) { export default function debug(message: string, ...additional: any[]) {
if (getPlatformEnv('BUILDER_DEBUG')) { if (getPlatformEnv('BUILDER_DEBUG')) {
console.log(message, ...additional); console.log(message, ...additional);
} else if (process.env.VERCEL_DEBUG_PREFIX) {
console.log(`${process.env.VERCEL_DEBUG_PREFIX}${message}`, ...additional);
} }
} }

View File

@@ -23,8 +23,7 @@ export interface ScanParentDirsResult {
*/ */
cliType: CliType; cliType: CliType;
/** /**
* The file path of found `package.json` file, or `undefined` if none was * The file path of found `package.json` file, or `undefined` if not found.
* found.
*/ */
packageJsonPath?: string; packageJsonPath?: string;
/** /**
@@ -33,8 +32,13 @@ export interface ScanParentDirsResult {
*/ */
packageJson?: PackageJson; packageJson?: PackageJson;
/** /**
* The `lockfileVersion` number from the `package-lock.json` file, * The file path of the lockfile (`yarn.lock`, `package-lock.json`, or `pnpm-lock.yaml`)
* when present. * or `undefined` if not found.
*/
lockfilePath?: string;
/**
* The `lockfileVersion` number from lockfile (`package-lock.json` or `pnpm-lock.yaml`),
* or `undefined` if not found.
*/ */
lockfileVersion?: number; lockfileVersion?: number;
} }
@@ -178,25 +182,9 @@ export async function getNodeBinPath({
}: { }: {
cwd: string; cwd: string;
}): Promise<string> { }): Promise<string> {
const { code, stdout, stderr } = await execAsync('npm', ['bin'], { const { lockfilePath } = await scanParentDirs(cwd);
cwd, const dir = path.dirname(lockfilePath || cwd);
prettyCommand: 'npm bin', return path.join(dir, 'node_modules', '.bin');
// in some rare cases, we saw `npm bin` exit with a non-0 code, but still
// output the right bin path, so we ignore the exit code
ignoreNon0Exit: true,
});
const nodeBinPath = stdout.trim();
if (path.isAbsolute(nodeBinPath)) {
return nodeBinPath;
}
throw new NowBuildError({
code: `BUILD_UTILS_GET_NODE_BIN_PATH`,
message: `Running \`npm bin\` failed to return a valid bin path (code=${code}, stdout=${stdout}, stderr=${stderr})`,
});
} }
async function chmodPlusX(fsPath: string) { async function chmodPlusX(fsPath: string) {
@@ -319,6 +307,7 @@ export async function scanParentDirs(
start: destPath, start: destPath,
filenames: ['yarn.lock', 'package-lock.json', 'pnpm-lock.yaml'], filenames: ['yarn.lock', 'package-lock.json', 'pnpm-lock.yaml'],
}); });
let lockfilePath: string | undefined;
let lockfileVersion: number | undefined; let lockfileVersion: number | undefined;
let cliType: CliType = 'yarn'; let cliType: CliType = 'yarn';
@@ -335,17 +324,25 @@ export async function scanParentDirs(
// Priority order is Yarn > pnpm > npm // Priority order is Yarn > pnpm > npm
if (hasYarnLock) { if (hasYarnLock) {
cliType = 'yarn'; cliType = 'yarn';
lockfilePath = yarnLockPath;
} else if (pnpmLockYaml) { } else if (pnpmLockYaml) {
cliType = 'pnpm'; cliType = 'pnpm';
// just ensure that it is read as a number and not a string lockfilePath = pnpmLockPath;
lockfileVersion = Number(pnpmLockYaml.lockfileVersion); lockfileVersion = Number(pnpmLockYaml.lockfileVersion);
} else if (packageLockJson) { } else if (packageLockJson) {
cliType = 'npm'; cliType = 'npm';
lockfilePath = npmLockPath;
lockfileVersion = packageLockJson.lockfileVersion; lockfileVersion = packageLockJson.lockfileVersion;
} }
const packageJsonPath = pkgJsonPath || undefined; const packageJsonPath = pkgJsonPath || undefined;
return { cliType, packageJson, lockfileVersion, packageJsonPath }; return {
cliType,
packageJson,
lockfilePath,
lockfileVersion,
packageJsonPath,
};
} }
export async function walkParentDirs({ export async function walkParentDirs({

View File

@@ -0,0 +1,46 @@
import { join, parse } from 'path';
import { getNodeBinPath } from '../src';
describe('Test `getNodeBinPath()`', () => {
it('should work with npm7', async () => {
const cwd = join(__dirname, 'fixtures', '20-npm-7');
const result = await getNodeBinPath({ cwd });
expect(result).toBe(join(cwd, 'node_modules', '.bin'));
});
it('should work with yarn', async () => {
const cwd = join(__dirname, 'fixtures', '19-yarn-v2');
const result = await getNodeBinPath({ cwd });
expect(result).toBe(join(cwd, 'node_modules', '.bin'));
});
it('should work with npm 6', async () => {
const cwd = join(__dirname, 'fixtures', '08-yarn-npm/with-npm');
const result = await getNodeBinPath({ cwd });
expect(result).toBe(join(cwd, 'node_modules', '.bin'));
});
it('should work with npm workspaces', async () => {
const cwd = join(__dirname, 'fixtures', '21-npm-workspaces/a');
const result = await getNodeBinPath({ cwd });
expect(result).toBe(join(cwd, '..', 'node_modules', '.bin'));
});
it('should work with pnpm', async () => {
const cwd = join(__dirname, 'fixtures', '22-pnpm');
const result = await getNodeBinPath({ cwd });
expect(result).toBe(join(cwd, 'node_modules', '.bin'));
});
it('should work with pnpm workspaces', async () => {
const cwd = join(__dirname, 'fixtures', '23-pnpm-workspaces/c');
const result = await getNodeBinPath({ cwd });
expect(result).toBe(join(cwd, '..', 'node_modules', '.bin'));
});
it('should fallback to cwd if no lockfile found', async () => {
const cwd = parse(process.cwd()).root;
const result = await getNodeBinPath({ cwd });
expect(result).toBe(join(cwd, 'node_modules', '.bin'));
});
});

View File

@@ -501,6 +501,7 @@ it('should return lockfileVersion 2 with npm7', async () => {
const result = await scanParentDirs(fixture); const result = await scanParentDirs(fixture);
expect(result.cliType).toEqual('npm'); expect(result.cliType).toEqual('npm');
expect(result.lockfileVersion).toEqual(2); expect(result.lockfileVersion).toEqual(2);
expect(result.lockfilePath).toEqual(path.join(fixture, 'package-lock.json'));
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json')); expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
}); });
@@ -509,6 +510,7 @@ it('should not return lockfileVersion with yarn', async () => {
const result = await scanParentDirs(fixture); const result = await scanParentDirs(fixture);
expect(result.cliType).toEqual('yarn'); expect(result.cliType).toEqual('yarn');
expect(result.lockfileVersion).toEqual(undefined); expect(result.lockfileVersion).toEqual(undefined);
expect(result.lockfilePath).toEqual(path.join(fixture, 'yarn.lock'));
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json')); expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
}); });
@@ -517,6 +519,7 @@ it('should return lockfileVersion 1 with older versions of npm', async () => {
const result = await scanParentDirs(fixture); const result = await scanParentDirs(fixture);
expect(result.cliType).toEqual('npm'); expect(result.cliType).toEqual('npm');
expect(result.lockfileVersion).toEqual(1); expect(result.lockfileVersion).toEqual(1);
expect(result.lockfilePath).toEqual(path.join(fixture, 'package-lock.json'));
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json')); expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
}); });
@@ -525,6 +528,9 @@ it('should detect npm Workspaces', async () => {
const result = await scanParentDirs(fixture); const result = await scanParentDirs(fixture);
expect(result.cliType).toEqual('npm'); expect(result.cliType).toEqual('npm');
expect(result.lockfileVersion).toEqual(2); expect(result.lockfileVersion).toEqual(2);
expect(result.lockfilePath).toEqual(
path.join(fixture, '..', 'package-lock.json')
);
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json')); expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
}); });
@@ -533,6 +539,7 @@ it('should detect pnpm without workspace', async () => {
const result = await scanParentDirs(fixture); const result = await scanParentDirs(fixture);
expect(result.cliType).toEqual('pnpm'); expect(result.cliType).toEqual('pnpm');
expect(result.lockfileVersion).toEqual(5.3); expect(result.lockfileVersion).toEqual(5.3);
expect(result.lockfilePath).toEqual(path.join(fixture, 'pnpm-lock.yaml'));
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json')); expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
}); });
@@ -541,6 +548,9 @@ it('should detect pnpm with workspaces', async () => {
const result = await scanParentDirs(fixture); const result = await scanParentDirs(fixture);
expect(result.cliType).toEqual('pnpm'); expect(result.cliType).toEqual('pnpm');
expect(result.lockfileVersion).toEqual(5.3); expect(result.lockfileVersion).toEqual(5.3);
expect(result.lockfilePath).toEqual(
path.join(fixture, '..', 'pnpm-lock.yaml')
);
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json')); expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
}); });
@@ -552,6 +562,7 @@ it('should detect package.json in nested backend', async () => {
const result = await scanParentDirs(fixture); const result = await scanParentDirs(fixture);
expect(result.cliType).toEqual('yarn'); expect(result.cliType).toEqual('yarn');
expect(result.lockfileVersion).toEqual(undefined); expect(result.lockfileVersion).toEqual(undefined);
// There is no lockfile but this test will pick up vercel/vercel/yarn.lock
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json')); expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
}); });
@@ -563,6 +574,7 @@ it('should detect package.json in nested frontend', async () => {
const result = await scanParentDirs(fixture); const result = await scanParentDirs(fixture);
expect(result.cliType).toEqual('yarn'); expect(result.cliType).toEqual('yarn');
expect(result.lockfileVersion).toEqual(undefined); expect(result.lockfileVersion).toEqual(undefined);
// There is no lockfile but this test will pick up vercel/vercel/yarn.lock
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json')); expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
}); });

View File

@@ -1,6 +1,6 @@
{ {
"name": "vercel", "name": "vercel",
"version": "28.1.4", "version": "28.2.1",
"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",
@@ -41,16 +41,16 @@
"node": ">= 14" "node": ">= 14"
}, },
"dependencies": { "dependencies": {
"@vercel/build-utils": "5.3.2", "@vercel/build-utils": "5.4.1",
"@vercel/go": "2.2.2", "@vercel/go": "2.2.4",
"@vercel/hydrogen": "0.0.15", "@vercel/hydrogen": "0.0.17",
"@vercel/next": "3.1.21", "@vercel/next": "3.1.23",
"@vercel/node": "2.5.10", "@vercel/node": "2.5.12",
"@vercel/python": "3.1.11", "@vercel/python": "3.1.13",
"@vercel/redwood": "1.0.19", "@vercel/redwood": "1.0.21",
"@vercel/remix": "1.0.20", "@vercel/remix": "1.0.22",
"@vercel/ruby": "1.3.28", "@vercel/ruby": "1.3.30",
"@vercel/static-build": "1.0.19", "@vercel/static-build": "1.0.21",
"update-notifier": "5.1.0" "update-notifier": "5.1.0"
}, },
"devDependencies": { "devDependencies": {
@@ -85,7 +85,6 @@
"@types/node-fetch": "2.5.10", "@types/node-fetch": "2.5.10",
"@types/npm-package-arg": "6.1.0", "@types/npm-package-arg": "6.1.0",
"@types/pluralize": "0.0.29", "@types/pluralize": "0.0.29",
"@types/progress": "2.0.3",
"@types/psl": "1.1.0", "@types/psl": "1.1.0",
"@types/semver": "6.0.1", "@types/semver": "6.0.1",
"@types/tar-fs": "1.16.1", "@types/tar-fs": "1.16.1",
@@ -96,9 +95,9 @@
"@types/which": "1.3.2", "@types/which": "1.3.2",
"@types/write-json-file": "2.2.1", "@types/write-json-file": "2.2.1",
"@types/yauzl-promise": "2.1.0", "@types/yauzl-promise": "2.1.0",
"@vercel/client": "12.2.1", "@vercel/client": "12.2.3",
"@vercel/frameworks": "1.1.3", "@vercel/frameworks": "1.1.3",
"@vercel/fs-detectors": "2.0.5", "@vercel/fs-detectors": "2.1.0",
"@vercel/fun": "1.0.4", "@vercel/fun": "1.0.4",
"@vercel/ncc": "0.24.0", "@vercel/ncc": "0.24.0",
"@zeit/source-map-support": "0.6.2", "@zeit/source-map-support": "0.6.2",
@@ -153,7 +152,6 @@
"ora": "3.4.0", "ora": "3.4.0",
"pcre-to-regexp": "1.0.0", "pcre-to-regexp": "1.0.0",
"pluralize": "7.0.0", "pluralize": "7.0.0",
"progress": "2.0.3",
"promisepipe": "3.0.0", "promisepipe": "3.0.0",
"psl": "1.1.31", "psl": "1.1.31",
"qr-image": "3.2.0", "qr-image": "3.2.0",

View File

@@ -4,7 +4,7 @@ const { statSync } = require('fs');
const pkg = require('../package'); const pkg = require('../package');
function error(command) { function error(command) {
console.error('> Error!', command); console.error('> Error:', command);
} }
function debug(str) { function debug(str) {

View File

@@ -37,6 +37,7 @@ import cliPkg from '../util/pkg';
import readJSONFile from '../util/read-json-file'; import readJSONFile from '../util/read-json-file';
import { CantParseJSONFile } from '../util/errors-ts'; import { CantParseJSONFile } from '../util/errors-ts';
import { import {
pickOverrides,
ProjectLinkAndSettings, ProjectLinkAndSettings,
readProjectSettings, readProjectSettings,
} from '../util/projects/project-settings'; } from '../util/projects/project-settings';
@@ -278,6 +279,11 @@ async function doBuild(
if (pkg instanceof CantParseJSONFile) throw pkg; if (pkg instanceof CantParseJSONFile) throw pkg;
if (vercelConfig instanceof CantParseJSONFile) throw vercelConfig; if (vercelConfig instanceof CantParseJSONFile) throw vercelConfig;
const projectSettings = {
...project.settings,
...pickOverrides(vercelConfig || {}),
};
// Get a list of source files // Get a list of source files
const files = (await getFiles(workPath, client)).map(f => const files = (await getFiles(workPath, client)).map(f =>
normalizePath(relative(workPath, f)) normalizePath(relative(workPath, f))
@@ -313,7 +319,7 @@ async function doBuild(
// Detect the Vercel Builders that will need to be invoked // Detect the Vercel Builders that will need to be invoked
const detectedBuilders = await detectBuilders(files, pkg, { const detectedBuilders = await detectBuilders(files, pkg, {
...vercelConfig, ...vercelConfig,
projectSettings: project.settings, projectSettings,
ignoreBuildScript: true, ignoreBuildScript: true,
featHandleMiss: true, featHandleMiss: true,
}); });
@@ -425,14 +431,14 @@ async function doBuild(
const buildConfig: Config = isZeroConfig const buildConfig: Config = isZeroConfig
? { ? {
outputDirectory: project.settings.outputDirectory ?? undefined, outputDirectory: projectSettings.outputDirectory ?? undefined,
...build.config, ...build.config,
projectSettings: project.settings, projectSettings,
installCommand: project.settings.installCommand ?? undefined, installCommand: projectSettings.installCommand ?? undefined,
devCommand: project.settings.devCommand ?? undefined, devCommand: projectSettings.devCommand ?? undefined,
buildCommand: project.settings.buildCommand ?? undefined, buildCommand: projectSettings.buildCommand ?? undefined,
framework: project.settings.framework, framework: projectSettings.framework,
nodeVersion: project.settings.nodeVersion, nodeVersion: projectSettings.nodeVersion,
} }
: build.config || {}; : build.config || {};
const buildOptions: BuildOptions = { const buildOptions: BuildOptions = {

View File

@@ -47,9 +47,7 @@ import {
import { SchemaValidationFailed } from '../../util/errors'; import { SchemaValidationFailed } from '../../util/errors';
import purchaseDomainIfAvailable from '../../util/domains/purchase-domain-if-available'; import purchaseDomainIfAvailable from '../../util/domains/purchase-domain-if-available';
import confirm from '../../util/input/confirm'; import confirm from '../../util/input/confirm';
import editProjectSettings, { import editProjectSettings from '../../util/input/edit-project-settings';
PartialProjectSettings,
} from '../../util/input/edit-project-settings';
import { import {
getLinkedProject, getLinkedProject,
linkFolderToProject, linkFolderToProject,
@@ -73,6 +71,7 @@ import { createGitMeta } from '../../util/create-git-meta';
import { isValidArchive } from '../../util/deploy/validate-archive-format'; import { isValidArchive } from '../../util/deploy/validate-archive-format';
import { parseEnv } from '../../util/parse-env'; import { parseEnv } from '../../util/parse-env';
import { errorToString, isErrnoException, isError } from '../../util/is-error'; import { errorToString, isErrnoException, isError } from '../../util/is-error';
import { pickOverrides } from '../../util/projects/project-settings';
export default async (client: Client): Promise<number> => { export default async (client: Client): Promise<number> => {
const { output } = client; const { output } = client;
@@ -509,14 +508,7 @@ export default async (client: Client): Promise<number> => {
let deployStamp = stamp(); let deployStamp = stamp();
let deployment = null; let deployment = null;
const localConfigurationOverrides: PartialProjectSettings = { const localConfigurationOverrides = pickOverrides(localConfig);
buildCommand: localConfig?.buildCommand,
devCommand: localConfig?.devCommand,
framework: localConfig?.framework,
commandForIgnoringBuildStep: localConfig?.ignoreCommand,
installCommand: localConfig?.installCommand,
outputDirectory: localConfig?.outputDirectory,
};
try { try {
const createArgs: any = { const createArgs: any = {

View File

@@ -6,7 +6,6 @@ import parseListen from '../../util/dev/parse-listen';
import { ProjectEnvVariable } from '../../types'; 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 { 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';
@@ -31,10 +30,7 @@ export default async function dev(
const listen = parseListen(opts['--listen'] || '3000'); const listen = parseListen(opts['--listen'] || '3000');
// retrieve dev command // retrieve dev command
let [link, frameworks] = await Promise.all([ let link = await getLinkedProject(client, cwd);
getLinkedProject(client, cwd),
getFrameworks(client),
]);
if (link.status === 'not_linked' && !process.env.__VERCEL_SKIP_DEV_CMD) { if (link.status === 'not_linked' && !process.env.__VERCEL_SKIP_DEV_CMD) {
link = await setupAndLink(client, cwd, { link = await setupAndLink(client, cwd, {
@@ -60,7 +56,6 @@ export default async function dev(
return link.exitCode; return link.exitCode;
} }
let devCommand: string | undefined;
let projectSettings: ProjectSettings | undefined; let projectSettings: ProjectSettings | undefined;
let projectEnvs: ProjectEnvVariable[] = []; let projectEnvs: ProjectEnvVariable[] = [];
let systemEnvValues: string[] = []; let systemEnvValues: string[] = [];
@@ -70,19 +65,6 @@ export default async function dev(
projectSettings = project; projectSettings = project;
if (project.devCommand) {
devCommand = project.devCommand;
} else if (project.framework) {
const framework = frameworks.find(f => f.slug === project.framework);
if (framework) {
const defaults = framework.settings.devCommand.value;
if (defaults) {
devCommand = defaults;
}
}
}
if (project.rootDirectory) { if (project.rootDirectory) {
cwd = join(cwd, project.rootDirectory); cwd = join(cwd, project.rootDirectory);
} }
@@ -95,16 +77,17 @@ export default async function dev(
]); ]);
} }
// This is just for tests - can be removed once project settings const devServer = new DevServer(cwd, {
// are respected locally in `.vercel/project.json` output,
if (process.env.VERCEL_DEV_COMMAND) { projectSettings,
devCommand = process.env.VERCEL_DEV_COMMAND; projectEnvs,
} systemEnvValues,
});
// If there is no Development Command, we must delete the // If there is no Development Command, we must delete the
// v3 Build Output because it will incorrectly be detected by // v3 Build Output because it will incorrectly be detected by
// @vercel/static-build in BuildOutputV3.getBuildOutputDirectory() // @vercel/static-build in BuildOutputV3.getBuildOutputDirectory()
if (!devCommand) { if (!devServer.devCommand) {
const outputDir = join(cwd, OUTPUT_DIR); const outputDir = join(cwd, OUTPUT_DIR);
if (await fs.pathExists(outputDir)) { if (await fs.pathExists(outputDir)) {
output.log(`Removing ${OUTPUT_DIR}`); output.log(`Removing ${OUTPUT_DIR}`);
@@ -112,13 +95,5 @@ export default async function dev(
} }
} }
const devServer = new DevServer(cwd, {
output,
devCommand,
projectSettings,
projectEnvs,
systemEnvValues,
});
await devServer.start(...listen); await devServer.start(...listen);
} }

View File

@@ -25,7 +25,7 @@ const help = () => {
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline( -t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
'TOKEN' 'TOKEN'
)} Login token )} Login token
-y, --yes Skip questions when setting up new project using default scope and settings -y, --yes Skip confirmation when connecting a Git provider
${chalk.dim('Examples:')} ${chalk.dim('Examples:')}
@@ -35,7 +35,9 @@ const help = () => {
${chalk.cyan(`$ ${getPkgName()} git connect`)} ${chalk.cyan(`$ ${getPkgName()} git connect`)}
${chalk.gray('')} Connect your Vercel Project to a Git repository using the remote URL ${chalk.gray(
''
)} Connect your Vercel Project to a Git repository using the remote URL
${chalk.cyan( ${chalk.cyan(
`$ ${getPkgName()} git connect https://github.com/user/repo.git` `$ ${getPkgName()} git connect https://github.com/user/repo.git`

View File

@@ -7,7 +7,7 @@ try {
process.cwd(); process.cwd();
} catch (err: unknown) { } catch (err: unknown) {
if (isError(err) && err.message.includes('uv_cwd')) { if (isError(err) && err.message.includes('uv_cwd')) {
console.error('Error! The current working directory does not exist.'); console.error('Error: The current working directory does not exist.');
process.exit(1); process.exit(1);
} }
} }

View File

@@ -1,5 +1,4 @@
import bytes from 'bytes'; import bytes from 'bytes';
import Progress from 'progress';
import chalk from 'chalk'; import chalk from 'chalk';
import { import {
ArchiveFormat, ArchiveFormat,
@@ -8,6 +7,7 @@ import {
VercelClientOptions, VercelClientOptions,
} from '@vercel/client'; } from '@vercel/client';
import { Output } from '../output'; import { Output } from '../output';
import { progress } from '../output/progress';
import Now from '../../util'; import Now from '../../util';
import { Org } from '../../types'; import { Org } from '../../types';
import ua from '../ua'; import ua from '../ua';
@@ -68,7 +68,6 @@ export default async function processDeployment({
} = args; } = args;
const { debug } = output; const { debug } = output;
let bar: Progress | null = null;
const { env = {} } = requestBody; const { env = {} } = requestBody;
@@ -114,32 +113,28 @@ export default async function processDeployment({
const missingSize = missing const missingSize = missing
.map((sha: string) => total.get(sha).data.length) .map((sha: string) => total.get(sha).data.length)
.reduce((a: number, b: number) => a + b, 0); .reduce((a: number, b: number) => a + b, 0);
const totalSizeHuman = bytes.format(missingSize, { decimalPlaces: 1 });
output.stopSpinner();
bar = new Progress(`${chalk.gray('>')} Upload [:bar] :percent :etas`, {
width: 20,
complete: '=',
incomplete: '',
total: missingSize,
clear: true,
});
bar.tick(0);
uploads.forEach((e: any) => uploads.forEach((e: any) =>
e.on('progress', () => { e.on('progress', () => {
if (!bar) return; const uploadedBytes = uploads.reduce((acc: number, e: any) => {
const totalBytesUploaded = uploads.reduce((acc: number, e: any) => {
return acc + e.bytesUploaded; return acc + e.bytesUploaded;
}, 0); }, 0);
// set the current progress bar value
bar.curr = totalBytesUploaded;
// trigger rendering
bar.tick(0);
if (bar.complete) { const bar = progress(uploadedBytes, missingSize);
if (!bar || uploadedBytes === missingSize) {
output.spinner(deployingSpinnerVal, 0); output.spinner(deployingSpinnerVal, 0);
} else {
const uploadedHuman = bytes.format(uploadedBytes, {
decimalPlaces: 1,
fixedDecimals: true,
});
output.spinner(
`Uploading ${chalk.reset(
`[${bar}] (${uploadedHuman}/${totalSizeHuman})`
)}`,
0
);
} }
}) })
); );
@@ -154,10 +149,6 @@ export default async function processDeployment({
} }
if (event.type === 'created') { if (event.type === 'created') {
if (bar && !bar.complete) {
bar.tick(bar.total + 1);
}
await linkFolderToProject( await linkFolderToProject(
output, output,
cwd || paths[0], cwd || paths[0],

View File

@@ -13,6 +13,7 @@ import {
Lambda, Lambda,
FileBlob, FileBlob,
FileFsRef, FileFsRef,
normalizePath,
} from '@vercel/build-utils'; } from '@vercel/build-utils';
import { isOfficialRuntime } from '@vercel/fs-detectors'; import { isOfficialRuntime } from '@vercel/fs-detectors';
import plural from 'pluralize'; import plural from 'pluralize';
@@ -269,7 +270,9 @@ export async function executeBuild(
const { cleanUrls } = vercelConfig; const { cleanUrls } = vercelConfig;
// Mimic fmeta-util and perform file renaming // Mimic fmeta-util and perform file renaming
Object.entries(output).forEach(([path, value]) => { for (const [originalPath, value] of Object.entries(output)) {
let path = normalizePath(originalPath);
if (cleanUrls && path.endsWith('.html')) { if (cleanUrls && path.endsWith('.html')) {
path = path.slice(0, -5); path = path.slice(0, -5);
@@ -284,7 +287,7 @@ export async function executeBuild(
} }
output[path] = value; output[path] = value;
}); }
// Convert the JSON-ified output map back into their corresponding `File` // Convert the JSON-ified output map back into their corresponding `File`
// subclass type instances. // subclass type instances.

View File

@@ -101,6 +101,7 @@ import {
isError, isError,
isSpawnError, isSpawnError,
} from '../is-error'; } from '../is-error';
import { pickOverrides } from '../projects/project-settings';
const frontendRuntimeSet = new Set( const frontendRuntimeSet = new Set(
frameworkList.map(f => f.useRuntime?.use || '@vercel/static-build') frameworkList.map(f => f.useRuntime?.use || '@vercel/static-build')
@@ -136,6 +137,7 @@ export default class DevServer {
public address: string; public address: string;
public devCacheDir: string; public devCacheDir: string;
private currentDevCommand?: string;
private caseSensitive: boolean; private caseSensitive: boolean;
private apiDir: string | null; private apiDir: string | null;
private apiExtensions: Set<string>; private apiExtensions: Set<string>;
@@ -149,10 +151,10 @@ export default class DevServer {
private watchAggregationTimeout: number; private watchAggregationTimeout: number;
private filter: (path: string) => boolean; private filter: (path: string) => boolean;
private podId: string; private podId: string;
private devCommand?: string;
private devProcess?: ChildProcess; private devProcess?: ChildProcess;
private devProcessPort?: number; private devProcessPort?: number;
private devServerPids: Set<number>; private devServerPids: Set<number>;
private originalProjectSettings?: ProjectSettings;
private projectSettings?: ProjectSettings; private projectSettings?: ProjectSettings;
private vercelConfigWarning: boolean; private vercelConfigWarning: boolean;
@@ -173,7 +175,7 @@ export default class DevServer {
this.projectEnvs = options.projectEnvs || []; this.projectEnvs = options.projectEnvs || [];
this.files = {}; this.files = {};
this.address = ''; this.address = '';
this.devCommand = options.devCommand; this.originalProjectSettings = options.projectSettings;
this.projectSettings = options.projectSettings; this.projectSettings = options.projectSettings;
this.caseSensitive = false; this.caseSensitive = false;
this.apiDir = null; this.apiDir = null;
@@ -549,6 +551,23 @@ export default class DevServer {
return this.getVercelConfigPromise; return this.getVercelConfigPromise;
} }
get devCommand() {
if (this.projectSettings?.devCommand) {
return this.projectSettings.devCommand;
} else if (this.projectSettings?.framework) {
const frameworkSlug = this.projectSettings.framework;
const framework = frameworkList.find(f => f.slug === frameworkSlug);
if (framework) {
const defaults = framework.settings.devCommand.value;
if (defaults) {
return defaults;
}
}
}
return undefined;
}
async _getVercelConfig(): Promise<VercelConfig> { async _getVercelConfig(): Promise<VercelConfig> {
const configPath = getVercelConfigPath(this.cwd); const configPath = getVercelConfigPath(this.cwd);
@@ -563,6 +582,12 @@ export default class DevServer {
]); ]);
await this.validateVercelConfig(vercelConfig); await this.validateVercelConfig(vercelConfig);
this.projectSettings = {
...this.originalProjectSettings,
...pickOverrides(vercelConfig),
};
const { error: routeError, routes: maybeRoutes } = const { error: routeError, routes: maybeRoutes } =
getTransformedRoutes(vercelConfig); getTransformedRoutes(vercelConfig);
if (routeError) { if (routeError) {
@@ -703,6 +728,11 @@ export default class DevServer {
} }
this.envConfigs = { buildEnv, runEnv, allEnv }; this.envConfigs = { buildEnv, runEnv, allEnv };
// If the `devCommand` was modified via project settings
// overrides then the dev process needs to be restarted
await this.runDevCommand();
return vercelConfig; return vercelConfig;
} }
@@ -1856,7 +1886,9 @@ export default class DevServer {
devCacheDir, devCacheDir,
env: { env: {
...envConfigs.runEnv, ...envConfigs.runEnv,
VERCEL_BUILDER_DEBUG: this.output.debugEnabled ? '1' : undefined, VERCEL_DEBUG_PREFIX: this.output.debugEnabled
? '[builder]'
: undefined,
}, },
buildEnv: { ...envConfigs.buildEnv }, buildEnv: { ...envConfigs.buildEnv },
}, },
@@ -2203,10 +2235,21 @@ export default class DevServer {
async runDevCommand() { async runDevCommand() {
const { devCommand, cwd } = this; const { devCommand, cwd } = this;
if (devCommand === this.currentDevCommand) {
// `devCommand` has not changed, so don't restart frontend dev process
return;
}
this.currentDevCommand = devCommand;
if (!devCommand) { if (!devCommand) {
return; return;
} }
if (this.devProcess) {
await treeKill(this.devProcess.pid);
}
this.output.log( this.output.log(
`Running Dev Command ${chalk.cyan.bold(`${devCommand}`)}` `Running Dev Command ${chalk.cyan.bold(`${devCommand}`)}`
); );

View File

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

View File

@@ -73,7 +73,7 @@ export class Output {
link?: string, link?: string,
action = 'Learn More' action = 'Learn More'
) => { ) => {
this.print(`${chalk.red(`Error!`)} ${str}\n`); this.print(`${chalk.red(`Error:`)} ${str}\n`);
const details = slug ? `https://err.sh/vercel/${slug}` : link; const details = slug ? `https://err.sh/vercel/${slug}` : link;
if (details) { if (details) {
this.print(`${chalk.bold(action)}: ${renderLink(details)}\n`); this.print(`${chalk.bold(action)}: ${renderLink(details)}\n`);

View File

@@ -23,5 +23,5 @@ export default function error(
metric.exception(messages.join('\n')).send(); metric.exception(messages.join('\n')).send();
} }
return `${chalk.red('Error!')} ${messages.join('\n')}`; return `${chalk.red('Error:')} ${messages.join('\n')}`;
} }

View File

@@ -0,0 +1,23 @@
export interface ProgressOptions {
width?: number;
complete?: string;
incomplete?: string;
}
/**
* Returns a raw progress bar string.
*/
export function progress(
current: number,
total: number,
opts: ProgressOptions = {}
): string | null {
const { width = 20, complete = '=', incomplete = '-' } = opts;
if (total <= 0 || current < 0 || current > total) {
// Let the caller decide how to handle out-of-range values
return null;
}
const unit = total / width;
const pos = Math.floor(current / unit);
return `${complete.repeat(pos)}${incomplete.repeat(width - pos)}`;
}

View File

@@ -2,6 +2,8 @@ import { outputJSON } from 'fs-extra';
import { Org, Project, ProjectLink } from '../../types'; import { Org, Project, ProjectLink } from '../../types';
import { getLinkFromDir, VERCEL_DIR, VERCEL_DIR_PROJECT } from './link'; import { getLinkFromDir, VERCEL_DIR, VERCEL_DIR_PROJECT } from './link';
import { join } from 'path'; import { join } from 'path';
import { VercelConfig } from '@vercel/client';
import { PartialProjectSettings } from '../input/edit-project-settings';
export type ProjectLinkAndSettings = ProjectLink & { export type ProjectLinkAndSettings = ProjectLink & {
settings: { settings: {
@@ -51,3 +53,26 @@ export async function writeProjectSettings(
export async function readProjectSettings(cwd: string) { export async function readProjectSettings(cwd: string) {
return await getLinkFromDir<ProjectLinkAndSettings>(cwd); return await getLinkFromDir<ProjectLinkAndSettings>(cwd);
} }
export function pickOverrides(
vercelConfig: VercelConfig
): PartialProjectSettings {
const overrides: PartialProjectSettings = {};
for (const prop of [
'buildCommand',
'devCommand',
'framework',
'ignoreCommand',
'installCommand',
'outputDirectory',
] as const) {
if (typeof vercelConfig[prop] !== 'undefined') {
if (prop === 'ignoreCommand') {
overrides.commandForIgnoringBuildStep = vercelConfig[prop];
} else {
overrides[prop] = vercelConfig[prop];
}
}
}
return overrides;
}

View File

@@ -22,28 +22,28 @@ export async function validateRootDirectory(
const suffix = errorSuffix ? ` ${errorSuffix}` : ''; const suffix = errorSuffix ? ` ${errorSuffix}` : '';
if (!pathStat) { if (!pathStat) {
output.print( output.error(
`${chalk.red('Error!')} The provided path ${chalk.cyan( `The provided path ${chalk.cyan(
`${toHumanPath(path)}` `${toHumanPath(path)}`
)} does not exist.${suffix}\n` )} does not exist.${suffix}`
); );
return false; return false;
} }
if (!pathStat.isDirectory()) { if (!pathStat.isDirectory()) {
output.print( output.error(
`${chalk.red('Error!')} The provided path ${chalk.cyan( `The provided path ${chalk.cyan(
`${toHumanPath(path)}` `${toHumanPath(path)}`
)} is a file, but expected a directory.${suffix}\n` )} is a file, but expected a directory.${suffix}`
); );
return false; return false;
} }
if (!path.startsWith(cwd)) { if (!path.startsWith(cwd)) {
output.print( output.error(
`${chalk.red('Error!')} The provided path ${chalk.cyan( `The provided path ${chalk.cyan(
`${toHumanPath(path)}` `${toHumanPath(path)}`
)} is outside of the project.${suffix}\n` )} is outside of the project.${suffix}`
); );
return false; return false;
} }
@@ -59,7 +59,7 @@ export default async function validatePaths(
// can't deploy more than 1 path // can't deploy more than 1 path
if (paths.length > 1) { if (paths.length > 1) {
output.print(`${chalk.red('Error!')} Can't deploy more than one path.\n`); output.error(`Can't deploy more than one path.`);
return { valid: false, exitCode: 1 }; return { valid: false, exitCode: 1 };
} }
@@ -69,11 +69,7 @@ export default async function validatePaths(
const pathStat = await stat(path).catch(() => null); const pathStat = await stat(path).catch(() => null);
if (!pathStat) { if (!pathStat) {
output.print( output.error(`Could not find ${chalk.cyan(`${toHumanPath(path)}`)}`);
`${chalk.red('Error!')} Could not find ${chalk.cyan(
`${toHumanPath(path)}`
)}\n`
);
return { valid: false, exitCode: 1 }; return { valid: false, exitCode: 1 };
} }

View File

@@ -1,5 +1,5 @@
{ {
"name": "nextjs", "name": "nextjs-node",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"dev": "next", "dev": "next",

View File

@@ -1,5 +1,5 @@
{ {
"name": "nextjs", "name": "nextjs-src-dir",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"dev": "next", "dev": "next",

View File

@@ -0,0 +1,3 @@
{
"devCommand": "next dev --port $PORT"
}

View File

@@ -5,5 +5,6 @@
"src": "/test", "src": "/test",
"dest": "/?route-param=b" "dest": "/?route-param=b"
} }
] ],
"devCommand": "next dev --port $PORT"
} }

View File

@@ -0,0 +1 @@
This is the original

View File

@@ -0,0 +1 @@
This is the overridden!

View File

@@ -0,0 +1,8 @@
{
"scripts": {
"build": "not used - required for zero-config"
},
"dependencies": {
"serve": "14.0.1"
}
}

View File

@@ -0,0 +1 @@
{"devCommand":"serve -p $PORT original"}

View File

@@ -0,0 +1,588 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@zeit/schemas@2.21.0":
version "2.21.0"
resolved "https://registry.yarnpkg.com/@zeit/schemas/-/schemas-2.21.0.tgz#cd242c6551ffb51830049d68d9743ab65b45b820"
integrity sha512-/J4WBTpWtQ4itN1rb3ao8LfClmVcmz2pO6oYb7Qd4h7VSqUhIbJIvrykz9Ew1WMg6eFWsKdsMHc5uPbFxqlCpg==
accepts@~1.3.5:
version "1.3.8"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==
dependencies:
mime-types "~2.1.34"
negotiator "0.6.3"
ajv@8.11.0:
version "8.11.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f"
integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==
dependencies:
fast-deep-equal "^3.1.1"
json-schema-traverse "^1.0.0"
require-from-string "^2.0.2"
uri-js "^4.2.2"
ansi-align@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59"
integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==
dependencies:
string-width "^4.1.0"
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-regex@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a"
integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==
ansi-styles@^4.1.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"
ansi-styles@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.1.0.tgz#87313c102b8118abd57371afab34618bf7350ed3"
integrity sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==
arch@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11"
integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==
arg@5.0.2:
version "5.0.2"
resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c"
integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==
balanced-match@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
boxen@7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/boxen/-/boxen-7.0.0.tgz#9e5f8c26e716793fc96edcf7cf754cdf5e3fbf32"
integrity sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==
dependencies:
ansi-align "^3.0.1"
camelcase "^7.0.0"
chalk "^5.0.1"
cli-boxes "^3.0.0"
string-width "^5.1.2"
type-fest "^2.13.0"
widest-line "^4.0.1"
wrap-ansi "^8.0.1"
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
dependencies:
balanced-match "^1.0.0"
concat-map "0.0.1"
bytes@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==
camelcase@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-7.0.0.tgz#fd112621b212126741f998d614cbc2a8623fd174"
integrity sha512-JToIvOmz6nhGsUhAYScbo2d6Py5wojjNfoxoc2mEVLUdJ70gJK2gnd+ABY1Tc3sVMyK7QDPtN0T/XdlCQWITyQ==
chalk-template@0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/chalk-template/-/chalk-template-0.4.0.tgz#692c034d0ed62436b9062c1707fadcd0f753204b"
integrity sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==
dependencies:
chalk "^4.1.2"
chalk@5.0.1, chalk@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.0.1.tgz#ca57d71e82bb534a296df63bbacc4a1c22b2a4b6"
integrity sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==
chalk@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
cli-boxes@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-3.0.0.tgz#71a10c716feeba005e4504f36329ef0b17cf3145"
integrity sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==
clipboardy@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/clipboardy/-/clipboardy-3.0.0.tgz#f3876247404d334c9ed01b6f269c11d09a5e3092"
integrity sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==
dependencies:
arch "^2.2.0"
execa "^5.1.1"
is-wsl "^2.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==
compressible@~2.0.16:
version "2.0.18"
resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba"
integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==
dependencies:
mime-db ">= 1.43.0 < 2"
compression@1.7.4:
version "1.7.4"
resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f"
integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==
dependencies:
accepts "~1.3.5"
bytes "3.0.0"
compressible "~2.0.16"
debug "2.6.9"
on-headers "~1.0.2"
safe-buffer "5.1.2"
vary "~1.1.2"
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
content-disposition@0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
integrity sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==
cross-spawn@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
dependencies:
path-key "^3.1.0"
shebang-command "^2.0.0"
which "^2.0.1"
debug@2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
dependencies:
ms "2.0.0"
deep-extend@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
eastasianwidth@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
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==
emoji-regex@^9.2.2:
version "9.2.2"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
execa@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==
dependencies:
cross-spawn "^7.0.3"
get-stream "^6.0.0"
human-signals "^2.1.0"
is-stream "^2.0.0"
merge-stream "^2.0.0"
npm-run-path "^4.0.1"
onetime "^5.1.2"
signal-exit "^3.0.3"
strip-final-newline "^2.0.0"
fast-deep-equal@^3.1.1:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
fast-url-parser@1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/fast-url-parser/-/fast-url-parser-1.1.3.tgz#f4af3ea9f34d8a271cf58ad2b3759f431f0b318d"
integrity sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==
dependencies:
punycode "^1.3.2"
get-stream@^6.0.0:
version "6.0.1"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
has-flag@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
human-signals@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
ini@~1.3.0:
version "1.3.8"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
is-docker@^2.0.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa"
integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==
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==
is-port-reachable@4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/is-port-reachable/-/is-port-reachable-4.0.0.tgz#dac044091ef15319c8ab2f34604d8794181f8c2d"
integrity sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==
is-stream@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077"
integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==
is-wsl@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
dependencies:
is-docker "^2.0.0"
isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
json-schema-traverse@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
merge-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
mime-db@1.52.0, "mime-db@>= 1.43.0 < 2":
version "1.52.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
mime-db@~1.33.0:
version "1.33.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db"
integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==
mime-types@2.1.18:
version "2.1.18"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8"
integrity sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==
dependencies:
mime-db "~1.33.0"
mime-types@~2.1.34:
version "2.1.35"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
dependencies:
mime-db "1.52.0"
mimic-fn@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
minimatch@3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
dependencies:
brace-expansion "^1.1.7"
minimist@^1.2.0:
version "1.2.6"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
negotiator@0.6.3:
version "0.6.3"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
npm-run-path@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==
dependencies:
path-key "^3.0.0"
on-headers@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f"
integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
onetime@^5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e"
integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==
dependencies:
mimic-fn "^2.1.0"
path-is-inside@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
integrity sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==
path-key@^3.0.0, path-key@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
path-to-regexp@2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.2.1.tgz#90b617025a16381a879bc82a38d4e8bdeb2bcf45"
integrity sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==
punycode@^1.3.2:
version "1.4.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==
punycode@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
range-parser@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
integrity sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==
rc@^1.0.1, rc@^1.1.6:
version "1.2.8"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
dependencies:
deep-extend "^0.6.0"
ini "~1.3.0"
minimist "^1.2.0"
strip-json-comments "~2.0.1"
registry-auth-token@3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.3.2.tgz#851fd49038eecb586911115af845260eec983f20"
integrity sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==
dependencies:
rc "^1.1.6"
safe-buffer "^5.0.1"
registry-url@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942"
integrity sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==
dependencies:
rc "^1.0.1"
require-from-string@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
safe-buffer@5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
safe-buffer@^5.0.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
serve-handler@6.1.3:
version "6.1.3"
resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.3.tgz#1bf8c5ae138712af55c758477533b9117f6435e8"
integrity sha512-FosMqFBNrLyeiIDvP1zgO6YoTzFYHxLDEIavhlmQ+knB2Z7l1t+kGLHkZIDN7UVWqQAmKI3D20A6F6jo3nDd4w==
dependencies:
bytes "3.0.0"
content-disposition "0.5.2"
fast-url-parser "1.1.3"
mime-types "2.1.18"
minimatch "3.0.4"
path-is-inside "1.0.2"
path-to-regexp "2.2.1"
range-parser "1.2.0"
serve@14.0.1:
version "14.0.1"
resolved "https://registry.yarnpkg.com/serve/-/serve-14.0.1.tgz#5b6ffc17e18e1a8e963cc392958d7df13e1ef9fd"
integrity sha512-tNGwxl27FwA8TbmMQqN0jTaSx8/trL532qZsJHX1VdiEIjjtMJHCs7AFS6OvtC7cTHOvmjXqt5yczejU6CV2Xg==
dependencies:
"@zeit/schemas" "2.21.0"
ajv "8.11.0"
arg "5.0.2"
boxen "7.0.0"
chalk "5.0.1"
chalk-template "0.4.0"
clipboardy "3.0.0"
compression "1.7.4"
is-port-reachable "4.0.0"
serve-handler "6.1.3"
update-check "1.5.4"
shebang-command@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
dependencies:
shebang-regex "^3.0.0"
shebang-regex@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
signal-exit@^3.0.3:
version "3.0.7"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
string-width@^4.1.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@^5.0.1, string-width@^5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==
dependencies:
eastasianwidth "^0.2.0"
emoji-regex "^9.2.2"
strip-ansi "^7.0.1"
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-ansi@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2"
integrity sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==
dependencies:
ansi-regex "^6.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==
strip-json-comments@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==
supports-color@^7.1.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
dependencies:
has-flag "^4.0.0"
type-fest@^2.13.0:
version "2.19.0"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b"
integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==
update-check@1.5.4:
version "1.5.4"
resolved "https://registry.yarnpkg.com/update-check/-/update-check-1.5.4.tgz#5b508e259558f1ad7dbc8b4b0457d4c9d28c8743"
integrity sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==
dependencies:
registry-auth-token "3.3.2"
registry-url "3.1.0"
uri-js@^4.2.2:
version "4.4.1"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
dependencies:
punycode "^2.1.0"
vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
which@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
dependencies:
isexe "^2.0.0"
widest-line@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-4.0.1.tgz#a0fc673aaba1ea6f0a0d35b3c2795c9a9cc2ebf2"
integrity sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==
dependencies:
string-width "^5.0.1"
wrap-ansi@^8.0.1:
version "8.0.1"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.0.1.tgz#2101e861777fec527d0ea90c57c6b03aac56a5b3"
integrity sha512-QFF+ufAqhoYHvoHdajT/Po7KoXVBPXS2bgjIam5isfWJPfIOnQZ50JtUiVvCv/sjgacf3yRrt2ZKUZ/V4itN4g==
dependencies:
ansi-styles "^6.1.0"
string-width "^5.0.1"
strip-ansi "^7.0.1"

View File

@@ -449,11 +449,7 @@ test('[vercel dev] should maintain query when proxy passing', async () => {
test('[vercel dev] should maintain query when dev server defines routes', async () => { test('[vercel dev] should maintain query when dev server defines routes', async () => {
const dir = fixture('dev-server-query'); const dir = fixture('dev-server-query');
const { dev, port, readyResolver } = await testFixture(dir, { const { dev, port, readyResolver } = await testFixture(dir);
env: {
VERCEL_DEV_COMMAND: 'next dev --port $PORT',
},
});
try { try {
await readyResolver; await readyResolver;
@@ -516,11 +512,7 @@ test('[vercel dev] should send `etag` header for static files', async () => {
test('[vercel dev] should frontend dev server and routes', async () => { test('[vercel dev] should frontend dev server and routes', async () => {
const dir = fixture('dev-server-and-routes'); const dir = fixture('dev-server-and-routes');
const { dev, port, readyResolver } = await testFixture(dir, { const { dev, port, readyResolver } = await testFixture(dir);
env: {
VERCEL_DEV_COMMAND: 'next dev --port $PORT',
},
});
try { try {
await readyResolver; await readyResolver;

View File

@@ -539,3 +539,40 @@ test(
); );
}) })
); );
test(
'[vercel dev] restarts dev process when `devCommand` setting is modified',
testFixtureStdio(
'project-settings-override',
async (_testPath: any, port: any) => {
const directory = fixture('project-settings-override');
const vercelJsonPath = join(directory, 'vercel.json');
const originalVercelJson = await fs.readJSON(vercelJsonPath);
try {
const originalResponse = await fetch(
`http://localhost:${port}/index.txt`
);
validateResponseHeaders(originalResponse);
const body = await originalResponse.text();
expect(body.trim()).toEqual('This is the original');
expect(originalResponse.status).toBe(200);
await fs.writeJSON(vercelJsonPath, {
devCommand: 'serve -p $PORT overridden',
});
const overriddenResponse = await fetch(
`http://localhost:${port}/index.txt`
);
validateResponseHeaders(overriddenResponse);
const body2 = await overriddenResponse.text();
expect(body2.trim()).toEqual('This is the overridden!');
expect(overriddenResponse.status).toBe(200);
} finally {
await fs.writeJSON(vercelJsonPath, originalVercelJson);
}
},
{ skipDeploy: true }
)
);

View File

@@ -1,5 +1,5 @@
{ {
"name": "only-module", "name": "prefer-browser",
"private": true, "private": true,
"main": "dist-main.js", "main": "dist-main.js",
"module": "dist-module.js", "module": "dist-module.js",

View File

@@ -0,0 +1 @@
output

View File

@@ -0,0 +1,9 @@
{
"orgId": ".",
"projectId": ".",
"settings": {
"buildCommand": "mkdir -p output && echo 2 > output/index.txt",
"outputDirectory": "output",
"framework": null
}
}

View File

@@ -0,0 +1,5 @@
{
"scripts": {
"build": "mkdir -p output && echo 1 > output/index.txt"
}
}

View File

@@ -0,0 +1,3 @@
{
"buildCommand": "mkdir -p output && echo 3 > output/index.txt"
}

View File

@@ -76,25 +76,6 @@ module.exports = async function prepare(session, binaryPath, tmpFixturesDir) {
}, },
}), }),
}, },
'build-env-debug': {
'now.json': JSON.stringify({
builds: [{ src: 'index.js', use: '@vercel/node' }],
}),
'package.json': JSON.stringify({
scripts: {
'now-build': 'node now-build.js',
},
}),
'now-build.js': `
const fs = require('fs');
fs.writeFileSync(
'index.js',
fs.readFileSync('index.js', 'utf8')
.replace('BUILD_ENV_DEBUG', process.env.NOW_BUILDER_DEBUG ? 'on' : 'off'),
);
`,
'index.js': `module.exports = (req, res) => { res.status(200).send('BUILD_ENV_DEBUG'); }`,
},
'now-revert-alias-1': { 'now-revert-alias-1': {
'index.json': JSON.stringify({ name: 'now-revert-alias-1' }), 'index.json': JSON.stringify({ name: 'now-revert-alias-1' }),
'now.json': getRevertAliasConfigFile(), 'now.json': getRevertAliasConfigFile(),

View File

@@ -360,7 +360,7 @@ test('default command should prompt login with empty auth.json', async t => {
} catch (err) { } catch (err) {
t.true( t.true(
err.stderr.includes( err.stderr.includes(
'Error! No existing credentials found. Please run `vercel login` or pass "--token"' 'Error: No existing credentials found. Please run `vercel login` or pass "--token"'
) )
); );
} }
@@ -749,7 +749,7 @@ test('deploy fails using --local-config flag with non-existent path', async t =>
t.is(exitCode, 1, formatOutput({ stderr, stdout })); t.is(exitCode, 1, formatOutput({ stderr, stdout }));
t.regex(stderr, /Error! Couldn't find a project configuration file at/); t.regex(stderr, /Error: Couldn't find a project configuration file at/);
t.regex(stderr, /does-not-exist\.json/); t.regex(stderr, /does-not-exist\.json/);
}); });
@@ -1451,7 +1451,7 @@ test('login with unregistered user', async t => {
console.log(stdout); console.log(stdout);
console.log(exitCode); console.log(exitCode);
const goal = `Error! Please sign up: https://vercel.com/signup`; const goal = `Error: Please sign up: https://vercel.com/signup`;
const lines = stderr.trim().split('\n'); const lines = stderr.trim().split('\n');
const last = lines[lines.length - 1]; const last = lines[lines.length - 1];
@@ -1629,7 +1629,7 @@ test('try to purchase a domain', async t => {
t.is(exitCode, 1); t.is(exitCode, 1);
t.regex( t.regex(
stderr, stderr,
/Error! Could not purchase domain\. Please add a payment method using/ /Error: Could not purchase domain\. Please add a payment method using/
); );
}); });
@@ -1655,7 +1655,7 @@ test('try to transfer-in a domain with "--code" option', async t => {
t.true( t.true(
stderr.includes( stderr.includes(
`Error! The domain "${session}-test.com" is not transferable.` `Error: The domain "${session}-test.com" is not transferable.`
) )
); );
t.is(exitCode, 1); t.is(exitCode, 1);
@@ -1680,7 +1680,7 @@ test('try to move an invalid domain', async t => {
console.log(stdout); console.log(stdout);
console.log(exitCode); console.log(exitCode);
t.true(stderr.includes(`Error! Domain not found under `)); t.true(stderr.includes(`Error: Domain not found under `));
t.is(exitCode, 1); t.is(exitCode, 1);
}); });
@@ -1964,7 +1964,7 @@ test('try to create a builds deployments with wrong now.json', async t => {
t.is(exitCode, 1); t.is(exitCode, 1);
t.true( t.true(
stderr.includes( stderr.includes(
'Error! Invalid now.json - should NOT have additional property `builder`. Did you mean `builds`?' 'Error: Invalid now.json - should NOT have additional property `builder`. Did you mean `builds`?'
) )
); );
t.true(stderr.includes('https://vercel.com/docs/configuration')); t.true(stderr.includes('https://vercel.com/docs/configuration'));
@@ -1988,7 +1988,7 @@ test('try to create a builds deployments with wrong vercel.json', async t => {
t.is(exitCode, 1); t.is(exitCode, 1);
t.true( t.true(
stderr.includes( stderr.includes(
'Error! Invalid vercel.json - should NOT have additional property `fake`. Please remove it.' 'Error: Invalid vercel.json - should NOT have additional property `fake`. Please remove it.'
) )
); );
t.true(stderr.includes('https://vercel.com/docs/configuration')); t.true(stderr.includes('https://vercel.com/docs/configuration'));
@@ -2009,7 +2009,7 @@ test('try to create a builds deployments with wrong `build.env` property', async
t.is(exitCode, 1, formatOutput({ stdout, stderr })); t.is(exitCode, 1, formatOutput({ stdout, stderr }));
t.true( t.true(
stderr.includes( stderr.includes(
'Error! Invalid vercel.json - should NOT have additional property `build.env`. Did you mean `{ "build": { "env": {"name": "value"} } }`?' 'Error: Invalid vercel.json - should NOT have additional property `build.env`. Did you mean `{ "build": { "env": {"name": "value"} } }`?'
), ),
formatOutput({ stdout, stderr }) formatOutput({ stdout, stderr })
); );
@@ -2170,47 +2170,8 @@ test('use build-env', async t => {
t.is(content.trim(), 'bar'); t.is(content.trim(), 'bar');
}); });
test('use `--debug` CLI flag', async t => {
const directory = fixture('build-env-debug');
const { stderr, stdout, exitCode } = await execa(
binaryPath,
[
directory,
'--public',
'--name',
session,
'--debug',
...defaultArgs,
'--yes',
],
{
reject: false,
}
);
console.log(stderr);
console.log(stdout);
console.log(exitCode);
// Ensure the exit code is right
t.is(exitCode, 0, `Received:\n"${stderr}"\n"${stdout}"`);
// Test if the output is really a URL
const deploymentUrl = pickUrl(stdout);
const { href, host } = new URL(deploymentUrl);
t.is(host.split('-')[0], session);
await waitForDeployment(href);
// get the content
const response = await fetch(href);
const content = await response.text();
t.is(content.trim(), 'off');
});
test('try to deploy non-existing path', async t => { test('try to deploy non-existing path', async t => {
const goal = `Error! The specified file or directory "${session}" does not exist.`; const goal = `Error: The specified file or directory "${session}" does not exist.`;
const { stderr, stdout, exitCode } = await execa( const { stderr, stdout, exitCode } = await execa(
binaryPath, binaryPath,
@@ -2230,7 +2191,7 @@ test('try to deploy non-existing path', async t => {
test('try to deploy with non-existing team', async t => { test('try to deploy with non-existing team', async t => {
const target = fixture('static-deployment'); const target = fixture('static-deployment');
const goal = `Error! The specified scope does not exist`; const goal = `Error: The specified scope does not exist`;
const { stderr, stdout, exitCode } = await execa( const { stderr, stdout, exitCode } = await execa(
binaryPath, binaryPath,
@@ -2313,7 +2274,7 @@ test('try to initialize example to existing directory', async t => {
tmpDir = tmp.dirSync({ unsafeCleanup: true }); tmpDir = tmp.dirSync({ unsafeCleanup: true });
const cwd = tmpDir.name; const cwd = tmpDir.name;
const goal = const goal =
'Error! Destination path "angular" already exists and is not an empty directory. You may use `--force` or `-f` to override it.'; 'Error: Destination path "angular" already exists and is not an empty directory. You may use `--force` or `-f` to override it.';
await ensureDir(path.join(cwd, 'angular')); await ensureDir(path.join(cwd, 'angular'));
createFile(path.join(cwd, 'angular', '.gitignore')); createFile(path.join(cwd, 'angular', '.gitignore'));
@@ -2330,7 +2291,7 @@ test('try to initialize misspelled example (noce) in non-tty', async t => {
tmpDir = tmp.dirSync({ unsafeCleanup: true }); tmpDir = tmp.dirSync({ unsafeCleanup: true });
const cwd = tmpDir.name; const cwd = tmpDir.name;
const goal = const goal =
'Error! No example found for noce, run `vercel init` to see the list of available examples.'; 'Error: No example found for noce, run `vercel init` to see the list of available examples.';
const { stdout, stderr, exitCode } = await execute(['init', 'noce'], { cwd }); const { stdout, stderr, exitCode } = await execute(['init', 'noce'], { cwd });
@@ -2346,7 +2307,7 @@ test('try to initialize example "example-404"', async t => {
tmpDir = tmp.dirSync({ unsafeCleanup: true }); tmpDir = tmp.dirSync({ unsafeCleanup: true });
const cwd = tmpDir.name; const cwd = tmpDir.name;
const goal = const goal =
'Error! No example found for example-404, run `vercel init` to see the list of available examples.'; 'Error: No example found for example-404, run `vercel init` to see the list of available examples.';
const { stdout, stderr, exitCode } = await execute(['init', 'example-404'], { const { stdout, stderr, exitCode } = await execute(['init', 'example-404'], {
cwd, cwd,
@@ -2522,7 +2483,7 @@ test('`vercel rm` should fail with unexpected option', async t => {
t.is(output.exitCode, 1, formatOutput(output)); t.is(output.exitCode, 1, formatOutput(output));
t.regex( t.regex(
output.stderr, output.stderr,
/Error! unknown or unexpected option: --fake/gm, /Error: unknown or unexpected option: --fake/gm,
formatOutput(output) formatOutput(output)
); );
}); });
@@ -2816,7 +2777,7 @@ test('invalid `--token`', async t => {
t.is(output.exitCode, 1, formatOutput(output)); t.is(output.exitCode, 1, formatOutput(output));
t.true( t.true(
output.stderr.includes( output.stderr.includes(
'Error! You defined "--token", but its contents are invalid. Must not contain: "\\n", ",", "."' 'Error: You defined "--token", but its contents are invalid. Must not contain: "\\n", ",", "."'
) )
); );
}); });
@@ -3539,7 +3500,7 @@ test('reject deploying with invalid token', async t => {
t.is(exitCode, 1, formatOutput({ stderr, stdout })); t.is(exitCode, 1, formatOutput({ stderr, stdout }));
t.regex( t.regex(
stderr, stderr,
/Error! Could not retrieve Project Settings\. To link your Project, remove the `\.vercel` directory and deploy again\./g /Error: Could not retrieve Project Settings\. To link your Project, remove the `\.vercel` directory and deploy again\./g
); );
}); });

View File

@@ -711,7 +711,7 @@ describe('build', () => {
// Error gets printed to the terminal // Error gets printed to the terminal
await expect(client.stderr).toOutput( await expect(client.stderr).toOutput(
'Error! Function must contain at least one property.' 'Error: Function must contain at least one property.'
); );
// `builds.json` contains top-level "error" property // `builds.json` contains top-level "error" property
@@ -894,6 +894,27 @@ describe('build', () => {
} }
}); });
it('should apply project settings overrides from "vercel.json"', async () => {
const cwd = fixture('project-settings-override');
const output = join(cwd, '.vercel/output');
try {
process.chdir(cwd);
const exitCode = await build(client);
expect(exitCode).toEqual(0);
// The `buildCommand` override in "vercel.json" outputs "3" to the
// index.txt file, so verify that that was produced in the build output
const contents = await fs.readFile(
join(output, 'static/index.txt'),
'utf8'
);
expect(contents.trim()).toEqual('3');
} finally {
process.chdir(originalCwd);
delete process.env.__VERCEL_BUILD_RUNNING;
}
});
describe('should find packages with different main/module/browser keys', function () { describe('should find packages with different main/module/browser keys', function () {
let output: string; let output: string;

View File

@@ -12,7 +12,7 @@ describe('deploy', () => {
client.setArgv('deploy', __filename); client.setArgv('deploy', __filename);
const exitCodePromise = deploy(client); const exitCodePromise = deploy(client);
await expect(client.stderr).toOutput( await expect(client.stderr).toOutput(
`Error! Support for single file deployments has been removed.\nLearn More: https://vercel.link/no-single-file-deployments\n` `Error: Support for single file deployments has been removed.\nLearn More: https://vercel.link/no-single-file-deployments\n`
); );
await expect(exitCodePromise).resolves.toEqual(1); await expect(exitCodePromise).resolves.toEqual(1);
}); });
@@ -21,7 +21,7 @@ describe('deploy', () => {
client.setArgv('deploy', __filename, join(__dirname, 'inspect.test.ts')); client.setArgv('deploy', __filename, join(__dirname, 'inspect.test.ts'));
const exitCodePromise = deploy(client); const exitCodePromise = deploy(client);
await expect(client.stderr).toOutput( await expect(client.stderr).toOutput(
`Error! Can't deploy more than one path.\n` `Error: Can't deploy more than one path.\n`
); );
await expect(exitCodePromise).resolves.toEqual(1); await expect(exitCodePromise).resolves.toEqual(1);
}); });
@@ -30,7 +30,7 @@ describe('deploy', () => {
client.setArgv('deploy', 'does-not-exists'); client.setArgv('deploy', 'does-not-exists');
const exitCodePromise = deploy(client); const exitCodePromise = deploy(client);
await expect(client.stderr).toOutput( await expect(client.stderr).toOutput(
`Error! The specified file or directory "does-not-exists" does not exist.\n` `Error: The specified file or directory "does-not-exists" does not exist.\n`
); );
await expect(exitCodePromise).resolves.toEqual(1); await expect(exitCodePromise).resolves.toEqual(1);
}); });
@@ -41,7 +41,7 @@ describe('deploy', () => {
client.setArgv('deploy', cwd, '--prebuilt'); client.setArgv('deploy', cwd, '--prebuilt');
const exitCodePromise = deploy(client); const exitCodePromise = deploy(client);
await expect(client.stderr).toOutput( await expect(client.stderr).toOutput(
'> Prebuilt deployment cannot be created because `vercel build` failed with error:\n\nError! The build failed (top-level)\n' '> Prebuilt deployment cannot be created because `vercel build` failed with error:\n\nError: The build failed (top-level)\n'
); );
await expect(exitCodePromise).resolves.toEqual(1); await expect(exitCodePromise).resolves.toEqual(1);
}); });
@@ -52,7 +52,7 @@ describe('deploy', () => {
client.setArgv('deploy', cwd, '--prebuilt'); client.setArgv('deploy', cwd, '--prebuilt');
const exitCodePromise = deploy(client); const exitCodePromise = deploy(client);
await expect(client.stderr).toOutput( await expect(client.stderr).toOutput(
'> Prebuilt deployment cannot be created because `vercel build` failed with error:\n\nError! The build failed within a Builder\n' '> Prebuilt deployment cannot be created because `vercel build` failed with error:\n\nError: The build failed within a Builder\n'
); );
await expect(exitCodePromise).resolves.toEqual(1); await expect(exitCodePromise).resolves.toEqual(1);
}); });
@@ -61,7 +61,7 @@ describe('deploy', () => {
client.setArgv('deploy', __dirname, '--prebuilt'); client.setArgv('deploy', __dirname, '--prebuilt');
const exitCodePromise = deploy(client); const exitCodePromise = deploy(client);
await expect(client.stderr).toOutput( await expect(client.stderr).toOutput(
'Error! The "--prebuilt" option was used, but no prebuilt output found in ".vercel/output". Run `vercel build` to generate a local build.\n' 'Error: The "--prebuilt" option was used, but no prebuilt output found in ".vercel/output". Run `vercel build` to generate a local build.\n'
); );
await expect(exitCodePromise).resolves.toEqual(1); await expect(exitCodePromise).resolves.toEqual(1);
}); });
@@ -80,7 +80,7 @@ describe('deploy', () => {
client.setArgv('deploy', cwd, '--prebuilt', '--prod'); client.setArgv('deploy', cwd, '--prebuilt', '--prod');
const exitCodePromise = deploy(client); const exitCodePromise = deploy(client);
await expect(client.stderr).toOutput( await expect(client.stderr).toOutput(
'Error! The "--prebuilt" option was used with the target environment "production",' + 'Error: The "--prebuilt" option was used with the target environment "production",' +
' but the prebuilt output found in ".vercel/output" was built with target environment "preview".' + ' but the prebuilt output found in ".vercel/output" was built with target environment "preview".' +
' Please run `vercel --prebuilt`.\n' + ' Please run `vercel --prebuilt`.\n' +
'Learn More: https://vercel.link/prebuilt-environment-mismatch\n' 'Learn More: https://vercel.link/prebuilt-environment-mismatch\n'
@@ -102,7 +102,7 @@ describe('deploy', () => {
client.setArgv('deploy', cwd, '--prebuilt'); client.setArgv('deploy', cwd, '--prebuilt');
const exitCodePromise = deploy(client); const exitCodePromise = deploy(client);
await expect(client.stderr).toOutput( await expect(client.stderr).toOutput(
'Error! The "--prebuilt" option was used with the target environment "preview",' + 'Error: The "--prebuilt" option was used with the target environment "preview",' +
' but the prebuilt output found in ".vercel/output" was built with target environment "production".' + ' but the prebuilt output found in ".vercel/output" was built with target environment "production".' +
' Please run `vercel --prebuilt --prod`.\n' + ' Please run `vercel --prebuilt --prod`.\n' +
'Learn More: https://vercel.link/prebuilt-environment-mismatch\n' 'Learn More: https://vercel.link/prebuilt-environment-mismatch\n'
@@ -118,7 +118,7 @@ describe('deploy', () => {
}; };
const exitCodePromise = deploy(client); const exitCodePromise = deploy(client);
await expect(client.stderr).toOutput( await expect(client.stderr).toOutput(
'Error! The value of the `version` property within vercel.json can only be `2`.\n' 'Error: The value of the `version` property within vercel.json can only be `2`.\n'
); );
await expect(exitCodePromise).resolves.toEqual(1); await expect(exitCodePromise).resolves.toEqual(1);
}); });
@@ -132,7 +132,7 @@ describe('deploy', () => {
}; };
const exitCodePromise = deploy(client); const exitCodePromise = deploy(client);
await expect(client.stderr).toOutput( await expect(client.stderr).toOutput(
'Error! The `version` property inside your vercel.json file must be a number.\n' 'Error: The `version` property inside your vercel.json file must be a number.\n'
); );
await expect(exitCodePromise).resolves.toEqual(1); await expect(exitCodePromise).resolves.toEqual(1);
}); });

View File

@@ -80,7 +80,7 @@ describe('git', () => {
const exitCode = await git(client); const exitCode = await git(client);
expect(exitCode).toEqual(1); expect(exitCode).toEqual(1);
await expect(client.stderr).toOutput( await expect(client.stderr).toOutput(
`Error! No local Git repository found. Run \`git clone <url>\` to clone a remote Git repository first.\n` `Error: No local Git repository found. Run \`git clone <url>\` to clone a remote Git repository first.\n`
); );
} finally { } finally {
process.chdir(originalCwd); process.chdir(originalCwd);
@@ -102,7 +102,7 @@ describe('git', () => {
const exitCode = await git(client); const exitCode = await git(client);
expect(exitCode).toEqual(1); expect(exitCode).toEqual(1);
await expect(client.stderr).toOutput( await expect(client.stderr).toOutput(
`Error! No remote URLs found in your Git config. Make sure you've configured a remote repo in your local Git config. Run \`git remote --help\` for more details.` `Error: No remote URLs found in your Git config. Make sure you've configured a remote repo in your local Git config. Run \`git remote --help\` for more details.`
); );
} finally { } finally {
await fs.rename(join(cwd, '.git'), join(cwd, 'git')); await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
@@ -129,7 +129,7 @@ describe('git', () => {
`Connecting Git remote: bababooey` `Connecting Git remote: bababooey`
); );
await expect(client.stderr).toOutput( await expect(client.stderr).toOutput(
`Error! Failed to parse Git repo data from the following remote URL: bababooey\n` `Error: Failed to parse Git repo data from the following remote URL: bababooey\n`
); );
} finally { } finally {
await fs.rename(join(cwd, '.git'), join(cwd, 'git')); await fs.rename(join(cwd, '.git'), join(cwd, 'git'));

View File

@@ -33,7 +33,7 @@ describe('inspect', () => {
const exitCode = await inspect(client); const exitCode = await inspect(client);
expect(exitCode).toEqual(1); expect(exitCode).toEqual(1);
await expect(client.stderr).toOutput( await expect(client.stderr).toOutput(
`Error! Failed to find deployment "bad.com" in ${user.username}\n` `Error: Failed to find deployment "bad.com" in ${user.username}\n`
); );
}); });
}); });

View File

@@ -7,7 +7,7 @@ describe('login', () => {
client.setArgv('login', '--token', 'foo'); client.setArgv('login', '--token', 'foo');
const exitCodePromise = login(client); const exitCodePromise = login(client);
await expect(client.stderr).toOutput( await expect(client.stderr).toOutput(
'Error! `--token` may not be used with the "login" command\n' 'Error: `--token` may not be used with the "login" command\n'
); );
await expect(exitCodePromise).resolves.toEqual(2); await expect(exitCodePromise).resolves.toEqual(2);
}); });

View File

@@ -0,0 +1,31 @@
import { progress } from '../../../../src/util/output/progress';
describe('progress()', () => {
test.each([
{ current: 0, total: 5, opts: { width: 5 }, expected: '-----' },
{ current: 1, total: 5, opts: { width: 5 }, expected: '=----' },
{ current: 2, total: 5, opts: { width: 5 }, expected: '==---' },
{ current: 3, total: 5, opts: { width: 5 }, expected: '===--' },
{ current: 4, total: 5, opts: { width: 5 }, expected: '====-' },
{ current: 5, total: 5, opts: { width: 5 }, expected: '=====' },
{ current: 0, total: 12, expected: '--------------------' },
{ current: 1, total: 12, expected: '=-------------------' },
{ current: 2, total: 12, expected: '===-----------------' },
{ current: 600, total: 1200, expected: '==========----------' },
{
current: 9,
total: 10,
opts: { complete: '.', incomplete: ' ', width: 10 },
expected: '......... ',
},
{ current: 10, total: 10, expected: '====================' },
{ current: 11, total: 10, expected: null },
{ current: -1, total: 10, expected: null },
{ current: 1, total: 0, expected: null },
])(
'$current / $total -> "$expected"',
({ current, total, opts, expected }) => {
expect(progress(current, total, opts)).toEqual(expected);
}
);
});

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/client", "name": "@vercel/client",
"version": "12.2.1", "version": "12.2.3",
"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",
@@ -31,7 +31,7 @@
"@types/node": "12.0.4", "@types/node": "12.0.4",
"@types/node-fetch": "2.5.4", "@types/node-fetch": "2.5.4",
"@types/recursive-readdir": "2.2.0", "@types/recursive-readdir": "2.2.0",
"@types/tar-fs": "^2.0.1", "@types/tar-fs": "1.16.1",
"typescript": "4.3.4" "typescript": "4.3.4"
}, },
"jest": { "jest": {
@@ -43,7 +43,7 @@
] ]
}, },
"dependencies": { "dependencies": {
"@vercel/build-utils": "5.3.2", "@vercel/build-utils": "5.4.1",
"@vercel/routing-utils": "2.0.2", "@vercel/routing-utils": "2.0.2",
"@zeit/fetch": "5.2.0", "@zeit/fetch": "5.2.0",
"async-retry": "1.2.3", "async-retry": "1.2.3",
@@ -54,6 +54,7 @@
"ms": "2.1.2", "ms": "2.1.2",
"node-fetch": "2.6.7", "node-fetch": "2.6.7",
"querystring": "^0.2.0", "querystring": "^0.2.0",
"sleep-promise": "8.0.1" "sleep-promise": "8.0.1",
"tar-fs": "1.16.3"
} }
} }

View File

@@ -1,3 +1,4 @@
# `@vercel/edge` # `@vercel/edge`
A set of utilities to help you deploy your app on the Edge using Vercel. A set of utilities to help you deploy any framework on the Edge using Vercel.
Please [follow the documentation](./docs) for examples and usage.

View File

@@ -0,0 +1,262 @@
# @vercel/edge
## Table of contents
### Interfaces
- [ExtraResponseInit](interfaces/ExtraResponseInit.md)
- [Geo](interfaces/Geo.md)
### Variables
- [CITY_HEADER_NAME](README.md#city_header_name)
- [COUNTRY_HEADER_NAME](README.md#country_header_name)
- [IP_HEADER_NAME](README.md#ip_header_name)
- [LATITUDE_HEADER_NAME](README.md#latitude_header_name)
- [LONGITUDE_HEADER_NAME](README.md#longitude_header_name)
- [REGION_HEADER_NAME](README.md#region_header_name)
### Functions
- [geolocation](README.md#geolocation)
- [ipAddress](README.md#ipaddress)
- [next](README.md#next)
- [rewrite](README.md#rewrite)
## Variables
### CITY_HEADER_NAME
`Const` **CITY_HEADER_NAME**: `"x-vercel-ip-city"`
City of the original client IP as calculated by Vercel Proxy.
#### Defined in
[src/edge-headers.ts:4](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L4)
---
### COUNTRY_HEADER_NAME
`Const` **COUNTRY_HEADER_NAME**: `"x-vercel-ip-country"`
Country of the original client IP as calculated by Vercel Proxy.
#### Defined in
[src/edge-headers.ts:8](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L8)
---
### IP_HEADER_NAME
`Const` **IP_HEADER_NAME**: `"x-real-ip"`
Client IP as calcualted by Vercel Proxy.
#### Defined in
[src/edge-headers.ts:12](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L12)
---
### LATITUDE_HEADER_NAME
`Const` **LATITUDE_HEADER_NAME**: `"x-vercel-ip-latitude"`
Latitude of the original client IP as calculated by Vercel Proxy.
#### Defined in
[src/edge-headers.ts:16](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L16)
---
### LONGITUDE_HEADER_NAME
`Const` **LONGITUDE_HEADER_NAME**: `"x-vercel-ip-longitude"`
Longitude of the original client IP as calculated by Vercel Proxy.
#### Defined in
[src/edge-headers.ts:20](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L20)
---
### REGION_HEADER_NAME
`Const` **REGION_HEADER_NAME**: `"x-vercel-ip-country-region"`
Region of the original client IP as calculated by Vercel Proxy.
#### Defined in
[src/edge-headers.ts:24](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L24)
## Functions
### geolocation
**geolocation**(`request`): [`Geo`](interfaces/Geo.md)
Returns the location information for the incoming request.
**`See`**
- [CITY_HEADER_NAME](README.md#city_header_name)
- [COUNTRY_HEADER_NAME](README.md#country_header_name)
- [REGION_HEADER_NAME](README.md#region_header_name)
- [LATITUDE_HEADER_NAME](README.md#latitude_header_name)
- [LONGITUDE_HEADER_NAME](README.md#longitude_header_name)
#### Parameters
| Name | Type | Description |
| :-------- | :-------- | :-------------------------------------------------------------- |
| `request` | `Request` | The incoming request object which provides the geolocation data |
#### Returns
[`Geo`](interfaces/Geo.md)
#### Defined in
[src/edge-headers.ts:80](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L80)
---
### ipAddress
**ipAddress**(`request`): `string` \| `undefined`
Returns the IP address of the request from the headers.
**`See`**
[IP_HEADER_NAME](README.md#ip_header_name)
#### Parameters
| Name | Type | Description |
| :-------- | :-------- | :------------------------------------------------ |
| `request` | `Request` | The incoming request object which provides the IP |
#### Returns
`string` \| `undefined`
#### Defined in
[src/edge-headers.ts:66](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L66)
---
### next
**next**(`init?`): `Response`
Returns a Response that instructs the system to continue processing the request.
**`Example`**
<caption>No-op middleware</caption>
```ts
import { next } from '@vercel/edge';
export default function middleware(_req: Request) {
return next();
}
```
**`Example`**
<caption>Add response headers to all requests</caption>
```ts
import { next } from '@vercel/edge';
export default function middleware(_req: Request) {
return next({
headers: { 'x-from-middleware': 'true' },
});
}
```
#### Parameters
| Name | Type | Description |
| :------ | :----------------------------------------------------- | :---------------------------------- |
| `init?` | [`ExtraResponseInit`](interfaces/ExtraResponseInit.md) | Additional options for the response |
#### Returns
`Response`
#### Defined in
[src/middleware-helpers.ts:94](https://github.com/vercel/vercel/blob/main/packages/edge/src/middleware-helpers.ts#L94)
---
### rewrite
**rewrite**(`destination`, `init?`): `Response`
Returns a response that rewrites the request to a different URL.
**`Example`**
<caption>Rewrite all feature-flagged requests from `/:path*` to `/experimental/:path*`</caption>
```ts
import { rewrite, next } from '@vercel/edge';
export default async function middleware(req: Request) {
const flagged = await getFlag(req, 'isExperimental');
if (flagged) {
const url = new URL(req.url);
url.pathname = `/experimental{url.pathname}`;
return rewrite(url);
}
return next();
}
```
**`Example`**
<caption>JWT authentication for `/api/:path*` requests</caption>
```ts
import { rewrite, next } from '@vercel/edge';
export default function middleware(req: Request) {
const auth = checkJwt(req.headers.get('Authorization'));
if (!checkJwt) {
return rewrite(new URL('/api/error-unauthorized', req.url));
}
const url = new URL(req.url);
url.searchParams.set('_userId', auth.userId);
return rewrite(url);
}
export const config = { matcher: '/api/users/:path*' };
```
#### Parameters
| Name | Type | Description |
| :------------ | :------------------------------------------------------------------------ | :---------------------------------- |
| `destination` | `string` \| [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL) | new URL to rewrite the request to |
| `init?` | [`ExtraResponseInit`](interfaces/ExtraResponseInit.md) | Additional options for the response |
#### Returns
`Response`
#### Defined in
[src/middleware-helpers.ts:53](https://github.com/vercel/vercel/blob/main/packages/edge/src/middleware-helpers.ts#L53)

View File

@@ -0,0 +1,56 @@
# Interface: ExtraResponseInit
## Hierarchy
- `Omit`<`ResponseInit`, `"headers"`\>
**`ExtraResponseInit`**
## Table of contents
### Properties
- [headers](ExtraResponseInit.md#headers)
- [status](ExtraResponseInit.md#status)
- [statusText](ExtraResponseInit.md#statustext)
## Properties
### headers
`Optional` **headers**: `HeadersInit`
These headers will be sent to the user response
along with the response headers from the origin.
#### Defined in
[src/middleware-helpers.ts:6](https://github.com/vercel/vercel/blob/main/packages/edge/src/middleware-helpers.ts#L6)
---
### status
`Optional` **status**: `number`
#### Inherited from
Omit.status
#### Defined in
node_modules/typescript/lib/lib.dom.d.ts:1578
---
### statusText
`Optional` **statusText**: `string`
#### Inherited from
Omit.statusText
#### Defined in
node_modules/typescript/lib/lib.dom.d.ts:1579

View File

@@ -0,0 +1,73 @@
# Interface: Geo
The location information of a given request.
## Table of contents
### Properties
- [city](Geo.md#city)
- [country](Geo.md#country)
- [latitude](Geo.md#latitude)
- [longitude](Geo.md#longitude)
- [region](Geo.md#region)
## Properties
### city
`Optional` **city**: `string`
The city that the request originated from.
#### Defined in
[src/edge-headers.ts:41](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L41)
---
### country
`Optional` **country**: `string`
The country that the request originated from.
#### Defined in
[src/edge-headers.ts:44](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L44)
---
### latitude
`Optional` **latitude**: `string`
The latitude of the client.
#### Defined in
[src/edge-headers.ts:50](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L50)
---
### longitude
`Optional` **longitude**: `string`
The longitude of the client.
#### Defined in
[src/edge-headers.ts:53](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L53)
---
### region
`Optional` **region**: `string`
The [Vercel Edge Network region](https://vercel.com/docs/concepts/edge-network/regions) that received the request.
#### Defined in
[src/edge-headers.ts:47](https://github.com/vercel/vercel/blob/main/packages/edge/src/edge-headers.ts#L47)

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/edge", "name": "@vercel/edge",
"version": "0.0.3", "version": "0.0.4",
"license": "MIT", "license": "MIT",
"main": "dist/index.js", "main": "dist/index.js",
"module": "dist/index.mjs", "module": "dist/index.mjs",
@@ -8,13 +8,17 @@
"scripts": { "scripts": {
"build": "tsup src/index.ts --dts --format esm,cjs", "build": "tsup src/index.ts --dts --format esm,cjs",
"test": "jest --env node --verbose --runInBand --bail", "test": "jest --env node --verbose --runInBand --bail",
"test-unit": "yarn test" "test-unit": "yarn test",
"build:docs": "typedoc && prettier --write docs/**/*.md docs/*.md"
}, },
"devDependencies": { "devDependencies": {
"@edge-runtime/jest-environment": "1.1.0-beta.7", "@edge-runtime/jest-environment": "1.1.0-beta.7",
"@types/jest": "27.4.1", "@types/jest": "27.4.1",
"ts-node": "8.9.1", "ts-node": "8.9.1",
"tsup": "6.1.2", "tsup": "6.1.2",
"typedoc": "0.23.10",
"typedoc-plugin-markdown": "3.13.4",
"typedoc-plugin-mdn-links": "2.0.0",
"typescript": "4.7.4" "typescript": "4.7.4"
}, },
"jest": { "jest": {

View File

@@ -1,25 +1,25 @@
/** /**
* City of the original client IP calculated by Vercel Proxy. * City of the original client IP as calculated by Vercel Proxy.
*/ */
export const CITY_HEADER_NAME = 'x-vercel-ip-city'; export const CITY_HEADER_NAME = 'x-vercel-ip-city';
/** /**
* Country of the original client IP calculated by Vercel Proxy. * Country of the original client IP as calculated by Vercel Proxy.
*/ */
export const COUNTRY_HEADER_NAME = 'x-vercel-ip-country'; export const COUNTRY_HEADER_NAME = 'x-vercel-ip-country';
/** /**
* Ip from Vercel Proxy. Do not confuse it with the client Ip. * Client IP as calcualted by Vercel Proxy.
*/ */
export const IP_HEADER_NAME = 'x-real-ip'; export const IP_HEADER_NAME = 'x-real-ip';
/** /**
* Latitude of the original client IP calculated by Vercel Proxy. * Latitude of the original client IP as calculated by Vercel Proxy.
*/ */
export const LATITUDE_HEADER_NAME = 'x-vercel-ip-latitude'; export const LATITUDE_HEADER_NAME = 'x-vercel-ip-latitude';
/** /**
* Longitude of the original client IP calculated by Vercel Proxy. * Longitude of the original client IP as calculated by Vercel Proxy.
*/ */
export const LONGITUDE_HEADER_NAME = 'x-vercel-ip-longitude'; export const LONGITUDE_HEADER_NAME = 'x-vercel-ip-longitude';
/** /**
* Region of the original client IP calculated by Vercel Proxy. * Region of the original client IP as calculated by Vercel Proxy.
*/ */
export const REGION_HEADER_NAME = 'x-vercel-ip-country-region'; export const REGION_HEADER_NAME = 'x-vercel-ip-country-region';
@@ -34,18 +34,22 @@ interface Request {
} }
/** /**
* The location information of a given request * The location information of a given request.
*/ */
export interface Geo { export interface Geo {
/** The city that the request originated from */ /** The city that the request originated from. */
city?: string; city?: string;
/** The country that the request originated from */
/** The country that the request originated from. */
country?: string; country?: string;
/** The Vercel Edge Network region that received the request */
/** The [Vercel Edge Network region](https://vercel.com/docs/concepts/edge-network/regions) that received the request. */
region?: string; region?: string;
/** The latitude of the client */
/** The latitude of the client. */
latitude?: string; latitude?: string;
/** The longitude of the client */
/** The longitude of the client. */
longitude?: string; longitude?: string;
} }
@@ -57,19 +61,21 @@ function getHeader(request: Request, key: string): string | undefined {
* Returns the IP address of the request from the headers. * Returns the IP address of the request from the headers.
* *
* @see {@link IP_HEADER_NAME} * @see {@link IP_HEADER_NAME}
* @param request The incoming request object which provides the IP
*/ */
export function ipAddress(request: Request): string | undefined { export function ipAddress(request: Request): string | undefined {
return getHeader(request, IP_HEADER_NAME); return getHeader(request, IP_HEADER_NAME);
} }
/** /**
* Returns the location information from for the incoming request * Returns the location information for the incoming request.
* *
* @see {@link CITY_HEADER_NAME} * @see {@link CITY_HEADER_NAME}
* @see {@link COUNTRY_HEADER_NAME} * @see {@link COUNTRY_HEADER_NAME}
* @see {@link REGION_HEADER_NAME} * @see {@link REGION_HEADER_NAME}
* @see {@link LATITUDE_HEADER_NAME} * @see {@link LATITUDE_HEADER_NAME}
* @see {@link LONGITUDE_HEADER_NAME} * @see {@link LONGITUDE_HEADER_NAME}
* @param request The incoming request object which provides the geolocation data
*/ */
export function geolocation(request: Request): Geo { export function geolocation(request: Request): Geo {
return { return {

View File

@@ -1,13 +1,54 @@
export type ExtraResponseInit = Omit<ResponseInit, 'headers'> & { export interface ExtraResponseInit extends Omit<ResponseInit, 'headers'> {
/** /**
* These headers will be sent to the user response * These headers will be sent to the user response
* along with the response headers from the origin * along with the response headers from the origin.
*/ */
headers?: HeadersInit; headers?: HeadersInit;
}; }
/** /**
* Rewrite the request into a different URL. * Returns a response that rewrites the request to a different URL.
*
* @param destination new URL to rewrite the request to
* @param init Additional options for the response
*
*
* @example
* <caption>Rewrite all feature-flagged requests from `/:path*` to `/experimental/:path*`</caption>
*
* ```ts
* import { rewrite, next } from '@vercel/edge';
*
* export default async function middleware(req: Request) {
* const flagged = await getFlag(req, 'isExperimental');
* if (flagged) {
* const url = new URL(req.url);
* url.pathname = `/experimental{url.pathname}`;
* return rewrite(url);
* }
*
* return next();
* }
* ```
*
* @example
* <caption>JWT authentication for `/api/:path*` requests</caption>
*
* ```ts
* import { rewrite, next } from '@vercel/edge';
*
* export default function middleware(req: Request) {
* const auth = checkJwt(req.headers.get('Authorization'));
* if (!checkJwt) {
* return rewrite(new URL('/api/error-unauthorized', req.url));
* }
* const url = new URL(req.url);
* url.searchParams.set('_userId', auth.userId);
* return rewrite(url);
* }
*
* export const config = { matcher: '/api/users/:path*' };
* ```
*/ */
export function rewrite( export function rewrite(
destination: string | URL, destination: string | URL,
@@ -22,7 +63,33 @@ export function rewrite(
} }
/** /**
* This tells the Middleware to continue with the request. * Returns a Response that instructs the system to continue processing the request.
*
* @param init Additional options for the response
*
* @example
* <caption>No-op middleware</caption>
*
* ```ts
* import { next } from '@vercel/edge';
*
* export default function middleware(_req: Request) {
* return next();
* }
* ```
*
* @example
* <caption>Add response headers to all requests</caption>
*
* ```ts
* import { next } from '@vercel/edge';
*
* export default function middleware(_req: Request) {
* return next({
* headers: { 'x-from-middleware': 'true' },
* })
* }
* ```
*/ */
export function next(init?: ExtraResponseInit): Response { export function next(init?: ExtraResponseInit): Response {
const headers = new Headers(init?.headers ?? {}); const headers = new Headers(init?.headers ?? {});

38
packages/edge/test/docs.test.ts vendored Normal file
View File

@@ -0,0 +1,38 @@
/// <reference types="@types/node" />
import { exec } from 'child_process';
import { promisify } from 'util';
import path from 'path';
const execAsync = promisify(exec);
const test = process.platform === 'win32' ? it.skip : it;
test('docs are up to date', async () => {
const cwd = path.resolve(__dirname, '../');
await execAsync(`yarn build:docs`, { cwd });
const result = await execAsync(`git status --short docs`, {
cwd,
encoding: 'utf-8',
});
const lines = result.stdout
.trim()
.split(/(?:\r?\n)+/)
.map(x => x.trim().split(/\s+/).slice(1).join(' '))
.filter(x => x.startsWith('docs/'))
.map(x => `* ${x}`)
.join('\n')
.trim();
if (lines !== '') {
const diff = await execAsync(`git diff docs`, { cwd, encoding: 'utf8' });
throw new Error(
'Docs are not up to date. Please re-run `yarn build:docs` to re-generate them.\nChanges:\n' +
lines +
'\n\n' +
diff.stdout
);
}
expect(result.stdout.trim()).toEqual('');
}, 120000);

View File

@@ -0,0 +1,10 @@
{
"$schema": "https://typedoc.org/schema.json",
"entryPoints": ["src/index.ts"],
"plugin": ["typedoc-plugin-markdown", "typedoc-plugin-mdn-links"],
"out": "docs",
"githubPages": false,
"gitRevision": "main",
"readme": "none",
"hideBreadcrumbs": true
}

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/fs-detectors", "name": "@vercel/fs-detectors",
"version": "2.0.5", "version": "2.1.0",
"description": "Vercel filesystem detectors", "description": "Vercel filesystem detectors",
"main": "./dist/index.js", "main": "./dist/index.js",
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",

View File

@@ -92,4 +92,16 @@ export abstract class DetectorFilesystem {
public chdir = (name: string): DetectorFilesystem => { public chdir = (name: string): DetectorFilesystem => {
return this._chdir(name); return this._chdir(name);
}; };
/**
* Writes a file to the filesystem cache.
* @param name the name of the file to write
* @param content The content of the file
*/
public writeFile(name: string, content?: string): void {
if (content)
this.readFileCache.set(name, Promise.resolve(Buffer.from(content)));
this.fileCache.set(name, Promise.resolve(true));
this.pathCache.set(name, Promise.resolve(true));
}
} }

View File

@@ -59,6 +59,19 @@ export const workspaceManagers: Array<
], ],
}, },
}, },
{
name: 'default',
slug: 'yarn',
detectors: {
every: [
{
path: 'package.json',
matchContent:
'"workspaces":\\s*(?:\\[[^\\]]*]|{[^}]*"packages":[^}]*})',
},
],
},
},
]; ];
export default workspaceManagers; export default workspaceManagers;

View File

@@ -0,0 +1,15 @@
{
"name": "a",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"debug": "^4.3.2"
}
}

View File

@@ -0,0 +1,15 @@
{
"name": "b",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"cowsay": "^1.5.0"
}
}

View File

@@ -0,0 +1,8 @@
{
"name": "21-npm-workspaces",
"version": "1.0.0",
"workspaces": [
"a",
"b"
]
}

View File

@@ -11,6 +11,7 @@ describe('workspace-managers', () => {
['25-multiple-lock-files-yarn', 'yarn'], ['25-multiple-lock-files-yarn', 'yarn'],
['26-multiple-lock-files-pnpm', 'pnpm'], ['26-multiple-lock-files-pnpm', 'pnpm'],
['22-pnpm', null], ['22-pnpm', null],
['38-workspaces-no-lock-file', 'yarn'],
])('with detectFramework', (fixturePath, frameworkSlug) => { ])('with detectFramework', (fixturePath, frameworkSlug) => {
const testName = frameworkSlug const testName = frameworkSlug
? `should detect a ${frameworkSlug} workspace for ${fixturePath}` ? `should detect a ${frameworkSlug} workspace for ${fixturePath}`

View File

@@ -160,6 +160,17 @@ describe('DetectorFilesystem', () => {
]); ]);
}); });
it('should be able to write files', async () => {
const files = {};
const fs = new VirtualFilesystem(files);
fs.writeFile('file.txt', 'Hello World');
expect(await fs.readFile('file.txt')).toEqual(Buffer.from('Hello World'));
expect(await fs.hasPath('file.txt')).toBe(true);
expect(await fs.isFile('file.txt')).toBe(true);
});
it('should be able to change directories', async () => { it('should be able to change directories', async () => {
const nextPackageJson = JSON.stringify({ const nextPackageJson = JSON.stringify({
dependencies: { dependencies: {

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