Compare commits

...

40 Commits

Author SHA1 Message Date
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
164 changed files with 5149 additions and 2306 deletions

View File

@@ -23,7 +23,7 @@ Make sure all the tests pass before making changes.
## 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
@@ -64,7 +64,7 @@ Integration tests create deployments to your Vercel account using the `test` pro
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.
@@ -82,11 +82,11 @@ nodeFileTrace(['path/to/entrypoint.js'], {
.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
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`
2. Run `yarn build` to compile typescript and other build steps

View File

@@ -18,11 +18,13 @@ jobs:
os: [ubuntu-latest]
node: [14]
runs-on: ${{ matrix.os }}
env:
TURBO_REMOTE_ONLY: true
TURBO_TEAM: vercel
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
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
with:
go-version: '1.13.15'

View File

@@ -18,11 +18,13 @@ jobs:
os: [ubuntu-latest, macos-latest, windows-latest]
node: [14]
runs-on: ${{ matrix.os }}
env:
TURBO_REMOTE_ONLY: true
TURBO_TEAM: vercel
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
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
with:
go-version: '1.13.15'
@@ -44,4 +46,4 @@ jobs:
- run: yarn workspace vercel run coverage
if: matrix.os == 'ubuntu-latest' && matrix.node == 14 # only run coverage once
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -38,10 +38,6 @@ jobs:
runs-on: ${{ matrix.runner }}
name: ${{matrix.scriptName}} (${{matrix.packageName}}, ${{matrix.chunkNumber}}, ${{ matrix.runner }})
if: ${{ needs.setup.outputs['tests'] != '[]' }}
env:
TURBO_REMOTE_ONLY: true
TURBO_TEAM: vercel
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
needs:
- setup
strategy:
@@ -49,6 +45,12 @@ jobs:
matrix:
include: ${{ fromJson(needs.setup.outputs['tests']) }}
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
with:
fetch-depth: 2
@@ -59,11 +61,11 @@ jobs:
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'yarn'
- name: Install Hugo
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: yarn install --network-timeout 1000000
- name: Build ${{matrix.packageName}} and all its dependencies

View File

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

View File

@@ -22,8 +22,5 @@
"@types/react-dom": "^17.0.9",
"typescript": "^4.1.2"
},
"engines": {
"node": "14.x"
},
"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.example
.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
# create a new project in the current directory
npm init svelte@next
npm init svelte
# create a new project in my-app
npm init svelte@next my-app
npm init svelte my-app
```
> Note: the `@next` is temporary
## 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
npm run dev
pnpm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
pnpm run dev -- --open
```
## 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
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": {
"baseUrl": ".",
"paths": {
"$lib": ["src/lib"],
"$lib/*": ["src/lib/*"]
}
},
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"]
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,22 +1,29 @@
{
"private": true,
"name": "sveltekit",
"version": "0.0.1",
"scripts": {
"dev": "svelte-kit dev",
"build": "svelte-kit build",
"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": {
"@sveltejs/adapter-auto": "next",
"@sveltejs/kit": "next",
"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",
"typescript": "~4.6.2"
},
"type": "module",
"dependencies": {
"@fontsource/fira-mono": "^4.5.0",
"@lukeed/uuid": "^2.0.0",
"cookie": "^0.4.1"
}
}

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@@ -2,54 +2,81 @@ import { invalidate } from '$app/navigation';
// this action (https://svelte.dev/tutorial/actions) allows us to
// 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 } = {}) {
let current_token;
let current_token;
async function handle_submit(e) {
const token = (current_token = {});
/** @param {SubmitEvent} e */
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 {
const response = await fetch(form.action, {
method: form.method,
headers: {
accept: 'application/json'
},
body: data
});
try {
const response = await fetch(form.action, {
method: form.method,
headers: {
accept: 'application/json'
},
body: data
});
if (token !== current_token) return;
if (token !== current_token) return;
if (response.ok) {
if (result) result({ data, form, response });
if (response.ok) {
if (result) result({ data, form, response });
const url = new URL(form.action);
url.search = url.hash = '';
invalidate(url.href);
} else if (error) {
error({ data, form, error: null, response });
} else {
console.error(await response.text());
}
} catch (e) {
if (error) {
error({ data, form, error: e, response: null });
} else {
throw e;
}
}
}
const url = new URL(form.action);
url.search = url.hash = '';
invalidate(url.href);
} else if (error) {
error({ data, form, error: null, response });
} else {
console.error(await response.text());
}
} catch (e) {
if (error && e instanceof Error) {
error({ data, form, error: e, response: null });
} else {
throw e;
}
}
}
form.addEventListener('submit', handle_submit);
form.addEventListener('submit', handle_submit);
return {
destroy() {
form.removeEventListener('submit', handle_submit);
}
};
return {
destroy() {
form.removeEventListener('submit', handle_submit);
}
};
}

View File

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

View File

@@ -1,45 +1,45 @@
<script>
import Header from '$lib/header/Header.svelte';
import '../app.css';
import Header from '$lib/header/Header.svelte';
import '../app.css';
</script>
<Header />
<main>
<slot />
<slot />
</main>
<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>
<style>
main {
flex: 1;
display: flex;
flex-direction: column;
padding: 1rem;
width: 100%;
max-width: 1024px;
margin: 0 auto;
box-sizing: border-box;
}
main {
flex: 1;
display: flex;
flex-direction: column;
padding: 1rem;
width: 100%;
max-width: 1024px;
margin: 0 auto;
box-sizing: border-box;
}
footer {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 40px;
}
footer {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 40px;
}
footer a {
font-weight: bold;
}
footer a {
font-weight: bold;
}
@media (min-width: 480px) {
footer {
padding: 40px 0;
}
}
@media (min-width: 480px) {
footer {
padding: 40px 0;
}
}
</style>

View File

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

View File

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

View File

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

View File

@@ -1,66 +1,70 @@
import { api } from './_api';
/** @type {import('./__types').RequestHandler} */
export const get = async ({ locals }) => {
// locals.userid comes from src/hooks.js
const response = await api('get', `todos/${locals.userid}`);
// locals.userid comes from src/hooks.js
const response = await api('get', `todos/${locals.userid}`);
if (response.status === 404) {
// user hasn't created a todo list.
// start with an empty array
return {
body: {
todos: []
}
};
}
if (response.status === 404) {
// user hasn't created a todo list.
// start with an empty array
return {
body: {
todos: []
}
};
}
if (response.status === 200) {
return {
body: {
todos: await response.json()
}
};
}
if (response.status === 200) {
return {
body: {
todos: await response.json()
}
};
}
return {
status: response.status
};
return {
status: response.status
};
};
/** @type {import('./index').RequestHandler} */
export const post = async ({ request, locals }) => {
const form = await request.formData();
const form = await request.formData();
await api('post', `todos/${locals.userid}`, {
text: form.get('text')
});
await api('post', `todos/${locals.userid}`, {
text: form.get('text')
});
return {};
return {};
};
// If the user has JavaScript disabled, the URL will change to
// include the method override unless we redirect back to /todos
const redirect = {
status: 303,
headers: {
location: '/todos'
}
status: 303,
headers: {
location: '/todos'
}
};
/** @type {import('./index').RequestHandler} */
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')}`, {
text: form.has('text') ? form.get('text') : undefined,
done: form.has('done') ? !!form.get('done') : undefined
});
await api('patch', `todos/${locals.userid}/${form.get('uid')}`, {
text: form.has('text') ? form.get('text') : undefined,
done: form.has('done') ? !!form.get('done') : undefined
});
return redirect;
return redirect;
};
/** @type {import('./index').RequestHandler} */
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>
import { enhance } from '$lib/form';
import { scale } from 'svelte/transition';
import { flip } from 'svelte/animate';
import { enhance } from '$lib/form';
import { scale } from 'svelte/transition';
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>
<svelte:head>
<title>Todos</title>
<title>Todos</title>
<meta name="description" content="A todo list app" />
</svelte:head>
<div class="todos">
<h1>Todos</h1>
<h1>Todos</h1>
<form
class="new"
action="/todos"
method="post"
use:enhance={{
result: async ({ form }) => {
form.reset();
}
}}
>
<input name="text" aria-label="Add todo" placeholder="+ tap to add a todo" />
</form>
<form
class="new"
action="/todos"
method="post"
use:enhance={{
result: async ({ form }) => {
form.reset();
}
}}
>
<input name="text" aria-label="Add todo" placeholder="+ tap to add a todo" />
</form>
{#each todos as todo (todo.uid)}
<div
class="todo"
class:done={todo.done}
transition:scale|local={{ start: 0.7 }}
animate:flip={{ duration: 200 }}
>
<form
action="/todos?_method=PATCH"
method="post"
use:enhance={{
pending: ({ data }) => {
todo.done = !!data.get('done');
}
}}
>
<input type="hidden" name="uid" value={todo.uid} />
<input type="hidden" name="done" value={todo.done ? '' : 'true'} />
<button class="toggle" aria-label="Mark todo as {todo.done ? 'not done' : 'done'}" />
</form>
{#each todos as todo (todo.uid)}
<div
class="todo"
class:done={todo.done}
transition:scale|local={{ start: 0.7 }}
animate:flip={{ duration: 200 }}
>
<form
action="/todos?_method=PATCH"
method="post"
use:enhance={{
pending: ({ data }) => {
todo.done = !!data.get('done');
}
}}
>
<input type="hidden" name="uid" value={todo.uid} />
<input type="hidden" name="done" value={todo.done ? '' : 'true'} />
<button class="toggle" aria-label="Mark todo as {todo.done ? 'not done' : 'done'}" />
</form>
<form class="text" action="/todos?_method=PATCH" method="post" use:enhance>
<input type="hidden" name="uid" value={todo.uid} />
<input aria-label="Edit todo" type="text" name="text" value={todo.text} />
<button class="save" aria-label="Save todo" />
</form>
<form class="text" action="/todos?_method=PATCH" method="post" use:enhance>
<input type="hidden" name="uid" value={todo.uid} />
<input aria-label="Edit todo" type="text" name="text" value={todo.text} />
<button class="save" aria-label="Save todo" />
</form>
<form
action="/todos?_method=DELETE"
method="post"
use:enhance={{
pending: () => (todo.pending_delete = true)
}}
>
<input type="hidden" name="uid" value={todo.uid} />
<button class="delete" aria-label="Delete todo" disabled={todo.pending_delete} />
</form>
</div>
{/each}
<form
action="/todos?_method=DELETE"
method="post"
use:enhance={{
pending: () => (todo.pending_delete = true)
}}
>
<input type="hidden" name="uid" value={todo.uid} />
<button class="delete" aria-label="Delete todo" disabled={todo.pending_delete} />
</form>
</div>
{/each}
</div>
<style>
.todos {
width: 100%;
max-width: var(--column-width);
margin: var(--column-margin-top) auto 0 auto;
line-height: 1;
}
.todos {
width: 100%;
max-width: var(--column-width);
margin: var(--column-margin-top) auto 0 auto;
line-height: 1;
}
.new {
margin: 0 0 0.5rem 0;
}
.new {
margin: 0 0 0.5rem 0;
}
input {
border: 1px solid transparent;
}
input {
border: 1px solid transparent;
}
input:focus-visible {
box-shadow: inset 1px 1px 6px rgba(0, 0, 0, 0.1);
border: 1px solid #ff3e00 !important;
outline: none;
}
input:focus-visible {
box-shadow: inset 1px 1px 6px rgba(0, 0, 0, 0.1);
border: 1px solid #ff3e00 !important;
outline: none;
}
.new input {
font-size: 28px;
width: 100%;
padding: 0.5em 1em 0.3em 1em;
box-sizing: border-box;
background: rgba(255, 255, 255, 0.05);
border-radius: 8px;
text-align: center;
}
.new input {
font-size: 28px;
width: 100%;
padding: 0.5em 1em 0.3em 1em;
box-sizing: border-box;
background: rgba(255, 255, 255, 0.05);
border-radius: 8px;
text-align: center;
}
.todo {
display: grid;
grid-template-columns: 2rem 1fr 2rem;
grid-gap: 0.5rem;
align-items: center;
margin: 0 0 0.5rem 0;
padding: 0.5rem;
background-color: white;
border-radius: 8px;
filter: drop-shadow(2px 4px 6px rgba(0, 0, 0, 0.1));
transform: translate(-1px, -1px);
transition: filter 0.2s, transform 0.2s;
}
.todo {
display: grid;
grid-template-columns: 2rem 1fr 2rem;
grid-gap: 0.5rem;
align-items: center;
margin: 0 0 0.5rem 0;
padding: 0.5rem;
background-color: white;
border-radius: 8px;
filter: drop-shadow(2px 4px 6px rgba(0, 0, 0, 0.1));
transform: translate(-1px, -1px);
transition: filter 0.2s, transform 0.2s;
}
.done {
transform: none;
opacity: 0.4;
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.1));
}
.done {
transform: none;
opacity: 0.4;
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.1));
}
form.text {
position: relative;
display: flex;
align-items: center;
flex: 1;
}
form.text {
position: relative;
display: flex;
align-items: center;
flex: 1;
}
.todo input {
flex: 1;
padding: 0.5em 2em 0.5em 0.8em;
border-radius: 3px;
}
.todo input {
flex: 1;
padding: 0.5em 2em 0.5em 0.8em;
border-radius: 3px;
}
.todo button {
width: 2em;
height: 2em;
border: none;
background-color: transparent;
background-position: 50% 50%;
background-repeat: no-repeat;
}
.todo button {
width: 2em;
height: 2em;
border: none;
background-color: transparent;
background-position: 50% 50%;
background-repeat: no-repeat;
}
button.toggle {
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 50%;
box-sizing: border-box;
background-size: 1em auto;
}
button.toggle {
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 50%;
box-sizing: border-box;
background-size: 1em auto;
}
.done .toggle {
background-image: url("data:image/svg+xml,%3Csvg width='22' height='16' viewBox='0 0 22 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 1.5L7.4375 14.5L1.5 8.5909' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
.done .toggle {
background-image: url("data:image/svg+xml,%3Csvg width='22' height='16' viewBox='0 0 22 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 1.5L7.4375 14.5L1.5 8.5909' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
.delete {
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.5 5V22H19.5V5H4.5Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M10 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M14 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M2 5H22' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M8 5L9.6445 2H14.3885L16 5H8Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3C/svg%3E%0A");
opacity: 0.2;
}
.delete {
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.5 5V22H19.5V5H4.5Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M10 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M14 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M2 5H22' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M8 5L9.6445 2H14.3885L16 5H8Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3C/svg%3E%0A");
opacity: 0.2;
}
.delete:hover,
.delete:focus {
transition: opacity 0.2s;
opacity: 1;
}
.delete:hover,
.delete:focus {
transition: opacity 0.2s;
opacity: 1;
}
.save {
position: absolute;
right: 0;
opacity: 0;
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 2H3.5C2.67158 2 2 2.67157 2 3.5V20.5C2 21.3284 2.67158 22 3.5 22H20.5C21.3284 22 22 21.3284 22 20.5V3.5C22 2.67157 21.3284 2 20.5 2Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M17 2V11H7.5V2H17Z' fill='white' stroke='white' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M13.5 5.5V7.5' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3Cpath d='M5.99844 2H18.4992' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E%0A");
}
.save {
position: absolute;
right: 0;
opacity: 0;
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 2H3.5C2.67158 2 2 2.67157 2 3.5V20.5C2 21.3284 2.67158 22 3.5 22H20.5C21.3284 22 22 21.3284 22 20.5V3.5C22 2.67157 21.3284 2 20.5 2Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M17 2V11H7.5V2H17Z' fill='white' stroke='white' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M13.5 5.5V7.5' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3Cpath d='M5.99844 2H18.4992' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E%0A");
}
.todo input:focus + .save,
.save:focus {
transition: opacity 0.2s;
opacity: 1;
}
.todo input:focus + .save,
.save:focus {
transition: opacity 0.2s;
opacity: 1;
}
</style>

View File

@@ -2,14 +2,14 @@ import adapter from '@sveltejs/adapter-auto';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter(),
kit: {
adapter: adapter(),
// Override http methods in the Todo forms
methodOverride: {
allowed: ['PATCH', 'DELETE']
}
}
// Override http methods in the Todo forms
methodOverride: {
allowed: ['PATCH', 'DELETE']
}
}
};
export default config;

View File

@@ -31,7 +31,7 @@
"prettier": "2.6.2",
"ts-eager": "2.0.2",
"ts-jest": "28.0.0-next.1",
"turbo": "1.2.9"
"turbo": "1.2.14"
},
"scripts": {
"lerna": "lerna",

View File

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

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 Sema from 'async-sema';
import spawn from 'cross-spawn';
import { coerce, intersects, validRange } from 'semver';
import { SpawnOptions } from 'child_process';
import { deprecate } from 'util';
import debug from '../debug';
@@ -205,7 +206,9 @@ export function getSpawnOptions(
if (!meta.isDev) {
// 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;
@@ -217,9 +220,9 @@ export async function getNodeVersion(
config: Config = {},
meta: Meta = {}
): Promise<NodeVersion> {
const latest = getLatestNodeVersion();
if (meta && meta.isDev) {
// Use the system-installed version of `node` in PATH for `vercel dev`
const latest = getLatestNodeVersion();
return { ...latest, runtime: 'nodejs' };
}
const { packageJson } = await scanParentDirs(destPath, true);
@@ -227,10 +230,27 @@ export async function getNodeVersion(
let isAuto = true;
if (packageJson && packageJson.engines && packageJson.engines.node) {
const { node } = packageJson.engines;
if (nodeVersion && nodeVersion !== node && !meta.isDev) {
if (
nodeVersion &&
validRange(node) &&
!intersects(nodeVersion, node) &&
!meta.isDev
) {
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`
);
} 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;
isAuto = false;
@@ -433,13 +453,13 @@ export function getEnvForPackageManager({
(nodeVersion?.major || 0) < 16
) {
// 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...');
}
} else if (cliType === 'pnpm') {
if (typeof lockfileVersion === 'number' && lockfileVersion === 5.4) {
// 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...');
}
} else {

View File

@@ -117,5 +117,14 @@ export const isStaticRuntime = (name?: string): boolean => {
};
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';

View File

@@ -82,7 +82,7 @@ export interface BuildOptions {
* is the Git Repository Root. This is only relevant for Monorepos.
* See https://vercel.com/blog/monorepos
*/
repoRootPath?: string;
repoRootPath: string;
/**
* 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.
* See https://vercel.com/blog/monorepos
*/
repoRootPath?: string;
repoRootPath: string;
/**
* An arbitrary object passed by the user in the build definition defined
@@ -295,6 +295,7 @@ export interface PackageJson {
readonly preferGlobal?: boolean;
readonly private?: boolean;
readonly publishConfig?: PackageJson.PublishConfig;
readonly packageManager?: string;
}
export interface NodeVersion {
@@ -427,7 +428,7 @@ export interface BuildResultV2Typical {
export type BuildResultV2 = BuildResultV2Typical | BuildResultBuildOutput;
export interface BuildResultV3 {
output: Lambda;
output: Lambda | EdgeFunction;
}
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(
stat => stat.type === 'dir'
);
return (
await Promise.all(
childDirectories.map(childDirectory =>

View File

@@ -17,5 +17,13 @@ checkPkgOrThrow('exeggcute');
// This is to satisfy `@vercel/static-build` which needs a `dist` directory.
const { exec } = require('exeggcute');
exec('mkdir dist', __dirname);
exec('echo "node-env:RANDOMNESS_PLACEHOLDER" > dist/index.html', __dirname);
exec('mkdir dist', __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();
b();
exec('mkdir public', __dirname);
exec('echo "Hello, World!" > public/index.html', __dirname);
exec('mkdir public', __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:
once: ^1.4.0
importers:
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:
/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:
resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}
dependencies:
wrappy: 1.0.2
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:
resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=}
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,6 @@
{
"private": true,
"engines": {
"node": "16.14.0"
}
}

View File

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

View File

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

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 () => {
expect(
await getNodeVersion(
path.join(__dirname, 'pkg-engine-node'),
undefined,
{ nodeVersion: '14' },
{}
)
).toHaveProperty('range', '14.x');
expect(warningMessages).toStrictEqual([]);
expect(
await getNodeVersion(
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');
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 () => {

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "24.2.5-canary.2",
"version": "25.0.2-canary.0",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -30,7 +30,6 @@
"scripts/preinstall.js"
],
"ava": {
"compileEnhancements": false,
"extensions": [
"ts"
],
@@ -40,19 +39,19 @@
]
},
"engines": {
"node": ">= 12"
"node": ">= 14"
},
"dependencies": {
"@vercel/build-utils": "3.1.1-canary.2",
"@vercel/go": "1.4.4-canary.2",
"@vercel/next": "2.8.67-canary.2",
"@vercel/node": "1.15.4-canary.2",
"@vercel/python": "2.3.4-canary.2",
"@vercel/redwood": "0.8.4-canary.2",
"@vercel/remix": "0.0.2-canary.2",
"@vercel/ruby": "1.3.7-canary.2",
"@vercel/static-build": "0.25.3-canary.2",
"update-notifier": "4.1.0"
"@vercel/build-utils": "4.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",
"update-notifier": "5.1.0"
},
"devDependencies": {
"@alex_neo/jest-expect-message": "1.0.5",
@@ -95,8 +94,8 @@
"@types/which": "1.3.2",
"@types/write-json-file": "2.2.1",
"@types/yauzl-promise": "2.1.0",
"@vercel/client": "11.0.4-canary.2",
"@vercel/frameworks": "0.9.2-canary.0",
"@vercel/client": "12.0.1-canary.0",
"@vercel/frameworks": "1.0.1",
"@vercel/ncc": "0.24.0",
"@zeit/fun": "0.11.2",
"@zeit/source-map-support": "0.6.2",

View File

@@ -4,6 +4,7 @@ import dotenv from 'dotenv';
import { join, relative } from 'path';
import {
detectBuilders,
normalizePath,
Files,
FileFsRef,
PackageJson,
@@ -45,6 +46,7 @@ import {
writeBuildResult,
} from '../util/build/write-build-result';
import { importBuilders, BuilderWithPkg } from '../util/build/import-builders';
import { initCorepack, cleanupCorepack } from '../util/build/corepack';
type BuildResult = BuildResultV2 | BuildResultV3;
@@ -177,7 +179,7 @@ export default async function main(client: Client): Promise<number> {
// Get a list of source files
const files = (await getFiles(workPath, client)).map(f =>
relative(workPath, f)
normalizePath(relative(workPath, f))
);
const routesResult = getTransformedRoutes({ nowConfig: vercelConfig || {} });
@@ -265,19 +267,20 @@ export default async function main(client: Client): Promise<number> {
}
// 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();
// Create fresh new output directory
await fs.mkdirp(OUTPUT_DIR);
await fs.mkdirp(outputDir);
const ops: Promise<Error | void>[] = [];
// Write the `detectedBuilders` result to output dir
ops.push(
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.',
target,
@@ -312,6 +315,10 @@ export default async function main(client: Client): Promise<number> {
// TODO: parallelize builds
const buildResults: Map<Builder, BuildResult> = new Map();
const overrides: PathOverride[] = [];
const repoRootPath = cwd;
const rootPackageJsonPath = repoRootPath || workPath;
const corepackShimDir = await initCorepack({ cwd, rootPackageJsonPath });
for (const build of builds) {
if (typeof build.src !== 'string') continue;
@@ -331,7 +338,6 @@ export default async function main(client: Client): Promise<number> {
framework: project.settings.framework,
nodeVersion: project.settings.nodeVersion,
};
const repoRootPath = cwd === workPath ? undefined : cwd;
const buildOptions: BuildOptions = {
files: filesMap,
entrypoint: build.src,
@@ -352,6 +358,7 @@ export default async function main(client: Client): Promise<number> {
// Start flushing the file outputs to the filesystem asynchronously
ops.push(
writeBuildResult(
outputDir,
buildResult,
build,
builder,
@@ -366,6 +373,10 @@ export default async function main(client: Client): Promise<number> {
);
}
if (corepackShimDir) {
cleanupCorepack(corepackShimDir);
}
// Wait for filesystem operations to complete
// TODO render progress bar?
let hadError = false;
@@ -379,7 +390,7 @@ export default async function main(client: Client): Promise<number> {
if (hadError) return 1;
// 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
const existingConfig = await readJSONFile<any>(configPath);
if (existingConfig instanceof CantParseJSONFile) {
@@ -437,7 +448,7 @@ export default async function main(client: Client): Promise<number> {
wildcard: mergedWildcard,
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(
`${prependEmoji(

View File

@@ -42,6 +42,13 @@ const help = () => {
${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.cyan(`$ ${getPkgName()} env add <name>`)}

View File

@@ -25,7 +25,7 @@ import {
const help = () => {
return console.log(`
${chalk.bold(`${logo} ${getPkgName()} pull`)} [path]
${chalk.bold(`${logo} ${getPkgName()} pull`)} [project-path]
${chalk.dim('Options:')}
@@ -42,25 +42,29 @@ const help = () => {
${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 ./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(
`$ ${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) {
return getArgs(client.argv.slice(2), {
'--yes': Boolean,
'--env': String, // deprecated
'--environment': String,
'--debug': Boolean,
'-d': '--debug',

View File

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

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
// dependency. `require.resolve()` will throw if the Builder is not a CLI
// 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],
});
}) as string;
builderPkg = await readJSON(pkgPath);
}
@@ -148,7 +149,9 @@ export async function resolveBuilders(
// TODO: handle `parsed.type === 'tag'` ("latest" vs. anything else?)
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, {
builder,

View File

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

View File

@@ -142,6 +142,7 @@ export async function executeBuild(
files,
entrypoint,
workPath,
repoRootPath: workPath,
config,
meta: {
isDev: true,

View File

@@ -1735,6 +1735,7 @@ export default class DevServer {
entrypoint: match.entrypoint,
workPath,
config: match.config || {},
repoRootPath: this.cwd,
meta: {
isDev: true,
requestPath,

View File

@@ -1,5 +1,4 @@
import { resolve } from 'path';
import _glob, { IOptions as GlobOptions } from 'glob';
import fs from 'fs-extra';
import { getVercelIgnore } from '@vercel/client';
import uniqueStrings from './unique-strings';
@@ -21,14 +20,6 @@ function flatten(
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,
* and maintains absolutes as such.
@@ -65,15 +56,16 @@ interface StaticFilesOptions {
export async function staticFiles(
path: string,
{ output, src }: StaticFilesOptions
) {
): Promise<string[]> {
const { debug, time } = output;
let files: string[] = [];
// The package.json `files` whitelist still
// honors ignores: https://docs.npmjs.com/files/package.json#files
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
const { ig } = await getVercelIgnore(path);
@@ -104,7 +96,7 @@ export async function staticFiles(
// Locate files
files = await time(
`Locating files ${path}`,
explode(search, {
explode([search], {
accepts,
output,
})
@@ -164,7 +156,7 @@ async function explode(
const all = await fs.readdir(file);
/* eslint-disable no-use-before-define */
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 */
}
if (!s.isFile()) {

View File

@@ -1,12 +1,12 @@
/**
* A fast implementation of an algorithm that takes an array and returns a copy of the array without duplicates.
* We used to use `array-unique` ( https://github.com/jonschlinkert/array-unique/blob/5d1fbe560da8125e28e4ad6fbfa9daaf9f2ec120/index.js )
* We used to use `array-unique` (https://github.com/jonschlinkert/array-unique/blob/5d1fbe560da8125e28e4ad6fbfa9daaf9f2ec120/index.js)
* but from running benchmarks, found the implementation to be too slow. This implementation has show to be upto ~10x faster for large
* projects
* @param {Array} arr Input array that potentially has duplicates
* @returns {Array} An array of the unique values in `arr`
*/
export default (arr: string[]) => {
export default (arr: string[]): string[] => {
const len = arr.length;
const res: string[] = [];
const o: { [key: string]: string | number } = {};

View File

@@ -0,0 +1,2 @@
!/*/.vercel
/*/.vercel/output

View File

@@ -0,0 +1 @@
!node_modules

View File

@@ -0,0 +1,15 @@
const { EdgeFunction } = require('@vercel/build-utils');
exports.version = 3;
exports.build = async ({ entrypoint, files }) => {
const output = new EdgeFunction({
name: entrypoint,
deploymentTarget: 'v8-worker',
entrypoint,
files: {
[entrypoint]: files[entrypoint]
},
});
return { output };
};

View File

@@ -0,0 +1,6 @@
{
"name": "edge-function",
"private": true,
"version": "0.0.0",
"main": "builder.js"
}

View File

@@ -0,0 +1,7 @@
{
"orgId": ".",
"projectId": ".",
"settings": {
"framework": null
}
}

View File

@@ -0,0 +1,5 @@
export const config = {
runtime: 'experimental-edge',
};
export default req => new Response('from edge');

View File

@@ -0,0 +1,7 @@
{
"functions": {
"api/*.js": {
"runtime": "edge-function@0.0.0"
}
}
}

View File

@@ -0,0 +1,7 @@
{
"orgId": ".",
"projectId": ".",
"settings": {
"framework": null
}
}

View File

@@ -0,0 +1 @@
export default (req, res) => res.end('Vercel');

View File

@@ -0,0 +1 @@
module.exports = (req, res) => res.end('Vercel');

View File

@@ -0,0 +1 @@
export default (req, res) => res.end('Vercel');

View File

@@ -0,0 +1,3 @@
import { IncomingMessage, ServerResponse } from 'http';
export default (req: IncomingMessage, res: ServerResponse) => res.end('Vercel');

View File

@@ -0,0 +1,7 @@
{
"orgId": ".",
"projectId": ".",
"settings": {
"framework": null
}
}

View File

@@ -0,0 +1 @@
<h1>Vercel</h1>

View File

@@ -0,0 +1 @@
!node_modules

View File

@@ -0,0 +1,14 @@
const { Lambda } = require('@vercel/build-utils');
exports.version = 3;
exports.build = async ({ entrypoint, files }) => {
const output = new Lambda({
files: {
[entrypoint]: files[entrypoint]
},
runtime: 'provided',
handler: entrypoint
});
return { output };
};

View File

@@ -0,0 +1,6 @@
{
"name": "txt-builder",
"private": true,
"version": "0.0.0",
"main": "index.js"
}

View File

@@ -0,0 +1,7 @@
{
"orgId": ".",
"projectId": ".",
"settings": {
"framework": null
}
}

View File

@@ -0,0 +1 @@
Text file

View File

@@ -0,0 +1,7 @@
{
"functions": {
"api/*.txt": {
"runtime": "txt-builder@0.0.0"
}
}
}

View File

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

View File

@@ -19,7 +19,7 @@ const getRevertAliasConfigFile = () => {
],
});
};
module.exports = async function prepare(session, binaryPath) {
module.exports = async function prepare(session, binaryPath, tmpFixturesDir) {
const spec = {
'static-single-file': {
'first.png': getImageFile(session, { size: 30 }),
@@ -426,16 +426,72 @@ module.exports = async function prepare(session, binaryPath) {
projectId: 'QmRoBYhejkkmssotLZr8tWgewPdPcjYucYUNERFbhJrRNi',
}),
},
'vc-build-static-build': {
'.vercel/project.json': JSON.stringify({
orgId: '.',
projectId: '.',
settings: {
framework: null,
},
}),
'package.json': JSON.stringify({
scripts: {
build: 'mkdir -p public && echo hi > public/index.txt',
},
}),
},
'vc-build-corepack-npm': {
'.vercel/project.json': JSON.stringify({
orgId: '.',
projectId: '.',
settings: {
framework: null,
},
}),
'package.json': JSON.stringify({
private: true,
packageManager: 'npm@8.1.0',
scripts: {
build: 'mkdir -p public && npm --version > public/index.txt',
},
}),
},
'vc-build-corepack-pnpm': {
'.vercel/project.json': JSON.stringify({
orgId: '.',
projectId: '.',
settings: {
framework: null,
},
}),
'package.json': JSON.stringify({
private: true,
packageManager: 'pnpm@7.1.0',
scripts: {
build: 'mkdir -p public && pnpm --version > public/index.txt',
},
}),
},
'vc-build-corepack-yarn': {
'.vercel/project.json': JSON.stringify({
orgId: '.',
projectId: '.',
settings: {
framework: null,
},
}),
'package.json': JSON.stringify({
private: true,
packageManager: 'yarn@2.4.3',
scripts: {
build: 'mkdir -p public && yarn --version > public/index.txt',
},
}),
},
};
for (const [typeName, needed] of Object.entries(spec)) {
const directory = join(
__dirname,
'..',
'fixtures',
'integration',
typeName
);
const directory = join(tmpFixturesDir, typeName);
await mkdirp(directory);

View File

@@ -4,7 +4,7 @@ import { URL, parse as parseUrl } from 'url';
import test from 'ava';
import semVer from 'semver';
import { Readable } from 'stream';
import { homedir } from 'os';
import { homedir, tmpdir } from 'os';
import _execa from 'execa';
import XDGAppPaths from 'xdg-app-paths';
import fetch from 'node-fetch';
@@ -31,7 +31,7 @@ function execa(file, args, options) {
}
function fixture(name) {
const directory = path.join(__dirname, 'fixtures', 'integration', name);
const directory = path.join(tmpFixturesDir, name);
const config = path.join(directory, 'project.json');
// We need to remove it, otherwise we can't re-use fixtures
@@ -146,6 +146,7 @@ let email;
let contextName;
let tmpDir;
let tmpFixturesDir = path.join(tmpdir(), 'tmp-fixtures');
let globalDir = XDGAppPaths('com.vercel.cli').dataDirs()[0];
@@ -327,7 +328,7 @@ async function setupProject(process, projectName, overrides) {
test.before(async () => {
try {
await createUser();
await prepareFixtures(contextName, binaryPath);
await prepareFixtures(contextName, binaryPath, tmpFixturesDir);
} catch (err) {
console.log('Failed `test.before`');
console.log(err);
@@ -335,6 +336,8 @@ test.before(async () => {
});
test.after.always(async () => {
delete process.env.ENABLE_EXPERIMENTAL_COREPACK;
if (loginApiServer) {
// Stop mock server
loginApiServer.close();
@@ -349,6 +352,11 @@ test.after.always(async () => {
// Remove config directory entirely
tmpDir.removeCallback();
}
if (tmpFixturesDir) {
console.log('removing tmpFixturesDir', tmpFixturesDir);
fs.removeSync(tmpFixturesDir);
}
});
test('default command should prompt login with empty auth.json', async t => {
@@ -390,6 +398,99 @@ test('login', async t => {
t.is(auth.token, token);
});
test('[vc build] should build project with corepack and select npm@8.1.0', async t => {
process.env.ENABLE_EXPERIMENTAL_COREPACK = '1';
const directory = fixture('vc-build-corepack-npm');
const before = await _execa('npm', ['--version'], {
cwd: directory,
reject: false,
});
const output = await execute(['build'], { cwd: directory });
t.is(output.exitCode, 0, formatOutput(output));
t.regex(output.stderr, /Build Completed/gm);
const after = await _execa('npm', ['--version'], {
cwd: directory,
reject: false,
});
// Ensure global npm didn't change
t.is(before.stdout, after.stdout);
// Ensure version is correct
t.is(
await fs.readFile(
path.join(directory, '.vercel/output/static/index.txt'),
'utf8'
),
'8.1.0\n'
);
// Ensure corepack will be cached
const contents = fs.readdirSync(
path.join(directory, '.vercel/cache/corepack')
);
t.deepEqual(contents, ['home', 'shim']);
});
test('[vc build] should build project with corepack and select pnpm@7.1.0', async t => {
process.env.ENABLE_EXPERIMENTAL_COREPACK = '1';
const directory = fixture('vc-build-corepack-pnpm');
const before = await _execa('pnpm', ['--version'], {
cwd: directory,
reject: false,
});
const output = await execute(['build'], { cwd: directory });
t.is(output.exitCode, 0, formatOutput(output));
t.regex(output.stderr, /Build Completed/gm);
const after = await _execa('pnpm', ['--version'], {
cwd: directory,
reject: false,
});
// Ensure global pnpm didn't change
t.is(before.stdout, after.stdout);
// Ensure version is correct
t.is(
await fs.readFile(
path.join(directory, '.vercel/output/static/index.txt'),
'utf8'
),
'7.1.0\n'
);
// Ensure corepack will be cached
const contents = fs.readdirSync(
path.join(directory, '.vercel/cache/corepack')
);
t.deepEqual(contents, ['home', 'shim']);
});
test('[vc build] should build project with corepack and select yarn@2.4.3', async t => {
process.env.ENABLE_EXPERIMENTAL_COREPACK = '1';
const directory = fixture('vc-build-corepack-yarn');
const before = await _execa('yarn', ['--version'], {
cwd: directory,
reject: false,
});
const output = await execute(['build'], { cwd: directory });
t.is(output.exitCode, 0, formatOutput(output));
t.regex(output.stderr, /Build Completed/gm);
const after = await _execa('yarn', ['--version'], {
cwd: directory,
reject: false,
});
// Ensure global yarn didn't change
t.is(before.stdout, after.stdout);
// Ensure version is correct
t.is(
await fs.readFile(
path.join(directory, '.vercel/output/static/index.txt'),
'utf8'
),
'2.4.3\n'
);
// Ensure corepack will be cached
const contents = fs.readdirSync(
path.join(directory, '.vercel/cache/corepack')
);
t.deepEqual(contents, ['home', 'shim']);
});
test('default command should deploy directory', async t => {
const projectDir = fixture('deploy-default-with-sub-directory');
const target = 'output';
@@ -1507,7 +1608,7 @@ test('try to purchase a domain', async t => {
const { stderr, stdout, exitCode } = await execa(
binaryPath,
['domains', 'buy', `${session}-test.org`, ...defaultArgs],
['domains', 'buy', `${session}-test.com`, ...defaultArgs],
{
reject: false,
input: stream,
@@ -1522,10 +1623,9 @@ test('try to purchase a domain', async t => {
console.log(exitCode);
t.is(exitCode, 1);
t.true(
stderr.includes(
`Error! Could not purchase domain. Please add a payment method using \`vercel billing add\`.`
)
t.regex(
stderr,
/Error! Could not purchase domain\. Please add a payment method using/
);
});
@@ -1537,7 +1637,7 @@ test('try to transfer-in a domain with "--code" option', async t => {
'transfer-in',
'--code',
'xyz',
`${session}-test.org`,
`${session}-test.com`,
...defaultArgs,
],
{
@@ -1551,7 +1651,7 @@ test('try to transfer-in a domain with "--code" option', async t => {
t.true(
stderr.includes(
`Error! The domain "${session}-test.org" is not transferable.`
`Error! The domain "${session}-test.com" is not transferable.`
)
);
t.is(exitCode, 1);
@@ -3782,3 +3882,30 @@ test('[vc link] should support the `--project` flag', async t => {
formatOutput(output)
);
});
test('[vc build] should build project with `@vercel/static-build`', async t => {
const directory = fixture('vc-build-static-build');
const output = await execute(['build'], { cwd: directory });
t.is(output.exitCode, 0);
t.true(output.stderr.includes('Build Completed in .vercel/output'));
t.is(
await fs.readFile(
path.join(directory, '.vercel/output/static/index.txt'),
'utf8'
),
'hi\n'
);
const config = await fs.readJSON(
path.join(directory, '.vercel/output/config.json')
);
t.is(config.version, 3);
const builds = await fs.readJSON(
path.join(directory, '.vercel/output/builds.json')
);
t.is(builds.target, 'preview');
t.is(builds.builds[0].src, 'package.json');
t.is(builds.builds[0].use, '@vercel/static-build');
});

View File

@@ -0,0 +1,240 @@
import ms from 'ms';
import fs from 'fs-extra';
import { join } from 'path';
import { client } from '../../mocks/client';
import build from '../../../src/commands/build';
jest.setTimeout(ms('1 minute'));
const fixture = (name: string) =>
join(__dirname, '../../fixtures/unit/commands/build', name);
describe('build', () => {
const originalCwd = process.cwd();
it('should build with `@vercel/static`', async () => {
const cwd = fixture('static');
const output = join(cwd, '.vercel/output');
try {
process.chdir(cwd);
const exitCode = await build(client);
expect(exitCode).toEqual(0);
// `builds.json` says that "@vercel/static" was run
const builds = await fs.readJSON(join(output, 'builds.json'));
expect(builds).toMatchObject({
target: 'preview',
builds: [
{
require: '@vercel/static',
apiVersion: 2,
src: '**',
use: '@vercel/static',
},
],
});
// "static" directory contains static files
const files = await fs.readdir(join(output, 'static'));
expect(files.sort()).toEqual(['index.html']);
} finally {
process.chdir(originalCwd);
delete process.env.__VERCEL_BUILD_RUNNING;
}
});
it('should build with `@vercel/node`', async () => {
const cwd = fixture('node');
const output = join(cwd, '.vercel/output');
try {
process.chdir(cwd);
const exitCode = await build(client);
expect(exitCode).toEqual(0);
// `builds.json` says that "@vercel/node" was run
const builds = await fs.readJSON(join(output, 'builds.json'));
expect(builds).toMatchObject({
target: 'preview',
builds: [
{
require: '@vercel/node',
apiVersion: 3,
use: '@vercel/node',
src: 'api/es6.js',
config: { zeroConfig: true },
},
{
require: '@vercel/node',
apiVersion: 3,
use: '@vercel/node',
src: 'api/index.js',
config: { zeroConfig: true },
},
{
require: '@vercel/node',
apiVersion: 3,
use: '@vercel/node',
src: 'api/mjs.mjs',
config: { zeroConfig: true },
},
{
require: '@vercel/node',
apiVersion: 3,
use: '@vercel/node',
src: 'api/typescript.ts',
config: { zeroConfig: true },
},
],
});
// "static" directory is empty
const hasStaticFiles = await fs.pathExists(join(output, 'static'));
expect(
hasStaticFiles,
'Expected ".vercel/output/static" to not exist'
).toEqual(false);
// "functions/api" directory has output Functions
const functions = await fs.readdir(join(output, 'functions/api'));
expect(functions.sort()).toEqual([
'es6.func',
'index.func',
'mjs.func',
'typescript.func',
]);
} finally {
process.chdir(originalCwd);
delete process.env.__VERCEL_BUILD_RUNNING;
}
});
it('should build with 3rd party Builder', async () => {
const cwd = fixture('third-party-builder');
const output = join(cwd, '.vercel/output');
try {
process.chdir(cwd);
const exitCode = await build(client);
expect(exitCode).toEqual(0);
// `builds.json` says that "@vercel/node" was run
const builds = await fs.readJSON(join(output, 'builds.json'));
expect(builds).toMatchObject({
target: 'preview',
builds: [
{
require: 'txt-builder',
apiVersion: 3,
use: 'txt-builder@0.0.0',
src: 'api/foo.txt',
config: {
zeroConfig: true,
functions: {
'api/*.txt': {
runtime: 'txt-builder@0.0.0',
},
},
},
},
{
require: '@vercel/static',
apiVersion: 2,
use: '@vercel/static',
src: '!{api/**,package.json}',
config: {
zeroConfig: true,
},
},
],
});
// "static" directory is empty
const hasStaticFiles = await fs.pathExists(join(output, 'static'));
expect(
hasStaticFiles,
'Expected ".vercel/output/static" to not exist'
).toEqual(false);
// "functions/api" directory has output Functions
const functions = await fs.readdir(join(output, 'functions/api'));
expect(functions.sort()).toEqual(['foo.func']);
const vcConfig = await fs.readJSON(
join(output, 'functions/api/foo.func/.vc-config.json')
);
expect(vcConfig).toMatchObject({
handler: 'api/foo.txt',
runtime: 'provided',
environment: {},
});
} finally {
process.chdir(originalCwd);
delete process.env.__VERCEL_BUILD_RUNNING;
}
});
it('should serialize `EdgeFunction` output in version 3 Builder', async () => {
const cwd = fixture('edge-function');
const output = join(cwd, '.vercel/output');
try {
process.chdir(cwd);
client.setArgv('build', '--prod');
const exitCode = await build(client);
expect(exitCode).toEqual(0);
// `builds.json` says that "@vercel/node" was run
const builds = await fs.readJSON(join(output, 'builds.json'));
expect(builds).toMatchObject({
target: 'production',
builds: [
{
require: 'edge-function',
apiVersion: 3,
use: 'edge-function@0.0.0',
src: 'api/edge.js',
config: {
zeroConfig: true,
functions: {
'api/*.js': {
runtime: 'edge-function@0.0.0',
},
},
},
},
{
require: '@vercel/static',
apiVersion: 2,
use: '@vercel/static',
src: '!{api/**,package.json}',
config: {
zeroConfig: true,
},
},
],
});
// "static" directory is empty
const hasStaticFiles = await fs.pathExists(join(output, 'static'));
expect(
hasStaticFiles,
'Expected ".vercel/output/static" to not exist'
).toEqual(false);
// "functions/api" directory has output Functions
const functions = await fs.readdir(join(output, 'functions/api'));
expect(functions.sort()).toEqual(['edge.func']);
const vcConfig = await fs.readJSON(
join(output, 'functions/api/edge.func/.vc-config.json')
);
expect(vcConfig).toMatchObject({
runtime: 'edge',
name: 'api/edge.js',
deploymentTarget: 'v8-worker',
entrypoint: 'api/edge.js',
});
} finally {
process.chdir(originalCwd);
delete process.env.__VERCEL_BUILD_RUNNING;
}
});
});

View File

@@ -10,7 +10,7 @@ import {
import vercelNextPkg from '@vercel/next/package.json';
import vercelNodePkg from '@vercel/node/package.json';
jest.setTimeout(ms('20 seconds'));
jest.setTimeout(ms('30 seconds'));
describe('importBuilders()', () => {
it('should import built-in Builders', async () => {

View File

@@ -18,8 +18,15 @@ const getStaticFiles = async (dir: string) => {
const normalizeWindowsPaths = (files: string[]) => {
if (process.platform === 'win32') {
const prefix = 'D:/a/vercel/vercel/packages/cli/test/fixtures/unit/';
return files.map(f => f.replace(/\\/g, '/').slice(prefix.length));
// GitHub Actions absolute path "f" that looks like:
// "D:/a/vercel/vercel/packages/cli/test/fixtures/unit/"
// but other OS's are relative path so we normalize here.
const prefix = 'packages/cli/test/fixtures/unit/';
return files.map(f => {
const normal = f.replace(/\\/g, '/');
const i = normal.indexOf(prefix);
return normal.slice(i + prefix.length);
});
}
return files;
};

View File

@@ -5,10 +5,10 @@
"noFallthroughCasesInSwitch": true,
"moduleResolution": "node",
"module": "commonjs",
"target": "es2019",
"target": "ES2020",
"esModuleInterop": true,
"allowJs": true,
"lib": ["esnext"],
"lib": ["ES2020"],
"resolveJsonModule": true,
"sourceMap": true,
"outDir": "./dist",

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/client",
"version": "11.0.4-canary.2",
"version": "12.0.1-canary.0",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"homepage": "https://vercel.com",
@@ -20,7 +20,7 @@
"test-unit": "yarn test tests/unit.*test.*"
},
"engines": {
"node": ">= 12"
"node": ">= 14"
},
"devDependencies": {
"@types/async-retry": "1.4.1",
@@ -42,7 +42,7 @@
]
},
"dependencies": {
"@vercel/build-utils": "3.1.1-canary.2",
"@vercel/build-utils": "4.0.1-canary.0",
"@zeit/fetch": "5.2.0",
"async-retry": "1.2.3",
"async-sema": "3.0.0",

View File

@@ -2,7 +2,7 @@
"compilerOptions": {
"declaration": true,
"esModuleInterop": true,
"lib": ["esnext"],
"lib": ["ES2020"],
"module": "commonjs",
"moduleResolution": "node",
"outDir": "dist",
@@ -12,7 +12,7 @@
"noUnusedLocals": true,
"noUnusedParameters": true,
"strict": true,
"target": "es2019"
"target": "ES2020"
},
"include": ["./src"]
}

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/frameworks",
"version": "0.9.2-canary.0",
"version": "1.0.1",
"main": "./dist/frameworks.js",
"types": "./dist/frameworks.d.ts",
"files": [
@@ -21,7 +21,7 @@
"@types/js-yaml": "3.12.1",
"@types/node": "12.0.4",
"@types/node-fetch": "2.5.8",
"@vercel/routing-utils": "1.13.3",
"@vercel/routing-utils": "1.13.4",
"ajv": "6.12.2",
"typescript": "4.3.4"
}

View File

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

View File

@@ -2,7 +2,7 @@ import tar from 'tar';
import execa from 'execa';
import fetch from 'node-fetch';
import { mkdirp, pathExists, readFile } from 'fs-extra';
import { join } from 'path';
import { join, delimiter } from 'path';
import stringArgv from 'string-argv';
import { debug } from '@vercel/build-utils';
const versionMap = new Map([
@@ -121,7 +121,7 @@ export async function createGo(
) {
const binPath = join(getGoDir(workPath), 'bin');
debug(`Adding ${binPath} to PATH`);
const path = `${binPath}:${process.env.PATH}`;
const path = `${binPath}${delimiter}${process.env.PATH}`;
const env: { [key: string]: string } = {
...process.env,
PATH: path,

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/go",
"version": "1.4.4-canary.2",
"version": "2.0.1-canary.0",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/go",
@@ -25,7 +25,7 @@
"@types/fs-extra": "^5.0.5",
"@types/node-fetch": "^2.3.0",
"@types/tar": "^4.0.0",
"@vercel/build-utils": "3.1.1-canary.2",
"@vercel/build-utils": "4.0.1-canary.0",
"@vercel/ncc": "0.24.0",
"async-retry": "1.3.1",
"execa": "^1.0.0",

View File

@@ -2,7 +2,7 @@
"compilerOptions": {
"declaration": false,
"esModuleInterop": true,
"lib": ["esnext"],
"lib": ["ES2020"],
"module": "commonjs",
"moduleResolution": "node",
"noEmitOnError": true,
@@ -13,6 +13,6 @@
"noImplicitThis": false,
"types": ["node"],
"strict": true,
"target": "es2018"
"target": "ES2020"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/next",
"version": "2.8.67-canary.2",
"version": "3.0.1-canary.0",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/next-js",
@@ -45,9 +45,9 @@
"@types/semver": "6.0.0",
"@types/text-table": "0.2.1",
"@types/webpack-sources": "3.2.0",
"@vercel/build-utils": "3.1.1-canary.2",
"@vercel/build-utils": "4.0.1-canary.0",
"@vercel/nft": "0.19.1",
"@vercel/routing-utils": "1.13.3",
"@vercel/routing-utils": "1.13.4",
"async-sema": "3.0.1",
"buffer-crc32": "0.2.13",
"cheerio": "1.0.0-rc.10",

View File

@@ -130,10 +130,11 @@ function getRealNextVersion(entryPath: string): string | false {
// First try to resolve the `next` dependency and get the real version from its
// package.json. This allows the builder to be used with frameworks like Blitz that
// bundle Next but where Next isn't in the project root's package.json
const nextVersion: string = require(resolveFrom(
entryPath,
'next/package.json'
)).version;
// NOTE: `eval('require')` is necessary to avoid bad transpilation to `__webpack_require__`
const nextVersion: string = eval('require')(
resolveFrom(entryPath, 'next/package.json')
).version;
console.log(`Detected Next.js version: ${nextVersion}`);
return nextVersion;
} catch (_ignored) {
@@ -320,7 +321,7 @@ export const build: BuildV2 = async ({
(nodeVersion?.major || 0) < 16
) {
// Ensure that npm 7 is at the beginning of the `$PATH`
env.PATH = `/node16/bin-npm7:${env.PATH}`;
env.PATH = `/node16/bin-npm7${path.delimiter}${env.PATH}`;
console.log('Detected `package-lock.json` generated by npm 7...');
}
}

View File

@@ -54,6 +54,7 @@ import prettyBytes from 'pretty-bytes';
// related PR: https://github.com/vercel/next.js/pull/30046
const CORRECT_NOT_FOUND_ROUTES_VERSION = 'v12.0.1';
const CORRECT_MIDDLEWARE_ORDER_VERSION = 'v12.1.7-canary.29';
export async function serverBuild({
dynamicPages,
@@ -131,6 +132,10 @@ export async function serverBuild({
nextVersion,
CORRECT_NOT_FOUND_ROUTES_VERSION
);
const isCorrectMiddlewareOrder = semver.gte(
nextVersion,
CORRECT_MIDDLEWARE_ORDER_VERSION
);
let hasStatic500 = !!staticPages[path.join(entryDirectory, '500')];
if (lambdaPageKeys.length === 0) {
@@ -788,6 +793,7 @@ export async function serverBuild({
entryPath,
outputDirectory,
routesManifest,
isCorrectMiddlewareOrder,
});
const dynamicRoutes = await getDynamicRoutes(
@@ -1025,6 +1031,10 @@ export async function serverBuild({
...redirects,
// middleware comes directly after redirects but before
// beforeFiles rewrites as middleware is not a "file" route
...(isCorrectMiddlewareOrder ? middleware.staticRoutes : []),
...beforeFilesRewrites,
// Make sure to 404 for the /404 path itself
@@ -1067,7 +1077,10 @@ export async function serverBuild({
},
]),
...middleware.staticRoutes,
// while middleware was in beta the order came right before
// handle: 'filesystem' we maintain this for older versions
// to prevent a local/deploy mismatch
...(!isCorrectMiddlewareOrder ? middleware.staticRoutes : []),
// Next.js page lambdas, `static/` folder, reserved assets, and `public/`
// folder

View File

@@ -244,9 +244,7 @@ export async function getRoutesManifest(
});
}
// eslint-disable-next-line @typescript-eslint/no-var-requires
const routesManifest: RoutesManifest = require(pathRoutesManifest);
const routesManifest: RoutesManifest = await fs.readJSON(pathRoutesManifest);
// remove temporary array based routeKeys from v1/v2 of routes
// manifest since it can result in invalid routes
for (const route of routesManifest.dataRoutes || []) {
@@ -368,10 +366,10 @@ export async function getDynamicRoutes(
let getSortedRoutes: ((normalizedPages: string[]) => string[]) | undefined;
try {
({ getRouteRegex, getSortedRoutes } = require(resolveFrom(
entryPath,
'next-server/dist/lib/router/utils'
)));
// NOTE: `eval('require')` is necessary to avoid bad transpilation to `__webpack_require__`
({ getRouteRegex, getSortedRoutes } = eval('require')(
resolveFrom(entryPath, 'next-server/dist/lib/router/utils')
));
if (typeof getRouteRegex !== 'function') {
getRouteRegex = undefined;
}
@@ -379,10 +377,10 @@ export async function getDynamicRoutes(
if (!getRouteRegex || !getSortedRoutes) {
try {
({ getRouteRegex, getSortedRoutes } = require(resolveFrom(
entryPath,
'next/dist/next-server/lib/router/utils'
)));
// NOTE: `eval('require')` is necessary to avoid bad transpilation to `__webpack_require__`
({ getRouteRegex, getSortedRoutes } = eval('require')(
resolveFrom(entryPath, 'next/dist/next-server/lib/router/utils')
));
if (typeof getRouteRegex !== 'function') {
getRouteRegex = undefined;
}
@@ -536,9 +534,7 @@ export async function getImagesManifest(
return undefined;
}
// eslint-disable-next-line @typescript-eslint/no-var-requires
const imagesManifest: NextImagesManifest = require(pathImagesManifest);
return imagesManifest;
return fs.readJson(pathImagesManifest);
}
type FileMap = { [page: string]: FileFsRef };
@@ -2146,10 +2142,12 @@ export async function getMiddlewareBundle({
entryPath,
outputDirectory,
routesManifest,
isCorrectMiddlewareOrder,
}: {
entryPath: string;
outputDirectory: string;
routesManifest: RoutesManifest;
isCorrectMiddlewareOrder: boolean;
}) {
const middlewareManifest = await getMiddlewareManifest(
entryPath,
@@ -2251,13 +2249,16 @@ export async function getMiddlewareBundle({
const edgeFile = worker.edgeFunction.name;
worker.edgeFunction.name = edgeFile.replace(/^pages\//, '');
source.edgeFunctions[edgeFile] = worker.edgeFunction;
const route = {
const route: Source = {
continue: true,
override: true,
middlewarePath: edgeFile,
src: worker.routeSrc,
};
if (isCorrectMiddlewareOrder) {
route.override = true;
}
if (routesManifest.version > 3 && isDynamicRoute(worker.page)) {
source.dynamicRouteMap.set(worker.page, route);
} else {
@@ -2299,8 +2300,7 @@ async function getMiddlewareManifest(
return;
}
// eslint-disable-next-line @typescript-eslint/no-var-requires
return require(middlewareManifestPath);
return fs.readJSON(middlewareManifestPath);
}
/**

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