Compare commits

..

76 Commits

Author SHA1 Message Date
JJ Kasper
4f8f3d373f Publish Canary
- vercel@25.1.1-canary.4
 - @vercel/next@3.0.3-canary.1
 - @vercel/node@2.2.1-canary.0
2022-06-16 18:00:29 -05:00
JJ Kasper
debb85b690 [next] Update error for internal missing page (#7987)
Update error for internal missing page
2022-06-16 16:44:12 -05:00
Thai Pangsakulyanont
bfef989ada [examples] Delete pnpm-lock.yaml from examples/sveltekit because it already contains yarn.lock (#7983)
Delete pnpm-lock.yaml

Co-authored-by: Lee Robinson <lrobinson2011@gmail.com>
2022-06-16 10:24:18 -07:00
JJ Kasper
4e0b6c5eaf [next] Update to skip middleware for on-demand revalidate (#7978)
Update to skip middleware for on-demand revalidate
2022-06-16 11:23:30 -05:00
Nathan Rajlich
0ace69ef75 Publish Stable
- @vercel/node@2.2.0
2022-06-15 13:01:57 -07:00
Nathan Rajlich
b7b7923f92 Publish Canary
- vercel@25.1.1-canary.3
 - @vercel/next@3.0.3-canary.0
 - @vercel/node@2.1.1-canary.2
2022-06-15 11:56:32 -07:00
Nathan Rajlich
8167233c56 [tests] Create tarballs of all packages in Vercel deployment (#7967)
For the Vercel deployment, run `yarn pack` in each of the packages in the monorepo and place them in the "public/tarballs" directory so that we can have npm-installable URLs of each package for every commit. The `package.json` of each package is also updated to reference the tarball when a package depends on other packages within the monorepo.

Try it out like:

```
$ npm i -g https://vercel-biww73ffq.vercel.sh/tarballs/vercel.tgz

# Notice how the package.json has the monorepo dependencies
# updated to point to the related tarballs from the same deployment:

$ cat /usr/local/lib/node_modules/vercel/package.json | grep biww
    "@vercel/build-utils": "https://vercel-biww73ffq.vercel.sh/tarballs/@vercel/build-utils.tgz",
    "@vercel/go": "https://vercel-biww73ffq.vercel.sh/tarballs/@vercel/go.tgz",
    "@vercel/next": "https://vercel-biww73ffq.vercel.sh/tarballs/@vercel/next.tgz",
    "@vercel/node": "https://vercel-biww73ffq.vercel.sh/tarballs/@vercel/node.tgz",
    "@vercel/python": "https://vercel-biww73ffq.vercel.sh/tarballs/@vercel/python.tgz",
    "@vercel/redwood": "https://vercel-biww73ffq.vercel.sh/tarballs/@vercel/redwood.tgz",
    "@vercel/remix": "https://vercel-biww73ffq.vercel.sh/tarballs/@vercel/remix.tgz",
    "@vercel/ruby": "https://vercel-biww73ffq.vercel.sh/tarballs/@vercel/ruby.tgz",
    "@vercel/static-build": "https://vercel-biww73ffq.vercel.sh/tarballs/@vercel/static-build.tgz",
    "@vercel/client": "https://vercel-biww73ffq.vercel.sh/tarballs/@vercel/client.tgz",
    "@vercel/frameworks": "https://vercel-biww73ffq.vercel.sh/tarballs/@vercel/frameworks.tgz",

# Also notice that the "version" in `package.json` gets updated to include the commit
# SHA so that we can easily identify which commit a given tarball was generated from:

$ vercel --version
25.1.1-canary.1-727b290
```
2022-06-15 17:44:23 +00:00
Michaël De Boey
32ee6aba92 [node] Move @vercel/build-utils to "dependencies" (#7934)
fix(node): move `@vercel/build-utils` to `dependencies`

Co-authored-by: Steven <steven@ceriously.com>
2022-06-15 13:18:02 -04:00
Nathan Rajlich
b48f7a7e6e [node] Add missing dependencies for dev-server.ts (#7969)
Some new imports were introduced recently but are missing from the `"dependencies"` object, so they were missing when the package was installed from the npm registry.
2022-06-15 01:38:17 +00:00
JJ Kasper
a961c9b992 Publish Stable
- @vercel/next@3.0.2
2022-06-14 17:35:24 -05:00
JJ Kasper
cf7c50d691 Publish Canary
- vercel@25.1.1-canary.2
 - @vercel/next@3.0.2-canary.2
2022-06-14 14:57:54 -05:00
JJ Kasper
f4be388a1f [next] Fix _next/data resolving priority with afterFiles (#7968)
* Fix _next/data resolving priority with afterFiles

* ensure x-nextjs-matched-path header is set
2022-06-14 14:52:49 -05:00
Nathan Rajlich
c1bc53dea8 Publish Canary
- @vercel/build-utils@4.1.1-canary.1
 - vercel@25.1.1-canary.1
 - @vercel/client@12.0.2-canary.1
 - @vercel/frameworks@1.0.2-canary.0
 - @vercel/go@2.0.2-canary.1
 - @vercel/next@3.0.2-canary.1
 - @vercel/node@2.1.1-canary.1
 - @vercel/python@3.0.2-canary.1
 - @vercel/redwood@1.0.2-canary.1
 - @vercel/remix@1.0.2-canary.1
 - @vercel/routing-utils@1.13.5-canary.0
 - @vercel/ruby@1.3.10-canary.1
 - @vercel/static-build@1.0.2-canary.1
2022-06-14 09:15:20 -07:00
Nathan Rajlich
6855e3df54 [tests] Allow for multiple probes (#7957)
The `probes` in the vercel.json files in tests fixtures weren't allowing both `mustContain` _and_ `responseHeaders` probes due to an `else` block. This PR allows to combine those checks into the same probe test.
2022-06-14 06:54:16 +00:00
Nathan Rajlich
0d39dbd1d9 [routing-utils] Convert tests to TypeScript (#7959)
Allows for the tests to be run without rebuilding the source code.
2022-06-13 23:29:00 -07:00
Nathan Rajlich
509c85182a [tests] Update ts-jest to v28.0.5 (#7962)
Gets rid of the annoying ts-jest update warning:

```
ts-jest[ts-jest-transformer] (WARN) Use 'ts-jest' entry point in v28 will resolve in refactored transformer. If you wish to use legacy transformer, please adjust your Jest 'transform' config. For example:
     module.exports = {
        transform: {
           '^.+\\.tsx?$': 'ts-jest/legacy'
        }
     }
```
2022-06-13 23:18:23 -07:00
Sean Massa
ae801e563d [cli] add support for Edge Functions in vercel dev (#7925)
Add support for Edge Functions when using `vercel dev`. This uses the [`edge-runtime`](https://edge-runtime.vercel.app/) to emulate the proper environment, [`ncc`](https://github.com/vercel/ncc) to parse the entrypoint (supporting TypeScript and importing node modules), and some custom marshaling to pass the request/response values through.
2022-06-13 22:50:16 +00:00
Steven
0e8278f490 [cli] Fix vc dev when user has ipv6 configured (#7956)
Running tests from #7455 

- Fixes #7306
- Closes #7455
2022-06-13 21:04:00 +00:00
Steven
0d302a6f48 [cli] Add alias for vc develop => vc dev (#7955)
This will make it easier for users who are used to `gatsby develop` or other CLIs that abbreviate to [`develop`](https://twitter.com/DiverseCauses/status/1536358138676486146).

Fixes this:
![image](https://user-images.githubusercontent.com/229881/173387083-7afd01db-00ab-4670-a07d-fa6c8cecea5d.png)
2022-06-13 17:01:46 +00:00
Lee Robinson
4e4f5f28a2 [examples] Add Vercel Analytics for CRA/SvelteKit templates. (#7894)
Successfully tested with:

- [CRA](https://front-git-analytics-for-all-frameworks.vercel.sh/now-examples/create-react-app/analytics?device=desktop&timePeriod=1d&percentile=75&timeInterval=3h&sort=datapoints&search=)
- [SvelteKit](https://front-git-analytics-for-all-frameworks.vercel.sh/now-examples/sveltekit/analytics?device=desktop&timePeriod=1d&percentile=75&timeInterval=3h&sort=datapoints&search=)

Dependencies have also been updated 👍
2022-06-13 16:30:14 +00:00
Steven
5205a4ec4b [cli] Fix vc dev with dynamic paths and ESM (#7946)
In a previous PR, the entrypoint path extension was stripped in `vc dev` to match the behavior of production deployments (`/api/user.js` => `/api/user`). However, it was supposed to map back to the original file extension before invoking the matching builder. This PR fixes the mapping for dynamic paths with ESM, such as `/api/[id].mjs` => `/api/[id]`.
2022-06-12 17:54:21 +00:00
Nathan Rajlich
2c15e496ed [tests] Remove as any casting in unit tests (#7945)
Maybe this casting was necessary at some point, but it doesn't seem to be anymore.
2022-06-10 22:30:51 +00:00
Matthew Stanciu
1f0ca46626 [cli] Add git metadata to vc deploy (#7910)
Currently, git metadata is not included on the Vercel dashboard when a project is deployed via the CLI. This PR populates the git metadata.

### 📋 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-06-10 20:53:47 +00:00
Steven
17cb5f1bc6 [cli] Remove boxen for warnings (#7944)
This never worked correctly and its too verbose. Lets remove boxen in favor of yellow text.

## Before

<img width="1432" alt="before" src="https://user-images.githubusercontent.com/229881/173138338-1cd1c81c-f294-4752-a08f-f80466fbd21c.png" />

## After

<img width="1432" alt="after" src="https://user-images.githubusercontent.com/229881/173138813-f9ff4de1-5bb7-4b60-8609-6abdd0e86fa0.png">
2022-06-10 20:28:00 +00:00
Nathan Rajlich
b095031292 [cli] Normalize "src" property in vc build (#7943)
This matches the production behavior.
2022-06-10 18:53:01 +00:00
Ethan Arrowood
f50bcbc0ba [cli] Implement vercel.json configuration overrides (#7811)
### Related Issues

This PR adds vercel.json configuration overrides support for both new and existing project flows through the CLI. It includes integration and unit tests.

### 📋 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-06-10 16:46:46 +00:00
JJ Kasper
4bf6295d7a Publish Canary
- @vercel/build-utils@4.1.1-canary.0
 - vercel@25.1.1-canary.0
 - @vercel/client@12.0.2-canary.0
 - @vercel/go@2.0.2-canary.0
 - @vercel/next@3.0.2-canary.0
 - @vercel/node@2.1.1-canary.0
 - @vercel/python@3.0.2-canary.0
 - @vercel/redwood@1.0.2-canary.0
 - @vercel/remix@1.0.2-canary.0
 - @vercel/ruby@1.3.10-canary.0
 - @vercel/static-build@1.0.2-canary.0
2022-06-10 09:37:52 -05:00
JJ Kasper
a4001ce10b [next] Add handling for resolving _next/data URLs (#7935)
* Add handling for resolving _next/data URLs

* update check

* update routes

* remove extra routes

* Update resolving
2022-06-10 08:50:59 -05:00
Nathan Rajlich
2df3432d88 [node] Add zero-config support for root-level middleware.js/middleware.ts file (#7928)
Adds support for `config.middleware` property in `@vercel/node` to output an Edge Middleware (by including a catch-all route in the output).

Also updates the zero-config detection logic to schedule a build of root-level `middleware.js`/`middleware.ts` files with `@vercel/node` with this middleware option enabled.
2022-06-09 23:04:39 +00:00
chloetedder
bcfc19de12 [build-utils] Add getProjectPaths that will evaluate if there is a monorepo (#7826)
<img width="1174" alt="Screen Shot 2022-05-19 at 3 53 55 PM" src="https://user-images.githubusercontent.com/35947020/169402845-4eb70a24-9954-44ac-8726-a6b56161d58c.png">

If there is no workspace detected, we still want to check if there are potential projects in the monorepo. An example of this case would be:

<img width="202" alt="Screen Shot 2022-05-19 at 10 29 12 AM" src="https://user-images.githubusercontent.com/35947020/169336400-e69845e3-616e-4857-80fe-c2614a65352a.png">

The directories `backend/app-three`, `frontend/app-one`, `frontend/app-two` all have definitions within their `package.json` files that define a framework assosiated with the project. This is what distinguishes them from the `package.json` files within `backend` and `frontend`

`getProjectPaths` loops through a depth of 3 (this can be changed but in most cases of this it won't be more than 3). For each directory it recursively calls itself until it finds a `package.json` file. It then checks if the directory has a framework defined and if so it adds the path to the final return value of all projects.

If any `skipPaths` are sent into the function, no projects will be looked for within those path(s). This would include the case for a hybrid monorepo where a workspace has already been detected

### Related Issues

> Fixes #1
> Related to #2

### 📋 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-06-09 15:15:25 +00:00
Matthew Stanciu
04381c669b [cli] Pull correct env variables in vc build command (#7933)
`vc build` runs `vc pull` if no project settings are found locally. By default, `vc pull` pulls development environment variables, but we want either preview or production environment variables in the context of `vc build`. This PR adds a flag to the internal `vc pull` call to pull the correct environment variables.

### 📋 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-06-09 06:15:30 +00:00
Steven
0c7b54edad Publish Stable
- @vercel/build-utils@4.1.0
 - vercel@25.1.0
 - @vercel/client@12.0.1
 - @vercel/go@2.0.1
 - @vercel/next@3.0.1
 - @vercel/node@2.1.0
 - @vercel/python@3.0.1
 - @vercel/redwood@1.0.1
 - @vercel/remix@1.0.1
 - @vercel/ruby@1.3.9
 - @vercel/static-build@1.0.1
 - @vercel/static-config@2.0.1
2022-06-08 10:44:17 -04:00
Steven
6d42816395 Publish Canary
- vercel@25.0.2-canary.1
 - @vercel/next@3.0.1-canary.1
 - @vercel/static-build@1.0.1-canary.1
2022-06-08 08:48:04 -04:00
Seiya Nuta
6fe6d05a42 Increase the maximum size of an edge function to 1MiB (#7903) 2022-06-08 16:30:49 +09:00
Steven
50a201f145 [cli][static-build] Fix case when vc dev is throwing the wrong error w/ BOAv3 (#7927)
Fixes an issue brought up in https://github.com/vercel/og-image/issues/207

The problem is that we allow a corner case with `vc dev` to run the build script, but this can break if the user ran `vc build` first due to the BOA V3 detection.

The workaround is to delete the BOA V3 directory when there is no Development Command provided since that will run the Build Command during development.

We also add a nicer error in the case when someone actually intended to emit BOA V3.
2022-06-07 21:31:43 +00:00
Gal Schlezinger
701a02ae9d [next] Support Edge API Endpoints (#7905)
* Add regression test for nested middleware

* [next] Allow edge api endpoints in Next.js

This reverts commit d4cef69cc9 (#7898)

* delete `functions`, not `middleware`

* Add an assertion

Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-06-07 23:36:46 +03:00
agadzik
39f7586621 Publish Canary
- @vercel/build-utils@4.0.1-canary.0
 - vercel@25.0.2-canary.0
 - @vercel/client@12.0.1-canary.0
 - @vercel/go@2.0.1-canary.0
 - @vercel/next@3.0.1-canary.0
 - @vercel/node@2.0.2-canary.0
 - @vercel/python@3.0.1-canary.0
 - @vercel/redwood@1.0.1-canary.0
 - @vercel/remix@1.0.1-canary.0
 - @vercel/ruby@1.3.9-canary.0
 - @vercel/static-build@1.0.1-canary.0
 - @vercel/static-config@2.0.1-canary.0
2022-06-07 12:33:07 -04:00
Andrew Gadzik
c4a39c8d29 [build-utils] Add getWorkspacePackagePaths function (#7859)
Gets the package paths from the workspace definition for a given git repository

```ts
/** 
 *  Gets the list of workspace package paths based on the configuration 
 * 
 * @example
 *
 * my-repo
 * |-- packages
 * |    |-- api-1
 * |    |-- api-2
 * |-- package.json
 * |-- pnpm-workspaces.yaml // packages: ["packages/*"]
 *
 * const fs = new ... // based on 'my-repo'
 * const workspace = { ..., type: "pnpm" }
 * getWorkspacePackagePaths({ fs, workspace }) => [
 *   "my-repo/packages/api-1",
 *   "my-repo/packages/api-2"
 * ]
 */
function getWorkspacePackagePaths(fs: DetectorFilesystem, workspace: Workspace): string[]
```

### Related Issues

Closes https://github.com/vercel/vercel/issues/7749

### 📋 Checklist

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

#### Tests

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

#### Code Review

- [x] This PR has a concise title and thorough description useful to a reviewer
- [x] Issue from task tracker has a link to this PR
2022-06-07 15:55:23 +00:00
Nathan Rajlich
3ac238cf08 [node] Add support for EdgeFunction output type (#7877)
Adds support for `@vercel/node` to return an `EdgeFunction` output when it detects `runtime: 'experimental-edge'` from the static `config` object. This is implemented in `@vercel/node` so that it can share the same file extension (`.js`/`.ts`) as Node.js Serverless Functions without modifying the zero-config detection logic.

**Example:**

```js
export const config = {
  runtime: 'experimental-edge',
};

export default req => {
  return new Response(`Hi, from an Edge Function!`);
};
```
2022-06-07 02:20:08 +00:00
Nathan Rajlich
8384813a0d [cli] Output EdgeFunctions from version 3 Builders in vc build (#7924)
Serializes `EdgeFunction` outputs in `vc build` when provided by a version 3 Builder.
2022-06-07 01:42:33 +00:00
Nathan Rajlich
c4587de439 [cli] Add initial vc build unit tests (#7869)
Adds some initial unit tests for the `vc build` command which includes some fixes for Windows.
2022-06-06 22:16:18 +00:00
Nathan Rajlich
d997dc4fbc [static-config] Use "runtime" instead of "use" (#7922)
The static configuration property name has been decided to be `runtime`.

This is a precursor for #7877.
2022-06-06 19:02:32 +00:00
Nathan Rajlich
d15b90bd4d [build-utils] Allow EdgeFunction as a valid output type for version 3 Builders (#7923)
A version 3 Builder is allowed to output an `EdgeFunction` output type. This already is handled correctly by the Vercel build infrastructure.
2022-06-06 18:42:14 +00:00
JJ Kasper
5b31297f0c [next] Ensure middleware route comes before beforeFiles rewrites (#7912)
* Ensure middleware route comes before beforeFiles rewrites

* update test

* add version lock and more tests

* update nested middleware test for version lock
2022-06-06 13:03:31 -04:00
Steven
e232566cbe Publish Stable
- vercel@25.0.1
 - @vercel/node@2.0.1
2022-06-05 21:38:44 -04:00
Steven
592689cad1 Publish Canary
- vercel@25.0.1-canary.0
 - @vercel/node@2.0.1-canary.0
2022-06-05 20:58:34 -04:00
Steven
9b08e72f76 [node] Skip TS compile for .d.ts assets (#7918)
Fixes an issue where `.d.ts` assets were incorrectly being compiled after a fix to nft in https://github.com/vercel/nft/pull/289

- [Repro Steps](https://github.com/cheapsteak/repro-vercel-cli-build-dts-error)
- [Incident](https://app.kintaba.com/incident/755531679765890512)
- [Slack](https://vercel.slack.com/archives/C03JNHA844R/p1654334373086879)

<img src="https://user-images.githubusercontent.com/229881/172055403-d2edfe1d-3a60-459f-bf40-506fea1d1ca4.png" height=500 />
2022-06-06 00:36:41 +00:00
Sean Massa
bd0e10cfe7 [cli] update vc env pull --help (#7911)
* update `vc env pull --help`

* update `vc env pull` help documentation

* try longer timeout

* remove dupe "env"

* remove unused --env option

* Update packages/cli/src/commands/env/index.ts

Co-authored-by: Steven <steven@ceriously.com>

Co-authored-by: Steven <steven@ceriously.com>
2022-06-05 16:22:32 -05:00
Sean Massa
28436ade60 [tests] increase test timeout; it has been failing recently (#7913) 2022-06-03 10:06:36 -05:00
Sean Massa
de0d2fba0b Publish Stable
- @vercel/build-utils@4.0.0
 - vercel@25.0.0
 - @vercel/client@12.0.0
 - @vercel/frameworks@1.0.1
 - @vercel/go@2.0.0
 - @vercel/next@3.0.0
 - @vercel/node-bridge@3.0.0
 - @vercel/node@2.0.0
 - @vercel/python@3.0.0
 - @vercel/redwood@1.0.0
 - @vercel/remix@1.0.0
 - @vercel/routing-utils@1.13.4
 - @vercel/ruby@1.3.8
 - @vercel/static-build@1.0.0
 - @vercel/static-config@2.0.0
2022-06-02 14:17:21 -05:00
Sean Massa
e0900128d6 [cli][client][tests] update to node14-compatible target (drop support for node12) (#7865)
Node 12 is EOL and we're starting to reference packages that don't install on node lower than 14, such as nuxt@3 and remix. Let's update to 14 for now.

---

Reimplements: https://github.com/vercel/vercel/pull/7819
2022-06-02 19:01:34 +00:00
Nathan Rajlich
8d15f30579 [tests] Remove ava compileEnhancements property from package.json (#7904)
VS Code was complaining about this property, so I guess it's not doing anything:

> Property `compileEnhancements` is not allowed.
2022-06-01 21:14:01 +00:00
Nathan Rajlich
960c66584c [build-utils] Make repoRootPath non-optional (#7909)
This value is always set in the Vercel build infra, so mark the type as non-optional.
2022-06-01 19:39:45 +00:00
JJ Kasper
1c8f91031a [next] Add regression test for nested middleware (#7901)
Add regression test for nested middleware
2022-06-01 10:04:16 -05:00
Steven
68cb23c3cc [cli] Add experimental corepack support (#7871)
This PR adds support for experimental [corepack](https://nodejs.org/api/corepack.html) to `vc build`.

Since this is still experimental, we only enable if the env var `ENABLE_EXPERIMENTAL_COREPACK=1` is set.

#### 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`
2022-06-01 14:31:33 +00:00
Nathan Rajlich
94f6ae2595 [examples] Remove ENABLE_FILE_SYSTEM_API env var from "remix" template (#7886)
Since the `@vercel/remix` Builder is now being used, this environment variable is no longer necessary.
2022-06-01 01:16:24 +00:00
Steven
b92aeac84d [build-utils] Fix warning for package.json engines (#7900)
This PR updates the way we handle warning for engines.node in `package.json`.

- should not warn when the engines version satisfies the project settings (previously it was an exact match)
- should warn when engines version is exact instead of range since it cannot be satisfied exactly
- should warn when engines version is greater than since it might introduce breaking changes for a future node.js version
2022-06-01 00:15:00 +00:00
JJ Kasper
00420b7a01 Publish Canary
- vercel@24.2.6-canary.0
 - @vercel/next@2.9.1-canary.0
 - @vercel/static-config@1.0.2-canary.0
2022-05-31 18:52:50 -05:00
Steven
a5128790d0 [tests] Fix turbo cache invalidation for GH Actions RUNNER_OS (#7899)
This PR fixes an issue where turbo was caching the result regardless of OS by adding the env var `RUNNER_OS` to the cache key.

https://docs.github.com/en/actions/learn-github-actions/environment-variables#detecting-the-operating-system
2022-05-31 22:42:26 +00:00
Seiya Nuta
ae9aa91f4f [static-config] Support extracting export const config from swc's AST (#7791)
This PR adds support for extracting `config` from swc's AST. `static-config` supports parsing from the source program but in some cases we already have parsed an AST to do other static code analysis.

### Related Issues

None

### 📋 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-05-31 20:44:29 +00:00
JJ Kasper
d4cef69cc9 Revert "[next] Allow edge api endpoints in Next.js" (#7898)
Reverts vercel/vercel#7855

We can re-land after we ensure the cases we have found failing are resolved.
2022-05-31 20:07:46 +00:00
Sean Massa
323f67c31a [cli] update update-notifier to fix notification of non-latest (#7897)
Looks like this is a bug in update-notifier, but they [smoothed it over](https://github.com/yeoman/update-notifier/pull/192) with a change that was deployed in [`5.0.0`](https://github.com/yeoman/update-notifier/releases/tag/v5.0.0). We’re currently on `4.1.0`.

This can cause an update notification for a PREVIOUS version, like so:

```
$  vc --version
> UPDATE AVAILABLE Run `npm i -g vercel@latest` to install Vercel CLI 24.2.5-canary.3
> Changelog: https://github.com/vercel/vercel/releases/tag/vercel@24.2.5-canary.3
Vercel CLI 24.2.5
24.2.5
```

While we're here, also sets the [`updatecheckinterval`](https://github.com/yeoman/update-notifier#updatecheckinterval) to 1 week.

---

Previous PR: https://github.com/vercel/vercel/pull/4896
2022-05-31 19:14:39 +00:00
Steven
63c499a826 [tests] Update domain tests from .org to .com (#7896)
Use a regex matcher and change the .org to .com
2022-05-31 18:07:25 +00:00
Steven
ad436313e1 Publish Stable
- @vercel/build-utils@3.1.1
 - vercel@24.2.5
 - @vercel/client@11.0.4
 - @vercel/frameworks@1.0.0
 - @vercel/go@1.4.4
 - @vercel/next@2.9.0
 - @vercel/node@1.15.4
 - @vercel/python@2.3.4
 - @vercel/redwood@0.8.4
 - @vercel/remix@0.0.2
 - @vercel/ruby@1.3.7
 - @vercel/static-build@0.26.0
2022-05-31 10:44:36 -04:00
Lee Robinson
c414288b2f [examples] Update SvelteKit example with latest boilerplate (#7892)
Replaces https://github.com/vercel/vercel/pull/7674 with the latest SvelteKit boilerplate.
2022-05-30 07:57:46 +00:00
Steven
b07ff7431f [tests] Bump turbo to 1.2.14 (#7887)
This should fix the caching problem

https://github.com/vercel/turborepo/releases/tag/v1.2.14
2022-05-27 23:46:38 +00:00
Steven
79fde4475c [build-utils][go][next][redwood][static-build] Fix path delimiter on windows (#7881)
This PR fixes an issue where the path delimiter was incorrect for windows and caused yarn (and other global CLIs) to no longer be found.

https://github.com/vercel/vercel/runs/6602572000?check_suite_focus=true#step:13:1357
2022-05-27 22:59:28 +00:00
Nathan Rajlich
855197c699 [node] Remove mkdirp-promise dependency (#7878)
There's no need to use this module since Node.js has `recursive: true` since Node v10.

Co-authored-by: Steven <steven@ceriously.com>
2022-05-26 15:07:09 -04:00
Nathan Rajlich
fbd9080859 [tests] Add vc build integration test (#7876)
[cli] Add `vc build` integration test

Adds a `vc build` integration test to ensure the ncc'd CLI works as
expected to supplement the unit tests from #7869.

Co-authored-by: Steven <steven@ceriously.com>
2022-05-26 15:05:31 -04:00
Steven
b5c5b7b82c [tests] Fix tests with promises (#7880)
These tests are asynchronous and would sometimes fail if the second call started before the first call finished. This PR fixes the usage since the function returns a promise.
2022-05-26 10:28:49 -04:00
Gal Schlezinger
0a072ee850 Publish Canary
- vercel@24.2.5-canary.3
 - @vercel/next@2.8.67-canary.3
2022-05-26 09:20:12 +03:00
Dalpat Rathore
0b56caba45 [docs] Fix CONTRIBUTING.md grammar (#7575)
Fix: CONTRIBUTING.md grammar

Co-authored-by: Steven <steven@ceriously.com>
2022-05-25 17:44:41 -04:00
Steven
ab3db60824 [tests] Conditionally set env vars (#7874)
This PR is a follow up to #7873 which didn't work as intended
2022-05-25 17:36:20 -04:00
Steven
f2f2ff2c67 [tests] Skip env vars for forks (#7873)
This fixes an issue when an external contributor submits a PR and the remote cache is not accessible 

```
$ turbo run build
 ERROR No caches are enabled. You can try "turbo login", "turbo link", or ensuring you are not passing --remote-only to enable caching:  <nil>
• Packages in scope: @vercel/build-utils, @vercel/client, @vercel/frameworks, @vercel/go, @vercel/next, @vercel/node, @vercel/node-bridge, @vercel/python, @vercel/redwood, @vercel/remix, @vercel/routing-utils, @vercel/ruby, @vercel/static-build, @vercel/static-config, vercel
```
2022-05-25 16:54:35 -04:00
Gal Schlezinger
ba7dafff71 [next] Allow edge api endpoints in Next.js (#7855)
Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-05-25 15:19:14 +03:00
JJ Kasper
987fb4d4f7 [next] Ensure test-next-local is run in CI (#7868)
* Ensure test-next-local is run in CI

* update turbo config

* update timeout

* update flakey test
2022-05-24 14:22:40 -05:00
391 changed files with 12896 additions and 2880 deletions

4
.gitattributes vendored
View File

@@ -8,3 +8,7 @@ packages/*/test/* linguist-vendored
# Go build fails with Windows line endings. # Go build fails with Windows line endings.
*.go text eol=lf *.go text eol=lf
go.mod text eol=lf go.mod text eol=lf
# Mark certain files as "binary" -- hide diffs
**/test/fixtures/**/git/**/* binary
**/test/fixtures/**/git/**/* linguist-generated

View File

@@ -23,7 +23,7 @@ Make sure all the tests pass before making changes.
## Verifying your change ## Verifying your change
Once you are done with your changes (we even suggest doing it along the way), make sure all the test still pass by running: Once you are done with your changes (we even suggest doing it along the way), make sure all the tests still pass by running:
``` ```
yarn test-unit yarn test-unit
@@ -64,7 +64,7 @@ Integration tests create deployments to your Vercel account using the `test` pro
x-now-trace=iad1] x-now-trace=iad1]
``` ```
In such cases you can visit the URL of the failed deployment and append `/_logs` so see the build error. In the case above, that would be https://test-8ashcdlew.vercel.app/_logs In such cases, you can visit the URL of the failed deployment and append `/_logs` to see the build error. In the case above, that would be https://test-8ashcdlew.vercel.app/_logs
The logs of this deployment will contain the actual error which may help you to understand what went wrong. The logs of this deployment will contain the actual error which may help you to understand what went wrong.
@@ -82,11 +82,11 @@ nodeFileTrace(['path/to/entrypoint.js'], {
.then(e => console.error(e)); .then(e => console.error(e));
``` ```
When you run this script, you'll see all imported files. If anything file is missing, the bug is in [@vercel/nft](https://github.com/vercel/nft) and not the Builder. When you run this script, you'll see all the imported files. If anything file is missing, the bug is in [@vercel/nft](https://github.com/vercel/nft) and not the Builder.
## Deploy a Builder with existing project ## Deploy a Builder with existing project
Sometimes you want to test changes to a Builder against an existing project, maybe with `vercel dev` or an actual deployment. You can avoid publishing every Builder change to npm by uploading the Builder as a tarball. Sometimes you want to test changes to a Builder against an existing project, maybe with `vercel dev` or actual deployment. You can avoid publishing every Builder change to npm by uploading the Builder as a tarball.
1. Change directory to the desired Builder `cd ./packages/node` 1. Change directory to the desired Builder `cd ./packages/node`
2. Run `yarn build` to compile typescript and other build steps 2. Run `yarn build` to compile typescript and other build steps

View File

@@ -18,11 +18,13 @@ jobs:
os: [ubuntu-latest] os: [ubuntu-latest]
node: [14] node: [14]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
env:
TURBO_REMOTE_ONLY: true
TURBO_TEAM: vercel
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
steps: steps:
- name: Conditionally set remote env
if: github.event.pull_request.head.repo.full_name == github.repository
run: |
echo "TURBO_REMOTE_ONLY=true" >> $GITHUB_ENV
echo "TURBO_TEAM=vercel" >> $GITHUB_ENV
echo "TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}" >> $GITHUB_ENV
- uses: actions/setup-go@v2 - uses: actions/setup-go@v2
with: with:
go-version: '1.13.15' go-version: '1.13.15'

View File

@@ -18,11 +18,13 @@ jobs:
os: [ubuntu-latest, macos-latest, windows-latest] os: [ubuntu-latest, macos-latest, windows-latest]
node: [14] node: [14]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
env:
TURBO_REMOTE_ONLY: true
TURBO_TEAM: vercel
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
steps: steps:
- name: Conditionally set remote env
if: github.event.pull_request.head.repo.full_name == github.repository
run: |
echo "TURBO_REMOTE_ONLY=true" >> $GITHUB_ENV
echo "TURBO_TEAM=vercel" >> $GITHUB_ENV
echo "TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}" >> $GITHUB_ENV
- uses: actions/setup-go@v2 - uses: actions/setup-go@v2
with: with:
go-version: '1.13.15' go-version: '1.13.15'
@@ -44,4 +46,4 @@ jobs:
- run: yarn workspace vercel run coverage - run: yarn workspace vercel run coverage
if: matrix.os == 'ubuntu-latest' && matrix.node == 14 # only run coverage once if: matrix.os == 'ubuntu-latest' && matrix.node == 14 # only run coverage once
env: env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -38,10 +38,6 @@ jobs:
runs-on: ${{ matrix.runner }} runs-on: ${{ matrix.runner }}
name: ${{matrix.scriptName}} (${{matrix.packageName}}, ${{matrix.chunkNumber}}, ${{ matrix.runner }}) name: ${{matrix.scriptName}} (${{matrix.packageName}}, ${{matrix.chunkNumber}}, ${{ matrix.runner }})
if: ${{ needs.setup.outputs['tests'] != '[]' }} if: ${{ needs.setup.outputs['tests'] != '[]' }}
env:
TURBO_REMOTE_ONLY: true
TURBO_TEAM: vercel
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
needs: needs:
- setup - setup
strategy: strategy:
@@ -49,6 +45,12 @@ jobs:
matrix: matrix:
include: ${{ fromJson(needs.setup.outputs['tests']) }} include: ${{ fromJson(needs.setup.outputs['tests']) }}
steps: steps:
- name: Conditionally set remote env
if: github.event.pull_request.head.repo.full_name == github.repository
run: |
echo "TURBO_REMOTE_ONLY=true" >> $GITHUB_ENV
echo "TURBO_TEAM=vercel" >> $GITHUB_ENV
echo "TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}" >> $GITHUB_ENV
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
fetch-depth: 2 fetch-depth: 2
@@ -59,11 +61,11 @@ jobs:
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
cache: 'yarn' cache: 'yarn'
- name: Install Hugo - name: Install Hugo
if: matrix.runner == 'macos-latest' if: matrix.runner == 'macos-latest'
run: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.56.0/hugo_0.56.0_macOS-64bit.tar.gz && tar -xzf hugo_0.56.0_macOS-64bit.tar.gz && mv ./hugo packages/cli/test/dev/fixtures/08-hugo/ run: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.56.0/hugo_0.56.0_macOS-64bit.tar.gz && tar -xzf hugo_0.56.0_macOS-64bit.tar.gz && mv ./hugo packages/cli/test/dev/fixtures/08-hugo/
- run: yarn install --network-timeout 1000000 - run: yarn install --network-timeout 1000000
- name: Build ${{matrix.packageName}} and all its dependencies - name: Build ${{matrix.packageName}} and all its dependencies

View File

@@ -1,18 +1 @@
* packages/*/test/**
# general
!utils/
!utils/run.js
!.yarnrc
!yarn.lock
!package.json
!turbo.json
# api
!api/
!api/**
# packages
!packages/
!packages/frameworks
!packages/frameworks/**

View File

@@ -1,5 +1,6 @@
import fs from 'fs/promises'; import fs from 'fs/promises';
import { join } from 'path'; import { join, dirname } from 'path';
import execa from 'execa';
import { getExampleList } from '../examples/example-list'; import { getExampleList } from '../examples/example-list';
import { mapOldToNew } from '../examples/map-old-to-new'; import { mapOldToNew } from '../examples/map-old-to-new';
@@ -40,7 +41,32 @@ async function main() {
JSON.stringify([...existingExamples, ...oldExamples]) JSON.stringify([...existingExamples, ...oldExamples])
); );
const { stdout: sha } = await execa('git', ['rev-parse', '--short', 'HEAD'], {
cwd: repoRoot,
});
const tarballsDir = join(pubDir, 'tarballs');
const packagesDir = join(repoRoot, 'packages');
const packages = await fs.readdir(packagesDir);
for (const pkg of packages) {
const fullDir = join(packagesDir, pkg);
const packageJsonRaw = await fs.readFile(
join(fullDir, 'package.json'),
'utf-8'
);
const packageJson = JSON.parse(packageJsonRaw);
const tarballName = `${packageJson.name
.replace('@', '')
.replace('/', '-')}-v${packageJson.version}-${sha.trim()}.tgz`;
const destTarballPath = join(tarballsDir, `${packageJson.name}.tgz`);
await fs.mkdir(dirname(destTarballPath), { recursive: true });
await fs.copyFile(join(fullDir, tarballName), destTarballPath);
}
console.log('Completed building static frontend.'); console.log('Completed building static frontend.');
} }
main().catch(console.error); main().catch(err => {
console.log('error running build:', err);
process.exit(1);
});

View File

@@ -1,6 +1,6 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "esnext", "target": "ES2020",
"skipLibCheck": true, "skipLibCheck": true,
"strict": false, "strict": false,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,

View File

@@ -0,0 +1,2 @@
# `REACT_APP` prefix is required to expose to client-side
REACT_APP_VERCEL_ANALYTICS_ID=$VERCEL_ANALYTICS_ID

View File

@@ -1,13 +1,13 @@
{ {
"private": true, "private": true,
"dependencies": { "dependencies": {
"@testing-library/jest-dom": "^5.16.1", "@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^12.1.2", "@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^14.2.0",
"react": "^17.0.2", "react": "^18.1.0",
"react-dom": "^17.0.2", "react-dom": "^18.1.0",
"react-scripts": "5.0.0", "react-scripts": "5.0.1",
"web-vitals": "^2.1.3" "web-vitals": "^2.1.4"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

View File

@@ -3,6 +3,7 @@ import ReactDOM from 'react-dom';
import './index.css'; import './index.css';
import App from './App'; import App from './App';
import reportWebVitals from './reportWebVitals'; import reportWebVitals from './reportWebVitals';
import { sendToVercelAnalytics } from './vitals';
ReactDOM.render( ReactDOM.render(
<React.StrictMode> <React.StrictMode>
@@ -11,7 +12,4 @@ ReactDOM.render(
document.getElementById('root') document.getElementById('root')
); );
// If you want to start measuring performance in your app, pass a function reportWebVitals(sendToVercelAnalytics);
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

View File

@@ -0,0 +1,41 @@
const vitalsUrl = 'https://vitals.vercel-analytics.com/v1/vitals';
function getConnectionSpeed() {
return 'connection' in navigator &&
navigator['connection'] &&
'effectiveType' in navigator['connection']
? navigator['connection']['effectiveType']
: '';
}
export function sendToVercelAnalytics(metric) {
const analyticsId = process.env.REACT_APP_VERCEL_ANALYTICS_ID;
if (!analyticsId) {
return;
}
const body = {
dsn: analyticsId,
id: metric.id,
page: window.location.pathname,
href: window.location.href,
event_name: metric.name,
value: metric.value.toString(),
speed: getConnectionSpeed(),
};
console.log({ body });
const blob = new Blob([new URLSearchParams(body).toString()], {
// This content type is necessary for `sendBeacon`
type: 'application/x-www-form-urlencoded',
});
if (navigator.sendBeacon) {
navigator.sendBeacon(vitalsUrl, blob);
} else
fetch(vitalsUrl, {
body: blob,
method: 'POST',
credentials: 'omit',
keepalive: true,
});
}

View File

@@ -1484,10 +1484,10 @@
"@svgr/plugin-svgo" "^5.5.0" "@svgr/plugin-svgo" "^5.5.0"
loader-utils "^2.0.0" loader-utils "^2.0.0"
"@testing-library/dom@^8.0.0": "@testing-library/dom@^8.5.0":
version "8.11.2" version "8.13.0"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.11.2.tgz#fc110c665a066c2287be765e4a35ba8dad737015" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.13.0.tgz#bc00bdd64c7d8b40841e27a70211399ad3af46f5"
integrity sha512-idsS/cqbYudXcVWngc1PuWNmXs416oBy2g/7Q8QAUREt5Z3MUkAL2XJD7xazLJ6esDfqRDi/ZBxk+OPPXitHRw== integrity sha512-9VHgfIatKNXQNaZTtLnalIy0jNZzY35a4S3oi08YAt9Hv1VsfZ/DfA45lM8D/UhtHBGJ4/lGwp0PZkVndRkoOQ==
dependencies: dependencies:
"@babel/code-frame" "^7.10.4" "@babel/code-frame" "^7.10.4"
"@babel/runtime" "^7.12.5" "@babel/runtime" "^7.12.5"
@@ -1498,10 +1498,10 @@
lz-string "^1.4.4" lz-string "^1.4.4"
pretty-format "^27.0.2" pretty-format "^27.0.2"
"@testing-library/jest-dom@^5.16.1": "@testing-library/jest-dom@^5.16.4":
version "5.16.1" version "5.16.4"
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.1.tgz#3db7df5ae97596264a7da9696fe14695ba02e51f" resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.4.tgz#938302d7b8b483963a3ae821f1c0808f872245cd"
integrity sha512-ajUJdfDIuTCadB79ukO+0l8O+QwN0LiSxDaYUTI4LndbbUsGi6rWU1SCexXzBA2NSjlVB9/vbkasQIL3tmPBjw== integrity sha512-Gy+IoFutbMQcky0k+bqqumXZ1cTGswLsFqmNLzNdSKkU9KGV2u9oXhukCbbJ9/LRPKiqwxEE8VpV/+YZlfkPUA==
dependencies: dependencies:
"@babel/runtime" "^7.9.2" "@babel/runtime" "^7.9.2"
"@types/testing-library__jest-dom" "^5.9.1" "@types/testing-library__jest-dom" "^5.9.1"
@@ -1513,20 +1513,19 @@
lodash "^4.17.15" lodash "^4.17.15"
redent "^3.0.0" redent "^3.0.0"
"@testing-library/react@^12.1.2": "@testing-library/react@^13.3.0":
version "12.1.2" version "13.3.0"
resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-12.1.2.tgz#f1bc9a45943461fa2a598bb4597df1ae044cfc76" resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-13.3.0.tgz#bf298bfbc5589326bbcc8052b211f3bb097a97c5"
integrity sha512-ihQiEOklNyHIpo2Y8FREkyD1QAea054U0MVbwH1m8N9TxeFz+KoJ9LkqoKqJlzx2JDm56DVwaJ1r36JYxZM05g== integrity sha512-DB79aA426+deFgGSjnf5grczDPiL4taK3hFaa+M5q7q20Kcve9eQottOG5kZ74KEr55v0tU2CQormSSDK87zYQ==
dependencies: dependencies:
"@babel/runtime" "^7.12.5" "@babel/runtime" "^7.12.5"
"@testing-library/dom" "^8.0.0" "@testing-library/dom" "^8.5.0"
"@types/react-dom" "^18.0.0"
"@testing-library/user-event@^13.5.0": "@testing-library/user-event@^14.2.0":
version "13.5.0" version "14.2.0"
resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-13.5.0.tgz#69d77007f1e124d55314a2b73fd204b333b13295" resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.2.0.tgz#8293560f8f80a00383d6c755ec3e0b918acb1683"
integrity sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg== integrity sha512-+hIlG4nJS6ivZrKnOP7OGsDu9Fxmryj9vCl8x0ZINtTJcCHs2zLsYif5GzuRiBF2ck5GZG2aQr7Msg+EHlnYVQ==
dependencies:
"@babel/runtime" "^7.12.5"
"@tootallnate/once@1": "@tootallnate/once@1":
version "1.1.2" version "1.1.2"
@@ -1735,6 +1734,11 @@
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.4.3.tgz#a3c65525b91fca7da00ab1a3ac2b5a2a4afbffbf" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.4.3.tgz#a3c65525b91fca7da00ab1a3ac2b5a2a4afbffbf"
integrity sha512-QzSuZMBuG5u8HqYz01qtMdg/Jfctlnvj1z/lYnIDXs/golxw0fxtRAHd9KrzjR7Yxz1qVeI00o0kiO3PmVdJ9w== integrity sha512-QzSuZMBuG5u8HqYz01qtMdg/Jfctlnvj1z/lYnIDXs/golxw0fxtRAHd9KrzjR7Yxz1qVeI00o0kiO3PmVdJ9w==
"@types/prop-types@*":
version "15.7.5"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
"@types/q@^1.5.1": "@types/q@^1.5.1":
version "1.5.5" version "1.5.5"
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.5.tgz#75a2a8e7d8ab4b230414505d92335d1dcb53a6df" resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.5.tgz#75a2a8e7d8ab4b230414505d92335d1dcb53a6df"
@@ -1750,6 +1754,22 @@
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
"@types/react-dom@^18.0.0":
version "18.0.5"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.5.tgz#330b2d472c22f796e5531446939eacef8378444a"
integrity sha512-OWPWTUrY/NIrjsAPkAk1wW9LZeIjSvkXRhclsFO8CZcZGCOg2G0YZy4ft+rOyYxy8B7ui5iZzi9OkDebZ7/QSA==
dependencies:
"@types/react" "*"
"@types/react@*":
version "18.0.9"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.9.tgz#d6712a38bd6cd83469603e7359511126f122e878"
integrity sha512-9bjbg1hJHUm4De19L1cHiW0Jvx3geel6Qczhjd0qY5VKVE2X5+x77YxAepuCwVh4vrgZJdgEJw48zrhRIeF4Nw==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/resolve@1.17.1": "@types/resolve@1.17.1":
version "1.17.1" version "1.17.1"
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6" resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6"
@@ -1762,6 +1782,11 @@
resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.1.tgz#d8f1c0d0dc23afad6dc16a9e993a0865774b4065" resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.1.tgz#d8f1c0d0dc23afad6dc16a9e993a0865774b4065"
integrity sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g== integrity sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==
"@types/scheduler@*":
version "0.16.2"
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39"
integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==
"@types/serve-index@^1.9.1": "@types/serve-index@^1.9.1":
version "1.9.1" version "1.9.1"
resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.1.tgz#1b5e85370a192c01ec6cec4735cf2917337a6278" resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.1.tgz#1b5e85370a192c01ec6cec4735cf2917337a6278"
@@ -3175,6 +3200,11 @@ cssstyle@^2.3.0:
dependencies: dependencies:
cssom "~0.3.6" cssom "~0.3.6"
csstype@^3.0.2:
version "3.1.0"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.0.tgz#4ddcac3718d787cf9df0d1b7d15033925c8f29f2"
integrity sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==
damerau-levenshtein@^1.0.7: damerau-levenshtein@^1.0.7:
version "1.0.8" version "1.0.8"
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7"
@@ -3622,10 +3652,10 @@ escodegen@^2.0.0:
optionalDependencies: optionalDependencies:
source-map "~0.6.1" source-map "~0.6.1"
eslint-config-react-app@^7.0.0: eslint-config-react-app@^7.0.1:
version "7.0.0" version "7.0.1"
resolved "https://registry.yarnpkg.com/eslint-config-react-app/-/eslint-config-react-app-7.0.0.tgz#0fa96d5ec1dfb99c029b1554362ab3fa1c3757df" resolved "https://registry.yarnpkg.com/eslint-config-react-app/-/eslint-config-react-app-7.0.1.tgz#73ba3929978001c5c86274c017ea57eb5fa644b4"
integrity sha512-xyymoxtIt1EOsSaGag+/jmcywRuieQoA2JbPCjnw9HukFj9/97aGPoZVFioaotzk1K5Qt9sHO5EutZbkrAXS0g== integrity sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==
dependencies: dependencies:
"@babel/core" "^7.16.0" "@babel/core" "^7.16.0"
"@babel/eslint-parser" "^7.16.3" "@babel/eslint-parser" "^7.16.3"
@@ -6841,10 +6871,10 @@ react-app-polyfill@^3.0.0:
regenerator-runtime "^0.13.9" regenerator-runtime "^0.13.9"
whatwg-fetch "^3.6.2" whatwg-fetch "^3.6.2"
react-dev-utils@^12.0.0: react-dev-utils@^12.0.1:
version "12.0.0" version "12.0.1"
resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-12.0.0.tgz#4eab12cdb95692a077616770b5988f0adf806526" resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-12.0.1.tgz#ba92edb4a1f379bd46ccd6bcd4e7bc398df33e73"
integrity sha512-xBQkitdxozPxt1YZ9O1097EJiVpwHr9FoAuEVURCKV0Av8NBERovJauzP7bo1ThvuhZ4shsQ1AJiu4vQpoT1AQ== integrity sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==
dependencies: dependencies:
"@babel/code-frame" "^7.16.0" "@babel/code-frame" "^7.16.0"
address "^1.1.2" address "^1.1.2"
@@ -6865,25 +6895,24 @@ react-dev-utils@^12.0.0:
open "^8.4.0" open "^8.4.0"
pkg-up "^3.1.0" pkg-up "^3.1.0"
prompts "^2.4.2" prompts "^2.4.2"
react-error-overlay "^6.0.10" react-error-overlay "^6.0.11"
recursive-readdir "^2.2.2" recursive-readdir "^2.2.2"
shell-quote "^1.7.3" shell-quote "^1.7.3"
strip-ansi "^6.0.1" strip-ansi "^6.0.1"
text-table "^0.2.0" text-table "^0.2.0"
react-dom@^17.0.2: react-dom@^18.1.0:
version "17.0.2" version "18.1.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.1.0.tgz#7f6dd84b706408adde05e1df575b3a024d7e8a2f"
integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== integrity sha512-fU1Txz7Budmvamp7bshe4Zi32d0ll7ect+ccxNu9FlObT605GOEB8BfO4tmRJ39R5Zj831VCpvQ05QPBW5yb+w==
dependencies: dependencies:
loose-envify "^1.1.0" loose-envify "^1.1.0"
object-assign "^4.1.1" scheduler "^0.22.0"
scheduler "^0.20.2"
react-error-overlay@^6.0.10: react-error-overlay@^6.0.11:
version "6.0.10" version "6.0.11"
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.10.tgz#0fe26db4fa85d9dbb8624729580e90e7159a59a6" resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb"
integrity sha512-mKR90fX7Pm5seCOfz8q9F+66VCc1PGsWSBxKbITjfKVQHMNF2zudxHnMdJiB1fRCb+XsbQV9sO9DCkgsMQgBIA== integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==
react-is@^16.13.1: react-is@^16.13.1:
version "16.13.1" version "16.13.1"
@@ -6900,10 +6929,10 @@ react-refresh@^0.11.0:
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046"
integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A== integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==
react-scripts@5.0.0: react-scripts@5.0.1:
version "5.0.0" version "5.0.1"
resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-5.0.0.tgz#6547a6d7f8b64364ef95273767466cc577cb4b60" resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-5.0.1.tgz#6285dbd65a8ba6e49ca8d651ce30645a6d980003"
integrity sha512-3i0L2CyIlROz7mxETEdfif6Sfhh9Lfpzi10CtcGs1emDQStmZfWjJbAIMtRD0opVUjQuFWqHZyRZ9PPzKCFxWg== integrity sha512-8VAmEm/ZAwQzJ+GOMLbBsTdDKOpuZh7RPs0UymvBR2vRk4iZWCskjbFnxqjrzoIvlNNRZ3QJFx6/qDSi6zSnaQ==
dependencies: dependencies:
"@babel/core" "^7.16.0" "@babel/core" "^7.16.0"
"@pmmmwh/react-refresh-webpack-plugin" "^0.5.3" "@pmmmwh/react-refresh-webpack-plugin" "^0.5.3"
@@ -6921,7 +6950,7 @@ react-scripts@5.0.0:
dotenv "^10.0.0" dotenv "^10.0.0"
dotenv-expand "^5.1.0" dotenv-expand "^5.1.0"
eslint "^8.3.0" eslint "^8.3.0"
eslint-config-react-app "^7.0.0" eslint-config-react-app "^7.0.1"
eslint-webpack-plugin "^3.1.1" eslint-webpack-plugin "^3.1.1"
file-loader "^6.2.0" file-loader "^6.2.0"
fs-extra "^10.0.0" fs-extra "^10.0.0"
@@ -6938,7 +6967,7 @@ react-scripts@5.0.0:
postcss-preset-env "^7.0.1" postcss-preset-env "^7.0.1"
prompts "^2.4.2" prompts "^2.4.2"
react-app-polyfill "^3.0.0" react-app-polyfill "^3.0.0"
react-dev-utils "^12.0.0" react-dev-utils "^12.0.1"
react-refresh "^0.11.0" react-refresh "^0.11.0"
resolve "^1.20.0" resolve "^1.20.0"
resolve-url-loader "^4.0.0" resolve-url-loader "^4.0.0"
@@ -6955,13 +6984,12 @@ react-scripts@5.0.0:
optionalDependencies: optionalDependencies:
fsevents "^2.3.2" fsevents "^2.3.2"
react@^17.0.2: react@^18.1.0:
version "17.0.2" version "18.1.0"
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" resolved "https://registry.yarnpkg.com/react/-/react-18.1.0.tgz#6f8620382decb17fdc5cc223a115e2adbf104890"
integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== integrity sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==
dependencies: dependencies:
loose-envify "^1.1.0" loose-envify "^1.1.0"
object-assign "^4.1.1"
readable-stream@^2.0.1: readable-stream@^2.0.1:
version "2.3.7" version "2.3.7"
@@ -7235,13 +7263,12 @@ saxes@^5.0.1:
dependencies: dependencies:
xmlchars "^2.2.0" xmlchars "^2.2.0"
scheduler@^0.20.2: scheduler@^0.22.0:
version "0.20.2" version "0.22.0"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.22.0.tgz#83a5d63594edf074add9a7198b1bae76c3db01b8"
integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ== integrity sha512-6QAm1BgQI88NPYymgGQLCZgvep4FyePDWFpXVK+zNSUgHwlqpJy8VEh8Et0KxTACS4VWwMousBElAZOH9nkkoQ==
dependencies: dependencies:
loose-envify "^1.1.0" loose-envify "^1.1.0"
object-assign "^4.1.1"
schema-utils@2.7.0: schema-utils@2.7.0:
version "2.7.0" version "2.7.0"
@@ -8156,10 +8183,10 @@ wbuf@^1.1.0, wbuf@^1.7.3:
dependencies: dependencies:
minimalistic-assert "^1.0.0" minimalistic-assert "^1.0.0"
web-vitals@^2.1.3: web-vitals@^2.1.4:
version "2.1.3" version "2.1.4"
resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-2.1.3.tgz#6dca59f41dbc3fcccdb889da06191b437b18f534" resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-2.1.4.tgz#76563175a475a5e835264d373704f9dde718290c"
integrity sha512-+ijpniAzcnQicXaXIN0/eHQAiV/jMt1oHGHTmz7VdAJPPkzzDhmoYPSpLgJTuFtUh+jCjxCoeTZPg7Ic+g8o7w== integrity sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg==
webidl-conversions@^4.0.2: webidl-conversions@^4.0.2:
version "4.0.2" version "4.0.2"

View File

@@ -22,8 +22,5 @@
"@types/react-dom": "^17.0.9", "@types/react-dom": "^17.0.9",
"typescript": "^4.1.2" "typescript": "^4.1.2"
}, },
"engines": {
"node": "14.x"
},
"sideEffects": false "sideEffects": false
} }

View File

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

View File

@@ -7,3 +7,4 @@ node_modules
.env.* .env.*
!.env.example !.env.example
.vercel .vercel
.output

View File

@@ -0,0 +1,13 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

View File

@@ -0,0 +1,6 @@
{
"useTabs": false,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100
}

View File

@@ -14,31 +14,29 @@ If you're seeing this, you've probably already done this step. Congrats!
```bash ```bash
# create a new project in the current directory # create a new project in the current directory
npm init svelte@next npm init svelte
# create a new project in my-app # create a new project in my-app
npm init svelte@next my-app npm init svelte my-app
``` ```
> Note: the `@next` is temporary
## Developing ## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: Once you've created a project and installed dependencies with `pnpm install`, start a development server:
```bash ```bash
npm run dev pnpm run dev
# or start the server and open the app in a new browser tab # or start the server and open the app in a new browser tab
npm run dev -- --open pnpm run dev -- --open
``` ```
## Building ## Building
This uses the adapter-auto for SvelteKit, which detects Vercel and runs adapter-vercel on your behalf. To create a production version of your app:
```bash ```bash
npm run build pnpm run build
``` ```
> You can preview the built app with `npm run preview`, regardless of whether you installed an adapter. This should _not_ be used to serve your app in production. You can preview the production build with `npm run preview`.

View File

@@ -1,10 +1,13 @@
{ {
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"baseUrl": ".", "allowJs": true,
"paths": { "checkJs": true,
"$lib": ["src/lib"], "esModuleInterop": true,
"$lib/*": ["src/lib/*"] "forceConsistentCasingInFileNames": true,
} "resolveJsonModule": true,
}, "skipLibCheck": true,
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"] "sourceMap": true,
"strict": true
}
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,22 +1,31 @@
{ {
"private": true, "private": true,
"name": "sveltekit",
"version": "0.0.1",
"scripts": { "scripts": {
"dev": "svelte-kit dev", "dev": "svelte-kit dev",
"build": "svelte-kit build", "build": "svelte-kit build",
"package": "svelte-kit package", "package": "svelte-kit package",
"preview": "svelte-kit preview" "preview": "svelte-kit preview",
"prepare": "svelte-kit sync",
"check": "svelte-check --tsconfig ./jsconfig.json",
"check:watch": "svelte-check --tsconfig ./jsconfig.json --watch",
"lint": "prettier --check --plugin-search-dir=. .",
"format": "prettier --write --plugin-search-dir=. ."
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/adapter-auto": "next", "@sveltejs/adapter-auto": "1.0.0-next.50",
"@sveltejs/kit": "next", "@sveltejs/kit": "1.0.0-next.347",
"svelte": "^3.46.0" "@types/cookie": "^0.4.1",
"prettier": "^2.5.1",
"prettier-plugin-svelte": "^2.5.0",
"svelte": "^3.46.0",
"svelte-check": "^2.2.6",
"svelte-preprocess": "^4.10.6",
"typescript": "~4.6.2"
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@fontsource/fira-mono": "^4.5.0", "@fontsource/fira-mono": "^4.5.0",
"@lukeed/uuid": "^2.0.0", "cookie": "^0.4.1",
"cookie": "^0.4.1" "web-vitals": "^2.1.4"
} }
} }

View File

@@ -1,107 +1,107 @@
@import '@fontsource/fira-mono'; @import '@fontsource/fira-mono';
:root { :root {
font-family: Arial, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, font-family: Arial, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
--font-mono: 'Fira Mono', monospace; --font-mono: 'Fira Mono', monospace;
--pure-white: #ffffff; --pure-white: #ffffff;
--primary-color: #b9c6d2; --primary-color: #b9c6d2;
--secondary-color: #d0dde9; --secondary-color: #d0dde9;
--tertiary-color: #edf0f8; --tertiary-color: #edf0f8;
--accent-color: #ff3e00; --accent-color: #ff3e00;
--heading-color: rgba(0, 0, 0, 0.7); --heading-color: rgba(0, 0, 0, 0.7);
--text-color: #444444; --text-color: #444444;
--background-without-opacity: rgba(255, 255, 255, 0.7); --background-without-opacity: rgba(255, 255, 255, 0.7);
--column-width: 42rem; --column-width: 42rem;
--column-margin-top: 4rem; --column-margin-top: 4rem;
} }
body { body {
min-height: 100vh; min-height: 100vh;
margin: 0; margin: 0;
background-color: var(--primary-color); background-color: var(--primary-color);
background: linear-gradient( background: linear-gradient(
180deg, 180deg,
var(--primary-color) 0%, var(--primary-color) 0%,
var(--secondary-color) 10.45%, var(--secondary-color) 10.45%,
var(--tertiary-color) 41.35% var(--tertiary-color) 41.35%
); );
} }
body::before { body::before {
content: ''; content: '';
width: 80vw; width: 80vw;
height: 100vh; height: 100vh;
position: absolute; position: absolute;
top: 0; top: 0;
left: 10vw; left: 10vw;
z-index: -1; z-index: -1;
background: radial-gradient( background: radial-gradient(
50% 50% at 50% 50%, 50% 50% at 50% 50%,
var(--pure-white) 0%, var(--pure-white) 0%,
rgba(255, 255, 255, 0) 100% rgba(255, 255, 255, 0) 100%
); );
opacity: 0.05; opacity: 0.05;
} }
#svelte { #svelte {
min-height: 100vh; min-height: 100vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
h1, h1,
h2, h2,
p { p {
font-weight: 400; font-weight: 400;
color: var(--heading-color); color: var(--heading-color);
} }
p { p {
line-height: 1.5; line-height: 1.5;
} }
a { a {
color: var(--accent-color); color: var(--accent-color);
text-decoration: none; text-decoration: none;
} }
a:hover { a:hover {
text-decoration: underline; text-decoration: underline;
} }
h1 { h1 {
font-size: 2rem; font-size: 2rem;
text-align: center; text-align: center;
} }
h2 { h2 {
font-size: 1rem; font-size: 1rem;
} }
pre { pre {
font-size: 16px; font-size: 16px;
font-family: var(--font-mono); font-family: var(--font-mono);
background-color: rgba(255, 255, 255, 0.45); background-color: rgba(255, 255, 255, 0.45);
border-radius: 3px; border-radius: 3px;
box-shadow: 2px 2px 6px rgb(255 255 255 / 25%); box-shadow: 2px 2px 6px rgb(255 255 255 / 25%);
padding: 0.5em; padding: 0.5em;
overflow-x: auto; overflow-x: auto;
color: var(--text-color); color: var(--text-color);
} }
input, input,
button { button {
font-size: inherit; font-size: inherit;
font-family: inherit; font-family: inherit;
} }
button:focus:not(:focus-visible) { button:focus:not(:focus-visible) {
outline: none; outline: none;
} }
@media (min-width: 720px) { @media (min-width: 720px) {
h1 { h1 {
font-size: 2.4rem; font-size: 2.4rem;
} }
} }

15
examples/sveltekit/src/app.d.ts vendored Normal file
View File

@@ -0,0 +1,15 @@
/// <reference types="@sveltejs/kit" />
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare namespace App {
interface Locals {
userid: string;
}
// interface Platform {}
// interface Session {}
// interface Stuff {}
}

View File

@@ -1,13 +1,12 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="description" content="Svelte demo app" /> <link rel="icon" href="%sveltekit.assets%/favicon.png" />
<link rel="icon" href="%svelte.assets%/favicon.png" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> %sveltekit.head%
%svelte.head% </head>
</head> <body>
<body> <div>%sveltekit.body%</div>
<div>%svelte.body%</div> </body>
</body>
</html> </html>

View File

@@ -1,23 +1,23 @@
import cookie from 'cookie'; import * as cookie from 'cookie';
import { v4 as uuid } from '@lukeed/uuid';
/** @type {import('@sveltejs/kit').Handle} */
export const handle = async ({ event, resolve }) => { export const handle = async ({ event, resolve }) => {
const cookies = cookie.parse(event.request.headers.get('cookie') || ''); const cookies = cookie.parse(event.request.headers.get('cookie') || '');
event.locals.userid = cookies.userid || uuid(); event.locals.userid = cookies['userid'] || crypto.randomUUID();
const response = await resolve(event); const response = await resolve(event);
if (!cookies.userid) { if (!cookies['userid']) {
// if this is the first time the user has visited this app, // if this is the first time the user has visited this app,
// set a cookie so that we recognise them when they return // set a cookie so that we recognise them when they return
response.headers.set( response.headers.set(
'set-cookie', 'set-cookie',
cookie.serialize('userid', event.locals.userid, { cookie.serialize('userid', event.locals.userid, {
path: '/', path: '/',
httpOnly: true httpOnly: true
}) })
); );
} }
return response; return response;
}; };

View File

@@ -1,102 +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);
function modulo(n, m) { /**
// handle negative numbers * @param {number} n
return ((n % m) + m) % m; * @param {number} m
} */
function modulo(n, m) {
// handle negative numbers
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;
color: var(--text-color); touch-action: manipulation;
font-size: 2rem; color: var(--text-color);
} 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

@@ -2,54 +2,81 @@ import { invalidate } from '$app/navigation';
// this action (https://svelte.dev/tutorial/actions) allows us to // this action (https://svelte.dev/tutorial/actions) allows us to
// progressively enhance a <form> that already works without JS // progressively enhance a <form> that already works without JS
/**
* @param {HTMLFormElement} form
* @param {{
* pending?: ({ data, form }: { data: FormData; form: HTMLFormElement }) => void;
* error?: ({
* data,
* form,
* response,
* error
* }: {
* data: FormData;
* form: HTMLFormElement;
* response: Response | null;
* error: Error | null;
* }) => void;
* result?: ({
* data,
* form,
* response
* }: {
* data: FormData;
* response: Response;
* form: HTMLFormElement;
* }) => void;
* }} [opts]
*/
export function enhance(form, { pending, error, result } = {}) { export function enhance(form, { pending, error, result } = {}) {
let current_token; let current_token;
async function handle_submit(e) { /** @param {SubmitEvent} e */
const token = (current_token = {}); async function handle_submit(e) {
const token = (current_token = {});
e.preventDefault(); e.preventDefault();
const data = new FormData(form); const data = new FormData(form);
if (pending) pending({ data, form }); if (pending) pending({ data, form });
try { try {
const response = await fetch(form.action, { const response = await fetch(form.action, {
method: form.method, method: form.method,
headers: { headers: {
accept: 'application/json' accept: 'application/json'
}, },
body: data body: data
}); });
if (token !== current_token) return; if (token !== current_token) return;
if (response.ok) { if (response.ok) {
if (result) result({ data, form, response }); if (result) result({ data, form, response });
const url = new URL(form.action); const url = new URL(form.action);
url.search = url.hash = ''; url.search = url.hash = '';
invalidate(url.href); invalidate(url.href);
} else if (error) { } else if (error) {
error({ data, form, error: null, response }); error({ data, form, error: null, response });
} else { } else {
console.error(await response.text()); console.error(await response.text());
} }
} catch (e) { } catch (e) {
if (error) { if (error && e instanceof Error) {
error({ data, form, error: e, response: null }); error({ data, form, error: e, response: null });
} else { } else {
throw e; throw e;
} }
} }
} }
form.addEventListener('submit', handle_submit); form.addEventListener('submit', handle_submit);
return { return {
destroy() { destroy() {
form.removeEventListener('submit', handle_submit); form.removeEventListener('submit', handle_submit);
} }
}; };
} }

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,66 @@
import { getCLS, getFCP, getFID, getLCP, getTTFB } from 'web-vitals';
const vitalsUrl = 'https://vitals.vercel-analytics.com/v1/vitals';
function getConnectionSpeed() {
return 'connection' in navigator &&
navigator['connection'] &&
'effectiveType' in navigator['connection']
? // @ts-ignore
navigator['connection']['effectiveType']
: '';
}
/**
* @param {import("web-vitals").Metric} metric
* @param {{ params: { [s: string]: any; } | ArrayLike<any>; path: string; analyticsId: string; debug: boolean; }} options
*/
function sendToAnalytics(metric, options) {
const page = Object.entries(options.params).reduce(
(acc, [key, value]) => acc.replace(value, `[${key}]`),
options.path
);
const body = {
dsn: options.analyticsId,
id: metric.id,
page,
href: location.href,
event_name: metric.name,
value: metric.value.toString(),
speed: getConnectionSpeed()
};
if (options.debug) {
console.log('[Analytics]', metric.name, JSON.stringify(body, null, 2));
}
const blob = new Blob([new URLSearchParams(body).toString()], {
// This content type is necessary for `sendBeacon`
type: 'application/x-www-form-urlencoded'
});
if (navigator.sendBeacon) {
navigator.sendBeacon(vitalsUrl, blob);
} else
fetch(vitalsUrl, {
body: blob,
method: 'POST',
credentials: 'omit',
keepalive: true
});
}
/**
* @param {any} options
*/
export function webVitals(options) {
try {
getFID((metric) => sendToAnalytics(metric, options));
getTTFB((metric) => sendToAnalytics(metric, options));
getLCP((metric) => sendToAnalytics(metric, options));
getCLS((metric) => sendToAnalytics(metric, options));
getFCP((metric) => sendToAnalytics(metric, options));
} catch (err) {
console.error('[Analytics]', err);
}
}

View File

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

View File

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

View File

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

View File

@@ -11,12 +11,17 @@
const base = 'https://api.svelte.dev'; const base = 'https://api.svelte.dev';
/**
* @param {string} method
* @param {string} resource
* @param {Record<string, unknown>} [data]
*/
export function api(method, resource, data) { export function api(method, resource, data) {
return fetch(`${base}/${resource}`, { return fetch(`${base}/${resource}`, {
method, method,
headers: { headers: {
'content-type': 'application/json' 'content-type': 'application/json'
}, },
body: data && JSON.stringify(data) body: data && JSON.stringify(data)
}); });
} }

View File

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

View File

@@ -1,178 +1,190 @@
<script> <script>
import { enhance } from '$lib/form'; import { enhance } from '$lib/form';
import { scale } from 'svelte/transition'; import { scale } from 'svelte/transition';
import { flip } from 'svelte/animate'; import { flip } from 'svelte/animate';
export let todos; /**
* @typedef {{
* uid: string;
* created_at: Date;
* text: string;
* done: boolean;
* pending_delete: boolean;
* }} Todo
*/
/** @type {Todo[]} */
export let todos;
</script> </script>
<svelte:head> <svelte:head>
<title>Todos</title> <title>Todos</title>
<meta name="description" content="A todo list app" />
</svelte:head> </svelte:head>
<div class="todos"> <div class="todos">
<h1>Todos</h1> <h1>Todos</h1>
<form <form
class="new" class="new"
action="/todos" action="/todos"
method="post" method="post"
use:enhance={{ use:enhance={{
result: async ({ form }) => { result: async ({ form }) => {
form.reset(); form.reset();
} }
}} }}
> >
<input name="text" aria-label="Add todo" placeholder="+ tap to add a todo" /> <input name="text" aria-label="Add todo" placeholder="+ tap to add a todo" />
</form> </form>
{#each todos as todo (todo.uid)} {#each todos as todo (todo.uid)}
<div <div
class="todo" class="todo"
class:done={todo.done} class:done={todo.done}
transition:scale|local={{ start: 0.7 }} transition:scale|local={{ start: 0.7 }}
animate:flip={{ duration: 200 }} animate:flip={{ duration: 200 }}
> >
<form <form
action="/todos?_method=PATCH" action="/todos?_method=PATCH"
method="post" method="post"
use:enhance={{ use:enhance={{
pending: ({ data }) => { pending: ({ data }) => {
todo.done = !!data.get('done'); todo.done = !!data.get('done');
} }
}} }}
> >
<input type="hidden" name="uid" value={todo.uid} /> <input type="hidden" name="uid" value={todo.uid} />
<input type="hidden" name="done" value={todo.done ? '' : 'true'} /> <input type="hidden" name="done" value={todo.done ? '' : 'true'} />
<button class="toggle" aria-label="Mark todo as {todo.done ? 'not done' : 'done'}" /> <button class="toggle" aria-label="Mark todo as {todo.done ? 'not done' : 'done'}" />
</form> </form>
<form class="text" action="/todos?_method=PATCH" method="post" use:enhance> <form class="text" action="/todos?_method=PATCH" method="post" use:enhance>
<input type="hidden" name="uid" value={todo.uid} /> <input type="hidden" name="uid" value={todo.uid} />
<input aria-label="Edit todo" type="text" name="text" value={todo.text} /> <input aria-label="Edit todo" type="text" name="text" value={todo.text} />
<button class="save" aria-label="Save todo" /> <button class="save" aria-label="Save todo" />
</form> </form>
<form <form
action="/todos?_method=DELETE" action="/todos?_method=DELETE"
method="post" method="post"
use:enhance={{ use:enhance={{
pending: () => (todo.pending_delete = true) pending: () => (todo.pending_delete = true)
}} }}
> >
<input type="hidden" name="uid" value={todo.uid} /> <input type="hidden" name="uid" value={todo.uid} />
<button class="delete" aria-label="Delete todo" disabled={todo.pending_delete} /> <button class="delete" aria-label="Delete todo" disabled={todo.pending_delete} />
</form> </form>
</div> </div>
{/each} {/each}
</div> </div>
<style> <style>
.todos { .todos {
width: 100%; width: 100%;
max-width: var(--column-width); max-width: var(--column-width);
margin: var(--column-margin-top) auto 0 auto; margin: var(--column-margin-top) auto 0 auto;
line-height: 1; line-height: 1;
} }
.new { .new {
margin: 0 0 0.5rem 0; margin: 0 0 0.5rem 0;
} }
input { input {
border: 1px solid transparent; border: 1px solid transparent;
} }
input:focus-visible { input:focus-visible {
box-shadow: inset 1px 1px 6px rgba(0, 0, 0, 0.1); box-shadow: inset 1px 1px 6px rgba(0, 0, 0, 0.1);
border: 1px solid #ff3e00 !important; border: 1px solid #ff3e00 !important;
outline: none; outline: none;
} }
.new input { .new input {
font-size: 28px; font-size: 28px;
width: 100%; width: 100%;
padding: 0.5em 1em 0.3em 1em; padding: 0.5em 1em 0.3em 1em;
box-sizing: border-box; box-sizing: border-box;
background: rgba(255, 255, 255, 0.05); background: rgba(255, 255, 255, 0.05);
border-radius: 8px; border-radius: 8px;
text-align: center; text-align: center;
} }
.todo { .todo {
display: grid; display: grid;
grid-template-columns: 2rem 1fr 2rem; grid-template-columns: 2rem 1fr 2rem;
grid-gap: 0.5rem; grid-gap: 0.5rem;
align-items: center; align-items: center;
margin: 0 0 0.5rem 0; margin: 0 0 0.5rem 0;
padding: 0.5rem; padding: 0.5rem;
background-color: white; background-color: white;
border-radius: 8px; border-radius: 8px;
filter: drop-shadow(2px 4px 6px rgba(0, 0, 0, 0.1)); filter: drop-shadow(2px 4px 6px rgba(0, 0, 0, 0.1));
transform: translate(-1px, -1px); transform: translate(-1px, -1px);
transition: filter 0.2s, transform 0.2s; transition: filter 0.2s, transform 0.2s;
} }
.done { .done {
transform: none; transform: none;
opacity: 0.4; opacity: 0.4;
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.1)); filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.1));
} }
form.text { form.text {
position: relative; position: relative;
display: flex; display: flex;
align-items: center; align-items: center;
flex: 1; flex: 1;
} }
.todo input { .todo input {
flex: 1; flex: 1;
padding: 0.5em 2em 0.5em 0.8em; padding: 0.5em 2em 0.5em 0.8em;
border-radius: 3px; border-radius: 3px;
} }
.todo button { .todo button {
width: 2em; width: 2em;
height: 2em; height: 2em;
border: none; border: none;
background-color: transparent; background-color: transparent;
background-position: 50% 50%; background-position: 50% 50%;
background-repeat: no-repeat; background-repeat: no-repeat;
} }
button.toggle { button.toggle {
border: 1px solid rgba(0, 0, 0, 0.2); border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 50%; border-radius: 50%;
box-sizing: border-box; box-sizing: border-box;
background-size: 1em auto; background-size: 1em auto;
} }
.done .toggle { .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"); 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 { .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"); 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; opacity: 0.2;
} }
.delete:hover, .delete:hover,
.delete:focus { .delete:focus {
transition: opacity 0.2s; transition: opacity 0.2s;
opacity: 1; opacity: 1;
} }
.save { .save {
position: absolute; position: absolute;
right: 0; right: 0;
opacity: 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"); 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, .todo input:focus + .save,
.save:focus { .save:focus {
transition: opacity 0.2s; transition: opacity 0.2s;
opacity: 1; opacity: 1;
} }
</style> </style>

View File

@@ -1,15 +1,21 @@
import adapter from '@sveltejs/adapter-auto'; import adapter from '@sveltejs/adapter-auto';
import preprocess from 'svelte-preprocess';
/** @type {import('@sveltejs/kit').Config} */ /** @type {import('@sveltejs/kit').Config} */
const config = { const config = {
kit: { preprocess: preprocess({
adapter: adapter(), replace: [
['import.meta.env.VERCEL_ANALYTICS_ID', JSON.stringify(process.env.VERCEL_ANALYTICS_ID)]
]
}),
kit: {
adapter: adapter(),
// Override http methods in the Todo forms // Override http methods in the Todo forms
methodOverride: { methodOverride: {
allowed: ['PATCH', 'DELETE'] allowed: ['PATCH', 'DELETE']
} }
} }
}; };
export default config; export default config;

1479
examples/sveltekit/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -30,8 +30,8 @@
"npm-package-arg": "6.1.0", "npm-package-arg": "6.1.0",
"prettier": "2.6.2", "prettier": "2.6.2",
"ts-eager": "2.0.2", "ts-eager": "2.0.2",
"ts-jest": "28.0.0-next.1", "ts-jest": "28.0.5",
"turbo": "1.2.9" "turbo": "1.2.14"
}, },
"scripts": { "scripts": {
"lerna": "lerna", "lerna": "lerna",
@@ -41,7 +41,7 @@
"publish-from-github": "./utils/publish.sh", "publish-from-github": "./utils/publish.sh",
"changelog": "node utils/changelog.js", "changelog": "node utils/changelog.js",
"build": "turbo run build", "build": "turbo run build",
"vercel-build": "yarn build && cd api && node -r ts-eager/register ./_lib/script/build.ts", "vercel-build": "yarn build && yarn run pack && cd api && node -r ts-eager/register ./_lib/script/build.ts",
"pre-commit": "lint-staged", "pre-commit": "lint-staged",
"test": "jest --rootDir=\"test\" --testPathPattern=\"\\.test.js\"", "test": "jest --rootDir=\"test\" --testPathPattern=\"\\.test.js\"",
"test-unit": "yarn test && turbo run test-unit", "test-unit": "yarn test && turbo run test-unit",
@@ -49,7 +49,8 @@
"test-integration-once": "turbo run test-integration-once", "test-integration-once": "turbo run test-integration-once",
"test-integration-dev": "turbo run test-integration-dev", "test-integration-dev": "turbo run test-integration-dev",
"lint": "eslint . --ext .ts,.js", "lint": "eslint . --ext .ts,.js",
"prepare": "husky install" "prepare": "husky install",
"pack": "cd utils && node -r ts-eager/register ./pack.ts"
}, },
"lint-staged": { "lint-staged": {
"./{*,{api,packages,test,utils}/**/*}.{js,ts}": [ "./{*,{api,packages,test,utils}/**/*}.{js,ts}": [

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/build-utils", "name": "@vercel/build-utils",
"version": "3.1.1-canary.2", "version": "4.1.1-canary.1",
"license": "MIT", "license": "MIT",
"main": "./dist/index.js", "main": "./dist/index.js",
"types": "./dist/index.d.js", "types": "./dist/index.d.js",
@@ -23,7 +23,7 @@
"@types/cross-spawn": "6.0.0", "@types/cross-spawn": "6.0.0",
"@types/end-of-stream": "^1.4.0", "@types/end-of-stream": "^1.4.0",
"@types/fs-extra": "9.0.13", "@types/fs-extra": "9.0.13",
"@types/glob": "^7.1.1", "@types/glob": "7.2.0",
"@types/jest": "27.4.1", "@types/jest": "27.4.1",
"@types/js-yaml": "3.12.1", "@types/js-yaml": "3.12.1",
"@types/ms": "0.7.31", "@types/ms": "0.7.31",
@@ -31,7 +31,7 @@
"@types/node-fetch": "^2.1.6", "@types/node-fetch": "^2.1.6",
"@types/semver": "6.0.0", "@types/semver": "6.0.0",
"@types/yazl": "2.4.2", "@types/yazl": "2.4.2",
"@vercel/frameworks": "0.9.2-canary.0", "@vercel/frameworks": "1.0.2-canary.0",
"@vercel/ncc": "0.24.0", "@vercel/ncc": "0.24.0",
"aggregate-error": "3.0.1", "aggregate-error": "3.0.1",
"async-retry": "1.2.3", "async-retry": "1.2.3",
@@ -40,7 +40,7 @@
"cross-spawn": "6.0.5", "cross-spawn": "6.0.5",
"end-of-stream": "1.4.1", "end-of-stream": "1.4.1",
"fs-extra": "10.0.0", "fs-extra": "10.0.0",
"glob": "7.1.3", "glob": "8.0.3",
"into-stream": "5.0.0", "into-stream": "5.0.0",
"js-yaml": "3.13.1", "js-yaml": "3.13.1",
"minimatch": "3.0.4", "minimatch": "3.0.4",

View File

@@ -23,7 +23,7 @@ interface ErrorResponse {
} }
interface Options { interface Options {
tag?: 'canary' | 'latest' | string; tag?: string;
functions?: BuilderFunctions; functions?: BuilderFunctions;
ignoreBuildScript?: boolean; ignoreBuildScript?: boolean;
projectSettings?: ProjectSettings; projectSettings?: ProjectSettings;
@@ -278,7 +278,7 @@ export async function detectBuilders(
// and package.json can be served as static files // and package.json can be served as static files
frontendBuilder = { frontendBuilder = {
use: '@vercel/static', use: '@vercel/static',
src: '!{api/**,package.json}', src: '!{api/**,package.json,middleware.[jt]s}',
config: { config: {
zeroConfig: true, zeroConfig: true,
}, },
@@ -355,7 +355,16 @@ function maybeGetApiBuilder(
apiMatches: Builder[], apiMatches: Builder[],
options: Options options: Options
) { ) {
if (!fileName.startsWith('api/')) { const middleware =
fileName === 'middleware.js' || fileName === 'middleware.ts';
// Root-level Middleware file is handled by `@vercel/next`, so don't
// schedule a separate Builder when "nextjs" framework is selected
if (middleware && options.projectSettings?.framework === 'nextjs') {
return null;
}
if (!(fileName.startsWith('api/') || middleware)) {
return null; return null;
} }
@@ -381,7 +390,7 @@ function maybeGetApiBuilder(
const { fnPattern, func } = getFunction(fileName, options); const { fnPattern, func } = getFunction(fileName, options);
const use = (func && func.runtime) || (match && match.use); const use = func?.runtime || match?.use;
if (!use) { if (!use) {
return null; return null;
@@ -389,6 +398,10 @@ function maybeGetApiBuilder(
const config: Config = { zeroConfig: true }; const config: Config = { zeroConfig: true };
if (middleware) {
config.middleware = true;
}
if (fnPattern && func) { if (fnPattern && func) {
config.functions = { [fnPattern]: func }; config.functions = { [fnPattern]: func };
@@ -428,6 +441,7 @@ function getApiMatches() {
const config = { zeroConfig: true }; const config = { zeroConfig: true };
return [ return [
{ src: 'middleware.[jt]s', use: `@vercel/node`, config },
{ src: 'api/**/*.js', use: `@vercel/node`, config }, { src: 'api/**/*.js', use: `@vercel/node`, config },
{ src: 'api/**/*.mjs', use: `@vercel/node`, config }, { src: 'api/**/*.mjs', use: `@vercel/node`, config },
{ src: 'api/**/*.ts', use: `@vercel/node`, config }, { src: 'api/**/*.ts', use: `@vercel/node`, config },

View File

@@ -0,0 +1,85 @@
import fs from 'fs';
import { DetectorFilesystem } from '../detectors/filesystem';
type GlobFs = typeof fs;
function normalizePath(path: string) {
// on windows, this will return a path like
// D:/c/package.json
// since we abstract the filesystem, we need to remove windows specific info from the path
// and let the FS decide how to process the path
// D:/c/package.json => /c/package.json
return path.replace(/^[a-zA-Z]:/, '');
}
export function getGlobFs(_fs: DetectorFilesystem): GlobFs {
const readdir = (
path: fs.PathLike,
callback: (err: NodeJS.ErrnoException | null, files: string[]) => void
): void => {
_fs
.readdir(normalizePath(String(path)))
.then(stats =>
callback(
null,
stats.map(stat => stat.name)
)
)
.catch(err => callback(err, []));
};
const stat = (
path: fs.PathLike,
callback: (
err: NodeJS.ErrnoException | null,
stats: fs.Stats | null
) => void
): void => {
_fs
.isFile(normalizePath(String(path)))
.then(isPathAFile => {
callback(null, {
ino: 0,
mode: 0,
nlink: 0,
uid: 0,
gid: 0,
rdev: 0,
size: 0,
blksize: 0,
blocks: 0,
atimeMs: 0,
mtimeMs: 0,
ctimeMs: 0,
birthtimeMs: 0,
atime: new Date(),
mtime: new Date(),
ctime: new Date(),
birthtime: new Date(),
dev: 0,
isBlockDevice: () => false,
isCharacterDevice: () => false,
isDirectory: () => !isPathAFile,
isFIFO: () => false,
isFile: () => isPathAFile,
isSocket: () => false,
isSymbolicLink: () => false,
});
})
.catch(err => callback(err, null));
};
return new Proxy(fs, {
get(_target, prop) {
switch (prop) {
case 'readdir':
return readdir;
case 'lstat':
case 'stat':
return stat;
default:
throw new Error('Not Implemented');
}
},
});
}

View File

@@ -3,6 +3,7 @@ import fs from 'fs-extra';
import path from 'path'; import path from 'path';
import Sema from 'async-sema'; import Sema from 'async-sema';
import spawn from 'cross-spawn'; import spawn from 'cross-spawn';
import { coerce, intersects, validRange } from 'semver';
import { SpawnOptions } from 'child_process'; import { SpawnOptions } from 'child_process';
import { deprecate } from 'util'; import { deprecate } from 'util';
import debug from '../debug'; import debug from '../debug';
@@ -205,7 +206,9 @@ export function getSpawnOptions(
if (!meta.isDev) { if (!meta.isDev) {
// Ensure that the selected Node version is at the beginning of the `$PATH` // Ensure that the selected Node version is at the beginning of the `$PATH`
opts.env.PATH = `/node${nodeVersion.major}/bin:${opts.env.PATH}`; opts.env.PATH = `/node${nodeVersion.major}/bin${path.delimiter}${
opts.env.PATH || process.env.PATH
}`;
} }
return opts; return opts;
@@ -217,9 +220,9 @@ export async function getNodeVersion(
config: Config = {}, config: Config = {},
meta: Meta = {} meta: Meta = {}
): Promise<NodeVersion> { ): Promise<NodeVersion> {
const latest = getLatestNodeVersion();
if (meta && meta.isDev) { if (meta && meta.isDev) {
// Use the system-installed version of `node` in PATH for `vercel dev` // Use the system-installed version of `node` in PATH for `vercel dev`
const latest = getLatestNodeVersion();
return { ...latest, runtime: 'nodejs' }; return { ...latest, runtime: 'nodejs' };
} }
const { packageJson } = await scanParentDirs(destPath, true); const { packageJson } = await scanParentDirs(destPath, true);
@@ -227,10 +230,27 @@ export async function getNodeVersion(
let isAuto = true; let isAuto = true;
if (packageJson && packageJson.engines && packageJson.engines.node) { if (packageJson && packageJson.engines && packageJson.engines.node) {
const { node } = packageJson.engines; const { node } = packageJson.engines;
if (nodeVersion && nodeVersion !== node && !meta.isDev) { if (
nodeVersion &&
validRange(node) &&
!intersects(nodeVersion, node) &&
!meta.isDev
) {
console.warn( console.warn(
`Warning: Due to "engines": { "node": "${node}" } in your \`package.json\` file, the Node.js Version defined in your Project Settings ("${nodeVersion}") will not apply. Learn More: http://vercel.link/node-version` `Warning: Due to "engines": { "node": "${node}" } in your \`package.json\` file, the Node.js Version defined in your Project Settings ("${nodeVersion}") will not apply. Learn More: http://vercel.link/node-version`
); );
} else if (coerce(node)?.raw === node && !meta.isDev) {
console.warn(
`Warning: Detected "engines": { "node": "${node}" } in your \`package.json\` with major.minor.patch, but only major Node.js Version can be selected. Learn More: http://vercel.link/node-version`
);
} else if (
validRange(node) &&
intersects(`${latest.major + 1}.x`, node) &&
!meta.isDev
) {
console.warn(
`Warning: Detected "engines": { "node": "${node}" } in your \`package.json\` that will automatically upgrade when a new major Node.js Version is released. Learn More: http://vercel.link/node-version`
);
} }
nodeVersion = node; nodeVersion = node;
isAuto = false; isAuto = false;
@@ -433,13 +453,13 @@ export function getEnvForPackageManager({
(nodeVersion?.major || 0) < 16 (nodeVersion?.major || 0) < 16
) { ) {
// Ensure that npm 7 is at the beginning of the `$PATH` // Ensure that npm 7 is at the beginning of the `$PATH`
newEnv.PATH = `/node16/bin-npm7:${env.PATH}`; newEnv.PATH = `/node16/bin-npm7${path.delimiter}${env.PATH}`;
console.log('Detected `package-lock.json` generated by npm 7...'); console.log('Detected `package-lock.json` generated by npm 7...');
} }
} else if (cliType === 'pnpm') { } else if (cliType === 'pnpm') {
if (typeof lockfileVersion === 'number' && lockfileVersion === 5.4) { if (typeof lockfileVersion === 'number' && lockfileVersion === 5.4) {
// Ensure that pnpm 7 is at the beginning of the `$PATH` // Ensure that pnpm 7 is at the beginning of the `$PATH`
newEnv.PATH = `/pnpm7/node_modules/.bin:${env.PATH}`; newEnv.PATH = `/pnpm7/node_modules/.bin${path.delimiter}${env.PATH}`;
console.log('Detected `pnpm-lock.yaml` generated by pnpm 7...'); console.log('Detected `pnpm-lock.yaml` generated by pnpm 7...');
} }
} else { } else {

View File

@@ -0,0 +1,60 @@
import { detectFramework } from './detect-framework';
import { DetectorFilesystem } from './detectors/filesystem';
import frameworks from '@vercel/frameworks';
const MAX_DEPTH_TRAVERSE = 3;
export interface GetProjectPathsOptions {
fs: DetectorFilesystem;
path?: string;
skipPaths?: string[];
depth?: number;
}
export type ProjectPath = string;
export const getProjectPaths = async ({
fs,
path,
skipPaths,
depth = MAX_DEPTH_TRAVERSE,
}: GetProjectPathsOptions): Promise<ProjectPath[]> => {
if (depth === 0) return [];
const allPaths: Array<ProjectPath> = [];
const topPath: string = path ?? './';
if (path && skipPaths?.includes(path)) {
return allPaths;
}
const framework = await detectFramework({
fs: fs.chdir(topPath),
frameworkList: frameworks,
});
if (framework !== null) allPaths.push(topPath);
if (depth > 1) {
const directoryContents = await fs.readdir(topPath);
const childDirectories = directoryContents.filter(
stat => stat.type === 'dir' && !skipPaths?.includes(stat.path)
);
const paths = (
await Promise.all(
childDirectories.map(({ path }) => {
return getProjectPaths({
fs,
path,
depth: depth - 1,
skipPaths,
});
})
)
).flat();
return [...paths, ...allPaths];
}
return allPaths;
};

View File

@@ -88,6 +88,7 @@ export {
} from './detect-builders'; } from './detect-builders';
export { detectFileSystemAPI } from './detect-file-system-api'; export { detectFileSystemAPI } from './detect-file-system-api';
export { detectFramework } from './detect-framework'; export { detectFramework } from './detect-framework';
export { getProjectPaths } from './get-project-paths';
export { DetectorFilesystem } from './detectors/filesystem'; export { DetectorFilesystem } from './detectors/filesystem';
export { readConfigFile } from './fs/read-config-file'; export { readConfigFile } from './fs/read-config-file';
export { normalizePath } from './fs/normalize-path'; export { normalizePath } from './fs/normalize-path';
@@ -117,5 +118,14 @@ export const isStaticRuntime = (name?: string): boolean => {
}; };
export { workspaceManagers } from './workspaces/workspace-managers'; export { workspaceManagers } from './workspaces/workspace-managers';
export { getWorkspaces } from './workspaces/get-workspaces'; export {
getWorkspaces,
GetWorkspaceOptions,
Workspace,
WorkspaceType,
} from './workspaces/get-workspaces';
export {
getWorkspacePackagePaths,
GetWorkspacePackagePathsOptions,
} from './workspaces/get-workspace-package-paths';
export { monorepoManagers } from './monorepos/monorepo-managers'; export { monorepoManagers } from './monorepos/monorepo-managers';

View File

@@ -82,7 +82,7 @@ export interface BuildOptions {
* is the Git Repository Root. This is only relevant for Monorepos. * is the Git Repository Root. This is only relevant for Monorepos.
* See https://vercel.com/blog/monorepos * See https://vercel.com/blog/monorepos
*/ */
repoRootPath?: string; repoRootPath: string;
/** /**
* An arbitrary object passed by the user in the build definition defined * An arbitrary object passed by the user in the build definition defined
@@ -123,7 +123,7 @@ export interface PrepareCacheOptions {
* is the Git Repository Root. This is only relevant for Monorepos. * is the Git Repository Root. This is only relevant for Monorepos.
* See https://vercel.com/blog/monorepos * See https://vercel.com/blog/monorepos
*/ */
repoRootPath?: string; repoRootPath: string;
/** /**
* An arbitrary object passed by the user in the build definition defined * An arbitrary object passed by the user in the build definition defined
@@ -295,6 +295,7 @@ export interface PackageJson {
readonly preferGlobal?: boolean; readonly preferGlobal?: boolean;
readonly private?: boolean; readonly private?: boolean;
readonly publishConfig?: PackageJson.PublishConfig; readonly publishConfig?: PackageJson.PublishConfig;
readonly packageManager?: string;
} }
export interface NodeVersion { export interface NodeVersion {
@@ -427,7 +428,9 @@ export interface BuildResultV2Typical {
export type BuildResultV2 = BuildResultV2Typical | BuildResultBuildOutput; export type BuildResultV2 = BuildResultV2Typical | BuildResultBuildOutput;
export interface BuildResultV3 { export interface BuildResultV3 {
output: Lambda; // TODO: use proper `Route` type from `routing-utils` (perhaps move types to a common package)
routes?: any[];
output: Lambda | EdgeFunction;
} }
export type BuildV2 = (options: BuildOptions) => Promise<BuildResultV2>; export type BuildV2 = (options: BuildOptions) => Promise<BuildResultV2>;

View File

@@ -0,0 +1,113 @@
import _path from 'path';
import yaml from 'js-yaml';
import glob from 'glob';
import { DetectorFilesystem } from '../detectors/filesystem';
import { Workspace } from './get-workspaces';
import { getGlobFs } from '../fs/get-glob-fs';
import { normalizePath } from '../fs/normalize-path';
const posixPath = _path.posix;
interface GetPackagePathOptions {
fs: DetectorFilesystem;
}
export interface GetWorkspacePackagePathsOptions extends GetPackagePathOptions {
fs: DetectorFilesystem;
workspace: Workspace;
}
export async function getWorkspacePackagePaths({
fs,
workspace,
}: GetWorkspacePackagePathsOptions): Promise<string[]> {
const { type, rootPath } = workspace;
const workspaceFs = fs.chdir(rootPath);
let results: string[] = [];
switch (type) {
case 'yarn':
case 'npm':
results = await getPackageJsonWorkspacePackagePaths({ fs: workspaceFs });
break;
case 'pnpm':
results = await getPnpmWorkspacePackagePaths({ fs: workspaceFs });
break;
default:
throw new Error(`Unknown workspace implementation: ${type}`);
}
return results.map(packagePath => {
return posixPath.join(rootPath, posixPath.dirname(packagePath));
});
}
type PackageJsonWithWorkspace = {
workspaces?:
| {
packages?: string[];
noHoist?: string[];
}
| string[];
};
type PnpmWorkspaces = {
packages?: string[];
};
async function getPackagePaths(
packages: string[],
fs: DetectorFilesystem
): Promise<string[]> {
return (
await Promise.all(
packages.map(
packageGlob =>
new Promise<string[]>((resolve, reject) => {
glob(
normalizePath(posixPath.join(packageGlob, 'package.json')),
{
cwd: '/',
fs: getGlobFs(fs),
},
(err, matches) => {
if (err) reject(err);
else resolve(matches);
}
);
})
)
)
).flat();
}
async function getPackageJsonWorkspacePackagePaths({
fs,
}: GetPackagePathOptions): Promise<string[]> {
const packageJsonAsBuffer = await fs.readFile('package.json');
const { workspaces } = JSON.parse(
packageJsonAsBuffer.toString()
) as PackageJsonWithWorkspace;
let packages: string[] = [];
if (Array.isArray(workspaces)) {
packages = workspaces;
} else {
packages = workspaces?.packages ?? [];
}
return getPackagePaths(packages, fs);
}
async function getPnpmWorkspacePackagePaths({
fs,
}: GetPackagePathOptions): Promise<string[]> {
const pnpmWorkspaceAsBuffer = await fs.readFile('pnpm-workspace.yaml');
const { packages = [] } = yaml.load(
pnpmWorkspaceAsBuffer.toString()
) as PnpmWorkspaces;
return getPackagePaths(packages, fs);
}

View File

@@ -36,7 +36,7 @@ export async function getWorkspaces({
const childDirectories = directoryContents.filter( const childDirectories = directoryContents.filter(
stat => stat.type === 'dir' stat => stat.type === 'dir'
); );
return ( return (
await Promise.all( await Promise.all(
childDirectories.map(childDirectory => childDirectories.map(childDirectory =>

View File

@@ -17,5 +17,13 @@ checkPkgOrThrow('exeggcute');
// This is to satisfy `@vercel/static-build` which needs a `dist` directory. // This is to satisfy `@vercel/static-build` which needs a `dist` directory.
const { exec } = require('exeggcute'); const { exec } = require('exeggcute');
exec('mkdir dist', __dirname); exec('mkdir dist', __dirname)
exec('echo "node-env:RANDOMNESS_PLACEHOLDER" > dist/index.html', __dirname); .then(() => {
exec(
'echo "node-env:RANDOMNESS_PLACEHOLDER" > dist/index.html',
__dirname
).then(() => {
console.log('Success');
});
})
.catch(console.error);

View File

@@ -6,5 +6,10 @@ const b = require('./b');
a(); a();
b(); b();
exec('mkdir public', __dirname); exec('mkdir public', __dirname)
exec('echo "Hello, World!" > public/index.html', __dirname); .then(() => {
exec('echo "Hello, World!" > public/index.html', __dirname).then(() => {
console.log('Success');
});
})
.catch(console.error);

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,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

@@ -1,19 +1,260 @@
lockfileVersion: 5.3 lockfileVersion: 5.4
specifiers: importers:
once: ^1.4.0
dependencies: .:
once: 1.4.0 specifiers:
once: ^1.4.0
dependencies:
once: 1.4.0
a:
specifiers:
debug: ^4.3.2
dependencies:
debug: 4.3.4
b:
specifiers:
cowsay: ^1.5.0
dependencies:
cowsay: 1.5.0
packages: packages:
/ansi-regex/3.0.1:
resolution: {integrity: sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==}
engines: {node: '>=4'}
dev: false
/ansi-regex/5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
dev: false
/ansi-styles/4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
dependencies:
color-convert: 2.0.1
dev: false
/camelcase/5.3.1:
resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
engines: {node: '>=6'}
dev: false
/cliui/6.0.0:
resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi: 6.2.0
dev: false
/color-convert/2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
dependencies:
color-name: 1.1.4
dev: false
/color-name/1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
dev: false
/cowsay/1.5.0:
resolution: {integrity: sha512-8Ipzr54Z8zROr/62C8f0PdhQcDusS05gKTS87xxdji8VbWefWly0k8BwGK7+VqamOrkv3eGsCkPtvlHzrhWsCA==}
engines: {node: '>= 4'}
hasBin: true
dependencies:
get-stdin: 8.0.0
string-width: 2.1.1
strip-final-newline: 2.0.0
yargs: 15.4.1
dev: false
/debug/4.3.4:
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
dependencies:
ms: 2.1.2
dev: false
/decamelize/1.2.0:
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
engines: {node: '>=0.10.0'}
dev: false
/emoji-regex/8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
dev: false
/find-up/4.1.0:
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
engines: {node: '>=8'}
dependencies:
locate-path: 5.0.0
path-exists: 4.0.0
dev: false
/get-caller-file/2.0.5:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
dev: false
/get-stdin/8.0.0:
resolution: {integrity: sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==}
engines: {node: '>=10'}
dev: false
/is-fullwidth-code-point/2.0.0:
resolution: {integrity: sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==}
engines: {node: '>=4'}
dev: false
/is-fullwidth-code-point/3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
dev: false
/locate-path/5.0.0:
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
engines: {node: '>=8'}
dependencies:
p-locate: 4.1.0
dev: false
/ms/2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
dev: false
/once/1.4.0: /once/1.4.0:
resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=} resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}
dependencies: dependencies:
wrappy: 1.0.2 wrappy: 1.0.2
dev: false dev: false
/p-limit/2.3.0:
resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
engines: {node: '>=6'}
dependencies:
p-try: 2.2.0
dev: false
/p-locate/4.1.0:
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
engines: {node: '>=8'}
dependencies:
p-limit: 2.3.0
dev: false
/p-try/2.2.0:
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
engines: {node: '>=6'}
dev: false
/path-exists/4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
dev: false
/require-directory/2.1.1:
resolution: {integrity: sha1-jGStX9MNqxyXbiNE/+f3kqam30I=}
engines: {node: '>=0.10.0'}
dev: false
/require-main-filename/2.0.0:
resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
dev: false
/set-blocking/2.0.0:
resolution: {integrity: sha1-BF+XgtARrppoA93TgrJDkrPYkPc=}
dev: false
/string-width/2.1.1:
resolution: {integrity: sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==}
engines: {node: '>=4'}
dependencies:
is-fullwidth-code-point: 2.0.0
strip-ansi: 4.0.0
dev: false
/string-width/4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
dependencies:
emoji-regex: 8.0.0
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1
dev: false
/strip-ansi/4.0.0:
resolution: {integrity: sha1-qEeQIusaw2iocTibY1JixQXuNo8=}
engines: {node: '>=4'}
dependencies:
ansi-regex: 3.0.1
dev: false
/strip-ansi/6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
dependencies:
ansi-regex: 5.0.1
dev: false
/strip-final-newline/2.0.0:
resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
engines: {node: '>=6'}
dev: false
/which-module/2.0.0:
resolution: {integrity: sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=}
dev: false
/wrap-ansi/6.2.0:
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
engines: {node: '>=8'}
dependencies:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
dev: false
/wrappy/1.0.2: /wrappy/1.0.2:
resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=} resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=}
dev: false dev: false
/y18n/4.0.3:
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
dev: false
/yargs-parser/18.1.3:
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
engines: {node: '>=6'}
dependencies:
camelcase: 5.3.1
decamelize: 1.2.0
dev: false
/yargs/15.4.1:
resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
engines: {node: '>=8'}
dependencies:
cliui: 6.0.0
decamelize: 1.2.0
find-up: 4.1.0
get-caller-file: 2.0.5
require-directory: 2.1.1
require-main-filename: 2.0.0
set-blocking: 2.0.0
string-width: 4.2.3
which-module: 2.0.0
y18n: 4.0.3
yargs-parser: 18.1.3
dev: false

View File

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

View File

@@ -0,0 +1,5 @@
{
"name": "backend",
"license": "MIT",
"version": "0.1.0"
}

View File

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

View File

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

View File

@@ -0,0 +1,5 @@
{
"name": "frontend",
"license": "MIT",
"version": "0.1.0"
}

View File

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

View File

@@ -0,0 +1,5 @@
{
"name": "backend",
"license": "MIT",
"version": "0.1.0"
}

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,9 @@
{
"name": "21-npm-workspaces",
"version": "1.0.0",
"private": true,
"workspaces": [
"a",
"b"
]
}

View File

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

View File

@@ -0,0 +1,5 @@
{
"name": "backend",
"license": "MIT",
"version": "0.1.0"
}

View File

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

View File

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

View File

@@ -0,0 +1,5 @@
{
"name": "frontend",
"license": "MIT",
"version": "0.1.0"
}

View File

@@ -0,0 +1,12 @@
{
"name": "app-one",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}

View File

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

View File

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

View File

@@ -0,0 +1,5 @@
{
"name": "frontend",
"license": "MIT",
"version": "0.1.0"
}

View File

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

View File

@@ -0,0 +1,6 @@
{
"private": true,
"engines": {
"node": "16.14.0"
}
}

View File

@@ -0,0 +1,6 @@
{
"private": true,
"engines": {
"node": ">=16"
}
}

View File

@@ -78,7 +78,7 @@ describe('Test `detectBuilders`', () => {
expect(builders![0].use).toBe('@vercel/node'); expect(builders![0].use).toBe('@vercel/node');
expect(builders![0].src).toBe('api/users.js'); expect(builders![0].src).toBe('api/users.js');
expect(builders![1].use).toBe('@vercel/static'); expect(builders![1].use).toBe('@vercel/static');
expect(builders![1].src).toBe('!{api/**,package.json}'); expect(builders![1].src).toBe('!{api/**,package.json,middleware.[jt]s}');
expect(builders!.length).toBe(2); expect(builders!.length).toBe(2);
expect(errors).toBe(null); expect(errors).toBe(null);
}); });
@@ -89,7 +89,7 @@ describe('Test `detectBuilders`', () => {
expect(builders![0].use).toBe('@vercel/node'); expect(builders![0].use).toBe('@vercel/node');
expect(builders![0].src).toBe('api/[endpoint].js'); expect(builders![0].src).toBe('api/[endpoint].js');
expect(builders![1].use).toBe('@vercel/static'); expect(builders![1].use).toBe('@vercel/static');
expect(builders![1].src).toBe('!{api/**,package.json}'); expect(builders![1].src).toBe('!{api/**,package.json,middleware.[jt]s}');
expect(builders!.length).toBe(2); expect(builders!.length).toBe(2);
expect(errors).toBe(null); expect(errors).toBe(null);
}); });
@@ -144,7 +144,7 @@ describe('Test `detectBuilders`', () => {
expect(builders![0].use).toBe('@vercel/node'); expect(builders![0].use).toBe('@vercel/node');
expect(builders![0].src).toBe('api/endpoint.js'); expect(builders![0].src).toBe('api/endpoint.js');
expect(builders![1].use).toBe('@vercel/static'); expect(builders![1].use).toBe('@vercel/static');
expect(builders![1].src).toBe('!{api/**,package.json}'); expect(builders![1].src).toBe('!{api/**,package.json,middleware.[jt]s}');
expect(builders!.length).toBe(2); expect(builders!.length).toBe(2);
}); });
@@ -347,7 +347,7 @@ describe('Test `detectBuilders`', () => {
expect(builders![0].use).toBe('@vercel/node'); expect(builders![0].use).toBe('@vercel/node');
expect(builders![0].src).toBe('api/index.ts'); expect(builders![0].src).toBe('api/index.ts');
expect(builders![1].use).toBe('@vercel/static'); expect(builders![1].use).toBe('@vercel/static');
expect(builders![1].src).toBe('!{api/**,package.json}'); expect(builders![1].src).toBe('!{api/**,package.json,middleware.[jt]s}');
}); });
it('functions with nextjs', async () => { it('functions with nextjs', async () => {
@@ -671,8 +671,8 @@ describe('Test `detectBuilders`', () => {
expect(builders![0].use).toBe('@vercel/static'); expect(builders![0].use).toBe('@vercel/static');
expect(defaultRoutes!.length).toBe(1); expect(defaultRoutes!.length).toBe(1);
expect((defaultRoutes![0] as any).src).toBe('/(.*)'); expect(defaultRoutes![0].src).toBe('/(.*)');
expect((defaultRoutes![0] as any).dest).toBe('/dist/$1'); expect(defaultRoutes![0].dest).toBe('/dist/$1');
}); });
it('Custom static output directory with api', async () => { it('Custom static output directory with api', async () => {
@@ -691,9 +691,9 @@ describe('Test `detectBuilders`', () => {
expect(builders![1].use).toBe('@vercel/static'); expect(builders![1].use).toBe('@vercel/static');
expect(defaultRoutes!.length).toBe(3); expect(defaultRoutes!.length).toBe(3);
expect((defaultRoutes![1] as any).status).toBe(404); expect(defaultRoutes![1].status).toBe(404);
expect((defaultRoutes![2] as any).src).toBe('/(.*)'); expect(defaultRoutes![2].src).toBe('/(.*)');
expect((defaultRoutes![2] as any).dest).toBe('/output/$1'); expect(defaultRoutes![2].dest).toBe('/output/$1');
}); });
it('Framework with non-package.json entrypoint', async () => { it('Framework with non-package.json entrypoint', async () => {
@@ -1010,7 +1010,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
expect(builders![0].use).toBe('@vercel/node'); expect(builders![0].use).toBe('@vercel/node');
expect(builders![0].src).toBe('api/users.js'); expect(builders![0].src).toBe('api/users.js');
expect(builders![1].use).toBe('@vercel/static'); expect(builders![1].use).toBe('@vercel/static');
expect(builders![1].src).toBe('!{api/**,package.json}'); expect(builders![1].src).toBe('!{api/**,package.json,middleware.[jt]s}');
expect(builders!.length).toBe(2); expect(builders!.length).toBe(2);
expect(errors).toBe(null); expect(errors).toBe(null);
@@ -1032,7 +1032,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
expect(builders![0].use).toBe('@vercel/node'); expect(builders![0].use).toBe('@vercel/node');
expect(builders![0].src).toBe('api/[endpoint].js'); expect(builders![0].src).toBe('api/[endpoint].js');
expect(builders![1].use).toBe('@vercel/static'); expect(builders![1].use).toBe('@vercel/static');
expect(builders![1].src).toBe('!{api/**,package.json}'); expect(builders![1].src).toBe('!{api/**,package.json,middleware.[jt]s}');
expect(builders!.length).toBe(2); expect(builders!.length).toBe(2);
expect(errors).toBe(null); expect(errors).toBe(null);
}); });
@@ -1258,7 +1258,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
expect(builders![0].use).toBe('@vercel/node'); expect(builders![0].use).toBe('@vercel/node');
expect(builders![0].src).toBe('api/endpoint.js'); expect(builders![0].src).toBe('api/endpoint.js');
expect(builders![1].use).toBe('@vercel/static'); expect(builders![1].use).toBe('@vercel/static');
expect(builders![1].src).toBe('!{api/**,package.json}'); expect(builders![1].src).toBe('!{api/**,package.json,middleware.[jt]s}');
expect(builders!.length).toBe(2); expect(builders!.length).toBe(2);
expect(defaultRoutes!.length).toBe(2); expect(defaultRoutes!.length).toBe(2);
@@ -1288,7 +1288,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
expect(builders![0].use).toBe('@vercel/node'); expect(builders![0].use).toBe('@vercel/node');
expect(builders![0].src).toBe('api/version.js'); expect(builders![0].src).toBe('api/version.js');
expect(builders![1].use).toBe('@vercel/static'); expect(builders![1].use).toBe('@vercel/static');
expect(builders![1].src).toBe('!{api/**,package.json}'); expect(builders![1].src).toBe('!{api/**,package.json,middleware.[jt]s}');
expect(builders!.length).toBe(2); expect(builders!.length).toBe(2);
expect(defaultRoutes!.length).toBe(2); expect(defaultRoutes!.length).toBe(2);
@@ -1567,7 +1567,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
expect(builders![0].use).toBe('@vercel/node'); expect(builders![0].use).toBe('@vercel/node');
expect(builders![0].src).toBe('api/index.ts'); expect(builders![0].src).toBe('api/index.ts');
expect(builders![1].use).toBe('@vercel/static'); expect(builders![1].use).toBe('@vercel/static');
expect(builders![1].src).toBe('!{api/**,package.json}'); expect(builders![1].src).toBe('!{api/**,package.json,middleware.[jt]s}');
expect(errorRoutes!.length).toBe(1); expect(errorRoutes!.length).toBe(1);
expect((errorRoutes![0] as Source).status).toBe(404); expect((errorRoutes![0] as Source).status).toBe(404);
}); });
@@ -2228,6 +2228,55 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
expect(builders).toBe(null); expect(builders).toBe(null);
expect(errors).toBe(null); expect(errors).toBe(null);
}); });
it('no package.json + no build + root-level "middleware.js"', async () => {
const files = ['middleware.js', 'index.html', 'web/middleware.js'];
const { builders, errors } = await detectBuilders(files, null, {
featHandleMiss,
});
expect(builders![0].use).toBe('@vercel/node');
expect(builders![0].src).toBe('middleware.js');
expect(builders![0].config?.middleware).toEqual(true);
expect(builders![1].use).toBe('@vercel/static');
expect(builders![1].src).toBe('!{api/**,package.json,middleware.[jt]s}');
expect(builders!.length).toBe(2);
expect(errors).toBe(null);
});
it('no package.json + no build + root-level "middleware.ts"', async () => {
const files = ['middleware.ts', 'index.html', 'web/middleware.js'];
const { builders, errors } = await detectBuilders(files, null, {
featHandleMiss,
});
expect(builders![0].use).toBe('@vercel/node');
expect(builders![0].src).toBe('middleware.ts');
expect(builders![0].config?.middleware).toEqual(true);
expect(builders![1].use).toBe('@vercel/static');
expect(builders![1].src).toBe('!{api/**,package.json,middleware.[jt]s}');
expect(builders!.length).toBe(2);
expect(errors).toBe(null);
});
it('should not add middleware builder when "nextjs" framework is selected', async () => {
const files = ['package.json', 'pages/index.ts', 'middleware.ts'];
const projectSettings = {
framework: 'nextjs',
};
const { builders } = await detectBuilders(files, null, {
projectSettings,
featHandleMiss,
});
expect(builders).toEqual([
{
use: '@vercel/next',
src: 'package.json',
config: {
zeroConfig: true,
framework: projectSettings.framework,
},
},
]);
});
}); });
it('Test `detectRoutes`', async () => { it('Test `detectRoutes`', async () => {
@@ -2236,10 +2285,10 @@ it('Test `detectRoutes`', async () => {
const { defaultRoutes } = await detectBuilders(files); const { defaultRoutes } = await detectBuilders(files);
expect(defaultRoutes!.length).toBe(3); expect(defaultRoutes!.length).toBe(3);
expect((defaultRoutes![0] as any).dest).toBe('/api/team.js'); expect(defaultRoutes![0].dest).toBe('/api/team.js');
expect((defaultRoutes![1] as any).dest).toBe('/api/user.go'); expect(defaultRoutes![1].dest).toBe('/api/user.go');
expect((defaultRoutes![2] as any).dest).not.toBeDefined(); expect(defaultRoutes![2].dest).not.toBeDefined();
expect((defaultRoutes![2] as any).status).toBe(404); expect(defaultRoutes![2].status).toBe(404);
} }
{ {
@@ -2286,10 +2335,10 @@ it('Test `detectRoutes`', async () => {
]; ];
const { defaultRoutes } = await detectBuilders(files); const { defaultRoutes } = await detectBuilders(files);
expect((defaultRoutes![2] as any).status).toBe(404); expect(defaultRoutes![2].status).toBe(404);
expect((defaultRoutes![2] as any).src).toBe('^/api(/.*)?$'); expect(defaultRoutes![2].src).toBe('^/api(/.*)?$');
expect((defaultRoutes![3] as any).src).toBe('/(.*)'); expect(defaultRoutes![3].src).toBe('/(.*)');
expect((defaultRoutes![3] as any).dest).toBe('/public/$1'); expect(defaultRoutes![3].dest).toBe('/public/$1');
expect(defaultRoutes!.length).toBe(4); expect(defaultRoutes!.length).toBe(4);
} }
@@ -2301,8 +2350,8 @@ it('Test `detectRoutes`', async () => {
const files = ['public/index.html', 'api/[endpoint].js']; const files = ['public/index.html', 'api/[endpoint].js'];
const { defaultRoutes } = await detectBuilders(files, pkg); const { defaultRoutes } = await detectBuilders(files, pkg);
expect((defaultRoutes![1] as any).status).toBe(404); expect(defaultRoutes![1].status).toBe(404);
expect((defaultRoutes![1] as any).src).toBe('^/api(/.*)?$'); expect(defaultRoutes![1].src).toBe('^/api(/.*)?$');
expect(defaultRoutes!.length).toBe(2); expect(defaultRoutes!.length).toBe(2);
} }
@@ -2320,14 +2369,10 @@ it('Test `detectRoutes`', async () => {
const { defaultRoutes } = await detectBuilders(files); const { defaultRoutes } = await detectBuilders(files);
expect(defaultRoutes!.length).toBe(3); expect(defaultRoutes!.length).toBe(3);
expect((defaultRoutes![0] as any).src).toBe( expect(defaultRoutes![0].src).toBe('^/api/date(/|/index|/index\\.js)?$');
'^/api/date(/|/index|/index\\.js)?$' expect(defaultRoutes![0].dest).toBe('/api/date/index.js');
); expect(defaultRoutes![1].src).toBe('^/api/(date/|date|date\\.js)$');
expect((defaultRoutes![0] as any).dest).toBe('/api/date/index.js'); expect(defaultRoutes![1].dest).toBe('/api/date.js');
expect((defaultRoutes![1] as any).src).toBe(
'^/api/(date/|date|date\\.js)$'
);
expect((defaultRoutes![1] as any).dest).toBe('/api/date.js');
} }
{ {
@@ -2336,16 +2381,10 @@ it('Test `detectRoutes`', async () => {
const { defaultRoutes } = await detectBuilders(files); const { defaultRoutes } = await detectBuilders(files);
expect(defaultRoutes!.length).toBe(3); expect(defaultRoutes!.length).toBe(3);
expect((defaultRoutes![0] as any).src).toBe( expect(defaultRoutes![0].src).toBe('^/api/([^/]+)(/|/index|/index\\.js)?$');
'^/api/([^/]+)(/|/index|/index\\.js)?$' expect(defaultRoutes![0].dest).toBe('/api/[date]/index.js?date=$1');
); expect(defaultRoutes![1].src).toBe('^/api/(date/|date|date\\.js)$');
expect((defaultRoutes![0] as any).dest).toBe( expect(defaultRoutes![1].dest).toBe('/api/date.js');
'/api/[date]/index.js?date=$1'
);
expect((defaultRoutes![1] as any).src).toBe(
'^/api/(date/|date|date\\.js)$'
);
expect((defaultRoutes![1] as any).dest).toBe('/api/date.js');
} }
{ {
@@ -2375,7 +2414,7 @@ it('Test `detectRoutes`', async () => {
const { defaultRoutes } = await detectBuilders(files, null, { functions }); const { defaultRoutes } = await detectBuilders(files, null, { functions });
expect(defaultRoutes!.length).toBe(2); expect(defaultRoutes!.length).toBe(2);
expect((defaultRoutes![0] as any).dest).toBe('/api/user.php'); expect(defaultRoutes![0].dest).toBe('/api/user.php');
} }
}); });

View File

@@ -1,4 +1,5 @@
import assert from 'assert'; import assert from 'assert';
import { delimiter } from 'path';
import { getEnvForPackageManager } from '../src'; import { getEnvForPackageManager } from '../src';
describe('Test `getEnvForPackageManager()`', () => { describe('Test `getEnvForPackageManager()`', () => {
@@ -34,7 +35,7 @@ describe('Test `getEnvForPackageManager()`', () => {
}, },
want: { want: {
FOO: 'bar', FOO: 'bar',
PATH: `/node16/bin-npm7:foo`, PATH: `/node16/bin-npm7${delimiter}foo`,
}, },
}, },
{ {
@@ -97,7 +98,7 @@ describe('Test `getEnvForPackageManager()`', () => {
}, },
want: { want: {
FOO: 'bar', FOO: 'bar',
PATH: '/pnpm7/node_modules/.bin:foo', PATH: `/pnpm7/node_modules/.bin${delimiter}foo`,
}, },
}, },
{ {

View File

@@ -0,0 +1,70 @@
import path from 'path';
import { normalizePath } from '../src';
import { getProjectPaths, ProjectPath } from '../src/get-project-paths';
import { FixtureFilesystem } from './utils/fixture-filesystem';
describe.each<{
fixturePath: string;
resultPaths: ProjectPath[];
skipPaths?: ProjectPath[];
readdirCalls: number;
}>([
{
fixturePath: '32-monorepo-highly-nested',
resultPaths: [],
readdirCalls: 2,
},
{
fixturePath: '33-hybrid-monorepo',
resultPaths: ['backend/app-three'],
readdirCalls: 2,
skipPaths: ['frontend'],
},
{
fixturePath: '34-monorepo-no-workspaces',
resultPaths: ['backend/app-three', 'frontend/app-one', 'frontend/app-two'],
readdirCalls: 3,
},
{
fixturePath: '35-no-monorepo',
resultPaths: [],
readdirCalls: 1,
},
{
fixturePath: '36-monorepo-some-nested',
resultPaths: ['frontend/app-two'],
readdirCalls: 2,
},
{
fixturePath: '37-project-depth-one-level',
resultPaths: ['./'],
readdirCalls: 1,
},
])(
'`getProjectPaths()`',
({ resultPaths, readdirCalls, fixturePath, skipPaths }) => {
const testName =
resultPaths.length > 0
? `should detect ${resultPaths.join()} project${
resultPaths.length > 1 ? 's' : ''
} for ${fixturePath}`
: `should not detect any path for ${fixturePath}`;
it(testName, async () => {
const fixture = path.join(
__dirname,
'non-deployed-fixtures',
fixturePath
);
const fs = new FixtureFilesystem(fixture);
const mockReaddir = jest.fn().mockImplementation(fs.readdir);
const mockHasPath = jest.fn().mockImplementation(fs.hasPath);
fs.readdir = mockReaddir;
fs.hasPath = mockHasPath;
const actualPaths = await getProjectPaths({ fs, skipPaths });
const normalizedPaths = actualPaths.map(path => normalizePath(path));
expect(normalizedPaths).toEqual(resultPaths);
expect(fs.readdir).toHaveBeenCalledTimes(readdirCalls);
});
}
);

View File

@@ -0,0 +1,38 @@
import path from 'path';
import { getWorkspaces } from '../src/workspaces/get-workspaces';
import { getWorkspacePackagePaths } from '../src/workspaces/get-workspace-package-paths';
import { FixtureFilesystem } from './utils/fixture-filesystem';
describe.each<[string, string[]]>([
['21-npm-workspaces', ['/a', '/b']],
['23-pnpm-workspaces', ['/c', '/d']],
['27-yarn-workspaces', ['/a', '/b']],
['25-multiple-lock-files-yarn', ['/a', '/b']],
['26-multiple-lock-files-pnpm', ['/a', '/b']],
[
'29-nested-workspaces',
['/backend/c', '/backend/d', '/frontend/a', '/frontend/b'],
],
['22-pnpm', []],
])('`getWorkspacesPackagePaths()`', (fixturePath, packagePaths) => {
const testName =
packagePaths.length > 0
? `should detect ${packagePaths.join()} package${
packagePaths.length > 1 ? 's' : ''
} for ${fixturePath}`
: `should not detect any workspace for ${fixturePath}`;
it(testName, async () => {
const fixture = path.join(__dirname, 'fixtures', fixturePath);
const fs = new FixtureFilesystem(fixture);
const workspaces = await getWorkspaces({ fs });
const actualPackagePaths = (
await Promise.all(
workspaces.map(workspace => getWorkspacePackagePaths({ fs, workspace }))
)
).flat();
expect(actualPackagePaths).toEqual(packagePaths);
});
});

View File

@@ -277,7 +277,45 @@ it('should prefer package.json engines over project setting from config and warn
]); ]);
}); });
it('should warn when package.json engines is exact version', async () => {
expect(
await getNodeVersion(
path.join(__dirname, 'pkg-engine-node-exact'),
undefined,
{},
{}
)
).toHaveProperty('range', '16.x');
expect(warningMessages).toStrictEqual([
'Warning: Detected "engines": { "node": "16.14.0" } in your `package.json` with major.minor.patch, but only major Node.js Version can be selected. Learn More: http://vercel.link/node-version',
]);
});
it('should warn when package.json engines is greater than', async () => {
expect(
await getNodeVersion(
path.join(__dirname, 'pkg-engine-node-greaterthan'),
undefined,
{},
{}
)
).toHaveProperty('range', '16.x');
expect(warningMessages).toStrictEqual([
'Warning: Detected "engines": { "node": ">=16" } in your `package.json` that will automatically upgrade when a new major Node.js Version is released. Learn More: http://vercel.link/node-version',
]);
});
it('should not warn when package.json engines matches project setting from config', async () => { it('should not warn when package.json engines matches project setting from config', async () => {
expect(
await getNodeVersion(
path.join(__dirname, 'pkg-engine-node'),
undefined,
{ nodeVersion: '14' },
{}
)
).toHaveProperty('range', '14.x');
expect(warningMessages).toStrictEqual([]);
expect( expect(
await getNodeVersion( await getNodeVersion(
path.join(__dirname, 'pkg-engine-node'), path.join(__dirname, 'pkg-engine-node'),
@@ -287,6 +325,16 @@ it('should not warn when package.json engines matches project setting from confi
) )
).toHaveProperty('range', '14.x'); ).toHaveProperty('range', '14.x');
expect(warningMessages).toStrictEqual([]); expect(warningMessages).toStrictEqual([]);
expect(
await getNodeVersion(
path.join(__dirname, 'pkg-engine-node'),
undefined,
{ nodeVersion: '<15' },
{}
)
).toHaveProperty('range', '14.x');
expect(warningMessages).toStrictEqual([]);
}); });
it('should get latest node version', async () => { it('should get latest node version', async () => {

View File

@@ -2,7 +2,7 @@
"compilerOptions": { "compilerOptions": {
"declaration": true, "declaration": true,
"esModuleInterop": true, "esModuleInterop": true,
"lib": ["esnext"], "lib": ["ES2020"],
"module": "commonjs", "module": "commonjs",
"moduleResolution": "node", "moduleResolution": "node",
"noEmitOnError": true, "noEmitOnError": true,
@@ -13,7 +13,7 @@
"outDir": "./dist", "outDir": "./dist",
"types": ["node", "jest"], "types": ["node", "jest"],
"strict": true, "strict": true,
"target": "es2019" "target": "ES2020"
}, },
"include": ["src/**/*"], "include": ["src/**/*"],
"exclude": ["node_modules"] "exclude": ["node_modules"]

View File

@@ -1,6 +1,6 @@
{ {
"name": "vercel", "name": "vercel",
"version": "24.2.5-canary.2", "version": "25.1.1-canary.4",
"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",
@@ -30,7 +30,6 @@
"scripts/preinstall.js" "scripts/preinstall.js"
], ],
"ava": { "ava": {
"compileEnhancements": false,
"extensions": [ "extensions": [
"ts" "ts"
], ],
@@ -40,19 +39,19 @@
] ]
}, },
"engines": { "engines": {
"node": ">= 12" "node": ">= 14"
}, },
"dependencies": { "dependencies": {
"@vercel/build-utils": "3.1.1-canary.2", "@vercel/build-utils": "4.1.1-canary.1",
"@vercel/go": "1.4.4-canary.2", "@vercel/go": "2.0.2-canary.1",
"@vercel/next": "2.8.67-canary.2", "@vercel/next": "3.0.3-canary.1",
"@vercel/node": "1.15.4-canary.2", "@vercel/node": "2.2.1-canary.0",
"@vercel/python": "2.3.4-canary.2", "@vercel/python": "3.0.2-canary.1",
"@vercel/redwood": "0.8.4-canary.2", "@vercel/redwood": "1.0.2-canary.1",
"@vercel/remix": "0.0.2-canary.2", "@vercel/remix": "1.0.2-canary.1",
"@vercel/ruby": "1.3.7-canary.2", "@vercel/ruby": "1.3.10-canary.1",
"@vercel/static-build": "0.25.3-canary.2", "@vercel/static-build": "1.0.2-canary.1",
"update-notifier": "4.1.0" "update-notifier": "5.1.0"
}, },
"devDependencies": { "devDependencies": {
"@alex_neo/jest-expect-message": "1.0.5", "@alex_neo/jest-expect-message": "1.0.5",
@@ -72,6 +71,7 @@
"@types/fs-extra": "9.0.13", "@types/fs-extra": "9.0.13",
"@types/glob": "7.1.1", "@types/glob": "7.1.1",
"@types/http-proxy": "1.16.2", "@types/http-proxy": "1.16.2",
"@types/ini": "1.3.31",
"@types/inquirer": "7.3.1", "@types/inquirer": "7.3.1",
"@types/jest": "27.4.1", "@types/jest": "27.4.1",
"@types/jest-expect-message": "1.0.3", "@types/jest-expect-message": "1.0.3",
@@ -95,8 +95,8 @@
"@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": "11.0.4-canary.2", "@vercel/client": "12.0.2-canary.1",
"@vercel/frameworks": "0.9.2-canary.0", "@vercel/frameworks": "1.0.2-canary.0",
"@vercel/ncc": "0.24.0", "@vercel/ncc": "0.24.0",
"@zeit/fun": "0.11.2", "@zeit/fun": "0.11.2",
"@zeit/source-map-support": "0.6.2", "@zeit/source-map-support": "0.6.2",
@@ -131,11 +131,13 @@
"fast-deep-equal": "3.1.3", "fast-deep-equal": "3.1.3",
"fs-extra": "10.0.0", "fs-extra": "10.0.0",
"get-port": "5.1.1", "get-port": "5.1.1",
"git-last-commit": "1.0.1",
"glob": "7.1.2", "glob": "7.1.2",
"http-proxy": "1.18.1", "http-proxy": "1.18.1",
"ini": "3.0.0",
"inquirer": "7.0.4", "inquirer": "7.0.4",
"is-docker": "2.2.1", "is-docker": "2.2.1",
"is-port-reachable": "3.0.0", "is-port-reachable": "3.1.0",
"is-url": "1.2.2", "is-url": "1.2.2",
"jaro-winkler": "0.2.8", "jaro-winkler": "0.2.8",
"jsonlines": "0.1.1", "jsonlines": "0.1.1",

View File

@@ -1,9 +1,10 @@
import fs from 'fs-extra'; import fs from 'fs-extra';
import chalk from 'chalk'; import chalk from 'chalk';
import dotenv from 'dotenv'; import dotenv from 'dotenv';
import { join, relative } from 'path'; import { join, normalize, relative } from 'path';
import { import {
detectBuilders, detectBuilders,
normalizePath,
Files, Files,
FileFsRef, FileFsRef,
PackageJson, PackageJson,
@@ -14,6 +15,7 @@ import {
BuildResultV2, BuildResultV2,
BuildResultV2Typical, BuildResultV2Typical,
BuildResultV3, BuildResultV3,
NowBuildError,
} from '@vercel/build-utils'; } from '@vercel/build-utils';
import minimatch from 'minimatch'; import minimatch from 'minimatch';
import { import {
@@ -45,6 +47,7 @@ import {
writeBuildResult, writeBuildResult,
} from '../util/build/write-build-result'; } from '../util/build/write-build-result';
import { importBuilders, BuilderWithPkg } from '../util/build/import-builders'; import { importBuilders, BuilderWithPkg } from '../util/build/import-builders';
import { initCorepack, cleanupCorepack } from '../util/build/corepack';
type BuildResult = BuildResultV2 | BuildResultV3; type BuildResult = BuildResultV2 | BuildResultV3;
@@ -99,6 +102,7 @@ export default async function main(client: Client): Promise<number> {
const argv = getArgs(client.argv.slice(2), { const argv = getArgs(client.argv.slice(2), {
'--cwd': String, '--cwd': String,
'--prod': Boolean, '--prod': Boolean,
'--yes': Boolean,
}); });
if (argv['--help']) { if (argv['--help']) {
@@ -112,42 +116,53 @@ export default async function main(client: Client): Promise<number> {
} }
const cwd = process.cwd(); const cwd = process.cwd();
// Build `target` influences which environment variables will be used
const target = argv['--prod'] ? 'production' : 'preview';
const yes = Boolean(argv['--yes']);
// TODO: read project settings from the API, fall back to local `project.json` if that fails // TODO: read project settings from the API, fall back to local `project.json` if that fails
// Read project settings, and pull them from Vercel if necessary // Read project settings, and pull them from Vercel if necessary
let project = await readProjectSettings(join(cwd, VERCEL_DIR)); let project = await readProjectSettings(join(cwd, VERCEL_DIR));
const isTTY = process.stdin.isTTY; const isTTY = process.stdin.isTTY;
while (!project?.settings) { while (!project?.settings) {
if (!isTTY) { let confirmed = yes;
client.output.print( if (!confirmed) {
`No Project Settings found locally. Run ${cli.getCommandName( if (!isTTY) {
'pull --yes' client.output.print(
)} to retreive them.` `No Project Settings found locally. Run ${cli.getCommandName(
); 'pull --yes'
return 1; )} to retreive them.`
} );
return 1;
}
const confirmed = await confirm( confirmed = await confirm(
`No Project Settings found locally. Run ${cli.getCommandName( `No Project Settings found locally. Run ${cli.getCommandName(
'pull' 'pull'
)} for retrieving them?`, )} for retrieving them?`,
true true
); );
}
if (!confirmed) { if (!confirmed) {
client.output.print(`Aborted. No Project Settings retrieved.\n`); client.output.print(`Aborted. No Project Settings retrieved.\n`);
return 0; return 0;
} }
client.argv = []; const { argv: originalArgv } = client;
client.argv = [
...originalArgv.slice(0, 2),
'pull',
`--environment`,
target,
];
const result = await pull(client); const result = await pull(client);
if (result !== 0) { if (result !== 0) {
return result; return result;
} }
client.argv = originalArgv;
project = await readProjectSettings(join(cwd, VERCEL_DIR)); project = await readProjectSettings(join(cwd, VERCEL_DIR));
} }
// Build `target` influences which environment variables will be used
const target = argv['--prod'] ? 'production' : 'preview';
// TODO: load env vars from the API, fall back to local files if that fails // TODO: load env vars from the API, fall back to local files if that fails
const envPath = await checkExists([ const envPath = await checkExists([
@@ -177,7 +192,7 @@ export default async function main(client: Client): Promise<number> {
// 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 =>
relative(workPath, f) normalizePath(relative(workPath, f))
); );
const routesResult = getTransformedRoutes({ nowConfig: vercelConfig || {} }); const routesResult = getTransformedRoutes({ nowConfig: vercelConfig || {} });
@@ -265,19 +280,20 @@ export default async function main(client: Client): Promise<number> {
} }
// Delete output directory from potential previous build // Delete output directory from potential previous build
await fs.remove(OUTPUT_DIR); const outputDir = join(cwd, OUTPUT_DIR);
await fs.remove(outputDir);
const buildStamp = stamp(); const buildStamp = stamp();
// Create fresh new output directory // Create fresh new output directory
await fs.mkdirp(OUTPUT_DIR); await fs.mkdirp(outputDir);
const ops: Promise<Error | void>[] = []; const ops: Promise<Error | void>[] = [];
// Write the `detectedBuilders` result to output dir // Write the `detectedBuilders` result to output dir
ops.push( ops.push(
fs.writeJSON( fs.writeJSON(
join(OUTPUT_DIR, 'builds.json'), join(outputDir, 'builds.json'),
{ {
'//': 'This file was generated by the `vercel build` command. It is not part of the Build Output API.', '//': 'This file was generated by the `vercel build` command. It is not part of the Build Output API.',
target, target,
@@ -312,6 +328,10 @@ export default async function main(client: Client): Promise<number> {
// TODO: parallelize builds // TODO: parallelize builds
const buildResults: Map<Builder, BuildResult> = new Map(); const buildResults: Map<Builder, BuildResult> = new Map();
const overrides: PathOverride[] = []; const overrides: PathOverride[] = [];
const repoRootPath = cwd;
const rootPackageJsonPath = repoRootPath || workPath;
const corepackShimDir = await initCorepack({ cwd, rootPackageJsonPath });
for (const build of builds) { for (const build of builds) {
if (typeof build.src !== 'string') continue; if (typeof build.src !== 'string') continue;
@@ -331,7 +351,6 @@ export default async function main(client: Client): Promise<number> {
framework: project.settings.framework, framework: project.settings.framework,
nodeVersion: project.settings.nodeVersion, nodeVersion: project.settings.nodeVersion,
}; };
const repoRootPath = cwd === workPath ? undefined : cwd;
const buildOptions: BuildOptions = { const buildOptions: BuildOptions = {
files: filesMap, files: filesMap,
entrypoint: build.src, entrypoint: build.src,
@@ -352,6 +371,7 @@ export default async function main(client: Client): Promise<number> {
// Start flushing the file outputs to the filesystem asynchronously // Start flushing the file outputs to the filesystem asynchronously
ops.push( ops.push(
writeBuildResult( writeBuildResult(
outputDir,
buildResult, buildResult,
build, build,
builder, builder,
@@ -366,6 +386,10 @@ export default async function main(client: Client): Promise<number> {
); );
} }
if (corepackShimDir) {
cleanupCorepack(corepackShimDir);
}
// Wait for filesystem operations to complete // Wait for filesystem operations to complete
// TODO render progress bar? // TODO render progress bar?
let hadError = false; let hadError = false;
@@ -379,7 +403,7 @@ export default async function main(client: Client): Promise<number> {
if (hadError) return 1; if (hadError) return 1;
// Merge existing `config.json` file into the one that will be produced // Merge existing `config.json` file into the one that will be produced
const configPath = join(OUTPUT_DIR, 'config.json'); const configPath = join(outputDir, 'config.json');
// TODO: properly type // TODO: properly type
const existingConfig = await readJSONFile<any>(configPath); const existingConfig = await readJSONFile<any>(configPath);
if (existingConfig instanceof CantParseJSONFile) { if (existingConfig instanceof CantParseJSONFile) {
@@ -437,7 +461,7 @@ export default async function main(client: Client): Promise<number> {
wildcard: mergedWildcard, wildcard: mergedWildcard,
overrides: mergedOverrides, overrides: mergedOverrides,
}; };
await fs.writeJSON(join(OUTPUT_DIR, 'config.json'), config, { spaces: 2 }); await fs.writeJSON(join(outputDir, 'config.json'), config, { spaces: 2 });
output.print( output.print(
`${prependEmoji( `${prependEmoji(
@@ -452,17 +476,33 @@ export default async function main(client: Client): Promise<number> {
} }
function expandBuild(files: string[], build: Builder): Builder[] { function expandBuild(files: string[], build: Builder): Builder[] {
if (!build.src) return []; if (!build.use) {
throw new NowBuildError({
code: `invalid_build_specification`,
message: 'Field `use` is missing in build specification',
link: 'https://vercel.com/docs/configuration#project/builds',
action: 'View Documentation',
});
}
let pattern = build.src; let src = normalize(build.src || '**');
if (pattern[0] === '/') { if (src === '.' || src === './') {
throw new NowBuildError({
code: `invalid_build_specification`,
message: 'A build `src` path resolves to an empty string',
link: 'https://vercel.com/docs/configuration#project/builds',
action: 'View Documentation',
});
}
if (src[0] === '/') {
// Remove a leading slash so that the globbing is relative // Remove a leading slash so that the globbing is relative
// to `cwd` instead of the root of the filesystem. // to `cwd` instead of the root of the filesystem.
pattern = pattern.substring(1); src = src.substring(1);
} }
const matches = files.filter( const matches = files.filter(
name => name === pattern || minimatch(name, pattern, { dot: true }) name => name === src || minimatch(name, src, { dot: true })
); );
return matches.map(m => { return matches.map(m => {

View File

@@ -43,7 +43,9 @@ 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 from '../../util/input/edit-project-settings'; import editProjectSettings, {
PartialProjectSettings,
} from '../../util/input/edit-project-settings';
import { import {
getLinkedProject, getLinkedProject,
linkFolderToProject, linkFolderToProject,
@@ -63,6 +65,7 @@ import { help } from './args';
import { getDeploymentChecks } from '../../util/deploy/get-deployment-checks'; import { getDeploymentChecks } from '../../util/deploy/get-deployment-checks';
import parseTarget from '../../util/deploy/parse-target'; import parseTarget from '../../util/deploy/parse-target';
import getPrebuiltJson from '../../util/deploy/get-prebuilt-json'; import getPrebuiltJson from '../../util/deploy/get-prebuilt-json';
import { createGitMeta } from '../../util/deploy/create-git-meta';
export default async (client: Client) => { export default async (client: Client) => {
const { output } = client; const { output } = client;
@@ -415,6 +418,8 @@ export default async (client: Client) => {
parseMeta(argv['--meta']) parseMeta(argv['--meta'])
); );
const gitMetadata = await createGitMeta(path, output);
// Merge dotenv config, `env` from vercel.json, and `--env` / `-e` arguments // Merge dotenv config, `env` from vercel.json, and `--env` / `-e` arguments
const deploymentEnv = Object.assign( const deploymentEnv = Object.assign(
{}, {},
@@ -453,6 +458,15 @@ export default async (client: Client) => {
let deployStamp = stamp(); let deployStamp = stamp();
let deployment = null; let deployment = null;
const localConfigurationOverrides: PartialProjectSettings = {
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 = {
name: project ? project.name : newProjectName, name: project ? project.name : newProjectName,
@@ -468,6 +482,7 @@ export default async (client: Client) => {
nowConfig: localConfig, nowConfig: localConfig,
regions, regions,
meta, meta,
gitMetadata,
deployStamp, deployStamp,
target, target,
skipAutoDetectionConfirmation: autoConfirm, skipAutoDetectionConfirmation: autoConfirm,
@@ -475,7 +490,12 @@ export default async (client: Client) => {
if (!localConfig.builds || localConfig.builds.length === 0) { if (!localConfig.builds || localConfig.builds.length === 0) {
// Only add projectSettings for zero config deployments // Only add projectSettings for zero config deployments
createArgs.projectSettings = { sourceFilesOutsideRootDirectory }; createArgs.projectSettings =
status === 'not_linked'
? {
sourceFilesOutsideRootDirectory,
}
: { ...localConfigurationOverrides, sourceFilesOutsideRootDirectory };
} }
deployment = await createDeploy( deployment = await createDeploy(
@@ -503,7 +523,9 @@ export default async (client: Client) => {
const settings = await editProjectSettings( const settings = await editProjectSettings(
output, output,
projectSettings, projectSettings,
framework framework,
false,
localConfigurationOverrides
); );
// deploy again, but send projectSettings this time // deploy again, but send projectSettings this time

View File

@@ -1,4 +1,5 @@
import { resolve, join } from 'path'; import { resolve, join } from 'path';
import fs from 'fs-extra';
import DevServer from '../../util/dev/server'; import DevServer from '../../util/dev/server';
import parseListen from '../../util/dev/parse-listen'; import parseListen from '../../util/dev/parse-listen';
@@ -12,6 +13,7 @@ import setupAndLink from '../../util/link/setup-and-link';
import getSystemEnvValues from '../../util/env/get-system-env-values'; import getSystemEnvValues from '../../util/env/get-system-env-values';
import { getCommandName } from '../../util/pkg-name'; import { getCommandName } from '../../util/pkg-name';
import param from '../../util/output/param'; import param from '../../util/output/param';
import { OUTPUT_DIR } from '../../util/build/write-build-result';
type Options = { type Options = {
'--listen': string; '--listen': string;
@@ -104,6 +106,15 @@ export default async function dev(
devCommand = process.env.VERCEL_DEV_COMMAND; devCommand = process.env.VERCEL_DEV_COMMAND;
} }
// If there is no Development Command, we must delete the
// v3 Build Output because it will incorrectly be detected by
// @vercel/static-build in BuildOutputV3.getBuildOutputDirectory()
if (!devCommand) {
output.log(`Removing ${OUTPUT_DIR}`);
const outputDir = join(cwd, OUTPUT_DIR);
await fs.remove(outputDir);
}
const devServer = new DevServer(cwd, { const devServer = new DevServer(cwd, {
output, output,
devCommand, devCommand,

View File

@@ -150,17 +150,7 @@ export default async function inspect(
`This Domain is not configured properly. To configure it you should either:`, `This Domain is not configured properly. To configure it you should either:`,
null, null,
null, null,
null, null
{
boxen: {
margin: {
left: 2,
right: 0,
bottom: 0,
top: 0,
},
},
}
); );
output.print( output.print(
` ${chalk.grey('a)')} ` + ` ${chalk.grey('a)')} ` +

View File

@@ -42,6 +42,13 @@ const help = () => {
${chalk.dim('Examples:')} ${chalk.dim('Examples:')}
${chalk.gray(
''
)} Pull all Development Environment Variables down from the cloud
${chalk.cyan(`$ ${getPkgName()} env pull <file>`)}
${chalk.cyan(`$ ${getPkgName()} env pull .env.development.local`)}
${chalk.gray('')} Add a new variable to multiple Environments ${chalk.gray('')} Add a new variable to multiple Environments
${chalk.cyan(`$ ${getPkgName()} env add <name>`)} ${chalk.cyan(`$ ${getPkgName()} env add <name>`)}

View File

@@ -9,6 +9,7 @@ export default new Map([
['certs', 'certs'], ['certs', 'certs'],
['deploy', 'deploy'], ['deploy', 'deploy'],
['dev', 'dev'], ['dev', 'dev'],
['develop', 'dev'],
['dns', 'dns'], ['dns', 'dns'],
['domain', 'domains'], ['domain', 'domains'],
['domains', 'domains'], ['domains', 'domains'],

View File

@@ -25,7 +25,7 @@ import {
const help = () => { const help = () => {
return console.log(` return console.log(`
${chalk.bold(`${logo} ${getPkgName()} pull`)} [path] ${chalk.bold(`${logo} ${getPkgName()} pull`)} [project-path]
${chalk.dim('Options:')} ${chalk.dim('Options:')}
@@ -42,25 +42,29 @@ const help = () => {
${chalk.dim('Examples:')} ${chalk.dim('Examples:')}
${chalk.gray('')} Pull the latest Project Settings from the cloud ${chalk.gray(
''
)} Pull the latest Environment Variables and Project Settings from the cloud
and stores them in \`.vercel/.env.\${target}.local\` and \`.vercel/project.json\` respectively.
${chalk.cyan(`$ ${getPkgName()} pull`)} ${chalk.cyan(`$ ${getPkgName()} pull`)}
${chalk.cyan(`$ ${getPkgName()} pull ./path-to-project`)} ${chalk.cyan(`$ ${getPkgName()} pull ./path-to-project`)}
${chalk.cyan(`$ ${getPkgName()} pull --env .env.local`)}
${chalk.cyan(`$ ${getPkgName()} pull ./path-to-project --env .env.local`)}
${chalk.gray('')} Pull specific environment's Project Settings from the cloud ${chalk.gray('')} Pull for a specific environment
${chalk.cyan( ${chalk.cyan(
`$ ${getPkgName()} pull --environment=${getEnvTargetPlaceholder()}` `$ ${getPkgName()} pull --environment=${getEnvTargetPlaceholder()}`
)} )}
${chalk.gray(
'If you want to download environment variables to a specific file, use `vercel env pull` instead.'
)}
`); `);
}; };
function processArgs(client: Client) { function processArgs(client: Client) {
return getArgs(client.argv.slice(2), { return getArgs(client.argv.slice(2), {
'--yes': Boolean, '--yes': Boolean,
'--env': String, // deprecated
'--environment': String, '--environment': String,
'--debug': Boolean, '--debug': Boolean,
'-d': '--debug', '-d': '--debug',

View File

@@ -58,6 +58,7 @@ const isCanary = pkg.version.includes('canary');
const notifier = updateNotifier({ const notifier = updateNotifier({
pkg, pkg,
distTag: isCanary ? 'canary' : 'latest', distTag: isCanary ? 'canary' : 'latest',
updateCheckInterval: 1000 * 60 * 60 * 24 * 7, // 1 week
}); });
const VERCEL_DIR = getGlobalPathConfig(); const VERCEL_DIR = getGlobalPathConfig();

View File

@@ -296,6 +296,15 @@ export interface Token {
teamId?: string; teamId?: string;
} }
export interface GitMetadata {
commitAuthorName?: string | undefined;
commitMessage?: string | undefined;
commitRef?: string | undefined;
commitSha?: string | undefined;
dirty?: boolean | undefined;
remoteUrl: string;
}
/** /**
* An object representing a Build on Vercel * An object representing a Build on Vercel
*/ */

View File

@@ -0,0 +1,82 @@
import { delimiter, join } from 'path';
import { PackageJson, spawnAsync } from '@vercel/build-utils';
import fs from 'fs-extra';
import { CantParseJSONFile } from '../errors-ts';
import { VERCEL_DIR } from '../projects/link';
import readJSONFile from '../read-json-file';
export async function initCorepack({
cwd,
rootPackageJsonPath,
}: {
cwd: string;
rootPackageJsonPath: string;
}): Promise<string | null> {
if (process.env.ENABLE_EXPERIMENTAL_COREPACK !== '1') {
// Since corepack is experimental, we need to exit early
// unless the user explicitly enables it with the env var.
return null;
}
const pkg = await readJSONFile<PackageJson>(
join(rootPackageJsonPath, 'package.json')
);
if (pkg instanceof CantParseJSONFile) {
console.warn(
'Warning: Could not enable corepack because package.json is invalid JSON'
);
} else if (!pkg?.packageManager) {
console.warn(
'Warning: Could not enable corepack because package.json is missing "packageManager" property'
);
} else {
console.log(
`Detected ENABLE_EXPERIMENTAL_COREPACK=1 and "${pkg.packageManager}" in package.json`
);
const corepackRootDir = join(cwd, VERCEL_DIR, 'cache', 'corepack');
const corepackHomeDir = join(corepackRootDir, 'home');
const corepackShimDir = join(corepackRootDir, 'shim');
await fs.mkdirp(corepackHomeDir);
await fs.mkdirp(corepackShimDir);
process.env.COREPACK_HOME = corepackHomeDir;
process.env.PATH = `${corepackShimDir}${delimiter}${process.env.PATH}`;
process.env.DEBUG = process.env.DEBUG
? `corepack,${process.env.DEBUG}`
: 'corepack';
const pkgManagerName = pkg.packageManager.split('@')[0];
// We must explicitly call `corepack enable npm` since `corepack enable`
// doesn't work with npm. See https://github.com/nodejs/corepack/pull/24
// Also, `corepack enable` is too broad and will change the verison of
// yarn & pnpm even though those versions are not specified by the user.
// See https://github.com/nodejs/corepack#known-good-releases
// Finally, we use `--install-directory` so we can cache the result to
// reuse for subsequent builds. See `@vercel/vc-build` for `prepareCache`.
await spawnAsync(
'corepack',
['enable', pkgManagerName, '--install-directory', corepackShimDir],
{
prettyCommand: `corepack enable ${pkgManagerName}`,
}
);
return corepackShimDir;
}
return null;
}
export function cleanupCorepack(corepackShimDir: string) {
if (process.env.COREPACK_HOME) {
delete process.env.COREPACK_HOME;
}
if (process.env.PATH) {
process.env.PATH = process.env.PATH.replace(
`${corepackShimDir}${delimiter}`,
''
);
}
if (process.env.DEBUG) {
if (process.env.DEBUG === 'corepack') {
delete process.env.DEBUG;
} else {
process.env.DEBUG = process.env.DEBUG.replace('corepack,', '');
}
}
}

View File

@@ -106,9 +106,10 @@ export async function resolveBuilders(
// If `pkgPath` wasn't found in `.vercel/builders` then try as a CLI local // If `pkgPath` wasn't found in `.vercel/builders` then try as a CLI local
// dependency. `require.resolve()` will throw if the Builder is not a CLI // dependency. `require.resolve()` will throw if the Builder is not a CLI
// dep, in which case we'll install it into `.vercel/builders`. // dep, in which case we'll install it into `.vercel/builders`.
pkgPath = require.resolve(`${name}/package.json`, { // NOTE: `eval('require')` is necessary to avoid bad transpilation to `__webpack_require__`
pkgPath = eval('require').resolve(`${name}/package.json`, {
paths: [__dirname], paths: [__dirname],
}); }) as string;
builderPkg = await readJSON(pkgPath); builderPkg = await readJSON(pkgPath);
} }
@@ -148,7 +149,9 @@ export async function resolveBuilders(
// TODO: handle `parsed.type === 'tag'` ("latest" vs. anything else?) // TODO: handle `parsed.type === 'tag'` ("latest" vs. anything else?)
const path = join(dirname(pkgPath), builderPkg.main || 'index.js'); const path = join(dirname(pkgPath), builderPkg.main || 'index.js');
const builder = require(path);
// NOTE: `eval('require')` is necessary to avoid bad transpilation to `__webpack_require__`
const builder = eval('require')(path);
builders.set(spec, { builders.set(spec, {
builder, builder,

View File

@@ -23,6 +23,7 @@ import { VERCEL_DIR } from '../projects/link';
export const OUTPUT_DIR = join(VERCEL_DIR, 'output'); export const OUTPUT_DIR = join(VERCEL_DIR, 'output');
export async function writeBuildResult( export async function writeBuildResult(
outputDir: string,
buildResult: BuildResultV2 | BuildResultV3, buildResult: BuildResultV2 | BuildResultV3,
build: Builder, build: Builder,
builder: BuilderV2 | BuilderV3, builder: BuilderV2 | BuilderV3,
@@ -31,9 +32,13 @@ export async function writeBuildResult(
) { ) {
const { version } = builder; const { version } = builder;
if (version === 2) { if (version === 2) {
return writeBuildResultV2(buildResult as BuildResultV2, cleanUrls); return writeBuildResultV2(
outputDir,
buildResult as BuildResultV2,
cleanUrls
);
} else if (version === 3) { } else if (version === 3) {
return writeBuildResultV3(buildResult as BuildResultV3, build); return writeBuildResultV3(outputDir, buildResult as BuildResultV3, build);
} }
throw new Error( throw new Error(
`Unsupported Builder version \`${version}\` from "${builderPkg.name}"` `Unsupported Builder version \`${version}\` from "${builderPkg.name}"`
@@ -67,11 +72,12 @@ export interface PathOverride {
* the filesystem. * the filesystem.
*/ */
async function writeBuildResultV2( async function writeBuildResultV2(
outputDir: string,
buildResult: BuildResultV2, buildResult: BuildResultV2,
cleanUrls?: boolean cleanUrls?: boolean
) { ) {
if ('buildOutputPath' in buildResult) { if ('buildOutputPath' in buildResult) {
await mergeBuilderOutput(buildResult); await mergeBuilderOutput(outputDir, buildResult);
return; return;
} }
@@ -79,16 +85,16 @@ async function writeBuildResultV2(
const overrides: Record<string, PathOverride> = {}; const overrides: Record<string, PathOverride> = {};
for (const [path, output] of Object.entries(buildResult.output)) { for (const [path, output] of Object.entries(buildResult.output)) {
if (isLambda(output)) { if (isLambda(output)) {
await writeLambda(output, path, lambdas); await writeLambda(outputDir, output, path, lambdas);
} else if (isPrerender(output)) { } else if (isPrerender(output)) {
await writeLambda(output.lambda, path, lambdas); await writeLambda(outputDir, output.lambda, path, lambdas);
// Write the fallback file alongside the Lambda directory // Write the fallback file alongside the Lambda directory
let fallback = output.fallback; let fallback = output.fallback;
if (fallback) { if (fallback) {
const ext = getFileExtension(fallback); const ext = getFileExtension(fallback);
const fallbackName = `${path}.prerender-fallback${ext}`; const fallbackName = `${path}.prerender-fallback${ext}`;
const fallbackPath = join(OUTPUT_DIR, 'functions', fallbackName); const fallbackPath = join(outputDir, 'functions', fallbackName);
const stream = fallback.toStream(); const stream = fallback.toStream();
await pipe( await pipe(
stream, stream,
@@ -101,7 +107,7 @@ async function writeBuildResultV2(
} }
const prerenderConfigPath = join( const prerenderConfigPath = join(
OUTPUT_DIR, outputDir,
'functions', 'functions',
`${path}.prerender-config.json` `${path}.prerender-config.json`
); );
@@ -112,9 +118,9 @@ async function writeBuildResultV2(
}; };
await fs.writeJSON(prerenderConfigPath, prerenderConfig, { spaces: 2 }); await fs.writeJSON(prerenderConfigPath, prerenderConfig, { spaces: 2 });
} else if (isFile(output)) { } else if (isFile(output)) {
await writeStaticFile(output, path, overrides, cleanUrls); await writeStaticFile(outputDir, output, path, overrides, cleanUrls);
} else if (isEdgeFunction(output)) { } else if (isEdgeFunction(output)) {
await writeEdgeFunction(output, path); await writeEdgeFunction(outputDir, output, path);
} else { } else {
throw new Error( throw new Error(
`Unsupported output type: "${(output as any).type}" for ${path}` `Unsupported output type: "${(output as any).type}" for ${path}`
@@ -128,15 +134,24 @@ async function writeBuildResultV2(
* Writes the output from the `build()` return value of a v3 Builder to * Writes the output from the `build()` return value of a v3 Builder to
* the filesystem. * the filesystem.
*/ */
async function writeBuildResultV3(buildResult: BuildResultV3, build: Builder) { async function writeBuildResultV3(
outputDir: string,
buildResult: BuildResultV3,
build: Builder
) {
const { output } = buildResult; const { output } = buildResult;
const src = build.src;
if (typeof src !== 'string') {
throw new Error(`Expected "build.src" to be a string`);
}
const ext = extname(src);
const path = build.config?.zeroConfig
? src.substring(0, src.length - ext.length)
: src;
if (isLambda(output)) { if (isLambda(output)) {
const src = build.src!; await writeLambda(outputDir, output, path);
const ext = extname(src); } else if (isEdgeFunction(output)) {
const path = build.config?.zeroConfig await writeEdgeFunction(outputDir, output, path);
? src.substring(0, src.length - ext.length)
: src;
await writeLambda(output, path);
} else { } else {
throw new Error( throw new Error(
`Unsupported output type: "${(output as any).type}" for ${build.src}` `Unsupported output type: "${(output as any).type}" for ${build.src}`
@@ -154,6 +169,7 @@ async function writeBuildResultV3(buildResult: BuildResultV3, build: Builder) {
* @param overrides Record of override configuration when a File is renamed or has other metadata * @param overrides Record of override configuration when a File is renamed or has other metadata
*/ */
async function writeStaticFile( async function writeStaticFile(
outputDir: string,
file: File, file: File,
path: string, path: string,
overrides: Record<string, PathOverride>, overrides: Record<string, PathOverride>,
@@ -191,7 +207,7 @@ async function writeStaticFile(
overrides[fsPath] = override; overrides[fsPath] = override;
} }
const dest = join(OUTPUT_DIR, 'static', fsPath); const dest = join(outputDir, 'static', fsPath);
await fs.mkdirp(dirname(dest)); await fs.mkdirp(dirname(dest));
// TODO: handle (or skip) symlinks? // TODO: handle (or skip) symlinks?
@@ -205,8 +221,12 @@ async function writeStaticFile(
* @param edgeFunction The `EdgeFunction` instance * @param edgeFunction The `EdgeFunction` instance
* @param path The URL path where the `EdgeFunction` can be accessed from * @param path The URL path where the `EdgeFunction` can be accessed from
*/ */
async function writeEdgeFunction(edgeFunction: EdgeFunction, path: string) { async function writeEdgeFunction(
const dest = join(OUTPUT_DIR, 'functions', `${path}.func`); outputDir: string,
edgeFunction: EdgeFunction,
path: string
) {
const dest = join(outputDir, 'functions', `${path}.func`);
await fs.mkdirp(dest); await fs.mkdirp(dest);
const ops: Promise<any>[] = []; const ops: Promise<any>[] = [];
@@ -235,11 +255,12 @@ async function writeEdgeFunction(edgeFunction: EdgeFunction, path: string) {
* @param lambdas (optional) Map of `Lambda` instances that have previously been written * @param lambdas (optional) Map of `Lambda` instances that have previously been written
*/ */
async function writeLambda( async function writeLambda(
outputDir: string,
lambda: Lambda, lambda: Lambda,
path: string, path: string,
lambdas?: Map<Lambda, string> lambdas?: Map<Lambda, string>
) { ) {
const dest = join(OUTPUT_DIR, 'functions', `${path}.func`); const dest = join(outputDir, 'functions', `${path}.func`);
// If the `lambda` has already been written to the filesystem at a different // If the `lambda` has already been written to the filesystem at a different
// location then create a symlink to the previous location instead of copying // location then create a symlink to the previous location instead of copying
@@ -248,7 +269,7 @@ async function writeLambda(
if (existingLambdaPath) { if (existingLambdaPath) {
const destDir = dirname(dest); const destDir = dirname(dest);
const targetDest = join( const targetDest = join(
OUTPUT_DIR, outputDir,
'functions', 'functions',
`${existingLambdaPath}.func` `${existingLambdaPath}.func`
); );
@@ -312,14 +333,17 @@ async function writeLambda(
* `.vercel/output` directory that was specified by the Builder into the * `.vercel/output` directory that was specified by the Builder into the
* `vc build` output directory. * `vc build` output directory.
*/ */
async function mergeBuilderOutput(buildResult: BuildResultBuildOutput) { async function mergeBuilderOutput(
const absOutputDir = resolve(OUTPUT_DIR); outputDir: string,
buildResult: BuildResultBuildOutput
) {
const absOutputDir = resolve(outputDir);
if (absOutputDir === buildResult.buildOutputPath) { if (absOutputDir === buildResult.buildOutputPath) {
// `.vercel/output` dir is already in the correct location, // `.vercel/output` dir is already in the correct location,
// so no need to do anything // so no need to do anything
return; return;
} }
await fs.copy(buildResult.buildOutputPath, OUTPUT_DIR); await fs.copy(buildResult.buildOutputPath, outputDir);
} }
/** /**

View File

@@ -0,0 +1,79 @@
import fs from 'fs-extra';
import { join } from 'path';
import ini from 'ini';
import git from 'git-last-commit';
import { exec } from 'child_process';
import { GitMetadata } from '../../types';
import { Output } from '../output';
export function isDirty(directory: string): Promise<boolean> {
return new Promise((resolve, reject) => {
exec('git status -s', { cwd: directory }, function (err, stdout, stderr) {
if (err) return reject(err);
if (stderr)
return reject(
new Error(
`Failed to determine if git repo has been modified: ${stderr.trim()}`
)
);
resolve(stdout.trim().length > 0);
});
});
}
function getLastCommit(directory: string): Promise<git.Commit> {
return new Promise((resolve, reject) => {
git.getLastCommit(
(err, commit) => {
if (err) return reject(err);
resolve(commit);
},
{ dst: directory }
);
});
}
export async function getRemoteUrl(
configPath: string,
output: Output
): Promise<string | null> {
let gitConfig;
try {
gitConfig = ini.parse(await fs.readFile(configPath, 'utf-8'));
} catch (error) {
output.debug(`Error while parsing repo data: ${error.message}`);
}
if (!gitConfig) {
return null;
}
const originUrl: string = gitConfig['remote "origin"']?.url;
if (originUrl) {
return originUrl;
}
return null;
}
export async function createGitMeta(
directory: string,
output: Output
): Promise<GitMetadata | undefined> {
const remoteUrl = await getRemoteUrl(join(directory, '.git/config'), output);
// If we can't get the repo URL, then don't return any metadata
if (!remoteUrl) {
return;
}
const [commit, dirty] = await Promise.all([
getLastCommit(directory),
isDirty(directory),
]);
return {
remoteUrl,
commitAuthorName: commit.author.name,
commitMessage: commit.subject,
commitRef: commit.branch,
commitSha: commit.hash,
dirty,
};
}

View File

@@ -142,6 +142,7 @@ export async function executeBuild(
files, files,
entrypoint, entrypoint,
workPath, workPath,
repoRootPath: workPath,
config, config,
meta: { meta: {
isDev: true, isDev: true,
@@ -416,10 +417,6 @@ export async function getBuildMatches(
src = src.substring(1); src = src.substring(1);
} }
// We need to escape brackets since `glob` will
// try to find a group otherwise
src = src.replace(/(\[|\])/g, '[$1]');
// lambda function files are trimmed of their file extension // lambda function files are trimmed of their file extension
const mapToEntrypoint = new Map<string, string>(); const mapToEntrypoint = new Map<string, string>();
const extensionless = devServer.getExtensionlessFile(src); const extensionless = devServer.getExtensionlessFile(src);
@@ -428,6 +425,10 @@ export async function getBuildMatches(
src = extensionless; src = extensionless;
} }
// We need to escape brackets since `glob` will
// try to find a group otherwise
src = src.replace(/(\[|\])/g, '[$1]');
const files = fileList const files = fileList
.filter(name => name === src || minimatch(name, src, { dot: true })) .filter(name => name === src || minimatch(name, src, { dot: true }))
.map(name => join(cwd, name)); .map(name => join(cwd, name));

View File

@@ -1735,6 +1735,7 @@ export default class DevServer {
entrypoint: match.entrypoint, entrypoint: match.entrypoint,
workPath, workPath,
config: match.config || {}, config: match.config || {},
repoRootPath: this.cwd,
meta: { meta: {
isDev: true, isDev: true,
requestPath, requestPath,
@@ -2446,12 +2447,10 @@ function needsBlockingBuild(buildMatch: BuildMatch): boolean {
return typeof builder.shouldServe !== 'function'; return typeof builder.shouldServe !== 'function';
} }
async function checkForPort( async function checkForPort(port: number, timeout: number): Promise<void> {
port: number | undefined, const opts = { host: '127.0.0.1' };
timeout: number
): Promise<void> {
const start = Date.now(); const start = Date.now();
while (!(await isPortReachable(port))) { while (!(await isPortReachable(port, opts))) {
if (Date.now() - start > timeout) { if (Date.now() - start > timeout) {
throw new Error(`Detecting port ${port} timed out after ${timeout}ms`); throw new Error(`Detecting port ${port} timed out after ${timeout}ms`);
} }

View File

@@ -1,5 +1,4 @@
import { resolve } from 'path'; import { resolve } from 'path';
import _glob, { IOptions as GlobOptions } from 'glob';
import fs from 'fs-extra'; import fs from 'fs-extra';
import { getVercelIgnore } from '@vercel/client'; import { getVercelIgnore } from '@vercel/client';
import uniqueStrings from './unique-strings'; import uniqueStrings from './unique-strings';
@@ -21,14 +20,6 @@ function flatten(
return res; return res;
} }
async function glob(pattern: string, options: GlobOptions): Promise<string[]> {
return new Promise((resolve, reject) => {
_glob(pattern, options, (err, files) => {
err ? reject(err) : resolve(files);
});
});
}
/** /**
* Transform relative paths into absolutes, * Transform relative paths into absolutes,
* and maintains absolutes as such. * and maintains absolutes as such.
@@ -65,15 +56,16 @@ interface StaticFilesOptions {
export async function staticFiles( export async function staticFiles(
path: string, path: string,
{ output, src }: StaticFilesOptions { output, src }: StaticFilesOptions
) { ): Promise<string[]> {
const { debug, time } = output; const { debug, time } = output;
let files: string[] = []; let files: string[] = [];
// The package.json `files` whitelist still // The package.json `files` whitelist still
// honors ignores: https://docs.npmjs.com/files/package.json#files // honors ignores: https://docs.npmjs.com/files/package.json#files
const source = src || '.'; const source = src || '.';
// Convert all filenames into absolute paths
const search = await glob(source, { cwd: path, absolute: true, dot: true }); // Ensure that `path` is an absolute path
const search = resolve(path, source);
// Compile list of ignored patterns and files // Compile list of ignored patterns and files
const { ig } = await getVercelIgnore(path); const { ig } = await getVercelIgnore(path);
@@ -104,7 +96,7 @@ export async function staticFiles(
// Locate files // Locate files
files = await time( files = await time(
`Locating files ${path}`, `Locating files ${path}`,
explode(search, { explode([search], {
accepts, accepts,
output, output,
}) })
@@ -164,7 +156,7 @@ async function explode(
const all = await fs.readdir(file); const all = await fs.readdir(file);
/* eslint-disable no-use-before-define */ /* eslint-disable no-use-before-define */
const recursive = many(all.map(subdir => asAbsolute(subdir, file))); const recursive = many(all.map(subdir => asAbsolute(subdir, file)));
return (recursive as any) as Promise<string | null>; return recursive as any as Promise<string | null>;
/* eslint-enable no-use-before-define */ /* eslint-enable no-use-before-define */
} }
if (!s.isFile()) { if (!s.isFile()) {

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