Compare commits

..

54 Commits

Author SHA1 Message Date
Nathan Rajlich
5f5b5b0340 Publish Canary
- vercel@20.1.3-canary.8
 - @vercel/next@2.6.39-canary.1
 - @vercel/node@1.8.5-canary.0
 - @vercel/static-build@0.17.16-canary.0
 - @vercel/redwood@0.1.2-canary.3
2020-11-11 10:31:14 -08:00
Nathan Rajlich
a0ce373dc9 [static-build][next][node][redwood] Invoke installCommand from Project settings (#5393)
* [static-build] Invoke `installCommand` from Project settings

* [next] Invoke `installCommand` from Project settings

* [redwood] Invoke `installCommand` from Project settings

* [node] Remove `--prefer-offline` npm flag

* [static-build] Remove `--prefer-offline` npm flag

* [next] Remove extra console.log()

* Move `installTime` to inside else branch

* Use `as keyof Config`

* [static-build] Add `installCommand` integration test

* [next] Add `installCommand` integration test

* Use `.`

* [redwood] Add `installCommand` integration test

* Skip `installCommand` if string is empty

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2020-11-11 10:26:57 -08:00
Steven
049ffb268b [examples] Update svelte example with latest rollup (#5404)
Updated the template using `npx degit sveltejs/template my-svelte-project`

I also added a `yarn.lock` file so dependencies don't break for future deployments.

Related to https://github.com/Samuel-Martineau/generator-svelte/issues/7
2020-11-11 09:44:06 -05:00
Andy Bitz
94483c8b36 Publish Stable
- @vercel/static-build@0.17.15
2020-11-10 21:32:13 +01:00
Andy Bitz
078f9de44e Publish Canary
- @vercel/frameworks@0.1.2-canary.2
 - @vercel/build-utils@2.5.5-canary.3
 - vercel@20.1.3-canary.7
 - @vercel/client@9.0.4-canary.4
 - @vercel/static-build@0.17.15-canary.0
 - @vercel/redwood@0.1.2-canary.2
2020-11-10 21:18:48 +01:00
Andy
0ae41f97a4 [static-build] Ignore framework routes if config/routes.json is present (#5401) 2020-11-10 21:17:45 +01:00
Nathan Rajlich
9fb6a1ec4c [frameworks] Use "value" instead of "placeholder" for frameworks with known installCommand (#5400) 2020-11-10 11:25:16 -08:00
Andy Bitz
21be46c8bb Publish Stable
- @vercel/static-build@0.17.14
2020-11-10 18:51:06 +01:00
Andy
91942fc4d2 [static-build] Expect routes array directly (#5399) 2020-11-10 18:49:51 +01:00
Andy Bitz
8e02f2289f Publish Stable
- @vercel/static-build@0.17.13
2020-11-10 16:21:58 +01:00
Andy Bitz
4205d643a1 Publish Canary
- @vercel/next@2.6.39-canary.0
 - @vercel/static-build@0.17.13-canary.0
2020-11-10 16:19:08 +01:00
Andy
b2dda4dbb9 [static-build] Support static and config directory in .vercel_build_output (#5396)
* Handle routes and static files from the build output directory

* Add logging

* Move comment
2020-11-10 16:18:07 +01:00
Joe Haddad
f36eac3d0a [next] files within the buildId directory are immutable (#5386)
Co-authored-by: Tim Neutkens <tim@timneutkens.nl>
2020-11-10 09:19:26 -05:00
Andy Bitz
a2c56425f4 Publish Stable
- @vercel/static-build@0.17.12
2020-11-10 00:43:05 +01:00
Andy Bitz
c759bfda9c Publish Canary
- @vercel/client@9.0.4-canary.3
 - @vercel/static-build@0.17.12-canary.0
2020-11-10 00:14:31 +01:00
Andy
57995001ac [static-build] Add directory for Serverless Functions (#5390)
* [@vercel/static-build] Add directory for Serverless Functions

* Update tests and only consider index.js files

* Import only once

* Only change for zeroConfig

* Update packages/now-static-build/src/utils/_shared.ts

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

* Update packages/now-static-build/test/fixtures/62-function-output-directory-with-static/now.json

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

* Update packages/now-static-build/test/fixtures/61-function-output-directory/now.json

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

* Update build

* Fix type

* Remove line

* Add to .vercelignore

* Fix paths in test

* Move more files

* Remove special case for test

Co-authored-by: Steven <steven@ceriously.com>
2020-11-10 00:09:23 +01:00
Nathan Rajlich
e5553d8ec2 Publish Canary
- @vercel/build-utils@2.5.5-canary.2
 - vercel@20.1.3-canary.6
 - @vercel/client@9.0.4-canary.2
2020-11-09 15:02:17 -08:00
Nathan Rajlich
dcee5b16c7 [build-utils] Add installCommand to Config object (#5391) 2020-11-09 22:27:29 +00:00
JJ Kasper
b190f2e118 Publish Stable
- @vercel/next@2.6.38
2020-11-07 12:11:05 -06:00
JJ Kasper
2389d3e936 Publish Canary
- @vercel/next@2.6.38-canary.0
2020-11-07 11:55:08 -06:00
JJ Kasper
0f4ed1965a [next] Correct data routes with basePath (#5381) 2020-11-06 23:40:32 -05:00
luc
e1e38ee536 Publish Canary
- vercel@20.1.3-canary.5
2020-11-06 16:50:13 +01:00
Luc Leray
dc1ff00610 [cli] Show error when removing env variable from no target environment (#5378)
* error if no env target is selected

* improve prompt ui

* add test
2020-11-06 16:49:34 +01:00
luc
5f31736603 Publish Canary
- vercel@20.1.3-canary.4
2020-11-05 23:17:08 +01:00
Luc Leray
9a57cc72dd [cli] (major) Update vercel env (#5372)
* prompt to select env type

* improve copy

* create env variable with type

* remove nanoid dependency

* use encodeURIComponent

* refactor envType -> type

* refactor envName -> key

* do not hide value input

* adjust value prompt depending on type

* handle std input for plaintext env variables

* show custom error when secret is not found

* handle secret name input starting with `@`

* fix system env value question

* improve ui of prompts

* expand env list in vc rm

* adjust tests

* environment -> environments

* list multiple targets in vc env ls

* show value for system env vars

* adjust tests

* capitalize targets

* add <type> to arguments

* always use stdInput for envValue

* fix number of arguments error

* adjust tests

* refactor ProjectEnvType

* refactor SYSTEM_ENV_VALUES

* fix typo for typePlaceholder

* use getCommandName

* use title

* remove @ts-ignore

* improve types

* show key controls for checkbox prompt
2020-11-05 22:58:58 +01:00
JJ Kasper
9d9c5f3753 Publish Stable
- @vercel/next@2.6.37
2020-11-05 12:44:43 -06:00
JJ Kasper
deeefc0c93 Publish Canary
- @vercel/next@2.6.37-canary.0
2020-11-05 11:50:05 -06:00
JJ Kasper
5c23b08bc1 Ensure auto-export dynamic routes are routed properly (#5377) 2020-11-05 11:48:29 -06:00
dependabot[bot]
7c4e25ccce Bump bl from 1.2.2 to 1.2.3 (#5363)
Bumps [bl](https://github.com/rvagg/bl) from 1.2.2 to 1.2.3.
- [Release notes](https://github.com/rvagg/bl/releases)
- [Commits](https://github.com/rvagg/bl/compare/v1.2.2...v1.2.3)

Signed-off-by: dependabot[bot] <support@github.com>
2020-11-05 10:24:17 -05:00
Joe Haddad
1bfa310945 Publish Stable
- @vercel/static-build@0.17.11
2020-11-04 23:29:30 -05:00
Joe Haddad
c96062266b Publish Canary
- @vercel/frameworks@0.1.2-canary.1
 - @vercel/build-utils@2.5.5-canary.1
 - vercel@20.1.3-canary.3
 - @vercel/client@9.0.4-canary.1
 - @vercel/static-build@0.17.11-canary.0
 - @vercel/redwood@0.1.2-canary.1
2020-11-04 23:12:46 -05:00
Joe Haddad
5bea99c1d9 [static-build] zero-config gatsby should support export default (#5375) 2020-11-04 23:11:40 -05:00
Luc Leray
358be773a2 [tests] Cleanup fixture folder before running CLI test (#5374) 2020-11-05 02:09:36 +01:00
Nathan Rajlich
9ebf4e531d [frameworks] Add installCommand placeholders (#5373) 2020-11-04 16:24:06 -08:00
Joe Haddad
71e79258b7 Publish Stable
- @vercel/next@2.6.36
 - @vercel/static-build@0.17.10
2020-11-03 10:49:41 -05:00
Joe Haddad
1dfafe7040 Publish Canary
- @vercel/next@2.6.36-canary.0
2020-11-03 10:40:07 -05:00
JJ Kasper
78ed452a99 [next] Ensure localeDetection: false is honored (#5365)
This disables adding the locale redirects when `localeDetection` is set to `false` 

x-ref: https://github.com/vercel/next.js/issues/18482
2020-11-03 01:58:40 +00:00
Joe Haddad
d408e2ef1a Publish Canary
- @vercel/static-build@0.17.10-canary.0
2020-11-02 15:04:48 -05:00
Joe Haddad
8ebb1fd9ce [static-build] add Vercel Analytics Support for Gatsby (#5366) 2020-11-02 15:03:45 -05:00
JJ Kasper
4eb5ad625c Publish Stable
- @vercel/next@2.6.35
2020-11-02 10:26:52 -06:00
luc
7164f6e58e Publish Canary
- vercel@20.1.3-canary.2
2020-11-02 15:43:54 +01:00
Naoyuki Kanezawa
a36d084b3e [cli] Add support for plaintext env variables (#5345)
* fix plain envs can not be retrieved

* add test

* add test for `vc dev`

* use v6 to retrieve env variables

* fix v6 return type

* use v6 to display env vars

* add test for vc env ls

* expand env vars with multiple targets

* minor type fixes

* always use v6 for getEnvVariables

Co-authored-by: luc <luc.leray@gmail.com>
2020-11-02 15:42:28 +01:00
JJ Kasper
8a16447fed Publish Canary
- vercel@20.1.3-canary.1
 - @vercel/next@2.6.35-canary.1
2020-11-01 12:24:51 -06:00
Leo Lamprecht
efda4ab6b9 Updated Next.js Template to use latest version of Next.js and React (#5358) 2020-11-01 17:13:16 +01:00
JJ Kasper
16060a71a9 [next] Add additional tests for i18n revalidate params (#5356) 2020-11-01 15:15:20 +01:00
Luc Leray
b18e0a7415 [cli] Replace util/prompt-bool by util/input/confirm (#5359) 2020-10-31 22:07:35 +01:00
JJ Kasper
1251f11a97 Publish Canary
- @vercel/next@2.6.35-canary.0
2020-10-31 00:34:03 -05:00
JJ Kasper
07235e22f6 [next] Fix root-most optional-catchall with i18n (#5354)
* Fix root-most optional-catchall with i18n

* Ensure dynamic prerenders revalidation

* Add test delay
2020-10-30 17:40:39 -05:00
JJ Kasper
81011df816 Publish Stable
- @vercel/next@2.6.34
2020-10-30 10:50:03 -05:00
JJ Kasper
c8d31bdcf7 Publish Canary
- @vercel/next@2.6.34-canary.0
2020-10-30 10:16:20 -05:00
JJ Kasper
5e7f1158ad [next] Ensure revalidation works with i18n (#5350)
This corrects some i18n pages being considered staticPages instead of prerender items which was breaking revalidate behavior. This also adds more verbose tests for i18n ensuring revalidation is working correctly

x-ref: https://github.com/vercel/next.js/discussions/18443
Fixes: https://github.com/vercel/next.js/issues/18503
2020-10-30 14:59:47 +00:00
JJ Kasper
df5aa1f10d Publish Stable
- @vercel/next@2.6.33
2020-10-27 13:41:00 -05:00
JJ Kasper
eb1ba97309 Publish Canary
- @vercel/next@2.6.33-canary.0
2020-10-27 13:33:33 -05:00
JJ Kasper
8047d6de49 [next] Fix index data path for non-locale path (#5339)
* Fix index data path for non-locale path

* Add index SSG test

* Update tests for canary

* Correct logic check to handle default locale
2020-10-27 13:33:02 -05:00
171 changed files with 74780 additions and 506 deletions

View File

@@ -34,6 +34,7 @@ packages/now-node-bridge/bridge.*
# now-static-build
packages/now-static-build/test/fixtures
packages/now-static-build/test/build-fixtures
# redwood
packages/redwood/test/fixtures

View File

@@ -8,8 +8,8 @@
"start": "next start"
},
"dependencies": {
"next": "9.5.4",
"react": "16.13.1",
"react-dom": "16.13.1"
"next": "10.0.0",
"react": "17.0.1",
"react-dom": "17.0.1"
}
}

View File

@@ -1,5 +1,8 @@
/node_modules/
/public/build/
.DS_Store
node_modules
public/bundle.*
.env
.env.local
.env.build
.vercel

View File

@@ -1,2 +0,0 @@
yarn.lock
README.md

View File

@@ -1,24 +1,21 @@
{
"name": "svelte-app",
"version": "1.0.0",
"private": true,
"scripts": {
"build": "rollup -c",
"dev": "rollup -c -w",
"start": "sirv public"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^13.0.0",
"@rollup/plugin-node-resolve": "^8.1.0",
"npm-run-all": "^4.1.5",
"rollup": "^2.18.0",
"rollup-plugin-livereload": "^1.0.0",
"rollup-plugin-svelte": "^5.0.3",
"rollup-plugin-terser": "^6.1.0",
"@rollup/plugin-commonjs": "^14.0.0",
"@rollup/plugin-node-resolve": "^8.0.0",
"rollup": "^2.3.4",
"rollup-plugin-livereload": "^2.0.0",
"rollup-plugin-svelte": "^6.0.0",
"rollup-plugin-terser": "^7.0.0",
"svelte": "^3.0.0"
},
"dependencies": {
"sirv-cli": "^1.0.1"
},
"scripts": {
"build": "rollup -c",
"autobuild": "rollup -c -w",
"dev": "run-p start:dev autobuild",
"start": "sirv public --single",
"start:dev": "sirv public --single --dev"
"sirv-cli": "^1.0.0"
}
}

View File

@@ -1,17 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf8" />
<meta name="viewport" content="width=device-width" />
<html lang="en">
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width,initial-scale=1'>
<title>Svelte + Node.js API</title>
<title>Svelte + Node.js API</title>
<link rel="icon" type="image/png" href="/favicon.png" />
<link rel="stylesheet" href="/global.css" />
<link rel="stylesheet" href="/bundle.css" />
</head>
<link rel='icon' type='image/png' href='/favicon.png'>
<link rel='stylesheet' href='/global.css'>
<link rel='stylesheet' href='/build/bundle.css'>
<body>
<script src="/bundle.js"></script>
</body>
<script defer src='/build/bundle.js'></script>
</head>
<body>
</body>
</html>

View File

@@ -6,42 +6,70 @@ import { terser } from 'rollup-plugin-terser';
const production = !process.env.ROLLUP_WATCH;
function serve() {
let server;
function toExit() {
if (server) server.kill(0);
}
return {
writeBundle() {
if (server) return;
server = require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
stdio: ['ignore', 'inherit', 'inherit'],
shell: true
});
process.on('SIGTERM', toExit);
process.on('exit', toExit);
}
};
}
export default {
input: 'src/main.js',
output: {
sourcemap: true,
format: 'iife',
name: 'app',
file: 'public/bundle.js',
},
plugins: [
svelte({
// enable run-time checks when not in production
dev: !production,
// we'll extract any component CSS out into
// a separate file better for performance
css: css => {
css.write('public/bundle.css');
},
}),
input: 'src/main.js',
output: {
sourcemap: true,
format: 'iife',
name: 'app',
file: 'public/build/bundle.js'
},
plugins: [
svelte({
// enable run-time checks when not in production
dev: !production,
// we'll extract any component CSS out into
// a separate file - better for performance
css: css => {
css.write('bundle.css');
}
}),
// If you have external dependencies installed from
// npm, you'll most likely need these plugins. In
// some cases you'll need additional configuration
// consult the documentation for details:
// https://github.com/rollup/rollup-plugin-commonjs
resolve({ browser: true }),
commonjs(),
// If you have external dependencies installed from
// npm, you'll most likely need these plugins. In
// some cases you'll need additional configuration -
// consult the documentation for details:
// https://github.com/rollup/plugins/tree/master/packages/commonjs
resolve({
browser: true,
dedupe: ['svelte']
}),
commonjs(),
// Watch the `public` directory and refresh the
// browser on changes when not in production
!production && livereload('public'),
// In dev mode, call `npm run start` once
// the bundle has been generated
!production && serve(),
// If we're building for production (npm run build
// instead of npm run dev), minify
production && terser(),
],
watch: {
clearScreen: false,
},
// Watch the `public` directory and refresh the
// browser on changes when not in production
!production && livereload('public'),
// If we're building for production (npm run build
// instead of npm run dev), minify
production && terser()
],
watch: {
clearScreen: false
}
};

View File

@@ -0,0 +1,128 @@
// @ts-check
/** This script modifies the project to support TS code in .svelte files like:
<script lang="ts">
export let name: string;
</script>
As well as validating the code for CI.
*/
/** To work on this script:
rm -rf test-template template && git clone sveltejs/template test-template && node scripts/setupTypeScript.js test-template
*/
const fs = require("fs")
const path = require("path")
const { argv } = require("process")
const projectRoot = argv[2] || path.join(__dirname, "..")
// Add deps to pkg.json
const packageJSON = JSON.parse(fs.readFileSync(path.join(projectRoot, "package.json"), "utf8"))
packageJSON.devDependencies = Object.assign(packageJSON.devDependencies, {
"svelte-check": "^1.0.0",
"svelte-preprocess": "^4.0.0",
"@rollup/plugin-typescript": "^6.0.0",
"typescript": "^3.9.3",
"tslib": "^2.0.0",
"@tsconfig/svelte": "^1.0.0"
})
// Add script for checking
packageJSON.scripts = Object.assign(packageJSON.scripts, {
"validate": "svelte-check"
})
// Write the package JSON
fs.writeFileSync(path.join(projectRoot, "package.json"), JSON.stringify(packageJSON, null, " "))
// mv src/main.js to main.ts - note, we need to edit rollup.config.js for this too
const beforeMainJSPath = path.join(projectRoot, "src", "main.js")
const afterMainTSPath = path.join(projectRoot, "src", "main.ts")
fs.renameSync(beforeMainJSPath, afterMainTSPath)
// Switch the app.svelte file to use TS
const appSveltePath = path.join(projectRoot, "src", "App.svelte")
let appFile = fs.readFileSync(appSveltePath, "utf8")
appFile = appFile.replace("<script>", '<script lang="ts">')
appFile = appFile.replace("export let name;", 'export let name: string;')
fs.writeFileSync(appSveltePath, appFile)
// Edit rollup config
const rollupConfigPath = path.join(projectRoot, "rollup.config.js")
let rollupConfig = fs.readFileSync(rollupConfigPath, "utf8")
// Edit imports
rollupConfig = rollupConfig.replace(`'rollup-plugin-terser';`, `'rollup-plugin-terser';
import sveltePreprocess from 'svelte-preprocess';
import typescript from '@rollup/plugin-typescript';`)
// Replace name of entry point
rollupConfig = rollupConfig.replace(`'src/main.js'`, `'src/main.ts'`)
// Add preprocess to the svelte config, this is tricky because there's no easy signifier.
// Instead we look for `css:` then the next `}` and add the preprocessor to that
let foundCSS = false
let match
// https://regex101.com/r/OtNjwo/1
const configEditor = new RegExp(/css:.|\n*}/gmi)
while (( match = configEditor.exec(rollupConfig)) != null) {
if (foundCSS) {
const endOfCSSIndex = match.index + 1
rollupConfig = rollupConfig.slice(0, endOfCSSIndex) + ",\n preprocess: sveltePreprocess()," + rollupConfig.slice(endOfCSSIndex);
break
}
if (match[0].includes("css:")) foundCSS = true
}
// Add TypeScript
rollupConfig = rollupConfig.replace(
'commonjs(),',
'commonjs(),\n\t\ttypescript({\n\t\t\tsourceMap: !production,\n\t\t\tinlineSources: !production\n\t\t}),'
);
fs.writeFileSync(rollupConfigPath, rollupConfig)
// Add TSConfig
const tsconfig = `{
"extends": "@tsconfig/svelte/tsconfig.json",
"include": ["src/**/*"],
"exclude": ["node_modules/*", "__sapper__/*", "public/*"]
}`
const tsconfigPath = path.join(projectRoot, "tsconfig.json")
fs.writeFileSync(tsconfigPath, tsconfig)
// Delete this script, but not during testing
if (!argv[2]) {
// Remove the script
fs.unlinkSync(path.join(__filename))
// Check for Mac's DS_store file, and if it's the only one left remove it
const remainingFiles = fs.readdirSync(path.join(__dirname))
if (remainingFiles.length === 1 && remainingFiles[0] === '.DS_store') {
fs.unlinkSync(path.join(__dirname, '.DS_store'))
}
// Check if the scripts folder is empty
if (fs.readdirSync(path.join(__dirname)).length === 0) {
// Remove the scripts folder
fs.rmdirSync(path.join(__dirname))
}
}
// Adds the extension recommendation
fs.mkdirSync(path.join(projectRoot, ".vscode"))
fs.writeFileSync(path.join(projectRoot, ".vscode", "extensions.json"), `{
"recommendations": ["svelte.svelte-vscode"]
}
`)
console.log("Converted to TypeScript.")
if (fs.existsSync(path.join(projectRoot, "node_modules"))) {
console.log("\nYou will need to re-run your dependency manager to get started.")
}

View File

@@ -1,10 +1,10 @@
import App from './App.svelte';
const app = new App({
target: document.body,
props: {
name: 'world',
},
target: document.body,
props: {
name: 'world'
}
});
export default app;
export default app;

635
examples/svelte/yarn.lock Normal file
View File

@@ -0,0 +1,635 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@babel/code-frame@^7.10.4":
version "7.10.4"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a"
integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==
dependencies:
"@babel/highlight" "^7.10.4"
"@babel/helper-validator-identifier@^7.10.4":
version "7.10.4"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2"
integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==
"@babel/highlight@^7.10.4":
version "7.10.4"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143"
integrity sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==
dependencies:
"@babel/helper-validator-identifier" "^7.10.4"
chalk "^2.0.0"
js-tokens "^4.0.0"
"@polka/url@^1.0.0-next.9":
version "1.0.0-next.11"
resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.11.tgz#aeb16f50649a91af79dbe36574b66d0f9e4d9f71"
integrity sha512-3NsZsJIA/22P3QUyrEDNA2D133H4j224twJrdipXN38dpnIOzAbUDtOwkcJ5pXmn75w7LSQDjA4tO9dm1XlqlA==
"@rollup/plugin-commonjs@^14.0.0":
version "14.0.0"
resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-14.0.0.tgz#4285f9ec2db686a31129e5a2b415c94aa1f836f0"
integrity sha512-+PSmD9ePwTAeU106i9FRdc+Zb3XUWyW26mo5Atr2mk82hor8+nPwkztEjFo8/B1fJKfaQDg9aM2bzQkjhi7zOw==
dependencies:
"@rollup/pluginutils" "^3.0.8"
commondir "^1.0.1"
estree-walker "^1.0.1"
glob "^7.1.2"
is-reference "^1.1.2"
magic-string "^0.25.2"
resolve "^1.11.0"
"@rollup/plugin-node-resolve@^8.0.0":
version "8.4.0"
resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-8.4.0.tgz#261d79a680e9dc3d86761c14462f24126ba83575"
integrity sha512-LFqKdRLn0ShtQyf6SBYO69bGE1upV6wUhBX0vFOUnLAyzx5cwp8svA0eHUnu8+YU57XOkrMtfG63QOpQx25pHQ==
dependencies:
"@rollup/pluginutils" "^3.1.0"
"@types/resolve" "1.17.1"
builtin-modules "^3.1.0"
deep-freeze "^0.0.1"
deepmerge "^4.2.2"
is-module "^1.0.0"
resolve "^1.17.0"
"@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b"
integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==
dependencies:
"@types/estree" "0.0.39"
estree-walker "^1.0.1"
picomatch "^2.2.2"
"@types/estree@*":
version "0.0.45"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.45.tgz#e9387572998e5ecdac221950dab3e8c3b16af884"
integrity sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g==
"@types/estree@0.0.39":
version "0.0.39"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
"@types/node@*":
version "14.14.7"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.7.tgz#8ea1e8f8eae2430cf440564b98c6dfce1ec5945d"
integrity sha512-Zw1vhUSQZYw+7u5dAwNbIA9TuTotpzY/OF7sJM9FqPOF3SPjKnxrjoTktXDZgUjybf4cWVBP7O8wvKdSaGHweg==
"@types/resolve@1.17.1":
version "1.17.1"
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6"
integrity sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==
dependencies:
"@types/node" "*"
ansi-styles@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
dependencies:
color-convert "^1.9.0"
anymatch@~3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142"
integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==
dependencies:
normalize-path "^3.0.0"
picomatch "^2.0.4"
async-limiter@~1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
binary-extensions@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9"
integrity sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
dependencies:
balanced-match "^1.0.0"
concat-map "0.0.1"
braces@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
dependencies:
fill-range "^7.0.1"
buffer-from@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
builtin-modules@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.1.0.tgz#aad97c15131eb76b65b50ef208e7584cd76a7484"
integrity sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==
chalk@^2.0.0:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
dependencies:
ansi-styles "^3.2.1"
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chokidar@^3.3.0:
version "3.4.3"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.3.tgz#c1df38231448e45ca4ac588e6c79573ba6a57d5b"
integrity sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==
dependencies:
anymatch "~3.1.1"
braces "~3.0.2"
glob-parent "~5.1.0"
is-binary-path "~2.1.0"
is-glob "~4.0.1"
normalize-path "~3.0.0"
readdirp "~3.5.0"
optionalDependencies:
fsevents "~2.1.2"
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
dependencies:
color-name "1.1.3"
color-name@1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
commander@^2.20.0:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
commondir@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
console-clear@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/console-clear/-/console-clear-1.1.1.tgz#995e20cbfbf14dd792b672cde387bd128d674bf7"
integrity sha512-pMD+MVR538ipqkG5JXeOEbKWS5um1H4LUUccUQG68qpeqBYbzYy79Gh55jkd2TtPdRfUaLWdv6LPP//5Zt0aPQ==
deep-freeze@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/deep-freeze/-/deep-freeze-0.0.1.tgz#3a0b0005de18672819dfd38cd31f91179c893e84"
integrity sha1-OgsABd4YZygZ39OM0x+RF5yJPoQ=
deepmerge@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
estree-walker@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362"
integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==
estree-walker@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700"
integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==
fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
dependencies:
to-regex-range "^5.0.1"
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
fsevents@~2.1.2:
version "2.1.3"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e"
integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
get-port@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc"
integrity sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=
glob-parent@~5.1.0:
version "5.1.1"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229"
integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==
dependencies:
is-glob "^4.0.1"
glob@^7.1.2:
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
has-flag@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
has@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
dependencies:
function-bind "^1.1.1"
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
dependencies:
once "^1.3.0"
wrappy "1"
inherits@2:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
is-binary-path@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
dependencies:
binary-extensions "^2.0.0"
is-core-module@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.1.0.tgz#a4cc031d9b1aca63eecbd18a650e13cb4eeab946"
integrity sha512-YcV7BgVMRFRua2FqQzKtTDMz8iCuLEyGKjr70q8Zm1yy2qKcurbFEd79PAdHV77oL3NrAaOVQIbMmiHQCHB7ZA==
dependencies:
has "^1.0.3"
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
is-glob@^4.0.1, is-glob@~4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
dependencies:
is-extglob "^2.1.1"
is-module@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591"
integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=
is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
is-reference@^1.1.2:
version "1.2.1"
resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7"
integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==
dependencies:
"@types/estree" "*"
jest-worker@^26.2.1:
version "26.6.2"
resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed"
integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==
dependencies:
"@types/node" "*"
merge-stream "^2.0.0"
supports-color "^7.0.0"
js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
kleur@^3.0.0:
version "3.0.3"
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
livereload-js@^3.1.0:
version "3.3.1"
resolved "https://registry.yarnpkg.com/livereload-js/-/livereload-js-3.3.1.tgz#61f887468086762e61fb2987412cf9d1dda99202"
integrity sha512-CBu1gTEfzVhlOK1WASKAAJ9Qx1fHECTq0SUB67sfxwQssopTyvzqTlgl+c0h9pZ6V+Fzd2rc510ppuNusg9teQ==
livereload@^0.9.1:
version "0.9.1"
resolved "https://registry.yarnpkg.com/livereload/-/livereload-0.9.1.tgz#65125dabdf2db4fd3f1169e953fe56e3bcc6f477"
integrity sha512-9g7sua11kkyZNo2hLRCG3LuZZwqexoyEyecSlV8cAsfAVVCZqLzVir6XDqmH0r+Vzgnd5LrdHDMyjtFnJQLAYw==
dependencies:
chokidar "^3.3.0"
livereload-js "^3.1.0"
opts ">= 1.2.0"
ws "^6.2.1"
local-access@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/local-access/-/local-access-1.0.1.tgz#5121258146d64e869046c642ea4f1dd39ff942bb"
integrity sha512-ykt2pgN0aqIy6KQC1CqdWTWkmUwNgaOS6dcpHVjyBJONA+Xi7AtSB1vuxC/U/0tjIP3wcRudwQk1YYzUvzk2bA==
magic-string@^0.25.2:
version "0.25.7"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051"
integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==
dependencies:
sourcemap-codec "^1.4.4"
merge-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
mime@^2.3.1:
version "2.4.6"
resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1"
integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==
minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
dependencies:
brace-expansion "^1.1.7"
mri@^1.1.0:
version "1.1.6"
resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.6.tgz#49952e1044db21dbf90f6cd92bc9c9a777d415a6"
integrity sha512-oi1b3MfbyGa7FJMP9GmLTttni5JoICpYBRlq+x5V16fZbLsnL9N3wFqqIm/nIG43FjUFkFh9Epzp/kzUGUnJxQ==
normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
once@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
dependencies:
wrappy "1"
"opts@>= 1.2.0":
version "2.0.2"
resolved "https://registry.yarnpkg.com/opts/-/opts-2.0.2.tgz#a17e189fbbfee171da559edd8a42423bc5993ce1"
integrity sha512-k41FwbcLnlgnFh69f4qdUfvDQ+5vaSDnVPFI/y5XuhKRq97EnVVneO9F1ESVCdiVu4fCS2L8usX3mU331hB7pg==
path-is-absolute@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
path-parse@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
randombytes@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
dependencies:
safe-buffer "^5.1.0"
readdirp@~3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e"
integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==
dependencies:
picomatch "^2.2.1"
require-relative@^0.8.7:
version "0.8.7"
resolved "https://registry.yarnpkg.com/require-relative/-/require-relative-0.8.7.tgz#7999539fc9e047a37928fa196f8e1563dabd36de"
integrity sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=
resolve@^1.11.0, resolve@^1.17.0:
version "1.19.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c"
integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==
dependencies:
is-core-module "^2.1.0"
path-parse "^1.0.6"
rollup-plugin-livereload@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/rollup-plugin-livereload/-/rollup-plugin-livereload-2.0.0.tgz#d3928d74e8cf2ae4286c5dd46b770fd3f3b82313"
integrity sha512-oC/8NqumGYuphkqrfszOHUUIwzKsaHBICw6QRwT5uD07gvePTS+HW+GFwu6f9K8W02CUuTvtIM9AWJrbj4wE1A==
dependencies:
livereload "^0.9.1"
rollup-plugin-svelte@^6.0.0:
version "6.1.1"
resolved "https://registry.yarnpkg.com/rollup-plugin-svelte/-/rollup-plugin-svelte-6.1.1.tgz#66362cf0500fb7a848283ebcf19d289a60ef0871"
integrity sha512-ijnm0pH1ScrY4uxwaNXBpNVejVzpL2769hIEbAlnqNUWZrffLspu5/k9/l/Wsj3NrEHLQ6wCKGagVJonyfN7ow==
dependencies:
require-relative "^0.8.7"
rollup-pluginutils "^2.8.2"
sourcemap-codec "^1.4.8"
rollup-plugin-terser@^7.0.0:
version "7.0.2"
resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz#e8fbba4869981b2dc35ae7e8a502d5c6c04d324d"
integrity sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==
dependencies:
"@babel/code-frame" "^7.10.4"
jest-worker "^26.2.1"
serialize-javascript "^4.0.0"
terser "^5.0.0"
rollup-pluginutils@^2.8.2:
version "2.8.2"
resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e"
integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==
dependencies:
estree-walker "^0.6.1"
rollup@^2.3.4:
version "2.33.1"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.33.1.tgz#802795164164ee63cd47769d8879c33ec8ae0c40"
integrity sha512-uY4O/IoL9oNW8MMcbA5hcOaz6tZTMIh7qJHx/tzIJm+n1wLoY38BLn6fuy7DhR57oNFLMbDQtDeJoFURt5933w==
optionalDependencies:
fsevents "~2.1.2"
sade@^1.6.0:
version "1.7.4"
resolved "https://registry.yarnpkg.com/sade/-/sade-1.7.4.tgz#ea681e0c65d248d2095c90578c03ca0bb1b54691"
integrity sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA==
dependencies:
mri "^1.1.0"
safe-buffer@^5.1.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
semiver@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/semiver/-/semiver-1.1.0.tgz#9c97fb02c21c7ce4fcf1b73e2c7a24324bdddd5f"
integrity sha512-QNI2ChmuioGC1/xjyYwyZYADILWyW6AmS1UH6gDj/SFUUUS4MBAWs/7mxnkRPc/F4iHezDP+O8t0dO8WHiEOdg==
serialize-javascript@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa"
integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==
dependencies:
randombytes "^2.1.0"
sirv-cli@^1.0.0:
version "1.0.8"
resolved "https://registry.yarnpkg.com/sirv-cli/-/sirv-cli-1.0.8.tgz#150c3f62694203a86cf5d71ec60e6ff4c34064a9"
integrity sha512-bJI+kkzQvMKfAOfgLzv09kWsdymLm39LgKmGjacB19GHIAQLCvXXg8e8HzcofTjDZlA8zVv4dQjU9SWkNzkJhw==
dependencies:
console-clear "^1.1.0"
get-port "^3.2.0"
kleur "^3.0.0"
local-access "^1.0.1"
sade "^1.6.0"
semiver "^1.0.0"
sirv "^1.0.7"
tinydate "^1.0.0"
sirv@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/sirv/-/sirv-1.0.7.tgz#ad8ca1f84430777a59162592626c2b8e9b9f1384"
integrity sha512-QMT2OTD3CTr8de9VByPmvSEeyt6k8/Cxg0J2kQJ5HNhIWfhFg9ypcIWWzez9rPWnGj+WtJ7AZD/MdT/vdilV/A==
dependencies:
"@polka/url" "^1.0.0-next.9"
mime "^2.3.1"
totalist "^1.0.0"
source-map-support@~0.5.19:
version "0.5.19"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
dependencies:
buffer-from "^1.0.0"
source-map "^0.6.0"
source-map@^0.6.0:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
source-map@~0.7.2:
version "0.7.3"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
sourcemap-codec@^1.4.4, sourcemap-codec@^1.4.8:
version "1.4.8"
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
supports-color@^5.3.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
dependencies:
has-flag "^3.0.0"
supports-color@^7.0.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
dependencies:
has-flag "^4.0.0"
svelte@^3.0.0:
version "3.29.7"
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.29.7.tgz#e254eb2d0d609ce0fd60f052d444ac4a66d90f7d"
integrity sha512-rx0g311kBODvEWUU01DFBUl3MJuJven04bvTVFUG/w0On/wuj0PajQY/QlXcJndFxG+W1s8iXKaB418tdHWc3A==
terser@^5.0.0:
version "5.3.8"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.3.8.tgz#991ae8ba21a3d990579b54aa9af11586197a75dd"
integrity sha512-zVotuHoIfnYjtlurOouTazciEfL7V38QMAOhGqpXDEg6yT13cF4+fEP9b0rrCEQTn+tT46uxgFsTZzhygk+CzQ==
dependencies:
commander "^2.20.0"
source-map "~0.7.2"
source-map-support "~0.5.19"
tinydate@^1.0.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/tinydate/-/tinydate-1.3.0.tgz#e6ca8e5a22b51bb4ea1c3a2a4fd1352dbd4c57fb"
integrity sha512-7cR8rLy2QhYHpsBDBVYnnWXm8uRTr38RoZakFSW7Bs7PzfMPNZthuMLkwqZv7MTu8lhQ91cOFYS5a7iFj2oR3w==
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
dependencies:
is-number "^7.0.0"
totalist@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df"
integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
ws@^6.2.1:
version "6.2.1"
resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb"
integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==
dependencies:
async-limiter "~1.0.0"

View File

@@ -17,6 +17,9 @@
]
},
"settings": {
"installCommand": {
"placeholder": "`yarn install` or `npm install`"
},
"buildCommand": {
"placeholder": "`npm run build` or `blitz build`"
},
@@ -47,6 +50,9 @@
]
},
"settings": {
"installCommand": {
"placeholder": "`yarn install` or `npm install`"
},
"buildCommand": {
"placeholder": "`npm run build` or `next build`"
},
@@ -82,6 +88,9 @@
]
},
"settings": {
"installCommand": {
"placeholder": "`yarn install` or `npm install`"
},
"buildCommand": {
"placeholder": "`npm run build` or `gatsby build`"
},
@@ -111,6 +120,9 @@
]
},
"settings": {
"installCommand": {
"placeholder": "`yarn install` or `npm install`"
},
"buildCommand": {
"placeholder": "`npm run build` or `hexo generate`"
},
@@ -140,6 +152,9 @@
]
},
"settings": {
"installCommand": {
"placeholder": "`yarn install` or `npm install`"
},
"buildCommand": {
"placeholder": "`npm run build` or `npx @11ty/eleventy`"
},
@@ -168,6 +183,9 @@
]
},
"settings": {
"installCommand": {
"placeholder": "`yarn install` or `npm install`"
},
"buildCommand": {
"placeholder": "`npm run build` or `docusaurus build`"
},
@@ -196,6 +214,9 @@
]
},
"settings": {
"installCommand": {
"placeholder": "`yarn install` or `npm install`"
},
"buildCommand": {
"placeholder": "`npm run build` or `docusaurus-build`"
},
@@ -224,6 +245,9 @@
]
},
"settings": {
"installCommand": {
"placeholder": "`yarn install` or `npm install`"
},
"buildCommand": {
"placeholder": "`npm run build` or `preact build`"
},
@@ -255,6 +279,9 @@
]
},
"settings": {
"installCommand": {
"placeholder": "`yarn install` or `npm install`"
},
"buildCommand": {
"placeholder": "`npm run build` or `dojo build`"
},
@@ -283,6 +310,9 @@
]
},
"settings": {
"installCommand": {
"placeholder": "`yarn install` or `npm install`"
},
"buildCommand": {
"placeholder": "`npm run build` or `ember build`"
},
@@ -311,6 +341,9 @@
]
},
"settings": {
"installCommand": {
"placeholder": "`yarn install` or `npm install`"
},
"buildCommand": {
"placeholder": "`npm run build` or `vue-cli-service build`"
},
@@ -339,6 +372,9 @@
]
},
"settings": {
"installCommand": {
"placeholder": "`yarn install` or `npm install`"
},
"buildCommand": {
"placeholder": "`npm run build` or `ng build && scully`"
},
@@ -367,6 +403,9 @@
]
},
"settings": {
"installCommand": {
"placeholder": "`yarn install` or `npm install`"
},
"buildCommand": {
"placeholder": "`npm run build` or `ng build`"
},
@@ -395,6 +434,9 @@
]
},
"settings": {
"installCommand": {
"placeholder": "`yarn install` or `npm install`"
},
"buildCommand": {
"placeholder": "`npm run build` or `ng build`"
},
@@ -423,6 +465,9 @@
]
},
"settings": {
"installCommand": {
"placeholder": "`yarn install` or `npm install`"
},
"buildCommand": {
"placeholder": "`npm run build` or `polymer build`"
},
@@ -451,6 +496,9 @@
]
},
"settings": {
"installCommand": {
"placeholder": "`yarn install` or `npm install`"
},
"buildCommand": {
"placeholder": "`npm run build` or `rollup -c`"
},
@@ -479,6 +527,9 @@
]
},
"settings": {
"installCommand": {
"placeholder": "`yarn install` or `npm install`"
},
"buildCommand": {
"placeholder": "`npm run build` or `react-scripts build`"
},
@@ -511,6 +562,9 @@
]
},
"settings": {
"installCommand": {
"placeholder": "`yarn install` or `npm install`"
},
"buildCommand": {
"placeholder": "`npm run build` or `react-scripts build`"
},
@@ -539,6 +593,9 @@
]
},
"settings": {
"installCommand": {
"placeholder": "`yarn install` or `npm install`"
},
"buildCommand": {
"placeholder": "`npm run build` or `gridsome build`"
},
@@ -567,6 +624,9 @@
]
},
"settings": {
"installCommand": {
"placeholder": "`yarn install` or `npm install`"
},
"buildCommand": {
"placeholder": "`npm run build` or `umi build`"
},
@@ -595,6 +655,9 @@
]
},
"settings": {
"installCommand": {
"placeholder": "`yarn install` or `npm install`"
},
"buildCommand": {
"placeholder": "`npm run build` or `sapper export`"
},
@@ -623,6 +686,9 @@
]
},
"settings": {
"installCommand": {
"placeholder": "`yarn install` or `npm install`"
},
"buildCommand": {
"placeholder": "`npm run build` or `saber build`"
},
@@ -651,6 +717,9 @@
]
},
"settings": {
"installCommand": {
"placeholder": "`yarn install` or `npm install`"
},
"buildCommand": {
"placeholder": "`npm run build` or `stencil build`"
},
@@ -679,6 +748,9 @@
]
},
"settings": {
"installCommand": {
"placeholder": "`yarn install` or `npm install`"
},
"buildCommand": {
"placeholder": "`npm run build` or `nuxt generate`"
},
@@ -709,6 +781,9 @@
]
},
"settings": {
"installCommand": {
"placeholder": "`yarn install` or `npm install`"
},
"buildCommand": {
"value": "yarn rw build && yarn rw db up --no-db-client --auto-approve && yarn rw dataMigrate up"
},
@@ -743,6 +818,9 @@
]
},
"settings": {
"installCommand": {
"placeholder": "None"
},
"buildCommand": {
"placeholder": "`npm run build` or `hugo -D --gc`"
},
@@ -770,6 +848,9 @@
]
},
"settings": {
"installCommand": {
"value": "bundle install"
},
"buildCommand": {
"placeholder": "`npm run build` or `jekyll build`"
},
@@ -797,6 +878,9 @@
]
},
"settings": {
"installCommand": {
"placeholder": "`yarn install` or `npm install`"
},
"buildCommand": {
"placeholder": "`npm run build` or `brunch build --production`"
},
@@ -824,6 +908,9 @@
]
},
"settings": {
"installCommand": {
"value": "bundle install"
},
"buildCommand": {
"value": "`npm run build` or `bundle exec middleman build`"
},
@@ -841,6 +928,9 @@
"logo": "https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/other.svg",
"description": "No framework or a unoptimized framework.",
"settings": {
"installCommand": {
"placeholder": "`yarn install` or `npm install`"
},
"buildCommand": {
"placeholder": "`npm run vercel-build` or `npm run build`"
},

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/frameworks",
"version": "0.1.2-canary.0",
"version": "0.1.2-canary.2",
"main": "frameworks.json",
"license": "UNLICENSED",
"scripts": {

View File

@@ -89,9 +89,15 @@ const Schema = {
},
settings: {
type: 'object',
required: ['buildCommand', 'devCommand', 'outputDirectory'],
required: [
'installCommand',
'buildCommand',
'devCommand',
'outputDirectory',
],
additionalProperties: false,
properties: {
installCommand: SchemaSettings,
buildCommand: SchemaSettings,
devCommand: SchemaSettings,
outputDirectory: SchemaSettings,

View File

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

View File

@@ -24,6 +24,7 @@ interface Options {
projectSettings?: {
framework?: string | null;
devCommand?: string | null;
installCommand?: string | null;
buildCommand?: string | null;
outputDirectory?: string | null;
createdAt?: number;
@@ -450,6 +451,10 @@ function detectFrontBuilder(
config.devCommand = projectSettings.devCommand;
}
if (projectSettings.installCommand) {
config.installCommand = projectSettings.installCommand;
}
if (projectSettings.buildCommand) {
config.buildCommand = projectSettings.buildCommand;
}

View File

@@ -41,6 +41,7 @@ export interface Config {
import?: { [key: string]: string };
functions?: BuilderFunctions;
outputDirectory?: string;
installCommand?: string;
buildCommand?: string;
devCommand?: string;
framework?: string;

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "20.1.3-canary.0",
"version": "20.1.3-canary.8",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -13,7 +13,7 @@
"scripts": {
"preinstall": "node ./scripts/preinstall.js",
"test-unit": "nyc ava test/unit.js test/dev-builder.unit.js test/dev-router.unit.js test/dev-server.unit.js test/dev-validate.unit.js --serial --fail-fast --verbose",
"test-integration-cli": "ava test/integration.js --serial --fail-fast --verbose",
"test-integration-cli": "rimraf test/fixtures/integration && ava test/integration.js --serial --fail-fast --verbose",
"test-integration-dev": "ava test/dev/integration.js --serial --fail-fast --verbose",
"prepublishOnly": "yarn build",
"coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov",
@@ -61,9 +61,9 @@
"node": ">= 10"
},
"dependencies": {
"@vercel/build-utils": "2.5.5-canary.0",
"@vercel/build-utils": "2.5.5-canary.3",
"@vercel/go": "1.1.6",
"@vercel/node": "1.8.4",
"@vercel/node": "1.8.5-canary.0",
"@vercel/python": "1.2.3",
"@vercel/ruby": "1.2.4",
"update-notifier": "4.1.0"
@@ -100,7 +100,7 @@
"@types/universal-analytics": "0.4.2",
"@types/which": "1.3.2",
"@types/write-json-file": "2.2.1",
"@vercel/frameworks": "0.1.2-canary.0",
"@vercel/frameworks": "0.1.2-canary.2",
"@vercel/ncc": "0.24.0",
"@zeit/fun": "0.11.2",
"@zeit/source-map-support": "0.6.2",
@@ -146,7 +146,6 @@
"minimatch": "3.0.4",
"mri": "1.1.5",
"ms": "2.1.2",
"nanoid": "3.0.2",
"node-fetch": "2.6.1",
"npm-package-arg": "6.1.0",
"nyc": "13.2.0",
@@ -158,6 +157,7 @@
"psl": "1.1.31",
"qr-image": "3.2.0",
"raw-body": "2.4.1",
"rimraf": "3.0.2",
"semver": "5.5.0",
"serve-handler": "6.1.1",
"sinon": "4.4.2",

View File

@@ -7,7 +7,7 @@ import getScope from '../../util/get-scope.ts';
import removeAliasById from '../../util/alias/remove-alias-by-id';
import stamp from '../../util/output/stamp.ts';
import strlen from '../../util/strlen.ts';
import promptBool from '../../util/prompt-bool';
import confirm from '../../util/input/confirm';
import { isValidName } from '../../util/is-valid-name';
import findAliasByAliasOrId from '../../util/alias/find-alias-by-alias-or-id';
import { getCommandName } from '../../util/pkg-name.ts';
@@ -108,5 +108,5 @@ async function confirmAliasRemove(output, alias) {
output.log(`The following alias will be removed permanently`);
output.print(` ${tbl}\n`);
return promptBool(output, chalk.red('Are you sure?'));
return confirm(chalk.red('Are you sure?'), false);
}

View File

@@ -1,6 +1,6 @@
import chalk from 'chalk';
import inquirer from 'inquirer';
import { ProjectEnvTarget, Project } from '../../types';
import { ProjectEnvTarget, Project, Secret, ProjectEnvType } from '../../types';
import { Output } from '../../util/output';
import Client from '../../util/client';
import stamp from '../../util/output/stamp';
@@ -11,12 +11,14 @@ import {
getEnvTargetPlaceholder,
getEnvTargetChoices,
} from '../../util/env/env-target';
import { isValidEnvType, getEnvTypePlaceholder } from '../../util/env/env-type';
import readStandardInput from '../../util/input/read-standard-input';
import param from '../../util/output/param';
import withSpinner from '../../util/with-spinner';
import { emoji, prependEmoji } from '../../util/emoji';
import { isKnownError } from '../../util/env/known-error';
import { getCommandName } from '../../util/pkg-name';
import { SYSTEM_ENV_VALUES } from '../../util/env/system-env';
type Options = {
'--debug': boolean;
@@ -29,38 +31,71 @@ export default async function add(
args: string[],
output: Output
) {
const stdInput = await readStandardInput();
let [envName, envTarget] = args;
// improve the way we show inquirer prompts
require('../../util/input/patch-inquirer');
if (args.length > 2) {
const stdInput = await readStandardInput();
let [envTypeArg, envName, envTargetArg] = args;
if (args.length > 3) {
output.error(
`Invalid number of arguments. Usage: ${getCommandName(
`env add <name> ${getEnvTargetPlaceholder()}`
`env add ${getEnvTypePlaceholder()} <name> ${getEnvTargetPlaceholder()}`
)}`
);
return 1;
}
if (stdInput && (!envName || !envTarget)) {
if (stdInput && (!envTypeArg || !envName || !envTargetArg)) {
output.error(
`Invalid number of arguments. Usage: ${getCommandName(
`env add <name> <target> < <file>`
`env add ${getEnvTypePlaceholder()} <name> <target> < <file>`
)}`
);
return 1;
}
let envTargets: ProjectEnvTarget[] = [];
if (envTarget) {
if (!isValidEnvTarget(envTarget)) {
if (envTargetArg) {
if (!isValidEnvTarget(envTargetArg)) {
output.error(
`The Environment ${param(
envTarget
envTargetArg
)} is invalid. It must be one of: ${getEnvTargetPlaceholder()}.`
);
return 1;
}
envTargets.push(envTarget);
envTargets.push(envTargetArg);
}
let envType: ProjectEnvType;
if (envTypeArg) {
if (!isValidEnvType(envTypeArg)) {
output.error(
`The Environment Variable type ${param(
envTypeArg
)} is invalid. It must be one of: ${getEnvTypePlaceholder()}.`
);
return 1;
}
envType = envTypeArg;
} else {
const answers = (await inquirer.prompt({
name: 'inputEnvType',
type: 'list',
message: `Which type of Environment Variable do you want to add?`,
choices: [
{ name: 'Plaintext', value: ProjectEnvType.Plaintext },
{
name: `Secret (can be created using ${getCommandName('secret add')})`,
value: ProjectEnvType.Secret,
},
{ name: 'Provided by System', value: ProjectEnvType.System },
],
})) as { inputEnvType: ProjectEnvType };
envType = answers.inputEnvType;
}
while (!envName) {
@@ -77,7 +112,7 @@ export default async function add(
}
}
const envs = await getEnvVariables(output, client, project.id, 4);
const { envs } = await getEnvVariables(output, client, project.id);
const existing = new Set(
envs.filter(r => r.key === envName).map(r => r.target)
);
@@ -98,15 +133,59 @@ export default async function add(
if (stdInput) {
envValue = stdInput;
} else if (isSystemEnvVariable(envName)) {
envValue = '';
} else {
} else if (envType === ProjectEnvType.Plaintext) {
const { inputValue } = await inquirer.prompt({
type: 'password',
type: 'input',
name: 'inputValue',
message: `Whats the value of ${envName}?`,
});
envValue = inputValue || '';
} else if (envType === ProjectEnvType.Secret) {
let secretId: string | null = null;
while (!secretId) {
let { secretName } = await inquirer.prompt({
type: 'input',
name: 'secretName',
message: `Whats the value of ${envName}?`,
});
secretName = secretName || '';
if (secretName[0] === '@') {
secretName = secretName.slice(1);
}
try {
const secret = await client.fetch<Secret>(
`/v2/now/secrets/${encodeURIComponent(secretName)}`
);
secretId = secret.uid;
} catch (error) {
if (error.status === 404) {
output.error(
`Please enter the name of an existing Secret (can be created with ${getCommandName(
'secret add'
)}).`
);
} else {
throw error;
}
}
}
envValue = secretId;
} else {
const { systemEnvValue } = await inquirer.prompt({
name: 'systemEnvValue',
type: 'list',
message: `Whats the value of ${envName}?`,
choices: SYSTEM_ENV_VALUES.map(value => ({ name: value, value })),
});
envValue = systemEnvValue;
}
while (envTargets.length === 0) {
@@ -127,7 +206,15 @@ export default async function add(
const addStamp = stamp();
try {
await withSpinner('Saving', () =>
addEnvRecord(output, client, project.id, envName, envValue, envTargets)
addEnvRecord(
output,
client,
project.id,
envType,
envName,
envValue,
envTargets
)
);
} catch (error) {
if (isKnownError(error) && error.serverMessage) {
@@ -148,7 +235,3 @@ export default async function add(
return 0;
}
function isSystemEnvVariable(envName: string) {
return envName.startsWith('VERCEL_');
}

View File

@@ -6,6 +6,7 @@ import getArgs from '../../util/get-args';
import getSubcommand from '../../util/get-subcommand';
import getInvalidSubcommand from '../../util/get-invalid-subcommand';
import { getEnvTargetPlaceholder } from '../../util/env/env-target';
import { getEnvTypePlaceholder } from '../../util/env/env-type';
import { getLinkedProject } from '../../util/projects/link';
import Client from '../../util/client';
import handleError from '../../util/handle-error';
@@ -18,16 +19,17 @@ import ls from './ls';
import rm from './rm';
const help = () => {
const placeholder = getEnvTargetPlaceholder();
const typePlaceholder = getEnvTypePlaceholder();
const targetPlaceholder = getEnvTargetPlaceholder();
console.log(`
${chalk.bold(`${logo} ${getPkgName()} env`)} [options] <command>
${chalk.dim('Commands:')}
ls [environment] List all variables for the specified Environment
add [name] [environment] Add an Environment Variable (see examples below)
rm [name] [environment] Remove an Environment Variable (see examples below)
pull [filename] Pull all Development Environment Variables from the cloud and write to a file [.env]
ls [environment] List all variables for the specified Environment
add [type] [name] [environment] Add an Environment Variable (see examples below)
rm [name] [environment] Remove an Environment Variable (see examples below)
pull [filename] Pull all Development Environment Variables from the cloud and write to a file [.env]
${chalk.dim('Options:')}
@@ -42,27 +44,32 @@ const help = () => {
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
'TOKEN'
)} Login token
-N, --next Show next page of results
${chalk.dim('Examples:')}
${chalk.gray('')} Add a new variable to multiple Environments
${chalk.cyan(`$ ${getPkgName()} env add <name>`)}
${chalk.cyan(`$ ${getPkgName()} env add API_TOKEN`)}
${chalk.cyan(`$ ${getPkgName()} env add ${typePlaceholder} <name>`)}
${chalk.cyan(`$ ${getPkgName()} env add secret API_TOKEN`)}
${chalk.gray('')} Add a new variable for a specific Environment
${chalk.cyan(`$ ${getPkgName()} env add <name> ${placeholder}`)}
${chalk.cyan(`$ ${getPkgName()} env add DB_CONNECTION production`)}
${chalk.cyan(
`$ ${getPkgName()} env add ${typePlaceholder} <name> ${targetPlaceholder}`
)}
${chalk.cyan(`$ ${getPkgName()} env add secret DB_PASS production`)}
${chalk.gray('')} Add a new Environment Variable from stdin
${chalk.cyan(
`$ cat <file> | ${getPkgName()} env add <name> ${placeholder}`
`$ cat <file> | ${getPkgName()} env add ${typePlaceholder} <name> ${targetPlaceholder}`
)}
${chalk.cyan(
`$ cat ~/.npmrc | ${getPkgName()} env add plain NPM_RC preview`
)}
${chalk.cyan(
`$ ${getPkgName()} env add plain API_URL production < url.txt`
)}
${chalk.cyan(`$ cat ~/.npmrc | ${getPkgName()} env add NPM_RC preview`)}
${chalk.cyan(`$ ${getPkgName()} env add DB_PASS production < secret.txt`)}
${chalk.gray('')} Remove an variable from multiple Environments
@@ -71,14 +78,8 @@ const help = () => {
${chalk.gray('')} Remove a variable from a specific Environment
${chalk.cyan(`$ ${getPkgName()} env rm <name> ${placeholder}`)}
${chalk.cyan(`$ ${getPkgName()} env rm <name> ${targetPlaceholder}`)}
${chalk.cyan(`$ ${getPkgName()} env rm NPM_RC preview`)}
${chalk.gray('')} Paginate results, where ${chalk.dim(
'`1584722256178`'
)} is the time in milliseconds since the UNIX epoch.
${chalk.cyan(`$ ${getPkgName()} env ls --next 1584722256178`)}
`);
};
@@ -96,8 +97,6 @@ export default async function main(ctx: NowContext) {
argv = getArgs(ctx.argv.slice(2), {
'--yes': Boolean,
'-y': '--yes',
'--next': Number,
'-N': '--next',
});
} catch (error) {
handleError(error);

View File

@@ -1,7 +1,12 @@
import chalk from 'chalk';
import ms from 'ms';
import { Output } from '../../util/output';
import { ProjectEnvVariable, ProjectEnvTarget, Project } from '../../types';
import {
ProjectEnvTarget,
Project,
ProjectEnvVariable,
ProjectEnvType,
} from '../../types';
import Client from '../../util/client';
import formatTable from '../../util/format-table';
import getEnvVariables from '../../util/env/get-env-records';
@@ -11,12 +16,13 @@ import {
} from '../../util/env/env-target';
import stamp from '../../util/output/stamp';
import param from '../../util/output/param';
import getCommandFlags from '../../util/get-command-flags';
import { getCommandName } from '../../util/pkg-name';
import ellipsis from '../../util/output/ellipsis';
// @ts-ignore
import title from 'title';
type Options = {
'--debug': boolean;
'--next'?: number;
};
export default async function ls(
@@ -26,8 +32,6 @@ export default async function ls(
args: string[],
output: Output
) {
const { '--next': nextTimestamp } = opts;
if (args.length > 1) {
output.error(
`Invalid number of arguments. Usage: ${getCommandName(
@@ -50,42 +54,21 @@ export default async function ls(
const lsStamp = stamp();
if (typeof nextTimestamp !== 'undefined' && Number.isNaN(nextTimestamp)) {
output.error('Please provide a number for flag --next');
return 1;
}
const { envs } = await getEnvVariables(output, client, project.id, envTarget);
const data = await getEnvVariables(
output,
client,
project.id,
5,
envTarget,
nextTimestamp
);
const { envs: records, pagination } = data;
output.log(
`${
records.length > 0 ? 'Environment Variables' : 'No Environment Variables'
envs.length > 0 ? 'Environment Variables' : 'No Environment Variables'
} found in Project ${chalk.bold(project.name)} ${chalk.gray(lsStamp())}`
);
console.log(getTable(records));
if (pagination && pagination.count === 20) {
const flags = getCommandFlags(opts, ['_', '--next']);
output.log(
`To display the next page run ${getCommandName(
`env ls${flags} --next ${pagination.next}`
)}`
);
}
console.log(getTable(envs));
return 0;
}
function getTable(records: ProjectEnvVariable[]) {
return formatTable(
['name', 'value', 'environment', 'created'],
['name', 'value', 'environments', 'created'],
['l', 'l', 'l', 'l', 'l'],
[
{
@@ -96,17 +79,27 @@ function getTable(records: ProjectEnvVariable[]) {
);
}
function getRow({
key,
system = false,
target,
createdAt = 0,
}: ProjectEnvVariable) {
function getRow(env: ProjectEnvVariable) {
let value: string;
if (env.type === ProjectEnvType.Plaintext) {
// replace space characters (line-break, etc.) with simple spaces
// to make sure the displayed value is a single line
const singleLineValue = env.value.replace(/\s/g, ' ');
value = chalk.gray(ellipsis(singleLineValue, 19));
} else if (env.type === ProjectEnvType.System) {
value = chalk.gray.italic(env.value);
} else {
value = chalk.gray.italic('Encrypted');
}
const now = Date.now();
return [
chalk.bold(key),
chalk.gray(chalk.italic(system ? 'Populated by System' : 'Encrypted')),
target || '',
`${ms(now - createdAt)} ago`,
chalk.bold(env.key),
value,
(Array.isArray(env.target) ? env.target : [env.target || ''])
.map(title)
.join(', '),
env.createdAt ? `${ms(now - env.createdAt)} ago` : '',
];
}

View File

@@ -1,7 +1,7 @@
import chalk from 'chalk';
import { ProjectEnvTarget, Project } from '../../types';
import { Output } from '../../util/output';
import promptBool from '../../util/prompt-bool';
import confirm from '../../util/input/confirm';
import Client from '../../util/client';
import stamp from '../../util/output/stamp';
import getDecryptedEnvRecords from '../../util/get-decrypted-env-records';
@@ -68,9 +68,9 @@ export default async function pull(
} else if (
exists &&
!skipConfirmation &&
!(await promptBool(
output,
`Found existing file ${param(filename)}. Do you want to overwrite?`
!(await confirm(
`Found existing file ${param(filename)}. Do you want to overwrite?`,
false
))
) {
output.log('Aborted');

View File

@@ -1,8 +1,8 @@
import chalk from 'chalk';
import inquirer from 'inquirer';
import { ProjectEnvTarget, Project } from '../../types';
import { ProjectEnvTarget, Project, ProjectEnvVariableV5 } from '../../types';
import { Output } from '../../util/output';
import promptBool from '../../util/prompt-bool';
import confirm from '../../util/input/confirm';
import removeEnvRecord from '../../util/env/remove-env-record';
import getEnvVariables from '../../util/env/get-env-records';
import {
@@ -30,6 +30,9 @@ export default async function rm(
args: string[],
output: Output
) {
// improve the way we show inquirer prompts
require('../../util/input/patch-inquirer');
if (args.length > 2) {
output.error(
`Invalid number of arguments. Usage: ${getCommandName(
@@ -69,7 +72,20 @@ export default async function rm(
envName = inputName;
}
const envs = await getEnvVariables(output, client, project.id, 4);
const data = await getEnvVariables(output, client, project.id);
// we expand env vars with multiple targets
const envs: ProjectEnvVariableV5[] = [];
for (let env of data.envs) {
if (Array.isArray(env.target)) {
for (let target of env.target) {
envs.push({ ...env, target });
}
} else {
envs.push({ ...env, target: env.target });
}
}
const existing = new Set(
envs.filter(r => r.key === envName).map(r => r.target)
);
@@ -79,7 +95,7 @@ export default async function rm(
return 1;
}
if (envTargets.length === 0) {
while (envTargets.length === 0) {
const choices = getEnvTargetChoices().filter(c => existing.has(c.value));
if (choices.length === 0) {
output.error(
@@ -97,6 +113,13 @@ export default async function rm(
message: `Remove ${envName} from which Environments (select multiple)?`,
choices,
});
if (inputTargets.length === 0) {
output.error(
'Please select an Environment to remove the Environment Variable from.'
);
}
envTargets = inputTargets;
}
}
@@ -104,11 +127,11 @@ export default async function rm(
const skipConfirmation = opts['--yes'];
if (
!skipConfirmation &&
!(await promptBool(
output,
!(await confirm(
`Removing Environment Variable ${param(
envName
)} from Project ${chalk.bold(project.name)}. Are you sure?`
)} from Project ${chalk.bold(project.name)}. Are you sure?`,
false
))
) {
output.log('Aborted');

View File

@@ -203,12 +203,23 @@ export enum ProjectEnvTarget {
Development = 'development',
}
export enum ProjectEnvType {
Plaintext = 'plain',
Secret = 'secret',
System = 'system',
}
export interface ProjectEnvVariable {
key: string;
value: string;
type: ProjectEnvType;
configurationId?: string | null;
createdAt?: number;
updatedAt?: number;
target?: ProjectEnvTarget | ProjectEnvTarget[];
}
export interface ProjectEnvVariableV5 extends ProjectEnvVariable {
target?: ProjectEnvTarget;
system?: boolean;
}

View File

@@ -1,60 +1,39 @@
import { Output } from '../output';
import Client from '../client';
import { Secret, ProjectEnvTarget, ProjectEnvVariable } from '../../types';
import { customAlphabet } from 'nanoid';
import slugify from '@sindresorhus/slugify';
import {
Secret,
ProjectEnvTarget,
ProjectEnvVariableV5,
ProjectEnvType,
} from '../../types';
export default async function addEnvRecord(
output: Output,
client: Client,
projectId: string,
envName: string,
envValue: string | undefined,
type: ProjectEnvType,
key: string,
envValue: string,
targets: ProjectEnvTarget[]
): Promise<void> {
output.debug(
`Adding Environment Variable ${envName} to ${targets.length} targets`
`Adding ${type} Environment Variable ${key} to ${targets.length} targets`
);
let values: string[] | undefined;
let value = envValue;
if (envValue) {
const secrets = await Promise.all(
targets.map(target =>
client.fetch<Secret>('/v2/now/secrets', {
method: 'POST',
body: JSON.stringify({
name: generateSecretName(envName, target),
value: envValue,
projectId: projectId,
decryptable: target === ProjectEnvTarget.Development,
}),
})
)
if (type === ProjectEnvType.Secret) {
const secret = await client.fetch<Secret>(
`/v2/now/secrets/${encodeURIComponent(envValue)}`
);
values = secrets.map(secret => secret.uid);
value = secret.uid;
}
const body = targets.map((target, i) => ({
key: envName,
value: values ? values[i] : '',
target,
}));
const body = { type, key, value, target: targets };
const urlProject = `/v4/projects/${projectId}/env`;
await client.fetch<ProjectEnvVariable>(urlProject, {
const urlProject = `/v6/projects/${projectId}/env`;
await client.fetch<ProjectEnvVariableV5>(urlProject, {
method: 'POST',
body: JSON.stringify(body),
});
}
const randomSecretSuffix = customAlphabet(
'123456789abcdefghijklmnopqrstuvwxyz',
4
);
function generateSecretName(envName: string, target: ProjectEnvTarget) {
return `${
slugify(envName).substring(0, 80) // we truncate because the max secret length is 100
}-${target}-${randomSecretSuffix()}`;
}

View File

@@ -0,0 +1,15 @@
import { ProjectEnvType } from '../../types';
function envTypes(): string[] {
return Object.values(ProjectEnvType);
}
export function isValidEnvType(
type?: string
): type is ProjectEnvType | undefined {
return typeof type === 'undefined' || envTypes().includes(type);
}
export function getEnvTypePlaceholder() {
return `<${envTypes().join(' | ')}>`;
}

View File

@@ -1,69 +1,24 @@
import { Output } from '../output';
import Client from '../client';
import {
ProjectEnvVariable,
ProjectEnvTarget,
PaginationOptions,
} from '../../types';
import { ProjectEnvVariable, ProjectEnvTarget } from '../../types';
import { URLSearchParams } from 'url';
type ApiVersion = 4 | 5;
type APIV4Response = ProjectEnvVariable[];
interface APIV5Response {
pagination: PaginationOptions;
envs: ProjectEnvVariable[];
}
export default async function getEnvVariables(
output: Output,
client: Client,
projectId: string,
apiVersion: 4,
target?: ProjectEnvTarget
): Promise<APIV4Response>;
export default async function getEnvVariables(
output: Output,
client: Client,
projectId: string,
apiVersion: 5,
target?: ProjectEnvTarget,
next?: number
): Promise<APIV5Response>;
export default async function getEnvVariables<V extends ApiVersion>(
output: Output,
client: Client,
projectId: string,
apiVersion: V,
target?: ProjectEnvTarget,
next?: number
) {
output.debug(
`Fetching Environment Variables of project ${projectId} and target ${target}`
);
const query = new URLSearchParams();
if (apiVersion >= 5) {
query.set('limit', String(20));
}
if (target) {
query.set('target', target);
}
if (next) {
query.set('until', String(next));
}
const url = `/v6/projects/${projectId}/env?${query}`;
const url = `/v${apiVersion}/projects/${projectId}/env?${query}`;
if (apiVersion === 5) {
return client.fetch<APIV5Response>(url);
} else if (apiVersion === 4) {
return client.fetch<APIV4Response>(url);
} else {
throw new Error('Unknown version: ' + apiVersion);
}
return client.fetch<{ envs: ProjectEnvVariable[] }>(url);
}

View File

@@ -1,6 +1,6 @@
import { Output } from '../output';
import Client from '../client';
import { ProjectEnvTarget, Secret, ProjectEnvVariable } from '../../types';
import { ProjectEnvTarget, Secret, ProjectEnvVariableV5 } from '../../types';
export default async function removeEnvRecord(
output: Output,
@@ -18,7 +18,7 @@ export default async function removeEnvRecord(
envName
)}${qs}`;
const env = await client.fetch<ProjectEnvVariable>(urlProject, {
const env = await client.fetch<ProjectEnvVariableV5>(urlProject, {
method: 'DELETE',
});

View File

@@ -0,0 +1,32 @@
export const SYSTEM_ENV_VALUES = [
'VERCEL_URL',
'VERCEL_GITHUB_COMMIT_ORG',
'VERCEL_GITHUB_COMMIT_REF',
'VERCEL_GITHUB_ORG',
'VERCEL_GITHUB_DEPLOYMENT',
'VERCEL_GITHUB_COMMIT_REPO',
'VERCEL_GITHUB_REPO',
'VERCEL_GITHUB_COMMIT_AUTHOR_LOGIN',
'VERCEL_GITHUB_COMMIT_AUTHOR_NAME',
'VERCEL_GITHUB_COMMIT_SHA',
'VERCEL_GITLAB_DEPLOYMENT',
'VERCEL_GITLAB_PROJECT_NAMESPACE',
'VERCEL_GITLAB_PROJECT_NAME',
'VERCEL_GITLAB_PROJECT_ID',
'VERCEL_GITLAB_PROJECT_PATH',
'VERCEL_GITLAB_COMMIT_REF',
'VERCEL_GITLAB_COMMIT_SHA',
'VERCEL_GITLAB_COMMIT_MESSAGE',
'VERCEL_GITLAB_COMMIT_AUTHOR_LOGIN',
'VERCEL_GITLAB_COMMIT_AUTHOR_NAME',
'VERCEL_BITBUCKET_DEPLOYMENT',
'VERCEL_BITBUCKET_REPO_OWNER',
'VERCEL_BITBUCKET_REPO_SLUG',
'VERCEL_BITBUCKET_REPO_NAME',
'VERCEL_BITBUCKET_COMMIT_REF',
'VERCEL_BITBUCKET_COMMIT_SHA',
'VERCEL_BITBUCKET_COMMIT_MESSAGE',
'VERCEL_BITBUCKET_COMMIT_AUTHOR_NAME',
'VERCEL_BITBUCKET_COMMIT_AUTHOR_URL',
'VERCEL_BITBUCKET_COMMIT_AUTHOR_AVATAR',
];

View File

@@ -2,7 +2,7 @@ import getEnvVariables from './env/get-env-records';
import getDecryptedSecret from './env/get-decrypted-secret';
import Client from './client';
import { Output } from './output/create-output';
import { ProjectEnvTarget, Project } from '../types';
import { ProjectEnvTarget, Project, ProjectEnvType } from '../types';
import { Env } from '@vercel/build-utils';
@@ -12,9 +12,15 @@ export default async function getDecryptedEnvRecords(
project: Project,
target: ProjectEnvTarget
): Promise<Env> {
const envs = await getEnvVariables(output, client, project.id, 4, target);
const { envs } = await getEnvVariables(output, client, project.id, target);
const decryptedValues = await Promise.all(
envs.map(async env => {
if (env.type === ProjectEnvType.System) {
return { value: '', found: true };
} else if (env.type === ProjectEnvType.Plaintext) {
return { value: env.value, found: true };
}
try {
const value = await getDecryptedSecret(output, client, env.value);
return { value, found: true };

View File

@@ -10,7 +10,7 @@ import chalk from 'chalk';
*/
// adjusted from https://github.com/SBoudrias/Inquirer.js/blob/942908f17319343d1acc7b876f990797c5695918/packages/inquirer/lib/prompts/base.js#L126
const getQuestion = function() {
const getQuestion = function () {
let message = `${chalk.gray('?')} ${this.opt.message} `;
if (this.opt.type === 'confirm') {
@@ -35,7 +35,7 @@ inquirer.prompt.prompts.input.prototype.getQuestion = getQuestion;
inquirer.prompt.prompts.confirm.prototype.getQuestion = getQuestion;
// adjusted from https://github.com/SBoudrias/Inquirer.js/blob/942908f17319343d1acc7b876f990797c5695918/packages/inquirer/lib/prompts/list.js#L80
inquirer.prompt.prompts.list.prototype.render = function() {
inquirer.prompt.prompts.list.prototype.render = function () {
// Render question
let message = this.getQuestion();
@@ -89,11 +89,22 @@ function listRender(choices, pointer) {
}
// adjusted from https://github.com/SBoudrias/Inquirer.js/blob/942908f17319343d1acc7b876f990797c5695918/packages/inquirer/lib/prompts/checkbox.js#L84
inquirer.prompt.prompts.checkbox.prototype.render = function(error) {
inquirer.prompt.prompts.checkbox.prototype.render = function (error) {
// Render question
let message = this.getQuestion();
let bottomContent = '';
if (!this.spaceKeyPressed) {
message +=
'(Press ' +
chalk.cyan.bold('<space>') +
' to select, ' +
chalk.cyan.bold('<a>') +
' to toggle all, ' +
chalk.cyan.bold('<i>') +
' to invert selection)';
}
// Render choices or answer depending on the state
if (this.status === 'answered') {
message += this.selection.length > 0 ? this.selection.join(', ') : 'None';
@@ -118,7 +129,7 @@ function renderChoices(choices, pointer) {
let output = '';
let separatorOffset = 0;
choices.forEach(function(choice, i) {
choices.forEach(function (choice, i) {
if (choice.type === 'separator') {
separatorOffset++;
output += '' + choice + '\n';
@@ -151,7 +162,7 @@ function renderChoices(choices, pointer) {
}
// adjusted from https://github.com/SBoudrias/Inquirer.js/blob/942908f17319343d1acc7b876f990797c5695918/packages/inquirer/lib/prompts/input.js#L44
inquirer.prompt.prompts.input.prototype.render = function(error) {
inquirer.prompt.prompts.input.prototype.render = function (error) {
let bottomContent = '';
let appendContent = '';
let message = this.getQuestion();
@@ -178,7 +189,7 @@ inquirer.prompt.prompts.input.prototype.render = function(error) {
};
// adjusted from https://github.com/SBoudrias/Inquirer.js/blob/942908f17319343d1acc7b876f990797c5695918/packages/inquirer/lib/prompts/confirm.js#L64
inquirer.prompt.prompts.confirm.prototype.render = function(answer) {
inquirer.prompt.prompts.confirm.prototype.render = function (answer) {
let message = this.getQuestion();
if (this.status === 'answered') {

View File

@@ -0,0 +1,3 @@
export default function ellipsis(str: string, length: number) {
return str.length > length ? `${str.slice(0, length - 1)}` : str;
}

View File

@@ -1,21 +0,0 @@
import chalk from 'chalk';
import { Output } from './output';
async function promptBool(output: Output, message: string): Promise<boolean> {
return new Promise<boolean>(resolve => {
output.print(`${chalk.gray('>')} ${message} ${chalk.gray('[y/N] ')}`);
process.stdin
.on('data', d => {
process.stdin.pause();
resolve(
d
.toString()
.trim()
.toLowerCase() === 'y'
);
})
.resume();
});
}
export default promptBool;

View File

@@ -422,6 +422,19 @@ test('Deploy `api-env` fixture and test `vercel env` command', async t => {
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
}
async function createSecret() {
const name = `my-secret${Math.floor(Math.random() * 10000)}`;
const res = await apiFetch('/v2/now/secrets', {
method: 'POST',
body: JSON.stringify({ name, value: 'my secret' }),
});
t.is(res.status, 200);
return name;
}
async function nowEnvLsIsEmpty() {
const { exitCode, stderr, stdout } = await execa(
binaryPath,
@@ -436,26 +449,34 @@ test('Deploy `api-env` fixture and test `vercel env` command', async t => {
t.regex(stderr, /No Environment Variables found in Project/gm);
}
async function nowEnvAdd() {
async function nowEnvAddPlaintext() {
const now = execa(binaryPath, ['env', 'add', ...defaultArgs], {
reject: false,
cwd: target,
});
await waitForPrompt(now, chunk =>
chunk.includes('Which type of Environment Variable do you want to add?')
);
now.stdin.write('\n'); // select plaintext
await waitForPrompt(now, chunk =>
chunk.includes('Whats the name of the variable?')
);
now.stdin.write('MY_ENV_VAR\n');
now.stdin.write('MY_PLAINTEXT_ENV_VAR\n');
await waitForPrompt(
now,
chunk =>
chunk.includes('Whats the value of') && chunk.includes('MY_ENV_VAR')
chunk.includes('Whats the value of') &&
chunk.includes('MY_PLAINTEXT_ENV_VAR')
);
now.stdin.write('MY_VALUE\n');
now.stdin.write('my plaintext value\n');
await waitForPrompt(
now,
chunk =>
chunk.includes('which Environments') && chunk.includes('MY_ENV_VAR')
chunk.includes('which Environments') &&
chunk.includes('MY_PLAINTEXT_ENV_VAR')
);
now.stdin.write('a\n'); // select all
@@ -464,10 +485,47 @@ test('Deploy `api-env` fixture and test `vercel env` command', async t => {
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
}
async function nowEnvAddSecret(secretName) {
const now = execa(binaryPath, ['env', 'add', ...defaultArgs], {
reject: false,
cwd: target,
});
await waitForPrompt(now, chunk =>
chunk.includes('Which type of Environment Variable do you want to add?')
);
now.stdin.write('j\n'); // select secret
await waitForPrompt(now, chunk =>
chunk.includes('Whats the name of the variable?')
);
now.stdin.write('MY_SECRET_ENV_VAR\n');
await waitForPrompt(
now,
chunk =>
chunk.includes('Whats the value of') &&
chunk.includes('MY_SECRET_ENV_VAR')
);
now.stdin.write(`@${secretName}\n`);
await waitForPrompt(
now,
chunk =>
chunk.includes('which Environments') &&
chunk.includes('MY_SECRET_ENV_VAR')
);
now.stdin.write('j \n'); // select preview
const { exitCode, stderr, stdout } = await now;
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
}
async function nowEnvAddFromStdin() {
const now = execa(
binaryPath,
['env', 'add', 'MY_STDIN_VAR', 'development', ...defaultArgs],
['env', 'add', 'plain', 'MY_STDIN_VAR', 'development', ...defaultArgs],
{
reject: false,
cwd: target,
@@ -481,13 +539,20 @@ test('Deploy `api-env` fixture and test `vercel env` command', async t => {
async function nowEnvAddSystemEnv() {
const now = execa(
binaryPath,
['env', 'add', 'VERCEL_URL', ...defaultArgs],
['env', 'add', 'system', 'VERCEL_URL', ...defaultArgs],
{
reject: false,
cwd: target,
}
);
await waitForPrompt(
now,
chunk =>
chunk.includes('Whats the value of') && chunk.includes('VERCEL_URL')
);
now.stdin.write(`\n`); // select VERCEL_URL
await waitForPrompt(
now,
chunk =>
@@ -513,23 +578,28 @@ test('Deploy `api-env` fixture and test `vercel env` command', async t => {
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
t.regex(stderr, /Environment Variables found in Project/gm);
console.log(stdout);
const lines = stdout.split('\n');
const myEnvVars = lines.filter(line => line.includes('MY_ENV_VAR'));
t.is(myEnvVars.length, 3);
t.regex(myEnvVars.join('\n'), /development/gm);
t.regex(myEnvVars.join('\n'), /preview/gm);
t.regex(myEnvVars.join('\n'), /production/gm);
const plaintextEnvs = lines.filter(line =>
line.includes('MY_PLAINTEXT_ENV_VAR')
);
t.is(plaintextEnvs.length, 1);
t.regex(plaintextEnvs[0], /Production, Preview, Development/gm);
const myStdinVars = lines.filter(line => line.includes('MY_STDIN_VAR'));
t.is(myStdinVars.length, 1);
t.regex(myStdinVars.join('\n'), /development/gm);
const secretEnvs = lines.filter(line => line.includes('MY_SECRET_ENV_VAR'));
t.is(secretEnvs.length, 1);
t.regex(secretEnvs[0], /Preview/gm);
const vercelVars = lines.filter(line => line.includes('VERCEL_URL'));
t.is(vercelVars.length, 3);
t.regex(vercelVars.join('\n'), /development/gm);
t.regex(vercelVars.join('\n'), /preview/gm);
t.regex(vercelVars.join('\n'), /production/gm);
const stdinEnvs = lines.filter(line => line.includes('MY_STDIN_VAR'));
t.is(stdinEnvs.length, 1);
t.regex(stdinEnvs[0], /Development/gm);
const systemEnvs = lines.filter(line => line.includes('VERCEL_URL'));
t.is(systemEnvs.length, 1);
t.regex(systemEnvs[0], /VERCEL_URL/gm);
t.regex(systemEnvs[0], /Production, Preview, Development/gm);
}
async function nowEnvPull() {
@@ -549,7 +619,7 @@ test('Deploy `api-env` fixture and test `vercel env` command', async t => {
t.true(contents.startsWith('# Created by Vercel CLI\n'));
const lines = new Set(contents.split('\n'));
t.true(lines.has('MY_ENV_VAR="MY_VALUE"'));
t.true(lines.has('MY_PLAINTEXT_ENV_VAR="my plaintext value"'));
t.true(lines.has('MY_STDIN_VAR="{"expect":"quotes"}"'));
t.true(lines.has('VERCEL_URL=""'));
}
@@ -603,7 +673,8 @@ test('Deploy `api-env` fixture and test `vercel env` command', async t => {
const apiRes = await fetch(apiUrl);
t.is(apiRes.status, 200, formatOutput({ stderr, stdout }));
const apiJson = await apiRes.json();
t.is(apiJson['MY_ENV_VAR'], 'MY_VALUE');
t.is(apiJson['MY_PLAINTEXT_ENV_VAR'], 'my plaintext value');
t.is(apiJson['MY_SECRET_ENV_VAR'], 'my secret');
t.is(apiJson['VERCEL_URL'], host);
const homeUrl = `https://${host}`;
@@ -611,7 +682,8 @@ test('Deploy `api-env` fixture and test `vercel env` command', async t => {
const homeRes = await fetch(homeUrl);
t.is(homeRes.status, 200, formatOutput({ stderr, stdout }));
const homeJson = await homeRes.json();
t.is(homeJson['MY_ENV_VAR'], 'MY_VALUE');
t.is(homeJson['MY_PLAINTEXT_ENV_VAR'], 'my plaintext value');
t.is(homeJson['MY_SECRET_ENV_VAR'], 'my secret');
t.is(homeJson['VERCEL_URL'], host);
}
@@ -639,14 +711,14 @@ test('Deploy `api-env` fixture and test `vercel env` command', async t => {
const apiJson = await apiRes.json();
t.is(apiJson['MY_ENV_VAR'], 'MY_VALUE');
t.is(apiJson['MY_PLAINTEXT_ENV_VAR'], 'my plaintext value');
t.is(apiJson['VERCEL_URL'], localhostNoProtocol);
const homeUrl = localhost[0];
const homeRes = await fetch(homeUrl);
const homeJson = await homeRes.json();
t.is(homeJson['MY_ENV_VAR'], 'MY_VALUE');
t.is(homeJson['MY_PLAINTEXT_ENV_VAR'], 'my plaintext value');
t.is(homeJson['VERCEL_URL'], localhostNoProtocol);
vc.kill('SIGTERM', { forceKillAfterTimeout: 2000 });
@@ -680,13 +752,15 @@ test('Deploy `api-env` fixture and test `vercel env` command', async t => {
const apiJson = await apiRes.json();
t.is(apiJson['VERCEL_URL'], localhostNoProtocol);
t.is(apiJson['MY_ENV_VAR'], 'MY_VALUE');
t.is(apiJson['MY_PLAINTEXT_ENV_VAR'], 'my plaintext value');
t.is(apiJson['MY_STDIN_VAR'], '{"expect":"quotes"}');
const homeUrl = localhost[0];
const homeRes = await fetch(homeUrl);
const homeJson = await homeRes.json();
t.is(homeJson['MY_ENV_VAR'], 'MY_VALUE');
t.is(homeJson['MY_PLAINTEXT_ENV_VAR'], 'my plaintext value');
t.is(homeJson['VERCEL_URL'], localhostNoProtocol);
t.is(homeJson['MY_STDIN_VAR'], '{"expect":"quotes"}');
vc.kill('SIGTERM', { forceKillAfterTimeout: 2000 });
@@ -702,12 +776,27 @@ test('Deploy `api-env` fixture and test `vercel env` command', async t => {
await waitForPrompt(now, chunk =>
chunk.includes('Whats the name of the variable?')
);
now.stdin.write('MY_ENV_VAR\n');
now.stdin.write('MY_PLAINTEXT_ENV_VAR\n');
// expect error if no environment is selected
await waitForPrompt(
now,
chunk =>
chunk.includes('which Environments') &&
chunk.includes('MY_PLAINTEXT_ENV_VAR')
);
now.stdin.write('\n'); // select none
await waitForPrompt(now, chunk =>
chunk.includes(
'Please select an Environment to remove the Environment Variable from.'
)
);
await waitForPrompt(
now,
chunk =>
chunk.includes('which Environments') && chunk.includes('MY_ENV_VAR')
chunk.includes('which Environments') &&
chunk.includes('MY_PLAINTEXT_ENV_VAR')
);
now.stdin.write('a\n'); // select all
@@ -719,7 +808,7 @@ test('Deploy `api-env` fixture and test `vercel env` command', async t => {
async function nowEnvRemoveWithArgs() {
const { exitCode, stderr, stdout } = await execa(
binaryPath,
['env', 'rm', 'MY_STDIN_VAR', 'development', '-y', ...defaultArgs],
['env', 'rm', 'MY_SECRET_ENV_VAR', 'preview', '-y', ...defaultArgs],
{
reject: false,
cwd: target,
@@ -727,6 +816,21 @@ test('Deploy `api-env` fixture and test `vercel env` command', async t => {
);
t.is(exitCode, 0, formatOutput({ stderr, stdout }));
const {
exitCode: exitCode2,
stderr: stderr2,
stdout: stdout2,
} = await execa(
binaryPath,
['env', 'rm', 'MY_STDIN_VAR', 'development', '-y', ...defaultArgs],
{
reject: false,
cwd: target,
}
);
t.is(exitCode2, 0, formatOutput({ stderr2, stdout2 }));
}
async function nowEnvRemoveWithNameOnly() {
@@ -751,8 +855,10 @@ test('Deploy `api-env` fixture and test `vercel env` command', async t => {
}
await nowDeploy();
const secretName = await createSecret();
await nowEnvLsIsEmpty();
await nowEnvAdd();
await nowEnvAddPlaintext();
await nowEnvAddSecret(secretName);
await nowEnvAddFromStdin();
await nowEnvAddSystemEnv();
await nowEnvLsIncludesVar();

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/client",
"version": "9.0.4-canary.0",
"version": "9.0.4-canary.4",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"homepage": "https://vercel.com",
@@ -37,7 +37,7 @@
]
},
"dependencies": {
"@vercel/build-utils": "2.5.5-canary.0",
"@vercel/build-utils": "2.5.5-canary.3",
"@zeit/fetch": "5.2.0",
"async-retry": "1.2.3",
"async-sema": "3.0.0",

View File

@@ -136,6 +136,7 @@ export async function getVercelIgnore(
'__pycache__',
'venv',
'CVS',
'.vercel_build_output',
];
const cwds = Array.isArray(cwd) ? cwd : [cwd];

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/next",
"version": "2.6.32",
"version": "2.6.39-canary.1",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/next-js",

View File

@@ -214,7 +214,7 @@ function startDevServer(entryPath: string, runtimeEnv: EnvConfig) {
return { forked, getUrl };
}
export const build = async ({
export async function build({
files,
workPath,
repoRootPath,
@@ -231,7 +231,7 @@ export const build = async ({
}>;
watch?: string[];
childProcesses: ChildProcess[];
}> => {
}> {
validateEntrypoint(entrypoint);
// Limit for max size each lambda can be, 50 MB if no custom limit
@@ -352,7 +352,7 @@ export const build = async ({
'now-build',
'build',
]);
const { buildCommand } = config;
const { installCommand, buildCommand } = config;
if (!buildScriptName && !buildCommand) {
console.log(
@@ -376,8 +376,22 @@ export const build = async ({
await writeNpmRc(entryPath, process.env.NPM_AUTH_TOKEN);
}
console.log('Installing dependencies...');
await runNpmInstall(entryPath, [], spawnOpts, meta);
if (typeof installCommand === 'string') {
if (installCommand.trim()) {
console.log(`Running "install" command: \`${installCommand}\`...`);
await execCommand(installCommand, {
...spawnOpts,
cwd: entryPath,
});
} else {
console.log(`Skipping "install" command...`);
}
} else {
console.log('Installing dependencies...');
const installTime = Date.now();
await runNpmInstall(entryPath, [], spawnOpts, meta);
debug(`Install complete [${Date.now() - installTime}ms]`);
}
// Refetch Next version now that dependencies are installed.
// This will now resolve the actual installed Next version,
@@ -421,7 +435,7 @@ export const build = async ({
});
}
const appMountPrefixNoTrailingSlash = path.posix
let appMountPrefixNoTrailingSlash = path.posix
.join('/', entryDirectory)
.replace(/\/+$/, '');
@@ -470,6 +484,30 @@ export const build = async ({
headers.push(...convertHeaders(routesManifest.headers));
}
if (routesManifest.basePath && routesManifest.basePath !== '/') {
const nextBasePath = routesManifest.basePath;
if (!nextBasePath.startsWith('/')) {
throw new NowBuildError({
code: 'NEXT_BASEPATH_STARTING_SLASH',
message:
'basePath must start with `/`. Please upgrade your `@vercel/next` builder and try again. Contact support if this continues to happen.',
});
}
if (nextBasePath.endsWith('/')) {
throw new NowBuildError({
code: 'NEXT_BASEPATH_TRAILING_SLASH',
message:
'basePath must not end with `/`. Please upgrade your `@vercel/next` builder and try again. Contact support if this continues to happen.',
});
}
entryDirectory = path.join(entryDirectory, nextBasePath);
appMountPrefixNoTrailingSlash = path.posix
.join('/', entryDirectory)
.replace(/\/+$/, '');
}
if (routesManifest.dataRoutes) {
// Load the /_next/data routes for both dynamic SSG and SSP pages.
// These must be combined and sorted to prevent conflicts
@@ -511,6 +549,7 @@ export const build = async ({
const { i18n } = routesManifest;
if (i18n) {
const origSrc = route.src;
route.src = route.src.replace(
// we need to double escape the build ID here
// to replace it properly
@@ -522,6 +561,21 @@ export const build = async ({
.join('|')})/`
);
// optional-catchall routes don't have slash between
// build-id and the regex
if (route.src === origSrc) {
route.src = route.src.replace(
// we need to double escape the build ID here
// to replace it properly
`/${escapedBuildId}`,
`/${escapedBuildId}/(?${
ssgDataRoute ? '<nextLocale>' : ':'
}${i18n.locales
.map(locale => escapeStringRegexp(locale))
.join('|')})[/]?`
);
}
// make sure to route to the correct prerender output
if (ssgDataRoute) {
route.dest = route.dest.replace(
@@ -538,26 +592,6 @@ export const build = async ({
hasPages404 = true;
}
if (routesManifest.basePath && routesManifest.basePath !== '/') {
const nextBasePath = routesManifest.basePath;
if (!nextBasePath.startsWith('/')) {
throw new NowBuildError({
code: 'NEXT_BASEPATH_STARTING_SLASH',
message:
'basePath must start with `/`. Please upgrade your `@vercel/next` builder and try again. Contact support if this continues to happen.',
});
}
if (nextBasePath.endsWith('/')) {
throw new NowBuildError({
code: 'NEXT_BASEPATH_TRAILING_SLASH',
message:
'basePath must not end with `/`. Please upgrade your `@vercel/next` builder and try again. Contact support if this continues to happen.',
});
}
entryDirectory = path.join(entryDirectory, nextBasePath);
}
break;
}
default: {
@@ -712,7 +746,7 @@ export const build = async ({
src: path.join(
'/',
entryDirectory,
'_next/static/(?:[^/]+/pages|pages|chunks|runtime|css|media)/.+'
`_next/static/(?:[^/]+/pages|pages|chunks|runtime|css|media|${escapedBuildId})/.+`
),
// Next.js assets contain a hash or entropy in their filenames, so they
// are guaranteed to be unique and cacheable indefinitely.
@@ -880,7 +914,9 @@ export const build = async ({
// Next.js versions so we need to also not treat it as a static page here.
if (
prerenderManifest.staticRoutes[routeName] ||
prerenderManifest.fallbackRoutes[routeName]
prerenderManifest.fallbackRoutes[routeName] ||
prerenderManifest.staticRoutes[normalizePage(pathname)] ||
prerenderManifest.fallbackRoutes[normalizePage(pathname)]
) {
return;
}
@@ -1391,17 +1427,25 @@ export const build = async ({
if (i18n) {
const { pathname } = url.parse(route.dest!);
const isFallback = prerenderManifest.fallbackRoutes[pathname!];
const isBlocking =
prerenderManifest.blockingFallbackRoutes[pathname!];
const isAutoExport =
staticPages[
addLocaleOrDefault(pathname!, routesManifest).substr(1)
];
const isLocalePrefixed = isFallback || isBlocking || isAutoExport;
route.src = route.src.replace(
'^',
`^${dynamicPrefix ? `${dynamicPrefix}[/]?` : '[/]?'}(?${
isFallback ? '<nextLocale>' : ':'
isLocalePrefixed ? '<nextLocale>' : ':'
}${i18n.locales
.map(locale => escapeStringRegexp(locale))
.join('|')})?`
);
if (isFallback) {
if (isLocalePrefixed) {
// ensure destination has locale prefix to match prerender output
// path so that the prerender object is used
route.dest = route.dest!.replace(
@@ -1457,7 +1501,7 @@ export const build = async ({
)}).some((locale) => {
if (pathnameParts[1].toLowerCase() === locale.toLowerCase()) {
pathnameParts.splice(1, 1)
pathname = pathnameParts.join('/') || '/'
pathname = pathnameParts.join('/') || '/index'
return true
}
return false
@@ -1493,7 +1537,7 @@ export const build = async ({
if (!toRender) {
try {
const { pathname } = url.parse(req.url)
toRender = stripLocalePath(pathname).replace(/\\/$/, '')
toRender = stripLocalePath(pathname).replace(/\\/$/, '') || '/index'
} catch (_) {
// handle failing to parse url
res.statusCode = 400
@@ -1512,7 +1556,7 @@ export const build = async ({
.replace(new RegExp('/_next/data/${escapedBuildId}/'), '/')
.replace(/\\.json$/, '')
toRender = stripLocalePath(toRender)
toRender = stripLocalePath(toRender) || '/index'
currentPage = pages[toRender]
}
@@ -1540,8 +1584,9 @@ export const build = async ({
if (!currentPage) {
console.error(
"Failed to find matching page for", toRender, "in lambda"
"Failed to find matching page for", {toRender, header: req.headers['x-nextjs-page'], url: req.url }, "in lambda"
)
console.error('pages in lambda', Object.keys(pages))
res.statusCode = 500
return res.end('internal server error')
}
@@ -1725,7 +1770,10 @@ export const build = async ({
outputPathData = outputPathData.replace(
new RegExp(`${escapeStringRegexp(origRouteFileNoExt)}.json$`),
`${routeFileNoExt}${
origRouteFileNoExt === '/index' ? '/index' : ''
routeFileNoExt !== origRouteFileNoExt &&
origRouteFileNoExt === '/index'
? '/index'
: ''
}.json`
);
}
@@ -1791,6 +1839,44 @@ export const build = async ({
});
++prerenderGroup;
if (routesManifest?.i18n && isBlocking) {
for (const locale of routesManifest.i18n.locales) {
const localeRouteFileNoExt = addLocaleOrDefault(
routeFileNoExt,
routesManifest,
locale
);
const localeOutputPathPage = path.posix.join(
entryDirectory,
localeRouteFileNoExt
);
const localeOutputPathData = outputPathData.replace(
new RegExp(`${escapeStringRegexp(origRouteFileNoExt)}.json$`),
`${localeRouteFileNoExt}${
localeRouteFileNoExt !== origRouteFileNoExt &&
origRouteFileNoExt === '/index'
? '/index'
: ''
}.json`
);
const origPrerenderPage = prerenders[outputPathPage];
const origPrerenderData = prerenders[outputPathData];
prerenders[localeOutputPathPage] = {
...origPrerenderPage,
group: prerenderGroup,
} as Prerender;
prerenders[localeOutputPathData] = {
...origPrerenderData,
group: prerenderGroup,
} as Prerender;
++prerenderGroup;
}
}
}
if ((nonDynamicSsg || isFallback) && routesManifest?.i18n && !locale) {
@@ -2018,7 +2104,7 @@ export const build = async ({
},
// Handle redirecting to locale specific domains
...(i18n.domains
...(i18n.domains && i18n.localeDetection !== false
? [
{
// TODO: enable redirecting between domains, will require
@@ -2055,26 +2141,29 @@ export const build = async ({
: []),
// Handle redirecting to locale paths
{
// TODO: enable redirecting between paths, will require
// updating the src with the desired locales to redirect.
// if default locale is included in this src it won't be visitable
// by users who prefer another language since the cookie isn't set
// on redirect currently like in `next start`
src: '/',
locale: {
redirect: i18n.locales.reduce(
(prev: Record<string, string>, locale) => {
prev[locale] =
locale === i18n.defaultLocale ? `/` : `/${locale}`;
return prev;
...(i18n.localeDetection !== false
? [
{
// TODO: if default locale is included in this src it won't be
// visitable by users who prefer another language since a
// cookie isn't set signaling the default locale is preferred
// on redirect currently, investigate adding this
src: '/',
locale: {
redirect: i18n.locales.reduce(
(prev: Record<string, string>, locale) => {
prev[locale] =
locale === i18n.defaultLocale ? `/` : `/${locale}`;
return prev;
},
{}
),
cookie: 'NEXT_LOCALE',
},
continue: true,
},
{}
),
cookie: 'NEXT_LOCALE',
},
continue: true,
},
]
: []),
{
src: `^${path.join('/', entryDirectory)}$`,
@@ -2083,6 +2172,10 @@ export const build = async ({
},
// Auto-prefix non-locale path with default locale
// note for prerendered pages this will cause
// x-now-route-matches to contain the path minus the locale
// e.g. for /de/posts/[slug] x-now-route-matches would have
// 1=posts%2Fpost-1
{
src: `^${path.join(
'/',
@@ -2125,10 +2218,22 @@ export const build = async ({
{ handle: 'filesystem' },
// map pages to their lambda
...pageLambdaRoutes,
...pageLambdaRoutes.filter(route => {
// filter out any SSG pages as they are already present in output
if ('headers' in route) {
let page = route.headers?.['x-nextjs-page']!;
page = page === '/index' ? '/' : page;
// map /blog/[post] to correct lambda for iSSG
...dynamicPageLambdaRoutes,
if (
prerenderManifest.staticRoutes[page] ||
prerenderManifest.fallbackRoutes[page] ||
prerenderManifest.blockingFallbackRoutes[page]
) {
return false;
}
}
return true;
}),
// These need to come before handle: miss or else they are grouped
// with that routing section
@@ -2205,7 +2310,7 @@ export const build = async ({
src: path.join(
'/',
entryDirectory,
'_next/static/(?:[^/]+/pages|pages|chunks|runtime|css|media)/.+'
`_next/static/(?:[^/]+/pages|pages|chunks|runtime|css|media|${escapedBuildId})/.+`
),
// Next.js assets contain a hash or entropy in their filenames, so they
// are guaranteed to be unique and cacheable indefinitely.
@@ -2281,7 +2386,7 @@ export const build = async ({
watch: [],
childProcesses: [],
};
};
}
export const prepareCache = async ({
workPath,

View File

@@ -327,6 +327,7 @@ export type RoutesManifest = {
routeKeys?: { [named: string]: string };
}>;
i18n?: {
localeDetection?: boolean;
defaultLocale: string;
locales: string[];
domains?: Array<{

View File

@@ -0,0 +1,22 @@
module.exports = {
generateBuildId() {
return 'testing-build-id';
},
i18n: {
localeDetection: false,
locales: ['nl-NL', 'nl-BE', 'nl', 'fr-BE', 'fr', 'en-US', 'en'],
defaultLocale: 'en-US',
// TODO: testing locale domains support, will require custom
// testing set-up as test accounts are used currently
domains: [
{
domain: 'example.be',
defaultLocale: 'nl-BE',
},
{
domain: 'example.fr',
defaultLocale: 'fr',
},
],
},
};

View File

@@ -0,0 +1,517 @@
{
"version": 2,
"builds": [
{
"src": "package.json",
"use": "@vercel/next"
}
],
"probes": [
{
"path": "/",
"headers": {
"accept-language": "en;q=0.9"
},
"fetchOptions": {
"redirect": "manual"
},
"status": 200,
"mustContain": "\"en-US\""
},
{
"path": "/",
"headers": {
"accept-language": "nl;q=0.9"
},
"fetchOptions": {
"redirect": "manual"
},
"status": 200,
"mustContain": "\"en-US\""
},
{
"path": "/",
"headers": {
"accept-language": "nl-NL;q=0.9"
},
"fetchOptions": {
"redirect": "manual"
},
"status": 200,
"mustContain": "\"en-US\""
},
{
"path": "/",
"headers": {
"accept-language": "fr;q=0.9"
},
"fetchOptions": {
"redirect": "manual"
},
"status": 200,
"mustContain": "\"en-US\""
},
{
"path": "/",
"headers": {
"accept-language": "en-US;q=0.9"
},
"fetchOptions": {
"redirect": "manual"
},
"status": 200,
"mustContain": "index page"
},
{
"path": "/en-US",
"headers": {
"accept-language": "nl;q=0.9"
},
"fetchOptions": {
"redirect": "manual"
},
"status": 200,
"mustContain": "index page"
},
{
"path": "/",
"status": 200,
"mustContain": "index page"
},
{
"path": "/",
"status": 200,
"mustContain": ">en-US<"
},
{
"path": "/en",
"status": 200,
"mustContain": "index page"
},
{
"path": "/en",
"status": 200,
"mustContain": ">en<"
},
{
"path": "/fr",
"status": 200,
"mustContain": "index page"
},
{
"path": "/fr",
"status": 200,
"mustContain": ">fr<"
},
{
"path": "/nl",
"status": 200,
"mustContain": "index page"
},
{
"path": "/nl",
"status": 200,
"mustContain": ">nl<"
},
{
"path": "/nl-NL",
"status": 200,
"mustContain": "index page"
},
{
"path": "/nl-NL",
"status": 200,
"mustContain": ">nl-NL<"
},
{
"path": "/non-existent",
"status": 404
},
{
"path": "/fr/non-existent",
"status": 404,
"mustContain": "lang=\"fr\""
},
{
"path": "/en/non-existent",
"status": 404,
"mustContain": "lang=\"en\""
},
{
"path": "/en-US/non-existent",
"status": 404,
"mustContain": "lang=\"en-US\""
},
{
"path": "/nl/non-existent",
"status": 404,
"mustContain": "lang=\"nl\""
},
{
"path": "/nl-NL/non-existent",
"status": 404,
"mustContain": "lang=\"nl-NL\""
},
{
"path": "/hello.txt",
"status": 200,
"mustContain": "hello world!"
},
{
"path": "/dynamic/hello",
"status": 200,
"mustContain": "dynamic page"
},
{
"path": "/dynamic/hello",
"status": 200,
"mustContain": "\"en-US\""
},
{
"path": "/en/dynamic/hello",
"status": 200,
"mustContain": "dynamic page"
},
{
"path": "/en/dynamic/hello",
"status": 200,
"mustContain": "\"en\""
},
{
"path": "/nl/dynamic/hello",
"status": 200,
"mustContain": "dynamic page"
},
{
"path": "/nl/dynamic/hello",
"status": 200,
"mustContain": "\"nl\""
},
{
"path": "/fr/dynamic/hello",
"status": 200,
"mustContain": "dynamic page"
},
{
"path": "/fr/dynamic/hello",
"status": 200,
"mustContain": "\"fr\""
},
{
"path": "/gsp",
"status": 200,
"mustContain": "gsp page"
},
{
"path": "/gsp",
"status": 200,
"mustContain": ">en-US<"
},
{
"path": "/en/gsp",
"status": 200,
"mustContain": "gsp page"
},
{
"path": "/en/gsp",
"status": 200,
"mustContain": ">en<"
},
{
"path": "/nl/gsp",
"status": 200,
"mustContain": "gsp page"
},
{
"path": "/nl/gsp",
"status": 200,
"mustContain": ">nl<"
},
{
"path": "/fr/gsp",
"status": 200,
"mustContain": "gsp page"
},
{
"path": "/fr/gsp",
"status": 200,
"mustContain": ">fr<"
},
{
"path": "/gssp",
"status": 200,
"mustContain": "gssp page"
},
{
"path": "/gssp",
"status": 200,
"mustContain": ">en-US<"
},
{
"path": "/en/gssp",
"status": 200,
"mustContain": "gssp page"
},
{
"path": "/en/gssp",
"status": 200,
"mustContain": ">en<"
},
{
"path": "/nl/gssp",
"status": 200,
"mustContain": "gssp page"
},
{
"path": "/nl/gssp",
"status": 200,
"mustContain": ">nl<"
},
{
"path": "/fr/gssp",
"status": 200,
"mustContain": "gssp page"
},
{
"path": "/fr/gssp",
"status": 200,
"mustContain": ">fr<"
},
{
"path": "/gssp/first",
"status": 200,
"mustContain": "gssp page"
},
{
"path": "/gssp/first",
"status": 200,
"mustContain": ">en-US<"
},
{
"path": "/gssp/first",
"status": 200,
"mustContain": "slug\":\"first\""
},
{
"path": "/en/gssp/first",
"status": 200,
"mustContain": "gssp page"
},
{
"path": "/en/gssp/first",
"status": 200,
"mustContain": ">en<"
},
{
"path": "/en/gssp/first",
"status": 200,
"mustContain": "slug\":\"first\""
},
{
"path": "/nl/gssp/first",
"status": 200,
"mustContain": "gssp page"
},
{
"path": "/nl/gssp/first",
"status": 200,
"mustContain": ">nl<"
},
{
"path": "/nl/gssp/first",
"status": 200,
"mustContain": "slug\":\"first\""
},
{
"path": "/fr/gssp/first",
"status": 200,
"mustContain": "gssp page"
},
{
"path": "/fr/gssp/first",
"status": 200,
"mustContain": ">fr<"
},
{
"path": "/fr/gssp/first",
"status": 200,
"mustContain": "slug\":\"first\""
},
// TODO: update when directory listing is disabled
// and these are proper 404s
{
"path": "/en/not-found",
"status": 200,
"mustContain": "Index of"
},
{
"path": "/nl/not-found",
"status": 200,
"mustContain": "Index of"
},
{
"path": "/en-US/not-found",
"status": 200,
"mustContain": "lang=\"en-US\""
},
{
"path": "/nl-NL/not-found",
"status": 200,
"mustContain": "lang=\"nl-NL\""
},
{
"path": "/fr/not-found",
"status": 200,
"mustContain": "lang=\"fr\""
},
// this will always be a 200 unless fallback: blocking is used
// since the static fallback page is served before the 404
// page is rendered
{
"path": "/en/not-found/fallback/first",
"status": 200,
"mustContain": "lang=\"en\""
},
{
"delay": 2000
},
{
"path": "/en/not-found/fallback/first",
"status": 200,
"mustNotContain": "gsp page"
},
{
"path": "/_next/data/testing-build-id/en/not-found/fallback/first.json",
"status": 404
},
{
"path": "/en/not-found/fallback/first",
"status": 200,
"mustContain": "lang=\"en\""
},
{
"path": "/en/not-found/fallback/first",
"status": 200,
"mustNotContain": "gsp page"
},
{
"path": "/fr/not-found/fallback/first",
"status": 200,
"mustContain": "lang=\"fr\""
},
{
"path": "/_next/data/testing-build-id/fr/not-found/fallback/first.json",
"status": 200
},
{
"path": "/fr/not-found/fallback/first",
"status": 200,
"mustContain": "lang=\"fr\""
},
{
"delay": 2000
},
{
"path": "/fr/not-found/fallback/first",
"status": 200,
"mustContain": "gsp page"
},
{
"path": "/_next/data/testing-build-id/en-US/index.json",
"status": 200,
"mustContain": "\"locale\":\"en-US\""
},
{
"path": "/_next/data/testing-build-id/en/index.json",
"status": 200,
"mustContain": "\"locale\":\"en\""
},
{
"path": "/_next/data/testing-build-id/fr/index.json",
"status": 200,
"mustContain": "\"locale\":\"fr\""
},
{
"path": "/_next/data/testing-build-id/nl/index.json",
"status": 200,
"mustContain": "\"locale\":\"nl\""
},
{
"path": "/_next/data/testing-build-id/en-US/gsp.json",
"status": 200,
"mustContain": "\"locale\":\"en-US\""
},
{
"path": "/_next/data/testing-build-id/en/gsp.json",
"status": 200,
"mustContain": "\"locale\":\"en\""
},
{
"path": "/_next/data/testing-build-id/fr/gsp.json",
"status": 200,
"mustContain": "\"locale\":\"fr\""
},
{
"path": "/_next/data/testing-build-id/nl/gsp.json",
"status": 200,
"mustContain": "\"locale\":\"nl\""
},
{
"path": "/gsp/blocking/first",
"status": 200,
"mustContain": "catchall"
},
{
"path": "/gsp/blocking/first",
"status": 200,
"mustContain": "lang=\"en-US\""
},
{
"path": "/_next/data/testing-build-id/en-US/gsp/blocking/first.json",
"status": 200,
"mustContain": "\"catchall\":\"yes\""
},
{
"path": "/nl-NL/gsp/blocking/first",
"status": 200,
"mustContain": "catchall"
},
{
"path": "/nl-NL/gsp/blocking/first",
"status": 200,
"mustContain": "lang=\"nl-NL\""
},
{
"path": "/_next/data/testing-build-id/nl-NL/gsp/blocking/first.json",
"status": 200,
"mustContain": "\"catchall\":\"yes\""
},
{
"path": "/fr/gsp/blocking/first",
"status": 200,
"mustContain": "catchall"
},
{
"path": "/fr/gsp/blocking/first",
"status": 200,
"mustContain": "lang=\"fr\""
},
{
"path": "/_next/data/testing-build-id/fr/gsp/blocking/first.json",
"status": 200,
"mustContain": "\"catchall\":\"yes\""
}
]
}

View File

@@ -0,0 +1,7 @@
{
"dependencies": {
"next": "canary",
"react": "^16.8.6",
"react-dom": "^16.8.6"
}
}

View File

@@ -0,0 +1,31 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
export default function Page(props) {
const router = useRouter();
return (
<>
<p id="another">another page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
<p id="router-as-path">{router.asPath}</p>
<Link href="/">
<a id="to-index">to /</a>
</Link>
<br />
</>
);
}
export const getServerSideProps = ({ locale, locales }) => {
return {
props: {
locale,
locales,
},
};
};

View File

@@ -0,0 +1,21 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
export default function Page(props) {
const router = useRouter();
return (
<>
<p id="auto-export">auto-export page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
<p id="router-as-path">{router.asPath}</p>
<Link href="/">
<a id="to-index">to /</a>
</Link>
</>
);
}

View File

@@ -0,0 +1,12 @@
import { useRouter } from 'next/router';
export default function Dynamic(props) {
const router = useRouter();
return (
<>
<p>dynamic page</p>
<p id="query">{JSON.stringify(router.query)}</p>
</>
);
}

View File

@@ -0,0 +1,43 @@
import Link from 'next/link';
const Slug = props => {
return (
<div>
<p id="props">{JSON.stringify(props)}</p>
<Link href="/gsp/blocking/hallo-wereld" locale={'nl-NL'}>
<a>/nl-NL/gsp/blocking/hallo-wereld</a>
</Link>
<br />
<Link href="/gsp/blocking/42" locale={'nl-NL'}>
<a>/nl-NL/gsp/blocking/42</a>
</Link>
<br />
<Link href="/gsp/blocking/hallo-welt" locale={'fr'}>
<a>/fr/gsp/blocking/hallo-welt</a>
</Link>
<br />
<Link href="/gsp/blocking/42" locale={'fr'}>
<a>/fr/gsp/blocking/42</a>
</Link>
</div>
);
};
export const getStaticProps = () => {
return {
props: {
random: Math.random(),
catchall: 'yes',
},
revalidate: 1,
};
};
export const getStaticPaths = () => {
return {
paths: [],
fallback: 'blocking',
};
};
export default Slug;

View File

@@ -0,0 +1,51 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
export default function Page(props) {
const router = useRouter();
if (router.isFallback) return 'Loading...';
return (
<>
<p id="gsp">gsp page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
<p id="router-as-path">{router.asPath}</p>
<Link href="/">
<a id="to-index">to /</a>
</Link>
<br />
</>
);
}
export const getStaticProps = ({ params, locale, locales }) => {
return {
props: {
random: Math.random(),
params,
locale,
locales,
},
revalidate: 1,
};
};
export const getStaticPaths = ({ locales }) => {
const paths = [];
for (const locale of locales) {
paths.push({ params: { slug: 'first' }, locale });
paths.push({ params: { slug: 'second' }, locale });
}
return {
// the default locale will be used since one isn't defined here
paths,
fallback: true,
};
};

View File

@@ -0,0 +1,32 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
export default function Page(props) {
const router = useRouter();
return (
<>
<p id="gsp">gsp page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
<p id="router-as-path">{router.asPath}</p>
<Link href="/">
<a id="to-index">to /</a>
</Link>
<br />
</>
);
}
// TODO: should non-dynamic GSP pages pre-render for each locale?
export const getStaticProps = ({ locale, locales }) => {
return {
props: {
locale,
locales,
},
};
};

View File

@@ -0,0 +1,49 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
export default function Page(props) {
const router = useRouter();
if (router.isFallback) return 'Loading...';
return (
<>
<p id="gsp">gsp page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
<p id="router-as-path">{router.asPath}</p>
<Link href="/">
<a id="to-index">to /</a>
</Link>
<br />
</>
);
}
export const getStaticProps = ({ params, locale, locales }) => {
return {
props: {
random: Math.random(),
params,
locale,
locales,
},
revalidate: 1,
};
};
export const getStaticPaths = () => {
return {
paths: [
{ params: { slug: 'first' } },
'/gsp/no-fallback/second',
{ params: { slug: 'first' }, locale: 'en-US' },
'/nl-NL/gsp/no-fallback/second',
'/fr/gsp/no-fallback/first',
],
fallback: false,
};
};

View File

@@ -0,0 +1,32 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
export default function Page(props) {
const router = useRouter();
return (
<>
<p id="gssp">gssp page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
<p id="router-as-path">{router.asPath}</p>
<Link href="/">
<a id="to-index">to /</a>
</Link>
<br />
</>
);
}
export const getServerSideProps = ({ params, locale, locales }) => {
return {
props: {
params,
locale,
locales,
},
};
};

View File

@@ -0,0 +1,31 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
export default function Page(props) {
const router = useRouter();
return (
<>
<p id="gssp">gssp page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
<p id="router-as-path">{router.asPath}</p>
<Link href="/">
<a id="to-index">to /</a>
</Link>
<br />
</>
);
}
export const getServerSideProps = ({ locale, locales }) => {
return {
props: {
locale,
locales,
},
};
};

View File

@@ -0,0 +1,57 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
export default function Page(props) {
const router = useRouter();
return (
<>
<p id="index">index page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
<p id="router-as-path">{router.asPath}</p>
<Link href="/another">
<a id="to-another">to /another</a>
</Link>
<br />
<Link href="/gsp">
<a id="to-gsp">to /gsp</a>
</Link>
<br />
<Link href="/gsp/fallback/first">
<a id="to-fallback-first">to /gsp/fallback/first</a>
</Link>
<br />
<Link href="/gsp/fallback/hello">
<a id="to-fallback-hello">to /gsp/fallback/hello</a>
</Link>
<br />
<Link href="/gsp/no-fallback/first">
<a id="to-no-fallback-first">to /gsp/no-fallback/first</a>
</Link>
<br />
<Link href="/gssp">
<a id="to-gssp">to /gssp</a>
</Link>
<br />
<Link href="/gssp/first">
<a id="to-gssp-slug">to /gssp/first</a>
</Link>
<br />
</>
);
}
export const getStaticProps = ({ locale, locales }) => {
return {
props: {
random: Math.random(),
locale,
locales,
},
revalidate: 1,
};
};

View File

@@ -0,0 +1,54 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
export default function Page(props) {
const router = useRouter();
const { nextLocale } = router.query;
return (
<>
<p id="links">links page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
<p id="router-as-path">{router.asPath}</p>
<Link href="/another" locale={nextLocale}>
<a id="to-another">to /another</a>
</Link>
<br />
<Link href="/gsp" locale={nextLocale}>
<a id="to-gsp">to /gsp</a>
</Link>
<br />
<Link href="/gsp/fallback/first" locale={nextLocale}>
<a id="to-fallback-first">to /gsp/fallback/first</a>
</Link>
<br />
<Link href="/gsp/fallback/hello" locale={nextLocale}>
<a id="to-fallback-hello">to /gsp/fallback/hello</a>
</Link>
<br />
<Link href="/gsp/no-fallback/first" locale={nextLocale}>
<a id="to-no-fallback-first">to /gsp/no-fallback/first</a>
</Link>
<br />
<Link href="/gssp" locale={nextLocale}>
<a id="to-gssp">to /gssp</a>
</Link>
<br />
<Link href="/gssp/first" locale={nextLocale}>
<a id="to-gssp-slug">to /gssp/first</a>
</Link>
<br />
</>
);
}
// make SSR page so we have query values immediately
export const getServerSideProps = () => {
return {
props: {},
};
};

View File

@@ -0,0 +1,50 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
export default function Page(props) {
const router = useRouter();
if (router.isFallback) return 'Loading...';
return (
<>
<p id="gsp">gsp page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
<p id="router-as-path">{router.asPath}</p>
<Link href="/">
<a id="to-index">to /</a>
</Link>
<br />
</>
);
}
export const getStaticProps = ({ params, locale, locales }) => {
if (locale === 'en' || locale === 'nl') {
return {
notFound: true,
};
}
return {
props: {
params,
locale,
locales,
},
};
};
export const getStaticPaths = () => {
return {
// the default locale will be used since one isn't defined here
paths: ['first', 'second'].map(slug => ({
params: { slug },
})),
fallback: true,
};
};

View File

@@ -0,0 +1,37 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
export default function Page(props) {
const router = useRouter();
return (
<>
<p id="gsp">gsp page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
<p id="router-as-path">{router.asPath}</p>
<Link href="/">
<a id="to-index">to /</a>
</Link>
<br />
</>
);
}
export const getStaticProps = ({ locale, locales }) => {
if (locale === 'en' || locale === 'nl') {
return {
notFound: true,
};
}
return {
props: {
locale,
locales,
},
};
};

View File

@@ -0,0 +1 @@
hello world!

View File

@@ -2,22 +2,20 @@ module.exports = {
generateBuildId() {
return 'testing-build-id';
},
experimental: {
i18n: {
locales: ['nl-NL', 'nl-BE', 'nl', 'fr-BE', 'fr', 'en-US', 'en'],
defaultLocale: 'en-US',
// TODO: testing locale domains support, will require custom
// testing set-up as test accounts are used currently
domains: [
{
domain: 'example.be',
defaultLocale: 'nl-BE',
},
{
domain: 'example.fr',
defaultLocale: 'fr',
},
],
},
i18n: {
locales: ['nl-NL', 'nl-BE', 'nl', 'fr-BE', 'fr', 'en-US', 'en'],
defaultLocale: 'en-US',
// TODO: testing locale domains support, will require custom
// testing set-up as test accounts are used currently
domains: [
{
domain: 'example.be',
defaultLocale: 'nl-BE',
},
{
domain: 'example.fr',
defaultLocale: 'fr',
},
],
},
};

View File

@@ -172,6 +172,47 @@
"mustContain": "hello world!"
},
{
"path": "/dynamic/hello",
"status": 200,
"mustContain": "dynamic page"
},
{
"path": "/dynamic/hello",
"status": 200,
"mustContain": "\"en-US\""
},
{
"path": "/en/dynamic/hello",
"status": 200,
"mustContain": "dynamic page"
},
{
"path": "/en/dynamic/hello",
"status": 200,
"mustContain": "\"en\""
},
{
"path": "/nl/dynamic/hello",
"status": 200,
"mustContain": "dynamic page"
},
{
"path": "/nl/dynamic/hello",
"status": 200,
"mustContain": "\"nl\""
},
{
"path": "/fr/dynamic/hello",
"status": 200,
"mustContain": "dynamic page"
},
{
"path": "/fr/dynamic/hello",
"status": 200,
"mustContain": "\"fr\""
},
{
"path": "/gsp",
"status": 200,

View File

@@ -0,0 +1,12 @@
import { useRouter } from 'next/router';
export default function Dynamic(props) {
const router = useRouter();
return (
<>
<p>dynamic page</p>
<p id="query">{JSON.stringify(router.query)}</p>
</>
);
}

View File

@@ -0,0 +1,182 @@
/* eslint-env jest */
const fetch = require('node-fetch');
const cheerio = require('cheerio');
module.exports = function (ctx) {
it('should revalidate content properly from /', async () => {
// we have to hit the _next/data URL first
const dataRes = await fetch(
`${ctx.deploymentUrl}/_next/data/testing-build-id/en-US.json`
);
expect(dataRes.status).toBe(200);
await dataRes.json();
const res = await fetch(`${ctx.deploymentUrl}/`);
expect(res.status).toBe(200);
let $ = cheerio.load(await res.text());
const props = JSON.parse($('#props').text());
const initialRandom = props.random;
expect($('#router-locale').text()).toBe('en-US');
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 2000));
const res2 = await fetch(`${ctx.deploymentUrl}/`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('en-US');
});
it('should revalidate content properly from /fr', async () => {
// we have to hit the _next/data URL first
const dataRes = await fetch(
`${ctx.deploymentUrl}/_next/data/testing-build-id/fr.json`
);
expect(dataRes.status).toBe(200);
await dataRes.json();
const res = await fetch(`${ctx.deploymentUrl}/fr`);
expect(res.status).toBe(200);
let $ = cheerio.load(await res.text());
const props = JSON.parse($('#props').text());
const initialRandom = props.random;
expect($('#router-locale').text()).toBe('fr');
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 2000));
const res2 = await fetch(`${ctx.deploymentUrl}/fr`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('fr');
});
it('should revalidate content properly from /nl-NL', async () => {
// we have to hit the _next/data URL first
const dataRes = await fetch(
`${ctx.deploymentUrl}/_next/data/testing-build-id/nl-NL/index.json`
);
expect(dataRes.status).toBe(200);
await dataRes.json();
const res = await fetch(`${ctx.deploymentUrl}/nl-NL`);
expect(res.status).toBe(200);
let $ = cheerio.load(await res.text());
const props = JSON.parse($('#props').text());
const initialRandom = props.random;
expect($('#router-locale').text()).toBe('nl-NL');
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 2000));
const res2 = await fetch(`${ctx.deploymentUrl}/nl-NL`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('nl-NL');
});
it('should revalidate content properly from /second', async () => {
// we have to hit the _next/data URL first
const dataRes = await fetch(
`${ctx.deploymentUrl}/_next/data/testing-build-id/en-US/second.json`
);
expect(dataRes.status).toBe(200);
await dataRes.json();
await new Promise(resolve => setTimeout(resolve, 2000));
const res = await fetch(`${ctx.deploymentUrl}/second`);
expect(res.status).toBe(200);
const html = await res.text();
let $ = cheerio.load(html);
const props = JSON.parse($('#props').text());
const initialRandom = props.random;
expect($('#router-locale').text()).toBe('en-US');
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 2000));
const res2 = await fetch(`${ctx.deploymentUrl}/second`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('en-US');
});
it('should revalidate content properly from /fr/second', async () => {
// we have to hit the _next/data URL first
const dataRes = await fetch(
`${ctx.deploymentUrl}/_next/data/testing-build-id/fr/second.json`
);
expect(dataRes.status).toBe(200);
await dataRes.json();
await new Promise(resolve => setTimeout(resolve, 2000));
const res = await fetch(`${ctx.deploymentUrl}/fr/second`);
expect(res.status).toBe(200);
const html = await res.text();
let $ = cheerio.load(html);
const props = JSON.parse($('#props').text());
const initialRandom = props.random;
expect($('#router-locale').text()).toBe('fr');
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 2000));
const res2 = await fetch(`${ctx.deploymentUrl}/fr/second`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('fr');
});
it('should revalidate content properly from /nl-NL/second', async () => {
// we have to hit the _next/data URL first
const dataRes = await fetch(
`${ctx.deploymentUrl}/_next/data/testing-build-id/nl-NL/second.json`
);
expect(dataRes.status).toBe(200);
await dataRes.json();
await new Promise(resolve => setTimeout(resolve, 2000));
const res = await fetch(`${ctx.deploymentUrl}/nl-NL/second`);
expect(res.status).toBe(200);
const html = await res.text();
let $ = cheerio.load(html);
const props = JSON.parse($('#props').text());
const initialRandom = props.random;
expect($('#router-locale').text()).toBe('nl-NL');
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 2000));
const res2 = await fetch(`${ctx.deploymentUrl}/nl-NL/second`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('nl-NL');
});
};

View File

@@ -0,0 +1,21 @@
module.exports = {
generateBuildId() {
return 'testing-build-id';
},
i18n: {
locales: ['nl-NL', 'nl-BE', 'nl', 'fr-BE', 'fr', 'en-US', 'en'],
defaultLocale: 'en-US',
// TODO: testing locale domains support, will require custom
// testing set-up as test accounts are used currently
domains: [
{
domain: 'example.be',
defaultLocale: 'nl-BE',
},
{
domain: 'example.fr',
defaultLocale: 'fr',
},
],
},
};

View File

@@ -0,0 +1,192 @@
{
"version": 2,
"builds": [
{
"src": "package.json",
"use": "@vercel/next"
}
],
"probes": [
{
"path": "/hello.txt",
"status": 200,
"mustContain": "hello world!"
},
{
"path": "/",
"headers": {
"accept-language": "en;q=0.9"
},
"fetchOptions": {
"redirect": "manual"
},
"status": 307,
"responseHeaders": {
"location": "//en/"
}
},
{
"path": "/",
"headers": {
"accept-language": "nl;q=0.9"
},
"fetchOptions": {
"redirect": "manual"
},
"status": 307,
"responseHeaders": {
"location": "//nl/"
}
},
{
"path": "/",
"headers": {
"accept-language": "nl-NL;q=0.9"
},
"fetchOptions": {
"redirect": "manual"
},
"status": 307,
"responseHeaders": {
"location": "//nl-NL/"
}
},
{
"path": "/",
"headers": {
"accept-language": "fr;q=0.9"
},
"fetchOptions": {
"redirect": "manual"
},
"status": 307,
"responseHeaders": {
"location": "//fr/"
}
},
{
"path": "/",
"headers": {
"accept-language": "en-US;q=0.9"
},
"fetchOptions": {
"redirect": "manual"
},
"status": 200,
"mustContain": "catchall page"
},
{
"path": "/en-US",
"headers": {
"accept-language": "nl;q=0.9"
},
"fetchOptions": {
"redirect": "manual"
},
"status": 200,
"mustContain": "catchall page"
},
{
"path": "/",
"status": 200,
"mustContain": "catchall page"
},
{
"path": "/",
"status": 200,
"mustContain": ">en-US<"
},
{
"path": "/en",
"status": 200,
"mustContain": "catchall page"
},
{
"path": "/en",
"status": 200,
"mustContain": ">en<"
},
{
"path": "/fr",
"status": 200,
"mustContain": "catchall page"
},
{
"path": "/fr",
"status": 200,
"mustContain": ">fr<"
},
{
"path": "/nl",
"status": 200,
"mustContain": "catchall page"
},
{
"path": "/nl",
"status": 200,
"mustContain": ">nl<"
},
{
"path": "/nl-NL",
"status": 200,
"mustContain": "catchall page"
},
{
"path": "/nl-NL",
"status": 200,
"mustContain": ">nl-NL<"
},
{
"path": "/first",
"status": 200,
"mustContain": "catchall page"
},
{
"path": "/first",
"status": 200,
"mustContain": ">en-US<"
},
{
"path": "/en/first",
"status": 200,
"mustContain": "catchall page"
},
{
"path": "/en/first",
"status": 200,
"mustContain": ">en<"
},
{
"path": "/fr/first",
"status": 200,
"mustContain": "catchall page"
},
{
"path": "/fr/first",
"status": 200,
"mustContain": ">fr<"
},
{
"path": "/nl/first",
"status": 200,
"mustContain": "catchall page"
},
{
"path": "/nl/first",
"status": 200,
"mustContain": ">nl<"
},
{
"path": "/nl-NL/first",
"status": 200,
"mustContain": "catchall page"
},
{
"path": "/nl-NL/first",
"status": 200,
"mustContain": ">nl-NL<"
}
]
}

View File

@@ -0,0 +1,7 @@
{
"dependencies": {
"next": "canary",
"react": "^16.8.6",
"react-dom": "^16.8.6"
}
}

View File

@@ -0,0 +1,69 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
const Slug = props => {
const router = useRouter();
// invariant ensuring fallback is never accidentally flipped
if (router.isFallback) {
return 'Loading...';
}
return (
<div>
<p>catchall page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
<p id="router-as-path">{router.asPath}</p>
<Link href="/gsp/blocking/hallo-wereld" locale={'nl-NL'}>
<a>/nl-NL/gsp/blocking/hallo-wereld</a>
</Link>
<br />
<Link href="/gsp/blocking/42" locale={'nl-NL'}>
<a>/nl-NL/gsp/blocking/42</a>
</Link>
<br />
<Link href="/gsp/blocking/hallo-welt" locale={'fr'}>
<a>/fr/gsp/blocking/hallo-welt</a>
</Link>
<br />
<Link href="/gsp/blocking/42" locale={'fr'}>
<a>/fr/gsp/blocking/42</a>
</Link>
<br />
<Link href="/">
<a>/</a>
</Link>
</div>
);
};
export const getStaticProps = ({ params }) => {
return {
props: {
params,
random: Math.random(),
catchall: 'yes',
},
revalidate: 1,
};
};
export const getStaticPaths = ({ locales }) => {
const paths = [];
for (const locale of locales) {
paths.push({ params: { slug: ['first'] }, locale });
paths.push({ params: { slug: ['first'] }, locale });
}
return {
paths,
fallback: 'blocking',
};
};
export default Slug;

View File

@@ -0,0 +1 @@
hello world!

View File

@@ -0,0 +1,412 @@
/* eslint-env jest */
const fetch = require('node-fetch');
const cheerio = require('cheerio');
module.exports = function (ctx) {
it('should revalidate content properly from /', async () => {
const dataRes = await fetch(
`${ctx.deploymentUrl}/_next/data/testing-build-id/en-US/index.json`
);
expect(dataRes.status).toBe(200);
await dataRes.json();
await new Promise(resolve => setTimeout(resolve, 2000));
const res = await fetch(`${ctx.deploymentUrl}/`);
expect(res.status).toBe(200);
let $ = cheerio.load(await res.text());
const props = JSON.parse($('#props').text());
const initialRandom = props.random;
expect($('#router-locale').text()).toBe('en-US');
expect(JSON.parse($('#router-query').text())).toEqual({});
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 2000));
const res2 = await fetch(`${ctx.deploymentUrl}/`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('en-US');
expect(JSON.parse($('#router-query').text())).toEqual({});
});
it('should revalidate content properly from /fr', async () => {
const dataRes = await fetch(
`${ctx.deploymentUrl}/_next/data/testing-build-id/fr/index.json`
);
expect(dataRes.status).toBe(200);
await dataRes.json();
await new Promise(resolve => setTimeout(resolve, 2000));
const res = await fetch(`${ctx.deploymentUrl}/fr`);
expect(res.status).toBe(200);
let $ = cheerio.load(await res.text());
const props = JSON.parse($('#props').text());
const initialRandom = props.random;
expect($('#router-locale').text()).toBe('fr');
expect(JSON.parse($('#router-query').text())).toEqual({});
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 2000));
const res2 = await fetch(`${ctx.deploymentUrl}/fr`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('fr');
expect(JSON.parse($('#router-query').text())).toEqual({});
});
it('should revalidate content properly from /nl-NL', async () => {
const dataRes = await fetch(
`${ctx.deploymentUrl}/_next/data/testing-build-id/nl-NL/index.json`
);
expect(dataRes.status).toBe(200);
await dataRes.json();
await new Promise(resolve => setTimeout(resolve, 2000));
const res = await fetch(`${ctx.deploymentUrl}/nl-NL`);
expect(res.status).toBe(200);
let $ = cheerio.load(await res.text());
const props = JSON.parse($('#props').text());
const initialRandom = props.random;
expect($('#router-locale').text()).toBe('nl-NL');
expect(JSON.parse($('#router-query').text())).toEqual({});
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 2000));
const res2 = await fetch(`${ctx.deploymentUrl}/nl-NL`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('nl-NL');
expect(JSON.parse($('#router-query').text())).toEqual({});
});
it('should revalidate content properly from /gsp/fallback/first', async () => {
// check the _next/data URL first
const dataRes = await fetch(
`${ctx.deploymentUrl}/_next/data/testing-build-id/en-US/gsp/fallback/first.json`
);
expect(dataRes.status).toBe(200);
await dataRes.json();
await new Promise(resolve => setTimeout(resolve, 2000));
const res = await fetch(`${ctx.deploymentUrl}/gsp/fallback/first`);
expect(res.status).toBe(200);
const html = await res.text();
let $ = cheerio.load(html);
const props = JSON.parse($('#props').text());
const initialRandom = props.random;
expect($('#router-locale').text()).toBe('en-US');
expect(props.params).toEqual({ slug: 'first' });
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'first' });
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 2000));
const res2 = await fetch(`${ctx.deploymentUrl}/gsp/fallback/first`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('en-US');
expect(props2.params).toEqual({ slug: 'first' });
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'first' });
});
it('should revalidate content properly from /fr/gsp/fallback/first', async () => {
// check the _next/data URL first
const dataRes = await fetch(
`${ctx.deploymentUrl}/_next/data/testing-build-id/fr/gsp/fallback/first.json`
);
expect(dataRes.status).toBe(200);
await dataRes.json();
await new Promise(resolve => setTimeout(resolve, 2000));
const res = await fetch(`${ctx.deploymentUrl}/fr/gsp/fallback/first`);
expect(res.status).toBe(200);
const html = await res.text();
let $ = cheerio.load(html);
const props = JSON.parse($('#props').text());
const initialRandom = props.random;
expect($('#router-locale').text()).toBe('fr');
expect(props.params).toEqual({ slug: 'first' });
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'first' });
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 2000));
const res2 = await fetch(`${ctx.deploymentUrl}/fr/gsp/fallback/first`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('fr');
expect(props2.params).toEqual({ slug: 'first' });
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'first' });
});
it('should revalidate content properly from /nl-NL/gsp/fallback/first', async () => {
// check the _next/data URL first
const dataRes = await fetch(
`${ctx.deploymentUrl}/_next/data/testing-build-id/nl-NL/gsp/fallback/first.json`
);
expect(dataRes.status).toBe(200);
await dataRes.json();
await new Promise(resolve => setTimeout(resolve, 2000));
const res = await fetch(`${ctx.deploymentUrl}/nl-NL/gsp/fallback/first`);
expect(res.status).toBe(200);
const html = await res.text();
let $ = cheerio.load(html);
const props = JSON.parse($('#props').text());
const initialRandom = props.random;
expect($('#router-locale').text()).toBe('nl-NL');
expect(props.params).toEqual({ slug: 'first' });
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'first' });
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 2000));
const res2 = await fetch(`${ctx.deploymentUrl}/nl-NL/gsp/fallback/first`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('nl-NL');
expect(props2.params).toEqual({ slug: 'first' });
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'first' });
});
//
it('should revalidate content properly from /gsp/fallback/new-page', async () => {
const dataRes = await fetch(
`${ctx.deploymentUrl}/_next/data/testing-build-id/en-US/gsp/fallback/new-page.json`
);
expect(dataRes.status).toBe(200);
await dataRes.json();
const initRes = await fetch(`${ctx.deploymentUrl}/gsp/fallback/new-page`);
expect(initRes.status).toBe(200);
await new Promise(resolve => setTimeout(resolve, 2000));
const res = await fetch(`${ctx.deploymentUrl}/gsp/fallback/new-page`);
expect(res.status).toBe(200);
const html = await res.text();
let $ = cheerio.load(html);
const props = JSON.parse($('#props').text());
const initialRandom = props.random;
expect($('#router-locale').text()).toBe('en-US');
expect(props.params).toEqual({ slug: 'new-page' });
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'new-page' });
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 2000));
const res2 = await fetch(`${ctx.deploymentUrl}/gsp/fallback/new-page`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('en-US');
expect(props2.params).toEqual({ slug: 'new-page' });
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'new-page' });
});
it('should revalidate content properly from /fr/gsp/fallback/new-page', async () => {
// we have to hit the _next/data URL first
const dataRes = await fetch(
`${ctx.deploymentUrl}/_next/data/testing-build-id/fr/gsp/fallback/new-page.json`
);
expect(dataRes.status).toBe(200);
await new Promise(resolve => setTimeout(resolve, 2000));
const res = await fetch(`${ctx.deploymentUrl}/fr/gsp/fallback/new-page`);
expect(res.status).toBe(200);
const html = await res.text();
let $ = cheerio.load(html);
const props = JSON.parse($('#props').text());
const initialRandom = props.random;
expect($('#router-locale').text()).toBe('fr');
expect(props.params).toEqual({ slug: 'new-page' });
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'new-page' });
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 2000));
const res2 = await fetch(`${ctx.deploymentUrl}/fr/gsp/fallback/new-page`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('fr');
});
it('should revalidate content properly from /nl-NL/gsp/fallback/new-page', async () => {
// we have to hit the _next/data URL first
const dataRes = await fetch(
`${ctx.deploymentUrl}/_next/data/testing-build-id/nl-NL/gsp/fallback/new-page.json`
);
expect(dataRes.status).toBe(200);
await new Promise(resolve => setTimeout(resolve, 2000));
const res = await fetch(`${ctx.deploymentUrl}/nl-NL/gsp/fallback/new-page`);
expect(res.status).toBe(200);
const html = await res.text();
let $ = cheerio.load(html);
const props = JSON.parse($('#props').text());
const initialRandom = props.random;
expect($('#router-locale').text()).toBe('nl-NL');
expect(props.params).toEqual({ slug: 'new-page' });
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'new-page' });
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 2000));
const res2 = await fetch(
`${ctx.deploymentUrl}/nl-NL/gsp/fallback/new-page`
);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('nl-NL');
expect(props2.params).toEqual({ slug: 'new-page' });
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'new-page' });
});
it('should revalidate content properly from /gsp/no-fallback/first', async () => {
const dataRes = await fetch(
`${ctx.deploymentUrl}/_next/data/testing-build-id/en-US/gsp/no-fallback/first.json`
);
expect(dataRes.status).toBe(200);
await dataRes.json();
await new Promise(resolve => setTimeout(resolve, 2000));
const res = await fetch(`${ctx.deploymentUrl}/gsp/no-fallback/first`);
expect(res.status).toBe(200);
let $ = cheerio.load(await res.text());
const props = JSON.parse($('#props').text());
const initialRandom = props.random;
expect($('#router-locale').text()).toBe('en-US');
expect(props.params).toEqual({ slug: 'first' });
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'first' });
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 2000));
const res2 = await fetch(`${ctx.deploymentUrl}/gsp/no-fallback/first`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('en-US');
expect(props2.params).toEqual({ slug: 'first' });
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'first' });
});
it('should revalidate content properly from /fr/gsp/no-fallback/first', async () => {
const dataRes = await fetch(
`${ctx.deploymentUrl}/_next/data/testing-build-id/fr/gsp/no-fallback/first.json`
);
expect(dataRes.status).toBe(200);
await dataRes.json();
await new Promise(resolve => setTimeout(resolve, 2000));
const res = await fetch(`${ctx.deploymentUrl}/fr/gsp/no-fallback/first`);
expect(res.status).toBe(200);
let $ = cheerio.load(await res.text());
const props = JSON.parse($('#props').text());
const initialRandom = props.random;
expect($('#router-locale').text()).toBe('fr');
expect(props.params).toEqual({ slug: 'first' });
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'first' });
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 2000));
const res2 = await fetch(`${ctx.deploymentUrl}/fr/gsp/no-fallback/first`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('fr');
expect(props2.params).toEqual({ slug: 'first' });
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'first' });
});
it('should revalidate content properly from /nl-NL/gsp/no-fallback/second', async () => {
const dataRes = await fetch(
`${ctx.deploymentUrl}/_next/data/testing-build-id/nl-NL/gsp/no-fallback/second.json`
);
expect(dataRes.status).toBe(200);
await dataRes.json();
await new Promise(resolve => setTimeout(resolve, 2000));
const res = await fetch(
`${ctx.deploymentUrl}/nl-NL/gsp/no-fallback/second`
);
expect(res.status).toBe(200);
let $ = cheerio.load(await res.text());
const props = JSON.parse($('#props').text());
const initialRandom = props.random;
expect($('#router-locale').text()).toBe('nl-NL');
expect(props.params).toEqual({ slug: 'second' });
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'second' });
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 2000));
const res2 = await fetch(
`${ctx.deploymentUrl}/nl-NL/gsp/no-fallback/second`
);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
const props2 = JSON.parse($('#props').text());
expect(initialRandom).not.toBe(props2.random);
expect($('#router-locale').text()).toBe('nl-NL');
expect(props2.params).toEqual({ slug: 'second' });
expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'second' });
});
};

View File

@@ -2,22 +2,20 @@ module.exports = {
generateBuildId() {
return 'testing-build-id';
},
experimental: {
i18n: {
locales: ['nl-NL', 'nl-BE', 'nl', 'fr-BE', 'fr', 'en-US', 'en'],
defaultLocale: 'en-US',
// TODO: testing locale domains support, will require custom
// testing set-up as test accounts are used currently
domains: [
{
domain: 'example.be',
defaultLocale: 'nl-BE',
},
{
domain: 'example.fr',
defaultLocale: 'fr',
},
],
},
i18n: {
locales: ['nl-NL', 'nl-BE', 'nl', 'fr-BE', 'fr', 'en-US', 'en'],
defaultLocale: 'en-US',
// TODO: testing locale domains support, will require custom
// testing set-up as test accounts are used currently
domains: [
{
domain: 'example.be',
defaultLocale: 'nl-BE',
},
{
domain: 'example.fr',
defaultLocale: 'fr',
},
],
},
};

View File

@@ -169,6 +169,47 @@
"mustContain": "hello world!"
},
{
"path": "/dynamic/hello",
"status": 200,
"mustContain": "dynamic page"
},
{
"path": "/dynamic/hello",
"status": 200,
"mustContain": "\"en-US\""
},
{
"path": "/en/dynamic/hello",
"status": 200,
"mustContain": "dynamic page"
},
{
"path": "/en/dynamic/hello",
"status": 200,
"mustContain": "\"en\""
},
{
"path": "/nl/dynamic/hello",
"status": 200,
"mustContain": "dynamic page"
},
{
"path": "/nl/dynamic/hello",
"status": 200,
"mustContain": "\"nl\""
},
{
"path": "/fr/dynamic/hello",
"status": 200,
"mustContain": "dynamic page"
},
{
"path": "/fr/dynamic/hello",
"status": 200,
"mustContain": "\"fr\""
},
{
"path": "/gsp",
"status": 200,
@@ -348,6 +389,9 @@
"status": 200,
"mustContain": "lang=\"en\""
},
{
"delay": 2000
},
{
"path": "/en/not-found/fallback/first",
"status": 200,
@@ -381,6 +425,9 @@
"status": 200,
"mustContain": "lang=\"fr\""
},
{
"delay": 2000
},
{
"path": "/fr/not-found/fallback/first",
"status": 200,
@@ -427,6 +474,52 @@
"path": "/_next/data/testing-build-id/nl/gsp.json",
"status": 200,
"mustContain": "\"locale\":\"nl\""
},
{
"path": "/gsp/blocking/first",
"status": 200,
"mustContain": "catchall"
},
{
"path": "/gsp/blocking/first",
"status": 200,
"mustContain": "lang=\"en-US\""
},
{
"path": "/_next/data/testing-build-id/en-US/gsp/blocking/first.json",
"status": 200,
"mustContain": "\"catchall\":\"yes\""
},
{
"path": "/nl-NL/gsp/blocking/first",
"status": 200,
"mustContain": "catchall"
},
{
"path": "/nl-NL/gsp/blocking/first",
"status": 200,
"mustContain": "lang=\"nl-NL\""
},
{
"path": "/_next/data/testing-build-id/nl-NL/gsp/blocking/first.json",
"status": 200,
"mustContain": "\"catchall\":\"yes\""
},
{
"path": "/fr/gsp/blocking/first",
"status": 200,
"mustContain": "catchall"
},
{
"path": "/fr/gsp/blocking/first",
"status": 200,
"mustContain": "lang=\"fr\""
},
{
"path": "/_next/data/testing-build-id/fr/gsp/blocking/first.json",
"status": 200,
"mustContain": "\"catchall\":\"yes\""
}
]
}

View File

@@ -0,0 +1,12 @@
import { useRouter } from 'next/router';
export default function Dynamic(props) {
const router = useRouter();
return (
<>
<p>dynamic page</p>
<p id="query">{JSON.stringify(router.query)}</p>
</>
);
}

View File

@@ -0,0 +1,43 @@
import Link from 'next/link';
const Slug = props => {
return (
<div>
<p id="props">{JSON.stringify(props)}</p>
<Link href="/gsp/blocking/hallo-wereld" locale={'nl-NL'}>
<a>/nl-NL/gsp/blocking/hallo-wereld</a>
</Link>
<br />
<Link href="/gsp/blocking/42" locale={'nl-NL'}>
<a>/nl-NL/gsp/blocking/42</a>
</Link>
<br />
<Link href="/gsp/blocking/hallo-welt" locale={'fr'}>
<a>/fr/gsp/blocking/hallo-welt</a>
</Link>
<br />
<Link href="/gsp/blocking/42" locale={'fr'}>
<a>/fr/gsp/blocking/42</a>
</Link>
</div>
);
};
export const getStaticProps = () => {
return {
props: {
random: Math.random(),
catchall: 'yes',
},
revalidate: 1,
};
};
export const getStaticPaths = () => {
return {
paths: [],
fallback: 'blocking',
};
};
export default Slug;

View File

@@ -26,19 +26,26 @@ export default function Page(props) {
export const getStaticProps = ({ params, locale, locales }) => {
return {
props: {
random: Math.random(),
params,
locale,
locales,
},
revalidate: 1,
};
};
export const getStaticPaths = () => {
export const getStaticPaths = ({ locales }) => {
const paths = [];
for (const locale of locales) {
paths.push({ params: { slug: 'first' }, locale });
paths.push({ params: { slug: 'second' }, locale });
}
return {
// the default locale will be used since one isn't defined here
paths: ['first', 'second'].map(slug => ({
params: { slug },
})),
paths,
fallback: true,
};
};

View File

@@ -26,10 +26,12 @@ export default function Page(props) {
export const getStaticProps = ({ params, locale, locales }) => {
return {
props: {
random: Math.random(),
params,
locale,
locales,
},
revalidate: 1,
};
};
@@ -40,6 +42,7 @@ export const getStaticPaths = () => {
'/gsp/no-fallback/second',
{ params: { slug: 'first' }, locale: 'en-US' },
'/nl-NL/gsp/no-fallback/second',
'/fr/gsp/no-fallback/first',
],
fallback: false,
};

View File

@@ -48,8 +48,10 @@ export default function Page(props) {
export const getStaticProps = ({ locale, locales }) => {
return {
props: {
random: Math.random(),
locale,
locales,
},
revalidate: 1,
};
};

View File

@@ -8,6 +8,12 @@
"cache-control": "public,max-age=31536000,immutable"
}
},
{
"path": "/_next/static/testing-build-id/_buildManifest.js",
"responseHeaders": {
"cache-control": "public,max-age=31536000,immutable"
}
},
{
"path": "/_next/static/invalid-build-id/pages/non-existent.js",
"notResponseHeaders": {

View File

@@ -8,6 +8,12 @@
"cache-control": "public,max-age=31536000,immutable"
}
},
{
"path": "/_next/static/testing-build-id/_buildManifest.js",
"responseHeaders": {
"cache-control": "public,max-age=31536000,immutable"
}
},
{
"path": "/",
"mustContain": "nextExport\":true"

View File

@@ -55,6 +55,26 @@
{
"path": "/docs/blog/post-1/comments",
"mustContain": "comments post: post-1"
},
{
"path": "/docs/_next/data/testing-build-id/blog/post-1.json",
"status": 200,
"mustContain": "\"post\""
},
{
"path": "/docs/_next/data/testing-build-id/blog/post-2/comments.json",
"status": 200,
"mustContain": "\"post\""
},
{
"path": "/docs/_next/data/testing-build-id/blog-ssg/post-1.json",
"status": 200,
"mustContain": "\"post\""
},
{
"path": "/docs/_next/data/testing-build-id/blog-ssg/post-2/comments.json",
"status": 200,
"mustContain": "\"post\""
}
]
}

View File

@@ -0,0 +1,16 @@
export const getStaticProps = ({ params }) => ({
props: {
post: params.post,
},
});
export const getStaticPaths = () => {
return {
paths: [{ params: { post: 'post-1' } }, { params: { post: 'post-2' } }],
fallback: true,
};
};
export default function Comment({ post }) {
return `comments post: ${post}`;
}

View File

@@ -0,0 +1,16 @@
export const getStaticProps = ({ params }) => ({
props: {
post: params.post,
},
});
export const getStaticPaths = () => {
return {
paths: [{ params: { post: 'post-1' } }, { params: { post: 'post-2' } }],
fallback: true,
};
};
export default function Post({ post }) {
return `index post: ${post}`;
}

View File

@@ -152,6 +152,16 @@
"x-vercel-cache": "/HIT|STALE|PRERENDER/"
}
},
{
"path": "/",
"status": 200,
"mustContain": "Hi"
},
{
"path": "/_next/data/testing-build-id/index.json",
"status": 200,
"mustContain": "\"hello\":\"index\""
},
{
"path": "/_next/data/testing-build-id/api-docs/second.json",
"status": 200

View File

@@ -1 +1,9 @@
export default () => 'Hi';
export const getStaticProps = () => {
return {
props: {
hello: 'index',
},
};
};

View File

@@ -0,0 +1 @@
install.txt

View File

@@ -0,0 +1,19 @@
const path = require('path');
const { promises: fs } = require('fs');
async function main() {
console.log('installCommand...');
await fs.writeFile(path.join(__dirname, 'install.txt'), `installCommand`);
console.log('Finished installing...');
}
main()
.then(() => {
process.exit(0);
})
.catch(error => {
console.error(error);
process.exit(1);
});

View File

@@ -0,0 +1,3 @@
module.exports = {
target: 'serverless',
};

View File

@@ -0,0 +1,10 @@
{
"scripts": {
"build": "next build"
},
"dependencies": {
"next": "canary",
"react": "^16.8.6",
"react-dom": "^16.8.6"
}
}

View File

@@ -0,0 +1,15 @@
import path from 'path';
import { promises as fs } from 'fs';
export async function getStaticProps(context) {
console.log(process.cwd());
const installPath = path.join(process.cwd(), 'install.txt');
const install = await fs.readFile(installPath, 'utf8');
return {
props: { install },
};
}
export default function ({ install }) {
return <div>{install}</div>;
}

View File

@@ -0,0 +1,20 @@
{
"builds": [
{
"src": "package.json",
"use": "@vercel/next",
"config": {
"zeroConfig": true,
"installCommand": "node install.js && yarn",
"buildCommand": "next build"
}
}
],
"probes": [
{
"path": "/",
"status": 200,
"mustContain": "installCommand"
}
]
}

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/node",
"version": "1.8.4",
"version": "1.8.5-canary.0",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",

View File

@@ -104,12 +104,7 @@ async function downloadInstallAndBundle({
} else {
const installTime = Date.now();
console.log('Installing dependencies...');
await runNpmInstall(
entrypointFsDirname,
['--prefer-offline'],
spawnOpts,
meta
);
await runNpmInstall(entrypointFsDirname, [], spawnOpts, meta);
debug(`Install complete [${Date.now() - installTime}ms]`);
}

View File

@@ -3,3 +3,6 @@ dist/
# bypass all ignored files for the cache fixtures
# because they contain node_modules and package-lock.json files
!test/cache-fixtures/**
/src/bridge.ts
/src/launcher.ts

View File

@@ -1,8 +1,17 @@
#!/bin/bash
set -euo pipefail
# Copy shared dependencies
bridge_defs="$(dirname $(pwd))/now-node-bridge/src/bridge.ts"
launcher_defs="$(dirname $(pwd))/now-node/src/launcher.ts"
cp -v "$bridge_defs" src
cp -v "$launcher_defs" src
# Start fresh
rm -rf dist
tsc
# Build with `ncc`
ncc build src/index.ts -e @vercel/build-utils -e @now/build-utils -o dist

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/static-build",
"version": "0.17.9",
"version": "0.17.16-canary.0",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/build-step",
@@ -19,6 +19,7 @@
"prepublishOnly": "./build.sh"
},
"devDependencies": {
"@types/aws-lambda": "8.10.64",
"@types/cross-spawn": "6.0.0",
"@types/ms": "0.7.31",
"@types/node-fetch": "2.5.4",

View File

@@ -33,6 +33,8 @@ const {
NowBuildError,
} = buildUtils;
import { Route, Source } from '@vercel/routing-utils';
import { readBuildOutputDirectory } from './utils/read-build-output';
import * as GatsbyUtils from './utils/gatsby';
const sleep = (n: number) => new Promise(resolve => setTimeout(resolve, n));
@@ -109,7 +111,7 @@ function getScriptName(pkg: PackageJson, cmd: string, { zeroConfig }: Config) {
}
function getCommand(
name: 'build' | 'dev',
name: 'install' | 'build' | 'dev',
pkg: PackageJson | null,
config: Config,
framework: Framework | undefined
@@ -118,7 +120,7 @@ function getCommand(
return null;
}
const propName = name === 'build' ? 'buildCommand' : 'devCommand';
const propName = `${name}Command` as keyof Config;
if (typeof config[propName] === 'string') {
return config[propName];
@@ -133,7 +135,7 @@ function getCommand(
}
if (framework) {
return framework[propName] || null;
return (framework as any)[propName] || null;
}
return null;
@@ -268,6 +270,7 @@ export async function build({
const devCommand = getCommand('dev', pkg, config, framework);
const buildCommand = getCommand('build', pkg, config, framework);
const installCommand = getCommand('install', pkg, config, framework);
if (pkg || buildCommand) {
const gemfilePath = path.join(workPath, 'Gemfile');
@@ -278,18 +281,20 @@ export async function build({
const routes: Route[] = [];
if (config.zeroConfig) {
if (existsSync(gemfilePath) && !meta.isDev) {
debug('Detected Gemfile, executing bundle install...');
await runBundleInstall(workPath, [], undefined, meta);
}
if (existsSync(requirementsPath) && !meta.isDev) {
debug('Detected requirements.txt, executing pip install...');
await runPipInstall(
workPath,
['-r', requirementsPath],
undefined,
meta
);
if (typeof installCommand !== 'string') {
if (existsSync(gemfilePath) && !meta.isDev) {
debug('Detected Gemfile, executing bundle install...');
await runBundleInstall(workPath, [], undefined, meta);
}
if (existsSync(requirementsPath) && !meta.isDev) {
debug('Detected requirements.txt, executing pip install...');
await runPipInstall(
workPath,
['-r', requirementsPath],
undefined,
meta
);
}
}
const { HUGO_VERSION, ZOLA_VERSION, GUTENBERG_VERSION } = process.env;
@@ -327,6 +332,22 @@ export async function build({
debug(
`Detected ${framework.name} framework. Optimizing your deployment...`
);
if (process.env.VERCEL_ANALYTICS_ID) {
const frameworkDirectory = path.join(
workPath,
path.dirname(entrypoint)
);
switch (framework.slug) {
case 'gatsby': {
await GatsbyUtils.injectVercelAnalyticsPlugin(frameworkDirectory);
break;
}
default: {
break;
}
}
}
}
const nodeVersion = await getNodeVersion(
@@ -340,10 +361,22 @@ export async function build({
if (meta.isDev) {
debug('Skipping dependency installation because dev mode is enabled');
} else {
const installTime = Date.now();
console.log('Installing dependencies...');
await runNpmInstall(entrypointDir, ['--prefer-offline'], spawnOpts, meta);
debug(`Install complete [${Date.now() - installTime}ms]`);
if (typeof installCommand === 'string') {
if (installCommand.trim()) {
console.log(`Running "install" command: \`${installCommand}\`...`);
await execCommand(installCommand, {
...spawnOpts,
cwd: entrypointDir,
});
} else {
console.log(`Skipping "install" command...`);
}
} else {
console.log('Installing dependencies...');
const installTime = Date.now();
await runNpmInstall(entrypointDir, [], spawnOpts, meta);
debug(`Install complete [${Date.now() - installTime}ms]`);
}
}
if (pkg && (buildCommand || devCommand)) {
@@ -358,7 +391,7 @@ export async function build({
};
debug(
`Added "${nodeBinPath}" to PATH env because a package.json file was found.`
`Added "${nodeBinPath}" to PATH env because a package.json file was found`
);
}
@@ -424,7 +457,7 @@ export async function build({
);
} else {
if (meta.isDev) {
debug(`WARN: A dev script is missing.`);
debug(`WARN: A dev script is missing`);
}
if (buildCommand) {
@@ -472,31 +505,48 @@ export async function build({
}
}
validateDistDir(distPath);
const extraOutputs = await readBuildOutputDirectory({ workPath });
if (framework) {
if (extraOutputs.staticFiles) {
output = Object.assign(
{},
extraOutputs.staticFiles,
extraOutputs.functions
);
} else {
// No need to verify the dist dir if there are other output files.
if (!extraOutputs.functions) {
validateDistDir(distPath);
}
let ignore: string[] = [];
if (config.zeroConfig && config.outputDirectory === '.') {
ignore = [
'.env',
'.env.*',
'.git/**',
'.vercel/**',
'node_modules/**',
'yarn.lock',
'package-lock.json',
'package.json',
'.vercel_build_output',
];
debug(`Using ignore: ${JSON.stringify(ignore)}`);
}
output = await glob('**', { cwd: distPath, ignore }, mountpoint);
Object.assign(output, extraOutputs.functions);
}
if (extraOutputs.routes) {
routes.push(...extraOutputs.routes);
} else if (framework) {
const frameworkRoutes = await getFrameworkRoutes(
framework,
outputDirPrefix
);
routes.push(...frameworkRoutes);
}
let ignore: string[] = [];
if (config.zeroConfig && config.outputDirectory === '.') {
ignore = [
'.env',
'.env.*',
'.git/**',
'.vercel/**',
'node_modules/**',
'yarn.lock',
'package-lock.json',
'package.json',
];
debug(`Using ignore: ${JSON.stringify(ignore)}`);
}
output = await glob('**', { cwd: distPath, ignore }, mountpoint);
}
const watch = [path.join(mountpoint.replace(/^\.\/?/, ''), '**/*')];

View File

@@ -0,0 +1,48 @@
import { constants, PathLike, promises as fs } from 'fs';
import { PackageJson } from '@vercel/build-utils';
import path from 'path';
export type DeepWriteable<T> = {
-readonly [P in keyof T]: DeepWriteable<T[P]>;
};
export async function fileExists(path: PathLike): Promise<boolean> {
return fs.access(path, constants.F_OK).then(
() => true,
() => false
);
}
/**
* Read package.json from files
*/
export async function readPackageJson(entryPath: string): Promise<PackageJson> {
const packagePath = path.join(entryPath, 'package.json');
try {
return JSON.parse(await fs.readFile(packagePath, 'utf8'));
} catch (err) {
return {};
}
}
/**
* Write package.json
*/
export async function writePackageJson(
workPath: string,
packageJson: PackageJson
) {
await fs.writeFile(
path.join(workPath, 'package.json'),
JSON.stringify(packageJson, null, 2)
);
}
export function isObjectEmpty(object: { [key: string]: unknown }) {
for (const _prop in object) {
return false;
}
return true;
}

View File

@@ -0,0 +1,86 @@
import { PackageJson } from '@vercel/build-utils';
import { promises as fs } from 'fs';
import * as path from 'path';
import {
fileExists,
readPackageJson,
DeepWriteable,
writePackageJson,
} from './_shared';
const defaultConfig = {
plugins: [
{
resolve: 'gatsby-plugin-vercel',
options: {},
},
],
};
export async function injectVercelAnalyticsPlugin(dir: string) {
// Gatsby requires a special variable name for environment variables to be
// exposed to the client-side JavaScript bundles:
process.env.GATSBY_VERCEL_ANALYTICS_ID = process.env.VERCEL_ANALYTICS_ID;
const gatsbyConfigName = 'gatsby-config.js';
const gatsbyPluginPackageName = 'gatsby-plugin-vercel';
const gatsbyConfigPath = path.join(dir, gatsbyConfigName);
const pkgJson: DeepWriteable<PackageJson> = (await readPackageJson(
dir
)) as DeepWriteable<PackageJson>;
if (!pkgJson.dependencies) {
pkgJson.dependencies = {};
}
if (!pkgJson.dependencies[gatsbyPluginPackageName]) {
pkgJson.dependencies[gatsbyPluginPackageName] = 'latest';
await writePackageJson(dir, pkgJson);
}
if (await fileExists(gatsbyConfigPath)) {
await fs.rename(
gatsbyConfigPath,
gatsbyConfigPath + '.__vercel_builder_backup__.js'
);
await fs.writeFile(
gatsbyConfigPath,
`const userConfig = require("./gatsby-config.js.__vercel_builder_backup__.js");
// https://github.com/gatsbyjs/gatsby/blob/354003fb2908e02ff12109ca3a02978a5a6e608c/packages/gatsby/src/bootstrap/prefer-default.ts
const preferDefault = m => (m && m.default) || m;
const vercelConfig = Object.assign(
{},
// https://github.com/gatsbyjs/gatsby/blob/a6ecfb2b01d761e8a3612b8ea132c698659923d9/packages/gatsby/src/services/initialize.ts#L113-L117
preferDefault(userConfig)
);
if (!vercelConfig.plugins) {
vercelConfig.plugins = [];
}
const hasPlugin = vercelConfig.plugins.find(
(p) =>
p && (p === "gatsby-plugin-vercel" || p.resolve === "gatsby-plugin-vercel")
);
if (!hasPlugin) {
vercelConfig.plugins = vercelConfig.plugins.slice();
vercelConfig.plugins.push({
resolve: "gatsby-plugin-vercel",
options: {},
});
}
module.exports = vercelConfig;
`
);
} else {
await fs.writeFile(
gatsbyConfigPath,
`module.exports = ${JSON.stringify(defaultConfig)}`
);
}
}

View File

@@ -0,0 +1,157 @@
import { FileBlob, Files, Lambda } from '@vercel/build-utils';
import { isObjectEmpty } from './_shared';
import { makeNowLauncher } from '../launcher';
import { promises as fs } from 'fs';
import { Route } from '@vercel/routing-utils';
import buildUtils from '../build-utils';
import path from 'path';
const { createLambda, debug, getLatestNodeVersion, glob } = buildUtils;
/**
* Reads the .vercel_build_output directory and returns and object
* that should be merged with the build outputs.
*
* At the moment only `functions/node` is supported for functions.
*/
export async function readBuildOutputDirectory({
workPath,
}: {
workPath: string;
}) {
const functions: { [key: string]: Lambda } = {};
const functionsMountPath = path.join('.vercel', 'functions');
Object.assign(
functions,
await readNodeFunctions({ workPath, functionsMountPath })
);
const staticFiles = await readStaticFiles({ workPath });
const routes = await readRoutesConfig({ workPath });
const outputs = {
staticFiles: isObjectEmpty(staticFiles) ? null : staticFiles,
functions: isObjectEmpty(functions) ? null : functions,
routes: routes.length ? routes : null,
};
if (outputs.functions) {
console.log(
`Detected Serverless Functions in ".vercel_build_output/functions"`
);
}
if (outputs.staticFiles) {
console.log(`Detected Static Assets in ".vercel_build_output/static"`);
}
if (outputs.routes) {
console.log(`Detected Configuration in ".vercel_build_output/config"`);
}
return outputs;
}
async function readStaticFiles({
workPath,
}: {
workPath: string;
}): Promise<Files> {
const staticFilePath = path.join(workPath, '.vercel_build_output', 'static');
const staticFiles = await glob('**', {
cwd: staticFilePath,
});
return staticFiles;
}
async function readNodeFunctions({
workPath,
functionsMountPath,
}: {
workPath: string;
functionsMountPath: string;
}) {
const output: { [key: string]: Lambda } = {};
const nodeFunctionPath = path.join(
workPath,
'.vercel_build_output',
'functions',
'node'
);
const nodeFunctionFiles = await glob('*/index.js', {
cwd: nodeFunctionPath,
});
const nodeBridgeData = await fs.readFile(path.join(__dirname, 'bridge.js'));
for (const fileName of Object.keys(nodeFunctionFiles)) {
const launcherFileName = '___now_launcher';
const bridgeFileName = '___now_bridge';
const launcherFiles: Files = {
[`${launcherFileName}.js`]: new FileBlob({
data: makeNowLauncher({
entrypointPath: `./index.js`,
bridgePath: `./${bridgeFileName}`,
helpersPath: '',
sourcemapSupportPath: '',
shouldAddHelpers: false,
shouldAddSourcemapSupport: false,
}),
}),
[`${bridgeFileName}.js`]: new FileBlob({
data: nodeBridgeData,
}),
};
const requiredFiles = await glob('**', {
cwd: path.join(nodeFunctionPath, path.dirname(fileName)),
});
const lambda = await createLambda({
files: {
...requiredFiles,
...launcherFiles,
},
handler: `${launcherFileName}.launcher`,
runtime: getLatestNodeVersion().runtime,
});
const parsed = path.parse(fileName);
const newPath = path.join(functionsMountPath, parsed.dir, parsed.name);
output[newPath] = lambda;
debug(
`Created Lambda "${newPath}" from "${path.join(
nodeFunctionPath,
fileName
)}".`
);
}
return output;
}
async function readRoutesConfig({
workPath,
}: {
workPath: string;
}): Promise<Route[]> {
const routesConfigPath = path.join(
workPath,
'.vercel_build_output',
'config',
'routes.json'
);
try {
return JSON.parse(await fs.readFile(routesConfigPath, 'utf8')) || [];
} catch (error) {
if (error.code === 'ENOENT') {
return [];
}
throw error;
}
}

View File

@@ -0,0 +1,69 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# dotenv environment variable files
.env*
# gatsby files
.cache/
public
# Mac files
.DS_Store
# Yarn
yarn-error.log
.pnp/
.pnp.js
# Yarn Integrity file
.yarn-integrity

View File

@@ -0,0 +1,19 @@
{
"name": "gatsby-starter-default",
"private": true,
"description": "A simple starter to get up and developing quickly with Gatsby",
"version": "0.1.0",
"dependencies": {
"gatsby": "^2.24.91",
"prop-types": "^15.7.2",
"react": "^16.12.0",
"react-dom": "^16.12.0"
},
"scripts": {
"build": "gatsby build",
"develop": "gatsby develop",
"start": "npm run develop",
"serve": "gatsby serve",
"clean": "gatsby clean"
}
}

View File

@@ -0,0 +1,5 @@
import React from 'react';
const NotFoundPage = () => <h1>404: Not Found</h1>;
export default NotFoundPage;

View File

@@ -0,0 +1,5 @@
import React from 'react';
const IndexPage = () => <h1>Hello World people</h1>;
export default IndexPage;

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