Compare commits

...

100 Commits

Author SHA1 Message Date
Nathan Rajlich
4a8504fc45 Publish Stable
- @vercel/build-utils@2.15.1
 - vercel@24.1.0
 - @vercel/client@10.4.1
 - @vercel/frameworks@0.7.1
 - @vercel/go@1.3.2
 - @vercel/node@1.14.1
 - @vercel/python@2.2.2
 - @vercel/redwood@0.7.0
 - @vercel/routing-utils@1.13.1
 - @vercel/ruby@1.3.2
 - @vercel/static-build@0.23.1
2022-04-11 10:14:53 -07:00
Nathan Rajlich
576217b344 [static-build] Fix prepareCache() when deploying with --prebuilt (#7659)
The `prepareCache()` function was throwing an error when doing `vc deploy --prebuilt` because the `package.json` file is not present.

Also just cleaned up the logic a bit.
2022-04-09 20:51:38 +00:00
Steven
f03129ac7a [tests] Bump mkdocs test fixture (#7660)
* [tests] Bump mkdocs test fixture

* Bump pelican
2022-04-09 14:24:26 -04:00
Sean Massa
3eaad1fd52 [cli] remove deprecation warning for vc env (#7654)
Removes the deprecation warning in the CLI for `vc env`. This command will coexist with `vc pull` for a slightly different purpose.
2022-04-08 19:46:11 -04:00
Nathan Rajlich
4e471491d8 [build-utils] Add repoRootPath to PrepareCacheOptions (#7658)
`repoRootPath` is provided to the `prepareCache()` function and is the same value as provided to the `build()` function.

Also remove `cachePath` since it's no longer used.
2022-04-08 20:39:47 +00:00
Sean Massa
99395fd9fe [cli] trim env file path output (#7653) 2022-04-08 13:14:23 -05:00
Sean Massa
4980fe6513 [cli] remove references to vc pull --env-file (#7647) 2022-04-05 13:30:41 -07:00
Sean Massa
24676ae020 [cli] Add --environment to vc pull (#7624)
* refactor in prep for changes

* pull all target env files

* update `pull` usage

* move .env files into /.vercel

* tighten types in helper functions

* add return type

* remove "any" type

* keep original behavior

* only use --env for root-level env file name

* add --target flag

* deprecate --env for --env-file

* rename --target to --environment

* more renaming

* pr feedback

* update `vc pull --help`

* Update pull.ts

* remove env-file logic from `vc pull`

Co-authored-by: Steven <steven@ceriously.com>
2022-04-05 11:08:29 -07:00
Steven
72ada9abd8 [examples] Bump Next.js + React (#7644)
Bump Next.js + React + ESLint
2022-04-04 09:13:20 -04:00
Steven
da893e7c57 [tests] Increase timeout cli tests (#7640)
* [tests] Increase network timeout for GH Actions

* [tests] Increase CLI integration test timeout
2022-04-01 10:52:47 -04:00
Sean Massa
e40eecafc9 [cli] Add support for vc --cwd <dir> (#7577)
`vc`'s default command is `deploy`, which can lead to ambiguous cli invocations when running `vc dir-or-command` like:

```
$  vc list
Vercel CLI 23.1.2
Error! The supplied argument "list" is ambiguous.
If you wish to deploy the subdirectory "list", first run "cd list".
```

when run in a directory that contains a subdirectory "list". This conflict will happen with any current and future commands, like `vc build`.

In order to make sure the CLI can be invoked either way, this PR deprecates the default behavior. Going forward, a user would see the following.

**Conflicting Command, Run Command**

```bash
$ vc list
# warning: Did you mean to deploy the subdirectory "list"? Use `vc --cwd list` instead.
# ... runs the `list` command
```

**Conflicting Command, Deploy Directory**

```bash
$ vc --cwd list
# ... deploy as normal
```

---

Card: https://linear.app/vercel/issue/BUI-33/prevent-ambiguous-vc-command-oror-dir
2022-03-30 21:06:16 +00:00
Steven
d9e5342eba [examples] Fix angular demo url (#7635) 2022-03-30 11:53:42 -05:00
Steven
b0ab13778d [frameworks] Update comments (#7636)
The comments were outdated
2022-03-30 10:14:03 -04:00
Nathan Rajlich
c76dfbe8c9 Publish Canary
- @vercel/build-utils@2.15.1-canary.0
 - vercel@24.0.2-canary.0
 - @vercel/client@10.4.1-canary.0
 - @vercel/frameworks@0.7.1-canary.0
 - @vercel/go@1.3.2-canary.0
 - @vercel/node@1.14.1-canary.0
 - @vercel/python@2.2.2-canary.0
 - @vercel/redwood@0.6.1-canary.0
 - @vercel/routing-utils@1.13.1-canary.0
 - @vercel/ruby@1.3.2-canary.0
 - @vercel/static-build@0.23.1-canary.0
2022-03-29 18:12:23 -07:00
Daniel Choudhury
ed6f3cc27e [redwood] Add default rewrites, cleanUrls and nested API function support
Working with @TooTallNate to update the Vercel Redwood builder,
in preparation for the Redwood 1.0 launch.

- Support for nested api functions in dist folder:

This is essentially a change to the glob to pick up functions 1 level deep in the dist folder. In prior version of Redwood, we had a workaround specific to Vercel that we would like to take out. See redwoodjs/redwood#4813.

- ZeroConfig redirects:

Redwood now generates a 200.html file on every build, which is the "fallback" page for when a html file cannot be matched with the path. This PR adds support for using this 200.html file, out of the box for Redwood projects on Vercel.

This is to make it easy for users to deploy a partly prerendered project - without having to mess with the vercel.json file.

Closes #7592.
Closes #7630.
2022-03-29 18:07:49 -07:00
Nathan Rajlich
7b0186bffe [build-utils] Use File instead of FileBase for builder output type (#7632)
This allows for the individual `File` subtypes to be properly narrowed.
2022-03-29 18:06:03 -07:00
Nathan Rajlich
45e22b6b60 [client] Drop "/now" prefix from API endpoints (#7616)
This prefix is not necessary for the API endpoints so remove.

Also updated a reference of `NowConfig` to use `VercelConfig` instead.
2022-03-26 21:34:11 +00:00
Nathan Rajlich
b2bfae6e2e [client] Don't consider rootDirectory for --prebuilt buildFileTree() (#7615)
`vc build` outputs to the root-level `.vercel` directory, so no need to consider `rootDirectory` of the Project Settings when collecting this file list.
2022-03-25 05:26:56 +00:00
Nathan Rajlich
b6ed718f52 [build-utils] Add a few more files to .npmignore (#7614)
These files don't need to be in the published npm package,
so added them to the npm ignore file.
2022-03-24 19:09:22 -04:00
Steven
a84c4a37e6 [tests] Add lint rule to prevent substr() (#7618)
Follow up to #7588 so we don't accidentally add substr() again
2022-03-24 19:08:42 -04:00
CommanderRoot
d2ba06c6cb [examples][cli][routing-utils][static-build] replace deprecated String.prototype.substr() (#7588)
.substr() is deprecated so we replace it with .slice() or substring() which aren't deprecated
Signed-off-by: Tobias Speicher <rootcommander@gmail.com>

Co-authored-by: Steven <steven@ceriously.com>
2022-03-24 17:03:40 -04:00
Steven
06e7753df0 [frameworks] Revert default version (#7613)
This is property is used for documentation purposes and must match the default version the the preinstall binary located in the build image.
2022-03-24 11:34:41 -04:00
Andrew Healey
235a9c3300 [examples] Revert Hugo example to a working state (#7611) 2022-03-24 13:00:22 +00:00
Nathan Rajlich
1b70402325 [client] Use .vercel/output for --prebuilt (#7555) 2022-03-23 13:27:02 -07:00
Steven
c76781fac9 Publish Stable
- @vercel/build-utils@2.15.0
 - vercel@24.0.1
 - @vercel/client@10.4.0
 - @vercel/frameworks@0.7.0
 - @vercel/go@1.3.1
 - @vercel/node-bridge@2.2.0
 - @vercel/node@1.14.0
 - @vercel/python@2.2.1
 - @vercel/redwood@0.6.0
 - @vercel/routing-utils@1.13.0
 - @vercel/ruby@1.3.1
 - @vercel/static-build@0.23.0
2022-03-22 15:17:06 -04:00
Steven
451e0b0cfb Publish Canary
- @vercel/build-utils@2.14.1-canary.8
 - vercel@24.0.1-canary.9
 - @vercel/client@10.3.1-canary.8
 - @vercel/frameworks@0.6.1-canary.7
 - @vercel/go@1.3.1-canary.8
 - @vercel/node@1.13.1-canary.9
 - @vercel/python@2.2.1-canary.8
 - @vercel/redwood@0.5.2-canary.6
 - @vercel/ruby@1.3.1-canary.8
 - @vercel/static-build@0.22.2-canary.6
2022-03-22 14:53:04 -04:00
Ethan Arrowood
cf477d45b2 [frameworks] add pnpm to framework install placeholders (#7587)
Add `pnpm` commands to framework `installCommand.placeholders` so that it appears in the UI.

Additionally, adds the `pnpm-lock.yaml` file to an ignore list like the other lock files.
2022-03-22 14:52:07 -04:00
Steven
cdd2d69e07 Publish Canary
- @vercel/static-build@0.22.2-canary.5
2022-03-22 13:45:48 -04:00
Lee Robinson
43f1f8b257 [static-build] Don't fail CRA builds on warnings (#7584) 2022-03-22 11:04:42 -05:00
Steven
34055e3599 Publish Canary
- @vercel/build-utils@2.14.1-canary.7
 - vercel@24.0.1-canary.8
 - @vercel/client@10.3.1-canary.7
 - @vercel/frameworks@0.6.1-canary.6
 - @vercel/go@1.3.1-canary.7
 - @vercel/node@1.13.1-canary.8
 - @vercel/python@2.2.1-canary.7
 - @vercel/redwood@0.5.2-canary.5
 - @vercel/ruby@1.3.1-canary.7
 - @vercel/static-build@0.22.2-canary.4
2022-03-22 11:32:44 -04:00
Steven
26abb0a85a [api] Update /api/examples/list to be static for vc init (#7596)
Previously, this API would download the entire git repository for each request. Instead, we can defer downloading to the `/download` API only and change the list API to be statically generated at build time.

This will improve the time to render the results from `vc init`.


## Before
- `https://vercel-lu6z5kf4s.vercel.sh/api/examples/list` responds in 6200 ms
- `https://vercel-lu6z5kf4s.vercel.sh/api/examples/list-all` responds in 800ms

## After
- `https://vercel-ctsxcwzgc.vercel.sh/api/examples/list` responds in 60 ms 
- `https://vercel-ctsxcwzgc.vercel.sh/api/examples/list-all` responds in 60 ms
2022-03-22 11:26:19 -04:00
Lee Robinson
a1e337e0dd [examples] Change hugo template to one that works with older version (#7601) 2022-03-22 08:42:34 -05:00
Steven
b72ead480f [tests] Bump turborepo to 1.1.9 (#7598)
Bump to latest turborepo
2022-03-22 03:15:42 +00:00
Lee Robinson
77cf68105f [Fix Hugo Version. (#7600) 2022-03-21 18:14:25 -05:00
Steven
d800f55dfa [cli] Fix vc init spinner (#7594) 2022-03-21 16:34:29 -04:00
Ethan Arrowood
9dde99f19e Publish Canary
- @vercel/build-utils@2.14.1-canary.6
 - vercel@24.0.1-canary.7
 - @vercel/client@10.3.1-canary.6
 - @vercel/frameworks@0.6.1-canary.5
 - @vercel/go@1.3.1-canary.6
 - @vercel/node@1.13.1-canary.7
 - @vercel/python@2.2.1-canary.6
 - @vercel/redwood@0.5.2-canary.4
 - @vercel/ruby@1.3.1-canary.6
 - @vercel/static-build@0.22.2-canary.3
2022-03-21 13:15:51 -06:00
Ethan Arrowood
fae7a083fc Revert "Remove file system environment variable (#7535)" (#7595)
This reverts commit e908378486.
2022-03-21 13:13:16 -06:00
Ethan Arrowood
cbd651d6ee add exit code check (#7578) 2022-03-18 15:48:30 -06:00
Nathan Rajlich
6077a706d1 [build-utils] Allow arbitrary properties on the Meta interface (#7582)
`vc build` is going to re-use the `meta` object between Builder
invocations, to allow arbitrary state to be shared between them. So
allow for additional properties to be specified on `Meta` with an
`unknown` type.
2022-03-17 17:27:42 -07:00
Nathan Rajlich
cedc82dd9e [build-utils] Mark arbitrary properties on Config as unknown (#7554) 2022-03-17 17:27:35 -07:00
Nathan Rajlich
b420006401 [build-utils] Remove AnalyzeOptions and analyze() docs (#7550)
The `analyze()` function on a Builder was removed a long time ago.
2022-03-17 17:27:26 -07:00
Steven
8ba604e8fc [tests] Remove unused dep cheerio (#7579)
- Closes #7576
- Closes #7500
2022-03-18 00:22:03 +00:00
Nathan Rajlich
fadeee4568 [cli] Update 14-svelte-node fixture to run "dev" command (#7583) 2022-03-17 16:18:50 -07:00
Lee Robinson
2b15ba7f46 Downgrade Hugo Version (#7562) 2022-03-16 14:39:06 -05:00
Ethan Arrowood
4cdfd0e58c [tests] Fix pnpm integration tests (#7563)
* fix test 24 and add test 25

* move pnp/symlink test to cli and complete hoisted test

* fix execa

* remove unnecessary gitignore

* Update packages/cli/test/integration.js

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

* Update packages/cli/test/integration.js

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

* complete cherry-pick

* remove package.json edit

* use fs-extra remove

* use fs-extra.remove correctly

Co-authored-by: Steven <steven@ceriously.com>
2022-03-16 14:49:02 -04:00
Steven
b3ccb5f3ef Publish Canary
- @vercel/build-utils@2.14.1-canary.5
 - vercel@24.0.1-canary.6
 - @vercel/client@10.3.1-canary.5
 - @vercel/frameworks@0.6.1-canary.4
 - @vercel/go@1.3.1-canary.5
 - @vercel/node@1.13.1-canary.6
 - @vercel/python@2.2.1-canary.5
 - @vercel/redwood@0.5.2-canary.3
 - @vercel/ruby@1.3.1-canary.5
 - @vercel/static-build@0.22.2-canary.2
2022-03-15 16:08:42 -04:00
Steven
584acc43b7 [node] Consolidate dev and prd tsconfig.json (#7549)
In a previous PR (#4254), the behavior of `vercel dev` was added separately without matching how deployments work.

This PR consolidates the `tsconfig.json` options for `vercel` deployments as well as `vercel dev` so they work the same.

Fixes https://github.com/vercel/customer-issues/issues/349
2022-03-15 16:03:28 -04:00
Adrian Bettridge-Wiese
f64be93b94 Add screenshots to frameworks (#7570) 2022-03-15 12:48:44 -07:00
Adrian Bettridge-Wiese
9abd31769e Revert "Add screenshot property to frameworks" (#7569)
This reverts commit 09e3b35e74.
2022-03-15 13:58:44 -04:00
Adrian Bettridge-Wiese
09e3b35e74 Add screenshot property to frameworks 2022-03-15 12:47:40 -05:00
Nathan Rajlich
8aa9a0ea05 [build-utils] Remove as cast in Lambda (#7553)
`Files` can now be properly narrowed by checking "type", so no need for the `as` cast here anymore.
2022-03-14 18:04:58 +00:00
Nathan Rajlich
b2d0ed74c6 Revert "[ruby] Use new Lambda() (#7551)"
This reverts commit 501be936c0.
2022-03-12 11:22:49 -08:00
Nathan Rajlich
aef936af0f Revert "[python] Use new Lambda()"
This reverts commit 7eba282af5.
2022-03-12 11:22:44 -08:00
Nathan Rajlich
501be936c0 [ruby] Use new Lambda() (#7551)
`createLambda()` is deprecated.
2022-03-11 21:19:32 -08:00
Nathan Rajlich
7eba282af5 [python] Use new Lambda()
`createLambda()` is deprecated.
2022-03-11 21:03:42 -08:00
Nathan Rajlich
cf3e4bd726 [build-utils] Use File type for Prerender fallback (#7539)
Tiny follow-up to #7530 since we can now use this union type to express
the same group of File implementation types.
2022-03-11 15:44:06 -05:00
Ethan Arrowood
ee5361b00e Revert "pnpm pnp symlinkn integration test (#7543)" (#7548)
This reverts commit bd929dd5c5.
2022-03-11 15:30:22 -05:00
Ethan Arrowood
bd929dd5c5 pnpm pnp symlinkn integration test (#7543)
* temp commit

* update test case

* check for build cache hit

* Update packages/build-utils/test/integration.test.ts

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

Co-authored-by: Steven <steven@ceriously.com>
2022-03-11 10:52:48 -07:00
Sean Massa
4ee064bb61 [static-build] Add @vercel/static-build Builder (#7536) 2022-03-10 13:21:24 -06:00
Sean Massa
312a2090a6 [client] preserve symlinks during deploy upload (#7533)
Ensure that deploy uploads preserve symlinks.

---

Card: https://linear.app/vercel/issue/BUI-23/preserve-symlinks-in-vc-deploy
2022-03-10 01:54:03 +00:00
Sean Massa
a82f117217 [build-utils] Preserve symlinks for FileRef and FileBlob types in download() (#7534)
Co-authored-by: Nathan Rajlich <n@n8.io>
2022-03-09 16:41:03 -06:00
Ethan Arrowood
e908378486 Remove file system environment variable (#7535)
When running this example using `pnpm`, the filesystem environment variable was causing issues.

This change removes the variable and the file since it no longer serves a purpose. 

Deployment was tested using `pnpm`, `npm`, and `yarn` and all seem to run successfully now
2022-03-09 13:53:23 -06:00
Sean Massa
8cda5263eb [build-utils] Streamle File types for better type narrowing (#7530) 2022-03-08 14:29:33 -08:00
Ethan Arrowood
a24fd64bce Publish Canary
- @vercel/build-utils@2.14.1-canary.4
 - vercel@24.0.1-canary.5
 - @vercel/client@10.3.1-canary.4
 - @vercel/frameworks@0.6.1-canary.3
 - @vercel/go@1.3.1-canary.4
 - @vercel/node-bridge@2.1.2-canary.1
 - @vercel/node@1.13.1-canary.5
 - @vercel/python@2.2.1-canary.4
 - @vercel/redwood@0.5.2-canary.2
 - @vercel/routing-utils@1.12.1-canary.0
 - @vercel/ruby@1.3.1-canary.4
2022-03-08 12:39:54 -07:00
Ethan Arrowood
0c515a46d5 [build-utils] Change pretty command to pnpm run (#7529) 2022-03-08 11:24:56 -08:00
Gal Schlezinger
f19690dc32 [build-utils] Add EdgeFunction output type (#7510)
Co-authored-by: Steven <steven@ceriously.com>
2022-03-08 18:58:26 +02:00
JJ Kasper
6b2a1c3866 [node-bridge][build-utils] Add multi payload lambda handling in node-bridge (#7507)
### Related Issues

x-ref: https://github.com/vercel/next.js/pull/34935

### 📋 Checklist

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

#### Tests

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

#### Code Review

- [ ] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
2022-03-06 08:57:45 +00:00
Sean Massa
1e54d606d7 [cli] fail when local config is missing (#7516)
Currently, when the path is not found in a `vc deploy -A SOME_PATH` command, the fallback config is used, which is unexpected behavior. This PR changes that to throw an error that says the path was not found.

---

Card: https://linear.app/vercel/issue/BUI-32/vc-deploy-a-should-fail-when-path-not-found
2022-03-04 00:33:45 +00:00
Nathan Rajlich
c4ab0ebe9c Publish Canary
- vercel@24.0.1-canary.4
 - @vercel/node@1.13.1-canary.4
2022-03-03 13:49:02 -08:00
Nathan Rajlich
321f1232a1 [node] Move @vercel/node-bridge to dependencies (#7515)
Fixes issue introduced by #7436 when using `vc dev`. Since the dev-server script now imports `@vercel/node-bridge` directly, instead of copying the relevant files into the package, the bridge needs to be a regular dependency otherwise the import will fail.

```
Error: Cannot find module '@vercel/node-bridge/launcher.js'
Require stack:
- /usr/local/lib/node_modules/vercel/node_modules/@vercel/node/dist/dev-server.js
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:902:15)
    at Function.Module._load (internal/modules/cjs/loader.js:746:27)
    at Module.require (internal/modules/cjs/loader.js:974:19)
    at require (internal/modules/cjs/helpers.js:93:18)
    at Object.<anonymous> (/usr/local/lib/node_modules/vercel/node_modules/@vercel/node/dist/dev-server.js:79:23)
    at Module._compile (internal/modules/cjs/loader.js:1085:14)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
    at Module.load (internal/modules/cjs/loader.js:950:32)
    at Function.Module._load (internal/modules/cjs/loader.js:790:12)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:76:12)
Error! `node api/hello.js` failed with exit code 1
```
2022-03-03 21:04:33 +00:00
Nathan Rajlich
8a8203e149 Publish Canary
- @vercel/redwood@0.5.2-canary.1
2022-03-02 17:23:02 -08:00
Sean Massa
33527165e7 remove outdated comment (#7512) 2022-03-02 17:21:08 -08:00
Sean Massa
db10383bc8 [redwood] Add @vercel/redwood Builder (#7503) 2022-03-02 15:17:29 -08:00
Sean Massa
56960e506e [cli] enhance changelog accuracy and format (#7387)
* refactor and add test to changelog

* group changelog into areas

* remove revert and reverted commits from changelog

* make sure Revert commits that revert a commit in a previous changelog are not filtered

* run "yarn test" as part of "yarn test-unit"

* remove advanced syntax

* remove advanced syntax

* temp changelog

* remove temp changelog

* remove global jest config

* Delete symlink

* scope jest dir to just the test files we care about
2022-03-02 10:22:56 -08:00
Ethan Arrowood
a17f3a96ec Publish Canary
- @vercel/build-utils@2.14.1-canary.3
 - vercel@24.0.1-canary.3
 - @vercel/client@10.3.1-canary.3
 - @vercel/go@1.3.1-canary.3
 - @vercel/node@1.13.1-canary.3
 - @vercel/python@2.2.1-canary.3
 - @vercel/ruby@1.3.1-canary.3
2022-03-02 08:21:30 -07:00
Sam Ko
9ff86a896c [examples] Update todo functionality in the "sveltekit" example (#7506) 2022-03-01 15:50:04 -08:00
Jared Palmer
6ccb4354f9 Add support for PNPM (#6834)
Add support for `pnpm` as well as new fixtures for `pnpm` with and without workspaces

#### 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 && yarn test-integration-once` 

#### 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-03-01 22:34:39 +00:00
Nathan Rajlich
82ba932447 Publish Canary
- @vercel/build-utils@2.14.1-canary.2
 - vercel@24.0.1-canary.2
 - @vercel/client@10.3.1-canary.2
 - @vercel/frameworks@0.6.1-canary.2
 - @vercel/go@1.3.1-canary.2
 - @vercel/node@1.13.1-canary.2
 - @vercel/python@2.2.1-canary.2
 - @vercel/ruby@1.3.1-canary.2
2022-02-28 19:57:09 -08:00
Nathan Rajlich
b995618afb [build-utils] Make files in Lambda be optional (#7498)
This ensures better backwards compatibility.
2022-02-28 19:53:25 -08:00
Nathan Rajlich
c897b24417 [build-utils] Allow Prerender type in BuildV2 output (#7499) 2022-02-28 19:52:55 -08:00
Nathan Rajlich
e64b2e1993 [build-utils] Make Builder routes output optional (#7497) 2022-02-28 16:05:17 -08:00
Haneen Mahdin
9358a5469e [docs] Fix wrong test command in CONTRIBUTING (#7392)
* docs: use meaningful word in contributing

I have changed the word `run` to `pass` in the Contributing Guidelines file inside `Verifying your change` documentation.
This provides a better understanding of how to run tests after every change.

* docs: remove `test` command run in local development

Remove guideline to test the code using `yarn test`, the test command does not exist in the project root but the build command automatically runs the tests.

* docs: add info about how tests are run

Add information about tests are run in this repo

* docs: fix testing scripts reference

Fixed some mistakes in the contributing readme
- Added `yarn test-unit` instead of `yarn test`

Co-authored-by: Steven <steven@ceriously.com>
2022-02-25 17:43:45 -05:00
Ella
2a24210b7d [examples] Fix link to Remix nested routes documentation (#7366)
* Remix example: fix link to nested routes

* Update about.tsx

Co-authored-by: Ella <72365100+eilla1@users.noreply.github.com>
Co-authored-by: Steven <steven@ceriously.com>
2022-02-23 12:26:49 -05:00
Lee Robinson
2644a59984 [examples] Re-add 11ty license and author info (#7476) 2022-02-23 11:19:27 -05:00
Lee Robinson
4eeb8c298c Update default Hugo & Zola versions. (#7467) 2022-02-22 14:23:59 -06:00
Nathan Rajlich
0351f02dff Publish Canary
- @vercel/build-utils@2.14.1-canary.1
 - vercel@24.0.1-canary.1
 - @vercel/client@10.3.1-canary.1
 - @vercel/frameworks@0.6.1-canary.1
 - @vercel/go@1.3.1-canary.1
 - @vercel/node@1.13.1-canary.1
 - @vercel/python@2.2.1-canary.1
 - @vercel/ruby@1.3.1-canary.1
2022-02-22 12:13:21 -08:00
Yuanlin Lin
0d7fa2f912 [frameworks] Updated Umi.js logo (#7470)
Co-authored-by: Andy <AndyBitz@users.noreply.github.com>
2022-02-22 19:59:32 +01:00
Nathan Rajlich
3b646880e7 [node] Use NodejsLambda class (#7436)
Updates `@vercel/node` to utilize the `NodejsLambda` class so that it doesn't need to explicitly bundle in the launcher/bridge files into the build result. This reduces the complexity of the Builder and is a cleaner separation of concerns. The `helpers.test.ts` file has been moved to `@vercel/node-bridge` so it's being removed in this PR as well.
2022-02-22 16:57:11 +00:00
Steven
350a0e5f36 [examples] Bump Next.js to 12.1.0 (#7458) 2022-02-18 18:53:26 -05:00
Steven
5c21d400bd Update dev test for image optimization with next@latest and python flask (#7454)
Related to https://github.com/vercel/next.js/pull/34431
2022-02-18 16:37:59 +00:00
Nathan Rajlich
04029013a6 Publish Canary
- @vercel/build-utils@2.14.1-canary.0
 - vercel@24.0.1-canary.0
 - @vercel/client@10.3.1-canary.0
 - @vercel/frameworks@0.6.1-canary.0
 - @vercel/go@1.3.1-canary.0
 - @vercel/node-bridge@2.1.2-canary.0
 - @vercel/node@1.13.1-canary.0
 - @vercel/python@2.2.1-canary.0
 - @vercel/ruby@1.3.1-canary.0
2022-02-17 20:40:07 -08:00
Nathan Rajlich
c65e7fa883 [node-bridge] Move helpers to node-bridge (#7451) 2022-02-17 20:36:37 -08:00
Knut Melvær
27b68be93f [frameworks] Add Sanity Studio (#7350)
Adds the default config and detector for a Sanity Studio project.
2022-02-16 14:26:39 -08:00
Nathan Rajlich
99fa729966 Publish Stable
- @vercel/build-utils@2.14.0
 - vercel@24.0.0
 - @vercel/client@10.3.0
 - @vercel/frameworks@0.6.0
 - @vercel/go@1.3.0
 - @vercel/node-bridge@2.1.1
 - @vercel/node@1.13.0
 - @vercel/python@2.2.0
 - @vercel/routing-utils@1.12.0
 - @vercel/ruby@1.3.0
 - @vercel/static-config@1.0.0-canary.0
2022-02-16 11:01:47 -08:00
Nathan Rajlich
2bb3da80e0 [cli] Remove timestamp based lookback in vc bisect --help (#7442)
Looking up a deployment via timestamp is not implemented in the `vc
bisect` command, so remove it from the `--help` output.
2022-02-16 01:54:47 -08:00
Nathan Rajlich
b852f34a27 [build-utils] Add Builder TypeScript types (#7386)
Adds formal type interfaces for Builders into `@vercel/build-utils`, and
updates the serverless functions runtimes to utilize them. This provides
type safety for the args/return values in i.e. the `build()` functions,
where previously they were not using any explicit return type.
2022-02-16 00:33:25 -08:00
Nathan Rajlich
ce8e6e3806 [build-utils] Add NodejsLambda class (#7423)
When an instance of this class is returned in a Builder's `output`, it is a signal to the build system that it needs to add additional files to the final Lambda before creating the zip file.
2022-02-15 22:17:12 +00:00
Nathan Rajlich
983946650e [cli] Remove initial iteration of vc build and Plugins (#7390)
* [cli] Remove initial iteration of `vc build` and Plugins

The `vercel build` command is being restructured, so we'll remove it for
now so that this initial iteration isn't included as part of the stable
CLI release.

* Update packages/cli/src/commands/init/init.ts

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

* Remove more `.output` references

* Remove unnecessary space

* Fix test

* Remove vc dev middleware tests for now

Co-authored-by: Steven <steven@ceriously.com>
2022-02-14 19:00:15 -08:00
Steven
59e4572e76 Publish Canary
- vercel@23.1.3-canary.76
 - @vercel/node@1.12.2-canary.11
 - vercel-plugin-node@1.12.2-canary.43
2022-02-12 20:51:14 -05:00
Steven
5c297122cb [cli][node] Bump nft to 0.17.5 (#7349)
- Fixes #7287
2022-02-12 22:39:18 +00:00
2067 changed files with 375017 additions and 16976 deletions

View File

@@ -1,7 +1,12 @@
node_modules
dist
examples
packages/node/src/bridge.ts
packages/*/test/fixtures
# cli
packages/cli/@types
packages/cli/download
packages/cli/dist
@@ -9,9 +14,24 @@ packages/cli/test/dev/fixtures
packages/cli/bin
packages/cli/link
packages/cli/src/util/dev/templates/*.ts
# client
packages/client/tests/fixtures
packages/client/lib
packages/node/src/bridge.ts
# node-bridge
packages/node-bridge/bridge.js
packages/node-bridge/launcher.js
packages/node-bridge/helpers.js
packages/node-bridge/source-map-support.js
# middleware
packages/middleware/src/entries.js
# static-build
packages/static-build/test/fixtures
packages/static-build/test/build-fixtures
packages/static-build/test/cache-fixtures
# redwood
packages/redwood/test/fixtures

View File

@@ -16,17 +16,17 @@ yarn install
yarn bootstrap
yarn build
yarn lint
yarn test
yarn test-unit
```
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 run by running:
Once you are done with your changes (we even suggest doing it along the way), make sure all the test still pass by running:
```
yarn build && yarn test
yarn test-unit
```
from the root of the project.

View File

@@ -11,7 +11,7 @@ on:
jobs:
test:
name: CLI
timeout-minutes: 30
timeout-minutes: 40
strategy:
fail-fast: false
matrix:

1
.gitignore vendored
View File

@@ -27,5 +27,4 @@ test/lib/deployment/failed-page.txt
/public
__pycache__
.vercel
.output
.turbo

View File

@@ -9,7 +9,6 @@ A Runtime is an npm module that implements the following interface:
interface Runtime {
version: number;
build: (options: BuildOptions) => Promise<BuildResult>;
analyze?: (options: AnalyzeOptions) => Promise<string>;
prepareCache?: (options: PrepareCacheOptions) => Promise<CacheOutputs>;
shouldServe?: (options: ShouldServeOptions) => Promise<boolean>;
startDevServer?: (
@@ -72,26 +71,6 @@ export async function build(options: BuildOptions) {
}
```
### `analyze()`
An **optional** exported function that returns a unique fingerprint used for the
purpose of [build
de-duplication](https://vercel.com/docs/v2/platform/deployments#deduplication).
If the `analyze()` function is not supplied, then a random fingerprint is
assigned to each build.
**Example:**
```typescript
import { AnalyzeOptions } from '@vercel/build-utils';
export async function analyze(options: AnalyzeOptions) {
// Do calculations to generate a fingerprint based off the source code here…
return 'fingerprint goes here';
}
```
### `prepareCache()`
An **optional** exported function that is executed after [`build()`](#build) is

46
api/_lib/script/build.ts Normal file
View File

@@ -0,0 +1,46 @@
import fs from 'fs/promises';
import { join } from 'path';
import { getExampleList } from '../examples/example-list';
import { mapOldToNew } from '../examples/map-old-to-new';
const repoRoot = join(__dirname, '..', '..', '..');
const pubDir = join(repoRoot, 'public');
async function main() {
console.log(`Building static frontend ${repoRoot}...`);
await fs.rm(pubDir, { recursive: true, force: true });
await fs.mkdir(pubDir);
const examples = await getExampleList();
const pathListAll = join(pubDir, 'list-all.json');
await fs.writeFile(pathListAll, JSON.stringify(examples));
const exampleDirs = await fs.readdir(join(repoRoot, 'examples'), {
withFileTypes: true,
});
const existingExamples = exampleDirs
.filter(dir => dir.isDirectory())
.map(dir => ({
name: dir.name,
visible: true,
suggestions: [],
}));
const oldExamples = Object.keys(mapOldToNew).map(key => ({
name: key,
visible: false,
suggestions: mapOldToNew[key],
}));
const pathList = join(pubDir, 'list.json');
await fs.writeFile(
pathList,
JSON.stringify([...existingExamples, ...oldExamples])
);
console.log('Completed building static frontend.');
}
main().catch(console.error);

View File

@@ -1,10 +0,0 @@
import { VercelRequest, VercelResponse } from '@vercel/node';
import { getExampleList } from '../_lib/examples/example-list';
import { withApiHandler } from '../_lib/util/with-api-handler';
export default withApiHandler(async function (
req: VercelRequest,
res: VercelResponse
) {
res.status(200).json(await getExampleList());
});

View File

@@ -1,27 +0,0 @@
import { extract } from '../_lib/examples/extract';
import { summary } from '../_lib/examples/summary';
import { VercelRequest, VercelResponse } from '@vercel/node';
import { mapOldToNew } from '../_lib/examples/map-old-to-new';
import { withApiHandler } from '../_lib/util/with-api-handler';
export default withApiHandler(async function (
req: VercelRequest,
res: VercelResponse
) {
await extract('https://github.com/vercel/vercel/archive/main.zip', '/tmp');
const exampleList = summary('/tmp/vercel-main/examples');
const existingExamples = Array.from(exampleList).map(key => ({
name: key,
visible: true,
suggestions: [],
}));
const oldExamples = Object.keys(mapOldToNew).map(key => ({
name: key,
visible: false,
suggestions: mapOldToNew[key],
}));
res.status(200).json([...existingExamples, ...oldExamples]);
});

View File

@@ -41,4 +41,3 @@ testem.log
.DS_Store
Thumbs.db
.vercel
.output

View File

@@ -10,7 +10,7 @@ Deploy your own Angular project with Vercel.
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/vercel/vercel/tree/main/examples/angular&template=angular)
_Live Example: https://angular-now-examples.vercel.app_
_Live Example: https://angular-template.vercel.app_
## Development server

View File

@@ -0,0 +1,20 @@
MIT License
Copyright (c) 2022 Zach Leatherman @zachleat
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,5 +1,19 @@
{
"private": true,
"repository": {
"type": "git",
"url": "git://github.com/11ty/eleventy-base-blog.git"
},
"author": {
"name": "Zach Leatherman",
"email": "zachleatherman@gmail.com",
"url": "https://zachleat.com/"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/11ty/eleventy-base-blog/issues"
},
"homepage": "https://github.com/11ty/eleventy-base-blog#readme",
"scripts": {
"build": "eleventy",
"watch": "eleventy --watch",

View File

@@ -56,7 +56,7 @@ async function main() {
// if it's an empty string, just ignore it
if (!formatted) return false;
let type = url.substr(-3) == 'css' ? 'style' : 'script';
let type = url.slice(-3) == 'css' ? 'style' : 'script';
results += `</${formatted}>;rel=preload;as=${type},`;
});

View File

@@ -23,6 +23,7 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env.local

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -7,12 +7,12 @@
"lint": "next lint"
},
"dependencies": {
"next": "^12.0.8",
"react": "17.0.2",
"react-dom": "17.0.2"
"next": "12.1.4",
"react": "18.0.0",
"react-dom": "18.0.0"
},
"devDependencies": {
"eslint": "8.7.0",
"eslint-config-next": "^12.0.8"
"eslint": "8.12.0",
"eslint-config-next": "12.1.4"
}
}

View File

@@ -2,7 +2,6 @@ node_modules
.cache
.vercel
.output
public/build
api/_build

View File

@@ -31,7 +31,7 @@ export default function Index() {
<p>
Wait a sec...<em>its children</em>? To understand what we mean by
this,{" "}
<a href="https://remix.run/tutorial/4-nested-routes-params">
<a href="https://remix.run/docs/en/v1/guides/routing">
read all about nested routes in the docs
</a>
.

View File

@@ -0,0 +1,3 @@
# Run `vercel env pull` to generate a .env file from your Vercel project
SANITY_STUDIO_API_PROJECT_ID=
SANITY_STUDIO_API_DATASET=

View File

@@ -0,0 +1,12 @@
# Logs
/logs
*.log
# Coverage directory used by tools like istanbul
/coverage
# Dependency directories
node_modules
# Compiled sanity studio
/dist

29
examples/sanity/README.md Normal file
View File

@@ -0,0 +1,29 @@
# Sanity Blogging Content Studio
Congratulations, you have now installed Sanity Studio, an open source real-time content editing environment connected to the Sanity backend.
Now you can do the following things:
- [Read “getting started” in the docs](https://www.sanity.io/docs/introduction/getting-started?utm_source=readme)
- Check out the example frontend: [React/Next.js](https://github.com/sanity-io/tutorial-sanity-blog-react-next)
- [Read the blog post about this template](https://www.sanity.io/blog/build-your-own-blog-with-sanity-and-next-js?utm_source=readme)
- [Join the community Slack](https://slack.sanity.io/?utm_source=readme)
- [Extend and build plugins](https://www.sanity.io/docs/content-studio/extending?utm_source=readme)
## Develop locally
Install dependencies:
```sh
npx @sanity/cli install
```
Pull down environment variables from your Vercel project (requires the [Vercel CLI](https://vercel.com/cli)):
```sh
vercel env pull
```
You can also run `npx @sanity/init` in this repo and agree to reconfigure it. You'll then be able to select from existing projects. The CLI will update `sanity.json` with the project ID and dataset name.

View File

@@ -0,0 +1,7 @@
{
"#": "Used by Sanity to keep track of configuration file checksums, do not delete or modify!",
"@sanity/default-layout": "bb034f391ba508a6ca8cd971967cbedeb131c4d19b17b28a0895f32db5d568ea",
"@sanity/default-login": "6fb6d3800aa71346e1b84d95bbcaa287879456f2922372bb0294e30b968cd37f",
"@sanity/form-builder": "b38478227ba5e22c91981da4b53436df22e48ff25238a55a973ed620be5068aa",
"@sanity/data-aspects": "d199e2c199b3e26cd28b68dc84d7fc01c9186bf5089580f2e2446994d36b3cb6"
}

View File

@@ -0,0 +1,3 @@
{
"listOptions": {}
}

View File

@@ -0,0 +1,6 @@
{
"toolSwitcher": {
"order": [],
"hidden": []
}
}

View File

@@ -0,0 +1,7 @@
{
"providers": {
"mode": "append",
"redirectOnSingle": false,
"entries": []
}
}

View File

@@ -0,0 +1,5 @@
{
"images": {
"directUploads": true
}
}

View File

@@ -0,0 +1,30 @@
{
"name": "verceltemplateblogstudio",
"private": true,
"version": "1.0.0",
"description": "This is the public list of examples for **Vercel**",
"main": "package.json",
"author": "Knut Melvær <knut@sanity.io>",
"license": "UNLICENSED",
"scripts": {
"start": "sanity start",
"build": "sanity build"
},
"keywords": [
"sanity"
],
"dependencies": {
"@sanity/core": "^2.26",
"@sanity/default-layout": "^2.26",
"@sanity/default-login": "^2.26",
"@sanity/desk-tool": "^2.26",
"@sanity/vision": "^2.26",
"prop-types": "^15.7",
"react": "^17.0",
"react-dom": "^17.0",
"styled-components": "^5.2"
},
"devDependencies": {
"@sanity/cli": "^2.26"
}
}

View File

@@ -0,0 +1 @@
User-specific packages can be placed here

View File

@@ -0,0 +1,29 @@
{
"root": true,
"project": {
"name": "vercel-template-blog-studio"
},
"api": {
"projectId": "YOUR_PROJECT_ID",
"dataset": "YOUR_DATASET_NAME"
},
"plugins": [
"@sanity/base",
"@sanity/default-layout",
"@sanity/default-login",
"@sanity/desk-tool"
],
"env": {
"development": {
"plugins": [
"@sanity/vision"
]
}
},
"parts": [
{
"name": "part:@sanity/base/schema",
"path": "./schemas/schema"
}
]
}

View File

@@ -0,0 +1,48 @@
export default {
name: 'author',
title: 'Author',
type: 'document',
fields: [
{
name: 'name',
title: 'Name',
type: 'string',
},
{
name: 'slug',
title: 'Slug',
type: 'slug',
options: {
source: 'name',
maxLength: 96,
},
},
{
name: 'image',
title: 'Image',
type: 'image',
options: {
hotspot: true,
},
},
{
name: 'bio',
title: 'Bio',
type: 'array',
of: [
{
title: 'Block',
type: 'block',
styles: [{title: 'Normal', value: 'normal'}],
lists: [],
},
],
},
],
preview: {
select: {
title: 'name',
media: 'image',
},
},
}

View File

@@ -0,0 +1,65 @@
/**
* This is the schema definition for the rich text fields used for
* for this blog studio. When you import it in schemas.js it can be
* reused in other parts of the studio with:
* {
* name: 'someName',
* title: 'Some title',
* type: 'blockContent'
* }
*/
export default {
title: 'Block Content',
name: 'blockContent',
type: 'array',
of: [
{
title: 'Block',
type: 'block',
// Styles let you set what your user can mark up blocks with. These
// correspond with HTML tags, but you can set any title or value
// you want and decide how you want to deal with it where you want to
// use your content.
styles: [
{title: 'Normal', value: 'normal'},
{title: 'H1', value: 'h1'},
{title: 'H2', value: 'h2'},
{title: 'H3', value: 'h3'},
{title: 'H4', value: 'h4'},
{title: 'Quote', value: 'blockquote'},
],
lists: [{title: 'Bullet', value: 'bullet'}],
// Marks let you mark up inline text in the block editor.
marks: {
// Decorators usually describe a single property e.g. a typographic
// preference or highlighting by editors.
decorators: [
{title: 'Strong', value: 'strong'},
{title: 'Emphasis', value: 'em'},
],
// Annotations can be any object structure e.g. a link or a footnote.
annotations: [
{
title: 'URL',
name: 'link',
type: 'object',
fields: [
{
title: 'URL',
name: 'href',
type: 'url',
},
],
},
],
},
},
// You can add additional types here. Note that you can't use
// primitive types such as 'string' and 'number' in the same array
// as a block type.
{
type: 'image',
options: {hotspot: true},
},
],
}

View File

@@ -0,0 +1,17 @@
export default {
name: 'category',
title: 'Category',
type: 'document',
fields: [
{
name: 'title',
title: 'Title',
type: 'string',
},
{
name: 'description',
title: 'Description',
type: 'text',
},
],
}

View File

@@ -0,0 +1,65 @@
export default {
name: 'post',
title: 'Post',
type: 'document',
fields: [
{
name: 'title',
title: 'Title',
type: 'string',
},
{
name: 'slug',
title: 'Slug',
type: 'slug',
options: {
source: 'title',
maxLength: 96,
},
},
{
name: 'author',
title: 'Author',
type: 'reference',
to: {type: 'author'},
},
{
name: 'mainImage',
title: 'Main image',
type: 'image',
options: {
hotspot: true,
},
},
{
name: 'categories',
title: 'Categories',
type: 'array',
of: [{type: 'reference', to: {type: 'category'}}],
},
{
name: 'publishedAt',
title: 'Published at',
type: 'datetime',
},
{
name: 'body',
title: 'Body',
type: 'blockContent',
},
],
preview: {
select: {
title: 'title',
author: 'author.name',
media: 'mainImage',
},
prepare(selection) {
const {author} = selection
return Object.assign({}, selection, {
subtitle: author && `by ${author}`,
})
},
},
}

View File

@@ -0,0 +1,29 @@
// First, we must import the schema creator
import createSchema from 'part:@sanity/base/schema-creator'
// Then import schema types from any plugins that might expose them
import schemaTypes from 'all:part:@sanity/base/schema-type'
// We import object and document schemas
import blockContent from './blockContent'
import category from './category'
import post from './post'
import author from './author'
// Then we give our schema to the builder and provide the result to Sanity
export default createSchema({
// We name our schema
name: 'default',
// Then proceed to concatenate our document type
// to the ones provided by any plugins that are installed
types: schemaTypes.concat([
// The following are document types which will appear
// in the studio.
post,
author,
category,
// When added to this list, object types can be used as
// { type: 'typename' } in other document schemas
blockContent,
]),
})

View File

@@ -0,0 +1 @@
Files placed here will be served by the Sanity server under the `/static`-prefix

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,6 @@
{
// Note: This config is only used to help editors like VS Code understand/resolve
// parts, the actual transpilation is done by babel. Any compiler configuration in
// here will be ignored.
"include": ["./node_modules/@sanity/base/types/**/*.ts", "./**/*.ts", "./**/*.tsx"]
}

9911
examples/sanity/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,6 @@ dist
worker
.solid
.vercel
.output
# dependencies
/node_modules

View File

@@ -11,7 +11,7 @@ export class AppProfile {
normalize(name: string): string {
if (name) {
return name.substr(0, 1).toUpperCase() + name.substr(1).toLowerCase();
return name.slice(0, 1).toUpperCase() + name.slice(1).toLowerCase();
}
return '';
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,7 @@
{
"private": true,
"name": "sveltekit",
"version": "0.0.1",
"scripts": {
"dev": "svelte-kit dev",
"build": "svelte-kit build",
@@ -9,7 +11,7 @@
"devDependencies": {
"@sveltejs/adapter-auto": "next",
"@sveltejs/kit": "next",
"svelte": "^3.44.0"
"svelte": "^3.46.0"
},
"type": "module",
"dependencies": {

View File

@@ -8,6 +8,6 @@
%svelte.head%
</head>
<body>
<div id="svelte">%svelte.body%</div>
<div>%svelte.body%</div>
</body>
</html>

View File

@@ -1 +0,0 @@
/// <reference types="@sveltejs/kit" />

View File

@@ -1,19 +1,22 @@
import cookie from 'cookie';
import { v4 as uuid } from '@lukeed/uuid';
export const handle = async ({ request, resolve }) => {
const cookies = cookie.parse(request.headers.cookie || '');
request.locals.userid = cookies.userid || uuid();
export const handle = async ({ event, resolve }) => {
const cookies = cookie.parse(event.request.headers.get('cookie') || '');
event.locals.userid = cookies.userid || uuid();
const response = await resolve(request);
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-cookie'] = cookie.serialize('userid', request.locals.userid, {
path: '/',
httpOnly: true
});
response.headers.set(
'set-cookie',
cookie.serialize('userid', event.locals.userid, {
path: '/',
httpOnly: true
})
);
}
return response;

View File

@@ -22,7 +22,7 @@
<div class="counter-viewport">
<div class="counter-digits" style="transform: translate(0, {100 * offset}%)">
<strong style="top: -100%" aria-hidden="true">{Math.floor($displayed_count + 1)}</strong>
<strong class="hidden" aria-hidden="true">{Math.floor($displayed_count + 1)}</strong>
<strong>{Math.floor($displayed_count)}</strong>
</div>
</div>
@@ -94,4 +94,9 @@
width: 100%;
height: 100%;
}
.hidden {
top: -100%;
user-select: none;
}
</style>

View File

@@ -1,6 +1,8 @@
import { invalidate } from '$app/navigation';
// this action (https://svelte.dev/tutorial/actions) allows us to
// progressively enhance a <form> that already works without JS
export function enhance(form, { pending, error, result }) {
export function enhance(form, { pending, error, result } = {}) {
let current_token;
async function handle_submit(e) {
@@ -8,31 +10,35 @@ export function enhance(form, { pending, error, result }) {
e.preventDefault();
const body = new FormData(form);
const data = new FormData(form);
if (pending) pending(body, form);
if (pending) pending({ data, form });
try {
const res = await fetch(form.action, {
const response = await fetch(form.action, {
method: form.method,
headers: {
accept: 'application/json'
},
body
body: data
});
if (token !== current_token) return;
if (res.ok) {
result(res, form);
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(res, null, form);
error({ data, form, error: null, response });
} else {
console.error(await res.text());
console.error(await response.text());
}
} catch (e) {
if (error) {
error(null, e, form);
error({ data, form, error: e, response: null });
} else {
throw e;
}

View File

@@ -1,14 +0,0 @@
import { api } from './_api';
// PATCH /todos/:uid.json
export const patch = async (request) => {
return api(request, `todos/${request.locals.userid}/${request.params.uid}`, {
text: request.body.get('text'),
done: request.body.has('done') ? !!request.body.get('done') : undefined
});
};
// DELETE /todos/:uid.json
export const del = async (request) => {
return api(request, `todos/${request.locals.userid}/${request.params.uid}`);
};

View File

@@ -1,6 +1,6 @@
/*
This module is used by the /todos.json and /todos/[uid].json
endpoints to make calls to api.svelte.dev, which stores todos
This module is used by the /todos endpoint to
make calls to api.svelte.dev, which stores todos
for each user. The leading underscore indicates that this is
a private module, _not_ an endpoint — visiting /todos/_api
will net you a 404 response.
@@ -11,35 +11,12 @@
const base = 'https://api.svelte.dev';
export async function api(request, resource, data) {
// user must have a cookie set
if (!request.locals.userid) {
return { status: 401 };
}
const res = await fetch(`${base}/${resource}`, {
method: request.method,
export function api(method, resource, data) {
return fetch(`${base}/${resource}`, {
method,
headers: {
'content-type': 'application/json'
},
body: data && JSON.stringify(data)
});
// if the request came from a <form> submission, the browser's default
// behaviour is to show the URL corresponding to the form's "action"
// attribute. in those cases, we want to redirect them back to the
// /todos page, rather than showing the response
if (res.ok && request.method !== 'GET' && request.headers.accept !== 'application/json') {
return {
status: 303,
headers: {
location: '/todos'
}
};
}
return {
status: res.status,
body: await res.json()
};
}

View File

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

View File

@@ -1,28 +0,0 @@
import { api } from './_api';
// GET /todos.json
export const get = async (request) => {
// request.locals.userid comes from src/hooks.js
const response = await api(request, `todos/${request.locals.userid}`);
if (response.status === 404) {
// user hasn't created a todo list.
// start with an empty array
return { body: [] };
}
return response;
};
// POST /todos.json
export const post = async (request) => {
const response = await api(request, `todos/${request.locals.userid}`, {
// because index.svelte posts a FormData object,
// request.body is _also_ a (readonly) FormData
// object, which allows us to get form data
// with the `body.get(key)` method
text: request.body.get('text')
});
return response;
};

View File

@@ -1,40 +1,9 @@
<script context="module">
import { enhance } from '$lib/form';
// see https://kit.svelte.dev/docs#loading
export const load = async ({ fetch }) => {
const res = await fetch('/todos.json');
if (res.ok) {
const todos = await res.json();
return {
props: { todos }
};
}
const { message } = await res.json();
return {
error: new Error(message)
};
};
</script>
<script>
import { enhance } from '$lib/form';
import { scale } from 'svelte/transition';
import { flip } from 'svelte/animate';
export let todos;
async function patch(res) {
const todo = await res.json();
todos = todos.map((t) => {
if (t.uid === todo.uid) return todo;
return t;
});
}
</script>
<svelte:head>
@@ -46,13 +15,10 @@
<form
class="new"
action="/todos.json"
action="/todos"
method="post"
use:enhance={{
result: async (res, form) => {
const created = await res.json();
todos = [...todos, created];
result: async ({ form }) => {
form.reset();
}
}}
@@ -68,41 +34,33 @@
animate:flip={{ duration: 200 }}
>
<form
action="/todos/{todo.uid}.json?_method=PATCH"
action="/todos?_method=PATCH"
method="post"
use:enhance={{
pending: (data) => {
pending: ({ data }) => {
todo.done = !!data.get('done');
},
result: patch
}
}}
>
<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/{todo.uid}.json?_method=PATCH"
method="post"
use:enhance={{
result: patch
}}
>
<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/{todo.uid}.json?_method=DELETE"
action="/todos?_method=DELETE"
method="post"
use:enhance={{
pending: () => (todo.pending_delete = true),
result: () => {
todos = todos.filter((t) => t.uid !== todo.uid);
}
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>
@@ -158,7 +116,7 @@
.done {
transform: none;
opacity: 0.4;
filter: drop-shadow(0px 0px 1px rgba(0, 0, 0, 0.1));
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.1));
}
form.text {

View File

@@ -5,8 +5,10 @@ const config = {
kit: {
adapter: adapter(),
// hydrate the <div id="svelte"> element in src/app.html
target: '#svelte'
// Override http methods in the Todo forms
methodOverride: {
allowed: ['PATCH', 'DELETE']
}
}
};

View File

@@ -32,7 +32,6 @@
"@typescript-eslint/parser": "4.28.0",
"async-retry": "1.2.3",
"buffer-replace": "1.0.0",
"cheerio": "1.0.0-rc.3",
"eslint": "7.29.0",
"eslint-config-prettier": "8.3.0",
"eslint-plugin-jest": "24.3.6",
@@ -43,8 +42,9 @@
"node-fetch": "2.6.1",
"npm-package-arg": "6.1.0",
"prettier": "2.3.1",
"ts-eager": "2.0.2",
"ts-jest": "27.0.4",
"turbo": "1.0.18"
"turbo": "1.1.9"
},
"scripts": {
"lerna": "lerna",
@@ -54,9 +54,10 @@
"publish-from-github": "./utils/publish.sh",
"changelog": "node utils/changelog.js",
"build": "turbo run build",
"vercel-build": "mkdir -p public && echo '<a href=\"https://vercel.com/import\">Import</a>' > public/output.html",
"vercel-build": "yarn build && cd api && node -r ts-eager/register ./_lib/script/build.ts",
"pre-commit": "lint-staged",
"test-unit": "node utils/run.js test-unit",
"test": "jest --rootDir=\"test\" --testPathPattern=\"\\.test.js\"",
"test-unit": "yarn test && node utils/run.js test-unit",
"test-integration-cli": "node utils/run.js test-integration-cli",
"test-integration-once": "node utils/run.js test-integration-once",
"test-integration-dev": "node utils/run.js test-integration-dev",
@@ -102,6 +103,14 @@
"es6": true
},
"rules": {
"no-restricted-syntax": [
"warn",
"WithStatement",
{
"message": "substr() is deprecated, use slice() or substring() instead",
"selector": "MemberExpression > Identifier[name='substr']"
}
],
"require-atomic-updates": 0,
"@typescript-eslint/ban-ts-comment": 0,
"@typescript-eslint/camelcase": 0,

View File

@@ -1,3 +1,6 @@
/src
/test
tmp
/tsconfig.json
/.turbo
/jest.config.js
tmp

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/build-utils",
"version": "2.13.1-canary.2",
"version": "2.15.1",
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.js",
@@ -30,7 +30,7 @@
"@types/node-fetch": "^2.1.6",
"@types/semver": "6.0.0",
"@types/yazl": "^2.4.1",
"@vercel/frameworks": "0.5.1-canary.21",
"@vercel/frameworks": "0.7.1",
"@vercel/ncc": "0.24.0",
"aggregate-error": "3.0.1",
"async-retry": "1.2.3",

View File

@@ -1,411 +0,0 @@
import fs from 'fs-extra';
import { join, parse, relative, dirname, basename, extname } from 'path';
import glob from './fs/glob';
import { normalizePath } from './fs/normalize-path';
import { Lambda } from './lambda';
import type { BuildOptions } from './types';
import { debug, getIgnoreFilter } from '.';
// `.output` was already created by the Build Command, so we have
// to ensure its contents don't get bundled into the Lambda. Similarily,
// we don't want to bundle anything from `.vercel` either. Lastly,
// Builders/Runtimes didn't have `vercel.json` or `now.json`.
const ignoredPaths = ['.output', '.vercel', 'vercel.json', 'now.json'];
const shouldIgnorePath = (
file: string,
ignoreFilter: any,
ignoreFile: boolean
) => {
const isNative = ignoredPaths.some(item => {
return file.startsWith(item);
});
if (!ignoreFile) {
return isNative;
}
return isNative || ignoreFilter(file);
};
const getSourceFiles = async (workPath: string, ignoreFilter: any) => {
const list = await glob('**', {
cwd: workPath,
});
// We're not passing this as an `ignore` filter to the `glob` function above,
// so that we can re-use exactly the same `getIgnoreFilter` method that the
// Build Step uses (literally the same code). Note that this exclusion only applies
// when deploying. Locally, another exclusion is needed, which is handled
// further below in the `convertRuntimeToPlugin` function.
for (const file in list) {
if (shouldIgnorePath(file, ignoreFilter, true)) {
delete list[file];
}
}
return list;
};
/**
* Convert legacy Runtime to a Plugin.
* @param buildRuntime - a legacy build() function from a Runtime
* @param packageName - the name of the package, for example `vercel-plugin-python`
* @param ext - the file extension, for example `.py`
*/
export function _experimental_convertRuntimeToPlugin(
buildRuntime: (options: BuildOptions) => Promise<{ output: Lambda }>,
packageName: string,
ext: string
) {
// This `build()` signature should match `plugin.build()` signature in `vercel build`.
return async function build({ workPath }: { workPath: string }) {
// We also don't want to provide any files to Runtimes that were ignored
// through `.vercelignore` or `.nowignore`, because the Build Step does the same.
const ignoreFilter = await getIgnoreFilter(workPath);
// Retrieve the files that are currently available on the File System,
// before the Legacy Runtime has even started to build.
const sourceFilesPreBuild = await getSourceFiles(workPath, ignoreFilter);
// Instead of doing another `glob` to get all the matching source files,
// we'll filter the list of existing files down to only the ones
// that are matching the entrypoint pattern, so we're first creating
// a clean new list to begin.
const entrypoints = Object.assign({}, sourceFilesPreBuild);
const entrypointMatch = new RegExp(`^api/.*${ext}$`);
// Up next, we'll strip out the files from the list of entrypoints
// that aren't actually considered entrypoints.
for (const file in entrypoints) {
if (!entrypointMatch.test(file)) {
delete entrypoints[file];
}
}
const pages: { [key: string]: any } = {};
const pluginName = packageName.replace('vercel-plugin-', '');
const outputPath = join(workPath, '.output');
const traceDir = join(
outputPath,
`inputs`,
// Legacy Runtimes can only provide API Routes, so that's
// why we can use this prefix for all of them. Here, we have to
// make sure to not use a cryptic hash name, because people
// need to be able to easily inspect the output.
`api-routes-${pluginName}`
);
await fs.ensureDir(traceDir);
const entryRoot = join(outputPath, 'server', 'pages');
for (const entrypoint of Object.keys(entrypoints)) {
const { output } = await buildRuntime({
files: sourceFilesPreBuild,
entrypoint,
workPath,
config: {
zeroConfig: true,
},
meta: {
avoidTopLevelInstall: true,
skipDownload: true,
},
});
const lambdaFiles = output.files;
// When deploying, the `files` that are passed to the Legacy Runtimes already
// have certain files that are ignored stripped, but locally, that list of
// files isn't used by the Legacy Runtimes, so we need to apply the filters
// to the outputs that they are returning instead.
for (const file in lambdaFiles) {
if (shouldIgnorePath(file, ignoreFilter, false)) {
delete lambdaFiles[file];
}
}
let handlerFileBase = output.handler;
let handlerFile = lambdaFiles[handlerFileBase];
let handlerHasImport = false;
const { handler } = output;
const handlerMethod = handler.split('.').pop();
const handlerFileName = handler.replace(`.${handlerMethod}`, '');
// For compiled languages, the launcher file for the Lambda generated
// by the Legacy Runtime matches the `handler` defined for it, but for
// interpreted languages, the `handler` consists of the launcher file name
// without an extension, plus the name of the method inside of that file
// that should be invoked, so we have to construct the file path explicitly.
if (!handlerFile) {
handlerFileBase = handlerFileName + ext;
handlerFile = lambdaFiles[handlerFileBase];
handlerHasImport = true;
}
if (!handlerFile || !handlerFile.fsPath) {
throw new Error(
`Could not find a handler file. Please ensure that \`files\` for the returned \`Lambda\` contains an \`FileFsRef\` named "${handlerFileBase}" with a valid \`fsPath\`.`
);
}
const handlerExtName = extname(handlerFile.fsPath);
const entryBase = basename(entrypoint).replace(ext, handlerExtName);
const entryPath = join(dirname(entrypoint), entryBase);
const entry = join(entryRoot, entryPath);
// Create the parent directory of the API Route that will be created
// for the current entrypoint inside of `.output/server/pages/api`.
await fs.ensureDir(dirname(entry));
// For compiled languages, the launcher file will be binary and therefore
// won't try to import a user-provided request handler (instead, it will
// contain it). But for interpreted languages, the launcher might try to
// load a user-provided request handler from the source file instead of bundling
// it, so we have to adjust the import statement inside the launcher to point
// to the respective source file. Previously, Legacy Runtimes simply expected
// the user-provided request-handler to be copied right next to the launcher,
// but with the new File System API, files won't be moved around unnecessarily.
if (handlerHasImport) {
const { fsPath } = handlerFile;
const encoding = 'utf-8';
// This is the true directory of the user-provided request handler in the
// source files, so that's what we will use as an import path in the launcher.
const locationPrefix = relative(entry, outputPath);
let handlerContent = await fs.readFile(fsPath, encoding);
const importPaths = [
// This is the full entrypoint path, like `./api/test.py`. In our tests
// Python didn't support importing from a parent directory without using different
// code in the launcher that registers it as a location for modules and then changing
// the importing syntax, but continuing to import it like before seems to work. If
// other languages need this, we should consider excluding Python explicitly.
// `./${entrypoint}`,
// This is the entrypoint path without extension, like `api/test`
entrypoint.slice(0, -ext.length),
];
// Generate a list of regular expressions that we can use for
// finding matches, but only allow matches if the import path is
// wrapped inside single (') or double quotes (").
const patterns = importPaths.map(path => {
// eslint-disable-next-line no-useless-escape
return new RegExp(`('|")(${path.replace(/\./g, '\\.')})('|")`, 'g');
});
let replacedMatch = null;
for (const pattern of patterns) {
const newContent = handlerContent.replace(
pattern,
(_, p1, p2, p3) => {
return `${p1}${join(locationPrefix, p2)}${p3}`;
}
);
if (newContent !== handlerContent) {
debug(
`Replaced "${pattern}" inside "${entry}" to ensure correct import of user-provided request handler`
);
handlerContent = newContent;
replacedMatch = true;
}
}
if (!replacedMatch) {
new Error(
`No replacable matches for "${importPaths[0]}" or "${importPaths[1]}" found in "${fsPath}"`
);
}
await fs.writeFile(entry, handlerContent, encoding);
} else {
await fs.copy(handlerFile.fsPath, entry);
}
// Legacy Runtimes based on interpreted languages will create a new launcher file
// for every entrypoint, but they will create each one inside `workPath`, which means that
// the launcher for one entrypoint will overwrite the launcher provided for the previous
// entrypoint. That's why, above, we copy the file contents into the new destination (and
// optionally transform them along the way), instead of linking. We then also want to remove
// the copy origin right here, so that the `workPath` doesn't contain a useless launcher file
// once the build has finished running.
await fs.remove(handlerFile.fsPath);
debug(`Removed temporary file "${handlerFile.fsPath}"`);
const nft = `${entry}.nft.json`;
const json = JSON.stringify({
version: 2,
files: Object.keys(lambdaFiles)
.map(file => {
const { fsPath } = lambdaFiles[file];
if (!fsPath) {
throw new Error(
`File "${file}" is missing valid \`fsPath\` property`
);
}
// The handler was already moved into position above.
if (file === handlerFileBase) {
return;
}
return normalizePath(relative(dirname(nft), fsPath));
})
.filter(Boolean),
});
await fs.writeFile(nft, json);
// Add an entry that will later on be added to the `functions-manifest.json`
// file that is placed inside of the `.output` directory.
pages[normalizePath(entryPath)] = {
// Because the underlying file used as a handler was placed
// inside `.output/server/pages/api`, it no longer has the name it originally
// had and is now named after the API Route that it's responsible for,
// so we have to adjust the name of the Lambda handler accordingly.
handler: handler.replace(handlerFileName, parse(entry).name),
runtime: output.runtime,
memory: output.memory,
maxDuration: output.maxDuration,
environment: output.environment,
allowQuery: output.allowQuery,
};
}
// Add any Serverless Functions that were exposed by the Legacy Runtime
// to the `functions-manifest.json` file provided in `.output`.
await _experimental_updateFunctionsManifest({ workPath, pages });
};
}
async function readJson(filePath: string): Promise<{ [key: string]: any }> {
try {
const str = await fs.readFile(filePath, 'utf8');
return JSON.parse(str);
} catch (err) {
if (err.code === 'ENOENT') {
return {};
}
throw err;
}
}
/**
* If `.output/functions-manifest.json` exists, append to the pages
* property. Otherwise write a new file.
*/
export async function _experimental_updateFunctionsManifest({
workPath,
pages,
}: {
workPath: string;
pages: { [key: string]: any };
}) {
const functionsManifestPath = join(
workPath,
'.output',
'functions-manifest.json'
);
const functionsManifest = await readJson(functionsManifestPath);
if (!functionsManifest.version) functionsManifest.version = 2;
if (!functionsManifest.pages) functionsManifest.pages = {};
for (const [pageKey, pageConfig] of Object.entries(pages)) {
functionsManifest.pages[pageKey] = { ...pageConfig };
}
await fs.writeFile(functionsManifestPath, JSON.stringify(functionsManifest));
}
/**
* Append routes to the `routes-manifest.json` file.
* If the file does not exist, it will be created.
*/
export async function _experimental_updateRoutesManifest({
workPath,
redirects,
rewrites,
headers,
dynamicRoutes,
staticRoutes,
}: {
workPath: string;
redirects?: {
source: string;
destination: string;
statusCode: number;
regex: string;
}[];
rewrites?: {
source: string;
destination: string;
regex: string;
}[];
headers?: {
source: string;
headers: {
key: string;
value: string;
}[];
regex: string;
}[];
dynamicRoutes?: {
page: string;
regex: string;
namedRegex?: string;
routeKeys?: { [named: string]: string };
}[];
staticRoutes?: {
page: string;
regex: string;
namedRegex?: string;
routeKeys?: { [named: string]: string };
}[];
}) {
const routesManifestPath = join(workPath, '.output', 'routes-manifest.json');
const routesManifest = await readJson(routesManifestPath);
if (!routesManifest.version) routesManifest.version = 3;
if (routesManifest.pages404 === undefined) routesManifest.pages404 = true;
if (redirects) {
if (!routesManifest.redirects) routesManifest.redirects = [];
routesManifest.redirects.push(...redirects);
}
if (rewrites) {
if (!routesManifest.rewrites) routesManifest.rewrites = [];
routesManifest.rewrites.push(...rewrites);
}
if (headers) {
if (!routesManifest.headers) routesManifest.headers = [];
routesManifest.headers.push(...headers);
}
if (dynamicRoutes) {
if (!routesManifest.dynamicRoutes) routesManifest.dynamicRoutes = [];
routesManifest.dynamicRoutes.push(...dynamicRoutes);
}
if (staticRoutes) {
if (!routesManifest.staticRoutes) routesManifest.staticRoutes = [];
routesManifest.staticRoutes.push(...staticRoutes);
}
await fs.writeFile(routesManifestPath, JSON.stringify(routesManifest));
}

View File

@@ -1,4 +1,4 @@
import { getPlatformEnv } from './';
import { getPlatformEnv } from './get-platform-env';
export default function debug(message: string, ...additional: any[]) {
if (getPlatformEnv('BUILDER_DEBUG')) {

View File

@@ -67,8 +67,7 @@ function getPublicBuilder(
typeof builder.src === 'string' &&
isOfficialRuntime('static', builder.use) &&
/^.*\/\*\*\/\*$/.test(builder.src) &&
builder.config &&
builder.config.zeroConfig === true
builder.config?.zeroConfig === true
) {
return builder as Builder & { src: string };
}

View File

@@ -0,0 +1,44 @@
import type { Files } from './types';
/**
* An Edge Functions output
*/
export class EdgeFunction {
type: 'EdgeFunction';
/**
* A display name for the edge function.
*/
name: string;
/**
* The deployment target.
* Only v8-worker is currently supported.
*/
deploymentTarget: 'v8-worker';
/**
* The entrypoint for the edge function.
*/
entrypoint: string;
/**
* The list of files to be included in the edge function bundle.
*/
files: Files;
/**
* Extra environment variables in use for the user code, to be
* assigned to the edge function.
*/
envVarsInUse?: string[];
constructor(params: Omit<EdgeFunction, 'type'>) {
this.type = 'EdgeFunction';
this.name = params.name;
this.deploymentTarget = params.deploymentTarget;
this.entrypoint = params.entrypoint;
this.files = params.files;
this.envVarsInUse = params.envVarsInUse;
}
}

View File

@@ -1,6 +1,6 @@
import assert from 'assert';
import intoStream from 'into-stream';
import { File } from './types';
import { FileBase } from './types';
interface FileBlobOptions {
mode?: number;
@@ -14,7 +14,7 @@ interface FromStreamOptions {
stream: NodeJS.ReadableStream;
}
export default class FileBlob implements File {
export default class FileBlob implements FileBase {
public type: 'FileBlob';
public mode: number;
public data: string | Buffer;
@@ -48,6 +48,10 @@ export default class FileBlob implements File {
return new FileBlob({ mode, contentType, data });
}
async toStreamAsync(): Promise<NodeJS.ReadableStream> {
return this.toStream();
}
toStream(): NodeJS.ReadableStream {
return intoStream(this.data);
}

View File

@@ -3,7 +3,7 @@ import fs from 'fs-extra';
import multiStream from 'multistream';
import path from 'path';
import Sema from 'async-sema';
import { File } from './types';
import { FileBase } from './types';
const semaToPreventEMFILE = new Sema(20);
@@ -20,7 +20,7 @@ interface FromStreamOptions {
fsPath: string;
}
class FileFsRef implements File {
class FileFsRef implements FileBase {
public type: 'FileFsRef';
public mode: number;
public fsPath: string;

View File

@@ -3,7 +3,7 @@ import fetch from 'node-fetch';
import multiStream from 'multistream';
import retry from 'async-retry';
import Sema from 'async-sema';
import { File } from './types';
import { FileBase } from './types';
interface FileRefOptions {
mode?: number;
@@ -23,7 +23,7 @@ class BailableError extends Error {
}
}
export default class FileRef implements File {
export default class FileRef implements FileBase {
public type: 'FileRef';
public mode: number;
public digest: string;

View File

@@ -3,6 +3,7 @@ import debug from '../debug';
import FileFsRef from '../file-fs-ref';
import { File, Files, Meta } from '../types';
import { remove, mkdirp, readlink, symlink } from 'fs-extra';
import streamToBuffer from './stream-to-buffer';
export interface DownloadedFiles {
[filePath: string]: FileFsRef;
@@ -15,19 +16,44 @@ export function isSymbolicLink(mode: number): boolean {
return (mode & S_IFMT) === S_IFLNK;
}
async function prepareSymlinkTarget(
file: File,
fsPath: string
): Promise<string> {
const mkdirPromise = mkdirp(path.dirname(fsPath));
if (file.type === 'FileFsRef') {
const [target] = await Promise.all([readlink(file.fsPath), mkdirPromise]);
return target;
}
if (file.type === 'FileRef' || file.type === 'FileBlob') {
const targetPathBufferPromise = await streamToBuffer(
await file.toStreamAsync()
);
const [targetPathBuffer] = await Promise.all([
targetPathBufferPromise,
mkdirPromise,
]);
return targetPathBuffer.toString('utf8');
}
throw new Error(
`file.type "${(file as any).type}" not supported for symlink`
);
}
async function downloadFile(file: File, fsPath: string): Promise<FileFsRef> {
const { mode } = file;
if (mode && isSymbolicLink(mode) && file.type === 'FileFsRef') {
const [target] = await Promise.all([
readlink((file as FileFsRef).fsPath),
mkdirp(path.dirname(fsPath)),
]);
if (isSymbolicLink(mode)) {
const target = await prepareSymlinkTarget(file, fsPath);
await symlink(target, fsPath);
return FileFsRef.fromFsPath({ mode, fsPath });
} else {
const stream = file.toStream();
return FileFsRef.fromStream({ mode, stream, fsPath });
}
const stream = file.toStream();
return FileFsRef.fromStream({ mode, stream, fsPath });
}
async function removeFile(basePath: string, fileMatched: string) {

View File

@@ -46,8 +46,8 @@ export default async function glob(
const files = await vanillaGlob(pattern, options);
for (const relativePath of files) {
const fsPath = normalizePath(path.join(options.cwd!, relativePath));
let stat: Stats = options.statCache![fsPath] as Stats;
const fsPath = normalizePath(path.join(options.cwd, relativePath));
let stat: Stats = options.statCache[fsPath] as Stats;
assert(
stat,
`statCache does not contain value for ${relativePath} (resolved to ${fsPath})`

View File

@@ -8,12 +8,13 @@ import { deprecate } from 'util';
import { NowBuildError } from '../errors';
import { Meta, PackageJson, NodeVersion, Config } from '../types';
import { getSupportedNodeVersion, getLatestNodeVersion } from './node-version';
import { readConfigFile } from './read-config-file';
export type CliType = 'yarn' | 'npm';
export type CliType = 'yarn' | 'npm' | 'pnpm';
export interface ScanParentDirsResult {
/**
* "yarn" or "npm", depending on the presence of lockfiles.
* "yarn", "npm", or "pnpm" depending on the presence of lockfiles.
*/
cliType: CliType;
/**
@@ -252,7 +253,7 @@ export async function scanParentDirs(
}
// eslint-disable-next-line no-await-in-loop
const [packageLockJson, hasYarnLock] = await Promise.all([
const [packageLockJson, hasYarnLock, pnpmLockYaml] = await Promise.all([
fs
.readJson(path.join(currentDestPath, 'package-lock.json'))
.catch(error => {
@@ -263,17 +264,26 @@ export async function scanParentDirs(
throw error;
}),
fs.pathExists(path.join(currentDestPath, 'yarn.lock')),
readConfigFile<{ lockfileVersion: number }>(
path.join(currentDestPath, 'pnpm-lock.yaml')
),
]);
if (packageLockJson && !hasYarnLock) {
if (packageLockJson && !hasYarnLock && !pnpmLockYaml) {
cliType = 'npm';
lockfileVersion = packageLockJson.lockfileVersion;
}
if (!packageLockJson && !hasYarnLock && pnpmLockYaml) {
cliType = 'pnpm';
// just ensure that it is read as a number and not a string
lockfileVersion = Number(pnpmLockYaml.lockfileVersion);
}
// Only stop iterating if a lockfile was found, because it's possible
// that the lockfile is in a higher path than where the `package.json`
// file was found.
if (packageLockJson || hasYarnLock) {
if (packageLockJson || hasYarnLock || pnpmLockYaml) {
break;
}
}
@@ -341,6 +351,13 @@ export async function runNpmInstall(
commandArgs = args
.filter(a => a !== '--prefer-offline')
.concat(['install', '--no-audit', '--unsafe-perm']);
} else if (cliType === 'pnpm') {
// PNPM's install command is similar to NPM's but without the audit nonsense
// @see options https://pnpm.io/cli/install
opts.prettyCommand = 'pnpm install';
commandArgs = args
.filter(a => a !== '--prefer-offline')
.concat(['install', '--unsafe-perm']);
} else {
opts.prettyCommand = 'yarn install';
commandArgs = ['install', ...args];
@@ -448,6 +465,8 @@ export async function runPackageJsonScript(
if (cliType === 'npm') {
opts.prettyCommand = `npm run ${scriptName}`;
} else if (cliType === 'pnpm') {
opts.prettyCommand = `pnpm run ${scriptName}`;
} else {
opts.prettyCommand = `yarn run ${scriptName}`;
}

View File

@@ -0,0 +1,23 @@
import { NowBuildError } from './errors';
/**
* Helper function to support both `VERCEL_` and legacy `NOW_` env vars.
* Throws an error if *both* env vars are defined.
*/
export const getPlatformEnv = (name: string): string | undefined => {
const vName = `VERCEL_${name}`;
const nName = `NOW_${name}`;
const v = process.env[vName];
const n = process.env[nName];
if (typeof v === 'string') {
if (typeof n === 'string') {
throw new NowBuildError({
code: 'CONFLICTING_ENV_VAR_NAMES',
message: `Both "${vName}" and "${nName}" env vars are defined. Please only define the "${vName}" env var.`,
link: 'https://vercel.link/combining-old-and-new-config',
});
}
return v;
}
return n;
};

View File

@@ -2,6 +2,7 @@ import FileBlob from './file-blob';
import FileFsRef from './file-fs-ref';
import FileRef from './file-ref';
import { Lambda, createLambda, getLambdaOptionsFromFunction } from './lambda';
import { NodejsLambda } from './nodejs-lambda';
import { Prerender } from './prerender';
import download, { DownloadedFiles, isSymbolicLink } from './fs/download';
import getWriteableDirectory from './fs/get-writable-directory';
@@ -31,17 +32,18 @@ import {
getLatestNodeVersion,
getDiscontinuedNodeVersions,
} from './fs/node-version';
import { NowBuildError } from './errors';
import streamToBuffer from './fs/stream-to-buffer';
import shouldServe from './should-serve';
import debug from './debug';
import getIgnoreFilter from './get-ignore-filter';
import { getPlatformEnv } from './get-platform-env';
export {
FileBlob,
FileFsRef,
FileRef,
Lambda,
NodejsLambda,
createLambda,
Prerender,
download,
@@ -69,6 +71,7 @@ export {
getLatestNodeVersion,
getDiscontinuedNodeVersions,
getSpawnOptions,
getPlatformEnv,
streamToBuffer,
shouldServe,
debug,
@@ -78,6 +81,7 @@ export {
getIgnoreFilter,
};
export { EdgeFunction } from './edge-function';
export {
detectBuilders,
detectOutputDirectory,
@@ -89,11 +93,6 @@ export { detectFramework } from './detect-framework';
export { DetectorFilesystem } from './detectors/filesystem';
export { readConfigFile } from './fs/read-config-file';
export { normalizePath } from './fs/normalize-path';
export {
_experimental_convertRuntimeToPlugin,
_experimental_updateFunctionsManifest,
_experimental_updateRoutesManifest,
} from './convert-runtime-to-plugin';
export * from './schemas';
export * from './types';
@@ -117,25 +116,3 @@ export const isOfficialRuntime = (desired: string, name?: string): boolean => {
export const isStaticRuntime = (name?: string): boolean => {
return isOfficialRuntime('static', name);
};
/**
* Helper function to support both `VERCEL_` and legacy `NOW_` env vars.
* Throws an error if *both* env vars are defined.
*/
export const getPlatformEnv = (name: string): string | undefined => {
const vName = `VERCEL_${name}`;
const nName = `NOW_${name}`;
const v = process.env[vName];
const n = process.env[nName];
if (typeof v === 'string') {
if (typeof n === 'string') {
throw new NowBuildError({
code: 'CONFLICTING_ENV_VAR_NAMES',
message: `Both "${vName}" and "${nName}" env vars are defined. Please only define the "${vName}" env var.`,
link: 'https://vercel.link/combining-old-and-new-config',
});
}
return v;
}
return n;
};

View File

@@ -3,17 +3,17 @@ import Sema from 'async-sema';
import { ZipFile } from 'yazl';
import minimatch from 'minimatch';
import { readlink } from 'fs-extra';
import { Files, Config } from './types';
import FileFsRef from './file-fs-ref';
import { isSymbolicLink } from './fs/download';
import streamToBuffer from './fs/stream-to-buffer';
import type { Files, Config } from './types';
interface Environment {
[key: string]: string;
}
interface LambdaOptions {
files: Files;
export type LambdaOptions = LambdaOptionsWithFiles | LambdaOptionsWithZipBuffer;
export interface LambdaOptionsBase {
handler: string;
runtime: string;
memory?: number;
@@ -21,10 +21,21 @@ interface LambdaOptions {
environment?: Environment;
allowQuery?: string[];
regions?: string[];
supportsMultiPayloads?: boolean;
}
export interface LambdaOptionsWithFiles extends LambdaOptionsBase {
files: Files;
}
/**
* @deprecated Use `LambdaOptionsWithFiles` instead.
*/
export interface LambdaOptionsWithZipBuffer extends LambdaOptionsBase {
/**
* @deprecated Use `files` property instead.
*/
zipBuffer?: Buffer;
zipBuffer: Buffer;
}
interface GetLambdaOptionsFromFunctionOptions {
@@ -33,33 +44,37 @@ interface GetLambdaOptionsFromFunctionOptions {
}
export class Lambda {
public type: 'Lambda';
public files: Files;
public handler: string;
public runtime: string;
public memory?: number;
public maxDuration?: number;
public environment: Environment;
public allowQuery?: string[];
public regions?: string[];
type: 'Lambda';
files?: Files;
handler: string;
runtime: string;
memory?: number;
maxDuration?: number;
environment: Environment;
allowQuery?: string[];
regions?: string[];
/**
* @deprecated Use `await lambda.createZip()` instead.
*/
public zipBuffer?: Buffer;
zipBuffer?: Buffer;
supportsMultiPayloads?: boolean;
constructor({
files,
handler,
runtime,
maxDuration,
memory,
environment = {},
allowQuery,
regions,
zipBuffer,
}: LambdaOptions) {
if (!zipBuffer) {
assert(typeof files === 'object', '"files" must be an object');
constructor(opts: LambdaOptions) {
const {
handler,
runtime,
maxDuration,
memory,
environment = {},
allowQuery,
regions,
supportsMultiPayloads,
} = opts;
if ('files' in opts) {
assert(typeof opts.files === 'object', '"files" must be an object');
}
if ('zipBuffer' in opts) {
assert(Buffer.isBuffer(opts.zipBuffer), '"zipBuffer" must be a Buffer');
}
assert(typeof handler === 'string', '"handler" is not a string');
assert(typeof runtime === 'string', '"runtime" is not a string');
@@ -81,6 +96,13 @@ export class Lambda {
);
}
if (supportsMultiPayloads !== undefined) {
assert(
typeof supportsMultiPayloads === 'boolean',
'"supportsMultiPayloads" is not a boolean'
);
}
if (regions !== undefined) {
assert(Array.isArray(regions), '"regions" is not an Array');
assert(
@@ -89,7 +111,7 @@ export class Lambda {
);
}
this.type = 'Lambda';
this.files = files;
this.files = 'files' in opts ? opts.files : undefined;
this.handler = handler;
this.runtime = runtime;
this.memory = memory;
@@ -97,12 +119,16 @@ export class Lambda {
this.environment = environment;
this.allowQuery = allowQuery;
this.regions = regions;
this.zipBuffer = zipBuffer;
this.zipBuffer = 'zipBuffer' in opts ? opts.zipBuffer : undefined;
this.supportsMultiPayloads = supportsMultiPayloads;
}
async createZip(): Promise<Buffer> {
let { zipBuffer } = this;
if (!zipBuffer) {
if (!this.files) {
throw new Error('`files` is not defined');
}
await sema.acquire();
try {
zipBuffer = await createZip(this.files);
@@ -136,7 +162,7 @@ export async function createZip(files: Files): Promise<Buffer> {
for (const name of names) {
const file = files[name];
if (file.mode && isSymbolicLink(file.mode) && file.type === 'FileFsRef') {
const symlinkTarget = await readlink((file as FileFsRef).fsPath);
const symlinkTarget = await readlink(file.fsPath);
symlinkTargets.set(name, symlinkTarget);
}
}
@@ -150,7 +176,7 @@ export async function createZip(files: Files): Promise<Buffer> {
if (typeof symlinkTarget === 'string') {
zipFile.addBuffer(Buffer.from(symlinkTarget, 'utf8'), name, opts);
} else {
const stream = file.toStream() as import('stream').Readable;
const stream = file.toStream();
stream.on('error', reject);
zipFile.addReadStream(stream, name, opts);
}

View File

@@ -0,0 +1,27 @@
import { Lambda, LambdaOptionsWithFiles } from './lambda';
interface NodejsLambdaOptions extends LambdaOptionsWithFiles {
shouldAddHelpers: boolean;
shouldAddSourcemapSupport: boolean;
awsLambdaHandler?: string;
}
export class NodejsLambda extends Lambda {
launcherType: 'Nodejs';
shouldAddHelpers: boolean;
shouldAddSourcemapSupport: boolean;
awsLambdaHandler?: string;
constructor({
shouldAddHelpers,
shouldAddSourcemapSupport,
awsLambdaHandler,
...opts
}: NodejsLambdaOptions) {
super(opts);
this.launcherType = 'Nodejs';
this.shouldAddHelpers = shouldAddHelpers;
this.shouldAddSourcemapSupport = shouldAddSourcemapSupport;
this.awsLambdaHandler = awsLambdaHandler;
}
}

View File

@@ -1,12 +1,10 @@
import FileBlob from './file-blob';
import FileFsRef from './file-fs-ref';
import FileRef from './file-ref';
import { File } from './types';
import { Lambda } from './lambda';
interface PrerenderOptions {
expiration: number | false;
lambda: Lambda;
fallback: FileBlob | FileFsRef | FileRef | null;
fallback: File | null;
group?: number;
bypassToken?: string | null /* optional to be non-breaking change */;
allowQuery?: string[];
@@ -16,7 +14,7 @@ export class Prerender {
public type: 'Prerender';
public expiration: number | false;
public lambda: Lambda;
public fallback: FileBlob | FileFsRef | FileRef | null;
public fallback: File | null;
public group?: number;
public bypassToken: string | null;
public allowQuery?: string[];

View File

@@ -1,20 +1,21 @@
import FileRef from './file-ref';
import FileFsRef from './file-fs-ref';
import type FileRef from './file-ref';
import type FileFsRef from './file-fs-ref';
import type FileBlob from './file-blob';
import type { Lambda } from './lambda';
import type { Prerender } from './prerender';
import type { EdgeFunction } from './edge-function';
export interface Env {
[name: string]: string | undefined;
}
export interface File {
export type File = FileRef | FileFsRef | FileBlob;
export interface FileBase {
type: string;
mode: number;
contentType?: string;
toStream: () => NodeJS.ReadableStream;
toStreamAsync?: () => Promise<NodeJS.ReadableStream>;
/**
* The absolute path to the file in the filesystem
*/
fsPath?: string;
}
export interface Files {
@@ -22,16 +23,6 @@ export interface Files {
}
export interface Config {
[key: string]:
| string
| string[]
| boolean
| number
| { [key: string]: string }
| BuilderFunctions
| ProjectSettings
| undefined
| null;
maxLambdaSize?: string;
includeFiles?: string | string[];
excludeFiles?: string | string[];
@@ -50,6 +41,7 @@ export interface Config {
devCommand?: string;
framework?: string | null;
nodeVersion?: string;
[key: string]: unknown;
}
export interface Meta {
@@ -62,35 +54,7 @@ export interface Meta {
env?: Env;
buildEnv?: Env;
avoidTopLevelInstall?: boolean;
}
export interface AnalyzeOptions {
/**
* All source files of the project
*/
files: {
[filePath: string]: FileRef;
};
/**
* Name of entrypoint file for this particular build job. Value
* `files[entrypoint]` is guaranteed to exist and be a valid File reference.
* `entrypoint` is always a discrete file and never a glob, since globs are
* expanded into separate builds at deployment time.
*/
entrypoint: string;
/**
* A writable temporary directory where you are encouraged to perform your
* build process. This directory will be populated with the restored cache.
*/
workPath: string;
/**
* An arbitrary object passed by the user in the build definition defined
* in `vercel.json`.
*/
config: Config;
[key: string]: unknown;
}
export interface BuildOptions {
@@ -155,10 +119,11 @@ export interface PrepareCacheOptions {
workPath: string;
/**
* A writable temporary directory where you can build a cache to use for
* the next run.
* The "Root Directory" is assigned to the `workPath` so the `repoRootPath`
* is the Git Repository Root. This is only relevant for Monorepos.
* See https://vercel.com/blog/monorepos
*/
cachePath: string;
repoRootPath?: string;
/**
* An arbitrary object passed by the user in the build definition defined
@@ -368,3 +333,49 @@ export interface ProjectSettings {
directoryListing?: boolean;
gitForkProtection?: boolean;
}
export interface BuilderV2 {
version: 2;
build: BuildV2;
prepareCache?: PrepareCache;
}
export interface BuilderV3 {
version: 3;
build: BuildV3;
prepareCache?: PrepareCache;
startDevServer?: StartDevServer;
}
type ImageFormat = 'image/avif' | 'image/webp';
export interface Images {
domains: string[];
sizes: number[];
minimumCacheTTL?: number;
formats?: ImageFormat[];
}
export interface BuildResultV2 {
// TODO: use proper `Route` type from `routing-utils` (perhaps move types to a common package)
routes?: any[];
images?: Images;
output: {
[key: string]: File | Lambda | Prerender | EdgeFunction;
};
wildcard?: Array<{
domain: string;
value: string;
}>;
}
export interface BuildResultV3 {
output: Lambda;
}
export type BuildV2 = (options: BuildOptions) => Promise<BuildResultV2>;
export type BuildV3 = (options: BuildOptions) => Promise<BuildResultV3>;
export type PrepareCache = (options: PrepareCacheOptions) => Promise<Files>;
export type StartDevServer = (
options: StartDevServerOptions
) => Promise<StartDevServerResult>;

View File

@@ -0,0 +1,15 @@
{
"name": "22-pnpm",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "mkdir -p public && (printf \"pnpm version: \" && pnpm -v) > public/index.txt"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"once": "^1.4.0"
}
}

View File

@@ -0,0 +1,19 @@
lockfileVersion: 5.3
specifiers:
once: ^1.4.0
dependencies:
once: 1.4.0
packages:
/once/1.4.0:
resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}
dependencies:
wrappy: 1.0.2
dev: false
/wrappy/1.0.2:
resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=}
dev: false

View File

@@ -0,0 +1,11 @@
{
"version": 2,
"builds": [{ "src": "package.json", "use": "@vercel/static-build" }],
"probes": [
{
"path": "/",
"mustContain": "pnpm version: 6",
"logMustContain": "pnpm run build"
}
]
}

View File

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

View File

@@ -0,0 +1,8 @@
{
"name": "d",
"license": "MIT",
"version": "0.1.0",
"devDependencies": {
"once": "1.4.0"
}
}

View File

@@ -0,0 +1,6 @@
{
"private": true,
"name": "23-pnpm-workspaces",
"license": "MIT",
"version": "1.0.0"
}

View File

@@ -0,0 +1,27 @@
lockfileVersion: 5.3
importers:
.:
specifiers: {}
c:
specifiers: {}
d:
specifiers:
once: 1.4.0
devDependencies:
once: 1.4.0
packages:
/once/1.4.0:
resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}
dependencies:
wrappy: 1.0.2
dev: true
/wrappy/1.0.2:
resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=}
dev: true

View File

@@ -0,0 +1,3 @@
packages:
- 'c'
- 'd'

View File

@@ -0,0 +1 @@
.vercel

View File

@@ -0,0 +1 @@
node-linker=hoisted

View File

@@ -0,0 +1,5 @@
const once = require('once');
module.exports = () => {
once(() => {});
};

View File

@@ -0,0 +1,5 @@
const once = require('once');
module.exports = () => {
once(() => {});
};

View File

@@ -0,0 +1,10 @@
const { exec } = require('exeggcute');
const a = require('./a');
const b = require('./b');
a();
b();
exec('mkdir public', __dirname);
exec('echo "Hello, World!" > public/index.html', __dirname);

View File

@@ -0,0 +1,11 @@
{
"private": "true",
"name": "24-pnpm-hoisted",
"scripts": {
"build": "ls -Al node_modules && node index.js"
},
"dependencies": {
"exeggcute": "^1.0.0",
"once": "^1.4.0"
}
}

View File

@@ -0,0 +1,25 @@
lockfileVersion: 5.3
specifiers:
exeggcute: ^1.0.0
once: ^1.4.0
dependencies:
exeggcute: 1.0.0
once: 1.4.0
packages:
/exeggcute/1.0.0:
resolution: {integrity: sha1-qLXakIhjGCDm9ggx4CvEc5mnbBU=}
dev: false
/once/1.4.0:
resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}
dependencies:
wrappy: 1.0.2
dev: false
/wrappy/1.0.2:
resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=}
dev: false

View File

@@ -0,0 +1,20 @@
{
"probes": [
{
"path": "/",
"mustContain": "Hello, World!"
},
{
"path": "/",
"logMustContain": "once"
},
{
"path": "/",
"logMustContain": "exeggcute"
},
{
"path": "/",
"logMustContain": "wrappy"
}
]
}

View File

@@ -31,6 +31,7 @@ const skipFixtures: string[] = [
'07-zero-config-jekyll',
'08-zero-config-middleman',
'21-npm-workspaces',
'23-pnpm-workspaces',
];
// eslint-disable-next-line no-restricted-syntax

View File

@@ -1,160 +0,0 @@
import { join } from 'path';
import fs from 'fs-extra';
import { BuildOptions, createLambda, FileFsRef } from '../src';
import { _experimental_convertRuntimeToPlugin } from '../src/convert-runtime-to-plugin';
async function fsToJson(dir: string, output: Record<string, any> = {}) {
const files = await fs.readdir(dir);
for (const file of files) {
const fsPath = join(dir, file);
const stat = await fs.stat(fsPath);
if (stat.isDirectory()) {
output[file] = {};
await fsToJson(fsPath, output[file]);
} else {
output[file] = await fs.readFile(fsPath, 'utf8');
}
}
return output;
}
const invalidFuncWorkpath = join(
__dirname,
'convert-runtime',
'invalid-functions'
);
const pythonApiWorkpath = join(__dirname, 'convert-runtime', 'python-api');
describe('convert-runtime-to-plugin', () => {
afterEach(async () => {
await fs.remove(join(invalidFuncWorkpath, '.output'));
await fs.remove(join(pythonApiWorkpath, '.output'));
});
it('should create correct fileystem for python', async () => {
const ext = '.py';
const workPath = pythonApiWorkpath;
const handlerName = 'vc__handler__python';
const handlerFileName = handlerName + ext;
const lambdaOptions = {
handler: `${handlerName}.vc_handler`,
runtime: 'python3.9',
memory: 512,
maxDuration: 5,
environment: {},
};
const buildRuntime = async (opts: BuildOptions) => {
const handlerPath = join(workPath, handlerFileName);
// This is the usual time at which a Legacy Runtime writes its Lambda launcher.
await fs.writeFile(handlerPath, '# handler');
opts.files[handlerFileName] = new FileFsRef({
fsPath: handlerPath,
});
const lambda = await createLambda({
files: opts.files,
...lambdaOptions,
});
return { output: lambda };
};
const packageName = 'vercel-plugin-python';
const build = await _experimental_convertRuntimeToPlugin(
buildRuntime,
packageName,
ext
);
await build({ workPath });
const output = await fsToJson(join(workPath, '.output'));
expect(output).toMatchObject({
'functions-manifest.json': expect.stringContaining('{'),
server: {
pages: {
api: {
'index.py': expect.stringContaining('handler'),
'index.py.nft.json': expect.stringContaining('{'),
users: {
'get.py': expect.stringContaining('handler'),
'get.py.nft.json': expect.stringContaining('{'),
'post.py': expect.stringContaining('handler'),
'post.py.nft.json': expect.stringContaining('{'),
},
},
},
},
});
const funcManifest = JSON.parse(output['functions-manifest.json']);
expect(funcManifest).toMatchObject({
version: 2,
pages: {
'api/index.py': { ...lambdaOptions, handler: 'index.vc_handler' },
'api/users/get.py': { ...lambdaOptions, handler: 'get.vc_handler' },
'api/users/post.py': {
...lambdaOptions,
handler: 'post.vc_handler',
memory: 512,
},
},
});
const indexJson = JSON.parse(output.server.pages.api['index.py.nft.json']);
expect(indexJson).toMatchObject({
version: 2,
files: [
'../../../../api/db/[id].py',
'../../../../api/index.py',
'../../../../api/project/[aid]/[bid]/index.py',
'../../../../api/users/get.py',
'../../../../api/users/post.py',
'../../../../file.txt',
'../../../../util/date.py',
'../../../../util/math.py',
],
});
const getJson = JSON.parse(
output.server.pages.api.users['get.py.nft.json']
);
expect(getJson).toMatchObject({
version: 2,
files: [
'../../../../../api/db/[id].py',
'../../../../../api/index.py',
'../../../../../api/project/[aid]/[bid]/index.py',
'../../../../../api/users/get.py',
'../../../../../api/users/post.py',
'../../../../../file.txt',
'../../../../../util/date.py',
'../../../../../util/math.py',
],
});
const postJson = JSON.parse(
output.server.pages.api.users['post.py.nft.json']
);
expect(postJson).toMatchObject({
version: 2,
files: [
'../../../../../api/db/[id].py',
'../../../../../api/index.py',
'../../../../../api/project/[aid]/[bid]/index.py',
'../../../../../api/users/get.py',
'../../../../../api/users/post.py',
'../../../../../file.txt',
'../../../../../util/date.py',
'../../../../../util/math.py',
],
});
expect(output.server.pages['file.txt']).toBeUndefined();
expect(output.server.pages.api['file.txt']).toBeUndefined();
});
});

View File

@@ -0,0 +1,21 @@
import { NodejsLambda, FileBlob } from '../src';
describe('Test `NodejsLambda`', () => {
it('should create an instance', () => {
const helloSrc = 'module.exports = (req, res) => res.end("hi");';
const lambda = new NodejsLambda({
files: {
'api/hello.js': new FileBlob({ data: helloSrc }),
},
handler: 'api/hello.js',
runtime: 'node14.x',
shouldAddHelpers: true,
shouldAddSourcemapSupport: false,
});
expect(lambda.handler).toEqual('api/hello.js');
expect(lambda.runtime).toEqual('node14.x');
expect(lambda.shouldAddHelpers).toEqual(true);
expect(lambda.shouldAddSourcemapSupport).toEqual(false);
expect(lambda.awsLambdaHandler).toBeUndefined();
});
});

View File

@@ -1,7 +1,7 @@
import ms from 'ms';
import path from 'path';
import fs from 'fs-extra';
import { strict as assert } from 'assert';
import fs, { readlink } from 'fs-extra';
import { strict as assert, strictEqual } from 'assert';
import { createZip } from '../src/lambda';
import { getSupportedNodeVersion } from '../src/fs/node-version';
import download from '../src/fs/download';
@@ -14,6 +14,7 @@ import {
runNpmInstall,
runPackageJsonScript,
scanParentDirs,
FileBlob,
} from '../src';
async function expectBuilderError(promise: Promise<any>, pattern: string) {
@@ -47,7 +48,7 @@ afterEach(() => {
console.warn = originalConsoleWarn;
});
it('should re-create symlinks properly', async () => {
it('should re-create FileFsRef symlinks properly', async () => {
if (process.platform === 'win32') {
console.log('Skipping test on windows');
return;
@@ -69,6 +70,72 @@ it('should re-create symlinks properly', async () => {
assert(linkStat.isSymbolicLink());
assert(linkDirStat.isSymbolicLink());
assert(aStat.isFile());
const [linkDirContents, linkTextContents] = await Promise.all([
readlink(path.join(outDir, 'link-dir')),
readlink(path.join(outDir, 'link.txt')),
]);
strictEqual(linkDirContents, 'dir');
strictEqual(linkTextContents, './a.txt');
});
it('should re-create FileBlob symlinks properly', async () => {
if (process.platform === 'win32') {
console.log('Skipping test on windows');
return;
}
const files = {
'a.txt': new FileBlob({
mode: 33188,
contentType: undefined,
data: 'a text',
}),
'dir/b.txt': new FileBlob({
mode: 33188,
contentType: undefined,
data: 'b text',
}),
'link-dir': new FileBlob({
mode: 41453,
contentType: undefined,
data: 'dir',
}),
'link.txt': new FileBlob({
mode: 41453,
contentType: undefined,
data: 'a.txt',
}),
};
strictEqual(Object.keys(files).length, 4);
const outDir = path.join(__dirname, 'symlinks-out');
await fs.remove(outDir);
const files2 = await download(files, outDir);
strictEqual(Object.keys(files2).length, 4);
const [linkStat, linkDirStat, aStat, dirStat] = await Promise.all([
fs.lstat(path.join(outDir, 'link.txt')),
fs.lstat(path.join(outDir, 'link-dir')),
fs.lstat(path.join(outDir, 'a.txt')),
fs.lstat(path.join(outDir, 'dir')),
]);
assert(linkStat.isSymbolicLink());
assert(linkDirStat.isSymbolicLink());
assert(aStat.isFile());
assert(dirStat.isDirectory());
const [linkDirContents, linkTextContents] = await Promise.all([
readlink(path.join(outDir, 'link-dir')),
readlink(path.join(outDir, 'link.txt')),
]);
strictEqual(linkDirContents, 'dir');
strictEqual(linkTextContents, 'a.txt');
});
it('should create zip files with symlinks properly', async () => {
@@ -332,3 +399,17 @@ it('should detect npm Workspaces', async () => {
expect(result.cliType).toEqual('npm');
expect(result.lockfileVersion).toEqual(2);
});
it('should detect pnpm', async () => {
const fixture = path.join(__dirname, 'fixtures', '22-pnpm');
const result = await scanParentDirs(fixture);
expect(result.cliType).toEqual('pnpm');
expect(result.lockfileVersion).toEqual(5.3);
});
it('should detect pnpm Workspaces', async () => {
const fixture = path.join(__dirname, 'fixtures', '23-pnpm-workspaces/a');
const result = await scanParentDirs(fixture);
expect(result.cliType).toEqual('pnpm');
expect(result.lockfileVersion).toEqual(5.3);
});

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "23.1.3-canary.75",
"version": "24.1.0",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -43,14 +43,12 @@
"node": ">= 12"
},
"dependencies": {
"@vercel/build-utils": "2.13.1-canary.2",
"@vercel/go": "1.2.4-canary.7",
"@vercel/node": "1.12.2-canary.10",
"@vercel/python": "2.1.2-canary.5",
"@vercel/ruby": "1.2.10-canary.3",
"update-notifier": "4.1.0",
"vercel-plugin-middleware": "0.0.0-canary.27",
"vercel-plugin-node": "1.12.2-canary.42"
"@vercel/build-utils": "2.15.1",
"@vercel/go": "1.3.2",
"@vercel/node": "1.14.1",
"@vercel/python": "2.2.2",
"@vercel/ruby": "1.3.2",
"update-notifier": "4.1.0"
},
"devDependencies": {
"@next/env": "11.1.2",
@@ -90,11 +88,11 @@
"@types/update-notifier": "5.1.0",
"@types/which": "1.3.2",
"@types/write-json-file": "2.2.1",
"@vercel/client": "10.2.3-canary.53",
"@vercel/client": "10.4.1",
"@vercel/fetch-retry": "5.0.3",
"@vercel/frameworks": "0.5.1-canary.21",
"@vercel/frameworks": "0.7.1",
"@vercel/ncc": "0.24.0",
"@vercel/nft": "0.17.0",
"@vercel/nft": "0.17.5",
"@zeit/fun": "0.11.2",
"@zeit/source-map-support": "0.6.2",
"ajv": "6.12.2",
@@ -163,7 +161,6 @@
"title": "3.4.1",
"tmp-promise": "1.0.3",
"tree-kill": "1.2.2",
"ts-eager": "2.0.2",
"ts-node": "8.3.0",
"typescript": "4.3.4",
"universal-analytics": "0.4.20",

View File

@@ -56,10 +56,6 @@ const help = () => {
${chalk.cyan(`$ ${pkgName} bisect --bad example-310pce9i0.vercel.app`)}
${chalk.gray('')} Bisect specifying a deployment that was working 3 days ago
${chalk.cyan(`$ ${pkgName} bisect --good 3d`)}
${chalk.gray('')} Automated bisect with a run script
${chalk.cyan(`$ ${pkgName} bisect --run ./test.sh`)}
@@ -201,7 +197,11 @@ export default async function main(client: Client): Promise<number> {
if (badDeployment.target !== goodDeployment.target) {
output.error(
`Bad deployment target "${badDeployment.target || 'preview'}" does not match good deployment target "${goodDeployment.target || 'preview'}"`
`Bad deployment target "${
badDeployment.target || 'preview'
}" does not match good deployment target "${
goodDeployment.target || 'preview'
}"`
);
return 1;
}

View File

@@ -1,911 +0,0 @@
import { loadEnvConfig, processEnv } from '@next/env';
import {
execCommand,
getScriptName,
GlobOptions,
scanParentDirs,
spawnAsync,
glob as buildUtilsGlob,
detectFileSystemAPI,
detectBuilders,
PackageJson,
} from '@vercel/build-utils';
import { nodeFileTrace } from '@vercel/nft';
import Sema from 'async-sema';
import chalk from 'chalk';
import { SpawnOptions } from 'child_process';
import { assert } from 'console';
import fs from 'fs-extra';
import ogGlob from 'glob';
import { dirname, isAbsolute, join, parse, relative } from 'path';
import pluralize from 'pluralize';
import Client from '../util/client';
import { VercelConfig } from '../util/dev/types';
import { emoji, prependEmoji } from '../util/emoji';
import { CantParseJSONFile } from '../util/errors-ts';
import getArgs from '../util/get-args';
import handleError from '../util/handle-error';
import confirm from '../util/input/confirm';
import { isSettingValue } from '../util/is-setting-value';
import cmd from '../util/output/cmd';
import logo from '../util/output/logo';
import param from '../util/output/param';
import stamp from '../util/output/stamp';
import { getCommandName, getPkgName } from '../util/pkg-name';
import { loadCliPlugins } from '../util/plugins';
import { findFramework } from '../util/projects/find-framework';
import { VERCEL_DIR } from '../util/projects/link';
import { readProjectSettings } from '../util/projects/project-settings';
import readJSONFile from '../util/read-json-file';
import pull from './pull';
const sema = new Sema(16, {
capacity: 100,
});
const help = () => {
return console.log(`
${chalk.bold(`${logo} ${getPkgName()} build`)}
${chalk.dim('Options:')}
-h, --help Output usage information
-A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline(
'FILE'
)} Path to the local ${'`vercel.json`'} file
-Q ${chalk.bold.underline('DIR')}, --global-config=${chalk.bold.underline(
'DIR'
)} Path to the global ${'`.vercel`'} directory
--cwd [path] The current working directory
-d, --debug Debug mode [off]
-y, --yes Skip the confirmation prompt
${chalk.dim('Examples:')}
${chalk.gray('')} Build the project
${chalk.cyan(`$ ${getPkgName()} build`)}
${chalk.cyan(`$ ${getPkgName()} build --cwd ./path-to-project`)}
`);
};
const OUTPUT_DIR = '.output';
export default async function main(client: Client) {
if (process.env.__VERCEL_BUILD_RUNNING) {
client.output.error(
`${cmd(
`${getPkgName()} build`
)} must not recursively invoke itself. Check the Build Command in the Project Settings or the ${cmd(
'build'
)} script in ${cmd('package.json')}`
);
client.output.error(
`Learn More: https://vercel.link/recursive-invocation-of-commands`
);
return 1;
} else {
process.env.__VERCEL_BUILD_RUNNING = '1';
}
let argv;
const buildStamp = stamp();
try {
argv = getArgs(client.argv.slice(2), {
'--debug': Boolean,
'--cwd': String,
});
} catch (err) {
handleError(err);
return 1;
}
if (argv['--help']) {
help();
return 2;
}
let cwd = argv['--cwd'] || process.cwd();
let project = await readProjectSettings(join(cwd, VERCEL_DIR));
// If there are no project settings, only then do we pull them down
while (!project?.settings) {
const confirmed = await confirm(
`No Project Settings found locally. Run ${getCommandName(
'pull'
)} for retrieving them?`,
true
);
if (!confirmed) {
client.output.print(`Aborted. No Project Settings retrieved.\n`);
return 0;
}
const result = await pull(client);
if (result !== 0) {
return result;
}
project = await readProjectSettings(join(cwd, VERCEL_DIR));
}
// If `rootDirectory` exists, then `baseDir` will be the repo's root directory.
const baseDir = cwd;
cwd = project.settings.rootDirectory
? join(cwd, project.settings.rootDirectory)
: cwd;
// Load the environment
const { combinedEnv, loadedEnvFiles } = loadEnvConfig(cwd, false, {
info: () => ({}), // we don't want to log this yet.
error: (...args: any[]) => client.output.error(args.join(' ')),
});
// Set process.env with loaded environment variables
processEnv(loadedEnvFiles);
const spawnOpts: {
env: Record<string, string | undefined>;
} = {
env: { ...combinedEnv, VERCEL: '1' },
};
process.chdir(cwd);
const pkg = await readJSONFile<PackageJson>('./package.json');
if (pkg instanceof CantParseJSONFile) {
throw pkg;
}
const vercelConfig = await readJSONFile<VercelConfig>('./vercel.json');
if (vercelConfig instanceof CantParseJSONFile) {
throw vercelConfig;
}
if (!process.env.NOW_BUILDER) {
// This validation is only necessary when
// a user runs `vercel build` locally.
const globFiles = await buildUtilsGlob('**', { cwd });
const zeroConfig = await detectBuilders(Object.keys(globFiles), pkg);
const { reason } = await detectFileSystemAPI({
files: globFiles,
projectSettings: project.settings,
builders: zeroConfig.builders || [],
pkg,
vercelConfig,
tag: '',
enableFlag: true,
});
if (reason) {
client.output.error(`${cmd(`${getPkgName()} build`)} failed: ${reason}`);
return 1;
}
}
const framework = findFramework(project.settings.framework);
// If this is undefined, we bail. If it is null, then findFramework should return "Other",
// so this should really never happen, but just in case....
if (framework === undefined) {
client.output.error(
`Framework detection failed or is malformed. Please run ${getCommandName(
'pull'
)} again.`
);
return 1;
}
const buildState = { ...project.settings };
const formatSetting = (
name: string,
override: string | null | undefined,
defaults: typeof framework.settings.outputDirectory
) =>
` - ${chalk.bold(`${name}:`)} ${`${
override
? override + ` (override)`
: 'placeholder' in defaults
? chalk.italic(`${defaults.placeholder}`)
: defaults.value
}`}`;
console.log(`Retrieved Project Settings:`);
console.log(
chalk.dim(` - ${chalk.bold(`Framework Preset:`)} ${framework.name}`)
);
console.log(
chalk.dim(
formatSetting(
'Build Command',
project.settings.buildCommand,
framework.settings.buildCommand
)
)
);
console.log(
chalk.dim(
formatSetting(
'Output Directory',
project.settings.outputDirectory,
framework.settings.outputDirectory
)
)
);
buildState.outputDirectory =
project.settings.outputDirectory ||
(isSettingValue(framework.settings.outputDirectory)
? framework.settings.outputDirectory.value
: null);
buildState.rootDirectory = project.settings.rootDirectory;
if (loadedEnvFiles.length > 0) {
console.log(
`Loaded Environment Variables from ${loadedEnvFiles.length} ${pluralize(
'file',
loadedEnvFiles.length
)}:`
);
for (let envFile of loadedEnvFiles) {
console.log(chalk.dim(` - ${envFile.path}`));
}
}
// Load plugins
const debug = argv['--debug'];
let plugins;
try {
plugins = await loadCliPlugins(cwd, client.output);
} catch (error) {
client.output.error('Failed to load CLI Plugins');
handleError(error, { debug });
return 1;
}
const origLog = console.log;
const origErr = console.error;
const prefixedLog = (
prefix: string,
args: any[],
logger: (...args: any[]) => void
) => {
if (typeof args[0] === 'string') {
args[0] = `${prefix} ${args[0]}`;
} else {
args.unshift(prefix);
}
return logger(...args);
};
if (plugins?.pluginCount && plugins?.pluginCount > 0) {
console.log(
`Loaded ${plugins.pluginCount} CLI ${pluralize(
'Plugin',
plugins.pluginCount
)}`
);
// preBuild Plugins
if (plugins.preBuildPlugins.length > 0) {
console.log(
`Running ${plugins.pluginCount} CLI ${pluralize(
'Plugin',
plugins.pluginCount
)} before Build Command:`
);
for (let item of plugins.preBuildPlugins) {
const { name, plugin, color } = item;
if (typeof plugin.preBuild === 'function') {
const pluginStamp = stamp();
const fullName = name + '.preBuild';
const prefix = chalk.gray(' > ') + color(fullName + ':');
client.output.debug(`Running ${fullName}:`);
try {
console.log = (...args: any[]) =>
prefixedLog(prefix, args, origLog);
console.error = (...args: any[]) =>
prefixedLog(prefix, args, origErr);
await plugin.preBuild();
client.output.debug(
`Completed ${fullName} ${chalk.dim(`${pluginStamp()}`)}`
);
} catch (error) {
client.output.error(`${prefix} failed`);
handleError(error, { debug });
return 1;
} finally {
console.log = origLog;
console.error = origErr;
}
}
}
}
}
// Clean the output directory
fs.removeSync(join(cwd, OUTPUT_DIR));
if (framework && process.env.VERCEL_URL && 'envPrefix' in framework) {
for (const key of Object.keys(process.env)) {
if (key.startsWith('VERCEL_')) {
const newKey = `${framework.envPrefix}${key}`;
// Set `process.env` and `spawnOpts.env` to make sure the variables are
// available to the `build` step and the CLI Plugins.
process.env[newKey] = process.env[newKey] || process.env[key];
spawnOpts.env[newKey] = process.env[newKey];
}
}
}
// Required for Next.js to produce the correct `.nft.json` files.
spawnOpts.env.NEXT_PRIVATE_OUTPUT_TRACE_ROOT = baseDir;
// Yarn v2 PnP mode may be activated, so force
// "node-modules" linker style
const env = {
YARN_NODE_LINKER: 'node-modules',
...spawnOpts.env,
};
if (typeof buildState.buildCommand === 'string') {
console.log(`Running Build Command: ${cmd(buildState.buildCommand)}`);
await execCommand(buildState.buildCommand, {
...spawnOpts,
env,
cwd,
});
} else if (fs.existsSync(join(cwd, 'package.json'))) {
await runPackageJsonScript(
client,
cwd,
['vercel-build', 'now-build', 'build'],
spawnOpts
);
} else if (typeof framework.settings.buildCommand.value === 'string') {
console.log(
`Running Build Command: ${cmd(framework.settings.buildCommand.value)}`
);
await execCommand(framework.settings.buildCommand.value, {
...spawnOpts,
env,
cwd,
});
}
if (!fs.existsSync(join(cwd, OUTPUT_DIR))) {
let dotNextDir: string | null = null;
// If a custom `outputDirectory` was set, we'll need to verify
// if it's `.next` output, or just static output.
const userOutputDirectory = project.settings.outputDirectory;
if (typeof userOutputDirectory === 'string') {
if (fs.existsSync(join(cwd, userOutputDirectory, 'BUILD_ID'))) {
dotNextDir = join(cwd, userOutputDirectory);
client.output.debug(
`Consider ${param(userOutputDirectory)} as ${param('.next')} output.`
);
}
} else if (fs.existsSync(join(cwd, '.next'))) {
dotNextDir = join(cwd, '.next');
client.output.debug(`Found ${param('.next')} directory.`);
}
// We cannot rely on the `framework` alone, as it might be a static export,
// and the current build might use a different project that's not in the settings.
const isNextOutput = Boolean(dotNextDir);
const nextExport = await getNextExportStatus(dotNextDir);
const outputDir =
isNextOutput && !nextExport ? OUTPUT_DIR : join(OUTPUT_DIR, 'static');
const getDistDir = framework.getFsOutputDir || framework.getOutputDirName;
const distDir =
(nextExport?.exportDetail.outDirectory
? relative(cwd, nextExport.exportDetail.outDirectory)
: false) ||
dotNextDir ||
userOutputDirectory ||
(await getDistDir(cwd));
await fs.ensureDir(join(cwd, outputDir));
const copyStamp = stamp();
client.output.spinner(
`Copying files from ${param(distDir)} to ${param(outputDir)}`
);
const files = await glob(join(relative(cwd, distDir), '**'), {
ignore: [
'node_modules/**',
'.vercel/**',
'.env',
'.env.*',
'.*ignore',
'_middleware.ts',
'_middleware.mts',
'_middleware.cts',
'_middleware.mjs',
'_middleware.cjs',
'_middleware.js',
'api/**',
'.git/**',
'.next/cache/**',
],
nodir: true,
dot: true,
cwd,
absolute: true,
});
await Promise.all(
files.map(f =>
smartCopy(
client,
f,
distDir === '.'
? join(cwd, outputDir, relative(cwd, f))
: f.replace(distDir, outputDir)
)
)
);
client.output.stopSpinner();
console.log(
`Copied ${files.length.toLocaleString()} files from ${param(
distDir
)} to ${param(outputDir)} ${copyStamp()}`
);
const buildManifestPath = join(cwd, OUTPUT_DIR, 'build-manifest.json');
const routesManifestPath = join(cwd, OUTPUT_DIR, 'routes-manifest.json');
if (!fs.existsSync(buildManifestPath)) {
client.output.debug(
`Generating build manifest: ${param(buildManifestPath)}`
);
const buildManifest = {
version: 1,
cache: framework.cachePattern ? [framework.cachePattern] : [],
};
await fs.writeJSON(buildManifestPath, buildManifest, { spaces: 2 });
}
if (!fs.existsSync(routesManifestPath)) {
client.output.debug(
`Generating routes manifest: ${param(routesManifestPath)}`
);
const routesManifest = {
version: 3,
pages404: true,
basePath: '',
redirects: framework.defaultRedirects ?? [],
headers: framework.defaultHeaders ?? [],
dynamicRoutes: [],
dataRoutes: [],
rewrites: framework.defaultRewrites ?? [],
};
await fs.writeJSON(
join(cwd, OUTPUT_DIR, 'routes-manifest.json'),
routesManifest,
{ spaces: 2 }
);
}
// Special Next.js processing.
if (nextExport) {
client.output.debug('Found `next export` output.');
const htmlFiles = await buildUtilsGlob(
'**/*.html',
join(cwd, OUTPUT_DIR, 'static')
);
if (nextExport.exportDetail.success !== true) {
client.output.error(
`Export of Next.js app failed. Please check your build logs.`
);
process.exit(1);
}
await fs.mkdirp(join(cwd, OUTPUT_DIR, 'server', 'pages'));
await fs.mkdirp(join(cwd, OUTPUT_DIR, 'static'));
await Promise.all(
Object.keys(htmlFiles).map(async fileName => {
await sema.acquire();
const input = join(cwd, OUTPUT_DIR, 'static', fileName);
const target = join(cwd, OUTPUT_DIR, 'server', 'pages', fileName);
await fs.mkdirp(dirname(target));
await fs.promises.rename(input, target).finally(() => {
sema.release();
});
})
);
for (const file of [
'BUILD_ID',
'images-manifest.json',
'routes-manifest.json',
'build-manifest.json',
]) {
const input = join(nextExport.dotNextDir, file);
if (fs.existsSync(input)) {
// Do not use `smartCopy`, since we want to overwrite if they already exist.
await fs.copyFile(input, join(OUTPUT_DIR, file));
}
}
} else if (isNextOutput) {
// The contents of `.output/static` should be placed inside of `.output/static/_next/static`
const tempStatic = '___static';
await fs.rename(
join(cwd, OUTPUT_DIR, 'static'),
join(cwd, OUTPUT_DIR, tempStatic)
);
await fs.mkdirp(join(cwd, OUTPUT_DIR, 'static', '_next', 'static'));
await fs.rename(
join(cwd, OUTPUT_DIR, tempStatic),
join(cwd, OUTPUT_DIR, 'static', '_next', 'static')
);
// Next.js might reference files from the `static` directory in `middleware-manifest.json`.
// Since we move all files from `static` to `static/_next/static`, we'll need to change
// those references as well and update the manifest file.
const middlewareManifest = join(
cwd,
OUTPUT_DIR,
'server',
'middleware-manifest.json'
);
if (fs.existsSync(middlewareManifest)) {
const manifest = await fs.readJSON(middlewareManifest);
Object.keys(manifest.middleware).forEach(key => {
const files = manifest.middleware[key].files.map((f: string) => {
if (f.startsWith('static/')) {
const next = f.replace(/^static\//gm, 'static/_next/static/');
client.output.debug(
`Replacing file in \`middleware-manifest.json\`: ${f} => ${next}`
);
return next;
}
return f;
});
manifest.middleware[key].files = files;
});
await fs.writeJSON(middlewareManifest, manifest);
}
// We want to pick up directories for user-provided static files into `.`output/static`.
// More specifically, the static directory contents would then be mounted to `output/static/static`,
// and the public directory contents would be mounted to `output/static`. Old Next.js versions
// allow `static`, and newer ones allow both, but since there's nobody that actually uses both,
// we can check for the existence of both and pick the first match that we find (first
// `public`, then`static`). We can't read both at the same time because that would mean we'd
// read public for old Next.js versions that don't support it, which might be breaking (and
// we don't want to make vercel build specific framework versions).
const nextSrcDirectory = dirname(distDir);
const publicFiles = await glob('public/**', {
nodir: true,
dot: true,
cwd: nextSrcDirectory,
absolute: true,
});
if (publicFiles.length > 0) {
await Promise.all(
publicFiles.map(f =>
smartCopy(
client,
f,
join(
OUTPUT_DIR,
'static',
relative(join(dirname(distDir), 'public'), f)
)
)
)
);
} else {
const staticFiles = await glob('static/**', {
nodir: true,
dot: true,
cwd: nextSrcDirectory,
absolute: true,
});
await Promise.all(
staticFiles.map(f =>
smartCopy(
client,
f,
join(
OUTPUT_DIR,
'static',
'static',
relative(join(dirname(distDir), 'static'), f)
)
)
)
);
}
// Regardless of the Next.js version, we make sure that it is compatible with
// the Filesystem API. We get there by moving all the files needed
// into the outputs directory `inputs` folder. Next.js is > 12, we can
// read the .nft.json files directly. If there aren't .nft.json files
// we trace and create them. We then resolve the files in each nft file list
// and move them into the "inputs" directory. We rename them with hashes to
// prevent collisions and then update the related .nft files accordingly
// to point to the newly named input files. Again, all of this is so that Next.js
// works with the Filesystem API (and so .output contains all inputs
// needed to run Next.js) and `vc --prebuilt`.
const nftFiles = await glob(join(OUTPUT_DIR, '**', '*.nft.json'), {
nodir: true,
dot: true,
ignore: ['cache/**'],
cwd,
absolute: true,
});
// If there are no .nft.json files, we know that Next.js < 12. We then
// execute the tracing on our own.
if (nftFiles.length === 0) {
const serverFiles = await glob(
join(OUTPUT_DIR, 'server', 'pages', '**', '*.js'),
{
nodir: true,
dot: true,
cwd,
ignore: ['webpack-runtime.js'],
absolute: true,
}
);
for (let f of serverFiles) {
const { ext, dir } = parse(f);
const { fileList } = await nodeFileTrace([f], {
ignore: [
relative(cwd, f),
'node_modules/next/dist/pages/**/*',
'node_modules/next/dist/compiled/webpack/(bundle4|bundle5).js',
'node_modules/react/**/*.development.js',
'node_modules/react-dom/**/*.development.js',
'node_modules/use-subscription/**/*.development.js',
'node_modules/sharp/**/*',
],
});
fileList.delete(relative(cwd, f));
const nftFileName = f.replace(ext, '.js.nft.json');
client.output.debug(`Creating ${nftFileName}`);
await fs.writeJSON(nftFileName, {
version: 2,
files: Array.from(fileList).map(fileListEntry =>
relative(dir, fileListEntry)
),
});
}
}
const requiredServerFilesPath = join(
OUTPUT_DIR,
'required-server-files.json'
);
if (fs.existsSync(requiredServerFilesPath)) {
client.output.debug(`Resolve ${param('required-server-files.json')}.`);
const requiredServerFilesJson = await fs.readJSON(
requiredServerFilesPath
);
await fs.writeJSON(requiredServerFilesPath, {
...requiredServerFilesJson,
appDir: '.',
files: requiredServerFilesJson.files.map((i: string) => {
const originalPath = join(requiredServerFilesJson.appDir, i);
const relPath = join(OUTPUT_DIR, relative(distDir, originalPath));
return relPath;
}),
});
}
}
}
// Build Plugins
if (plugins?.buildPlugins && plugins.buildPlugins.length > 0) {
console.log(
`Running ${plugins.pluginCount} CLI ${pluralize(
'Plugin',
plugins.pluginCount
)} after Build Command:`
);
let vercelConfig: VercelConfig = {};
try {
vercelConfig = await fs.readJSON(join(cwd, 'vercel.json'));
} catch (error) {
if (error.code !== 'ENOENT') {
throw new Error(`Failed to read vercel.json: ${error.message}`);
}
}
for (let item of plugins.buildPlugins) {
const { name, plugin, color } = item;
if (typeof plugin.build === 'function') {
const pluginStamp = stamp();
const fullName = name + '.build';
const prefix = chalk.gray(' > ') + color(fullName + ':');
client.output.debug(`Running ${fullName}:`);
try {
console.log = (...args: any[]) => prefixedLog(prefix, args, origLog);
console.error = (...args: any[]) =>
prefixedLog(prefix, args, origErr);
await plugin.build({
vercelConfig,
workPath: cwd,
});
client.output.debug(
`Completed ${fullName} ${chalk.dim(`${pluginStamp()}`)}`
);
} catch (error) {
client.output.error(`${prefix} failed`);
handleError(error, { debug });
return 1;
} finally {
console.log = origLog;
console.error = origLog;
}
}
}
}
console.log(
`${prependEmoji(
`Build Completed in ${chalk.bold(OUTPUT_DIR)} ${chalk.gray(
buildStamp()
)}`,
emoji('success')
)}`
);
return 0;
}
export async function runPackageJsonScript(
client: Client,
destPath: string,
scriptNames: string | Iterable<string>,
spawnOpts?: SpawnOptions
) {
assert(isAbsolute(destPath));
const { packageJson, cliType, lockfileVersion } = await scanParentDirs(
destPath,
true
);
const scriptName = getScriptName(
packageJson,
typeof scriptNames === 'string' ? [scriptNames] : scriptNames
);
if (!scriptName) return false;
client.output.debug('Running user script...');
const runScriptTime = Date.now();
const opts: any = { cwd: destPath, ...spawnOpts };
const env = (opts.env = { ...process.env, ...opts.env });
if (cliType === 'npm') {
opts.prettyCommand = `npm run ${scriptName}`;
if (typeof lockfileVersion === 'number' && lockfileVersion >= 2) {
// Ensure that npm 7 is at the beginning of the `$PATH`
env.PATH = `/node16/bin-npm7:${env.PATH}`;
}
} else {
opts.prettyCommand = `yarn run ${scriptName}`;
// Yarn v2 PnP mode may be activated, so force "node-modules" linker style
if (!env.YARN_NODE_LINKER) {
env.YARN_NODE_LINKER = 'node-modules';
}
}
console.log(`Running Build Command: ${cmd(opts.prettyCommand)}\n`);
await spawnAsync(cliType, ['run', scriptName], opts);
console.log(); // give it some room
client.output.debug(`Script complete [${Date.now() - runScriptTime}ms]`);
return true;
}
async function linkOrCopy(existingPath: string, newPath: string) {
try {
if (
newPath.endsWith('.nft.json') ||
newPath.endsWith('middleware-manifest.json') ||
newPath.endsWith('required-server-files.json')
) {
await fs.copy(existingPath, newPath, {
overwrite: true,
});
} else {
await fs.createLink(existingPath, newPath);
}
} catch (err: any) {
// eslint-disable-line
// If a symlink to the same file already exists
// then trying to copy it will make an empty file from it.
if (err['code'] === 'EEXIST') return;
// In some VERY rare cases (1 in a thousand), symlink creation fails on Windows.
// In that case, we just fall back to copying.
// This issue is reproducible with "pnpm add @material-ui/icons@4.9.1"
await fs.copy(existingPath, newPath, {
overwrite: true,
});
}
}
async function smartCopy(client: Client, from: string, to: string) {
sema.acquire();
try {
client.output.debug(`Copying from ${from} to ${to}`);
await linkOrCopy(from, to);
} finally {
sema.release();
}
}
async function glob(pattern: string, options: GlobOptions): Promise<string[]> {
return new Promise((resolve, reject) => {
ogGlob(pattern, options, (err, files) => {
err ? reject(err) : resolve(files);
});
});
}
/**
* Files will only exist when `next export` was used.
*/
async function getNextExportStatus(dotNextDir: string | null) {
if (!dotNextDir) {
return null;
}
const exportDetail: {
success: boolean;
outDirectory: string;
} | null = await fs
.readJson(join(dotNextDir, 'export-detail.json'))
.catch(error => {
if (error.code === 'ENOENT') {
return null;
}
throw error;
});
if (!exportDetail) {
return null;
}
const exportMarker: {
version: 1;
exportTrailingSlash: boolean;
hasExportPathMap: boolean;
} | null = await fs
.readJSON(join(dotNextDir, 'export-marker.json'))
.catch(error => {
if (error.code === 'ENOENT') {
return null;
}
throw error;
});
return {
dotNextDir,
exportDetail,
exportMarker: {
trailingSlash: exportMarker?.hasExportPathMap
? exportMarker.exportTrailingSlash
: false,
},
};
}

View File

@@ -42,6 +42,7 @@ export const help = () => `
-h, --help Output usage information
-v, --version Output the version number
--cwd Current working directory
-V, --platform-version Set the platform version to deploy to
-A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline(
'FILE'

View File

@@ -892,8 +892,8 @@ const parseEnv = (env?: string[] | Dictionary<string>) => {
if (equalsSign === -1) {
key = e;
} else {
key = e.substr(0, equalsSign);
value = e.substr(equalsSign + 1);
key = e.slice(0, equalsSign);
value = e.slice(equalsSign + 1);
}
o[key] = value;

View File

@@ -1,4 +1,5 @@
import chalk from 'chalk';
import { ProjectEnvTarget } from '../../types';
import Client from '../../util/client';
import { getEnvTargetPlaceholder } from '../../util/env/env-target';
import getArgs from '../../util/get-args';
@@ -137,14 +138,14 @@ export default async function main(client: Client) {
case 'rm':
return rm(client, project, argv, args, output);
case 'pull':
output.warn(
`${getCommandName(
'env pull'
)} is deprecated and will be removed in future releases. Run ${getCommandName(
'pull'
)} instead.`
return pull(
client,
project,
ProjectEnvTarget.Development,
argv,
args,
output
);
return pull(client, project, argv, args, output);
default:
output.error(getInvalidSubcommand(COMMAND_CONFIG));
help();

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