Compare commits

...

14 Commits

Author SHA1 Message Date
Sean Massa
3d3774ee7e Publish Stable
- @vercel/build-utils@5.0.4
 - vercel@27.2.0
 - @vercel/client@12.1.3
 - @vercel/go@2.0.8
 - @vercel/hydrogen@0.0.5
 - @vercel/next@3.1.8
 - @vercel/node@2.4.5
 - @vercel/python@3.1.0
 - @vercel/redwood@1.0.9
 - @vercel/remix@1.0.10
 - @vercel/ruby@1.3.16
 - @vercel/static-build@1.0.9
2022-07-21 15:03:11 -05:00
Sean Massa
50f8eec7cb [cli][dev] support environment variables in edge functions during vc dev (#8207)
Edge Function support in `vc dev` was not passing through the environment variables, which is supported by [production Edge Functions](https://vercel.com/docs/concepts/functions/edge-functions/edge-functions-api#environment-variables).

This PR passes those through. I updated a test for it and manually tested on a separate project.
2022-07-21 17:53:52 +00:00
Sean Massa
45374e2f90 [cli] improve isBundledBuilder logic (#8086)
The logic that determines if a builder needs to be installed has a check to see if the builder should already be bundled: `isBundledBuilder`. This was looking for specific conditions that made it (1) a bit hard to follow and (2) very sensitive to `canary` tags (and having "canary" in the version specifier).

This causes general development problems because local changes weren't always used by local CLI builds. Depedendant packages (like `@vercel/node`) would be installed from the latest `canary` release instead.

This caused problems in CI and released CLI versions where the latest `canary` of dependant packages might be rather old, causing that old code to be used instead of the latest non-canary releases.

The issue was mitigated for now by publishing canary releases for all packages.

---

Paired with @styfle @MatthewStanciu.

@TooTallNate: Is this change too broad? Are there cases where we wouldn't want to do this?
2022-07-21 15:45:29 +00:00
Steven
fd9142b6f3 [cli] Bump @vercel/fun to 1.0.4 (#8198)
Bump `@vercel/fun` to [1.0.4](https://github.com/vercel/fun/releases/tag/1.0.4)
2022-07-21 00:00:03 +00:00
JJ Kasper
8cf67b549b [next] Ensure manifests are specific to the included pages (#8172)
### Related Issues

This updates to filter the `routes-manifest` and `pages-manifest` to only include entries for the pages that are being included in the specific serverless function. This fixes the case where multiple dynamic routes could match a path but one is configured with `fallback: false` so shouldn't match when executing for a different dynamic route. 

A regression test for this specific scenario has been added in the `00-mixed-dynamic-routes` fixture. 

### 📋 Checklist

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

#### Tests

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

#### Code Review

- [ ] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
2022-07-20 23:06:44 +00:00
Sean Massa
5dc6f48e44 [cli][dev] handle no response from edge functions (#8095)
When an edge function has no response during `vc dev`, we were seeing an unhelpful error message:

> The event listener did not respond.

Now, we'll see a much more specific error message:

> Unhandled rejection: Edge Function "api/edge-no-response.ts" did not return a response.
> Error! Failed to complete request to /api/edge-no-response: Error: socket hang up
2022-07-20 20:09:29 +00:00
Kevin Tan
66c8544e8f [python] support Sanic >=21 and python >= 3.10 (#8045)
### Related Issues

1. exception from python 3.10:
```
TypeError: As of 3.10, the *loop* parameter was removed from Queue() since it is no longer necessary`
```

Remove the deprecated argument `loop` from `Queue`, which can also be omitted in python version < 3.10

2. exception from Sanic > 21.3:
```
File "C:\Users\Kevin\AppData\Local\Temp\zeit-fun-03f18b2d2c7d7\sanic\signals.py", line 93, in get
    group, param_basket = self.find_route(
TypeError: 'NoneType' object is not callable
```
As of Sanic > 21.3, it cannot serve requests immediately after initializing, instead, we need implement the [ASGI lifespan protocol](https://asgi.readthedocs.io/en/latest/specs/lifespan.html) and wait for the startup event completed.  

here I complemented the protocol copied from (same source of the previous HTTP procotol): <https://github.com/jordaneremieff/mangum/blob/main/mangum/protocols/lifespan.py>


### Related link:
https://github.com/encode/uvicorn/pull/498

### 📋 Checklist

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

#### Tests

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

#### Code Review

- [x] This PR has a concise title and thorough description useful to a reviewer
- [x] Issue from task tracker has a link to this PR


Co-authored-by: Steven <steven@ceriously.com>
2022-07-20 09:50:53 -04:00
Matthew Stanciu
0140db38fa [cli] Support multiple remote URLs in Git config (#8145)
Two features that handle a user's local Git config have been shipped:

- #8100 
- #7910 

Both of these features currently pull only from the user's remote origin URL. This covers 90% of cases, but there are cases in which the user has more than one remote URL, and may want to use one other than the origin URL, especially in `vc git connect`. This PR:

- Adds support for multiple remote URLs in a user's Git config
- Updates `vc git connect` to prompt the user to select a URL if they have multiple remote URLs
- Updates `createGitMeta` to send the connected Git repository url by default, origin url otherwise

### 📋 Checklist

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

#### Tests

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

#### Code Review

- [ ] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
2022-07-20 07:17:53 +00:00
Chris Barber
e5421c27e8 [cli][client][build-utils][node][static-build] updated node-fetch to fix high severity security vulnerability (#8180)
Update `node-fetch 2.6.1 -> 2.6.7` to fix high severity security vulnerability: Exposure of Sensitive Information to an Unauthorized Actor (https://github.com/advisories/GHSA-r683-j2x4-v87g).

`node-fetch` was updated in the root, `api`, `build-utils`, `cli`, `client`, `node`, and `static-build`.

### Related Issues

> https://linear.app/vercel/issue/VCCLI-196/update-vercelnode-dep-node-fetch-261-267

### 📋 Checklist

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

#### Tests

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

#### Code Review

- [ ] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
2022-07-19 22:15:09 +00:00
Matthew Stanciu
5afc527233 [cli] Add --environment flag to vc env pull (#8162)
Right now, `vc env pull` only pulls development environment variables. This PR adds a new flag, `--environment,` which allows users to specify which environment to pull from.

### 📋 Checklist

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

#### Tests

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

#### Code Review

- [ ] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
2022-07-19 20:02:13 +00:00
Nathan Rajlich
de9518b010 [cli] Write top-level error to builds.json file in "vc build" (#8193)
If an error happens outside of a Builder (i.e. `detectBuilders()` function fails), then write the serialized error into the `builds.json` file at the top-level of the file (there is no `builds[]` when an error happens at the top-level).
2022-07-19 19:30:17 +00:00
Nathan Rajlich
c322d1dbba [cli] Set ignoreBuildScript: true option in "vc build" command (#8184)
This matches the behavior in production and prevents the error:

```
> Error! Your `package.json` file is missing a `build` property inside the `scripts` property.
```
2022-07-19 18:52:33 +00:00
Steven
18c19ead76 [tests] Upgrade python tests to 3.9 (#8181)
New python 3.6 deployments will fail today per the previous announcement https://vercel.com/changelog/python-3-6-is-being-deprecated

This PR updates the tests to use python 3.9 instead.
2022-07-19 17:43:20 +00:00
Steven
9d80c27382 [cli] Print full error when unknown/unexpected (#8059)
If we don’t know the error, we should not assume it has a stack prop and instead print the whole thing to avoid accidentally printing `undefined`. 

- related to https://github.com/vercel/vercel/discussions/8043
2022-07-18 10:40:09 -04:00
102 changed files with 1923 additions and 928 deletions

View File

@@ -11,7 +11,7 @@
"dependencies": { "dependencies": {
"@sentry/node": "5.11.1", "@sentry/node": "5.11.1",
"got": "10.2.1", "got": "10.2.1",
"node-fetch": "2.6.1", "node-fetch": "2.6.7",
"parse-github-url": "1.0.2", "parse-github-url": "1.0.2",
"tar-fs": "2.0.0", "tar-fs": "2.0.0",
"unzip-stream": "0.3.0" "unzip-stream": "0.3.0"

View File

@@ -26,7 +26,7 @@
"jest": "28.0.2", "jest": "28.0.2",
"json5": "2.1.1", "json5": "2.1.1",
"lint-staged": "9.2.5", "lint-staged": "9.2.5",
"node-fetch": "2.6.1", "node-fetch": "2.6.7",
"npm-package-arg": "6.1.0", "npm-package-arg": "6.1.0",
"prettier": "2.6.2", "prettier": "2.6.2",
"ts-eager": "2.0.2", "ts-eager": "2.0.2",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/build-utils", "name": "@vercel/build-utils",
"version": "5.0.3", "version": "5.0.4",
"license": "MIT", "license": "MIT",
"main": "./dist/index.js", "main": "./dist/index.js",
"types": "./dist/index.d.js", "types": "./dist/index.d.js",
@@ -44,7 +44,7 @@
"js-yaml": "3.13.1", "js-yaml": "3.13.1",
"minimatch": "3.0.4", "minimatch": "3.0.4",
"multistream": "2.1.1", "multistream": "2.1.1",
"node-fetch": "2.6.1", "node-fetch": "2.6.7",
"semver": "6.1.1", "semver": "6.1.1",
"typescript": "4.3.4", "typescript": "4.3.4",
"yazl": "2.5.1" "yazl": "2.5.1"

View File

@@ -1,6 +1,6 @@
{ {
"name": "vercel", "name": "vercel",
"version": "27.1.5", "version": "27.2.0",
"preferGlobal": true, "preferGlobal": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"description": "The command-line interface for Vercel", "description": "The command-line interface for Vercel",
@@ -42,16 +42,16 @@
"node": ">= 14" "node": ">= 14"
}, },
"dependencies": { "dependencies": {
"@vercel/build-utils": "5.0.3", "@vercel/build-utils": "5.0.4",
"@vercel/go": "2.0.7", "@vercel/go": "2.0.8",
"@vercel/hydrogen": "0.0.4", "@vercel/hydrogen": "0.0.5",
"@vercel/next": "3.1.7", "@vercel/next": "3.1.8",
"@vercel/node": "2.4.4", "@vercel/node": "2.4.5",
"@vercel/python": "3.0.7", "@vercel/python": "3.1.0",
"@vercel/redwood": "1.0.8", "@vercel/redwood": "1.0.9",
"@vercel/remix": "1.0.9", "@vercel/remix": "1.0.10",
"@vercel/ruby": "1.3.15", "@vercel/ruby": "1.3.16",
"@vercel/static-build": "1.0.8", "@vercel/static-build": "1.0.9",
"update-notifier": "5.1.0" "update-notifier": "5.1.0"
}, },
"devDependencies": { "devDependencies": {
@@ -96,11 +96,11 @@
"@types/which": "1.3.2", "@types/which": "1.3.2",
"@types/write-json-file": "2.2.1", "@types/write-json-file": "2.2.1",
"@types/yauzl-promise": "2.1.0", "@types/yauzl-promise": "2.1.0",
"@vercel/client": "12.1.2", "@vercel/client": "12.1.3",
"@vercel/frameworks": "1.1.1", "@vercel/frameworks": "1.1.1",
"@vercel/fs-detectors": "2.0.1", "@vercel/fs-detectors": "2.0.1",
"@vercel/fun": "1.0.4",
"@vercel/ncc": "0.24.0", "@vercel/ncc": "0.24.0",
"@zeit/fun": "0.11.2",
"@zeit/source-map-support": "0.6.2", "@zeit/source-map-support": "0.6.2",
"ajv": "6.12.2", "ajv": "6.12.2",
"alpha-sort": "2.0.1", "alpha-sort": "2.0.1",
@@ -147,7 +147,7 @@
"minimatch": "3.0.4", "minimatch": "3.0.4",
"mri": "1.1.5", "mri": "1.1.5",
"ms": "2.1.2", "ms": "2.1.2",
"node-fetch": "2.6.1", "node-fetch": "2.6.7",
"npm-package-arg": "6.1.0", "npm-package-arg": "6.1.0",
"open": "8.4.0", "open": "8.4.0",
"ora": "3.4.0", "ora": "3.4.0",

View File

@@ -56,11 +56,11 @@ async function main() {
args.push('src/index.ts'); args.push('src/index.ts');
await execa('yarn', args, { stdio: 'inherit', cwd: dirRoot }); await execa('yarn', args, { stdio: 'inherit', cwd: dirRoot });
// `ncc` has some issues with `@zeit/fun`'s runtime files: // `ncc` has some issues with `@vercel/fun`'s runtime files:
// - Executable bits on the `bootstrap` files appear to be lost: // - Executable bits on the `bootstrap` files appear to be lost:
// https://github.com/zeit/ncc/pull/182 // https://github.com/vercel/ncc/pull/182
// - The `bootstrap.js` asset does not get copied into the output dir: // - The `bootstrap.js` asset does not get copied into the output dir:
// https://github.com/zeit/ncc/issues/278 // https://github.com/vercel/ncc/issues/278
// //
// Aside from those issues, all the same files from the `runtimes` directory // Aside from those issues, all the same files from the `runtimes` directory
// should be copied into the output runtimes dir, specifically the `index.js` // should be copied into the output runtimes dir, specifically the `index.js`
@@ -70,7 +70,7 @@ async function main() {
// with `fun`'s cache invalidation mechanism and they need to be shasum'd. // with `fun`'s cache invalidation mechanism and they need to be shasum'd.
const runtimes = join( const runtimes = join(
dirRoot, dirRoot,
'../../node_modules/@zeit/fun/dist/src/runtimes' '../../node_modules/@vercel/fun/dist/src/runtimes'
); );
await cpy('**/*', join(distRoot, 'runtimes'), { await cpy('**/*', join(distRoot, 'runtimes'), {
parents: true, parents: true,

View File

@@ -25,7 +25,7 @@ import {
MergeRoutesProps, MergeRoutesProps,
Route, Route,
} from '@vercel/routing-utils'; } from '@vercel/routing-utils';
import { VercelConfig } from '@vercel/client'; import type { VercelConfig } from '@vercel/client';
import pull from './pull'; import pull from './pull';
import { staticFiles as getFiles } from '../util/get-files'; import { staticFiles as getFiles } from '../util/get-files';
@@ -36,7 +36,10 @@ import * as cli from '../util/pkg-name';
import cliPkg from '../util/pkg'; import cliPkg from '../util/pkg';
import readJSONFile from '../util/read-json-file'; import readJSONFile from '../util/read-json-file';
import { CantParseJSONFile } from '../util/errors-ts'; import { CantParseJSONFile } from '../util/errors-ts';
import { readProjectSettings } from '../util/projects/project-settings'; import {
ProjectLinkAndSettings,
readProjectSettings,
} from '../util/projects/project-settings';
import { VERCEL_DIR } from '../util/projects/link'; import { VERCEL_DIR } from '../util/projects/link';
import confirm from '../util/input/confirm'; import confirm from '../util/input/confirm';
import { emoji, prependEmoji } from '../util/emoji'; import { emoji, prependEmoji } from '../util/emoji';
@@ -46,19 +49,31 @@ import {
PathOverride, PathOverride,
writeBuildResult, writeBuildResult,
} from '../util/build/write-build-result'; } from '../util/build/write-build-result';
import { importBuilders, BuilderWithPkg } from '../util/build/import-builders'; import { importBuilders } from '../util/build/import-builders';
import { initCorepack, cleanupCorepack } from '../util/build/corepack'; import { initCorepack, cleanupCorepack } from '../util/build/corepack';
import { sortBuilders } from '../util/build/sort-builders'; import { sortBuilders } from '../util/build/sort-builders';
import { toEnumerableError } from '../util/error';
type BuildResult = BuildResultV2 | BuildResultV3; type BuildResult = BuildResultV2 | BuildResultV3;
interface SerializedBuilder extends Builder { interface SerializedBuilder extends Builder {
error?: Error; error?: any;
require?: string; require?: string;
requirePath?: string; requirePath?: string;
apiVersion: number; apiVersion: number;
} }
/**
* Contents of the `builds.json` file.
*/
interface BuildsManifest {
'//': string;
target: string;
argv: string[];
error?: any;
builds?: SerializedBuilder[];
}
const help = () => { const help = () => {
return console.log(` return console.log(`
${chalk.bold(`${cli.logo} ${cli.name} build`)} ${chalk.bold(`${cli.logo} ${cli.name} build`)}
@@ -175,7 +190,6 @@ export default async function main(client: Client): Promise<number> {
} }
// TODO: load env vars from the API, fall back to local files if that fails // TODO: load env vars from the API, fall back to local files if that fails
const envPath = await checkExists([ const envPath = await checkExists([
join(cwd, VERCEL_DIR, `.env.${target}.local`), join(cwd, VERCEL_DIR, `.env.${target}.local`),
join(cwd, `.env`), join(cwd, `.env`),
@@ -189,6 +203,48 @@ export default async function main(client: Client): Promise<number> {
process.env.VERCEL = '1'; process.env.VERCEL = '1';
process.env.NOW_BUILDER = '1'; process.env.NOW_BUILDER = '1';
// Delete output directory from potential previous build
const outputDir = argv['--output']
? resolve(argv['--output'])
: join(cwd, OUTPUT_DIR);
await fs.remove(outputDir);
const buildsJson: BuildsManifest = {
'//': 'This file was generated by the `vercel build` command. It is not part of the Build Output API.',
target,
argv: process.argv,
};
try {
return await doBuild(client, project, buildsJson, cwd, outputDir);
} catch (err: any) {
output.prettyError(err);
// Write error to `builds.json` file
buildsJson.error = toEnumerableError(err);
const buildsJsonPath = join(outputDir, 'builds.json');
const configJsonPath = join(outputDir, 'config.json');
await fs.outputJSON(buildsJsonPath, buildsJson, {
spaces: 2,
});
await fs.writeJSON(configJsonPath, { version: 3 }, { spaces: 2 });
return 1;
}
}
/**
* Execute the Project's builders. If this function throws an error,
* then it will be serialized into the `builds.json` manifest file.
*/
async function doBuild(
client: Client,
project: ProjectLinkAndSettings,
buildsJson: BuildsManifest,
cwd: string,
outputDir: string
): Promise<number> {
const { output } = client;
const workPath = join(cwd, project.settings.rootDirectory || '.'); const workPath = join(cwd, project.settings.rootDirectory || '.');
// Load `package.json` and `vercel.json` files // Load `package.json` and `vercel.json` files
@@ -208,17 +264,16 @@ export default async function main(client: Client): Promise<number> {
const routesResult = getTransformedRoutes(vercelConfig || {}); const routesResult = getTransformedRoutes(vercelConfig || {});
if (routesResult.error) { if (routesResult.error) {
output.prettyError(routesResult.error); throw routesResult.error;
return 1;
} }
if (vercelConfig?.builds && vercelConfig.functions) { if (vercelConfig?.builds && vercelConfig.functions) {
output.prettyError({ throw new NowBuildError({
code: 'bad_request',
message: message:
'The `functions` property cannot be used in conjunction with the `builds` property. Please remove one of them.', 'The `functions` property cannot be used in conjunction with the `builds` property. Please remove one of them.',
link: 'https://vercel.link/functions-and-builds', link: 'https://vercel.link/functions-and-builds',
}); });
return 1;
} }
let builds = vercelConfig?.builds || []; let builds = vercelConfig?.builds || [];
@@ -236,12 +291,12 @@ export default async function main(client: Client): Promise<number> {
const detectedBuilders = await detectBuilders(files, pkg, { const detectedBuilders = await detectBuilders(files, pkg, {
...vercelConfig, ...vercelConfig,
projectSettings: project.settings, projectSettings: project.settings,
ignoreBuildScript: true,
featHandleMiss: true, featHandleMiss: true,
}); });
if (detectedBuilders.errors && detectedBuilders.errors.length > 0) { if (detectedBuilders.errors && detectedBuilders.errors.length > 0) {
output.prettyError(detectedBuilders.errors[0]); throw detectedBuilders.errors[0];
return 1;
} }
for (const w of detectedBuilders.warnings) { for (const w of detectedBuilders.warnings) {
@@ -274,13 +329,7 @@ export default async function main(client: Client): Promise<number> {
const builderSpecs = new Set(builds.map(b => b.use)); const builderSpecs = new Set(builds.map(b => b.use));
let buildersWithPkgs: Map<string, BuilderWithPkg>; const buildersWithPkgs = await importBuilders(builderSpecs, cwd, output);
try {
buildersWithPkgs = await importBuilders(builderSpecs, cwd, output);
} catch (err: any) {
output.prettyError(err);
return 1;
}
// Populate Files -> FileFsRef mapping // Populate Files -> FileFsRef mapping
const filesMap: Files = {}; const filesMap: Files = {};
@@ -290,12 +339,6 @@ export default async function main(client: Client): Promise<number> {
filesMap[path] = new FileFsRef({ mode, fsPath }); filesMap[path] = new FileFsRef({ mode, fsPath });
} }
// Delete output directory from potential previous build
const outputDir = argv['--output']
? resolve(argv['--output'])
: join(cwd, OUTPUT_DIR);
await fs.remove(outputDir);
const buildStamp = stamp(); const buildStamp = stamp();
// Create fresh new output directory // Create fresh new output directory
@@ -322,12 +365,7 @@ export default async function main(client: Client): Promise<number> {
]; ];
}) })
); );
const buildsJson = { buildsJson.builds = Array.from(buildsJsonBuilds.values());
'//': 'This file was generated by the `vercel build` command. It is not part of the Build Output API.',
target,
argv: process.argv,
builds: Array.from(buildsJsonBuilds.values()),
};
const buildsJsonPath = join(outputDir, 'builds.json'); const buildsJsonPath = join(outputDir, 'builds.json');
const writeBuildsJsonPromise = fs.writeJSON(buildsJsonPath, buildsJson, { const writeBuildsJsonPromise = fs.writeJSON(buildsJsonPath, buildsJson, {
spaces: 2, spaces: 2,
@@ -406,25 +444,17 @@ export default async function main(client: Client): Promise<number> {
) )
); );
} catch (err: any) { } catch (err: any) {
const configJson = { const writeConfigJsonPromise = fs.writeJSON(
version: 3,
};
const configJsonPromise = fs.writeJSON(
join(outputDir, 'config.json'), join(outputDir, 'config.json'),
configJson, { version: 3 },
{ spaces: 2 } { spaces: 2 }
); );
await Promise.all([writeBuildsJsonPromise, configJsonPromise]); await Promise.all([writeBuildsJsonPromise, writeConfigJsonPromise]);
const buildJsonBuild = buildsJsonBuilds.get(build); const buildJsonBuild = buildsJsonBuilds.get(build);
if (buildJsonBuild) { if (buildJsonBuild) {
buildJsonBuild.error = { buildJsonBuild.error = toEnumerableError(err);
name: err.name,
message: err.message,
stack: err.stack,
...err,
};
await fs.writeJSON(buildsJsonPath, buildsJson, { await fs.writeJSON(buildsJsonPath, buildsJson, {
spaces: 2, spaces: 2,
@@ -441,15 +471,12 @@ export default async function main(client: Client): Promise<number> {
// Wait for filesystem operations to complete // Wait for filesystem operations to complete
// TODO render progress bar? // TODO render progress bar?
let hadError = false;
const errors = await Promise.all(ops); const errors = await Promise.all(ops);
for (const error of errors) { for (const error of errors) {
if (error) { if (error) {
hadError = true; throw error;
output.prettyError(error);
} }
} }
if (hadError) return 1;
// Merge existing `config.json` file into the one that will be produced // Merge existing `config.json` file into the one that will be produced
const configPath = join(outputDir, 'config.json'); const configPath = join(outputDir, 'config.json');

View File

@@ -428,7 +428,7 @@ export default async (client: Client) => {
parseMeta(argv['--meta']) parseMeta(argv['--meta'])
); );
const gitMetadata = await createGitMeta(path, output); const gitMetadata = await createGitMeta(path, output, project);
// Merge dotenv config, `env` from vercel.json, and `--env` / `-e` arguments // Merge dotenv config, `env` from vercel.json, and `--env` / `-e` arguments
const deploymentEnv = Object.assign( const deploymentEnv = Object.assign(

View File

@@ -1,7 +1,9 @@
import chalk from 'chalk'; import chalk from 'chalk';
import { ProjectEnvTarget } from '../../types';
import Client from '../../util/client'; import Client from '../../util/client';
import { getEnvTargetPlaceholder } from '../../util/env/env-target'; import {
getEnvTargetPlaceholder,
isValidEnvTarget,
} from '../../util/env/env-target';
import getArgs from '../../util/get-args'; import getArgs from '../../util/get-args';
import getInvalidSubcommand from '../../util/get-invalid-subcommand'; import getInvalidSubcommand from '../../util/get-invalid-subcommand';
import getSubcommand from '../../util/get-subcommand'; import getSubcommand from '../../util/get-subcommand';
@@ -29,6 +31,7 @@ const help = () => {
${chalk.dim('Options:')} ${chalk.dim('Options:')}
-h, --help Output usage information -h, --help Output usage information
--environment Set the Environment (development, preview, production) when pulling Environment Variables
-A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline( -A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline(
'FILE' 'FILE'
)} Path to the local ${'`vercel.json`'} file )} Path to the local ${'`vercel.json`'} file
@@ -111,6 +114,7 @@ export default async function main(client: Client) {
argv = getArgs(client.argv.slice(2), { argv = getArgs(client.argv.slice(2), {
'--yes': Boolean, '--yes': Boolean,
'-y': '--yes', '-y': '--yes',
'--environment': String,
}); });
} catch (error) { } catch (error) {
handleError(error); handleError(error);
@@ -126,6 +130,17 @@ export default async function main(client: Client) {
const subArgs = argv._.slice(1); const subArgs = argv._.slice(1);
const { subcommand, args } = getSubcommand(subArgs, COMMAND_CONFIG); const { subcommand, args } = getSubcommand(subArgs, COMMAND_CONFIG);
const { output, config } = client; const { output, config } = client;
const target = argv['--environment']?.toLowerCase() || 'development';
if (!isValidEnvTarget(target)) {
output.error(
`Invalid environment \`${chalk.cyan(
target
)}\`. Valid options: ${getEnvTargetPlaceholder()}`
);
return 1;
}
const link = await getLinkedProject(client, cwd); const link = await getLinkedProject(client, cwd);
if (link.status === 'error') { if (link.status === 'error') {
return link.exitCode; return link.exitCode;
@@ -150,7 +165,7 @@ export default async function main(client: Client) {
return pull( return pull(
client, client,
project, project,
ProjectEnvTarget.Development, target,
argv, argv,
args, args,
output, output,

View File

@@ -2,8 +2,9 @@ import chalk from 'chalk';
import { join } from 'path'; import { join } from 'path';
import { Org, Project } from '../../types'; import { Org, Project } from '../../types';
import Client from '../../util/client'; import Client from '../../util/client';
import { parseGitConfig, pluckRemoteUrl } from '../../util/create-git-meta'; import { parseGitConfig, pluckRemoteUrls } from '../../util/create-git-meta';
import confirm from '../../util/input/confirm'; import confirm from '../../util/input/confirm';
import list, { ListChoice } from '../../util/input/list';
import { Output } from '../../util/output'; import { Output } from '../../util/output';
import link from '../../util/output/link'; import link from '../../util/output/link';
import { getCommandName } from '../../util/pkg-name'; import { getCommandName } from '../../util/pkg-name';
@@ -64,20 +65,37 @@ export default async function connect(
); );
return 1; return 1;
} }
const remoteUrl = pluckRemoteUrl(gitConfig); const remoteUrls = pluckRemoteUrls(gitConfig);
if (!remoteUrl) { if (!remoteUrls) {
output.error( output.error(
`No remote origin URL found in your Git config. Make sure you've configured a remote repo in your local Git config. Run ${chalk.cyan( `No remote URLs found in your Git config. Make sure you've configured a remote repo in your local Git config. Run ${chalk.cyan(
'`git remote --help`' '`git remote --help`'
)} for more details.` )} for more details.`
); );
return 1; return 1;
} }
output.log(`Identified Git remote "origin": ${link(remoteUrl)}`);
let remoteUrl: string;
if (Object.keys(remoteUrls).length > 1) {
output.log(`Found multiple remote URLs.`);
remoteUrl = await selectRemoteUrl(client, remoteUrls);
} else {
// If only one is found, get it — usually "origin"
remoteUrl = Object.values(remoteUrls)[0];
}
if (remoteUrl === '') {
output.log('Aborted.');
return 0;
}
output.log(`Connecting Git remote: ${link(remoteUrl)}`);
const parsedUrl = parseRepoUrl(remoteUrl); const parsedUrl = parseRepoUrl(remoteUrl);
if (!parsedUrl) { if (!parsedUrl) {
output.error( output.error(
`Failed to parse Git repo data from the following remote URL in your Git config: ${link( `Failed to parse Git repo data from the following remote URL: ${link(
remoteUrl remoteUrl
)}` )}`
); );
@@ -166,3 +184,22 @@ async function confirmRepoConnect(
} }
return shouldReplaceProject; return shouldReplaceProject;
} }
async function selectRemoteUrl(
client: Client,
remoteUrls: { [key: string]: string }
): Promise<string> {
let choices: ListChoice[] = [];
for (const [urlKey, urlValue] of Object.entries(remoteUrls)) {
choices.push({
name: `${urlValue} ${chalk.gray(`(${urlKey})`)}`,
value: urlValue,
short: urlKey,
});
}
return await list(client, {
message: 'Which remote do you want to connect?',
choices,
});
}

View File

@@ -750,9 +750,7 @@ const main = async () => {
// Otherwise it is an unexpected error and we should show the trace // Otherwise it is an unexpected error and we should show the trace
// and an unexpected error message // and an unexpected error message
output.error( output.error(`An unexpected error occurred in ${subcommand}: ${err}`);
`An unexpected error occurred in ${subcommand}: ${err.stack}`
);
} }
return 1; return 1;

View File

@@ -3,76 +3,42 @@ import { join } from 'path';
import ini from 'ini'; import ini from 'ini';
import git from 'git-last-commit'; import git from 'git-last-commit';
import { exec } from 'child_process'; import { exec } from 'child_process';
import { GitMetadata } from '../types'; import { GitMetadata, Project } from '../types';
import { Output } from './output'; import { Output } from './output';
export function isDirty(directory: string, output: Output): Promise<boolean> {
return new Promise(resolve => {
exec('git status -s', { cwd: directory }, function (err, stdout, stderr) {
let debugMessage = `Failed to determine if Git repo has been modified:`;
if (err || stderr) {
if (err) debugMessage += `\n${err}`;
if (stderr) debugMessage += `\n${stderr.trim()}`;
output.debug(debugMessage);
return resolve(false);
}
resolve(stdout.trim().length > 0);
});
});
}
function getLastCommit(directory: string): Promise<git.Commit> {
return new Promise((resolve, reject) => {
git.getLastCommit(
(err, commit) => {
if (err) return reject(err);
resolve(commit);
},
{ dst: directory }
);
});
}
export async function parseGitConfig(configPath: string, output: Output) {
try {
return ini.parse(await fs.readFile(configPath, 'utf-8'));
} catch (error) {
output.debug(`Error while parsing repo data: ${error.message}`);
}
}
export function pluckRemoteUrl(gitConfig: {
[key: string]: any;
}): string | undefined {
// Assuming "origin" is the remote url that the user would want to use
return gitConfig['remote "origin"']?.url;
}
export async function getRemoteUrl(
configPath: string,
output: Output
): Promise<string | null> {
let gitConfig = await parseGitConfig(configPath, output);
if (!gitConfig) {
return null;
}
const originUrl = pluckRemoteUrl(gitConfig);
if (originUrl) {
return originUrl;
}
return null;
}
export async function createGitMeta( export async function createGitMeta(
directory: string, directory: string,
output: Output output: Output,
project?: Project | null
): Promise<GitMetadata | undefined> { ): Promise<GitMetadata | undefined> {
const remoteUrl = await getRemoteUrl(join(directory, '.git/config'), output); // If a Git repository is already connected via `vc git`, use that remote url
let remoteUrl;
if (project?.link) {
// in the form of org/repo
const { repo } = project.link;
const remoteUrls = await getRemoteUrls(
join(directory, '.git/config'),
output
);
if (remoteUrls) {
for (const urlValue of Object.values(remoteUrls)) {
if (urlValue.includes(repo)) {
remoteUrl = urlValue;
}
}
}
}
// If we couldn't get a remote url from the connected repo, default to the origin url
if (!remoteUrl) {
remoteUrl = await getOriginUrl(join(directory, '.git/config'), output);
}
// If we can't get the repo URL, then don't return any metadata // If we can't get the repo URL, then don't return any metadata
if (!remoteUrl) { if (!remoteUrl) {
return; return;
} }
const [commit, dirty] = await Promise.all([ const [commit, dirty] = await Promise.all([
getLastCommit(directory).catch(err => { getLastCommit(directory).catch(err => {
output.debug( output.debug(
@@ -96,3 +62,97 @@ export async function createGitMeta(
dirty, dirty,
}; };
} }
function getLastCommit(directory: string): Promise<git.Commit> {
return new Promise((resolve, reject) => {
git.getLastCommit(
(err, commit) => {
if (err) return reject(err);
resolve(commit);
},
{ dst: directory }
);
});
}
export function isDirty(directory: string, output: Output): Promise<boolean> {
return new Promise(resolve => {
exec('git status -s', { cwd: directory }, function (err, stdout, stderr) {
let debugMessage = `Failed to determine if Git repo has been modified:`;
if (err || stderr) {
if (err) debugMessage += `\n${err}`;
if (stderr) debugMessage += `\n${stderr.trim()}`;
output.debug(debugMessage);
return resolve(false);
}
resolve(stdout.trim().length > 0);
});
});
}
export async function parseGitConfig(configPath: string, output: Output) {
try {
return ini.parse(await fs.readFile(configPath, 'utf-8'));
} catch (error) {
output.debug(`Error while parsing repo data: ${error.message}`);
}
}
export function pluckRemoteUrls(gitConfig: {
[key: string]: any;
}): { [key: string]: string } | undefined {
let remoteUrls: { [key: string]: string } = {};
for (const key of Object.keys(gitConfig)) {
if (key.includes('remote')) {
// ex. remote "origin" — matches origin
const remoteName = key.match(/(?<=").*(?=")/g)?.[0];
const remoteUrl = gitConfig[key]?.url;
if (remoteName && remoteUrl) {
remoteUrls[remoteName] = remoteUrl;
}
}
}
if (Object.keys(remoteUrls).length === 0) {
return;
}
return remoteUrls;
}
export async function getRemoteUrls(
configPath: string,
output: Output
): Promise<{ [key: string]: string } | undefined> {
const config = await parseGitConfig(configPath, output);
if (!config) {
return;
}
const remoteUrls = pluckRemoteUrls(config);
return remoteUrls;
}
export function pluckOriginUrl(gitConfig: {
[key: string]: any;
}): string | undefined {
// Assuming "origin" is the remote url that the user would want to use
return gitConfig['remote "origin"']?.url;
}
export async function getOriginUrl(
configPath: string,
output: Output
): Promise<string | null> {
let gitConfig = await parseGitConfig(configPath, output);
if (!gitConfig) {
return null;
}
const originUrl = pluckOriginUrl(gitConfig);
if (originUrl) {
return originUrl;
}
return null;
}

View File

@@ -11,14 +11,11 @@ import cliPkg from '../pkg';
import cmd from '../output/cmd'; import cmd from '../output/cmd';
import { Output } from '../output'; import { Output } from '../output';
import { getDistTag } from '../get-dist-tag';
import { NoBuilderCacheError } from '../errors-ts'; import { NoBuilderCacheError } from '../errors-ts';
import * as staticBuilder from './static-builder'; import * as staticBuilder from './static-builder';
import { BuilderWithPackage } from './types'; import { BuilderWithPackage } from './types';
type CliPackageJson = typeof cliPkg;
const require_: typeof require = eval('require'); const require_: typeof require = eval('require');
const registryTypes = new Set(['version', 'tag', 'range']); const registryTypes = new Set(['version', 'tag', 'range']);
@@ -37,8 +34,6 @@ const localBuilders: { [key: string]: BuilderWithPackage } = {
'@vercel/static': createStaticBuilder('vercel'), '@vercel/static': createStaticBuilder('vercel'),
}; };
const distTag = getDistTag(cliPkg.version);
export const cacheDirPromise = prepareCacheDir(); export const cacheDirPromise = prepareCacheDir();
export const builderDirPromise = prepareBuilderDir(); export const builderDirPromise = prepareBuilderDir();
@@ -102,9 +97,8 @@ function parseVersionSafe(rawSpec: string) {
export function filterPackage( export function filterPackage(
builderSpec: string, builderSpec: string,
distTag: string,
buildersPkg: PackageJson, buildersPkg: PackageJson,
cliPkg: Partial<CliPackageJson> cliPkg: Partial<PackageJson>
) { ) {
if (builderSpec in localBuilders) return false; if (builderSpec in localBuilders) return false;
const parsed = npa(builderSpec); const parsed = npa(builderSpec);
@@ -126,31 +120,6 @@ export function filterPackage(
return false; return false;
} }
// Skip install of already installed Runtime with tag compatible match
if (
parsed.name &&
parsed.type === 'tag' &&
parsed.fetchSpec === distTag &&
buildersPkg.dependencies
) {
const parsedInstalled = npa(
`${parsed.name}@${buildersPkg.dependencies[parsed.name]}`
);
if (parsedInstalled.type !== 'version') {
return true;
}
const semverInstalled = semver.parse(parsedInstalled.rawSpec);
if (!semverInstalled) {
return true;
}
if (semverInstalled.prerelease.length > 0) {
return semverInstalled.prerelease[0] !== distTag;
}
if (distTag === 'latest') {
return false;
}
}
return true; return true;
} }
@@ -183,7 +152,7 @@ export async function installBuilders(
// Filter out any packages that come packaged with Vercel CLI // Filter out any packages that come packaged with Vercel CLI
const packagesToInstall = packages.filter(p => const packagesToInstall = packages.filter(p =>
filterPackage(p, distTag, buildersPkgBefore, cliPkg) filterPackage(p, buildersPkgBefore, cliPkg)
); );
if (packagesToInstall.length === 0) { if (packagesToInstall.length === 0) {
@@ -392,20 +361,13 @@ export function isBundledBuilder(
return false; return false;
} }
const bundledVersion = dependencies[parsed.name]; const inCliDependencyList = !!dependencies[parsed.name];
if (bundledVersion) { const inScope = parsed.scope === '@vercel';
if (parsed.type === 'tag') { const isVersionedReference = ['tag', 'version', 'range'].includes(
if (parsed.fetchSpec === 'canary') { parsed.type
return bundledVersion.includes('canary'); );
} else if (parsed.fetchSpec === 'latest') {
return !bundledVersion.includes('canary');
}
} else if (parsed.type === 'version') {
return parsed.fetchSpec === bundledVersion;
}
}
return false; return inCliDependencyList && inScope && isVersionedReference;
} }
function getPackageName( function getPackageName(

View File

@@ -4,7 +4,7 @@ import ms from 'ms';
import bytes from 'bytes'; import bytes from 'bytes';
import { delimiter, dirname, join } from 'path'; import { delimiter, dirname, join } from 'path';
import { fork, ChildProcess } from 'child_process'; import { fork, ChildProcess } from 'child_process';
import { createFunction } from '@zeit/fun'; import { createFunction } from '@vercel/fun';
import { import {
Builder, Builder,
BuildOptions, BuildOptions,

View File

@@ -1,6 +1,6 @@
import http from 'http'; import http from 'http';
import { ChildProcess } from 'child_process'; import { ChildProcess } from 'child_process';
import { Lambda as FunLambda } from '@zeit/fun'; import { Lambda as FunLambda } from '@vercel/fun';
import { import {
Builder as BuildConfig, Builder as BuildConfig,
BuildOptions, BuildOptions,

View File

@@ -87,3 +87,18 @@ export async function responseErrorMessage(
return `${message} (${res.status})`; return `${message} (${res.status})`;
} }
/**
* Returns a new Object with enumberable properties that match
* the provided `err` instance, for use with `JSON.stringify()`.
*/
export function toEnumerableError<E extends Partial<Error>>(err: E) {
const enumerable: {
[K in keyof E]?: E[K];
} = {};
enumerable.name = err.name;
for (const key of Object.getOwnPropertyNames(err) as (keyof E)[]) {
enumerable[key] = err[key];
}
return enumerable;
}

View File

@@ -14,7 +14,7 @@ interface ListSeparator {
separator: string; separator: string;
} }
type ListChoice = ListEntry | ListSeparator | typeof inquirer.Separator; export type ListChoice = ListEntry | ListSeparator | typeof inquirer.Separator;
interface ListOptions { interface ListOptions {
message: string; message: string;

View File

@@ -0,0 +1,7 @@
export const config = {
runtime: 'experimental-edge',
};
export default async function edge(request, event) {
// nothing returned
}

View File

@@ -17,6 +17,7 @@ export default async function edge(request, event) {
decamelized: decamelize('someCamelCaseThing'), decamelized: decamelize('someCamelCaseThing'),
uppercase: upper('someThing'), uppercase: upper('someThing'),
optionalChaining: request?.doesnotexist ?? 'fallback', optionalChaining: request?.doesnotexist ?? 'fallback',
ENV_VAR_IN_EDGE: process.env.ENV_VAR_IN_EDGE,
}) })
); );
} }

View File

@@ -0,0 +1,3 @@
export default function serverless(request, response) {
return response.send('hello from a serverless function');
}

View File

@@ -0,0 +1,7 @@
export const config = {
runtime: 'experimental-edge',
};
export default async function edge(request, event) {
// no response
}

View File

@@ -16,7 +16,11 @@ const {
test('[vercel dev] should support edge functions', async () => { test('[vercel dev] should support edge functions', async () => {
const dir = fixture('edge-function'); const dir = fixture('edge-function');
const { dev, port, readyResolver } = await testFixture(dir); const { dev, port, readyResolver } = await testFixture(dir, {
env: {
ENV_VAR_IN_EDGE: '1',
},
});
try { try {
await readyResolver; await readyResolver;
@@ -42,6 +46,7 @@ test('[vercel dev] should support edge functions', async () => {
decamelized: 'some_camel_case_thing', decamelized: 'some_camel_case_thing',
uppercase: 'SOMETHING', uppercase: 'SOMETHING',
optionalChaining: 'fallback', optionalChaining: 'fallback',
ENV_VAR_IN_EDGE: '1',
}); });
} finally { } finally {
await dev.kill('SIGTERM'); await dev.kill('SIGTERM');
@@ -56,6 +61,31 @@ test(
}) })
); );
test('[vercel dev] throws an error when an edge function has no response', async () => {
const dir = fixture('edge-function-error');
const { dev, port, readyResolver } = await testFixture(dir);
try {
await readyResolver;
let res = await fetch(`http://localhost:${port}/api/edge-no-response`);
validateResponseHeaders(res);
const { stdout, stderr } = await dev.kill('SIGTERM');
expect(await res.status).toBe(500);
expect(await res.text()).toMatch('FUNCTION_INVOCATION_FAILED');
expect(stdout).toMatch(
/Unhandled rejection: Edge Function "api\/edge-no-response.js" did not return a response./g
);
expect(stderr).toMatch(
/Failed to complete request to \/api\/edge-no-response: Error: socket hang up/g
);
} finally {
await dev.kill('SIGTERM');
}
});
test('[vercel dev] should support edge functions returning intentional 500 responses', async () => { test('[vercel dev] should support edge functions returning intentional 500 responses', async () => {
const dir = fixture('edge-function'); const dir = fixture('edge-function');
const { dev, port, readyResolver } = await testFixture(dir); const { dev, port, readyResolver } = await testFixture(dir);

View File

@@ -442,6 +442,17 @@ test(
}) })
); );
test(
'[vercel dev] Middleware that has no response',
testFixtureStdio('middleware-no-response', async (testPath: any) => {
await testPath(
500,
'/api/hello',
'A server error has occurred\n\nEDGE_FUNCTION_INVOCATION_FAILED'
);
})
);
test( test(
'[vercel dev] Middleware that does basic rewrite', '[vercel dev] Middleware that does basic rewrite',
testFixtureStdio('middleware-rewrite', async (testPath: any) => { testFixtureStdio('middleware-rewrite', async (testPath: any) => {

View File

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

View File

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

View File

@@ -0,0 +1,5 @@
{
"functions": {
"invalid.js": {}
}
}

View File

@@ -0,0 +1,9 @@
{
"orgId": ".",
"projectId": ".",
"settings": {
"framework": null,
"buildCommand": null,
"outputDirectory": "out"
}
}

View File

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

View File

@@ -0,0 +1,5 @@
{
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
}
}

View File

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

View File

@@ -0,0 +1,4 @@
{
"orgId": "team_dummy",
"projectId": "multiple-remotes"
}

View File

@@ -0,0 +1 @@
ref: refs/heads/master

View File

@@ -0,0 +1,13 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[remote "origin"]
url = https://github.com/user/repo.git
fetch = +refs/heads/*:refs/remotes/origin/*
[remote "secondary"]
url = https://github.com/user/repo2.git
fetch = +refs/heads/*:refs/remotes/secondary/*

View File

@@ -0,0 +1 @@
Unnamed repository; edit this file 'description' to name the repository.

View File

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

View File

@@ -0,0 +1,4 @@
{
"orgId": "team_dummy",
"projectId": "connected-repo"
}

View File

@@ -0,0 +1 @@
add hi

View File

@@ -0,0 +1 @@
ref: refs/heads/master

View File

@@ -0,0 +1,13 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[remote "origin"]
url = https://github.com/user/repo
fetch = +refs/heads/*:refs/remotes/origin/*
[remote "secondary"]
url = https://github.com/user/repo2
fetch = +refs/heads/*:refs/remotes/secondary/*

View File

@@ -0,0 +1 @@
Unnamed repository; edit this file 'description' to name the repository.

View File

@@ -0,0 +1 @@
8050816205303e5957b2909083c50677930d5b29

View File

@@ -0,0 +1 @@
hi

View File

@@ -0,0 +1 @@
ref: refs/heads/master

View File

@@ -0,0 +1,13 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[remote "origin"]
url = https://github.com/user/repo
fetch = +refs/heads/*:refs/remotes/origin/*
[remote "secondary"]
url = https://github.com/user/repo2
fetch = +refs/heads/*:refs/remotes/secondary/*

View File

@@ -0,0 +1 @@
Unnamed repository; edit this file 'description' to name the repository.

View File

@@ -622,6 +622,32 @@ describe('build', () => {
} }
}); });
it('should store `detectBuilders()` error in `builds.json`', async () => {
const cwd = fixture('error-vercel-json-validation');
const output = join(cwd, '.vercel/output');
try {
process.chdir(cwd);
const exitCode = await build(client);
expect(exitCode).toEqual(1);
// `builds.json` contains top-level "error" property
const builds = await fs.readJSON(join(output, 'builds.json'));
expect(builds.builds).toBeUndefined();
expect(builds.error.code).toEqual('invalid_function');
expect(builds.error.message).toEqual(
'Function must contain at least one property.'
);
// `config.json` contains `version`
const configJson = await fs.readJSON(join(output, 'config.json'));
expect(configJson.version).toBe(3);
} finally {
process.chdir(originalCwd);
delete process.env.__VERCEL_BUILD_RUNNING;
}
});
it('should store Builder error in `builds.json`', async () => { it('should store Builder error in `builds.json`', async () => {
const cwd = fixture('node-error'); const cwd = fixture('node-error');
const output = join(cwd, '.vercel/output'); const output = join(cwd, '.vercel/output');
@@ -643,7 +669,7 @@ describe('build', () => {
expect(errorBuilds[0].error.hideStackTrace).toEqual(true); expect(errorBuilds[0].error.hideStackTrace).toEqual(true);
expect(errorBuilds[0].error.code).toEqual('NODE_TYPESCRIPT_ERROR'); expect(errorBuilds[0].error.code).toEqual('NODE_TYPESCRIPT_ERROR');
// `config.json`` contains `version` // `config.json` contains `version`
const configJson = await fs.readJSON(join(output, 'config.json')); const configJson = await fs.readJSON(join(output, 'config.json'));
expect(configJson.version).toBe(3); expect(configJson.version).toBe(3);
} finally { } finally {
@@ -651,4 +677,35 @@ describe('build', () => {
delete process.env.__VERCEL_BUILD_RUNNING; delete process.env.__VERCEL_BUILD_RUNNING;
} }
}); });
it('should allow for missing "build" script', async () => {
const cwd = fixture('static-with-pkg');
const output = join(cwd, '.vercel/output');
try {
process.chdir(cwd);
const exitCode = await build(client);
expect(exitCode).toEqual(0);
// `builds.json` says that "@vercel/static" was run
const builds = await fs.readJSON(join(output, 'builds.json'));
expect(builds).toMatchObject({
target: 'preview',
builds: [
{
require: '@vercel/static',
apiVersion: 2,
src: '**',
use: '@vercel/static',
},
],
});
// "static" directory contains static files
const files = await fs.readdir(join(output, 'static'));
expect(files.sort()).toEqual(['index.html', 'package.json']);
} finally {
process.chdir(originalCwd);
delete process.env.__VERCEL_BUILD_RUNNING;
}
});
}); });

View File

@@ -57,6 +57,69 @@ describe('env', () => {
expect(devFileHasDevEnv).toBeTruthy(); expect(devFileHasDevEnv).toBeTruthy();
}); });
it('should use given environment', async () => {
const cwd = setupFixture('vercel-env-pull');
useUser();
useTeams('team_dummy');
useProject({
...defaultProject,
id: 'vercel-env-pull',
name: 'vercel-env-pull',
});
client.setArgv(
'env',
'pull',
'.env.production',
'--environment',
'production',
'--cwd',
cwd
);
const exitCodePromise = env(client);
await expect(client.stderr).toOutput(
`Downloading "production" Environment Variables for Project vercel-env-pull`
);
await expect(client.stderr).toOutput('Created .env.production file');
await expect(exitCodePromise).resolves.toEqual(0);
const rawProdEnv = await fs.readFile(path.join(cwd, '.env.production'));
// check for development env value
const envFileHasEnv = rawProdEnv
.toString()
.includes('REDIS_CONNECTION_STRING');
expect(envFileHasEnv).toBeTruthy();
});
it('should throw an error when it does not recognize given environment', async () => {
const cwd = setupFixture('vercel-env-pull');
useUser();
useTeams('team_dummy');
useProject({
...defaultProject,
id: 'vercel-env-pull',
name: 'vercel-env-pull',
});
client.setArgv(
'env',
'pull',
'.env.production',
'--environment',
'something-invalid',
'--cwd',
cwd
);
const exitCodePromise = env(client);
await expect(client.stderr).toOutput(
`Invalid environment \`something-invalid\`. Valid options: <production | preview | development>`
);
await expect(exitCodePromise).resolves.toEqual(1);
});
it('should expose production system env variables', async () => { it('should expose production system env variables', async () => {
const cwd = setupFixture('vercel-env-pull'); const cwd = setupFixture('vercel-env-pull');
useUser(); useUser();

View File

@@ -25,7 +25,7 @@ describe('git', () => {
id: 'unlinked', id: 'unlinked',
name: 'unlinked', name: 'unlinked',
}); });
client.setArgv('projects', 'connect'); client.setArgv('git', 'connect');
const gitPromise = git(client); const gitPromise = git(client);
await expect(client.stderr).toOutput('Set up'); await expect(client.stderr).toOutput('Set up');
@@ -40,7 +40,7 @@ describe('git', () => {
client.stdin.write('y\n'); client.stdin.write('y\n');
await expect(client.stderr).toOutput( await expect(client.stderr).toOutput(
`Identified Git remote "origin": https://github.com/user/repo.git` `Connecting Git remote: https://github.com/user/repo.git`
); );
const exitCode = await gitPromise; const exitCode = await gitPromise;
@@ -76,7 +76,7 @@ describe('git', () => {
id: 'no-git-config', id: 'no-git-config',
name: 'no-git-config', name: 'no-git-config',
}); });
client.setArgv('projects', 'connect', '--confirm'); client.setArgv('git', 'connect', '--confirm');
const exitCode = await git(client); const exitCode = await git(client);
expect(exitCode).toEqual(1); expect(exitCode).toEqual(1);
await expect(client.stderr).toOutput( await expect(client.stderr).toOutput(
@@ -98,11 +98,11 @@ describe('git', () => {
id: 'no-remote-url', id: 'no-remote-url',
name: 'no-remote-url', name: 'no-remote-url',
}); });
client.setArgv('projects', 'connect', '--confirm'); client.setArgv('git', 'connect', '--confirm');
const exitCode = await git(client); const exitCode = await git(client);
expect(exitCode).toEqual(1); expect(exitCode).toEqual(1);
await expect(client.stderr).toOutput( await expect(client.stderr).toOutput(
`Error! No remote origin URL found in your Git config. Make sure you've configured a remote repo in your local Git config. Run \`git remote --help\` for more details.` `Error! No remote URLs found in your Git config. Make sure you've configured a remote repo in your local Git config. Run \`git remote --help\` for more details.`
); );
} finally { } finally {
await fs.rename(join(cwd, '.git'), join(cwd, 'git')); await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
@@ -121,15 +121,15 @@ describe('git', () => {
id: 'bad-remote-url', id: 'bad-remote-url',
name: 'bad-remote-url', name: 'bad-remote-url',
}); });
client.setArgv('projects', 'connect', '--confirm'); client.setArgv('git', 'connect', '--confirm');
const exitCode = await git(client); const exitCode = await git(client);
expect(exitCode).toEqual(1); expect(exitCode).toEqual(1);
await expect(client.stderr).toOutput( await expect(client.stderr).toOutput(
`Identified Git remote "origin": bababooey` `Connecting Git remote: bababooey`
); );
await expect(client.stderr).toOutput( await expect(client.stderr).toOutput(
`Error! Failed to parse Git repo data from the following remote URL in your Git config: bababooey\n` `Error! Failed to parse Git repo data from the following remote URL: bababooey\n`
); );
} finally { } finally {
await fs.rename(join(cwd, '.git'), join(cwd, 'git')); await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
@@ -148,11 +148,11 @@ describe('git', () => {
id: 'new-connection', id: 'new-connection',
name: 'new-connection', name: 'new-connection',
}); });
client.setArgv('projects', 'connect', '--confirm'); client.setArgv('git', 'connect', '--confirm');
const gitPromise = git(client); const gitPromise = git(client);
await expect(client.stderr).toOutput( await expect(client.stderr).toOutput(
`Identified Git remote "origin": https://github.com/user/repo` `Connecting Git remote: https://github.com/user/repo`
); );
await expect(client.stderr).toOutput( await expect(client.stderr).toOutput(
`> Connected GitHub repository user/repo!\n` `> Connected GitHub repository user/repo!\n`
@@ -201,11 +201,11 @@ describe('git', () => {
updatedAt: 1656109539791, updatedAt: 1656109539791,
}; };
client.setArgv('projects', 'connect', '--confirm'); client.setArgv('git', 'connect', '--confirm');
const gitPromise = git(client); const gitPromise = git(client);
await expect(client.stderr).toOutput( await expect(client.stderr).toOutput(
`Identified Git remote "origin": https://github.com/user2/repo2` `Connecting Git remote: https://github.com/user2/repo2`
); );
await expect(client.stderr).toOutput( await expect(client.stderr).toOutput(
`> Connected GitHub repository user2/repo2!\n` `> Connected GitHub repository user2/repo2!\n`
@@ -253,11 +253,11 @@ describe('git', () => {
createdAt: 1656109539791, createdAt: 1656109539791,
updatedAt: 1656109539791, updatedAt: 1656109539791,
}; };
client.setArgv('projects', 'connect', '--confirm'); client.setArgv('git', 'connect', '--confirm');
const gitPromise = git(client); const gitPromise = git(client);
await expect(client.stderr).toOutput( await expect(client.stderr).toOutput(
`Identified Git remote "origin": https://github.com/user/repo` `Connecting Git remote: https://github.com/user/repo`
); );
await expect(client.stderr).toOutput( await expect(client.stderr).toOutput(
`> user/repo is already connected to your project.\n` `> user/repo is already connected to your project.\n`
@@ -283,11 +283,11 @@ describe('git', () => {
name: 'invalid-repo', name: 'invalid-repo',
}); });
client.setArgv('projects', 'connect', '--confirm'); client.setArgv('git', 'connect', '--confirm');
const gitPromise = git(client); const gitPromise = git(client);
await expect(client.stderr).toOutput( await expect(client.stderr).toOutput(
`Identified Git remote "origin": https://github.com/laksfj/asdgklsadkl` `Connecting Git remote: https://github.com/laksfj/asdgklsadkl`
); );
await expect(client.stderr).toOutput( await expect(client.stderr).toOutput(
`Failed to link laksfj/asdgklsadkl. Make sure there aren't any typos and that you have access to the repository if it's private.` `Failed to link laksfj/asdgklsadkl. Make sure there aren't any typos and that you have access to the repository if it's private.`
@@ -300,6 +300,56 @@ describe('git', () => {
process.chdir(originalCwd); process.chdir(originalCwd);
} }
}); });
it('should connect the default option of multiple remotes', async () => {
const cwd = fixture('multiple-remotes');
try {
process.chdir(cwd);
await fs.rename(join(cwd, 'git'), join(cwd, '.git'));
useUser();
useTeams('team_dummy');
useProject({
...defaultProject,
id: 'multiple-remotes',
name: 'multiple-remotes',
});
client.setArgv('git', 'connect');
const gitPromise = git(client);
await expect(client.stderr).toOutput('Found multiple remote URLs.');
await expect(client.stderr).toOutput(
'Which remote do you want to connect?'
);
client.stdin.write('\r');
await expect(client.stderr).toOutput(
'Connecting Git remote: https://github.com/user/repo.git'
);
await expect(client.stderr).toOutput(
'Connected GitHub repository user/repo!'
);
const exitCode = await gitPromise;
expect(exitCode).toEqual(0);
const project: Project = await client.fetch(
`/v8/projects/multiple-remotes`
);
expect(project.link).toMatchObject({
type: 'github',
repo: 'user/repo',
repoId: 1010,
gitCredentialId: '',
sourceless: true,
createdAt: 1656109539791,
updatedAt: 1656109539791,
});
} finally {
await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
process.chdir(originalCwd);
}
});
}); });
describe('disconnect', () => { describe('disconnect', () => {
const originalCwd = process.cwd(); const originalCwd = process.cwd();

View File

@@ -4,31 +4,58 @@ import os from 'os';
import { getWriteableDirectory } from '@vercel/build-utils'; import { getWriteableDirectory } from '@vercel/build-utils';
import { import {
createGitMeta, createGitMeta,
getRemoteUrl, getOriginUrl,
getRemoteUrls,
isDirty, isDirty,
} from '../../../../src/util/create-git-meta'; } from '../../../../src/util/create-git-meta';
import { client } from '../../../mocks/client'; import { client } from '../../../mocks/client';
import { parseRepoUrl } from '../../../../src/util/projects/connect-git-provider'; import { parseRepoUrl } from '../../../../src/util/projects/connect-git-provider';
import { readOutputStream } from '../../../helpers/read-output-stream'; import { readOutputStream } from '../../../helpers/read-output-stream';
import { useUser } from '../../../mocks/user';
import { defaultProject, useProject } from '../../../mocks/project';
import { Project } from '../../../../src/types';
const fixture = (name: string) => const fixture = (name: string) =>
join(__dirname, '../../../fixtures/unit/create-git-meta', name); join(__dirname, '../../../fixtures/unit/create-git-meta', name);
describe('getRemoteUrl', () => { describe('getOriginUrl', () => {
it('does not provide data for no-origin', async () => { it('does not provide data for no-origin', async () => {
const configPath = join(fixture('no-origin'), 'git/config'); const configPath = join(fixture('no-origin'), 'git/config');
const data = await getRemoteUrl(configPath, client.output); const data = await getOriginUrl(configPath, client.output);
expect(data).toBeNull(); expect(data).toBeNull();
}); });
it('displays debug message when repo data cannot be parsed', async () => { it('displays debug message when repo data cannot be parsed', async () => {
const dir = await getWriteableDirectory(); const dir = await getWriteableDirectory();
client.output.debugEnabled = true; client.output.debugEnabled = true;
const data = await getRemoteUrl(join(dir, 'git/config'), client.output); const data = await getOriginUrl(join(dir, 'git/config'), client.output);
expect(data).toBeNull(); expect(data).toBeNull();
await expect(client.stderr).toOutput('Error while parsing repo data'); await expect(client.stderr).toOutput('Error while parsing repo data');
}); });
}); });
describe('getRemoteUrls', () => {
it('does not provide data when there are no remote urls', async () => {
const configPath = join(fixture('no-origin'), 'git/config');
const data = await getRemoteUrls(configPath, client.output);
expect(data).toBeUndefined();
});
it('returns an object when multiple urls are present', async () => {
const configPath = join(fixture('multiple-remotes'), 'git/config');
const data = await getRemoteUrls(configPath, client.output);
expect(data).toMatchObject({
origin: 'https://github.com/user/repo',
secondary: 'https://github.com/user/repo2',
});
});
it('returns an object for origin url', async () => {
const configPath = join(fixture('test-github'), 'git/config');
const data = await getRemoteUrls(configPath, client.output);
expect(data).toMatchObject({
origin: 'https://github.com/user/repo.git',
});
});
});
describe('parseRepoUrl', () => { describe('parseRepoUrl', () => {
it('should be null when a url does not match the regex', () => { it('should be null when a url does not match the regex', () => {
const parsedUrl = parseRepoUrl('https://examplecom/foo'); const parsedUrl = parseRepoUrl('https://examplecom/foo');
@@ -244,4 +271,45 @@ describe('createGitMeta', () => {
await fs.remove(tmpDir); await fs.remove(tmpDir);
} }
}); });
it('uses the repo url for a connected project', async () => {
const originalCwd = process.cwd();
const directory = fixture('connected-repo');
try {
process.chdir(directory);
await fs.rename(join(directory, 'git'), join(directory, '.git'));
useUser();
const project = useProject({
...defaultProject,
id: 'connected-repo',
name: 'connected-repo',
});
project.project.link = {
type: 'github',
repo: 'user/repo2',
repoId: 1010,
gitCredentialId: '',
sourceless: true,
createdAt: 1656109539791,
updatedAt: 1656109539791,
};
const data = await createGitMeta(
directory,
client.output,
project.project as Project
);
expect(data).toMatchObject({
remoteUrl: 'https://github.com/user/repo2',
commitAuthorName: 'Matthew Stanciu',
commitMessage: 'add hi',
commitRef: 'master',
commitSha: '8050816205303e5957b2909083c50677930d5b29',
dirty: true,
});
} finally {
await fs.rename(join(directory, '.git'), join(directory, 'git'));
process.chdir(originalCwd);
}
});
}); });

View File

@@ -6,19 +6,25 @@ import {
} from '../../../../src/util/dev/builder-cache'; } from '../../../../src/util/dev/builder-cache';
describe('filterPackage', () => { describe('filterPackage', () => {
it('should filter install "latest", cached canary', () => { const cliPkg = {
dependencies: {
'@vercel/build-utils': '0.0.1',
},
};
it('should filter package that does not appear in CLI package.json', () => {
const result = filterPackage('@vercel/other', {}, cliPkg);
expect(result).toEqual(true);
});
it('should not filter "latest", cached canary', () => {
const buildersPkg = { const buildersPkg = {
dependencies: { dependencies: {
'@vercel/build-utils': '0.0.1-canary.0', '@vercel/build-utils': '0.0.1-canary.0',
}, },
}; };
const result = filterPackage( const result = filterPackage('@vercel/build-utils', buildersPkg, cliPkg);
'@vercel/build-utils', expect(result).toEqual(false);
'canary',
buildersPkg,
{}
);
expect(result).toEqual(true);
}); });
it('should filter install "canary", cached stable', () => { it('should filter install "canary", cached stable', () => {
@@ -29,11 +35,10 @@ describe('filterPackage', () => {
}; };
const result = filterPackage( const result = filterPackage(
'@vercel/build-utils@canary', '@vercel/build-utils@canary',
'latest',
buildersPkg, buildersPkg,
{} cliPkg
); );
expect(result).toEqual(true); expect(result).toEqual(false);
}); });
it('should filter install "latest", cached stable', () => { it('should filter install "latest", cached stable', () => {
@@ -42,12 +47,7 @@ describe('filterPackage', () => {
'@vercel/build-utils': '0.0.1', '@vercel/build-utils': '0.0.1',
}, },
}; };
const result = filterPackage( const result = filterPackage('@vercel/build-utils', buildersPkg, cliPkg);
'@vercel/build-utils',
'latest',
buildersPkg,
{}
);
expect(result).toEqual(false); expect(result).toEqual(false);
}); });
@@ -59,9 +59,8 @@ describe('filterPackage', () => {
}; };
const result = filterPackage( const result = filterPackage(
'@vercel/build-utils@canary', '@vercel/build-utils@canary',
'canary',
buildersPkg, buildersPkg,
{} cliPkg
); );
expect(result).toEqual(false); expect(result).toEqual(false);
}); });
@@ -72,12 +71,7 @@ describe('filterPackage', () => {
'@vercel/build-utils': '0.0.1', '@vercel/build-utils': '0.0.1',
}, },
}; };
const result = filterPackage( const result = filterPackage('https://tarball.now.sh', buildersPkg, cliPkg);
'https://tarball.now.sh',
'latest',
buildersPkg,
{}
);
expect(result).toEqual(true); expect(result).toEqual(true);
}); });
@@ -87,27 +81,7 @@ describe('filterPackage', () => {
'@vercel/build-utils': '0.0.1-canary.0', '@vercel/build-utils': '0.0.1-canary.0',
}, },
}; };
const result = filterPackage( const result = filterPackage('https://tarball.now.sh', buildersPkg, cliPkg);
'https://tarball.now.sh',
'canary',
buildersPkg,
{}
);
expect(result).toEqual(true);
});
it('should filter install "latest", cached URL - stable', () => {
const buildersPkg = {
dependencies: {
'@vercel/build-utils': 'https://tarball.now.sh',
},
};
const result = filterPackage(
'@vercel/build-utils',
'latest',
buildersPkg,
{}
);
expect(result).toEqual(true); expect(result).toEqual(true);
}); });
@@ -117,13 +91,8 @@ describe('filterPackage', () => {
'@vercel/build-utils': 'https://tarball.now.sh', '@vercel/build-utils': 'https://tarball.now.sh',
}, },
}; };
const result = filterPackage( const result = filterPackage('@vercel/build-utils', buildersPkg, cliPkg);
'@vercel/build-utils', expect(result).toEqual(false);
'canary',
buildersPkg,
{}
);
expect(result).toEqual(true);
}); });
it('should filter install not bundled version, cached same version', () => { it('should filter install not bundled version, cached same version', () => {
@@ -134,9 +103,8 @@ describe('filterPackage', () => {
}; };
const result = filterPackage( const result = filterPackage(
'not-bundled-package@0.0.1', 'not-bundled-package@0.0.1',
'_',
buildersPkg, buildersPkg,
{} cliPkg
); );
expect(result).toEqual(false); expect(result).toEqual(false);
}); });
@@ -149,9 +117,8 @@ describe('filterPackage', () => {
}; };
const result = filterPackage( const result = filterPackage(
'not-bundled-package@0.0.1', 'not-bundled-package@0.0.1',
'_',
buildersPkg, buildersPkg,
{} cliPkg
); );
expect(result).toEqual(true); expect(result).toEqual(true);
}); });
@@ -162,7 +129,7 @@ describe('filterPackage', () => {
'not-bundled-package': '0.0.1', 'not-bundled-package': '0.0.1',
}, },
}; };
const result = filterPackage('not-bundled-package', '_', buildersPkg, {}); const result = filterPackage('not-bundled-package', buildersPkg, cliPkg);
expect(result).toEqual(true); expect(result).toEqual(true);
}); });
@@ -174,9 +141,8 @@ describe('filterPackage', () => {
}; };
const result = filterPackage( const result = filterPackage(
'not-bundled-package@alpha', 'not-bundled-package@alpha',
'_',
buildersPkg, buildersPkg,
{} cliPkg
); );
expect(result).toEqual(true); expect(result).toEqual(true);
}); });
@@ -213,89 +179,57 @@ describe('getBuildUtils', () => {
}); });
describe('isBundledBuilder', () => { describe('isBundledBuilder', () => {
it('should work with "stable" releases', () => {
const cliPkg = { const cliPkg = {
dependencies: { dependencies: {
'@vercel/node': '1.6.1', '@vercel/node': '0.0.1',
}, },
}; };
// "canary" tag it('should not detect when dependency does not appear in CLI package.json', () => {
{
const parsed = npa('@vercel/node@canary');
const result = isBundledBuilder(parsed, cliPkg);
expect(result).toEqual(false);
}
// "latest" tag
{
const parsed = npa('@vercel/node'); const parsed = npa('@vercel/node');
const result = isBundledBuilder(parsed, cliPkg); const result = isBundledBuilder(parsed, {});
expect(result).toEqual(true);
}
// specific matching version
{
const parsed = npa('@vercel/node@1.6.1');
const result = isBundledBuilder(parsed, cliPkg);
expect(result).toEqual(true);
}
// specific non-matching version
{
const parsed = npa('@vercel/node@1.6.0');
const result = isBundledBuilder(parsed, cliPkg);
expect(result).toEqual(false); expect(result).toEqual(false);
}
// URL
{
const parsed = npa('https://example.com');
const result = isBundledBuilder(parsed, cliPkg);
expect(result).toEqual(false);
}
}); });
it('should work with "canary" releases', () => { it('should detect "canary" tagged releases', () => {
const cliPkg = {
dependencies: {
'@vercel/node': '1.6.1-canary.0',
},
};
// "canary" tag
{
const parsed = npa('@vercel/node@canary'); const parsed = npa('@vercel/node@canary');
const result = isBundledBuilder(parsed, cliPkg); const result = isBundledBuilder(parsed, cliPkg);
expect(result).toEqual(true); expect(result).toEqual(true);
} });
// "latest" tag it('should detect "canary" versioned releases', () => {
{
const parsed = npa('@vercel/node');
const result = isBundledBuilder(parsed, cliPkg);
expect(result).toEqual(false);
}
// specific matching version
{
const parsed = npa('@vercel/node@1.6.1-canary.0'); const parsed = npa('@vercel/node@1.6.1-canary.0');
const result = isBundledBuilder(parsed, cliPkg); const result = isBundledBuilder(parsed, cliPkg);
expect(result).toEqual(true); expect(result).toEqual(true);
} });
// specific non-matching version it('should detect latest releases', () => {
{ const parsed = npa('@vercel/node');
const parsed = npa('@vercel/node@1.5.2-canary.9');
const result = isBundledBuilder(parsed, cliPkg); const result = isBundledBuilder(parsed, cliPkg);
expect(result).toEqual(false); expect(result).toEqual(true);
} });
// URL it('should detect "latest" tagged releases', () => {
{ const parsed = npa('@vercel/node@latest');
const result = isBundledBuilder(parsed, cliPkg);
expect(result).toEqual(true);
});
it('should detect versioned releases', () => {
const parsed = npa('@vercel/node@1.6.1');
const result = isBundledBuilder(parsed, cliPkg);
expect(result).toEqual(true);
});
it('should NOT detect URL releases', () => {
const parsed = npa('https://example.com'); const parsed = npa('https://example.com');
const result = isBundledBuilder(parsed, cliPkg); const result = isBundledBuilder(parsed, cliPkg);
expect(result).toEqual(false); expect(result).toEqual(false);
} });
it('should NOT detect git url releases', () => {
const parsed = npa('git://example.com/repo.git');
const result = isBundledBuilder(parsed, cliPkg);
expect(result).toEqual(false);
}); });
}); });

View File

@@ -2,7 +2,11 @@ import fetch from 'node-fetch';
import listen from 'async-listen'; import listen from 'async-listen';
import { createServer, IncomingMessage, Server, ServerResponse } from 'http'; import { createServer, IncomingMessage, Server, ServerResponse } from 'http';
import { JSONValue } from '../../../src/types'; import { JSONValue } from '../../../src/types';
import { responseError, responseErrorMessage } from '../../../src/util/error'; import {
responseError,
responseErrorMessage,
toEnumerableError,
} from '../../../src/util/error';
const send = (res: ServerResponse, statusCode: number, body: JSONValue) => { const send = (res: ServerResponse, statusCode: number, body: JSONValue) => {
res.statusCode = statusCode; res.statusCode = statusCode;
@@ -10,7 +14,7 @@ const send = (res: ServerResponse, statusCode: number, body: JSONValue) => {
res.end(JSON.stringify(body)); res.end(JSON.stringify(body));
}; };
describe('responseError', () => { describe('responseError()', () => {
let url: string; let url: string;
let server: Server; let server: Server;
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -187,3 +191,43 @@ describe('responseError', () => {
expect(formatted.retryAfter).toEqual(undefined); expect(formatted.retryAfter).toEqual(undefined);
}); });
}); });
describe('toEnumerableError()', () => {
it('should JSON stringify Error', () => {
const err = new Error('An error');
const enumerable = toEnumerableError(err);
expect(JSON.stringify(err)).toEqual('{}');
// Delete `stack` since it makes stringify nondeterministic
// (due to filenames / line numbers)
expect(typeof enumerable.stack).toEqual('string');
delete enumerable.stack;
expect(JSON.stringify(enumerable)).toEqual(
'{"name":"Error","message":"An error"}'
);
});
it('should JSON stringify Error with custom properties', () => {
const err = new Error('An error');
Object.defineProperty(err, 'custom', {
enumerable: false,
value: 'value',
});
Object.defineProperty(err, 'userError', {
enumerable: false,
value: true,
});
const enumerable = toEnumerableError(err);
expect(JSON.stringify(err)).toEqual('{}');
// Delete `stack` since it makes stringify undeterministinc
// (due to filenames / line numbers)
expect(typeof enumerable.stack).toEqual('string');
delete enumerable.stack;
expect(JSON.stringify(enumerable)).toEqual(
'{"name":"Error","message":"An error","custom":"value","userError":true}'
);
});
});

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/client", "name": "@vercel/client",
"version": "12.1.2", "version": "12.1.3",
"main": "dist/index.js", "main": "dist/index.js",
"typings": "dist/index.d.ts", "typings": "dist/index.d.ts",
"homepage": "https://vercel.com", "homepage": "https://vercel.com",
@@ -42,7 +42,7 @@
] ]
}, },
"dependencies": { "dependencies": {
"@vercel/build-utils": "5.0.3", "@vercel/build-utils": "5.0.4",
"@vercel/routing-utils": "2.0.0", "@vercel/routing-utils": "2.0.0",
"@zeit/fetch": "5.2.0", "@zeit/fetch": "5.2.0",
"async-retry": "1.2.3", "async-retry": "1.2.3",
@@ -51,7 +51,7 @@
"ignore": "4.0.6", "ignore": "4.0.6",
"minimatch": "5.0.1", "minimatch": "5.0.1",
"ms": "2.1.2", "ms": "2.1.2",
"node-fetch": "2.6.1", "node-fetch": "2.6.7",
"querystring": "^0.2.0", "querystring": "^0.2.0",
"sleep-promise": "8.0.1" "sleep-promise": "8.0.1"
} }

View File

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

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/hydrogen", "name": "@vercel/hydrogen",
"version": "0.0.4", "version": "0.0.5",
"license": "MIT", "license": "MIT",
"main": "./dist/index.js", "main": "./dist/index.js",
"homepage": "https://vercel.com/docs", "homepage": "https://vercel.com/docs",
@@ -22,7 +22,7 @@
"devDependencies": { "devDependencies": {
"@types/jest": "27.5.1", "@types/jest": "27.5.1",
"@types/node": "*", "@types/node": "*",
"@vercel/build-utils": "5.0.3", "@vercel/build-utils": "5.0.4",
"typescript": "4.6.4" "typescript": "4.6.4"
} }
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/next", "name": "@vercel/next",
"version": "3.1.7", "version": "3.1.8",
"license": "MIT", "license": "MIT",
"main": "./dist/index", "main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/next-js", "homepage": "https://vercel.com/docs/runtimes#official-runtimes/next-js",
@@ -45,7 +45,7 @@
"@types/semver": "6.0.0", "@types/semver": "6.0.0",
"@types/text-table": "0.2.1", "@types/text-table": "0.2.1",
"@types/webpack-sources": "3.2.0", "@types/webpack-sources": "3.2.0",
"@vercel/build-utils": "5.0.3", "@vercel/build-utils": "5.0.4",
"@vercel/nft": "0.20.1", "@vercel/nft": "0.20.1",
"@vercel/routing-utils": "2.0.0", "@vercel/routing-utils": "2.0.0",
"async-sema": "3.0.1", "async-sema": "3.0.1",

View File

@@ -57,6 +57,7 @@ const CORRECT_NOT_FOUND_ROUTES_VERSION = 'v12.0.1';
const CORRECT_MIDDLEWARE_ORDER_VERSION = 'v12.1.7-canary.29'; const CORRECT_MIDDLEWARE_ORDER_VERSION = 'v12.1.7-canary.29';
const NEXT_DATA_MIDDLEWARE_RESOLVING_VERSION = 'v12.1.7-canary.33'; const NEXT_DATA_MIDDLEWARE_RESOLVING_VERSION = 'v12.1.7-canary.33';
const EMPTY_ALLOW_QUERY_FOR_PRERENDERED_VERSION = 'v12.2.0'; const EMPTY_ALLOW_QUERY_FOR_PRERENDERED_VERSION = 'v12.2.0';
const CORRECTED_MANIFESTS_VERSION = 'v12.2.0';
export async function serverBuild({ export async function serverBuild({
dynamicPages, dynamicPages,
@@ -146,6 +147,10 @@ export async function serverBuild({
nextVersion, nextVersion,
CORRECT_MIDDLEWARE_ORDER_VERSION CORRECT_MIDDLEWARE_ORDER_VERSION
); );
const isCorrectManifests = semver.gte(
nextVersion,
CORRECTED_MANIFESTS_VERSION
);
let hasStatic500 = !!staticPages[path.join(entryDirectory, '500')]; let hasStatic500 = !!staticPages[path.join(entryDirectory, '500')];
if (lambdaPageKeys.length === 0) { if (lambdaPageKeys.length === 0) {
@@ -409,7 +414,7 @@ export async function serverBuild({
fsPath = path.join(requiredServerFilesManifest.appDir, file); fsPath = path.join(requiredServerFilesManifest.appDir, file);
} }
const relativePath = path.join(path.relative(baseDir, fsPath)); const relativePath = path.relative(baseDir, fsPath);
const { mode } = await fs.lstat(fsPath); const { mode } = await fs.lstat(fsPath);
lstatSema.release(); lstatSema.release();
@@ -676,18 +681,80 @@ export async function serverBuild({
); );
for (const group of combinedGroups) { for (const group of combinedGroups) {
const lambda = await createLambdaFromPseudoLayers({ const groupPageFiles: { [key: string]: PseudoFile } = {};
files: launcherFiles,
layers: [ for (const page of [...group.pages, ...internalPages]) {
group.pseudoLayer,
[...group.pages, ...internalPages].reduce((prev, page) => {
const pageFileName = path.normalize( const pageFileName = path.normalize(
path.relative(baseDir, lambdaPages[page].fsPath) path.relative(baseDir, lambdaPages[page].fsPath)
); );
prev[pageFileName] = compressedPages[page]; groupPageFiles[pageFileName] = compressedPages[page];
return prev; }
}, {} as { [key: string]: PseudoFile }),
], const updatedManifestFiles: { [name: string]: FileBlob } = {};
if (isCorrectManifests) {
// filter dynamic routes to only the included dynamic routes
// in this specific serverless function so that we don't
// accidentally match a dynamic route while resolving that
// is not actually in this specific serverless function
for (const manifest of [
'routes-manifest.json',
'server/pages-manifest.json',
] as const) {
const fsPath = path.join(entryPath, outputDirectory, manifest);
const relativePath = path.relative(baseDir, fsPath);
delete group.pseudoLayer[relativePath];
const manifestData = await fs.readJSON(fsPath);
const normalizedPages = new Set(
group.pages.map(page => {
page = `/${page.replace(/\.js$/, '')}`;
if (page === '/index') page = '/';
return page;
})
);
switch (manifest) {
case 'routes-manifest.json': {
const filterItem = (item: { page: string }) =>
normalizedPages.has(item.page);
manifestData.dynamicRoutes =
manifestData.dynamicRoutes?.filter(filterItem);
manifestData.staticRoutes =
manifestData.staticRoutes?.filter(filterItem);
break;
}
case 'server/pages-manifest.json': {
for (const key of Object.keys(manifestData)) {
if (isDynamicRoute(key) && !normalizedPages.has(key)) {
delete manifestData[key];
}
}
break;
}
default: {
throw new NowBuildError({
message: `Unexpected manifest value ${manifest}, please contact support if this continues`,
code: 'NEXT_MANIFEST_INVARIANT',
});
}
}
updatedManifestFiles[relativePath] = new FileBlob({
contentType: 'application/json',
data: JSON.stringify(manifestData),
});
}
}
const lambda = await createLambdaFromPseudoLayers({
files: {
...launcherFiles,
...updatedManifestFiles,
},
layers: [group.pseudoLayer, groupPageFiles],
handler: path.join( handler: path.join(
path.relative( path.relative(
baseDir, baseDir,

View File

@@ -0,0 +1,8 @@
const path = require('path');
const { deployAndTest } = require('../../utils');
describe(`${__dirname.split(path.sep).pop()}`, () => {
it('should deploy and pass probe checks', async () => {
await deployAndTest(__dirname);
});
});

View File

@@ -0,0 +1,13 @@
{
"dependencies": {
"next": "canary",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
}
}

View File

@@ -0,0 +1,17 @@
export default function Page(props) {
return (
<>
<p>/[...slug] page</p>
<p>{JSON.stringify(props)}</p>
</>
);
}
export const getServerSideProps = async ({ params }) => {
return {
props: {
params,
page: '[...slug]',
},
};
};

View File

@@ -0,0 +1,29 @@
export default function Page(props) {
return (
<>
<p>/[slug] page</p>
<p>{JSON.stringify(props)}</p>
</>
);
}
export const getStaticProps = async ({ params }) => {
return {
props: {
params,
page: '[slug]',
now: Date.now(),
},
};
};
export const getStaticPaths = async () => {
return {
paths: [
{ params: { slug: 'static-1' } },
{ params: { slug: 'static-2' } },
{ params: { slug: 'static-3' } },
],
fallback: false,
};
};

View File

@@ -0,0 +1,17 @@
export default function Page(props) {
return (
<>
<p>index page</p>
<p>{JSON.stringify(props)}</p>
</>
);
}
export const getStaticProps = async () => {
return {
props: {
page: 'index',
now: Date.now(),
},
};
};

View File

@@ -0,0 +1,36 @@
{
"version": 2,
"builds": [{ "src": "package.json", "use": "@vercel/next" }],
"probes": [
{
"path": "/",
"status": 200,
"mustContain": "index page"
},
{
"path": "/static-1",
"status": 200,
"mustContain": "[slug] page"
},
{
"path": "/static-2",
"status": 200,
"mustContain": "[slug] page"
},
{
"path": "/dynamic-1",
"status": 200,
"mustContain": "[...slug] page"
},
{
"path": "/dynamic-2",
"status": 200,
"mustContain": "[...slug] page"
},
{
"path": "/long/dynamic",
"status": 200,
"mustContain": "[...slug] page"
}
]
}

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/node", "name": "@vercel/node",
"version": "2.4.4", "version": "2.4.5",
"license": "MIT", "license": "MIT",
"main": "./dist/index", "main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js", "homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
@@ -31,13 +31,13 @@
}, },
"dependencies": { "dependencies": {
"@types/node": "*", "@types/node": "*",
"@vercel/build-utils": "5.0.3", "@vercel/build-utils": "5.0.4",
"@vercel/node-bridge": "3.0.0", "@vercel/node-bridge": "3.0.0",
"@vercel/static-config": "2.0.1", "@vercel/static-config": "2.0.1",
"edge-runtime": "1.0.1", "edge-runtime": "1.0.1",
"esbuild": "0.14.47", "esbuild": "0.14.47",
"exit-hook": "2.2.1", "exit-hook": "2.2.1",
"node-fetch": "2.6.1", "node-fetch": "2.6.7",
"ts-node": "8.9.1", "ts-node": "8.9.1",
"typescript": "4.3.4" "typescript": "4.3.4"
}, },

View File

@@ -148,21 +148,26 @@ async function serializeRequest(message: IncomingMessage) {
}); });
} }
async function compileUserCode(entrypoint: string) { async function compileUserCode(
entrypointPath: string,
entrypointLabel: string
) {
try { try {
const result = await esbuild.build({ const result = await esbuild.build({
platform: 'node', platform: 'node',
target: 'node14', target: 'node14',
sourcemap: 'inline', sourcemap: 'inline',
bundle: true, bundle: true,
entryPoints: [entrypoint], entryPoints: [entrypointPath],
write: false, // operate in memory write: false, // operate in memory
format: 'cjs', format: 'cjs',
}); });
const compiledFile = result.outputFiles?.[0]; const compiledFile = result.outputFiles?.[0];
if (!compiledFile) { if (!compiledFile) {
throw new Error(`Compilation of ${entrypoint} produced no output files.`); throw new Error(
`Compilation of ${entrypointLabel} produced no output files.`
);
} }
const userCode = new TextDecoder().decode(compiledFile.contents); const userCode = new TextDecoder().decode(compiledFile.contents);
@@ -198,6 +203,10 @@ async function compileUserCode(entrypoint: string) {
let response = await edgeHandler(event.request, event); let response = await edgeHandler(event.request, event);
if (!response) {
throw new Error('Edge Function "${entrypointLabel}" did not return a response.');
}
return event.respondWith(response); return event.respondWith(response);
} catch (error) { } catch (error) {
// we can't easily show a meaningful stack trace // we can't easily show a meaningful stack trace
@@ -233,6 +242,9 @@ async function createEdgeRuntime(userCode: string | undefined) {
module: { module: {
exports: {}, exports: {},
}, },
process: {
env: process.env,
},
}); });
return context; return context;
}, },
@@ -252,9 +264,10 @@ async function createEdgeRuntime(userCode: string | undefined) {
} }
async function createEdgeEventHandler( async function createEdgeEventHandler(
entrypoint: string entrypointPath: string,
entrypointLabel: string
): Promise<(request: IncomingMessage) => Promise<VercelProxyResponse>> { ): Promise<(request: IncomingMessage) => Promise<VercelProxyResponse>> {
const userCode = await compileUserCode(entrypoint); const userCode = await compileUserCode(entrypointPath, entrypointLabel);
const server = await createEdgeRuntime(userCode); const server = await createEdgeRuntime(userCode);
return async function (request: IncomingMessage) { return async function (request: IncomingMessage) {
@@ -317,17 +330,17 @@ async function createEventHandler(
config: Config, config: Config,
options: { shouldAddHelpers: boolean } options: { shouldAddHelpers: boolean }
): Promise<(request: IncomingMessage) => Promise<VercelProxyResponse>> { ): Promise<(request: IncomingMessage) => Promise<VercelProxyResponse>> {
const entryPointPath = join(process.cwd(), entrypoint!); const entrypointPath = join(process.cwd(), entrypoint!);
const runtime = parseRuntime(entrypoint, entryPointPath); const runtime = parseRuntime(entrypoint, entrypointPath);
// `middleware.js`/`middleware.ts` file is always run as // `middleware.js`/`middleware.ts` file is always run as
// an Edge Function, otherwise needs to be opted-in via // an Edge Function, otherwise needs to be opted-in via
// `export const config = { runtime: 'experimental-edge' }` // `export const config = { runtime: 'experimental-edge' }`
if (config.middleware === true || runtime === 'experimental-edge') { if (config.middleware === true || runtime === 'experimental-edge') {
return createEdgeEventHandler(entryPointPath); return createEdgeEventHandler(entrypointPath, entrypoint);
} }
return createServerlessEventHandler(entryPointPath, options); return createServerlessEventHandler(entrypointPath, options);
} }
let handleEvent: (request: IncomingMessage) => Promise<VercelProxyResponse>; let handleEvent: (request: IncomingMessage) => Promise<VercelProxyResponse>;

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/python", "name": "@vercel/python",
"version": "3.0.7", "version": "3.1.0",
"main": "./dist/index.js", "main": "./dist/index.js",
"license": "MIT", "license": "MIT",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/python", "homepage": "https://vercel.com/docs/runtimes#official-runtimes/python",
@@ -23,7 +23,7 @@
"devDependencies": { "devDependencies": {
"@types/execa": "^0.9.0", "@types/execa": "^0.9.0",
"@types/jest": "27.4.1", "@types/jest": "27.4.1",
"@vercel/build-utils": "5.0.3", "@vercel/build-utils": "5.0.4",
"@vercel/ncc": "0.24.0", "@vercel/ncc": "0.24.0",
"execa": "^1.0.0", "execa": "^1.0.0",
"typescript": "4.3.4" "typescript": "4.3.4"

View File

@@ -9,4 +9,4 @@ verify_ssl = true
flask = "*" flask = "*"
[requires] [requires]
python_version = "3.6" python_version = "3.9"

View File

@@ -1,11 +1,11 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "8ec50e78e90ad609e540d41d1ed90f3fb880ffbdf6049b0a6b2f1a00158a3288" "sha256": "f7f1cea682a03d85328caf2f88382c4380283d3892a9ba31b374784fb29536c4"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
"python_version": "3.6" "python_version": "3.9"
}, },
"sources": [ "sources": [
{ {
@@ -18,72 +18,105 @@
"default": { "default": {
"click": { "click": {
"hashes": [ "hashes": [
"sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e",
"sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"
], ],
"version": "==7.0" "markers": "python_version >= '3.7'",
"version": "==8.1.3"
}, },
"flask": { "flask": {
"hashes": [ "hashes": [
"sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48", "sha256:15972e5017df0575c3d6c090ba168b6db90259e620ac8d7ea813a396bad5b6cb",
"sha256:a080b744b7e345ccfcbc77954861cb05b3c63786e93f2b3875e0913d44b43f05" "sha256:9013281a7402ad527f8fd56375164f3aa021ecfaff89bfe3825346c24f87e04c"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.0.2" "version": "==2.1.3"
},
"importlib-metadata": {
"hashes": [
"sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670",
"sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"
],
"markers": "python_version < '3.10'",
"version": "==4.12.0"
}, },
"itsdangerous": { "itsdangerous": {
"hashes": [ "hashes": [
"sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44",
"sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"
], ],
"version": "==1.1.0" "markers": "python_version >= '3.7'",
"version": "==2.1.2"
}, },
"jinja2": { "jinja2": {
"hashes": [ "hashes": [
"sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852",
"sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b" "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"
], ],
"version": "==2.10.1" "markers": "python_version >= '3.7'",
"version": "==3.1.2"
}, },
"markupsafe": { "markupsafe": {
"hashes": [ "hashes": [
"sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003",
"sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88",
"sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5",
"sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7",
"sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a",
"sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603",
"sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1",
"sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135",
"sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247",
"sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6",
"sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601",
"sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77",
"sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02",
"sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e",
"sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63",
"sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f",
"sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980",
"sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b",
"sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812",
"sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff",
"sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96",
"sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1",
"sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925",
"sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a",
"sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6",
"sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e",
"sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f",
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4",
"sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f",
"sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3",
"sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c",
"sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a",
"sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417",
"sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a",
"sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a",
"sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37",
"sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452",
"sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933",
"sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a",
"sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"
], ],
"version": "==1.1.1" "markers": "python_version >= '3.7'",
"version": "==2.1.1"
}, },
"werkzeug": { "werkzeug": {
"hashes": [ "hashes": [
"sha256:0a73e8bb2ff2feecfc5d56e6f458f5b99290ef34f565ffb2665801ff7de6af7a", "sha256:1ce08e8093ed67d638d63879fd1ba3735817f7a80de3674d293f5984f25fb6e6",
"sha256:7fad9770a8778f9576693f0cc29c7dcc36964df916b83734f4431c0e612a7fbc" "sha256:72a4b735692dd3135217911cbeaa1be5fa3f62bffb8745c5215420a03dc55255"
], ],
"version": "==0.15.2" "markers": "python_version >= '3.7'",
"version": "==2.1.2"
},
"zipp": {
"hashes": [
"sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2",
"sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009"
],
"markers": "python_version >= '3.7'",
"version": "==3.8.1"
} }
}, },
"develop": {} "develop": {}

View File

@@ -4,8 +4,7 @@
"probes": [ "probes": [
{ {
"path": "/", "path": "/",
"mustContain": "wsgi:RANDOMNESS_PLACEHOLDER", "mustContain": "wsgi:RANDOMNESS_PLACEHOLDER"
"logMustContain": "Python version \"3.6\" detected in Pipfile.lock has reached End-of-Life. Deployments created on or after 2022-07-18 will fail to build"
} }
] ]
} }

View File

@@ -9,4 +9,4 @@ verify_ssl = true
flask = "*" flask = "*"
[requires] [requires]
python_version = "3.6" python_version = "3.9"

View File

@@ -1,11 +1,11 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "8ec50e78e90ad609e540d41d1ed90f3fb880ffbdf6049b0a6b2f1a00158a3288" "sha256": "f7f1cea682a03d85328caf2f88382c4380283d3892a9ba31b374784fb29536c4"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
"python_version": "3.6" "python_version": "3.9"
}, },
"sources": [ "sources": [
{ {
@@ -18,72 +18,105 @@
"default": { "default": {
"click": { "click": {
"hashes": [ "hashes": [
"sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e",
"sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"
], ],
"version": "==7.0" "markers": "python_version >= '3.7'",
"version": "==8.1.3"
}, },
"flask": { "flask": {
"hashes": [ "hashes": [
"sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48", "sha256:15972e5017df0575c3d6c090ba168b6db90259e620ac8d7ea813a396bad5b6cb",
"sha256:a080b744b7e345ccfcbc77954861cb05b3c63786e93f2b3875e0913d44b43f05" "sha256:9013281a7402ad527f8fd56375164f3aa021ecfaff89bfe3825346c24f87e04c"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.0.2" "version": "==2.1.3"
},
"importlib-metadata": {
"hashes": [
"sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670",
"sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"
],
"markers": "python_version < '3.10'",
"version": "==4.12.0"
}, },
"itsdangerous": { "itsdangerous": {
"hashes": [ "hashes": [
"sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44",
"sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"
], ],
"version": "==1.1.0" "markers": "python_version >= '3.7'",
"version": "==2.1.2"
}, },
"jinja2": { "jinja2": {
"hashes": [ "hashes": [
"sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852",
"sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b" "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"
], ],
"version": "==2.10.1" "markers": "python_version >= '3.7'",
"version": "==3.1.2"
}, },
"markupsafe": { "markupsafe": {
"hashes": [ "hashes": [
"sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003",
"sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88",
"sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5",
"sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7",
"sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a",
"sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603",
"sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1",
"sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135",
"sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247",
"sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6",
"sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601",
"sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77",
"sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02",
"sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e",
"sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63",
"sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f",
"sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980",
"sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b",
"sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812",
"sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff",
"sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96",
"sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1",
"sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925",
"sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a",
"sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6",
"sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e",
"sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f",
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4",
"sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f",
"sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3",
"sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c",
"sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a",
"sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417",
"sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a",
"sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a",
"sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37",
"sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452",
"sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933",
"sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a",
"sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"
], ],
"version": "==1.1.1" "markers": "python_version >= '3.7'",
"version": "==2.1.1"
}, },
"werkzeug": { "werkzeug": {
"hashes": [ "hashes": [
"sha256:0a73e8bb2ff2feecfc5d56e6f458f5b99290ef34f565ffb2665801ff7de6af7a", "sha256:1ce08e8093ed67d638d63879fd1ba3735817f7a80de3674d293f5984f25fb6e6",
"sha256:7fad9770a8778f9576693f0cc29c7dcc36964df916b83734f4431c0e612a7fbc" "sha256:72a4b735692dd3135217911cbeaa1be5fa3f62bffb8745c5215420a03dc55255"
], ],
"version": "==0.15.2" "markers": "python_version >= '3.7'",
"version": "==2.1.2"
},
"zipp": {
"hashes": [
"sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2",
"sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009"
],
"markers": "python_version >= '3.7'",
"version": "==3.8.1"
} }
}, },
"develop": {} "develop": {}

View File

@@ -7,8 +7,7 @@
"probes": [ "probes": [
{ {
"path": "/", "path": "/",
"mustContain": "RANDOMNESS_PLACEHOLDER:env", "mustContain": "RANDOMNESS_PLACEHOLDER:env"
"logMustContain": "Python version \"3.6\" detected in Pipfile.lock has reached End-of-Life. Deployments created on or after 2022-07-18 will fail to build"
} }
] ]
} }

View File

@@ -9,4 +9,4 @@ verify_ssl = true
django = "*" django = "*"
[requires] [requires]
python_version = "3.6" python_version = "3.9"

View File

@@ -1,11 +1,11 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "68309cd71a258c30a39567fce09a09ad5c4ff0bdc85b6fba22b47598c985c883" "sha256": "c36ae28fea7b9a4cc02145632e2f41469af2e7b38b801903abb8333d3306f36b"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
"python_version": "3.6" "python_version": "3.9"
}, },
"sources": [ "sources": [
{ {
@@ -16,27 +16,29 @@
] ]
}, },
"default": { "default": {
"asgiref": {
"hashes": [
"sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4",
"sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"
],
"markers": "python_version >= '3.7'",
"version": "==3.5.2"
},
"django": { "django": {
"hashes": [ "hashes": [
"sha256:7c3543e4fb070d14e10926189a7fcf42ba919263b7473dceaefce34d54e8a119", "sha256:a67a793ff6827fd373555537dca0da293a63a316fe34cb7f367f898ccca3c3ae",
"sha256:a2814bffd1f007805b19194eb0b9a331933b82bd5da1c3ba3d7b7ba16e06dc4b" "sha256:ca54ebedfcbc60d191391efbf02ba68fb52165b8bf6ccd6fe71f098cac1fe59e"
], ],
"index": "pypi", "index": "pypi",
"version": "==2.2" "version": "==4.0.6"
},
"pytz": {
"hashes": [
"sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9",
"sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c"
],
"version": "==2018.9"
}, },
"sqlparse": { "sqlparse": {
"hashes": [ "hashes": [
"sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177", "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae",
"sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873" "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d"
], ],
"version": "==0.3.0" "markers": "python_version >= '3.5'",
"version": "==0.4.2"
} }
}, },
"develop": {} "develop": {}

View File

@@ -1,207 +0,0 @@
{
"_meta": {
"hash": {
"sha256": "93dcd591e5690d3a71cb02979cbe317e83e3c03ec020867bf1554a480ef5cd8a"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.6"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"aiofiles": {
"hashes": [
"sha256:021ea0ba314a86027c166ecc4b4c07f2d40fc0f4b3a950d1868a0f2571c2bbee",
"sha256:1e644c2573f953664368de28d2aa4c89dfd64550429d0c27c4680ccd3aa4985d"
],
"version": "==0.4.0"
},
"certifi": {
"hashes": [
"sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939",
"sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695"
],
"version": "==2019.6.16"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"h11": {
"hashes": [
"sha256:acca6a44cb52a32ab442b1779adf0875c443c689e9e028f8d831a3769f9c5208",
"sha256:f2b1ca39bfed357d1f19ac732913d5f9faa54a5062eca7d2ec3a916cfb7ae4c7"
],
"version": "==0.8.1"
},
"h2": {
"hashes": [
"sha256:c8f387e0e4878904d4978cd688a3195f6b169d49b1ffa572a3d347d7adc5e09f",
"sha256:fd07e865a3272ac6ef195d8904de92dc7b38dc28297ec39cfa22716b6d62e6eb"
],
"version": "==3.1.0"
},
"hpack": {
"hashes": [
"sha256:0edd79eda27a53ba5be2dfabf3b15780928a0dff6eb0c60a3d6767720e970c89",
"sha256:8eec9c1f4bfae3408a3f30500261f7e6a65912dc138526ea054f9ad98892e9d2"
],
"version": "==3.0.0"
},
"httpcore": {
"hashes": [
"sha256:96f910b528d47b683242ec207050c7bbaa99cd1b9a07f78ea80cf61e55556b58"
],
"version": "==0.3.0"
},
"httptools": {
"hashes": [
"sha256:e00cbd7ba01ff748e494248183abc6e153f49181169d8a3d41bb49132ca01dfc"
],
"version": "==0.0.13"
},
"hyperframe": {
"hashes": [
"sha256:5187962cb16dcc078f23cb5a4b110098d546c3f41ff2d4038a9896893bbd0b40",
"sha256:a9f5c17f2cc3c719b917c4f33ed1c61bd1f8dfac4b1bd23b7c80b3400971b41f"
],
"version": "==5.2.0"
},
"idna": {
"hashes": [
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
],
"version": "==2.8"
},
"multidict": {
"hashes": [
"sha256:024b8129695a952ebd93373e45b5d341dbb87c17ce49637b34000093f243dd4f",
"sha256:041e9442b11409be5e4fc8b6a97e4bcead758ab1e11768d1e69160bdde18acc3",
"sha256:045b4dd0e5f6121e6f314d81759abd2c257db4634260abcfe0d3f7083c4908ef",
"sha256:047c0a04e382ef8bd74b0de01407e8d8632d7d1b4db6f2561106af812a68741b",
"sha256:068167c2d7bbeebd359665ac4fff756be5ffac9cda02375b5c5a7c4777038e73",
"sha256:148ff60e0fffa2f5fad2eb25aae7bef23d8f3b8bdaf947a65cdbe84a978092bc",
"sha256:1d1c77013a259971a72ddaa83b9f42c80a93ff12df6a4723be99d858fa30bee3",
"sha256:1d48bc124a6b7a55006d97917f695effa9725d05abe8ee78fd60d6588b8344cd",
"sha256:31dfa2fc323097f8ad7acd41aa38d7c614dd1960ac6681745b6da124093dc351",
"sha256:34f82db7f80c49f38b032c5abb605c458bac997a6c3142e0d6c130be6fb2b941",
"sha256:3d5dd8e5998fb4ace04789d1d008e2bb532de501218519d70bb672c4c5a2fc5d",
"sha256:4a6ae52bd3ee41ee0f3acf4c60ceb3f44e0e3bc52ab7da1c2b2aa6703363a3d1",
"sha256:4b02a3b2a2f01d0490dd39321c74273fed0568568ea0e7ea23e02bd1fb10a10b",
"sha256:4b843f8e1dd6a3195679d9838eb4670222e8b8d01bc36c9894d6c3538316fa0a",
"sha256:5de53a28f40ef3c4fd57aeab6b590c2c663de87a5af76136ced519923d3efbb3",
"sha256:61b2b33ede821b94fa99ce0b09c9ece049c7067a33b279f343adfe35108a4ea7",
"sha256:6a3a9b0f45fd75dc05d8e93dc21b18fc1670135ec9544d1ad4acbcf6b86781d0",
"sha256:76ad8e4c69dadbb31bad17c16baee61c0d1a4a73bed2590b741b2e1a46d3edd0",
"sha256:7ba19b777dc00194d1b473180d4ca89a054dd18de27d0ee2e42a103ec9b7d014",
"sha256:7c1b7eab7a49aa96f3db1f716f0113a8a2e93c7375dd3d5d21c4941f1405c9c5",
"sha256:7fc0eee3046041387cbace9314926aa48b681202f8897f8bff3809967a049036",
"sha256:8ccd1c5fff1aa1427100ce188557fc31f1e0a383ad8ec42c559aabd4ff08802d",
"sha256:8e08dd76de80539d613654915a2f5196dbccc67448df291e69a88712ea21e24a",
"sha256:c18498c50c59263841862ea0501da9f2b3659c00db54abfbf823a80787fde8ce",
"sha256:c49db89d602c24928e68c0d510f4fcf8989d77defd01c973d6cbe27e684833b1",
"sha256:ce20044d0317649ddbb4e54dab3c1bcc7483c78c27d3f58ab3d0c7e6bc60d26a",
"sha256:d1071414dd06ca2eafa90c85a079169bfeb0e5f57fd0b45d44c092546fcd6fd9",
"sha256:d3be11ac43ab1a3e979dac80843b42226d5d3cccd3986f2e03152720a4297cd7",
"sha256:db603a1c235d110c860d5f39988ebc8218ee028f07a7cbc056ba6424372ca31b"
],
"version": "==4.5.2"
},
"requests": {
"hashes": [
"sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4",
"sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"
],
"version": "==2.22.0"
},
"requests-async": {
"hashes": [
"sha256:8731420451383196ecf2fd96082bfc8ae5103ada90aba185888499d7784dde6f"
],
"version": "==0.5.0"
},
"rfc3986": {
"hashes": [
"sha256:0344d0bd428126ce554e7ca2b61787b6a28d2bbd19fc70ed2dd85efe31176405",
"sha256:df4eba676077cefb86450c8f60121b9ae04b94f65f85b69f3f731af0516b7b18"
],
"version": "==1.3.2"
},
"sanic": {
"hashes": [
"sha256:cc64978266025afb0e7c0f8be928e2b81670c5d58ddac290d04c9d0da6ec2112",
"sha256:ebd806298782400db811ea9d63e8096e835e67f0b5dc5e66e507532984a82bb3"
],
"index": "pypi",
"version": "==19.6.0"
},
"ujson": {
"hashes": [
"sha256:f66073e5506e91d204ab0c614a148d5aa938bdbf104751be66f8ad7a222f5f86"
],
"markers": "sys_platform != 'win32' and implementation_name == 'cpython'",
"version": "==1.35"
},
"urllib3": {
"hashes": [
"sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1",
"sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232"
],
"version": "==1.25.3"
},
"uvloop": {
"hashes": [
"sha256:0fcd894f6fc3226a962ee7ad895c4f52e3f5c3c55098e21efb17c071849a0573",
"sha256:2f31de1742c059c96cb76b91c5275b22b22b965c886ee1fced093fa27dde9e64",
"sha256:459e4649fcd5ff719523de33964aa284898e55df62761e7773d088823ccbd3e0",
"sha256:67867aafd6e0bc2c30a079603a85d83b94f23c5593b3cc08ec7e58ac18bf48e5",
"sha256:8c200457e6847f28d8bb91c5e5039d301716f5f2fce25646f5fb3fd65eda4a26",
"sha256:958906b9ca39eb158414fbb7d6b8ef1b7aee4db5c8e8e5d00fcbb69a1ce9dca7",
"sha256:ac1dca3d8f3ef52806059e81042ee397ac939e5a86c8a3cea55d6b087db66115",
"sha256:b284c22d8938866318e3b9d178142b8be316c52d16fcfe1560685a686718a021",
"sha256:c48692bf4587ce281d641087658eca275a5ad3b63c78297bbded96570ae9ce8f",
"sha256:fefc3b2b947c99737c348887db2c32e539160dcbeb7af9aa6b53db7a283538fe"
],
"markers": "sys_platform != 'win32' and implementation_name == 'cpython'",
"version": "==0.12.2"
},
"websockets": {
"hashes": [
"sha256:0e2f7d6567838369af074f0ef4d0b802d19fa1fee135d864acc656ceefa33136",
"sha256:2a16dac282b2fdae75178d0ed3d5b9bc3258dabfae50196cbb30578d84b6f6a6",
"sha256:5a1fa6072405648cb5b3688e9ed3b94be683ce4a4e5723e6f5d34859dee495c1",
"sha256:5c1f55a1274df9d6a37553fef8cff2958515438c58920897675c9bc70f5a0538",
"sha256:669d1e46f165e0ad152ed8197f7edead22854a6c90419f544e0f234cc9dac6c4",
"sha256:695e34c4dbea18d09ab2c258994a8bf6a09564e762655408241f6a14592d2908",
"sha256:6b2e03d69afa8d20253455e67b64de1a82ff8612db105113cccec35d3f8429f0",
"sha256:79ca7cdda7ad4e3663ea3c43bfa8637fc5d5604c7737f19a8964781abbd1148d",
"sha256:7fd2dd9a856f72e6ed06f82facfce01d119b88457cd4b47b7ae501e8e11eba9c",
"sha256:82c0354ac39379d836719a77ee360ef865377aa6fdead87909d50248d0f05f4d",
"sha256:8f3b956d11c5b301206382726210dc1d3bee1a9ccf7aadf895aaf31f71c3716c",
"sha256:91ec98640220ae05b34b79ee88abf27f97ef7c61cf525eec57ea8fcea9f7dddb",
"sha256:952be9540d83dba815569d5cb5f31708801e0bbfc3a8c5aef1890b57ed7e58bf",
"sha256:99ac266af38ba1b1fe13975aea01ac0e14bb5f3a3200d2c69f05385768b8568e",
"sha256:9fa122e7adb24232247f8a89f2d9070bf64b7869daf93ac5e19546b409e47e96",
"sha256:a0873eadc4b8ca93e2e848d490809e0123eea154aa44ecd0109c4d0171869584",
"sha256:cb998bd4d93af46b8b49ecf5a72c0a98e5cc6d57fdca6527ba78ad89d6606484",
"sha256:e02e57346f6a68523e3c43bbdf35dde5c440318d1f827208ae455f6a2ace446d",
"sha256:e79a5a896bcee7fff24a788d72e5c69f13e61369d055f28113e71945a7eb1559",
"sha256:ee55eb6bcf23ecc975e6b47c127c201b913598f38b6a300075f84eeef2d3baff",
"sha256:f1414e6cbcea8d22843e7eafdfdfae3dd1aba41d1945f6ca66e4806c07c4f454"
],
"version": "==6.0"
}
},
"develop": {}
}

View File

@@ -1,6 +1,6 @@
from sanic import Sanic from sanic import Sanic
from sanic import response from sanic import response
app = Sanic() app = Sanic(name='test')
@app.route("/") @app.route("/")

View File

@@ -0,0 +1,22 @@
sanic==20.12.6
# below is meant to lock the version of transitive deps
aiofiles==0.8.0; python_version >= "3.6" and python_version < "4.0"
certifi==2021.10.8
click==8.1.2; python_version >= "3.7"
h11==0.9.0
httpcore==0.11.1; python_version >= "3.6"
httptools==0.4.0; python_version >= "3.5"
httpx==0.15.4; python_version >= "3.6"
idna==3.3
importlib-metadata==4.11.3; python_version < "3.10"
itsdangerous==2.1.2; python_version >= "3.7"
jinja2==3.1.1; python_version >= "3.7"
markupsafe==2.1.1; python_version >= "3.7"
multidict==5.2.0; python_version >= "3.6"
rfc3986[idna2008]==1.5.0
sniffio==1.2.0; python_version >= "3.5"
ujson==5.2.0; sys_platform != "win32" and implementation_name == "cpython"
uvloop==0.16.0; sys_platform != "win32" and implementation_name == "cpython"
websockets==9.1; python_full_version >= "3.6.1"
werkzeug==2.1.1; python_version >= "3.7"
zipp==3.8.0; python_version >= "3.7"

View File

@@ -6,6 +6,6 @@ class handler(BaseHTTPRequestHandler):
self.send_response(200) self.send_response(200)
self.send_header("Content-Type", "image/png") self.send_header("Content-Type", "image/png")
self.end_headers() self.end_headers()
with open("zeit-white-triangle.png", "rb") as image: with open("triangle.png", "rb") as image:
self.wfile.write(image.read()) self.wfile.write(image.read())
return return

View File

@@ -1,17 +1,12 @@
const fs = require('fs'); const { readFile } = require('fs/promises');
const path = require('path'); const { join } = require('path');
module.exports = async function ({ deploymentUrl, fetch }) { module.exports = async function ({ deploymentUrl, fetch }) {
const nowjson = require('./now.json'); const resp = await fetch(`https://${deploymentUrl}`);
const probe = nowjson.probes[0];
const probeUrl = `https://${deploymentUrl}${probe.path}`;
const resp = await fetch(probeUrl);
const bytes = await resp.arrayBuffer(); const bytes = await resp.arrayBuffer();
const image = fs.readFileSync( const image = await readFile(join(__dirname, 'triangle.png'));
path.join(__dirname, 'zeit-white-triangle.png')
);
if (!image.equals(new Uint8Array(bytes))) { if (!image.equals(new Uint8Array(bytes))) {
throw new Error(`unexpected response: ${bytes}`); throw new Error(`unexpected response: ${bytes}`);

View File

Before

Width:  |  Height:  |  Size: 383 B

After

Width:  |  Height:  |  Size: 383 B

View File

@@ -5,12 +5,5 @@
"src": "*.py", "src": "*.py",
"use": "@vercel/python" "use": "@vercel/python"
} }
],
"probes": [
{
"path": "/",
"method": "GET",
"status": 200
}
] ]
} }

View File

@@ -6,7 +6,7 @@ verify_ssl = true
[dev-packages] [dev-packages]
[packages] [packages]
sanic = "*" sanic = "22.3.2"
[requires] [requires]
python_version = "3.6" python_version = "3.10"

View File

@@ -0,0 +1,203 @@
{
"_meta": {
"hash": {
"sha256": "390914ba991c8a230704140fc6be20ca33aa3b6d6359ca151be04d07c9bc6536"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.10"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"aiofiles": {
"hashes": [
"sha256:7a973fc22b29e9962d0897805ace5856e6a566ab1f0c8e5c91ff6c866519c937",
"sha256:8334f23235248a3b2e83b2c3a78a22674f39969b96397126cc93664d9a901e59"
],
"markers": "python_version >= '3.6' and python_version < '4.0'",
"version": "==0.8.0"
},
"httptools": {
"hashes": [
"sha256:1a99346ebcb801b213c591540837340bdf6fd060a8687518d01c607d338b7424",
"sha256:1ee0b459257e222b878a6c09ccf233957d3a4dcb883b0847640af98d2d9aac23",
"sha256:20a45bcf22452a10fa8d58b7dbdb474381f6946bf5b8933e3662d572bc61bae4",
"sha256:29bf97a5c532da9c7a04de2c7a9c31d1d54f3abd65a464119b680206bbbb1055",
"sha256:2c9a930c378b3d15d6b695fb95ebcff81a7395b4f9775c4f10a076beb0b2c1ff",
"sha256:2db44a0b294d317199e9f80123e72c6b005c55b625b57fae36de68670090fa48",
"sha256:3194f6d6443befa8d4db16c1946b2fc428a3ceb8ab32eb6f09a59f86104dc1a0",
"sha256:34d2903dd2a3dd85d33705b6fde40bf91fc44411661283763fd0746723963c83",
"sha256:48e48530d9b995a84d1d89ae6b3ec4e59ea7d494b150ac3bbc5e2ac4acce92cd",
"sha256:54bbd295f031b866b9799dd39cb45deee81aca036c9bff9f58ca06726f6494f1",
"sha256:5d1fe6b6661022fd6cac541f54a4237496b246e6f1c0a6b41998ee08a1135afe",
"sha256:645373c070080e632480a3d251d892cb795be3d3a15f86975d0f1aca56fd230d",
"sha256:6a1a7dfc1f9c78a833e2c4904757a0f47ce25d08634dd2a52af394eefe5f9777",
"sha256:701e66b59dd21a32a274771238025d58db7e2b6ecebbab64ceff51b8e31527ae",
"sha256:72aa3fbe636b16d22e04b5a9d24711b043495e0ecfe58080addf23a1a37f3409",
"sha256:7af6bdbd21a2a25d6784f6d67f44f5df33ef39b6159543b9f9064d365c01f919",
"sha256:7ee9f226acab9085037582c059d66769862706e8e8cd2340470ceb8b3850873d",
"sha256:7f7bfb74718f52d5ed47d608d507bf66d3bc01d4a8b3e6dd7134daaae129357b",
"sha256:8e2eb957787cbb614a0f006bfc5798ff1d90ac7c4dd24854c84edbdc8c02369e",
"sha256:903f739c9fb78dab8970b0f3ea51f21955b24b45afa77b22ff0e172fc11ef111",
"sha256:98993805f1e3cdb53de4eed02b55dcc953cdf017ba7bbb2fd89226c086a6d855",
"sha256:9967d9758df505975913304c434cb9ab21e2c609ad859eb921f2f615a038c8de",
"sha256:a113789e53ac1fa26edf99856a61e4c493868e125ae0dd6354cf518948fbbd5c",
"sha256:a522d12e2ddbc2e91842ffb454a1aeb0d47607972c7d8fc88bd0838d97fb8a2a",
"sha256:abe829275cdd4174b4c4e65ad718715d449e308d59793bf3a931ee1bf7e7b86c",
"sha256:c286985b5e194ca0ebb2908d71464b9be8f17cc66d6d3e330e8d5407248f56ad",
"sha256:cd1295f52971097f757edfbfce827b6dbbfb0f7a74901ee7d4933dff5ad4c9af",
"sha256:ceafd5e960b39c7e0d160a1936b68eb87c5e79b3979d66e774f0c77d4d8faaed",
"sha256:d1f27bb0f75bef722d6e22dc609612bfa2f994541621cd2163f8c943b6463dfe",
"sha256:d3a4e165ca6204f34856b765d515d558dc84f1352033b8721e8d06c3e44930c3",
"sha256:d9b90bf58f3ba04e60321a23a8723a1ff2a9377502535e70495e5ada8e6e6722",
"sha256:f72b5d24d6730035128b238decdc4c0f2104b7056a7ca55cf047c106842ec890",
"sha256:fcddfe70553be717d9745990dfdb194e22ee0f60eb8f48c0794e7bfeda30d2d5",
"sha256:fdb9f9ed79bc6f46b021b3319184699ba1a22410a82204e6e89c774530069683"
],
"markers": "python_full_version >= '3.5.0'",
"version": "==0.4.0"
},
"multidict": {
"hashes": [
"sha256:0327292e745a880459ef71be14e709aaea2f783f3537588fb4ed09b6c01bca60",
"sha256:041b81a5f6b38244b34dc18c7b6aba91f9cdaf854d9a39e5ff0b58e2b5773b9c",
"sha256:0556a1d4ea2d949efe5fd76a09b4a82e3a4a30700553a6725535098d8d9fb672",
"sha256:05f6949d6169878a03e607a21e3b862eaf8e356590e8bdae4227eedadacf6e51",
"sha256:07a017cfa00c9890011628eab2503bee5872f27144936a52eaab449be5eaf032",
"sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2",
"sha256:19adcfc2a7197cdc3987044e3f415168fc5dc1f720c932eb1ef4f71a2067e08b",
"sha256:19d9bad105dfb34eb539c97b132057a4e709919ec4dd883ece5838bcbf262b80",
"sha256:225383a6603c086e6cef0f2f05564acb4f4d5f019a4e3e983f572b8530f70c88",
"sha256:23b616fdc3c74c9fe01d76ce0d1ce872d2d396d8fa8e4899398ad64fb5aa214a",
"sha256:2957489cba47c2539a8eb7ab32ff49101439ccf78eab724c828c1a54ff3ff98d",
"sha256:2d36e929d7f6a16d4eb11b250719c39560dd70545356365b494249e2186bc389",
"sha256:2e4a0785b84fb59e43c18a015ffc575ba93f7d1dbd272b4cdad9f5134b8a006c",
"sha256:3368bf2398b0e0fcbf46d85795adc4c259299fec50c1416d0f77c0a843a3eed9",
"sha256:373ba9d1d061c76462d74e7de1c0c8e267e9791ee8cfefcf6b0b2495762c370c",
"sha256:4070613ea2227da2bfb2c35a6041e4371b0af6b0be57f424fe2318b42a748516",
"sha256:45183c96ddf61bf96d2684d9fbaf6f3564d86b34cb125761f9a0ef9e36c1d55b",
"sha256:4571f1beddff25f3e925eea34268422622963cd8dc395bb8778eb28418248e43",
"sha256:47e6a7e923e9cada7c139531feac59448f1f47727a79076c0b1ee80274cd8eee",
"sha256:47fbeedbf94bed6547d3aa632075d804867a352d86688c04e606971595460227",
"sha256:497988d6b6ec6ed6f87030ec03280b696ca47dbf0648045e4e1d28b80346560d",
"sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae",
"sha256:50bd442726e288e884f7be9071016c15a8742eb689a593a0cac49ea093eef0a7",
"sha256:514fe2b8d750d6cdb4712346a2c5084a80220821a3e91f3f71eec11cf8d28fd4",
"sha256:5774d9218d77befa7b70d836004a768fb9aa4fdb53c97498f4d8d3f67bb9cfa9",
"sha256:5fdda29a3c7e76a064f2477c9aab1ba96fd94e02e386f1e665bca1807fc5386f",
"sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013",
"sha256:626fe10ac87851f4cffecee161fc6f8f9853f0f6f1035b59337a51d29ff3b4f9",
"sha256:6701bf8a5d03a43375909ac91b6980aea74b0f5402fbe9428fc3f6edf5d9677e",
"sha256:684133b1e1fe91eda8fa7447f137c9490a064c6b7f392aa857bba83a28cfb693",
"sha256:6f3cdef8a247d1eafa649085812f8a310e728bdf3900ff6c434eafb2d443b23a",
"sha256:75bdf08716edde767b09e76829db8c1e5ca9d8bb0a8d4bd94ae1eafe3dac5e15",
"sha256:7c40b7bbece294ae3a87c1bc2abff0ff9beef41d14188cda94ada7bcea99b0fb",
"sha256:8004dca28e15b86d1b1372515f32eb6f814bdf6f00952699bdeb541691091f96",
"sha256:8064b7c6f0af936a741ea1efd18690bacfbae4078c0c385d7c3f611d11f0cf87",
"sha256:89171b2c769e03a953d5969b2f272efa931426355b6c0cb508022976a17fd376",
"sha256:8cbf0132f3de7cc6c6ce00147cc78e6439ea736cee6bca4f068bcf892b0fd658",
"sha256:9cc57c68cb9139c7cd6fc39f211b02198e69fb90ce4bc4a094cf5fe0d20fd8b0",
"sha256:a007b1638e148c3cfb6bf0bdc4f82776cef0ac487191d093cdc316905e504071",
"sha256:a2c34a93e1d2aa35fbf1485e5010337c72c6791407d03aa5f4eed920343dd360",
"sha256:a45e1135cb07086833ce969555df39149680e5471c04dfd6a915abd2fc3f6dbc",
"sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3",
"sha256:aef9cc3d9c7d63d924adac329c33835e0243b5052a6dfcbf7732a921c6e918ba",
"sha256:b9d153e7f1f9ba0b23ad1568b3b9e17301e23b042c23870f9ee0522dc5cc79e8",
"sha256:bfba7c6d5d7c9099ba21f84662b037a0ffd4a5e6b26ac07d19e423e6fdf965a9",
"sha256:c207fff63adcdf5a485969131dc70e4b194327666b7e8a87a97fbc4fd80a53b2",
"sha256:d0509e469d48940147e1235d994cd849a8f8195e0bca65f8f5439c56e17872a3",
"sha256:d16cce709ebfadc91278a1c005e3c17dd5f71f5098bfae1035149785ea6e9c68",
"sha256:d48b8ee1d4068561ce8033d2c344cf5232cb29ee1a0206a7b828c79cbc5982b8",
"sha256:de989b195c3d636ba000ee4281cd03bb1234635b124bf4cd89eeee9ca8fcb09d",
"sha256:e07c8e79d6e6fd37b42f3250dba122053fddb319e84b55dd3a8d6446e1a7ee49",
"sha256:e2c2e459f7050aeb7c1b1276763364884595d47000c1cddb51764c0d8976e608",
"sha256:e5b20e9599ba74391ca0cfbd7b328fcc20976823ba19bc573983a25b32e92b57",
"sha256:e875b6086e325bab7e680e4316d667fc0e5e174bb5611eb16b3ea121c8951b86",
"sha256:f4f052ee022928d34fe1f4d2bc743f32609fb79ed9c49a1710a5ad6b2198db20",
"sha256:fcb91630817aa8b9bc4a74023e4198480587269c272c58b3279875ed7235c293",
"sha256:fd9fc9c4849a07f3635ccffa895d57abce554b467d611a5009ba4f39b78a8849",
"sha256:feba80698173761cddd814fa22e88b0661e98cb810f9f986c54aa34d281e4937",
"sha256:feea820722e69451743a3d56ad74948b68bf456984d63c1a92e8347b7b88452d"
],
"markers": "python_version >= '3.7'",
"version": "==6.0.2"
},
"sanic": {
"hashes": [
"sha256:d0653de59cf3073d8ca047253db72c868510d45d753c13b6316aede769fb66fb",
"sha256:f6eb181e7a1b9a77a3b916de0f1413eddbe384c59487c2a4bff0c7a41e66e080"
],
"index": "pypi",
"version": "==22.6.0"
},
"sanic-routing": {
"hashes": [
"sha256:8282fc05659ff2cebbffa4fb69697601fc9d653c34e0b5bdeed04a832e793fb9",
"sha256:fde5a7d4079794b585b9a2904accd0de5a70abf27c82d2658b40c551ef44f703"
],
"version": "==22.3.0"
},
"websockets": {
"hashes": [
"sha256:07cdc0a5b2549bcfbadb585ad8471ebdc7bdf91e32e34ae3889001c1c106a6af",
"sha256:210aad7fdd381c52e58777560860c7e6110b6174488ef1d4b681c08b68bf7f8c",
"sha256:28dd20b938a57c3124028680dc1600c197294da5db4292c76a0b48efb3ed7f76",
"sha256:2f94fa3ae454a63ea3a19f73b95deeebc9f02ba2d5617ca16f0bbdae375cda47",
"sha256:31564a67c3e4005f27815634343df688b25705cccb22bc1db621c781ddc64c69",
"sha256:347974105bbd4ea068106ec65e8e8ebd86f28c19e529d115d89bd8cc5cda3079",
"sha256:379e03422178436af4f3abe0aa8f401aa77ae2487843738542a75faf44a31f0c",
"sha256:3eda1cb7e9da1b22588cefff09f0951771d6ee9fa8dbe66f5ae04cc5f26b2b55",
"sha256:51695d3b199cd03098ae5b42833006a0f43dc5418d3102972addc593a783bc02",
"sha256:54c000abeaff6d8771a4e2cef40900919908ea7b6b6a30eae72752607c6db559",
"sha256:5b936bf552e4f6357f5727579072ff1e1324717902127ffe60c92d29b67b7be3",
"sha256:6075fd24df23133c1b078e08a9b04a3bc40b31a8def4ee0b9f2c8865acce913e",
"sha256:661f641b44ed315556a2fa630239adfd77bd1b11cb0b9d96ed8ad90b0b1e4978",
"sha256:6ea6b300a6bdd782e49922d690e11c3669828fe36fc2471408c58b93b5535a98",
"sha256:6ed1d6f791eabfd9808afea1e068f5e59418e55721db8b7f3bfc39dc831c42ae",
"sha256:7934e055fd5cd9dee60f11d16c8d79c4567315824bacb1246d0208a47eca9755",
"sha256:7ab36e17af592eec5747c68ef2722a74c1a4a70f3772bc661079baf4ae30e40d",
"sha256:7f6d96fdb0975044fdd7953b35d003b03f9e2bcf85f2d2cf86285ece53e9f991",
"sha256:83e5ca0d5b743cde3d29fda74ccab37bdd0911f25bd4cdf09ff8b51b7b4f2fa1",
"sha256:85506b3328a9e083cc0a0fb3ba27e33c8db78341b3eb12eb72e8afd166c36680",
"sha256:8af75085b4bc0b5c40c4a3c0e113fa95e84c60f4ed6786cbb675aeb1ee128247",
"sha256:8b1359aba0ff810d5830d5ab8e2c4a02bebf98a60aa0124fb29aa78cfdb8031f",
"sha256:8fbd7d77f8aba46d43245e86dd91a8970eac4fb74c473f8e30e9c07581f852b2",
"sha256:907e8247480f287aa9bbc9391bd6de23c906d48af54c8c421df84655eef66af7",
"sha256:93d5ea0b5da8d66d868b32c614d2b52d14304444e39e13a59566d4acb8d6e2e4",
"sha256:97bc9d41e69a7521a358f9b8e44871f6cdeb42af31815c17aed36372d4eec667",
"sha256:994cdb1942a7a4c2e10098d9162948c9e7b235df755de91ca33f6e0481366fdb",
"sha256:a141de3d5a92188234afa61653ed0bbd2dde46ad47b15c3042ffb89548e77094",
"sha256:a1e15b230c3613e8ea82c9fc6941b2093e8eb939dd794c02754d33980ba81e36",
"sha256:aad5e300ab32036eb3fdc350ad30877210e2f51bceaca83fb7fef4d2b6c72b79",
"sha256:b529fdfa881b69fe563dbd98acce84f3e5a67df13de415e143ef053ff006d500",
"sha256:b9c77f0d1436ea4b4dc089ed8335fa141e6a251a92f75f675056dac4ab47a71e",
"sha256:bb621ec2dbbbe8df78a27dbd9dd7919f9b7d32a73fafcb4d9252fc4637343582",
"sha256:c7250848ce69559756ad0086a37b82c986cd33c2d344ab87fea596c5ac6d9442",
"sha256:c8d1d14aa0f600b5be363077b621b1b4d1eb3fbf90af83f9281cda668e6ff7fd",
"sha256:d1655a6fc7aecd333b079d00fb3c8132d18988e47f19740c69303bf02e9883c6",
"sha256:d6353ba89cfc657a3f5beabb3b69be226adbb5c6c7a66398e17809b0ce3c4731",
"sha256:da4377904a3379f0c1b75a965fff23b28315bcd516d27f99a803720dfebd94d4",
"sha256:e49ea4c1a9543d2bd8a747ff24411509c29e4bdcde05b5b0895e2120cb1a761d",
"sha256:e4e08305bfd76ba8edab08dcc6496f40674f44eb9d5e23153efa0a35750337e8",
"sha256:e6fa05a680e35d0fcc1470cb070b10e6fe247af54768f488ed93542e71339d6f",
"sha256:e7e6f2d6fd48422071cc8a6f8542016f350b79cc782752de531577d35e9bd677",
"sha256:e904c0381c014b914136c492c8fa711ca4cced4e9b3d110e5e7d436d0fc289e8",
"sha256:ec2b0ab7edc8cd4b0eb428b38ed89079bdc20c6bdb5f889d353011038caac2f9",
"sha256:ef5ce841e102278c1c2e98f043db99d6755b1c58bde475516aef3a008ed7f28e",
"sha256:f351c7d7d92f67c0609329ab2735eee0426a03022771b00102816a72715bb00b",
"sha256:fab7c640815812ed5f10fbee7abbf58788d602046b7bb3af9b1ac753a6d5e916",
"sha256:fc06cc8073c8e87072138ba1e431300e2d408f054b27047d047b549455066ff4"
],
"markers": "python_version >= '3.7'",
"version": "==10.3"
}
},
"develop": {}
}

View File

@@ -0,0 +1,25 @@
async def app(scope, receive, send):
if scope['type'] == 'http':
await send(
{
"type": "http.response.start",
"status": 200,
"headers": [[b"content-type", b"text/html"]],
}
)
await send(
{
"type": "http.response.body",
"body": b"asgi:RANDOMNESS_PLACEHOLDER"
}
)
elif scope['type'] == 'lifespan':
while True:
message = await receive()
if message['type'] == 'lifespan.startup':
await send({'type': 'lifespan.startup.complete'})
elif message['type'] == 'lifespan.shutdown':
await send({'type': 'lifespan.shutdown.complete'})
return
else:
assert False, "unreachable"

View File

@@ -0,0 +1,8 @@
from sanic import Sanic
from sanic import response
app = Sanic("vercel_test")
@app.route("/api/sani22.py")
async def index(request):
return response.text("asgi:RANDOMNESS_PLACEHOLDER")

View File

@@ -0,0 +1,13 @@
{
"version": 2,
"builds": [
{
"src": "api/*.py",
"use": "@vercel/python"
}
],
"probes": [
{ "path": "/api/raw.py", "mustContain": "asgi:RANDOMNESS_PLACEHOLDER" },
{ "path": "/api/sani22.py", "mustContain": "asgi:RANDOMNESS_PLACEHOLDER" }
]
}

View File

@@ -19,9 +19,6 @@ it('should only match supported versions, otherwise throw an error', async () =>
expect( expect(
getSupportedPythonVersion({ pipLockPythonVersion: '3.9' }) getSupportedPythonVersion({ pipLockPythonVersion: '3.9' })
).toHaveProperty('runtime', 'python3.9'); ).toHaveProperty('runtime', 'python3.9');
expect(
getSupportedPythonVersion({ pipLockPythonVersion: '3.6' })
).toHaveProperty('runtime', 'python3.6');
}); });
it('should ignore minor version in vercel dev', async () => { it('should ignore minor version in vercel dev', async () => {

View File

@@ -167,13 +167,23 @@ elif 'app' in __vc_variables:
else: else:
print('using Asynchronous Server Gateway Interface (ASGI)') print('using Asynchronous Server Gateway Interface (ASGI)')
# Originally authored by Jordan Eremieff and included under MIT license: # Originally authored by Jordan Eremieff and included under MIT license:
# https://github.com/erm/mangum/blob/b4d21c8f5e304a3e17b88bc9fa345106acc50ad7/mangum/__init__.py # https://github.com/erm/mangum/blob/07ce20a0e2f67c5c2593258a92c03fdc66d9edda/mangum/__init__.py
# https://github.com/erm/mangum/blob/b4d21c8f5e304a3e17b88bc9fa345106acc50ad7/LICENSE # https://github.com/erm/mangum/blob/07ce20a0e2f67c5c2593258a92c03fdc66d9edda/LICENSE
import asyncio import asyncio
import enum import enum
import logging
from contextlib import ExitStack
from urllib.parse import urlparse from urllib.parse import urlparse
from werkzeug.datastructures import Headers from werkzeug.datastructures import Headers
def get_event_loop():
try:
return asyncio.get_running_loop()
except:
if sys.version_info < (3, 10):
return asyncio.get_event_loop()
else:
return asyncio.get_event_loop_policy().get_event_loop()
class ASGICycleState(enum.Enum): class ASGICycleState(enum.Enum):
REQUEST = enum.auto() REQUEST = enum.auto()
@@ -194,8 +204,8 @@ elif 'app' in __vc_variables:
ASGI instance using the connection scope. ASGI instance using the connection scope.
Runs until the response is completely read from the application. Runs until the response is completely read from the application.
""" """
loop = asyncio.new_event_loop() loop = get_event_loop()
self.app_queue = asyncio.Queue(loop=loop) self.app_queue = asyncio.Queue()
self.put_message({'type': 'http.request', 'body': body, 'more_body': False}) self.put_message({'type': 'http.request', 'body': body, 'more_body': False})
asgi_instance = app(self.scope, self.receive, self.send) asgi_instance = app(self.scope, self.receive, self.send)
@@ -257,6 +267,156 @@ elif 'app' in __vc_variables:
self.response['body'] = base64.b64encode(self.body).decode('utf-8') self.response['body'] = base64.b64encode(self.body).decode('utf-8')
self.response['encoding'] = 'base64' self.response['encoding'] = 'base64'
class LifespanFailure(Exception):
"""Raise when a lifespan failure event is sent by an application."""
class LifespanUnsupported(Exception):
"""Raise when lifespan events are not supported by an application."""
class UnexpectedMessage(Exception):
"""Raise when an unexpected message type is received during an ASGI cycle."""
class LifespanCycleState(enum.Enum):
"""
The state of the ASGI `lifespan` connection.
* **CONNECTING** - Initial state. The ASGI application instance will be run with
the connection scope containing the `lifespan` type.
* **STARTUP** - The lifespan startup event has been pushed to the queue to be
received by the application.
* **SHUTDOWN** - The lifespan shutdown event has been pushed to the queue to be
received by the application.
* **FAILED** - A lifespan failure has been detected, and the connection will be
closed with an error.
* **UNSUPPORTED** - An application attempted to send a message before receiving
the lifepan startup event. If the lifespan argument is "on", then the connection
will be closed with an error.
"""
CONNECTING = enum.auto()
STARTUP = enum.auto()
SHUTDOWN = enum.auto()
FAILED = enum.auto()
UNSUPPORTED = enum.auto()
class Lifespan:
def __init__(self, app):
self.app = app
self.state = LifespanCycleState.CONNECTING
self.exception = None
self.logger = logging.getLogger('lifespan')
self.loop = get_event_loop()
self.app_queue = asyncio.Queue()
self.startup_event = asyncio.Event()
self.shutdown_event = asyncio.Event()
def __enter__(self) -> None:
"""Runs the event loop for application startup."""
self.loop.create_task(self.run())
self.loop.run_until_complete(self.startup())
def __exit__(
self,
exc_type,
exc_value,
traceback,
) -> None:
"""Runs the event loop for application shutdown."""
self.loop.run_until_complete(self.shutdown())
async def run(self):
"""Calls the application with the `lifespan` connection scope."""
try:
await self.app(
{"type": "lifespan", "asgi": {"spec_version": "2.0", "version": "3.0"}},
self.receive,
self.send,
)
except LifespanUnsupported:
self.logger.info("ASGI 'lifespan' protocol appears unsupported.")
except (LifespanFailure, UnexpectedMessage) as exc:
self.exception = exc
except BaseException as exc:
self.logger.error("Exception in 'lifespan' protocol.", exc_info=exc)
finally:
self.startup_event.set()
self.shutdown_event.set()
async def send(self, message):
"""Awaited by the application to send ASGI `lifespan` events."""
message_type = message["type"]
if self.state is LifespanCycleState.CONNECTING:
# If a message is sent before the startup event is received by the
# application, then assume that lifespan is unsupported.
self.state = LifespanCycleState.UNSUPPORTED
raise LifespanUnsupported("Lifespan protocol appears unsupported.")
if message_type not in (
"lifespan.startup.complete",
"lifespan.shutdown.complete",
"lifespan.startup.failed",
"lifespan.shutdown.failed",
):
self.state = LifespanCycleState.FAILED
raise UnexpectedMessage(f"Unexpected '{message_type}' event received.")
if self.state is LifespanCycleState.STARTUP:
if message_type == "lifespan.startup.complete":
self.startup_event.set()
elif message_type == "lifespan.startup.failed":
self.state = LifespanCycleState.FAILED
self.startup_event.set()
message_value = message.get("message", "")
raise LifespanFailure(f"Lifespan startup failure. {message_value}")
elif self.state is LifespanCycleState.SHUTDOWN:
if message_type == "lifespan.shutdown.complete":
self.shutdown_event.set()
elif message_type == "lifespan.shutdown.failed":
self.state = LifespanCycleState.FAILED
self.shutdown_event.set()
message_value = message.get("message", "")
raise LifespanFailure(f"Lifespan shutdown failure. {message_value}")
async def receive(self):
"""Awaited by the application to receive ASGI `lifespan` events."""
if self.state is LifespanCycleState.CONNECTING:
# Connection established. The next event returned by the queue will be
# `lifespan.startup` to inform the application that the connection is
# ready to receive lfiespan messages.
self.state = LifespanCycleState.STARTUP
elif self.state is LifespanCycleState.STARTUP:
# Connection shutting down. The next event returned by the queue will be
# `lifespan.shutdown` to inform the application that the connection is now
# closing so that it may perform cleanup.
self.state = LifespanCycleState.SHUTDOWN
return await self.app_queue.get()
async def startup(self) -> None:
"""Pushes the `lifespan` startup event to the queue and handles errors."""
await self.app_queue.put({"type": "lifespan.startup"})
await self.startup_event.wait()
if self.state is LifespanCycleState.FAILED:
raise LifespanFailure(self.exception)
if not self.exception:
self.logger.info("Application startup complete.")
else:
self.logger.info("Application startup failed.")
async def shutdown(self) -> None:
"""Pushes the `lifespan` shutdown event to the queue and handles errors."""
await self.app_queue.put({"type": "lifespan.shutdown"})
await self.shutdown_event.wait()
if self.state is LifespanCycleState.FAILED:
raise LifespanFailure(self.exception)
def vc_handler(event, context): def vc_handler(event, context):
payload = json.loads(event['body']) payload = json.loads(event['body'])
@@ -289,6 +449,10 @@ elif 'app' in __vc_variables:
'raw_path': path.encode(), 'raw_path': path.encode(),
} }
with ExitStack() as stack:
lifespan = Lifespan(__vc_module.app)
stack.enter_context(lifespan)
asgi_cycle = ASGICycle(scope) asgi_cycle = ASGICycle(scope)
response = asgi_cycle(__vc_module.app, body) response = asgi_cycle(__vc_module.app, body)
return response return response

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/redwood", "name": "@vercel/redwood",
"version": "1.0.8", "version": "1.0.9",
"main": "./dist/index.js", "main": "./dist/index.js",
"license": "MIT", "license": "MIT",
"homepage": "https://vercel.com/docs", "homepage": "https://vercel.com/docs",
@@ -28,6 +28,6 @@
"@types/aws-lambda": "8.10.19", "@types/aws-lambda": "8.10.19",
"@types/node": "*", "@types/node": "*",
"@types/semver": "6.0.0", "@types/semver": "6.0.0",
"@vercel/build-utils": "5.0.3" "@vercel/build-utils": "5.0.4"
} }
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/remix", "name": "@vercel/remix",
"version": "1.0.9", "version": "1.0.10",
"license": "MIT", "license": "MIT",
"main": "./dist/index.js", "main": "./dist/index.js",
"homepage": "https://vercel.com/docs", "homepage": "https://vercel.com/docs",
@@ -26,7 +26,7 @@
"devDependencies": { "devDependencies": {
"@types/jest": "27.5.1", "@types/jest": "27.5.1",
"@types/node": "*", "@types/node": "*",
"@vercel/build-utils": "5.0.3", "@vercel/build-utils": "5.0.4",
"typescript": "4.6.4" "typescript": "4.6.4"
} }
} }

View File

@@ -1,7 +1,7 @@
{ {
"name": "@vercel/ruby", "name": "@vercel/ruby",
"author": "Nathan Cahill <nathan@nathancahill.com>", "author": "Nathan Cahill <nathan@nathancahill.com>",
"version": "1.3.15", "version": "1.3.16",
"license": "MIT", "license": "MIT",
"main": "./dist/index", "main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/ruby", "homepage": "https://vercel.com/docs/runtimes#official-runtimes/ruby",
@@ -23,7 +23,7 @@
"devDependencies": { "devDependencies": {
"@types/fs-extra": "8.0.0", "@types/fs-extra": "8.0.0",
"@types/semver": "6.0.0", "@types/semver": "6.0.0",
"@vercel/build-utils": "5.0.3", "@vercel/build-utils": "5.0.4",
"@vercel/ncc": "0.24.0", "@vercel/ncc": "0.24.0",
"execa": "2.0.4", "execa": "2.0.4",
"fs-extra": "^7.0.1", "fs-extra": "^7.0.1",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/static-build", "name": "@vercel/static-build",
"version": "1.0.8", "version": "1.0.9",
"license": "MIT", "license": "MIT",
"main": "./dist/index", "main": "./dist/index",
"homepage": "https://vercel.com/docs/build-step", "homepage": "https://vercel.com/docs/build-step",
@@ -37,7 +37,7 @@
"@types/ms": "0.7.31", "@types/ms": "0.7.31",
"@types/node-fetch": "2.5.4", "@types/node-fetch": "2.5.4",
"@types/promise-timeout": "1.3.0", "@types/promise-timeout": "1.3.0",
"@vercel/build-utils": "5.0.3", "@vercel/build-utils": "5.0.4",
"@vercel/frameworks": "1.1.1", "@vercel/frameworks": "1.1.1",
"@vercel/ncc": "0.24.0", "@vercel/ncc": "0.24.0",
"@vercel/routing-utils": "2.0.0", "@vercel/routing-utils": "2.0.0",
@@ -45,7 +45,7 @@
"get-port": "5.0.0", "get-port": "5.0.0",
"is-port-reachable": "2.0.1", "is-port-reachable": "2.0.1",
"ms": "2.1.2", "ms": "2.1.2",
"node-fetch": "2.6.1", "node-fetch": "2.6.7",
"rc9": "1.2.0", "rc9": "1.2.0",
"typescript": "4.3.4" "typescript": "4.3.4"
} }

View File

@@ -98,7 +98,11 @@ async function nowDeploy(bodies, randomness, uploadNowJson) {
logWithinTest('state is READY, moving on'); logWithinTest('state is READY, moving on');
break; break;
} }
logWithinTest('state is ', readyState, 'retrying in 1 second'); if (i > 0 && i % 25 === 0) {
logWithinTest(
`State of https://${deploymentUrl} is ${readyState}, retry number ${i}`
);
}
await new Promise(r => setTimeout(r, 1000)); await new Promise(r => setTimeout(r, 1000));
} }

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